Skip to content

Commit

Permalink
GLSP-979: Introduce 'deselectAll' flag for SelectAction (#257)
Browse files Browse the repository at this point in the history
- Introduce optional flag that indicates deselection of all elements
- Introduce convenience creation methods for Select action

eclipse-glsp/glsp#979
  • Loading branch information
martin-fleck-at authored Jul 5, 2023
1 parent 80bb01c commit 855c7d2
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 13 deletions.
15 changes: 14 additions & 1 deletion packages/client/src/features/select/select-feedback-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,23 @@ export namespace SelectFeedbackAction {
return Action.hasKind(object, KIND) && hasArrayProp(object, 'selectedElementsIDs') && hasArrayProp(object, 'deselectedElementsIDs');
}

export function create(options?: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] }): SelectFeedbackAction {
export function create(options?: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] | boolean }): SelectFeedbackAction {
return { ...SelectAction.create(options), kind: KIND };
}

export function addSelection(selectedElementsIDs: string[]): SelectFeedbackAction {
return { ...SelectAction.addSelection(selectedElementsIDs), kind: KIND };
}

export function removeSelection(deselectedElementsIDs: string[]): SelectFeedbackAction {
return { ...SelectAction.removeSelection(deselectedElementsIDs), kind: KIND };
}

export function setSelection(selectedElementsIDs: string[]): SelectFeedbackAction {
return { ...SelectAction.setSelection(selectedElementsIDs), kind: KIND };
}
}

@injectable()
export class SelectFeedbackCommand extends Command {
static readonly KIND = SelectFeedbackAction.KIND;
Expand Down
4 changes: 3 additions & 1 deletion packages/client/src/features/select/selection-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,9 @@ export class SelectCommand extends Command {
const model = context.root;
const selectionGuard = (element: any): element is SModelElement => element instanceof SChildElement && isSelectable(element);
const selectedElements = getElements(model.index, this.action.selectedElementsIDs, selectionGuard);
const deselectedElements = getElements(model.index, this.action.deselectedElementsIDs, selectionGuard);
const deselectedElements = this.action.deselectAll
? this.selectionService.getSelectedElements()
: getElements(model.index, this.action.deselectedElementsIDs, selectionGuard);

this.selectionService.updateSelection(model, pluck(selectedElements, 'id'), pluck(deselectedElements, 'id'));
return model;
Expand Down
6 changes: 1 addition & 5 deletions packages/client/src/features/tools/marquee-mouse-tool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,11 +114,7 @@ export class MarqueeMouseListener extends DragAwareMouseListener {
const nodeIdsSelected = this.nodes.filter(e => this.marqueeUtil.isNodeMarked(toAbsoluteBounds(e))).map(e => e.id);
const edgeIdsSelected = this.edges.filter(e => this.isEdgeMarked(e)).map(e => this.domHelper.findSModelIdByDOMElement(e));
const selected = nodeIdsSelected.concat(edgeIdsSelected);
return [
SelectAction.create({ deselectedElementsIDs: Array.from(target.root.index.all().map(e => e.id)) }),
SelectAction.create({ selectedElementsIDs: selected.concat(this.previouslySelected) }),
this.marqueeUtil.drawMarqueeAction()
];
return [SelectAction.setSelection(selected.concat(this.previouslySelected)), this.marqueeUtil.drawMarqueeAction()];
}
return [];
}
Expand Down
52 changes: 50 additions & 2 deletions packages/protocol/src/action-protocol/element-selection.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,67 @@ describe('Element selection actions', () => {
const expected: SelectAction = {
kind: 'elementSelected',
selectedElementsIDs: [],
deselectedElementsIDs: []
deselectedElementsIDs: [],
deselectAll: false
};
expect(SelectAction.create()).to.deep.equals(expected);
});
it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => {
const expected: SelectAction = {
kind: 'elementSelected',
selectedElementsIDs: ['selected'],
deselectedElementsIDs: ['deselected']
deselectedElementsIDs: ['deselected'],
deselectAll: false
};
const { selectedElementsIDs, deselectedElementsIDs } = expected;
expect(SelectAction.create({ deselectedElementsIDs, selectedElementsIDs })).to.deep.equals(expected);
});
it('should return an object conforming to the interface with matching properties for the given required and optional arguments: deselectAll', () => {
const expected: SelectAction = {
kind: 'elementSelected',
selectedElementsIDs: ['selected'],
deselectedElementsIDs: [],
deselectAll: true
};
const { selectedElementsIDs } = expected;
expect(SelectAction.create({ deselectedElementsIDs: true, selectedElementsIDs })).to.deep.equals(expected);
});
});
describe('addSelection', () => {
it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => {
const expected: SelectAction = {
kind: 'elementSelected',
selectedElementsIDs: ['selected'],
deselectedElementsIDs: [],
deselectAll: false
};
const { selectedElementsIDs } = expected;
expect(SelectAction.addSelection(selectedElementsIDs)).to.deep.equals(expected);
});
});
describe('removeSelection', () => {
it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => {
const expected: SelectAction = {
kind: 'elementSelected',
selectedElementsIDs: [],
deselectedElementsIDs: ['deselected'],
deselectAll: false
};
const { deselectedElementsIDs } = expected;
expect(SelectAction.removeSelection(deselectedElementsIDs)).to.deep.equals(expected);
});
});
describe('setSelection', () => {
it('should return an object conforming to the interface with matching properties for the given required and optional arguments', () => {
const expected: SelectAction = {
kind: 'elementSelected',
selectedElementsIDs: ['selected'],
deselectedElementsIDs: [],
deselectAll: true
};
const { selectedElementsIDs } = expected;
expect(SelectAction.setSelection(selectedElementsIDs)).to.deep.equals(expected);
});
});
});

Expand Down
27 changes: 23 additions & 4 deletions packages/protocol/src/action-protocol/element-selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
import * as sprotty from 'sprotty-protocol/lib/actions';
import { isStringArray } from '../utils/array-util';
import { hasArrayProp, hasBooleanProp } from '../utils/type-util';
import { Action } from './base-protocol';

Expand All @@ -36,6 +37,11 @@ export interface SelectAction extends Action, sprotty.SelectAction {
* The identifiers of the elements to mark as not selected.
*/
deselectedElementsIDs: string[];

/**
* Whether all currently selected elements should be deselected.
*/
deselectAll?: boolean;
}

export namespace SelectAction {
Expand All @@ -45,14 +51,27 @@ export namespace SelectAction {
return Action.hasKind(object, KIND) && hasArrayProp(object, 'selectedElementsIDs') && hasArrayProp(object, 'deselectedElementsIDs');
}

export function create(options: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] } = {}): SelectAction {
export function create(options: { selectedElementsIDs?: string[]; deselectedElementsIDs?: string[] | boolean } = {}): SelectAction {
const deselectedElementsIDs = options.deselectedElementsIDs ?? [];
return {
kind: KIND,
selectedElementsIDs: [],
deselectedElementsIDs: [],
...options
selectedElementsIDs: options.selectedElementsIDs ?? [],
deselectedElementsIDs: isStringArray(deselectedElementsIDs, true) ? deselectedElementsIDs : [],
deselectAll: typeof deselectedElementsIDs === 'boolean' ? deselectedElementsIDs : false
};
}

export function addSelection(selectedElementsIDs: string[]): SelectAction {
return create({ selectedElementsIDs });
}

export function removeSelection(deselectedElementsIDs: string[]): SelectAction {
return create({ deselectedElementsIDs });
}

export function setSelection(selectedElementsIDs: string[]): SelectAction {
return create({ selectedElementsIDs, deselectedElementsIDs: true });
}
}

/**
Expand Down

0 comments on commit 855c7d2

Please sign in to comment.