import { browser } from "$app/environment"; import { parseJsonStringOrDefault } from "$lib/Effect/json"; import root from "$lib/Utility/root"; import { get, writable } from "svelte/store"; import settingsSyncPulled from "./settingsSyncPulled"; import settingsSyncTimes from "./settingsSyncTimes"; import identity from "./identity"; const VERSION = "1.0.1"; export interface Settings { cacheMangaMinutes: number; cacheMinutes: number; displayUpcomingAnimeCollapsed: boolean; displayAnimeCollapsed: boolean; displayMangaCollapsed: boolean; displayNotStarted: boolean; displayUnresolved: boolean; calculateChaptersRoundedDown: boolean; displayOutboundLinksTo: | "anilist" | "livechartme" | "animeschedule" | "myanimelist"; displayPausedMedia: boolean; displayLimitListHeight: boolean; displaySocialButton: boolean; calculateGuessingDisabled: boolean; displayHoverNavigation: boolean; displayTitleFormat: "english" | "romaji" | "native"; calculateGuessMethod: "median" | "iqr_median" | "iqr_mode" | "mode"; calculateDisableOutOfDateVolumeWarning: boolean; calculatePreferNativeChapterCount: boolean; displayPlannedAnime: boolean; displayFurigana: boolean; displayAoButa: | "kaede" | "mai" | "mai_2" | "nodoka" | "rio" | "sakuta" | "shouko" | "tomoe" | "random" | "none"; disableManga: boolean; disableAnime: boolean; disableUpcomingAnime: boolean; display24HourTime: boolean; displayCountdownRightAligned: boolean; displayNativeCountdown: boolean; displayHoverCover: boolean; displayDisableAnimations: boolean; displayDisableNotifications: boolean; displayCoverModeAnime: boolean; displayCoverModeManga: boolean; displayCoverWidth: number; displayShortCountdown: boolean; displayScheduleListMode: boolean; displayLanguage: "en" | "ja"; displayDisableLastActivityWarning: boolean; settingsSync: boolean; settingsVersion?: string; displayBlurAdultContent: boolean; displayCopyMediaTitleNotLink: boolean; displayTotalDueEpisodes: boolean; displayTotalEpisodes: boolean; displayAniListNotifications: boolean; displayFiltersIncludeCompleted: boolean; displayDataSaver: boolean; debugDummyLists: boolean; debugShowListTimings: boolean; displayScheduleFilterList: boolean; displayReverseSort: boolean; displayAnimeSort: "difference" | "start_date" | "end_date" | "time_remaining"; displayMediaListFilter: boolean; displayCustomCSS: string; displayMediaRoulette: boolean; } const defaultSettings: Settings = { // Display displayOutboundLinksTo: "anilist", displayPausedMedia: true, displayPlannedAnime: true, displayLimitListHeight: false, displaySocialButton: false, displayUnresolved: false, displayTitleFormat: "english", displayFurigana: false, displayHoverNavigation: false, displayNotStarted: false, displayUpcomingAnimeCollapsed: false, displayAnimeCollapsed: false, displayMangaCollapsed: false, displayAoButa: "none", disableManga: false, disableAnime: false, disableUpcomingAnime: false, display24HourTime: false, displayCountdownRightAligned: false, displayNativeCountdown: false, displayHoverCover: false, displayDisableAnimations: false, displayDisableNotifications: false, displayCoverModeAnime: true, displayCoverModeManga: true, displayCoverWidth: 100, // 116.609 displayShortCountdown: false, displayScheduleListMode: false, displayLanguage: "en", displayDisableLastActivityWarning: false, displayBlurAdultContent: true, displayCopyMediaTitleNotLink: false, displayTotalDueEpisodes: false, displayTotalEpisodes: false, displayAniListNotifications: false, displayFiltersIncludeCompleted: false, displayDataSaver: false, displayScheduleFilterList: false, displayReverseSort: false, displayAnimeSort: "time_remaining", displayMediaListFilter: false, displayCustomCSS: "", displayMediaRoulette: false, // Debug debugDummyLists: false, debugShowListTimings: false, // Calculation calculateChaptersRoundedDown: true, calculateDisableOutOfDateVolumeWarning: false, calculateGuessingDisabled: true, calculateGuessMethod: "iqr_mode", calculatePreferNativeChapterCount: false, // Cache cacheMangaMinutes: 120, cacheMinutes: 30, // Sync settingsSync: false, settingsVersion: VERSION, }; const createStore = () => { const initialValue = browser ? parseJsonStringOrDefault( localStorage.getItem("settings") || "", defaultSettings, ) : defaultSettings; const store = writable(initialValue); let state: Settings = initialValue; store.subscribe((value) => { state = value; if (browser) localStorage.setItem("settings", JSON.stringify(value)); }); return { subscribe: store.subscribe, set: store.set, update: store.update, reset: () => store.set(defaultSettings), get: () => { const keys = Object.keys(defaultSettings); const settingsKeys = Object.keys(state); const updatedSettings = { ...state }; for (const key of keys) if (!settingsKeys.includes(key)) (updatedSettings as unknown as Record)[key] = ( defaultSettings as unknown as Record )[key]; if (browser) localStorage.setItem("settings", JSON.stringify(updatedSettings)); return updatedSettings; }, setKey: (key: keyof Settings, value: unknown) => store.update((settings) => ({ ...settings, [key]: value })), }; }; const settings = createStore(); settings.subscribe((value) => { if (!browser) return; if (value.settingsSync && get(settingsSyncPulled) === true) { fetch(root(`/api/configuration?id=${get(identity).id}`)) .then((response) => { if (!response.ok) return; return response.json().then((data) => { const isEqualsJson = ( firstObject: Settings, secondObject: Settings, ) => { type AnyObject = { [key: string]: unknown }; return ( Object.keys(firstObject).length === Object.keys(secondObject).length && Object.keys(firstObject).every( (key) => (firstObject as unknown as AnyObject)[key] === (secondObject as unknown as AnyObject)[key], ) ); }; if (data?.configuration && !isEqualsJson(data.configuration, value)) fetch(root(`/api/configuration`), { method: "PUT", body: JSON.stringify(value), }) .then((response) => { if (response.ok) console.log("Pushed local configuration"); settingsSyncTimes.update((times) => ({ ...times, lastPush: new Date(), })); }) .catch((error) => console.error("Settings sync push failed", error), ); }); }) .catch((error) => console.error("Settings sync pull failed", error)); } }); export default settings;