refactor: SchedulePage.tsx

This commit is contained in:
tao.chen
2026-08-07 20:40:41 +08:00
parent 084de5b2d6
commit 567786215e
@@ -2,7 +2,6 @@ import {
type DragEvent, type DragEvent,
type FormEvent, type FormEvent,
type MouseEvent as ReactMouseEvent, type MouseEvent as ReactMouseEvent,
type PointerEvent as ReactPointerEvent,
useEffect, useEffect,
useMemo, useMemo,
useRef, useRef,
@@ -24,6 +23,8 @@ import { ScheduleInspector } from "./ScheduleInspector";
import { ScheduleList } from "./ScheduleList"; import { ScheduleList } from "./ScheduleList";
import { ArtifactList } from "./ArtifactList"; import { ArtifactList } from "./ArtifactList";
import { ScheduleCanvasHeader } from "./ScheduleCanvasHeader"; import { ScheduleCanvasHeader } from "./ScheduleCanvasHeader";
import { useCanvasNodeDrag } from "./hooks/useCanvasNodeDrag";
import { useContextMenuDismiss } from "./hooks/useContextMenuDismiss";
import { import {
EMPTY_NODE_FORM, EMPTY_NODE_FORM,
type ScheduleContextMenu, type ScheduleContextMenu,
@@ -37,16 +38,6 @@ type Notice = {
message: string; message: string;
}; };
type DragState = {
nodeId: string;
pointerId: number;
startClientX: number;
startClientY: number;
originX: number;
originY: number;
moved: boolean;
};
type ScheduleContextMenuTarget = type ScheduleContextMenuTarget =
| { kind: "schedule-list" } | { kind: "schedule-list" }
| { kind: "schedule"; schedule: Schedule } | { kind: "schedule"; schedule: Schedule }
@@ -110,7 +101,6 @@ export default function SchedulePage({
const runsLoading = useSchedulesStore((s) => s.runsLoading); const runsLoading = useSchedulesStore((s) => s.runsLoading);
const setRunsLoading = useSchedulesStore((s) => s.setRunsLoading); const setRunsLoading = useSchedulesStore((s) => s.setRunsLoading);
const canvasRef = useRef<HTMLDivElement | null>(null); const canvasRef = useRef<HTMLDivElement | null>(null);
const dragRef = useRef<DragState | null>(null);
const positionDraftCount = useSchedulesStore((state) => state.positionDraftCount); const positionDraftCount = useSchedulesStore((state) => state.positionDraftCount);
const selectedNode = schedule?.nodes.find( const selectedNode = schedule?.nodes.find(
@@ -128,26 +118,7 @@ export default function SchedulePage({
setNodeForm(selectedNode ? nodeToForm(selectedNode) : EMPTY_NODE_FORM); setNodeForm(selectedNode ? nodeToForm(selectedNode) : EMPTY_NODE_FORM);
}, [selectedNode?.node_id, selectedNode?.updated_at]); }, [selectedNode?.node_id, selectedNode?.updated_at]);
useEffect(() => { useContextMenuDismiss();
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]);
const openContextMenu = ( const openContextMenu = (
event: ReactMouseEvent<HTMLElement | SVGElement>, event: ReactMouseEvent<HTMLElement | SVGElement>,
@@ -204,77 +175,7 @@ export default function SchedulePage({
void useSchedulesStore.getState().addArtifactAt(artifact, positionX, positionY); void useSchedulesStore.getState().addArtifactAt(artifact, positionX, positionY);
}; };
const startNodeDrag = ( const { startDrag, moveDrag, finishDrag } = useCanvasNodeDrag({ busy, linkSourceId });
event: ReactPointerEvent<HTMLDivElement>,
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<HTMLDivElement>,
): 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<HTMLDivElement>,
): 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 filteredSchedules = useMemo(() => { const filteredSchedules = useMemo(() => {
const keyword = scheduleKeyword.trim().toLowerCase(); const keyword = scheduleKeyword.trim().toLowerCase();
@@ -466,10 +367,10 @@ export default function SchedulePage({
width: NODE_WIDTH, width: NODE_WIDTH,
height: NODE_HEIGHT, height: NODE_HEIGHT,
}} }}
onPointerDown={(event) => startNodeDrag(event, node)} onPointerDown={(event) => startDrag(event, node)}
onPointerMove={moveNode} onPointerMove={moveDrag}
onPointerUp={finishNodeDrag} onPointerUp={finishDrag}
onPointerCancel={finishNodeDrag} onPointerCancel={finishDrag}
onContextMenu={(event) => openContextMenu(event, { onContextMenu={(event) => openContextMenu(event, {
kind: "node", kind: "node",
node, node,