@likec4/diagram 1.4.0 → 1.6.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/LICENSE +1 -1
- package/dist/LikeC4Diagram.css.d.ts +0 -1
- package/dist/LikeC4Diagram.d.ts +1 -1
- package/dist/LikeC4Diagram.props.d.ts +19 -4
- package/dist/StaticLikeC4Diagram.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +3937 -3646
- package/dist/state/DiagramContext.d.ts +95 -4159
- package/dist/state/diagramStore.d.ts +100 -4164
- package/dist/state/hooks.d.ts +4 -0
- package/dist/state/index.d.ts +1 -2
- package/dist/style.css +281 -191
- package/dist/ui/options/EdgeOptions.d.ts +19 -0
- package/dist/ui/options/NodeOptions.d.ts +2 -2
- package/dist/xyflow/EdgeMarkers.d.ts +11 -0
- package/dist/xyflow/XYFlowEvents.d.ts +8 -5
- package/dist/xyflow/const.d.ts +3 -3
- package/dist/xyflow/diagram-to-xyflow.d.ts +1 -1
- package/dist/xyflow/edges/RelationshipEdge.d.ts +3 -2
- package/dist/xyflow/edges/edges.css.d.ts +1 -0
- package/dist/xyflow/edges/utils.d.ts +1 -0
- package/dist/xyflow/hooks/index.d.ts +3 -2
- package/dist/xyflow/hooks/useXYFlow.d.ts +13 -12
- package/dist/xyflow/nodes/compound/CompoundNode.css.d.ts +1 -0
- package/dist/xyflow/nodes/element/ElementShapeSvg.d.ts +5 -0
- package/dist/xyflow/nodes/element/element.css.d.ts +5 -2
- package/dist/xyflow/types.d.ts +2 -32
- package/package.json +19 -19
- package/src/LikeC4Diagram.css.ts +0 -8
- package/src/LikeC4Diagram.props.ts +23 -5
- package/src/LikeC4Diagram.tsx +18 -21
- package/src/StaticLikeC4Diagram.tsx +1 -0
- package/src/globals.css.ts +10 -3
- package/src/index.ts +1 -1
- package/src/mantine.css.ts +2 -2
- package/src/state/DiagramContext.tsx +44 -13
- package/src/state/diagramStore.ts +72 -53
- package/src/state/hooks.ts +5 -0
- package/src/state/index.ts +1 -2
- package/src/ui/BackwardForwardPanel.css.ts +9 -9
- package/src/ui/DiagramTitlePanel.css.ts +7 -2
- package/src/ui/DiagramTitlePanel.tsx +5 -3
- package/src/ui/DynamicViewWalkthrough.tsx +6 -4
- package/src/ui/OptionsPanel.tsx +37 -27
- package/src/ui/options/EdgeOptions.tsx +157 -0
- package/src/ui/options/NodeOptions.tsx +37 -29
- package/src/xyflow/EdgeMarkers.tsx +148 -0
- package/src/xyflow/FitviewOnDiagramChange.tsx +9 -5
- package/src/xyflow/SelectEdgesOnNodeFocus.tsx +59 -55
- package/src/xyflow/SyncWithDiagram.tsx +13 -20
- package/src/xyflow/XYFlow.tsx +3 -4
- package/src/xyflow/XYFlowEvents.tsx +48 -33
- package/src/xyflow/const.ts +3 -3
- package/src/xyflow/diagram-to-xyflow.ts +17 -41
- package/src/xyflow/edges/RelationshipEdge.tsx +208 -119
- package/src/xyflow/edges/edges.css.ts +40 -26
- package/src/xyflow/edges/utils.ts +10 -0
- package/src/xyflow/hooks/index.ts +4 -2
- package/src/xyflow/hooks/useLayoutConstraints.ts +3 -4
- package/src/xyflow/hooks/useXYFlow.ts +28 -1
- package/src/xyflow/nodes/compound/CompoundNode.css.ts +34 -24
- package/src/xyflow/nodes/compound/CompoundNode.tsx +52 -53
- package/src/xyflow/nodes/element/ElementLink.tsx +12 -16
- package/src/xyflow/nodes/element/ElementNode.tsx +118 -114
- package/src/xyflow/nodes/element/ElementShapeSvg.tsx +2 -2
- package/src/xyflow/nodes/element/element.css.ts +147 -55
- package/src/xyflow/nodes/shared/NavigateToBtn.tsx +1 -1
- package/src/xyflow/types.ts +2 -4
- package/src/xyflow/utils.ts +3 -6
- package/dist/KeepAspectRatioContainer.d.ts +0 -9
- package/dist/icons/Clipboard.d.ts +0 -3
- package/dist/icons/Link.d.ts +0 -3
- package/dist/icons/ZoomIn.d.ts +0 -3
- package/dist/icons/index.d.ts +0 -3
- package/dist/mantine/theme.d.ts +0 -3
- package/dist/state/WhenInitialized.d.ts +0 -3
- package/dist/ui/Camera.d.ts +0 -3
- package/dist/xyflow/nodes/element/ElementIcon.d.ts +0 -10
- package/src/KeepAspectRatioContainer.tsx +0 -36
- package/src/icons/Clipboard.tsx +0 -24
- package/src/icons/Link.tsx +0 -23
- package/src/icons/ZoomIn.tsx +0 -25
- package/src/icons/index.ts +0 -3
- package/src/mantine/theme.ts +0 -20
- package/src/state/WhenInitialized.tsx +0 -24
- package/src/ui/Camera.tsx +0 -174
- package/src/xyflow/nodes/element/ElementIcon.tsx +0 -44
|
@@ -1,31 +1,51 @@
|
|
|
1
|
+
import { shallowEqual } from 'fast-equals'
|
|
1
2
|
import { createContext, type PropsWithChildren, useEffect, useRef } from 'react'
|
|
2
|
-
import { hasSubObject,
|
|
3
|
+
import { hasSubObject, isNonNullish, pickBy } from 'remeda'
|
|
3
4
|
import { useUpdateEffect } from '../hooks'
|
|
4
|
-
import { useXYFlow } from '../xyflow/hooks'
|
|
5
|
+
import { useXYFlow, useXYStoreApi } from '../xyflow/hooks/useXYFlow'
|
|
5
6
|
import { createDiagramStore, type DiagramInitialState } from './diagramStore'
|
|
6
7
|
|
|
7
8
|
export type DiagramZustandStore = ReturnType<typeof createDiagramStore>
|
|
8
9
|
export const DiagramContext = createContext<DiagramZustandStore | null>(null)
|
|
9
10
|
|
|
10
|
-
export
|
|
11
|
+
export type DiagramContextProviderProps = PropsWithChildren<
|
|
12
|
+
DiagramInitialState & {
|
|
13
|
+
className: string
|
|
14
|
+
keepAspectRatio: boolean
|
|
15
|
+
}
|
|
16
|
+
>
|
|
17
|
+
|
|
18
|
+
export function DiagramContextProvider({
|
|
19
|
+
children,
|
|
20
|
+
view,
|
|
21
|
+
className,
|
|
22
|
+
keepAspectRatio,
|
|
23
|
+
...props
|
|
24
|
+
}: DiagramContextProviderProps) {
|
|
25
|
+
const containerRef = useRef<HTMLDivElement>(null)
|
|
26
|
+
const xystore = useXYStoreApi()
|
|
11
27
|
const xyflow = useXYFlow()
|
|
12
28
|
const store = useRef<DiagramZustandStore>()
|
|
13
29
|
|
|
14
30
|
if (!store.current) {
|
|
15
31
|
store.current = createDiagramStore({
|
|
32
|
+
xystore,
|
|
16
33
|
xyflow,
|
|
17
34
|
view,
|
|
35
|
+
getContainer: () => containerRef.current,
|
|
18
36
|
...props
|
|
19
37
|
})
|
|
20
38
|
}
|
|
21
39
|
|
|
22
40
|
useEffect(
|
|
23
41
|
() => {
|
|
24
|
-
if (store.current
|
|
25
|
-
|
|
42
|
+
if (!store.current) return
|
|
43
|
+
const state = store.current.getState()
|
|
44
|
+
if (state.xyflow !== xyflow || state.xystore !== xystore) {
|
|
45
|
+
store.current.setState({ xyflow, xystore }, false, 'update xyflow and xystore')
|
|
26
46
|
}
|
|
27
47
|
},
|
|
28
|
-
[xyflow]
|
|
48
|
+
[xyflow, xystore]
|
|
29
49
|
)
|
|
30
50
|
|
|
31
51
|
useUpdateEffect(
|
|
@@ -35,16 +55,27 @@ export function DiagramContextProvider({ children, view, ...props }: PropsWithCh
|
|
|
35
55
|
if (state.view !== view) {
|
|
36
56
|
state.updateView(view)
|
|
37
57
|
}
|
|
38
|
-
|
|
39
|
-
|
|
58
|
+
const newProps = pickBy(props, isNonNullish)
|
|
59
|
+
if (!hasSubObject(state, newProps)) {
|
|
60
|
+
store.current.setState(newProps, false, 'update incoming props')
|
|
40
61
|
}
|
|
41
62
|
},
|
|
42
|
-
[view, props]
|
|
63
|
+
[view, props],
|
|
64
|
+
shallowEqual
|
|
43
65
|
)
|
|
44
|
-
|
|
45
66
|
return (
|
|
46
|
-
<
|
|
47
|
-
{
|
|
48
|
-
|
|
67
|
+
<div
|
|
68
|
+
ref={containerRef}
|
|
69
|
+
className={className}
|
|
70
|
+
{...(keepAspectRatio && {
|
|
71
|
+
style: {
|
|
72
|
+
aspectRatio: `${Math.ceil(view.width)}/${Math.ceil(view.height)}`,
|
|
73
|
+
maxHeight: Math.ceil(view.height)
|
|
74
|
+
}
|
|
75
|
+
})}>
|
|
76
|
+
<DiagramContext.Provider value={store.current}>
|
|
77
|
+
{children}
|
|
78
|
+
</DiagramContext.Provider>
|
|
79
|
+
</div>
|
|
49
80
|
)
|
|
50
81
|
}
|
|
@@ -10,13 +10,19 @@ import type { Exact, Except, RequiredKeysOf, Simplify } from 'type-fest'
|
|
|
10
10
|
import { devtools, subscribeWithSelector } from 'zustand/middleware'
|
|
11
11
|
import { shallow } from 'zustand/shallow'
|
|
12
12
|
import { createWithEqualityFn } from 'zustand/traditional'
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
Changes,
|
|
15
|
+
DiagramNodeWithNavigate,
|
|
16
|
+
ElementIconRenderer,
|
|
17
|
+
LikeC4DiagramEventHandlers
|
|
18
|
+
} from '../LikeC4Diagram.props'
|
|
14
19
|
import { MinZoom } from '../xyflow/const'
|
|
15
20
|
import type { XYStoreApi } from '../xyflow/hooks'
|
|
16
21
|
import type { XYFlowEdge, XYFlowInstance, XYFlowNode } from '../xyflow/types'
|
|
17
22
|
import { bezierControlPoints, isSamePoint } from '../xyflow/utils'
|
|
18
23
|
|
|
19
24
|
export type DiagramStore = {
|
|
25
|
+
readonly storeDevId: string
|
|
20
26
|
// Incoming props
|
|
21
27
|
view: DiagramView
|
|
22
28
|
readonly: boolean
|
|
@@ -28,11 +34,15 @@ export type DiagramStore = {
|
|
|
28
34
|
nodesDraggable: boolean
|
|
29
35
|
nodesSelectable: boolean
|
|
30
36
|
experimentalEdgeEditing: boolean
|
|
37
|
+
renderIcon: ElementIconRenderer | null
|
|
38
|
+
// Diagram Container, see DiagramContainer.tsx
|
|
39
|
+
getContainer: () => HTMLDivElement | null
|
|
31
40
|
|
|
32
41
|
// Internal state
|
|
33
42
|
viewSyncDebounceTimeout: number | null
|
|
34
43
|
|
|
35
44
|
xyflow: XYFlowInstance
|
|
45
|
+
xystore: XYStoreApi
|
|
36
46
|
initialized: boolean
|
|
37
47
|
xyflowSynced: boolean
|
|
38
48
|
|
|
@@ -72,6 +82,7 @@ export type DiagramInitialState = // Required properties
|
|
|
72
82
|
& Pick<
|
|
73
83
|
DiagramStore,
|
|
74
84
|
| 'view'
|
|
85
|
+
| 'renderIcon'
|
|
75
86
|
| 'readonly'
|
|
76
87
|
| 'showElementLinks'
|
|
77
88
|
| 'fitViewEnabled'
|
|
@@ -82,13 +93,6 @@ export type DiagramInitialState = // Required properties
|
|
|
82
93
|
| 'nodesSelectable'
|
|
83
94
|
| 'experimentalEdgeEditing'
|
|
84
95
|
>
|
|
85
|
-
// Optional properties
|
|
86
|
-
// & Partial<
|
|
87
|
-
// Pick<
|
|
88
|
-
// DiagramStore,
|
|
89
|
-
// 'colorScheme'
|
|
90
|
-
// >
|
|
91
|
-
// >
|
|
92
96
|
& LikeC4DiagramEventHandlers
|
|
93
97
|
|
|
94
98
|
interface DiagramStoreActions {
|
|
@@ -110,9 +114,9 @@ interface DiagramStoreActions {
|
|
|
110
114
|
triggerChangeElementStyle: (change: Changes.ChangeElementStyle) => void
|
|
111
115
|
|
|
112
116
|
cancelSaveManualLayout: () => void
|
|
113
|
-
triggerSaveManualLayout: (
|
|
117
|
+
triggerSaveManualLayout: () => void
|
|
114
118
|
triggerOnNavigateTo: (xynodeId: string, event: ReactMouseEvent) => void
|
|
115
|
-
fitDiagram: (
|
|
119
|
+
fitDiagram: (duration?: number) => void
|
|
116
120
|
|
|
117
121
|
nextDynamicStep: (increment?: number) => void
|
|
118
122
|
stopDynamicView: () => void
|
|
@@ -120,7 +124,10 @@ interface DiagramStoreActions {
|
|
|
120
124
|
|
|
121
125
|
export type DiagramState = Simplify<DiagramStore & DiagramStoreActions>
|
|
122
126
|
|
|
123
|
-
const DEFAULT_PROPS: Except<
|
|
127
|
+
const DEFAULT_PROPS: Except<
|
|
128
|
+
DiagramStore,
|
|
129
|
+
RequiredKeysOf<DiagramInitialState> | 'xystore' | 'xyflow' | 'getContainer' | 'storeDevId'
|
|
130
|
+
> = {
|
|
124
131
|
viewSyncDebounceTimeout: null,
|
|
125
132
|
initialized: false,
|
|
126
133
|
xyflowSynced: false,
|
|
@@ -145,15 +152,16 @@ const DEFAULT_PROPS: Except<DiagramStore, RequiredKeysOf<DiagramInitialState> |
|
|
|
145
152
|
onCanvasDblClick: null
|
|
146
153
|
}
|
|
147
154
|
|
|
148
|
-
type CreateDiagramStore = DiagramInitialState & Pick<DiagramStore, 'xyflow'>
|
|
155
|
+
export type CreateDiagramStore = DiagramInitialState & Pick<DiagramStore, 'xystore' | 'xyflow' | 'getContainer'>
|
|
149
156
|
|
|
150
157
|
const noReplace = false
|
|
151
158
|
|
|
152
|
-
let
|
|
159
|
+
let StoreDevId = 1
|
|
153
160
|
|
|
154
161
|
const EmptyStringSet: ReadonlySet<string> = new StringSet()
|
|
155
162
|
|
|
156
163
|
export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props: T) {
|
|
164
|
+
const storeDevId = 'DiagramStore' + String(StoreDevId++).padStart(2, '0')
|
|
157
165
|
return createWithEqualityFn<
|
|
158
166
|
DiagramState,
|
|
159
167
|
[
|
|
@@ -164,9 +172,9 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
164
172
|
subscribeWithSelector(
|
|
165
173
|
devtools(
|
|
166
174
|
(set, get) => ({
|
|
175
|
+
storeDevId,
|
|
167
176
|
...DEFAULT_PROPS,
|
|
168
177
|
...(props as CreateDiagramStore),
|
|
169
|
-
|
|
170
178
|
isDynamicView: () => {
|
|
171
179
|
return get().view?.__ === 'dynamic'
|
|
172
180
|
},
|
|
@@ -188,16 +196,16 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
188
196
|
hoveredNodeId
|
|
189
197
|
} = get()
|
|
190
198
|
|
|
191
|
-
if (viewSyncDebounceTimeout !== null) {
|
|
192
|
-
clearTimeout(viewSyncDebounceTimeout)
|
|
193
|
-
viewSyncDebounceTimeout = null
|
|
194
|
-
}
|
|
195
|
-
|
|
196
199
|
if (shallowEqual(current, nextView)) {
|
|
197
200
|
DEV && console.debug('store: skip updateView')
|
|
198
201
|
return
|
|
199
202
|
}
|
|
200
203
|
|
|
204
|
+
if (viewSyncDebounceTimeout !== null) {
|
|
205
|
+
clearTimeout(viewSyncDebounceTimeout)
|
|
206
|
+
viewSyncDebounceTimeout = null
|
|
207
|
+
}
|
|
208
|
+
|
|
201
209
|
const isSameView = current.id === nextView.id
|
|
202
210
|
|
|
203
211
|
if (isSameView) {
|
|
@@ -275,7 +283,7 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
275
283
|
dimmed
|
|
276
284
|
},
|
|
277
285
|
noReplace,
|
|
278
|
-
'update-view'
|
|
286
|
+
isSameView ? 'update-view [same]' : 'update-view [another]'
|
|
279
287
|
)
|
|
280
288
|
},
|
|
281
289
|
|
|
@@ -374,7 +382,7 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
374
382
|
return origin
|
|
375
383
|
}
|
|
376
384
|
let element = origin
|
|
377
|
-
for (const [key, value] of entries
|
|
385
|
+
for (const [key, value] of entries(change.style)) {
|
|
378
386
|
switch (key) {
|
|
379
387
|
case 'shape':
|
|
380
388
|
if (value !== element.shape) {
|
|
@@ -443,7 +451,7 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
443
451
|
}
|
|
444
452
|
},
|
|
445
453
|
|
|
446
|
-
triggerSaveManualLayout: (
|
|
454
|
+
triggerSaveManualLayout: () => {
|
|
447
455
|
let { viewSyncDebounceTimeout: debounced, onChange } = get()
|
|
448
456
|
if (!onChange) {
|
|
449
457
|
DEV && console.debug('ignore triggerSaveManualLayout, as no onChange handler is set')
|
|
@@ -453,8 +461,8 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
453
461
|
clearTimeout(debounced)
|
|
454
462
|
}
|
|
455
463
|
debounced = setTimeout(() => {
|
|
464
|
+
const { xyflow, onChange, xystore } = get()
|
|
456
465
|
const { nodeLookup } = xystore.getState()
|
|
457
|
-
const { xyflow, onChange } = get()
|
|
458
466
|
set({ viewSyncDebounceTimeout: null })
|
|
459
467
|
|
|
460
468
|
const movedNodes = new StringSet()
|
|
@@ -500,7 +508,7 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
500
508
|
}
|
|
501
509
|
|
|
502
510
|
onChange?.({ change })
|
|
503
|
-
}, 2000)
|
|
511
|
+
}, 2000) as any as number // explicit typecast to number to suppress TS error in astro build
|
|
504
512
|
set(
|
|
505
513
|
{
|
|
506
514
|
viewSyncDebounceTimeout: debounced
|
|
@@ -513,8 +521,9 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
513
521
|
triggerOnNavigateTo: (xynodeId, event) => {
|
|
514
522
|
const { xyflow, view } = get()
|
|
515
523
|
const xynode = xyflow.getInternalNode(xynodeId)
|
|
516
|
-
|
|
517
|
-
|
|
524
|
+
invariant(!!xynode, `node not found: ${xynodeId}`)
|
|
525
|
+
const element = xynode.data.element as DiagramNodeWithNavigate
|
|
526
|
+
invariant(element.navigateTo, `node is not navigable: ${xynodeId}`)
|
|
518
527
|
set(
|
|
519
528
|
{
|
|
520
529
|
lastClickedNodeId: xynodeId,
|
|
@@ -540,20 +549,23 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
540
549
|
)
|
|
541
550
|
},
|
|
542
551
|
|
|
543
|
-
fitDiagram: (
|
|
544
|
-
const {
|
|
545
|
-
const {
|
|
552
|
+
fitDiagram: (duration = 500) => {
|
|
553
|
+
const { fitViewPadding, view, focusedNodeId, activeDynamicViewStep, xystore } = get()
|
|
554
|
+
const { width, height, panZoom, transform } = xystore.getState()
|
|
555
|
+
|
|
546
556
|
const bounds = {
|
|
547
557
|
x: 0,
|
|
548
558
|
y: 0,
|
|
549
559
|
width: view.width,
|
|
550
560
|
height: view.height
|
|
551
561
|
}
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
562
|
+
const maxZoom = Math.max(1, transform[2])
|
|
563
|
+
const viewport = getViewportForBounds(bounds, width, height, MinZoom, maxZoom, fitViewPadding)
|
|
564
|
+
panZoom?.setViewport(viewport, { duration })
|
|
565
|
+
if ((focusedNodeId ?? activeDynamicViewStep) !== null) {
|
|
555
566
|
set(
|
|
556
567
|
{
|
|
568
|
+
activeDynamicViewStep: null,
|
|
557
569
|
focusedNodeId: null,
|
|
558
570
|
dimmed: EmptyStringSet
|
|
559
571
|
},
|
|
@@ -564,7 +576,7 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
564
576
|
},
|
|
565
577
|
|
|
566
578
|
nextDynamicStep: (increment = 1) => {
|
|
567
|
-
const { view, activeDynamicViewStep, xyflow, fitViewPadding } = get()
|
|
579
|
+
const { view, activeDynamicViewStep, xyflow, xystore, fitViewPadding } = get()
|
|
568
580
|
invariant(view.__ === 'dynamic', 'view is not dynamic')
|
|
569
581
|
const nextStep = (activeDynamicViewStep ?? 0) + increment
|
|
570
582
|
if (nextStep <= 0 || nextStep > view.edges.length) {
|
|
@@ -589,36 +601,43 @@ export function createDiagramStore<T extends Exact<CreateDiagramStore, T>>(props
|
|
|
589
601
|
}
|
|
590
602
|
dimmed.add(n.id)
|
|
591
603
|
}
|
|
592
|
-
|
|
604
|
+
const { fitView, transform } = xystore.getState()
|
|
605
|
+
fitView({
|
|
593
606
|
duration: 400,
|
|
594
|
-
|
|
607
|
+
includeHiddenNodes: true,
|
|
608
|
+
maxZoom: Math.max(1, transform[2]),
|
|
595
609
|
minZoom: MinZoom,
|
|
596
|
-
padding: fitViewPadding,
|
|
610
|
+
padding: Math.max(fitViewPadding, 0.2),
|
|
597
611
|
nodes: selected
|
|
598
612
|
})
|
|
599
|
-
set(
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
613
|
+
set(
|
|
614
|
+
{
|
|
615
|
+
focusedNodeId: null,
|
|
616
|
+
activeDynamicViewStep: nextStep,
|
|
617
|
+
dimmed
|
|
618
|
+
},
|
|
619
|
+
noReplace,
|
|
620
|
+
'nextDynamicStep'
|
|
621
|
+
)
|
|
604
622
|
},
|
|
605
623
|
|
|
606
624
|
stopDynamicView: () => {
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
625
|
+
if (get().activeDynamicViewStep !== null) {
|
|
626
|
+
set(
|
|
627
|
+
{
|
|
628
|
+
activeDynamicViewStep: null,
|
|
629
|
+
focusedNodeId: null,
|
|
630
|
+
dimmed: EmptyStringSet
|
|
631
|
+
},
|
|
632
|
+
noReplace,
|
|
633
|
+
'stopDynamicView'
|
|
634
|
+
)
|
|
635
|
+
get().fitDiagram()
|
|
636
|
+
}
|
|
618
637
|
}
|
|
619
638
|
}),
|
|
620
639
|
{
|
|
621
|
-
name:
|
|
640
|
+
name: `${storeDevId} - ${props.view.id}`,
|
|
622
641
|
enabled: DEV
|
|
623
642
|
}
|
|
624
643
|
)
|
package/src/state/index.ts
CHANGED
|
@@ -3,20 +3,20 @@ import { mantine } from '../mantine.css'
|
|
|
3
3
|
import { vars } from '../theme.css'
|
|
4
4
|
|
|
5
5
|
export const panel = style({
|
|
6
|
-
top: fallbackVar(vars.navigationPanel.top, '
|
|
6
|
+
top: fallbackVar(vars.navigationPanel.top, '0.8rem'),
|
|
7
7
|
left: fallbackVar(vars.navigationPanel.left, '1rem'),
|
|
8
8
|
margin: 0
|
|
9
9
|
})
|
|
10
10
|
|
|
11
11
|
globalStyle(`${panel} .mantine-ActionIcon-root`, {
|
|
12
12
|
vars: {
|
|
13
|
-
['--ai-size']: 'var(--ai-size-
|
|
14
|
-
},
|
|
15
|
-
'@media': {
|
|
16
|
-
[mantine.largerThan('md')]: {
|
|
17
|
-
vars: {
|
|
18
|
-
['--ai-size']: 'var(--ai-size-lg)'
|
|
19
|
-
}
|
|
20
|
-
}
|
|
13
|
+
['--ai-size']: 'var(--ai-size-lg)'
|
|
21
14
|
}
|
|
15
|
+
// '@media': {
|
|
16
|
+
// [mantine.largerThan('lg')]: {
|
|
17
|
+
// vars: {
|
|
18
|
+
// ['--ai-size']: 'var(--ai-size-xl)'
|
|
19
|
+
// }
|
|
20
|
+
// }
|
|
21
|
+
// }
|
|
22
22
|
})
|
|
@@ -11,14 +11,19 @@ export const container = style({
|
|
|
11
11
|
|
|
12
12
|
export const card = style({
|
|
13
13
|
cursor: 'default',
|
|
14
|
-
minWidth:
|
|
14
|
+
minWidth: 200,
|
|
15
15
|
maxWidth: 'calc(100vw - 16px)',
|
|
16
16
|
backgroundColor: `color-mix(in srgb, ${mantine.colors.body}, transparent 20%)`,
|
|
17
17
|
WebkitBackdropFilter: fallbackVar(vars.safariAnimationHook, 'blur(8px)'),
|
|
18
18
|
backdropFilter: fallbackVar(vars.safariAnimationHook, 'blur(8px)'),
|
|
19
19
|
'@media': {
|
|
20
|
+
[mantine.largerThan('sm')]: {
|
|
21
|
+
minWidth: 300,
|
|
22
|
+
maxWidth: '90vw'
|
|
23
|
+
},
|
|
20
24
|
[mantine.largerThan('md')]: {
|
|
21
|
-
|
|
25
|
+
minWidth: 400,
|
|
26
|
+
maxWidth: '70vw'
|
|
22
27
|
}
|
|
23
28
|
},
|
|
24
29
|
selectors: {
|
|
@@ -3,7 +3,7 @@ import { useLocalStorage } from '@mantine/hooks'
|
|
|
3
3
|
import { IconMenu } from '@tabler/icons-react'
|
|
4
4
|
import clsx from 'clsx'
|
|
5
5
|
import { AnimatePresence, m } from 'framer-motion'
|
|
6
|
-
import { type DiagramState, useDiagramState } from '../state'
|
|
6
|
+
import { type DiagramState, useDiagramState } from '../state/hooks'
|
|
7
7
|
import * as css from './DiagramTitlePanel.css'
|
|
8
8
|
|
|
9
9
|
const selector = (s: DiagramState) => ({
|
|
@@ -39,16 +39,18 @@ export default function DiagramTitlePanel() {
|
|
|
39
39
|
<Card
|
|
40
40
|
radius="sm"
|
|
41
41
|
className={css.card}
|
|
42
|
+
withBorder
|
|
42
43
|
p={'md'}
|
|
44
|
+
pb={isCollapsed ? 'sm' : 'md'}
|
|
43
45
|
onDoubleClick={e => e.stopPropagation()}>
|
|
44
|
-
<CardSection mb={
|
|
46
|
+
<CardSection mb={isCollapsed ? 10 : 'sm'}>
|
|
45
47
|
<Button
|
|
46
48
|
fullWidth
|
|
47
49
|
size="xs"
|
|
48
50
|
h={'sm'}
|
|
49
51
|
py={2}
|
|
50
52
|
radius={'0'}
|
|
51
|
-
variant=
|
|
53
|
+
variant={isCollapsed ? 'light' : 'subtle'}
|
|
52
54
|
color="gray"
|
|
53
55
|
onClick={toggle}
|
|
54
56
|
>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Box, Button } from '@mantine/core'
|
|
1
|
+
import { Box, Button, ButtonGroup } from '@mantine/core'
|
|
2
2
|
import { useHotkeys } from '@mantine/hooks'
|
|
3
3
|
import {
|
|
4
4
|
IconPlayerPlayFilled,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from '@tabler/icons-react'
|
|
9
9
|
import clsx from 'clsx'
|
|
10
10
|
import { isNullish, isNumber } from 'remeda'
|
|
11
|
-
import { useDiagramState } from '../state/
|
|
11
|
+
import { useDiagramState } from '../state/hooks'
|
|
12
12
|
import { useXYStore } from '../xyflow/hooks/useXYFlow'
|
|
13
13
|
import * as css from './DynamicViewWalkthrough.css'
|
|
14
14
|
|
|
@@ -58,6 +58,8 @@ export function DynamicViewWalkthrough() {
|
|
|
58
58
|
return (
|
|
59
59
|
<Box
|
|
60
60
|
className={clsx('react-flow__panel', css.container)}
|
|
61
|
+
onClick={(e) => e.stopPropagation()}
|
|
62
|
+
onDoubleClick={(e) => e.stopPropagation()}
|
|
61
63
|
>
|
|
62
64
|
{isNullish(activeDynamicViewStep)
|
|
63
65
|
&& (
|
|
@@ -71,7 +73,7 @@ export function DynamicViewWalkthrough() {
|
|
|
71
73
|
</Button>
|
|
72
74
|
)}
|
|
73
75
|
{isNumber(activeDynamicViewStep) && (
|
|
74
|
-
<
|
|
76
|
+
<ButtonGroup className={css.buttons}>
|
|
75
77
|
<Button
|
|
76
78
|
{...buttonProps}
|
|
77
79
|
pl={'lg'}
|
|
@@ -98,7 +100,7 @@ export function DynamicViewWalkthrough() {
|
|
|
98
100
|
<IconPlayerSkipForwardFilled />
|
|
99
101
|
</Button>
|
|
100
102
|
)}
|
|
101
|
-
</
|
|
103
|
+
</ButtonGroup>
|
|
102
104
|
)}
|
|
103
105
|
</Box>
|
|
104
106
|
)
|
package/src/ui/OptionsPanel.tsx
CHANGED
|
@@ -1,38 +1,42 @@
|
|
|
1
1
|
import { Card } from '@mantine/core'
|
|
2
|
+
import { useOnSelectionChange } from '@xyflow/react'
|
|
2
3
|
import clsx from 'clsx'
|
|
3
|
-
import {
|
|
4
|
-
import { shallowEqual } from 'fast-equals'
|
|
4
|
+
import { deepEqual as eq } from 'fast-equals'
|
|
5
5
|
import { AnimatePresence, m } from 'framer-motion'
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import useTilg from 'tilg'
|
|
9
|
-
import { useDiagramState } from '../state'
|
|
10
|
-
import { useXYStore } from '../xyflow/hooks'
|
|
6
|
+
import { useState } from 'react'
|
|
7
|
+
import { useDiagramState } from '../state/hooks'
|
|
11
8
|
import { NodeOptions } from './options/NodeOptions'
|
|
12
9
|
import * as styles from './OptionsPanel.css'
|
|
13
10
|
|
|
14
|
-
const Empty = [] as string[]
|
|
15
11
|
export default function OptionsPanel() {
|
|
16
|
-
const {
|
|
12
|
+
const { isFocused, viewId } = useDiagramState(s => ({
|
|
17
13
|
viewId: s.view.id,
|
|
18
|
-
|
|
14
|
+
isFocused: s.focusedNodeId !== null || s.activeDynamicViewStep !== null
|
|
19
15
|
}))
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
16
|
+
const [selected, setSelected] = useState<{
|
|
17
|
+
nodes: string[]
|
|
18
|
+
edges: string[]
|
|
19
|
+
}>({
|
|
20
|
+
nodes: [],
|
|
21
|
+
edges: []
|
|
22
|
+
})
|
|
23
|
+
useOnSelectionChange({
|
|
24
|
+
onChange: ({ nodes, edges }) => {
|
|
25
|
+
const newSelected = {
|
|
26
|
+
nodes: nodes.map(n => n.id).sort(),
|
|
27
|
+
edges: edges.map(e => e.id).sort()
|
|
28
|
+
}
|
|
29
|
+
if (!eq(selected, newSelected)) {
|
|
30
|
+
setSelected(newSelected)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
})
|
|
34
|
+
const { nodes } = selected
|
|
35
|
+
// const hasAnySelection = !isFocused && (nodes.length > 0 || edges.length > 0)
|
|
36
|
+
const hasAnySelection = !isFocused && nodes.length > 0
|
|
33
37
|
return (
|
|
34
38
|
<AnimatePresence mode="wait">
|
|
35
|
-
{
|
|
39
|
+
{hasAnySelection && (
|
|
36
40
|
<m.div
|
|
37
41
|
key={viewId}
|
|
38
42
|
initial={{ opacity: 0, scale: 0.85 }}
|
|
@@ -44,11 +48,17 @@ export default function OptionsPanel() {
|
|
|
44
48
|
transition={{ duration: 0.14 }}
|
|
45
49
|
className={clsx('react-flow__panel', styles.panel)}
|
|
46
50
|
style={{
|
|
47
|
-
transformOrigin: 'center right'
|
|
51
|
+
transformOrigin: 'center right',
|
|
52
|
+
maxWidth: '320px'
|
|
48
53
|
}}
|
|
49
54
|
>
|
|
50
|
-
<Card shadow="
|
|
51
|
-
<NodeOptions selectedNodeIds={
|
|
55
|
+
<Card shadow="md">
|
|
56
|
+
{nodes.length > 0 && <NodeOptions selectedNodeIds={nodes} />}
|
|
57
|
+
{
|
|
58
|
+
/* nodes.length === 0 && edges.length > 0 && (
|
|
59
|
+
<EdgeOptions selectedEdgeIds={edges} />
|
|
60
|
+
) */
|
|
61
|
+
}
|
|
52
62
|
</Card>
|
|
53
63
|
</m.div>
|
|
54
64
|
)}
|