@finos/legend-extension-dsl-diagram 4.0.1 → 4.1.1

Sign up to get free protection for your applications and to get access to all the features.
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 +5 -5
  30. package/package.json +12 -12
  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;