@eclipse-glsp/client 2.1.0-next.303 → 2.1.0-next.305
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 +15 -0
- package/lib/base/action-dispatcher.d.ts.map +1 -1
- package/lib/base/action-dispatcher.js +5 -1
- package/lib/base/action-dispatcher.js.map +1 -1
- package/lib/base/model/glsp-model-source.d.ts +16 -1
- package/lib/base/model/glsp-model-source.d.ts.map +1 -1
- package/lib/base/model/glsp-model-source.js +19 -3
- package/lib/base/model/glsp-model-source.js.map +1 -1
- package/lib/base/selection-service.d.ts +1 -1
- package/lib/base/selection-service.d.ts.map +1 -1
- package/lib/base/selection-service.js +15 -20
- package/lib/base/selection-service.js.map +1 -1
- package/lib/base/selection-service.spec.js +9 -7
- package/lib/base/selection-service.spec.js.map +1 -1
- package/lib/features/tools/change-bounds/change-bounds-tool-feedback.d.ts +1 -0
- package/lib/features/tools/change-bounds/change-bounds-tool-feedback.d.ts.map +1 -1
- package/lib/features/tools/change-bounds/change-bounds-tool-feedback.js +8 -3
- package/lib/features/tools/change-bounds/change-bounds-tool-feedback.js.map +1 -1
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/base/action-dispatcher.ts +5 -1
- package/src/base/model/glsp-model-source.ts +26 -2
- package/src/base/selection-service.spec.ts +9 -7
- package/src/base/selection-service.ts +17 -21
- package/src/features/tools/change-bounds/change-bounds-tool-feedback.ts +8 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eclipse-glsp/client",
|
|
3
|
-
"version": "2.1.0-next.
|
|
3
|
+
"version": "2.1.0-next.305+87e78dc",
|
|
4
4
|
"description": "A sprotty-based client for GLSP",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eclipse",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"watch": "tsc -w"
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@eclipse-glsp/sprotty": "2.1.0-next.
|
|
50
|
+
"@eclipse-glsp/sprotty": "2.1.0-next.305+87e78dc",
|
|
51
51
|
"autocompleter": "^9.1.0",
|
|
52
52
|
"file-saver": "^2.0.5",
|
|
53
53
|
"lodash": "4.17.21"
|
|
@@ -59,5 +59,5 @@
|
|
|
59
59
|
"publishConfig": {
|
|
60
60
|
"access": "public"
|
|
61
61
|
},
|
|
62
|
-
"gitHead": "
|
|
62
|
+
"gitHead": "87e78dc75c8346396619bbb1126335860f9fb50b"
|
|
63
63
|
}
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
*
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
|
-
import { inject, injectable } from 'inversify';
|
|
17
16
|
import { Action, ActionDispatcher, RequestAction, ResponseAction } from '@eclipse-glsp/sprotty';
|
|
17
|
+
import { inject, injectable } from 'inversify';
|
|
18
|
+
import { OptionalAction } from './model/glsp-model-source';
|
|
18
19
|
import { ModelInitializationConstraint } from './model/model-initialization-constraint';
|
|
19
20
|
|
|
20
21
|
@injectable()
|
|
@@ -76,6 +77,9 @@ export class GLSPActionDispatcher extends ActionDispatcher {
|
|
|
76
77
|
action.responseId = '';
|
|
77
78
|
}
|
|
78
79
|
}
|
|
80
|
+
if (!this.hasHandler(action) && OptionalAction.is(action)) {
|
|
81
|
+
return Promise.resolve();
|
|
82
|
+
}
|
|
79
83
|
return super.handleAction(action);
|
|
80
84
|
}
|
|
81
85
|
|
|
@@ -14,7 +14,6 @@
|
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
16
|
|
|
17
|
-
import { inject, injectable, preDestroy } from 'inversify';
|
|
18
17
|
import {
|
|
19
18
|
Action,
|
|
20
19
|
ActionMessage,
|
|
@@ -29,8 +28,10 @@ import {
|
|
|
29
28
|
ModelSource,
|
|
30
29
|
TYPES
|
|
31
30
|
} from '@eclipse-glsp/sprotty';
|
|
31
|
+
import { inject, injectable, preDestroy } from 'inversify';
|
|
32
32
|
import { GLSPActionHandlerRegistry } from '../action-handler-registry';
|
|
33
33
|
import { IDiagramOptions } from './diagram-loader';
|
|
34
|
+
|
|
34
35
|
/**
|
|
35
36
|
* A helper interface that allows the client to mark actions that have been received from the server.
|
|
36
37
|
*/
|
|
@@ -44,7 +45,7 @@ export namespace ServerAction {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
/**
|
|
47
|
-
* Mark the given action as {@link ServerAction} by attaching the "
|
|
48
|
+
* Mark the given action as {@link ServerAction} by attaching the "__receivedFromServer" property
|
|
48
49
|
* @param action The action that should be marked as server action
|
|
49
50
|
*/
|
|
50
51
|
export function mark(action: Action): void {
|
|
@@ -52,6 +53,29 @@ export namespace ServerAction {
|
|
|
52
53
|
}
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
/**
|
|
57
|
+
* A helper interface that allows the client to mark actions that can be considered optional and should not throw an error if
|
|
58
|
+
* no handler is available.
|
|
59
|
+
*/
|
|
60
|
+
export interface OptionalAction extends Action {
|
|
61
|
+
__skipErrorIfNoHandler: true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export namespace OptionalAction {
|
|
65
|
+
export function is(object: unknown): object is ServerAction {
|
|
66
|
+
return Action.is(object) && '__skipErrorIfNoHandler' in object && object.__skipErrorIfNoHandler === true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Mark the given action as {@link OptionalAction} by attaching the "__skipErrorIfNoHandler" property
|
|
71
|
+
* @param action The action that should be marked as optional action
|
|
72
|
+
*/
|
|
73
|
+
export function mark<T extends Action>(action: T): T & OptionalAction {
|
|
74
|
+
(action as unknown as OptionalAction).__skipErrorIfNoHandler = true;
|
|
75
|
+
return action as T & OptionalAction;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
55
79
|
/**
|
|
56
80
|
* Central component for enabling the client-server action flow with the help of an underlying {@link GLSPClient}.
|
|
57
81
|
* Handles & forwards actions that are intended for the GLSP server. In addition, it handles {@link ActionMessage}s received
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
*
|
|
14
14
|
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
|
|
15
15
|
********************************************************************************/
|
|
16
|
+
import { Action, Disposable, GModelRoot, GNode, TYPES, initializeContainer } from '@eclipse-glsp/sprotty';
|
|
16
17
|
import { AssertionError, expect } from 'chai';
|
|
17
18
|
import { Container, injectable } from 'inversify';
|
|
18
19
|
import * as sinon from 'sinon';
|
|
19
|
-
import { Action, Disposable, GModelRoot, GNode, TYPES, initializeContainer } from '@eclipse-glsp/sprotty';
|
|
20
20
|
import { defaultModule } from './default.module';
|
|
21
21
|
import { IFeedbackActionDispatcher, IFeedbackEmitter } from './feedback/feedback-action-dispatcher';
|
|
22
22
|
import { ISelectionListener, SelectFeedbackAction, SelectionService } from './selection-service';
|
|
@@ -217,13 +217,13 @@ describe('SelectionService', () => {
|
|
|
217
217
|
selectionService.updateSelection(root, [], ['node4']);
|
|
218
218
|
assertListener(listener, root, ['node2', 'node3'], ['node4'], 4);
|
|
219
219
|
});
|
|
220
|
-
it('A registered listener should be notified of root changes.', () => {
|
|
220
|
+
it('A registered listener should NOT be notified of root changes.', () => {
|
|
221
221
|
selectionService.updateSelection(root, [], []);
|
|
222
|
-
assertListener(listener, root, [], [],
|
|
222
|
+
assertListener(listener, root, [], [], 0);
|
|
223
223
|
|
|
224
224
|
const newRoot = createRoot('node1', 'newNode2', 'newNode3');
|
|
225
225
|
selectionService.updateSelection(newRoot, [], []);
|
|
226
|
-
assertListener(listener, newRoot, [], [],
|
|
226
|
+
assertListener(listener, newRoot, [], [], 0);
|
|
227
227
|
});
|
|
228
228
|
it('Selecting the same elements consecutively should not trigger a listener update.', () => {
|
|
229
229
|
selectionService.updateSelection(root, ['node1'], []);
|
|
@@ -290,8 +290,10 @@ describe('SelectionService', () => {
|
|
|
290
290
|
expectedCalled: number
|
|
291
291
|
): void {
|
|
292
292
|
expect(listener.selectionChanged.callCount).to.be.equal(expectedCalled);
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
293
|
+
if (expectedCalled > 0) {
|
|
294
|
+
expect(listener.selectionChanged.lastCall.args[0]).to.be.deep.equals(expectedRoot);
|
|
295
|
+
expect(listener.selectionChanged.lastCall.args[1]).to.be.deep.equals(expectedSelection);
|
|
296
|
+
expect(listener.selectionChanged.lastCall.args[2]).to.be.deep.equals(expectedDeselection);
|
|
297
|
+
}
|
|
296
298
|
}
|
|
297
299
|
});
|
|
@@ -96,41 +96,41 @@ export class SelectionService implements IGModelRootListener, Disposable {
|
|
|
96
96
|
this.updateSelection(root, [], []);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
-
updateSelection(
|
|
100
|
-
if (
|
|
99
|
+
updateSelection(newRoot: Readonly<GModelRoot>, select: string[], deselect: string[]): void {
|
|
100
|
+
if (newRoot === undefined && select.length === 0 && deselect.length === 0) {
|
|
101
101
|
return;
|
|
102
102
|
}
|
|
103
103
|
const prevRoot = this.root;
|
|
104
104
|
const prevSelectedElementIDs = new Set(this.selectedElementIDs);
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
this.root = root;
|
|
106
|
+
this.root = newRoot;
|
|
108
107
|
|
|
109
|
-
//
|
|
110
|
-
// - select all elements that are not deselected at the same time (no-op)
|
|
111
|
-
// - deselect all elements that are not selected at the same time (no-op) but was selected
|
|
108
|
+
// We only select elements that are not part of the deselection
|
|
112
109
|
const toSelect = [...select].filter(selectId => deselect.indexOf(selectId) === -1);
|
|
110
|
+
|
|
111
|
+
// We only need to deselect elements that are not part of the selection
|
|
112
|
+
// If an element is part of both the select and deselect, it's state is not changed
|
|
113
113
|
const toDeselect = [...deselect].filter(deselectId => select.indexOf(deselectId) === -1 && this.selectedElementIDs.has(deselectId));
|
|
114
|
-
for (const id of toDeselect) {
|
|
115
|
-
this.selectedElementIDs.delete(id);
|
|
116
|
-
}
|
|
117
|
-
for (const id of toSelect) {
|
|
118
|
-
this.selectedElementIDs.add(id);
|
|
119
|
-
}
|
|
120
114
|
|
|
115
|
+
// update selected element ids
|
|
116
|
+
toDeselect.forEach(toDeselectId => this.selectedElementIDs.delete(toDeselectId));
|
|
117
|
+
toSelect.forEach(toSelectId => this.selectedElementIDs.add(toSelectId));
|
|
118
|
+
|
|
119
|
+
// check if the newly or previously selected elements still exist in the updated root
|
|
121
120
|
const deselectedElementIDs = new Set(toDeselect);
|
|
122
|
-
// see if selected elements still exist in the updated root
|
|
123
121
|
for (const id of this.selectedElementIDs) {
|
|
124
|
-
const element =
|
|
122
|
+
const element = newRoot.index.getById(id);
|
|
125
123
|
if (element === undefined) {
|
|
124
|
+
// element to be selected does not exist in the root...
|
|
126
125
|
this.selectedElementIDs.delete(id);
|
|
127
|
-
if (prevRoot
|
|
126
|
+
if (prevRoot?.index.getById(id)) {
|
|
127
|
+
// ...but existed in the previous root, so we want to consider it deselected
|
|
128
128
|
deselectedElementIDs.add(id);
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
131
|
}
|
|
132
132
|
|
|
133
|
-
// only send out changes if there actually are changes, i.e.,
|
|
133
|
+
// only send out changes if there actually are changes, i.e., any of the selected elements ids has changed
|
|
134
134
|
const selectionChanged =
|
|
135
135
|
prevSelectedElementIDs.size !== this.selectedElementIDs.size ||
|
|
136
136
|
![...prevSelectedElementIDs].every(value => this.selectedElementIDs.has(value));
|
|
@@ -142,10 +142,6 @@ export class SelectionService implements IGModelRootListener, Disposable {
|
|
|
142
142
|
deselectedElementsIDs: [...deselectedElementIDs]
|
|
143
143
|
})
|
|
144
144
|
]);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
const rootChanged = prevRoot !== root;
|
|
148
|
-
if (rootChanged || selectionChanged) {
|
|
149
145
|
// notify listeners after the feedback action
|
|
150
146
|
this.notifyListeners(this.root, this.selectedElementIDs, deselectedElementIDs);
|
|
151
147
|
}
|
|
@@ -36,6 +36,7 @@ import { DebouncedFunc, debounce } from 'lodash';
|
|
|
36
36
|
import { DragAwareMouseListener } from '../../../base/drag-aware-mouse-listener';
|
|
37
37
|
import { CursorCSS, cursorFeedbackAction } from '../../../base/feedback/css-feedback';
|
|
38
38
|
import { FeedbackCommand } from '../../../base/feedback/feedback-command';
|
|
39
|
+
import { OptionalAction } from '../../../base/model/glsp-model-source';
|
|
39
40
|
import { forEachElement } from '../../../utils/gmodel-util';
|
|
40
41
|
import { SResizeHandle, addResizeHandles, isResizable, removeResizeHandles } from '../../change-bounds/model';
|
|
41
42
|
import { createMovementRestrictionFeedback, removeMovementRestrictionFeedback } from '../../change-bounds/movement-restrictor';
|
|
@@ -125,7 +126,7 @@ export namespace MoveInitializedEventAction {
|
|
|
125
126
|
}
|
|
126
127
|
|
|
127
128
|
export function create(): MoveInitializedEventAction {
|
|
128
|
-
return { kind: KIND };
|
|
129
|
+
return OptionalAction.mark({ kind: KIND });
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
|
|
@@ -141,7 +142,7 @@ export namespace MoveFinishedEventAction {
|
|
|
141
142
|
}
|
|
142
143
|
|
|
143
144
|
export function create(): MoveFinishedEventAction {
|
|
144
|
-
return { kind: KIND };
|
|
145
|
+
return OptionalAction.mark({ kind: KIND });
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
148
|
|
|
@@ -157,6 +158,7 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements
|
|
|
157
158
|
protected positionUpdater;
|
|
158
159
|
protected elementId2startPos = new Map<string, Point>();
|
|
159
160
|
protected pendingMoveInitialized?: DebouncedFunc<() => void>;
|
|
161
|
+
protected moveInitialized = false;
|
|
160
162
|
|
|
161
163
|
constructor(protected tool: ChangeBoundsTool) {
|
|
162
164
|
super();
|
|
@@ -178,10 +180,12 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements
|
|
|
178
180
|
}
|
|
179
181
|
|
|
180
182
|
protected scheduleMoveInitialized(): void {
|
|
183
|
+
this.moveInitialized = false;
|
|
181
184
|
this.pendingMoveInitialized?.cancel();
|
|
182
185
|
this.pendingMoveInitialized = debounce(() => {
|
|
183
186
|
this.tool.registerFeedback([MoveInitializedEventAction.create()], this);
|
|
184
187
|
this.pendingMoveInitialized = undefined;
|
|
188
|
+
this.moveInitialized = true;
|
|
185
189
|
}, 750);
|
|
186
190
|
this.pendingMoveInitialized();
|
|
187
191
|
}
|
|
@@ -329,11 +333,12 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener implements
|
|
|
329
333
|
const moveAction = MoveAction.create(elementMoves, { animate: false, finished: true });
|
|
330
334
|
this.tool.deregisterFeedback(this, [moveAction]);
|
|
331
335
|
}
|
|
332
|
-
} else if (resetFeedback) {
|
|
336
|
+
} else if (resetFeedback && this.moveInitialized) {
|
|
333
337
|
this.tool.deregisterFeedback(this, [MoveFinishedEventAction.create()]);
|
|
334
338
|
}
|
|
335
339
|
this.positionUpdater.resetPosition();
|
|
336
340
|
this._isMouseDrag = false;
|
|
341
|
+
this.moveInitialized = false;
|
|
337
342
|
this.rootElement = undefined;
|
|
338
343
|
this.elementId2startPos.clear();
|
|
339
344
|
}
|