feat(app): rought our url persistence
This commit is contained in:
committed by
Tobias Fried
parent
2bcf098d1d
commit
6db9a08f7f
@@ -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"
|
||||
},
|
||||
|
||||
@@ -20,8 +20,8 @@ const errorFallback = <Notice message="Search error" />;
|
||||
const waitingFallback = <Notice type="none" message="" />;
|
||||
|
||||
const App: React.FC<any> = () => {
|
||||
useIconParameters();
|
||||
usePersistSettings();
|
||||
// useIconParameters();
|
||||
// usePersistSettings();
|
||||
|
||||
const isDark = useRecoilValue(isDarkThemeSelector);
|
||||
|
||||
|
||||
156
src/index.tsx
156
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(
|
||||
<StrictMode>
|
||||
<RecoilRoot>
|
||||
<RecoilURLSync
|
||||
location={{ part: "queryParams" }}
|
||||
serialize={(data) => {
|
||||
console.log(data);
|
||||
return "";
|
||||
}}
|
||||
deserialize={() => {}}
|
||||
>
|
||||
<RecoilSyncLocalStorage>
|
||||
<App />
|
||||
</RecoilSyncLocalStorage>
|
||||
</RecoilURLSync>
|
||||
</RecoilRoot>
|
||||
</StrictMode>
|
||||
);
|
||||
|
||||
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: #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;"
|
||||
);
|
||||
|
||||
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 { 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<string>({
|
||||
key: "searchQuery",
|
||||
default: "",
|
||||
effects: [
|
||||
syncEffect({
|
||||
itemKey: "q",
|
||||
refine: custom((q) => {
|
||||
return (q as string) ?? "";
|
||||
}),
|
||||
syncDefault: false,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const iconWeightAtom = atom<IconStyle>({
|
||||
key: "iconWeight",
|
||||
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>({
|
||||
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<string>({
|
||||
key: "iconColor",
|
||||
default: "#000000",
|
||||
effects: [
|
||||
syncEffect({
|
||||
itemKey: "color",
|
||||
refine: custom((c) => {
|
||||
return (c as string) ?? "#000000";
|
||||
}),
|
||||
syncDefault: false,
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const iconPreviewOpenAtom = atom<string | false>({
|
||||
|
||||
@@ -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<ReadonlyArray<IconEntry>>({
|
||||
if (!query) return icons;
|
||||
|
||||
return new Promise((resolve) =>
|
||||
// @ts-ignore
|
||||
resolve(fuse.search(query).map((value) => value.item))
|
||||
);
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user