From a633eff7883f6ad26909cc1eab2a66fc67c47cce Mon Sep 17 00:00:00 2001 From: SnapOtter Date: Thu, 7 May 2026 20:21:46 +0800 Subject: [PATCH] fix: wire export shortcut, text tool canvas integration, and undo rendering 1. Export dialog (Ctrl+Shift+S): replaced react-hotkeys-hook handler with a capture-phase keydown listener on window so the browser's native "Save Page As" dialog is intercepted before it can fire. 2. Text tool: created useTextTool hook that spawns an inline textarea overlay on canvas click, commits the text as a Konva Text object on blur/Enter, and wired it into the useActiveToolHandlers dispatcher. 3. Undo (Ctrl+Z): removed the 500ms debounce from zundo's handleSet. The debounce caused a race where calling undo before the timer fired would discard the future-states stack, making undo appear to do nothing. The equality function (keyed on _historyVersion) already prevents intermediate states from being recorded, so the debounce was redundant. --- .../src/components/editor/editor-canvas.tsx | 5 +- .../src/components/editor/tools/text-tool.tsx | 183 ++++++++++++++++++ apps/web/src/hooks/use-editor-shortcuts.ts | 25 ++- apps/web/src/stores/editor-store.ts | 13 +- 4 files changed, 209 insertions(+), 17 deletions(-) create mode 100644 apps/web/src/components/editor/tools/text-tool.tsx diff --git a/apps/web/src/components/editor/editor-canvas.tsx b/apps/web/src/components/editor/editor-canvas.tsx index 3ffaa8a1..1b893bc9 100644 --- a/apps/web/src/components/editor/editor-canvas.tsx +++ b/apps/web/src/components/editor/editor-canvas.tsx @@ -30,6 +30,7 @@ import { useFillTool } from "./tools/fill-tool"; import { useGradientTool } from "./tools/gradient-tool"; import { MoveToolTransformer, useMoveTool } from "./tools/move-tool"; import { useShapeTool } from "./tools/shape-tool"; +import { useTextTool } from "./tools/text-tool"; // Module-level stage ref for export dialog access (Issue #6) export const editorStageRefHolder: { current: Konva.Stage | null } = { @@ -390,6 +391,7 @@ function useActiveToolHandlers(stageRef: React.RefObject) { const brushTool = useBrushTool(); const eraserTool = useEraserTool(); const shapeTool = useShapeTool(); + const textTool = useTextTool(); const fillTool = useFillTool(stageRef); const gradientTool = useGradientTool(); const moveTool = useMoveTool(); @@ -412,12 +414,13 @@ function useActiveToolHandlers(stageRef: React.RefObject) { "shape-arrow": shapeTool, "shape-polygon": shapeTool, "shape-star": shapeTool, + text: textTool, fill: fillTool, gradient: gradientTool, }; return toolMap[activeTool] ?? null; - }, [activeTool, brushTool, eraserTool, shapeTool, fillTool, gradientTool]); + }, [activeTool, brushTool, eraserTool, shapeTool, textTool, fillTool, gradientTool]); return { handlers, moveTool }; } diff --git a/apps/web/src/components/editor/tools/text-tool.tsx b/apps/web/src/components/editor/tools/text-tool.tsx new file mode 100644 index 00000000..72e1f2c8 --- /dev/null +++ b/apps/web/src/components/editor/tools/text-tool.tsx @@ -0,0 +1,183 @@ +// apps/web/src/components/editor/tools/text-tool.tsx + +import type Konva from "konva"; +import { useCallback, useEffect, useRef } from "react"; +import { generateId } from "@/lib/utils"; +import { useEditorStore } from "@/stores/editor-store"; +import type { CanvasObject, TextAttrs } from "@/types/editor"; + +/** + * Text tool hook. Clicking the canvas with the text tool active creates a + * temporary