web: implement settings core

this was a torture
This commit is contained in:
wukko 2024-06-19 21:12:51 +06:00
parent 21e03a407c
commit 009a2cc863
No known key found for this signature in database
GPG key ID: 3E30B3F26C7B4AA2
6 changed files with 139 additions and 2 deletions

12
web/package-lock.json generated
View file

@ -11,7 +11,8 @@
"dependencies": { "dependencies": {
"@fontsource-variable/noto-sans-mono": "^5.0.20", "@fontsource-variable/noto-sans-mono": "^5.0.20",
"@fontsource/ibm-plex-mono": "^5.0.13", "@fontsource/ibm-plex-mono": "^5.0.13",
"@tabler/icons-svelte": "^3.6.0" "@tabler/icons-svelte": "^3.6.0",
"ts-deepmerge": "^7.0.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint/js": "^9.5.0", "@eslint/js": "^9.5.0",
@ -3048,6 +3049,15 @@
"typescript": ">=4.2.0" "typescript": ">=4.2.0"
} }
}, },
"node_modules/ts-deepmerge": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/ts-deepmerge/-/ts-deepmerge-7.0.0.tgz",
"integrity": "sha512-WZ/iAJrKDhdINv1WG6KZIGHrZDar6VfhftG1QJFpVbOYZMYJLJOvZOo1amictRXVdBXZIgBHKswMTXzElngprA==",
"license": "ISC",
"engines": {
"node": ">=14.13.1"
}
},
"node_modules/tslib": { "node_modules/tslib": {
"version": "2.6.3", "version": "2.6.3",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz",

View file

@ -36,6 +36,7 @@
"dependencies": { "dependencies": {
"@fontsource-variable/noto-sans-mono": "^5.0.20", "@fontsource-variable/noto-sans-mono": "^5.0.20",
"@fontsource/ibm-plex-mono": "^5.0.13", "@fontsource/ibm-plex-mono": "^5.0.13",
"@tabler/icons-svelte": "^3.6.0" "@tabler/icons-svelte": "^3.6.0",
"ts-deepmerge": "^7.0.0"
} }
} }

42
web/src/lib/settings.ts Normal file
View file

@ -0,0 +1,42 @@
import { readable, type Updater } from 'svelte/store';
import { merge } from 'ts-deepmerge';
import type { RecursivePartial } from './types/generic';
import type { CobaltSettings } from './types/settings';
import defaultSettings from "$lib/settings/defaults";
const writeToStorage = (settings: CobaltSettings) => {
localStorage.setItem(
"settings",
JSON.stringify(settings)
);
return settings;
}
const loadFromStorage = () => {
const settings = localStorage.getItem('settings');
if (!settings) {
return writeToStorage(defaultSettings);
}
return JSON.parse(settings) as CobaltSettings;
}
let update: (_: Updater<CobaltSettings>) => void;
export default readable<CobaltSettings>(
loadFromStorage(),
(_, _update) => { update = _update }
);
// update settings from outside
export function updateSetting(settings: RecursivePartial<CobaltSettings>) {
update((current) => {
// deep merge partial type into full CobaltSettings type
current = merge(current, settings) as CobaltSettings;
return writeToStorage(current);
});
}

View file

@ -0,0 +1,35 @@
import type { CobaltSettings } from "$lib/types/settings";
const defaultSettings: CobaltSettings = {
schemaVersion: 1,
accessibility: {
reduceAnimations: false,
reduceTransparency: false
},
appearance: {
theme: "auto"
},
general: {
customProcessingEndpoint: "",
seenOnboarding: false,
seenSafetyWarning: false
},
save: {
audioFormat: "mp3",
disableMetadata: false,
downloadMode: "auto",
downloadPopup: true,
filenameStyle: "classic",
tiktokH265: false,
tiktokFullAudio: false,
twitterGif: false,
videoQuality: "720",
youtubeVideoCodec: "h264",
youtubeDubBrowserLang: false
},
privacy: {
trafficAnalytics: true
}
}
export default defaultSettings;

View file

@ -0,0 +1,8 @@
// more readable version of recursive partial taken from stackoverflow:
// https://stackoverflow.com/a/51365037
export type RecursivePartial<Type> = {
[Key in keyof Type]?:
Type[Key] extends (infer ElementType)[] ? RecursivePartial<ElementType>[] :
Type[Key] extends object | undefined ? RecursivePartial<Type[Key]> :
Type[Key];
};

View file

@ -0,0 +1,41 @@
type CobaltSettingsAccessibility = {
reduceAnimations: boolean,
reduceTransparency: boolean,
};
type CobaltSettingsAppearance = {
theme: "auto" | "light" | "dark",
};
type CobaltSettingsGeneral = {
customProcessingEndpoint: string,
seenOnboarding: boolean,
seenSafetyWarning: boolean,
};
type CobaltSettingsSave = {
audioFormat: "best" | "mp3" | "ogg" | "wav" | "opus",
disableMetadata: boolean,
downloadMode: "auto" | "audio" | "mute",
downloadPopup: boolean,
filenameStyle: "classic" | "basic" | "pretty" | "nerdy",
tiktokH265: boolean,
tiktokFullAudio: boolean,
twitterGif: boolean,
videoQuality: "max" | "2160" | "1440" | "1080" | "720" | "360" | "240" | "144",
youtubeVideoCodec: "h264" | "av1" | "vp9",
youtubeDubBrowserLang: boolean,
};
type CobaltSettingsPrivacy = {
trafficAnalytics: boolean,
};
export type CobaltSettings = {
schemaVersion: number,
accessibility: CobaltSettingsAccessibility,
appearance: CobaltSettingsAppearance,
general: CobaltSettingsGeneral,
save: CobaltSettingsSave,
privacy: CobaltSettingsPrivacy,
};