From f76a3a347ae01daf395a299afa1459fe2ffe67b0 Mon Sep 17 00:00:00 2001 From: Pongstr Date: Mon, 23 Dec 2024 13:42:31 +0200 Subject: [PATCH] fix: code review revisions --- src/keyboard/KeyboardViewport.tsx | 114 +++++++++++++----------------- src/misc/useLocalStorageState.ts | 42 +++++------ 2 files changed, 73 insertions(+), 83 deletions(-) diff --git a/src/keyboard/KeyboardViewport.tsx b/src/keyboard/KeyboardViewport.tsx index 458b308..bc4913b 100644 --- a/src/keyboard/KeyboardViewport.tsx +++ b/src/keyboard/KeyboardViewport.tsx @@ -1,45 +1,27 @@ import { FC, PropsWithChildren, useEffect, useRef } from "react"; +import { useLocalStorageState } from "../misc/useLocalStorageState"; +import { ExpandIcon, MaximizeIcon, ShrinkIcon } from "lucide-react"; type KeyboardViewportType = PropsWithChildren<{ className?: string; }>; -const KEYMAP_SCALE = "keymap:scale"; -const DEFAULT_SCALE = window.localStorage.getItem(KEYMAP_SCALE) ?? "1"; +const KEYMAP_SCALE = "keymapScale"; +const DEFAULT_SCALE = 1; export const KeyboardViewport: FC = ({ children, className, }) => { const targetRef = useRef(null); - const scaleRef = useRef(null); - const setScale = (param: "increase" | "decrease") => { - if (!targetRef.current || !scaleRef.current) return; - - const current = scaleRef.current.value; - - if (param === "increase" && Number(current) < 2) { - scaleRef.current.value = String(Number(scaleRef.current.value) + 0.2); - } - - if (param === "decrease" && Number(current) > 0.2) { - scaleRef.current.value = String(Number(scaleRef.current.value) - 0.2); - } - - localStorage.setItem(KEYMAP_SCALE, scaleRef.current.value); - targetRef.current.style.setProperty( - "transform", - `scale(${scaleRef.current.value})`, - ); - }; + const [scale, setScale] = useLocalStorageState(KEYMAP_SCALE, DEFAULT_SCALE); const resetScale = () => { - if (!targetRef.current || !scaleRef.current) return; + if (!targetRef.current) return; targetRef.current.style.translate = "unset"; targetRef.current.style.setProperty("transform", "scale(1)"); - scaleRef.current.value = "1"; - localStorage.setItem(KEYMAP_SCALE, "1"); + setScale(DEFAULT_SCALE); }; useEffect(() => { @@ -49,7 +31,7 @@ export const KeyboardViewport: FC = ({ const offset = { x: 0, y: 0 }; let isPanningActive = false; - function panStart(e: KeyboardEvent) { + function keyDownPanStart(e: KeyboardEvent) { if (e.key !== " ") return; e.preventDefault(); @@ -57,7 +39,15 @@ export const KeyboardViewport: FC = ({ isPanningActive = true; } - function panEnd(e: KeyboardEvent) { + function pointerDownPanStart(e: PointerEvent) { + if (e.button !== 0) return; + e.preventDefault(); + + target.style.cursor = "grab"; + isPanningActive = true; + } + + function keyUpPanEnd(e: KeyboardEvent) { if (e.key !== " ") return; isPanningActive = false; target.style.cursor = "unset"; @@ -70,35 +60,27 @@ export const KeyboardViewport: FC = ({ target.style.translate = `${offset.x}px ${offset.y}px`; } - document.addEventListener("keydown", panStart); - document.addEventListener("keyup", panEnd); - target.addEventListener("pointermove", panMove); - - return () => { - document.removeEventListener("keydown", panStart); - document.removeEventListener("keyup", panEnd); - target.removeEventListener("pointermove", panMove); - }; - }, []); - - useEffect(() => { - if (!scaleRef.current || !targetRef.current) return; - - const input = scaleRef.current; - const target = targetRef.current; + function pointerUpPanEnd() { + isPanningActive = false; + target.style.cursor = "unset"; + } - input.value = DEFAULT_SCALE; - target.style.setProperty("transform", `scale(${DEFAULT_SCALE})`); + document.addEventListener("keydown", keyDownPanStart); + document.addEventListener("keyup", keyUpPanEnd); - function onInputChange(e: Event) { - const value = (e.currentTarget as HTMLInputElement).value; - target.style.setProperty("transform", `scale(${value})`); - localStorage.setItem(KEYMAP_SCALE, value); - } + target.addEventListener("pointermove", panMove); + target.addEventListener("pointerdown", pointerDownPanStart); + target.addEventListener("pointerup", pointerUpPanEnd); + target.addEventListener("pointerleave", pointerUpPanEnd); - input.addEventListener("change", onInputChange); return () => { - input.removeEventListener("change", onInputChange); + document.removeEventListener("keydown", keyDownPanStart); + document.removeEventListener("keyup", keyUpPanEnd); + + target.removeEventListener("pointermove", panMove); + target.removeEventListener("pointerdown", pointerDownPanStart); + target.removeEventListener("pointerup", pointerUpPanEnd); + target.removeEventListener("pointerleave", pointerUpPanEnd); }; }, []); @@ -111,17 +93,20 @@ export const KeyboardViewport: FC = ({ >
{children}
-
+
= ({ min={0.25} max={2} step={0.01} - ref={scaleRef} - defaultValue={DEFAULT_SCALE} className="mx-auto h-1 w-28 cursor-pointer appearance-none rounded-lg" + value={scale} + onChange={(e) => setScale(Number(e.target.value))} />
diff --git a/src/misc/useLocalStorageState.ts b/src/misc/useLocalStorageState.ts index e066212..064be3c 100644 --- a/src/misc/useLocalStorageState.ts +++ b/src/misc/useLocalStorageState.ts @@ -1,10 +1,15 @@ import { useEffect, useState } from "react"; function basicSerialize(value: T): string { - if (typeof value === "object") { - return JSON.stringify(value); + return typeof value !== "string" ? JSON.stringify(value) : String(value); +} + +function toJson(value: string): T { + try { + return JSON.parse(value) as T; + } catch { + return value as T; } - return String(value); } export function useLocalStorageState( @@ -14,25 +19,22 @@ export function useLocalStorageState( serialize?: (value: T) => string; deserialize?: (value: string) => T; }, -) { - const reactState = useState(() => { - const savedValue = localStorage.getItem(key); - if (savedValue !== null) { - if (options?.deserialize) { - return options.deserialize(savedValue); - } - return savedValue as T; // Assuming T is a string - } - return defaultValue; - }); +): [T, React.Dispatch>] { + const [state, setState] = useState(() => { + const saved = localStorage.getItem(key); - const [state] = reactState; + if (saved === null) return defaultValue; + return ( + options?.deserialize?.(saved) ?? (toJson(saved) as T) ?? defaultValue + ); + }); useEffect(() => { - const serializedState = - options?.serialize?.(state) || basicSerialize(state); - localStorage.setItem(key, serializedState); - }, [state, key, options]); + localStorage.setItem( + key, + options?.serialize?.(state) ?? basicSerialize(state), + ); + }, [key, state, options]); - return reactState; + return [state, setState]; }