feat(app): rought our url persistence
This commit is contained in:
committed by
Tobias Fried
parent
2bcf098d1d
commit
6db9a08f7f
@@ -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"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
|
|||||||
160
src/index.tsx
160
src/index.tsx
@@ -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;"
|
||||||
);
|
);
|
||||||
|
|||||||
84
src/state/RecoilSyncLocalStorage.tsx
Normal file
84
src/state/RecoilSyncLocalStorage.tsx
Normal 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);
|
||||||
|
}
|
||||||
@@ -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>({
|
||||||
|
|||||||
@@ -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))
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user