@finos/legend-extension-dsl-diagram 4.0.0 → 4.1.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.
Files changed (38) hide show
  1. package/lib/DiagramRenderer.d.ts +11 -0
  2. package/lib/DiagramRenderer.d.ts.map +1 -1
  3. package/lib/DiagramRenderer.js +112 -0
  4. package/lib/DiagramRenderer.js.map +1 -1
  5. package/lib/components/studio/DSLDiagram_LegendStudioDocumentation.d.ts +0 -2
  6. package/lib/components/studio/DSLDiagram_LegendStudioDocumentation.d.ts.map +1 -1
  7. package/lib/components/studio/DSLDiagram_LegendStudioDocumentation.js +0 -14
  8. package/lib/components/studio/DSLDiagram_LegendStudioDocumentation.js.map +1 -1
  9. package/lib/components/studio/DSLDiagram_LegendStudioPlugin.d.ts +1 -2
  10. package/lib/components/studio/DSLDiagram_LegendStudioPlugin.d.ts.map +1 -1
  11. package/lib/components/studio/DSLDiagram_LegendStudioPlugin.js +6 -4
  12. package/lib/components/studio/DSLDiagram_LegendStudioPlugin.js.map +1 -1
  13. package/lib/components/studio/DiagramEditor.d.ts.map +1 -1
  14. package/lib/components/studio/DiagramEditor.js +4 -3
  15. package/lib/components/studio/DiagramEditor.js.map +1 -1
  16. package/lib/index.css +2 -2
  17. package/lib/index.css.map +1 -1
  18. package/lib/models/metamodels/pure/packageableElements/diagram/DSLDiagram_AssociationView.js +1 -1
  19. package/lib/models/metamodels/pure/packageableElements/diagram/DSLDiagram_AssociationView.js.map +1 -1
  20. package/lib/models/metamodels/pure/packageableElements/diagram/DSLDiagram_ClassView.js +1 -1
  21. package/lib/models/metamodels/pure/packageableElements/diagram/DSLDiagram_ClassView.js.map +1 -1
  22. package/lib/models/protocols/pure/DSLDiagram_PureProtocolProcessorPlugin.d.ts +0 -1
  23. package/lib/models/protocols/pure/DSLDiagram_PureProtocolProcessorPlugin.d.ts.map +1 -1
  24. package/lib/models/protocols/pure/DSLDiagram_PureProtocolProcessorPlugin.js +0 -7
  25. package/lib/models/protocols/pure/DSLDiagram_PureProtocolProcessorPlugin.js.map +1 -1
  26. package/lib/models/protocols/pure/v1/transformation/pureGraph/V1_DSLDiagram_TransformerHelper.d.ts.map +1 -1
  27. package/lib/models/protocols/pure/v1/transformation/pureGraph/V1_DSLDiagram_TransformerHelper.js +2 -2
  28. package/lib/models/protocols/pure/v1/transformation/pureGraph/V1_DSLDiagram_TransformerHelper.js.map +1 -1
  29. package/lib/package.json +3 -3
  30. package/package.json +10 -10
  31. package/src/DiagramRenderer.ts +192 -0
  32. package/src/components/studio/DSLDiagram_LegendStudioDocumentation.ts +0 -20
  33. package/src/components/studio/DSLDiagram_LegendStudioPlugin.tsx +7 -13
  34. package/src/components/studio/DiagramEditor.tsx +111 -0
  35. package/src/models/metamodels/pure/packageableElements/diagram/DSLDiagram_AssociationView.ts +1 -1
  36. package/src/models/metamodels/pure/packageableElements/diagram/DSLDiagram_ClassView.ts +1 -1
  37. package/src/models/protocols/pure/DSLDiagram_PureProtocolProcessorPlugin.ts +0 -8
  38. package/src/models/protocols/pure/v1/transformation/pureGraph/V1_DSLDiagram_TransformerHelper.ts +1 -2
@@ -103,6 +103,17 @@ export enum DIAGRAM_RELATIONSHIP_EDIT_MODE {
103
103
  NONE,
104
104
  }
105
105
 
106
+ export enum ALIGNER_OPERATOR {
107
+ ALIGN_LEFT,
108
+ ALIGN_CENTER,
109
+ ALIGN_RIGHT,
110
+ ALIGN_TOP,
111
+ ALIGN_MIDDLE,
112
+ ALIGN_BOTTOM,
113
+ SPACE_HORIZONTALLY,
114
+ SPACE_VERTICALLY,
115
+ }
116
+
106
117
  const MIN_ZOOM_LEVEL = 0.05; // 5%
107
118
  const FIT_ZOOM_PADDING = 10;
108
119
  export const DIAGRAM_ZOOM_LEVELS = [
@@ -317,6 +328,7 @@ export class DiagramRenderer {
317
328
  setMiddleClick: action,
318
329
  setRightClick: action,
319
330
  setZoomLevel: action,
331
+ align: action,
320
332
  });
321
333
 
322
334
  this.diagram = diagram;
@@ -680,6 +692,186 @@ export class DiagramRenderer {
680
692
  }
681
693
  }
682
694
 
695
+ align(op: ALIGNER_OPERATOR): void {
696
+ if (this.selectedClasses.length < 2) {
697
+ return;
698
+ }
699
+ switch (op) {
700
+ case ALIGNER_OPERATOR.ALIGN_LEFT: {
701
+ const leftBound = this.selectedClasses.reduce(
702
+ (val, view) => Math.min(val, view.position.x),
703
+ Number.MAX_SAFE_INTEGER,
704
+ );
705
+ this.selectedClasses.forEach((view) =>
706
+ positionedRectangle_setPosition(
707
+ view,
708
+ new Point(leftBound, view.position.y),
709
+ ),
710
+ );
711
+ break;
712
+ }
713
+ case ALIGNER_OPERATOR.ALIGN_CENTER: {
714
+ const center =
715
+ this.selectedClasses.reduce(
716
+ (val, view) => val + view.position.x + view.rectangle.width,
717
+ 0,
718
+ ) / this.selectedClasses.length;
719
+ this.selectedClasses.forEach((view) =>
720
+ positionedRectangle_setPosition(
721
+ view,
722
+ new Point(center - view.rectangle.width / 2, view.position.y),
723
+ ),
724
+ );
725
+ break;
726
+ }
727
+ case ALIGNER_OPERATOR.ALIGN_RIGHT: {
728
+ const rightBound = this.selectedClasses.reduce(
729
+ (val, view) => Math.max(val, view.position.x + view.rectangle.width),
730
+ -Number.MAX_SAFE_INTEGER,
731
+ );
732
+ this.selectedClasses.forEach((view) =>
733
+ positionedRectangle_setPosition(
734
+ view,
735
+ new Point(rightBound - view.rectangle.width, view.position.y),
736
+ ),
737
+ );
738
+ break;
739
+ }
740
+ case ALIGNER_OPERATOR.ALIGN_TOP: {
741
+ const topBound = this.selectedClasses.reduce(
742
+ (val, view) => Math.min(val, view.position.y),
743
+ Number.MAX_SAFE_INTEGER,
744
+ );
745
+ this.selectedClasses.forEach((view) =>
746
+ positionedRectangle_setPosition(
747
+ view,
748
+ new Point(view.position.x, topBound),
749
+ ),
750
+ );
751
+ break;
752
+ }
753
+ case ALIGNER_OPERATOR.ALIGN_MIDDLE: {
754
+ const middle =
755
+ this.selectedClasses.reduce(
756
+ (val, view) => val + view.position.y + view.rectangle.height,
757
+ 0,
758
+ ) / this.selectedClasses.length;
759
+ this.selectedClasses.forEach((view) =>
760
+ positionedRectangle_setPosition(
761
+ view,
762
+ new Point(view.position.x, middle - view.rectangle.height / 2),
763
+ ),
764
+ );
765
+ break;
766
+ }
767
+ case ALIGNER_OPERATOR.ALIGN_BOTTOM: {
768
+ const bottomBound = this.selectedClasses.reduce(
769
+ (val, view) => Math.max(val, view.position.y + view.rectangle.height),
770
+ -Number.MAX_SAFE_INTEGER,
771
+ );
772
+ this.selectedClasses.forEach((view) =>
773
+ positionedRectangle_setPosition(
774
+ view,
775
+ new Point(view.position.x, bottomBound - view.rectangle.height),
776
+ ),
777
+ );
778
+ break;
779
+ }
780
+ case ALIGNER_OPERATOR.SPACE_HORIZONTALLY: {
781
+ const sorted = this.selectedClasses
782
+ .slice()
783
+ .sort((a, b) => a.position.x - b.position.x);
784
+ // NOTE: handle special case where there are only 2 views, make them adjacent
785
+ if (sorted.length === 2) {
786
+ const previousView = sorted[0] as ClassView;
787
+ const currentView = sorted[1] as ClassView;
788
+ positionedRectangle_setPosition(
789
+ currentView,
790
+ new Point(
791
+ previousView.position.x + previousView.rectangle.width,
792
+ currentView.position.y,
793
+ ),
794
+ );
795
+ } else {
796
+ const spacings = [];
797
+ for (let idx = 1; idx < sorted.length; idx++) {
798
+ const previousView = sorted[idx - 1] as ClassView;
799
+ const currentView = sorted[idx] as ClassView;
800
+ spacings.push(
801
+ currentView.position.x -
802
+ (previousView.position.x + previousView.rectangle.width),
803
+ );
804
+ }
805
+ const averageSpacing =
806
+ spacings.reduce((val, distance) => val + distance, 0) /
807
+ spacings.length;
808
+ for (let idx = 1; idx < sorted.length; idx++) {
809
+ const previousView = sorted[idx - 1] as ClassView;
810
+ const currentView = sorted[idx] as ClassView;
811
+ positionedRectangle_setPosition(
812
+ currentView,
813
+ new Point(
814
+ previousView.position.x +
815
+ previousView.rectangle.width +
816
+ averageSpacing,
817
+ currentView.position.y,
818
+ ),
819
+ );
820
+ }
821
+ }
822
+ break;
823
+ }
824
+ case ALIGNER_OPERATOR.SPACE_VERTICALLY: {
825
+ const sorted = this.selectedClasses
826
+ .slice()
827
+ .sort((a, b) => a.position.y - b.position.y);
828
+ // NOTE: handle special case where there are only 2 views, make them adjacent
829
+ if (sorted.length === 2) {
830
+ const previousView = sorted[0] as ClassView;
831
+ const currentView = sorted[1] as ClassView;
832
+ positionedRectangle_setPosition(
833
+ currentView,
834
+ new Point(
835
+ currentView.position.x,
836
+ previousView.position.y + previousView.rectangle.height,
837
+ ),
838
+ );
839
+ } else {
840
+ const spacings = [];
841
+ for (let idx = 1; idx < sorted.length; idx++) {
842
+ const previousView = sorted[idx - 1] as ClassView;
843
+ const currentView = sorted[idx] as ClassView;
844
+ spacings.push(
845
+ currentView.position.y -
846
+ (previousView.position.y + previousView.rectangle.height),
847
+ );
848
+ }
849
+ const averageSpacing =
850
+ spacings.reduce((val, distance) => val + distance, 0) /
851
+ spacings.length;
852
+ for (let idx = 1; idx < sorted.length; idx++) {
853
+ const previousView = sorted[idx - 1] as ClassView;
854
+ const currentView = sorted[idx] as ClassView;
855
+ positionedRectangle_setPosition(
856
+ currentView,
857
+ new Point(
858
+ currentView.position.x,
859
+ previousView.position.y +
860
+ previousView.rectangle.height +
861
+ averageSpacing,
862
+ ),
863
+ );
864
+ }
865
+ }
866
+ break;
867
+ }
868
+ default:
869
+ break;
870
+ }
871
+
872
+ this.drawScreen();
873
+ }
874
+
683
875
  truncateTextWithEllipsis(val: string, limit = this.maxLineLength): string {
684
876
  const ellipsis = '...';
685
877
  return val.length > limit
@@ -14,27 +14,7 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- import type { LegendApplicationDocumentationEntryConfig } from '@finos/legend-application';
18
-
19
17
  export enum DSL_DIAGRAM_LEGEND_STUDIO_DOCUMENTATION_KEY {
20
18
  GRAMMAR_PARSER = 'dsl-diagram.grammar.parser',
21
19
  GRAMMAR_ELEMENT_DIAGRAM = 'dsl-diagram.grammar.element.diagram',
22
20
  }
23
-
24
- export const DSL_DIAGRAM_DOCUMENTATION_ENTRIES: Record<
25
- string,
26
- LegendApplicationDocumentationEntryConfig
27
- > = {
28
- [DSL_DIAGRAM_LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER]: {
29
- title: `What is Diagram DSL about?`,
30
- markdownText: {
31
- value: `\`Diagram DSL\` (coressponding to \`###Diagram\` section in \`Pure\`) concerns with visualizing data models and their relationship`,
32
- },
33
- },
34
- [DSL_DIAGRAM_LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_ELEMENT_DIAGRAM]: {
35
- title: `What is a diagram element?`,
36
- markdownText: {
37
- value: `A \`Diagram\` element specifies the visualization/rendering of data models and their relationship`,
38
- },
39
- },
40
- };
@@ -42,25 +42,18 @@ import { Diagram } from '../../models/metamodels/pure/packageableElements/diagra
42
42
  import { DiagramEditorState } from '../../stores/studio/DiagramEditorState.js';
43
43
  import { DiagramEditor } from './DiagramEditor.js';
44
44
  import { ClassDiagramPreview } from './ClassDiagramPreview.js';
45
- import {
46
- type LegendApplicationDocumentationEntry,
47
- type LegendApplicationKeyedDocumentationEntry,
48
- collectKeyedDocumnetationEntriesFromConfig,
49
- } from '@finos/legend-application';
50
45
  import {
51
46
  PURE_GRAMMAR_DIAGRAM_ELEMENT_TYPE_LABEL,
52
47
  PURE_GRAMMAR_DIAGRAM_PARSER_NAME,
53
48
  } from '../../graphManager/DSLDiagram_PureGraphManagerPlugin.js';
54
- import {
55
- DSL_DIAGRAM_DOCUMENTATION_ENTRIES,
56
- DSL_DIAGRAM_LEGEND_STUDIO_DOCUMENTATION_KEY,
57
- } from './DSLDiagram_LegendStudioDocumentation.js';
49
+ import { DSL_DIAGRAM_LEGEND_STUDIO_DOCUMENTATION_KEY } from './DSLDiagram_LegendStudioDocumentation.js';
58
50
  import {
59
51
  EMPTY_DIAGRAM_SNIPPET,
60
52
  getDiagramSnippetWithGeneralizationView,
61
53
  getDiagramSnippetWithOneClassView,
62
54
  getDiagramSnippetWithPropertyView,
63
55
  } from './DSLDiagram_CodeSnippets.js';
56
+ import type { LegendApplicationDocumentationEntry } from '@finos/legend-application';
64
57
 
65
58
  const DIAGRAM_ELEMENT_TYPE = 'DIAGRAM';
66
59
  const DIAGRAM_ELEMENT_PROJECT_EXPLORER_DND_TYPE = 'PROJECT_EXPLORER_DIAGRAM';
@@ -73,10 +66,11 @@ export class DSLDiagram_LegendStudioPlugin
73
66
  super(packageJson.extensions.studioPlugin, packageJson.version);
74
67
  }
75
68
 
76
- override getExtraKeyedDocumentationEntries(): LegendApplicationKeyedDocumentationEntry[] {
77
- return collectKeyedDocumnetationEntriesFromConfig(
78
- DSL_DIAGRAM_DOCUMENTATION_ENTRIES,
79
- );
69
+ override getExtraRequiredDocumentationKeys(): string[] {
70
+ return [
71
+ DSL_DIAGRAM_LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_ELEMENT_DIAGRAM,
72
+ DSL_DIAGRAM_LEGEND_STUDIO_DOCUMENTATION_KEY.GRAMMAR_PARSER,
73
+ ];
80
74
  }
81
75
 
82
76
  override getExtraClassPreviewRenderers(): ClassPreviewRenderer[] {
@@ -26,6 +26,7 @@ import { useResizeDetector } from 'react-resize-detector';
26
26
  import { type DropTargetMonitor, useDrop } from 'react-dnd';
27
27
  import { observer } from 'mobx-react-lite';
28
28
  import {
29
+ ALIGNER_OPERATOR,
29
30
  DiagramRenderer,
30
31
  DIAGRAM_INTERACTION_MODE,
31
32
  DIAGRAM_RELATIONSHIP_EDIT_MODE,
@@ -70,6 +71,14 @@ import {
70
71
  ZoomInIcon,
71
72
  ZoomOutIcon,
72
73
  Dialog,
74
+ AlignEndIcon,
75
+ DistributeHorizontalIcon,
76
+ DistributeVerticalIcon,
77
+ AlignStartIcon,
78
+ AlignCenterIcon,
79
+ AlignTopIcon,
80
+ AlignMiddleIcon,
81
+ AlignBottomIcon,
73
82
  } from '@finos/legend-art';
74
83
  import {
75
84
  type Type,
@@ -1258,9 +1267,111 @@ const DiagramEditorHeader = observer(
1258
1267
  diagramEditorState.setSidePanelState(undefined);
1259
1268
  }
1260
1269
  };
1270
+ const isAlignerDisabled =
1271
+ diagramEditorState.renderer.selectedClasses.length < 2;
1261
1272
 
1262
1273
  return (
1263
1274
  <>
1275
+ <div className="diagram-editor__header__group">
1276
+ <button
1277
+ className="diagram-editor__header__action diagram-editor__header__group__action"
1278
+ title="Align left"
1279
+ disabled={isAlignerDisabled}
1280
+ tabIndex={-1}
1281
+ onClick={(): void =>
1282
+ diagramEditorState.renderer.align(ALIGNER_OPERATOR.ALIGN_LEFT)
1283
+ }
1284
+ >
1285
+ <AlignStartIcon className="diagram-editor__icon--aligner" />
1286
+ </button>
1287
+ <button
1288
+ className="diagram-editor__header__action diagram-editor__header__group__action"
1289
+ title="Align center"
1290
+ disabled={isAlignerDisabled}
1291
+ tabIndex={-1}
1292
+ onClick={(): void =>
1293
+ diagramEditorState.renderer.align(ALIGNER_OPERATOR.ALIGN_CENTER)
1294
+ }
1295
+ >
1296
+ <AlignCenterIcon className="diagram-editor__icon--aligner" />
1297
+ </button>
1298
+ <button
1299
+ className="diagram-editor__header__action diagram-editor__header__group__action"
1300
+ title="Align right"
1301
+ disabled={isAlignerDisabled}
1302
+ tabIndex={-1}
1303
+ onClick={(): void =>
1304
+ diagramEditorState.renderer.align(ALIGNER_OPERATOR.ALIGN_RIGHT)
1305
+ }
1306
+ >
1307
+ <AlignEndIcon className="diagram-editor__icon--aligner" />
1308
+ </button>
1309
+ </div>
1310
+ <div className="diagram-editor__header__group__separator" />
1311
+ <div className="diagram-editor__header__group">
1312
+ <button
1313
+ className="diagram-editor__header__action diagram-editor__header__group__action"
1314
+ title="Align top"
1315
+ disabled={isAlignerDisabled}
1316
+ tabIndex={-1}
1317
+ onClick={(): void =>
1318
+ diagramEditorState.renderer.align(ALIGNER_OPERATOR.ALIGN_TOP)
1319
+ }
1320
+ >
1321
+ <AlignTopIcon className="diagram-editor__icon--aligner" />
1322
+ </button>
1323
+ <button
1324
+ className="diagram-editor__header__action diagram-editor__header__group__action"
1325
+ title="Align middle"
1326
+ disabled={isAlignerDisabled}
1327
+ tabIndex={-1}
1328
+ onClick={(): void =>
1329
+ diagramEditorState.renderer.align(ALIGNER_OPERATOR.ALIGN_MIDDLE)
1330
+ }
1331
+ >
1332
+ <AlignMiddleIcon className="diagram-editor__icon--aligner" />
1333
+ </button>
1334
+ <button
1335
+ className="diagram-editor__header__action diagram-editor__header__group__action"
1336
+ title="Align bottom"
1337
+ disabled={isAlignerDisabled}
1338
+ tabIndex={-1}
1339
+ onClick={(): void =>
1340
+ diagramEditorState.renderer.align(ALIGNER_OPERATOR.ALIGN_BOTTOM)
1341
+ }
1342
+ >
1343
+ <AlignBottomIcon className="diagram-editor__icon--aligner" />
1344
+ </button>
1345
+ </div>
1346
+ <div className="diagram-editor__header__group__separator" />
1347
+ <div className="diagram-editor__header__group">
1348
+ <button
1349
+ className="diagram-editor__header__action diagram-editor__header__group__action"
1350
+ title="Space horizontally"
1351
+ disabled={isAlignerDisabled}
1352
+ tabIndex={-1}
1353
+ onClick={(): void =>
1354
+ diagramEditorState.renderer.align(
1355
+ ALIGNER_OPERATOR.SPACE_HORIZONTALLY,
1356
+ )
1357
+ }
1358
+ >
1359
+ <DistributeHorizontalIcon className="diagram-editor__icon--aligner" />
1360
+ </button>
1361
+ <button
1362
+ className="diagram-editor__header__action diagram-editor__header__group__action"
1363
+ title="Space vertically"
1364
+ disabled={isAlignerDisabled}
1365
+ tabIndex={-1}
1366
+ onClick={(): void =>
1367
+ diagramEditorState.renderer.align(
1368
+ ALIGNER_OPERATOR.SPACE_VERTICALLY,
1369
+ )
1370
+ }
1371
+ >
1372
+ <DistributeVerticalIcon className="diagram-editor__icon--aligner" />
1373
+ </button>
1374
+ </div>
1264
1375
  <DropdownMenu
1265
1376
  className="diagram-editor__header__dropdown"
1266
1377
  content={
@@ -43,7 +43,7 @@ export class AssociationView extends PropertyHolderView implements Hashable {
43
43
  return hashArray([
44
44
  DIAGRAM_HASH_STRUCTURE.ASSOCIATION_VIEW,
45
45
  super.hashCode,
46
- this.association.hashValue,
46
+ this.association.valueForSerialization ?? '',
47
47
  ]);
48
48
  }
49
49
  }
@@ -47,7 +47,7 @@ export class ClassView extends PositionedRectangle implements Hashable {
47
47
  DIAGRAM_HASH_STRUCTURE.CLASS_VIEW,
48
48
  super.hashCode,
49
49
  this.id,
50
- this.class.hashValue,
50
+ this.class.valueForSerialization ?? '',
51
51
  this.hideProperties?.toString() ?? '',
52
52
  this.hideTaggedValues?.toString() ?? '',
53
53
  this.hideStereotypes?.toString() ?? '',
@@ -157,12 +157,4 @@ export class DSLDiagram_PureProtocolProcessorPlugin extends PureProtocolProcesso
157
157
  },
158
158
  ];
159
159
  }
160
-
161
- override V1_getExtraSourceInformationKeys(): string[] {
162
- return [
163
- 'classSourceInformation',
164
- 'sourceViewSourceInformation',
165
- 'targetViewSourceInformation',
166
- ];
167
- }
168
160
  }
@@ -28,7 +28,6 @@ import { V1_Rectangle } from '../../model/packageableElements/diagram/geometry/V
28
28
  import { V1_PropertyView } from '../../model/packageableElements/diagram/V1_DSLDiagram_PropertyView.js';
29
29
  import {
30
30
  V1_initPackageableElement,
31
- V1_transformElementReference,
32
31
  V1_transformPropertyReference,
33
32
  } from '@finos/legend-graph';
34
33
 
@@ -74,7 +73,7 @@ const transformGenerationView = (
74
73
 
75
74
  const transformClassView = (element: ClassView): V1_ClassView => {
76
75
  const _classView = new V1_ClassView();
77
- _classView.class = V1_transformElementReference(element.class);
76
+ _classView.class = element.class.valueForSerialization ?? '';
78
77
  _classView.hideProperties = element.hideProperties;
79
78
  _classView.hideStereotypes = element.hideStereotypes;
80
79
  _classView.hideTaggedValues = element.hideTaggedValues;