@tldiagram/core-ui 1.90.1 → 1.92.0
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/index.d.ts +0 -1
- package/dist/index.js +6768 -7231
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +1 -1
- package/package.json +1 -1
- package/src/App.tsx +0 -4
- package/src/index.ts +0 -1
- package/src/pages/ViewEditor/components/EditorMenus.tsx +6 -4
- package/src/pages/ViewEditor/components/EditorOverlays.tsx +3 -2
- package/src/pages/ViewEditor/components/EmptyCanvasState.tsx +3 -2
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +17 -26
- package/src/pages/ViewEditor/hooks/useViewContextNeighbours.ts +1 -1
- package/src/pages/ViewEditor/index.tsx +103 -40
- package/dist/demo/DemoPage.d.ts +0 -9
- package/dist/demo/seed.d.ts +0 -9
- package/dist/demo/store.d.ts +0 -137
- package/src/demo/DemoPage.tsx +0 -184
- package/src/demo/seed.ts +0 -67
- package/src/demo/store.ts +0 -536
|
@@ -161,7 +161,7 @@ export declare function useCanvasInteractions({ viewId, canEdit, drawingMode: _d
|
|
|
161
161
|
clientX: number;
|
|
162
162
|
clientY: number;
|
|
163
163
|
}) => void;
|
|
164
|
-
showAddingElementAt: (clientX: number, clientY: number, expandResults?: boolean, mode?: "add" | "connect") => void;
|
|
164
|
+
showAddingElementAt: (clientX: number, clientY: number, expandResults?: boolean, mode?: "add" | "connect", forceConnect?: boolean) => void;
|
|
165
165
|
onNodesChange: (changes: NodeChange[]) => void;
|
|
166
166
|
onEdgesChange: (changes: EdgeChange[]) => void;
|
|
167
167
|
onNodeDragStart: NodeDragHandler;
|
package/package.json
CHANGED
package/src/App.tsx
CHANGED
|
@@ -10,7 +10,6 @@ import Settings from './pages/Settings'
|
|
|
10
10
|
import AppearanceSettings from './pages/AppearanceSettings'
|
|
11
11
|
import { HeaderProvider, useHeader } from './components/HeaderContext'
|
|
12
12
|
import TopMenuBar from './components/TopMenuBar'
|
|
13
|
-
import DemoPage, { DemoNavigator } from './demo/DemoPage'
|
|
14
13
|
import { ThemeProvider } from './context/ThemeContext'
|
|
15
14
|
import { ACCENT_DEFAULT, BACKGROUND_DEFAULT, ELEMENT_DEFAULT, hexToRgba } from './constants/colors'
|
|
16
15
|
import { platform } from './platform/local'
|
|
@@ -111,9 +110,6 @@ export default function App() {
|
|
|
111
110
|
{platform.getRoutes({ user: null })}
|
|
112
111
|
|
|
113
112
|
<Route path="/explore/shared/:token" element={<Box h="100vh" overflow="hidden"><HeaderProvider><SharedInfiniteZoom /></HeaderProvider></Box>} />
|
|
114
|
-
<Route path="/demo" element={<DemoNavigator />} />
|
|
115
|
-
<Route path="/demo/:id" element={<DemoPage />} />
|
|
116
|
-
|
|
117
113
|
<Route
|
|
118
114
|
element={
|
|
119
115
|
<HeaderProvider>
|
package/src/index.ts
CHANGED
|
@@ -148,7 +148,6 @@ export * from './crossBranch/store'
|
|
|
148
148
|
export * from './crossBranch/types'
|
|
149
149
|
|
|
150
150
|
// ─── Demo ────────────────────────────────────────────────────────────────────
|
|
151
|
-
export { default as DemoPage, DemoNavigator } from './demo/DemoPage'
|
|
152
151
|
export * from './demo/viewEditor'
|
|
153
152
|
|
|
154
153
|
// ─── Utilities ───────────────────────────────────────────────────────────────
|
|
@@ -26,7 +26,7 @@ interface ConnectorContextMenuProps {
|
|
|
26
26
|
onDelete: (edgeId: number) => void
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
export const ConnectorContextMenu: React.FC<ConnectorContextMenuProps> = ({
|
|
29
|
+
export const ConnectorContextMenu: React.FC<ConnectorContextMenuProps> = React.memo(({
|
|
30
30
|
menu,
|
|
31
31
|
onEdit,
|
|
32
32
|
onMoveSource,
|
|
@@ -68,14 +68,15 @@ export const ConnectorContextMenu: React.FC<ConnectorContextMenuProps> = ({
|
|
|
68
68
|
</VStack>
|
|
69
69
|
</Box>
|
|
70
70
|
)
|
|
71
|
-
}
|
|
71
|
+
})
|
|
72
|
+
ConnectorContextMenu.displayName = 'ConnectorContextMenu'
|
|
72
73
|
|
|
73
74
|
interface CanvasContextMenuProps {
|
|
74
75
|
menu: { x: number; y: number; flowX: number; flowY: number } | null
|
|
75
76
|
onAddElement: (x: number, y: number) => void
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
export const CanvasContextMenu: React.FC<CanvasContextMenuProps> = ({ menu, onAddElement }) => {
|
|
79
|
+
export const CanvasContextMenu: React.FC<CanvasContextMenuProps> = React.memo(({ menu, onAddElement }) => {
|
|
79
80
|
const { canEdit, snapToGrid, setSnapToGrid } = useViewEditorContext()
|
|
80
81
|
if (!menu) return null
|
|
81
82
|
|
|
@@ -109,4 +110,5 @@ export const CanvasContextMenu: React.FC<CanvasContextMenuProps> = ({ menu, onAd
|
|
|
109
110
|
</VStack>
|
|
110
111
|
</Box>
|
|
111
112
|
)
|
|
112
|
-
}
|
|
113
|
+
})
|
|
114
|
+
CanvasContextMenu.displayName = 'CanvasContextMenu'
|
|
@@ -22,7 +22,7 @@ interface EditorOverlaysProps {
|
|
|
22
22
|
rfNodes: RFNode[]
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
export const EditorOverlays: React.FC<EditorOverlaysProps> = ({
|
|
25
|
+
export const EditorOverlays: React.FC<EditorOverlaysProps> = React.memo(({
|
|
26
26
|
connectGhostPos,
|
|
27
27
|
clickConnectMode,
|
|
28
28
|
clickConnectCursorPos,
|
|
@@ -169,4 +169,5 @@ export const EditorOverlays: React.FC<EditorOverlaysProps> = ({
|
|
|
169
169
|
|
|
170
170
|
</>
|
|
171
171
|
)
|
|
172
|
-
}
|
|
172
|
+
})
|
|
173
|
+
EditorOverlays.displayName = 'EditorOverlays'
|
|
@@ -6,7 +6,7 @@ interface EmptyCanvasStateProps {
|
|
|
6
6
|
hasNodes: boolean
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
export const EmptyCanvasState: React.FC<EmptyCanvasStateProps> = ({ isMobile, hasNodes }) => {
|
|
9
|
+
export const EmptyCanvasState: React.FC<EmptyCanvasStateProps> = React.memo(({ isMobile, hasNodes }) => {
|
|
10
10
|
if (hasNodes) return null
|
|
11
11
|
|
|
12
12
|
return (
|
|
@@ -39,4 +39,5 @@ export const EmptyCanvasState: React.FC<EmptyCanvasStateProps> = ({ isMobile, ha
|
|
|
39
39
|
</Text>
|
|
40
40
|
</Box>
|
|
41
41
|
)
|
|
42
|
-
}
|
|
42
|
+
})
|
|
43
|
+
EmptyCanvasState.displayName = 'EmptyCanvasState'
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
} from '../../../utils/edgeDistribution'
|
|
37
37
|
|
|
38
38
|
const SNAP_RADIUS = 75
|
|
39
|
-
const CONTEXT_BOUNDARY_INSET = 36
|
|
40
39
|
|
|
41
40
|
interface CanvasInteractionOptions {
|
|
42
41
|
viewId: number | null
|
|
@@ -260,37 +259,29 @@ export function useCanvasInteractions({
|
|
|
260
259
|
const openConnectorPanelRef = useRef(openConnectorPanel)
|
|
261
260
|
openConnectorPanelRef.current = openConnectorPanel
|
|
262
261
|
|
|
263
|
-
const resolvePickerMode = useCallback((flowX: number, flowY: number, preferredMode: 'add' | 'connect') => {
|
|
262
|
+
const resolvePickerMode = useCallback((flowX: number, flowY: number, preferredMode: 'add' | 'connect', forceConnect = false) => {
|
|
264
263
|
if (preferredMode !== 'connect') return preferredMode
|
|
264
|
+
if (forceConnect) return 'connect'
|
|
265
265
|
|
|
266
|
-
const
|
|
267
|
-
if (
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
for (const node of mainNodes) {
|
|
275
|
-
const width = node.width ?? 200
|
|
276
|
-
const height = node.height ?? 90
|
|
277
|
-
minX = Math.min(minX, node.position.x)
|
|
278
|
-
minY = Math.min(minY, node.position.y)
|
|
279
|
-
maxX = Math.max(maxX, node.position.x + width)
|
|
280
|
-
maxY = Math.max(maxY, node.position.y + height)
|
|
281
|
-
}
|
|
266
|
+
const boundaryNode = rfNodesRef.current.find((node) => node.type === 'ContextBoundaryElement')
|
|
267
|
+
if (!boundaryNode) return 'add'
|
|
268
|
+
|
|
269
|
+
const boundaryData = boundaryNode.data as { width?: number; height?: number } | undefined
|
|
270
|
+
const width = boundaryData?.width ?? boundaryNode.width
|
|
271
|
+
const height = boundaryData?.height ?? boundaryNode.height
|
|
272
|
+
if (width == null || height == null) return 'add'
|
|
282
273
|
|
|
283
274
|
const withinBoundary =
|
|
284
|
-
flowX >=
|
|
285
|
-
flowX <=
|
|
286
|
-
flowY >=
|
|
287
|
-
flowY <=
|
|
275
|
+
flowX >= boundaryNode.position.x &&
|
|
276
|
+
flowX <= boundaryNode.position.x + width &&
|
|
277
|
+
flowY >= boundaryNode.position.y &&
|
|
278
|
+
flowY <= boundaryNode.position.y + height
|
|
288
279
|
|
|
289
280
|
return withinBoundary ? 'add' : 'connect'
|
|
290
281
|
}, [rfNodesRef])
|
|
291
282
|
|
|
292
283
|
// ── showAddingElementAt ─────────────────────────────────────────────────────
|
|
293
|
-
const showAddingElementAt = useCallback((clientX: number, clientY: number, expandResults = false, mode: 'add' | 'connect' = 'add') => {
|
|
284
|
+
const showAddingElementAt = useCallback((clientX: number, clientY: number, expandResults = false, mode: 'add' | 'connect' = 'add', forceConnect = false) => {
|
|
294
285
|
const rect = containerRef.current?.getBoundingClientRect()
|
|
295
286
|
if (!rect) return
|
|
296
287
|
const flowPos = screenToFlowPositionRef.current({ x: clientX, y: clientY })
|
|
@@ -305,7 +296,7 @@ export function useCanvasInteractions({
|
|
|
305
296
|
? Math.max(100, Math.min(px, rect.width - 450))
|
|
306
297
|
: Math.max(120, Math.min(px, rect.width - 120))
|
|
307
298
|
const y = Math.max(40, Math.min(py, rect.height - 250))
|
|
308
|
-
setAddingElementAt({ x, y, flowX, flowY, expandResults, mode: resolvePickerMode(flowX, flowY, mode) })
|
|
299
|
+
setAddingElementAt({ x, y, flowX, flowY, expandResults, mode: resolvePickerMode(flowX, flowY, mode, forceConnect) })
|
|
309
300
|
}, [containerRef, snapToGrid, resolvePickerMode])
|
|
310
301
|
|
|
311
302
|
// ── Inline element adder handlers ───────────────────────────────────────────
|
|
@@ -633,7 +624,7 @@ export function useCanvasInteractions({
|
|
|
633
624
|
} else {
|
|
634
625
|
setPendingConnectionSource(sourceElementId)
|
|
635
626
|
suppressNextPaneClickRef.current = true
|
|
636
|
-
showAddingElementAt(clientX, clientY, true, 'connect')
|
|
627
|
+
showAddingElementAt(clientX, clientY, true, 'connect', 'shiftKey' in event && event.shiftKey)
|
|
637
628
|
}
|
|
638
629
|
}, [canEdit, setConnectors, showAddingElementAt, rfNodesRef, viewIdRef])
|
|
639
630
|
|
|
@@ -913,7 +904,7 @@ export function useCanvasInteractions({
|
|
|
913
904
|
} else {
|
|
914
905
|
setInteractionSourceId(null)
|
|
915
906
|
setPendingConnectionSource(sourceId)
|
|
916
|
-
showAddingElementAt(e.clientX, e.clientY, true, 'connect')
|
|
907
|
+
showAddingElementAt(e.clientX, e.clientY, true, 'connect', e.shiftKey)
|
|
917
908
|
}
|
|
918
909
|
return
|
|
919
910
|
}
|
|
@@ -112,7 +112,7 @@ export function useViewContextNeighbours({
|
|
|
112
112
|
const centerY = (minY + maxY) / 2
|
|
113
113
|
const boundaryW = maxX - minX
|
|
114
114
|
const boundaryH = maxY - minY
|
|
115
|
-
const totalInset =
|
|
115
|
+
const totalInset = 200
|
|
116
116
|
const padding = 180
|
|
117
117
|
const radiusX = boundaryW / 2 + padding
|
|
118
118
|
const radiusY = boundaryH / 2 + padding
|
|
@@ -13,7 +13,7 @@ import ReactFlow, {
|
|
|
13
13
|
useReactFlow,
|
|
14
14
|
applyNodeChanges,
|
|
15
15
|
} from 'reactflow'
|
|
16
|
-
import type { EdgeMarker as RFEdgeMarker, Node as RFNode, NodeChange } from 'reactflow'
|
|
16
|
+
import type { Edge as RFEdge, EdgeMarker as RFEdgeMarker, Node as RFNode, NodeChange } from 'reactflow'
|
|
17
17
|
import 'reactflow/dist/style.css'
|
|
18
18
|
import { toPng, toSvg } from 'html-to-image'
|
|
19
19
|
import {
|
|
@@ -708,64 +708,105 @@ function ViewEditorInner({
|
|
|
708
708
|
const contextNodeIdsRef = useRef<Set<string>>(new Set())
|
|
709
709
|
useEffect(() => {
|
|
710
710
|
contextNodeIdsRef.current = new Set(contextNodes.map((n) => n.id))
|
|
711
|
-
setLiveContextNodes((prev) =>
|
|
712
|
-
|
|
713
|
-
|
|
711
|
+
setLiveContextNodes((prev) => {
|
|
712
|
+
const prevById = new Map(prev.map((p) => [p.id, p]))
|
|
713
|
+
return contextNodes.map((n) => {
|
|
714
|
+
const existing = prevById.get(n.id)
|
|
714
715
|
if (existing?.width != null && existing?.height != null) {
|
|
715
716
|
return { ...n, width: existing.width, height: existing.height }
|
|
716
717
|
}
|
|
717
718
|
return n
|
|
718
719
|
})
|
|
719
|
-
)
|
|
720
|
+
})
|
|
720
721
|
}, [contextNodes])
|
|
721
722
|
|
|
723
|
+
const fadedNodeCacheRef = useRef<WeakMap<RFNode, RFNode>>(new WeakMap())
|
|
724
|
+
const fadedEdgeCacheRef = useRef<WeakMap<RFEdge, RFEdge>>(new WeakMap())
|
|
725
|
+
|
|
722
726
|
const flowNodes = useMemo(() => {
|
|
723
|
-
const allNodes =
|
|
724
|
-
|
|
727
|
+
const allNodes = liveContextNodes.length === 0
|
|
728
|
+
? rfNodes
|
|
729
|
+
: rfNodes.length === 0
|
|
730
|
+
? liveContextNodes
|
|
731
|
+
: [...liveContextNodes, ...rfNodes]
|
|
732
|
+
|
|
733
|
+
let hasNodeSel = false
|
|
734
|
+
const selectedNodeIds = new Set<string>()
|
|
735
|
+
for (const n of allNodes) {
|
|
736
|
+
if (n.selected) { selectedNodeIds.add(n.id); hasNodeSel = true }
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
const allEdges = contextConnectors.length === 0
|
|
740
|
+
? rfEdges
|
|
741
|
+
: rfEdges.length === 0
|
|
742
|
+
? contextConnectors
|
|
743
|
+
: [...contextConnectors, ...rfEdges]
|
|
725
744
|
|
|
726
|
-
const allEdges = [...contextConnectors, ...rfEdges]
|
|
727
745
|
const selectedEdgeEndPoints = new Set<string>()
|
|
728
|
-
|
|
746
|
+
let hasEdgeSel = false
|
|
747
|
+
for (const e of allEdges) {
|
|
729
748
|
if (e.selected) {
|
|
730
749
|
selectedEdgeEndPoints.add(e.source)
|
|
731
750
|
selectedEdgeEndPoints.add(e.target)
|
|
751
|
+
hasEdgeSel = true
|
|
732
752
|
}
|
|
733
|
-
}
|
|
753
|
+
}
|
|
734
754
|
|
|
735
755
|
const neighborNodeIds = new Set<string>()
|
|
736
|
-
if (
|
|
737
|
-
|
|
756
|
+
if (hasNodeSel) {
|
|
757
|
+
for (const e of allEdges) {
|
|
738
758
|
if (selectedNodeIds.has(e.source)) neighborNodeIds.add(e.target)
|
|
739
759
|
if (selectedNodeIds.has(e.target)) neighborNodeIds.add(e.source)
|
|
740
|
-
}
|
|
760
|
+
}
|
|
741
761
|
}
|
|
742
762
|
|
|
743
|
-
if (
|
|
763
|
+
if (!hasNodeSel && !hasEdgeSel) return allNodes
|
|
744
764
|
|
|
765
|
+
const cache = fadedNodeCacheRef.current
|
|
745
766
|
return allNodes.map((n) => {
|
|
746
767
|
const isHighlighted = selectedNodeIds.has(n.id) || selectedEdgeEndPoints.has(n.id) || neighborNodeIds.has(n.id)
|
|
747
768
|
if (isHighlighted) return n
|
|
748
|
-
|
|
769
|
+
const cached = cache.get(n)
|
|
770
|
+
if (cached) return cached
|
|
771
|
+
const faded: RFNode = {
|
|
749
772
|
...n,
|
|
750
773
|
style: { ...n.style, opacity: (Number(n.style?.opacity ?? 1)) * 0.2 },
|
|
751
774
|
}
|
|
775
|
+
cache.set(n, faded)
|
|
776
|
+
return faded
|
|
752
777
|
})
|
|
753
778
|
}, [liveContextNodes, rfNodes, contextConnectors, rfEdges])
|
|
754
779
|
|
|
755
780
|
const flowEdges = useMemo(() => {
|
|
756
|
-
const allEdges =
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
781
|
+
const allEdges = contextConnectors.length === 0
|
|
782
|
+
? rfEdges
|
|
783
|
+
: rfEdges.length === 0
|
|
784
|
+
? contextConnectors
|
|
785
|
+
: [...contextConnectors, ...rfEdges]
|
|
786
|
+
const allNodes = liveContextNodes.length === 0
|
|
787
|
+
? rfNodes
|
|
788
|
+
: rfNodes.length === 0
|
|
789
|
+
? liveContextNodes
|
|
790
|
+
: [...liveContextNodes, ...rfNodes]
|
|
791
|
+
|
|
792
|
+
const selectedNodeIds = new Set<string>()
|
|
793
|
+
let hasNodeSel = false
|
|
794
|
+
for (const n of allNodes) {
|
|
795
|
+
if (n.selected) { selectedNodeIds.add(n.id); hasNodeSel = true }
|
|
796
|
+
}
|
|
797
|
+
let hasEdgeSel = false
|
|
798
|
+
for (const e of allEdges) { if (e.selected) { hasEdgeSel = true; break } }
|
|
760
799
|
|
|
761
|
-
if (
|
|
800
|
+
if (!hasNodeSel && !hasEdgeSel) return allEdges
|
|
762
801
|
|
|
802
|
+
const cache = fadedEdgeCacheRef.current
|
|
763
803
|
return allEdges.map((e) => {
|
|
764
804
|
const isHighlighted = e.selected || selectedNodeIds.has(e.source) || selectedNodeIds.has(e.target)
|
|
765
805
|
if (isHighlighted) return e
|
|
766
|
-
|
|
806
|
+
const cached = cache.get(e)
|
|
807
|
+
if (cached) return cached
|
|
767
808
|
const multiplier = 0.2
|
|
768
|
-
|
|
809
|
+
const faded: RFEdge = {
|
|
769
810
|
...e,
|
|
770
811
|
style: { ...e.style, opacity: (Number(e.style?.opacity ?? 0.8)) * multiplier },
|
|
771
812
|
labelStyle: e.labelStyle ? { ...e.labelStyle, opacity: (Number(e.labelStyle.opacity ?? 1)) * multiplier } : undefined,
|
|
@@ -773,6 +814,8 @@ function ViewEditorInner({
|
|
|
773
814
|
markerEnd: fadeMarker(e.markerEnd, multiplier),
|
|
774
815
|
markerStart: fadeMarker(e.markerStart, multiplier),
|
|
775
816
|
}
|
|
817
|
+
cache.set(e, faded)
|
|
818
|
+
return faded
|
|
776
819
|
})
|
|
777
820
|
}, [contextConnectors, rfEdges, liveContextNodes, rfNodes])
|
|
778
821
|
|
|
@@ -870,13 +913,18 @@ function ViewEditorInner({
|
|
|
870
913
|
[-vw * VIEW_EDITOR_EMPTY_EXTENT_RATIO, -vh * VIEW_EDITOR_EMPTY_EXTENT_RATIO],
|
|
871
914
|
[vw * VIEW_EDITOR_EMPTY_EXTENT_RATIO, vh * VIEW_EDITOR_EMPTY_EXTENT_RATIO],
|
|
872
915
|
]
|
|
873
|
-
|
|
916
|
+
const boundsNodes = liveContextNodes.length === 0
|
|
917
|
+
? rfNodes
|
|
918
|
+
: rfNodes.length === 0
|
|
919
|
+
? liveContextNodes
|
|
920
|
+
: [...liveContextNodes, ...rfNodes]
|
|
921
|
+
if (boundsNodes.length === 0 && drawingPaths.length === 0) {
|
|
874
922
|
setComputedMinZoom((prev) => prev === VIEW_EDITOR_MIN_ZOOM_FLOOR ? prev : VIEW_EDITOR_MIN_ZOOM_FLOOR)
|
|
875
923
|
setComputedTranslateExtent((prev) => areTranslateExtentsEqual(prev, emptyExtent) ? prev : emptyExtent)
|
|
876
924
|
return
|
|
877
925
|
}
|
|
878
926
|
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity
|
|
879
|
-
for (const n of
|
|
927
|
+
for (const n of boundsNodes) {
|
|
880
928
|
minX = Math.min(minX, n.position.x); minY = Math.min(minY, n.position.y)
|
|
881
929
|
maxX = Math.max(maxX, n.position.x + (n.width ?? 180)); maxY = Math.max(maxY, n.position.y + (n.height ?? 80))
|
|
882
930
|
}
|
|
@@ -903,7 +951,7 @@ function ViewEditorInner({
|
|
|
903
951
|
const cx = (minX + maxX) / 2; const cy = (minY + maxY) / 2
|
|
904
952
|
const nextTranslateExtent: [[number, number], [number, number]] = [[cx - spanX / 2, cy - spanY / 2], [cx + spanX / 2, cy + spanY / 2]]
|
|
905
953
|
setComputedTranslateExtent((prev) => areTranslateExtentsEqual(prev, nextTranslateExtent) ? prev : nextTranslateExtent)
|
|
906
|
-
}, [
|
|
954
|
+
}, [rfNodes, liveContextNodes, drawingPaths])
|
|
907
955
|
|
|
908
956
|
// ── Keyboard shortcuts for drawing ────────────────────────────────────────
|
|
909
957
|
useEffect(() => {
|
|
@@ -951,7 +999,27 @@ function ViewEditorInner({
|
|
|
951
999
|
// ── Share ──────────────────────────────────────────────────────────────────
|
|
952
1000
|
const onShare = useCallback(() => {}, [])
|
|
953
1001
|
|
|
1002
|
+
const handleExplorerHoverZoom = useCallback((elementId: number | null, type: 'in' | 'out' | null) => {
|
|
1003
|
+
setHoveredZoom(type && elementId ? { elementId, type } : null)
|
|
1004
|
+
}, [])
|
|
1005
|
+
const handleToggleExplorer = useCallback(() => setIsExplorerOpen((v) => !v), [])
|
|
1006
|
+
const handleCloseLibrary = useCallback(() => setLibraryOpen(false), [])
|
|
1007
|
+
const handleCreateNewLibraryRef = useRef<() => void>(() => {})
|
|
1008
|
+
const handleCreateNewLibrary = useCallback(() => handleCreateNewLibraryRef.current(), [])
|
|
1009
|
+
const handleFocusModeChange = useCallback((v: boolean) => setCrossBranchEnabled(!v), [setCrossBranchEnabled])
|
|
1010
|
+
const handleOpenExport = useCallback(() => exportModal.onOpen(), [exportModal])
|
|
1011
|
+
const handleConnectorSave = useCallback((updated: Connector) => {
|
|
1012
|
+
upsertConnectorGraphSnapshot(updated)
|
|
1013
|
+
setConnectors((prev) => prev.map((c) => (c.id === updated.id ? updated : c)))
|
|
1014
|
+
}, [setConnectors])
|
|
1015
|
+
const handleConnectorDeleteInPanel = useCallback((edgeId: number) => {
|
|
1016
|
+
if (viewId != null) removeConnectorGraphSnapshot(viewId, edgeId)
|
|
1017
|
+
setConnectors((prev) => prev.filter((c) => c.id !== edgeId))
|
|
1018
|
+
}, [setConnectors, viewId])
|
|
1019
|
+
const handleViewSave = useCallback((updated: ViewTreeNode) => setView(updated), [setView])
|
|
1020
|
+
|
|
954
1021
|
// ── Library helpers ────────────────────────────────────────────────────────
|
|
1022
|
+
// Assigned below; referenced by memoized callbacks (e.g. ElementLibrary onCreateNew).
|
|
955
1023
|
const handleAddElementAtCenter = useCallback((forceCenter = false) => {
|
|
956
1024
|
if (!canEdit) return
|
|
957
1025
|
const rect = containerRef.current?.getBoundingClientRect()
|
|
@@ -965,6 +1033,7 @@ function ViewEditorInner({
|
|
|
965
1033
|
}
|
|
966
1034
|
showAddingElementAt(cx, cy, true)
|
|
967
1035
|
}, [canEdit, showAddingElementAt, lastMousePosRef])
|
|
1036
|
+
handleCreateNewLibraryRef.current = () => handleAddElementAtCenter(true)
|
|
968
1037
|
|
|
969
1038
|
const handleTapAdd = useCallback(async (obj: WorkspaceElement) => {
|
|
970
1039
|
if (!canEdit || !viewId || existingElementIds.has(obj.id)) return
|
|
@@ -1177,8 +1246,8 @@ function ViewEditorInner({
|
|
|
1177
1246
|
treeNodes={treeData}
|
|
1178
1247
|
linksMap={linksMap} viewElements={viewElements}
|
|
1179
1248
|
onNavigate={canvas.stableOnNavigateToView}
|
|
1180
|
-
onHoverZoom={
|
|
1181
|
-
isOpen={isExplorerOpen} onToggle={
|
|
1249
|
+
onHoverZoom={handleExplorerHoverZoom}
|
|
1250
|
+
isOpen={isExplorerOpen} onToggle={handleToggleExplorer}
|
|
1182
1251
|
isMobile={isMobileLayout}
|
|
1183
1252
|
activeTags={activeTags}
|
|
1184
1253
|
setActiveTags={handleSetActiveTags}
|
|
@@ -1288,9 +1357,9 @@ function ViewEditorInner({
|
|
|
1288
1357
|
hasDrawingPaths={drawingPaths.length > 0} drawingVisible={drawingVisible} setDrawingVisible={setDrawingVisible}
|
|
1289
1358
|
extrasOpen={extrasOpen} setExtrasOpen={setExtrasOpen}
|
|
1290
1359
|
focusMode={!crossBranchSettings.enabled}
|
|
1291
|
-
onFocusModeChange={
|
|
1360
|
+
onFocusModeChange={handleFocusModeChange}
|
|
1292
1361
|
disableImportExport={disableImportExport}
|
|
1293
|
-
onImport={importModal.onOpen} onExport={
|
|
1362
|
+
onImport={importModal.onOpen} onExport={handleOpenExport} onShare={onShare}
|
|
1294
1363
|
allTags={availableTags}
|
|
1295
1364
|
layers={layers}
|
|
1296
1365
|
tagColors={tagColors}
|
|
@@ -1310,8 +1379,8 @@ function ViewEditorInner({
|
|
|
1310
1379
|
<ElementLibrary
|
|
1311
1380
|
existingElementIds={existingElementIds}
|
|
1312
1381
|
existingElements={existingElements}
|
|
1313
|
-
onCreateNew={
|
|
1314
|
-
isOpen={libraryOpen} onClose={
|
|
1382
|
+
onCreateNew={handleCreateNewLibrary} refresh={libraryRefresh}
|
|
1383
|
+
isOpen={libraryOpen} onClose={handleCloseLibrary}
|
|
1315
1384
|
onTapAdd={canEdit ? handleTapAdd : undefined}
|
|
1316
1385
|
onFindElement={handleFindElement}
|
|
1317
1386
|
onTouchDrop={canEdit ? handleTouchDrop : undefined}
|
|
@@ -1334,14 +1403,8 @@ function ViewEditorInner({
|
|
|
1334
1403
|
<ConnectorPanel
|
|
1335
1404
|
isOpen={connectorPanel.isOpen} onClose={connectorPanel.onClose} connector={selectedEdge}
|
|
1336
1405
|
orgId={''}
|
|
1337
|
-
onSave={
|
|
1338
|
-
|
|
1339
|
-
setConnectors((prev) => prev.map((connector) => (connector.id === updated.id ? updated : connector)))
|
|
1340
|
-
}} autoSave
|
|
1341
|
-
onDelete={(edgeId: number) => {
|
|
1342
|
-
if (viewId != null) removeConnectorGraphSnapshot(viewId, edgeId)
|
|
1343
|
-
setConnectors((prev) => prev.filter((connector) => connector.id !== edgeId))
|
|
1344
|
-
}}
|
|
1406
|
+
onSave={handleConnectorSave} autoSave
|
|
1407
|
+
onDelete={handleConnectorDeleteInPanel}
|
|
1345
1408
|
hasBackdrop={isMobileLayout}
|
|
1346
1409
|
connectorPanelAfterContentSlot={connectorPanelAfterContentSlot}
|
|
1347
1410
|
/>
|
|
@@ -1355,7 +1418,7 @@ function ViewEditorInner({
|
|
|
1355
1418
|
<ViewPanel
|
|
1356
1419
|
isOpen={viewDetails.isOpen} onClose={viewDetails.onClose}
|
|
1357
1420
|
view={view as ViewTreeNode}
|
|
1358
|
-
onSave={
|
|
1421
|
+
onSave={handleViewSave} hasBackdrop={isMobileLayout}
|
|
1359
1422
|
/>
|
|
1360
1423
|
|
|
1361
1424
|
<ExportModal
|
package/dist/demo/DemoPage.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Demo entry point.
|
|
3
|
-
* Overrides the real `api` singleton with localStorage-backed implementations
|
|
4
|
-
* and patches window.history to redirect /views/:id → /demo/:id, all for the
|
|
5
|
-
* lifetime of this component. Restores everything on unmount.
|
|
6
|
-
* No auth required; served at /demo and /demo/:id routes.
|
|
7
|
-
*/
|
|
8
|
-
export declare function DemoNavigator(): null;
|
|
9
|
-
export default function DemoPage(): import("react/jsx-runtime").JSX.Element;
|
package/dist/demo/seed.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import type { LibraryElement, PlacedElement, Connector, ViewTreeNode, ViewLayer } from '../types';
|
|
2
|
-
export declare const DEMO_ELEMENTS: LibraryElement[];
|
|
3
|
-
export declare const DEMO_VIEWS: ViewTreeNode[];
|
|
4
|
-
type ViewPlacements = Record<number, PlacedElement[]>;
|
|
5
|
-
type ViewConnectors = Record<number, Connector[]>;
|
|
6
|
-
export declare const DEMO_PLACEMENTS: ViewPlacements;
|
|
7
|
-
export declare const DEMO_CONNECTORS: ViewConnectors;
|
|
8
|
-
export declare const DEMO_LAYERS: Record<number, ViewLayer[]>;
|
|
9
|
-
export {};
|
package/dist/demo/store.d.ts
DELETED
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* localStorage-backed store for the demo mode.
|
|
3
|
-
* Implements the subset of the `api` interface used by ViewEditor and its hooks.
|
|
4
|
-
* Data is scoped under the `diag:demo:*` key namespace to avoid colliding
|
|
5
|
-
* with a real logged-in session.
|
|
6
|
-
*/
|
|
7
|
-
import type { LibraryElement, PlacedElement, Connector, ViewTreeNode, ViewLayer, Tag, ElementPlacement, ExploreData } from '../types';
|
|
8
|
-
export declare function initDemoStore(): void;
|
|
9
|
-
export declare function resetDemoStore(): void;
|
|
10
|
-
export declare const demoApi: {
|
|
11
|
-
explore: {
|
|
12
|
-
load: () => Promise<ExploreData>;
|
|
13
|
-
};
|
|
14
|
-
elements: {
|
|
15
|
-
list: (_params?: unknown) => Promise<LibraryElement[]>;
|
|
16
|
-
get: (id: number) => Promise<LibraryElement>;
|
|
17
|
-
create: (data: Partial<LibraryElement>) => Promise<LibraryElement>;
|
|
18
|
-
update: (id: number, data: Partial<LibraryElement>) => Promise<LibraryElement>;
|
|
19
|
-
delete: (_orgId: string, id: number) => Promise<void>;
|
|
20
|
-
placements: (id: number) => Promise<ElementPlacement[]>;
|
|
21
|
-
};
|
|
22
|
-
workspace: {
|
|
23
|
-
orgs: {
|
|
24
|
-
tagColors: {
|
|
25
|
-
list: () => Promise<Record<string, Tag>>;
|
|
26
|
-
set: (tag: string, color: string, description?: string) => Promise<void>;
|
|
27
|
-
};
|
|
28
|
-
};
|
|
29
|
-
views: {
|
|
30
|
-
list: () => Promise<{
|
|
31
|
-
id: number;
|
|
32
|
-
owner_element_id: number | null;
|
|
33
|
-
name: string;
|
|
34
|
-
label: string | null;
|
|
35
|
-
is_root: boolean;
|
|
36
|
-
created_at: string;
|
|
37
|
-
updated_at: string;
|
|
38
|
-
}[]>;
|
|
39
|
-
get: (id: number) => Promise<ViewTreeNode>;
|
|
40
|
-
content: (id: number) => Promise<{
|
|
41
|
-
placements: PlacedElement[];
|
|
42
|
-
connectors: Connector[];
|
|
43
|
-
}>;
|
|
44
|
-
tree: () => Promise<ViewTreeNode[]>;
|
|
45
|
-
create: (data: {
|
|
46
|
-
name: string;
|
|
47
|
-
label?: string;
|
|
48
|
-
parent_view_id?: number | null;
|
|
49
|
-
}) => Promise<{
|
|
50
|
-
id: number;
|
|
51
|
-
owner_element_id: number | null;
|
|
52
|
-
name: string;
|
|
53
|
-
label: string | null;
|
|
54
|
-
is_root: boolean;
|
|
55
|
-
created_at: string;
|
|
56
|
-
updated_at: string;
|
|
57
|
-
}>;
|
|
58
|
-
update: (id: number, data: {
|
|
59
|
-
name: string;
|
|
60
|
-
label?: string;
|
|
61
|
-
}) => Promise<{
|
|
62
|
-
id: number;
|
|
63
|
-
owner_element_id: number | null;
|
|
64
|
-
name: string;
|
|
65
|
-
label: string | null;
|
|
66
|
-
is_root: boolean;
|
|
67
|
-
created_at: string;
|
|
68
|
-
updated_at: string;
|
|
69
|
-
}>;
|
|
70
|
-
delete: (_orgId: string, id: number) => Promise<void>;
|
|
71
|
-
placements: {
|
|
72
|
-
list: (viewId: number) => Promise<ElementPlacement[]>;
|
|
73
|
-
add: (viewId: number, elementId: number, x?: number, y?: number) => Promise<ElementPlacement>;
|
|
74
|
-
updatePosition: (viewId: number, elementId: number, x: number, y: number) => Promise<void>;
|
|
75
|
-
remove: (viewId: number, elementId: number) => Promise<void>;
|
|
76
|
-
};
|
|
77
|
-
layers: {
|
|
78
|
-
list: (viewId: number) => Promise<ViewLayer[]>;
|
|
79
|
-
create: (viewId: number, data: {
|
|
80
|
-
name: string;
|
|
81
|
-
tags: string[];
|
|
82
|
-
color?: string;
|
|
83
|
-
}) => Promise<ViewLayer>;
|
|
84
|
-
update: (viewId: number, layerId: number, data: Partial<ViewLayer>) => Promise<ViewLayer>;
|
|
85
|
-
delete: (viewId: number, layerId: number) => Promise<void>;
|
|
86
|
-
};
|
|
87
|
-
reactions: {
|
|
88
|
-
list: (_viewId: number) => Promise<never[]>;
|
|
89
|
-
};
|
|
90
|
-
threads: {
|
|
91
|
-
listForElement: () => Promise<never[]>;
|
|
92
|
-
listForConnector: () => Promise<never[]>;
|
|
93
|
-
createForElement: () => Promise<never>;
|
|
94
|
-
createForConnector: () => Promise<never>;
|
|
95
|
-
addComment: () => Promise<never>;
|
|
96
|
-
resolve: () => Promise<void>;
|
|
97
|
-
};
|
|
98
|
-
thumbnail: (_id: number) => Promise<null>;
|
|
99
|
-
rename: (id: number, name: string) => Promise<{
|
|
100
|
-
id: number;
|
|
101
|
-
owner_element_id: number | null;
|
|
102
|
-
name: string;
|
|
103
|
-
label: string | null;
|
|
104
|
-
is_root: boolean;
|
|
105
|
-
created_at: string;
|
|
106
|
-
updated_at: string;
|
|
107
|
-
}>;
|
|
108
|
-
setLevel: () => Promise<void>;
|
|
109
|
-
reparent: () => Promise<never>;
|
|
110
|
-
};
|
|
111
|
-
connectors: {
|
|
112
|
-
list: (viewId: number) => Promise<Connector[]>;
|
|
113
|
-
create: (viewId: number, data: {
|
|
114
|
-
source_element_id: number;
|
|
115
|
-
target_element_id: number;
|
|
116
|
-
label?: string;
|
|
117
|
-
description?: string;
|
|
118
|
-
relationship?: string;
|
|
119
|
-
direction?: string;
|
|
120
|
-
style?: string;
|
|
121
|
-
url?: string;
|
|
122
|
-
source_handle?: string | null;
|
|
123
|
-
target_handle?: string | null;
|
|
124
|
-
}) => Promise<Connector>;
|
|
125
|
-
update: (viewId: number, connectorId: number, data: Partial<Connector>) => Promise<Connector>;
|
|
126
|
-
delete: (_orgId: string, connectorId: number) => Promise<void>;
|
|
127
|
-
};
|
|
128
|
-
elements: {
|
|
129
|
-
list: (params?: unknown) => Promise<LibraryElement[]>;
|
|
130
|
-
get: (id: number) => Promise<LibraryElement>;
|
|
131
|
-
create: (data: Partial<LibraryElement>) => Promise<LibraryElement>;
|
|
132
|
-
update: (id: number, data: Partial<LibraryElement>) => Promise<LibraryElement>;
|
|
133
|
-
delete: (orgId: string, id: number) => Promise<void>;
|
|
134
|
-
placements: (id: number) => Promise<ElementPlacement[]>;
|
|
135
|
-
};
|
|
136
|
-
};
|
|
137
|
-
};
|