dishui 0.0.68 → 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/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/package.json +2 -1
|
@@ -344,6 +344,10 @@ function oe({ pixiRef: e, exportPdfRef: t }) {
|
|
|
344
344
|
var U = P(function({ initialDocument: e, onChange: t, width: n, height: r, readOnly: o = !1, onBack: c, onExportPNG: u, onPublish: d, showMiniMap: f = !0, showGrid: v = !0, gridSize: y = 20, className: b, showAI: x = !0, showAIConfig: S = !0, aiConfig: C }, E) {
|
|
345
345
|
let [O, A] = z(() => re(e ?? i())), [j, M] = z(ie), N = R(null), P = R(null), L = R(null), H = R(null), U = te(H), { wrappedSetDoc: W, undo: G, redo: K, canUndo: q, canRedo: J } = ee(O, A), Y = R(null), X = R(null), Z = R(null), Q = R(null), de = R(t);
|
|
346
346
|
de.current = t;
|
|
347
|
+
let fe = R(O);
|
|
348
|
+
I(() => {
|
|
349
|
+
fe.current !== O && (fe.current = O, o || de.current?.(O));
|
|
350
|
+
}, [O, o]);
|
|
347
351
|
let $ = R(O);
|
|
348
352
|
$.current = O, ne(E, () => ({
|
|
349
353
|
exportPNG: async (e) => {
|
|
@@ -352,24 +356,24 @@ var U = P(function({ initialDocument: e, onChange: t, width: n, height: r, readO
|
|
|
352
356
|
},
|
|
353
357
|
getDocument: () => $.current
|
|
354
358
|
}), []);
|
|
355
|
-
let
|
|
359
|
+
let pe = F((e) => {
|
|
356
360
|
W((t) => {
|
|
357
361
|
let n = typeof e == "function" ? e(t) : e;
|
|
358
|
-
return o ? n.canvases === t.canvases && n.title === t.title ? n : t :
|
|
362
|
+
return o ? n.canvases === t.canvases && n.title === t.title ? n : t : n;
|
|
359
363
|
});
|
|
360
|
-
}, [W, o]),
|
|
364
|
+
}, [W, o]), me = F(() => {
|
|
361
365
|
o || G();
|
|
362
|
-
}, [o, G]),
|
|
366
|
+
}, [o, G]), he = F(() => {
|
|
363
367
|
o || K();
|
|
364
|
-
}, [o, K]),
|
|
368
|
+
}, [o, K]), ge = F((e) => {
|
|
365
369
|
N.current = e, P.current && !L.current && (L.current = new k(P.current));
|
|
366
|
-
}, []),
|
|
370
|
+
}, []), _e = F(async () => {
|
|
367
371
|
if (u) return void u();
|
|
368
372
|
let e = N.current;
|
|
369
373
|
if (!e || e.destroyed) return;
|
|
370
374
|
let t = await e.exportPNG(16777215);
|
|
371
375
|
t && m(t, `${h($.current.title)}.png`);
|
|
372
|
-
}, [u]),
|
|
376
|
+
}, [u]), ve = F(async () => {
|
|
373
377
|
await Q.current?.();
|
|
374
378
|
}, []);
|
|
375
379
|
I(() => {
|
|
@@ -391,17 +395,17 @@ var U = P(function({ initialDocument: e, onChange: t, width: n, height: r, readO
|
|
|
391
395
|
e = !0, t && !t.destroyed && t.app.ticker.remove(n);
|
|
392
396
|
};
|
|
393
397
|
}, [O]);
|
|
394
|
-
let
|
|
398
|
+
let ye = {
|
|
395
399
|
width: n ?? "100%",
|
|
396
400
|
height: r ?? "100%"
|
|
397
401
|
};
|
|
398
402
|
return /* @__PURE__ */ B("div", {
|
|
399
403
|
ref: H,
|
|
400
404
|
className: `flow-container ${b ?? ""}`,
|
|
401
|
-
style:
|
|
405
|
+
style: ye,
|
|
402
406
|
children: /* @__PURE__ */ B(a, {
|
|
403
407
|
doc: O,
|
|
404
|
-
setDoc:
|
|
408
|
+
setDoc: pe,
|
|
405
409
|
selection: j,
|
|
406
410
|
setSelection: M,
|
|
407
411
|
children: /* @__PURE__ */ B(s, {
|
|
@@ -415,8 +419,8 @@ var U = P(function({ initialDocument: e, onChange: t, width: n, height: r, readO
|
|
|
415
419
|
showAIConfig: S,
|
|
416
420
|
children: [
|
|
417
421
|
/* @__PURE__ */ B(ae, {
|
|
418
|
-
onUndo:
|
|
419
|
-
onRedo:
|
|
422
|
+
onUndo: me,
|
|
423
|
+
onRedo: he,
|
|
420
424
|
groupRef: Y,
|
|
421
425
|
geoGroupRef: X,
|
|
422
426
|
ungroupRef: Z
|
|
@@ -430,19 +434,19 @@ var U = P(function({ initialDocument: e, onChange: t, width: n, height: r, readO
|
|
|
430
434
|
toolbar: o ? null : /* @__PURE__ */ B(g, {
|
|
431
435
|
compact: U,
|
|
432
436
|
onBack: c,
|
|
433
|
-
onUndo:
|
|
434
|
-
onRedo:
|
|
437
|
+
onUndo: me,
|
|
438
|
+
onRedo: he,
|
|
435
439
|
canUndo: q,
|
|
436
440
|
canRedo: J,
|
|
437
441
|
onGroup: () => Y.current?.(),
|
|
438
442
|
onGeoGroup: () => X.current?.(),
|
|
439
443
|
onUngroup: () => Z.current?.(),
|
|
440
|
-
onExportPNG:
|
|
441
|
-
onExportPDF:
|
|
444
|
+
onExportPNG: _e,
|
|
445
|
+
onExportPDF: ve,
|
|
442
446
|
onPublish: d
|
|
443
447
|
}),
|
|
444
448
|
children: [
|
|
445
|
-
/* @__PURE__ */ B(p, { onPixiReady:
|
|
449
|
+
/* @__PURE__ */ B(p, { onPixiReady: ge }),
|
|
446
450
|
/* @__PURE__ */ B(T, {}),
|
|
447
451
|
/* @__PURE__ */ V(se, { children: [!o && /* @__PURE__ */ B(_, {}), /* @__PURE__ */ B(D, {})] }),
|
|
448
452
|
!o && /* @__PURE__ */ B(ue, {}),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Flow.js","names":[],"sources":["../../../../src/components/pro/Flow/Flow.tsx"],"sourcesContent":["import React, { useState, useCallback, useRef, useEffect, useMemo, forwardRef, useImperativeHandle } from 'react';\nimport { FlowProvider, useFlowContext } from './context/FlowContext';\nimport { FlowUIProvider, useFlowUI } from './context/FlowUIContext';\nimport { FlowAIProvider } from './context/FlowAIContext';\nimport FlowCanvas from './FlowCanvas';\nimport FlowToolbar, { FlowZoomControls } from './panels/FlowToolbar';\nimport { ShapePaletteContent, SvgStampContent, MediaStampContent } from './panels/ShapePalette';\nimport { InspectorPanelContent } from './panels/InspectorPanel';\nimport { OutlinePanelContent } from './panels/OutlinePanel';\nimport { AIPanelContent } from './panels/AIPanel';\nimport { PresentationMode } from './panels/PresentationMode';\nimport { MediaOverlay } from './nodes/MediaOverlay';\nimport FlowPanelGroup, { type PanelTab } from './panels/FlowPanelGroup';\nimport type { FlowPanelDock } from './context/FlowUIContext';\nimport Breadcrumb from './panels/Breadcrumb';\nimport ContextMenu from './panels/ContextMenu';\nimport type { ContextMenuItem } from './panels/ContextMenu';\nimport Select from '../../Select';\nimport { MiniMapRenderer } from './canvas/MiniMapRenderer';\nimport { CodeEditor, type CodeEditorHandle } from '../CodeEditor';\nimport { EditorView } from '@codemirror/view';\nimport { useUndoRedo } from './hooks/useUndoRedo';\nimport { useClipboard } from './hooks/useClipboard';\nimport { useCompactMode } from './hooks/useCompactMode';\nimport { downloadDataURL, safeFileBaseName } from './hooks/useFlowImportExport';\nimport { usePdfExport } from './hooks/usePdfExport';\nimport { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts';\nimport { useMindMap } from './hooks/useMindMap';\nimport { useDeleteSelection } from './hooks/useDeleteSelection';\nimport { createEmptyDocument, ROOT_CANVAS_ID, DEFAULT_NODE_DATA } from './constants';\nimport { normalizeEdgeHandles } from './edges/edgePaths';\nimport { generateId } from './nodes/nodeUtils';\nimport type { FlowProps, FlowDocument, FlowNode, SelectionState, FlowCanvasData } from './types';\nimport type { PixiApp } from './canvas/PixiApp';\nimport './styles.css';\n\nfunction normalizeDocument(doc: FlowDocument): FlowDocument {\n let changed = false;\n const canvases = { ...doc.canvases };\n for (const [id, canvas] of Object.entries(canvases)) {\n const nodes = canvas.nodes.map(node => {\n if (node.type !== 'shapeSvg') return node;\n const legacyPlacement = node.data.labelPlacement as string | undefined;\n const labelPlacement = legacyPlacement === 'above'\n ? 'top'\n : legacyPlacement === 'center'\n ? 'middle'\n : legacyPlacement === 'below'\n ? 'bottom'\n : node.data.labelPlacement;\n if (labelPlacement === node.data.labelPlacement) return node;\n changed = true;\n return { ...node, data: { ...node.data, labelPlacement } };\n });\n // Mindmap nodes/edges created before theme tokens (≤ 0.0.56) persisted\n // literal colors, so they never followed theme switches. That exact palette\n // was only ever emitted by the old mindmap creator, so remap it to the same\n // tokens the current creator uses.\n let themedNodesChanged = false;\n const themedNodes = nodes.map(node => {\n if (!node.data.isMindMapNode) return node;\n const fill = node.data.fill === '#ffffff' || node.data.fill === '#eef2ff'\n ? 'theme:base-100'\n : node.data.fill;\n const stroke = node.data.stroke === '#6366f1'\n ? 'theme:base-content'\n : node.data.stroke === '#4f46e5'\n ? 'theme:primary'\n : node.data.stroke;\n if (fill === node.data.fill && stroke === node.data.stroke) return node;\n themedNodesChanged = true;\n return { ...node, data: { ...node.data, fill, stroke } };\n });\n const nodeMap = new Map<string, FlowNode>();\n for (const node of themedNodes) nodeMap.set(node.id, node);\n let edgesChanged = false;\n const edges = normalizeEdgeHandles(canvas.edges, nodeMap).map(edge => {\n if (edge.data.strokeColor !== '#94a3b8') return edge;\n const source = nodeMap.get(edge.source);\n const target = nodeMap.get(edge.target);\n if (!source?.data.isMindMapNode || !target?.data.isMindMapNode) return edge;\n edgesChanged = true;\n return { ...edge, data: { ...edge.data, strokeColor: 'theme:base-content' } };\n });\n if (themedNodesChanged || edgesChanged) changed = true;\n if (themedNodes !== nodes || edges !== canvas.edges) {\n canvases[id] = { ...canvas, nodes: themedNodes, edges };\n }\n }\n return changed ? { ...doc, canvases } : doc;\n}\n\nconst EMPTY_SELECTION: SelectionState = {\n nodeIds: new Set(),\n edgeIds: new Set(),\n lineIds: new Set(),\n};\n\nfunction FlowKeyboardShortcutsWire({ onUndo, onRedo, groupRef, geoGroupRef, ungroupRef }: {\n onUndo: () => void;\n onRedo: () => void;\n groupRef: React.MutableRefObject<(() => void) | null>;\n geoGroupRef: React.MutableRefObject<(() => void) | null>;\n ungroupRef: React.MutableRefObject<(() => void) | null>;\n}) {\n const { selection, setSelection, currentCanvas, setDoc, currentCanvasId } = useFlowContext();\n const { viewport } = useFlowUI();\n const { copy, cut, paste, duplicate } = useClipboard();\n const deleteSelected = useDeleteSelection();\n\n const selectAll = useCallback(() => {\n setSelection({\n nodeIds: new Set(currentCanvas.nodes.map(n => n.id)),\n edgeIds: new Set(currentCanvas.edges.map(e => e.id)),\n lineIds: new Set(currentCanvas.lines.map(l => l.id)),\n });\n }, [currentCanvas, setSelection]);\n\n const createGroup = useCallback((type: 'flowGroup' | 'geometryGroup') => {\n const nodeIds = selection.nodeIds;\n const lineIds = selection.lineIds;\n const totalCount = nodeIds.size + lineIds.size;\n if (totalCount < 2) return;\n const nodes = currentCanvas.nodes.filter(n => nodeIds.has(n.id));\n const lines = currentCanvas.lines.filter(l => lineIds.has(l.id));\n const PAD = 20;\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const n of nodes) {\n minX = Math.min(minX, n.position.x);\n minY = Math.min(minY, n.position.y);\n maxX = Math.max(maxX, n.position.x + n.width);\n maxY = Math.max(maxY, n.position.y + n.height);\n }\n for (const l of lines) {\n for (const p of l.points) {\n minX = Math.min(minX, p.x); minY = Math.min(minY, p.y);\n maxX = Math.max(maxX, p.x); maxY = Math.max(maxY, p.y);\n }\n }\n const isGeo = type === 'geometryGroup';\n const groupId = generateId('group');\n const groupNode: FlowNode = {\n id: groupId,\n type,\n position: { x: minX - PAD, y: minY - PAD },\n width: maxX - minX + PAD * 2,\n height: maxY - minY + PAD * 2,\n data: {\n ...DEFAULT_NODE_DATA,\n label: isGeo ? 'Geo Group' : 'Group',\n fill: isGeo ? '#fefce8' : '#f0f9ff',\n stroke: '#9ca3af', strokeWidth: 1.5,\n borderStyle: isGeo ? 'dashed' : 'dotted',\n isGroup: true,\n },\n };\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: { ...prev.canvases, [currentCanvasId]: {\n ...canvas,\n nodes: isGeo\n ? [groupNode, ...canvas.nodes]\n : [groupNode, ...canvas.nodes.map(n => nodeIds.has(n.id) ? { ...n, parentId: groupId } : n)],\n lines: isGeo\n ? canvas.lines\n : canvas.lines.map(l => lineIds.has(l.id) ? { ...l, parentId: groupId } : l),\n }},\n };\n });\n setSelection({ nodeIds: new Set([groupId]), edgeIds: new Set(), lineIds: new Set() });\n }, [selection, currentCanvas, setDoc, currentCanvasId, setSelection]);\n\n const groupSelected = useCallback(() => createGroup('flowGroup'), [createGroup]);\n const geoGroupSelected = useCallback(() => createGroup('geometryGroup'), [createGroup]);\n\n const ungroupSelected = useCallback(() => {\n const ids = selection.nodeIds;\n if (ids.size === 0) return;\n const groupNodeIds = new Set<string>();\n const childNodeIds = new Set<string>();\n const childLineIds = new Set<string>();\n for (const node of currentCanvas.nodes) {\n if (ids.has(node.id) && (node.type === 'flowGroup' || node.type === 'geometryGroup' || node.data.isGroup)) {\n groupNodeIds.add(node.id);\n }\n }\n if (groupNodeIds.size === 0) return;\n for (const node of currentCanvas.nodes) {\n if (node.parentId && groupNodeIds.has(node.parentId)) {\n childNodeIds.add(node.id);\n }\n }\n for (const line of currentCanvas.lines) {\n if (line.parentId && groupNodeIds.has(line.parentId)) {\n childLineIds.add(line.id);\n }\n }\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: { ...prev.canvases, [currentCanvasId]: {\n ...canvas,\n nodes: canvas.nodes\n .filter(n => !groupNodeIds.has(n.id))\n .map(n => childNodeIds.has(n.id) ? { ...n, parentId: undefined } : n),\n lines: canvas.lines\n .map(l => childLineIds.has(l.id) ? { ...l, parentId: undefined } : l),\n }},\n };\n });\n setSelection({ nodeIds: childNodeIds, edgeIds: new Set(), lineIds: childLineIds });\n }, [selection, currentCanvas, setDoc, currentCanvasId, setSelection]);\n\n groupRef.current = groupSelected;\n geoGroupRef.current = geoGroupSelected;\n ungroupRef.current = ungroupSelected;\n\n const changeZIndex = useCallback((mode: 'front' | 'back' | 'up' | 'down') => {\n const nodeIds = selection.nodeIds;\n const lineIds = selection.lineIds;\n if (nodeIds.size === 0 && lineIds.size === 0) return;\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n const nodeAllZ = canvas.nodes.map(n => n.zIndex ?? 0);\n const nodeMaxZ = nodeAllZ.length > 0 ? Math.max(...nodeAllZ) : 0;\n const nodeMinZ = nodeAllZ.length > 0 ? Math.min(...nodeAllZ) : 0;\n const lineAllZ = canvas.lines.map(l => l.zIndex ?? 0);\n const lineMaxZ = lineAllZ.length > 0 ? Math.max(...lineAllZ) : 0;\n const lineMinZ = lineAllZ.length > 0 ? Math.min(...lineAllZ) : 0;\n const applyZ = (z: number, maxZ: number, minZ: number) => {\n if (mode === 'front') return maxZ + 1;\n if (mode === 'back') return minZ - 1;\n if (mode === 'up') return z + 1;\n return z - 1;\n };\n return {\n ...prev,\n canvases: { ...prev.canvases, [currentCanvasId]: {\n ...canvas,\n nodes: canvas.nodes.map(n => nodeIds.has(n.id) ? { ...n, zIndex: applyZ(n.zIndex ?? 0, nodeMaxZ, nodeMinZ) } : n),\n lines: canvas.lines.map(l => lineIds.has(l.id) ? { ...l, zIndex: applyZ(l.zIndex ?? 0, lineMaxZ, lineMinZ) } : l),\n }},\n };\n });\n }, [selection, setDoc, currentCanvasId]);\n\n useEffect(() => {\n const handlePaste = (event: ClipboardEvent) => {\n const target = event.target as HTMLElement | null;\n if (target?.tagName === 'INPUT' || target?.tagName === 'TEXTAREA' || target?.isContentEditable) return;\n\n const imageItem = Array.from(event.clipboardData?.items ?? []).find(item => item.type.startsWith('image/'));\n if (!imageItem) {\n event.preventDefault();\n paste();\n return;\n }\n\n const file = imageItem.getAsFile();\n if (!file) return;\n event.preventDefault();\n const reader = new FileReader();\n reader.onload = () => {\n if (typeof reader.result !== 'string') return;\n const selectedImage = selection.nodeIds.size === 1\n ? currentCanvas.nodes.find(node => selection.nodeIds.has(node.id) && node.type === 'mediaNode' && node.data.mediaKind === 'image')\n : undefined;\n\n if (selectedImage) {\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...canvas,\n nodes: canvas.nodes.map(node => node.id === selectedImage.id\n ? { ...node, data: { ...node.data, mediaContent: reader.result as string } }\n : node),\n },\n },\n };\n });\n return;\n }\n\n const id = generateId('node');\n const width = 320;\n const height = 220;\n const screenWidth = window.innerWidth;\n const screenHeight = window.innerHeight;\n const centerX = (screenWidth / 2 - viewport.x) / viewport.zoom;\n const centerY = (screenHeight / 2 - viewport.y) / viewport.zoom;\n const highestZ = Math.max(0, ...currentCanvas.nodes.map(node => node.zIndex ?? 0));\n const imageNode: FlowNode = {\n id,\n type: 'mediaNode',\n position: { x: centerX - width / 2, y: centerY - height / 2 },\n width,\n height,\n zIndex: highestZ + 1,\n data: {\n ...DEFAULT_NODE_DATA,\n label: file.name || 'Image',\n fill: 'theme:base-100',\n stroke: 'theme:base-content',\n strokeWidth: 1.5,\n color: 'theme:base-content',\n fontSize: 13,\n borderStyle: 'solid',\n mediaKind: 'image',\n mediaContent: reader.result,\n },\n };\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: { ...canvas, nodes: [...canvas.nodes, imageNode] },\n },\n };\n });\n setSelection({ nodeIds: new Set([id]), edgeIds: new Set(), lineIds: new Set() });\n };\n reader.readAsDataURL(file);\n };\n\n window.addEventListener('paste', handlePaste);\n return () => window.removeEventListener('paste', handlePaste);\n }, [currentCanvas, currentCanvasId, paste, selection, setDoc, setSelection, viewport]);\n\n const actions = useMemo(() => ({\n undo: onUndo, redo: onRedo, copy, cut, paste, duplicate, deleteSelected, selectAll,\n group: groupSelected, geoGroup: geoGroupSelected, ungroup: ungroupSelected,\n bringToFront: () => changeZIndex('front'),\n bringForward: () => changeZIndex('up'),\n sendBackward: () => changeZIndex('down'),\n sendToBack: () => changeZIndex('back'),\n }), [onUndo, onRedo, copy, cut, paste, duplicate, deleteSelected, selectAll, groupSelected, geoGroupSelected, ungroupSelected, changeZIndex]);\n\n useKeyboardShortcuts(actions);\n useMindMap();\n return null;\n}\n\n/**\n * Bridges the anchor→PDF export (which needs canvas/viewport context) out to the\n * toolbar in the outer Flow component via a ref. Renders nothing.\n */\nfunction PdfExportWire({\n pixiRef,\n exportPdfRef,\n}: {\n pixiRef: React.RefObject<PixiApp | null>;\n exportPdfRef: React.MutableRefObject<(() => Promise<boolean>) | null>;\n}) {\n const exportPdf = usePdfExport(pixiRef);\n exportPdfRef.current = exportPdf;\n return null;\n}\n\n/** Imperative handle exposed via a ref on the `<Flow>` component. */\nexport interface FlowHandle {\n /** Export the current diagram to a PNG data URL (null if unavailable). */\n exportPNG: (backgroundColor?: number) => Promise<string | null>;\n /** Get the current document. */\n getDocument: () => FlowDocument;\n}\n\nconst Flow = forwardRef<FlowHandle, FlowProps>(function Flow({\n initialDocument,\n onChange,\n width,\n height,\n readOnly = false,\n onBack,\n onExportPNG,\n onPublish,\n showMiniMap = true,\n showGrid = true,\n gridSize = 20,\n className,\n showAI = true,\n showAIConfig = true,\n aiConfig,\n}: FlowProps, ref) {\n const [doc, setDoc] = useState<FlowDocument>(() => normalizeDocument(initialDocument ?? createEmptyDocument()));\n const [selection, setSelection] = useState<SelectionState>(EMPTY_SELECTION);\n const pixiRef = useRef<PixiApp | null>(null);\n const miniMapCanvasRef = useRef<HTMLCanvasElement>(null);\n const miniMapRef = useRef<MiniMapRenderer | null>(null);\n // Small-screen detection (container width based, see useCompactMode).\n const rootRef = useRef<HTMLDivElement>(null);\n const compact = useCompactMode(rootRef);\n\n const { wrappedSetDoc, undo, redo, canUndo, canRedo } = useUndoRedo(doc, setDoc);\n const groupRef = useRef<(() => void) | null>(null);\n const geoGroupRef = useRef<(() => void) | null>(null);\n const ungroupRef = useRef<(() => void) | null>(null);\n const exportPdfRef = useRef<(() => Promise<boolean>) | null>(null);\n\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n\n // Keep the latest doc in a ref for the imperative handle.\n const docRef = useRef(doc);\n docRef.current = doc;\n\n useImperativeHandle(ref, () => ({\n exportPNG: async (backgroundColor?: number) => {\n const pixi = pixiRef.current;\n if (!pixi || pixi.destroyed) return null;\n return pixi.exportPNG(backgroundColor);\n },\n getDocument: () => docRef.current,\n }), []);\n\n const outerSetDoc: React.Dispatch<React.SetStateAction<FlowDocument>> = useCallback(\n (action) => {\n wrappedSetDoc(prev => {\n const next = typeof action === 'function' ? action(prev) : action;\n if (readOnly) {\n // Read-only: discard any content mutation. Only pure canvas\n // navigation (stack push/pop — entering/leaving groups via\n // double-click or breadcrumb) is allowed through. onChange is not\n // fired, so hosts won't treat navigation as an unsaved change.\n const navigationOnly = next.canvases === prev.canvases && next.title === prev.title;\n return navigationOnly ? next : prev;\n }\n queueMicrotask(() => onChangeRef.current?.(next));\n return next;\n });\n },\n [wrappedSetDoc, readOnly],\n );\n\n // Undo/redo mutate the document directly through useUndoRedo's raw setDoc\n // (bypassing outerSetDoc), so they need their own read-only guard.\n const guardedUndo = useCallback(() => { if (!readOnly) undo(); }, [readOnly, undo]);\n const guardedRedo = useCallback(() => { if (!readOnly) redo(); }, [readOnly, redo]);\n\n const handlePixiReady = useCallback((pixi: PixiApp) => {\n pixiRef.current = pixi;\n if (miniMapCanvasRef.current && !miniMapRef.current) {\n miniMapRef.current = new MiniMapRenderer(miniMapCanvasRef.current);\n }\n }, []);\n\n // Built-in PNG export: if the host supplies `onExportPNG` we defer to it,\n // otherwise snapshot the Pixi scene and download it directly so the toolbar\n // button always works out of the box.\n const handleExportPNG = useCallback(async () => {\n if (onExportPNG) return void onExportPNG();\n const pixi = pixiRef.current;\n if (!pixi || pixi.destroyed) return;\n const url = await pixi.exportPNG(0xffffff);\n if (url) downloadDataURL(url, `${safeFileBaseName(docRef.current.title)}.png`);\n }, [onExportPNG]);\n\n // Built-in PDF export: capture each anchor's presentation framing into a\n // multi-page PDF (implemented by PdfExportWire inside the providers).\n const handleExportPDF = useCallback(async () => {\n await exportPdfRef.current?.();\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n let subscribed: PixiApp | null = null;\n\n const tickerFn = () => {\n const pixi = pixiRef.current;\n const mm = miniMapRef.current;\n if (!pixi || pixi.destroyed || !mm) return;\n const vp = { x: pixi.world.position.x, y: pixi.world.position.y, zoom: pixi.world.scale.x };\n const currentId = doc.stack[doc.stack.length - 1] ?? ROOT_CANVAS_ID;\n const canvas = doc.canvases[currentId];\n if (canvas) mm.update(canvas.nodes, vp, pixi.app.screen.width, pixi.app.screen.height);\n };\n\n // Pixi initializes asynchronously; the minimap renderer is created in\n // handlePixiReady. Poll until both exist, then attach the ticker. This\n // avoids a race where the effect runs before pixi is ready and the minimap\n // stays blank until the next document edit.\n const attach = () => {\n if (cancelled) return;\n const pixi = pixiRef.current;\n if (pixi && !pixi.destroyed && miniMapRef.current) {\n pixi.app.ticker.add(tickerFn);\n subscribed = pixi;\n } else {\n requestAnimationFrame(attach);\n }\n };\n attach();\n\n return () => {\n cancelled = true;\n if (subscribed && !subscribed.destroyed) subscribed.app.ticker.remove(tickerFn);\n };\n }, [doc]);\n\n const style: React.CSSProperties = { width: width ?? '100%', height: height ?? '100%' };\n\n return (\n <div ref={rootRef} className={`flow-container ${className ?? ''}`} style={style}>\n <FlowProvider doc={doc} setDoc={outerSetDoc} selection={selection} setSelection={setSelection}>\n <FlowUIProvider showMiniMap={showMiniMap} showGrid={showGrid} gridSize={gridSize} showAI={showAI} readOnly={readOnly}>\n <FlowAIProvider initialConfig={aiConfig} showAIConfig={showAIConfig}>\n <FlowKeyboardShortcutsWire onUndo={guardedUndo} onRedo={guardedRedo} groupRef={groupRef} geoGroupRef={geoGroupRef} ungroupRef={ungroupRef} />\n <PdfExportWire pixiRef={pixiRef} exportPdfRef={exportPdfRef} />\n <UnifiedPanelGroup\n compact={compact}\n toolbar={!readOnly ? (\n <FlowToolbar\n compact={compact}\n onBack={onBack}\n onUndo={guardedUndo} onRedo={guardedRedo} canUndo={canUndo} canRedo={canRedo}\n onGroup={() => groupRef.current?.()} onGeoGroup={() => geoGroupRef.current?.()} onUngroup={() => ungroupRef.current?.()}\n onExportPNG={handleExportPNG}\n onExportPDF={handleExportPDF}\n onPublish={onPublish}\n />\n ) : null}\n >\n <FlowCanvas onPixiReady={handlePixiReady} />\n {/* Media content (markdown/mermaid/data/…) must stay visible in\n presentation & read-only preview — PixiJS only draws the node\n frame, so hiding this layer would blank every media node. */}\n <MediaOverlay />\n <PresentationGate>\n {!readOnly && <FlowZoomControls />}\n <Breadcrumb />\n </PresentationGate>\n {!readOnly && <FlowContextMenuWire />}\n <MiniMapWrapper canvasRef={miniMapCanvasRef} pixiRef={pixiRef} miniMapRef={miniMapRef} />\n <PresentationMode />\n </UnifiedPanelGroup>\n </FlowAIProvider>\n </FlowUIProvider>\n </FlowProvider>\n </div>\n );\n});\n\nexport default Flow;\n\ninterface TabDef {\n id: string;\n label: string;\n icon: string;\n showKey?: 'showPalette' | 'showInspector' | 'showOutline' | 'showJson' | 'showAI';\n}\n\n/**\n * Hides all editable overlay UI (toolbar, panels, breadcrumb) while in\n * presentation mode, keeping only the canvas visible for the slideshow.\n */\nfunction PresentationGate({ children }: { children: React.ReactNode }) {\n const { presentation } = useFlowUI();\n return (\n <div className=\"flow-overlay\" style={{ display: presentation ? 'none' : undefined }}>\n {children}\n </div>\n );\n}\n\nconst ALL_TAB_DEFS: TabDef[] = [\n { id: 'shapes', label: 'Shapes', icon: '▢', showKey: 'showPalette' },\n { id: 'icons', label: 'Icons', icon: '★', showKey: 'showPalette' },\n { id: 'media', label: 'Media', icon: '▶', showKey: 'showPalette' },\n { id: 'inspector', label: 'Inspector', icon: '⚙', showKey: 'showInspector' },\n { id: 'outline', label: 'Outline', icon: '☰', showKey: 'showOutline' },\n { id: 'json', label: 'JSON', icon: '{ }', showKey: 'showJson' },\n { id: 'ai', label: 'AI', icon: '✨', showKey: 'showAI' },\n];\n\nfunction tabContent(id: string): React.ReactNode {\n switch (id) {\n case 'shapes': return <ShapePaletteContent />;\n case 'icons': return <SvgStampContent />;\n case 'media': return <MediaStampContent />;\n case 'inspector': return <InspectorPanelContent />;\n case 'outline': return <OutlinePanelContent />;\n case 'json': return <JsonEditorPanel />;\n case 'ai': return <AIPanelContent />;\n default: return null;\n }\n}\n\n/** Tab ids whose panels stamp objects onto the canvas (auto-collapse in compact mode). */\nconst STAMP_PANEL_IDS = new Set(['shapes', 'icons', 'media']);\n\nfunction UnifiedPanelGroup({ children, toolbar, compact }: { children: React.ReactNode; toolbar?: React.ReactNode; compact?: boolean }) {\n const ui = useFlowUI();\n const { panelDocks, setPanelDock, presentation, readOnly, setPendingPlacement } = ui;\n const { setShowPalette, setShowInspector, setShowOutline, setShowJson, setShowAI } = ui;\n\n // Read-only: hide every editing panel (palette/inspector/outline/JSON/AI).\n const visibleTabs = readOnly\n ? []\n : ALL_TAB_DEFS.filter(def =>\n !def.showKey || (ui as any)[def.showKey],\n );\n\n const toPanelTab = (def: TabDef): PanelTab => ({\n id: def.id, label: def.label, icon: def.icon, content: tabContent(def.id),\n });\n\n // Compact (small-screen) mode: the chosen panel renders full-screen above\n // the canvas. Shape/icon/media stamp panels collapse automatically once a\n // stamp is picked (pendingPlacement armed) so the user is returned to the\n // canvas to tap it into place; a virtual ESC button on the canvas cancels\n // continuous placement.\n const [activePanel, setActivePanel] = useState<string | null>(null);\n const prevPlacementKeyRef = useRef<string | null>(null);\n useEffect(() => {\n if (!compact) {\n prevPlacementKeyRef.current = null;\n return;\n }\n const pp = ui.pendingPlacement;\n const key = pp ? `${pp.type}:${(pp.data as any)?.svgId ?? ''}:${(pp.data as any)?.mediaKind ?? ''}` : null;\n const prev = prevPlacementKeyRef.current;\n prevPlacementKeyRef.current = key;\n if (key && key !== prev && activePanel && STAMP_PANEL_IDS.has(activePanel)) {\n setActivePanel(null);\n }\n }, [ui.pendingPlacement, compact, activePanel]);\n\n // Floating-panel drag: highlight the dock edge it hovers over.\n const [dropHint, setDropHint] = useState<'left' | 'right' | null>(null);\n const handleDock = useCallback((tabId: string, dock: FlowPanelDock) => setPanelDock(tabId, dock), [setPanelDock]);\n // Each panel has its own close (✕) button; closed panels come back via the\n // matching toolbar toggle (shapes/icons/media share the palette flag).\n const handleClose = useCallback((tabId: string) => {\n if (tabId === 'inspector') setShowInspector(false);\n else if (tabId === 'outline') setShowOutline(false);\n else if (tabId === 'json') setShowJson(false);\n else if (tabId === 'ai') setShowAI(false);\n else setShowPalette(false);\n }, [setShowInspector, setShowOutline, setShowJson, setShowAI, setShowPalette]);\n\n // NOTE: every hook must run before this early return — moving hooks below it\n // would break the hooks-of-render contract in compact mode.\n if (compact && !presentation) {\n const activeDef = visibleTabs.find(d => d.id === activePanel) ?? null;\n return (\n <div className=\"flow-workspace\">\n {toolbar}\n {visibleTabs.length > 0 && (\n <div className=\"flow-mobile-panel-bar\">\n <Select\n placeholder=\"选择面板…\"\n options={visibleTabs.map(d => ({ value: d.id, label: d.label }))}\n value={activePanel ?? undefined}\n onChange={(v) => {\n // Switching panels always opens a clean \"pick a type\" state: a\n // stale armed stamp (continuous placement) would otherwise be\n // placed by the next canvas tap or toggle the tapped item off.\n setPendingPlacement(null);\n setActivePanel(String(v));\n }}\n />\n </div>\n )}\n <div className=\"flow-workspace-main\">\n <div className=\"flow-stage\">\n {children}\n </div>\n {activeDef && (\n <div className=\"flow-mobile-panel\">\n <div className=\"flow-mobile-panel-header\">\n <span className=\"flow-mobile-panel-title\">\n {activeDef.icon && <span className=\"flow-panel-tab-icon\">{activeDef.icon}</span>}\n {activeDef.label}\n </span>\n <button\n type=\"button\"\n className=\"flow-mobile-panel-close\"\n aria-label=\"关闭面板\"\n onClick={() => setActivePanel(null)}\n >✕</button>\n </div>\n <div className=\"flow-mobile-panel-body\">{tabContent(activeDef.id)}</div>\n </div>\n )}\n </div>\n </div>\n );\n }\n\n // Desktop layout: the left/right panel groups are merged into one panel\n // list docked on the left by default. Panels are switched with a dropdown\n // Select; users can move any panel to the right dock or let it float (via\n // header buttons or by dragging it onto a workspace edge). Docks without\n // panels are not rendered so the canvas fills the freed space.\n const getDock = (id: string): FlowPanelDock => panelDocks[id] ?? 'left';\n const leftTabs: PanelTab[] = visibleTabs\n .filter(def => getDock(def.id) === 'left')\n .map(toPanelTab);\n const rightTabs: PanelTab[] = visibleTabs\n .filter(def => getDock(def.id) === 'right')\n .map(toPanelTab);\n const floatList = visibleTabs.filter(def => getDock(def.id) === 'float');\n\n return (\n <div className=\"flow-workspace\">\n {/* Slim full-width toolbar strip above panels + canvas. */}\n {!presentation && toolbar}\n <div className=\"flow-workspace-main\">\n {!presentation && leftTabs.length > 0 && (\n <FlowPanelGroup\n tabs={leftTabs}\n defaultTab={leftTabs[0]?.id}\n resizeEdge=\"right\"\n defaultWidth={220}\n dock=\"left\"\n onDock={handleDock}\n onClose={handleClose}\n />\n )}\n <div className=\"flow-stage\">\n {children}\n </div>\n {!presentation && rightTabs.length > 0 && (\n <FlowPanelGroup\n tabs={rightTabs}\n defaultTab={rightTabs[0]?.id}\n resizeEdge=\"left\"\n defaultWidth={220}\n dock=\"right\"\n onDock={handleDock}\n onClose={handleClose}\n />\n )}\n {/* Floating panels drift above the docks; dragging one over a\n workspace edge shows a drop-zone hint and dropping attaches it to\n that dock. Rendered as a direct child of the (position:relative)\n workspace-main so they are not clipped by the stage's\n `overflow: hidden`. */}\n {!presentation && floatList.map((def, i) => (\n <FlowPanelGroup\n key={def.id}\n tabs={[toPanelTab(def)]}\n resizeEdge=\"right\"\n defaultWidth={220}\n dock=\"float\"\n style={{ top: 48 + i * 40, left: 24 + i * 30 }}\n onDock={handleDock}\n onClose={handleClose}\n onDragHintChange={setDropHint}\n />\n ))}\n {!presentation && dropHint && (\n <div className={`flow-dock-dropzone flow-dock-dropzone--${dropHint}`}>\n <span>{dropHint === 'left' ? '⇤ 停靠左侧' : '停靠右侧 ⇥'}</span>\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction MiniMapWrapper({\n canvasRef,\n pixiRef,\n miniMapRef,\n}: {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n pixiRef: React.RefObject<PixiApp | null>;\n miniMapRef: React.RefObject<MiniMapRenderer | null>;\n}) {\n const { showMiniMap, setViewport, presentation } = useFlowUI();\n const draggingRef = useRef(false);\n\n // Move the main canvas so that the world point under the minimap click is\n // centered in the viewport. Routed through setViewport so the change syncs\n // with the ViewportManager (setting world.position directly would be\n // overwritten on the next viewport apply).\n const navigateTo = useCallback((clientX: number, clientY: number) => {\n const canvas = canvasRef.current;\n const pixi = pixiRef.current;\n const mm = miniMapRef.current;\n if (!canvas || !pixi || pixi.destroyed || !mm) return;\n\n const rect = canvas.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n // CSS pixels -> canvas backing pixels.\n const { width: cw, height: ch } = mm.size;\n const px = ((clientX - rect.left) / rect.width) * cw;\n const py = ((clientY - rect.top) / rect.height) * ch;\n\n const world = mm.minimapToWorld(px, py);\n if (!world) return;\n\n const zoom = pixi.world.scale.x || 1;\n const screenW = pixi.app.screen.width;\n const screenH = pixi.app.screen.height;\n setViewport({\n x: screenW / 2 - world.x * zoom,\n y: screenH / 2 - world.y * zoom,\n zoom,\n });\n }, [canvasRef, pixiRef, miniMapRef, setViewport]);\n\n const onPointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>) => {\n e.preventDefault();\n (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);\n draggingRef.current = true;\n navigateTo(e.clientX, e.clientY);\n }, [navigateTo]);\n\n const onPointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {\n if (!draggingRef.current) return;\n navigateTo(e.clientX, e.clientY);\n }, [navigateTo]);\n\n const onPointerUp = useCallback((e: React.PointerEvent<HTMLDivElement>) => {\n draggingRef.current = false;\n try { (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId); } catch { /* noop */ }\n }, []);\n\n return (\n <div\n className=\"flow-minimap\"\n style={{ display: showMiniMap && !presentation ? undefined : 'none', cursor: 'pointer' }}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n title=\"点击或拖动以移动画布\"\n >\n <canvas ref={canvasRef} width={180} height={120} style={{ width: '100%', height: '100%', pointerEvents: 'none' }} />\n </div>\n );\n}\n\n/**\n * Find the line range (1-based) of a JSON object with the given id.\n */\nfunction findElementLineRange(jsonStr: string, elementId: string): { startLine: number; endLine: number } | null {\n const idPattern = `\"id\": \"${elementId}\"`;\n const idx = jsonStr.indexOf(idPattern);\n if (idx === -1) return null;\n\n let braceDepth = 0;\n let startIdx = -1;\n for (let i = idx; i >= 0; i--) {\n if (jsonStr[i] === '}') braceDepth++;\n if (jsonStr[i] === '{') {\n if (braceDepth === 0) { startIdx = i; break; }\n braceDepth--;\n }\n }\n if (startIdx === -1) return null;\n\n braceDepth = 0;\n let endIdx = -1;\n for (let i = startIdx; i < jsonStr.length; i++) {\n if (jsonStr[i] === '{') braceDepth++;\n if (jsonStr[i] === '}') {\n braceDepth--;\n if (braceDepth === 0) { endIdx = i; break; }\n }\n }\n if (endIdx === -1) return null;\n\n const startLine = jsonStr.slice(0, startIdx).split('\\n').length;\n const endLine = jsonStr.slice(0, endIdx + 1).split('\\n').length;\n return { startLine, endLine };\n}\n\nfunction clearLineHighlights(container: HTMLElement) {\n container.querySelectorAll('.cm-line.flow-json-hl').forEach(el => el.classList.remove('flow-json-hl'));\n}\n\nfunction applyLineHighlightsViaView(handle: CodeEditorHandle, startLine: number, endLine: number) {\n const view = handle.getView();\n if (!view) return;\n for (let ln = startLine; ln <= endLine; ln++) {\n try {\n const lineInfo = view.state.doc.line(ln);\n const dom = view.domAtPos(lineInfo.from);\n const lineEl = dom.node instanceof HTMLElement\n ? dom.node.closest('.cm-line')\n : dom.node.parentElement?.closest('.cm-line');\n if (lineEl) lineEl.classList.add('flow-json-hl');\n } catch { /* line not in viewport */ }\n }\n}\n\nfunction JsonEditorPanel() {\n const { setDoc, currentCanvasId, selection, currentCanvas } = useFlowContext();\n const [jsonStr, setJsonStr] = useState('');\n const [error, setError] = useState<string | null>(null);\n const skipNextSync = useRef(false);\n const editorRef = useRef<CodeEditorHandle>(null);\n const wrapRef = useRef<HTMLDivElement>(null);\n\n const singleId = useMemo(() => {\n const total = selection.nodeIds.size + selection.edgeIds.size + selection.lineIds.size;\n if (total !== 1) return null;\n if (selection.nodeIds.size === 1) return [...selection.nodeIds][0];\n if (selection.edgeIds.size === 1) return [...selection.edgeIds][0];\n if (selection.lineIds.size === 1) return [...selection.lineIds][0];\n return null;\n }, [selection]);\n\n useEffect(() => {\n if (skipNextSync.current) { skipNextSync.current = false; return; }\n setJsonStr(JSON.stringify(currentCanvas, null, 2));\n setError(null);\n }, [currentCanvas]);\n\n useEffect(() => {\n const wrap = wrapRef.current;\n if (wrap) clearLineHighlights(wrap);\n if (!singleId) return;\n\n const timer = setTimeout(() => {\n const range = findElementLineRange(jsonStr, singleId);\n if (!range || !wrapRef.current) return;\n\n const view = editorRef.current?.getView();\n if (!view) return;\n\n const w = wrapRef.current;\n const scroller = view.scrollDOM;\n\n scroller.style.scrollBehavior = 'smooth';\n\n const lineInfo = view.state.doc.line(range.startLine);\n view.dispatch({\n effects: EditorView.scrollIntoView(lineInfo.from, { y: 'start', yMargin: 0 }),\n });\n\n const onScrollEnd = () => {\n scroller.style.scrollBehavior = '';\n clearLineHighlights(w);\n applyLineHighlightsViaView(editorRef.current!, range.startLine, range.endLine);\n };\n\n let scrollTimer: ReturnType<typeof setTimeout>;\n const onScroll = () => {\n clearTimeout(scrollTimer);\n scrollTimer = setTimeout(onScrollEnd, 100);\n };\n scroller.addEventListener('scroll', onScroll);\n scrollTimer = setTimeout(() => {\n scroller.removeEventListener('scroll', onScroll);\n onScrollEnd();\n }, 800);\n }, 120);\n return () => clearTimeout(timer);\n }, [singleId, jsonStr]);\n\n const handleChange = useCallback((value: string) => {\n setJsonStr(value);\n try {\n const parsed = JSON.parse(value);\n if (parsed && Array.isArray(parsed.nodes) && Array.isArray(parsed.edges) && Array.isArray(parsed.lines)) {\n setError(null);\n skipNextSync.current = true;\n setDoc(prev => ({\n ...prev,\n canvases: { ...prev.canvases, [currentCanvasId]: parsed },\n }));\n } else {\n setError('JSON must have nodes, edges, lines arrays');\n }\n } catch (e: any) {\n setError(e.message?.slice(0, 60) ?? 'Invalid JSON');\n }\n }, [setDoc, currentCanvasId]);\n\n const label = singleId\n ? (selection.nodeIds.has(singleId) ? 'Node' : selection.edgeIds.has(singleId) ? 'Edge' : 'Line')\n : null;\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>\n <div className=\"flow-json-header\">\n <span className=\"flow-json-scope\">\n {label ? `${label}: ${singleId!.slice(0, 8)}` : `Canvas`}\n </span>\n </div>\n {error && (\n <div style={{ fontSize: 11, color: 'oklch(var(--er))', padding: '4px 0', flexShrink: 0 }}>\n {error}\n </div>\n )}\n <div ref={wrapRef} style={{ flex: 1, minHeight: 0 }}>\n <CodeEditor\n ref={editorRef}\n value={jsonStr}\n onChange={handleChange}\n language=\"json\"\n lineNumbers={false}\n height=\"100%\"\n highlightActiveLine={false}\n />\n </div>\n </div>\n );\n}\n\nfunction FlowContextMenuWire() {\n const { contextMenu, setContextMenu } = useFlowUI();\n const { doc, setDoc, currentCanvasId, pushCanvas, setSelection } = useFlowContext();\n const { duplicate } = useClipboard();\n\n if (!contextMenu) return null;\n\n const canvas = doc.canvases[currentCanvasId];\n if (!canvas) { setContextMenu(null); return null; }\n\n const node = contextMenu.nodeId ? canvas.nodes.find(n => n.id === contextMenu.nodeId) : null;\n const items: ContextMenuItem[] = [];\n\n if (node) {\n // Duplicate the node (Cmd/Ctrl+D equivalent). Ensure it is selected first so\n // the clone covers the intended target even on a right-click without a prior\n // left-click selection.\n items.push({\n label: 'Duplicate',\n icon: '⧉',\n action: () => {\n duplicate(24, 24, { nodeIds: new Set([node.id]) });\n },\n });\n items.push({ label: '---', action: () => {} });\n\n const hasChild = !!node.data.childCanvasId;\n\n if (hasChild) {\n items.push({\n label: 'Enter Sub-canvas',\n icon: '↓',\n action: () => {\n pushCanvas(node.data.childCanvasId!);\n setSelection({ nodeIds: new Set(), edgeIds: new Set(), lineIds: new Set() });\n },\n });\n items.push({ label: '---', action: () => {} });\n items.push({\n label: 'Delete Sub-canvas',\n icon: '🗑',\n danger: true,\n action: () => {\n const childId = node.data.childCanvasId!;\n setDoc(prev => {\n const canvases = { ...prev.canvases };\n collectAndRemoveCanvases(canvases, childId);\n const c = canvases[currentCanvasId];\n if (!c) return prev;\n canvases[currentCanvasId] = {\n ...c,\n nodes: c.nodes.map(n =>\n n.id === node.id\n ? { ...n, data: { ...n.data, childCanvasId: undefined } }\n : n,\n ),\n };\n const stack = prev.stack.filter(id => canvases[id]);\n if (stack.length === 0) stack.push(ROOT_CANVAS_ID);\n return { ...prev, canvases, stack };\n });\n },\n });\n } else {\n items.push({\n label: 'Create Sub-canvas',\n icon: '⊞',\n action: () => {\n const childCanvasId = generateId('canvas');\n setDoc(prev => {\n const c = prev.canvases[currentCanvasId];\n if (!c) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...c,\n nodes: c.nodes.map(n =>\n n.id === node.id\n ? { ...n, data: { ...n.data, childCanvasId } }\n : n,\n ),\n },\n [childCanvasId]: { nodes: [], edges: [], lines: [] },\n },\n };\n });\n pushCanvas(childCanvasId);\n },\n });\n }\n }\n\n if (items.length === 0) return null;\n\n return (\n <ContextMenu\n x={contextMenu.x}\n y={contextMenu.y}\n items={items}\n onClose={() => setContextMenu(null)}\n />\n );\n}\n\nfunction collectAndRemoveCanvases(\n canvases: Record<string, FlowCanvasData>,\n canvasId: string,\n): void {\n const canvas = canvases[canvasId];\n if (!canvas) return;\n for (const node of canvas.nodes) {\n if (node.data.childCanvasId) {\n collectAndRemoveCanvases(canvases, node.data.childCanvasId);\n }\n }\n delete canvases[canvasId];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Flow.js","names":[],"sources":["../../../../src/components/pro/Flow/Flow.tsx"],"sourcesContent":["import React, { useState, useCallback, useRef, useEffect, useMemo, forwardRef, useImperativeHandle } from 'react';\nimport { FlowProvider, useFlowContext } from './context/FlowContext';\nimport { FlowUIProvider, useFlowUI } from './context/FlowUIContext';\nimport { FlowAIProvider } from './context/FlowAIContext';\nimport FlowCanvas from './FlowCanvas';\nimport FlowToolbar, { FlowZoomControls } from './panels/FlowToolbar';\nimport { ShapePaletteContent, SvgStampContent, MediaStampContent } from './panels/ShapePalette';\nimport { InspectorPanelContent } from './panels/InspectorPanel';\nimport { OutlinePanelContent } from './panels/OutlinePanel';\nimport { AIPanelContent } from './panels/AIPanel';\nimport { PresentationMode } from './panels/PresentationMode';\nimport { MediaOverlay } from './nodes/MediaOverlay';\nimport FlowPanelGroup, { type PanelTab } from './panels/FlowPanelGroup';\nimport type { FlowPanelDock } from './context/FlowUIContext';\nimport Breadcrumb from './panels/Breadcrumb';\nimport ContextMenu from './panels/ContextMenu';\nimport type { ContextMenuItem } from './panels/ContextMenu';\nimport Select from '../../Select';\nimport { MiniMapRenderer } from './canvas/MiniMapRenderer';\nimport { CodeEditor, type CodeEditorHandle } from '../CodeEditor';\nimport { EditorView } from '@codemirror/view';\nimport { useUndoRedo } from './hooks/useUndoRedo';\nimport { useClipboard } from './hooks/useClipboard';\nimport { useCompactMode } from './hooks/useCompactMode';\nimport { downloadDataURL, safeFileBaseName } from './hooks/useFlowImportExport';\nimport { usePdfExport } from './hooks/usePdfExport';\nimport { useKeyboardShortcuts } from './hooks/useKeyboardShortcuts';\nimport { useMindMap } from './hooks/useMindMap';\nimport { useDeleteSelection } from './hooks/useDeleteSelection';\nimport { createEmptyDocument, ROOT_CANVAS_ID, DEFAULT_NODE_DATA } from './constants';\nimport { normalizeEdgeHandles } from './edges/edgePaths';\nimport { generateId } from './nodes/nodeUtils';\nimport type { FlowProps, FlowDocument, FlowNode, SelectionState, FlowCanvasData } from './types';\nimport type { PixiApp } from './canvas/PixiApp';\nimport './styles.css';\n\nfunction normalizeDocument(doc: FlowDocument): FlowDocument {\n let changed = false;\n const canvases = { ...doc.canvases };\n for (const [id, canvas] of Object.entries(canvases)) {\n const nodes = canvas.nodes.map(node => {\n if (node.type !== 'shapeSvg') return node;\n const legacyPlacement = node.data.labelPlacement as string | undefined;\n const labelPlacement = legacyPlacement === 'above'\n ? 'top'\n : legacyPlacement === 'center'\n ? 'middle'\n : legacyPlacement === 'below'\n ? 'bottom'\n : node.data.labelPlacement;\n if (labelPlacement === node.data.labelPlacement) return node;\n changed = true;\n return { ...node, data: { ...node.data, labelPlacement } };\n });\n // Mindmap nodes/edges created before theme tokens (≤ 0.0.56) persisted\n // literal colors, so they never followed theme switches. That exact palette\n // was only ever emitted by the old mindmap creator, so remap it to the same\n // tokens the current creator uses.\n let themedNodesChanged = false;\n const themedNodes = nodes.map(node => {\n if (!node.data.isMindMapNode) return node;\n const fill = node.data.fill === '#ffffff' || node.data.fill === '#eef2ff'\n ? 'theme:base-100'\n : node.data.fill;\n const stroke = node.data.stroke === '#6366f1'\n ? 'theme:base-content'\n : node.data.stroke === '#4f46e5'\n ? 'theme:primary'\n : node.data.stroke;\n if (fill === node.data.fill && stroke === node.data.stroke) return node;\n themedNodesChanged = true;\n return { ...node, data: { ...node.data, fill, stroke } };\n });\n const nodeMap = new Map<string, FlowNode>();\n for (const node of themedNodes) nodeMap.set(node.id, node);\n let edgesChanged = false;\n const edges = normalizeEdgeHandles(canvas.edges, nodeMap).map(edge => {\n if (edge.data.strokeColor !== '#94a3b8') return edge;\n const source = nodeMap.get(edge.source);\n const target = nodeMap.get(edge.target);\n if (!source?.data.isMindMapNode || !target?.data.isMindMapNode) return edge;\n edgesChanged = true;\n return { ...edge, data: { ...edge.data, strokeColor: 'theme:base-content' } };\n });\n if (themedNodesChanged || edgesChanged) changed = true;\n if (themedNodes !== nodes || edges !== canvas.edges) {\n canvases[id] = { ...canvas, nodes: themedNodes, edges };\n }\n }\n return changed ? { ...doc, canvases } : doc;\n}\n\nconst EMPTY_SELECTION: SelectionState = {\n nodeIds: new Set(),\n edgeIds: new Set(),\n lineIds: new Set(),\n};\n\nfunction FlowKeyboardShortcutsWire({ onUndo, onRedo, groupRef, geoGroupRef, ungroupRef }: {\n onUndo: () => void;\n onRedo: () => void;\n groupRef: React.MutableRefObject<(() => void) | null>;\n geoGroupRef: React.MutableRefObject<(() => void) | null>;\n ungroupRef: React.MutableRefObject<(() => void) | null>;\n}) {\n const { selection, setSelection, currentCanvas, setDoc, currentCanvasId } = useFlowContext();\n const { viewport } = useFlowUI();\n const { copy, cut, paste, duplicate } = useClipboard();\n const deleteSelected = useDeleteSelection();\n\n const selectAll = useCallback(() => {\n setSelection({\n nodeIds: new Set(currentCanvas.nodes.map(n => n.id)),\n edgeIds: new Set(currentCanvas.edges.map(e => e.id)),\n lineIds: new Set(currentCanvas.lines.map(l => l.id)),\n });\n }, [currentCanvas, setSelection]);\n\n const createGroup = useCallback((type: 'flowGroup' | 'geometryGroup') => {\n const nodeIds = selection.nodeIds;\n const lineIds = selection.lineIds;\n const totalCount = nodeIds.size + lineIds.size;\n if (totalCount < 2) return;\n const nodes = currentCanvas.nodes.filter(n => nodeIds.has(n.id));\n const lines = currentCanvas.lines.filter(l => lineIds.has(l.id));\n const PAD = 20;\n let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;\n for (const n of nodes) {\n minX = Math.min(minX, n.position.x);\n minY = Math.min(minY, n.position.y);\n maxX = Math.max(maxX, n.position.x + n.width);\n maxY = Math.max(maxY, n.position.y + n.height);\n }\n for (const l of lines) {\n for (const p of l.points) {\n minX = Math.min(minX, p.x); minY = Math.min(minY, p.y);\n maxX = Math.max(maxX, p.x); maxY = Math.max(maxY, p.y);\n }\n }\n const isGeo = type === 'geometryGroup';\n const groupId = generateId('group');\n const groupNode: FlowNode = {\n id: groupId,\n type,\n position: { x: minX - PAD, y: minY - PAD },\n width: maxX - minX + PAD * 2,\n height: maxY - minY + PAD * 2,\n data: {\n ...DEFAULT_NODE_DATA,\n label: isGeo ? 'Geo Group' : 'Group',\n fill: isGeo ? '#fefce8' : '#f0f9ff',\n stroke: '#9ca3af', strokeWidth: 1.5,\n borderStyle: isGeo ? 'dashed' : 'dotted',\n isGroup: true,\n },\n };\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: { ...prev.canvases, [currentCanvasId]: {\n ...canvas,\n nodes: isGeo\n ? [groupNode, ...canvas.nodes]\n : [groupNode, ...canvas.nodes.map(n => nodeIds.has(n.id) ? { ...n, parentId: groupId } : n)],\n lines: isGeo\n ? canvas.lines\n : canvas.lines.map(l => lineIds.has(l.id) ? { ...l, parentId: groupId } : l),\n }},\n };\n });\n setSelection({ nodeIds: new Set([groupId]), edgeIds: new Set(), lineIds: new Set() });\n }, [selection, currentCanvas, setDoc, currentCanvasId, setSelection]);\n\n const groupSelected = useCallback(() => createGroup('flowGroup'), [createGroup]);\n const geoGroupSelected = useCallback(() => createGroup('geometryGroup'), [createGroup]);\n\n const ungroupSelected = useCallback(() => {\n const ids = selection.nodeIds;\n if (ids.size === 0) return;\n const groupNodeIds = new Set<string>();\n const childNodeIds = new Set<string>();\n const childLineIds = new Set<string>();\n for (const node of currentCanvas.nodes) {\n if (ids.has(node.id) && (node.type === 'flowGroup' || node.type === 'geometryGroup' || node.data.isGroup)) {\n groupNodeIds.add(node.id);\n }\n }\n if (groupNodeIds.size === 0) return;\n for (const node of currentCanvas.nodes) {\n if (node.parentId && groupNodeIds.has(node.parentId)) {\n childNodeIds.add(node.id);\n }\n }\n for (const line of currentCanvas.lines) {\n if (line.parentId && groupNodeIds.has(line.parentId)) {\n childLineIds.add(line.id);\n }\n }\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: { ...prev.canvases, [currentCanvasId]: {\n ...canvas,\n nodes: canvas.nodes\n .filter(n => !groupNodeIds.has(n.id))\n .map(n => childNodeIds.has(n.id) ? { ...n, parentId: undefined } : n),\n lines: canvas.lines\n .map(l => childLineIds.has(l.id) ? { ...l, parentId: undefined } : l),\n }},\n };\n });\n setSelection({ nodeIds: childNodeIds, edgeIds: new Set(), lineIds: childLineIds });\n }, [selection, currentCanvas, setDoc, currentCanvasId, setSelection]);\n\n groupRef.current = groupSelected;\n geoGroupRef.current = geoGroupSelected;\n ungroupRef.current = ungroupSelected;\n\n const changeZIndex = useCallback((mode: 'front' | 'back' | 'up' | 'down') => {\n const nodeIds = selection.nodeIds;\n const lineIds = selection.lineIds;\n if (nodeIds.size === 0 && lineIds.size === 0) return;\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n const nodeAllZ = canvas.nodes.map(n => n.zIndex ?? 0);\n const nodeMaxZ = nodeAllZ.length > 0 ? Math.max(...nodeAllZ) : 0;\n const nodeMinZ = nodeAllZ.length > 0 ? Math.min(...nodeAllZ) : 0;\n const lineAllZ = canvas.lines.map(l => l.zIndex ?? 0);\n const lineMaxZ = lineAllZ.length > 0 ? Math.max(...lineAllZ) : 0;\n const lineMinZ = lineAllZ.length > 0 ? Math.min(...lineAllZ) : 0;\n const applyZ = (z: number, maxZ: number, minZ: number) => {\n if (mode === 'front') return maxZ + 1;\n if (mode === 'back') return minZ - 1;\n if (mode === 'up') return z + 1;\n return z - 1;\n };\n return {\n ...prev,\n canvases: { ...prev.canvases, [currentCanvasId]: {\n ...canvas,\n nodes: canvas.nodes.map(n => nodeIds.has(n.id) ? { ...n, zIndex: applyZ(n.zIndex ?? 0, nodeMaxZ, nodeMinZ) } : n),\n lines: canvas.lines.map(l => lineIds.has(l.id) ? { ...l, zIndex: applyZ(l.zIndex ?? 0, lineMaxZ, lineMinZ) } : l),\n }},\n };\n });\n }, [selection, setDoc, currentCanvasId]);\n\n useEffect(() => {\n const handlePaste = (event: ClipboardEvent) => {\n const target = event.target as HTMLElement | null;\n if (target?.tagName === 'INPUT' || target?.tagName === 'TEXTAREA' || target?.isContentEditable) return;\n\n const imageItem = Array.from(event.clipboardData?.items ?? []).find(item => item.type.startsWith('image/'));\n if (!imageItem) {\n event.preventDefault();\n paste();\n return;\n }\n\n const file = imageItem.getAsFile();\n if (!file) return;\n event.preventDefault();\n const reader = new FileReader();\n reader.onload = () => {\n if (typeof reader.result !== 'string') return;\n const selectedImage = selection.nodeIds.size === 1\n ? currentCanvas.nodes.find(node => selection.nodeIds.has(node.id) && node.type === 'mediaNode' && node.data.mediaKind === 'image')\n : undefined;\n\n if (selectedImage) {\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...canvas,\n nodes: canvas.nodes.map(node => node.id === selectedImage.id\n ? { ...node, data: { ...node.data, mediaContent: reader.result as string } }\n : node),\n },\n },\n };\n });\n return;\n }\n\n const id = generateId('node');\n const width = 320;\n const height = 220;\n const screenWidth = window.innerWidth;\n const screenHeight = window.innerHeight;\n const centerX = (screenWidth / 2 - viewport.x) / viewport.zoom;\n const centerY = (screenHeight / 2 - viewport.y) / viewport.zoom;\n const highestZ = Math.max(0, ...currentCanvas.nodes.map(node => node.zIndex ?? 0));\n const imageNode: FlowNode = {\n id,\n type: 'mediaNode',\n position: { x: centerX - width / 2, y: centerY - height / 2 },\n width,\n height,\n zIndex: highestZ + 1,\n data: {\n ...DEFAULT_NODE_DATA,\n label: file.name || 'Image',\n fill: 'theme:base-100',\n stroke: 'theme:base-content',\n strokeWidth: 1.5,\n color: 'theme:base-content',\n fontSize: 13,\n borderStyle: 'solid',\n mediaKind: 'image',\n mediaContent: reader.result,\n },\n };\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: { ...canvas, nodes: [...canvas.nodes, imageNode] },\n },\n };\n });\n setSelection({ nodeIds: new Set([id]), edgeIds: new Set(), lineIds: new Set() });\n };\n reader.readAsDataURL(file);\n };\n\n window.addEventListener('paste', handlePaste);\n return () => window.removeEventListener('paste', handlePaste);\n }, [currentCanvas, currentCanvasId, paste, selection, setDoc, setSelection, viewport]);\n\n const actions = useMemo(() => ({\n undo: onUndo, redo: onRedo, copy, cut, paste, duplicate, deleteSelected, selectAll,\n group: groupSelected, geoGroup: geoGroupSelected, ungroup: ungroupSelected,\n bringToFront: () => changeZIndex('front'),\n bringForward: () => changeZIndex('up'),\n sendBackward: () => changeZIndex('down'),\n sendToBack: () => changeZIndex('back'),\n }), [onUndo, onRedo, copy, cut, paste, duplicate, deleteSelected, selectAll, groupSelected, geoGroupSelected, ungroupSelected, changeZIndex]);\n\n useKeyboardShortcuts(actions);\n useMindMap();\n return null;\n}\n\n/**\n * Bridges the anchor→PDF export (which needs canvas/viewport context) out to the\n * toolbar in the outer Flow component via a ref. Renders nothing.\n */\nfunction PdfExportWire({\n pixiRef,\n exportPdfRef,\n}: {\n pixiRef: React.RefObject<PixiApp | null>;\n exportPdfRef: React.MutableRefObject<(() => Promise<boolean>) | null>;\n}) {\n const exportPdf = usePdfExport(pixiRef);\n exportPdfRef.current = exportPdf;\n return null;\n}\n\n/** Imperative handle exposed via a ref on the `<Flow>` component. */\nexport interface FlowHandle {\n /** Export the current diagram to a PNG data URL (null if unavailable). */\n exportPNG: (backgroundColor?: number) => Promise<string | null>;\n /** Get the current document. */\n getDocument: () => FlowDocument;\n}\n\nconst Flow = forwardRef<FlowHandle, FlowProps>(function Flow({\n initialDocument,\n onChange,\n width,\n height,\n readOnly = false,\n onBack,\n onExportPNG,\n onPublish,\n showMiniMap = true,\n showGrid = true,\n gridSize = 20,\n className,\n showAI = true,\n showAIConfig = true,\n aiConfig,\n}: FlowProps, ref) {\n const [doc, setDoc] = useState<FlowDocument>(() => normalizeDocument(initialDocument ?? createEmptyDocument()));\n const [selection, setSelection] = useState<SelectionState>(EMPTY_SELECTION);\n const pixiRef = useRef<PixiApp | null>(null);\n const miniMapCanvasRef = useRef<HTMLCanvasElement>(null);\n const miniMapRef = useRef<MiniMapRenderer | null>(null);\n // Small-screen detection (container width based, see useCompactMode).\n const rootRef = useRef<HTMLDivElement>(null);\n const compact = useCompactMode(rootRef);\n\n const { wrappedSetDoc, undo, redo, canUndo, canRedo } = useUndoRedo(doc, setDoc);\n const groupRef = useRef<(() => void) | null>(null);\n const geoGroupRef = useRef<(() => void) | null>(null);\n const ungroupRef = useRef<(() => void) | null>(null);\n const exportPdfRef = useRef<(() => Promise<boolean>) | null>(null);\n\n const onChangeRef = useRef(onChange);\n onChangeRef.current = onChange;\n const notifiedDocRef = useRef(doc);\n\n // Notify only committed changes, including undo/redo; skip mount and\n // read-only navigation rather than scheduling callbacks inside an updater.\n useEffect(() => {\n if (notifiedDocRef.current === doc) return;\n notifiedDocRef.current = doc;\n if (!readOnly) onChangeRef.current?.(doc);\n }, [doc, readOnly]);\n\n // Keep the latest doc in a ref for the imperative handle.\n const docRef = useRef(doc);\n docRef.current = doc;\n\n useImperativeHandle(ref, () => ({\n exportPNG: async (backgroundColor?: number) => {\n const pixi = pixiRef.current;\n if (!pixi || pixi.destroyed) return null;\n return pixi.exportPNG(backgroundColor);\n },\n getDocument: () => docRef.current,\n }), []);\n\n const outerSetDoc: React.Dispatch<React.SetStateAction<FlowDocument>> = useCallback(\n (action) => {\n wrappedSetDoc(prev => {\n const next = typeof action === 'function' ? action(prev) : action;\n if (readOnly) {\n // Read-only: discard any content mutation. Only pure canvas\n // navigation (stack push/pop — entering/leaving groups via\n // double-click or breadcrumb) is allowed through. onChange is not\n // fired, so hosts won't treat navigation as an unsaved change.\n const navigationOnly = next.canvases === prev.canvases && next.title === prev.title;\n return navigationOnly ? next : prev;\n }\n return next;\n });\n },\n [wrappedSetDoc, readOnly],\n );\n\n // Undo/redo mutate the document directly through useUndoRedo's raw setDoc\n // (bypassing outerSetDoc), so they need their own read-only guard.\n const guardedUndo = useCallback(() => { if (!readOnly) undo(); }, [readOnly, undo]);\n const guardedRedo = useCallback(() => { if (!readOnly) redo(); }, [readOnly, redo]);\n\n const handlePixiReady = useCallback((pixi: PixiApp) => {\n pixiRef.current = pixi;\n if (miniMapCanvasRef.current && !miniMapRef.current) {\n miniMapRef.current = new MiniMapRenderer(miniMapCanvasRef.current);\n }\n }, []);\n\n // Built-in PNG export: if the host supplies `onExportPNG` we defer to it,\n // otherwise snapshot the Pixi scene and download it directly so the toolbar\n // button always works out of the box.\n const handleExportPNG = useCallback(async () => {\n if (onExportPNG) return void onExportPNG();\n const pixi = pixiRef.current;\n if (!pixi || pixi.destroyed) return;\n const url = await pixi.exportPNG(0xffffff);\n if (url) downloadDataURL(url, `${safeFileBaseName(docRef.current.title)}.png`);\n }, [onExportPNG]);\n\n // Built-in PDF export: capture each anchor's presentation framing into a\n // multi-page PDF (implemented by PdfExportWire inside the providers).\n const handleExportPDF = useCallback(async () => {\n await exportPdfRef.current?.();\n }, []);\n\n useEffect(() => {\n let cancelled = false;\n let subscribed: PixiApp | null = null;\n\n const tickerFn = () => {\n const pixi = pixiRef.current;\n const mm = miniMapRef.current;\n if (!pixi || pixi.destroyed || !mm) return;\n const vp = { x: pixi.world.position.x, y: pixi.world.position.y, zoom: pixi.world.scale.x };\n const currentId = doc.stack[doc.stack.length - 1] ?? ROOT_CANVAS_ID;\n const canvas = doc.canvases[currentId];\n if (canvas) mm.update(canvas.nodes, vp, pixi.app.screen.width, pixi.app.screen.height);\n };\n\n // Pixi initializes asynchronously; the minimap renderer is created in\n // handlePixiReady. Poll until both exist, then attach the ticker. This\n // avoids a race where the effect runs before pixi is ready and the minimap\n // stays blank until the next document edit.\n const attach = () => {\n if (cancelled) return;\n const pixi = pixiRef.current;\n if (pixi && !pixi.destroyed && miniMapRef.current) {\n pixi.app.ticker.add(tickerFn);\n subscribed = pixi;\n } else {\n requestAnimationFrame(attach);\n }\n };\n attach();\n\n return () => {\n cancelled = true;\n if (subscribed && !subscribed.destroyed) subscribed.app.ticker.remove(tickerFn);\n };\n }, [doc]);\n\n const style: React.CSSProperties = { width: width ?? '100%', height: height ?? '100%' };\n\n return (\n <div ref={rootRef} className={`flow-container ${className ?? ''}`} style={style}>\n <FlowProvider doc={doc} setDoc={outerSetDoc} selection={selection} setSelection={setSelection}>\n <FlowUIProvider showMiniMap={showMiniMap} showGrid={showGrid} gridSize={gridSize} showAI={showAI} readOnly={readOnly}>\n <FlowAIProvider initialConfig={aiConfig} showAIConfig={showAIConfig}>\n <FlowKeyboardShortcutsWire onUndo={guardedUndo} onRedo={guardedRedo} groupRef={groupRef} geoGroupRef={geoGroupRef} ungroupRef={ungroupRef} />\n <PdfExportWire pixiRef={pixiRef} exportPdfRef={exportPdfRef} />\n <UnifiedPanelGroup\n compact={compact}\n toolbar={!readOnly ? (\n <FlowToolbar\n compact={compact}\n onBack={onBack}\n onUndo={guardedUndo} onRedo={guardedRedo} canUndo={canUndo} canRedo={canRedo}\n onGroup={() => groupRef.current?.()} onGeoGroup={() => geoGroupRef.current?.()} onUngroup={() => ungroupRef.current?.()}\n onExportPNG={handleExportPNG}\n onExportPDF={handleExportPDF}\n onPublish={onPublish}\n />\n ) : null}\n >\n <FlowCanvas onPixiReady={handlePixiReady} />\n {/* Media content (markdown/mermaid/data/…) must stay visible in\n presentation & read-only preview — PixiJS only draws the node\n frame, so hiding this layer would blank every media node. */}\n <MediaOverlay />\n <PresentationGate>\n {!readOnly && <FlowZoomControls />}\n <Breadcrumb />\n </PresentationGate>\n {!readOnly && <FlowContextMenuWire />}\n <MiniMapWrapper canvasRef={miniMapCanvasRef} pixiRef={pixiRef} miniMapRef={miniMapRef} />\n <PresentationMode />\n </UnifiedPanelGroup>\n </FlowAIProvider>\n </FlowUIProvider>\n </FlowProvider>\n </div>\n );\n});\n\nexport default Flow;\n\ninterface TabDef {\n id: string;\n label: string;\n icon: string;\n showKey?: 'showPalette' | 'showInspector' | 'showOutline' | 'showJson' | 'showAI';\n}\n\n/**\n * Hides all editable overlay UI (toolbar, panels, breadcrumb) while in\n * presentation mode, keeping only the canvas visible for the slideshow.\n */\nfunction PresentationGate({ children }: { children: React.ReactNode }) {\n const { presentation } = useFlowUI();\n return (\n <div className=\"flow-overlay\" style={{ display: presentation ? 'none' : undefined }}>\n {children}\n </div>\n );\n}\n\nconst ALL_TAB_DEFS: TabDef[] = [\n { id: 'shapes', label: 'Shapes', icon: '▢', showKey: 'showPalette' },\n { id: 'icons', label: 'Icons', icon: '★', showKey: 'showPalette' },\n { id: 'media', label: 'Media', icon: '▶', showKey: 'showPalette' },\n { id: 'inspector', label: 'Inspector', icon: '⚙', showKey: 'showInspector' },\n { id: 'outline', label: 'Outline', icon: '☰', showKey: 'showOutline' },\n { id: 'json', label: 'JSON', icon: '{ }', showKey: 'showJson' },\n { id: 'ai', label: 'AI', icon: '✨', showKey: 'showAI' },\n];\n\nfunction tabContent(id: string): React.ReactNode {\n switch (id) {\n case 'shapes': return <ShapePaletteContent />;\n case 'icons': return <SvgStampContent />;\n case 'media': return <MediaStampContent />;\n case 'inspector': return <InspectorPanelContent />;\n case 'outline': return <OutlinePanelContent />;\n case 'json': return <JsonEditorPanel />;\n case 'ai': return <AIPanelContent />;\n default: return null;\n }\n}\n\n/** Tab ids whose panels stamp objects onto the canvas (auto-collapse in compact mode). */\nconst STAMP_PANEL_IDS = new Set(['shapes', 'icons', 'media']);\n\nfunction UnifiedPanelGroup({ children, toolbar, compact }: { children: React.ReactNode; toolbar?: React.ReactNode; compact?: boolean }) {\n const ui = useFlowUI();\n const { panelDocks, setPanelDock, presentation, readOnly, setPendingPlacement } = ui;\n const { setShowPalette, setShowInspector, setShowOutline, setShowJson, setShowAI } = ui;\n\n // Read-only: hide every editing panel (palette/inspector/outline/JSON/AI).\n const visibleTabs = readOnly\n ? []\n : ALL_TAB_DEFS.filter(def =>\n !def.showKey || (ui as any)[def.showKey],\n );\n\n const toPanelTab = (def: TabDef): PanelTab => ({\n id: def.id, label: def.label, icon: def.icon, content: tabContent(def.id),\n });\n\n // Compact (small-screen) mode: the chosen panel renders full-screen above\n // the canvas. Shape/icon/media stamp panels collapse automatically once a\n // stamp is picked (pendingPlacement armed) so the user is returned to the\n // canvas to tap it into place; a virtual ESC button on the canvas cancels\n // continuous placement.\n const [activePanel, setActivePanel] = useState<string | null>(null);\n const prevPlacementKeyRef = useRef<string | null>(null);\n useEffect(() => {\n if (!compact) {\n prevPlacementKeyRef.current = null;\n return;\n }\n const pp = ui.pendingPlacement;\n const key = pp ? `${pp.type}:${(pp.data as any)?.svgId ?? ''}:${(pp.data as any)?.mediaKind ?? ''}` : null;\n const prev = prevPlacementKeyRef.current;\n prevPlacementKeyRef.current = key;\n if (key && key !== prev && activePanel && STAMP_PANEL_IDS.has(activePanel)) {\n setActivePanel(null);\n }\n }, [ui.pendingPlacement, compact, activePanel]);\n\n // Floating-panel drag: highlight the dock edge it hovers over.\n const [dropHint, setDropHint] = useState<'left' | 'right' | null>(null);\n const handleDock = useCallback((tabId: string, dock: FlowPanelDock) => setPanelDock(tabId, dock), [setPanelDock]);\n // Each panel has its own close (✕) button; closed panels come back via the\n // matching toolbar toggle (shapes/icons/media share the palette flag).\n const handleClose = useCallback((tabId: string) => {\n if (tabId === 'inspector') setShowInspector(false);\n else if (tabId === 'outline') setShowOutline(false);\n else if (tabId === 'json') setShowJson(false);\n else if (tabId === 'ai') setShowAI(false);\n else setShowPalette(false);\n }, [setShowInspector, setShowOutline, setShowJson, setShowAI, setShowPalette]);\n\n // NOTE: every hook must run before this early return — moving hooks below it\n // would break the hooks-of-render contract in compact mode.\n if (compact && !presentation) {\n const activeDef = visibleTabs.find(d => d.id === activePanel) ?? null;\n return (\n <div className=\"flow-workspace\">\n {toolbar}\n {visibleTabs.length > 0 && (\n <div className=\"flow-mobile-panel-bar\">\n <Select\n placeholder=\"选择面板…\"\n options={visibleTabs.map(d => ({ value: d.id, label: d.label }))}\n value={activePanel ?? undefined}\n onChange={(v) => {\n // Switching panels always opens a clean \"pick a type\" state: a\n // stale armed stamp (continuous placement) would otherwise be\n // placed by the next canvas tap or toggle the tapped item off.\n setPendingPlacement(null);\n setActivePanel(String(v));\n }}\n />\n </div>\n )}\n <div className=\"flow-workspace-main\">\n <div className=\"flow-stage\">\n {children}\n </div>\n {activeDef && (\n <div className=\"flow-mobile-panel\">\n <div className=\"flow-mobile-panel-header\">\n <span className=\"flow-mobile-panel-title\">\n {activeDef.icon && <span className=\"flow-panel-tab-icon\">{activeDef.icon}</span>}\n {activeDef.label}\n </span>\n <button\n type=\"button\"\n className=\"flow-mobile-panel-close\"\n aria-label=\"关闭面板\"\n onClick={() => setActivePanel(null)}\n >✕</button>\n </div>\n <div className=\"flow-mobile-panel-body\">{tabContent(activeDef.id)}</div>\n </div>\n )}\n </div>\n </div>\n );\n }\n\n // Desktop layout: the left/right panel groups are merged into one panel\n // list docked on the left by default. Panels are switched with a dropdown\n // Select; users can move any panel to the right dock or let it float (via\n // header buttons or by dragging it onto a workspace edge). Docks without\n // panels are not rendered so the canvas fills the freed space.\n const getDock = (id: string): FlowPanelDock => panelDocks[id] ?? 'left';\n const leftTabs: PanelTab[] = visibleTabs\n .filter(def => getDock(def.id) === 'left')\n .map(toPanelTab);\n const rightTabs: PanelTab[] = visibleTabs\n .filter(def => getDock(def.id) === 'right')\n .map(toPanelTab);\n const floatList = visibleTabs.filter(def => getDock(def.id) === 'float');\n\n return (\n <div className=\"flow-workspace\">\n {/* Slim full-width toolbar strip above panels + canvas. */}\n {!presentation && toolbar}\n <div className=\"flow-workspace-main\">\n {!presentation && leftTabs.length > 0 && (\n <FlowPanelGroup\n tabs={leftTabs}\n defaultTab={leftTabs[0]?.id}\n resizeEdge=\"right\"\n defaultWidth={220}\n dock=\"left\"\n onDock={handleDock}\n onClose={handleClose}\n />\n )}\n <div className=\"flow-stage\">\n {children}\n </div>\n {!presentation && rightTabs.length > 0 && (\n <FlowPanelGroup\n tabs={rightTabs}\n defaultTab={rightTabs[0]?.id}\n resizeEdge=\"left\"\n defaultWidth={220}\n dock=\"right\"\n onDock={handleDock}\n onClose={handleClose}\n />\n )}\n {/* Floating panels drift above the docks; dragging one over a\n workspace edge shows a drop-zone hint and dropping attaches it to\n that dock. Rendered as a direct child of the (position:relative)\n workspace-main so they are not clipped by the stage's\n `overflow: hidden`. */}\n {!presentation && floatList.map((def, i) => (\n <FlowPanelGroup\n key={def.id}\n tabs={[toPanelTab(def)]}\n resizeEdge=\"right\"\n defaultWidth={220}\n dock=\"float\"\n style={{ top: 48 + i * 40, left: 24 + i * 30 }}\n onDock={handleDock}\n onClose={handleClose}\n onDragHintChange={setDropHint}\n />\n ))}\n {!presentation && dropHint && (\n <div className={`flow-dock-dropzone flow-dock-dropzone--${dropHint}`}>\n <span>{dropHint === 'left' ? '⇤ 停靠左侧' : '停靠右侧 ⇥'}</span>\n </div>\n )}\n </div>\n </div>\n );\n}\n\nfunction MiniMapWrapper({\n canvasRef,\n pixiRef,\n miniMapRef,\n}: {\n canvasRef: React.RefObject<HTMLCanvasElement | null>;\n pixiRef: React.RefObject<PixiApp | null>;\n miniMapRef: React.RefObject<MiniMapRenderer | null>;\n}) {\n const { showMiniMap, setViewport, presentation } = useFlowUI();\n const draggingRef = useRef(false);\n\n // Move the main canvas so that the world point under the minimap click is\n // centered in the viewport. Routed through setViewport so the change syncs\n // with the ViewportManager (setting world.position directly would be\n // overwritten on the next viewport apply).\n const navigateTo = useCallback((clientX: number, clientY: number) => {\n const canvas = canvasRef.current;\n const pixi = pixiRef.current;\n const mm = miniMapRef.current;\n if (!canvas || !pixi || pixi.destroyed || !mm) return;\n\n const rect = canvas.getBoundingClientRect();\n if (rect.width === 0 || rect.height === 0) return;\n // CSS pixels -> canvas backing pixels.\n const { width: cw, height: ch } = mm.size;\n const px = ((clientX - rect.left) / rect.width) * cw;\n const py = ((clientY - rect.top) / rect.height) * ch;\n\n const world = mm.minimapToWorld(px, py);\n if (!world) return;\n\n const zoom = pixi.world.scale.x || 1;\n const screenW = pixi.app.screen.width;\n const screenH = pixi.app.screen.height;\n setViewport({\n x: screenW / 2 - world.x * zoom,\n y: screenH / 2 - world.y * zoom,\n zoom,\n });\n }, [canvasRef, pixiRef, miniMapRef, setViewport]);\n\n const onPointerDown = useCallback((e: React.PointerEvent<HTMLDivElement>) => {\n e.preventDefault();\n (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);\n draggingRef.current = true;\n navigateTo(e.clientX, e.clientY);\n }, [navigateTo]);\n\n const onPointerMove = useCallback((e: React.PointerEvent<HTMLDivElement>) => {\n if (!draggingRef.current) return;\n navigateTo(e.clientX, e.clientY);\n }, [navigateTo]);\n\n const onPointerUp = useCallback((e: React.PointerEvent<HTMLDivElement>) => {\n draggingRef.current = false;\n try { (e.currentTarget as HTMLElement).releasePointerCapture(e.pointerId); } catch { /* noop */ }\n }, []);\n\n return (\n <div\n className=\"flow-minimap\"\n style={{ display: showMiniMap && !presentation ? undefined : 'none', cursor: 'pointer' }}\n onPointerDown={onPointerDown}\n onPointerMove={onPointerMove}\n onPointerUp={onPointerUp}\n title=\"点击或拖动以移动画布\"\n >\n <canvas ref={canvasRef} width={180} height={120} style={{ width: '100%', height: '100%', pointerEvents: 'none' }} />\n </div>\n );\n}\n\n/**\n * Find the line range (1-based) of a JSON object with the given id.\n */\nfunction findElementLineRange(jsonStr: string, elementId: string): { startLine: number; endLine: number } | null {\n const idPattern = `\"id\": \"${elementId}\"`;\n const idx = jsonStr.indexOf(idPattern);\n if (idx === -1) return null;\n\n let braceDepth = 0;\n let startIdx = -1;\n for (let i = idx; i >= 0; i--) {\n if (jsonStr[i] === '}') braceDepth++;\n if (jsonStr[i] === '{') {\n if (braceDepth === 0) { startIdx = i; break; }\n braceDepth--;\n }\n }\n if (startIdx === -1) return null;\n\n braceDepth = 0;\n let endIdx = -1;\n for (let i = startIdx; i < jsonStr.length; i++) {\n if (jsonStr[i] === '{') braceDepth++;\n if (jsonStr[i] === '}') {\n braceDepth--;\n if (braceDepth === 0) { endIdx = i; break; }\n }\n }\n if (endIdx === -1) return null;\n\n const startLine = jsonStr.slice(0, startIdx).split('\\n').length;\n const endLine = jsonStr.slice(0, endIdx + 1).split('\\n').length;\n return { startLine, endLine };\n}\n\nfunction clearLineHighlights(container: HTMLElement) {\n container.querySelectorAll('.cm-line.flow-json-hl').forEach(el => el.classList.remove('flow-json-hl'));\n}\n\nfunction applyLineHighlightsViaView(handle: CodeEditorHandle, startLine: number, endLine: number) {\n const view = handle.getView();\n if (!view) return;\n for (let ln = startLine; ln <= endLine; ln++) {\n try {\n const lineInfo = view.state.doc.line(ln);\n const dom = view.domAtPos(lineInfo.from);\n const lineEl = dom.node instanceof HTMLElement\n ? dom.node.closest('.cm-line')\n : dom.node.parentElement?.closest('.cm-line');\n if (lineEl) lineEl.classList.add('flow-json-hl');\n } catch { /* line not in viewport */ }\n }\n}\n\nfunction JsonEditorPanel() {\n const { setDoc, currentCanvasId, selection, currentCanvas } = useFlowContext();\n const [jsonStr, setJsonStr] = useState('');\n const [error, setError] = useState<string | null>(null);\n const skipNextSync = useRef(false);\n const editorRef = useRef<CodeEditorHandle>(null);\n const wrapRef = useRef<HTMLDivElement>(null);\n\n const singleId = useMemo(() => {\n const total = selection.nodeIds.size + selection.edgeIds.size + selection.lineIds.size;\n if (total !== 1) return null;\n if (selection.nodeIds.size === 1) return [...selection.nodeIds][0];\n if (selection.edgeIds.size === 1) return [...selection.edgeIds][0];\n if (selection.lineIds.size === 1) return [...selection.lineIds][0];\n return null;\n }, [selection]);\n\n useEffect(() => {\n if (skipNextSync.current) { skipNextSync.current = false; return; }\n setJsonStr(JSON.stringify(currentCanvas, null, 2));\n setError(null);\n }, [currentCanvas]);\n\n useEffect(() => {\n const wrap = wrapRef.current;\n if (wrap) clearLineHighlights(wrap);\n if (!singleId) return;\n\n const timer = setTimeout(() => {\n const range = findElementLineRange(jsonStr, singleId);\n if (!range || !wrapRef.current) return;\n\n const view = editorRef.current?.getView();\n if (!view) return;\n\n const w = wrapRef.current;\n const scroller = view.scrollDOM;\n\n scroller.style.scrollBehavior = 'smooth';\n\n const lineInfo = view.state.doc.line(range.startLine);\n view.dispatch({\n effects: EditorView.scrollIntoView(lineInfo.from, { y: 'start', yMargin: 0 }),\n });\n\n const onScrollEnd = () => {\n scroller.style.scrollBehavior = '';\n clearLineHighlights(w);\n applyLineHighlightsViaView(editorRef.current!, range.startLine, range.endLine);\n };\n\n let scrollTimer: ReturnType<typeof setTimeout>;\n const onScroll = () => {\n clearTimeout(scrollTimer);\n scrollTimer = setTimeout(onScrollEnd, 100);\n };\n scroller.addEventListener('scroll', onScroll);\n scrollTimer = setTimeout(() => {\n scroller.removeEventListener('scroll', onScroll);\n onScrollEnd();\n }, 800);\n }, 120);\n return () => clearTimeout(timer);\n }, [singleId, jsonStr]);\n\n const handleChange = useCallback((value: string) => {\n setJsonStr(value);\n try {\n const parsed = JSON.parse(value);\n if (parsed && Array.isArray(parsed.nodes) && Array.isArray(parsed.edges) && Array.isArray(parsed.lines)) {\n setError(null);\n skipNextSync.current = true;\n setDoc(prev => ({\n ...prev,\n canvases: { ...prev.canvases, [currentCanvasId]: parsed },\n }));\n } else {\n setError('JSON must have nodes, edges, lines arrays');\n }\n } catch (e: any) {\n setError(e.message?.slice(0, 60) ?? 'Invalid JSON');\n }\n }, [setDoc, currentCanvasId]);\n\n const label = singleId\n ? (selection.nodeIds.has(singleId) ? 'Node' : selection.edgeIds.has(singleId) ? 'Edge' : 'Line')\n : null;\n\n return (\n <div style={{ display: 'flex', flexDirection: 'column', height: '100%', minHeight: 0 }}>\n <div className=\"flow-json-header\">\n <span className=\"flow-json-scope\">\n {label ? `${label}: ${singleId!.slice(0, 8)}` : `Canvas`}\n </span>\n </div>\n {error && (\n <div style={{ fontSize: 11, color: 'oklch(var(--er))', padding: '4px 0', flexShrink: 0 }}>\n {error}\n </div>\n )}\n <div ref={wrapRef} style={{ flex: 1, minHeight: 0 }}>\n <CodeEditor\n ref={editorRef}\n value={jsonStr}\n onChange={handleChange}\n language=\"json\"\n lineNumbers={false}\n height=\"100%\"\n highlightActiveLine={false}\n />\n </div>\n </div>\n );\n}\n\nfunction FlowContextMenuWire() {\n const { contextMenu, setContextMenu } = useFlowUI();\n const { doc, setDoc, currentCanvasId, pushCanvas, setSelection } = useFlowContext();\n const { duplicate } = useClipboard();\n\n if (!contextMenu) return null;\n\n const canvas = doc.canvases[currentCanvasId];\n if (!canvas) { setContextMenu(null); return null; }\n\n const node = contextMenu.nodeId ? canvas.nodes.find(n => n.id === contextMenu.nodeId) : null;\n const items: ContextMenuItem[] = [];\n\n if (node) {\n // Duplicate the node (Cmd/Ctrl+D equivalent). Ensure it is selected first so\n // the clone covers the intended target even on a right-click without a prior\n // left-click selection.\n items.push({\n label: 'Duplicate',\n icon: '⧉',\n action: () => {\n duplicate(24, 24, { nodeIds: new Set([node.id]) });\n },\n });\n items.push({ label: '---', action: () => {} });\n\n const hasChild = !!node.data.childCanvasId;\n\n if (hasChild) {\n items.push({\n label: 'Enter Sub-canvas',\n icon: '↓',\n action: () => {\n pushCanvas(node.data.childCanvasId!);\n setSelection({ nodeIds: new Set(), edgeIds: new Set(), lineIds: new Set() });\n },\n });\n items.push({ label: '---', action: () => {} });\n items.push({\n label: 'Delete Sub-canvas',\n icon: '🗑',\n danger: true,\n action: () => {\n const childId = node.data.childCanvasId!;\n setDoc(prev => {\n const canvases = { ...prev.canvases };\n collectAndRemoveCanvases(canvases, childId);\n const c = canvases[currentCanvasId];\n if (!c) return prev;\n canvases[currentCanvasId] = {\n ...c,\n nodes: c.nodes.map(n =>\n n.id === node.id\n ? { ...n, data: { ...n.data, childCanvasId: undefined } }\n : n,\n ),\n };\n const stack = prev.stack.filter(id => canvases[id]);\n if (stack.length === 0) stack.push(ROOT_CANVAS_ID);\n return { ...prev, canvases, stack };\n });\n },\n });\n } else {\n items.push({\n label: 'Create Sub-canvas',\n icon: '⊞',\n action: () => {\n const childCanvasId = generateId('canvas');\n setDoc(prev => {\n const c = prev.canvases[currentCanvasId];\n if (!c) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...c,\n nodes: c.nodes.map(n =>\n n.id === node.id\n ? { ...n, data: { ...n.data, childCanvasId } }\n : n,\n ),\n },\n [childCanvasId]: { nodes: [], edges: [], lines: [] },\n },\n };\n });\n pushCanvas(childCanvasId);\n },\n });\n }\n }\n\n if (items.length === 0) return null;\n\n return (\n <ContextMenu\n x={contextMenu.x}\n y={contextMenu.y}\n items={items}\n onClose={() => setContextMenu(null)}\n />\n );\n}\n\nfunction collectAndRemoveCanvases(\n canvases: Record<string, FlowCanvasData>,\n canvasId: string,\n): void {\n const canvas = canvases[canvasId];\n if (!canvas) return;\n for (const node of canvas.nodes) {\n if (node.data.childCanvasId) {\n collectAndRemoveCanvases(canvases, node.data.childCanvasId);\n }\n }\n delete canvases[canvasId];\n}\n"],"mappings":"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"}
|
|
@@ -19,9 +19,15 @@ function i() {
|
|
|
19
19
|
...e.canvases,
|
|
20
20
|
[s]: {
|
|
21
21
|
...t,
|
|
22
|
-
nodes: t.nodes.filter((e) => !a.nodeIds.has(e.id))
|
|
22
|
+
nodes: t.nodes.filter((e) => !a.nodeIds.has(e.id)).map((e) => e.parentId && a.nodeIds.has(e.parentId) ? {
|
|
23
|
+
...e,
|
|
24
|
+
parentId: void 0
|
|
25
|
+
} : e),
|
|
23
26
|
edges: t.edges.filter((e) => !a.edgeIds.has(e.id) && !a.nodeIds.has(e.source) && !a.nodeIds.has(e.target)),
|
|
24
|
-
lines: t.lines.filter((e) => !a.lineIds.has(e.id))
|
|
27
|
+
lines: t.lines.filter((e) => !a.lineIds.has(e.id)).map((e) => e.parentId && a.nodeIds.has(e.parentId) ? {
|
|
28
|
+
...e,
|
|
29
|
+
parentId: void 0
|
|
30
|
+
} : e)
|
|
25
31
|
}
|
|
26
32
|
}
|
|
27
33
|
} : e;
|
|
@@ -39,26 +45,29 @@ function i() {
|
|
|
39
45
|
]), f = n((e = 20, n = 20) => {
|
|
40
46
|
let r = l.current;
|
|
41
47
|
if (!r) return;
|
|
42
|
-
let
|
|
43
|
-
let
|
|
44
|
-
return
|
|
48
|
+
let a = /* @__PURE__ */ new Map(), u = r.nodes.map((r) => {
|
|
49
|
+
let i = t("node");
|
|
50
|
+
return a.set(r.id, i), {
|
|
45
51
|
...r,
|
|
46
|
-
id:
|
|
52
|
+
id: i,
|
|
47
53
|
position: {
|
|
48
54
|
x: r.position.x + e,
|
|
49
55
|
y: r.position.y + n
|
|
50
56
|
},
|
|
51
57
|
data: { ...r.data }
|
|
52
58
|
};
|
|
53
|
-
})
|
|
59
|
+
});
|
|
60
|
+
for (let e of u) e.parentId &&= a.get(e.parentId) ?? (i.nodes.some((t) => t.id === e.parentId) ? e.parentId : void 0);
|
|
61
|
+
let d = r.edges.filter((e) => a.has(e.source) && a.has(e.target)).map((e) => ({
|
|
54
62
|
...e,
|
|
55
63
|
id: t("edge"),
|
|
56
|
-
source:
|
|
57
|
-
target:
|
|
64
|
+
source: a.get(e.source),
|
|
65
|
+
target: a.get(e.target),
|
|
58
66
|
data: { ...e.data }
|
|
59
|
-
})),
|
|
67
|
+
})), f = r.lines.map((r) => ({
|
|
60
68
|
...r,
|
|
61
69
|
id: t("line"),
|
|
70
|
+
parentId: r.parentId ? a.get(r.parentId) ?? (i.nodes.some((e) => e.id === r.parentId) ? r.parentId : void 0) : void 0,
|
|
62
71
|
points: r.points.map((t) => ({
|
|
63
72
|
x: t.x + e,
|
|
64
73
|
y: t.y + n
|
|
@@ -72,28 +81,29 @@ function i() {
|
|
|
72
81
|
...e.canvases,
|
|
73
82
|
[s]: {
|
|
74
83
|
...t,
|
|
75
|
-
nodes: [...t.nodes, ...
|
|
76
|
-
edges: [...t.edges, ...
|
|
77
|
-
lines: [...t.lines, ...
|
|
84
|
+
nodes: [...t.nodes, ...u],
|
|
85
|
+
edges: [...t.edges, ...d],
|
|
86
|
+
lines: [...t.lines, ...f]
|
|
78
87
|
}
|
|
79
88
|
}
|
|
80
89
|
} : e;
|
|
81
90
|
}), c({
|
|
82
|
-
nodeIds: new Set(
|
|
83
|
-
edgeIds: new Set(
|
|
84
|
-
lineIds: new Set(
|
|
91
|
+
nodeIds: new Set(u.map((e) => e.id)),
|
|
92
|
+
edgeIds: new Set(d.map((e) => e.id)),
|
|
93
|
+
lineIds: new Set(f.map((e) => e.id))
|
|
85
94
|
});
|
|
86
95
|
}, [
|
|
87
96
|
o,
|
|
88
97
|
s,
|
|
89
|
-
c
|
|
98
|
+
c,
|
|
99
|
+
i
|
|
90
100
|
]), p = l.current !== null;
|
|
91
101
|
return {
|
|
92
102
|
copy: u,
|
|
93
103
|
cut: d,
|
|
94
104
|
paste: f,
|
|
95
105
|
duplicate: n((e = 24, n = 24, r) => {
|
|
96
|
-
let l = r
|
|
106
|
+
let l = r ? r.nodeIds ?? /* @__PURE__ */ new Set() : a.nodeIds, u = r ? r.edgeIds ?? /* @__PURE__ */ new Set() : a.edgeIds, d = r ? r.lineIds ?? /* @__PURE__ */ new Set() : a.lineIds, f = i.nodes.filter((e) => l.has(e.id)), p = i.lines.filter((e) => d.has(e.id)), m = new Set(f.map((e) => e.id)), h = i.edges.filter((e) => u.has(e.id) || m.has(e.source) && m.has(e.target));
|
|
97
107
|
if (f.length === 0 && p.length === 0 && h.length === 0) return;
|
|
98
108
|
let g = /* @__PURE__ */ new Map(), _ = f.map((r) => {
|
|
99
109
|
let i = t("node");
|
|
@@ -117,6 +127,7 @@ function i() {
|
|
|
117
127
|
})), y = p.map((r) => ({
|
|
118
128
|
...r,
|
|
119
129
|
id: t("line"),
|
|
130
|
+
parentId: r.parentId ? g.get(r.parentId) ?? r.parentId : void 0,
|
|
120
131
|
points: r.points.map((t) => ({
|
|
121
132
|
x: t.x + e,
|
|
122
133
|
y: t.y + n
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useClipboard.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useClipboard.ts"],"sourcesContent":["import { useCallback, useRef } from 'react';\nimport { useFlowContext } from '../context/FlowContext';\nimport { generateId } from '../nodes/nodeUtils';\nimport type { FlowNode, FlowEdge, FlowLine } from '../types';\n\ninterface ClipboardData {\n nodes: FlowNode[];\n edges: FlowEdge[];\n lines: FlowLine[];\n}\n\n/**\n * Copy/paste/cut for selected flow elements.\n * Uses internal clipboard (not system clipboard) for structured data.\n */\nexport function useClipboard() {\n const { currentCanvas, selection, setDoc, currentCanvasId, setSelection } = useFlowContext();\n const clipboardRef = useRef<ClipboardData | null>(null);\n\n const copy = useCallback(() => {\n const nodes = currentCanvas.nodes.filter(n => selection.nodeIds.has(n.id));\n const edges = currentCanvas.edges.filter(e => selection.edgeIds.has(e.id));\n const lines = currentCanvas.lines.filter(l => selection.lineIds.has(l.id));\n\n // Also include edges that connect selected nodes\n const nodeIds = new Set(nodes.map(n => n.id));\n const connectedEdges = currentCanvas.edges.filter(\n e => nodeIds.has(e.source) && nodeIds.has(e.target) && !selection.edgeIds.has(e.id),\n );\n\n clipboardRef.current = {\n nodes: [...nodes],\n edges: [...edges, ...connectedEdges],\n lines: [...lines],\n };\n }, [currentCanvas, selection]);\n\n const cut = useCallback(() => {\n copy();\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...canvas,\n nodes: canvas.nodes.filter(n => !selection.nodeIds.has(n.id)),\n edges: canvas.edges.filter(e =>\n !selection.edgeIds.has(e.id) &&\n !selection.nodeIds.has(e.source) &&\n !selection.nodeIds.has(e.target),\n ),\n lines: canvas.lines.filter(l => !selection.lineIds.has(l.id)),\n },\n },\n };\n });\n setSelection({ nodeIds: new Set(), edgeIds: new Set(), lineIds: new Set() });\n }, [copy, setDoc, currentCanvasId, selection, setSelection]);\n\n const paste = useCallback((offsetX = 20, offsetY = 20) => {\n const cb = clipboardRef.current;\n if (!cb) return;\n\n const idMap = new Map<string, string>();\n const newNodes: FlowNode[] = cb.nodes.map(n => {\n const newId = generateId('node');\n idMap.set(n.id, newId);\n return {\n ...n,\n id: newId,\n position: { x: n.position.x + offsetX, y: n.position.y + offsetY },\n data: { ...n.data },\n };\n });\n\n const newEdges: FlowEdge[] = cb.edges\n .filter(e => idMap.has(e.source) && idMap.has(e.target))\n .map(e => ({\n ...e,\n id: generateId('edge'),\n source: idMap.get(e.source)!,\n target: idMap.get(e.target)!,\n data: { ...e.data },\n }));\n\n const newLines: FlowLine[] = cb.lines.map(l => ({\n ...l,\n id: generateId('line'),\n points: l.points.map(p => ({ x: p.x + offsetX, y: p.y + offsetY })),\n }));\n\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...canvas,\n nodes: [...canvas.nodes, ...newNodes],\n edges: [...canvas.edges, ...newEdges],\n lines: [...canvas.lines, ...newLines],\n },\n },\n };\n });\n\n setSelection({\n nodeIds: new Set(newNodes.map(n => n.id)),\n edgeIds: new Set(newEdges.map(e => e.id)),\n lineIds: new Set(newLines.map(l => l.id)),\n });\n }, [setDoc, currentCanvasId, setSelection]);\n\n const hasClipboard = clipboardRef.current !== null;\n\n /**\n * Duplicate the current selection in place (Cmd/Ctrl+D). Unlike copy+paste\n * this does NOT touch the clipboard, so a prior copy stays intact. Clones the\n * selected nodes (plus edges wholly between them) and lines with a small\n * offset, then selects the clones.\n */\n const duplicate = useCallback((\n offsetX = 24,\n offsetY = 24,\n targets?: { nodeIds?: Set<string>; edgeIds?: Set<string>; lineIds?: Set<string> },\n ) => {\n const nodeSel = targets?.nodeIds ?? selection.nodeIds;\n const edgeSel = targets?.edgeIds ?? selection.edgeIds;\n const lineSel = targets?.lineIds ?? selection.lineIds;\n const nodes = currentCanvas.nodes.filter(n => nodeSel.has(n.id));\n const lines = currentCanvas.lines.filter(l => lineSel.has(l.id));\n const nodeIdSet = new Set(nodes.map(n => n.id));\n const edges = currentCanvas.edges.filter(e =>\n edgeSel.has(e.id) ||\n (nodeIdSet.has(e.source) && nodeIdSet.has(e.target)),\n );\n if (nodes.length === 0 && lines.length === 0 && edges.length === 0) return;\n\n const idMap = new Map<string, string>();\n // Assign fresh ids to nodes first so intra-selection edges can be remapped.\n const newNodes: FlowNode[] = nodes.map(n => {\n const newId = generateId('node');\n idMap.set(n.id, newId);\n return { ...n, id: newId, position: { x: n.position.x + offsetX, y: n.position.y + offsetY }, data: { ...n.data } };\n });\n // Re-parent cloned children onto cloned groups when both were duplicated.\n for (const n of newNodes) {\n if (n.parentId && idMap.has(n.parentId)) n.parentId = idMap.get(n.parentId);\n }\n\n const newEdges: FlowEdge[] = edges\n .filter(e => idMap.has(e.source) && idMap.has(e.target))\n .map(e => ({\n ...e,\n id: generateId('edge'),\n source: idMap.get(e.source)!,\n target: idMap.get(e.target)!,\n data: { ...e.data },\n }));\n\n const newLines: FlowLine[] = lines.map(l => ({\n ...l,\n id: generateId('line'),\n points: l.points.map(p => ({ x: p.x + offsetX, y: p.y + offsetY })),\n }));\n\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...canvas,\n nodes: [...canvas.nodes, ...newNodes],\n edges: [...canvas.edges, ...newEdges],\n lines: [...canvas.lines, ...newLines],\n },\n },\n };\n });\n\n setSelection({\n nodeIds: new Set(newNodes.map(n => n.id)),\n edgeIds: new Set(newEdges.map(e => e.id)),\n lineIds: new Set(newLines.map(l => l.id)),\n });\n }, [currentCanvas, selection, setDoc, currentCanvasId, setSelection]);\n\n return { copy, cut, paste, duplicate, hasClipboard };\n}\n"],"mappings":";;;;AAeA,SAAgB,IAAe;CAC7B,IAAM,EAAE,kBAAe,cAAW,WAAQ,oBAAiB,oBAAiB,EAAe,GACrF,IAAe,EAA6B,IAAI,GAEhD,IAAO,QAAkB;EAC7B,IAAM,IAAQ,EAAc,MAAM,QAAO,MAAK,EAAU,QAAQ,IAAI,EAAE,EAAE,CAAC,GACnE,IAAQ,EAAc,MAAM,QAAO,MAAK,EAAU,QAAQ,IAAI,EAAE,EAAE,CAAC,GACnE,IAAQ,EAAc,MAAM,QAAO,MAAK,EAAU,QAAQ,IAAI,EAAE,EAAE,CAAC,GAGnE,IAAU,IAAI,IAAI,EAAM,KAAI,MAAK,EAAE,EAAE,CAAC,GACtC,IAAiB,EAAc,MAAM,QACzC,MAAK,EAAQ,IAAI,EAAE,MAAM,KAAK,EAAQ,IAAI,EAAE,MAAM,KAAK,CAAC,EAAU,QAAQ,IAAI,EAAE,EAAE,CACpF;EAEA,EAAa,UAAU;GACrB,OAAO,CAAC,GAAG,CAAK;GAChB,OAAO,CAAC,GAAG,GAAO,GAAG,CAAc;GACnC,OAAO,CAAC,GAAG,CAAK;EAClB;CACF,GAAG,CAAC,GAAe,CAAS,CAAC,GAEvB,IAAM,QAAkB;EAsB5B,AArBA,EAAK,GACL,GAAO,MAAQ;GACb,IAAM,IAAS,EAAK,SAAS;GAE7B,OADK,IACE;IACL,GAAG;IACH,UAAU;KACR,GAAG,EAAK;MACP,IAAkB;MACjB,GAAG;MACH,OAAO,EAAO,MAAM,QAAO,MAAK,CAAC,EAAU,QAAQ,IAAI,EAAE,EAAE,CAAC;MAC5D,OAAO,EAAO,MAAM,QAAO,MACzB,CAAC,EAAU,QAAQ,IAAI,EAAE,EAAE,KAC3B,CAAC,EAAU,QAAQ,IAAI,EAAE,MAAM,KAC/B,CAAC,EAAU,QAAQ,IAAI,EAAE,MAAM,CACjC;MACA,OAAO,EAAO,MAAM,QAAO,MAAK,CAAC,EAAU,QAAQ,IAAI,EAAE,EAAE,CAAC;KAC9D;IACF;GACF,IAhBoB;EAiBtB,CAAC,GACD,EAAa;GAAE,yBAAS,IAAI,IAAI;GAAG,yBAAS,IAAI,IAAI;GAAG,yBAAS,IAAI,IAAI;EAAE,CAAC;CAC7E,GAAG;EAAC;EAAM;EAAQ;EAAiB;EAAW;CAAY,CAAC,GAErD,IAAQ,GAAa,IAAU,IAAI,IAAU,OAAO;EACxD,IAAM,IAAK,EAAa;EACxB,IAAI,CAAC,GAAI;EAET,IAAM,oBAAQ,IAAI,IAAoB,GAChC,IAAuB,EAAG,MAAM,KAAI,MAAK;GAC7C,IAAM,IAAQ,EAAW,MAAM;GAE/B,OADA,EAAM,IAAI,EAAE,IAAI,CAAK,GACd;IACL,GAAG;IACH,IAAI;IACJ,UAAU;KAAE,GAAG,EAAE,SAAS,IAAI;KAAS,GAAG,EAAE,SAAS,IAAI;IAAQ;IACjE,MAAM,EAAE,GAAG,EAAE,KAAK;GACpB;EACF,CAAC,GAEK,IAAuB,EAAG,MAC7B,QAAO,MAAK,EAAM,IAAI,EAAE,MAAM,KAAK,EAAM,IAAI,EAAE,MAAM,CAAC,CAAC,CACvD,KAAI,OAAM;GACT,GAAG;GACH,IAAI,EAAW,MAAM;GACrB,QAAQ,EAAM,IAAI,EAAE,MAAM;GAC1B,QAAQ,EAAM,IAAI,EAAE,MAAM;GAC1B,MAAM,EAAE,GAAG,EAAE,KAAK;EACpB,EAAE,GAEE,IAAuB,EAAG,MAAM,KAAI,OAAM;GAC9C,GAAG;GACH,IAAI,EAAW,MAAM;GACrB,QAAQ,EAAE,OAAO,KAAI,OAAM;IAAE,GAAG,EAAE,IAAI;IAAS,GAAG,EAAE,IAAI;GAAQ,EAAE;EACpE,EAAE;EAmBF,AAjBA,GAAO,MAAQ;GACb,IAAM,IAAS,EAAK,SAAS;GAE7B,OADK,IACE;IACL,GAAG;IACH,UAAU;KACR,GAAG,EAAK;MACP,IAAkB;MACjB,GAAG;MACH,OAAO,CAAC,GAAG,EAAO,OAAO,GAAG,CAAQ;MACpC,OAAO,CAAC,GAAG,EAAO,OAAO,GAAG,CAAQ;MACpC,OAAO,CAAC,GAAG,EAAO,OAAO,GAAG,CAAQ;KACtC;IACF;GACF,IAZoB;EAatB,CAAC,GAED,EAAa;GACX,SAAS,IAAI,IAAI,EAAS,KAAI,MAAK,EAAE,EAAE,CAAC;GACxC,SAAS,IAAI,IAAI,EAAS,KAAI,MAAK,EAAE,EAAE,CAAC;GACxC,SAAS,IAAI,IAAI,EAAS,KAAI,MAAK,EAAE,EAAE,CAAC;EAC1C,CAAC;CACH,GAAG;EAAC;EAAQ;EAAiB;CAAY,CAAC,GAEpC,IAAe,EAAa,YAAY;CA6E9C,OAAO;EAAE;EAAM;EAAK;EAAO,WArET,GAChB,IAAU,IACV,IAAU,IACV,MACG;GACH,IAAM,IAAU,GAAS,WAAW,EAAU,SACxC,IAAU,GAAS,WAAW,EAAU,SACxC,IAAU,GAAS,WAAW,EAAU,SACxC,IAAQ,EAAc,MAAM,QAAO,MAAK,EAAQ,IAAI,EAAE,EAAE,CAAC,GACzD,IAAQ,EAAc,MAAM,QAAO,MAAK,EAAQ,IAAI,EAAE,EAAE,CAAC,GACzD,IAAY,IAAI,IAAI,EAAM,KAAI,MAAK,EAAE,EAAE,CAAC,GACxC,IAAQ,EAAc,MAAM,QAAO,MACvC,EAAQ,IAAI,EAAE,EAAE,KACf,EAAU,IAAI,EAAE,MAAM,KAAK,EAAU,IAAI,EAAE,MAAM,CACpD;GACA,IAAI,EAAM,WAAW,KAAK,EAAM,WAAW,KAAK,EAAM,WAAW,GAAG;GAEpE,IAAM,oBAAQ,IAAI,IAAoB,GAEhC,IAAuB,EAAM,KAAI,MAAK;IAC1C,IAAM,IAAQ,EAAW,MAAM;IAE/B,OADA,EAAM,IAAI,EAAE,IAAI,CAAK,GACd;KAAE,GAAG;KAAG,IAAI;KAAO,UAAU;MAAE,GAAG,EAAE,SAAS,IAAI;MAAS,GAAG,EAAE,SAAS,IAAI;KAAQ;KAAG,MAAM,EAAE,GAAG,EAAE,KAAK;IAAE;GACpH,CAAC;GAED,KAAK,IAAM,KAAK,GACd,AAAI,EAAE,YAAY,EAAM,IAAI,EAAE,QAAQ,MAAG,EAAE,WAAW,EAAM,IAAI,EAAE,QAAQ;GAG5E,IAAM,IAAuB,EAC1B,QAAO,MAAK,EAAM,IAAI,EAAE,MAAM,KAAK,EAAM,IAAI,EAAE,MAAM,CAAC,CAAC,CACvD,KAAI,OAAM;IACT,GAAG;IACH,IAAI,EAAW,MAAM;IACrB,QAAQ,EAAM,IAAI,EAAE,MAAM;IAC1B,QAAQ,EAAM,IAAI,EAAE,MAAM;IAC1B,MAAM,EAAE,GAAG,EAAE,KAAK;GACpB,EAAE,GAEE,IAAuB,EAAM,KAAI,OAAM;IAC3C,GAAG;IACH,IAAI,EAAW,MAAM;IACrB,QAAQ,EAAE,OAAO,KAAI,OAAM;KAAE,GAAG,EAAE,IAAI;KAAS,GAAG,EAAE,IAAI;IAAQ,EAAE;GACpE,EAAE;GAmBF,AAjBA,GAAO,MAAQ;IACb,IAAM,IAAS,EAAK,SAAS;IAE7B,OADK,IACE;KACL,GAAG;KACH,UAAU;MACR,GAAG,EAAK;OACP,IAAkB;OACjB,GAAG;OACH,OAAO,CAAC,GAAG,EAAO,OAAO,GAAG,CAAQ;OACpC,OAAO,CAAC,GAAG,EAAO,OAAO,GAAG,CAAQ;OACpC,OAAO,CAAC,GAAG,EAAO,OAAO,GAAG,CAAQ;MACtC;KACF;IACF,IAZoB;GAatB,CAAC,GAED,EAAa;IACX,SAAS,IAAI,IAAI,EAAS,KAAI,MAAK,EAAE,EAAE,CAAC;IACxC,SAAS,IAAI,IAAI,EAAS,KAAI,MAAK,EAAE,EAAE,CAAC;IACxC,SAAS,IAAI,IAAI,EAAS,KAAI,MAAK,EAAE,EAAE,CAAC;GAC1C,CAAC;EACH,GAAG;GAAC;GAAe;GAAW;GAAQ;GAAiB;EAAY,CAExC;EAAW;CAAa;AACrD"}
|
|
1
|
+
{"version":3,"file":"useClipboard.js","names":[],"sources":["../../../../../src/components/pro/Flow/hooks/useClipboard.ts"],"sourcesContent":["import { useCallback, useRef } from 'react';\nimport { useFlowContext } from '../context/FlowContext';\nimport { generateId } from '../nodes/nodeUtils';\nimport type { FlowNode, FlowEdge, FlowLine } from '../types';\n\ninterface ClipboardData {\n nodes: FlowNode[];\n edges: FlowEdge[];\n lines: FlowLine[];\n}\n\n/**\n * Copy/paste/cut for selected flow elements.\n * Uses internal clipboard (not system clipboard) for structured data.\n */\nexport function useClipboard() {\n const { currentCanvas, selection, setDoc, currentCanvasId, setSelection } = useFlowContext();\n const clipboardRef = useRef<ClipboardData | null>(null);\n\n const copy = useCallback(() => {\n const nodes = currentCanvas.nodes.filter(n => selection.nodeIds.has(n.id));\n const edges = currentCanvas.edges.filter(e => selection.edgeIds.has(e.id));\n const lines = currentCanvas.lines.filter(l => selection.lineIds.has(l.id));\n\n // Also include edges that connect selected nodes\n const nodeIds = new Set(nodes.map(n => n.id));\n const connectedEdges = currentCanvas.edges.filter(\n e => nodeIds.has(e.source) && nodeIds.has(e.target) && !selection.edgeIds.has(e.id),\n );\n\n clipboardRef.current = {\n nodes: [...nodes],\n edges: [...edges, ...connectedEdges],\n lines: [...lines],\n };\n }, [currentCanvas, selection]);\n\n const cut = useCallback(() => {\n copy();\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...canvas,\n nodes: canvas.nodes.filter(n => !selection.nodeIds.has(n.id))\n .map(n => n.parentId && selection.nodeIds.has(n.parentId) ? { ...n, parentId: undefined } : n),\n edges: canvas.edges.filter(e =>\n !selection.edgeIds.has(e.id) &&\n !selection.nodeIds.has(e.source) &&\n !selection.nodeIds.has(e.target),\n ),\n lines: canvas.lines.filter(l => !selection.lineIds.has(l.id))\n .map(l => l.parentId && selection.nodeIds.has(l.parentId) ? { ...l, parentId: undefined } : l),\n },\n },\n };\n });\n setSelection({ nodeIds: new Set(), edgeIds: new Set(), lineIds: new Set() });\n }, [copy, setDoc, currentCanvasId, selection, setSelection]);\n\n const paste = useCallback((offsetX = 20, offsetY = 20) => {\n const cb = clipboardRef.current;\n if (!cb) return;\n\n const idMap = new Map<string, string>();\n const newNodes: FlowNode[] = cb.nodes.map(n => {\n const newId = generateId('node');\n idMap.set(n.id, newId);\n return {\n ...n,\n id: newId,\n position: { x: n.position.x + offsetX, y: n.position.y + offsetY },\n data: { ...n.data },\n };\n });\n\n for (const n of newNodes) {\n if (n.parentId) n.parentId = idMap.get(n.parentId)\n ?? (currentCanvas.nodes.some(parent => parent.id === n.parentId) ? n.parentId : undefined);\n }\n\n const newEdges: FlowEdge[] = cb.edges\n .filter(e => idMap.has(e.source) && idMap.has(e.target))\n .map(e => ({\n ...e,\n id: generateId('edge'),\n source: idMap.get(e.source)!,\n target: idMap.get(e.target)!,\n data: { ...e.data },\n }));\n\n const newLines: FlowLine[] = cb.lines.map(l => ({\n ...l,\n id: generateId('line'),\n parentId: l.parentId ? idMap.get(l.parentId)\n ?? (currentCanvas.nodes.some(parent => parent.id === l.parentId) ? l.parentId : undefined) : undefined,\n points: l.points.map(p => ({ x: p.x + offsetX, y: p.y + offsetY })),\n }));\n\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...canvas,\n nodes: [...canvas.nodes, ...newNodes],\n edges: [...canvas.edges, ...newEdges],\n lines: [...canvas.lines, ...newLines],\n },\n },\n };\n });\n\n setSelection({\n nodeIds: new Set(newNodes.map(n => n.id)),\n edgeIds: new Set(newEdges.map(e => e.id)),\n lineIds: new Set(newLines.map(l => l.id)),\n });\n }, [setDoc, currentCanvasId, setSelection, currentCanvas]);\n\n const hasClipboard = clipboardRef.current !== null;\n\n /**\n * Duplicate the current selection in place (Cmd/Ctrl+D). Unlike copy+paste\n * this does NOT touch the clipboard, so a prior copy stays intact. Clones the\n * selected nodes (plus edges wholly between them) and lines with a small\n * offset, then selects the clones.\n */\n const duplicate = useCallback((\n offsetX = 24,\n offsetY = 24,\n targets?: { nodeIds?: Set<string>; edgeIds?: Set<string>; lineIds?: Set<string> },\n ) => {\n const nodeSel = targets ? targets.nodeIds ?? new Set<string>() : selection.nodeIds;\n const edgeSel = targets ? targets.edgeIds ?? new Set<string>() : selection.edgeIds;\n const lineSel = targets ? targets.lineIds ?? new Set<string>() : selection.lineIds;\n const nodes = currentCanvas.nodes.filter(n => nodeSel.has(n.id));\n const lines = currentCanvas.lines.filter(l => lineSel.has(l.id));\n const nodeIdSet = new Set(nodes.map(n => n.id));\n const edges = currentCanvas.edges.filter(e =>\n edgeSel.has(e.id) ||\n (nodeIdSet.has(e.source) && nodeIdSet.has(e.target)),\n );\n if (nodes.length === 0 && lines.length === 0 && edges.length === 0) return;\n\n const idMap = new Map<string, string>();\n // Assign fresh ids to nodes first so intra-selection edges can be remapped.\n const newNodes: FlowNode[] = nodes.map(n => {\n const newId = generateId('node');\n idMap.set(n.id, newId);\n return { ...n, id: newId, position: { x: n.position.x + offsetX, y: n.position.y + offsetY }, data: { ...n.data } };\n });\n // Re-parent cloned children onto cloned groups when both were duplicated.\n for (const n of newNodes) {\n if (n.parentId && idMap.has(n.parentId)) n.parentId = idMap.get(n.parentId);\n }\n\n const newEdges: FlowEdge[] = edges\n .filter(e => idMap.has(e.source) && idMap.has(e.target))\n .map(e => ({\n ...e,\n id: generateId('edge'),\n source: idMap.get(e.source)!,\n target: idMap.get(e.target)!,\n data: { ...e.data },\n }));\n\n const newLines: FlowLine[] = lines.map(l => ({\n ...l,\n id: generateId('line'),\n parentId: l.parentId ? idMap.get(l.parentId) ?? l.parentId : undefined,\n points: l.points.map(p => ({ x: p.x + offsetX, y: p.y + offsetY })),\n }));\n\n setDoc(prev => {\n const canvas = prev.canvases[currentCanvasId];\n if (!canvas) return prev;\n return {\n ...prev,\n canvases: {\n ...prev.canvases,\n [currentCanvasId]: {\n ...canvas,\n nodes: [...canvas.nodes, ...newNodes],\n edges: [...canvas.edges, ...newEdges],\n lines: [...canvas.lines, ...newLines],\n },\n },\n };\n });\n\n setSelection({\n nodeIds: new Set(newNodes.map(n => n.id)),\n edgeIds: new Set(newEdges.map(e => e.id)),\n lineIds: new Set(newLines.map(l => l.id)),\n });\n }, [currentCanvas, selection, setDoc, currentCanvasId, setSelection]);\n\n return { copy, cut, paste, duplicate, hasClipboard };\n}\n"],"mappings":"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"}
|
|
@@ -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":"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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 {
|
|
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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dishui",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.69",
|
|
4
4
|
"description": "New Components UI Framework with AI Powered",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"docs:generate": "node scripts/check-docs.mjs --write",
|
|
64
64
|
"docs:check": "node scripts/check-docs.mjs",
|
|
65
65
|
"test:docs": "node --test scripts/check-docs.test.mjs",
|
|
66
|
+
"test:flow": "node --test scripts/flow-hooks.test.mjs scripts/flow-import-export.test.mjs",
|
|
66
67
|
"lint": "oxlint --config .oxlintrc.json src",
|
|
67
68
|
"lint:fix": "oxlint --config .oxlintrc.json --fix src",
|
|
68
69
|
"prepublishOnly": "npm run build",
|