diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts index c9467411..68433595 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool-move-feedback.ts @@ -185,10 +185,18 @@ export class FeedbackMoveMouseListener extends DragAwareMouseListener { if (!this.tracker.isTracking()) { return []; } - // only reset the move of invalid elements, the others will be handled by the change bounds tool itself - this.getElementsToMove(target) - .filter(element => this.tool.changeBoundsManager.isValid(element)) - .forEach(element => this.elementId2startPos.delete(element.id)); + const elementsToMove = this.getElementsToMove(target); + if (!this.tool.movementOptions.allElementsNeedToBeValid) { + // only reset the move of invalid elements, the others will be handled by the change bounds tool itself + elementsToMove + .filter(element => this.tool.changeBoundsManager.isValid(element)) + .forEach(element => this.elementId2startPos.delete(element.id)); + } else { + if (elementsToMove.every(element => this.tool.changeBoundsManager.isValid(element))) { + // do not reset any element as all are valid + this.elementId2startPos.clear(); + } + } this.dispose(); return []; } diff --git a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts index 4237e667..cafebcb0 100644 --- a/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts +++ b/packages/client/src/features/tools/change-bounds/change-bounds-tool.ts @@ -63,6 +63,11 @@ import { import { FeedbackMoveMouseListener } from './change-bounds-tool-move-feedback'; import { ChangeBoundsTracker, TrackedElementResize, TrackedResize } from './change-bounds-tracker'; +export interface IMovementOptions { + /** If set to true, a move with multiple elements is only performed if each individual move is valid. */ + readonly allElementsNeedToBeValid: boolean; +} + /** * The change bounds tool has the license to move multiple elements or resize a single element by implementing the ChangeBounds operation. * In contrast to Sprotty's implementation this tool only sends a `ChangeBoundsOperationAction` when an operation has finished and does not @@ -84,6 +89,7 @@ export class ChangeBoundsTool extends BaseEditTool { @inject(EdgeRouterRegistry) @optional() readonly edgeRouterRegistry?: EdgeRouterRegistry; @inject(TYPES.IMovementRestrictor) @optional() readonly movementRestrictor?: IMovementRestrictor; @inject(TYPES.IChangeBoundsManager) readonly changeBoundsManager: IChangeBoundsManager; + @inject(TYPES.IMovementOptions) @optional() readonly movementOptions: IMovementOptions = { allElementsNeedToBeValid: true }; get id(): string { return ChangeBoundsTool.ID; @@ -260,7 +266,11 @@ export class ChangeBoundsListener extends DragAwareMouseListener implements ISel protected getElementsToMove(target: GModelElement): SelectableBoundsAware[] { const selectedElements = getMatchingElements(target.index, isNonRoutableSelectedMovableBoundsAware); const selectionSet: Set = new Set(selectedElements); - return selectedElements.filter(element => this.isValidMove(element, selectionSet)); + const elementsToMove = selectedElements.filter(element => this.isValidMove(element, selectionSet)); + if (this.tool.movementOptions.allElementsNeedToBeValid && elementsToMove.length !== selectionSet.size) { + return []; + } + return elementsToMove; } protected handleMoveElementsOnServer(elementsToMove: SelectableBoundsAware[]): Operation[] { diff --git a/packages/glsp-sprotty/src/types.ts b/packages/glsp-sprotty/src/types.ts index 2054a8a3..27ac94cc 100644 --- a/packages/glsp-sprotty/src/types.ts +++ b/packages/glsp-sprotty/src/types.ts @@ -30,6 +30,7 @@ export const TYPES = { IToolFactory: Symbol('Factory'), ITypeHintProvider: Symbol('ITypeHintProvider'), IMovementRestrictor: Symbol('IMovementRestrictor'), + IMovementOptions: Symbol('IMovementOptions'), ISelectionListener: Symbol('ISelectionListener'), /** @deprecated Use {@link TYPES.IGModelRootListener} instead */ // eslint-disable-next-line deprecation/deprecation