diff --git a/src/hooks/useSettings.ts b/src/hooks/useSettings.ts new file mode 100644 index 0000000..1a4a424 --- /dev/null +++ b/src/hooks/useSettings.ts @@ -0,0 +1,16 @@ +import { useCallback } from "react"; +import { useRecoilState } from "recoil"; + +import { addressDisplayOptionsState } from "@/states/settings"; + +export const useSettings = () => { + const [addressDisplayOptions, setAddressDisplayOptions] = useRecoilState(addressDisplayOptionsState); + const toggleDisplayOption = useCallback((key: keyof typeof addressDisplayOptions) => { + setAddressDisplayOptions((prevOptions) => ({ + ...prevOptions, + [key]: !prevOptions[key], + })); + }, [setAddressDisplayOptions]); + + return { addressDisplayOptions, toggleDisplayOption }; +}; diff --git a/src/shared/storage.ts b/src/shared/storage.ts new file mode 100644 index 0000000..2cd72d5 --- /dev/null +++ b/src/shared/storage.ts @@ -0,0 +1,70 @@ +import type { Browser } from "webextension-polyfill"; +import type { AddressData, SearchKey } from "./models/address"; +import { isExtension } from "./utils"; + +let browser: Browser | null = null; +if (isExtension()) { + browser = await import("webextension-polyfill"); +} + +type SearchResultOptions = { + showEng: boolean; + showRoad: boolean; + showLegacy: boolean; +}; + +export type Settings = Partial<{ + searchResult: Partial; + addressData: AddressData[]; + prevSearchKey: SearchKey; +}>; + +export const DEFAULT_SETTINGS: Settings = { + searchResult: { + showEng: false, + showRoad: true, + showLegacy: true, + }, + addressData: [], + prevSearchKey: { + countPerPage: "20", + currentPage: "1", + keyword: "", + end: false, + }, +}; + +export const getAllStorageData = () => { + if (!isExtension() || !browser) { + console.info("This runtime does not running on extension"); + return null; + } + return browser.storage.local.get(); +}; + +export const getSearchResultSettings = () => { + if (!isExtension() || !browser) { + console.info("This runtime does not running on extension"); + return null; + } + return browser.storage.local.get("searchResult"); +}; + +export const validateSettingsData = (settingsData: unknown, expected: Record = DEFAULT_SETTINGS): boolean => { + if (typeof settingsData !== "object" || settingsData === null) { + return false; + } + + const expectedKeys = Object.keys(expected); + return Object.entries(settingsData).every(([k, v]) => { + if (!expectedKeys.includes(k)) { + return false; + } + + if (typeof v === "object" && v !== null) { + return validateSettingsData(v, expected[k]); + } + + return typeof v === typeof expected[k]; + }); +}; diff --git a/src/states/settings.ts b/src/states/settings.ts new file mode 100644 index 0000000..132d6ab --- /dev/null +++ b/src/states/settings.ts @@ -0,0 +1,27 @@ +import { atom } from "recoil"; + +import { getSearchResultSettings, validateSettingsData, DEFAULT_SETTINGS } from "@/shared/storage"; + +export const addressDisplayOptionsState = atom({ + key: "display-options", + default: { + engAddrShown: true, + roadAddrShown: true, + streetNumAddrShown: true, + }, + effects: [({ trigger, setSelf, onSet }) => { + if (trigger === "get") { + getSearchResultSettings()?.then((settings) => { + if (!validateSettingsData(settings, DEFAULT_SETTINGS.searchResult)) { + return; + } + + setSelf({ + engAddrShown: settings.showEng, + roadAddrShown: settings.showRoad, + streetNumAddrShown: settings.showLegacy, + }); + }); + } + }], +});