@hydranium/glsp-client 1.0.0-next.379 → 1.0.0-next.381
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/README.md +22 -7
- package/lib/diagram-message-details.d.ts +18 -0
- package/lib/diagram-message-details.d.ts.map +1 -0
- package/lib/diagram-message-details.js +22 -0
- package/lib/diagram-message-details.js.map +1 -0
- package/lib/diagram-message-dismiss.d.ts +18 -0
- package/lib/diagram-message-dismiss.d.ts.map +1 -0
- package/lib/diagram-message-dismiss.js +22 -0
- package/lib/diagram-message-dismiss.js.map +1 -0
- package/lib/diagram-mount.d.ts +4 -0
- package/lib/diagram-mount.d.ts.map +1 -1
- package/lib/diagram-mount.js +17 -0
- package/lib/diagram-mount.js.map +1 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.d.ts.map +1 -1
- package/lib/index.js +3 -0
- package/lib/index.js.map +1 -1
- package/lib/message-overlay.d.ts +59 -0
- package/lib/message-overlay.d.ts.map +1 -0
- package/lib/message-overlay.js +230 -0
- package/lib/message-overlay.js.map +1 -0
- package/lib/messages/index.d.ts +2 -0
- package/lib/messages/index.d.ts.map +1 -1
- package/lib/messages/index.js +5 -1
- package/lib/messages/index.js.map +1 -1
- package/lib/reattach.d.ts +15 -0
- package/lib/reattach.d.ts.map +1 -0
- package/lib/reattach.js +26 -0
- package/lib/reattach.js.map +1 -0
- package/lib/status-overlay.d.ts.map +1 -1
- package/lib/status-overlay.js +2 -8
- package/lib/status-overlay.js.map +1 -1
- package/package.json +12 -6
- package/src/diagram-message-details.ts +20 -0
- package/src/diagram-message-dismiss.ts +20 -0
- package/src/diagram-mount.ts +19 -0
- package/src/index.ts +3 -0
- package/src/message-overlay.ts +254 -0
- package/src/messages/index.ts +2 -0
- package/src/reattach.ts +27 -0
- package/src/status-overlay.ts +6 -8
- package/style/message-overlay.css +74 -0
|
@@ -0,0 +1,15 @@
|
|
|
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 MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
/**
|
|
10
|
+
* Puts a UI extension's element back into the diagram's base div. sprotty's
|
|
11
|
+
* first render replaces that div, so an element inserted before it is left
|
|
12
|
+
* detached. Takes the extension's own lookups, which are protected.
|
|
13
|
+
*/
|
|
14
|
+
export declare function reattachUIExtension(container: HTMLElement | undefined, parentContainer: () => HTMLElement | null | undefined, insert: (container: HTMLElement, parent: HTMLElement) => void): void;
|
|
15
|
+
//# sourceMappingURL=reattach.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reattach.d.ts","sourceRoot":"","sources":["../src/reattach.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;GAIG;AACH,wBAAgB,mBAAmB,CAChC,SAAS,EAAE,WAAW,GAAG,SAAS,EAClC,eAAe,EAAE,MAAM,WAAW,GAAG,IAAI,GAAG,SAAS,EACrD,MAAM,EAAE,CAAC,SAAS,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,KAAK,IAAI,GAC7D,IAAI,CAQN"}
|
package/lib/reattach.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/********************************************************************************
|
|
3
|
+
* Copyright (c) 2026 EclipseSource and others.
|
|
4
|
+
*
|
|
5
|
+
* This program and the accompanying materials are made available under the
|
|
6
|
+
* terms of the MIT License which is available in the project root.
|
|
7
|
+
*
|
|
8
|
+
* SPDX-License-Identifier: MIT
|
|
9
|
+
********************************************************************************/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.reattachUIExtension = reattachUIExtension;
|
|
12
|
+
/**
|
|
13
|
+
* Puts a UI extension's element back into the diagram's base div. sprotty's
|
|
14
|
+
* first render replaces that div, so an element inserted before it is left
|
|
15
|
+
* detached. Takes the extension's own lookups, which are protected.
|
|
16
|
+
*/
|
|
17
|
+
function reattachUIExtension(container, parentContainer, insert) {
|
|
18
|
+
if (!container || container.isConnected) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const parent = parentContainer();
|
|
22
|
+
if (parent) {
|
|
23
|
+
insert(container, parent);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=reattach.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reattach.js","sourceRoot":"","sources":["../src/reattach.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;AAElF;;;;GAIG;AACH,6BACG,SAAkC,EAClC,eAAqD,EACrD,MAA6D;IAE7D,IAAI,CAAC,SAAS,IAAI,SAAS,CAAC,WAAW,EAAE,CAAC;QACvC,OAAO;IACV,CAAC;IACD,MAAM,MAAM,GAAG,eAAe,EAAE,CAAC;IACjC,IAAI,MAAM,EAAE,CAAC;QACV,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC;AACJ,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-overlay.d.ts","sourceRoot":"","sources":["../src/status-overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"status-overlay.d.ts","sourceRoot":"","sources":["../src/status-overlay.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF,OAAO,EAAE,KAAK,YAAY,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIxE;;;;;;;GAOG;AACH,qBACa,sBAAuB,SAAQ,aAAa;IAC7C,MAAM,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAG1C;IAEQ,IAAI,CAAC,GAAG,IAAI,EAAE,UAAU,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,GAAG,IAAI,CAG9D;IAED,SAAS,CAAC,QAAQ,IAAI,IAAI,CAMzB;CACH"}
|
package/lib/status-overlay.js
CHANGED
|
@@ -17,6 +17,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
17
17
|
exports.HydraniumStatusOverlay = void 0;
|
|
18
18
|
const client_1 = require("@eclipse-glsp/client");
|
|
19
19
|
const inversify_1 = require("inversify");
|
|
20
|
+
const reattach_1 = require("./reattach");
|
|
20
21
|
/**
|
|
21
22
|
* GLSP's status overlay, kept on the page.
|
|
22
23
|
*
|
|
@@ -35,14 +36,7 @@ let HydraniumStatusOverlay = class HydraniumStatusOverlay extends client_1.Statu
|
|
|
35
36
|
this.reattach();
|
|
36
37
|
}
|
|
37
38
|
reattach() {
|
|
38
|
-
|
|
39
|
-
if (!container || container.isConnected) {
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
const parent = this.getParentContainer();
|
|
43
|
-
if (parent) {
|
|
44
|
-
this.insertContainerIntoParent(container, parent);
|
|
45
|
-
}
|
|
39
|
+
(0, reattach_1.reattachUIExtension)(this.containerElement, () => this.getParentContainer(), (container, parent) => this.insertContainerIntoParent(container, parent));
|
|
46
40
|
}
|
|
47
41
|
};
|
|
48
42
|
exports.HydraniumStatusOverlay = HydraniumStatusOverlay;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"status-overlay.js","sourceRoot":"","sources":["../src/status-overlay.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;AAElF,iDAAwE;AACxE,yCAAuC;
|
|
1
|
+
{"version":3,"file":"status-overlay.js","sourceRoot":"","sources":["../src/status-overlay.ts"],"names":[],"mappings":";AAAA;;;;;;;kFAOkF;;;;;;;;;AAElF,iDAAwE;AACxE,yCAAuC;AACvC,yCAAiD;AAEjD;;;;;;;GAOG;AAEI,IAAM,sBAAsB,GAA5B,MAAM,sBAAuB,SAAQ,sBAAa;IAC7C,MAAM,CAAC,MAAoB;QACjC,IAAI,CAAC,QAAQ,EAAE,CAAC;QAChB,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACxB,CAAC;IAEQ,IAAI,CAAC,GAAG,IAAuC;QACrD,KAAK,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,CAAC;QACpB,IAAI,CAAC,QAAQ,EAAE,CAAC;IACnB,CAAC;IAES,QAAQ;QACf,IAAA,8BAAmB,EAChB,IAAI,CAAC,gBAA2C,EAChD,GAAG,EAAE,CAAC,IAAI,CAAC,kBAAkB,EAAE,EAC/B,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,CAAC,IAAI,CAAC,yBAAyB,CAAC,SAAS,EAAE,MAAM,CAAC,CAC1E,CAAC;IACL,CAAC;CACH,CAAA;;iCAlBY,sBAAsB;IADlC,IAAA,sBAAU,GAAE;GACA,sBAAsB,CAkBlC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/glsp-client",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.381",
|
|
4
4
|
"description": "Host-neutral client classes for GLSP heads built on @hydranium/glsp-server, the binder that mounts them in a Theia frontend, a VS Code webview or a plain browser page, the one call that mounts a diagram in a page, and the edge whose labels keep finite bounds without bendpoints.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"diagram",
|
|
@@ -26,9 +26,12 @@
|
|
|
26
26
|
"files": [
|
|
27
27
|
"lib",
|
|
28
28
|
"src",
|
|
29
|
+
"style",
|
|
29
30
|
"!lib/**/*.tsbuildinfo"
|
|
30
31
|
],
|
|
31
|
-
"sideEffects":
|
|
32
|
+
"sideEffects": [
|
|
33
|
+
"**/*.css"
|
|
34
|
+
],
|
|
32
35
|
"main": "lib/index.js",
|
|
33
36
|
"types": "lib/index.d.ts",
|
|
34
37
|
"exports": {
|
|
@@ -43,7 +46,8 @@
|
|
|
43
46
|
"./testing": {
|
|
44
47
|
"types": "./lib/testing/index.d.ts",
|
|
45
48
|
"default": "./lib/testing/index.js"
|
|
46
|
-
}
|
|
49
|
+
},
|
|
50
|
+
"./style/message-overlay.css": "./style/message-overlay.css"
|
|
47
51
|
},
|
|
48
52
|
"publishConfig": {
|
|
49
53
|
"access": "public"
|
|
@@ -59,7 +63,7 @@
|
|
|
59
63
|
},
|
|
60
64
|
"devDependencies": {
|
|
61
65
|
"@eclipse-glsp/client": "2.8.0",
|
|
62
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
66
|
+
"@hydranium/protocol": "1.0.0-next.381",
|
|
63
67
|
"@types/node": "^22.0.0",
|
|
64
68
|
"inversify": "6.2.2",
|
|
65
69
|
"reflect-metadata": "0.2.2",
|
|
@@ -69,13 +73,15 @@
|
|
|
69
73
|
},
|
|
70
74
|
"peerDependencies": {
|
|
71
75
|
"@eclipse-glsp/client": "^2.7.0",
|
|
72
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
76
|
+
"@hydranium/protocol": "1.0.0-next.381",
|
|
73
77
|
"inversify": "^6.1.3",
|
|
74
78
|
"snabbdom": "~3.5.1"
|
|
75
79
|
},
|
|
76
80
|
"engines": {
|
|
77
81
|
"node": ">=22.13"
|
|
78
82
|
},
|
|
83
|
+
"//exports": "The stylesheet key is an asset entry, spelled as the exact filename so nothing becomes reachable automatically. It lets an adopter `@import` the sheet, re-bundle it or override it by specifier. `tsc` emits no assets, and `check:exports` asserts the file exists and ships through `files`.",
|
|
79
84
|
"//peerDependencies": "`inversify` is imported plainly, as `@eclipse-glsp/client` imports it, and peered so the classes here decorate with the same copy the diagram container resolves; `@theia/core/shared/inversify` re-exports that copy, so a Theia frontend satisfies it without a second install. `snabbdom` is a peer for the `VNode` type the hidden-bounds updater reads, the same reason the Theia package peers it.",
|
|
80
|
-
"//prepack": "The publish guard, a `prepack` rather than a `prepare`: `prepack` runs only when a tarball is made (`npm pack`, `npm publish`) and never on install, so it cannot break the install it has no business touching. What it defends against: `files` lists `lib`, `lib` is gitignored, and a `files` entry matching nothing is skipped SILENTLY — so `npm publish` from an unbuilt tree emits a tarball of `src` and nothing else, with no error."
|
|
85
|
+
"//prepack": "The publish guard, a `prepack` rather than a `prepare`: `prepack` runs only when a tarball is made (`npm pack`, `npm publish`) and never on install, so it cannot break the install it has no business touching. What it defends against: `files` lists `lib`, `lib` is gitignored, and a `files` entry matching nothing is skipped SILENTLY — so `npm publish` from an unbuilt tree emits a tarball of `src` and nothing else, with no error.",
|
|
86
|
+
"//sideEffects": "A stylesheet IS the side effect: `style/message-overlay.css` is pulled in by a bare import in `message-overlay.js`, and a CSS module exports nothing, so under a blanket `false` a bundler drops the import as unused and the diagram's messages render unstyled. Nothing fails at build time."
|
|
81
87
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
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 MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { defineMessage } from '@hydranium/protocol';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The label of the disclosure that holds a server message's details on the
|
|
14
|
+
* diagram. Formatted in English by `HydraniumMessageOverlay`. A host that knows
|
|
15
|
+
* the reading user's locale overrides its `detailsLabel`.
|
|
16
|
+
*
|
|
17
|
+
* In a module of its own, apart from the overlay, so the `./messages` barrel
|
|
18
|
+
* loads no `@eclipse-glsp/client`.
|
|
19
|
+
*/
|
|
20
|
+
export const DIAGRAM_MESSAGE_DETAILS = defineMessage('hydranium/glsp-client/diagram-message-details', 'Details');
|
|
@@ -0,0 +1,20 @@
|
|
|
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 MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import { defineMessage } from '@hydranium/protocol';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The label of the button that closes a server message on the diagram.
|
|
14
|
+
* Formatted in English by `HydraniumMessageOverlay`. A host that knows the
|
|
15
|
+
* reading user's locale overrides its `dismissLabel`.
|
|
16
|
+
*
|
|
17
|
+
* In a module of its own, apart from the overlay, so the `./messages` barrel
|
|
18
|
+
* loads no `@eclipse-glsp/client`.
|
|
19
|
+
*/
|
|
20
|
+
export const DIAGRAM_MESSAGE_DISMISS = defineMessage('hydranium/glsp-client/diagram-message-dismiss', 'Dismiss');
|
package/src/diagram-mount.ts
CHANGED
|
@@ -13,7 +13,9 @@ import {
|
|
|
13
13
|
createDiagramOptionsModule,
|
|
14
14
|
DiagramLoader,
|
|
15
15
|
EditorContextService,
|
|
16
|
+
FallbackActionHandler,
|
|
16
17
|
FitToScreenAction,
|
|
18
|
+
type IActionHandler,
|
|
17
19
|
type GLSPActionDispatcher,
|
|
18
20
|
type IDiagramOptions,
|
|
19
21
|
InitializeCanvasBoundsAction,
|
|
@@ -26,6 +28,7 @@ import { HydraniumGlspActionDispatcher } from './action-dispatcher';
|
|
|
26
28
|
import { type DiagramLoadOutcome, HydraniumDiagramLoader } from './diagram-loader';
|
|
27
29
|
import type { HydraniumGlspClient } from './glsp-client';
|
|
28
30
|
import { createGlspClientModule } from './glsp-client-module';
|
|
31
|
+
import { HydraniumMessageOverlay } from './message-overlay';
|
|
29
32
|
import { enableTouchDragging } from './touch-input';
|
|
30
33
|
import { HydraniumClientTypes } from './types';
|
|
31
34
|
|
|
@@ -117,6 +120,10 @@ export const DIAGRAM_FIT_PADDING = 24;
|
|
|
117
120
|
* that scrolls, send the element's box in page coordinates whenever it
|
|
118
121
|
* differs from the model's. A zero-sized box, which a pane dragged shut
|
|
119
122
|
* reports, is skipped: a zero canvas culls the whole model.
|
|
123
|
+
* - **Server messages.** GLSP's standalone modules only log the server's
|
|
124
|
+
* messages and progress reports, so a refused undo changes nothing the user
|
|
125
|
+
* sees. {@link HydraniumMessageOverlay} shows them on the diagram instead.
|
|
126
|
+
* Rebind it to a subclass to show them elsewhere.
|
|
120
127
|
* - **Touch input.** sprotty binds mouse events only; see
|
|
121
128
|
* {@link enableTouchDragging}. Give the element `touch-action: none`.
|
|
122
129
|
* - **The context menu.** GLSP warns on every container built without a
|
|
@@ -149,6 +156,18 @@ export async function mountGlspDiagram(options: GlspDiagramMountOptions): Promis
|
|
|
149
156
|
// a token those modules bind.
|
|
150
157
|
const hydraniumModule = new ContainerModule((bind, unbind, isBound, rebind) => {
|
|
151
158
|
createGlspClientModule({ bind, unbind, isBound, rebind }, { logger: options.logger, trackEditsInFlight: true });
|
|
159
|
+
// The fallback is what the standalone modules register for the server's
|
|
160
|
+
// messages and progress, and it only logs them. A singleton, since GLSP
|
|
161
|
+
// resolves it once per action kind and a host may rebind the overlay
|
|
162
|
+
// without a scope.
|
|
163
|
+
bind(HydraniumMessageOverlay).toSelf().inSingletonScope();
|
|
164
|
+
if (isBound(FallbackActionHandler)) {
|
|
165
|
+
rebind<IActionHandler>(FallbackActionHandler)
|
|
166
|
+
.toDynamicValue(context => context.container.get(HydraniumMessageOverlay))
|
|
167
|
+
.inSingletonScope();
|
|
168
|
+
// Through the fallback, so the instance started is the one showing the messages.
|
|
169
|
+
bind(TYPES.IDiagramStartup).toService(FallbackActionHandler);
|
|
170
|
+
}
|
|
152
171
|
});
|
|
153
172
|
const container = options.initializeDiagramContainer(
|
|
154
173
|
new Container(),
|
package/src/index.ts
CHANGED
|
@@ -15,12 +15,15 @@
|
|
|
15
15
|
export * from './action-dispatcher';
|
|
16
16
|
export * from './diagram-load-failed';
|
|
17
17
|
export * from './diagram-loader';
|
|
18
|
+
export * from './diagram-message-details';
|
|
19
|
+
export * from './diagram-message-dismiss';
|
|
18
20
|
export * from './diagram-mount';
|
|
19
21
|
export * from './edge';
|
|
20
22
|
export * from './glsp-client';
|
|
21
23
|
export * from './glsp-connection';
|
|
22
24
|
export * from './glsp-client-module';
|
|
23
25
|
export * from './hidden-bounds-updater';
|
|
26
|
+
export * from './message-overlay';
|
|
24
27
|
export * from './select-mouse-listener';
|
|
25
28
|
export * from './sprotty-logger-adapter';
|
|
26
29
|
export * from './status-overlay';
|
|
@@ -0,0 +1,254 @@
|
|
|
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 MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
type Action,
|
|
12
|
+
EndProgressAction,
|
|
13
|
+
GLSPAbstractUIExtension,
|
|
14
|
+
type IActionHandler,
|
|
15
|
+
type IDiagramStartup,
|
|
16
|
+
MessageAction,
|
|
17
|
+
StartProgressAction,
|
|
18
|
+
UpdateProgressAction
|
|
19
|
+
} from '@eclipse-glsp/client';
|
|
20
|
+
import { injectable } from 'inversify';
|
|
21
|
+
import '../style/message-overlay.css';
|
|
22
|
+
import { DIAGRAM_MESSAGE_DETAILS } from './diagram-message-details';
|
|
23
|
+
import { DIAGRAM_MESSAGE_DISMISS } from './diagram-message-dismiss';
|
|
24
|
+
import { reattachUIExtension } from './reattach';
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Shows the server's messages and progress reports on the diagram, for a host
|
|
28
|
+
* with no notifications of its own. GLSP's standalone modules only log them,
|
|
29
|
+
* so a refused undo would otherwise change nothing the user can see.
|
|
30
|
+
*
|
|
31
|
+
* A message stays until it is dismissed, and only the newest
|
|
32
|
+
* {@link HydraniumMessageOverlay.maxMessages} are kept. A repeated one moves
|
|
33
|
+
* to the newest instead of stacking. A progress report shows from its start
|
|
34
|
+
* to its end, with a spinner until it reports a percentage. Severity `NONE`
|
|
35
|
+
* says nothing and is dropped.
|
|
36
|
+
*
|
|
37
|
+
* A message row shows its severity's icon and carries the severity as a
|
|
38
|
+
* class, and the stylesheet edges it in GLSP's `--glsp-*-foreground` colour.
|
|
39
|
+
* A progress row has the info edge. A host with notifications of its own
|
|
40
|
+
* subclasses this class to forward them, and rebinds it to the subclass in a
|
|
41
|
+
* module only that host loads.
|
|
42
|
+
*/
|
|
43
|
+
@injectable()
|
|
44
|
+
export class HydraniumMessageOverlay extends GLSPAbstractUIExtension implements IActionHandler, IDiagramStartup {
|
|
45
|
+
static readonly ID = 'hydranium-message-overlay';
|
|
46
|
+
|
|
47
|
+
/** How many distinct messages are kept, so they cannot cover the canvas. A repeat replaces its row. */
|
|
48
|
+
protected maxMessages = 3;
|
|
49
|
+
protected readonly messageRows: HTMLElement[] = [];
|
|
50
|
+
protected readonly progressRows = new Map<string, HTMLElement>();
|
|
51
|
+
|
|
52
|
+
id(): string {
|
|
53
|
+
return HydraniumMessageOverlay.ID;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
containerClass(): string {
|
|
57
|
+
return 'hydranium-messages';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected initializeContents(_container: HTMLElement): void {
|
|
61
|
+
// Rows are added as messages arrive.
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
handle(action: Action): void {
|
|
65
|
+
if (MessageAction.is(action)) {
|
|
66
|
+
this.showMessage(action);
|
|
67
|
+
} else if (StartProgressAction.is(action)) {
|
|
68
|
+
this.startProgress(action);
|
|
69
|
+
} else if (UpdateProgressAction.is(action)) {
|
|
70
|
+
this.updateProgress(action);
|
|
71
|
+
} else if (EndProgressAction.is(action)) {
|
|
72
|
+
this.endProgress(action);
|
|
73
|
+
} else {
|
|
74
|
+
this.logger.log(this, 'Unhandled action received:', action);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
protected showMessage(action: MessageAction): void {
|
|
79
|
+
if (action.severity === 'NONE') {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
// An alert is announced when it is inserted, which a status in a region
|
|
83
|
+
// created with it is not. A refused undo is a warning and must be heard.
|
|
84
|
+
const kind = action.severity.toLowerCase();
|
|
85
|
+
const row = this.createRow(kind, action.severity !== 'INFO' && action.severity !== 'OK');
|
|
86
|
+
row.append(this.createIcon(action.severity), this.createText('hydranium-message-text', action.message));
|
|
87
|
+
if (action.details) {
|
|
88
|
+
const details = document.createElement('details');
|
|
89
|
+
const summary = document.createElement('summary');
|
|
90
|
+
summary.textContent = this.detailsLabel();
|
|
91
|
+
const text = document.createElement('pre');
|
|
92
|
+
text.textContent = action.details;
|
|
93
|
+
details.append(summary, text);
|
|
94
|
+
row.append(details);
|
|
95
|
+
}
|
|
96
|
+
const dismiss = document.createElement('button');
|
|
97
|
+
dismiss.setAttribute('type', 'button');
|
|
98
|
+
dismiss.classList.add('codicon', 'codicon-close');
|
|
99
|
+
dismiss.setAttribute('aria-label', this.dismissLabel());
|
|
100
|
+
dismiss.addEventListener('click', () => this.removeMessage(row));
|
|
101
|
+
row.append(dismiss);
|
|
102
|
+
if (!this.addRow(row, action.message)) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
// A held Ctrl+Z repeats its refusal, which the newest row then says once.
|
|
106
|
+
// Its details replace those of the row it repeats.
|
|
107
|
+
const repeated = this.messageRows.find(
|
|
108
|
+
shown =>
|
|
109
|
+
shown.classList.contains(`hydranium-message-${kind}`) &&
|
|
110
|
+
shown.querySelector('.hydranium-message-text')?.textContent === action.message
|
|
111
|
+
);
|
|
112
|
+
if (repeated) {
|
|
113
|
+
this.removeMessage(repeated);
|
|
114
|
+
}
|
|
115
|
+
this.messageRows.push(row);
|
|
116
|
+
while (this.messageRows.length > this.maxMessages) {
|
|
117
|
+
this.removeMessage(this.messageRows[0]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
protected removeMessage(row: HTMLElement): void {
|
|
122
|
+
const index = this.messageRows.indexOf(row);
|
|
123
|
+
if (index >= 0) {
|
|
124
|
+
this.messageRows.splice(index, 1);
|
|
125
|
+
}
|
|
126
|
+
// Back to the diagram, as a row leaving the page would drop the focus on the body.
|
|
127
|
+
const hadFocus = row.contains(document.activeElement);
|
|
128
|
+
row.remove();
|
|
129
|
+
if (hadFocus) {
|
|
130
|
+
this.focusDiagram();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
protected startProgress(action: StartProgressAction): void {
|
|
135
|
+
// A start repeated under its id would otherwise leave the first row with no end.
|
|
136
|
+
if (this.progressRows.has(action.progressId)) {
|
|
137
|
+
this.endProgress(EndProgressAction.create(action.progressId));
|
|
138
|
+
}
|
|
139
|
+
const row = this.createRow('progress', false);
|
|
140
|
+
const spinner = document.createElement('i');
|
|
141
|
+
spinner.classList.add('hydranium-progress-spinner', 'codicon', 'codicon-loading', 'codicon-modifier-spin');
|
|
142
|
+
spinner.setAttribute('aria-hidden', 'true');
|
|
143
|
+
row.append(
|
|
144
|
+
spinner,
|
|
145
|
+
this.createText('hydranium-message-text', action.title),
|
|
146
|
+
this.createText('hydranium-progress-message', ''),
|
|
147
|
+
this.createText('hydranium-progress-percentage', '')
|
|
148
|
+
);
|
|
149
|
+
if (this.addRow(row, action.title)) {
|
|
150
|
+
this.progressRows.set(action.progressId, row);
|
|
151
|
+
this.updateProgress(action);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
protected updateProgress(action: Pick<UpdateProgressAction, 'progressId' | 'message' | 'percentage'>): void {
|
|
156
|
+
const row = this.progressRows.get(action.progressId);
|
|
157
|
+
if (row === undefined) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const message = row.querySelector('.hydranium-progress-message');
|
|
161
|
+
const percentage = row.querySelector('.hydranium-progress-percentage');
|
|
162
|
+
const spinner = row.querySelector<HTMLElement>('.hydranium-progress-spinner');
|
|
163
|
+
if (message && action.message !== undefined) {
|
|
164
|
+
message.textContent = action.message;
|
|
165
|
+
}
|
|
166
|
+
// A negative percentage reports work of unknown size, as GLSP has it.
|
|
167
|
+
// The spinner stands in for a percentage, so it shows only while there is none.
|
|
168
|
+
if (action.percentage !== undefined) {
|
|
169
|
+
const known = action.percentage >= 0;
|
|
170
|
+
if (percentage) {
|
|
171
|
+
percentage.textContent = known ? `${Math.round(action.percentage)}%` : '';
|
|
172
|
+
}
|
|
173
|
+
if (spinner) {
|
|
174
|
+
spinner.hidden = known;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
protected endProgress(action: EndProgressAction): void {
|
|
180
|
+
this.progressRows.get(action.progressId)?.remove();
|
|
181
|
+
this.progressRows.delete(action.progressId);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** English, as this tier knows no locale. A host that knows one renders {@link DIAGRAM_MESSAGE_DISMISS} here. */
|
|
185
|
+
protected dismissLabel(): string {
|
|
186
|
+
return DIAGRAM_MESSAGE_DISMISS.format();
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** English, as this tier knows no locale. A host that knows one renders {@link DIAGRAM_MESSAGE_DETAILS} here. */
|
|
190
|
+
protected detailsLabel(): string {
|
|
191
|
+
return DIAGRAM_MESSAGE_DETAILS.format();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
protected createRow(kind: string, alert: boolean): HTMLElement {
|
|
195
|
+
const row = document.createElement('div');
|
|
196
|
+
// Prefixed, as a bare `progress` or `warning` would take the page's own styles for it.
|
|
197
|
+
row.classList.add('hydranium-message', `hydranium-message-${kind}`);
|
|
198
|
+
row.setAttribute('role', alert ? 'alert' : 'status');
|
|
199
|
+
return row;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** The severity as a glyph too, as GLSP's default colours can be too faint on a light page. */
|
|
203
|
+
protected createIcon(severity: MessageAction['severity']): HTMLElement {
|
|
204
|
+
const glyph =
|
|
205
|
+
severity === 'FATAL' || severity === 'ERROR' ? 'error' : severity === 'WARNING' ? 'warning' : severity === 'OK' ? 'pass' : 'info';
|
|
206
|
+
const icon = document.createElement('i');
|
|
207
|
+
icon.classList.add('hydranium-message-icon', 'codicon', `codicon-${glyph}`);
|
|
208
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
209
|
+
return icon;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
protected focusDiagram(): void {
|
|
213
|
+
this.getParentContainer()?.querySelector<SVGElement>('svg.sprotty-graph')?.focus();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
protected createText(className: string, text: string): HTMLElement {
|
|
217
|
+
const span = document.createElement('span');
|
|
218
|
+
span.classList.add(className);
|
|
219
|
+
span.textContent = text;
|
|
220
|
+
return span;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** Adds a row to the overlay, or logs its text when the diagram has no element to show it in. */
|
|
224
|
+
protected addRow(row: HTMLElement, text: string): boolean {
|
|
225
|
+
// Checked before `initialize`, which logs the same problem without the text.
|
|
226
|
+
const noElement = !this.containerElement && !this.getContainer() && !this.getParentContainer()?.isConnected;
|
|
227
|
+
if (!noElement && this.initialize()) {
|
|
228
|
+
this.reattach();
|
|
229
|
+
// Still detached when the base div itself left the page.
|
|
230
|
+
if (this.containerElement.isConnected) {
|
|
231
|
+
this.containerElement.append(row);
|
|
232
|
+
// The overlay scrolls once it fills the diagram, and the newest row is at its bottom.
|
|
233
|
+
this.containerElement.scrollTop = this.containerElement.scrollHeight;
|
|
234
|
+
return true;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
this.logger.warn(this, 'No diagram element to show a server message in:', text);
|
|
238
|
+
return false;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** A row a startup hook showed before the first render would otherwise wait for the next row. */
|
|
242
|
+
postModelInitialization(): void {
|
|
243
|
+
this.reattach();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/** Puts the element back into the base div, which sprotty's first render replaces. */
|
|
247
|
+
protected reattach(): void {
|
|
248
|
+
reattachUIExtension(
|
|
249
|
+
this.containerElement as HTMLElement | undefined,
|
|
250
|
+
() => this.getParentContainer(),
|
|
251
|
+
(container, parent) => this.insertContainerIntoParent(container, parent)
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
}
|
package/src/messages/index.ts
CHANGED
package/src/reattach.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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 MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Puts a UI extension's element back into the diagram's base div. sprotty's
|
|
12
|
+
* first render replaces that div, so an element inserted before it is left
|
|
13
|
+
* detached. Takes the extension's own lookups, which are protected.
|
|
14
|
+
*/
|
|
15
|
+
export function reattachUIExtension(
|
|
16
|
+
container: HTMLElement | undefined,
|
|
17
|
+
parentContainer: () => HTMLElement | null | undefined,
|
|
18
|
+
insert: (container: HTMLElement, parent: HTMLElement) => void
|
|
19
|
+
): void {
|
|
20
|
+
if (!container || container.isConnected) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const parent = parentContainer();
|
|
24
|
+
if (parent) {
|
|
25
|
+
insert(container, parent);
|
|
26
|
+
}
|
|
27
|
+
}
|
package/src/status-overlay.ts
CHANGED
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { type StatusAction, StatusOverlay } from '@eclipse-glsp/client';
|
|
11
11
|
import { injectable } from 'inversify';
|
|
12
|
+
import { reattachUIExtension } from './reattach';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* GLSP's status overlay, kept on the page.
|
|
@@ -31,13 +32,10 @@ export class HydraniumStatusOverlay extends StatusOverlay {
|
|
|
31
32
|
}
|
|
32
33
|
|
|
33
34
|
protected reattach(): void {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
if (parent) {
|
|
40
|
-
this.insertContainerIntoParent(container, parent);
|
|
41
|
-
}
|
|
35
|
+
reattachUIExtension(
|
|
36
|
+
this.containerElement as HTMLElement | undefined,
|
|
37
|
+
() => this.getParentContainer(),
|
|
38
|
+
(container, parent) => this.insertContainerIntoParent(container, parent)
|
|
39
|
+
);
|
|
42
40
|
}
|
|
43
41
|
}
|
|
@@ -0,0 +1,74 @@
|
|
|
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 MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
/* Bottom right: GLSP's status sits top left and its tool palette top right. */
|
|
11
|
+
.hydranium-messages {
|
|
12
|
+
position: absolute;
|
|
13
|
+
right: 10px;
|
|
14
|
+
bottom: 10px;
|
|
15
|
+
/* Above the tool palette (1000), which a tall overlay reaches, so its controls stay reachable. */
|
|
16
|
+
z-index: 1001;
|
|
17
|
+
display: flex;
|
|
18
|
+
flex-direction: column;
|
|
19
|
+
gap: 4px;
|
|
20
|
+
max-width: min(480px, calc(100% - 20px));
|
|
21
|
+
/* Scrolls rather than grow past the diagram, where the page may clip a row's controls. */
|
|
22
|
+
max-height: calc(100% - 20px);
|
|
23
|
+
overflow-y: auto;
|
|
24
|
+
pointer-events: none;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.hydranium-message {
|
|
28
|
+
display: flex;
|
|
29
|
+
flex-wrap: wrap;
|
|
30
|
+
align-items: center;
|
|
31
|
+
gap: 8px;
|
|
32
|
+
padding: 6px 8px;
|
|
33
|
+
border-left: 3px solid var(--glsp-info-foreground);
|
|
34
|
+
background: var(--glsp-issue-background);
|
|
35
|
+
box-shadow: 0 1px 4px rgb(0 0 0 / 25%);
|
|
36
|
+
pointer-events: auto;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.hydranium-message-error,
|
|
40
|
+
.hydranium-message-fatal {
|
|
41
|
+
border-left-color: var(--glsp-error-foreground);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.hydranium-message-warning {
|
|
45
|
+
border-left-color: var(--glsp-warning-foreground);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
.hydranium-message-text {
|
|
49
|
+
flex: 1;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Above the codicon rules' `display`, whichever stylesheet a page loads last. */
|
|
53
|
+
.hydranium-progress-spinner.codicon[hidden] {
|
|
54
|
+
display: none;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
.hydranium-message > button {
|
|
58
|
+
border: none;
|
|
59
|
+
background: none;
|
|
60
|
+
color: inherit;
|
|
61
|
+
cursor: pointer;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.hydranium-message > details {
|
|
65
|
+
flex-basis: 100%;
|
|
66
|
+
order: 1;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.hydranium-message > details > pre {
|
|
70
|
+
max-height: 12em;
|
|
71
|
+
margin: 4px 0 0;
|
|
72
|
+
overflow-y: auto;
|
|
73
|
+
white-space: pre-wrap;
|
|
74
|
+
}
|