From 2d96045302ac30366634f09420c81a0faa153997 Mon Sep 17 00:00:00 2001 From: unbound Date: Wed, 5 Aug 2026 11:24:50 +0200 Subject: [PATCH 1/2] Fix bugs and clean up code --- package.json | 6 +-- pnpm-lock.yaml | 22 ++--------- src/achievements.tsx | 2 +- src/components/AppProviders.tsx | 18 +++++++-- src/components/ResultDialog.tsx | 6 +-- src/components/Settings.helpers.tsx | 18 ++++----- src/components/ThemeContext.tsx | 2 +- src/components/VictoryConfetti.tsx | 55 --------------------------- src/components/Wiki/Wiki.utils.ts | 3 +- src/components/Wiki/WikiLogic.tsx | 4 +- src/components/WikiLanguageSelect.tsx | 8 ++-- src/hooks/useStopwatch.ts | 2 +- src/pages/Settings.tsx | 6 +-- src/stores/GameStore.ts | 1 - src/stores/StatisticsStore.ts | 6 +-- src/types/typescript.ts | 1 - src/utils/toast.ts | 4 ++ 17 files changed, 47 insertions(+), 117 deletions(-) delete mode 100644 src/types/typescript.ts create mode 100644 src/utils/toast.ts diff --git a/package.json b/package.json index 8e49dbc..e234db5 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "typesafe-i18n": "typesafe-i18n", "format": "biome format --write ./src", "lint": "biome lint --fix && eslint --fix \"./src/**/*.{ts,tsx}\"", - "test:browser": "vitest" + "test:browser": "vitest --run" }, "dependencies": { "@radix-ui/react-dialog": "1.0.5", @@ -28,7 +28,6 @@ "@radix-ui/react-visually-hidden": "1.0.3", "@tanstack/react-query": "5.38.0", "@tanstack/react-router": "^1.121.2", - "@tanstack/react-router-devtools": "^1.121.2", "canvas-confetti": "^1.9.3", "clsx": "2.1.1", "dompurify": "3.1.4", @@ -52,10 +51,10 @@ "@tailwindcss/container-queries": "0.1.1", "@tanstack/eslint-plugin-query": "^5.35.6", "@tanstack/react-query-devtools": "^5.38.0", + "@tanstack/react-router-devtools": "^1.121.2", "@tanstack/router-cli": "^1.128.8", "@tanstack/router-plugin": "^1.128.8", "@total-typescript/ts-reset": "0.6.1", - "@tweakpane/core": "^2.0.5", "@types/canvas-confetti": "^1.9.0", "@types/dompurify": "3.0.5", "@types/node": "^22.16.0", @@ -81,7 +80,6 @@ "prettier-plugin-tailwindcss": "0.5.14", "rollup-plugin-visualizer": "5.12.0", "tailwindcss": "3.4.3", - "tweakpane": "^4.0.5", "type-fest": "4.18.3", "typescript": "5.9.0-beta", "vite": "6.3.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a227b6a..9cef91a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,9 +38,6 @@ importers: '@tanstack/react-router': specifier: ^1.121.2 version: 1.121.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@tanstack/react-router-devtools': - specifier: ^1.121.2 - version: 1.121.2(@tanstack/react-router@1.121.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.128.8)(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tiny-invariant@1.3.3) canvas-confetti: specifier: ^1.9.3 version: 1.9.3 @@ -105,6 +102,9 @@ importers: '@tanstack/react-query-devtools': specifier: ^5.38.0 version: 5.38.0(@tanstack/react-query@5.38.0(react@18.3.1))(react@18.3.1) + '@tanstack/react-router-devtools': + specifier: ^1.121.2 + version: 1.121.2(@tanstack/react-router@1.121.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@tanstack/router-core@1.128.8)(csstype@3.1.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(tiny-invariant@1.3.3) '@tanstack/router-cli': specifier: ^1.128.8 version: 1.128.8 @@ -114,9 +114,6 @@ importers: '@total-typescript/ts-reset': specifier: 0.6.1 version: 0.6.1 - '@tweakpane/core': - specifier: ^2.0.5 - version: 2.0.5 '@types/canvas-confetti': specifier: ^1.9.0 version: 1.9.0 @@ -192,9 +189,6 @@ importers: tailwindcss: specifier: 3.4.3 version: 3.4.3 - tweakpane: - specifier: ^4.0.5 - version: 4.0.5 type-fest: specifier: 4.18.3 version: 4.18.3 @@ -1654,9 +1648,6 @@ packages: '@total-typescript/ts-reset@0.6.1': resolution: {integrity: sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==} - '@tweakpane/core@2.0.5': - resolution: {integrity: sha512-punBgD5rKCF5vcNo6BsSOXiDR/NSs9VM7SG65QSLJIxfRaGgj54ree9zQW6bO3pNFf3AogiGgaNODUVQRk9YqQ==} - '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -3676,9 +3667,6 @@ packages: engines: {node: '>=18.0.0'} hasBin: true - tweakpane@4.0.5: - resolution: {integrity: sha512-rxEXdSI+ArlG1RyO6FghC4ZUX8JkEfz8F3v1JuteXSV0pEtHJzyo07fcDG+NsJfN5L39kSbCYbB9cBGHyuI/tQ==} - type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -5538,8 +5526,6 @@ snapshots: '@total-typescript/ts-reset@0.6.1': {} - '@tweakpane/core@2.0.5': {} - '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -7720,8 +7706,6 @@ snapshots: optionalDependencies: fsevents: 2.3.3 - tweakpane@4.0.5: {} - type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/src/achievements.tsx b/src/achievements.tsx index f0cb8bd..348ffb4 100644 --- a/src/achievements.tsx +++ b/src/achievements.tsx @@ -263,7 +263,7 @@ const init = {} as Record< (typeof ACHIEVEMENTS_LIST)[number]["id"] | (string & {}), () => boolean >; -export const achivementConditionCheckByIdMap = ACHIEVEMENTS_LIST.reduce((acc, achievement) => { +export const achievementConditionCheckByIdMap = ACHIEVEMENTS_LIST.reduce((acc, achievement) => { acc[achievement.id] = achievement.conditionCheck.bind(achievement); return acc; }, init); diff --git a/src/components/AppProviders.tsx b/src/components/AppProviders.tsx index 277c97b..c739b39 100644 --- a/src/components/AppProviders.tsx +++ b/src/components/AppProviders.tsx @@ -1,8 +1,7 @@ import * as Portal from "@radix-ui/react-portal"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; import { createRouter, RouterProvider } from "@tanstack/react-router"; -import { TanStackRouterDevtools } from "@tanstack/react-router-devtools"; +import { lazy, Suspense } from "react"; import type React from "react"; import { Toaster } from "react-hot-toast"; @@ -13,6 +12,13 @@ import { StopwatchContextProvider } from "./StopwatchContext"; import { ThemeContextProvider } from "./ThemeContext"; import { TooltipProvider } from "./Tooltip"; +const ReactQueryDevtools = import.meta.env.DEV + ? lazy(() => import("@tanstack/react-query-devtools").then((m) => ({ default: m.ReactQueryDevtools }))) + : null; +const TanStackRouterDevtools = import.meta.env.DEV + ? lazy(() => import("@tanstack/react-router-devtools").then((m) => ({ default: m.TanStackRouterDevtools }))) + : null; + export const queryClient = new QueryClient({ defaultOptions: { queries: { @@ -50,8 +56,12 @@ export const AppProviders = () => { }} /> - - + {ReactQueryDevtools && TanStackRouterDevtools && ( + + + + + )} diff --git a/src/components/ResultDialog.tsx b/src/components/ResultDialog.tsx index 92ee585..6db29a1 100644 --- a/src/components/ResultDialog.tsx +++ b/src/components/ResultDialog.tsx @@ -1,7 +1,6 @@ import * as Dialog from "@radix-ui/react-dialog"; import * as Portal from "@radix-ui/react-portal"; import { useEffect, useState } from "react"; -import { toast } from "react-hot-toast"; import { useResetGame } from "../hooks/useResetGame"; import { useI18nContext } from "../i18n/i18n-react"; import { @@ -12,6 +11,7 @@ import { useIsWin, useStartingArticle, } from "../stores/GameStore"; +import { copyNotification } from "../utils/toast"; import { ModalContent, ModalDescription, ModalRoot, ModalTitle, ModalTrigger } from "./Modal"; import { StartArrowEnd } from "./StartArrowEnd"; import { StopwatchDisplay } from "./StopwatchDisplay"; @@ -31,8 +31,6 @@ export const ResultDialog = () => { const cheatingAttempts = useCheatingAttempts(); const missedWins = history.slice(0, -2).reduce((acc, el) => acc + el.winningLinks, 0); - const copyNotification = () => toast.success(LL["Copied to clipboard"](), { position: "top-center" }); - useEffect(() => { setOpen(isWin); }, [isWin]); @@ -83,7 +81,7 @@ export const ResultDialog = () => { className="border-b-[1px] border-b-transparent hover:border-b-primary-blue focus-visible:border-b-primary-blue" onClick={async () => { await navigator.clipboard.writeText(window.location.href); - copyNotification(); + copyNotification(LL["Copied to clipboard"]()); }} > {LL["Share Result"]()} diff --git a/src/components/Settings.helpers.tsx b/src/components/Settings.helpers.tsx index b55e213..69b1839 100644 --- a/src/components/Settings.helpers.tsx +++ b/src/components/Settings.helpers.tsx @@ -1,18 +1,18 @@ import { useEffect } from "react"; -import { toast } from "react-hot-toast"; import type { Article } from "../stores/GameStore"; import { useSettingsStoreActions } from "../stores/SettingsStore"; +import { errorToast } from "../utils/toast"; import type { WikiRandom } from "./RandomButton/RandomButton.types"; export const getHighestLinksPage = (data: WikiRandom) => { if (!data.query?.pages) return; - const highestLinksPage = Object.values(data.query.pages) - .filter((page) => Object.hasOwn(page, "linkshere")) - .reduce((prev, current) => { - const previousLinksphere = prev.linkshere ?? []; - const currentLinksphere = current.linkshere ?? []; - return previousLinksphere.length > currentLinksphere.length ? prev : current; - }); + const pagesWithLinks = Object.values(data.query.pages).filter((page) => Object.hasOwn(page, "linkshere")); + if (pagesWithLinks.length === 0) return; + const highestLinksPage = pagesWithLinks.reduce((prev, current) => { + const previousLinksphere = prev.linkshere ?? []; + const currentLinksphere = current.linkshere ?? []; + return previousLinksphere.length > currentLinksphere.length ? prev : current; + }); const title = highestLinksPage.title; const pageid = highestLinksPage.pageid; @@ -47,8 +47,6 @@ interface RandomSuccessProps { failText: string; } -const errorToast = (text: string) => toast.error(text, { position: "bottom-center" }); - export const handleOnRandomSuccess = ({ setArticle, data, failText }: RandomSuccessProps) => { const articleWithLinks = getHighestLinksPage(data); if (!articleWithLinks?.title || articleWithLinks.title.includes("(disambiguation)")) { diff --git a/src/components/ThemeContext.tsx b/src/components/ThemeContext.tsx index b0b9a0d..74b363f 100644 --- a/src/components/ThemeContext.tsx +++ b/src/components/ThemeContext.tsx @@ -9,7 +9,7 @@ export const ThemeContext = React.createContext(null); export const ThemeContextProvider = ({ children }: { children: React.ReactNode }) => { const defaultDark = window.matchMedia("(prefers-color-scheme: dark)").matches; - const [colorMode, setTheme] = useLocalStorage("theme", defaultDark ? "dark" : "light"); + const [colorMode, setTheme] = useLocalStorage("color-theme", defaultDark ? "dark" : "light"); const switchTheme = () => { const newTheme = colorMode === "light" ? "dark" : "light"; diff --git a/src/components/VictoryConfetti.tsx b/src/components/VictoryConfetti.tsx index 33b1655..f38f524 100644 --- a/src/components/VictoryConfetti.tsx +++ b/src/components/VictoryConfetti.tsx @@ -73,61 +73,6 @@ const corner4: Options = { origin: { x: 1, y: 1 }, }; -const SHOW_CONFETTI_DEBUG_PANEL = false; - -// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -if (import.meta.env.MODE === "development" && SHOW_CONFETTI_DEBUG_PANEL) { - void import("tweakpane").then((TweakpaneModule) => { - const pane = new TweakpaneModule.Pane(); - - const addFolderWithExport = (title: string, options: Options) => { - const folder = pane.addFolder({ - title: title, - expanded: false, - }); - - // Dynamically add bindings for each property in the options - for (const [key, value] of Object.entries(options)) { - if (["colors", "shapes"].includes(key)) { - continue; - } - if (key === "origin") { - folder.addBinding(options, key as keyof Options, { - x: { min: -0.5, max: 1 }, - y: { min: -0.5, max: 1, inverted: true }, - }); - continue; - } - - if (typeof value === "number") { - folder.addBinding(options, key as keyof Options, { - min: value > 1 ? 1 : 0, - max: value > 1 ? 500 : 1, - step: value > 1 ? 0.1 : 0.01, - }); - } else { - folder.addBinding(options, key as keyof Options); - } - } - - // Add Export Button - const exportBtn = folder.addButton({ title: "Copy to clipboard" }); - exportBtn.on("click", () => { - void navigator.clipboard.writeText(JSON.stringify(options, null, 2)); - }); - }; - - // Add folders with specific settings - addFolderWithExport("Realistic2", middle1); - addFolderWithExport("Realistic3", middle2); - addFolderWithExport("Common corner settings", commonCornerExplosionOpts); - addFolderWithExport("corner1", corner1); - addFolderWithExport("corner2", corner2); - addFolderWithExport("corner3", corner3); - addFolderWithExport("corner4", corner4); - }); // <-- Properly close the `.then()` block here -} - export const VictoryConfetti = () => { const canvasRef = useRef(null); const confetti = useRef(null); diff --git a/src/components/Wiki/Wiki.utils.ts b/src/components/Wiki/Wiki.utils.ts index 04da644..653906c 100644 --- a/src/components/Wiki/Wiki.utils.ts +++ b/src/components/Wiki/Wiki.utils.ts @@ -1,10 +1,9 @@ import { useCallback, useEffect } from "react"; -import { toast } from "react-hot-toast"; import { useI18nContext } from "../../i18n/i18n-react"; import { useGameStoreActions, useIsGameRunning } from "../../stores/GameStore"; import { useIsCtrlFEnabled } from "../../stores/SettingsStore"; +import { errorToast } from "../../utils/toast"; -const errorToast = (text: string) => toast.error(text, { position: "bottom-center" }); const isNotDev = process.env.NODE_ENV !== "development"; export const useNoCheating = () => { diff --git a/src/components/Wiki/WikiLogic.tsx b/src/components/Wiki/WikiLogic.tsx index a0af05e..012c31b 100644 --- a/src/components/Wiki/WikiLogic.tsx +++ b/src/components/Wiki/WikiLogic.tsx @@ -1,12 +1,10 @@ import { useNavigate } from "@tanstack/react-router"; import type { MouseEvent } from "react"; -import { toast } from "react-hot-toast"; import { useI18nContext } from "../../i18n/i18n-react"; import { useGameStoreActions } from "../../stores/GameStore"; +import { errorToast } from "../../utils/toast"; import { useStopwatchActions } from "../StopwatchContext"; -const errorToast = (text: string) => toast.error(text, { position: "bottom-center" }); - const IMAGE_EXT = [".jpg", ".jpeg", ".png", ".webp", ".avif", ".svg"]; const handleShowHideButton = (e: MouseEvent) => { diff --git a/src/components/WikiLanguageSelect.tsx b/src/components/WikiLanguageSelect.tsx index a3709e0..cff90f8 100644 --- a/src/components/WikiLanguageSelect.tsx +++ b/src/components/WikiLanguageSelect.tsx @@ -90,7 +90,7 @@ export const LANGUAGES: readonly WikiLanguage[] = [ { value: "arz", label: "مصرى", isoCode: "" }, { value: "pl", label: "Polski", isoCode: "pl" }, { value: "ja", label: "日本語", isoCode: "jp" }, - { value: "zh", label: "中国人", isoCode: "cn" }, + { value: "zh", label: "中文", isoCode: "zh" }, { value: "vi", label: "Tiếng Việt", isoCode: "vi" }, { value: "war", label: "Winaray", isoCode: "" }, { value: "uk", label: "українська", isoCode: "" }, @@ -119,7 +119,7 @@ export const LANGUAGES: readonly WikiLanguage[] = [ { label: "dansk", value: "da", isoCode: "" }, { label: "български", value: "bg", isoCode: "" }, { label: "Cymraeg", value: "cy", isoCode: "" }, - { label: "slovenčina", value: "sk ", isoCode: "" }, + { label: "slovenčina", value: "sk", isoCode: "" }, { label: "تۆرکجه", value: "azb", isoCode: "" }, { label: "eesti", value: "et", isoCode: "" }, { label: "қазақша", value: "kk", isoCode: "" }, @@ -142,7 +142,7 @@ export const LANGUAGES: readonly WikiLanguage[] = [ { label: "বাংলা", value: "bn", isoCode: "" }, { label: "македонски", value: "mk", isoCode: "" }, { label: "asturianu", value: "ast", isoCode: "" }, - { label: "粵語 zh", value: "yue", isoCode: "" }, + { label: "粵語", value: "yue", isoCode: "" }, { label: "Ladin", value: "lld", isoCode: "" }, { label: "latviešu", value: "lv", isoCode: "" }, { label: "тоҷикӣ", value: "tg", isoCode: "" }, @@ -189,7 +189,7 @@ export const LANGUAGES: readonly WikiLanguage[] = [ { label: "नेपाली", value: "ne", isoCode: "" }, { label: "ગુજરાતી", value: "gu", isoCode: "" }, { label: "ಕನ್ನಡ", value: "kn", isoCode: "" }, - { label: "Alemannisch", value: "als ", isoCode: "" }, + { label: "Alemannisch", value: "als", isoCode: "" }, { label: "interlingua", value: "ia", isoCode: "" }, { label: "Kotava", value: "avk", isoCode: "" }, { label: "Boarisch", value: "bar", isoCode: "" }, diff --git a/src/hooks/useStopwatch.ts b/src/hooks/useStopwatch.ts index d75ae0d..3719804 100644 --- a/src/hooks/useStopwatch.ts +++ b/src/hooks/useStopwatch.ts @@ -93,7 +93,7 @@ const useStopwatch = () => { return { min: pad(String(minutes), 2), sec: pad(String(seconds), 2), - ms: pad(newMs.toFixed(0), 3), + ms: pad(String(Math.min(999, Math.round(newMs))), 3), }; }, []); diff --git a/src/pages/Settings.tsx b/src/pages/Settings.tsx index 67ee70e..647cbeb 100644 --- a/src/pages/Settings.tsx +++ b/src/pages/Settings.tsx @@ -1,7 +1,6 @@ import { useIsFetching } from "@tanstack/react-query"; import { useNavigate } from "@tanstack/react-router"; import { type FormEvent, useState } from "react"; -import { toast } from "react-hot-toast"; import ArticleAutocomplete from "../components/ArticleAutocomplete/ArticleAutocomplete"; import ArticlePreview from "../components/ArticlePreview/ArticlePreview"; import RandomButton from "../components/RandomButton/RandomButton"; @@ -21,6 +20,7 @@ import type { Article } from "../stores/GameStore"; import { useEndingArticle, useGameStoreActions, useStartingArticle } from "../stores/GameStore"; import { useIsCtrlFEnabled, useSettingsStoreActions, useWikiLanguage } from "../stores/SettingsStore"; import { useStatsStoreActions } from "../stores/StatisticsStore"; +import { copyNotification } from "../utils/toast"; const Settings = () => { const { LL } = useI18nContext(); @@ -46,8 +46,6 @@ const Settings = () => { trackedStats: ["single_random_pressed", "multiple_random_pressed", "article_preview_pressed"], }); - const copyNotification = () => toast.success(LL["Copied to clipboard"](), { position: "top-center" }); - const startGameHandler = (e: FormEvent) => { e.preventDefault(); resetGame(); @@ -117,7 +115,7 @@ const Settings = () => { className="mt-4 w-fit border-b-[1px] border-b-transparent py-3 hover:border-b-primary-blue focus-visible:border-b-primary-blue" onClick={async () => { await navigator.clipboard.writeText(`${window.location.href}&lang=${wikiLang}`); - copyNotification(); + copyNotification(LL["Copied to clipboard"]()); }} > {LL["Share settings"]()} diff --git a/src/stores/GameStore.ts b/src/stores/GameStore.ts index 556dd67..6ddf442 100644 --- a/src/stores/GameStore.ts +++ b/src/stores/GameStore.ts @@ -158,7 +158,6 @@ export const useStartingArticle = () => useGameStore((state) => state.startingAr export const useEndingArticle = () => useGameStore((state) => state.endingArticle); export const useHistory = () => useGameStore((state) => state.history); export const useClicks = () => useGameStore((state) => (state.history.length > 1 ? state.history.length - 1 : 0)); -export const useCurrentArticle = () => useGameStore((state) => state.history.slice(-1)[0]?.title); export const useIsWin = () => useGameStore((state) => state.isWin); export const useCheatingAttempts = () => useGameStore((state) => state.cheatingAttempts); diff --git a/src/stores/StatisticsStore.ts b/src/stores/StatisticsStore.ts index d1064c7..3705283 100644 --- a/src/stores/StatisticsStore.ts +++ b/src/stores/StatisticsStore.ts @@ -2,7 +2,7 @@ import { produce } from "immer"; import { create } from "zustand"; import { createJSONStorage, devtools, persist } from "zustand/middleware"; import { immer } from "zustand/middleware/immer"; -import { ACHIEVEMENTS_LIST, type Achievement, achivementConditionCheckByIdMap } from "../achievements"; +import { ACHIEVEMENTS_LIST, type Achievement, achievementConditionCheckByIdMap } from "../achievements"; /* Data gets persisted in local storage @@ -160,7 +160,7 @@ export const useStatsStore = create()( const typedPersistedState = persistedState as PersistedStore; const unlockedAchievements = produce(currentState.achievements, (draftState) => { - typedPersistedState.achievements.forEach((storageAchievement) => { + (typedPersistedState?.achievements ?? []).forEach((storageAchievement) => { const completedAchievement = draftState.find( (draftAchievement) => draftAchievement.id === storageAchievement.id, ); @@ -186,7 +186,7 @@ export const useStatsStore = create()( export const checkAchievements = (achievements: readonly Achievement[]) => { return achievements.filter((achievement) => { - const conditionFn = achivementConditionCheckByIdMap[achievement.id]; + const conditionFn = achievementConditionCheckByIdMap[achievement.id]; return !achievement.unlocked && conditionFn(); }); }; diff --git a/src/types/typescript.ts b/src/types/typescript.ts deleted file mode 100644 index e015060..0000000 --- a/src/types/typescript.ts +++ /dev/null @@ -1 +0,0 @@ -export const nameof = (name: keyof T) => name; diff --git a/src/utils/toast.ts b/src/utils/toast.ts new file mode 100644 index 0000000..d4e6284 --- /dev/null +++ b/src/utils/toast.ts @@ -0,0 +1,4 @@ +import { toast } from "react-hot-toast"; + +export const errorToast = (text: string) => toast.error(text, { position: "bottom-center" }); +export const copyNotification = (text: string) => toast.success(text, { position: "top-center" }); \ No newline at end of file From 016e48bea938653d82eeedc3338a3200a4e87bbd Mon Sep 17 00:00:00 2001 From: unbound Date: Wed, 5 Aug 2026 12:34:30 +0200 Subject: [PATCH 2/2] Restore tweakpane confetti debug panel --- package.json | 2 ++ pnpm-lock.yaml | 16 +++++++++ src/components/VictoryConfetti.tsx | 55 ++++++++++++++++++++++++++++++ 3 files changed, 73 insertions(+) diff --git a/package.json b/package.json index e234db5..2b3b904 100644 --- a/package.json +++ b/package.json @@ -55,6 +55,7 @@ "@tanstack/router-cli": "^1.128.8", "@tanstack/router-plugin": "^1.128.8", "@total-typescript/ts-reset": "0.6.1", + "@tweakpane/core": "^2.0.5", "@types/canvas-confetti": "^1.9.0", "@types/dompurify": "3.0.5", "@types/node": "^22.16.0", @@ -80,6 +81,7 @@ "prettier-plugin-tailwindcss": "0.5.14", "rollup-plugin-visualizer": "5.12.0", "tailwindcss": "3.4.3", + "tweakpane": "^4.0.5", "type-fest": "4.18.3", "typescript": "5.9.0-beta", "vite": "6.3.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9cef91a..f3678b4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -114,6 +114,9 @@ importers: '@total-typescript/ts-reset': specifier: 0.6.1 version: 0.6.1 + '@tweakpane/core': + specifier: ^2.0.5 + version: 2.0.5 '@types/canvas-confetti': specifier: ^1.9.0 version: 1.9.0 @@ -189,6 +192,9 @@ importers: tailwindcss: specifier: 3.4.3 version: 3.4.3 + tweakpane: + specifier: ^4.0.5 + version: 4.0.5 type-fest: specifier: 4.18.3 version: 4.18.3 @@ -1648,6 +1654,9 @@ packages: '@total-typescript/ts-reset@0.6.1': resolution: {integrity: sha512-cka47fVSo6lfQDIATYqb/vO1nvFfbPw7uWLayIXIhGETj0wcOOlrlkobOMDNQOFr9QOafegUPq13V2+6vtD7yg==} + '@tweakpane/core@2.0.5': + resolution: {integrity: sha512-punBgD5rKCF5vcNo6BsSOXiDR/NSs9VM7SG65QSLJIxfRaGgj54ree9zQW6bO3pNFf3AogiGgaNODUVQRk9YqQ==} + '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -3667,6 +3676,9 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + tweakpane@4.0.5: + resolution: {integrity: sha512-rxEXdSI+ArlG1RyO6FghC4ZUX8JkEfz8F3v1JuteXSV0pEtHJzyo07fcDG+NsJfN5L39kSbCYbB9cBGHyuI/tQ==} + type-check@0.4.0: resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} engines: {node: '>= 0.8.0'} @@ -5526,6 +5538,8 @@ snapshots: '@total-typescript/ts-reset@0.6.1': {} + '@tweakpane/core@2.0.5': {} + '@types/aria-query@5.0.4': {} '@types/babel__core@7.20.5': @@ -7706,6 +7720,8 @@ snapshots: optionalDependencies: fsevents: 2.3.3 + tweakpane@4.0.5: {} + type-check@0.4.0: dependencies: prelude-ls: 1.2.1 diff --git a/src/components/VictoryConfetti.tsx b/src/components/VictoryConfetti.tsx index f38f524..33b1655 100644 --- a/src/components/VictoryConfetti.tsx +++ b/src/components/VictoryConfetti.tsx @@ -73,6 +73,61 @@ const corner4: Options = { origin: { x: 1, y: 1 }, }; +const SHOW_CONFETTI_DEBUG_PANEL = false; + +// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition +if (import.meta.env.MODE === "development" && SHOW_CONFETTI_DEBUG_PANEL) { + void import("tweakpane").then((TweakpaneModule) => { + const pane = new TweakpaneModule.Pane(); + + const addFolderWithExport = (title: string, options: Options) => { + const folder = pane.addFolder({ + title: title, + expanded: false, + }); + + // Dynamically add bindings for each property in the options + for (const [key, value] of Object.entries(options)) { + if (["colors", "shapes"].includes(key)) { + continue; + } + if (key === "origin") { + folder.addBinding(options, key as keyof Options, { + x: { min: -0.5, max: 1 }, + y: { min: -0.5, max: 1, inverted: true }, + }); + continue; + } + + if (typeof value === "number") { + folder.addBinding(options, key as keyof Options, { + min: value > 1 ? 1 : 0, + max: value > 1 ? 500 : 1, + step: value > 1 ? 0.1 : 0.01, + }); + } else { + folder.addBinding(options, key as keyof Options); + } + } + + // Add Export Button + const exportBtn = folder.addButton({ title: "Copy to clipboard" }); + exportBtn.on("click", () => { + void navigator.clipboard.writeText(JSON.stringify(options, null, 2)); + }); + }; + + // Add folders with specific settings + addFolderWithExport("Realistic2", middle1); + addFolderWithExport("Realistic3", middle2); + addFolderWithExport("Common corner settings", commonCornerExplosionOpts); + addFolderWithExport("corner1", corner1); + addFolderWithExport("corner2", corner2); + addFolderWithExport("corner3", corner3); + addFolderWithExport("corner4", corner4); + }); // <-- Properly close the `.then()` block here +} + export const VictoryConfetti = () => { const canvasRef = useRef(null); const confetti = useRef(null);