@eclipse-glsp/client 2.7.0-next.3 → 2.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/base/action-dispatcher.d.ts +2 -1
- package/lib/base/action-dispatcher.d.ts.map +1 -1
- package/lib/base/action-dispatcher.js +48 -24
- package/lib/base/action-dispatcher.js.map +1 -1
- package/lib/base/default.module.d.ts.map +1 -1
- package/lib/base/default.module.js +2 -0
- package/lib/base/default.module.js.map +1 -1
- package/lib/base/editor-context-service.d.ts +4 -3
- package/lib/base/editor-context-service.d.ts.map +1 -1
- package/lib/base/editor-context-service.js +11 -1
- package/lib/base/editor-context-service.js.map +1 -1
- package/lib/base/model/diagram-loader.d.ts +8 -6
- package/lib/base/model/diagram-loader.d.ts.map +1 -1
- package/lib/base/model/diagram-loader.js +1 -1
- package/lib/base/model/diagram-loader.js.map +1 -1
- package/lib/base/model/glsp-model-source.d.ts +5 -2
- package/lib/base/model/glsp-model-source.d.ts.map +1 -1
- package/lib/base/model/glsp-model-source.js +28 -1
- package/lib/base/model/glsp-model-source.js.map +1 -1
- package/lib/base/selection-service.d.ts +4 -3
- package/lib/base/selection-service.d.ts.map +1 -1
- package/lib/base/selection-service.js +6 -1
- package/lib/base/selection-service.js.map +1 -1
- package/lib/base/shortcuts/available-shortcuts-extension.d.ts +1 -1
- package/lib/base/shortcuts/available-shortcuts-extension.d.ts.map +1 -1
- package/lib/base/shortcuts/available-shortcuts-extension.js +3 -4
- package/lib/base/shortcuts/available-shortcuts-extension.js.map +1 -1
- package/lib/base/view/mouse-tool.d.ts +1 -1
- package/lib/base/view/mouse-tool.d.ts.map +1 -1
- package/lib/base/view/mouse-tool.js +3 -19
- package/lib/base/view/mouse-tool.js.map +1 -1
- package/lib/features/change-bounds/move-element-handler.d.ts +2 -3
- package/lib/features/change-bounds/move-element-handler.d.ts.map +1 -1
- package/lib/features/change-bounds/move-element-handler.js +2 -3
- package/lib/features/change-bounds/move-element-handler.js.map +1 -1
- package/lib/features/change-bounds/resize/resize-handler.d.ts +2 -3
- package/lib/features/change-bounds/resize/resize-handler.d.ts.map +1 -1
- package/lib/features/change-bounds/resize/resize-handler.js +2 -3
- package/lib/features/change-bounds/resize/resize-handler.js.map +1 -1
- package/lib/features/export/default-png-diagram-exporter.d.ts +52 -0
- package/lib/features/export/default-png-diagram-exporter.d.ts.map +1 -0
- package/lib/features/export/default-png-diagram-exporter.js +157 -0
- package/lib/features/export/default-png-diagram-exporter.js.map +1 -0
- package/lib/features/export/default-svg-diagram-exporter.d.ts +32 -0
- package/lib/features/export/default-svg-diagram-exporter.d.ts.map +1 -0
- package/lib/features/export/default-svg-diagram-exporter.js +54 -0
- package/lib/features/export/default-svg-diagram-exporter.js.map +1 -0
- package/lib/features/export/diagram-export-postprocessor.d.ts +33 -0
- package/lib/features/export/diagram-export-postprocessor.d.ts.map +1 -0
- package/lib/features/export/diagram-export-postprocessor.js +84 -0
- package/lib/features/export/diagram-export-postprocessor.js.map +1 -0
- package/lib/features/export/diagram-exporter.d.ts +44 -0
- package/lib/features/export/diagram-exporter.d.ts.map +1 -0
- package/lib/features/export/diagram-exporter.js +18 -0
- package/lib/features/export/diagram-exporter.js.map +1 -0
- package/lib/features/export/export-modules.d.ts +1 -1
- package/lib/features/export/export-modules.d.ts.map +1 -1
- package/lib/features/export/export-modules.js +20 -3
- package/lib/features/export/export-modules.js.map +1 -1
- package/lib/features/export/export-result-action-handler.d.ts +28 -0
- package/lib/features/export/export-result-action-handler.d.ts.map +1 -0
- package/lib/features/export/export-result-action-handler.js +72 -0
- package/lib/features/export/export-result-action-handler.js.map +1 -0
- package/lib/features/export/export-svg-action-handler.d.ts +19 -0
- package/lib/features/export/export-svg-action-handler.d.ts.map +1 -1
- package/lib/features/export/export-svg-action-handler.js +4 -15
- package/lib/features/export/export-svg-action-handler.js.map +1 -1
- package/lib/features/export/glsp-svg-exporter.d.ts +18 -1
- package/lib/features/export/glsp-svg-exporter.d.ts.map +1 -1
- package/lib/features/export/glsp-svg-exporter.js +44 -11
- package/lib/features/export/glsp-svg-exporter.js.map +1 -1
- package/lib/features/export/request-export-command.d.ts +29 -0
- package/lib/features/export/request-export-command.d.ts.map +1 -0
- package/lib/features/export/request-export-command.js +73 -0
- package/lib/features/export/request-export-command.js.map +1 -0
- package/lib/features/export/request-export-key-listener.d.ts +21 -0
- package/lib/features/export/request-export-key-listener.d.ts.map +1 -0
- package/lib/features/export/request-export-key-listener.js +40 -0
- package/lib/features/export/request-export-key-listener.js.map +1 -0
- package/lib/features/helper-lines/helper-line-feedback.d.ts +1 -1
- package/lib/features/helper-lines/helper-line-feedback.d.ts.map +1 -1
- package/lib/features/helper-lines/helper-line-feedback.js +2 -3
- package/lib/features/helper-lines/helper-line-feedback.js.map +1 -1
- package/lib/features/label-edit-ui/label-edit-ui.d.ts +1 -1
- package/lib/features/label-edit-ui/label-edit-ui.js +1 -1
- package/lib/features/routing/edge-router.d.ts +2 -1
- package/lib/features/routing/edge-router.d.ts.map +1 -1
- package/lib/features/routing/edge-router.js +2 -1
- package/lib/features/routing/edge-router.js.map +1 -1
- package/lib/features/routing/routing-module.d.ts +1 -1
- package/lib/features/routing/routing-module.d.ts.map +1 -1
- package/lib/features/routing/routing-module.js +7 -1
- package/lib/features/routing/routing-module.js.map +1 -1
- package/lib/features/routing/sticky-manhattan-anchors.d.ts +29 -0
- package/lib/features/routing/sticky-manhattan-anchors.d.ts.map +1 -0
- package/lib/features/routing/sticky-manhattan-anchors.js +62 -0
- package/lib/features/routing/sticky-manhattan-anchors.js.map +1 -0
- package/lib/features/routing/sticky-manhattan-edge-router.d.ts +107 -0
- package/lib/features/routing/sticky-manhattan-edge-router.d.ts.map +1 -0
- package/lib/features/routing/sticky-manhattan-edge-router.js +626 -0
- package/lib/features/routing/sticky-manhattan-edge-router.js.map +1 -0
- package/lib/features/search-palette/search-palette.d.ts +2 -2
- package/lib/features/search-palette/search-palette.d.ts.map +1 -1
- package/lib/features/search-palette/search-palette.js +3 -4
- package/lib/features/search-palette/search-palette.js.map +1 -1
- package/lib/features/select/select-module.d.ts +1 -1
- package/lib/features/select/select-module.js +1 -1
- package/lib/features/tool-palette/tool-palette.d.ts.map +1 -1
- package/lib/features/tool-palette/tool-palette.js +1 -2
- package/lib/features/tool-palette/tool-palette.js.map +1 -1
- package/lib/features/tools/change-bounds/change-bounds-tool-move-feedback.d.ts +2 -3
- package/lib/features/tools/change-bounds/change-bounds-tool-move-feedback.d.ts.map +1 -1
- package/lib/features/tools/change-bounds/change-bounds-tool-move-feedback.js +2 -3
- package/lib/features/tools/change-bounds/change-bounds-tool-move-feedback.js.map +1 -1
- package/lib/features/viewport/origin-viewport.d.ts +2 -13
- package/lib/features/viewport/origin-viewport.d.ts.map +1 -1
- package/lib/features/viewport/origin-viewport.js +3 -19
- package/lib/features/viewport/origin-viewport.js.map +1 -1
- package/lib/index.d.ts +11 -1
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +11 -1
- package/lib/index.js.map +1 -1
- package/lib/views/gedge-view-with-gaps-on-intersections.d.ts +39 -0
- package/lib/views/gedge-view-with-gaps-on-intersections.d.ts.map +1 -0
- package/lib/views/gedge-view-with-gaps-on-intersections.js +85 -0
- package/lib/views/gedge-view-with-gaps-on-intersections.js.map +1 -0
- package/lib/views/rounded-corner-manhattan-edge-view.d.ts +58 -0
- package/lib/views/rounded-corner-manhattan-edge-view.d.ts.map +1 -0
- package/lib/views/rounded-corner-manhattan-edge-view.js +170 -0
- package/lib/views/rounded-corner-manhattan-edge-view.js.map +1 -0
- package/package.json +4 -6
- package/src/base/action-dispatcher.spec.ts +103 -3
- package/src/base/action-dispatcher.ts +50 -25
- package/src/base/default.module.ts +4 -0
- package/src/base/editor-context-service.ts +17 -5
- package/src/base/model/diagram-loader.ts +8 -6
- package/src/base/model/glsp-model-source.ts +35 -1
- package/src/base/selection-service.ts +11 -2
- package/src/base/shortcuts/available-shortcuts-extension.ts +3 -4
- package/src/base/view/mouse-tool.ts +4 -19
- package/src/features/change-bounds/move-element-handler.ts +3 -2
- package/src/features/change-bounds/resize/resize-handler.ts +4 -3
- package/src/features/export/default-png-diagram-exporter.ts +145 -0
- package/src/features/export/default-svg-diagram-exporter.ts +39 -0
- package/src/features/export/diagram-export-postprocessor.ts +82 -0
- package/src/features/export/diagram-exporter.ts +45 -0
- package/src/features/export/export-modules.ts +24 -4
- package/src/features/export/export-result-action-handler.ts +66 -0
- package/src/features/export/export-svg-action-handler.ts +7 -2
- package/src/features/export/glsp-svg-exporter.ts +48 -12
- package/src/features/export/request-export-command.ts +65 -0
- package/src/features/export/request-export-key-listener.ts +29 -0
- package/src/features/helper-lines/helper-line-feedback.ts +6 -4
- package/src/features/label-edit-ui/label-edit-ui.ts +1 -1
- package/src/features/routing/edge-router.ts +2 -2
- package/src/features/routing/routing-module.ts +8 -1
- package/src/features/routing/sticky-manhattan-anchors.ts +53 -0
- package/src/features/routing/sticky-manhattan-edge-router.spec.ts +212 -0
- package/src/features/routing/sticky-manhattan-edge-router.ts +713 -0
- package/src/features/search-palette/search-palette.ts +3 -4
- package/src/features/select/select-module.ts +1 -1
- package/src/features/tool-palette/tool-palette.ts +1 -1
- package/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts +13 -3
- package/src/features/viewport/origin-viewport.ts +10 -23
- package/src/index.ts +11 -1
- package/src/views/gedge-view-with-gaps-on-intersections.tsx +99 -0
- package/src/views/rounded-corner-manhattan-edge-view.ts +171 -0
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
Deferred,
|
|
22
22
|
EMPTY_ROOT,
|
|
23
23
|
GModelRoot,
|
|
24
|
+
HandleActionResult,
|
|
24
25
|
IActionDispatcher,
|
|
26
|
+
MaybePromise,
|
|
25
27
|
RejectAction,
|
|
26
28
|
RequestAction,
|
|
27
29
|
ResponseAction,
|
|
@@ -132,17 +134,15 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo
|
|
|
132
134
|
}
|
|
133
135
|
|
|
134
136
|
protected async handleResponseAction(action: ResponseAction): Promise<void> {
|
|
135
|
-
// clear any pending timeout
|
|
136
137
|
const timeout = this.timeouts.get(action.responseId);
|
|
137
138
|
if (timeout !== undefined) {
|
|
138
139
|
clearTimeout(timeout);
|
|
139
140
|
this.timeouts.delete(action.responseId);
|
|
140
141
|
}
|
|
141
142
|
|
|
142
|
-
// check for matching request
|
|
143
143
|
const request = this.requests.get(action.responseId);
|
|
144
144
|
if (!request) {
|
|
145
|
-
//
|
|
145
|
+
// No pending request: re-dispatch as a normal action.
|
|
146
146
|
this.logger.log(this, 'No matching request for response, dispatch normally', action);
|
|
147
147
|
action.responseId = '';
|
|
148
148
|
return this.handleAction(action);
|
|
@@ -150,7 +150,6 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo
|
|
|
150
150
|
|
|
151
151
|
this.requests.delete(action.responseId);
|
|
152
152
|
if (RejectAction.is(action)) {
|
|
153
|
-
// translation reject action to request rejection
|
|
154
153
|
request.reject(new Error(action.message));
|
|
155
154
|
this.logger.warn(this, `Request with id ${action.responseId} failed.`, action.message, action.detail);
|
|
156
155
|
} else {
|
|
@@ -169,34 +168,47 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo
|
|
|
169
168
|
}
|
|
170
169
|
this.logger.warn(this, 'Missing handler for action', action);
|
|
171
170
|
const error = new Error(`Missing handler for action '${action.kind}'`);
|
|
172
|
-
if (
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
request.reject(error);
|
|
171
|
+
if (RequestAction.is(action)) {
|
|
172
|
+
const request = this.requests.get(action.requestId);
|
|
173
|
+
if (request !== undefined) {
|
|
174
|
+
this.requests.delete(action.requestId);
|
|
175
|
+
request.reject(error);
|
|
176
|
+
}
|
|
179
177
|
}
|
|
178
|
+
throw error;
|
|
180
179
|
}
|
|
181
180
|
|
|
182
181
|
protected async handleActionWithHandler(action: Action, handlers: ActionHandler[]): Promise<void> {
|
|
183
182
|
this.logger.log(this, 'Handle', action);
|
|
184
|
-
|
|
183
|
+
// No `await` inside the loop: invoke handlers in one burst and await their results collectively.
|
|
184
|
+
const handlerResults: PromiseLike<unknown>[] = [];
|
|
185
185
|
for (const handler of handlers) {
|
|
186
|
-
const
|
|
187
|
-
if (
|
|
188
|
-
handlerResults.push(this.
|
|
189
|
-
} else
|
|
190
|
-
|
|
191
|
-
|
|
186
|
+
const maybeResult = handler.handle(action);
|
|
187
|
+
if (MaybePromise.isPromise(maybeResult)) {
|
|
188
|
+
handlerResults.push(maybeResult.then(result => this.processHandlerResult(result)));
|
|
189
|
+
} else {
|
|
190
|
+
const resultPromise = this.processHandlerResult(maybeResult);
|
|
191
|
+
if (resultPromise) {
|
|
192
|
+
handlerResults.push(resultPromise);
|
|
193
|
+
}
|
|
192
194
|
}
|
|
193
195
|
}
|
|
194
196
|
await Promise.all(handlerResults);
|
|
195
197
|
}
|
|
196
198
|
|
|
199
|
+
protected processHandlerResult(result: HandleActionResult): Promise<unknown> | undefined {
|
|
200
|
+
if (Action.is(result)) {
|
|
201
|
+
return this.dispatch(result);
|
|
202
|
+
}
|
|
203
|
+
if (result !== undefined) {
|
|
204
|
+
this.blockUntil = result.blockUntil;
|
|
205
|
+
return this.commandStack.execute(result);
|
|
206
|
+
}
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
197
210
|
override request<Res extends ResponseAction>(action: RequestAction<Res>): Promise<Res> {
|
|
198
|
-
if (!action.requestId
|
|
199
|
-
// No request id has been specified. So we use a generated one.
|
|
211
|
+
if (!action.requestId || action.requestId === '') {
|
|
200
212
|
action.requestId = RequestAction.generateRequestId();
|
|
201
213
|
}
|
|
202
214
|
return super.request(action);
|
|
@@ -214,19 +226,20 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo
|
|
|
214
226
|
*/
|
|
215
227
|
requestUntil<Res extends ResponseAction>(
|
|
216
228
|
action: RequestAction<Res>,
|
|
217
|
-
timeoutMs = 2000,
|
|
229
|
+
timeoutMs: number = action.timeout ?? 2000,
|
|
218
230
|
rejectOnTimeout = false
|
|
219
231
|
): Promise<Res | undefined> {
|
|
220
|
-
if (!action.requestId
|
|
221
|
-
// No request id has been specified. So we use a generated one.
|
|
232
|
+
if (!action.requestId || action.requestId === '') {
|
|
222
233
|
action.requestId = RequestAction.generateRequestId();
|
|
223
234
|
}
|
|
235
|
+
// Stamp the effective timeout onto the action so the receiving side
|
|
236
|
+
// (handleServerRequest/handleClientRequest) can respect it.
|
|
237
|
+
action.timeout = timeoutMs;
|
|
224
238
|
|
|
225
239
|
const requestId = action.requestId;
|
|
226
240
|
const timeout = setTimeout(() => {
|
|
227
241
|
const deferred = this.requests.get(requestId);
|
|
228
242
|
if (deferred !== undefined) {
|
|
229
|
-
// cleanup
|
|
230
243
|
clearTimeout(timeout);
|
|
231
244
|
this.requests.delete(requestId);
|
|
232
245
|
|
|
@@ -240,6 +253,18 @@ export class GLSPActionDispatcher extends ActionDispatcher implements IGModelRoo
|
|
|
240
253
|
}
|
|
241
254
|
}, timeoutMs);
|
|
242
255
|
this.timeouts.set(requestId, timeout);
|
|
243
|
-
|
|
256
|
+
|
|
257
|
+
const result = super.request<Res>(action);
|
|
258
|
+
// handleResponseAction only clears the timeout on the response path; clear it here on every
|
|
259
|
+
// other settle path (timeout, rejection, missing handler) so the timer and map entry can't leak.
|
|
260
|
+
const clearRequestTimeout = (): void => {
|
|
261
|
+
const pending = this.timeouts.get(requestId);
|
|
262
|
+
if (pending !== undefined) {
|
|
263
|
+
clearTimeout(pending);
|
|
264
|
+
this.timeouts.delete(requestId);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
result.then(clearRequestTimeout, clearRequestTimeout);
|
|
268
|
+
return result;
|
|
244
269
|
}
|
|
245
270
|
}
|
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
import {
|
|
17
17
|
ActionHandlerRegistry,
|
|
18
18
|
FeatureModule,
|
|
19
|
+
GetEditorContextAction,
|
|
20
|
+
GetSelectionAction,
|
|
19
21
|
KeyTool,
|
|
20
22
|
LocationPostprocessor,
|
|
21
23
|
MousePositionTracker,
|
|
@@ -83,6 +85,7 @@ export const defaultModule = new FeatureModule(
|
|
|
83
85
|
|
|
84
86
|
configureActionHandler(context, SetEditModeAction.KIND, EditorContextService);
|
|
85
87
|
configureActionHandler(context, SetDirtyStateAction.KIND, EditorContextService);
|
|
88
|
+
configureActionHandler(context, GetEditorContextAction.KIND, EditorContextService);
|
|
86
89
|
|
|
87
90
|
bind(FocusTracker).toSelf().inSingletonScope();
|
|
88
91
|
bind(TYPES.IDiagramStartup).toService(FocusTracker);
|
|
@@ -121,6 +124,7 @@ export const defaultModule = new FeatureModule(
|
|
|
121
124
|
bind(SelectionService).toSelf().inSingletonScope();
|
|
122
125
|
bind(TYPES.IGModelRootListener).toService(SelectionService);
|
|
123
126
|
bind(TYPES.IDiagramStartup).toService(SelectionService);
|
|
127
|
+
configureActionHandler(context, GetSelectionAction.KIND, SelectionService);
|
|
124
128
|
|
|
125
129
|
// Feedback Support ------------------------------------
|
|
126
130
|
// Generic re-usable feedback modifying css classes
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2020-
|
|
2
|
+
* Copyright (c) 2020-2026 EclipseSource and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -21,14 +21,14 @@ import {
|
|
|
21
21
|
DisposableCollection,
|
|
22
22
|
EditMode,
|
|
23
23
|
EditorContext,
|
|
24
|
+
EditorContextResult,
|
|
24
25
|
Emitter,
|
|
25
26
|
Event,
|
|
26
|
-
findParentByFeature,
|
|
27
27
|
GModelElement,
|
|
28
28
|
GModelRoot,
|
|
29
|
+
GetEditorContextAction,
|
|
29
30
|
IActionDispatcher,
|
|
30
31
|
IActionHandler,
|
|
31
|
-
isViewport,
|
|
32
32
|
LazyInjector,
|
|
33
33
|
MaybePromise,
|
|
34
34
|
MousePositionTracker,
|
|
@@ -37,7 +37,9 @@ import {
|
|
|
37
37
|
SetEditModeAction,
|
|
38
38
|
TYPES,
|
|
39
39
|
ValueChange,
|
|
40
|
-
Viewport
|
|
40
|
+
Viewport,
|
|
41
|
+
findParentByFeature,
|
|
42
|
+
isViewport
|
|
41
43
|
} from '@eclipse-glsp/sprotty';
|
|
42
44
|
import { inject, injectable, postConstruct, preDestroy } from 'inversify';
|
|
43
45
|
import { FocusChange, FocusTracker } from './focus/focus-tracker';
|
|
@@ -177,6 +179,8 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra
|
|
|
177
179
|
return {
|
|
178
180
|
selectedElementIds: Array.from(this.selectionService.getSelectedElementIDs()),
|
|
179
181
|
lastMousePosition: this.mousePositionTracker.lastPositionOnDiagram,
|
|
182
|
+
viewport: this.viewportData,
|
|
183
|
+
canvasBounds: this.canvasBounds,
|
|
180
184
|
args
|
|
181
185
|
};
|
|
182
186
|
}
|
|
@@ -185,18 +189,26 @@ export class EditorContextService implements IActionHandler, Disposable, IDiagra
|
|
|
185
189
|
return {
|
|
186
190
|
selectedElementIds,
|
|
187
191
|
lastMousePosition: this.mousePositionTracker.lastPositionOnDiagram,
|
|
192
|
+
viewport: this.viewportData,
|
|
193
|
+
canvasBounds: this.canvasBounds,
|
|
188
194
|
args
|
|
189
195
|
};
|
|
190
196
|
}
|
|
191
197
|
|
|
192
|
-
handle(action: Action): void {
|
|
198
|
+
handle(action: Action): Action | void {
|
|
193
199
|
if (SetEditModeAction.is(action)) {
|
|
194
200
|
this.handleSetEditModeAction(action);
|
|
195
201
|
} else if (SetDirtyStateAction.is(action)) {
|
|
196
202
|
this.handleSetDirtyStateAction(action);
|
|
203
|
+
} else if (GetEditorContextAction.is(action)) {
|
|
204
|
+
return this.handleGetEditorContext(action);
|
|
197
205
|
}
|
|
198
206
|
}
|
|
199
207
|
|
|
208
|
+
protected handleGetEditorContext(action: GetEditorContextAction): EditorContextResult {
|
|
209
|
+
return EditorContextResult.create(this.get(), { responseId: action.requestId });
|
|
210
|
+
}
|
|
211
|
+
|
|
200
212
|
protected handleSetEditModeAction(action: SetEditModeAction): void {
|
|
201
213
|
const oldValue = this._editMode;
|
|
202
214
|
this._editMode = action.editMode;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2023-
|
|
2
|
+
* Copyright (c) 2023-2026 EclipseSource and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -117,7 +117,7 @@ export namespace IDiagramStartup {
|
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
export interface DiagramLoadingOptions {
|
|
120
|
+
export interface DiagramLoadingOptions<P extends InitializeParameters = InitializeParameters> {
|
|
121
121
|
/**
|
|
122
122
|
* Optional custom options that should be used the initial {@link RequestModelAction}.
|
|
123
123
|
* These options will be merged with the default options (`diagramType` and `sourceUri`).
|
|
@@ -126,10 +126,12 @@ export interface DiagramLoadingOptions {
|
|
|
126
126
|
requestModelOptions?: Args;
|
|
127
127
|
|
|
128
128
|
/**
|
|
129
|
-
* Optional partial {@link InitializeParameters} that should be used for {@link GLSPClient.initializeServer}
|
|
130
|
-
* {@link GLSPClient} has not been initialized yet.
|
|
129
|
+
* Optional partial {@link InitializeParameters} that should be used for {@link GLSPClient.initializeServer}
|
|
130
|
+
* request if the underlying {@link GLSPClient} has not been initialized yet. The generic type parameter
|
|
131
|
+
* `P` lets callers narrow this to a more specific extension type (e.g., `McpInitializeParameters`)
|
|
132
|
+
* without leaking that extension into the base loader API.
|
|
131
133
|
*/
|
|
132
|
-
initializeParameters?: Partial<
|
|
134
|
+
initializeParameters?: Partial<P>;
|
|
133
135
|
|
|
134
136
|
/**
|
|
135
137
|
* Flag to enable/disable client side notifications during the loading process.
|
|
@@ -171,7 +173,7 @@ export class DiagramLoader {
|
|
|
171
173
|
return this.lazyInjector.getAll<IDiagramStartup>(TYPES.IDiagramStartup);
|
|
172
174
|
}
|
|
173
175
|
|
|
174
|
-
async load(options: DiagramLoadingOptions = {}): Promise<void> {
|
|
176
|
+
async load<P extends InitializeParameters = InitializeParameters>(options: DiagramLoadingOptions<P> = {}): Promise<void> {
|
|
175
177
|
this.diagramStartups.sort(Ranked.sort);
|
|
176
178
|
await this.invokeStartupHook('preLoadDiagram');
|
|
177
179
|
const resolvedOptions: ResolvedDiagramLoadingOptions = {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2023-
|
|
2
|
+
* Copyright (c) 2023-2026 EclipseSource and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -22,10 +22,14 @@ import {
|
|
|
22
22
|
DisposeClientSessionParameters,
|
|
23
23
|
GLSPClient,
|
|
24
24
|
GModelRootSchema,
|
|
25
|
+
IActionDispatcher,
|
|
25
26
|
ILogger,
|
|
26
27
|
InitializeClientSessionParameters,
|
|
27
28
|
InitializeResult,
|
|
28
29
|
ModelSource,
|
|
30
|
+
RejectAction,
|
|
31
|
+
RequestAction,
|
|
32
|
+
ResponseAction,
|
|
29
33
|
TYPES
|
|
30
34
|
} from '@eclipse-glsp/sprotty';
|
|
31
35
|
import { inject, injectable, preDestroy } from 'inversify';
|
|
@@ -94,6 +98,8 @@ export class GLSPModelSource extends ModelSource implements Disposable {
|
|
|
94
98
|
@inject(TYPES.IDiagramOptions)
|
|
95
99
|
protected options: IDiagramOptions;
|
|
96
100
|
|
|
101
|
+
declare readonly actionDispatcher: IActionDispatcher;
|
|
102
|
+
|
|
97
103
|
protected toDispose = new DisposableCollection();
|
|
98
104
|
clientId: string;
|
|
99
105
|
|
|
@@ -164,9 +170,37 @@ export class GLSPModelSource extends ModelSource implements Disposable {
|
|
|
164
170
|
const action = message.action;
|
|
165
171
|
ServerAction.mark(action);
|
|
166
172
|
this.logger.log(this, 'receiving', action);
|
|
173
|
+
if (RequestAction.is(action)) {
|
|
174
|
+
this.handleServerRequest(action);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
167
177
|
this.actionDispatcher.dispatch(action);
|
|
168
178
|
}
|
|
169
179
|
|
|
180
|
+
// Fire-and-forget: intentionally not awaited by messageReceived()
|
|
181
|
+
protected async handleServerRequest(action: RequestAction<ResponseAction>): Promise<void> {
|
|
182
|
+
try {
|
|
183
|
+
const response =
|
|
184
|
+
action.timeout !== undefined
|
|
185
|
+
? await this.actionDispatcher.requestUntil(action, action.timeout, true)
|
|
186
|
+
: await this.actionDispatcher.request(action);
|
|
187
|
+
if (response) {
|
|
188
|
+
this.sendResponseToServer(response);
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
this.logger.error(this, `Failed to handle server request '${action.kind}' (${action.requestId})`, error);
|
|
192
|
+
const reject = RejectAction.create(`Failed to handle request '${action.kind}' (${action.requestId})`, {
|
|
193
|
+
responseId: action.requestId,
|
|
194
|
+
detail: error?.toString?.()
|
|
195
|
+
});
|
|
196
|
+
this.sendResponseToServer(reject);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
protected sendResponseToServer(response: ResponseAction): void {
|
|
201
|
+
this.forwardToServer(response);
|
|
202
|
+
}
|
|
203
|
+
|
|
170
204
|
override initialize(registry: GLSPActionHandlerRegistry): void {
|
|
171
205
|
// Registering actions here is discouraged and it's recommended
|
|
172
206
|
// to implemented dedicated action handlers.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2019-
|
|
2
|
+
* Copyright (c) 2019-2026 EclipseSource and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -23,12 +23,15 @@ import {
|
|
|
23
23
|
Emitter,
|
|
24
24
|
Event,
|
|
25
25
|
GChildElement,
|
|
26
|
+
GetSelectionAction,
|
|
26
27
|
GModelElement,
|
|
27
28
|
GModelRoot,
|
|
29
|
+
IActionHandler,
|
|
28
30
|
ILogger,
|
|
29
31
|
LazyInjector,
|
|
30
32
|
SelectAction,
|
|
31
33
|
SelectAllAction,
|
|
34
|
+
SelectionResult,
|
|
32
35
|
SprottySelectAllCommand,
|
|
33
36
|
SprottySelectCommand,
|
|
34
37
|
TYPES,
|
|
@@ -60,7 +63,7 @@ export interface SelectionChange {
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
@injectable()
|
|
63
|
-
export class SelectionService implements IGModelRootListener, Disposable, IDiagramStartup {
|
|
66
|
+
export class SelectionService implements IGModelRootListener, IActionHandler, Disposable, IDiagramStartup {
|
|
64
67
|
@inject(TYPES.IFeedbackActionDispatcher)
|
|
65
68
|
protected feedbackDispatcher: IFeedbackActionDispatcher;
|
|
66
69
|
|
|
@@ -84,6 +87,12 @@ export class SelectionService implements IGModelRootListener, Disposable, IDiagr
|
|
|
84
87
|
this.lazyInjector.getAll<ISelectionListener>(TYPES.ISelectionListener).forEach(listener => this.addListener(listener));
|
|
85
88
|
}
|
|
86
89
|
|
|
90
|
+
handle(action: Action): Action | void {
|
|
91
|
+
if (GetSelectionAction.is(action)) {
|
|
92
|
+
return SelectionResult.create(this.getSelectedElementIDs(), action.requestId);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
87
96
|
@preDestroy()
|
|
88
97
|
dispose(): void {
|
|
89
98
|
this.toDispose.dispose();
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2023-
|
|
2
|
+
* Copyright (c) 2023-2026 Business Informatics Group (TU Wien) and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -14,9 +14,8 @@
|
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
16
|
|
|
17
|
-
import { GModelRoot, matchesKeystroke, TYPES } from '@eclipse-glsp/sprotty';
|
|
17
|
+
import { GModelRoot, groupBy, matchesKeystroke, TYPES } from '@eclipse-glsp/sprotty';
|
|
18
18
|
import { inject, injectable } from 'inversify';
|
|
19
|
-
import { groupBy } from 'lodash';
|
|
20
19
|
import { GLSPAbstractUIExtension } from '../../base/ui-extension/ui-extension';
|
|
21
20
|
import { messages } from '../messages';
|
|
22
21
|
import type { IShortcutManager, ShortcutRegistration } from './shortcuts-manager';
|
|
@@ -79,7 +78,7 @@ export class AvailableShortcutsUIExtension extends GLSPAbstractUIExtension {
|
|
|
79
78
|
headerRow.appendChild(keybindingCell);
|
|
80
79
|
tableHead.appendChild(headerRow);
|
|
81
80
|
|
|
82
|
-
for (const [group, shortcuts] of
|
|
81
|
+
for (const [group, shortcuts] of grouped.entries()) {
|
|
83
82
|
tableBody.appendChild(this.createGroupHeader(group));
|
|
84
83
|
shortcuts.forEach(s => {
|
|
85
84
|
tableBody.appendChild(this.createEntry(s));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2019-
|
|
2
|
+
* Copyright (c) 2019-2026 EclipseSource and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
Disposable,
|
|
19
19
|
GModelElement,
|
|
20
20
|
GModelRoot,
|
|
21
|
+
groupBy,
|
|
21
22
|
LazyInjector,
|
|
22
23
|
MaybePromise,
|
|
23
24
|
MouseListener,
|
|
@@ -51,7 +52,7 @@ export class GLSPMouseTool extends MouseTool implements IDiagramStartup {
|
|
|
51
52
|
|
|
52
53
|
override register(mouseListener: MouseListener): void {
|
|
53
54
|
super.register(mouseListener);
|
|
54
|
-
this.rankedMouseListeners = groupBy(this.mouseListeners, listener => Ranked.getRank(listener));
|
|
55
|
+
this.rankedMouseListeners = groupBy(this.mouseListeners, listener => Ranked.getRank(listener), true);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
registerListener(mouseListener: MouseListener): Disposable {
|
|
@@ -61,7 +62,7 @@ export class GLSPMouseTool extends MouseTool implements IDiagramStartup {
|
|
|
61
62
|
|
|
62
63
|
override deregister(mouseListener: MouseListener): void {
|
|
63
64
|
super.deregister(mouseListener);
|
|
64
|
-
this.rankedMouseListeners = groupBy(this.mouseListeners, listener => Ranked.getRank(listener));
|
|
65
|
+
this.rankedMouseListeners = groupBy(this.mouseListeners, listener => Ranked.getRank(listener), true);
|
|
65
66
|
}
|
|
66
67
|
|
|
67
68
|
protected override handleEvent<K extends MouseListenerMethods>(methodName: K, model: GModelRoot, event: MouseEvent): void {
|
|
@@ -104,19 +105,3 @@ export class GLSPMouseTool extends MouseTool implements IDiagramStartup {
|
|
|
104
105
|
}
|
|
105
106
|
}
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
-
function groupBy<K, T>(array: Array<T>, keyFunction: (x: T) => K): Map<K, T[]> {
|
|
109
|
-
const unsortedMap = array.reduce((result: Map<K, T[]>, item: T) => {
|
|
110
|
-
const key = keyFunction(item);
|
|
111
|
-
if (!result.has(key)) {
|
|
112
|
-
result.set(key, [item]);
|
|
113
|
-
} else {
|
|
114
|
-
const entries = result.get(key);
|
|
115
|
-
if (entries) {
|
|
116
|
-
entries.push(item);
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
return result;
|
|
120
|
-
}, new Map<K, T[]>());
|
|
121
|
-
return new Map<K, T[]>([...unsortedMap.entries()].sort());
|
|
122
|
-
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2023-
|
|
2
|
+
* Copyright (c) 2023-2026 Business Informatics Group (TU Wien) and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import {
|
|
18
18
|
Action,
|
|
19
19
|
ChangeBoundsOperation,
|
|
20
|
+
DebouncedFunc,
|
|
20
21
|
ElementAndBounds,
|
|
21
22
|
ElementMove,
|
|
22
23
|
IActionDispatcher,
|
|
@@ -27,10 +28,10 @@ import {
|
|
|
27
28
|
MoveViewportAction,
|
|
28
29
|
Point,
|
|
29
30
|
TYPES,
|
|
31
|
+
debounce,
|
|
30
32
|
isBoundsAware
|
|
31
33
|
} from '@eclipse-glsp/sprotty';
|
|
32
34
|
import { inject, injectable, optional, postConstruct } from 'inversify';
|
|
33
|
-
import { DebouncedFunc, debounce } from 'lodash';
|
|
34
35
|
import { EditorContextService } from '../../base/editor-context-service';
|
|
35
36
|
import { IFeedbackActionDispatcher } from '../../base/feedback/feedback-action-dispatcher';
|
|
36
37
|
import { FeedbackEmitter } from '../../base/feedback/feedback-emitter';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/********************************************************************************
|
|
2
|
-
* Copyright (c) 2023-
|
|
2
|
+
* Copyright (c) 2023-2026 Business Informatics Group (TU Wien) and others.
|
|
3
3
|
*
|
|
4
4
|
* This program and the accompanying materials are made available under the
|
|
5
5
|
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
import {
|
|
18
18
|
Action,
|
|
19
19
|
ChangeBoundsOperation,
|
|
20
|
+
DebouncedFunc,
|
|
20
21
|
Dimension,
|
|
21
22
|
ElementAndBounds,
|
|
22
23
|
GModelElement,
|
|
@@ -27,10 +28,10 @@ import {
|
|
|
27
28
|
ISnapper,
|
|
28
29
|
Point,
|
|
29
30
|
SetBoundsAction,
|
|
30
|
-
TYPES
|
|
31
|
+
TYPES,
|
|
32
|
+
debounce
|
|
31
33
|
} from '@eclipse-glsp/sprotty';
|
|
32
34
|
import { inject, injectable, optional, postConstruct } from 'inversify';
|
|
33
|
-
import { DebouncedFunc, debounce } from 'lodash';
|
|
34
35
|
import { EditorContextService } from '../../../base/editor-context-service';
|
|
35
36
|
import { IFeedbackActionDispatcher } from '../../../base/feedback/feedback-action-dispatcher';
|
|
36
37
|
import { FeedbackEmitter } from '../../../base/feedback/feedback-emitter';
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the Eclipse Public License v. 2.0 which is available at
|
|
6
|
+
* http://www.eclipse.org/legal/epl-2.0.
|
|
7
|
+
*
|
|
8
|
+
* This Source Code may also be made available under the following Secondary
|
|
9
|
+
* Licenses when the conditions for such availability set forth in the Eclipse
|
|
10
|
+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
|
|
11
|
+
* with the GNU Classpath Exception which is available at
|
|
12
|
+
* https://www.gnu.org/software/classpath/license.html.
|
|
13
|
+
*
|
|
14
|
+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
|
+
********************************************************************************/
|
|
16
|
+
|
|
17
|
+
import { Action, ExportFormat, ExportMimeType, GModelRoot, PngExportOptions } from '@eclipse-glsp/sprotty';
|
|
18
|
+
import { inject, injectable } from 'inversify';
|
|
19
|
+
import { DiagramExporter } from './diagram-exporter';
|
|
20
|
+
import { GLSPSvgExporter } from './glsp-svg-exporter';
|
|
21
|
+
|
|
22
|
+
/** Default raster width (in CSS px) when neither `width` nor `height` is specified. */
|
|
23
|
+
const DEFAULT_PNG_WIDTH = 1024;
|
|
24
|
+
const DEFAULT_PNG_BACKGROUND = 'white';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Default PNG strategy for the unified export registry. Reuses {@link GLSPSvgExporter}'s
|
|
28
|
+
* SVG-rendering pipeline and rasterises the result client-side via `OffscreenCanvas`.
|
|
29
|
+
* Adopters that need server-side rendering replace this binding with their own strategy.
|
|
30
|
+
*/
|
|
31
|
+
@injectable()
|
|
32
|
+
export class DefaultPngDiagramExporter implements DiagramExporter<PngExportOptions> {
|
|
33
|
+
readonly format: ExportFormat = 'png';
|
|
34
|
+
readonly mimeType: ExportMimeType = 'image/png';
|
|
35
|
+
readonly encoding = 'base64' as const;
|
|
36
|
+
|
|
37
|
+
@inject(GLSPSvgExporter) protected svgExporter: GLSPSvgExporter;
|
|
38
|
+
|
|
39
|
+
async export(root: GModelRoot, options: PngExportOptions = {}, cause?: Action): Promise<string> {
|
|
40
|
+
if (typeof document === 'undefined' || typeof OffscreenCanvas === 'undefined') {
|
|
41
|
+
throw new Error('PNG export failed: requires a DOM environment with OffscreenCanvas support');
|
|
42
|
+
}
|
|
43
|
+
const svgString = this.svgExporter.exportToString(root, options, cause);
|
|
44
|
+
const { width, height } = this.computeDimensions(svgString, options);
|
|
45
|
+
// Pin the SVG root to the target raster size BEFORE loading into `<img>` so the browser
|
|
46
|
+
// rasterises the vector content at full resolution.
|
|
47
|
+
const sizedSvg = this.applyRasterSize(svgString, width, height);
|
|
48
|
+
const blob = await this.rasterise(sizedSvg, width, height, options.background ?? DEFAULT_PNG_BACKGROUND);
|
|
49
|
+
return this.blobToBase64(blob);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Rewrite the root `<svg>` tag so its rendered size matches the target raster dimensions.
|
|
54
|
+
* Replaces `width`/`height` attributes and strips conflicting `style` declarations
|
|
55
|
+
* (the SVG exporter emits `width: …px !important` on the inline style for viewer-friendly
|
|
56
|
+
* display, which would otherwise win over the attributes). The existing `viewBox`
|
|
57
|
+
* preserves the vector coordinate system so the content scales without distortion.
|
|
58
|
+
*/
|
|
59
|
+
protected applyRasterSize(svgString: string, width: number, height: number): string {
|
|
60
|
+
return svgString.replace(/<svg\b([^>]*)>/, (_, attrs: string) => {
|
|
61
|
+
const cleaned = attrs
|
|
62
|
+
.replace(/\swidth="[^"]*"/, '')
|
|
63
|
+
.replace(/\sheight="[^"]*"/, '')
|
|
64
|
+
.replace(/style="([^"]*)"/, (_match, styleBody: string) => {
|
|
65
|
+
const stripped = styleBody
|
|
66
|
+
.replace(/(?:^|;)\s*width\s*:[^;]*/gi, '')
|
|
67
|
+
.replace(/(?:^|;)\s*height\s*:[^;]*/gi, '')
|
|
68
|
+
.replace(/^\s*;+/, '')
|
|
69
|
+
.trim();
|
|
70
|
+
return stripped ? `style="${stripped}"` : '';
|
|
71
|
+
});
|
|
72
|
+
return `<svg${cleaned} width="${width}" height="${height}">`;
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
protected computeDimensions(svgString: string, options: PngExportOptions): { width: number; height: number } {
|
|
77
|
+
const requestedWidth = options.width ?? DEFAULT_PNG_WIDTH;
|
|
78
|
+
const requestedHeight = options.height;
|
|
79
|
+
if (requestedHeight !== undefined) {
|
|
80
|
+
return { width: requestedWidth, height: requestedHeight };
|
|
81
|
+
}
|
|
82
|
+
// Read the aspect from the SVG attributes directly to avoid a redundant bitmap decode.
|
|
83
|
+
const aspect = this.parseAspectRatio(svgString);
|
|
84
|
+
return { width: requestedWidth, height: requestedWidth / aspect };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Read the aspect ratio from the root `<svg>` tag's `viewBox`, falling back to the
|
|
89
|
+
* `width`/`height` attributes. GLSP-emitted SVGs always carry a `viewBox` (set by the
|
|
90
|
+
* underlying sprotty exporter), so the fallback is a defensive safeguard.
|
|
91
|
+
*/
|
|
92
|
+
protected parseAspectRatio(svgString: string): number {
|
|
93
|
+
const root = svgString.match(/<svg\b([^>]*)>/);
|
|
94
|
+
const attrs = root?.[1] ?? '';
|
|
95
|
+
const viewBox = attrs.match(/viewBox\s*=\s*"\s*[\d.eE+-]+\s+[\d.eE+-]+\s+([\d.eE+-]+)\s+([\d.eE+-]+)\s*"/);
|
|
96
|
+
if (viewBox) {
|
|
97
|
+
return parseFloat(viewBox[1]) / parseFloat(viewBox[2]);
|
|
98
|
+
}
|
|
99
|
+
const widthAttr = attrs.match(/\swidth\s*=\s*"([\d.eE+-]+)/);
|
|
100
|
+
const heightAttr = attrs.match(/\sheight\s*=\s*"([\d.eE+-]+)/);
|
|
101
|
+
if (widthAttr && heightAttr) {
|
|
102
|
+
return parseFloat(widthAttr[1]) / parseFloat(heightAttr[1]);
|
|
103
|
+
}
|
|
104
|
+
throw new Error('PNG export failed: SVG missing viewBox/width/height — cannot determine aspect ratio');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
protected async rasterise(svgString: string, width: number, height: number, background: string): Promise<Blob> {
|
|
108
|
+
const url = URL.createObjectURL(new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' }));
|
|
109
|
+
try {
|
|
110
|
+
const bitmap = await this.bitmapFromSvgUrl(url);
|
|
111
|
+
const offscreen = new OffscreenCanvas(width, height);
|
|
112
|
+
const ctx = offscreen.getContext('2d');
|
|
113
|
+
if (!ctx) {
|
|
114
|
+
throw new Error('PNG export failed: 2D context unavailable on OffscreenCanvas');
|
|
115
|
+
}
|
|
116
|
+
ctx.fillStyle = background;
|
|
117
|
+
ctx.fillRect(0, 0, width, height);
|
|
118
|
+
ctx.drawImage(bitmap, 0, 0, width, height);
|
|
119
|
+
return offscreen.convertToBlob({ type: 'image/png' });
|
|
120
|
+
} finally {
|
|
121
|
+
URL.revokeObjectURL(url);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
protected bitmapFromSvgUrl(url: string): Promise<ImageBitmap> {
|
|
126
|
+
return new Promise((resolve, reject) => {
|
|
127
|
+
const img = new Image();
|
|
128
|
+
img.onload = () => createImageBitmap(img).then(resolve, reject);
|
|
129
|
+
img.onerror = () => reject(new Error('PNG export failed: SVG could not be loaded into Image'));
|
|
130
|
+
img.src = url;
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected blobToBase64(blob: Blob): Promise<string> {
|
|
135
|
+
return new Promise((resolve, reject) => {
|
|
136
|
+
const reader = new FileReader();
|
|
137
|
+
reader.onloadend = () => {
|
|
138
|
+
const result = reader.result as string;
|
|
139
|
+
resolve(result.replace(/^data:[^;]+;base64,/, ''));
|
|
140
|
+
};
|
|
141
|
+
reader.onerror = () => reject(reader.error ?? new Error('PNG export failed: FileReader error'));
|
|
142
|
+
reader.readAsDataURL(blob);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|