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