feat(app): rought our url persistence

This commit is contained in:
rektdeckard
2023-08-21 00:31:00 -06:00
committed by Tobias Fried
parent 2bcf098d1d
commit 6db9a08f7f
6 changed files with 266 additions and 28 deletions

View File

@@ -28,7 +28,8 @@
}, },
"dependencies": { "dependencies": {
"@phosphor-icons/core": "^2.0.2", "@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", "file-saver": "^2.0.2",
"framer-motion": "^9.0.1", "framer-motion": "^9.0.1",
"fuse.js": "^6.4.1", "fuse.js": "^6.4.1",
@@ -40,6 +41,7 @@
"react-hotkeys-hook": "^3.2.1", "react-hotkeys-hook": "^3.2.1",
"react-use": "^17.4.0", "react-use": "^17.4.0",
"recoil": "^0.7.6", "recoil": "^0.7.6",
"recoil-sync": "^0.2.0",
"svg2png-converter": "^1.0.2", "svg2png-converter": "^1.0.2",
"tinycolor2": "^1.4.2" "tinycolor2": "^1.4.2"
}, },

View File

@@ -20,8 +20,8 @@ const errorFallback = <Notice message="Search error" />;
const waitingFallback = <Notice type="none" message="" />; const waitingFallback = <Notice type="none" message="" />;
const App: React.FC<any> = () => { const App: React.FC<any> = () => {
useIconParameters(); // useIconParameters();
usePersistSettings(); // usePersistSettings();
const isDark = useRecoilValue(isDarkThemeSelector); const isDark = useRecoilValue(isDarkThemeSelector);

View File

@@ -1,10 +1,12 @@
import { StrictMode } from "react"; import { StrictMode } from "react";
import { createRoot } from "react-dom/client"; import { createRoot } from "react-dom/client";
import { RecoilRoot } from "recoil"; import { RecoilRoot } from "recoil";
import { RecoilURLSync } from "recoil-sync";
import App from "./components/App"; import App from "./components/App";
import RecoilSyncLocalStorage from "./state/RecoilSyncLocalStorage";
import ReactGA from "react-ga4"; import ReactGA from "react-ga4";
const GA_MEASUREMENT_ID = 'G-1C1REQCLFB' const GA_MEASUREMENT_ID = "G-1C1REQCLFB";
ReactGA.initialize(GA_MEASUREMENT_ID); ReactGA.initialize(GA_MEASUREMENT_ID);
const container = document.getElementById("root"); const container = document.getElementById("root");
@@ -13,12 +15,24 @@ const root = createRoot(container!);
root.render( root.render(
<StrictMode> <StrictMode>
<RecoilRoot> <RecoilRoot>
<App /> <RecoilURLSync
location={{ part: "queryParams" }}
serialize={(data) => {
console.log(data);
return "";
}}
deserialize={() => {}}
>
<RecoilSyncLocalStorage>
<App />
</RecoilSyncLocalStorage>
</RecoilURLSync>
</RecoilRoot> </RecoilRoot>
</StrictMode> </StrictMode>
); );
console.log(` console.log(
`
%c sphorphosphor %co%cspho %c sphorphosphor %co%cspho
%c s%cphorphosphor %co%csphorpho%cs %c s%cphorphosphor %co%csphorpho%cs
@@ -45,26 +59,122 @@ console.log(`
%cThanks for your interest in Phosphor <3 %cThanks for your interest in Phosphor <3
Hire me at https://tobiasfried.com Hire me at https://tobiasfried.com
`, `,
"color: #8861A8;", "color: #442B78;", "color: #5B399F;", "color: #8861A8;",
"color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #442B78;",
"color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #5B399F;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #8861A8;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #CE93FE;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #442B78;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #925BFF;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #442B78;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #65461E;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #8861A8;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #8861A8;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #CE93FE;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #CE93FE;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #442B78;",
"color: #65461E;", "color: #F7AC49;", "color: #65461E;", "color: #442B78;", "color: #925BFF;", "color: #442B78;", "color: #925BFF;",
"color: #65461E;", "color: #A17030;", "color: #65461E;", "color: #442B78;", "color: #5B399F;", "color: #442B78;", "color: #442B78;",
"color: #0E481F;", "color: #0E481E;", "color: #65461E;",
"color: #0E481F;", "color: #0EA147;", "color: #19873A;", "color: #F7AC49;",
"color: #0E481F;", "color: #0EA147;", "color: #19873A;", "color: #65461E;",
"color: #0E481F;", "color: #0EA147;", "color: #19873A;", "color: #8861A8;",
"color: #0E481F;", "color: #0EA147;", "color: #19873A;", "color: #CE93FE;",
"color: #0E481F;", "color: #0EA147;", "color: #19873A;", "color: #442B78;",
"color: #0E481F;", "color: #0EA147;", "color: #19873A;", "color: #925BFF;",
"color: #A17030;" "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;"
); );

View File

@@ -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 (
<RecoilSync
storeKey={STORAGE_KEY}
read={read}
write={write}
listen={listen}
>
{children}
</RecoilSync>
);
};
function parseJSON(value: string): unknown {
return value === "undefined" ? undefined : JSON.parse(value);
}

View File

@@ -1,25 +1,65 @@
import { atom } from "recoil"; import { atom } from "recoil";
import { syncEffect } from "recoil-sync";
import { custom, number, string, stringLiterals } from "@recoiljs/refine";
import { IconStyle } from "@phosphor-icons/core"; import { IconStyle } from "@phosphor-icons/core";
import { IconEntry } from "@/lib"; import { IconEntry } from "@/lib";
export const searchQueryAtom = atom<string>({ export const searchQueryAtom = atom<string>({
key: "searchQuery", key: "searchQuery",
default: "", default: "",
effects: [
syncEffect({
itemKey: "q",
refine: custom((q) => {
return (q as string) ?? "";
}),
syncDefault: false,
}),
],
}); });
export const iconWeightAtom = atom<IconStyle>({ export const iconWeightAtom = atom<IconStyle>({
key: "iconWeight", key: "iconWeight",
default: IconStyle.REGULAR, default: IconStyle.REGULAR,
effects: [
syncEffect<IconStyle>({
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<number>({ export const iconSizeAtom = atom<number>({
key: "iconSize", key: "iconSize",
default: 32, 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<string>({ export const iconColorAtom = atom<string>({
key: "iconColor", key: "iconColor",
default: "#000000", default: "#000000",
effects: [
syncEffect({
itemKey: "color",
refine: custom((c) => {
return (c as string) ?? "#000000";
}),
syncDefault: false,
}),
],
}); });
export const iconPreviewOpenAtom = atom<string | false>({ export const iconPreviewOpenAtom = atom<string | false>({

View File

@@ -1,5 +1,6 @@
import { selector, selectorFamily } from "recoil"; import { selector, selectorFamily } from "recoil";
import TinyColor from "tinycolor2"; import TinyColor from "tinycolor2";
// @ts-ignore
import Fuse from "fuse.js"; import Fuse from "fuse.js";
import { IconCategory } from "@phosphor-icons/core"; import { IconCategory } from "@phosphor-icons/core";
@@ -26,6 +27,7 @@ export const filteredQueryResultsSelector = selector<ReadonlyArray<IconEntry>>({
if (!query) return icons; if (!query) return icons;
return new Promise((resolve) => return new Promise((resolve) =>
// @ts-ignore
resolve(fuse.search(query).map((value) => value.item)) resolve(fuse.search(query).map((value) => value.item))
); );
}, },