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))
);
},