@tldiagram/core-ui 1.92.0 → 1.94.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/api/client.d.ts +13 -1
- package/dist/components/ElementNode.d.ts +14 -1
- package/dist/components/ZUI/ZUICanvas.d.ts +1 -0
- package/dist/config/runtime-vscode.d.ts +1 -0
- package/dist/config/runtime.d.ts +1 -0
- package/dist/index.js +10875 -9550
- package/dist/pages/InfiniteZoom.d.ts +5 -2
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.d.ts +10 -3
- package/dist/pages/ViewEditor/hooks/useCanvasInteractions.test.d.ts +1 -0
- package/dist/pages/ViewEditor/hooks/useViewData.d.ts +27 -24
- package/dist/pages/ViewsGrid.d.ts +9 -1
- package/dist/shims/empty-node-module.d.ts +2 -0
- package/dist/store/useStore.d.ts +80 -0
- package/dist/store/useStore.test.d.ts +1 -0
- package/package.json +10 -7
- package/src/api/client.ts +39 -1
- package/src/components/ElementNode.tsx +21 -59
- package/src/components/ElementPanel.tsx +2 -3
- package/src/components/LayoutSection.tsx +95 -104
- package/src/components/ViewGridNode.tsx +1 -4
- package/src/components/ZUI/ZUICanvas.tsx +138 -1
- package/src/components/ZUI/renderer.ts +166 -66
- package/src/components/ZUI/useZUIInteraction.ts +235 -81
- package/src/config/runtime-vscode.ts +6 -0
- package/src/config/runtime.ts +4 -0
- package/src/main.tsx +26 -14
- package/src/pages/InfiniteZoom.tsx +14 -5
- package/src/pages/ViewEditor/context.tsx +14 -3
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.test.ts +30 -0
- package/src/pages/ViewEditor/hooks/useCanvasInteractions.ts +294 -146
- package/src/pages/ViewEditor/hooks/useViewData.ts +459 -256
- package/src/pages/ViewEditor/index.tsx +67 -70
- package/src/pages/Views.tsx +552 -83
- package/src/pages/ViewsGrid.tsx +26 -337
- package/src/shims/empty-node-module.ts +1 -0
- package/src/store/useStore.test.ts +285 -0
- package/src/store/useStore.ts +327 -0
|
@@ -2,6 +2,9 @@ interface Props {
|
|
|
2
2
|
sharedToken?: string;
|
|
3
3
|
shareSlot?: React.ReactNode;
|
|
4
4
|
}
|
|
5
|
-
export
|
|
5
|
+
export interface InfiniteZoomHandle {
|
|
6
|
+
focusDiagram(viewId: number): boolean;
|
|
7
|
+
}
|
|
8
|
+
declare const InfiniteZoom: import("react").ForwardRefExoticComponent<Props & import("react").RefAttributes<InfiniteZoomHandle>>;
|
|
9
|
+
export default InfiniteZoom;
|
|
6
10
|
export declare function SharedInfiniteZoom(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
export {};
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import type { DrawingCanvasHandle } from '../../../components/DrawingCanvas';
|
|
2
2
|
import { type Connection, type Edge as RFEdge, type EdgeChange, type Node as RFNode, type NodeChange, type NodeDragHandler, type OnConnect, type OnConnectStartParams } from 'reactflow';
|
|
3
3
|
import type { Connector, PlacedElement, ViewTreeNode, LibraryElement, ViewLayer, ViewConnector, IncomingViewConnector } from '../../../types';
|
|
4
|
+
export declare function applyNodeChangesWithStructuralSharing(changes: NodeChange[], nodes: RFNode[]): RFNode[];
|
|
5
|
+
export declare function getConnectorDeletionTarget(selectedConnector: Connector | null): number | null;
|
|
4
6
|
interface CanvasInteractionOptions {
|
|
5
7
|
viewId: number | null;
|
|
6
8
|
canEdit: boolean;
|
|
@@ -41,12 +43,11 @@ interface CanvasInteractionOptions {
|
|
|
41
43
|
openConnectorPanel: () => void;
|
|
42
44
|
closeConnectorPanel: () => void;
|
|
43
45
|
selectedElement: LibraryElement | null;
|
|
44
|
-
|
|
46
|
+
selectedConnector: Connector | null;
|
|
45
47
|
connectors: Connector[];
|
|
46
48
|
layers: ViewLayer[];
|
|
47
49
|
setSelectedElement: React.Dispatch<React.SetStateAction<LibraryElement | null>>;
|
|
48
50
|
setSelectedEdge: (e: Connector | null) => void;
|
|
49
|
-
setSelectedEdgeId: (id: number | null) => void;
|
|
50
51
|
setSelectedProxyConnectorDetails: React.Dispatch<React.SetStateAction<import('../../../crossBranch/types').ProxyConnectorDetails | null>>;
|
|
51
52
|
openProxyConnectorPanel: () => void;
|
|
52
53
|
closeProxyConnectorPanel: () => void;
|
|
@@ -79,7 +80,12 @@ type HandleReconnectDragState = {
|
|
|
79
80
|
hoveredNodeId?: string;
|
|
80
81
|
hoveredHandleId?: string;
|
|
81
82
|
};
|
|
82
|
-
|
|
83
|
+
type InteractionStartOptions = {
|
|
84
|
+
sourceHandle?: string;
|
|
85
|
+
clientX?: number;
|
|
86
|
+
clientY?: number;
|
|
87
|
+
};
|
|
88
|
+
export declare function useCanvasInteractions({ viewId, canEdit, drawingMode: _drawingMode, isMobileLayout: _isMobileLayout, rfNodesRef, rfEdgesRef: _rfEdgesRef, viewElementsRef, viewIdRef, incomingLinksRef, treeDataRef, navigateRef, containerRef, interactionSourceIdRef, hoveredZoomRef, hoverPanLockedUntilRef, setViewElements: _setViewElements, setConnectors: _setConnectors, setRfNodes, setRfEdges, setLinksMap, setParentLinksMap: _setParentLinksMap, setHoveredZoom, refreshGrid, refreshElements, stableOnConnectTo, existingElementIds, linksMapRef, parentLinksMapRef, openElementPanel: _openElementPanel, closeElementPanel: closeElementPanel, openConnectorPanel: openConnectorPanel, closeConnectorPanel: closeConnectorPanel, selectedElement, selectedConnector, connectors, layers, setSelectedElement, setSelectedEdge, setSelectedProxyConnectorDetails, openProxyConnectorPanel, closeProxyConnectorPanel, handleElementDeleted, handleElementPermanentlyDeleted, handleConnectorDeleted, handleUpdateTags, drawingCanvasRef, snapToGrid, onMoveStateChange, }: CanvasInteractionOptions): {
|
|
83
89
|
canvasMenu: {
|
|
84
90
|
x: number;
|
|
85
91
|
y: number;
|
|
@@ -154,6 +160,7 @@ export declare function useCanvasInteractions({ viewId, canEdit, drawingMode: _d
|
|
|
154
160
|
stableOnHoverZoom: (elementId: number, type: "in" | "out" | null) => void;
|
|
155
161
|
stableOnRemoveElement: (elementId: number) => Promise<void>;
|
|
156
162
|
stableOnConnectTo: (targetElementId: number) => Promise<void>;
|
|
163
|
+
stableOnInteractionStart: (elementId: number, options?: InteractionStartOptions) => void;
|
|
157
164
|
stableOnStartHandleReconnect: (args: {
|
|
158
165
|
edgeId: string;
|
|
159
166
|
endpoint: "source" | "target";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
import type { ViewTreeNode, PlacedElement, LibraryElement, Connector, IncomingViewConnector, ViewConnector, Tag } from '../../../types';
|
|
1
|
+
import type { ViewTreeNode, PlacedElement, LibraryElement, Connector, Tag } from '../../../types';
|
|
3
2
|
interface ViewDataOptions {
|
|
4
3
|
viewId: number | null;
|
|
5
4
|
interactionSourceId: number | null;
|
|
@@ -8,7 +7,7 @@ interface ViewDataOptions {
|
|
|
8
7
|
sourceHandle: string;
|
|
9
8
|
targetHandle?: string;
|
|
10
9
|
} | null;
|
|
11
|
-
|
|
10
|
+
selectedConnector: Connector | null;
|
|
12
11
|
activeTags: string[];
|
|
13
12
|
hiddenLayerTags: string[];
|
|
14
13
|
hoveredLayerTags: string[] | null;
|
|
@@ -19,7 +18,11 @@ interface ViewDataOptions {
|
|
|
19
18
|
stableOnNavigateToView: (id: number) => void;
|
|
20
19
|
stableOnSelect: (obj: PlacedElement) => void;
|
|
21
20
|
stableOnOpenCodePreview: (elementId: number) => void;
|
|
22
|
-
stableOnInteractionStart: (elementId: number
|
|
21
|
+
stableOnInteractionStart: (elementId: number, options?: {
|
|
22
|
+
sourceHandle?: string;
|
|
23
|
+
clientX?: number;
|
|
24
|
+
clientY?: number;
|
|
25
|
+
}) => void;
|
|
23
26
|
stableOnConnectTo: (targetElementId: number) => Promise<void>;
|
|
24
27
|
stableOnStartHandleReconnect: (args: {
|
|
25
28
|
edgeId: string;
|
|
@@ -35,40 +38,40 @@ interface ViewDataOptions {
|
|
|
35
38
|
type: 'in' | 'out' | null;
|
|
36
39
|
} | null>;
|
|
37
40
|
}
|
|
38
|
-
export declare function useViewData({ viewId, interactionSourceId, clickConnectMode,
|
|
41
|
+
export declare function useViewData({ viewId, interactionSourceId, clickConnectMode, selectedConnector, activeTags, hiddenLayerTags, hoveredLayerTags, hoveredLayerColor, tagColors, stableOnZoomIn, stableOnZoomOut, stableOnNavigateToView, stableOnSelect, stableOnOpenCodePreview, stableOnInteractionStart, stableOnConnectTo, stableOnStartHandleReconnect, stableOnRemoveElement, stableOnHoverZoom, hoveredZoomRef, }: ViewDataOptions): {
|
|
39
42
|
view: ViewTreeNode | null | undefined;
|
|
40
|
-
setView:
|
|
43
|
+
setView: (view: ViewTreeNode | null | undefined) => void;
|
|
41
44
|
viewElements: PlacedElement[];
|
|
42
|
-
setViewElements:
|
|
45
|
+
setViewElements: (next: import("../../../store/useStore").StoreSetter<PlacedElement[]>) => void;
|
|
43
46
|
connectors: Connector[];
|
|
44
|
-
setConnectors:
|
|
45
|
-
rfNodes:
|
|
46
|
-
setRfNodes: import("
|
|
47
|
-
rfEdges:
|
|
48
|
-
setRfEdges: import("
|
|
49
|
-
linksMap: Record<number, ViewConnector[]>;
|
|
50
|
-
setLinksMap: import("
|
|
51
|
-
parentLinksMap: Record<number, ViewConnector[]>;
|
|
52
|
-
setParentLinksMap: import("
|
|
53
|
-
incomingLinks: IncomingViewConnector[];
|
|
47
|
+
setConnectors: (next: import("../../../store/useStore").StoreSetter<Connector[]>) => void;
|
|
48
|
+
rfNodes: import("reactflow").Node[];
|
|
49
|
+
setRfNodes: (next: import("../../../store/useStore").StoreSetter<import("reactflow").Node[]>) => void;
|
|
50
|
+
rfEdges: import("reactflow").Edge[];
|
|
51
|
+
setRfEdges: (next: import("../../../store/useStore").StoreSetter<import("reactflow").Edge[]>) => void;
|
|
52
|
+
linksMap: Record<number, import("../../..").ViewConnector[]>;
|
|
53
|
+
setLinksMap: (next: import("../../../store/useStore").StoreSetter<Record<number, import("../../..").ViewConnector[]>>) => void;
|
|
54
|
+
parentLinksMap: Record<number, import("../../..").ViewConnector[]>;
|
|
55
|
+
setParentLinksMap: (next: import("../../../store/useStore").StoreSetter<Record<number, import("../../..").ViewConnector[]>>) => void;
|
|
56
|
+
incomingLinks: import("../../..").IncomingViewConnector[];
|
|
54
57
|
treeData: ViewTreeNode[];
|
|
55
58
|
allElements: LibraryElement[];
|
|
56
59
|
libraryRefresh: number;
|
|
57
|
-
setLibraryRefresh:
|
|
60
|
+
setLibraryRefresh: (next: import("../../../store/useStore").StoreSetter<number>) => void;
|
|
58
61
|
existingElementIds: Set<number>;
|
|
59
62
|
viewElementsRef: import("react").MutableRefObject<PlacedElement[]>;
|
|
60
|
-
linksMapRef: import("react").MutableRefObject<Record<number, ViewConnector[]>>;
|
|
61
|
-
parentLinksMapRef: import("react").MutableRefObject<Record<number, ViewConnector[]>>;
|
|
62
|
-
incomingLinksRef: import("react").MutableRefObject<IncomingViewConnector[]>;
|
|
63
|
+
linksMapRef: import("react").MutableRefObject<Record<number, import("../../..").ViewConnector[]>>;
|
|
64
|
+
parentLinksMapRef: import("react").MutableRefObject<Record<number, import("../../..").ViewConnector[]>>;
|
|
65
|
+
incomingLinksRef: import("react").MutableRefObject<import("../../..").IncomingViewConnector[]>;
|
|
63
66
|
treeDataRef: import("react").MutableRefObject<ViewTreeNode[]>;
|
|
64
|
-
rfNodesRef: import("react").MutableRefObject<
|
|
65
|
-
rfEdgesRef: import("react").MutableRefObject<
|
|
67
|
+
rfNodesRef: import("react").MutableRefObject<import("reactflow").Node[]>;
|
|
68
|
+
rfEdgesRef: import("react").MutableRefObject<import("reactflow").Edge[]>;
|
|
66
69
|
viewIdRef: import("react").MutableRefObject<number | null>;
|
|
67
70
|
refreshGrid: () => Promise<void>;
|
|
68
71
|
refreshElements: () => Promise<void>;
|
|
69
72
|
handleElementDeleted: (deletedId: number) => void;
|
|
70
73
|
handleElementPermanentlyDeleted: (deletedId: number) => void;
|
|
71
74
|
handleElementSaved: (saved: LibraryElement) => void;
|
|
72
|
-
setAllElements:
|
|
75
|
+
setAllElements: (next: import("../../../store/useStore").StoreSetter<LibraryElement[]>) => void;
|
|
73
76
|
};
|
|
74
77
|
export {};
|
|
@@ -1,6 +1,14 @@
|
|
|
1
|
+
import { type Dispatch, type SetStateAction } from 'react';
|
|
1
2
|
import 'reactflow/dist/style.css';
|
|
3
|
+
import type { ViewTreeNode } from '../types';
|
|
2
4
|
interface Props {
|
|
3
5
|
onShare?: (viewId: number) => void;
|
|
6
|
+
treeData: ViewTreeNode[];
|
|
7
|
+
loading: boolean;
|
|
8
|
+
focusedId: number | null;
|
|
9
|
+
onFocusChange: (viewId: number | null) => void;
|
|
10
|
+
setTreeData: Dispatch<SetStateAction<ViewTreeNode[]>>;
|
|
11
|
+
refreshTree: () => Promise<void>;
|
|
4
12
|
}
|
|
5
|
-
export default function ViewsGrid(
|
|
13
|
+
export default function ViewsGrid(props: Props): import("react/jsx-runtime").JSX.Element;
|
|
6
14
|
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import type { Edge as RFEdge, Node as RFNode } from 'reactflow';
|
|
2
|
+
import type { Connector, IncomingViewConnector, LibraryElement, PlacedElement, ViewConnector, ViewTreeNode } from '../types';
|
|
3
|
+
export type StoreSetter<T> = T | ((previous: T) => T);
|
|
4
|
+
export type ViewEditorUiState = {
|
|
5
|
+
viewId: number | null;
|
|
6
|
+
canEdit: boolean;
|
|
7
|
+
isOwner: boolean;
|
|
8
|
+
isFreePlan: boolean;
|
|
9
|
+
snapToGrid: boolean;
|
|
10
|
+
selectedElement: LibraryElement | null;
|
|
11
|
+
selectedConnector: Connector | null;
|
|
12
|
+
};
|
|
13
|
+
export type ViewContentLinks = {
|
|
14
|
+
linksMap: Record<number, ViewConnector[]>;
|
|
15
|
+
parentLinksMap: Record<number, ViewConnector[]>;
|
|
16
|
+
incomingLinks: IncomingViewConnector[];
|
|
17
|
+
};
|
|
18
|
+
export type ViewContentPayload = ViewContentLinks & {
|
|
19
|
+
view: ViewTreeNode | null;
|
|
20
|
+
viewElements: PlacedElement[];
|
|
21
|
+
connectors: Connector[];
|
|
22
|
+
treeData: ViewTreeNode[];
|
|
23
|
+
};
|
|
24
|
+
export type CanvasStoreState = ViewEditorUiState & {
|
|
25
|
+
view: ViewTreeNode | null | undefined;
|
|
26
|
+
viewElements: PlacedElement[];
|
|
27
|
+
connectors: Connector[];
|
|
28
|
+
nodes: RFNode[];
|
|
29
|
+
edges: RFEdge[];
|
|
30
|
+
linksMap: Record<number, ViewConnector[]>;
|
|
31
|
+
parentLinksMap: Record<number, ViewConnector[]>;
|
|
32
|
+
incomingLinks: IncomingViewConnector[];
|
|
33
|
+
treeData: ViewTreeNode[];
|
|
34
|
+
allElements: LibraryElement[];
|
|
35
|
+
libraryRefresh: number;
|
|
36
|
+
setViewEditorUi: (patch: Partial<ViewEditorUiState>) => void;
|
|
37
|
+
setSnapToGrid: (snapToGrid: boolean) => void;
|
|
38
|
+
setSelectedElement: (selectedElement: LibraryElement | null) => void;
|
|
39
|
+
setSelectedConnector: (selectedConnector: Connector | null) => void;
|
|
40
|
+
setView: (view: ViewTreeNode | null | undefined) => void;
|
|
41
|
+
setViewElements: (next: StoreSetter<PlacedElement[]>) => void;
|
|
42
|
+
setConnectors: (next: StoreSetter<Connector[]>) => void;
|
|
43
|
+
setNodes: (next: StoreSetter<RFNode[]>) => void;
|
|
44
|
+
setEdges: (next: StoreSetter<RFEdge[]>) => void;
|
|
45
|
+
setLinksMap: (next: StoreSetter<Record<number, ViewConnector[]>>) => void;
|
|
46
|
+
setParentLinksMap: (next: StoreSetter<Record<number, ViewConnector[]>>) => void;
|
|
47
|
+
setIncomingLinks: (next: StoreSetter<IncomingViewConnector[]>) => void;
|
|
48
|
+
setTreeData: (next: StoreSetter<ViewTreeNode[]>) => void;
|
|
49
|
+
setAllElements: (next: StoreSetter<LibraryElement[]>) => void;
|
|
50
|
+
setLibraryRefresh: (next: StoreSetter<number>) => void;
|
|
51
|
+
resetCanvas: () => void;
|
|
52
|
+
hydrateViewContent: (payload: ViewContentPayload) => void;
|
|
53
|
+
updateElementPosition: (elementId: number, x: number, y: number) => void;
|
|
54
|
+
removeElementPlacement: (elementId: number) => void;
|
|
55
|
+
removeElementEverywhere: (elementId: number) => void;
|
|
56
|
+
mergeSavedElement: (saved: LibraryElement) => void;
|
|
57
|
+
upsertConnector: (connector: Connector) => void;
|
|
58
|
+
replaceConnector: (connector: Connector) => void;
|
|
59
|
+
removeConnector: (connectorId: number) => void;
|
|
60
|
+
};
|
|
61
|
+
export declare const emptyViewEditorUiState: ViewEditorUiState;
|
|
62
|
+
export declare function findViewByOwner(nodes: ViewTreeNode[], elementId: number): ViewTreeNode | null;
|
|
63
|
+
export declare function findViewPath(nodes: ViewTreeNode[], targetId: number, path?: ViewTreeNode[]): ViewTreeNode[] | null;
|
|
64
|
+
export declare function buildViewContentLinks(tree: ViewTreeNode[], viewId: number, viewElements: PlacedElement[]): ViewContentLinks;
|
|
65
|
+
export declare function selectExistingElementIds(state: Pick<CanvasStoreState, 'viewElements'>): Set<number>;
|
|
66
|
+
export declare function selectElementById(state: Pick<CanvasStoreState, 'viewElements'>, elementId: number): PlacedElement | undefined;
|
|
67
|
+
export declare function selectConnectorById(state: Pick<CanvasStoreState, 'connectors'>, connectorId: number): Connector | undefined;
|
|
68
|
+
export declare function updatePlacedElementPosition(elements: PlacedElement[], elementId: number, x: number, y: number): PlacedElement[];
|
|
69
|
+
export declare function removePlacedElement(elements: PlacedElement[], elementId: number): PlacedElement[];
|
|
70
|
+
export declare function placedElementToLibraryElement(element: PlacedElement): LibraryElement;
|
|
71
|
+
export declare function buildElementLibraryItems(allElements: LibraryElement[], viewElements: PlacedElement[]): LibraryElement[];
|
|
72
|
+
export declare function mergeSavedElementIntoPlacements(elements: PlacedElement[], saved: LibraryElement): PlacedElement[];
|
|
73
|
+
export declare function upsertConnectorInList(connectors: Connector[], connector: Connector): Connector[];
|
|
74
|
+
export declare function removeConnectorFromList(connectors: Connector[], connectorId: number): Connector[];
|
|
75
|
+
export declare const useStore: import("zustand").UseBoundStore<import("zustand").StoreApi<CanvasStoreState>>;
|
|
76
|
+
export declare const canvasSelectors: {
|
|
77
|
+
existingElementIds: typeof selectExistingElementIds;
|
|
78
|
+
elementById: typeof selectElementById;
|
|
79
|
+
connectorById: typeof selectConnectorById;
|
|
80
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tldiagram/core-ui",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.94.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -38,7 +38,9 @@
|
|
|
38
38
|
"dependencies": {
|
|
39
39
|
"@buf/tldiagramcom_diagram.bufbuild_es": "^2.11.0-20260419172603-8192c519478e.1",
|
|
40
40
|
"@bufbuild/protobuf": "^2.11.0",
|
|
41
|
-
"
|
|
41
|
+
"@tanstack/react-query": "^5.100.1",
|
|
42
|
+
"esbuild": "^0.25.12",
|
|
43
|
+
"zustand": "^5.0.12"
|
|
42
44
|
},
|
|
43
45
|
"peerDependencies": {
|
|
44
46
|
"@chakra-ui/icons": "^2.2.4",
|
|
@@ -59,13 +61,13 @@
|
|
|
59
61
|
"@emotion/styled": "^11.14.0",
|
|
60
62
|
"@uiw/react-codemirror": "^4.25.8",
|
|
61
63
|
"d3-force": "^3.0.0",
|
|
62
|
-
"
|
|
64
|
+
"dagre": "^0.8.5",
|
|
63
65
|
"framer-motion": "^11.18.2",
|
|
64
66
|
"html-to-image": "^1.11.13",
|
|
65
67
|
"mermaid-ast": "^0.8.2",
|
|
66
68
|
"react": "^18.3.1",
|
|
67
69
|
"react-dom": "^18.3.1",
|
|
68
|
-
"react-router-dom": "^6.
|
|
70
|
+
"react-router-dom": "^6.30.3",
|
|
69
71
|
"reactflow": "^11.11.4",
|
|
70
72
|
"web-tree-sitter": "0.21.0"
|
|
71
73
|
},
|
|
@@ -125,12 +127,13 @@
|
|
|
125
127
|
"@emotion/react": "^11.14.0",
|
|
126
128
|
"@emotion/styled": "^11.14.0",
|
|
127
129
|
"@eslint/js": "^9.0.0",
|
|
130
|
+
"@types/dagre": "^0.7.54",
|
|
128
131
|
"@types/react": "^18.3.12",
|
|
129
132
|
"@types/react-dom": "^18.3.1",
|
|
130
133
|
"@uiw/react-codemirror": "^4.25.8",
|
|
131
134
|
"@vitejs/plugin-react": "^4.3.4",
|
|
132
135
|
"d3-force": "^3.0.0",
|
|
133
|
-
"
|
|
136
|
+
"dagre": "^0.8.5",
|
|
134
137
|
"eslint": "^9.0.0",
|
|
135
138
|
"eslint-plugin-react-hooks": "^5.0.0",
|
|
136
139
|
"eslint-plugin-react-refresh": "^0.4.0",
|
|
@@ -140,11 +143,11 @@
|
|
|
140
143
|
"mermaid-ast": "^0.8.2",
|
|
141
144
|
"react": "^18.3.1",
|
|
142
145
|
"react-dom": "^18.3.1",
|
|
143
|
-
"react-router-dom": "^6.
|
|
146
|
+
"react-router-dom": "^6.30.3",
|
|
144
147
|
"reactflow": "^11.11.4",
|
|
145
148
|
"typescript": "^5.6.3",
|
|
146
149
|
"typescript-eslint": "^8.0.0",
|
|
147
|
-
"vite": "^6.
|
|
150
|
+
"vite": "^6.4.2",
|
|
148
151
|
"vite-tsconfig-paths": "^6.1.1",
|
|
149
152
|
"vitest": "^4.1.2",
|
|
150
153
|
"web-tree-sitter": "^0.21.0"
|
package/src/api/client.ts
CHANGED
|
@@ -46,6 +46,7 @@ import {
|
|
|
46
46
|
ImportService,
|
|
47
47
|
} from '@buf/tldiagramcom_diagram.bufbuild_es/diag/v1/import_service_pb'
|
|
48
48
|
import { transport } from './transport'
|
|
49
|
+
import { apiUrl, fetchApiAsset } from '../config/runtime'
|
|
49
50
|
|
|
50
51
|
export interface DependenciesResponse {
|
|
51
52
|
elements: DependencyElement[]
|
|
@@ -383,6 +384,36 @@ export const api = {
|
|
|
383
384
|
return (json.views ?? []).map(mapDiagram)
|
|
384
385
|
}),
|
|
385
386
|
|
|
387
|
+
// Lazy: root-level views only. Use for sidebar first render on huge workspaces.
|
|
388
|
+
treeRoots: (opts: { limit?: number; offset?: number; search?: string } = {}): Promise<{ views: ViewTreeNode[]; totalCount: number }> =>
|
|
389
|
+
rpc(async () => {
|
|
390
|
+
const res = await workspaceClient.getWorkspace({
|
|
391
|
+
includeContent: false,
|
|
392
|
+
level: 0,
|
|
393
|
+
limit: opts.limit ?? 0,
|
|
394
|
+
offset: opts.offset ?? 0,
|
|
395
|
+
search: opts.search ?? '',
|
|
396
|
+
})
|
|
397
|
+
const json = j<{ views: ProtoDiagram[]; total_count?: number }>(GetWorkspaceResponseSchema, res)
|
|
398
|
+
return {
|
|
399
|
+
views: (json.views ?? []).map(mapDiagram),
|
|
400
|
+
totalCount: Number(json.total_count ?? 0),
|
|
401
|
+
}
|
|
402
|
+
}),
|
|
403
|
+
|
|
404
|
+
// Lazy: direct children of a parent view. Used on tree node expand.
|
|
405
|
+
treeChildren: (parentId: number, opts: { limit?: number; offset?: number } = {}): Promise<ViewTreeNode[]> =>
|
|
406
|
+
rpc(async () => {
|
|
407
|
+
const res = await workspaceClient.getWorkspace({
|
|
408
|
+
includeContent: false,
|
|
409
|
+
parentId,
|
|
410
|
+
limit: opts.limit ?? 0,
|
|
411
|
+
offset: opts.offset ?? 0,
|
|
412
|
+
})
|
|
413
|
+
const json = j<{ views: ProtoDiagram[] }>(GetWorkspaceResponseSchema, res)
|
|
414
|
+
return (json.views ?? []).map(mapDiagram)
|
|
415
|
+
}),
|
|
416
|
+
|
|
386
417
|
get: (id: number): Promise<ViewTreeNode> =>
|
|
387
418
|
rpc(async () => {
|
|
388
419
|
const res = await workspaceClient.getView({ viewId: id })
|
|
@@ -422,7 +453,14 @@ export const api = {
|
|
|
422
453
|
delete: (_orgId: string, id: number): Promise<void> =>
|
|
423
454
|
rpc(async () => { await workspaceClient.deleteView({ orgId: '', viewId: id }) }),
|
|
424
455
|
|
|
425
|
-
thumbnail: async (
|
|
456
|
+
thumbnail: async (id: number): Promise<string | null> => {
|
|
457
|
+
const res = await fetchApiAsset(apiUrl(`/views/${id}/thumbnail.svg`), {
|
|
458
|
+
headers: { Accept: 'image/svg+xml' },
|
|
459
|
+
})
|
|
460
|
+
if (!res.ok) return null
|
|
461
|
+
const svg = await res.text()
|
|
462
|
+
return URL.createObjectURL(new Blob([svg], { type: 'image/svg+xml;charset=utf-8' }))
|
|
463
|
+
},
|
|
426
464
|
|
|
427
465
|
placements: {
|
|
428
466
|
list: (diagramId: number): Promise<ElementPlacement[]> =>
|
|
@@ -12,9 +12,6 @@ import { ZoomInIcon, ZoomOutIcon, TrashIcon as TrashSvg, EditIcon as EditSvg } f
|
|
|
12
12
|
import { vscodeBridge } from '../lib/vscodeBridge'
|
|
13
13
|
import type { ExtensionToWebviewMessage } from '../types/vscode-messages'
|
|
14
14
|
import {
|
|
15
|
-
DEFAULT_SOURCE_HANDLE_SIDE,
|
|
16
|
-
DEFAULT_TARGET_HANDLE_SIDE,
|
|
17
|
-
ensureVisualHandleId,
|
|
18
15
|
getVisualHandleId,
|
|
19
16
|
getVisualHandleStyle,
|
|
20
17
|
HANDLE_SLOT_CENTER_INDEX,
|
|
@@ -141,7 +138,7 @@ interface NodeData extends PlacedElement {
|
|
|
141
138
|
onZoomOut: (elementId: number) => void
|
|
142
139
|
onNavigateToDiagram: (viewId: number) => void
|
|
143
140
|
onSelect: (obj: PlacedElement) => void
|
|
144
|
-
onInteractionStart: (elementId: number) => void
|
|
141
|
+
onInteractionStart: (elementId: number, options?: { sourceHandle?: string; clientX?: number; clientY?: number }) => void
|
|
145
142
|
onConnectTo: (elementId: number) => void
|
|
146
143
|
onStartHandleReconnect?: (args: { edgeId: string; endpoint: 'source' | 'target'; handleId: string; clientX: number; clientY: number }) => void
|
|
147
144
|
onRemove: (elementId: number) => void
|
|
@@ -154,6 +151,10 @@ interface NodeData extends PlacedElement {
|
|
|
154
151
|
layerHighlightColor?: string
|
|
155
152
|
forceShowTagPopup?: boolean
|
|
156
153
|
isCanvasMoving?: boolean
|
|
154
|
+
connectedHandleIds?: readonly string[]
|
|
155
|
+
selectedHandleIds?: readonly string[]
|
|
156
|
+
reconnectCandidates?: readonly { handleId: string; edgeId: string; endpoint: 'source' | 'target'; selected: boolean }[]
|
|
157
|
+
isConnectorHighlighted?: boolean
|
|
157
158
|
}
|
|
158
159
|
|
|
159
160
|
interface Props {
|
|
@@ -285,61 +286,13 @@ function ElementNode({ data, selected }: Props) {
|
|
|
285
286
|
const zoom = useStore(zoomSelector)
|
|
286
287
|
useAccentColor()
|
|
287
288
|
|
|
288
|
-
const nodeId = String(data.element_id)
|
|
289
|
-
const isConnectorHighlighted = useStore((s) =>
|
|
290
|
-
s.edges.some(e => e.selected && (e.source === nodeId || e.target === nodeId))
|
|
291
|
-
)
|
|
292
|
-
const { connectedHandleKey, selectedHandleKey } = useStore((s) => {
|
|
293
|
-
const connectedHandles = new Set<string>()
|
|
294
|
-
const selectedHandles = new Set<string>()
|
|
295
|
-
for (const connector of s.edges) {
|
|
296
|
-
if (connector.source === nodeId) {
|
|
297
|
-
const targetNode = s.nodeInternals.get(connector.target)
|
|
298
|
-
if (targetNode && targetNode.type !== 'ContextBoundaryElement' && targetNode.type !== 'contextNeighborNode') {
|
|
299
|
-
const handleId = ensureVisualHandleId(connector.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE)
|
|
300
|
-
if (handleId) {
|
|
301
|
-
connectedHandles.add(handleId)
|
|
302
|
-
if (connector.selected) selectedHandles.add(handleId)
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
if (connector.target === nodeId) {
|
|
307
|
-
const sourceNode = s.nodeInternals.get(connector.source)
|
|
308
|
-
if (sourceNode && sourceNode.type !== 'ContextBoundaryElement' && sourceNode.type !== 'contextNeighborNode') {
|
|
309
|
-
const handleId = ensureVisualHandleId(connector.targetHandle, DEFAULT_TARGET_HANDLE_SIDE)
|
|
310
|
-
if (handleId) {
|
|
311
|
-
connectedHandles.add(handleId)
|
|
312
|
-
if (connector.selected) selectedHandles.add(handleId)
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
return {
|
|
318
|
-
connectedHandleKey: Array.from(connectedHandles).sort().join('|'),
|
|
319
|
-
selectedHandleKey: Array.from(selectedHandles).sort().join('|'),
|
|
320
|
-
}
|
|
321
|
-
})
|
|
322
|
-
const handleReconnectCandidates = useStore((s) => {
|
|
323
|
-
const candidates: Array<{ handleId: string; edgeId: string; endpoint: 'source' | 'target'; selected: boolean }> = []
|
|
324
|
-
for (const connector of s.edges) {
|
|
325
|
-
if (connector.source === nodeId) {
|
|
326
|
-
const handleId = ensureVisualHandleId(connector.sourceHandle, DEFAULT_SOURCE_HANDLE_SIDE)
|
|
327
|
-
if (handleId) candidates.push({ handleId, edgeId: connector.id, endpoint: 'source', selected: !!connector.selected })
|
|
328
|
-
}
|
|
329
|
-
if (connector.target === nodeId) {
|
|
330
|
-
const handleId = ensureVisualHandleId(connector.targetHandle, DEFAULT_TARGET_HANDLE_SIDE)
|
|
331
|
-
if (handleId) candidates.push({ handleId, edgeId: connector.id, endpoint: 'target', selected: !!connector.selected })
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
return candidates
|
|
335
|
-
})
|
|
336
289
|
const connectedHandleIds = useMemo(
|
|
337
|
-
() => new Set(
|
|
338
|
-
[
|
|
290
|
+
() => new Set(data.connectedHandleIds ?? []),
|
|
291
|
+
[data.connectedHandleIds],
|
|
339
292
|
)
|
|
340
293
|
const selectedHandleIds = useMemo(
|
|
341
|
-
() => new Set(
|
|
342
|
-
[
|
|
294
|
+
() => new Set(data.selectedHandleIds ?? []),
|
|
295
|
+
[data.selectedHandleIds],
|
|
343
296
|
)
|
|
344
297
|
const activeSides = useMemo(() => {
|
|
345
298
|
const sides = new Set<string>()
|
|
@@ -351,7 +304,7 @@ function ElementNode({ data, selected }: Props) {
|
|
|
351
304
|
}, [connectedHandleIds])
|
|
352
305
|
const reconnectCandidateByHandle = useMemo(() => {
|
|
353
306
|
const next = new Map<string, { edgeId: string; endpoint: 'source' | 'target' }>()
|
|
354
|
-
const sortedCandidates = [...
|
|
307
|
+
const sortedCandidates = [...(data.reconnectCandidates ?? [])].sort((left, right) => {
|
|
355
308
|
if (left.selected !== right.selected) return left.selected ? -1 : 1
|
|
356
309
|
return left.edgeId.localeCompare(right.edgeId)
|
|
357
310
|
})
|
|
@@ -361,7 +314,7 @@ function ElementNode({ data, selected }: Props) {
|
|
|
361
314
|
}
|
|
362
315
|
}
|
|
363
316
|
return next
|
|
364
|
-
}, [
|
|
317
|
+
}, [data.reconnectCandidates])
|
|
365
318
|
|
|
366
319
|
const derivedPrimaryIconPath = (() => {
|
|
367
320
|
const selected = data.technology_connectors?.find((link) => link.type === 'catalog' && !!link.is_primary_icon && !!link.slug)
|
|
@@ -509,7 +462,7 @@ function ElementNode({ data, selected }: Props) {
|
|
|
509
462
|
isSelected={selected}
|
|
510
463
|
isSource={isSource}
|
|
511
464
|
isTarget={isTarget}
|
|
512
|
-
isConnectorHighlighted={isConnectorHighlighted}
|
|
465
|
+
isConnectorHighlighted={!!data.isConnectorHighlighted}
|
|
513
466
|
minW="180px"
|
|
514
467
|
maxW="230px"
|
|
515
468
|
cursor={bodyCursor}
|
|
@@ -552,6 +505,15 @@ function ElementNode({ data, selected }: Props) {
|
|
|
552
505
|
position={position}
|
|
553
506
|
id={handleId}
|
|
554
507
|
className={className}
|
|
508
|
+
onClick={(e: React.MouseEvent) => {
|
|
509
|
+
e.preventDefault()
|
|
510
|
+
e.stopPropagation()
|
|
511
|
+
data.onInteractionStart(data.element_id, {
|
|
512
|
+
sourceHandle: handleId,
|
|
513
|
+
clientX: e.clientX,
|
|
514
|
+
clientY: e.clientY,
|
|
515
|
+
})
|
|
516
|
+
}}
|
|
555
517
|
style={{
|
|
556
518
|
...getVisualHandleStyle(position, slot),
|
|
557
519
|
background: 'var(--accent)',
|
|
@@ -480,7 +480,7 @@ function ElementPanel({ isOpen, onClose, element, onSave, autoSave = false, onDe
|
|
|
480
480
|
try {
|
|
481
481
|
if (viewId != null) {
|
|
482
482
|
await api.workspace.views.placements.remove(viewId, element.id)
|
|
483
|
-
} else if (orgId) {
|
|
483
|
+
} else if (orgId !== undefined) {
|
|
484
484
|
await api.elements.delete(orgId, element.id)
|
|
485
485
|
}
|
|
486
486
|
onDelete?.(element.id)
|
|
@@ -491,8 +491,7 @@ function ElementPanel({ isOpen, onClose, element, onSave, autoSave = false, onDe
|
|
|
491
491
|
const handlePermanentDelete = async () => {
|
|
492
492
|
if (isReadOnly || !element) return
|
|
493
493
|
try {
|
|
494
|
-
|
|
495
|
-
await api.elements.delete(orgId, element.id)
|
|
494
|
+
await api.elements.delete(orgId ?? '', element.id)
|
|
496
495
|
onPermanentDelete?.(element.id)
|
|
497
496
|
confirmPermanentDelete.onClose()
|
|
498
497
|
onClose()
|