// 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; clearAllPositionDrafts: () => void; prunePositionDrafts: (validNodeIds: Set) => 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 = (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 }); }, });