2f398ae0b3
- 제어모드 IDE: ControlCardPanel, control/ide/* (Canvas/LeftRail/RightRail/PanZoomStage/V3RuleNode 등), schemas, lib/api/control - 레지스트리 정리: aggregation-widget, status-count, section-card/paper, table-list(legacy/v2), tabs-widget 폐기 → table/_shared/ 로 통합 - InvLegacyButtonConfigPanel cp 마이그레이션 - canonical data view cleanup 후속 노트
187 lines
7.2 KiB
TypeScript
187 lines
7.2 KiB
TypeScript
import { create } from 'zustand';
|
|
import { devtools } from 'zustand/middleware';
|
|
|
|
/**
|
|
* 제어 노드 16종 정의 (mockup CTRL_NODE_TYPES)
|
|
* out: 커스텀 출력 포트. 없으면 기본 [{port:'out', label:'→'}]
|
|
*/
|
|
export const CTRL_NODE_TYPES: Record<string, {
|
|
icon: string; label: string; rgb: string; cat: string;
|
|
out?: { port: string; label: string; cls: string }[];
|
|
}> = {
|
|
'timer': { icon: '⏱', label: '타이머', rgb: '0,206,201', cat: '트리거' },
|
|
'condition': { icon: '◇', label: '조건분기', rgb: '253,203,110', cat: '조건',
|
|
out: [{ port: 'yes', label: 'Y', cls: 'port-yes' }, { port: 'no', label: 'N', cls: 'port-no' }] },
|
|
'validation': { icon: '✔', label: '데이터 검증', rgb: '255,107,129', cat: '조건',
|
|
out: [{ port: 'pass', label: '✓', cls: 'port-yes' }, { port: 'fail', label: '✗', cls: 'port-no' }] },
|
|
'status-change': { icon: '🔄', label: '상태 변경', rgb: '108,92,231', cat: '액션' },
|
|
'auto-insert': { icon: '📝', label: '자동 등록', rgb: '85,239,196', cat: '액션' },
|
|
'calculation': { icon: '🧮', label: '계산/수식', rgb: '45,152,218', cat: '액션' },
|
|
'delete': { icon: '🗑', label: '삭제/보관', rgb: '255,71,87', cat: '액션' },
|
|
'document': { icon: '📄', label: '문서 생성', rgb: '162,155,254', cat: '액션' },
|
|
'approval': { icon: '✋', label: '승인/결재', rgb: '255,165,2', cat: '흐름',
|
|
out: [{ port: 'approved', label: '✓', cls: 'port-yes' }, { port: 'rejected', label: '✗', cls: 'port-no' }] },
|
|
'delay': { icon: '⏳', label: '대기/지연', rgb: '72,219,251', cat: '흐름' },
|
|
'loop': { icon: '🔁', label: '반복', rgb: '223,142,254', cat: '흐름',
|
|
out: [{ port: 'each', label: '→', cls: '' }, { port: 'done', label: '✓', cls: 'port-yes' }] },
|
|
'parallel': { icon: '🔀', label: '병렬 실행', rgb: '0,206,201', cat: '흐름' },
|
|
'merge': { icon: '⤵', label: '병합/합류', rgb: '149,175,192', cat: '흐름' },
|
|
'webhook': { icon: '🌐', label: '외부 호출', rgb: '116,185,255', cat: '연동' },
|
|
'notification': { icon: '📨', label: '알림 발송', rgb: '253,121,168', cat: '연동' },
|
|
'log': { icon: '📜', label: '로그 기록', rgb: '150,150,160', cat: '기록' },
|
|
};
|
|
|
|
interface ControlModeState {
|
|
/** 제어 모드 활성 여부 */
|
|
active: boolean;
|
|
/** 읽기 / 편집 / 실행 / 이력 모드 (선택된 카드 컨텍스트 안의 토글, v3 — IDE 4-segmented tabs) */
|
|
mode: 'view' | 'edit' | 'run' | 'history';
|
|
/** 선택된 카드 ID — 카드 클릭 시 좌측 축소 + 그 옆에 제어 패널 */
|
|
selectedCardId: string | null;
|
|
/** 활성 흐름 — FlowViewer 내부 상태 (selectedCardId 와 동기화) */
|
|
activeFlowCardId: string | null;
|
|
/** 흐름 엣지 배열 (BFS 결과) */
|
|
flowEdges: Record<string, any>[];
|
|
/** 테이블 노드 위치 */
|
|
tablePositions: Record<string, { x: number; y: number }>;
|
|
|
|
/** 규칙 빌더 — 노드 */
|
|
ruleNodes: Record<string, any>[];
|
|
/** 규칙 빌더 — 연결 */
|
|
ruleConnections: Record<string, any>[];
|
|
/** 현재 편집 중인 룰 ID */
|
|
activeRuleId: string | null;
|
|
|
|
/** 설정 팝오버 대상 노드 ID */
|
|
configNodeId: string | null;
|
|
|
|
// 액션
|
|
toggleControlMode: () => void;
|
|
setMode: (mode: 'view' | 'edit' | 'run' | 'history') => void;
|
|
setSelectedCardId: (cardId: string | null) => void;
|
|
setActiveFlowCard: (cardId: string | null) => void;
|
|
setFlowEdges: (edges: Record<string, any>[]) => void;
|
|
setTablePositions: (pos: Record<string, { x: number; y: number }>) => void;
|
|
setRuleNodes: (nodes: Record<string, any>[]) => void;
|
|
addRuleNode: (node: Record<string, any>) => void;
|
|
updateRuleNode: (nodeId: string, updates: Record<string, any>) => void;
|
|
removeRuleNode: (nodeId: string) => void;
|
|
moveRuleNode: (nodeId: string, x: number, y: number) => void;
|
|
setRuleConnections: (conns: Record<string, any>[]) => void;
|
|
addRuleConnection: (conn: Record<string, any>) => void;
|
|
removeRuleConnection: (connId: string) => void;
|
|
setActiveRuleId: (ruleId: string | null) => void;
|
|
setConfigNodeId: (nodeId: string | null) => void;
|
|
resetControlMode: () => void;
|
|
}
|
|
|
|
let _nodeSeq = 0;
|
|
export function genNodeId(prefix: string) { return `${prefix}-${++_nodeSeq}`; }
|
|
let _connSeq = 0;
|
|
export function genConnId() { return `conn-${++_connSeq}`; }
|
|
|
|
export const useControlMode = create<ControlModeState>()(
|
|
devtools(
|
|
(set) => ({
|
|
active: false,
|
|
mode: 'view',
|
|
selectedCardId: null,
|
|
activeFlowCardId: null,
|
|
flowEdges: [],
|
|
tablePositions: {},
|
|
ruleNodes: [],
|
|
ruleConnections: [],
|
|
activeRuleId: null,
|
|
configNodeId: null,
|
|
|
|
toggleControlMode: () =>
|
|
set((s) => ({
|
|
active: !s.active,
|
|
mode: 'view',
|
|
selectedCardId: null,
|
|
activeFlowCardId: null,
|
|
flowEdges: [],
|
|
tablePositions: {},
|
|
ruleNodes: [],
|
|
ruleConnections: [],
|
|
configNodeId: null,
|
|
})),
|
|
|
|
setMode: (mode) => set({ mode, configNodeId: null }),
|
|
|
|
setSelectedCardId: (cardId) =>
|
|
set({
|
|
selectedCardId: cardId,
|
|
// 카드 바꾸면 모드/룰 초기화 (각 카드는 자기 제어 컨텍스트)
|
|
mode: 'view',
|
|
activeFlowCardId: cardId,
|
|
ruleNodes: [],
|
|
ruleConnections: [],
|
|
activeRuleId: null,
|
|
configNodeId: null,
|
|
}),
|
|
|
|
setActiveFlowCard: (cardId) => set({ activeFlowCardId: cardId }),
|
|
|
|
setFlowEdges: (edges) => set({ flowEdges: edges }),
|
|
|
|
setTablePositions: (pos) => set({ tablePositions: pos }),
|
|
|
|
setRuleNodes: (nodes) => set({ ruleNodes: nodes }),
|
|
|
|
addRuleNode: (node) => set((s) => ({ ruleNodes: [...s.ruleNodes, node] })),
|
|
|
|
updateRuleNode: (nodeId, updates) =>
|
|
set((s) => ({
|
|
ruleNodes: s.ruleNodes.map((n) =>
|
|
n.id === nodeId ? { ...n, ...updates } : n
|
|
),
|
|
})),
|
|
|
|
removeRuleNode: (nodeId) =>
|
|
set((s) => ({
|
|
ruleNodes: s.ruleNodes.filter((n) => n.id !== nodeId),
|
|
ruleConnections: s.ruleConnections.filter(
|
|
(c) => c.from_node_id !== nodeId && c.to_node_id !== nodeId
|
|
),
|
|
})),
|
|
|
|
moveRuleNode: (nodeId, x, y) =>
|
|
set((s) => ({
|
|
ruleNodes: s.ruleNodes.map((n) =>
|
|
n.id === nodeId ? { ...n, x, y } : n
|
|
),
|
|
})),
|
|
|
|
setRuleConnections: (conns) => set({ ruleConnections: conns }),
|
|
|
|
addRuleConnection: (conn) =>
|
|
set((s) => ({ ruleConnections: [...s.ruleConnections, conn] })),
|
|
|
|
removeRuleConnection: (connId) =>
|
|
set((s) => ({
|
|
ruleConnections: s.ruleConnections.filter((c) => c.id !== connId),
|
|
})),
|
|
|
|
setActiveRuleId: (ruleId) => set({ activeRuleId: ruleId }),
|
|
|
|
setConfigNodeId: (nodeId) => set({ configNodeId: nodeId }),
|
|
|
|
resetControlMode: () =>
|
|
set({
|
|
active: false,
|
|
mode: 'view',
|
|
selectedCardId: null,
|
|
activeFlowCardId: null,
|
|
flowEdges: [],
|
|
tablePositions: {},
|
|
ruleNodes: [],
|
|
ruleConnections: [],
|
|
activeRuleId: null,
|
|
configNodeId: null,
|
|
}),
|
|
}),
|
|
{ name: 'control-mode-store' }
|
|
)
|
|
);
|