Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Diagram schema migration #306

Merged
merged 11 commits into from
Oct 15, 2023
2 changes: 2 additions & 0 deletions src/main/apollon-editor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
loreanvictor marked this conversation as resolved.
Show resolved Hide resolved
Object.values(this.discreteModelSubscribers).forEach((subscriber) => subscriber(lastModel));
}
} catch (error) {
Expand Down
82 changes: 49 additions & 33 deletions src/main/components/store/model-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Apollon.UMLElement>((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;
})
: [];

Expand All @@ -69,11 +73,11 @@ export class ModelState {
return [element, ...children.reduce<UMLElement[]>((acc, val) => [...acc, ...deserialize(val)], [])];
};

const elements = apollonElements
const elements = Object.values(apollonElements)
.filter((element) => !element.owner)
.reduce<UMLElement[]>((acc, val) => [...acc, ...deserialize(val)], []);

const relationships = apollonRelationships.map<UMLRelationship>((apollonRelationship) => {
const relationships = Object.values(apollonRelationships).map<UMLRelationship>((apollonRelationship) => {
const relationship = new UMLRelationships[apollonRelationship.type]();
relationship.deserialize(apollonRelationship);
return relationship;
Expand All @@ -91,15 +95,15 @@ 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;
});

return {
diagram,
interactive: [...model.interactive.elements, ...model.interactive.relationships],
elements: [...elements, ...relationships].reduce((acc, val) => ({ ...acc, [val.id]: { ...val } }), {}),
assessments: (model.assessments || []).reduce<AssessmentState>(
assessments: (Object.values(model.assessments) || []).reduce<AssessmentState>(
(acc, val) => ({
...acc,
[val.modelElementId]: {
Expand All @@ -120,39 +124,49 @@ export class ModelState {
const elements = Object.values(state.elements)
.map<UMLElement | null>((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<Apollon.UMLElement[]>((acc, val) => [...acc, ...serialize(val)], [])
.map<Apollon.UMLElement>((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.values(elements)
.filter((element) => !element.owner)
.reduce<Apollon.UMLElement[]>((acc, val) => [...acc, ...serialize(val)], []);
.reduce((acc, element) => ({ ...acc, ...serialize(element) }), {}) 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;
}
Expand All @@ -166,24 +180,26 @@ export class ModelState {
relationships: state.interactive.filter((id) => UMLRelationship.isUMLRelationship(state.elements[id])),
};

const assessments = Object.keys(state.assessments).map<Apollon.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,
}));
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,
};
}
Expand Down
10 changes: 7 additions & 3 deletions src/main/scenes/svg.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ 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;
Expand Down Expand Up @@ -81,10 +82,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);
Expand All @@ -107,11 +111,11 @@ const getInitialState = ({ model, options }: Props): State => {
return [root, ...updates];
};

const elements = apollonElements
const elements = Object.values(apollonElements)
.filter((element) => !element.owner)
.reduce<UMLElement[]>((acc, val) => [...acc, ...deserialize(val)], []);

const relationships = apollonRelationships.map<UMLRelationship>((apollonRelationship) => {
const relationships = Object.values(apollonRelationships).map<UMLRelationship>((apollonRelationship) => {
const relationship = new UMLRelationships[apollonRelationship.type]();
relationship.deserialize(apollonRelationship);
return relationship;
Expand Down
6 changes: 3 additions & 3 deletions src/main/typings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down