From 52052529ffaceacd77a3ecdd45f45ae0dece8ed1 Mon Sep 17 00:00:00 2001 From: Sean Sun <1194458432@qq.com> Date: Fri, 24 Apr 2026 17:30:43 +0800 Subject: [PATCH] =?UTF-8?q?feat(image-editor):=20wire=20Hand=20tool=20?= =?UTF-8?q?=E2=80=94=20drag-to-pan=20when=20active?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Promotes the Hand (H) tool from stub to functional. When selected, the Workspace enters pan mode (drag-to-pan instead of drag-to-draw); behavior matches Space-hold pan, just modal instead of momentary. Implementation: panMode now derives from `spaceHeld || tool === 'hand'` inside ImageEditor.tsx. Workspace's existing drag-to-pan logic activates naturally. OptionsBar gets a Hand-specific hint variant. Co-Authored-By: Claude Opus 4.7 (1M context) --- src/components/image-editor/OptionsBar.tsx | 12 ++++++++++++ src/components/image-editor/ToolsPalette.tsx | 1 - src/components/image-editor/tool-meta.ts | 1 - src/i18n/en.json | 1 + src/i18n/zh-CN.json | 1 + src/pages/ImageEditor.tsx | 10 +++++++--- 6 files changed, 21 insertions(+), 5 deletions(-) diff --git a/src/components/image-editor/OptionsBar.tsx b/src/components/image-editor/OptionsBar.tsx index a22723d..152694e 100644 --- a/src/components/image-editor/OptionsBar.tsx +++ b/src/components/image-editor/OptionsBar.tsx @@ -171,6 +171,18 @@ export function OptionsBar({ ) } + if (tool === 'hand') { + return ( +
+
+ + {t('pages.imageEditor.handToolHint')} + +
+
+ ) + } + if (tool === 'text') { return (
diff --git a/src/components/image-editor/ToolsPalette.tsx b/src/components/image-editor/ToolsPalette.tsx index 9bb91a8..6e84716 100644 --- a/src/components/image-editor/ToolsPalette.tsx +++ b/src/components/image-editor/ToolsPalette.tsx @@ -160,7 +160,6 @@ const GROUPS: ToolDef[][] = [ icon: , labelKey: 'pages.imageEditor.tool.hand', shortcut: 'H', - stub: true, }, { id: 'zoom', icon: , labelKey: 'pages.imageEditor.tool.zoom', shortcut: 'Z' }, ], diff --git a/src/components/image-editor/tool-meta.ts b/src/components/image-editor/tool-meta.ts index 8d865c0..172b3c0 100644 --- a/src/components/image-editor/tool-meta.ts +++ b/src/components/image-editor/tool-meta.ts @@ -21,7 +21,6 @@ export const STUB_TOOLS: ReadonlySet = new Set([ 'dodge', 'pen', 'arrowPath', - 'hand', 'rotateView', 'frame', 'note', diff --git a/src/i18n/en.json b/src/i18n/en.json index c1ad936..cb77c6a 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -521,6 +521,7 @@ "moveToolHint": "Move (V): drag layers; Cmd/Ctrl+J duplicate; Delete remove.", "zoomToolHint": "Zoom (Z): click to zoom in 2×, Alt-click to zoom out. Cmd/Ctrl+wheel zooms at cursor.", "eyedropperHint": "Eyedropper (I): click anywhere on the canvas to set the foreground color.", + "handToolHint": "Hand (H): drag the canvas to pan. Hold Space to pan with any other tool.", "textToolHint": "Type (T): click on the canvas to add text.", "toolStubHint": "{{tool}} is a placeholder — the palette button is here for parity with Photoshop, but the tool isn't implemented yet.", "toolStubToast": "{{tool}} is not yet implemented.", diff --git a/src/i18n/zh-CN.json b/src/i18n/zh-CN.json index 30ff37d..2e7dda9 100644 --- a/src/i18n/zh-CN.json +++ b/src/i18n/zh-CN.json @@ -521,6 +521,7 @@ "moveToolHint": "移动 (V):拖动图层;Cmd/Ctrl+J 复制;Delete 删除。", "zoomToolHint": "缩放 (Z):单击放大 2×,Alt 单击缩小。Cmd/Ctrl+滚轮在光标位置缩放。", "eyedropperHint": "吸管 (I):在画布上单击以拾取前景色。", + "handToolHint": "抓手 (H):拖动画布以平移。任何工具下按住空格也可临时平移。", "textToolHint": "文字 (T):在画布上单击以新增文字。", "toolStubHint": "{{tool}} 仅作占位 —— 工具栏按钮是为了对齐 Photoshop 而保留,但功能尚未实现。", "toolStubToast": "{{tool}} 暂未实现。", diff --git a/src/pages/ImageEditor.tsx b/src/pages/ImageEditor.tsx index 86e93fc..7c6833c 100644 --- a/src/pages/ImageEditor.tsx +++ b/src/pages/ImageEditor.tsx @@ -89,10 +89,14 @@ export function ImageEditorPage() { const moveLayerRef = useRef<(d: 'forward' | 'backward' | 'front' | 'back') => void>(() => {}) const deleteLayerRef = useRef<() => void>(() => {}) - // Zoom + pan + Space-held pan mode. + // Zoom + pan. Workspace enters pan mode when the user holds Space OR when + // the Hand tool is active — both treat the canvas as drag-to-pan instead + // of drag-to-draw. const [zoom, setZoom] = useState(1) const [pan, setPan] = useState({ x: 0, y: 0 }) - const [panMode, setPanMode] = useState(false) + const [spaceHeld, setSpaceHeld] = useState(false) + const setPanMode = setSpaceHeld + const panMode = spaceHeld || tool === 'hand' const ZOOM_MIN = 0.1 const ZOOM_MAX = 8 @@ -229,7 +233,7 @@ export function ImageEditorPage() { window.removeEventListener('keydown', onKeyDown) window.removeEventListener('keyup', onKeyUp) } - }, [focused, zoomIn, zoomOut, zoomReset, swapColors, resetColors, selectedLayerId, trySetTool]) + }, [focused, zoomIn, zoomOut, zoomReset, swapColors, resetColors, selectedLayerId, trySetTool, setPanMode]) // ── Layer state helpers ────────────────────────────────────────────────── const setLayers = useCallback(