Skip to content

Commit d55af1b

Browse files
committed
GLSP-1717: Fix bounds computation error after diagram export
Exports render the diagram through the hidden viewer, so the hidden bounds updater collected bounds and routing points for the export clone but never cleaned them up. The stale entries leaked into the next computedBounds action, causing 'Model element not found' server errors when elements had been deleted in the meantime. - Always reset collected bounds data and routing points in postUpdate, regardless of the rendering cause or errors during computation Fixes eclipse-glsp/glsp#1717
1 parent e07d42b commit d55af1b

1 file changed

Lines changed: 65 additions & 58 deletions

File tree

packages/client/src/features/bounds/glsp-hidden-bounds-updater.ts

Lines changed: 65 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -67,74 +67,81 @@ export class GLSPHiddenBoundsUpdater extends HiddenBoundsUpdater {
6767
}
6868

6969
override postUpdate(cause?: Action): void {
70-
if (cause === undefined || cause.kind !== RequestBoundsAction.KIND) {
71-
return;
72-
}
70+
try {
71+
if (cause === undefined || cause.kind !== RequestBoundsAction.KIND) {
72+
return;
73+
}
7374

74-
if (LocalRequestBoundsAction.is(cause) && cause.elementIDs) {
75-
this.focusOnElements(cause.elementIDs);
76-
}
75+
if (LocalRequestBoundsAction.is(cause) && cause.elementIDs) {
76+
this.focusOnElements(cause.elementIDs);
77+
}
7778

78-
// collect bounds and layout data in element2BoundsData
79-
this.getBoundsFromDOM();
80-
this.layouter.layout(this.getElement2BoundsData());
79+
// collect bounds and layout data in element2BoundsData
80+
this.getBoundsFromDOM();
81+
this.layouter.layout(this.getElement2BoundsData());
8182

82-
// prepare data for action
83-
const resizes: ElementAndBounds[] = [];
84-
const alignments: ElementAndAlignment[] = [];
85-
const layoutData: ElementAndLayoutData[] = [];
86-
this.getElement2BoundsData().forEach((boundsData, element) => {
87-
if (boundsData.boundsChanged && boundsData.bounds !== undefined) {
88-
const resize: ElementAndBounds = {
89-
elementId: element.id,
90-
newSize: {
91-
width: boundsData.bounds.width,
92-
height: boundsData.bounds.height
93-
}
94-
};
95-
// don't copy position if the element is layouted by the server
96-
if (element instanceof GChildElement && isLayoutContainer(element.parent)) {
97-
resize.newPosition = {
98-
x: boundsData.bounds.x,
99-
y: boundsData.bounds.y
83+
// prepare data for action
84+
const resizes: ElementAndBounds[] = [];
85+
const alignments: ElementAndAlignment[] = [];
86+
const layoutData: ElementAndLayoutData[] = [];
87+
this.getElement2BoundsData().forEach((boundsData, element) => {
88+
if (boundsData.boundsChanged && boundsData.bounds !== undefined) {
89+
const resize: ElementAndBounds = {
90+
elementId: element.id,
91+
newSize: {
92+
width: boundsData.bounds.width,
93+
height: boundsData.bounds.height
94+
}
10095
};
96+
// don't copy position if the element is layouted by the server
97+
if (element instanceof GChildElement && isLayoutContainer(element.parent)) {
98+
resize.newPosition = {
99+
x: boundsData.bounds.x,
100+
y: boundsData.bounds.y
101+
};
102+
}
103+
resizes.push(resize);
101104
}
102-
resizes.push(resize);
103-
}
104-
if (boundsData.alignmentChanged && boundsData.alignment !== undefined) {
105-
alignments.push({
106-
elementId: element.id,
107-
newAlignment: boundsData.alignment
108-
});
109-
}
110-
if (LayoutAware.is(boundsData)) {
111-
layoutData.push({ elementId: element.id, layoutData: boundsData.layoutData });
112-
}
113-
});
114-
const routes = this.element2route.length === 0 ? undefined : this.element2route;
105+
if (boundsData.alignmentChanged && boundsData.alignment !== undefined) {
106+
alignments.push({
107+
elementId: element.id,
108+
newAlignment: boundsData.alignment
109+
});
110+
}
111+
if (LayoutAware.is(boundsData)) {
112+
layoutData.push({ elementId: element.id, layoutData: boundsData.layoutData });
113+
}
114+
});
115+
const routes = this.element2route.length === 0 ? undefined : this.element2route;
115116

116-
// prepare and dispatch action
117-
const responseId = (cause as RequestBoundsAction).requestId;
118-
const revision = this.root !== undefined ? this.root.revision : undefined;
119-
const canvasBounds = this.editorContext.canvasBounds;
120-
const viewport = this.editorContext.viewportData;
121-
const computedBoundsAction = ComputedBoundsAction.create(resizes, {
122-
revision,
123-
alignments,
124-
layoutData,
125-
routes,
126-
responseId,
127-
canvasBounds,
128-
viewport
129-
});
130-
if (LocalRequestBoundsAction.is(cause)) {
131-
LocalComputedBoundsAction.mark(computedBoundsAction);
117+
// prepare and dispatch action
118+
const responseId = (cause as RequestBoundsAction).requestId;
119+
const revision = this.root !== undefined ? this.root.revision : undefined;
120+
const canvasBounds = this.editorContext.canvasBounds;
121+
const viewport = this.editorContext.viewportData;
122+
const computedBoundsAction = ComputedBoundsAction.create(resizes, {
123+
revision,
124+
alignments,
125+
layoutData,
126+
routes,
127+
responseId,
128+
canvasBounds,
129+
viewport
130+
});
131+
if (LocalRequestBoundsAction.is(cause)) {
132+
LocalComputedBoundsAction.mark(computedBoundsAction);
133+
}
134+
this.actionDispatcher.dispatch(computedBoundsAction);
135+
} finally {
136+
// always reset collected data so hidden renderings with other causes (e.g. exports/failures) do not leak into the next run
137+
this.cleanUp();
132138
}
133-
this.actionDispatcher.dispatch(computedBoundsAction);
139+
}
134140

135-
// cleanup
141+
protected cleanUp(): void {
136142
this.getElement2BoundsData().clear();
137143
this.element2route = [];
144+
this.root = undefined;
138145
}
139146

140147
protected focusOnElements(elementIDs: string[]): void {

0 commit comments

Comments
 (0)