From 1dbb65abd7f79cf21d90a9de34e1e615defa16c2 Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Thu, 28 Sep 2023 18:44:43 +0200 Subject: [PATCH 01/10] initial commit --- src/main/components/store/model-state.ts | 59 ++++++++++++++---------- src/main/scenes/svg.tsx | 11 +++-- src/main/typings.ts | 6 +-- 3 files changed, 45 insertions(+), 31 deletions(-) diff --git a/src/main/components/store/model-state.ts b/src/main/components/store/model-state.ts index a304de6ca..83328266e 100644 --- a/src/main/components/store/model-state.ts +++ b/src/main/components/store/model-state.ts @@ -45,22 +45,26 @@ export interface ModelState { lastAction: LastActionState; } +// TODO: fix this + export class ModelState { static fromModel(model: Apollon.UMLModel): PartialModelState { + // TODO: check for version, as it is different based on 2.0.0 vs 3.0.0 + const apollonElements = model.elements; const apollonRelationships = model.relationships; const deserialize = (apollonElement: Apollon.UMLElement): UMLElement[] => { const element = new UMLElements[apollonElement.type](); const children: Apollon.UMLElement[] = UMLContainer.isUMLContainer(element) - ? apollonElements + ? Object.values(apollonElements) .filter((child) => child.owner === apollonElement.id) - .map((val) => { - const parent = apollonElements.find((e) => e.id === val.owner)!; + .map((val) => { + const parent = apollonElements[val.owner!]; return { ...val, bounds: { ...val.bounds, x: val.bounds.x - parent.bounds.x, y: val.bounds.y - parent.bounds.y }, - }; + } as Apollon.UMLElement; }) : []; @@ -69,11 +73,11 @@ export class ModelState { return [element, ...children.reduce((acc, val) => [...acc, ...deserialize(val)], [])]; }; - const elements = apollonElements + const elements = Object.values(apollonElements) .filter((element) => !element.owner) .reduce((acc, val) => [...acc, ...deserialize(val)], []); - const relationships = apollonRelationships.map((apollonRelationship) => { + const relationships = Object.values(apollonRelationships).map((apollonRelationship) => { const relationship = new UMLRelationships[apollonRelationship.type](); relationship.deserialize(apollonRelationship); return relationship; @@ -91,7 +95,7 @@ export class ModelState { // set diagram to keep diagram type const diagram: UMLDiagram = new UMLDiagram(); diagram.type = model.type as UMLDiagramType; - diagram.ownedRelationships = model.relationships.map((s) => { + diagram.ownedRelationships = Object.values(model.relationships).map((s) => { return s.id; }); @@ -99,7 +103,7 @@ export class ModelState { diagram, interactive: [...model.interactive.elements, ...model.interactive.relationships], elements: [...elements, ...relationships].reduce((acc, val) => ({ ...acc, [val.id]: { ...val } }), {}), - assessments: (model.assessments || []).reduce( + assessments: (Object.values(model.assessments) || []).reduce( (acc, val) => ({ ...acc, [val.modelElementId]: { @@ -140,19 +144,22 @@ export class ModelState { ]; }; - const apollonElements = Object.values(elements) - .filter((element) => !element.owner) - .reduce((acc, val) => [...acc, ...serialize(val)], []); + const apollonElements = Object.fromEntries( + Object.entries(elements) + .filter(([, element]) => !element.owner) + ) as {[id: string]: Apollon.UMLElement}; + + const apollonElementsArray = Object.values(apollonElements); const apollonRelationships: Apollon.UMLRelationship[] = relationships.map((relationship) => relationship.serialize(), ); - const roots = [...apollonElements, ...apollonRelationships].filter((element) => !element.owner); + const roots = [...apollonElementsArray, ...apollonRelationships].filter((element) => !element.owner); const bounds = computeBoundingBoxForElements(roots); bounds.width = Math.ceil(bounds.width / 20) * 20; bounds.height = Math.ceil(bounds.height / 20) * 20; - for (const element of apollonElements) { + for (const element of apollonElementsArray) { element.bounds.x -= bounds.x; element.bounds.y -= bounds.y; } @@ -166,24 +173,26 @@ export class ModelState { relationships: state.interactive.filter((id) => UMLRelationship.isUMLRelationship(state.elements[id])), }; - const assessments = Object.keys(state.assessments).map((id) => ({ - modelElementId: id, - elementType: state.elements[id].type as UMLElementType | UMLRelationshipType, - score: state.assessments[id].score, - feedback: state.assessments[id].feedback, - label: state.assessments[id].label, - labelColor: state.assessments[id].labelColor, - correctionStatus: state.assessments[id].correctionStatus, - dropInfo: state.assessments[id].dropInfo, - })); + const assessments = Object.fromEntries( + Object.entries(state.assessments).map(([id, assessment]) => [id, { + modelElementId: id, + elementType: state.elements[id].type as UMLElementType | UMLRelationshipType, + score: state.assessments[id].score, + feedback: state.assessments[id].feedback, + label: state.assessments[id].label, + labelColor: state.assessments[id].labelColor, + correctionStatus: state.assessments[id].correctionStatus, + dropInfo: state.assessments[id].dropInfo, + }]) + ); return { - version: '2.0.0', + version: '3.0.0', type: state.diagram.type, size: { width: state.diagram.bounds.width, height: state.diagram.bounds.height }, interactive, elements: apollonElements, - relationships: apollonRelationships, + relationships: Object.fromEntries(apollonRelationships.map((relationship) => [relationship.id, relationship])), assessments, }; } diff --git a/src/main/scenes/svg.tsx b/src/main/scenes/svg.tsx index e36c34510..2d4448896 100644 --- a/src/main/scenes/svg.tsx +++ b/src/main/scenes/svg.tsx @@ -21,6 +21,8 @@ import { ThemeProvider } from 'styled-components'; import { UMLClassifierComponent } from '../packages/common/uml-classifier/uml-classifier-component'; import { UMLClassifierMemberComponent } from '../packages/common/uml-classifier/uml-classifier-member-component'; +// FIXME: svg export ignores children? + type Props = { model: Apollon.UMLModel; options?: Apollon.ExportOptions; @@ -81,10 +83,13 @@ const getInitialState = ({ model, options }: Props): State => { const apollonElements = model.elements; const apollonRelationships = model.relationships; + // TODO: deserialize based on version. the following code is for 3.0.0, write for 2.0.0 as well. + // TODO: optimize this code. + const deserialize = (apollonElement: Apollon.UMLElement): UMLElement[] => { const element = new UMLElements[apollonElement.type](); const apollonChildren: Apollon.UMLElement[] = UMLContainer.isUMLContainer(element) - ? apollonElements.filter((child) => child.owner === apollonElement.id) + ? Object.values(apollonElements).filter((child) => child.owner === apollonElement.id) : []; element.deserialize(apollonElement, apollonChildren); @@ -107,11 +112,11 @@ const getInitialState = ({ model, options }: Props): State => { return [root, ...updates]; }; - const elements = apollonElements + const elements = Object.values(apollonElements) .filter((element) => !element.owner) .reduce((acc, val) => [...acc, ...deserialize(val)], []); - const relationships = apollonRelationships.map((apollonRelationship) => { + const relationships = Object.values(apollonRelationships).map((apollonRelationship) => { const relationship = new UMLRelationships[apollonRelationship.type](); relationship.deserialize(apollonRelationship); return relationship; diff --git a/src/main/typings.ts b/src/main/typings.ts index 89bbb57ed..e6f948c1f 100644 --- a/src/main/typings.ts +++ b/src/main/typings.ts @@ -34,10 +34,10 @@ export type UMLModel = { version: string; type: UMLDiagramType; size: { width: number; height: number }; - elements: UMLElement[]; + elements: { [id: string]: UMLElement }; interactive: Selection; - relationships: UMLRelationship[]; - assessments: Assessment[]; + relationships: { [id: string]: UMLRelationship }; + assessments: { [id: string]: Assessment }; }; export type UMLModelElementType = UMLElementType | UMLRelationshipType | UMLDiagramType; From 0f7ec528e7b659556205f81374ca9fe9799f1c6a Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Thu, 28 Sep 2023 20:10:50 +0200 Subject: [PATCH 02/10] fix some transformation issues --- src/main/apollon-editor.ts | 2 ++ src/main/components/store/model-state.ts | 35 ++++++++++++++---------- 2 files changed, 23 insertions(+), 14 deletions(-) diff --git a/src/main/apollon-editor.ts b/src/main/apollon-editor.ts index ed4d373e3..ec9bf8856 100644 --- a/src/main/apollon-editor.ts +++ b/src/main/apollon-editor.ts @@ -372,6 +372,8 @@ export class ApollonEditor { this.store.getState().lastAction.endsWith('DELETE') ) { const lastModel = ModelState.toModel(this.store.getState()); + // eslint-disable-next-line no-console + console.log(lastModel); Object.values(this.discreteModelSubscribers).forEach((subscriber) => subscriber(lastModel)); } } catch (error) { diff --git a/src/main/components/store/model-state.ts b/src/main/components/store/model-state.ts index 83328266e..04489e6b8 100644 --- a/src/main/components/store/model-state.ts +++ b/src/main/components/store/model-state.ts @@ -124,31 +124,38 @@ export class ModelState { const elements = Object.values(state.elements) .map((element) => UMLElementRepository.get(element)) .reduce<{ [id: string]: UMLElement }>((acc, val) => ({ ...acc, ...(val && { [val.id]: val }) }), {}); + const relationships: UMLRelationship[] = Object.values(state.elements) .filter((x): x is IUMLRelationship => UMLRelationship.isUMLRelationship(x)) .map((relationship) => UMLRelationshipRepository.get(relationship)!); - const serialize = (element: UMLElement): Apollon.UMLElement[] => { + const serialize = (element: UMLElement): { [id: string]: Apollon.UMLElement } => { const children: UMLElement[] = UMLContainer.isUMLContainer(element) ? element.ownedElements.map((id) => elements[id]) : []; - return [ - element.serialize(children) as Apollon.UMLElement, - ...children - .reduce((acc, val) => [...acc, ...serialize(val)], []) - .map((val) => ({ - ...val, - bounds: { ...val.bounds, x: val.bounds.x + element.bounds.x, y: val.bounds.y + element.bounds.y }, - })), - ]; + const res = { + [element.id]: element.serialize(children) as Apollon.UMLElement, + } + + for (const child of children) { + const childres = serialize(child); + Object.values(childres).forEach((child) => { + child.bounds.x += element.bounds.x; + child.bounds.y += element.bounds.y; + }); + + Object.assign(res, childres); + } + + return res; }; - const apollonElements = Object.fromEntries( - Object.entries(elements) - .filter(([, element]) => !element.owner) - ) as {[id: string]: Apollon.UMLElement}; + const apollonElements = Object.values(elements) + .filter((element) => !element.owner) + .reduce((acc, element) => ({ ...acc, ...serialize(element) }), {}) as {[id: string]: Apollon.UMLElement}; + const apollonElementsArray = Object.values(apollonElements); const apollonRelationships: Apollon.UMLRelationship[] = relationships.map((relationship) => From 0aa30e2af704fd0b24f2eee6f8ec50d4e7c30dea Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Fri, 29 Sep 2023 10:01:50 +0200 Subject: [PATCH 03/10] remove fixme comment in svg (fixed) --- src/main/scenes/svg.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scenes/svg.tsx b/src/main/scenes/svg.tsx index 2d4448896..a2d7db750 100644 --- a/src/main/scenes/svg.tsx +++ b/src/main/scenes/svg.tsx @@ -21,7 +21,6 @@ import { ThemeProvider } from 'styled-components'; import { UMLClassifierComponent } from '../packages/common/uml-classifier/uml-classifier-component'; import { UMLClassifierMemberComponent } from '../packages/common/uml-classifier/uml-classifier-member-component'; -// FIXME: svg export ignores children? type Props = { model: Apollon.UMLModel; From ad8d17076baa0544a617341c8bb71dc0418783e7 Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Fri, 6 Oct 2023 07:55:33 +0200 Subject: [PATCH 04/10] switch schema for interactive from array to 'set' --- public/index.ts | 5 ++++- src/main/apollon-editor.ts | 21 ++++++++++++++++----- src/main/components/store/model-state.ts | 17 +++++++++++++---- src/main/typings.ts | 4 ++-- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/public/index.ts b/public/index.ts index 60a388b3f..87f2b445d 100644 --- a/public/index.ts +++ b/public/index.ts @@ -52,7 +52,10 @@ export const setTheming = (theming: string) => { export const draw = async (mode?: 'include' | 'exclude') => { if (!editor) return; - const filter: string[] = [...editor.model.interactive.elements, ...editor.model.interactive.relationships]; + const filter: string[] = [ + ...Object.entries(editor.model.interactive.elements).filter(([, value]) => value).map(([key]) => key), + ...Object.entries(editor.model.interactive.relationships).filter(([, value]) => value).map(([key]) => key), + ]; const exportParam = mode ? { [mode]: filter, scale: editor.getScaleFactor() } : { scale: editor.getScaleFactor() }; diff --git a/src/main/apollon-editor.ts b/src/main/apollon-editor.ts index ec9bf8856..5d40c9d06 100644 --- a/src/main/apollon-editor.ts +++ b/src/main/apollon-editor.ts @@ -105,7 +105,7 @@ export class ApollonEditor { }; } - selection: Apollon.Selection = { elements: [], relationships: [] }; + selection: Apollon.Selection = { elements: {}, relationships: {} }; private root?: Root; private currentModelState?: ModelState; private assessments: Apollon.Assessment[] = []; @@ -194,7 +194,12 @@ export class ApollonEditor { if (!this.store) return; const dispatch = this.store.dispatch as Dispatch; dispatch(UMLElementRepository.deselect()); - dispatch(UMLElementRepository.select([...selection.elements, ...selection.relationships])); + dispatch( + UMLElementRepository.select([ + ...Object.entries(selection.elements).filter(([, selected]) => selected).map(([id]) => id), + ...Object.entries(selection.relationships).filter(([, selected]) => selected).map(([id]) => id), + ]) + ); } _getNewSubscriptionId(subscribers: { [key: number]: any }): number { @@ -327,8 +332,14 @@ export class ApollonEditor { if (!this.store) return; const { elements, selected, assessments } = this.store.getState(); const selection: Apollon.Selection = { - elements: selected.filter((id) => elements[id].type in UMLElementType), - relationships: selected.filter((id) => elements[id].type in UMLRelationshipType), + elements: + selected + .filter((id) => elements[id].type in UMLElementType) + .reduce((acc, id) => ({ ...acc, [id]: true }), {}), + relationships: + selected + .filter((id) => elements[id].type in UMLRelationshipType) + .reduce((acc, id) => ({ ...acc, [id]: true }), {}) }; // check if previous selection differs from current selection, if yes -> notify subscribers @@ -372,7 +383,7 @@ export class ApollonEditor { this.store.getState().lastAction.endsWith('DELETE') ) { const lastModel = ModelState.toModel(this.store.getState()); - // eslint-disable-next-line no-console + // tslint:disable-next-line:no-console console.log(lastModel); Object.values(this.discreteModelSubscribers).forEach((subscriber) => subscriber(lastModel)); } diff --git a/src/main/components/store/model-state.ts b/src/main/components/store/model-state.ts index 04489e6b8..efa43b5f0 100644 --- a/src/main/components/store/model-state.ts +++ b/src/main/components/store/model-state.ts @@ -101,7 +101,10 @@ export class ModelState { return { diagram, - interactive: [...model.interactive.elements, ...model.interactive.relationships], + interactive: [ + ...Object.entries(model.interactive.elements).filter(([, value]) => value).map(([key]) => key), + ...Object.entries(model.interactive.relationships).filter(([, value]) => value).map(([key]) => key), + ], elements: [...elements, ...relationships].reduce((acc, val) => ({ ...acc, [val.id]: { ...val } }), {}), assessments: (Object.values(model.assessments) || []).reduce( (acc, val) => ({ @@ -136,7 +139,7 @@ export class ModelState { const res = { [element.id]: element.serialize(children) as Apollon.UMLElement, - } + }; for (const child of children) { const childres = serialize(child); @@ -176,8 +179,14 @@ export class ModelState { } const interactive: Apollon.Selection = { - elements: state.interactive.filter((id) => UMLElement.isUMLElement(state.elements[id])), - relationships: state.interactive.filter((id) => UMLRelationship.isUMLRelationship(state.elements[id])), + elements: + state.interactive + .filter((id) => UMLElement.isUMLElement(state.elements[id])) + .reduce((acc, val) => ({ ...acc, [val]: true }), {}), + relationships: + state.interactive + .filter((id) => UMLRelationship.isUMLRelationship(state.elements[id])) + .reduce((acc, val) => ({ ...acc, [val]: true }), {}) }; const assessments = Object.fromEntries( diff --git a/src/main/typings.ts b/src/main/typings.ts index e6f948c1f..e4b9d45aa 100644 --- a/src/main/typings.ts +++ b/src/main/typings.ts @@ -26,8 +26,8 @@ export type ApollonOptions = { }; export type Selection = { - elements: string[]; - relationships: string[]; + elements: { [id: string]: boolean }; + relationships: { [id: string]: boolean }; }; export type UMLModel = { From b2348cd1970b220398acaf072ae1852399c00f6c Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Fri, 6 Oct 2023 08:52:11 +0200 Subject: [PATCH 05/10] add backwards compatibility support --- src/main/compat/index.ts | 21 +++++++++++++++++++ src/main/compat/v2/index.ts | 2 ++ src/main/compat/v2/transform.ts | 17 ++++++++++++++++ src/main/compat/v2/typings.ts | 26 ++++++++++++++++++++++++ src/main/components/store/model-state.ts | 7 ++++--- src/main/typings.ts | 2 +- 6 files changed, 71 insertions(+), 4 deletions(-) create mode 100644 src/main/compat/index.ts create mode 100644 src/main/compat/v2/index.ts create mode 100644 src/main/compat/v2/transform.ts create mode 100644 src/main/compat/v2/typings.ts diff --git a/src/main/compat/index.ts b/src/main/compat/index.ts new file mode 100644 index 000000000..ddb7a59be --- /dev/null +++ b/src/main/compat/index.ts @@ -0,0 +1,21 @@ +import { UMLModel } from '../typings'; +import { isV2, v2ModeltoV3Model, UMLModelCompat as UMLModelCompatV2 } from './v2'; + +// +// when adding new versions with backward compatibility, +// this should be the union of all compat model types exported +// by the corresponding version module, i.e.: +// +// ```ts +// export type UMLModelCompat = UMLModelCompatV2 | UMLModelCompatV3; +// ``` +// +export type UMLModelCompat = UMLModelCompatV2; + +export function backwardsCompatibleModel(model: UMLModelCompat): UMLModel { + if (isV2(model)) { + return v2ModeltoV3Model(model); + } else { + return model; + } +} diff --git a/src/main/compat/v2/index.ts b/src/main/compat/v2/index.ts new file mode 100644 index 000000000..c4682c131 --- /dev/null +++ b/src/main/compat/v2/index.ts @@ -0,0 +1,2 @@ +export * from './typings'; +export * from './transform'; diff --git a/src/main/compat/v2/transform.ts b/src/main/compat/v2/transform.ts new file mode 100644 index 000000000..eddec5391 --- /dev/null +++ b/src/main/compat/v2/transform.ts @@ -0,0 +1,17 @@ +import { UMLModel } from '../../typings'; +import { UMLModelV2 } from './typings'; + + +export function v2ModeltoV3Model(model: UMLModelV2): UMLModel { + return { + ...model, + version: '3.0.0', + elements: model.elements.reduce((acc, val) => ({ ...acc, [val.id]: val }), {}), + relationships: model.relationships.reduce((acc, val) => ({ ...acc, [val.id]: val }), {}), + assessments: model.assessments.reduce((acc, val) => ({ ...acc, [val.modelElementId]: val }), {}), + interactive: { + elements: model.interactive.elements.reduce((acc, val) => ({ ...acc, [val]: true }), {}), + relationships: model.interactive.relationships.reduce((acc, val) => ({ ...acc, [val]: true }), {}), + }, + }; +} diff --git a/src/main/compat/v2/typings.ts b/src/main/compat/v2/typings.ts new file mode 100644 index 000000000..2dbbf947f --- /dev/null +++ b/src/main/compat/v2/typings.ts @@ -0,0 +1,26 @@ +import { UMLModel, UMLDiagramType, UMLElement, UMLRelationship, Assessment } from '../../typings'; + + +export type SelectionV2 = { + elements: string[], + relationships: string[], +}; + + +export type UMLModelV2 = { + version: `2.${number}.${number}`; + type: UMLDiagramType; + size: { width: number; height: number }; + elements: UMLElement[]; + interactive: SelectionV2; + relationships: UMLRelationship[]; + assessments: Assessment[]; +}; + + +export type UMLModelCompat = UMLModel | UMLModelV2; + + +export function isV2(model: UMLModelCompat): model is UMLModelV2 { + return model.version.startsWith('2.'); +} diff --git a/src/main/components/store/model-state.ts b/src/main/components/store/model-state.ts index efa43b5f0..957c36587 100644 --- a/src/main/components/store/model-state.ts +++ b/src/main/components/store/model-state.ts @@ -20,6 +20,7 @@ import { ReconnectableState } from '../../services/uml-relationship/reconnectabl import { IUMLRelationship, UMLRelationship } from '../../services/uml-relationship/uml-relationship'; import { UMLRelationshipRepository } from '../../services/uml-relationship/uml-relationship-repository'; import * as Apollon from '../../typings'; +import { backwardsCompatibleModel, UMLModelCompat } from '../../compat'; import { computeBoundingBoxForElements } from '../../utils/geometry/boundary'; import { UMLDiagram } from '../../services/uml-diagram/uml-diagram'; import { UMLDiagramType } from '../../typings'; @@ -45,11 +46,11 @@ export interface ModelState { lastAction: LastActionState; } -// TODO: fix this +// TODO: simplify this code. export class ModelState { - static fromModel(model: Apollon.UMLModel): PartialModelState { - // TODO: check for version, as it is different based on 2.0.0 vs 3.0.0 + static fromModel(compatModel: UMLModelCompat): PartialModelState { + const model = backwardsCompatibleModel(compatModel); const apollonElements = model.elements; const apollonRelationships = model.relationships; diff --git a/src/main/typings.ts b/src/main/typings.ts index e4b9d45aa..8496a9a5d 100644 --- a/src/main/typings.ts +++ b/src/main/typings.ts @@ -31,7 +31,7 @@ export type Selection = { }; export type UMLModel = { - version: string; + version: `3.${number}.${number}`; type: UMLDiagramType; size: { width: number; height: number }; elements: { [id: string]: UMLElement }; From 5d69f0271df1a3c48f568c62cd8a724749cd7105 Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Thu, 12 Oct 2023 10:44:18 +0200 Subject: [PATCH 06/10] update tests, add jsdoc, run prettier --- docs/source/user/api/typings.d.ts | 10 +-- src/main/apollon-editor.ts | 26 +++--- src/main/compat/index.ts | 34 +++++-- src/main/compat/v2/transform.ts | 9 +- src/main/compat/v2/typings.ts | 37 ++++++-- .../connectable/connection-preview.tsx | 7 +- src/main/components/store/model-state.ts | 49 ++++++----- src/main/components/store/util.ts | 27 ++++++ .../uml-element/movable/movable.tsx | 3 +- src/main/scenes/svg.tsx | 1 - src/main/services/editor/editor-repository.ts | 2 +- src/main/utils/clamp.ts | 2 +- src/tests/unit/apollon-editor-test.tsx | 29 +++--- .../unit/test-resources/class-diagram-v2.json | 88 +++++++++++++++++++ .../unit/test-resources/class-diagram.json | 30 +++---- 15 files changed, 262 insertions(+), 92 deletions(-) create mode 100644 src/main/components/store/util.ts create mode 100644 src/tests/unit/test-resources/class-diagram-v2.json diff --git a/docs/source/user/api/typings.d.ts b/docs/source/user/api/typings.d.ts index a9c8f0682..d23b1ebc8 100644 --- a/docs/source/user/api/typings.d.ts +++ b/docs/source/user/api/typings.d.ts @@ -23,8 +23,8 @@ export type ApollonOptions = { scale?: number; }; export type Selection = { - elements: string[]; - relationships: string[]; + elements: {[id: string]: boolean}; + relationships: {[id: string]: boolean}; }; export type UMLModel = { version: string; @@ -33,10 +33,10 @@ export type UMLModel = { width: number; height: number; }; - elements: UMLElement[]; + elements: {[id: string]: UMLElement}; interactive: Selection; - relationships: UMLRelationship[]; - assessments: Assessment[]; + relationships: {[id: string]: UMLRelationship}; + assessments: {[id: string]: Assessment}; }; export type UMLModelElementType = UMLElementType | UMLRelationshipType | UMLDiagramType; export type UMLModelElement = { diff --git a/src/main/apollon-editor.ts b/src/main/apollon-editor.ts index 5d40c9d06..37930e80e 100644 --- a/src/main/apollon-editor.ts +++ b/src/main/apollon-editor.ts @@ -196,9 +196,13 @@ export class ApollonEditor { dispatch(UMLElementRepository.deselect()); dispatch( UMLElementRepository.select([ - ...Object.entries(selection.elements).filter(([, selected]) => selected).map(([id]) => id), - ...Object.entries(selection.relationships).filter(([, selected]) => selected).map(([id]) => id), - ]) + ...Object.entries(selection.elements) + .filter(([, selected]) => selected) + .map(([id]) => id), + ...Object.entries(selection.relationships) + .filter(([, selected]) => selected) + .map(([id]) => id), + ]), ); } @@ -332,14 +336,12 @@ export class ApollonEditor { if (!this.store) return; const { elements, selected, assessments } = this.store.getState(); const selection: Apollon.Selection = { - elements: - selected - .filter((id) => elements[id].type in UMLElementType) - .reduce((acc, id) => ({ ...acc, [id]: true }), {}), - relationships: - selected - .filter((id) => elements[id].type in UMLRelationshipType) - .reduce((acc, id) => ({ ...acc, [id]: true }), {}) + elements: selected + .filter((id) => elements[id].type in UMLElementType) + .reduce((acc, id) => ({ ...acc, [id]: true }), {}), + relationships: selected + .filter((id) => elements[id].type in UMLRelationshipType) + .reduce((acc, id) => ({ ...acc, [id]: true }), {}), }; // check if previous selection differs from current selection, if yes -> notify subscribers @@ -383,8 +385,6 @@ export class ApollonEditor { this.store.getState().lastAction.endsWith('DELETE') ) { const lastModel = ModelState.toModel(this.store.getState()); - // tslint:disable-next-line:no-console - console.log(lastModel); Object.values(this.discreteModelSubscribers).forEach((subscriber) => subscriber(lastModel)); } } catch (error) { diff --git a/src/main/compat/index.ts b/src/main/compat/index.ts index ddb7a59be..9d4acdb2b 100644 --- a/src/main/compat/index.ts +++ b/src/main/compat/index.ts @@ -1,17 +1,33 @@ import { UMLModel } from '../typings'; import { isV2, v2ModeltoV3Model, UMLModelCompat as UMLModelCompatV2 } from './v2'; -// -// when adding new versions with backward compatibility, -// this should be the union of all compat model types exported -// by the corresponding version module, i.e.: -// -// ```ts -// export type UMLModelCompat = UMLModelCompatV2 | UMLModelCompatV3; -// ``` -// +/** + * + * Represents all model versions that can be converted to the latest version. + * + */ +/* + * + * HINT for future maintainers: + * + * this should always be the union of compatible model versions, i.e. + * if the model version is moved to V4, while support for V2 is still required, + * this should look like the following: + * + * ```ts + * export type UMLModelCompat = UMLModelCompatV2 | UMLModelCompatV3; + * ``` + */ export type UMLModelCompat = UMLModelCompatV2; +/** + * + * Converts a model to the latest version. + * + * @param {UMLModelCompat} model model to convert + * @returns {UMLModel} the converted model + * + */ export function backwardsCompatibleModel(model: UMLModelCompat): UMLModel { if (isV2(model)) { return v2ModeltoV3Model(model); diff --git a/src/main/compat/v2/transform.ts b/src/main/compat/v2/transform.ts index eddec5391..8851bcf76 100644 --- a/src/main/compat/v2/transform.ts +++ b/src/main/compat/v2/transform.ts @@ -1,7 +1,14 @@ import { UMLModel } from '../../typings'; import { UMLModelV2 } from './typings'; - +/** + * + * Converts a v2 model to a v3 model. + * + * @param {UMLModelV2} model model to convert + * @returns {UMLModel} the converted model + * + */ export function v2ModeltoV3Model(model: UMLModelV2): UMLModel { return { ...model, diff --git a/src/main/compat/v2/typings.ts b/src/main/compat/v2/typings.ts index 2dbbf947f..87f94e74b 100644 --- a/src/main/compat/v2/typings.ts +++ b/src/main/compat/v2/typings.ts @@ -1,12 +1,26 @@ import { UMLModel, UMLDiagramType, UMLElement, UMLRelationship, Assessment } from '../../typings'; - +/** + * + * Represents the selection type in V2 schema. + * + */ export type SelectionV2 = { - elements: string[], - relationships: string[], + elements: string[]; + relationships: string[]; }; - +/** + * + * Represents the V2 model. + * + * @todo This type definition reuses unchanged elements from latest model version. + * This is not ideal, as future maintainers modifying the latest model might make + * changes that would then need to be reflected here. Ideally, model definitions for + * different versions should be completely separate, though that results in a lot of + * code duplication. + * + */ export type UMLModelV2 = { version: `2.${number}.${number}`; type: UMLDiagramType; @@ -17,10 +31,21 @@ export type UMLModelV2 = { assessments: Assessment[]; }; - +/** + * + * Represents a model compatible with either V2 or latest version. + * + */ export type UMLModelCompat = UMLModel | UMLModelV2; - +/** + * + * Returns whether the given model is a V2 model. + * + * @param {UMLModelCompat} model model to check + * @returns {boolean} `true` if the model is a V2 model, `false` otherwise + * + */ export function isV2(model: UMLModelCompat): model is UMLModelV2 { return model.version.startsWith('2.'); } diff --git a/src/main/components/connectable/connection-preview.tsx b/src/main/components/connectable/connection-preview.tsx index 42a704143..54dc8a0db 100644 --- a/src/main/components/connectable/connection-preview.tsx +++ b/src/main/components/connectable/connection-preview.tsx @@ -96,10 +96,9 @@ class Preview extends Component { if (event instanceof PointerEvent) { position = new Point(event.clientX - offset.x, event.clientY - offset.y).scale(1 / zoomFactor); } else { - position = new Point( - event.targetTouches[0].clientX - offset.x, - event.targetTouches[0].clientY - offset.y, - ).scale(1 / zoomFactor); + position = new Point(event.targetTouches[0].clientX - offset.x, event.targetTouches[0].clientY - offset.y).scale( + 1 / zoomFactor, + ); } this.setState({ position }); diff --git a/src/main/components/store/model-state.ts b/src/main/components/store/model-state.ts index 957c36587..c3cc3e579 100644 --- a/src/main/components/store/model-state.ts +++ b/src/main/components/store/model-state.ts @@ -26,6 +26,7 @@ import { UMLDiagram } from '../../services/uml-diagram/uml-diagram'; import { UMLDiagramType } from '../../typings'; import { CopyState } from '../../services/copypaste/copy-types'; import { LastActionState } from '../../services/last-action/last-action-types'; +import { arrayToInclusionMap, inclusionMapToArray } from './util'; export type PartialModelState = Omit, 'editor'> & { editor?: Partial }; @@ -46,7 +47,9 @@ export interface ModelState { lastAction: LastActionState; } -// TODO: simplify this code. +// TODO: simplify this code, break it into smaller pieces. +// FIXME: this code has issues in various cases, including when +// the boundary of the diagram is determined by some relationship. export class ModelState { static fromModel(compatModel: UMLModelCompat): PartialModelState { @@ -103,8 +106,8 @@ export class ModelState { return { diagram, interactive: [ - ...Object.entries(model.interactive.elements).filter(([, value]) => value).map(([key]) => key), - ...Object.entries(model.interactive.relationships).filter(([, value]) => value).map(([key]) => key), + ...inclusionMapToArray(model.interactive.elements), + ...inclusionMapToArray(model.interactive.relationships), ], elements: [...elements, ...relationships].reduce((acc, val) => ({ ...acc, [val.id]: { ...val } }), {}), assessments: (Object.values(model.assessments) || []).reduce( @@ -155,11 +158,10 @@ export class ModelState { return res; }; - const apollonElements = Object.values(elements) .filter((element) => !element.owner) - .reduce((acc, element) => ({ ...acc, ...serialize(element) }), {}) as {[id: string]: Apollon.UMLElement}; - + .reduce((acc, element) => ({ ...acc, ...serialize(element) }), {}) as { [id: string]: Apollon.UMLElement }; + const apollonElementsArray = Object.values(apollonElements); const apollonRelationships: Apollon.UMLRelationship[] = relationships.map((relationship) => @@ -180,27 +182,26 @@ export class ModelState { } const interactive: Apollon.Selection = { - elements: - state.interactive - .filter((id) => UMLElement.isUMLElement(state.elements[id])) - .reduce((acc, val) => ({ ...acc, [val]: true }), {}), - relationships: - state.interactive - .filter((id) => UMLRelationship.isUMLRelationship(state.elements[id])) - .reduce((acc, val) => ({ ...acc, [val]: true }), {}) + elements: arrayToInclusionMap(state.interactive.filter((id) => UMLElement.isUMLElement(state.elements[id]))), + relationships: arrayToInclusionMap( + state.interactive.filter((id) => UMLRelationship.isUMLRelationship(state.elements[id])), + ), }; const assessments = Object.fromEntries( - Object.entries(state.assessments).map(([id, assessment]) => [id, { - modelElementId: id, - elementType: state.elements[id].type as UMLElementType | UMLRelationshipType, - score: state.assessments[id].score, - feedback: state.assessments[id].feedback, - label: state.assessments[id].label, - labelColor: state.assessments[id].labelColor, - correctionStatus: state.assessments[id].correctionStatus, - dropInfo: state.assessments[id].dropInfo, - }]) + Object.entries(state.assessments).map(([id, assessment]) => [ + id, + { + modelElementId: id, + elementType: state.elements[id].type as UMLElementType | UMLRelationshipType, + score: state.assessments[id].score, + feedback: state.assessments[id].feedback, + label: state.assessments[id].label, + labelColor: state.assessments[id].labelColor, + correctionStatus: state.assessments[id].correctionStatus, + dropInfo: state.assessments[id].dropInfo, + }, + ]), ); return { diff --git a/src/main/components/store/util.ts b/src/main/components/store/util.ts new file mode 100644 index 000000000..faa97cd94 --- /dev/null +++ b/src/main/components/store/util.ts @@ -0,0 +1,27 @@ +/** + * + * converts an inclusion map of strings to an array of strings. + * inclusion maps emulate a set of strings (e.g. identifiers). + * + * @param {{[id: string]: boolean}} map - an inclusion map for strings + * @returns {string[]} - an array of all included strings + * + */ +export function inclusionMapToArray(map: Record): string[] { + return Object.entries(map) + .filter(([, value]) => value) + .map(([key]) => key); +} + +/** + * + * converts an array of strings to an inclusion map of strings. + * inclusion maps emulate a set of strings (e.g. identifiers). + * + * @param {string[]} array - an array of strings + * @returns {{[id: string]: boolean}} - an inclusion map for all strings in the array + * + */ +export function arrayToInclusionMap(array: string[]): Record { + return array.reduce>((acc, val) => ({ ...acc, [val]: true }), {}); +} diff --git a/src/main/components/uml-element/movable/movable.tsx b/src/main/components/uml-element/movable/movable.tsx index d0959e5b5..bc67f38c6 100644 --- a/src/main/components/uml-element/movable/movable.tsx +++ b/src/main/components/uml-element/movable/movable.tsx @@ -51,8 +51,7 @@ export const movable = ( moveWindow: { x: number; y: number } = { x: 0, y: 0 }; move = (x: number, y: number) => { - - const {zoomFactor = 1} = this.props; + const { zoomFactor = 1 } = this.props; x = Math.round(x / 10) * 10; y = Math.round(y / 10) * 10; diff --git a/src/main/scenes/svg.tsx b/src/main/scenes/svg.tsx index a2d7db750..a6c331067 100644 --- a/src/main/scenes/svg.tsx +++ b/src/main/scenes/svg.tsx @@ -21,7 +21,6 @@ import { ThemeProvider } from 'styled-components'; import { UMLClassifierComponent } from '../packages/common/uml-classifier/uml-classifier-component'; import { UMLClassifierMemberComponent } from '../packages/common/uml-classifier/uml-classifier-member-component'; - type Props = { model: Apollon.UMLModel; options?: Apollon.ExportOptions; diff --git a/src/main/services/editor/editor-repository.ts b/src/main/services/editor/editor-repository.ts index 880b6dacd..274f9826b 100644 --- a/src/main/services/editor/editor-repository.ts +++ b/src/main/services/editor/editor-repository.ts @@ -1,4 +1,4 @@ -import {ApollonView, ChangeViewAction, ChangeZoomFactorAction, EditorActionTypes} from './editor-types'; +import { ApollonView, ChangeViewAction, ChangeZoomFactorAction, EditorActionTypes } from './editor-types'; export class EditorRepository { static changeView = (view: ApollonView): ChangeViewAction => ({ diff --git a/src/main/utils/clamp.ts b/src/main/utils/clamp.ts index 1c982590c..844c8f474 100644 --- a/src/main/utils/clamp.ts +++ b/src/main/utils/clamp.ts @@ -6,4 +6,4 @@ */ export const clamp = (value: number, min: number, max: number): number => { return Math.max(min, Math.min(value, max)); -}; \ No newline at end of file +}; diff --git a/src/tests/unit/apollon-editor-test.tsx b/src/tests/unit/apollon-editor-test.tsx index 461da5715..9d13f1454 100644 --- a/src/tests/unit/apollon-editor-test.tsx +++ b/src/tests/unit/apollon-editor-test.tsx @@ -4,6 +4,7 @@ import { act, render as testLibraryRender } from '@testing-library/react'; import * as Apollon from '../../main/apollon-editor'; import * as ApollonTypes from '../../main/typings'; import testClassDiagram from './test-resources/class-diagram.json'; +import testClassDiagramV2 from './test-resources/class-diagram-v2.json'; import { Selection } from '../../../docs/source/user/api/typings'; import { Assessment, SVG, UMLDiagramType, UMLModel } from '../../main'; import { getRealStore } from './test-utils/test-utils'; @@ -12,6 +13,7 @@ import { IAssessment } from '../../main/services/assessment/assessment'; import { ModelState } from '../../main/components/store/model-state'; import { UMLElementCommonRepository } from '../../main/services/uml-element/uml-element-common-repository'; import { UMLClass } from '../../main/packages/uml-class-diagram/uml-class/uml-class'; +import { arrayToInclusionMap } from '../../main/components/store/util'; import fn = jest.fn; let editor = {} as Apollon.ApollonEditor; @@ -65,6 +67,13 @@ describe('test apollon editor ', () => { expect(testClassDiagram).toEqual(editor.model); }); + it('get and set v2 model', () => { + act(() => { + editor.model = testClassDiagramV2 as any; + }); + + expect(testClassDiagram).toEqual(editor.model); + }); it('exportModelAsSvg', async () => { const svg: ApollonTypes.SVG = await editor.exportAsSVG(); expect(ignoreSVGClassNames(svg.svg)).toEqual(testClassDiagramAsSVG); @@ -72,8 +81,8 @@ describe('test apollon editor ', () => { it('subscribeToSelection', async () => { const selection: Selection = { - elements: testClassDiagram.elements.map((element) => element.id), - relationships: testClassDiagram.relationships.map((relationship) => relationship.id), + elements: arrayToInclusionMap(Object.keys(testClassDiagram.elements)), + relationships: arrayToInclusionMap(Object.keys(testClassDiagram.relationships)), }; const selectionCallback = (s: Selection) => { return expect(s).toEqual(selection); @@ -85,8 +94,8 @@ describe('test apollon editor ', () => { }); it('unsubscribeFromSelection', async () => { const selection: Selection = { - elements: testClassDiagram.elements.map((element) => element.id), - relationships: testClassDiagram.relationships.map((relationship) => relationship.id), + elements: arrayToInclusionMap(Object.keys(testClassDiagram.elements)), + relationships: arrayToInclusionMap(Object.keys(testClassDiagram.relationships)), }; const selectionCallback = fn((s: Selection) => {}); @@ -99,7 +108,7 @@ describe('test apollon editor ', () => { // unsubscribe and call select again editor.unsubscribeFromSelectionChange(selectionSubscription); act(() => { - editor.select({ elements: [], relationships: [] }); + editor.select({ elements: {}, relationships: {} }); }); setTimeout(() => { expect(selectionCallback).toBeCalledTimes(1); @@ -111,8 +120,8 @@ describe('test apollon editor ', () => { // this validates that the timing is enough so that selection callback would be called twice // it is still possible that callback would be called twice in unsubscribeFromSelection and not in unsubscribeFromSelectionValidation, but less likely const selection: Selection = { - elements: testClassDiagram.elements.map((element) => element.id), - relationships: testClassDiagram.relationships.map((relationship) => relationship.id), + elements: arrayToInclusionMap(Object.keys(testClassDiagram.elements)), + relationships: arrayToInclusionMap(Object.keys(testClassDiagram.relationships)), }; const selectionCallback = fn((s: Selection) => {}); @@ -125,7 +134,7 @@ describe('test apollon editor ', () => { setTimeout(() => { // just select again act(() => { - editor.select({ elements: [], relationships: [] }); + editor.select({ elements: {}, relationships: {} }); }); setTimeout(() => { // should be called twice @@ -259,7 +268,7 @@ describe('test apollon editor ', () => { const modelChangedCallback = (model: UMLModel) => { // created element should be included in model - return expect(model.elements.filter((e) => e.id === element.id)).toHaveLength(1); + return expect(model.elements[element.id]).toBeDefined(); }; // subscribe to model changes editor.subscribeToModelChange(modelChangedCallback); @@ -340,7 +349,7 @@ describe('test apollon editor ', () => { const modelChangedCallback = (model: UMLModel) => { // created element should be included in model - return expect(model.elements.filter((e) => e.id === element.id)).toHaveLength(1); + return expect(model.elements[element.id]).toBeDefined(); }; // subscribe to model changes editor.subscribeToModelDiscreteChange(modelChangedCallback); diff --git a/src/tests/unit/test-resources/class-diagram-v2.json b/src/tests/unit/test-resources/class-diagram-v2.json new file mode 100644 index 000000000..31219a36f --- /dev/null +++ b/src/tests/unit/test-resources/class-diagram-v2.json @@ -0,0 +1,88 @@ +{ + "version": "2.0.0", + "type": "ClassDiagram", + "size": { "width": 860, "height": 260 }, + "interactive": { "elements": [], "relationships": [] }, + "elements": [ + { + "id": "c10b995a-036c-4e9e-aa67-0570ada5cb6a", + "name": "Package", + "type": "Package", + "owner": null, + "bounds": { "x": 0, "y": 0, "width": 350, "height": 220 } + }, + { + "id": "04d3509e-0dce-458b-bf62-f3555497a5a4", + "name": "Class", + "type": "Class", + "owner": "c10b995a-036c-4e9e-aa67-0570ada5cb6a", + "bounds": { "x": 80, "y": 70, "width": 200, "height": 100 }, + "attributes": ["90f94404-1fc6-4121-97ed-6b2c6d57d525"], + "methods": ["12d8bb82-e4e5-4505-a0eb-48ddab0fc0a0"] + }, + { + "id": "90f94404-1fc6-4121-97ed-6b2c6d57d525", + "name": "+ attribute: Type", + "type": "ClassAttribute", + "owner": "04d3509e-0dce-458b-bf62-f3555497a5a4", + "bounds": { "x": 80, "y": 110, "width": 200, "height": 30 } + }, + { + "id": "12d8bb82-e4e5-4505-a0eb-48ddab0fc0a0", + "name": "+ method()", + "type": "ClassMethod", + "owner": "04d3509e-0dce-458b-bf62-f3555497a5a4", + "bounds": { "x": 80, "y": 140, "width": 200, "height": 30 } + }, + { + "id": "9eadc4f6-caa0-4835-a24c-71c0c1ccbc39", + "name": "Class", + "type": "Class", + "owner": null, + "bounds": { "x": 620, "y": 90, "width": 200, "height": 100 }, + "attributes": ["dbd4193a-4483-43df-8934-77192be006c2"], + "methods": ["e7ef41ee-290e-4df2-a535-199f1c5521fd"] + }, + { + "id": "dbd4193a-4483-43df-8934-77192be006c2", + "name": "+ attribute: Type", + "type": "ClassAttribute", + "owner": "9eadc4f6-caa0-4835-a24c-71c0c1ccbc39", + "bounds": { "x": 620, "y": 130, "width": 200, "height": 30 } + }, + { + "id": "e7ef41ee-290e-4df2-a535-199f1c5521fd", + "name": "+ method()", + "type": "ClassMethod", + "owner": "9eadc4f6-caa0-4835-a24c-71c0c1ccbc39", + "bounds": { "x": 620, "y": 160, "width": 200, "height": 30 } + } + ], + "relationships": [ + { + "id": "f5c4e20d-8347-4136-bc02-b7a016e017f5", + "name": "", + "type": "ClassBidirectional", + "owner": null, + "bounds": { "x": 280, "y": 130, "width": 340, "height": 1 }, + "path": [ + { "x": 340, "y": 0 }, + { "x": 0, "y": 0 } + ], + "source": { + "direction": "Left", + "element": "9eadc4f6-caa0-4835-a24c-71c0c1ccbc39", + "multiplicity": "", + "role": "" + }, + "target": { + "direction": "Right", + "element": "04d3509e-0dce-458b-bf62-f3555497a5a4", + "multiplicity": "", + "role": "" + }, + "isManuallyLayouted": false + } + ], + "assessments": [] +} diff --git a/src/tests/unit/test-resources/class-diagram.json b/src/tests/unit/test-resources/class-diagram.json index 31219a36f..a417921e0 100644 --- a/src/tests/unit/test-resources/class-diagram.json +++ b/src/tests/unit/test-resources/class-diagram.json @@ -1,17 +1,17 @@ { - "version": "2.0.0", + "version": "3.0.0", "type": "ClassDiagram", "size": { "width": 860, "height": 260 }, - "interactive": { "elements": [], "relationships": [] }, - "elements": [ - { + "interactive": { "elements": {}, "relationships": {} }, + "elements": { + "c10b995a-036c-4e9e-aa67-0570ada5cb6a": { "id": "c10b995a-036c-4e9e-aa67-0570ada5cb6a", "name": "Package", "type": "Package", "owner": null, "bounds": { "x": 0, "y": 0, "width": 350, "height": 220 } }, - { + "04d3509e-0dce-458b-bf62-f3555497a5a4": { "id": "04d3509e-0dce-458b-bf62-f3555497a5a4", "name": "Class", "type": "Class", @@ -20,21 +20,21 @@ "attributes": ["90f94404-1fc6-4121-97ed-6b2c6d57d525"], "methods": ["12d8bb82-e4e5-4505-a0eb-48ddab0fc0a0"] }, - { + "90f94404-1fc6-4121-97ed-6b2c6d57d525": { "id": "90f94404-1fc6-4121-97ed-6b2c6d57d525", "name": "+ attribute: Type", "type": "ClassAttribute", "owner": "04d3509e-0dce-458b-bf62-f3555497a5a4", "bounds": { "x": 80, "y": 110, "width": 200, "height": 30 } }, - { + "12d8bb82-e4e5-4505-a0eb-48ddab0fc0a0": { "id": "12d8bb82-e4e5-4505-a0eb-48ddab0fc0a0", "name": "+ method()", "type": "ClassMethod", "owner": "04d3509e-0dce-458b-bf62-f3555497a5a4", "bounds": { "x": 80, "y": 140, "width": 200, "height": 30 } }, - { + "9eadc4f6-caa0-4835-a24c-71c0c1ccbc39": { "id": "9eadc4f6-caa0-4835-a24c-71c0c1ccbc39", "name": "Class", "type": "Class", @@ -43,23 +43,23 @@ "attributes": ["dbd4193a-4483-43df-8934-77192be006c2"], "methods": ["e7ef41ee-290e-4df2-a535-199f1c5521fd"] }, - { + "dbd4193a-4483-43df-8934-77192be006c2": { "id": "dbd4193a-4483-43df-8934-77192be006c2", "name": "+ attribute: Type", "type": "ClassAttribute", "owner": "9eadc4f6-caa0-4835-a24c-71c0c1ccbc39", "bounds": { "x": 620, "y": 130, "width": 200, "height": 30 } }, - { + "e7ef41ee-290e-4df2-a535-199f1c5521fd": { "id": "e7ef41ee-290e-4df2-a535-199f1c5521fd", "name": "+ method()", "type": "ClassMethod", "owner": "9eadc4f6-caa0-4835-a24c-71c0c1ccbc39", "bounds": { "x": 620, "y": 160, "width": 200, "height": 30 } } - ], - "relationships": [ - { + }, + "relationships": { + "f5c4e20d-8347-4136-bc02-b7a016e017f5": { "id": "f5c4e20d-8347-4136-bc02-b7a016e017f5", "name": "", "type": "ClassBidirectional", @@ -83,6 +83,6 @@ }, "isManuallyLayouted": false } - ], - "assessments": [] + }, + "assessments": {} } From 01daa2cfce95a6d687533cbb0273ef206c63b197 Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Thu, 12 Oct 2023 11:22:37 +0200 Subject: [PATCH 07/10] remove unnecessary todo --- src/main/scenes/svg.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/scenes/svg.tsx b/src/main/scenes/svg.tsx index a6c331067..62c641d7c 100644 --- a/src/main/scenes/svg.tsx +++ b/src/main/scenes/svg.tsx @@ -81,8 +81,6 @@ const getInitialState = ({ model, options }: Props): State => { const apollonElements = model.elements; const apollonRelationships = model.relationships; - // TODO: deserialize based on version. the following code is for 3.0.0, write for 2.0.0 as well. - // TODO: optimize this code. const deserialize = (apollonElement: Apollon.UMLElement): UMLElement[] => { const element = new UMLElements[apollonElement.type](); From d372c1c791f5b481799410300cfc40c7d9dc8b9f Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Thu, 12 Oct 2023 12:40:58 +0200 Subject: [PATCH 08/10] prettier --- src/main/scenes/svg.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/scenes/svg.tsx b/src/main/scenes/svg.tsx index 62c641d7c..450af1d13 100644 --- a/src/main/scenes/svg.tsx +++ b/src/main/scenes/svg.tsx @@ -81,7 +81,6 @@ const getInitialState = ({ model, options }: Props): State => { const apollonElements = model.elements; const apollonRelationships = model.relationships; - const deserialize = (apollonElement: Apollon.UMLElement): UMLElement[] => { const element = new UMLElements[apollonElement.type](); const apollonChildren: Apollon.UMLElement[] = UMLContainer.isUMLContainer(element) From 086bd34d57548576ad8b8718216e4863bbd282cf Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Sun, 15 Oct 2023 17:45:03 +0200 Subject: [PATCH 09/10] fix communication link schema --- src/main/apollon-editor.ts | 3 + src/main/compat/v2/transform.ts | 17 ++- src/main/compat/v2/typings.ts | 31 +++- .../uml-communication-link.ts | 4 +- src/main/typings.ts | 10 +- src/tests/unit/apollon-editor-test.tsx | 19 +++ .../communication-diagram-v2.json | 138 ++++++++++++++++++ .../test-resources/communication-diagram.json | 138 ++++++++++++++++++ 8 files changed, 349 insertions(+), 11 deletions(-) create mode 100644 src/tests/unit/test-resources/communication-diagram-v2.json create mode 100644 src/tests/unit/test-resources/communication-diagram.json diff --git a/src/main/apollon-editor.ts b/src/main/apollon-editor.ts index 37930e80e..9d5abae38 100644 --- a/src/main/apollon-editor.ts +++ b/src/main/apollon-editor.ts @@ -1,3 +1,5 @@ +// tslint:disable: no-console + import 'pepjs'; import { createElement } from 'react'; import { createRoot, Root } from 'react-dom/client'; @@ -385,6 +387,7 @@ export class ApollonEditor { this.store.getState().lastAction.endsWith('DELETE') ) { const lastModel = ModelState.toModel(this.store.getState()); + console.log(lastModel); Object.values(this.discreteModelSubscribers).forEach((subscriber) => subscriber(lastModel)); } } catch (error) { diff --git a/src/main/compat/v2/transform.ts b/src/main/compat/v2/transform.ts index 8851bcf76..1a1084a0a 100644 --- a/src/main/compat/v2/transform.ts +++ b/src/main/compat/v2/transform.ts @@ -1,5 +1,5 @@ -import { UMLModel } from '../../typings'; -import { UMLModelV2 } from './typings'; +import { UMLCommunicationLink, UMLModel, UMLRelationshipType } from '../../typings'; +import { UMLModelV2, isCommunicationLink } from './typings'; /** * @@ -14,7 +14,18 @@ export function v2ModeltoV3Model(model: UMLModelV2): UMLModel { ...model, version: '3.0.0', elements: model.elements.reduce((acc, val) => ({ ...acc, [val.id]: val }), {}), - relationships: model.relationships.reduce((acc, val) => ({ ...acc, [val.id]: val }), {}), + relationships: model.relationships + .map((relationship) => { + if (isCommunicationLink(relationship)) { + return { + ...relationship, + messages: relationship.messages.reduce((acc, val) => ({ ...acc, [val.id]: val }), {}), + } as UMLCommunicationLink; + } else { + return relationship; + } + }) + .reduce((acc, val) => ({ ...acc, [val.id]: val }), {}), assessments: model.assessments.reduce((acc, val) => ({ ...acc, [val.modelElementId]: val }), {}), interactive: { elements: model.interactive.elements.reduce((acc, val) => ({ ...acc, [val]: true }), {}), diff --git a/src/main/compat/v2/typings.ts b/src/main/compat/v2/typings.ts index 87f94e74b..72bc1f4f2 100644 --- a/src/main/compat/v2/typings.ts +++ b/src/main/compat/v2/typings.ts @@ -1,4 +1,18 @@ -import { UMLModel, UMLDiagramType, UMLElement, UMLRelationship, Assessment } from '../../typings'; +import { UMLModel, UMLDiagramType, UMLElement, UMLRelationship, Assessment, UMLCommunicationLink, UMLRelationshipType } from '../../typings'; + + +/** + * + * Represents a communication link in V2 schema. + * + */ +export type UMLCommunicationLinkV2 = UMLRelationship & { + messages: { + id: string; + name: string; + direction: 'source' | 'target'; + }[]; +}; /** * @@ -27,7 +41,7 @@ export type UMLModelV2 = { size: { width: number; height: number }; elements: UMLElement[]; interactive: SelectionV2; - relationships: UMLRelationship[]; + relationships: (UMLRelationship | UMLCommunicationLinkV2)[]; assessments: Assessment[]; }; @@ -49,3 +63,16 @@ export type UMLModelCompat = UMLModel | UMLModelV2; export function isV2(model: UMLModelCompat): model is UMLModelV2 { return model.version.startsWith('2.'); } + + +/** + * + * Returns whether given relationship is a communication link in v2 schema. + * + * @param {UMLRelationship} rel relationship to check + * @returns {boolean} `true` if the relationship is a communication link, `false` otherwise. + * + */ +export function isCommunicationLink(rel: UMLRelationship): rel is UMLCommunicationLinkV2 { + return rel.type === UMLRelationshipType.CommunicationLink; +} diff --git a/src/main/packages/uml-communication-diagram/uml-communication-link/uml-communication-link.ts b/src/main/packages/uml-communication-diagram/uml-communication-link/uml-communication-link.ts index 4e942ccf0..11283dda8 100644 --- a/src/main/packages/uml-communication-diagram/uml-communication-link/uml-communication-link.ts +++ b/src/main/packages/uml-communication-diagram/uml-communication-link/uml-communication-link.ts @@ -28,7 +28,7 @@ export class UMLCommunicationLink extends UMLRelationship implements IUMLCommuni serialize(): Apollon.UMLCommunicationLink { return { ...super.serialize(), - messages: this.messages, + messages: this.messages.reduce((acc, message) => ({ ...acc, [message.id]: message }), {}), }; } @@ -40,7 +40,7 @@ export class UMLCommunicationLink extends UMLRelationship implements IUMLCommuni } super.deserialize(values, children); - this.messages = values.messages.map((message) => new CommunicationLinkMessage(message)); + this.messages = Object.values(values.messages).map((message) => new CommunicationLinkMessage(message)); } render(canvas: ILayer, source?: UMLElement, target?: UMLElement): ILayoutable[] { diff --git a/src/main/typings.ts b/src/main/typings.ts index 8496a9a5d..0f2261acc 100644 --- a/src/main/typings.ts +++ b/src/main/typings.ts @@ -105,10 +105,12 @@ export type UMLAssociation = UMLRelationship & { export type UMLCommunicationLink = UMLRelationship & { messages: { - id: string; - name: string; - direction: 'source' | 'target'; - }[]; + [id: string]: { + id: string; + name: string; + direction: 'source' | 'target'; + }; + }; }; export type FeedbackCorrectionStatus = { diff --git a/src/tests/unit/apollon-editor-test.tsx b/src/tests/unit/apollon-editor-test.tsx index 9d13f1454..9143a77fe 100644 --- a/src/tests/unit/apollon-editor-test.tsx +++ b/src/tests/unit/apollon-editor-test.tsx @@ -5,6 +5,8 @@ import * as Apollon from '../../main/apollon-editor'; import * as ApollonTypes from '../../main/typings'; import testClassDiagram from './test-resources/class-diagram.json'; import testClassDiagramV2 from './test-resources/class-diagram-v2.json'; +import testCommunicationDiagram from './test-resources/communication-diagram.json' +import testCommunicationDiagramV2 from './test-resources/communication-diagram-v2.json' import { Selection } from '../../../docs/source/user/api/typings'; import { Assessment, SVG, UMLDiagramType, UMLModel } from '../../main'; import { getRealStore } from './test-utils/test-utils'; @@ -74,6 +76,23 @@ describe('test apollon editor ', () => { expect(testClassDiagram).toEqual(editor.model); }); + + it('get and set communication diagram.', () => { + act(() => { + editor.model = testCommunicationDiagram as any; + }); + + expect(testCommunicationDiagram).toEqual(editor.model); + }); + + it('get and set communication diagram v2.', () => { + act(() => { + editor.model = testCommunicationDiagramV2 as any; + }); + + expect(testCommunicationDiagram).toEqual(editor.model); + }); + it('exportModelAsSvg', async () => { const svg: ApollonTypes.SVG = await editor.exportAsSVG(); expect(ignoreSVGClassNames(svg.svg)).toEqual(testClassDiagramAsSVG); diff --git a/src/tests/unit/test-resources/communication-diagram-v2.json b/src/tests/unit/test-resources/communication-diagram-v2.json new file mode 100644 index 000000000..e7252f712 --- /dev/null +++ b/src/tests/unit/test-resources/communication-diagram-v2.json @@ -0,0 +1,138 @@ +{ + "version": "2.0.0", + "type": "CommunicationDiagram", + "size": { + "width": 380, + "height": 240 + }, + "interactive": { + "elements": [], + "relationships": [] + }, + "elements": [ + { + "id": "17015424-a50b-4333-82b5-fdb3e272bced", + "name": "Object", + "type": "ObjectName", + "owner": null, + "bounds": { + "x": 0, + "y": 0, + "width": 160, + "height": 50 + }, + "attributes": [ + "fbc0789f-4f3b-4297-b190-82b271ec3299" + ], + "methods": [] + }, + { + "id": "fbc0789f-4f3b-4297-b190-82b271ec3299", + "name": "+ attribute: Type", + "type": "ObjectAttribute", + "owner": "17015424-a50b-4333-82b5-fdb3e272bced", + "bounds": { + "x": 0.5, + "y": 30.5, + "width": 159, + "height": 20 + } + }, + { + "id": "b5d8e7d3-e83a-48b0-a130-02aca060c174", + "name": "Object", + "type": "ObjectName", + "owner": null, + "bounds": { + "x": 170, + "y": 110, + "width": 160, + "height": 50 + }, + "attributes": [ + "26ba3925-cdec-44cc-9d75-1fb913855f24" + ], + "methods": [] + }, + { + "id": "26ba3925-cdec-44cc-9d75-1fb913855f24", + "name": "+ attribute: Type", + "type": "ObjectAttribute", + "owner": "b5d8e7d3-e83a-48b0-a130-02aca060c174", + "bounds": { + "x": 170.5, + "y": 140.5, + "width": 159, + "height": 20 + } + } + ], + "relationships": [ + { + "id": "d582f6af-82a3-4640-831c-7214b9e78194", + "name": "", + "type": "CommunicationLink", + "owner": null, + "bounds": { + "x": 70.41015625, + "y": 50, + "width": 99.58984375, + "height": 132.5 + }, + "path": [ + { + "x": 9.58984375, + "y": 0 + }, + { + "x": 9.58984375, + "y": 85 + }, + { + "x": 99.58984375, + "y": 85 + } + ], + "source": { + "direction": "Down", + "element": "17015424-a50b-4333-82b5-fdb3e272bced" + }, + "target": { + "direction": "Left", + "element": "b5d8e7d3-e83a-48b0-a130-02aca060c174" + }, + "isManuallyLayouted": false, + "messages": [ + { + "id": "75beeac0-c2da-44aa-8268-53a4bfd52de0", + "name": "halo", + "bounds": { + "x": 0, + "y": 66.5, + "width": 24.1796875, + "height": 14.5 + }, + "owner": null, + "resizeFrom": "bottomRight", + "direction": "source", + "type": "CommunicationLinkMessage" + }, + { + "id": "47ca1330-1cfe-46d9-aed3-f3297f83825d", + "name": "hola", + "bounds": { + "x": 0, + "y": 118, + "width": 24.1796875, + "height": 14.5 + }, + "owner": null, + "resizeFrom": "bottomRight", + "direction": "target", + "type": "CommunicationLinkMessage" + } + ] + } + ], + "assessments": [] +} \ No newline at end of file diff --git a/src/tests/unit/test-resources/communication-diagram.json b/src/tests/unit/test-resources/communication-diagram.json new file mode 100644 index 000000000..bb6e9ec72 --- /dev/null +++ b/src/tests/unit/test-resources/communication-diagram.json @@ -0,0 +1,138 @@ +{ + "version": "3.0.0", + "type": "CommunicationDiagram", + "size": { + "width": 380, + "height": 240 + }, + "interactive": { + "elements": {}, + "relationships": {} + }, + "elements": { + "17015424-a50b-4333-82b5-fdb3e272bced": { + "id": "17015424-a50b-4333-82b5-fdb3e272bced", + "name": "Object", + "type": "ObjectName", + "owner": null, + "bounds": { + "x": 0, + "y": 0, + "width": 160, + "height": 50 + }, + "attributes": [ + "fbc0789f-4f3b-4297-b190-82b271ec3299" + ], + "methods": [] + }, + "fbc0789f-4f3b-4297-b190-82b271ec3299": { + "id": "fbc0789f-4f3b-4297-b190-82b271ec3299", + "name": "+ attribute: Type", + "type": "ObjectAttribute", + "owner": "17015424-a50b-4333-82b5-fdb3e272bced", + "bounds": { + "x": 0.5, + "y": 30.5, + "width": 159, + "height": 20 + } + }, + "b5d8e7d3-e83a-48b0-a130-02aca060c174": { + "id": "b5d8e7d3-e83a-48b0-a130-02aca060c174", + "name": "Object", + "type": "ObjectName", + "owner": null, + "bounds": { + "x": 170, + "y": 110, + "width": 160, + "height": 50 + }, + "attributes": [ + "26ba3925-cdec-44cc-9d75-1fb913855f24" + ], + "methods": [] + }, + "26ba3925-cdec-44cc-9d75-1fb913855f24": { + "id": "26ba3925-cdec-44cc-9d75-1fb913855f24", + "name": "+ attribute: Type", + "type": "ObjectAttribute", + "owner": "b5d8e7d3-e83a-48b0-a130-02aca060c174", + "bounds": { + "x": 170.5, + "y": 140.5, + "width": 159, + "height": 20 + } + } + }, + "relationships": { + "d582f6af-82a3-4640-831c-7214b9e78194": { + "id": "d582f6af-82a3-4640-831c-7214b9e78194", + "name": "", + "type": "CommunicationLink", + "owner": null, + "bounds": { + "x": 70.41015625, + "y": 50, + "width": 99.58984375, + "height": 132.5 + }, + "path": [ + { + "x": 9.58984375, + "y": 0 + }, + { + "x": 9.58984375, + "y": 85 + }, + { + "x": 99.58984375, + "y": 85 + } + ], + "source": { + "direction": "Down", + "element": "17015424-a50b-4333-82b5-fdb3e272bced" + }, + "target": { + "direction": "Left", + "element": "b5d8e7d3-e83a-48b0-a130-02aca060c174" + }, + "isManuallyLayouted": false, + "messages": { + "75beeac0-c2da-44aa-8268-53a4bfd52de0": { + "id": "75beeac0-c2da-44aa-8268-53a4bfd52de0", + "name": "halo", + "bounds": { + "x": 0, + "y": 66.5, + "width": 24.1796875, + "height": 14.5 + }, + "owner": null, + "resizeFrom": "bottomRight", + "direction": "source", + "type": "CommunicationLinkMessage" + }, + "47ca1330-1cfe-46d9-aed3-f3297f83825d": { + "id": "47ca1330-1cfe-46d9-aed3-f3297f83825d", + "name": "hola", + "bounds": { + "x": 0, + "y": 118, + "width": 24.1796875, + "height": 14.5 + }, + "owner": null, + "resizeFrom": "bottomRight", + "direction": "target", + "type": "CommunicationLinkMessage" + } + } + } + }, + "assessments": {} +} \ No newline at end of file From 50c70cb48924aaf0a8b481dc746e6836ce3b0f05 Mon Sep 17 00:00:00 2001 From: Eugene Ghanizadeh Khoub Date: Sun, 15 Oct 2023 17:51:23 +0200 Subject: [PATCH 10/10] fix formatting --- src/main/compat/v2/typings.ts | 22 +- src/tests/unit/apollon-editor-test.tsx | 4 +- .../communication-diagram-v2.json | 236 +++++++++--------- .../test-resources/communication-diagram.json | 234 +++++++++-------- 4 files changed, 247 insertions(+), 249 deletions(-) diff --git a/src/main/compat/v2/typings.ts b/src/main/compat/v2/typings.ts index 72bc1f4f2..90cf33c32 100644 --- a/src/main/compat/v2/typings.ts +++ b/src/main/compat/v2/typings.ts @@ -1,10 +1,17 @@ -import { UMLModel, UMLDiagramType, UMLElement, UMLRelationship, Assessment, UMLCommunicationLink, UMLRelationshipType } from '../../typings'; - +import { + UMLModel, + UMLDiagramType, + UMLElement, + UMLRelationship, + Assessment, + UMLCommunicationLink, + UMLRelationshipType, +} from '../../typings'; /** - * + * * Represents a communication link in V2 schema. - * + * */ export type UMLCommunicationLinkV2 = UMLRelationship & { messages: { @@ -64,14 +71,13 @@ export function isV2(model: UMLModelCompat): model is UMLModelV2 { return model.version.startsWith('2.'); } - /** - * + * * Returns whether given relationship is a communication link in v2 schema. - * + * * @param {UMLRelationship} rel relationship to check * @returns {boolean} `true` if the relationship is a communication link, `false` otherwise. - * + * */ export function isCommunicationLink(rel: UMLRelationship): rel is UMLCommunicationLinkV2 { return rel.type === UMLRelationshipType.CommunicationLink; diff --git a/src/tests/unit/apollon-editor-test.tsx b/src/tests/unit/apollon-editor-test.tsx index 9143a77fe..b632854ed 100644 --- a/src/tests/unit/apollon-editor-test.tsx +++ b/src/tests/unit/apollon-editor-test.tsx @@ -5,8 +5,8 @@ import * as Apollon from '../../main/apollon-editor'; import * as ApollonTypes from '../../main/typings'; import testClassDiagram from './test-resources/class-diagram.json'; import testClassDiagramV2 from './test-resources/class-diagram-v2.json'; -import testCommunicationDiagram from './test-resources/communication-diagram.json' -import testCommunicationDiagramV2 from './test-resources/communication-diagram-v2.json' +import testCommunicationDiagram from './test-resources/communication-diagram.json'; +import testCommunicationDiagramV2 from './test-resources/communication-diagram-v2.json'; import { Selection } from '../../../docs/source/user/api/typings'; import { Assessment, SVG, UMLDiagramType, UMLModel } from '../../main'; import { getRealStore } from './test-utils/test-utils'; diff --git a/src/tests/unit/test-resources/communication-diagram-v2.json b/src/tests/unit/test-resources/communication-diagram-v2.json index e7252f712..a6e533fac 100644 --- a/src/tests/unit/test-resources/communication-diagram-v2.json +++ b/src/tests/unit/test-resources/communication-diagram-v2.json @@ -2,137 +2,133 @@ "version": "2.0.0", "type": "CommunicationDiagram", "size": { - "width": 380, - "height": 240 + "width": 380, + "height": 240 }, "interactive": { - "elements": [], - "relationships": [] + "elements": [], + "relationships": [] }, "elements": [ - { - "id": "17015424-a50b-4333-82b5-fdb3e272bced", - "name": "Object", - "type": "ObjectName", - "owner": null, - "bounds": { - "x": 0, - "y": 0, - "width": 160, - "height": 50 - }, - "attributes": [ - "fbc0789f-4f3b-4297-b190-82b271ec3299" - ], - "methods": [] - }, - { - "id": "fbc0789f-4f3b-4297-b190-82b271ec3299", - "name": "+ attribute: Type", - "type": "ObjectAttribute", - "owner": "17015424-a50b-4333-82b5-fdb3e272bced", - "bounds": { - "x": 0.5, - "y": 30.5, - "width": 159, - "height": 20 - } + { + "id": "17015424-a50b-4333-82b5-fdb3e272bced", + "name": "Object", + "type": "ObjectName", + "owner": null, + "bounds": { + "x": 0, + "y": 0, + "width": 160, + "height": 50 }, - { - "id": "b5d8e7d3-e83a-48b0-a130-02aca060c174", - "name": "Object", - "type": "ObjectName", - "owner": null, - "bounds": { - "x": 170, - "y": 110, - "width": 160, - "height": 50 - }, - "attributes": [ - "26ba3925-cdec-44cc-9d75-1fb913855f24" - ], - "methods": [] + "attributes": ["fbc0789f-4f3b-4297-b190-82b271ec3299"], + "methods": [] + }, + { + "id": "fbc0789f-4f3b-4297-b190-82b271ec3299", + "name": "+ attribute: Type", + "type": "ObjectAttribute", + "owner": "17015424-a50b-4333-82b5-fdb3e272bced", + "bounds": { + "x": 0.5, + "y": 30.5, + "width": 159, + "height": 20 + } + }, + { + "id": "b5d8e7d3-e83a-48b0-a130-02aca060c174", + "name": "Object", + "type": "ObjectName", + "owner": null, + "bounds": { + "x": 170, + "y": 110, + "width": 160, + "height": 50 }, - { - "id": "26ba3925-cdec-44cc-9d75-1fb913855f24", - "name": "+ attribute: Type", - "type": "ObjectAttribute", - "owner": "b5d8e7d3-e83a-48b0-a130-02aca060c174", - "bounds": { - "x": 170.5, - "y": 140.5, - "width": 159, - "height": 20 - } + "attributes": ["26ba3925-cdec-44cc-9d75-1fb913855f24"], + "methods": [] + }, + { + "id": "26ba3925-cdec-44cc-9d75-1fb913855f24", + "name": "+ attribute: Type", + "type": "ObjectAttribute", + "owner": "b5d8e7d3-e83a-48b0-a130-02aca060c174", + "bounds": { + "x": 170.5, + "y": 140.5, + "width": 159, + "height": 20 } + } ], "relationships": [ - { - "id": "d582f6af-82a3-4640-831c-7214b9e78194", - "name": "", - "type": "CommunicationLink", - "owner": null, + { + "id": "d582f6af-82a3-4640-831c-7214b9e78194", + "name": "", + "type": "CommunicationLink", + "owner": null, + "bounds": { + "x": 70.41015625, + "y": 50, + "width": 99.58984375, + "height": 132.5 + }, + "path": [ + { + "x": 9.58984375, + "y": 0 + }, + { + "x": 9.58984375, + "y": 85 + }, + { + "x": 99.58984375, + "y": 85 + } + ], + "source": { + "direction": "Down", + "element": "17015424-a50b-4333-82b5-fdb3e272bced" + }, + "target": { + "direction": "Left", + "element": "b5d8e7d3-e83a-48b0-a130-02aca060c174" + }, + "isManuallyLayouted": false, + "messages": [ + { + "id": "75beeac0-c2da-44aa-8268-53a4bfd52de0", + "name": "halo", "bounds": { - "x": 70.41015625, - "y": 50, - "width": 99.58984375, - "height": 132.5 - }, - "path": [ - { - "x": 9.58984375, - "y": 0 - }, - { - "x": 9.58984375, - "y": 85 - }, - { - "x": 99.58984375, - "y": 85 - } - ], - "source": { - "direction": "Down", - "element": "17015424-a50b-4333-82b5-fdb3e272bced" + "x": 0, + "y": 66.5, + "width": 24.1796875, + "height": 14.5 }, - "target": { - "direction": "Left", - "element": "b5d8e7d3-e83a-48b0-a130-02aca060c174" + "owner": null, + "resizeFrom": "bottomRight", + "direction": "source", + "type": "CommunicationLinkMessage" + }, + { + "id": "47ca1330-1cfe-46d9-aed3-f3297f83825d", + "name": "hola", + "bounds": { + "x": 0, + "y": 118, + "width": 24.1796875, + "height": 14.5 }, - "isManuallyLayouted": false, - "messages": [ - { - "id": "75beeac0-c2da-44aa-8268-53a4bfd52de0", - "name": "halo", - "bounds": { - "x": 0, - "y": 66.5, - "width": 24.1796875, - "height": 14.5 - }, - "owner": null, - "resizeFrom": "bottomRight", - "direction": "source", - "type": "CommunicationLinkMessage" - }, - { - "id": "47ca1330-1cfe-46d9-aed3-f3297f83825d", - "name": "hola", - "bounds": { - "x": 0, - "y": 118, - "width": 24.1796875, - "height": 14.5 - }, - "owner": null, - "resizeFrom": "bottomRight", - "direction": "target", - "type": "CommunicationLinkMessage" - } - ] - } + "owner": null, + "resizeFrom": "bottomRight", + "direction": "target", + "type": "CommunicationLinkMessage" + } + ] + } ], "assessments": [] -} \ No newline at end of file +} diff --git a/src/tests/unit/test-resources/communication-diagram.json b/src/tests/unit/test-resources/communication-diagram.json index bb6e9ec72..22761a833 100644 --- a/src/tests/unit/test-resources/communication-diagram.json +++ b/src/tests/unit/test-resources/communication-diagram.json @@ -2,137 +2,133 @@ "version": "3.0.0", "type": "CommunicationDiagram", "size": { - "width": 380, - "height": 240 + "width": 380, + "height": 240 }, "interactive": { - "elements": {}, - "relationships": {} + "elements": {}, + "relationships": {} }, "elements": { - "17015424-a50b-4333-82b5-fdb3e272bced": { - "id": "17015424-a50b-4333-82b5-fdb3e272bced", - "name": "Object", - "type": "ObjectName", - "owner": null, - "bounds": { - "x": 0, - "y": 0, - "width": 160, - "height": 50 - }, - "attributes": [ - "fbc0789f-4f3b-4297-b190-82b271ec3299" - ], - "methods": [] - }, - "fbc0789f-4f3b-4297-b190-82b271ec3299": { - "id": "fbc0789f-4f3b-4297-b190-82b271ec3299", - "name": "+ attribute: Type", - "type": "ObjectAttribute", - "owner": "17015424-a50b-4333-82b5-fdb3e272bced", - "bounds": { - "x": 0.5, - "y": 30.5, - "width": 159, - "height": 20 - } + "17015424-a50b-4333-82b5-fdb3e272bced": { + "id": "17015424-a50b-4333-82b5-fdb3e272bced", + "name": "Object", + "type": "ObjectName", + "owner": null, + "bounds": { + "x": 0, + "y": 0, + "width": 160, + "height": 50 }, - "b5d8e7d3-e83a-48b0-a130-02aca060c174": { - "id": "b5d8e7d3-e83a-48b0-a130-02aca060c174", - "name": "Object", - "type": "ObjectName", - "owner": null, - "bounds": { - "x": 170, - "y": 110, - "width": 160, - "height": 50 - }, - "attributes": [ - "26ba3925-cdec-44cc-9d75-1fb913855f24" - ], - "methods": [] + "attributes": ["fbc0789f-4f3b-4297-b190-82b271ec3299"], + "methods": [] + }, + "fbc0789f-4f3b-4297-b190-82b271ec3299": { + "id": "fbc0789f-4f3b-4297-b190-82b271ec3299", + "name": "+ attribute: Type", + "type": "ObjectAttribute", + "owner": "17015424-a50b-4333-82b5-fdb3e272bced", + "bounds": { + "x": 0.5, + "y": 30.5, + "width": 159, + "height": 20 + } + }, + "b5d8e7d3-e83a-48b0-a130-02aca060c174": { + "id": "b5d8e7d3-e83a-48b0-a130-02aca060c174", + "name": "Object", + "type": "ObjectName", + "owner": null, + "bounds": { + "x": 170, + "y": 110, + "width": 160, + "height": 50 }, - "26ba3925-cdec-44cc-9d75-1fb913855f24": { - "id": "26ba3925-cdec-44cc-9d75-1fb913855f24", - "name": "+ attribute: Type", - "type": "ObjectAttribute", - "owner": "b5d8e7d3-e83a-48b0-a130-02aca060c174", - "bounds": { - "x": 170.5, - "y": 140.5, - "width": 159, - "height": 20 - } + "attributes": ["26ba3925-cdec-44cc-9d75-1fb913855f24"], + "methods": [] + }, + "26ba3925-cdec-44cc-9d75-1fb913855f24": { + "id": "26ba3925-cdec-44cc-9d75-1fb913855f24", + "name": "+ attribute: Type", + "type": "ObjectAttribute", + "owner": "b5d8e7d3-e83a-48b0-a130-02aca060c174", + "bounds": { + "x": 170.5, + "y": 140.5, + "width": 159, + "height": 20 } + } }, "relationships": { - "d582f6af-82a3-4640-831c-7214b9e78194": { - "id": "d582f6af-82a3-4640-831c-7214b9e78194", - "name": "", - "type": "CommunicationLink", - "owner": null, + "d582f6af-82a3-4640-831c-7214b9e78194": { + "id": "d582f6af-82a3-4640-831c-7214b9e78194", + "name": "", + "type": "CommunicationLink", + "owner": null, + "bounds": { + "x": 70.41015625, + "y": 50, + "width": 99.58984375, + "height": 132.5 + }, + "path": [ + { + "x": 9.58984375, + "y": 0 + }, + { + "x": 9.58984375, + "y": 85 + }, + { + "x": 99.58984375, + "y": 85 + } + ], + "source": { + "direction": "Down", + "element": "17015424-a50b-4333-82b5-fdb3e272bced" + }, + "target": { + "direction": "Left", + "element": "b5d8e7d3-e83a-48b0-a130-02aca060c174" + }, + "isManuallyLayouted": false, + "messages": { + "75beeac0-c2da-44aa-8268-53a4bfd52de0": { + "id": "75beeac0-c2da-44aa-8268-53a4bfd52de0", + "name": "halo", "bounds": { - "x": 70.41015625, - "y": 50, - "width": 99.58984375, - "height": 132.5 + "x": 0, + "y": 66.5, + "width": 24.1796875, + "height": 14.5 }, - "path": [ - { - "x": 9.58984375, - "y": 0 - }, - { - "x": 9.58984375, - "y": 85 - }, - { - "x": 99.58984375, - "y": 85 - } - ], - "source": { - "direction": "Down", - "element": "17015424-a50b-4333-82b5-fdb3e272bced" - }, - "target": { - "direction": "Left", - "element": "b5d8e7d3-e83a-48b0-a130-02aca060c174" + "owner": null, + "resizeFrom": "bottomRight", + "direction": "source", + "type": "CommunicationLinkMessage" + }, + "47ca1330-1cfe-46d9-aed3-f3297f83825d": { + "id": "47ca1330-1cfe-46d9-aed3-f3297f83825d", + "name": "hola", + "bounds": { + "x": 0, + "y": 118, + "width": 24.1796875, + "height": 14.5 }, - "isManuallyLayouted": false, - "messages": { - "75beeac0-c2da-44aa-8268-53a4bfd52de0": { - "id": "75beeac0-c2da-44aa-8268-53a4bfd52de0", - "name": "halo", - "bounds": { - "x": 0, - "y": 66.5, - "width": 24.1796875, - "height": 14.5 - }, - "owner": null, - "resizeFrom": "bottomRight", - "direction": "source", - "type": "CommunicationLinkMessage" - }, - "47ca1330-1cfe-46d9-aed3-f3297f83825d": { - "id": "47ca1330-1cfe-46d9-aed3-f3297f83825d", - "name": "hola", - "bounds": { - "x": 0, - "y": 118, - "width": 24.1796875, - "height": 14.5 - }, - "owner": null, - "resizeFrom": "bottomRight", - "direction": "target", - "type": "CommunicationLinkMessage" - } - } + "owner": null, + "resizeFrom": "bottomRight", + "direction": "target", + "type": "CommunicationLinkMessage" + } } + } }, "assessments": {} -} \ No newline at end of file +}