refactor: SchedulePage.tsx
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user