Files
model-platform/frontend/app/features/schedules/state/canvasSlice.ts
T

109 lines
3.5 KiB
TypeScript

// Canvas slice: selection, edit forms, context menu, left-panel filter inputs,
// position-draft reactive mirror.
import type { StateCreator } from "zustand";
import type { NodePositionDraft, ScheduleContextMenu, ScheduleForm, NodeForm } from "./types";
import { positionDrafts, clearPositionDrafts } from "./helpers";
import type { SchedulesStore } from "./useSchedulesStore";
// ---- State ----
export type CanvasSliceState = {
selectedNodeId: string | null;
selectedEdgeId: string | null;
linkSourceId: string | null;
scheduleKeyword: string;
artifactKeyword: string;
scheduleForm: ScheduleForm;
nodeForm: NodeForm;
contextMenu: ScheduleContextMenu | null;
positionDraftCount: number;
};
// ---- Actions ----
export type CanvasSliceActions = {
// pure setters
setSelectedNodeId: (id: string | null) => void;
setSelectedEdgeId: (id: string | null) => void;
setLinkSourceId: (id: string | null) => void;
setScheduleKeyword: (k: string) => void;
setArtifactKeyword: (k: string) => void;
setScheduleForm: (form: ScheduleForm) => void;
setNodeForm: (form: NodeForm) => void;
setContextMenu: (menu: ScheduleContextMenu | null) => void;
closeContextMenu: () => void;
// drag helpers
setPositionDraft: (nodeId: string, draft: NodePositionDraft) => void;
setPositionDraftCount: (count: number) => void;
getPositionDrafts: () => Map<string, NodePositionDraft>;
clearAllPositionDrafts: () => void;
prunePositionDrafts: (validNodeIds: Set<string>) => void;
};
// `SchedulesStore` is the combined state+actions of all slices, so `get()`
// returns a fully typed snapshot — no `any` propagation.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
export const createCanvasSlice: StateCreator<SchedulesStore, [], [], CanvasSliceState & CanvasSliceActions> = (set, get) => ({
// ---- initial state ----
selectedNodeId: null,
selectedEdgeId: null,
linkSourceId: null,
scheduleKeyword: "",
artifactKeyword: "",
scheduleForm: {
scheduleName: "",
description: "",
triggerType: "manual",
cronExpression: "",
timezone: "Asia/Shanghai",
enabled: false,
maxConcurrency: "1",
failurePolicy: "stop",
},
nodeForm: {
nodeName: "",
timeoutSeconds: "600",
retryCount: "0",
retryIntervalSec: "5",
argumentsJson: "{}",
envRefsJson: "{}",
pythonVersion: "3.12",
},
contextMenu: null,
positionDraftCount: 0,
// ---- pure setters ----
setSelectedNodeId: (id) => set({ selectedNodeId: id }),
setSelectedEdgeId: (id) => set({ selectedEdgeId: id }),
setLinkSourceId: (id) => set({ linkSourceId: id }),
setScheduleKeyword: (k) => set({ scheduleKeyword: k }),
setArtifactKeyword: (k) => set({ artifactKeyword: k }),
setScheduleForm: (form) => set({ scheduleForm: form }),
setNodeForm: (form) => set({ nodeForm: form }),
setContextMenu: (menu) => set({ contextMenu: menu }),
closeContextMenu: () => set({ contextMenu: null }),
// ---- drag helpers ----
setPositionDraft: (nodeId, draft) => {
positionDrafts.set(nodeId, draft);
set({ positionDraftCount: positionDrafts.size });
},
setPositionDraftCount: (count) => set({ positionDraftCount: count }),
getPositionDrafts: () => positionDrafts,
clearAllPositionDrafts: () => {
clearPositionDrafts();
set({ positionDraftCount: 0 });
},
prunePositionDrafts: (validNodeIds) => {
for (const nodeId of [...positionDrafts.keys()]) {
if (!validNodeIds.has(nodeId)) positionDrafts.delete(nodeId);
}
set({ positionDraftCount: positionDrafts.size });
},
});