From 567786215ec51ff8faacc4f763975cc1463178c2 Mon Sep 17 00:00:00 2001 From: "tao.chen" <93983997+taochen-ct@users.noreply.github.com> Date: Fri, 7 Aug 2026 20:40:41 +0800 Subject: [PATCH] refactor: SchedulePage.tsx --- .../app/features/schedules/SchedulePage.tsx | 115 ++---------------- 1 file changed, 8 insertions(+), 107 deletions(-) diff --git a/frontend/app/features/schedules/SchedulePage.tsx b/frontend/app/features/schedules/SchedulePage.tsx index 9c5b244..2a53868 100644 --- a/frontend/app/features/schedules/SchedulePage.tsx +++ b/frontend/app/features/schedules/SchedulePage.tsx @@ -2,7 +2,6 @@ import { type DragEvent, type FormEvent, type MouseEvent as ReactMouseEvent, - type PointerEvent as ReactPointerEvent, useEffect, useMemo, useRef, @@ -24,6 +23,8 @@ import { ScheduleInspector } from "./ScheduleInspector"; import { ScheduleList } from "./ScheduleList"; import { ArtifactList } from "./ArtifactList"; import { ScheduleCanvasHeader } from "./ScheduleCanvasHeader"; +import { useCanvasNodeDrag } from "./hooks/useCanvasNodeDrag"; +import { useContextMenuDismiss } from "./hooks/useContextMenuDismiss"; import { EMPTY_NODE_FORM, type ScheduleContextMenu, @@ -37,16 +38,6 @@ type Notice = { message: string; }; -type DragState = { - nodeId: string; - pointerId: number; - startClientX: number; - startClientY: number; - originX: number; - originY: number; - moved: boolean; -}; - type ScheduleContextMenuTarget = | { kind: "schedule-list" } | { kind: "schedule"; schedule: Schedule } @@ -110,7 +101,6 @@ export default function SchedulePage({ const runsLoading = useSchedulesStore((s) => s.runsLoading); const setRunsLoading = useSchedulesStore((s) => s.setRunsLoading); const canvasRef = useRef(null); - const dragRef = useRef(null); const positionDraftCount = useSchedulesStore((state) => state.positionDraftCount); const selectedNode = schedule?.nodes.find( @@ -128,26 +118,7 @@ export default function SchedulePage({ setNodeForm(selectedNode ? nodeToForm(selectedNode) : EMPTY_NODE_FORM); }, [selectedNode?.node_id, selectedNode?.updated_at]); - useEffect(() => { - if (!contextMenu) return undefined; - const close = (): void => setContextMenu(null); - const onKeyDown = (event: KeyboardEvent): void => { - if (event.key === "Escape") close(); - }; - window.addEventListener("pointerdown", close); - window.addEventListener("blur", close); - window.addEventListener("resize", close); - window.addEventListener("scroll", close, true); - window.addEventListener("keydown", onKeyDown); - return () => { - window.removeEventListener("pointerdown", close); - window.removeEventListener("blur", close); - window.removeEventListener("resize", close); - window.removeEventListener("scroll", close, true); - window.removeEventListener("keydown", onKeyDown); - }; - }, [contextMenu]); - + useContextMenuDismiss(); const openContextMenu = ( event: ReactMouseEvent, @@ -204,77 +175,7 @@ export default function SchedulePage({ void useSchedulesStore.getState().addArtifactAt(artifact, positionX, positionY); }; - const startNodeDrag = ( - event: ReactPointerEvent, - node: ScheduleNode, - ): void => { - if (event.button !== 0 || busy || linkSourceId) return; - const target = event.target as HTMLElement; - if (target.closest("button")) return; - event.currentTarget.setPointerCapture(event.pointerId); - dragRef.current = { - nodeId: node.node_id, - pointerId: event.pointerId, - startClientX: event.clientX, - startClientY: event.clientY, - originX: node.position_x, - originY: node.position_y, - moved: false, - }; - setSelectedNodeId(node.node_id); - setSelectedEdgeId(null); - }; - - const moveNode = ( - event: ReactPointerEvent, - ): void => { - const drag = dragRef.current; - if (!drag || drag.pointerId !== event.pointerId) return; - const deltaX = event.clientX - drag.startClientX; - const deltaY = event.clientY - drag.startClientY; - if (Math.abs(deltaX) + Math.abs(deltaY) > 3) drag.moved = true; - setSchedule((current) => { - if (!current) return current; - return { - ...current, - nodes: current.nodes.map((item) => ( - item.node_id === drag.nodeId - ? { - ...item, - position_x: Math.max(10, drag.originX + deltaX), - position_y: Math.max(10, drag.originY + deltaY), - } - : item - )), - }; - }); - }; - - const finishNodeDrag = ( - event: ReactPointerEvent, - ): void => { - const drag = dragRef.current; - if (!drag || drag.pointerId !== event.pointerId) return; - dragRef.current = null; - if (!drag.moved) return; - const current = useSchedulesStore.getState().schedule; - const node = current?.nodes.find((item) => item.node_id === drag.nodeId); - if (!current || !node) return; - const position = { - position_x: Math.round(node.position_x), - position_y: Math.round(node.position_y), - }; - useSchedulesStore.getState().setPositionDraft(node.node_id, position); - setSchedule((value) => { - if (!value) return value; - return { - ...value, - nodes: value.nodes.map((item) => ( - item.node_id === node.node_id ? { ...item, ...position } : item - )), - }; - }); - }; + const { startDrag, moveDrag, finishDrag } = useCanvasNodeDrag({ busy, linkSourceId }); const filteredSchedules = useMemo(() => { const keyword = scheduleKeyword.trim().toLowerCase(); @@ -466,10 +367,10 @@ export default function SchedulePage({ width: NODE_WIDTH, height: NODE_HEIGHT, }} - onPointerDown={(event) => startNodeDrag(event, node)} - onPointerMove={moveNode} - onPointerUp={finishNodeDrag} - onPointerCancel={finishNodeDrag} + onPointerDown={(event) => startDrag(event, node)} + onPointerMove={moveDrag} + onPointerUp={finishDrag} + onPointerCancel={finishDrag} onContextMenu={(event) => openContextMenu(event, { kind: "node", node,