diff --git a/src/lib/extension/base/CacheablePreferences.ts b/src/lib/extension/base/CacheablePreferences.ts index 2bcdbd2..f981763 100644 --- a/src/lib/extension/base/CacheablePreferences.ts +++ b/src/lib/extension/base/CacheablePreferences.ts @@ -70,6 +70,10 @@ export class PreferenceField< set(value: Fields[Key]) { return this.#preferences.writeRaw(this.#fieldKey, value); } + + get key(): Key { + return this.#fieldKey; + } } /** diff --git a/src/lib/store-sync.ts b/src/lib/store-sync.ts new file mode 100644 index 0000000..772d757 --- /dev/null +++ b/src/lib/store-sync.ts @@ -0,0 +1,65 @@ +import type { Writable } from "svelte/store"; +import CacheablePreferences, { type PreferenceField } from "$lib/extension/base/CacheablePreferences"; + +interface PrerefenceConnectionOptions { + /** + * Optional callback to apply modifications to the value when sending them from the preference field to the single + * storage. If not provided, value itself will be sent instead. + */ + updateToStore?: (value: any) => ValueType; +} + +export class PreferenceSync = Record> { + readonly #preferences: CacheablePreferences; + readonly #syncedPairs: [PreferenceField, Writable, PrerefenceConnectionOptions][] = []; + + protected constructor(preferences: CacheablePreferences) { + this.#preferences = preferences; + } + + /** + * Connect the specific preference field to the provided storage. + * @param field Field object. + * @param store Storage object. Should contain the appropriate type for the value. + * @param [options] Additional settings. + */ + connect( + field: PreferenceField, + store: Writable, + options: PrerefenceConnectionOptions = {} + ): this { + this.#syncedPairs.push([field as any, store, options]); + + return this; + } + + async startSync(): Promise { + await Promise.allSettled( + this.#syncedPairs.map( + ([field, store, options]) => field.get() + .then(initialValue => store.set(options.updateToStore?.(initialValue) ?? initialValue)) + ) + ); + + for (const [field, store] of this.#syncedPairs) { + store.subscribe(value => field.set(value)); + } + + this.#preferences.subscribe(updatedSettings => { + for (const [field, store, options] of this.#syncedPairs) { + if (!(field.key in updatedSettings)) { + continue; + } + + const updatedValue = updatedSettings[field.key]; + store.set(options.updateToStore?.(updatedValue) ?? updatedValue); + } + }); + } + + static for< + PreferencesType extends Record = Record + >(preferences: CacheablePreferences): PreferenceSync { + return new PreferenceSync(preferences); + } +} diff --git a/src/stores/preferences/misc.ts b/src/stores/preferences/misc.ts index 121939a..8395462 100644 --- a/src/stores/preferences/misc.ts +++ b/src/stores/preferences/misc.ts @@ -1,18 +1,11 @@ import { writable } from "svelte/store"; import MiscPreferences from "$lib/extension/preferences/MiscPreferences"; +import { PreferenceSync } from "$lib/store-sync"; export const fullScreenViewerEnabled = writable(true); const preferences = new MiscPreferences(); -Promise.allSettled([ - preferences.fullscreenViewer.get().then(v => fullScreenViewerEnabled.set(v)) -]).then(() => { - fullScreenViewerEnabled.subscribe(value => { - void preferences.fullscreenViewer.set(value); - }); - - preferences.subscribe(settings => { - fullScreenViewerEnabled.set(Boolean(settings.fullscreenViewer)); - }); -}); +void PreferenceSync.for(preferences) + .connect(preferences.fullscreenViewer, fullScreenViewerEnabled, {updateToStore: Boolean}) + .startSync(); diff --git a/src/stores/preferences/profiles.ts b/src/stores/preferences/profiles.ts index f7a65fc..55a056b 100644 --- a/src/stores/preferences/profiles.ts +++ b/src/stores/preferences/profiles.ts @@ -1,18 +1,13 @@ import { writable } from "svelte/store"; import TaggingProfilesPreferences from "$lib/extension/preferences/TaggingProfilesPreferences"; +import { PreferenceSync } from "$lib/store-sync"; export const stripBlacklistedTagsEnabled = writable(true); const preferences = new TaggingProfilesPreferences(); -Promise - .all([ - preferences.stripBlacklistedTags.get().then(v => stripBlacklistedTagsEnabled.set(v ?? true)) - ]) - .then(() => { - preferences.subscribe(settings => { - stripBlacklistedTagsEnabled.set(typeof settings.stripBlacklistedTags === 'boolean' ? settings.stripBlacklistedTags : true); - }); - - stripBlacklistedTagsEnabled.subscribe(v => preferences.stripBlacklistedTags.set(v)); - }); +void PreferenceSync.for(preferences) + .connect(preferences.stripBlacklistedTags, stripBlacklistedTagsEnabled, { + updateToStore: maybeBoolean => typeof maybeBoolean === 'boolean' ? maybeBoolean : true, + }) + .startSync(); diff --git a/src/stores/preferences/tags.ts b/src/stores/preferences/tags.ts index 84b1239..3929f8a 100644 --- a/src/stores/preferences/tags.ts +++ b/src/stores/preferences/tags.ts @@ -1,5 +1,6 @@ import { writable } from "svelte/store"; import TagsPreferences from "$lib/extension/preferences/TagsPreferences"; +import { PreferenceSync } from "$lib/store-sync"; const preferences = new TagsPreferences(); @@ -7,28 +8,8 @@ export const shouldSeparateTagGroups = writable(false); export const shouldReplaceLinksOnForumPosts = writable(false); export const shouldReplaceTextOfTagLinks = writable(true); -Promise - .allSettled([ - preferences.groupSeparation.get().then(value => shouldSeparateTagGroups.set(value)), - preferences.replaceLinks.get().then(value => shouldReplaceLinksOnForumPosts.set(value)), - preferences.replaceLinkText.get().then(value => shouldReplaceTextOfTagLinks.set(value)), - ]) - .then(() => { - shouldSeparateTagGroups.subscribe(value => { - void preferences.groupSeparation.set(value); - }); - - shouldReplaceLinksOnForumPosts.subscribe(value => { - void preferences.replaceLinks.set(value); - }); - - shouldReplaceTextOfTagLinks.subscribe(value => { - void preferences.replaceLinkText.set(value); - }); - - preferences.subscribe(settings => { - shouldSeparateTagGroups.set(Boolean(settings.groupSeparation)); - shouldReplaceLinksOnForumPosts.set(Boolean(settings.replaceLinks)); - shouldReplaceTextOfTagLinks.set(Boolean(settings.replaceLinkText)); - }); - }); +void PreferenceSync.for(preferences) + .connect(preferences.groupSeparation, shouldSeparateTagGroups, {updateToStore: Boolean}) + .connect(preferences.replaceLinks, shouldReplaceLinksOnForumPosts, {updateToStore: Boolean}) + .connect(preferences.replaceLinkText, shouldReplaceTextOfTagLinks, {updateToStore: Boolean}) + .startSync();