@likec4/diagram 1.23.1 → 1.24.1
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/LikeC4Diagram.css.d.ts +5 -0
- package/dist/LikeC4Diagram.css.js +1 -0
- package/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.js +6 -2
- package/dist/LikeC4Diagram.props.d.ts +6 -0
- package/dist/StaticLikeC4Diagram.js +2 -1
- package/dist/base/BaseXYFlow.d.ts +1 -1
- package/dist/base/BaseXYFlow.js +12 -3
- package/dist/base/const.d.ts +1 -1
- package/dist/base/const.js +1 -1
- package/dist/base/primitives/compound/CompoundActionButton.css.js +2 -0
- package/dist/base/primitives/compound/CompoundDetailsButton.css.js +2 -0
- package/dist/base/primitives/compound/CompoundNodeContainer.css.d.ts +0 -1
- package/dist/base/primitives/compound/CompoundNodeContainer.css.js +4 -4
- package/dist/base/primitives/compound/CompoundNodeContainer.js +35 -22
- package/dist/base/primitives/compound/CompoundTitle.css.js +3 -1
- package/dist/base/primitives/customNode.js +1 -1
- package/dist/base/primitives/edge/EdgeContainer.js +5 -4
- package/dist/base/primitives/edge/EdgeLabel.css.d.ts +0 -1
- package/dist/base/primitives/edge/EdgeLabel.css.js +2 -5
- package/dist/base/primitives/edge/EdgeLabel.js +5 -5
- package/dist/base/primitives/edge/edge.css.d.ts +1 -1
- package/dist/base/primitives/edge/edge.css.js +4 -5
- package/dist/base/primitives/element/ElementActionButtons.css.js +2 -0
- package/dist/base/primitives/element/ElementActionButtons.js +4 -3
- package/dist/base/primitives/element/ElementDetailsButton.css.js +2 -0
- package/dist/base/primitives/element/ElementNodeContainer.css.js +2 -0
- package/dist/base/primitives/element/ElementNodeContainer.js +3 -1
- package/dist/base/primitives/element/ElementShape.css.js +4 -2
- package/dist/base/primitives/element/ElementShape.js +2 -3
- package/dist/base/primitives/element/ElementTitle.css.js +2 -0
- package/dist/base/primitives/element/styles.css.js +2 -0
- package/dist/components/SearchControl.css.js +2 -0
- package/dist/context/FramerMotionConfig.d.ts +9 -1
- package/dist/context/FramerMotionConfig.js +5 -2
- package/dist/context/RootContainer.js +13 -1
- package/dist/diagram.css +1 -1
- package/dist/globals.css.d.ts +2 -0
- package/dist/hooks/safeContext.js +1 -0
- package/dist/hooks/useDiagram.d.ts +415 -693
- package/dist/hooks/useDiagram.js +1 -0
- package/dist/hooks/useIsReducedGraphics.d.ts +4 -0
- package/dist/hooks/useIsReducedGraphics.js +8 -0
- package/dist/hooks/useXYFlow.js +1 -1
- package/dist/likec4diagram/DiagramUI.d.ts +2 -1
- package/dist/likec4diagram/DiagramUI.js +5 -3
- package/dist/likec4diagram/custom/nodes/nodeTypes.js +2 -2
- package/dist/likec4diagram/ui/controls/styles.css.js +2 -0
- package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.css.js +2 -0
- package/dist/likec4diagram/ui/diagram-title/DiagramTitlePanel.js +3 -4
- package/dist/likec4diagram/ui/search/LikeC4Search.css.js +2 -0
- package/dist/overlays/element-details/ElementDetailsCard.js +12 -8
- package/dist/overlays/element-details/actor.d.ts +9084 -2569
- package/dist/overlays/overlay/Overlay.js +6 -3
- package/dist/overlays/relationship-details/RelationshipDetails.js +31 -17
- package/dist/overlays/relationship-details/actor.d.ts +228 -3
- package/dist/overlays/relationship-details/actor.js +86 -14
- package/dist/overlays/relationship-details/hooks.d.ts +3 -0
- package/dist/overlays/relationship-details/hooks.js +6 -2
- package/dist/overlays/relationships-browser/-useRelationshipsView.js +12 -3
- package/dist/overlays/relationships-browser/RelationshipsBrowser.js +12 -3
- package/dist/overlays/relationships-browser/actor.d.ts +271 -54
- package/dist/overlays/relationships-browser/actor.js +86 -23
- package/dist/state/DiagramActorProvider.js +12 -2
- package/dist/state/diagram-machine.d.ts +426 -694
- package/dist/state/diagram-machine.js +89 -68
- package/dist/state/persistence.d.ts +5 -0
- package/dist/state/persistence.js +23 -0
- package/package.json +9 -9
- package/src/LikeC4Diagram.css.ts +13 -5
- package/src/LikeC4Diagram.props.ts +7 -0
- package/src/LikeC4Diagram.tsx +33 -24
- package/src/StaticLikeC4Diagram.tsx +2 -1
- package/src/base/BaseXYFlow.tsx +14 -9
- package/src/base/const.ts +1 -1
- package/src/base/primitives/compound/CompoundActionButton.css.ts +6 -0
- package/src/base/primitives/compound/CompoundDetailsButton.css.ts +4 -0
- package/src/base/primitives/compound/CompoundNodeContainer.css.ts +41 -59
- package/src/base/primitives/compound/CompoundNodeContainer.tsx +42 -22
- package/src/base/primitives/compound/CompoundTitle.css.ts +14 -13
- package/src/base/primitives/customNode.ts +3 -2
- package/src/base/primitives/edge/EdgeContainer.tsx +4 -3
- package/src/base/primitives/edge/EdgeLabel.css.ts +17 -11
- package/src/base/primitives/edge/EdgeLabel.tsx +4 -4
- package/src/base/primitives/edge/edge.css.ts +47 -27
- package/src/base/primitives/element/ElementActionButtons.css.ts +4 -0
- package/src/base/primitives/element/ElementActionButtons.tsx +4 -2
- package/src/base/primitives/element/ElementDetailsButton.css.ts +6 -8
- package/src/base/primitives/element/ElementNodeContainer.css.ts +18 -21
- package/src/base/primitives/element/ElementNodeContainer.tsx +3 -1
- package/src/base/primitives/element/ElementShape.css.ts +10 -6
- package/src/base/primitives/element/ElementShape.tsx +2 -2
- package/src/components/SearchControl.css.ts +11 -4
- package/src/context/FramerMotionConfig.tsx +13 -2
- package/src/context/RootContainer.tsx +9 -2
- package/src/globals.css.ts +3 -0
- package/src/hooks/safeContext.tsx +2 -1
- package/src/hooks/useDiagram.ts +1 -0
- package/src/hooks/useIsReducedGraphics.ts +5 -0
- package/src/hooks/useXYFlow.ts +1 -1
- package/src/likec4diagram/DiagramUI.tsx +5 -3
- package/src/likec4diagram/custom/nodes/nodeTypes.tsx +2 -2
- package/src/likec4diagram/ui/controls/styles.css.ts +11 -1
- package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.css.ts +10 -3
- package/src/likec4diagram/ui/diagram-title/DiagramTitlePanel.tsx +6 -3
- package/src/likec4diagram/ui/search/LikeC4Search.css.ts +1 -0
- package/src/overlays/element-details/ElementDetailsCard.tsx +238 -231
- package/src/overlays/overlay/Overlay.css.ts +1 -9
- package/src/overlays/overlay/Overlay.tsx +6 -3
- package/src/overlays/relationship-details/RelationshipDetails.tsx +83 -66
- package/src/overlays/relationship-details/actor.ts +106 -13
- package/src/overlays/relationship-details/hooks.ts +6 -2
- package/src/overlays/relationships-browser/-useRelationshipsView.ts +10 -0
- package/src/overlays/relationships-browser/RelationshipsBrowser.tsx +16 -8
- package/src/overlays/relationships-browser/actor.ts +92 -22
- package/src/state/DiagramActorProvider.tsx +13 -2
- package/src/state/diagram-machine.ts +88 -65
- package/src/state/persistence.ts +22 -0
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import { nonNullable, invariant,
|
|
1
|
+
import { nonNullable, invariant, DiagramNode, nonexhaustive, getBBoxCenter, getParallelStepsPrefix } from "@likec4/core";
|
|
2
2
|
import { getViewportForBounds, applyEdgeChanges, applyNodeChanges } from "@xyflow/react";
|
|
3
3
|
import { nodeToRect } from "@xyflow/system";
|
|
4
4
|
import { hasAtLeast, prop, clamp, first } from "remeda";
|
|
5
|
-
import { setup, enqueueActions, assertEvent, stopChild,
|
|
5
|
+
import { setup, enqueueActions, assign, assertEvent, stopChild, sendTo, cancel, raise, spawnChild, and, or } from "xstate";
|
|
6
6
|
import { MinZoom } from "../base/const.js";
|
|
7
7
|
import { AllDisabled } from "../context/DiagramFeatures.js";
|
|
8
8
|
import { createLayoutConstraints } from "../likec4diagram/useLayoutConstraints.js";
|
|
9
9
|
import { overlaysActorLogic } from "../overlays/overlaysActor.js";
|
|
10
10
|
import { getAligner, toNodeRect } from "./aligners.js";
|
|
11
|
-
import { resetEdgeControlPoints, updateEdgeData, updateNodeData, updateNavigationHistory, mergeXYNodesEdges, navigateForward, navigateBack, unfocusNodesEdges, updateActiveWalkthrough, focusNodesEdges
|
|
11
|
+
import { lastClickedNode, resetEdgeControlPoints, updateEdgeData, updateNodeData, updateNavigationHistory, mergeXYNodesEdges, navigateForward, navigateBack, unfocusNodesEdges, updateActiveWalkthrough, focusNodesEdges } from "./assign.js";
|
|
12
12
|
import { hotkeyActorLogic } from "./hotkeyActor.js";
|
|
13
|
+
import { DiagramToggledFeaturesPersistence } from "./persistence.js";
|
|
13
14
|
import { syncManualLayoutActorLogic } from "./syncManualLayoutActor.js";
|
|
14
|
-
import { typedSystem, focusedBounds } from "./utils.js";
|
|
15
|
+
import { typedSystem, findDiagramNode, focusedBounds } from "./utils.js";
|
|
15
16
|
const diagramMachine = setup({
|
|
16
17
|
types: {
|
|
17
18
|
context: {},
|
|
@@ -40,7 +41,7 @@ const diagramMachine = setup({
|
|
|
40
41
|
}
|
|
41
42
|
nonexhaustive(event.type);
|
|
42
43
|
},
|
|
43
|
-
"click: selected node
|
|
44
|
+
"click: selected node": ({ event }) => {
|
|
44
45
|
assertEvent(event, "xyflow.nodeClick");
|
|
45
46
|
return event.node.selected === true;
|
|
46
47
|
},
|
|
@@ -52,6 +53,10 @@ const diagramMachine = setup({
|
|
|
52
53
|
"click: focused node": ({ context, event }) => {
|
|
53
54
|
assertEvent(event, "xyflow.nodeClick");
|
|
54
55
|
return context.focusedNode === event.node.id;
|
|
56
|
+
},
|
|
57
|
+
"click: node has connections": ({ context, event }) => {
|
|
58
|
+
assertEvent(event, "xyflow.nodeClick");
|
|
59
|
+
return context.xyedges.some((e) => e.source === event.node.id || e.target === event.node.id);
|
|
55
60
|
}
|
|
56
61
|
},
|
|
57
62
|
actions: {
|
|
@@ -61,6 +66,24 @@ const diagramMachine = setup({
|
|
|
61
66
|
},
|
|
62
67
|
"trigger:OpenSource": (_, _params) => {
|
|
63
68
|
},
|
|
69
|
+
"assign lastClickedNode": assign(({ context, event }) => {
|
|
70
|
+
assertEvent(event, "xyflow.nodeClick");
|
|
71
|
+
return {
|
|
72
|
+
lastClickedNode: lastClickedNode({ context, event })
|
|
73
|
+
};
|
|
74
|
+
}),
|
|
75
|
+
"open source of focused or last clicked node": enqueueActions(({ context, enqueue }) => {
|
|
76
|
+
var _a;
|
|
77
|
+
const nodeId = context.focusedNode ?? ((_a = context.lastClickedNode) == null ? void 0 : _a.id);
|
|
78
|
+
if (!nodeId || !context.features.enableVscode) return;
|
|
79
|
+
const diagramNode = findDiagramNode(context, nodeId);
|
|
80
|
+
if (!diagramNode) return;
|
|
81
|
+
if (DiagramNode.deploymentRef(diagramNode)) {
|
|
82
|
+
enqueue.raise({ type: "open.source", deployment: DiagramNode.deploymentRef(diagramNode) });
|
|
83
|
+
} else if (DiagramNode.modelRef(diagramNode)) {
|
|
84
|
+
enqueue.raise({ type: "open.source", element: DiagramNode.modelRef(diagramNode) });
|
|
85
|
+
}
|
|
86
|
+
}),
|
|
64
87
|
"xyflow:fitDiagram": ({ context }, params) => {
|
|
65
88
|
const {
|
|
66
89
|
bounds = context.view.bounds,
|
|
@@ -166,7 +189,7 @@ const diagramMachine = setup({
|
|
|
166
189
|
context: ({ input, self, spawn }) => ({
|
|
167
190
|
...input,
|
|
168
191
|
features: { ...AllDisabled },
|
|
169
|
-
toggledFeatures: {
|
|
192
|
+
toggledFeatures: DiagramToggledFeaturesPersistence.read() ?? {
|
|
170
193
|
enableReadOnly: true
|
|
171
194
|
},
|
|
172
195
|
initialized: false,
|
|
@@ -225,9 +248,10 @@ const diagramMachine = setup({
|
|
|
225
248
|
// TODO: xstate fails to infer the type of the guard
|
|
226
249
|
guard: and([
|
|
227
250
|
"enabled: FocusMode",
|
|
251
|
+
"click: node has connections",
|
|
228
252
|
or([
|
|
229
253
|
"click: same node",
|
|
230
|
-
"click: selected node
|
|
254
|
+
"click: selected node"
|
|
231
255
|
])
|
|
232
256
|
]),
|
|
233
257
|
actions: [
|
|
@@ -239,9 +263,10 @@ const diagramMachine = setup({
|
|
|
239
263
|
target: "focused"
|
|
240
264
|
},
|
|
241
265
|
{
|
|
242
|
-
actions:
|
|
243
|
-
lastClickedNode
|
|
244
|
-
|
|
266
|
+
actions: [
|
|
267
|
+
"assign lastClickedNode",
|
|
268
|
+
"open source of focused or last clicked node"
|
|
269
|
+
]
|
|
245
270
|
}
|
|
246
271
|
],
|
|
247
272
|
"xyflow.paneClick": {
|
|
@@ -284,6 +309,7 @@ const diagramMachine = setup({
|
|
|
284
309
|
...focusNodesEdges(s),
|
|
285
310
|
viewportBeforeFocus: { ...s.context.viewport }
|
|
286
311
|
})),
|
|
312
|
+
"open source of focused or last clicked node",
|
|
287
313
|
spawnChild("hotkeyActorLogic", { id: "hotkey" }),
|
|
288
314
|
{
|
|
289
315
|
type: "xyflow:fitDiagram",
|
|
@@ -316,6 +342,7 @@ const diagramMachine = setup({
|
|
|
316
342
|
focusedNode: ({ event }) => event.node.id
|
|
317
343
|
}),
|
|
318
344
|
assign(focusNodesEdges),
|
|
345
|
+
"open source of focused or last clicked node",
|
|
319
346
|
{
|
|
320
347
|
type: "xyflow:fitDiagram",
|
|
321
348
|
params: focusedBounds
|
|
@@ -329,6 +356,7 @@ const diagramMachine = setup({
|
|
|
329
356
|
focusedNode: ({ event }) => event.nodeId
|
|
330
357
|
}),
|
|
331
358
|
assign(focusNodesEdges),
|
|
359
|
+
"open source of focused or last clicked node",
|
|
332
360
|
{
|
|
333
361
|
type: "xyflow:fitDiagram",
|
|
334
362
|
params: focusedBounds
|
|
@@ -360,69 +388,60 @@ const diagramMachine = setup({
|
|
|
360
388
|
},
|
|
361
389
|
// Navigating to another view (after `navigateTo` event)
|
|
362
390
|
navigating: {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
type: "trigger:NavigateTo",
|
|
372
|
-
params: ({ context }) => ({
|
|
373
|
-
viewId: nonNullable(context.lastOnNavigate, "Invalid state, lastOnNavigate is null").toView
|
|
374
|
-
})
|
|
375
|
-
});
|
|
376
|
-
}),
|
|
377
|
-
on: {
|
|
378
|
-
"update.view": {
|
|
379
|
-
actions: enqueueActions(({ enqueue, context, event }) => {
|
|
380
|
-
const { fromNode, toNode } = findCorrespondingNode(context, event);
|
|
381
|
-
if (fromNode && toNode) {
|
|
382
|
-
enqueue({
|
|
383
|
-
type: "xyflow:alignNodeFromToAfterNavigate",
|
|
384
|
-
params: {
|
|
385
|
-
fromNode: fromNode.id,
|
|
386
|
-
toPosition: {
|
|
387
|
-
x: toNode.position[0],
|
|
388
|
-
y: toNode.position[1]
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
});
|
|
392
|
-
enqueue.raise({ type: "fitDiagram" }, { id: "fitDiagram", delay: 80 });
|
|
393
|
-
} else {
|
|
394
|
-
enqueue({
|
|
395
|
-
type: "xyflow:setViewportCenter",
|
|
396
|
-
params: getBBoxCenter(event.view.bounds)
|
|
397
|
-
});
|
|
398
|
-
enqueue.raise({ type: "fitDiagram", duration: 200 }, { id: "fitDiagram", delay: 25 });
|
|
399
|
-
}
|
|
400
|
-
enqueue.assign(updateNavigationHistory);
|
|
401
|
-
enqueue.assign(mergeXYNodesEdges);
|
|
402
|
-
}),
|
|
403
|
-
target: "done"
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
},
|
|
407
|
-
done: {
|
|
408
|
-
type: "final"
|
|
391
|
+
entry: [
|
|
392
|
+
"closeAllOverlays",
|
|
393
|
+
"stopSyncLayout",
|
|
394
|
+
{
|
|
395
|
+
type: "trigger:NavigateTo",
|
|
396
|
+
params: ({ context }) => ({
|
|
397
|
+
viewId: nonNullable(context.lastOnNavigate, "Invalid state, lastOnNavigate is null").toView
|
|
398
|
+
})
|
|
409
399
|
}
|
|
410
|
-
|
|
411
|
-
onDone: {
|
|
412
|
-
target: "idle"
|
|
413
|
-
},
|
|
400
|
+
],
|
|
414
401
|
exit: assign({
|
|
415
402
|
syncLayoutActorRef: ({ self, context, spawn }) => spawn("syncManualLayoutActorLogic", {
|
|
416
403
|
id: "syncLayout",
|
|
417
404
|
systemId: "syncLayout",
|
|
418
405
|
input: { parent: self, viewId: context.view.id }
|
|
419
|
-
})
|
|
420
|
-
lastOnNavigate: null
|
|
421
|
-
})
|
|
406
|
+
})
|
|
407
|
+
// lastOnNavigate: null,
|
|
408
|
+
}),
|
|
409
|
+
on: {
|
|
410
|
+
"update.view": {
|
|
411
|
+
actions: enqueueActions(({ enqueue, context, event }) => {
|
|
412
|
+
const { fromNode, toNode } = findCorrespondingNode(context, event);
|
|
413
|
+
if (fromNode && toNode) {
|
|
414
|
+
enqueue({
|
|
415
|
+
type: "xyflow:alignNodeFromToAfterNavigate",
|
|
416
|
+
params: {
|
|
417
|
+
fromNode: fromNode.id,
|
|
418
|
+
toPosition: {
|
|
419
|
+
x: toNode.position[0],
|
|
420
|
+
y: toNode.position[1]
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
enqueue.raise({ type: "fitDiagram" }, { id: "fitDiagram", delay: 80 });
|
|
425
|
+
} else {
|
|
426
|
+
enqueue({
|
|
427
|
+
type: "xyflow:setViewportCenter",
|
|
428
|
+
params: getBBoxCenter(event.view.bounds)
|
|
429
|
+
});
|
|
430
|
+
enqueue.raise({ type: "fitDiagram", duration: 200 }, { id: "fitDiagram", delay: 25 });
|
|
431
|
+
}
|
|
432
|
+
enqueue.assign(updateNavigationHistory);
|
|
433
|
+
enqueue.assign(mergeXYNodesEdges);
|
|
434
|
+
enqueue.assign({
|
|
435
|
+
lastOnNavigate: null
|
|
436
|
+
});
|
|
437
|
+
}),
|
|
438
|
+
target: "#idle"
|
|
439
|
+
}
|
|
440
|
+
}
|
|
422
441
|
},
|
|
423
442
|
walkthrough: {
|
|
424
443
|
entry: [
|
|
425
|
-
|
|
444
|
+
spawnChild("hotkeyActorLogic", { id: "hotkey" }),
|
|
426
445
|
assign({
|
|
427
446
|
viewportBeforeFocus: ({ context }) => context.viewport,
|
|
428
447
|
activeWalkthrough: ({ context, event }) => {
|
|
@@ -682,10 +701,12 @@ const diagramMachine = setup({
|
|
|
682
701
|
},
|
|
683
702
|
"toggle.feature": {
|
|
684
703
|
actions: assign({
|
|
685
|
-
toggledFeatures: ({ context, event }) =>
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
704
|
+
toggledFeatures: ({ context, event }) => {
|
|
705
|
+
return DiagramToggledFeaturesPersistence.write({
|
|
706
|
+
...context.toggledFeatures,
|
|
707
|
+
[`enable${event.feature}`]: event.forceValue ?? !(context.toggledFeatures[`enable${event.feature}`] ?? context.features[`enable${event.feature}`])
|
|
708
|
+
});
|
|
709
|
+
}
|
|
689
710
|
})
|
|
690
711
|
}
|
|
691
712
|
},
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { pickBy, isBoolean } from "remeda";
|
|
2
|
+
const key = `likec4:diagram:toggledFeatures`;
|
|
3
|
+
const DiagramToggledFeaturesPersistence = {
|
|
4
|
+
read() {
|
|
5
|
+
try {
|
|
6
|
+
let fromStorage = sessionStorage.getItem(key);
|
|
7
|
+
if (fromStorage) {
|
|
8
|
+
return JSON.parse(fromStorage);
|
|
9
|
+
}
|
|
10
|
+
throw new Error(`Workspace ${key} not found`);
|
|
11
|
+
} catch (e) {
|
|
12
|
+
console.error(`Error reading fromStorage ${key}:`, e);
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
write(toggledFeatures) {
|
|
17
|
+
sessionStorage.setItem(key, JSON.stringify(pickBy(toggledFeatures, isBoolean)));
|
|
18
|
+
return toggledFeatures;
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
export {
|
|
22
|
+
DiagramToggledFeaturesPersistence
|
|
23
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@likec4/diagram",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.24.1",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"bugs": "https://github.com/likec4/likec4/issues",
|
|
6
6
|
"homepage": "https://likec4.dev",
|
|
@@ -37,21 +37,21 @@
|
|
|
37
37
|
"@tabler/icons-react": "3.29.0",
|
|
38
38
|
"@vanilla-extract/dynamic": "^2.1.2",
|
|
39
39
|
"@xstate/react": "^5.0.2",
|
|
40
|
-
"@xyflow/react": "12.4.
|
|
41
|
-
"@xyflow/system": "0.0.
|
|
40
|
+
"@xyflow/react": "12.4.4",
|
|
41
|
+
"@xyflow/system": "0.0.52",
|
|
42
42
|
"bezier-js": "^6.1.4",
|
|
43
43
|
"clsx": "^2.1.1",
|
|
44
44
|
"d3-shape": "^3.2.0",
|
|
45
45
|
"esm-env": "^1.2.2",
|
|
46
46
|
"fast-equals": "^5.2.2",
|
|
47
|
-
"framer-motion": "^12.4.
|
|
47
|
+
"framer-motion": "^12.4.7",
|
|
48
48
|
"khroma": "^2.1.0",
|
|
49
49
|
"nanostores": "0.11.3",
|
|
50
50
|
"react-error-boundary": "^5.0.0",
|
|
51
|
-
"remeda": "^2.20.
|
|
51
|
+
"remeda": "^2.20.2",
|
|
52
52
|
"type-fest": "4.34.1",
|
|
53
53
|
"xstate": "^5.19.2",
|
|
54
|
-
"@likec4/core": "1.
|
|
54
|
+
"@likec4/core": "1.24.1"
|
|
55
55
|
},
|
|
56
56
|
"peerDependencies": {
|
|
57
57
|
"@mantine/core": "7.17.0",
|
|
@@ -72,14 +72,14 @@
|
|
|
72
72
|
"typescript": "5.7.3",
|
|
73
73
|
"vite": "6.1.0",
|
|
74
74
|
"vite-plugin-dts": "^4.5.0",
|
|
75
|
-
"vitest": "^3.0.
|
|
76
|
-
"@likec4/tsconfig": "1.
|
|
75
|
+
"vitest": "^3.0.6",
|
|
76
|
+
"@likec4/tsconfig": "1.24.1"
|
|
77
77
|
},
|
|
78
78
|
"scripts": {
|
|
79
79
|
"typecheck": "tsc --noEmit",
|
|
80
80
|
"build": "vite build",
|
|
81
81
|
"lint": "run -T eslint src/ --fix",
|
|
82
|
-
"clean": "rm -r -f -g
|
|
82
|
+
"clean": "rm -r -f -g \"dist/*\" .tsbuildinfo",
|
|
83
83
|
"test": "vitest run --no-isolate"
|
|
84
84
|
}
|
|
85
85
|
}
|
package/src/LikeC4Diagram.css.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { globalStyle, style } from '@vanilla-extract/css'
|
|
2
|
+
import { whereNotReducedGraphics, whereReducedGraphics } from './globals.css'
|
|
2
3
|
import { vars, xyvars } from './theme-vars'
|
|
3
4
|
|
|
4
5
|
export const cssReactFlow = style({
|
|
@@ -13,6 +14,10 @@ export const cssReactFlow = style({
|
|
|
13
14
|
},
|
|
14
15
|
})
|
|
15
16
|
|
|
17
|
+
export const reactFlowReducedGraphics = `${whereReducedGraphics} ${cssReactFlow}`
|
|
18
|
+
export const reactFlow = `${whereNotReducedGraphics} ${cssReactFlow}`
|
|
19
|
+
export { whereNotReducedGraphics, whereReducedGraphics }
|
|
20
|
+
|
|
16
21
|
export const notInitialized = style({
|
|
17
22
|
opacity: 0,
|
|
18
23
|
})
|
|
@@ -30,11 +35,6 @@ globalStyle(`.react-flow${cssReactFlow}`, {
|
|
|
30
35
|
globalStyle(`.react-flow${cssReactFlow} .react-flow__pane`, {
|
|
31
36
|
WebkitUserSelect: 'none',
|
|
32
37
|
})
|
|
33
|
-
|
|
34
|
-
globalStyle(`.react-flow${cssReactFlow} .xyflow__viewport`, {
|
|
35
|
-
willChange: 'transform',
|
|
36
|
-
})
|
|
37
|
-
|
|
38
38
|
export const cssDisablePan = style({})
|
|
39
39
|
|
|
40
40
|
export const cssTransparentBg = style({})
|
|
@@ -58,3 +58,11 @@ export const hiddenIfZoomTooSmall = style({
|
|
|
58
58
|
},
|
|
59
59
|
},
|
|
60
60
|
})
|
|
61
|
+
|
|
62
|
+
export const hiddenIfReducedGraphics = style({
|
|
63
|
+
selectors: {
|
|
64
|
+
[`${reactFlowReducedGraphics} &`]: {
|
|
65
|
+
visibility: 'hidden',
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
})
|
|
@@ -172,6 +172,13 @@ export interface LikeC4DiagramProperties {
|
|
|
172
172
|
*/
|
|
173
173
|
experimentalEdgeEditing?: boolean | undefined
|
|
174
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Improve performance by hiding certain elements and reducing visual effects (disable mix-blend, shadows, animations)
|
|
177
|
+
*
|
|
178
|
+
* @default 'auto' - will be set to true if view has more then 3000 * 2000 pixels
|
|
179
|
+
*/
|
|
180
|
+
reduceGraphics?: 'auto' | boolean | undefined
|
|
181
|
+
|
|
175
182
|
/**
|
|
176
183
|
* Render icon for an element, bundled or remote
|
|
177
184
|
* By default, if icon is http:// or https://, it will be rendered as an image
|
package/src/LikeC4Diagram.tsx
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
IconRendererProvider,
|
|
10
10
|
RootContainer,
|
|
11
11
|
} from './context'
|
|
12
|
+
import { ReducedGraphicsContext } from './hooks/useIsReducedGraphics'
|
|
12
13
|
import { LikeC4CustomColors } from './LikeC4CustomColors'
|
|
13
14
|
import { type LikeC4DiagramEventHandlers, type LikeC4DiagramProperties } from './LikeC4Diagram.props'
|
|
14
15
|
import type { Types } from './likec4diagram/types'
|
|
@@ -41,6 +42,7 @@ export function LikeC4Diagram({
|
|
|
41
42
|
initialWidth,
|
|
42
43
|
initialHeight,
|
|
43
44
|
experimentalEdgeEditing = false,
|
|
45
|
+
reduceGraphics = 'auto',
|
|
44
46
|
onCanvasClick,
|
|
45
47
|
onCanvasContextMenu,
|
|
46
48
|
onCanvasDblClick,
|
|
@@ -81,6 +83,11 @@ export function LikeC4Diagram({
|
|
|
81
83
|
}
|
|
82
84
|
}
|
|
83
85
|
|
|
86
|
+
// If view has more then 3000 * 2000 pixels - assume it is a big diagram
|
|
87
|
+
const isReducedGraphicsMode = reduceGraphics === 'auto'
|
|
88
|
+
? (view.bounds?.width ?? 1) * (view.bounds?.height ?? 1) > 6_000_000
|
|
89
|
+
: reduceGraphics
|
|
90
|
+
|
|
84
91
|
return (
|
|
85
92
|
<EnsureMantine>
|
|
86
93
|
<FramerMotionConfig>
|
|
@@ -119,30 +126,32 @@ export function LikeC4Diagram({
|
|
|
119
126
|
onOpenSource,
|
|
120
127
|
onBurgerMenuClick,
|
|
121
128
|
}}>
|
|
122
|
-
<
|
|
123
|
-
{
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
129
|
+
<ReducedGraphicsContext value={isReducedGraphicsMode}>
|
|
130
|
+
<RootContainer className={className}>
|
|
131
|
+
{!isEmpty(view.customColorDefinitions) && (
|
|
132
|
+
<LikeC4CustomColors customColors={view.customColorDefinitions} />
|
|
133
|
+
)}
|
|
134
|
+
<XYFlowProvider
|
|
135
|
+
fitView={fitView}
|
|
136
|
+
{...initialRef.current}
|
|
137
|
+
>
|
|
138
|
+
<DiagramActorProvider
|
|
139
|
+
input={{
|
|
140
|
+
view,
|
|
141
|
+
pannable,
|
|
142
|
+
zoomable,
|
|
143
|
+
fitViewPadding,
|
|
144
|
+
...xyNodesEdges,
|
|
145
|
+
}}>
|
|
146
|
+
<LikeC4DiagramXYFlow
|
|
147
|
+
nodesDraggable={nodesDraggable}
|
|
148
|
+
nodesSelectable={nodesSelectable}
|
|
149
|
+
background={background}
|
|
150
|
+
/>
|
|
151
|
+
</DiagramActorProvider>
|
|
152
|
+
</XYFlowProvider>
|
|
153
|
+
</RootContainer>
|
|
154
|
+
</ReducedGraphicsContext>
|
|
146
155
|
</DiagramEventHandlers>
|
|
147
156
|
</DiagramFeatures>
|
|
148
157
|
</IconRendererProvider>
|
|
@@ -43,11 +43,12 @@ export function StaticLikeC4Diagram({
|
|
|
43
43
|
enableRelationshipDetails={enableRelationshipDetails}
|
|
44
44
|
enableRelationshipBrowser={enableRelationshipBrowser}
|
|
45
45
|
enableDynamicViewWalkthrough={false}
|
|
46
|
+
showNavigationButtons={false}
|
|
46
47
|
experimentalEdgeEditing={false}
|
|
47
48
|
enableFocusMode={false}
|
|
49
|
+
enableSearch={false}
|
|
48
50
|
nodesSelectable={false}
|
|
49
51
|
nodesDraggable={false}
|
|
50
|
-
enableSearch={false}
|
|
51
52
|
{...rest}
|
|
52
53
|
/>
|
|
53
54
|
)
|
package/src/base/BaseXYFlow.tsx
CHANGED
|
@@ -8,9 +8,11 @@ import {
|
|
|
8
8
|
useStoreApi,
|
|
9
9
|
} from '@xyflow/react'
|
|
10
10
|
import clsx from 'clsx'
|
|
11
|
+
import { shallowEqual } from 'fast-equals'
|
|
11
12
|
import { useMemo } from 'react'
|
|
12
13
|
import type { SetRequired, Simplify } from 'type-fest'
|
|
13
|
-
import {
|
|
14
|
+
import { useIsReducedGraphics } from '../hooks/useIsReducedGraphics'
|
|
15
|
+
import { useUpdateEffect } from '../hooks/useUpdateEffect'
|
|
14
16
|
import { useIsZoomTooSmall } from '../hooks/useXYFlow'
|
|
15
17
|
import * as css from '../LikeC4Diagram.css'
|
|
16
18
|
import { stopPropagation } from '../utils/xyflow'
|
|
@@ -41,10 +43,8 @@ export type BaseXYFlowProps<NodeType extends Base.Node, EdgeType extends Base.Ed
|
|
|
41
43
|
| 'zoomActivationKeyCode'
|
|
42
44
|
| 'zoomOnScroll'
|
|
43
45
|
| 'elementsSelectable'
|
|
44
|
-
| '
|
|
45
|
-
| '
|
|
46
|
-
| 'onEdgeMouseEnter'
|
|
47
|
-
| 'onEdgeMouseLeave'
|
|
46
|
+
| 'onNodeDoubleClick'
|
|
47
|
+
| 'onEdgeDoubleClick'
|
|
48
48
|
| 'fitViewOptions'
|
|
49
49
|
>,
|
|
50
50
|
// Required props
|
|
@@ -79,6 +79,7 @@ export const BaseXYFlow = <
|
|
|
79
79
|
}: BaseXYFlowProps<NodeType, EdgeType>) => {
|
|
80
80
|
const isBgWithPattern = background !== 'transparent' && background !== 'solid'
|
|
81
81
|
const isZoomTooSmall = useIsZoomTooSmall()
|
|
82
|
+
const reduceGraphics = useIsReducedGraphics()
|
|
82
83
|
return (
|
|
83
84
|
<ReactFlow<NodeType, EdgeType>
|
|
84
85
|
colorMode={colorMode}
|
|
@@ -90,7 +91,12 @@ export const BaseXYFlow = <
|
|
|
90
91
|
background === 'transparent' && css.cssTransparentBg,
|
|
91
92
|
className,
|
|
92
93
|
)}
|
|
93
|
-
|
|
94
|
+
{...isZoomTooSmall && {
|
|
95
|
+
['data-likec4-zoom-small']: true,
|
|
96
|
+
}}
|
|
97
|
+
{...reduceGraphics && {
|
|
98
|
+
['data-likec4-reduced-graphics']: true,
|
|
99
|
+
}}
|
|
94
100
|
zoomOnPinch={zoomable}
|
|
95
101
|
zoomOnScroll={!pannable && zoomable}
|
|
96
102
|
{...(!zoomable && {
|
|
@@ -115,8 +121,7 @@ export const BaseXYFlow = <
|
|
|
115
121
|
{...(!pannable && {
|
|
116
122
|
selectionKeyCode: null,
|
|
117
123
|
})}
|
|
118
|
-
|
|
119
|
-
//onlyRenderVisibleElements={nodes.length > 75}
|
|
124
|
+
onlyRenderVisibleElements={reduceGraphics}
|
|
120
125
|
elementsSelectable={nodesSelectable}
|
|
121
126
|
nodesFocusable={nodesDraggable || nodesSelectable}
|
|
122
127
|
edgesFocusable={false}
|
|
@@ -202,7 +207,7 @@ const ViewportResizeHanlder = ({
|
|
|
202
207
|
}: {
|
|
203
208
|
onViewportResize: () => void
|
|
204
209
|
}) => {
|
|
205
|
-
const { width, height } = useStore(selectDimensions)
|
|
210
|
+
const { width, height } = useStore(selectDimensions, shallowEqual)
|
|
206
211
|
useUpdateEffect(onViewportResize, [width, height])
|
|
207
212
|
|
|
208
213
|
return <></>
|
package/src/base/const.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { style } from '@vanilla-extract/css'
|
|
2
|
+
import { reactFlowReducedGraphics } from '../../../LikeC4Diagram.css'
|
|
2
3
|
import { transitions, vars } from '../../../theme-vars'
|
|
3
4
|
|
|
4
5
|
export const container = style({
|
|
@@ -45,11 +46,16 @@ export const actionIcon = style({
|
|
|
45
46
|
},
|
|
46
47
|
[`:where([data-hovered='true']) &`]: {
|
|
47
48
|
opacity: 1,
|
|
49
|
+
transitionDelay: '100ms',
|
|
50
|
+
transitionDuration: '300ms',
|
|
48
51
|
// boxShadow: '1px 1px 3px 0px rgba(0, 0, 0, 0.15)',
|
|
49
52
|
// color: `var(--ai-color-hover)`,
|
|
50
53
|
// vars: {
|
|
51
54
|
// '--ai-bg': `var(--ai-bg-hover)`,
|
|
52
55
|
// },
|
|
53
56
|
},
|
|
57
|
+
[`${reactFlowReducedGraphics} &`]: {
|
|
58
|
+
boxShadow: 'none',
|
|
59
|
+
},
|
|
54
60
|
},
|
|
55
61
|
})
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createVar, globalStyle, style } from '@vanilla-extract/css'
|
|
2
|
+
import { reactFlowReducedGraphics } from '../../../LikeC4Diagram.css'
|
|
2
3
|
import { vars } from '../../../theme-vars'
|
|
3
4
|
|
|
4
5
|
export const container = style({
|
|
@@ -47,6 +48,9 @@ export const actionIcon = style({
|
|
|
47
48
|
'--ai-bg': `color-mix(in srgb , ${vars.element.fill}, transparent 99%)`,
|
|
48
49
|
},
|
|
49
50
|
},
|
|
51
|
+
[`${reactFlowReducedGraphics} &`]: {
|
|
52
|
+
boxShadow: 'none',
|
|
53
|
+
},
|
|
50
54
|
},
|
|
51
55
|
})
|
|
52
56
|
|