dishui 0.0.67 → 0.0.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/calendar.js +5 -0
  2. package/dist/components/Demo/index.js +76 -80
  3. package/dist/components/Demo/index.js.map +1 -1
  4. package/dist/components/pro/Calendar/Calendar.js +576 -0
  5. package/dist/components/pro/Calendar/Calendar.js.map +1 -0
  6. package/dist/components/pro/Calendar/hooks/useCalendarDrag.js +104 -0
  7. package/dist/components/pro/Calendar/hooks/useCalendarDrag.js.map +1 -0
  8. package/dist/components/pro/Calendar/hooks/useCalendarState.js +187 -0
  9. package/dist/components/pro/Calendar/hooks/useCalendarState.js.map +1 -0
  10. package/dist/components/pro/Calendar/utils.js +193 -0
  11. package/dist/components/pro/Calendar/utils.js.map +1 -0
  12. package/dist/components/pro/Flow/Flow.js +21 -17
  13. package/dist/components/pro/Flow/Flow.js.map +1 -1
  14. package/dist/components/pro/Flow/hooks/useClipboard.js +29 -18
  15. package/dist/components/pro/Flow/hooks/useClipboard.js.map +1 -1
  16. package/dist/components/pro/Flow/hooks/useFlowImportExport.js +35 -23
  17. package/dist/components/pro/Flow/hooks/useFlowImportExport.js.map +1 -1
  18. package/dist/components/pro/Flow/hooks/useKeyboardShortcuts.js +8 -7
  19. package/dist/components/pro/Flow/hooks/useKeyboardShortcuts.js.map +1 -1
  20. package/dist/components/pro/Flow/hooks/useUndoRedo.d.ts +3 -3
  21. package/dist/components/pro/Flow/hooks/useUndoRedo.js +31 -20
  22. package/dist/components/pro/Flow/hooks/useUndoRedo.js.map +1 -1
  23. package/dist/components/pro/Gantt/Gantt.js +868 -0
  24. package/dist/components/pro/Gantt/Gantt.js.map +1 -0
  25. package/dist/components/pro/Gantt/hooks/useGanttDrag.js +99 -0
  26. package/dist/components/pro/Gantt/hooks/useGanttDrag.js.map +1 -0
  27. package/dist/components/pro/Gantt/hooks/useGanttState.js +95 -0
  28. package/dist/components/pro/Gantt/hooks/useGanttState.js.map +1 -0
  29. package/dist/components/pro/Gantt/utils.js +236 -0
  30. package/dist/components/pro/Gantt/utils.js.map +1 -0
  31. package/dist/dishui.css +1 -1
  32. package/dist/gantt.js +5 -0
  33. package/dist/hooks/useCopyToClipboard.d.ts +2 -1
  34. package/dist/hooks/useCopyToClipboard.js +17 -9
  35. package/dist/hooks/useCopyToClipboard.js.map +1 -1
  36. package/dist/index.d.ts +4 -0
  37. package/dist/index.js +9 -1
  38. package/dist/utils/i18n.js +40 -0
  39. package/dist/utils/i18n.js.map +1 -1
  40. package/package.json +13 -1
  41. package/dist/pages/components/Gantt/index.d.ts +0 -3
  42. package/dist/pages/components/Gantt/index.en.d.ts +0 -3
  43. package/dist/pages/components/Gantt/index.zh.d.ts +0 -3
@@ -6,11 +6,23 @@ import { archifyToFlowDocument as a } from "../archify/convert.js";
6
6
  import { useCallback as o } from "react";
7
7
  //#region src/components/pro/Flow/hooks/useFlowImportExport.ts
8
8
  var s = 1;
9
- function c(e, t = "flowchart") {
9
+ function c(e) {
10
+ return typeof e == "object" && !!e && !Array.isArray(e);
11
+ }
12
+ function l(e) {
13
+ return !c(e) || !c(e.canvases) || !Array.isArray(e.stack) ? !1 : e.stack.every((e) => typeof e == "string") && (e.title === void 0 || typeof e.title == "string") && Object.values(e.canvases).every((e) => c(e) && Array.isArray(e.nodes) && Array.isArray(e.edges) && Array.isArray(e.lines));
14
+ }
15
+ function u(e, t = "flowchart") {
10
16
  return (e ?? "").trim().replace(/[\\/:*?"<>|]+/g, "-").replace(/\s+/g, " ").trim() || t;
11
17
  }
12
- function l() {
13
- let { doc: e, setDoc: r } = n();
18
+ function d() {
19
+ let { doc: e, setDoc: r, setSelection: c } = n(), u = o((e) => {
20
+ r(e), c({
21
+ nodeIds: /* @__PURE__ */ new Set(),
22
+ edgeIds: /* @__PURE__ */ new Set(),
23
+ lineIds: /* @__PURE__ */ new Set()
24
+ });
25
+ }, [r, c]);
14
26
  return {
15
27
  exportJSON: o(() => {
16
28
  let t = {
@@ -25,12 +37,12 @@ function l() {
25
37
  try {
26
38
  let t = JSON.parse(e);
27
39
  if (i(t)) {
28
- r(a(t));
40
+ u(a(t));
29
41
  return;
30
42
  }
43
+ if (!l(t)) throw Error("Invalid document format");
31
44
  let n = t;
32
- if (!n.canvases || !n.stack) throw Error("Invalid document format");
33
- r({
45
+ u({
34
46
  canvases: n.canvases,
35
47
  stack: n.stack,
36
48
  title: n.title
@@ -38,26 +50,26 @@ function l() {
38
50
  } catch (e) {
39
51
  console.error("Failed to import JSON:", e);
40
52
  }
41
- }, [r]),
53
+ }, [u]),
42
54
  importArchify: o((e) => {
43
55
  try {
44
56
  let t = typeof e == "string" ? JSON.parse(e) : e;
45
- return i(t) ? (r(a(t)), !0) : !1;
57
+ return i(t) ? (u(a(t)), !0) : !1;
46
58
  } catch (e) {
47
59
  return console.error("Failed to import Archify spec:", e), !1;
48
60
  }
49
- }, [r]),
61
+ }, [u]),
50
62
  importMermaid: o((e) => {
51
- let t = d(e);
52
- r(t);
53
- }, [r]),
54
- exportMarkdown: o(() => u(e), [e]),
63
+ let t = p(e);
64
+ u(t);
65
+ }, [u]),
66
+ exportMarkdown: o(() => f(e), [e]),
55
67
  clearDocument: o(() => {
56
- r(t());
57
- }, [r])
68
+ u(t());
69
+ }, [u])
58
70
  };
59
71
  }
60
- function u(t) {
72
+ function f(t) {
61
73
  let n = [], r = (e) => {
62
74
  let t = (e.data.label ?? "").trim();
63
75
  return t ? t.replace(/\s+/g, " ") : e.type === "mediaNode" ? `(${e.data.mediaKind ?? "media"})` : `(${e.type})`;
@@ -101,7 +113,7 @@ function u(t) {
101
113
  for (let e of Object.keys(t.canvases)) a.has(e) || (a.add(e), n.push(""), n.push(`## 画布 ${e}`), n.push(""), i(e, 2, a));
102
114
  return n.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
103
115
  }
104
- function d(t) {
116
+ function p(t) {
105
117
  let n = t.split("\n").map((e) => e.trim()).filter((e) => e && !e.startsWith("%%")), i = /* @__PURE__ */ new Map(), a = [], o = !1;
106
118
  for (let e of n) {
107
119
  if (/^(graph|flowchart)\s+(LR|RL)/i.test(e)) {
@@ -112,10 +124,10 @@ function d(t) {
112
124
  let t = e.match(/^(\w+)(?:\[([^\]]*)\])?\s*(-->|---|-\.->|==>)(?:\|([^|]*)\|)?\s*(\w+)(?:\[([^\]]*)\])?/);
113
125
  if (t) {
114
126
  let [, e, n, , r, o, s] = t;
115
- n && !i.has(e) && i.set(e, {
127
+ n !== void 0 && i.set(e, {
116
128
  id: e,
117
129
  label: n
118
- }), s && !i.has(o) && i.set(o, {
130
+ }), s !== void 0 && i.set(o, {
119
131
  id: o,
120
132
  label: s
121
133
  }), i.has(e) || i.set(e, {
@@ -134,7 +146,7 @@ function d(t) {
134
146
  let n = e.match(/^(\w+)\[([^\]]*)\]/);
135
147
  if (n) {
136
148
  let [, e, t] = n;
137
- i.has(e) || i.set(e, {
149
+ i.set(e, {
138
150
  id: e,
139
151
  label: t
140
152
  });
@@ -185,14 +197,14 @@ function d(t) {
185
197
  stack: [e]
186
198
  };
187
199
  }
188
- async function f(e) {
200
+ async function m(e) {
189
201
  return e.toDataURL("image/png");
190
202
  }
191
- function p(e, t) {
203
+ function h(e, t) {
192
204
  let n = document.createElement("a");
193
205
  n.href = e, n.download = t, document.body.appendChild(n), n.click(), document.body.removeChild(n);
194
206
  }
195
207
  //#endregion
196
- export { p as downloadDataURL, f as exportCanvasToPNG, c as safeFileBaseName, l as useFlowImportExport };
208
+ export { h as downloadDataURL, m as exportCanvasToPNG, u as safeFileBaseName, d as useFlowImportExport };
197
209
 
198
210
  //# sourceMappingURL=useFlowImportExport.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useFlowImportExport.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useFlowImportExport.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useFlowContext } from '../context/FlowContext';\nimport type { FlowDocument, FlowCanvasData, FlowNode, FlowEdge, FlowLine } from '../types';\nimport { createEmptyDocument, ROOT_CANVAS_ID } from '../constants';\nimport { generateId } from '../nodes/nodeUtils';\nimport { isArchifySpec, archifyToFlowDocument } from '../archify';\n\nconst PERSIST_VERSION = 1;\n\ninterface SerializedDocument {\n version: number;\n canvases: Record<string, FlowCanvasData>;\n stack: string[];\n title?: string;\n}\n\n/**\n * Sanitize a project title into a safe file base name, falling back to a\n * default when empty. Shared by all export paths so filenames stay consistent.\n */\nexport function safeFileBaseName(title: string | undefined, fallback = 'flowchart'): string {\n const name = (title ?? '').trim().replace(/[\\\\/:*?\"<>|]+/g, '-').replace(/\\s+/g, ' ').trim();\n return name || fallback;\n}\n\n/**\n * Serialize/deserialize FlowDocument to/from JSON.\n * Also includes Mermaid flowchart import.\n */\nexport function useFlowImportExport() {\n const { doc, setDoc } = useFlowContext();\n\n const exportJSON = useCallback((): string => {\n const serialized: SerializedDocument = {\n version: PERSIST_VERSION,\n canvases: doc.canvases,\n stack: doc.stack,\n title: doc.title,\n };\n return JSON.stringify(serialized, null, 2);\n }, [doc]);\n\n const importJSON = useCallback((json: string) => {\n try {\n const parsed = JSON.parse(json) as unknown;\n // Auto-detect an Archify spec and convert it to a Flow document so the\n // same \"import JSON\" entry point accepts both native and Archify files.\n if (isArchifySpec(parsed)) {\n setDoc(archifyToFlowDocument(parsed));\n return;\n }\n const doc = parsed as SerializedDocument;\n if (!doc.canvases || !doc.stack) throw new Error('Invalid document format');\n setDoc({\n canvases: doc.canvases,\n stack: doc.stack,\n title: doc.title,\n });\n } catch (err) {\n console.error('Failed to import JSON:', err);\n }\n }, [setDoc]);\n\n /**\n * Import an Archify diagram spec (architecture / workflow / sequence /\n * dataflow / lifecycle), converting it to a native Flow document. Accepts a\n * JSON string or an already-parsed object. Returns true on success.\n */\n const importArchify = useCallback((input: string | object): boolean => {\n try {\n const spec = typeof input === 'string' ? JSON.parse(input) : input;\n if (!isArchifySpec(spec)) return false;\n setDoc(archifyToFlowDocument(spec));\n return true;\n } catch (err) {\n console.error('Failed to import Archify spec:', err);\n return false;\n }\n }, [setDoc]);\n\n const importMermaid = useCallback((mermaidCode: string) => {\n const doc = parseMermaidFlowchart(mermaidCode);\n setDoc(doc);\n }, [setDoc]);\n\n const exportMarkdown = useCallback((): string => {\n return documentToMarkdown(doc);\n }, [doc]);\n\n const clearDocument = useCallback(() => {\n setDoc(createEmptyDocument());\n }, [setDoc]);\n\n return { exportJSON, importJSON, importArchify, importMermaid, exportMarkdown, clearDocument };\n}\n\n/**\n * Render a FlowDocument as a Markdown outline describing the node hierarchy\n * and connections. Groups become sections with their children nested beneath;\n * edges are listed as a connection summary per canvas. Sub-canvases (drill-down\n * nodes) are rendered as nested sections.\n */\nfunction documentToMarkdown(doc: FlowDocument): string {\n const lines: string[] = [];\n\n const nodeLabel = (n: FlowNode): string => {\n const raw = (n.data.label ?? '').trim();\n if (raw) return raw.replace(/\\s+/g, ' ');\n if (n.type === 'mediaNode') return `(${n.data.mediaKind ?? 'media'})`;\n return `(${n.type})`;\n };\n\n const renderCanvas = (canvasId: string, headingLevel: number, seenCanvases: Set<string>) => {\n const canvas = doc.canvases[canvasId];\n if (!canvas) return;\n\n // Index nodes by id and by parent for hierarchy traversal.\n const byId = new Map(canvas.nodes.map((n) => [n.id, n]));\n const childrenOf = new Map<string | undefined, FlowNode[]>();\n for (const n of canvas.nodes) {\n const key = n.parentId && byId.has(n.parentId) ? n.parentId : undefined;\n const list = childrenOf.get(key) ?? [];\n list.push(n);\n childrenOf.set(key, list);\n }\n\n const emitNode = (node: FlowNode, depth: number) => {\n const indent = ' '.repeat(depth);\n const isGroup = node.data.isGroup || node.type === 'flowGroup' || node.type === 'geometryGroup';\n const bullet = isGroup ? `**${nodeLabel(node)}**` : nodeLabel(node);\n lines.push(`${indent}- ${bullet}`);\n\n // Media content (markdown/data/etc.) as a short description line.\n if (node.type === 'mediaNode' && node.data.mediaKind) {\n const kind = node.data.mediaKind;\n if (kind === 'data' && node.data.dataConfig) {\n const cfg = node.data.dataConfig;\n const cols = cfg.columns.map((c) => c.label ?? c.key).join(', ');\n lines.push(`${indent} - 数据表:${cfg.rows.length} 行 · 列(${cols})`);\n } else if (node.data.mediaContent && kind !== 'image' && kind !== 'model3d') {\n const preview = node.data.mediaContent.trim().split('\\n')[0].slice(0, 80);\n if (preview) lines.push(`${indent} - ${kind}: ${preview}`);\n }\n }\n\n // Recurse into visual group children.\n const kids = childrenOf.get(node.id) ?? [];\n for (const kid of kids) emitNode(kid, depth + 1);\n\n // Recurse into a drill-down sub-canvas, if any.\n const childCanvasId = node.data.childCanvasId;\n if (childCanvasId && doc.canvases[childCanvasId] && !seenCanvases.has(childCanvasId)) {\n seenCanvases.add(childCanvasId);\n renderCanvas(childCanvasId, headingLevel + 1, seenCanvases);\n }\n };\n\n const roots = childrenOf.get(undefined) ?? [];\n for (const root of roots) emitNode(root, 0);\n\n // Connections summary.\n if (canvas.edges.length > 0) {\n lines.push('');\n lines.push(`${'#'.repeat(Math.min(6, headingLevel + 1))} 连接关系`);\n for (const edge of canvas.edges) {\n const from = byId.get(edge.source);\n const to = byId.get(edge.target);\n if (!from || !to) continue;\n const edgeLabel = edge.data.labelText ? ` (${edge.data.labelText})` : '';\n lines.push(`- ${nodeLabel(from)} → ${nodeLabel(to)}${edgeLabel}`);\n }\n }\n };\n\n const seen = new Set<string>([ROOT_CANVAS_ID]);\n lines.push('# 流程图');\n lines.push('');\n renderCanvas(ROOT_CANVAS_ID, 1, seen);\n\n // Any orphan canvases not reached via drill-down nodes.\n for (const canvasId of Object.keys(doc.canvases)) {\n if (seen.has(canvasId)) continue;\n seen.add(canvasId);\n lines.push('');\n lines.push(`## 画布 ${canvasId}`);\n lines.push('');\n renderCanvas(canvasId, 2, seen);\n }\n\n return lines.join('\\n').replace(/\\n{3,}/g, '\\n\\n').trim() + '\\n';\n}\n\n/**\n * Simple Mermaid flowchart parser.\n * Handles basic `graph TD` / `flowchart LR` syntax with node definitions and edges.\n */\nfunction parseMermaidFlowchart(code: string): FlowDocument {\n const lines = code.split('\\n').map(l => l.trim()).filter(l => l && !l.startsWith('%%'));\n\n const nodeMap = new Map<string, { id: string; label: string }>();\n const edges: Array<{ from: string; to: string; label?: string }> = [];\n\n let isHorizontal = false;\n\n for (const line of lines) {\n // Detect direction\n if (/^(graph|flowchart)\\s+(LR|RL)/i.test(line)) {\n isHorizontal = true;\n continue;\n }\n if (/^(graph|flowchart)\\s+/i.test(line)) continue;\n\n // Edge patterns: A --> B, A -->|label| B, A --- B\n const edgeMatch = line.match(/^(\\w+)(?:\\[([^\\]]*)\\])?\\s*(-->|---|-\\.->|==>)(?:\\|([^|]*)\\|)?\\s*(\\w+)(?:\\[([^\\]]*)\\])?/);\n if (edgeMatch) {\n const [, fromId, fromLabel, , edgeLabel, toId, toLabel] = edgeMatch;\n if (fromLabel && !nodeMap.has(fromId)) nodeMap.set(fromId, { id: fromId, label: fromLabel });\n if (toLabel && !nodeMap.has(toId)) nodeMap.set(toId, { id: toId, label: toLabel });\n if (!nodeMap.has(fromId)) nodeMap.set(fromId, { id: fromId, label: fromId });\n if (!nodeMap.has(toId)) nodeMap.set(toId, { id: toId, label: toId });\n edges.push({ from: fromId, to: toId, label: edgeLabel?.trim() });\n continue;\n }\n\n // Standalone node: A[Label]\n const nodeMatch = line.match(/^(\\w+)\\[([^\\]]*)\\]/);\n if (nodeMatch) {\n const [, id, label] = nodeMatch;\n if (!nodeMap.has(id)) nodeMap.set(id, { id, label });\n }\n }\n\n // Layout: simple grid placement\n const nodeIds = [...nodeMap.keys()];\n const cols = isHorizontal ? nodeIds.length : Math.ceil(Math.sqrt(nodeIds.length));\n const spacing = { x: 200, y: 120 };\n\n const nodes: FlowNode[] = nodeIds.map((id, i) => {\n const col = i % cols;\n const row = Math.floor(i / cols);\n const data = nodeMap.get(id)!;\n return {\n id: data.id,\n type: 'shapeRoundRect' as const,\n position: { x: 100 + col * spacing.x, y: 100 + row * spacing.y },\n width: 140,\n height: 60,\n data: {\n label: data.label,\n fill: 'theme:base-100',\n stroke: 'theme:base-content',\n strokeWidth: 2,\n color: 'theme:base-content',\n fontSize: 14,\n },\n };\n });\n\n const flowEdges: FlowEdge[] = edges.map((e) => ({\n id: generateId('edge'),\n source: e.from,\n target: e.to,\n type: 'smoothstep' as const,\n data: {\n strokeColor: 'theme:base-content',\n strokeWidth: 2,\n lineStyle: 'solid' as const,\n markerEnd: 'arrowclosed' as const,\n labelText: e.label,\n },\n }));\n\n return {\n canvases: {\n [ROOT_CANVAS_ID]: { nodes, edges: flowEdges, lines: [] },\n },\n stack: [ROOT_CANVAS_ID],\n };\n}\n\n/**\n * Export the PixiJS canvas as PNG data URL.\n */\nexport async function exportCanvasToPNG(canvas: HTMLCanvasElement): Promise<string> {\n return canvas.toDataURL('image/png');\n}\n\n/**\n * Download a data URL as a file.\n */\nexport function downloadDataURL(dataUrl: string, filename: string): void {\n const a = document.createElement('a');\n a.href = dataUrl;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useFlowImportExport.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useFlowImportExport.ts"],"sourcesContent":["import { useCallback } from 'react';\nimport { useFlowContext } from '../context/FlowContext';\nimport type { FlowDocument, FlowCanvasData, FlowNode, FlowEdge } from '../types';\nimport { createEmptyDocument, ROOT_CANVAS_ID } from '../constants';\nimport { generateId } from '../nodes/nodeUtils';\nimport { isArchifySpec, archifyToFlowDocument } from '../archify';\n\nconst PERSIST_VERSION = 1;\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\nfunction isDocument(value: unknown): value is FlowDocument {\n if (!isRecord(value) || !isRecord(value.canvases) || !Array.isArray(value.stack)) return false;\n return value.stack.every(id => typeof id === 'string')\n && (value.title === undefined || typeof value.title === 'string')\n && Object.values(value.canvases).every(canvas => isRecord(canvas)\n && Array.isArray(canvas.nodes) && Array.isArray(canvas.edges) && Array.isArray(canvas.lines));\n}\n\ninterface SerializedDocument {\n version: number;\n canvases: Record<string, FlowCanvasData>;\n stack: string[];\n title?: string;\n}\n\n/**\n * Sanitize a project title into a safe file base name, falling back to a\n * default when empty. Shared by all export paths so filenames stay consistent.\n */\nexport function safeFileBaseName(title: string | undefined, fallback = 'flowchart'): string {\n const name = (title ?? '').trim().replace(/[\\\\/:*?\"<>|]+/g, '-').replace(/\\s+/g, ' ').trim();\n return name || fallback;\n}\n\n/**\n * Serialize/deserialize FlowDocument to/from JSON.\n * Also includes Mermaid flowchart import.\n */\nexport function useFlowImportExport() {\n const { doc, setDoc, setSelection } = useFlowContext();\n\n const replaceDocument = useCallback((next: FlowDocument) => {\n setDoc(next);\n setSelection({ nodeIds: new Set(), edgeIds: new Set(), lineIds: new Set() });\n }, [setDoc, setSelection]);\n\n const exportJSON = useCallback((): string => {\n const serialized: SerializedDocument = {\n version: PERSIST_VERSION,\n canvases: doc.canvases,\n stack: doc.stack,\n title: doc.title,\n };\n return JSON.stringify(serialized, null, 2);\n }, [doc]);\n\n const importJSON = useCallback((json: string) => {\n try {\n const parsed = JSON.parse(json) as unknown;\n // Auto-detect an Archify spec and convert it to a Flow document so the\n // same \"import JSON\" entry point accepts both native and Archify files.\n if (isArchifySpec(parsed)) {\n replaceDocument(archifyToFlowDocument(parsed));\n return;\n }\n if (!isDocument(parsed)) throw new Error('Invalid document format');\n const doc = parsed;\n replaceDocument({\n canvases: doc.canvases,\n stack: doc.stack,\n title: doc.title,\n });\n } catch (err) {\n console.error('Failed to import JSON:', err);\n }\n }, [replaceDocument]);\n\n /**\n * Import an Archify diagram spec (architecture / workflow / sequence /\n * dataflow / lifecycle), converting it to a native Flow document. Accepts a\n * JSON string or an already-parsed object. Returns true on success.\n */\n const importArchify = useCallback((input: string | object): boolean => {\n try {\n const spec = typeof input === 'string' ? JSON.parse(input) : input;\n if (!isArchifySpec(spec)) return false;\n replaceDocument(archifyToFlowDocument(spec));\n return true;\n } catch (err) {\n console.error('Failed to import Archify spec:', err);\n return false;\n }\n }, [replaceDocument]);\n\n const importMermaid = useCallback((mermaidCode: string) => {\n const doc = parseMermaidFlowchart(mermaidCode);\n replaceDocument(doc);\n }, [replaceDocument]);\n\n const exportMarkdown = useCallback((): string => {\n return documentToMarkdown(doc);\n }, [doc]);\n\n const clearDocument = useCallback(() => {\n replaceDocument(createEmptyDocument());\n }, [replaceDocument]);\n\n return { exportJSON, importJSON, importArchify, importMermaid, exportMarkdown, clearDocument };\n}\n\n/**\n * Render a FlowDocument as a Markdown outline describing the node hierarchy\n * and connections. Groups become sections with their children nested beneath;\n * edges are listed as a connection summary per canvas. Sub-canvases (drill-down\n * nodes) are rendered as nested sections.\n */\nfunction documentToMarkdown(doc: FlowDocument): string {\n const lines: string[] = [];\n\n const nodeLabel = (n: FlowNode): string => {\n const raw = (n.data.label ?? '').trim();\n if (raw) return raw.replace(/\\s+/g, ' ');\n if (n.type === 'mediaNode') return `(${n.data.mediaKind ?? 'media'})`;\n return `(${n.type})`;\n };\n\n const renderCanvas = (canvasId: string, headingLevel: number, seenCanvases: Set<string>) => {\n const canvas = doc.canvases[canvasId];\n if (!canvas) return;\n\n // Index nodes by id and by parent for hierarchy traversal.\n const byId = new Map(canvas.nodes.map((n) => [n.id, n]));\n const childrenOf = new Map<string | undefined, FlowNode[]>();\n for (const n of canvas.nodes) {\n const key = n.parentId && byId.has(n.parentId) ? n.parentId : undefined;\n const list = childrenOf.get(key) ?? [];\n list.push(n);\n childrenOf.set(key, list);\n }\n\n const emitNode = (node: FlowNode, depth: number) => {\n const indent = ' '.repeat(depth);\n const isGroup = node.data.isGroup || node.type === 'flowGroup' || node.type === 'geometryGroup';\n const bullet = isGroup ? `**${nodeLabel(node)}**` : nodeLabel(node);\n lines.push(`${indent}- ${bullet}`);\n\n // Media content (markdown/data/etc.) as a short description line.\n if (node.type === 'mediaNode' && node.data.mediaKind) {\n const kind = node.data.mediaKind;\n if (kind === 'data' && node.data.dataConfig) {\n const cfg = node.data.dataConfig;\n const cols = cfg.columns.map((c) => c.label ?? c.key).join(', ');\n lines.push(`${indent} - 数据表:${cfg.rows.length} 行 · 列(${cols})`);\n } else if (node.data.mediaContent && kind !== 'image' && kind !== 'model3d') {\n const preview = node.data.mediaContent.trim().split('\\n')[0].slice(0, 80);\n if (preview) lines.push(`${indent} - ${kind}: ${preview}`);\n }\n }\n\n // Recurse into visual group children.\n const kids = childrenOf.get(node.id) ?? [];\n for (const kid of kids) emitNode(kid, depth + 1);\n\n // Recurse into a drill-down sub-canvas, if any.\n const childCanvasId = node.data.childCanvasId;\n if (childCanvasId && doc.canvases[childCanvasId] && !seenCanvases.has(childCanvasId)) {\n seenCanvases.add(childCanvasId);\n renderCanvas(childCanvasId, headingLevel + 1, seenCanvases);\n }\n };\n\n const roots = childrenOf.get(undefined) ?? [];\n for (const root of roots) emitNode(root, 0);\n\n // Connections summary.\n if (canvas.edges.length > 0) {\n lines.push('');\n lines.push(`${'#'.repeat(Math.min(6, headingLevel + 1))} 连接关系`);\n for (const edge of canvas.edges) {\n const from = byId.get(edge.source);\n const to = byId.get(edge.target);\n if (!from || !to) continue;\n const edgeLabel = edge.data.labelText ? ` (${edge.data.labelText})` : '';\n lines.push(`- ${nodeLabel(from)} → ${nodeLabel(to)}${edgeLabel}`);\n }\n }\n };\n\n const seen = new Set<string>([ROOT_CANVAS_ID]);\n lines.push('# 流程图');\n lines.push('');\n renderCanvas(ROOT_CANVAS_ID, 1, seen);\n\n // Any orphan canvases not reached via drill-down nodes.\n for (const canvasId of Object.keys(doc.canvases)) {\n if (seen.has(canvasId)) continue;\n seen.add(canvasId);\n lines.push('');\n lines.push(`## 画布 ${canvasId}`);\n lines.push('');\n renderCanvas(canvasId, 2, seen);\n }\n\n return lines.join('\\n').replace(/\\n{3,}/g, '\\n\\n').trim() + '\\n';\n}\n\n/**\n * Simple Mermaid flowchart parser.\n * Handles basic `graph TD` / `flowchart LR` syntax with node definitions and edges.\n */\nfunction parseMermaidFlowchart(code: string): FlowDocument {\n const lines = code.split('\\n').map(l => l.trim()).filter(l => l && !l.startsWith('%%'));\n\n const nodeMap = new Map<string, { id: string; label: string }>();\n const edges: Array<{ from: string; to: string; label?: string }> = [];\n\n let isHorizontal = false;\n\n for (const line of lines) {\n // Detect direction\n if (/^(graph|flowchart)\\s+(LR|RL)/i.test(line)) {\n isHorizontal = true;\n continue;\n }\n if (/^(graph|flowchart)\\s+/i.test(line)) continue;\n\n // Edge patterns: A --> B, A -->|label| B, A --- B\n const edgeMatch = line.match(/^(\\w+)(?:\\[([^\\]]*)\\])?\\s*(-->|---|-\\.->|==>)(?:\\|([^|]*)\\|)?\\s*(\\w+)(?:\\[([^\\]]*)\\])?/);\n if (edgeMatch) {\n const [, fromId, fromLabel, , edgeLabel, toId, toLabel] = edgeMatch;\n if (fromLabel !== undefined) nodeMap.set(fromId, { id: fromId, label: fromLabel });\n if (toLabel !== undefined) nodeMap.set(toId, { id: toId, label: toLabel });\n if (!nodeMap.has(fromId)) nodeMap.set(fromId, { id: fromId, label: fromId });\n if (!nodeMap.has(toId)) nodeMap.set(toId, { id: toId, label: toId });\n edges.push({ from: fromId, to: toId, label: edgeLabel?.trim() });\n continue;\n }\n\n // Standalone node: A[Label]\n const nodeMatch = line.match(/^(\\w+)\\[([^\\]]*)\\]/);\n if (nodeMatch) {\n const [, id, label] = nodeMatch;\n nodeMap.set(id, { id, label });\n }\n }\n\n // Layout: simple grid placement\n const nodeIds = [...nodeMap.keys()];\n const cols = isHorizontal ? nodeIds.length : Math.ceil(Math.sqrt(nodeIds.length));\n const spacing = { x: 200, y: 120 };\n\n const nodes: FlowNode[] = nodeIds.map((id, i) => {\n const col = i % cols;\n const row = Math.floor(i / cols);\n const data = nodeMap.get(id)!;\n return {\n id: data.id,\n type: 'shapeRoundRect' as const,\n position: { x: 100 + col * spacing.x, y: 100 + row * spacing.y },\n width: 140,\n height: 60,\n data: {\n label: data.label,\n fill: 'theme:base-100',\n stroke: 'theme:base-content',\n strokeWidth: 2,\n color: 'theme:base-content',\n fontSize: 14,\n },\n };\n });\n\n const flowEdges: FlowEdge[] = edges.map((e) => ({\n id: generateId('edge'),\n source: e.from,\n target: e.to,\n type: 'smoothstep' as const,\n data: {\n strokeColor: 'theme:base-content',\n strokeWidth: 2,\n lineStyle: 'solid' as const,\n markerEnd: 'arrowclosed' as const,\n labelText: e.label,\n },\n }));\n\n return {\n canvases: {\n [ROOT_CANVAS_ID]: { nodes, edges: flowEdges, lines: [] },\n },\n stack: [ROOT_CANVAS_ID],\n };\n}\n\n/**\n * Export the PixiJS canvas as PNG data URL.\n */\nexport async function exportCanvasToPNG(canvas: HTMLCanvasElement): Promise<string> {\n return canvas.toDataURL('image/png');\n}\n\n/**\n * Download a data URL as a file.\n */\nexport function downloadDataURL(dataUrl: string, filename: string): void {\n const a = document.createElement('a');\n a.href = dataUrl;\n a.download = filename;\n document.body.appendChild(a);\n a.click();\n document.body.removeChild(a);\n}\n"],"mappings":"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"}
@@ -3,30 +3,31 @@ import { useFlowUI as t } from "../context/FlowUIContext.js";
3
3
  import { useEffect as n } from "react";
4
4
  //#region src/components/pro/Flow/hooks/useKeyboardShortcuts.ts
5
5
  function r(r) {
6
- let { selection: i, setSelection: a, currentCanvas: o } = e(), { editingNodeId: s, setToolMode: c, setPendingPlacement: l } = t();
6
+ let { setSelection: i } = e(), { editingNodeId: a, setToolMode: o, setPendingPlacement: s } = t();
7
7
  n(() => {
8
8
  let e = (e) => {
9
+ if (e.defaultPrevented || e.isComposing) return;
9
10
  let t = e.target;
10
- if (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable || s) return;
11
+ if (t?.tagName === "INPUT" || t?.tagName === "TEXTAREA" || t?.tagName === "SELECT" || t?.isContentEditable || a) return;
11
12
  let n = e.metaKey || e.ctrlKey;
12
13
  if (n && e.key === "z" && !e.shiftKey) e.preventDefault(), r.undo();
13
14
  else if (n && (e.key === "Z" || e.key === "z" && e.shiftKey)) e.preventDefault(), r.redo();
14
15
  else if (n && e.key === "c") e.preventDefault(), r.copy();
15
16
  else if (n && e.key === "x") e.preventDefault(), r.cut();
16
17
  else if (n && e.key === "v") return;
17
- else n && e.key === "a" ? (e.preventDefault(), r.selectAll()) : n && (e.key === "d" || e.key === "D") ? (e.preventDefault(), r.duplicate()) : n && e.altKey && (e.key === "g" || e.key === "G") ? (e.preventDefault(), r.geoGroup?.()) : n && e.key === "g" && !e.shiftKey && !e.altKey ? (e.preventDefault(), r.group?.()) : n && (e.key === "G" || e.key === "g" && e.shiftKey) && !e.altKey ? (e.preventDefault(), r.ungroup?.()) : n && e.shiftKey && e.key === "ArrowUp" ? (e.preventDefault(), r.bringToFront?.()) : n && !e.shiftKey && e.key === "ArrowUp" ? (e.preventDefault(), r.bringForward?.()) : n && !e.shiftKey && e.key === "ArrowDown" ? (e.preventDefault(), r.sendBackward?.()) : n && e.shiftKey && e.key === "ArrowDown" ? (e.preventDefault(), r.sendToBack?.()) : e.key === "Delete" || e.key === "Backspace" ? (e.preventDefault(), r.deleteSelected()) : e.key === "Escape" && (a({
18
+ else n && e.key === "a" ? (e.preventDefault(), r.selectAll()) : n && (e.key === "d" || e.key === "D") ? (e.preventDefault(), r.duplicate()) : n && e.altKey && (e.key === "g" || e.key === "G") ? (e.preventDefault(), r.geoGroup?.()) : n && e.key === "g" && !e.shiftKey && !e.altKey ? (e.preventDefault(), r.group?.()) : n && (e.key === "G" || e.key === "g" && e.shiftKey) && !e.altKey ? (e.preventDefault(), r.ungroup?.()) : n && e.shiftKey && e.key === "ArrowUp" ? (e.preventDefault(), r.bringToFront?.()) : n && !e.shiftKey && e.key === "ArrowUp" ? (e.preventDefault(), r.bringForward?.()) : n && !e.shiftKey && e.key === "ArrowDown" ? (e.preventDefault(), r.sendBackward?.()) : n && e.shiftKey && e.key === "ArrowDown" ? (e.preventDefault(), r.sendToBack?.()) : e.key === "Delete" || e.key === "Backspace" ? (e.preventDefault(), r.deleteSelected()) : e.key === "Escape" && (i({
18
19
  nodeIds: /* @__PURE__ */ new Set(),
19
20
  edgeIds: /* @__PURE__ */ new Set(),
20
21
  lineIds: /* @__PURE__ */ new Set()
21
- }), c("select"), l(null));
22
+ }), o("select"), s(null));
22
23
  };
23
24
  return window.addEventListener("keydown", e), () => window.removeEventListener("keydown", e);
24
25
  }, [
25
26
  r,
26
- s,
27
27
  a,
28
- c,
29
- l
28
+ i,
29
+ o,
30
+ s
30
31
  ]);
31
32
  }
32
33
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardShortcuts.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useKeyboardShortcuts.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { useFlowContext } from '../context/FlowContext';\nimport { useFlowUI } from '../context/FlowUIContext';\n\ninterface KeyboardShortcutActions {\n undo: () => void;\n redo: () => void;\n copy: () => void;\n cut: () => void;\n paste: () => void;\n duplicate: () => void;\n deleteSelected: () => void;\n selectAll: () => void;\n group?: () => void;\n geoGroup?: () => void;\n ungroup?: () => void;\n bringToFront?: () => void;\n bringForward?: () => void;\n sendBackward?: () => void;\n sendToBack?: () => void;\n}\n\n/**\n * Registers global keyboard shortcuts for the flow editor.\n */\nexport function useKeyboardShortcuts(actions: KeyboardShortcutActions) {\n const { selection, setSelection, currentCanvas } = useFlowContext();\n const { editingNodeId, setToolMode, setPendingPlacement } = useFlowUI();\n\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n // Don't intercept when editing text in inputs\n const target = e.target as HTMLElement;\n if (target.tagName === 'INPUT' || target.tagName === 'TEXTAREA' || target.isContentEditable) return;\n if (editingNodeId) return;\n\n const meta = e.metaKey || e.ctrlKey;\n\n if (meta && e.key === 'z' && !e.shiftKey) {\n e.preventDefault();\n actions.undo();\n } else if (meta && (e.key === 'Z' || (e.key === 'z' && e.shiftKey))) {\n e.preventDefault();\n actions.redo();\n } else if (meta && e.key === 'c') {\n e.preventDefault();\n actions.copy();\n } else if (meta && e.key === 'x') {\n e.preventDefault();\n actions.cut();\n } else if (meta && e.key === 'v') {\n // Let the native paste event inspect image clipboard data. Structured\n // Flow clipboard fallback is handled there when no image is present.\n return;\n } else if (meta && e.key === 'a') {\n e.preventDefault();\n actions.selectAll();\n } else if (meta && (e.key === 'd' || e.key === 'D')) {\n e.preventDefault();\n actions.duplicate();\n } else if (meta && e.altKey && (e.key === 'g' || e.key === 'G')) {\n e.preventDefault();\n actions.geoGroup?.();\n } else if (meta && e.key === 'g' && !e.shiftKey && !e.altKey) {\n e.preventDefault();\n actions.group?.();\n } else if (meta && (e.key === 'G' || (e.key === 'g' && e.shiftKey)) && !e.altKey) {\n e.preventDefault();\n actions.ungroup?.();\n } else if (meta && e.shiftKey && e.key === 'ArrowUp') {\n e.preventDefault();\n actions.bringToFront?.();\n } else if (meta && !e.shiftKey && e.key === 'ArrowUp') {\n e.preventDefault();\n actions.bringForward?.();\n } else if (meta && !e.shiftKey && e.key === 'ArrowDown') {\n e.preventDefault();\n actions.sendBackward?.();\n } else if (meta && e.shiftKey && e.key === 'ArrowDown') {\n e.preventDefault();\n actions.sendToBack?.();\n } else if (e.key === 'Delete' || e.key === 'Backspace') {\n e.preventDefault();\n actions.deleteSelected();\n } else if (e.key === 'Escape') {\n setSelection({ nodeIds: new Set(), edgeIds: new Set(), lineIds: new Set() });\n setToolMode('select');\n setPendingPlacement(null);\n }\n };\n\n window.addEventListener('keydown', handler);\n return () => window.removeEventListener('keydown', handler);\n }, [actions, editingNodeId, setSelection, setToolMode, setPendingPlacement]);\n}\n"],"mappings":";;;;AAyBA,SAAgB,EAAqB,GAAkC;CACrE,IAAM,EAAE,cAAW,iBAAc,qBAAkB,EAAe,GAC5D,EAAE,kBAAe,gBAAa,2BAAwB,EAAU;CAEtE,QAAgB;EACd,IAAM,KAAW,MAAqB;GAEpC,IAAM,IAAS,EAAE;GAEjB,IADI,EAAO,YAAY,WAAW,EAAO,YAAY,cAAc,EAAO,qBACtE,GAAe;GAEnB,IAAM,IAAO,EAAE,WAAW,EAAE;GAE5B,IAAI,KAAQ,EAAE,QAAQ,OAAO,CAAC,EAAE,UAE9B,AADA,EAAE,eAAe,GACjB,EAAQ,KAAK;QACR,IAAI,MAAS,EAAE,QAAQ,OAAQ,EAAE,QAAQ,OAAO,EAAE,WAEvD,AADA,EAAE,eAAe,GACjB,EAAQ,KAAK;QACR,IAAI,KAAQ,EAAE,QAAQ,KAE3B,AADA,EAAE,eAAe,GACjB,EAAQ,KAAK;QACR,IAAI,KAAQ,EAAE,QAAQ,KAE3B,AADA,EAAE,eAAe,GACjB,EAAQ,IAAI;QACP,IAAI,KAAQ,EAAE,QAAQ,KAG3B;QACK,AAAI,KAAQ,EAAE,QAAQ,OAC3B,EAAE,eAAe,GACjB,EAAQ,UAAU,KACT,MAAS,EAAE,QAAQ,OAAO,EAAE,QAAQ,QAC7C,EAAE,eAAe,GACjB,EAAQ,UAAU,KACT,KAAQ,EAAE,WAAW,EAAE,QAAQ,OAAO,EAAE,QAAQ,QACzD,EAAE,eAAe,GACjB,EAAQ,WAAW,KACV,KAAQ,EAAE,QAAQ,OAAO,CAAC,EAAE,YAAY,CAAC,EAAE,UACpD,EAAE,eAAe,GACjB,EAAQ,QAAQ,KACP,MAAS,EAAE,QAAQ,OAAQ,EAAE,QAAQ,OAAO,EAAE,aAAc,CAAC,EAAE,UACxE,EAAE,eAAe,GACjB,EAAQ,UAAU,KACT,KAAQ,EAAE,YAAY,EAAE,QAAQ,aACzC,EAAE,eAAe,GACjB,EAAQ,eAAe,KACd,KAAQ,CAAC,EAAE,YAAY,EAAE,QAAQ,aAC1C,EAAE,eAAe,GACjB,EAAQ,eAAe,KACd,KAAQ,CAAC,EAAE,YAAY,EAAE,QAAQ,eAC1C,EAAE,eAAe,GACjB,EAAQ,eAAe,KACd,KAAQ,EAAE,YAAY,EAAE,QAAQ,eACzC,EAAE,eAAe,GACjB,EAAQ,aAAa,KACZ,EAAE,QAAQ,YAAY,EAAE,QAAQ,eACzC,EAAE,eAAe,GACjB,EAAQ,eAAe,KACd,EAAE,QAAQ,aACnB,EAAa;IAAE,yBAAS,IAAI,IAAI;IAAG,yBAAS,IAAI,IAAI;IAAG,yBAAS,IAAI,IAAI;GAAE,CAAC,GAC3E,EAAY,QAAQ,GACpB,EAAoB,IAAI;EAE5B;EAGA,OADA,OAAO,iBAAiB,WAAW,CAAO,SAC7B,OAAO,oBAAoB,WAAW,CAAO;CAC5D,GAAG;EAAC;EAAS;EAAe;EAAc;EAAa;CAAmB,CAAC;AAC7E"}
1
+ {"version":3,"file":"useKeyboardShortcuts.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useKeyboardShortcuts.ts"],"sourcesContent":["import { useEffect } from 'react';\nimport { useFlowContext } from '../context/FlowContext';\nimport { useFlowUI } from '../context/FlowUIContext';\n\ninterface KeyboardShortcutActions {\n undo: () => void;\n redo: () => void;\n copy: () => void;\n cut: () => void;\n paste: () => void;\n duplicate: () => void;\n deleteSelected: () => void;\n selectAll: () => void;\n group?: () => void;\n geoGroup?: () => void;\n ungroup?: () => void;\n bringToFront?: () => void;\n bringForward?: () => void;\n sendBackward?: () => void;\n sendToBack?: () => void;\n}\n\n/**\n * Registers global keyboard shortcuts for the flow editor.\n */\nexport function useKeyboardShortcuts(actions: KeyboardShortcutActions) {\n const { setSelection } = useFlowContext();\n const { editingNodeId, setToolMode, setPendingPlacement } = useFlowUI();\n\n useEffect(() => {\n const handler = (e: KeyboardEvent) => {\n // Don't intercept when editing text in inputs\n if (e.defaultPrevented || e.isComposing) return;\n const target = e.target as HTMLElement | null;\n if (target?.tagName === 'INPUT' || target?.tagName === 'TEXTAREA' || target?.tagName === 'SELECT' || target?.isContentEditable) return;\n if (editingNodeId) return;\n\n const meta = e.metaKey || e.ctrlKey;\n\n if (meta && e.key === 'z' && !e.shiftKey) {\n e.preventDefault();\n actions.undo();\n } else if (meta && (e.key === 'Z' || (e.key === 'z' && e.shiftKey))) {\n e.preventDefault();\n actions.redo();\n } else if (meta && e.key === 'c') {\n e.preventDefault();\n actions.copy();\n } else if (meta && e.key === 'x') {\n e.preventDefault();\n actions.cut();\n } else if (meta && e.key === 'v') {\n // Let the native paste event inspect image clipboard data. Structured\n // Flow clipboard fallback is handled there when no image is present.\n return;\n } else if (meta && e.key === 'a') {\n e.preventDefault();\n actions.selectAll();\n } else if (meta && (e.key === 'd' || e.key === 'D')) {\n e.preventDefault();\n actions.duplicate();\n } else if (meta && e.altKey && (e.key === 'g' || e.key === 'G')) {\n e.preventDefault();\n actions.geoGroup?.();\n } else if (meta && e.key === 'g' && !e.shiftKey && !e.altKey) {\n e.preventDefault();\n actions.group?.();\n } else if (meta && (e.key === 'G' || (e.key === 'g' && e.shiftKey)) && !e.altKey) {\n e.preventDefault();\n actions.ungroup?.();\n } else if (meta && e.shiftKey && e.key === 'ArrowUp') {\n e.preventDefault();\n actions.bringToFront?.();\n } else if (meta && !e.shiftKey && e.key === 'ArrowUp') {\n e.preventDefault();\n actions.bringForward?.();\n } else if (meta && !e.shiftKey && e.key === 'ArrowDown') {\n e.preventDefault();\n actions.sendBackward?.();\n } else if (meta && e.shiftKey && e.key === 'ArrowDown') {\n e.preventDefault();\n actions.sendToBack?.();\n } else if (e.key === 'Delete' || e.key === 'Backspace') {\n e.preventDefault();\n actions.deleteSelected();\n } else if (e.key === 'Escape') {\n setSelection({ nodeIds: new Set(), edgeIds: new Set(), lineIds: new Set() });\n setToolMode('select');\n setPendingPlacement(null);\n }\n };\n\n window.addEventListener('keydown', handler);\n return () => window.removeEventListener('keydown', handler);\n }, [actions, editingNodeId, setSelection, setToolMode, setPendingPlacement]);\n}\n"],"mappings":";;;;AAyBA,SAAgB,EAAqB,GAAkC;CACrE,IAAM,EAAE,oBAAiB,EAAe,GAClC,EAAE,kBAAe,gBAAa,2BAAwB,EAAU;CAEtE,QAAgB;EACd,IAAM,KAAW,MAAqB;GAEpC,IAAI,EAAE,oBAAoB,EAAE,aAAa;GACzC,IAAM,IAAS,EAAE;GAEjB,IADI,GAAQ,YAAY,WAAW,GAAQ,YAAY,cAAc,GAAQ,YAAY,YAAY,GAAQ,qBACzG,GAAe;GAEnB,IAAM,IAAO,EAAE,WAAW,EAAE;GAE5B,IAAI,KAAQ,EAAE,QAAQ,OAAO,CAAC,EAAE,UAE9B,AADA,EAAE,eAAe,GACjB,EAAQ,KAAK;QACR,IAAI,MAAS,EAAE,QAAQ,OAAQ,EAAE,QAAQ,OAAO,EAAE,WAEvD,AADA,EAAE,eAAe,GACjB,EAAQ,KAAK;QACR,IAAI,KAAQ,EAAE,QAAQ,KAE3B,AADA,EAAE,eAAe,GACjB,EAAQ,KAAK;QACR,IAAI,KAAQ,EAAE,QAAQ,KAE3B,AADA,EAAE,eAAe,GACjB,EAAQ,IAAI;QACP,IAAI,KAAQ,EAAE,QAAQ,KAG3B;QACK,AAAI,KAAQ,EAAE,QAAQ,OAC3B,EAAE,eAAe,GACjB,EAAQ,UAAU,KACT,MAAS,EAAE,QAAQ,OAAO,EAAE,QAAQ,QAC7C,EAAE,eAAe,GACjB,EAAQ,UAAU,KACT,KAAQ,EAAE,WAAW,EAAE,QAAQ,OAAO,EAAE,QAAQ,QACzD,EAAE,eAAe,GACjB,EAAQ,WAAW,KACV,KAAQ,EAAE,QAAQ,OAAO,CAAC,EAAE,YAAY,CAAC,EAAE,UACpD,EAAE,eAAe,GACjB,EAAQ,QAAQ,KACP,MAAS,EAAE,QAAQ,OAAQ,EAAE,QAAQ,OAAO,EAAE,aAAc,CAAC,EAAE,UACxE,EAAE,eAAe,GACjB,EAAQ,UAAU,KACT,KAAQ,EAAE,YAAY,EAAE,QAAQ,aACzC,EAAE,eAAe,GACjB,EAAQ,eAAe,KACd,KAAQ,CAAC,EAAE,YAAY,EAAE,QAAQ,aAC1C,EAAE,eAAe,GACjB,EAAQ,eAAe,KACd,KAAQ,CAAC,EAAE,YAAY,EAAE,QAAQ,eAC1C,EAAE,eAAe,GACjB,EAAQ,eAAe,KACd,KAAQ,EAAE,YAAY,EAAE,QAAQ,eACzC,EAAE,eAAe,GACjB,EAAQ,aAAa,KACZ,EAAE,QAAQ,YAAY,EAAE,QAAQ,eACzC,EAAE,eAAe,GACjB,EAAQ,eAAe,KACd,EAAE,QAAQ,aACnB,EAAa;IAAE,yBAAS,IAAI,IAAI;IAAG,yBAAS,IAAI,IAAI;IAAG,yBAAS,IAAI,IAAI;GAAE,CAAC,GAC3E,EAAY,QAAQ,GACpB,EAAoB,IAAI;EAE5B;EAGA,OADA,OAAO,iBAAiB,WAAW,CAAO,SAC7B,OAAO,oBAAoB,WAAW,CAAO;CAC5D,GAAG;EAAC;EAAS;EAAe;EAAc;EAAa;CAAmB,CAAC;AAC7E"}
@@ -1,12 +1,12 @@
1
1
  import type { FlowDocument } from '../types';
2
2
  /**
3
3
  * Time-coalesced undo/redo for FlowDocument.
4
- * Wraps setDoc to automatically capture history snapshots.
4
+ * Commands capture history outside React updaters, which React may replay.
5
5
  */
6
6
  export declare function useUndoRedo(doc: FlowDocument, setDoc: React.Dispatch<React.SetStateAction<FlowDocument>>): {
7
+ canUndo: boolean;
8
+ canRedo: boolean;
7
9
  wrappedSetDoc: import("react").Dispatch<import("react").SetStateAction<FlowDocument>>;
8
10
  undo: () => void;
9
11
  redo: () => void;
10
- canUndo: boolean;
11
- canRedo: boolean;
12
12
  };
@@ -1,30 +1,41 @@
1
- import { useCallback as e, useRef as t } from "react";
1
+ import { useCallback as e, useLayoutEffect as t, useRef as n, useState as r } from "react";
2
2
  //#region src/components/pro/Flow/hooks/useUndoRedo.ts
3
- var n = 50, r = 300;
4
- function i(i, a) {
5
- let o = t([]), s = t([]), c = t(0), l = t(!1), u = e((e) => {
6
- let t = Date.now();
7
- t - c.current < r && o.current.length > 0 ? o.current[o.current.length - 1] = e : (o.current.push(e), o.current.length > n && o.current.shift()), c.current = t, s.current = [];
8
- }, []);
3
+ var i = 50, a = 300;
4
+ function o(o, s) {
5
+ let c = n([]), l = n([]), u = n(-Infinity), d = n(o), [f, p] = r({
6
+ canUndo: !1,
7
+ canRedo: !1
8
+ });
9
+ t(() => {
10
+ d.current = o;
11
+ }, [o]);
12
+ let m = e((e) => {
13
+ d.current = e, s(e), p({
14
+ canUndo: c.current.length > 0,
15
+ canRedo: l.current.length > 0
16
+ });
17
+ }, [s]);
9
18
  return {
10
19
  wrappedSetDoc: e((e) => {
11
- a((t) => (l.current || u(t), l.current = !1, typeof e == "function" ? e(t) : e));
12
- }, [a, u]),
20
+ let t = d.current, n = typeof e == "function" ? e(t) : e;
21
+ if (n === t) return;
22
+ let r = Date.now();
23
+ (r - u.current >= a || c.current.length === 0) && (c.current.push(t), c.current.length > i && c.current.shift()), u.current = r, l.current = [], m(n);
24
+ }, [m]),
13
25
  undo: e(() => {
14
- if (o.current.length === 0) return;
15
- let e = o.current.pop();
16
- a((t) => (s.current.push(t), e)), l.current = !0;
17
- }, [a]),
26
+ if (c.current.length === 0) return;
27
+ let e = c.current.pop();
28
+ l.current.push(d.current), u.current = -Infinity, m(e);
29
+ }, [m]),
18
30
  redo: e(() => {
19
- if (s.current.length === 0) return;
20
- let e = s.current.pop();
21
- a((t) => (o.current.push(t), e)), l.current = !0;
22
- }, [a]),
23
- canUndo: o.current.length > 0,
24
- canRedo: s.current.length > 0
31
+ if (l.current.length === 0) return;
32
+ let e = l.current.pop();
33
+ c.current.push(d.current), u.current = -Infinity, m(e);
34
+ }, [m]),
35
+ ...f
25
36
  };
26
37
  }
27
38
  //#endregion
28
- export { i as useUndoRedo };
39
+ export { o as useUndoRedo };
29
40
 
30
41
  //# sourceMappingURL=useUndoRedo.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useUndoRedo.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useUndoRedo.ts"],"sourcesContent":["import { useRef, useCallback, useEffect } from 'react';\nimport type { FlowDocument } from '../types';\n\nconst MAX_HISTORY = 50;\nconst COALESCE_MS = 300;\n\n/**\n * Time-coalesced undo/redo for FlowDocument.\n * Wraps setDoc to automatically capture history snapshots.\n */\nexport function useUndoRedo(\n doc: FlowDocument,\n setDoc: React.Dispatch<React.SetStateAction<FlowDocument>>,\n) {\n const historyRef = useRef<FlowDocument[]>([]);\n const futureRef = useRef<FlowDocument[]>([]);\n const lastPushRef = useRef(0);\n const skipRef = useRef(false);\n\n // Push to history with time coalescing\n const pushHistory = useCallback((snapshot: FlowDocument) => {\n const now = Date.now();\n if (now - lastPushRef.current < COALESCE_MS && historyRef.current.length > 0) {\n historyRef.current[historyRef.current.length - 1] = snapshot;\n } else {\n historyRef.current.push(snapshot);\n if (historyRef.current.length > MAX_HISTORY) {\n historyRef.current.shift();\n }\n }\n lastPushRef.current = now;\n futureRef.current = [];\n }, []);\n\n // Wrap setDoc to record history\n const wrappedSetDoc: React.Dispatch<React.SetStateAction<FlowDocument>> = useCallback(\n (action) => {\n setDoc(prev => {\n if (!skipRef.current) {\n pushHistory(prev);\n }\n skipRef.current = false;\n return typeof action === 'function' ? action(prev) : action;\n });\n },\n [setDoc, pushHistory],\n );\n\n const undo = useCallback(() => {\n if (historyRef.current.length === 0) return;\n const prev = historyRef.current.pop()!;\n setDoc(current => {\n futureRef.current.push(current);\n return prev;\n });\n skipRef.current = true;\n }, [setDoc]);\n\n const redo = useCallback(() => {\n if (futureRef.current.length === 0) return;\n const next = futureRef.current.pop()!;\n setDoc(current => {\n historyRef.current.push(current);\n return next;\n });\n skipRef.current = true;\n }, [setDoc]);\n\n const canUndo = historyRef.current.length > 0;\n const canRedo = futureRef.current.length > 0;\n\n return { wrappedSetDoc, undo, redo, canUndo, canRedo };\n}\n"],"mappings":";;AAGA,IAAM,IAAc,IACd,IAAc;AAMpB,SAAgB,EACd,GACA,GACA;CACA,IAAM,IAAa,EAAuB,CAAC,CAAC,GACtC,IAAY,EAAuB,CAAC,CAAC,GACrC,IAAc,EAAO,CAAC,GACtB,IAAU,EAAO,EAAK,GAGtB,IAAc,GAAa,MAA2B;EAC1D,IAAM,IAAM,KAAK,IAAI;EAUrB,AATI,IAAM,EAAY,UAAU,KAAe,EAAW,QAAQ,SAAS,IACzE,EAAW,QAAQ,EAAW,QAAQ,SAAS,KAAK,KAEpD,EAAW,QAAQ,KAAK,CAAQ,GAC5B,EAAW,QAAQ,SAAS,KAC9B,EAAW,QAAQ,MAAM,IAG7B,EAAY,UAAU,GACtB,EAAU,UAAU,CAAC;CACvB,GAAG,CAAC,CAAC;CAuCL,OAAO;EAAE,eApCiE,GACvE,MAAW;GACV,GAAO,OACA,EAAQ,WACX,EAAY,CAAI,GAElB,EAAQ,UAAU,IACX,OAAO,KAAW,aAAa,EAAO,CAAI,IAAI,EACtD;EACH,GACA,CAAC,GAAQ,CAAW,CA0Bb;EAAe,MAvBX,QAAkB;GAC7B,IAAI,EAAW,QAAQ,WAAW,GAAG;GACrC,IAAM,IAAO,EAAW,QAAQ,IAAI;GAKpC,AAJA,GAAO,OACL,EAAU,QAAQ,KAAK,CAAO,GACvB,EACR,GACD,EAAQ,UAAU;EACpB,GAAG,CAAC,CAAM,CAec;EAAM,MAbjB,QAAkB;GAC7B,IAAI,EAAU,QAAQ,WAAW,GAAG;GACpC,IAAM,IAAO,EAAU,QAAQ,IAAI;GAKnC,AAJA,GAAO,OACL,EAAW,QAAQ,KAAK,CAAO,GACxB,EACR,GACD,EAAQ,UAAU;EACpB,GAAG,CAAC,CAAM,CAKoB;EAAM,SAHpB,EAAW,QAAQ,SAAS;EAGC,SAF7B,EAAU,QAAQ,SAAS;CAEU;AACvD"}
1
+ {"version":3,"file":"useUndoRedo.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useUndoRedo.ts"],"sourcesContent":["import { useRef, useCallback, useLayoutEffect, useState } from 'react';\nimport type { FlowDocument } from '../types';\n\nconst MAX_HISTORY = 50;\nconst COALESCE_MS = 300;\n\n/**\n * Time-coalesced undo/redo for FlowDocument.\n * Commands capture history outside React updaters, which React may replay.\n */\nexport function useUndoRedo(\n doc: FlowDocument,\n setDoc: React.Dispatch<React.SetStateAction<FlowDocument>>,\n) {\n const historyRef = useRef<FlowDocument[]>([]);\n const futureRef = useRef<FlowDocument[]>([]);\n const lastPushRef = useRef(-Infinity);\n const currentRef = useRef(doc);\n const [availability, setAvailability] = useState({ canUndo: false, canRedo: false });\n\n // Track externally committed documents too, without writing refs in render.\n useLayoutEffect(() => {\n currentRef.current = doc;\n }, [doc]);\n\n const commit = useCallback((next: FlowDocument) => {\n // Advance immediately so commands in one React batch see preceding commands.\n currentRef.current = next;\n setDoc(next);\n setAvailability({\n canUndo: historyRef.current.length > 0,\n canRedo: futureRef.current.length > 0,\n });\n }, [setDoc]);\n\n const wrappedSetDoc: React.Dispatch<React.SetStateAction<FlowDocument>> = useCallback(\n (action) => {\n const prev = currentRef.current;\n const next = typeof action === 'function' ? action(prev) : action;\n if (next === prev) return;\n\n const now = Date.now();\n // A coalesced edit retains the snapshot from before the first edit.\n if (now - lastPushRef.current >= COALESCE_MS || historyRef.current.length === 0) {\n historyRef.current.push(prev);\n if (historyRef.current.length > MAX_HISTORY) historyRef.current.shift();\n }\n lastPushRef.current = now;\n futureRef.current = [];\n commit(next);\n },\n [commit],\n );\n\n const undo = useCallback(() => {\n if (historyRef.current.length === 0) return;\n const prev = historyRef.current.pop()!;\n futureRef.current.push(currentRef.current);\n lastPushRef.current = -Infinity;\n commit(prev);\n }, [commit]);\n\n const redo = useCallback(() => {\n if (futureRef.current.length === 0) return;\n const next = futureRef.current.pop()!;\n historyRef.current.push(currentRef.current);\n lastPushRef.current = -Infinity;\n commit(next);\n }, [commit]);\n\n return { wrappedSetDoc, undo, redo, ...availability };\n}\n"],"mappings":";;AAGA,IAAM,IAAc,IACd,IAAc;AAMpB,SAAgB,EACd,GACA,GACA;CACA,IAAM,IAAa,EAAuB,CAAC,CAAC,GACtC,IAAY,EAAuB,CAAC,CAAC,GACrC,IAAc,EAAO,SAAS,GAC9B,IAAa,EAAO,CAAG,GACvB,CAAC,GAAc,KAAmB,EAAS;EAAE,SAAS;EAAO,SAAS;CAAM,CAAC;CAGnF,QAAsB;EACpB,EAAW,UAAU;CACvB,GAAG,CAAC,CAAG,CAAC;CAER,IAAM,IAAS,GAAa,MAAuB;EAIjD,AAFA,EAAW,UAAU,GACrB,EAAO,CAAI,GACX,EAAgB;GACd,SAAS,EAAW,QAAQ,SAAS;GACrC,SAAS,EAAU,QAAQ,SAAS;EACtC,CAAC;CACH,GAAG,CAAC,CAAM,CAAC;CAqCX,OAAO;EAAE,eAnCiE,GACvE,MAAW;GACV,IAAM,IAAO,EAAW,SAClB,IAAO,OAAO,KAAW,aAAa,EAAO,CAAI,IAAI;GAC3D,IAAI,MAAS,GAAM;GAEnB,IAAM,IAAM,KAAK,IAAI;GAQrB,CANI,IAAM,EAAY,WAAW,KAAe,EAAW,QAAQ,WAAW,OAC5E,EAAW,QAAQ,KAAK,CAAI,GACxB,EAAW,QAAQ,SAAS,KAAa,EAAW,QAAQ,MAAM,IAExE,EAAY,UAAU,GACtB,EAAU,UAAU,CAAC,GACrB,EAAO,CAAI;EACb,GACA,CAAC,CAAM,CAmBA;EAAe,MAhBX,QAAkB;GAC7B,IAAI,EAAW,QAAQ,WAAW,GAAG;GACrC,IAAM,IAAO,EAAW,QAAQ,IAAI;GAGpC,AAFA,EAAU,QAAQ,KAAK,EAAW,OAAO,GACzC,EAAY,UAAU,WACtB,EAAO,CAAI;EACb,GAAG,CAAC,CAAM,CAUc;EAAM,MARjB,QAAkB;GAC7B,IAAI,EAAU,QAAQ,WAAW,GAAG;GACpC,IAAM,IAAO,EAAU,QAAQ,IAAI;GAGnC,AAFA,EAAW,QAAQ,KAAK,EAAW,OAAO,GAC1C,EAAY,UAAU,WACtB,EAAO,CAAI;EACb,GAAG,CAAC,CAAM,CAEoB;EAAM,GAAG;CAAa;AACtD"}