mirror of
https://github.com/muerwre/markdown-home-tab.git
synced 2025-04-25 00:46:41 +07:00
make global storage provider
This commit is contained in:
parent
2ba35b8e2b
commit
3a4e8e7702
9 changed files with 109 additions and 53 deletions
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "markdown-home-tab",
|
"name": "markdown-home-tab",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.5",
|
"version": "0.0.6",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
|
import { noop } from "~/utils/noop";
|
||||||
|
|
||||||
export interface ColorSettings {
|
export interface ColorSettings {
|
||||||
backgroundColor: string;
|
backgroundColor: string;
|
||||||
|
@ -26,10 +27,9 @@ export const defaultSettings: SettingsValue = {
|
||||||
|
|
||||||
export const SettingsContext = createContext({
|
export const SettingsContext = createContext({
|
||||||
settings: defaultSettings,
|
settings: defaultSettings,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
update: noop as (value: Partial<SettingsValue>) => void,
|
||||||
update: (_: Partial<SettingsValue>) => {},
|
show: noop,
|
||||||
show: () => {},
|
hide: noop,
|
||||||
hide: () => {},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useSettings = () => useContext(SettingsContext);
|
export const useSettings = () => useContext(SettingsContext);
|
||||||
|
|
|
@ -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<SettingsValue>) => {
|
|
||||||
if (!hydrated) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSettings((v) => {
|
|
||||||
const updatedValue = { ...v, ...val };
|
|
||||||
storage.set("", updatedValue);
|
|
||||||
return updatedValue;
|
|
||||||
});
|
|
||||||
}, [hydrated]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
storage.get<SettingsValue>("").then(next => {
|
|
||||||
setSettings(prev => ({ ...prev, ...next }));
|
|
||||||
}).finally(() => setHydrated(true));
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { settings, update, hydrated };
|
|
||||||
};
|
|
17
src/modules/settings/hooks/useSettings.ts
Normal file
17
src/modules/settings/hooks/useSettings.ts
Normal file
|
@ -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 };
|
||||||
|
};
|
|
@ -5,23 +5,19 @@ import { ModalPage } from "~/modules/modal/components/ModalPage";
|
||||||
import { Modal } from "~/modules/modal/containers/Modal";
|
import { Modal } from "~/modules/modal/containers/Modal";
|
||||||
import { SettingsContainer } from "../../containers/SettingsContainer";
|
import { SettingsContainer } from "../../containers/SettingsContainer";
|
||||||
import { SettingsContext } from "../../context/SettingsContext";
|
import { SettingsContext } from "../../context/SettingsContext";
|
||||||
import { usePersistSettings } from "../../hooks/usePersistSettings";
|
import { useSettings } from "../../hooks/useSettings";
|
||||||
import styles from "./styles.module.scss";
|
import styles from "./styles.module.scss";
|
||||||
|
|
||||||
const SettingsProvider: FC<PropsWithChildren> = ({ children }) => {
|
const SettingsProvider: FC<PropsWithChildren> = ({ children }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const { settings, update, hydrated } = usePersistSettings();
|
const { settings, update } = useSettings();
|
||||||
|
|
||||||
const [settingsModalVisible, setSettingsModalVisible] = useState(false);
|
const [settingsModalVisible, setSettingsModalVisible] = useState(false);
|
||||||
|
|
||||||
const show = useCallback(() => setSettingsModalVisible(true), []);
|
const show = useCallback(() => setSettingsModalVisible(true), []);
|
||||||
const hide = useCallback(() => setSettingsModalVisible(false), []);
|
const hide = useCallback(() => setSettingsModalVisible(false), []);
|
||||||
|
|
||||||
if (!hydrated) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingsContext.Provider value={{ settings, update, show, hide }}>
|
<SettingsContext.Provider value={{ settings, update, show, hide }}>
|
||||||
{settingsModalVisible && (
|
{settingsModalVisible && (
|
||||||
|
|
|
@ -1,13 +1,16 @@
|
||||||
import { createContext, useContext } from "react";
|
import { createContext, useContext } from "react";
|
||||||
import { SerializedDockview } from "dockview";
|
import { SerializedDockview } from "dockview";
|
||||||
import { noop } from "~/utils/noop";
|
import { noop } from "~/utils/noop";
|
||||||
|
import { SettingsValue } from "~/modules/settings/context/SettingsContext";
|
||||||
|
|
||||||
export const StorageContext = createContext({
|
export const StorageContext = createContext({
|
||||||
layout: null as SerializedDockview | null,
|
layout: null as SerializedDockview | null,
|
||||||
panels: {} as Record<string, string>,
|
panels: {} as Record<string, string>,
|
||||||
|
settings: {} as Partial<SettingsValue>,
|
||||||
hydrated: false,
|
hydrated: false,
|
||||||
setPanel: noop as (uuid: string, content: string) => void,
|
setPanel: noop as (uuid: string, content: string) => void,
|
||||||
setLayout: noop as (layout: SerializedDockview) => void,
|
setLayout: noop as (layout: SerializedDockview) => void,
|
||||||
|
setSettings: noop as (layout: Partial<SettingsValue>) => void,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const useStorage = () => useContext(StorageContext);
|
export const useStorage = () => useContext(StorageContext);
|
||||||
|
|
|
@ -4,9 +4,11 @@ import {
|
||||||
hydrateLayout,
|
hydrateLayout,
|
||||||
storeLayoutLocally,
|
storeLayoutLocally,
|
||||||
storePanelLocally,
|
storePanelLocally,
|
||||||
|
storeSettingsLocally,
|
||||||
} from "~/utils/hydrate";
|
} from "~/utils/hydrate";
|
||||||
import { useDelayedSync } from "./hooks/useDelayedSync";
|
import { useDelayedSync } from "./hooks/useDelayedSync";
|
||||||
import { StorageContext } from "./StorageContext";
|
import { StorageContext } from "./StorageContext";
|
||||||
|
import { SettingsValue } from "~/modules/settings/context/SettingsContext";
|
||||||
|
|
||||||
const debounceDelay = 500;
|
const debounceDelay = 500;
|
||||||
|
|
||||||
|
@ -14,8 +16,10 @@ export const StorageProvider = ({ children }: { children: ReactNode }) => {
|
||||||
const [hydrated, setHydrated] = useState(false);
|
const [hydrated, setHydrated] = useState(false);
|
||||||
const [layout, setLayoutValue] = useState<SerializedDockview | null>(null);
|
const [layout, setLayoutValue] = useState<SerializedDockview | null>(null);
|
||||||
const [panels, setPanelsValue] = useState<Record<string, string>>({});
|
const [panels, setPanelsValue] = useState<Record<string, string>>({});
|
||||||
|
const [settings, setSettingsValue] = useState<Partial<SettingsValue>>({});
|
||||||
|
|
||||||
const { storeLayout, storePanel } = useDelayedSync(debounceDelay);
|
const { storeLayout, storePanel, storeSettings } =
|
||||||
|
useDelayedSync(debounceDelay);
|
||||||
|
|
||||||
const setPanel = useCallback(
|
const setPanel = useCallback(
|
||||||
(uuid: string, value: string) => {
|
(uuid: string, value: string) => {
|
||||||
|
@ -35,6 +39,12 @@ export const StorageProvider = ({ children }: { children: ReactNode }) => {
|
||||||
[storeLayout]
|
[storeLayout]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const setSettings = useCallback((value: Partial<SettingsValue>) => {
|
||||||
|
setSettingsValue(value);
|
||||||
|
storeSettingsLocally(value);
|
||||||
|
storeSettings(value);
|
||||||
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (hydrated) {
|
if (hydrated) {
|
||||||
return;
|
return;
|
||||||
|
@ -47,6 +57,7 @@ export const StorageProvider = ({ children }: { children: ReactNode }) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayout(result.layout);
|
setLayout(result.layout);
|
||||||
|
setSettings(result.settings);
|
||||||
|
|
||||||
Object.entries(result.panels).forEach(([uuid, value]) => {
|
Object.entries(result.panels).forEach(([uuid, value]) => {
|
||||||
setPanel(uuid, value);
|
setPanel(uuid, value);
|
||||||
|
@ -55,11 +66,19 @@ export const StorageProvider = ({ children }: { children: ReactNode }) => {
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
setHydrated(true);
|
setHydrated(true);
|
||||||
});
|
});
|
||||||
}, [hydrated, setLayout, setPanel]);
|
}, [hydrated, setLayout, setPanel, setSettings]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<StorageContext.Provider
|
<StorageContext.Provider
|
||||||
value={{ hydrated, layout, panels, setLayout, setPanel }}
|
value={{
|
||||||
|
hydrated,
|
||||||
|
layout,
|
||||||
|
panels,
|
||||||
|
settings,
|
||||||
|
setLayout,
|
||||||
|
setPanel,
|
||||||
|
setSettings,
|
||||||
|
}}
|
||||||
>
|
>
|
||||||
{hydrated ? children : null}
|
{hydrated ? children : null}
|
||||||
</StorageContext.Provider>
|
</StorageContext.Provider>
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
import { useCallback, useRef } from "react";
|
import { useCallback, useRef } from "react";
|
||||||
import { storeLayoutInSync, storePanelInSync } from "~/utils/hydrate";
|
import {
|
||||||
|
storeLayoutInSync,
|
||||||
|
storePanelInSync,
|
||||||
|
storeSettingsInSync,
|
||||||
|
} from "~/utils/hydrate";
|
||||||
import { DebouncedFunc } from "lodash";
|
import { DebouncedFunc } from "lodash";
|
||||||
import { SerializedDockview } from "dockview";
|
import { SerializedDockview } from "dockview";
|
||||||
import debounce from "lodash.debounce";
|
import debounce from "lodash.debounce";
|
||||||
|
import { SettingsValue } from "~/modules/settings/context/SettingsContext";
|
||||||
|
|
||||||
export const useDelayedSync = (debounceDelay: number) => {
|
export const useDelayedSync = (debounceDelay: number) => {
|
||||||
const layoutTimer = useRef<DebouncedFunc<typeof storeLayoutInSync>>();
|
const layoutTimer = useRef<DebouncedFunc<typeof storeLayoutInSync>>();
|
||||||
|
const settingsTimer = useRef<DebouncedFunc<typeof storeSettingsInSync>>();
|
||||||
const panelTimers = useRef<
|
const panelTimers = useRef<
|
||||||
Record<string, DebouncedFunc<typeof storePanelInSync>>
|
Record<string, DebouncedFunc<typeof storePanelInSync>>
|
||||||
>({});
|
>({});
|
||||||
|
@ -34,5 +40,16 @@ export const useDelayedSync = (debounceDelay: number) => {
|
||||||
[debounceDelay]
|
[debounceDelay]
|
||||||
);
|
);
|
||||||
|
|
||||||
return { storeLayout, storePanel };
|
const storeSettings = useCallback(
|
||||||
|
(settings: Partial<SettingsValue>) => {
|
||||||
|
if (settingsTimer.current) {
|
||||||
|
settingsTimer.current.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
settingsTimer.current = debounce(storeSettingsInSync, debounceDelay);
|
||||||
|
settingsTimer.current(settings);
|
||||||
|
},
|
||||||
|
[debounceDelay]
|
||||||
|
);
|
||||||
|
return { storeLayout, storePanel, storeSettings };
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,12 +1,15 @@
|
||||||
import { SerializedDockview } from "dockview";
|
import { SerializedDockview } from "dockview";
|
||||||
|
import { SettingsValue } from "~/modules/settings/context/SettingsContext";
|
||||||
import { hasBrowserStorage, hasChromeStorage } from "~/utils/storage";
|
import { hasBrowserStorage, hasChromeStorage } from "~/utils/storage";
|
||||||
|
|
||||||
interface Result {
|
interface Result {
|
||||||
layout: SerializedDockview;
|
layout: SerializedDockview;
|
||||||
panels: Record<string, string>;
|
panels: Record<string, string>;
|
||||||
|
settings: Partial<SettingsValue>;
|
||||||
}
|
}
|
||||||
|
|
||||||
const layoutKey = "dockview_persistance_layout";
|
const layoutKey = "dockview_persistance_layout";
|
||||||
|
const settingsKey = "settings";
|
||||||
const panelPrefix = "MarkdownEditorContainerMarkdownEditorContainer";
|
const panelPrefix = "MarkdownEditorContainerMarkdownEditorContainer";
|
||||||
|
|
||||||
const makePanelKey = (uuid: string) => `${panelPrefix}${uuid}`;
|
const makePanelKey = (uuid: string) => `${panelPrefix}${uuid}`;
|
||||||
|
@ -27,9 +30,16 @@ const getFromBrowserStorage = async (): Promise<Result | null> => {
|
||||||
{} as Record<string, string>
|
{} as Record<string, string>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const settings =
|
||||||
|
typeof result[settingsKey] === "object" &&
|
||||||
|
Object.keys(result[settingsKey]).length
|
||||||
|
? result[settingsKey]
|
||||||
|
: {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
layout,
|
layout,
|
||||||
panels,
|
panels,
|
||||||
|
settings,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -49,9 +59,16 @@ const getFromChromeStorage = async (): Promise<Result | null> => {
|
||||||
{} as Record<string, string>
|
{} as Record<string, string>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const settings =
|
||||||
|
typeof result[settingsKey] === "object" &&
|
||||||
|
Object.keys(result[settingsKey]).length
|
||||||
|
? result[settingsKey]
|
||||||
|
: {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
layout,
|
layout,
|
||||||
panels,
|
panels,
|
||||||
|
settings,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -76,9 +93,20 @@ const getFromLocalStorage = () => {
|
||||||
{} as Record<string, string>
|
{} as Record<string, string>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const rawSettings = localStorage.getItem(settingsKey);
|
||||||
|
const parsedSettings =
|
||||||
|
rawSettings && (JSON.parse(rawSettings) as Partial<SettingsValue>);
|
||||||
|
const settings =
|
||||||
|
parsedSettings &&
|
||||||
|
typeof parsedSettings === "object" &&
|
||||||
|
Object.keys(parsedSettings).length
|
||||||
|
? parsedSettings
|
||||||
|
: {};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
layout,
|
layout,
|
||||||
panels,
|
panels,
|
||||||
|
settings,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -125,3 +153,16 @@ export const storePanelInSync = (uuid: string, value: string) => {
|
||||||
return chrome.storage.sync.set({ [`${panelPrefix}${uuid}`]: value });
|
return chrome.storage.sync.set({ [`${panelPrefix}${uuid}`]: value });
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const storeSettingsLocally = (settings: Partial<SettingsValue>) =>
|
||||||
|
localStorage.setItem(settingsKey, JSON.stringify(settings));
|
||||||
|
|
||||||
|
export const storeSettingsInSync = (settings: Partial<SettingsValue>) => {
|
||||||
|
if (hasBrowserStorage()) {
|
||||||
|
return browser.storage.sync.set({ [settingsKey]: settings });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasChromeStorage()) {
|
||||||
|
return chrome.storage.sync.set({ [settingsKey]: settings });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue