@eclipse-glsp/client 0.10.0-next.e5fcffe.169 → 1.0.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.
Files changed (164) hide show
  1. package/css/decoration.css +5 -1
  2. package/css/glsp-sprotty.css +39 -0
  3. package/css/tool-palette.css +2 -2
  4. package/lib/base/container-modules.d.ts.map +1 -1
  5. package/lib/base/container-modules.js +10 -9
  6. package/lib/base/container-modules.js.map +1 -1
  7. package/lib/base/editor-context-service.d.ts +2 -3
  8. package/lib/base/editor-context-service.d.ts.map +1 -1
  9. package/lib/base/editor-context-service.js +11 -12
  10. package/lib/base/editor-context-service.js.map +1 -1
  11. package/lib/base/model/update-model-command.d.ts +14 -7
  12. package/lib/base/model/update-model-command.d.ts.map +1 -1
  13. package/lib/base/model/update-model-command.js +31 -41
  14. package/lib/base/model/update-model-command.js.map +1 -1
  15. package/lib/base/source-uri-aware.d.ts +2 -17
  16. package/lib/base/source-uri-aware.d.ts.map +1 -1
  17. package/lib/base/source-uri-aware.js +4 -6
  18. package/lib/base/source-uri-aware.js.map +1 -1
  19. package/lib/features/change-bounds/movement-restrictor.d.ts +36 -6
  20. package/lib/features/change-bounds/movement-restrictor.d.ts.map +1 -1
  21. package/lib/features/change-bounds/movement-restrictor.js +54 -39
  22. package/lib/features/change-bounds/movement-restrictor.js.map +1 -1
  23. package/lib/features/change-bounds/snap.d.ts +20 -5
  24. package/lib/features/change-bounds/snap.d.ts.map +1 -1
  25. package/lib/features/change-bounds/snap.js +19 -8
  26. package/lib/features/change-bounds/snap.js.map +1 -1
  27. package/lib/features/context-menu/selection-service-aware-context-menu-mouse-listener.d.ts +5 -7
  28. package/lib/features/context-menu/selection-service-aware-context-menu-mouse-listener.d.ts.map +1 -1
  29. package/lib/features/context-menu/selection-service-aware-context-menu-mouse-listener.js +7 -14
  30. package/lib/features/context-menu/selection-service-aware-context-menu-mouse-listener.js.map +1 -1
  31. package/lib/features/hover/hover.d.ts +1 -2
  32. package/lib/features/hover/hover.d.ts.map +1 -1
  33. package/lib/features/hover/hover.js +2 -15
  34. package/lib/features/hover/hover.js.map +1 -1
  35. package/lib/features/layout/di.config.d.ts +2 -2
  36. package/lib/features/layout/di.config.d.ts.map +1 -1
  37. package/lib/features/layout/di.config.js +6 -5
  38. package/lib/features/layout/di.config.js.map +1 -1
  39. package/lib/features/layout/{layout-commands.d.ts → layout-elements-action.d.ts} +75 -57
  40. package/lib/features/layout/layout-elements-action.d.ts.map +1 -0
  41. package/lib/features/layout/{layout-commands.js → layout-elements-action.js} +153 -197
  42. package/lib/features/layout/layout-elements-action.js.map +1 -0
  43. package/lib/features/layout/layout-elements-action.spec.d.ts +3 -0
  44. package/lib/features/layout/layout-elements-action.spec.d.ts.map +1 -0
  45. package/lib/features/layout/{layout-commands.spec.js → layout-elements-action.spec.js} +63 -56
  46. package/lib/features/layout/layout-elements-action.spec.js.map +1 -0
  47. package/lib/features/mouse-tool/mouse-tool.d.ts +0 -1
  48. package/lib/features/mouse-tool/mouse-tool.d.ts.map +1 -1
  49. package/lib/features/mouse-tool/mouse-tool.js +9 -5
  50. package/lib/features/mouse-tool/mouse-tool.js.map +1 -1
  51. package/lib/features/select/select-mouse-listener.d.ts +3 -1
  52. package/lib/features/select/select-mouse-listener.d.ts.map +1 -1
  53. package/lib/features/select/select-mouse-listener.js +62 -0
  54. package/lib/features/select/select-mouse-listener.js.map +1 -1
  55. package/lib/features/select/selection-service.d.ts +12 -3
  56. package/lib/features/select/selection-service.d.ts.map +1 -1
  57. package/lib/features/select/selection-service.js +31 -45
  58. package/lib/features/select/selection-service.js.map +1 -1
  59. package/lib/features/select/selection-service.spec.js +1 -1
  60. package/lib/features/select/selection-service.spec.js.map +1 -1
  61. package/lib/features/source-model-watcher/di.config.d.ts +4 -0
  62. package/lib/features/{model-source-watcher → source-model-watcher}/di.config.d.ts.map +1 -1
  63. package/lib/features/{model-source-watcher → source-model-watcher}/di.config.js +4 -4
  64. package/lib/features/{model-source-watcher → source-model-watcher}/di.config.js.map +1 -1
  65. package/lib/features/{model-source-watcher/model-source-changed-action-handler.d.ts → source-model-watcher/source-model-changed-action-handler.d.ts} +9 -9
  66. package/lib/features/{model-source-watcher/model-source-changed-action-handler.d.ts.map → source-model-watcher/source-model-changed-action-handler.d.ts.map} +1 -1
  67. package/lib/features/{model-source-watcher/model-source-changed-action-handler.js → source-model-watcher/source-model-changed-action-handler.js} +19 -19
  68. package/lib/features/{model-source-watcher/model-source-changed-action-handler.js.map → source-model-watcher/source-model-changed-action-handler.js.map} +1 -1
  69. package/lib/features/tool-feedback/change-bounds-tool-feedback.js +1 -1
  70. package/lib/features/tool-feedback/change-bounds-tool-feedback.js.map +1 -1
  71. package/lib/features/tool-feedback/edge-edit-tool-feedback.d.ts +3 -2
  72. package/lib/features/tool-feedback/edge-edit-tool-feedback.d.ts.map +1 -1
  73. package/lib/features/tool-feedback/edge-edit-tool-feedback.js +13 -6
  74. package/lib/features/tool-feedback/edge-edit-tool-feedback.js.map +1 -1
  75. package/lib/features/tools/di.config.d.ts +9 -2
  76. package/lib/features/tools/di.config.d.ts.map +1 -1
  77. package/lib/features/tools/di.config.js +14 -3
  78. package/lib/features/tools/di.config.js.map +1 -1
  79. package/lib/features/tools/enable-default-tools-on-focus-loss.d.ts +26 -0
  80. package/lib/features/tools/enable-default-tools-on-focus-loss.d.ts.map +1 -0
  81. package/lib/features/tools/enable-default-tools-on-focus-loss.js +29 -0
  82. package/lib/features/tools/enable-default-tools-on-focus-loss.js.map +1 -0
  83. package/lib/features/validation/di.config.d.ts +0 -15
  84. package/lib/features/validation/di.config.d.ts.map +1 -1
  85. package/lib/features/validation/di.config.js +6 -4
  86. package/lib/features/validation/di.config.js.map +1 -1
  87. package/lib/features/validation/issue-marker.d.ts +11 -5
  88. package/lib/features/validation/issue-marker.d.ts.map +1 -1
  89. package/lib/features/validation/issue-marker.js +25 -5
  90. package/lib/features/validation/issue-marker.js.map +1 -1
  91. package/lib/features/validation/marker-navigator.d.ts +11 -17
  92. package/lib/features/validation/marker-navigator.d.ts.map +1 -1
  93. package/lib/features/validation/marker-navigator.js +32 -61
  94. package/lib/features/validation/marker-navigator.js.map +1 -1
  95. package/lib/features/validation/validate.d.ts +8 -19
  96. package/lib/features/validation/validate.d.ts.map +1 -1
  97. package/lib/features/validation/validate.js +29 -55
  98. package/lib/features/validation/validate.js.map +1 -1
  99. package/lib/index.d.ts +6 -6
  100. package/lib/index.d.ts.map +1 -1
  101. package/lib/index.js +16 -15
  102. package/lib/index.js.map +1 -1
  103. package/lib/lib/model.d.ts +1 -1
  104. package/lib/lib/model.d.ts.map +1 -1
  105. package/lib/lib/model.js +3 -3
  106. package/lib/lib/model.js.map +1 -1
  107. package/lib/model-source/glsp-diagram-server.d.ts +1 -1
  108. package/lib/model-source/glsp-diagram-server.d.ts.map +1 -1
  109. package/lib/model-source/glsp-diagram-server.js +1 -1
  110. package/lib/model-source/glsp-diagram-server.js.map +1 -1
  111. package/lib/utils/layout-utils.d.ts +4 -20
  112. package/lib/utils/layout-utils.d.ts.map +1 -1
  113. package/lib/utils/layout-utils.js +1 -1
  114. package/lib/utils/layout-utils.js.map +1 -1
  115. package/lib/views/base-view-module.d.ts.map +1 -1
  116. package/lib/views/base-view-module.js +3 -1
  117. package/lib/views/base-view-module.js.map +1 -1
  118. package/lib/views/glsp-projection-view.d.ts +30 -0
  119. package/lib/views/glsp-projection-view.d.ts.map +1 -0
  120. package/lib/views/glsp-projection-view.js +142 -0
  121. package/lib/views/glsp-projection-view.js.map +1 -0
  122. package/lib/views/index.d.ts +1 -0
  123. package/lib/views/index.d.ts.map +1 -1
  124. package/lib/views/index.js +1 -0
  125. package/lib/views/index.js.map +1 -1
  126. package/package.json +4 -4
  127. package/src/base/container-modules.ts +6 -5
  128. package/src/base/editor-context-service.ts +15 -7
  129. package/src/base/model/update-model-command.ts +48 -34
  130. package/src/base/source-uri-aware.ts +5 -4
  131. package/src/features/change-bounds/movement-restrictor.ts +73 -43
  132. package/src/features/change-bounds/snap.ts +15 -12
  133. package/src/features/context-menu/selection-service-aware-context-menu-mouse-listener.ts +19 -25
  134. package/src/features/hover/hover.ts +2 -24
  135. package/src/features/layout/di.config.ts +12 -6
  136. package/src/features/layout/{layout-commands.spec.ts → layout-elements-action.spec.ts} +56 -48
  137. package/src/features/layout/{layout-commands.ts → layout-elements-action.ts} +195 -207
  138. package/src/features/mouse-tool/mouse-tool.ts +8 -6
  139. package/src/features/select/select-mouse-listener.ts +81 -1
  140. package/src/features/select/selection-service.spec.ts +1 -1
  141. package/src/features/select/selection-service.ts +37 -50
  142. package/src/features/{model-source-watcher → source-model-watcher}/di.config.ts +5 -5
  143. package/src/features/{model-source-watcher/model-source-changed-action-handler.ts → source-model-watcher/source-model-changed-action-handler.ts} +12 -12
  144. package/src/features/tool-feedback/change-bounds-tool-feedback.ts +1 -1
  145. package/src/features/tool-feedback/edge-edit-tool-feedback.ts +14 -6
  146. package/src/features/tools/di.config.ts +13 -2
  147. package/src/features/tools/enable-default-tools-on-focus-loss.ts +33 -0
  148. package/src/features/validation/di.config.ts +10 -7
  149. package/src/features/validation/issue-marker.ts +34 -12
  150. package/src/features/validation/marker-navigator.ts +29 -66
  151. package/src/features/validation/validate.ts +36 -71
  152. package/src/index.ts +8 -7
  153. package/src/lib/model.ts +1 -1
  154. package/src/model-source/glsp-diagram-server.ts +1 -1
  155. package/src/utils/layout-utils.ts +7 -27
  156. package/src/views/base-view-module.ts +5 -1
  157. package/src/views/glsp-projection-view.tsx +166 -0
  158. package/src/views/index.ts +2 -0
  159. package/lib/features/layout/layout-commands.d.ts.map +0 -1
  160. package/lib/features/layout/layout-commands.js.map +0 -1
  161. package/lib/features/layout/layout-commands.spec.d.ts +0 -3
  162. package/lib/features/layout/layout-commands.spec.d.ts.map +0 -1
  163. package/lib/features/layout/layout-commands.spec.js.map +0 -1
  164. package/lib/features/model-source-watcher/di.config.d.ts +0 -4
@@ -13,12 +13,21 @@
13
13
  *
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
- import { Action, ChangeBoundsOperation, ElementAndBounds, hasArrayProp, hasNumberProp, SetBoundsAction } from '@eclipse-glsp/protocol';
16
+ import {
17
+ Action,
18
+ ChangeBoundsOperation,
19
+ ElementAndBounds,
20
+ hasArrayProp,
21
+ hasNumberProp,
22
+ hasStringProp,
23
+ SetBoundsAction,
24
+ Writable
25
+ } from '@eclipse-glsp/protocol';
17
26
  import { inject, injectable, optional } from 'inversify';
18
- import { Command, CommandExecutionContext, CommandReturn, ElementMove, IActionDispatcher, MoveAction, SModelElement } from 'sprotty';
27
+ import { ElementMove, IActionDispatcher, IActionHandler, ICommand, MoveAction, SModelElement } from 'sprotty';
19
28
  import { TYPES } from '../../base/types';
20
- import { toValidElementAndBounds, toValidElementMove, WriteableElementAndBounds, WriteableElementMove } from '../../utils/layout-utils';
21
- import { BoundsAwareModelElement } from '../../utils/smodel-util';
29
+ import { toValidElementAndBounds, toValidElementMove } from '../../utils/layout-utils';
30
+ import { BoundsAwareModelElement, getElements } from '../../utils/smodel-util';
22
31
  import { isBoundsAwareMoveable, isResizable } from '../change-bounds/model';
23
32
  import { IMovementRestrictor } from '../change-bounds/movement-restrictor';
24
33
  import { SelectionService } from '../select/selection-service';
@@ -33,7 +42,14 @@ export enum ResizeDimension {
33
42
  Width_And_Height
34
43
  }
35
44
 
36
- export namespace Reduce {
45
+ /**
46
+ * A function that computes a single number from a set of given numbers
47
+ * i.e. it reduces the given numbers to one single number.
48
+ * Mainly used by the {@link ResizeElementsCommand} to reduce the given dimensions to a target dimension value.
49
+ */
50
+ export type ReduceFunction = (...values: number[]) => number;
51
+
52
+ export namespace ReduceFunction {
37
53
  /**
38
54
  * Returns the minimal value of the given numbers.
39
55
  * @param values Numbers to be evaluated.
@@ -79,13 +95,18 @@ export namespace Reduce {
79
95
  }
80
96
 
81
97
  /**
82
- * Returns the reduce function that corresponds to the given {@link ReduceFunctionKind}.
98
+ * Returns the reduce function that corresponds to the given {@link ReduceFunctionType}.
83
99
  * @param type The reduce function kind.
84
100
  * @returns The corresponding reduce function.
85
101
  */
102
+ export function get(type: ReduceFunctionType): ReduceFunction {
103
+ return ReduceFunction[type];
104
+ }
86
105
  }
87
106
 
88
- type ReduceFn = (...values: number[]) => number;
107
+ /** Union type of all {@link ReduceFunction} keys. */
108
+ export type ReduceFunctionType = Exclude<keyof typeof ReduceFunction, 'get'>;
109
+
89
110
  export interface ResizeElementsAction extends Action {
90
111
  kind: typeof ResizeElementsAction.KIND;
91
112
 
@@ -93,14 +114,16 @@ export interface ResizeElementsAction extends Action {
93
114
  * IDs of the elements that should be resized. If no IDs are given, the selected elements will be resized.
94
115
  */
95
116
  elementIds: string[];
117
+
96
118
  /**
97
119
  * Resize dimension. The default is {@link ResizeDimension.Width}.
98
120
  */
99
121
  dimension: ResizeDimension;
122
+
100
123
  /**
101
- * Function to reduce the dimension to a target dimension value, see Reduce.* for potential functions.
124
+ * Type of the {@link ReduceFunction} that should be used to reduce the dimension to a target dimension value
102
125
  */
103
- reductionFunction: ReduceFn;
126
+ reduceFunction: ReduceFunctionType;
104
127
  }
105
128
 
106
129
  export namespace ResizeElementsAction {
@@ -111,14 +134,14 @@ export namespace ResizeElementsAction {
111
134
  Action.hasKind(object, KIND) &&
112
135
  hasArrayProp(object, 'elementIds') &&
113
136
  hasNumberProp(object, 'dimension') &&
114
- 'reductionFunction' in object
137
+ hasStringProp(object, 'reduceFunction')
115
138
  );
116
139
  }
117
140
 
118
141
  export function create(options: {
119
142
  elementIds?: string[];
120
143
  dimension?: ResizeDimension;
121
- reductionFunction: ReduceFn;
144
+ reduceFunction: ReduceFunctionType;
122
145
  }): ResizeElementsAction {
123
146
  return {
124
147
  kind: KIND,
@@ -129,99 +152,24 @@ export namespace ResizeElementsAction {
129
152
  }
130
153
  }
131
154
 
132
- // eslint-disable-next-line no-shadow
133
- export enum Alignment {
134
- Left,
135
- Center,
136
- Right,
137
- Top,
138
- Middle,
139
- Bottom
140
- }
141
-
142
- export namespace Select {
143
- export function all(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] {
144
- return elements;
145
- }
146
-
147
- export function first(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] {
148
- return [elements[0]];
149
- }
150
-
151
- export function last(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] {
152
- return [elements[elements.length - 1]];
153
- }
154
- }
155
-
156
- type SelectFn = (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[];
157
- export interface AlignElementsAction extends Action {
158
- kind: typeof AlignElementsAction.KIND;
155
+ @injectable()
156
+ export abstract class LayoutElementsActionHandler implements IActionHandler {
157
+ @inject(TYPES.IActionDispatcher)
158
+ protected actionDispatcher: IActionDispatcher;
159
159
 
160
- /**
161
- * IDs of the elements that should be aligned. If no IDs are given, the selected elements will be aligned.
162
- */
163
- elementIds: string[];
164
- /**
165
- * Alignment direction. The default is {@link Alignment.Left}
166
- */
167
- alignment: Alignment;
168
- /**
169
- * Function to selected elements that are considered during alignment calculation, see Select.* for potential functions.
170
- * The default value is {@link Select.all}.
171
- */
172
- selectionFunction: SelectFn;
173
- }
160
+ @inject(TYPES.SelectionService)
161
+ protected selectionService: SelectionService;
174
162
 
175
- export namespace AlignElementsAction {
176
- export const KIND = 'alignElements';
163
+ @inject(TYPES.IMovementRestrictor)
164
+ @optional()
165
+ protected movementRestrictor?: IMovementRestrictor;
177
166
 
178
- export function is(object: any): object is AlignElementsAction {
179
- return (
180
- Action.hasKind(object, KIND) &&
181
- hasArrayProp(object, 'elementIds') &&
182
- hasNumberProp(object, 'alignment') &&
183
- 'selectionFunction' in object
184
- );
185
- }
167
+ abstract handle(action: Action): void | Action | ICommand;
186
168
 
187
- export function create(
188
- options: { elementIds?: string[]; alignment?: Alignment; selectionFunction?: SelectFn } = {}
189
- ): AlignElementsAction {
190
- return {
191
- kind: KIND,
192
- elementIds: [],
193
- alignment: Alignment.Left,
194
- selectionFunction: Select.all,
195
- ...options
196
- };
197
- }
198
- }
199
- @injectable()
200
- abstract class LayoutElementsCommand extends Command {
201
- constructor(
202
- @inject(TYPES.Action) protected action: ResizeElementsAction | AlignElementsAction,
203
- @inject(TYPES.IActionDispatcher) protected actionDispatcher: IActionDispatcher,
204
- @inject(TYPES.SelectionService) protected selectionService: SelectionService,
205
- @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor
206
- ) {
207
- super();
208
- }
209
-
210
- getActionElements(context: CommandExecutionContext): BoundsAwareModelElement[] {
211
- const model = context.root;
212
- const elementIDs = this.action.elementIds;
213
- if (elementIDs.length === 0) {
214
- // collect the selected elements from the selection service (selection order is kept by service)
215
- this.selectionService.getSelectedElementIDs().forEach(elementID => elementIDs.push(elementID));
216
- }
217
- const boundsAwareElements: BoundsAwareModelElement[] = [];
218
- elementIDs.forEach(id => {
219
- const element = model.index.getById(id);
220
- if (element && this.isActionElement(element)) {
221
- boundsAwareElements.push(element);
222
- }
223
- });
224
- return boundsAwareElements;
169
+ getSelectedElements(selection: { elementIds: string[] }): BoundsAwareModelElement[] {
170
+ const index = this.selectionService.getModelRoot().index;
171
+ const selectedElements = selection.elementIds.length > 0 ? selection.elementIds : this.selectionService.getSelectedElementIDs();
172
+ return getElements(index, selectedElements, this.isActionElement);
225
173
  }
226
174
 
227
175
  protected abstract isActionElement(element: SModelElement): element is BoundsAwareModelElement;
@@ -236,79 +184,60 @@ abstract class LayoutElementsCommand extends Command {
236
184
  }
237
185
 
238
186
  @injectable()
239
- export class ResizeElementsCommand extends LayoutElementsCommand {
240
- static readonly KIND = ResizeElementsAction.KIND;
241
-
242
- constructor(
243
- @inject(TYPES.Action) protected override action: ResizeElementsAction,
244
- @inject(TYPES.IActionDispatcher) protected override actionDispatcher: IActionDispatcher,
245
- @inject(TYPES.SelectionService) protected override selectionService: SelectionService,
246
- @inject(TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor
247
- ) {
248
- super(action, actionDispatcher, selectionService, movementRestrictor);
249
- }
250
-
251
- protected isActionElement(element: SModelElement): element is BoundsAwareModelElement {
252
- return isResizable(element);
253
- }
254
-
255
- execute(context: CommandExecutionContext): CommandReturn {
256
- const elements = this.getActionElements(context);
187
+ export class ResizeElementsActionHandler extends LayoutElementsActionHandler {
188
+ handle(action: ResizeElementsAction): void {
189
+ const elements = this.getSelectedElements(action);
257
190
  if (elements.length > 1) {
258
- switch (this.action.dimension) {
191
+ const reduceFn = ReduceFunction.get(action.reduceFunction);
192
+ switch (action.dimension) {
259
193
  case ResizeDimension.Width:
260
- this.resizeWidth(elements);
261
- break;
194
+ return this.resizeWidth(elements, reduceFn);
262
195
  case ResizeDimension.Height:
263
- this.resizeHeight(elements);
264
- break;
196
+ return this.resizeHeight(elements, reduceFn);
265
197
  case ResizeDimension.Width_And_Height:
266
- this.resizeWidthAndHeight(elements);
267
- break;
198
+ return this.resizeWidthAndHeight(elements, reduceFn);
268
199
  }
269
200
  }
270
- return context.root;
271
201
  }
272
202
 
273
- resizeWidth(elements: BoundsAwareModelElement[]): void {
274
- const targetWidth = this.action.reductionFunction(...elements.map(element => element.bounds.width));
203
+ resizeWidth(elements: BoundsAwareModelElement[], reduceFn: ReduceFunction): void {
204
+ const targetWidth = reduceFn(...elements.map(element => element.bounds.width));
275
205
  this.dispatchResizeActions(elements, (element, bounds) => {
276
- // resize around center
206
+ // resize around center (horizontal)
277
207
  const halfDiffWidth = 0.5 * (targetWidth - element.bounds.width);
278
- bounds.newPosition.x = element.bounds.x - halfDiffWidth;
208
+ bounds.newPosition!.x = element.bounds.x - halfDiffWidth;
279
209
  bounds.newSize.width = targetWidth;
280
210
  });
281
211
  }
282
212
 
283
- resizeHeight(elements: BoundsAwareModelElement[]): void {
284
- const targetHeight = this.action.reductionFunction(...elements.map(element => element.bounds.height));
213
+ resizeHeight(elements: BoundsAwareModelElement[], reduceFn: ReduceFunction): void {
214
+ const targetHeight = reduceFn(...elements.map(element => element.bounds.height));
285
215
  this.dispatchResizeActions(elements, (element, bounds) => {
286
- // resize around middle
216
+ // resize around middle (vertical)
287
217
  const halfDiffHeight = 0.5 * (targetHeight - element.bounds.height);
288
- bounds.newPosition.y = element.bounds.y - halfDiffHeight;
218
+ bounds.newPosition!.y = element.bounds.y - halfDiffHeight;
289
219
  bounds.newSize.height = targetHeight;
290
220
  });
291
221
  }
292
222
 
293
- resizeWidthAndHeight(elements: BoundsAwareModelElement[]): void {
294
- const targetWidth = this.action.reductionFunction(...elements.map(element => element.bounds.width));
295
- const targetHeight = this.action.reductionFunction(...elements.map(element => element.bounds.height));
223
+ resizeWidthAndHeight(elements: BoundsAwareModelElement[], reduceFn: ReduceFunction): void {
224
+ const targetWidth = reduceFn(...elements.map(element => element.bounds.width));
225
+ const targetHeight = reduceFn(...elements.map(element => element.bounds.height));
296
226
  this.dispatchResizeActions(elements, (element, bounds) => {
297
- // resize around center and middle
227
+ // resize around center and middle (horizontal & vertical)
298
228
  const halfDiffWidth = 0.5 * (targetWidth - element.bounds.width);
299
229
  const halfDiffHeight = 0.5 * (targetHeight - element.bounds.height);
300
- bounds.newPosition.x = element.bounds.x - halfDiffWidth;
301
- bounds.newPosition.y = element.bounds.y - halfDiffHeight;
302
- bounds.newSize.width = targetWidth;
303
- bounds.newSize.height = targetHeight;
230
+ bounds.newPosition = { x: element.bounds.x - halfDiffWidth, y: element.bounds.y - halfDiffHeight };
231
+ bounds.newSize = { width: targetWidth, height: targetHeight };
304
232
  });
305
233
  }
306
234
 
307
235
  dispatchResizeActions(
308
236
  elements: BoundsAwareModelElement[],
309
- change: (element: BoundsAwareModelElement, bounds: WriteableElementAndBounds) => void
237
+ change: (element: BoundsAwareModelElement, bounds: Writable<ElementAndBounds>) => void
310
238
  ): void {
311
239
  const elementAndBounds: ElementAndBounds[] = []; // client- and server-side resize
240
+
312
241
  elements.forEach(element => {
313
242
  const elementChange = this.createElementAndBounds(element, change);
314
243
  if (elementChange) {
@@ -321,9 +250,9 @@ export class ResizeElementsCommand extends LayoutElementsCommand {
321
250
 
322
251
  createElementAndBounds(
323
252
  element: BoundsAwareModelElement,
324
- change: (_element: BoundsAwareModelElement, _bounds: WriteableElementAndBounds) => void
325
- ): WriteableElementAndBounds | undefined {
326
- const bounds: WriteableElementAndBounds = {
253
+ change: (_element: BoundsAwareModelElement, _bounds: Writable<ElementAndBounds>) => void
254
+ ): Writable<ElementAndBounds> | undefined {
255
+ const bounds: ElementAndBounds = {
327
256
  elementId: element.id,
328
257
  newPosition: {
329
258
  x: element.bounds.x,
@@ -337,107 +266,173 @@ export class ResizeElementsCommand extends LayoutElementsCommand {
337
266
  change(element, bounds);
338
267
  return toValidElementAndBounds(element, bounds, this.movementRestrictor);
339
268
  }
269
+ protected isActionElement(element: SModelElement): element is BoundsAwareModelElement {
270
+ return isResizable(element);
271
+ }
272
+ }
273
+
274
+ // eslint-disable-next-line no-shadow
275
+ export enum Alignment {
276
+ Left,
277
+ Center,
278
+ Right,
279
+ Top,
280
+ Middle,
281
+ Bottom
282
+ }
340
283
 
341
- undo(context: CommandExecutionContext): CommandReturn {
342
- // we dispatch another action which can be undone, so no explicit implementation necessary
343
- return context.root;
284
+ /**
285
+ * A function that retrieves a specific (sub)-selection of elements from a given set of elements.
286
+ * Mainly used by the {@link AlignElementsActionHandler}.
287
+ */
288
+ export type SelectFunction = (elements: BoundsAwareModelElement[]) => BoundsAwareModelElement[];
289
+ export namespace SelectFunction {
290
+ /**
291
+ * Select all elements from the given set of elements.
292
+ * @param elements The set of elements.
293
+ * @returns All elements.
294
+ */
295
+ export function all(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] {
296
+ return elements;
344
297
  }
345
298
 
346
- redo(context: CommandExecutionContext): CommandReturn {
347
- // we dispatch another action which can be redone, so no explicit implementation necessary
348
- return context.root;
299
+ /**
300
+ * Select the first element from a given set of elements.
301
+ * @param elements The elements.
302
+ * @returns An array containing the first element of the given elements.
303
+ */
304
+ export function first(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] {
305
+ return [elements[0]];
306
+ }
307
+
308
+ /**
309
+ * Select the last element from a given set of elements.
310
+ * @param elements The elements.
311
+ * @returns An array containing the last element of the given elements.
312
+ */
313
+ export function last(elements: BoundsAwareModelElement[]): BoundsAwareModelElement[] {
314
+ return [elements[elements.length - 1]];
315
+ }
316
+
317
+ /**
318
+ * Returns the select function that corresponds to the given {@link SelectFunctionType}.
319
+ * @param type The select function type.
320
+ * @returns The corresponding select function.
321
+ */
322
+ export function get(kind: SelectFunctionType): SelectFunction {
323
+ return SelectFunction[kind];
349
324
  }
350
325
  }
351
326
 
352
- @injectable()
353
- export class AlignElementsCommand extends LayoutElementsCommand {
354
- static readonly KIND = AlignElementsAction.KIND;
327
+ /** Union type of all {@link SelectFunction} keys. */
328
+ export type SelectFunctionType = Exclude<keyof typeof SelectFunction, 'get'>;
355
329
 
356
- constructor(
357
- @inject(TYPES.Action) protected override action: AlignElementsAction,
358
- @inject(TYPES.IActionDispatcher) protected override actionDispatcher: IActionDispatcher,
359
- @inject(TYPES.SelectionService) protected override selectionService: SelectionService,
360
- @inject(TYPES.IMovementRestrictor) @optional() override readonly movementRestrictor?: IMovementRestrictor
361
- ) {
362
- super(action, actionDispatcher, selectionService, movementRestrictor);
330
+ export interface AlignElementsAction extends Action {
331
+ kind: typeof AlignElementsAction.KIND;
332
+
333
+ /**
334
+ * IDs of the elements that should be aligned. If no IDs are given, the selected elements will be aligned.
335
+ */
336
+ elementIds: string[];
337
+ /**
338
+ * Alignment direction. The default is {@link Alignment.Left}
339
+ */
340
+ alignment: Alignment;
341
+ /**
342
+ * Function to selected elements that are considered during alignment calculation.
343
+ * The default value is {@link Select.all}.
344
+ */
345
+ selectFunction: SelectFunctionType;
346
+ }
347
+
348
+ export namespace AlignElementsAction {
349
+ export const KIND = 'alignElements';
350
+
351
+ export function is(object: any): object is AlignElementsAction {
352
+ return (
353
+ Action.hasKind(object, KIND) &&
354
+ hasArrayProp(object, 'elementIds') &&
355
+ hasNumberProp(object, 'alignment') &&
356
+ hasStringProp(object, 'selectFunction')
357
+ );
363
358
  }
364
359
 
365
- protected isActionElement(element: SModelElement): element is BoundsAwareModelElement {
366
- return isBoundsAwareMoveable(element);
360
+ export function create(
361
+ options: { elementIds?: string[]; alignment?: Alignment; selectionFunction?: SelectFunctionType } = {}
362
+ ): AlignElementsAction {
363
+ return {
364
+ kind: KIND,
365
+ elementIds: [],
366
+ alignment: Alignment.Left,
367
+ selectFunction: 'all',
368
+ ...options
369
+ };
367
370
  }
371
+ }
368
372
 
369
- execute(context: CommandExecutionContext): CommandReturn {
370
- const elements = this.getActionElements(context);
373
+ @injectable()
374
+ export class AlignElementsActionHandler extends LayoutElementsActionHandler {
375
+ handle(action: AlignElementsAction): void {
376
+ const elements = this.getSelectedElements(action);
377
+ const selectFn = SelectFunction.get(action.selectFunction);
378
+ const calculatedElements = selectFn(elements);
371
379
  if (elements.length > 1) {
372
- switch (this.action.alignment) {
380
+ switch (action.alignment) {
373
381
  case Alignment.Left:
374
- this.alignLeft(elements);
375
- break;
382
+ return this.alignLeft(calculatedElements);
376
383
  case Alignment.Center:
377
- this.alignCenter(elements);
378
- break;
384
+ return this.alignCenter(calculatedElements);
379
385
  case Alignment.Right:
380
- this.alignRight(elements);
381
- break;
386
+ return this.alignRight(calculatedElements);
382
387
  case Alignment.Top:
383
- this.alignTop(elements);
384
- break;
388
+ return this.alignTop(calculatedElements);
385
389
  case Alignment.Middle:
386
- this.alignMiddle(elements);
387
- break;
390
+ return this.alignMiddle(calculatedElements);
388
391
  case Alignment.Bottom:
389
- this.alignBottom(elements);
390
- break;
392
+ return this.alignBottom(calculatedElements);
391
393
  }
392
394
  }
393
- return context.root;
394
395
  }
395
396
 
396
397
  alignLeft(elements: BoundsAwareModelElement[]): void {
397
- const calculationElements = this.action.selectionFunction(elements);
398
- const minX = calculationElements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b));
398
+ const minX = elements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b));
399
399
  this.dispatchAlignActions(elements, (_, move) => (move.toPosition.x = minX));
400
400
  }
401
401
 
402
402
  alignCenter(elements: BoundsAwareModelElement[]): void {
403
- const calculationElements = this.action.selectionFunction(elements);
404
- const minX = calculationElements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b));
405
- const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b));
403
+ const minX = elements.map(element => element.bounds.x).reduce((a, b) => Math.min(a, b));
404
+ const maxX = elements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b));
406
405
  const diffX = maxX - minX;
407
406
  const centerX = minX + 0.5 * diffX;
408
407
  this.dispatchAlignActions(elements, (element, move) => (move.toPosition.x = centerX - 0.5 * element.bounds.width));
409
408
  }
410
409
 
411
410
  alignRight(elements: BoundsAwareModelElement[]): void {
412
- const calculationElements = this.action.selectionFunction(elements);
413
- const maxX = calculationElements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b));
411
+ const maxX = elements.map(element => element.bounds.x + element.bounds.width).reduce((a, b) => Math.max(a, b));
414
412
  this.dispatchAlignActions(elements, (element, move) => (move.toPosition.x = maxX - element.bounds.width));
415
413
  }
416
414
 
417
415
  alignTop(elements: BoundsAwareModelElement[]): void {
418
- const calculationElements = this.action.selectionFunction(elements);
419
- const minY = calculationElements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b));
416
+ const minY = elements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b));
420
417
  this.dispatchAlignActions(elements, (_, move) => (move.toPosition.y = minY));
421
418
  }
422
419
 
423
420
  alignMiddle(elements: BoundsAwareModelElement[]): void {
424
- const calculationElements = this.action.selectionFunction(elements);
425
- const minY = calculationElements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b));
426
- const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b));
421
+ const minY = elements.map(element => element.bounds.y).reduce((a, b) => Math.min(a, b));
422
+ const maxY = elements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b));
427
423
  const diffY = maxY - minY;
428
424
  const middleY = minY + 0.5 * diffY;
429
425
  this.dispatchAlignActions(elements, (element, move) => (move.toPosition.y = middleY - 0.5 * element.bounds.height));
430
426
  }
431
427
 
432
428
  alignBottom(elements: BoundsAwareModelElement[]): void {
433
- const calculationElements = this.action.selectionFunction(elements);
434
- const maxY = calculationElements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b));
429
+ const maxY = elements.map(element => element.bounds.y + element.bounds.height).reduce((a, b) => Math.max(a, b));
435
430
  this.dispatchAlignActions(elements, (element, move) => (move.toPosition.y = maxY - element.bounds.height));
436
431
  }
437
432
 
438
433
  dispatchAlignActions(
439
434
  elements: BoundsAwareModelElement[],
440
- change: (element: BoundsAwareModelElement, move: WriteableElementMove) => void
435
+ change: (element: BoundsAwareModelElement, move: Writable<ElementMove>) => void
441
436
  ): void {
442
437
  const moves: ElementMove[] = []; // client-side move
443
438
  const elementAndBounds: ElementAndBounds[] = []; // server-side move
@@ -455,9 +450,9 @@ export class AlignElementsCommand extends LayoutElementsCommand {
455
450
 
456
451
  createElementMove(
457
452
  element: BoundsAwareModelElement,
458
- change: (_element: BoundsAwareModelElement, _move: WriteableElementMove) => void
459
- ): WriteableElementMove | undefined {
460
- const move: WriteableElementMove = {
453
+ change: (_element: BoundsAwareModelElement, _move: Writable<ElementMove>) => void
454
+ ): Writable<ElementMove> | undefined {
455
+ const move: ElementMove = {
461
456
  elementId: element.id,
462
457
  fromPosition: {
463
458
  x: element.bounds.x,
@@ -485,14 +480,7 @@ export class AlignElementsCommand extends LayoutElementsCommand {
485
480
  }
486
481
  };
487
482
  }
488
-
489
- undo(context: CommandExecutionContext): CommandReturn {
490
- // we dispatch another action which can be undone, so no explicit implementation necessary
491
- return context.root;
492
- }
493
-
494
- redo(context: CommandExecutionContext): CommandReturn {
495
- // we dispatch another action which can be redone, so no explicit implementation necessary
496
- return context.root;
483
+ protected isActionElement(element: SModelElement): element is BoundsAwareModelElement {
484
+ return isBoundsAwareMoveable(element);
497
485
  }
498
486
  }
@@ -13,6 +13,7 @@
13
13
  *
14
14
  * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
15
15
  ********************************************************************************/
16
+ import { Action } from '@eclipse-glsp/protocol';
16
17
  import { injectable, multiInject, optional } from 'inversify';
17
18
  import { MouseListener, MouseTool, SModelElement, SModelRoot } from 'sprotty';
18
19
  import { TYPES } from '../../base/types';
@@ -48,10 +49,6 @@ export class RankingMouseTool extends MouseTool implements IMouseTool {
48
49
  this.rankedMouseListeners = groupBy(this.mouseListeners, listener => getRank(listener));
49
50
  }
50
51
 
51
- contextMenu(model: SModelRoot, event: MouseEvent): void {
52
- event.preventDefault();
53
- }
54
-
55
52
  protected override handleEvent<K extends MouseListenerMethods>(methodName: K, model: SModelRoot, event: MouseEvent): void {
56
53
  this.focusOnMouseEvent(methodName, model);
57
54
  const element = this.getTargetElement(model, event);
@@ -82,8 +79,13 @@ export class RankingMouseTool extends MouseTool implements IMouseTool {
82
79
  if (actions.length > 0) {
83
80
  event.preventDefault();
84
81
  for (const actionOrPromise of actions) {
85
- const action = await actionOrPromise;
86
- await this.actionDispatcher.dispatch(action);
82
+ if (Action.is(actionOrPromise)) {
83
+ await this.actionDispatcher.dispatch(actionOrPromise);
84
+ } else {
85
+ actionOrPromise.then((action: Action) => {
86
+ this.actionDispatcher.dispatch(action);
87
+ });
88
+ }
87
89
  }
88
90
  }
89
91
  }