From b9e41ac135d8300f958e62fc25533bab44e164f8 Mon Sep 17 00:00:00 2001
From: rektdeckard
Date: Thu, 4 Jan 2024 19:39:05 -0700
Subject: [PATCH] feat(app): url persistence
---
package.json | 4 +-
src/components/App/App.tsx | 41 ++++++++-----------
.../ErrorBoundary/ErrorBoundary.tsx | 13 +++++-
src/components/SearchInput/SearchInput.tsx | 4 +-
.../SettingsActions/SettingsActions.tsx | 13 ++----
src/index.tsx | 29 +++++++------
src/state/atoms.ts | 40 ++++++++++++++----
7 files changed, 86 insertions(+), 58 deletions(-)
diff --git a/package.json b/package.json
index 7ff722a..9b587d6 100644
--- a/package.json
+++ b/package.json
@@ -37,9 +37,9 @@
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-dropdown-select": "^4.4.2",
- "react-ga4": "^2.0.0",
+ "react-ga4": "^2.1.0",
"react-hotkeys-hook": "^3.2.1",
- "react-use": "^17.4.0",
+ "react-use": "^17.4.2",
"recoil": "^0.7.6",
"recoil-sync": "^0.2.0",
"svg2png-converter": "^1.0.2",
diff --git a/src/components/App/App.tsx b/src/components/App/App.tsx
index 1884ddb..99de892 100644
--- a/src/components/App/App.tsx
+++ b/src/components/App/App.tsx
@@ -9,40 +9,33 @@ import Footer from "@/components/Footer";
import ErrorBoundary from "@/components/ErrorBoundary";
import Notice from "@/components/Notice";
// import Recipes from "@/components/Recipes";
-import {
- useIconParameters,
- usePersistSettings,
- useCSSVariables,
-} from "@/hooks";
+import { useCSSVariables } from "@/hooks";
import { isDarkThemeSelector } from "@/state";
const errorFallback = ;
const waitingFallback = ;
const App: React.FC = () => {
- // useIconParameters();
- // usePersistSettings();
-
const isDark = useRecoilValue(isDarkThemeSelector);
- const properties = useMemo(
- () => ({
- "--foreground": isDark ? "white" : "var(--moss)",
- "--foreground-card": isDark ? "white" : "var(--moss)",
- "--foreground-secondary": isDark ? "var(--pewter)" : "var(--elephant)",
- "--background": isDark ? "var(--slate)" : "var(--vellum)",
- "--background-card": isDark ? "var(--stone)" : "var(--vellum)",
- "--background-layer": isDark ? "var(--scrim)" : "var(--translucent)",
- "--border-card": isDark ? "var(--shadow)" : "var(--moss-shadow)",
- "--border-secondary": isDark ? "var(--scrim)" : "var(--moss-shadow)",
- "--hover-tabs": isDark ? "var(--slate-sheer)" : "var(--ghost-sheer)",
- "--hover-buttons": isDark ? "var(--scrim)" : "var(--slate)",
- }),
- [isDark]
+ useCSSVariables(
+ useMemo(
+ () => ({
+ "--foreground": isDark ? "white" : "var(--moss)",
+ "--foreground-card": isDark ? "white" : "var(--moss)",
+ "--foreground-secondary": isDark ? "var(--pewter)" : "var(--elephant)",
+ "--background": isDark ? "var(--slate)" : "var(--vellum)",
+ "--background-card": isDark ? "var(--stone)" : "var(--vellum)",
+ "--background-layer": isDark ? "var(--scrim)" : "var(--translucent)",
+ "--border-card": isDark ? "var(--shadow)" : "var(--moss-shadow)",
+ "--border-secondary": isDark ? "var(--scrim)" : "var(--moss-shadow)",
+ "--hover-tabs": isDark ? "var(--slate-sheer)" : "var(--ghost-sheer)",
+ "--hover-buttons": isDark ? "var(--scrim)" : "var(--slate)",
+ }),
+ [isDark]
+ )
);
- useCSSVariables(properties);
-
return (
diff --git a/src/components/ErrorBoundary/ErrorBoundary.tsx b/src/components/ErrorBoundary/ErrorBoundary.tsx
index 4922981..c826b20 100644
--- a/src/components/ErrorBoundary/ErrorBoundary.tsx
+++ b/src/components/ErrorBoundary/ErrorBoundary.tsx
@@ -1,4 +1,5 @@
import { Component, ErrorInfo, ReactNode } from "react";
+import ReactGA from "react-ga4";
interface ErrorBoundaryProps {
fallback?: JSX.Element | ReactNode;
@@ -23,8 +24,16 @@ export default class ErrorBoundary extends Component<
}
componentDidCatch(error: any, info: ErrorInfo) {
- void error;
- console.info(info);
+ console.error(error);
+ ReactGA.event("exception", {
+ description: JSON.stringify({
+ error:
+ error instanceof Error
+ ? error.message
+ : error.toString?.() ?? "UNSERIALIZEABLE",
+ info,
+ }),
+ });
}
render(): JSX.Element | ReactNode {
diff --git a/src/components/SearchInput/SearchInput.tsx b/src/components/SearchInput/SearchInput.tsx
index 3c9a995..13427fc 100644
--- a/src/components/SearchInput/SearchInput.tsx
+++ b/src/components/SearchInput/SearchInput.tsx
@@ -52,7 +52,7 @@ const SearchInput = (_: SearchInputProps) => {
}, [query]);
/* eslint-enable react-hooks/exhaustive-deps */
- const [isReady] = useDebounce(
+ const [_isReady, _cancel] = useDebounce(
() => {
if (value !== query) {
setQuery(value);
@@ -95,7 +95,7 @@ const SearchInput = (_: SearchInputProps) => {
/>
{!value && !isMobile && {isApple ? : "Ctrl + "}K}
{value ? (
- isReady() ? (
+ value === query ? (
) : (
diff --git a/src/components/SettingsActions/SettingsActions.tsx b/src/components/SettingsActions/SettingsActions.tsx
index 40f4d57..9a78930 100644
--- a/src/components/SettingsActions/SettingsActions.tsx
+++ b/src/components/SettingsActions/SettingsActions.tsx
@@ -1,6 +1,6 @@
import { useState } from "react";
import ReactGA from "react-ga4";
-import { useRecoilState, useResetRecoilState } from "recoil";
+import { useRecoilState, useResetRecoilState, useSetRecoilState } from "recoil";
import {
ArrowCounterClockwise,
CheckCircle,
@@ -21,21 +21,16 @@ import "./SettingsActions.css";
const SettingsActions = () => {
const [weight, setWeight] = useRecoilState(iconWeightAtom);
- const [size, setSize] = useRecoilState(iconSizeAtom);
- const [color, setColor] = useRecoilState(iconColorAtom);
+ const setSize = useSetRecoilState(iconSizeAtom);
+ const setColor = useSetRecoilState(iconColorAtom);
const reset = useResetRecoilState(resetSettingsSelector);
const [copied, setCopied] = useTransientState(false, 2000);
const [booped, setBooped] = useState(false);
const copyDeepLinkToClipboard = () => {
- const paramString = new URLSearchParams([
- ["weight", weight.toString()],
- ["size", size.toString()],
- ["color", color.replace("#", "")],
- ]).toString();
void navigator.clipboard
- ?.writeText(`${window.location.host}?${paramString}`)
+ ?.writeText(`${window.location.origin}${window.location.search}`)
.then(() => {
setCopied(true);
})
diff --git a/src/index.tsx b/src/index.tsx
index d64aa17..0994f81 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -1,10 +1,11 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { RecoilRoot } from "recoil";
-import { RecoilURLSync } from "recoil-sync";
+import { RecoilURLSyncJSON } from "recoil-sync";
import App from "./components/App";
-import RecoilSyncLocalStorage from "./state/RecoilSyncLocalStorage";
import ReactGA from "react-ga4";
+import ErrorBoundary from "./components/ErrorBoundary";
+import Notice from "./components/Notice";
const GA_MEASUREMENT_ID = "G-1C1REQCLFB";
ReactGA.initialize(GA_MEASUREMENT_ID);
@@ -15,18 +16,22 @@ const root = createRoot(container!);
root.render(
- {
- console.log(data);
- return "";
- }}
- deserialize={() => {}}
+
+ An error occurred. Try going{" "}
+ home.
+
+ }
+ />
+ }
>
-
+
-
-
+
+
);
diff --git a/src/state/atoms.ts b/src/state/atoms.ts
index 209e4f9..15ce9e8 100644
--- a/src/state/atoms.ts
+++ b/src/state/atoms.ts
@@ -1,6 +1,7 @@
import { atom } from "recoil";
import { syncEffect } from "recoil-sync";
-import { custom, number, string, stringLiterals } from "@recoiljs/refine";
+import TinyColor from "tinycolor2";
+import { custom, stringLiterals } from "@recoiljs/refine";
import { IconStyle } from "@phosphor-icons/core";
import { IconEntry } from "@/lib";
@@ -11,7 +12,7 @@ export const searchQueryAtom = atom({
syncEffect({
itemKey: "q",
refine: custom((q) => {
- return (q as string) ?? "";
+ return (q as string).toString() ?? "";
}),
syncDefault: false,
}),
@@ -24,10 +25,21 @@ export const iconWeightAtom = atom({
effects: [
syncEffect({
itemKey: "weight",
- refine: custom((w) => {
- const isWeight = (w as string)?.toUpperCase?.() in IconStyle;
- return isWeight ? (w as IconStyle) : IconStyle.REGULAR;
- }, `Unrecognized weight`),
+ refine: stringLiterals({
+ thin: IconStyle.THIN,
+ light: IconStyle.LIGHT,
+ regular: IconStyle.REGULAR,
+ bold: IconStyle.BOLD,
+ fill: IconStyle.FILL,
+ duotone: IconStyle.DUOTONE,
+ }),
+ write: (atom, w) => {
+ if (typeof w === "string") {
+ atom.write("weight", w);
+ } else {
+ atom.reset("weight");
+ }
+ },
syncDefault: false,
}),
],
@@ -55,8 +67,22 @@ export const iconColorAtom = atom({
syncEffect({
itemKey: "color",
refine: custom((c) => {
- return (c as string) ?? "#000000";
+ if (typeof c === "string") {
+ const normalizedColor = TinyColor(c);
+ if (normalizedColor.isValid()) {
+ return normalizedColor.toHexString();
+ }
+ }
+ return "#000000";
}),
+ write: (atom, c) => {
+ if (typeof c === "string") {
+ const color = c.replace("#", "");
+ atom.write("color", color);
+ } else {
+ atom.reset("color");
+ }
+ },
syncDefault: false,
}),
],