@finos/legend-extension-dsl-diagram 8.1.195 → 8.1.196

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.
Files changed (95) hide show
  1. package/lib/components/DiagramRenderer.d.ts +39 -2
  2. package/lib/components/DiagramRenderer.d.ts.map +1 -1
  3. package/lib/components/DiagramRenderer.js +83 -61
  4. package/lib/components/DiagramRenderer.js.map +1 -1
  5. package/lib/components/DiagramViewer.d.ts +39 -0
  6. package/lib/components/DiagramViewer.d.ts.map +1 -0
  7. package/lib/components/DiagramViewer.js +170 -0
  8. package/lib/components/DiagramViewer.js.map +1 -0
  9. package/lib/components/DiagramViewerState.d.ts +44 -0
  10. package/lib/components/DiagramViewerState.d.ts.map +1 -0
  11. package/lib/components/DiagramViewerState.js +136 -0
  12. package/lib/components/DiagramViewerState.js.map +1 -0
  13. package/lib/components/index.d.ts +4 -1
  14. package/lib/components/index.d.ts.map +1 -1
  15. package/lib/components/index.js +4 -1
  16. package/lib/components/index.js.map +1 -1
  17. package/lib/graph-manager/index.d.ts +3 -0
  18. package/lib/graph-manager/index.d.ts.map +1 -1
  19. package/lib/graph-manager/index.js +3 -0
  20. package/lib/graph-manager/index.js.map +1 -1
  21. package/lib/index.css +2 -2
  22. package/lib/index.css.map +1 -1
  23. package/lib/index.d.ts +0 -1
  24. package/lib/index.d.ts.map +1 -1
  25. package/lib/index.js +0 -1
  26. package/lib/index.js.map +1 -1
  27. package/lib/package.json +1 -2
  28. package/package.json +3 -4
  29. package/src/components/DiagramRenderer.ts +221 -103
  30. package/src/components/DiagramViewer.tsx +650 -0
  31. package/src/components/DiagramViewerState.ts +171 -0
  32. package/src/components/index.ts +4 -0
  33. package/src/graph-manager/index.ts +3 -0
  34. package/src/index.ts +0 -1
  35. package/tsconfig.json +2 -12
  36. package/lib/__lib__/studio/DSL_Diagram_LegendStudioApplicationNavigationContext.d.ts +0 -19
  37. package/lib/__lib__/studio/DSL_Diagram_LegendStudioApplicationNavigationContext.d.ts.map +0 -1
  38. package/lib/__lib__/studio/DSL_Diagram_LegendStudioApplicationNavigationContext.js +0 -20
  39. package/lib/__lib__/studio/DSL_Diagram_LegendStudioApplicationNavigationContext.js.map +0 -1
  40. package/lib/__lib__/studio/DSL_Diagram_LegendStudioCodeSnippet.d.ts +0 -20
  41. package/lib/__lib__/studio/DSL_Diagram_LegendStudioCodeSnippet.d.ts.map +0 -1
  42. package/lib/__lib__/studio/DSL_Diagram_LegendStudioCodeSnippet.js +0 -81
  43. package/lib/__lib__/studio/DSL_Diagram_LegendStudioCodeSnippet.js.map +0 -1
  44. package/lib/__lib__/studio/DSL_Diagram_LegendStudioCommand.d.ts +0 -28
  45. package/lib/__lib__/studio/DSL_Diagram_LegendStudioCommand.d.ts.map +0 -1
  46. package/lib/__lib__/studio/DSL_Diagram_LegendStudioCommand.js +0 -62
  47. package/lib/__lib__/studio/DSL_Diagram_LegendStudioCommand.js.map +0 -1
  48. package/lib/__lib__/studio/DSL_Diagram_LegendStudioDocumentation.d.ts +0 -20
  49. package/lib/__lib__/studio/DSL_Diagram_LegendStudioDocumentation.d.ts.map +0 -1
  50. package/lib/__lib__/studio/DSL_Diagram_LegendStudioDocumentation.js +0 -21
  51. package/lib/__lib__/studio/DSL_Diagram_LegendStudioDocumentation.js.map +0 -1
  52. package/lib/__lib__/studio/DSL_Diagram_LegendStudioTesting.d.ts +0 -20
  53. package/lib/__lib__/studio/DSL_Diagram_LegendStudioTesting.d.ts.map +0 -1
  54. package/lib/__lib__/studio/DSL_Diagram_LegendStudioTesting.js +0 -21
  55. package/lib/__lib__/studio/DSL_Diagram_LegendStudioTesting.js.map +0 -1
  56. package/lib/components/studio/ClassDiagramPreview.d.ts +0 -22
  57. package/lib/components/studio/ClassDiagramPreview.d.ts.map +0 -1
  58. package/lib/components/studio/ClassDiagramPreview.js +0 -64
  59. package/lib/components/studio/ClassDiagramPreview.js.map +0 -1
  60. package/lib/components/studio/DSL_Diagram_LegendStudioApplicationPlugin.d.ts +0 -42
  61. package/lib/components/studio/DSL_Diagram_LegendStudioApplicationPlugin.d.ts.map +0 -1
  62. package/lib/components/studio/DSL_Diagram_LegendStudioApplicationPlugin.js +0 -216
  63. package/lib/components/studio/DSL_Diagram_LegendStudioApplicationPlugin.js.map +0 -1
  64. package/lib/components/studio/DiagramEditor.d.ts +0 -19
  65. package/lib/components/studio/DiagramEditor.d.ts.map +0 -1
  66. package/lib/components/studio/DiagramEditor.js +0 -436
  67. package/lib/components/studio/DiagramEditor.js.map +0 -1
  68. package/lib/components/studio/InheritanceDiagramRenderer.d.ts +0 -22
  69. package/lib/components/studio/InheritanceDiagramRenderer.d.ts.map +0 -1
  70. package/lib/components/studio/InheritanceDiagramRenderer.js +0 -34
  71. package/lib/components/studio/InheritanceDiagramRenderer.js.map +0 -1
  72. package/lib/components/studio/index.d.ts +0 -18
  73. package/lib/components/studio/index.d.ts.map +0 -1
  74. package/lib/components/studio/index.js +0 -18
  75. package/lib/components/studio/index.js.map +0 -1
  76. package/lib/stores/studio/DSL_Diagram_GraphModifierHelper.d.ts +0 -57
  77. package/lib/stores/studio/DSL_Diagram_GraphModifierHelper.d.ts.map +0 -1
  78. package/lib/stores/studio/DSL_Diagram_GraphModifierHelper.js +0 -94
  79. package/lib/stores/studio/DSL_Diagram_GraphModifierHelper.js.map +0 -1
  80. package/lib/stores/studio/DiagramEditorState.d.ts +0 -88
  81. package/lib/stores/studio/DiagramEditorState.d.ts.map +0 -1
  82. package/lib/stores/studio/DiagramEditorState.js +0 -341
  83. package/lib/stores/studio/DiagramEditorState.js.map +0 -1
  84. package/src/__lib__/studio/DSL_Diagram_LegendStudioApplicationNavigationContext.ts +0 -19
  85. package/src/__lib__/studio/DSL_Diagram_LegendStudioCodeSnippet.ts +0 -88
  86. package/src/__lib__/studio/DSL_Diagram_LegendStudioCommand.ts +0 -64
  87. package/src/__lib__/studio/DSL_Diagram_LegendStudioDocumentation.ts +0 -20
  88. package/src/__lib__/studio/DSL_Diagram_LegendStudioTesting.ts +0 -20
  89. package/src/components/studio/ClassDiagramPreview.tsx +0 -83
  90. package/src/components/studio/DSL_Diagram_LegendStudioApplicationPlugin.tsx +0 -313
  91. package/src/components/studio/DiagramEditor.tsx +0 -1514
  92. package/src/components/studio/InheritanceDiagramRenderer.ts +0 -50
  93. package/src/components/studio/index.ts +0 -19
  94. package/src/stores/studio/DSL_Diagram_GraphModifierHelper.ts +0 -166
  95. package/src/stores/studio/DiagramEditorState.ts +0 -487
@@ -0,0 +1,650 @@
1
+ /**
2
+ * Copyright (c) 2025-present, Goldman Sachs
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
15
+ */
16
+
17
+ import {
18
+ AnchorLinkIcon,
19
+ CaretDownIcon,
20
+ CenterFocusIcon,
21
+ CircleIcon,
22
+ CloseIcon,
23
+ ContextMenu,
24
+ CustomSelectorInput,
25
+ DescriptionIcon,
26
+ ControlledDropdownMenu,
27
+ MenuContent,
28
+ MenuContentDivider,
29
+ MenuContentItem,
30
+ MousePointerIcon,
31
+ MoveIcon,
32
+ ShapesIcon,
33
+ ThinChevronDownIcon,
34
+ ThinChevronLeftIcon,
35
+ ThinChevronRightIcon,
36
+ ThinChevronUpIcon,
37
+ ZoomInIcon,
38
+ ZoomOutIcon,
39
+ clsx,
40
+ useResizeDetector,
41
+ createFilter,
42
+ MarkdownTextViewer,
43
+ } from '@finos/legend-art';
44
+ import { observer } from 'mobx-react-lite';
45
+ import { forwardRef, useEffect, useRef } from 'react';
46
+ import type { Diagram } from '../graph/metamodel/pure/packageableElements/diagram/DSL_Diagram_Diagram.js';
47
+ import {
48
+ type DiagramAnalysisResult,
49
+ DIAGRAM_INTERACTION_MODE,
50
+ DIAGRAM_RELATIONSHIP_EDIT_MODE,
51
+ DIAGRAM_ZOOM_LEVELS,
52
+ DiagramRenderer,
53
+ } from './DiagramRenderer.js';
54
+ import type { DiagramViewerState } from './DiagramViewerState.js';
55
+ import { at } from '@finos/legend-shared';
56
+ import {
57
+ useCommands,
58
+ type GenericLegendApplicationStore,
59
+ type NavigationZone,
60
+ } from '@finos/legend-application';
61
+ import type { Class } from '@finos/legend-graph';
62
+
63
+ enum DIAGRAM_VIEWER_MODES {
64
+ DIAGRAM_VIEWER = 'diagram-viewer',
65
+ MODELS_DOCUMENTATION = 'models-documentation',
66
+ }
67
+
68
+ const DiagramCanvas = observer(
69
+ forwardRef<
70
+ HTMLDivElement,
71
+ {
72
+ diagramViewerState: DiagramViewerState;
73
+ diagram: Diagram;
74
+ actions: {
75
+ onQueryClass?: ((_class: Class) => void) | undefined;
76
+ onViewClassDocumentation: (classPath: string) => void;
77
+ hasClassDocumentation: (classPath: string) => boolean;
78
+ onGenerateAnchorForActivity: (activity: string) => string;
79
+ onChangeZone: (zone: NavigationZone, force?: boolean) => void;
80
+ };
81
+ }
82
+ >(function DiagramCanvas(props, _ref) {
83
+ const { diagramViewerState, diagram, actions } = props;
84
+ const {
85
+ onQueryClass,
86
+ onViewClassDocumentation,
87
+ hasClassDocumentation,
88
+ onGenerateAnchorForActivity,
89
+ onChangeZone,
90
+ } = actions;
91
+ const ref = _ref as React.RefObject<HTMLDivElement>;
92
+ const descriptionText = diagramViewerState.currentDiagram?.description;
93
+
94
+ const { width, height } = useResizeDetector<HTMLDivElement>({
95
+ refreshMode: 'debounce',
96
+ refreshRate: 50,
97
+ targetRef: ref,
98
+ });
99
+
100
+ useEffect(() => {
101
+ diagramViewerState.setExpandDescription(false);
102
+ }, [diagramViewerState, diagramViewerState.currentDiagram]);
103
+
104
+ useEffect(() => {
105
+ const renderer = new DiagramRenderer(ref.current, diagram);
106
+ diagramViewerState.setDiagramRenderer(renderer);
107
+ diagramViewerState.setupDiagramRenderer();
108
+ renderer.render({ initial: true });
109
+ }, [ref, diagramViewerState, diagram]);
110
+
111
+ useEffect(() => {
112
+ if (diagramViewerState.isDiagramRendererInitialized) {
113
+ diagramViewerState.diagramRenderer.refresh();
114
+ }
115
+ }, [diagramViewerState, width, height]);
116
+
117
+ const queryClass = (): void => {
118
+ if (onQueryClass && diagramViewerState.contextMenuClassView) {
119
+ onQueryClass(diagramViewerState.contextMenuClassView.class.value);
120
+ }
121
+ };
122
+
123
+ const viewClassDocumentation = (): void => {
124
+ if (
125
+ diagramViewerState.contextMenuClassView &&
126
+ hasClassDocumentation(
127
+ diagramViewerState.contextMenuClassView.class.value.path,
128
+ )
129
+ ) {
130
+ onViewClassDocumentation(
131
+ diagramViewerState.contextMenuClassView.class.value.path,
132
+ );
133
+ onChangeZone(
134
+ onGenerateAnchorForActivity(
135
+ DIAGRAM_VIEWER_MODES.MODELS_DOCUMENTATION,
136
+ ),
137
+ );
138
+ }
139
+ };
140
+
141
+ return (
142
+ <ContextMenu
143
+ className="data-space__viewer__diagram-viewer__canvas"
144
+ content={
145
+ <MenuContent>
146
+ <MenuContentItem
147
+ onClick={queryClass}
148
+ disabled={!diagramViewerState.contextMenuClassView}
149
+ >
150
+ Query
151
+ </MenuContentItem>
152
+ <MenuContentItem
153
+ onClick={viewClassDocumentation}
154
+ disabled={
155
+ !diagramViewerState.contextMenuClassView ||
156
+ !hasClassDocumentation(
157
+ diagramViewerState.contextMenuClassView.class.value.path,
158
+ )
159
+ }
160
+ >
161
+ See Model Documentation
162
+ </MenuContentItem>
163
+ </MenuContent>
164
+ }
165
+ disabled={!diagramViewerState.contextMenuClassView}
166
+ menuProps={{ elevation: 7 }}
167
+ onClose={(): void =>
168
+ diagramViewerState.setContextMenuClassView(undefined)
169
+ }
170
+ >
171
+ {diagramViewerState.showDescription && (
172
+ <div
173
+ className={clsx('data-space__viewer__diagram-viewer__description', {
174
+ 'data-space__viewer__diagram-viewer__description--expanded':
175
+ diagramViewerState.expandDescription,
176
+ })}
177
+ >
178
+ <button
179
+ className="data-space__viewer__diagram-viewer__description__close-btn"
180
+ tabIndex={-1}
181
+ title="Hide Description"
182
+ onClick={() => diagramViewerState.setShowDescription(false)}
183
+ >
184
+ <CloseIcon />
185
+ </button>
186
+ <div className="data-space__viewer__diagram-viewer__description__title">
187
+ {diagramViewerState.currentDiagram?.title
188
+ ? diagramViewerState.currentDiagram.title
189
+ : 'Untitled'}
190
+ </div>
191
+ <div className="data-space__viewer__diagram-viewer__description__content">
192
+ {descriptionText ? (
193
+ <MarkdownTextViewer
194
+ value={{
195
+ value: descriptionText,
196
+ }}
197
+ />
198
+ ) : (
199
+ <div className="data-space__viewer__diagram-viewer__description__content__placeholder">
200
+ (not specified)
201
+ </div>
202
+ )}
203
+ </div>
204
+ <button
205
+ className="data-space__viewer__diagram-viewer__description__expand-btn"
206
+ tabIndex={-1}
207
+ title={
208
+ diagramViewerState.expandDescription ? 'Collapse' : 'Expand'
209
+ }
210
+ onClick={() =>
211
+ diagramViewerState.setExpandDescription(
212
+ !diagramViewerState.expandDescription,
213
+ )
214
+ }
215
+ >
216
+ {diagramViewerState.expandDescription ? (
217
+ <ThinChevronUpIcon />
218
+ ) : (
219
+ <ThinChevronDownIcon />
220
+ )}
221
+ </button>
222
+ </div>
223
+ )}
224
+ <div
225
+ ref={ref}
226
+ className={clsx(
227
+ 'diagram-canvas',
228
+ diagramViewerState.diagramCursorClass,
229
+ )}
230
+ tabIndex={0}
231
+ />
232
+ </ContextMenu>
233
+ );
234
+ }),
235
+ );
236
+
237
+ type DiagramOption = {
238
+ label: React.ReactNode;
239
+ value: DiagramAnalysisResult;
240
+ };
241
+ const buildDiagramOption = (diagram: DiagramAnalysisResult): DiagramOption => ({
242
+ label: (
243
+ <div className="data-space__viewer__diagram-viewer__header__navigation__selector__label">
244
+ <ShapesIcon className="data-space__viewer__diagram-viewer__header__navigation__selector__icon" />
245
+ <div className="data-space__viewer__diagram-viewer__header__navigation__selector__title">
246
+ {diagram.title ? diagram.title : 'Untitled'}
247
+ </div>
248
+ </div>
249
+ ),
250
+ value: diagram,
251
+ });
252
+
253
+ const DiagramViewerHeader = observer(
254
+ (props: {
255
+ applicationStore: GenericLegendApplicationStore;
256
+ diagramViewerState: DiagramViewerState;
257
+ actions: {
258
+ onSyncZoneWithNavigation: (diagram: DiagramAnalysisResult) => void;
259
+ };
260
+ }) => {
261
+ const { applicationStore, diagramViewerState, actions } = props;
262
+ const { onSyncZoneWithNavigation } = actions;
263
+ const diagramOptions = diagramViewerState.diagrams.map(buildDiagramOption);
264
+ const selectedDiagramOption = diagramViewerState.currentDiagram
265
+ ? buildDiagramOption(diagramViewerState.currentDiagram)
266
+ : null;
267
+ const onDiagramOptionChange = (option: DiagramOption): void => {
268
+ if (option.value !== diagramViewerState.currentDiagram) {
269
+ diagramViewerState.setCurrentDiagram(option.value);
270
+ }
271
+ };
272
+ const diagramFilterOption = createFilter({
273
+ ignoreCase: true,
274
+ ignoreAccents: false,
275
+ stringify: (option: { data: DiagramOption }) => option.data.value.title,
276
+ });
277
+ const createModeSwitcher =
278
+ (
279
+ editMode: DIAGRAM_INTERACTION_MODE,
280
+ relationshipMode: DIAGRAM_RELATIONSHIP_EDIT_MODE,
281
+ ): (() => void) =>
282
+ (): void =>
283
+ diagramViewerState.diagramRenderer.changeMode(
284
+ editMode,
285
+ relationshipMode,
286
+ );
287
+ const createCenterZoomer =
288
+ (zoomLevel: number): (() => void) =>
289
+ (): void => {
290
+ diagramViewerState.diagramRenderer.zoomCenter(zoomLevel / 100);
291
+ };
292
+ const zoomToFit = (): void =>
293
+ diagramViewerState.diagramRenderer.zoomToFit();
294
+
295
+ return (
296
+ <div className="data-space__viewer__diagram-viewer__header">
297
+ <div className="data-space__viewer__diagram-viewer__header__navigation">
298
+ <CustomSelectorInput
299
+ className="data-space__viewer__diagram-viewer__header__navigation__selector"
300
+ options={diagramOptions}
301
+ onChange={onDiagramOptionChange}
302
+ value={selectedDiagramOption}
303
+ placeholder="Search for a diagram"
304
+ darkMode={
305
+ !applicationStore.layoutService
306
+ .TEMPORARY__isLightColorThemeEnabled
307
+ }
308
+ filterOption={diagramFilterOption}
309
+ />
310
+ <div className="data-space__viewer__diagram-viewer__header__navigation__pager">
311
+ <input
312
+ className="data-space__viewer__diagram-viewer__header__navigation__pager__input input--dark"
313
+ value={diagramViewerState.currentDiagramIndex}
314
+ type="number"
315
+ onChange={(event) => {
316
+ const value = parseInt(event.target.value, 10);
317
+ if (
318
+ isNaN(value) ||
319
+ value < 1 ||
320
+ value > diagramViewerState.diagrams.length
321
+ ) {
322
+ return;
323
+ }
324
+ diagramViewerState.setCurrentDiagram(
325
+ at(diagramViewerState.diagrams, value - 1),
326
+ );
327
+ }}
328
+ />
329
+ </div>
330
+ <div className="data-space__viewer__diagram-viewer__header__navigation__pager__count">
331
+ /{diagramViewerState.diagrams.length}
332
+ </div>
333
+ </div>
334
+ <div className="data-space__viewer__diagram-viewer__header__actions">
335
+ {diagramViewerState.isDiagramRendererInitialized && (
336
+ <>
337
+ <div className="data-space__viewer__diagram-viewer__header__group">
338
+ <button
339
+ className="data-space__viewer__diagram-viewer__header__tool"
340
+ tabIndex={-1}
341
+ onClick={(): void => {
342
+ if (diagramViewerState.currentDiagram) {
343
+ onSyncZoneWithNavigation(
344
+ diagramViewerState.currentDiagram,
345
+ );
346
+ }
347
+ }}
348
+ title="Copy Link"
349
+ >
350
+ <AnchorLinkIcon />
351
+ </button>
352
+ <button
353
+ className="data-space__viewer__diagram-viewer__header__tool"
354
+ tabIndex={-1}
355
+ onClick={() => diagramViewerState.diagramRenderer.recenter()}
356
+ title="Recenter (R)"
357
+ >
358
+ <CenterFocusIcon className="data-space__viewer__diagram-viewer__icon--recenter" />
359
+ </button>
360
+ <button
361
+ className={clsx(
362
+ 'data-space__viewer__diagram-viewer__header__tool',
363
+ {
364
+ 'data-space__viewer__diagram-viewer__header__tool--active':
365
+ diagramViewerState.showDescription,
366
+ },
367
+ )}
368
+ tabIndex={-1}
369
+ onClick={() =>
370
+ diagramViewerState.setShowDescription(
371
+ !diagramViewerState.showDescription,
372
+ )
373
+ }
374
+ title="Toggle Description (D)"
375
+ >
376
+ <DescriptionIcon className="data-space__viewer__diagram-viewer__icon--description" />
377
+ </button>
378
+ </div>
379
+ <div className="data-space__viewer__diagram-viewer__header__group__separator" />
380
+ <div className="data-space__viewer__diagram-viewer__header__group">
381
+ <button
382
+ className={clsx(
383
+ 'data-space__viewer__diagram-viewer__header__tool',
384
+ {
385
+ 'data-space__viewer__diagram-viewer__header__tool--active':
386
+ diagramViewerState.diagramRenderer.interactionMode ===
387
+ DIAGRAM_INTERACTION_MODE.LAYOUT,
388
+ },
389
+ )}
390
+ tabIndex={-1}
391
+ onClick={createModeSwitcher(
392
+ DIAGRAM_INTERACTION_MODE.LAYOUT,
393
+ DIAGRAM_RELATIONSHIP_EDIT_MODE.NONE,
394
+ )}
395
+ title="View Tool (V)"
396
+ >
397
+ <MousePointerIcon className="data-space__viewer__diagram-viewer__icon--layout" />
398
+ </button>
399
+ <button
400
+ className={clsx(
401
+ 'data-space__viewer__diagram-viewer__header__tool',
402
+ {
403
+ 'data-space__viewer__diagram-viewer__header__tool--active':
404
+ diagramViewerState.diagramRenderer.interactionMode ===
405
+ DIAGRAM_INTERACTION_MODE.PAN,
406
+ },
407
+ )}
408
+ tabIndex={-1}
409
+ onClick={createModeSwitcher(
410
+ DIAGRAM_INTERACTION_MODE.PAN,
411
+ DIAGRAM_RELATIONSHIP_EDIT_MODE.NONE,
412
+ )}
413
+ title="Pan Tool (M)"
414
+ >
415
+ <MoveIcon className="data-space__viewer__diagram-viewer__icon--pan" />
416
+ </button>
417
+ <button
418
+ className={clsx(
419
+ 'data-space__viewer__diagram-viewer__header__tool',
420
+ {
421
+ 'data-space__viewer__diagram-viewer__header__tool--active':
422
+ diagramViewerState.diagramRenderer.interactionMode ===
423
+ DIAGRAM_INTERACTION_MODE.ZOOM_IN,
424
+ },
425
+ )}
426
+ tabIndex={-1}
427
+ title="Zoom In (Z)"
428
+ onClick={createModeSwitcher(
429
+ DIAGRAM_INTERACTION_MODE.ZOOM_IN,
430
+ DIAGRAM_RELATIONSHIP_EDIT_MODE.NONE,
431
+ )}
432
+ >
433
+ <ZoomInIcon className="data-space__viewer__diagram-viewer__icon--zoom-in" />
434
+ </button>
435
+ <button
436
+ className={clsx(
437
+ 'data-space__viewer__diagram-viewer__header__tool',
438
+ {
439
+ 'data-space__viewer__diagram-viewer__header__tool--active':
440
+ diagramViewerState.diagramRenderer.interactionMode ===
441
+ DIAGRAM_INTERACTION_MODE.ZOOM_OUT,
442
+ },
443
+ )}
444
+ tabIndex={-1}
445
+ title="Zoom Out (Z)"
446
+ onClick={createModeSwitcher(
447
+ DIAGRAM_INTERACTION_MODE.ZOOM_OUT,
448
+ DIAGRAM_RELATIONSHIP_EDIT_MODE.NONE,
449
+ )}
450
+ >
451
+ <ZoomOutIcon className="data-space__viewer__diagram-viewer__icon--zoom-out" />
452
+ </button>
453
+ </div>
454
+ <div className="data-space__viewer__diagram-viewer__header__group__separator" />
455
+ <ControlledDropdownMenu
456
+ className="data-space__viewer__diagram-viewer__header__group data-space__viewer__diagram-viewer__header__dropdown"
457
+ title="Zoom..."
458
+ content={
459
+ <MenuContent>
460
+ <MenuContentItem
461
+ className="data-space__viewer__diagram-viewer__header__zoomer__dropdown__menu__item"
462
+ onClick={zoomToFit}
463
+ >
464
+ Fit
465
+ </MenuContentItem>
466
+ <MenuContentDivider />
467
+ {DIAGRAM_ZOOM_LEVELS.map((zoomLevel) => (
468
+ <MenuContentItem
469
+ key={zoomLevel}
470
+ className="data-space__viewer__diagram-viewer__header__zoomer__dropdown__menu__item"
471
+ onClick={createCenterZoomer(zoomLevel)}
472
+ >
473
+ {zoomLevel}%
474
+ </MenuContentItem>
475
+ ))}
476
+ </MenuContent>
477
+ }
478
+ menuProps={{
479
+ anchorOrigin: { vertical: 'bottom', horizontal: 'right' },
480
+ transformOrigin: { vertical: 'top', horizontal: 'right' },
481
+ elevation: 7,
482
+ }}
483
+ >
484
+ <div className="data-space__viewer__diagram-viewer__header__dropdown__label data-space__viewer__diagram-viewer__header__zoomer__dropdown__label">
485
+ {Math.round(diagramViewerState.diagramRenderer.zoom * 100)}%
486
+ </div>
487
+ <div className="data-space__viewer__diagram-viewer__header__dropdown__trigger data-space__viewer__diagram-viewer__header__zoomer__dropdown__trigger">
488
+ <CaretDownIcon />
489
+ </div>
490
+ </ControlledDropdownMenu>
491
+ </>
492
+ )}
493
+ </div>
494
+ </div>
495
+ );
496
+ },
497
+ );
498
+
499
+ export const DiagramPlaceholder: React.FC<{ message: string }> = (props) => (
500
+ <div className="diagram-viewer__placeholder">{props.message}</div>
501
+ );
502
+
503
+ export const DiagramViewer = observer(
504
+ (props: {
505
+ applicationStore: GenericLegendApplicationStore;
506
+ diagramViewerState: DiagramViewerState;
507
+ actions: {
508
+ onQueryClass?: ((_class: Class) => void) | undefined;
509
+ onViewClassDocumentation: (classPath: string) => void;
510
+ hasClassDocumentation: (classPath: string) => boolean;
511
+ onSyncZoneWithNavigation: (diagram: DiagramAnalysisResult) => void;
512
+ onGenerateAnchorForActivity: (activity: string) => string;
513
+ onChangeZone: (zone: NavigationZone, force?: boolean) => void;
514
+ onSetWikiPageAnchor: (anchorKey: string, element: HTMLElement) => void;
515
+ onUnsetWikiPageAnchor: (anchorKey: string) => void;
516
+ };
517
+ }) => {
518
+ const { diagramViewerState, applicationStore, actions } = props;
519
+ const {
520
+ onSyncZoneWithNavigation,
521
+ onGenerateAnchorForActivity,
522
+ onSetWikiPageAnchor,
523
+ onUnsetWikiPageAnchor,
524
+ onChangeZone,
525
+ } = actions;
526
+ const diagrams = diagramViewerState.diagrams;
527
+ const sectionRef = useRef<HTMLDivElement>(null);
528
+ const anchor = onGenerateAnchorForActivity(
529
+ DIAGRAM_VIEWER_MODES.DIAGRAM_VIEWER,
530
+ );
531
+
532
+ useCommands(diagramViewerState);
533
+
534
+ useEffect(() => {
535
+ if (sectionRef.current) {
536
+ onSetWikiPageAnchor(anchor, sectionRef.current);
537
+ }
538
+ return () => onUnsetWikiPageAnchor(anchor);
539
+ }, [onSetWikiPageAnchor, onUnsetWikiPageAnchor, anchor]);
540
+
541
+ const diagramCanvasRef = useRef<HTMLDivElement>(null);
542
+ const previousDiagram = diagramViewerState.previousDiagram;
543
+ const nextDiagram = diagramViewerState.nextDiagram;
544
+
545
+ const showPreviousDiagram = (): void => {
546
+ if (previousDiagram) {
547
+ diagramViewerState.setCurrentDiagram(previousDiagram);
548
+ onSyncZoneWithNavigation(previousDiagram);
549
+ }
550
+ };
551
+ const showNextDiagram = (): void => {
552
+ if (nextDiagram) {
553
+ diagramViewerState.setCurrentDiagram(nextDiagram);
554
+ onSyncZoneWithNavigation(nextDiagram);
555
+ }
556
+ };
557
+
558
+ return (
559
+ <div ref={sectionRef} className="data-space__viewer__wiki__section">
560
+ <div className="data-space__viewer__wiki__section__header">
561
+ <div className="data-space__viewer__wiki__section__header__label">
562
+ Diagrams
563
+ <button
564
+ className="data-space__viewer__wiki__section__header__anchor"
565
+ tabIndex={-1}
566
+ onClick={() => onChangeZone(anchor, true)}
567
+ >
568
+ <AnchorLinkIcon />
569
+ </button>
570
+ </div>
571
+ </div>
572
+ <div className="data-space__viewer__wiki__section__content">
573
+ {diagrams.length > 0 && (
574
+ <div className="data-space__viewer__diagram-viewer">
575
+ <DiagramViewerHeader
576
+ applicationStore={applicationStore}
577
+ diagramViewerState={diagramViewerState}
578
+ actions={actions}
579
+ />
580
+ <div className="data-space__viewer__diagram-viewer__carousel">
581
+ <div className="data-space__viewer__diagram-viewer__carousel__frame">
582
+ <div className="data-space__viewer__diagram-viewer__carousel__frame__display">
583
+ {diagramViewerState.currentDiagram && (
584
+ <DiagramCanvas
585
+ diagramViewerState={diagramViewerState}
586
+ diagram={diagramViewerState.currentDiagram.diagram}
587
+ ref={diagramCanvasRef}
588
+ actions={actions}
589
+ />
590
+ )}
591
+ </div>
592
+ <button
593
+ className="data-space__viewer__diagram-viewer__carousel__frame__navigator data-space__viewer__diagram-viewer__carousel__frame__navigator--back"
594
+ tabIndex={-1}
595
+ title={`Previous - ${
596
+ previousDiagram?.title
597
+ ? previousDiagram.title
598
+ : '(untitled)'
599
+ } (⇦)`}
600
+ disabled={!previousDiagram}
601
+ onClick={showPreviousDiagram}
602
+ >
603
+ <ThinChevronLeftIcon />
604
+ </button>
605
+ <button
606
+ className="data-space__viewer__diagram-viewer__carousel__frame__navigator data-space__viewer__diagram-viewer__carousel__frame__navigator--next"
607
+ tabIndex={-1}
608
+ title={`Next - ${
609
+ nextDiagram?.title ? nextDiagram.title : '(untitled)'
610
+ } (⇨)`}
611
+ disabled={!nextDiagram}
612
+ onClick={showNextDiagram}
613
+ >
614
+ <ThinChevronRightIcon />
615
+ </button>
616
+ <div className="data-space__viewer__diagram-viewer__carousel__frame__indicators">
617
+ <div className="data-space__viewer__diagram-viewer__carousel__frame__indicators__notch">
618
+ {diagrams.map((diagram) => (
619
+ <button
620
+ key={diagram.uuid}
621
+ className={clsx(
622
+ 'data-space__viewer__diagram-viewer__carousel__frame__indicator',
623
+ {
624
+ 'data-space__viewer__diagram-viewer__carousel__frame__indicator--active':
625
+ diagramViewerState.currentDiagram === diagram,
626
+ },
627
+ )}
628
+ title={`View Diagram - ${
629
+ diagram.title ? diagram.title : '(untitled)'
630
+ }`}
631
+ onClick={() => {
632
+ diagramViewerState.setCurrentDiagram(diagram);
633
+ onSyncZoneWithNavigation(diagram);
634
+ }}
635
+ >
636
+ <CircleIcon />
637
+ </button>
638
+ ))}
639
+ </div>
640
+ </div>
641
+ </div>
642
+ </div>
643
+ </div>
644
+ )}
645
+ {!diagrams.length && <DiagramPlaceholder message="(not specified)" />}
646
+ </div>
647
+ </div>
648
+ );
649
+ },
650
+ );