From 6db9a08f7ffd0e177691412d7b358683bd8b1399 Mon Sep 17 00:00:00 2001 From: rektdeckard Date: Mon, 21 Aug 2023 00:31:00 -0600 Subject: [PATCH] feat(app): rought our url persistence --- package.json | 4 +- src/components/App/App.tsx | 4 +- src/index.tsx | 160 ++++++++++++++++++++++----- src/state/RecoilSyncLocalStorage.tsx | 84 ++++++++++++++ src/state/atoms.ts | 40 +++++++ src/state/selectors.ts | 2 + 6 files changed, 266 insertions(+), 28 deletions(-) create mode 100644 src/state/RecoilSyncLocalStorage.tsx diff --git a/package.json b/package.json index 6f1598b..7ff722a 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,8 @@ }, "dependencies": { "@phosphor-icons/core": "^2.0.2", - "@phosphor-icons/react": "^2.1.3", + "@phosphor-icons/react": "^2.0.15", + "@recoiljs/refine": "^0.1.1", "file-saver": "^2.0.2", "framer-motion": "^9.0.1", "fuse.js": "^6.4.1", @@ -40,6 +41,7 @@ "react-hotkeys-hook": "^3.2.1", "react-use": "^17.4.0", "recoil": "^0.7.6", + "recoil-sync": "^0.2.0", "svg2png-converter": "^1.0.2", "tinycolor2": "^1.4.2" }, diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx index 499a3a9..1884ddb 100644 --- a/src/components/App/App.tsx +++ b/src/components/App/App.tsx @@ -20,8 +20,8 @@ const errorFallback = ; const waitingFallback = ; const App: React.FC = () => { - useIconParameters(); - usePersistSettings(); + // useIconParameters(); + // usePersistSettings(); const isDark = useRecoilValue(isDarkThemeSelector); diff --git a/src/index.tsx b/src/index.tsx index 050a569..d64aa17 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -1,10 +1,12 @@ import { StrictMode } from "react"; import { createRoot } from "react-dom/client"; import { RecoilRoot } from "recoil"; +import { RecoilURLSync } from "recoil-sync"; import App from "./components/App"; +import RecoilSyncLocalStorage from "./state/RecoilSyncLocalStorage"; import ReactGA from "react-ga4"; -const GA_MEASUREMENT_ID = 'G-1C1REQCLFB' +const GA_MEASUREMENT_ID = "G-1C1REQCLFB"; ReactGA.initialize(GA_MEASUREMENT_ID); const container = document.getElementById("root"); @@ -13,12 +15,24 @@ const root = createRoot(container!); root.render( - + { + console.log(data); + return ""; + }} + deserialize={() => {}} + > + + + + ); -console.log(` +console.log( + ` %c sphorphosphor %co%cspho %c s%cphorphosphor %co%csphorpho%cs @@ -45,26 +59,122 @@ console.log(` %cThanks for your interest in Phosphor <3 Hire me at https://tobiasfried.com `, -"color: #8861A8;", "color: #442B78;", "color: #5B399F;", -"color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", -"color: #65461E;", "color: #A17030;", "color: #65461E;", "color: #442B78;", "color: #5B399F;", "color: #442B78;", -"color: #0E481F;", "color: #0E481E;", -"color: #0E481F;", "color: #0EA147;", "color: #19873A;", -"color: #0E481F;", "color: #0EA147;", "color: #19873A;", -"color: #0E481F;", "color: #0EA147;", "color: #19873A;", -"color: #0E481F;", "color: #0EA147;", "color: #19873A;", -"color: #0E481F;", "color: #0EA147;", "color: #19873A;", -"color: #0E481F;", "color: #0EA147;", "color: #19873A;", -"color: #A17030;" + "color: #8861A8;", + "color: #442B78;", + "color: #5B399F;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #8861A8;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #CE93FE;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #F7AC49;", + "color: #65461E;", + "color: #442B78;", + "color: #925BFF;", + "color: #442B78;", + "color: #65461E;", + "color: #A17030;", + "color: #65461E;", + "color: #442B78;", + "color: #5B399F;", + "color: #442B78;", + "color: #0E481F;", + "color: #0E481E;", + "color: #0E481F;", + "color: #0EA147;", + "color: #19873A;", + "color: #0E481F;", + "color: #0EA147;", + "color: #19873A;", + "color: #0E481F;", + "color: #0EA147;", + "color: #19873A;", + "color: #0E481F;", + "color: #0EA147;", + "color: #19873A;", + "color: #0E481F;", + "color: #0EA147;", + "color: #19873A;", + "color: #0E481F;", + "color: #0EA147;", + "color: #19873A;", + "color: #A17030;" ); diff --git a/src/state/RecoilSyncLocalStorage.tsx b/src/state/RecoilSyncLocalStorage.tsx new file mode 100644 index 0000000..57e17da --- /dev/null +++ b/src/state/RecoilSyncLocalStorage.tsx @@ -0,0 +1,84 @@ +import { useCallback, ReactNode } from "react"; +import { DefaultValue } from "recoil"; +import { ReadItem, WriteItems, ListenToItems, RecoilSync } from "recoil-sync"; +import { STORAGE_KEY } from "."; + +const DEFAULT_VALUE = new DefaultValue(); + +export default ({ children }: { children: ReactNode }) => { + const read: ReadItem = useCallback((itemKey) => { + if (typeof document === "undefined") return DEFAULT_VALUE; // SSR + + const item = localStorage.getItem(itemKey); + + let parsed: unknown; + + try { + parsed = item === null ? DEFAULT_VALUE : parseJSON(item); + } catch { + parsed = DEFAULT_VALUE; + console.warn({ itemKey, item }, "parseJSON failed"); + } + + return parsed; + }, []); + + const write: WriteItems = useCallback(({ diff }) => { + if (typeof document === "undefined") return; // SSR + + for (const [key, value] of diff) { + if (value instanceof DefaultValue) { + localStorage.removeItem(key); + } else { + // reasons for setItem to fail: https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem#exceptions + try { + localStorage.setItem(key, JSON.stringify(value)); + } catch (err) { + console.warn({ err, key, value }, "localStorage.setItem failed"); + } + } + } + }, []); + + const listen: ListenToItems = useCallback( + ({ updateItem, updateAllKnownItems }) => { + const onStorage = (event: StorageEvent) => { + // ignore clear() calls + if (event.storageArea === localStorage && event.key !== null) { + let parsed: unknown; + try { + parsed = + event.newValue === null + ? DEFAULT_VALUE + : parseJSON(event.newValue); + } catch { + parsed = DEFAULT_VALUE; + console.warn({ event }, "parseJSON failed"); + } + + updateItem(event.key, parsed); + } + }; + + window.addEventListener("storage", onStorage); + + return () => window.removeEventListener("storage", onStorage); + }, + [] + ); + + return ( + + {children} + + ); +}; + +function parseJSON(value: string): unknown { + return value === "undefined" ? undefined : JSON.parse(value); +} diff --git a/src/state/atoms.ts b/src/state/atoms.ts index b6d570f..209e4f9 100644 --- a/src/state/atoms.ts +++ b/src/state/atoms.ts @@ -1,25 +1,65 @@ import { atom } from "recoil"; +import { syncEffect } from "recoil-sync"; +import { custom, number, string, stringLiterals } from "@recoiljs/refine"; import { IconStyle } from "@phosphor-icons/core"; import { IconEntry } from "@/lib"; export const searchQueryAtom = atom({ key: "searchQuery", default: "", + effects: [ + syncEffect({ + itemKey: "q", + refine: custom((q) => { + return (q as string) ?? ""; + }), + syncDefault: false, + }), + ], }); export const iconWeightAtom = atom({ key: "iconWeight", default: IconStyle.REGULAR, + effects: [ + syncEffect({ + itemKey: "weight", + refine: custom((w) => { + const isWeight = (w as string)?.toUpperCase?.() in IconStyle; + return isWeight ? (w as IconStyle) : IconStyle.REGULAR; + }, `Unrecognized weight`), + syncDefault: false, + }), + ], }); export const iconSizeAtom = atom({ key: "iconSize", default: 32, + effects: [ + syncEffect({ + itemKey: "size", + refine: custom((s) => { + const size = Number.isFinite(Number(s)) ? Number(s) : 32; + return Math.min(Math.max(size, 16), 96); + }), + syncDefault: false, + }), + ], }); export const iconColorAtom = atom({ key: "iconColor", default: "#000000", + effects: [ + syncEffect({ + itemKey: "color", + refine: custom((c) => { + return (c as string) ?? "#000000"; + }), + syncDefault: false, + }), + ], }); export const iconPreviewOpenAtom = atom({ diff --git a/src/state/selectors.ts b/src/state/selectors.ts index d76514a..4f6d856 100644 --- a/src/state/selectors.ts +++ b/src/state/selectors.ts @@ -1,5 +1,6 @@ import { selector, selectorFamily } from "recoil"; import TinyColor from "tinycolor2"; +// @ts-ignore import Fuse from "fuse.js"; import { IconCategory } from "@phosphor-icons/core"; @@ -26,6 +27,7 @@ export const filteredQueryResultsSelector = selector>({ if (!query) return icons; return new Promise((resolve) => + // @ts-ignore resolve(fuse.search(query).map((value) => value.item)) ); },