diff --git a/package.json b/package.json index 32fd0f4..38bd2fd 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "markdown-home-tab", "private": true, - "version": "0.0.5", + "version": "0.0.6", "type": "module", "scripts": { "dev": "vite", diff --git a/src/modules/settings/context/SettingsContext.ts b/src/modules/settings/context/SettingsContext.ts index d85bf1c..5d2d59d 100644 --- a/src/modules/settings/context/SettingsContext.ts +++ b/src/modules/settings/context/SettingsContext.ts @@ -1,4 +1,5 @@ import { createContext, useContext } from "react"; +import { noop } from "~/utils/noop"; export interface ColorSettings { backgroundColor: string; @@ -26,10 +27,9 @@ export const defaultSettings: SettingsValue = { export const SettingsContext = createContext({ settings: defaultSettings, - // eslint-disable-next-line @typescript-eslint/no-unused-vars - update: (_: Partial) => {}, - show: () => {}, - hide: () => {}, + update: noop as (value: Partial) => void, + show: noop, + hide: noop, }); export const useSettings = () => useContext(SettingsContext); diff --git a/src/modules/settings/hooks/usePersistSettings.ts b/src/modules/settings/hooks/usePersistSettings.ts deleted file mode 100644 index 18ba095..0000000 --- a/src/modules/settings/hooks/usePersistSettings.ts +++ /dev/null @@ -1,37 +0,0 @@ -import { useCallback, useEffect, useState } from "react"; -import { SettingsValue } from "~/modules/settings/context/SettingsContext"; -import { useDefaultTheme } from "~/modules/theme/hooks/useDefaultTheme"; -import { BrowserSyncStorage } from "~/utils"; -import { defaultSettings } from "../context/SettingsContext"; - -const storage = new BrowserSyncStorage('settings'); - -export const usePersistSettings = () => { - const [hydrated, setHydrated] = useState(false); - - const defaultColors = useDefaultTheme(); - - const [settings, setSettings] = useState({ - ...defaultSettings, ...defaultColors - }); - - const update = useCallback((val: Partial) => { - if (!hydrated) { - return; - } - - setSettings((v) => { - const updatedValue = { ...v, ...val }; - storage.set("", updatedValue); - return updatedValue; - }); - }, [hydrated]); - - useEffect(() => { - storage.get("").then(next => { - setSettings(prev => ({ ...prev, ...next })); - }).finally(() => setHydrated(true)); - }, []) - - return { settings, update, hydrated }; -}; diff --git a/src/modules/settings/hooks/useSettings.ts b/src/modules/settings/hooks/useSettings.ts new file mode 100644 index 0000000..8257b87 --- /dev/null +++ b/src/modules/settings/hooks/useSettings.ts @@ -0,0 +1,17 @@ +import { useMemo } from "react"; +import { useStorage } from "~/modules/storage/StorageContext"; +import { useDefaultTheme } from "~/modules/theme/hooks/useDefaultTheme"; +import { defaultSettings } from "../context/SettingsContext"; + +export const useSettings = () => { + const defaultColors = useDefaultTheme(); + + const { settings: storedSettings, setSettings } = useStorage(); + + const settings = useMemo( + () => ({ ...defaultSettings, ...defaultColors, ...storedSettings }), + [defaultColors, storedSettings] + ); + + return { settings, update: setSettings }; +}; diff --git a/src/modules/settings/providers/SettingsProvider/index.tsx b/src/modules/settings/providers/SettingsProvider/index.tsx index e663575..5bb89af 100644 --- a/src/modules/settings/providers/SettingsProvider/index.tsx +++ b/src/modules/settings/providers/SettingsProvider/index.tsx @@ -5,23 +5,19 @@ import { ModalPage } from "~/modules/modal/components/ModalPage"; import { Modal } from "~/modules/modal/containers/Modal"; import { SettingsContainer } from "../../containers/SettingsContainer"; import { SettingsContext } from "../../context/SettingsContext"; -import { usePersistSettings } from "../../hooks/usePersistSettings"; +import { useSettings } from "../../hooks/useSettings"; import styles from "./styles.module.scss"; const SettingsProvider: FC = ({ children }) => { const { t } = useTranslation(); - const { settings, update, hydrated } = usePersistSettings(); + const { settings, update } = useSettings(); const [settingsModalVisible, setSettingsModalVisible] = useState(false); const show = useCallback(() => setSettingsModalVisible(true), []); const hide = useCallback(() => setSettingsModalVisible(false), []); - if (!hydrated) { - return null; - } - return ( {settingsModalVisible && ( diff --git a/src/modules/storage/StorageContext.ts b/src/modules/storage/StorageContext.ts index b86c79b..4fbfdf2 100644 --- a/src/modules/storage/StorageContext.ts +++ b/src/modules/storage/StorageContext.ts @@ -1,13 +1,16 @@ import { createContext, useContext } from "react"; import { SerializedDockview } from "dockview"; import { noop } from "~/utils/noop"; +import { SettingsValue } from "~/modules/settings/context/SettingsContext"; export const StorageContext = createContext({ layout: null as SerializedDockview | null, panels: {} as Record, + settings: {} as Partial, hydrated: false, setPanel: noop as (uuid: string, content: string) => void, setLayout: noop as (layout: SerializedDockview) => void, + setSettings: noop as (layout: Partial) => void, }); export const useStorage = () => useContext(StorageContext); diff --git a/src/modules/storage/StorageProvider.tsx b/src/modules/storage/StorageProvider.tsx index a2d882d..0e815ff 100644 --- a/src/modules/storage/StorageProvider.tsx +++ b/src/modules/storage/StorageProvider.tsx @@ -4,9 +4,11 @@ import { hydrateLayout, storeLayoutLocally, storePanelLocally, + storeSettingsLocally, } from "~/utils/hydrate"; import { useDelayedSync } from "./hooks/useDelayedSync"; import { StorageContext } from "./StorageContext"; +import { SettingsValue } from "~/modules/settings/context/SettingsContext"; const debounceDelay = 500; @@ -14,8 +16,10 @@ export const StorageProvider = ({ children }: { children: ReactNode }) => { const [hydrated, setHydrated] = useState(false); const [layout, setLayoutValue] = useState(null); const [panels, setPanelsValue] = useState>({}); + const [settings, setSettingsValue] = useState>({}); - const { storeLayout, storePanel } = useDelayedSync(debounceDelay); + const { storeLayout, storePanel, storeSettings } = + useDelayedSync(debounceDelay); const setPanel = useCallback( (uuid: string, value: string) => { @@ -35,6 +39,12 @@ export const StorageProvider = ({ children }: { children: ReactNode }) => { [storeLayout] ); + const setSettings = useCallback((value: Partial) => { + setSettingsValue(value); + storeSettingsLocally(value); + storeSettings(value); + }, []); + useEffect(() => { if (hydrated) { return; @@ -47,6 +57,7 @@ export const StorageProvider = ({ children }: { children: ReactNode }) => { } setLayout(result.layout); + setSettings(result.settings); Object.entries(result.panels).forEach(([uuid, value]) => { setPanel(uuid, value); @@ -55,11 +66,19 @@ export const StorageProvider = ({ children }: { children: ReactNode }) => { .finally(() => { setHydrated(true); }); - }, [hydrated, setLayout, setPanel]); + }, [hydrated, setLayout, setPanel, setSettings]); return ( {hydrated ? children : null} diff --git a/src/modules/storage/hooks/useDelayedSync.ts b/src/modules/storage/hooks/useDelayedSync.ts index c9b8600..ecd4f5e 100644 --- a/src/modules/storage/hooks/useDelayedSync.ts +++ b/src/modules/storage/hooks/useDelayedSync.ts @@ -1,11 +1,17 @@ import { useCallback, useRef } from "react"; -import { storeLayoutInSync, storePanelInSync } from "~/utils/hydrate"; +import { + storeLayoutInSync, + storePanelInSync, + storeSettingsInSync, +} from "~/utils/hydrate"; import { DebouncedFunc } from "lodash"; import { SerializedDockview } from "dockview"; import debounce from "lodash.debounce"; +import { SettingsValue } from "~/modules/settings/context/SettingsContext"; export const useDelayedSync = (debounceDelay: number) => { const layoutTimer = useRef>(); + const settingsTimer = useRef>(); const panelTimers = useRef< Record> >({}); @@ -34,5 +40,16 @@ export const useDelayedSync = (debounceDelay: number) => { [debounceDelay] ); - return { storeLayout, storePanel }; + const storeSettings = useCallback( + (settings: Partial) => { + if (settingsTimer.current) { + settingsTimer.current.cancel(); + } + + settingsTimer.current = debounce(storeSettingsInSync, debounceDelay); + settingsTimer.current(settings); + }, + [debounceDelay] + ); + return { storeLayout, storePanel, storeSettings }; }; diff --git a/src/utils/hydrate.ts b/src/utils/hydrate.ts index 1b87090..1a9c1ee 100644 --- a/src/utils/hydrate.ts +++ b/src/utils/hydrate.ts @@ -1,12 +1,15 @@ import { SerializedDockview } from "dockview"; +import { SettingsValue } from "~/modules/settings/context/SettingsContext"; import { hasBrowserStorage, hasChromeStorage } from "~/utils/storage"; interface Result { layout: SerializedDockview; panels: Record; + settings: Partial; } const layoutKey = "dockview_persistance_layout"; +const settingsKey = "settings"; const panelPrefix = "MarkdownEditorContainerMarkdownEditorContainer"; const makePanelKey = (uuid: string) => `${panelPrefix}${uuid}`; @@ -27,9 +30,16 @@ const getFromBrowserStorage = async (): Promise => { {} as Record ); + const settings = + typeof result[settingsKey] === "object" && + Object.keys(result[settingsKey]).length + ? result[settingsKey] + : {}; + return { layout, panels, + settings, }; }; @@ -49,9 +59,16 @@ const getFromChromeStorage = async (): Promise => { {} as Record ); + const settings = + typeof result[settingsKey] === "object" && + Object.keys(result[settingsKey]).length + ? result[settingsKey] + : {}; + return { layout, panels, + settings, }; }; @@ -76,9 +93,20 @@ const getFromLocalStorage = () => { {} as Record ); + const rawSettings = localStorage.getItem(settingsKey); + const parsedSettings = + rawSettings && (JSON.parse(rawSettings) as Partial); + const settings = + parsedSettings && + typeof parsedSettings === "object" && + Object.keys(parsedSettings).length + ? parsedSettings + : {}; + return { layout, panels, + settings, }; }; @@ -125,3 +153,16 @@ export const storePanelInSync = (uuid: string, value: string) => { return chrome.storage.sync.set({ [`${panelPrefix}${uuid}`]: value }); } }; + +export const storeSettingsLocally = (settings: Partial) => + localStorage.setItem(settingsKey, JSON.stringify(settings)); + +export const storeSettingsInSync = (settings: Partial) => { + if (hasBrowserStorage()) { + return browser.storage.sync.set({ [settingsKey]: settings }); + } + + if (hasChromeStorage()) { + return chrome.storage.sync.set({ [settingsKey]: settings }); + } +};