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.
- package/dist/calendar.js +5 -0
- package/dist/components/Demo/index.js +76 -80
- package/dist/components/Demo/index.js.map +1 -1
- package/dist/components/pro/Calendar/Calendar.js +576 -0
- package/dist/components/pro/Calendar/Calendar.js.map +1 -0
- package/dist/components/pro/Calendar/hooks/useCalendarDrag.js +104 -0
- package/dist/components/pro/Calendar/hooks/useCalendarDrag.js.map +1 -0
- package/dist/components/pro/Calendar/hooks/useCalendarState.js +187 -0
- package/dist/components/pro/Calendar/hooks/useCalendarState.js.map +1 -0
- package/dist/components/pro/Calendar/utils.js +193 -0
- package/dist/components/pro/Calendar/utils.js.map +1 -0
- package/dist/components/pro/Flow/Flow.js +21 -17
- package/dist/components/pro/Flow/Flow.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useClipboard.js +29 -18
- package/dist/components/pro/Flow/hooks/useClipboard.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useFlowImportExport.js +35 -23
- package/dist/components/pro/Flow/hooks/useFlowImportExport.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useKeyboardShortcuts.js +8 -7
- package/dist/components/pro/Flow/hooks/useKeyboardShortcuts.js.map +1 -1
- package/dist/components/pro/Flow/hooks/useUndoRedo.d.ts +3 -3
- package/dist/components/pro/Flow/hooks/useUndoRedo.js +31 -20
- package/dist/components/pro/Flow/hooks/useUndoRedo.js.map +1 -1
- package/dist/components/pro/Gantt/Gantt.js +868 -0
- package/dist/components/pro/Gantt/Gantt.js.map +1 -0
- package/dist/components/pro/Gantt/hooks/useGanttDrag.js +99 -0
- package/dist/components/pro/Gantt/hooks/useGanttDrag.js.map +1 -0
- package/dist/components/pro/Gantt/hooks/useGanttState.js +95 -0
- package/dist/components/pro/Gantt/hooks/useGanttState.js.map +1 -0
- package/dist/components/pro/Gantt/utils.js +236 -0
- package/dist/components/pro/Gantt/utils.js.map +1 -0
- package/dist/dishui.css +1 -1
- package/dist/gantt.js +5 -0
- package/dist/hooks/useCopyToClipboard.d.ts +2 -1
- package/dist/hooks/useCopyToClipboard.js +17 -9
- package/dist/hooks/useCopyToClipboard.js.map +1 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +9 -1
- package/dist/utils/i18n.js +40 -0
- package/dist/utils/i18n.js.map +1 -1
- package/package.json +13 -1
- package/dist/pages/components/Gantt/index.d.ts +0 -3
- package/dist/pages/components/Gantt/index.en.d.ts +0 -3
- 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
|
|
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
|
|
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
|
-
|
|
40
|
+
u(a(t));
|
|
29
41
|
return;
|
|
30
42
|
}
|
|
43
|
+
if (!l(t)) throw Error("Invalid document format");
|
|
31
44
|
let n = t;
|
|
32
|
-
|
|
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
|
-
}, [
|
|
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) ? (
|
|
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
|
-
}, [
|
|
61
|
+
}, [u]),
|
|
50
62
|
importMermaid: o((e) => {
|
|
51
|
-
let t =
|
|
52
|
-
|
|
53
|
-
}, [
|
|
54
|
-
exportMarkdown: o(() =>
|
|
63
|
+
let t = p(e);
|
|
64
|
+
u(t);
|
|
65
|
+
}, [u]),
|
|
66
|
+
exportMarkdown: o(() => f(e), [e]),
|
|
55
67
|
clearDocument: o(() => {
|
|
56
|
-
|
|
57
|
-
}, [
|
|
68
|
+
u(t());
|
|
69
|
+
}, [u])
|
|
58
70
|
};
|
|
59
71
|
}
|
|
60
|
-
function
|
|
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
|
|
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
|
|
127
|
+
n !== void 0 && i.set(e, {
|
|
116
128
|
id: e,
|
|
117
129
|
label: n
|
|
118
|
-
}), s
|
|
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.
|
|
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
|
|
200
|
+
async function m(e) {
|
|
189
201
|
return e.toDataURL("image/png");
|
|
190
202
|
}
|
|
191
|
-
function
|
|
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 {
|
|
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":";;;;;;;AAOA,IAAM,IAAkB;AAExB,SAAS,EAAS,GAAkD;CAClE,OAAyB,OAAO,KAAU,cAAnC,KAA+C,CAAC,MAAM,QAAQ,CAAK;AAC5E;AAEA,SAAS,EAAW,GAAuC;CAEzD,OADI,CAAC,EAAS,CAAK,KAAK,CAAC,EAAS,EAAM,QAAQ,KAAK,CAAC,MAAM,QAAQ,EAAM,KAAK,IAAU,KAClF,EAAM,MAAM,OAAM,MAAM,OAAO,KAAO,QAAQ,MAC/C,EAAM,UAAU,KAAA,KAAa,OAAO,EAAM,SAAU,aACrD,OAAO,OAAO,EAAM,QAAQ,CAAC,CAAC,OAAM,MAAU,EAAS,CAAM,KAC3D,MAAM,QAAQ,EAAO,KAAK,KAAK,MAAM,QAAQ,EAAO,KAAK,KAAK,MAAM,QAAQ,EAAO,KAAK,CAAC;AAClG;AAaA,SAAgB,EAAiB,GAA2B,IAAW,aAAqB;CAE1F,QADc,KAAS,GAAA,CAAI,KAAK,CAAC,CAAC,QAAQ,kBAAkB,GAAG,CAAC,CAAC,QAAQ,QAAQ,GAAG,CAAC,CAAC,KAC/E,KAAQ;AACjB;AAMA,SAAgB,IAAsB;CACpC,IAAM,EAAE,QAAK,WAAQ,oBAAiB,EAAe,GAE/C,IAAkB,GAAa,MAAuB;EAE1D,AADA,EAAO,CAAI,GACX,EAAa;GAAE,yBAAS,IAAI,IAAI;GAAG,yBAAS,IAAI,IAAI;GAAG,yBAAS,IAAI,IAAI;EAAE,CAAC;CAC7E,GAAG,CAAC,GAAQ,CAAY,CAAC;CA+DzB,OAAO;EAAE,YA7DU,QAA0B;GAC3C,IAAM,IAAiC;IACrC,SAAS;IACT,UAAU,EAAI;IACd,OAAO,EAAI;IACX,OAAO,EAAI;GACb;GACA,OAAO,KAAK,UAAU,GAAY,MAAM,CAAC;EAC3C,GAAG,CAAC,CAAG,CAqDE;EAAY,YAnDF,GAAa,MAAiB;GAC/C,IAAI;IACF,IAAM,IAAS,KAAK,MAAM,CAAI;IAG9B,IAAI,EAAc,CAAM,GAAG;KACzB,EAAgB,EAAsB,CAAM,CAAC;KAC7C;IACF;IACA,IAAI,CAAC,EAAW,CAAM,GAAG,MAAU,MAAM,yBAAyB;IAClE,IAAM,IAAM;IACZ,EAAgB;KACd,UAAU,EAAI;KACd,OAAO,EAAI;KACX,OAAO,EAAI;IACb,CAAC;GACH,SAAS,GAAK;IACZ,QAAQ,MAAM,0BAA0B,CAAG;GAC7C;EACF,GAAG,CAAC,CAAe,CAgCE;EAAY,eAzBX,GAAa,MAAoC;GACrE,IAAI;IACF,IAAM,IAAO,OAAO,KAAU,WAAW,KAAK,MAAM,CAAK,IAAI;IAG7D,OAFK,EAAc,CAAI,KACvB,EAAgB,EAAsB,CAAI,CAAC,GACpC,MAF0B;GAGnC,SAAS,GAAK;IAEZ,OADA,QAAQ,MAAM,kCAAkC,CAAG,GAC5C;GACT;EACF,GAAG,CAAC,CAAe,CAec;EAAe,eAb1B,GAAa,MAAwB;GACzD,IAAM,IAAM,EAAsB,CAAW;GAC7C,EAAgB,CAAG;EACrB,GAAG,CAAC,CAAe,CAU6B;EAAe,gBARxC,QACd,EAAmB,CAAG,GAC5B,CAAC,CAAG,CAMwD;EAAgB,eAJzD,QAAkB;GACtC,EAAgB,EAAoB,CAAC;EACvC,GAAG,CAAC,CAAe,CAE4D;CAAc;AAC/F;AAQA,SAAS,EAAmB,GAA2B;CACrD,IAAM,IAAkB,CAAC,GAEnB,KAAa,MAAwB;EACzC,IAAM,KAAO,EAAE,KAAK,SAAS,GAAA,CAAI,KAAK;EAGtC,OAFI,IAAY,EAAI,QAAQ,QAAQ,GAAG,IACnC,EAAE,SAAS,cAAoB,IAAI,EAAE,KAAK,aAAa,QAAQ,KAC5D,IAAI,EAAE,KAAK;CACpB,GAEM,KAAgB,GAAkB,GAAsB,MAA8B;EAC1F,IAAM,IAAS,EAAI,SAAS;EAC5B,IAAI,CAAC,GAAQ;EAGb,IAAM,IAAO,IAAI,IAAI,EAAO,MAAM,KAAK,MAAM,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,GACjD,oBAAa,IAAI,IAAoC;EAC3D,KAAK,IAAM,KAAK,EAAO,OAAO;GAC5B,IAAM,IAAM,EAAE,YAAY,EAAK,IAAI,EAAE,QAAQ,IAAI,EAAE,WAAW,KAAA,GACxD,IAAO,EAAW,IAAI,CAAG,KAAK,CAAC;GAErC,AADA,EAAK,KAAK,CAAC,GACX,EAAW,IAAI,GAAK,CAAI;EAC1B;EAEA,IAAM,KAAY,GAAgB,MAAkB;GAClD,IAAM,IAAS,KAAK,OAAO,CAAK,GAE1B,IADU,EAAK,KAAK,WAAW,EAAK,SAAS,eAAe,EAAK,SAAS,kBACvD,KAAK,EAAU,CAAI,EAAE,MAAM,EAAU,CAAI;GAIlE,IAHA,EAAM,KAAK,GAAG,EAAO,IAAI,GAAQ,GAG7B,EAAK,SAAS,eAAe,EAAK,KAAK,WAAW;IACpD,IAAM,IAAO,EAAK,KAAK;IACvB,IAAI,MAAS,UAAU,EAAK,KAAK,YAAY;KAC3C,IAAM,IAAM,EAAK,KAAK,YAChB,IAAO,EAAI,QAAQ,KAAK,MAAM,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC,KAAK,IAAI;KAC/D,EAAM,KAAK,GAAG,EAAO,UAAU,EAAI,KAAK,OAAO,SAAS,EAAK,EAAE;IACjE,OAAO,IAAI,EAAK,KAAK,gBAAgB,MAAS,WAAW,MAAS,WAAW;KAC3E,IAAM,IAAU,EAAK,KAAK,aAAa,KAAK,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,EAAE,CAAC,MAAM,GAAG,EAAE;KACxE,AAAI,KAAS,EAAM,KAAK,GAAG,EAAO,MAAM,EAAK,IAAI,GAAS;IAC5D;GACF;GAGA,IAAM,IAAO,EAAW,IAAI,EAAK,EAAE,KAAK,CAAC;GACzC,KAAK,IAAM,KAAO,GAAM,EAAS,GAAK,IAAQ,CAAC;GAG/C,IAAM,IAAgB,EAAK,KAAK;GAChC,AAAI,KAAiB,EAAI,SAAS,MAAkB,CAAC,EAAa,IAAI,CAAa,MACjF,EAAa,IAAI,CAAa,GAC9B,EAAa,GAAe,IAAe,GAAG,CAAY;EAE9D,GAEM,IAAQ,EAAW,IAAI,KAAA,CAAS,KAAK,CAAC;EAC5C,KAAK,IAAM,KAAQ,GAAO,EAAS,GAAM,CAAC;EAG1C,IAAI,EAAO,MAAM,SAAS,GAAG;GAE3B,AADA,EAAM,KAAK,EAAE,GACb,EAAM,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,GAAG,IAAe,CAAC,CAAC,EAAE,MAAM;GAC9D,KAAK,IAAM,KAAQ,EAAO,OAAO;IAC/B,IAAM,IAAO,EAAK,IAAI,EAAK,MAAM,GAC3B,IAAK,EAAK,IAAI,EAAK,MAAM;IAC/B,IAAI,CAAC,KAAQ,CAAC,GAAI;IAClB,IAAM,IAAY,EAAK,KAAK,YAAY,KAAK,EAAK,KAAK,UAAU,KAAK;IACtE,EAAM,KAAK,KAAK,EAAU,CAAI,EAAE,KAAK,EAAU,CAAE,IAAI,GAAW;GAClE;EACF;CACF,GAEM,oBAAO,IAAI,IAAY,CAAC,CAAc,CAAC;CAG7C,AAFA,EAAM,KAAK,OAAO,GAClB,EAAM,KAAK,EAAE,GACb,EAAa,GAAgB,GAAG,CAAI;CAGpC,KAAK,IAAM,KAAY,OAAO,KAAK,EAAI,QAAQ,GACzC,EAAK,IAAI,CAAQ,MACrB,EAAK,IAAI,CAAQ,GACjB,EAAM,KAAK,EAAE,GACb,EAAM,KAAK,SAAS,GAAU,GAC9B,EAAM,KAAK,EAAE,GACb,EAAa,GAAU,GAAG,CAAI;CAGhC,OAAO,EAAM,KAAK,IAAI,CAAC,CAAC,QAAQ,WAAW,MAAM,CAAC,CAAC,KAAK,IAAI;AAC9D;AAMA,SAAS,EAAsB,GAA4B;CACzD,IAAM,IAAQ,EAAK,MAAM,IAAI,CAAC,CAAC,KAAI,MAAK,EAAE,KAAK,CAAC,CAAC,CAAC,QAAO,MAAK,KAAK,CAAC,EAAE,WAAW,IAAI,CAAC,GAEhF,oBAAU,IAAI,IAA2C,GACzD,IAA6D,CAAC,GAEhE,IAAe;CAEnB,KAAK,IAAM,KAAQ,GAAO;EAExB,IAAI,gCAAgC,KAAK,CAAI,GAAG;GAC9C,IAAe;GACf;EACF;EACA,IAAI,yBAAyB,KAAK,CAAI,GAAG;EAGzC,IAAM,IAAY,EAAK,MAAM,wFAAwF;EACrH,IAAI,GAAW;GACb,IAAM,GAAG,GAAQ,KAAa,GAAW,GAAM,KAAW;GAK1D,AAJI,MAAc,KAAA,KAAW,EAAQ,IAAI,GAAQ;IAAE,IAAI;IAAQ,OAAO;GAAU,CAAC,GAC7E,MAAY,KAAA,KAAW,EAAQ,IAAI,GAAM;IAAE,IAAI;IAAM,OAAO;GAAQ,CAAC,GACpE,EAAQ,IAAI,CAAM,KAAG,EAAQ,IAAI,GAAQ;IAAE,IAAI;IAAQ,OAAO;GAAO,CAAC,GACtE,EAAQ,IAAI,CAAI,KAAG,EAAQ,IAAI,GAAM;IAAE,IAAI;IAAM,OAAO;GAAK,CAAC,GACnE,EAAM,KAAK;IAAE,MAAM;IAAQ,IAAI;IAAM,OAAO,GAAW,KAAK;GAAE,CAAC;GAC/D;EACF;EAGA,IAAM,IAAY,EAAK,MAAM,oBAAoB;EACjD,IAAI,GAAW;GACb,IAAM,GAAG,GAAI,KAAS;GACtB,EAAQ,IAAI,GAAI;IAAE;IAAI;GAAM,CAAC;EAC/B;CACF;CAGA,IAAM,IAAU,CAAC,GAAG,EAAQ,KAAK,CAAC,GAC5B,IAAO,IAAe,EAAQ,SAAS,KAAK,KAAK,KAAK,KAAK,EAAQ,MAAM,CAAC,GAC1E,IAAU;EAAE,GAAG;EAAK,GAAG;CAAI,GAE3B,IAAoB,EAAQ,KAAK,GAAI,MAAM;EAC/C,IAAM,IAAM,IAAI,GACV,IAAM,KAAK,MAAM,IAAI,CAAI,GACzB,IAAO,EAAQ,IAAI,CAAE;EAC3B,OAAO;GACL,IAAI,EAAK;GACT,MAAM;GACN,UAAU;IAAE,GAAG,MAAM,IAAM,EAAQ;IAAG,GAAG,MAAM,IAAM,EAAQ;GAAE;GAC/D,OAAO;GACP,QAAQ;GACR,MAAM;IACJ,OAAO,EAAK;IACZ,MAAM;IACN,QAAQ;IACR,aAAa;IACb,OAAO;IACP,UAAU;GACZ;EACF;CACF,CAAC,GAEK,IAAwB,EAAM,KAAK,OAAO;EAC9C,IAAI,EAAW,MAAM;EACrB,QAAQ,EAAE;EACV,QAAQ,EAAE;EACV,MAAM;EACN,MAAM;GACJ,aAAa;GACb,aAAa;GACb,WAAW;GACX,WAAW;GACX,WAAW,EAAE;EACf;CACF,EAAE;CAEF,OAAO;EACL,UAAU,GACP,IAAiB;GAAE;GAAO,OAAO;GAAW,OAAO,CAAC;EAAE,EACzD;EACA,OAAO,CAAC,CAAc;CACxB;AACF;AAKA,eAAsB,EAAkB,GAA4C;CAClF,OAAO,EAAO,UAAU,WAAW;AACrC;AAKA,SAAgB,EAAgB,GAAiB,GAAwB;CACvE,IAAM,IAAI,SAAS,cAAc,GAAG;CAKpC,AAJA,EAAE,OAAO,GACT,EAAE,WAAW,GACb,SAAS,KAAK,YAAY,CAAC,GAC3B,EAAE,MAAM,GACR,SAAS,KAAK,YAAY,CAAC;AAC7B"}
|
|
@@ -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 {
|
|
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
|
|
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" && (
|
|
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
|
-
}),
|
|
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
|
-
|
|
29
|
-
|
|
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 {
|
|
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
|
-
*
|
|
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,
|
|
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
|
|
4
|
-
function
|
|
5
|
-
let
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
12
|
-
|
|
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 (
|
|
15
|
-
let e =
|
|
16
|
-
|
|
17
|
-
}, [
|
|
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 (
|
|
20
|
-
let e =
|
|
21
|
-
|
|
22
|
-
}, [
|
|
23
|
-
|
|
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 {
|
|
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,
|
|
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"}
|