@eclipse-glsp/client
Version:
A sprotty-based client for GLSP
274 lines • 13.2 kB
JavaScript
"use strict";
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.SelectFeedbackAction = exports.SelectAllCommand = exports.SelectCommand = exports.SelectionService = exports.ISelectionListener = void 0;
/********************************************************************************
* Copyright (c) 2019-2024 EclipseSource and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/
const sprotty_1 = require("@eclipse-glsp/sprotty");
const inversify_1 = require("inversify");
const gmodel_util_1 = require("../utils/gmodel-util");
var ISelectionListener;
(function (ISelectionListener) {
function is(object) {
return sprotty_1.AnyObject.is(object) && (0, sprotty_1.hasFunctionProp)(object, 'selectionChanged');
}
ISelectionListener.is = is;
})(ISelectionListener || (exports.ISelectionListener = ISelectionListener = {}));
let SelectionService = class SelectionService {
constructor() {
this.selectedElementIDs = new Set();
this.toDispose = new sprotty_1.DisposableCollection();
this.onSelectionChangedEmitter = new sprotty_1.Emitter();
}
initialize() {
this.toDispose.push(this.onSelectionChangedEmitter);
}
preLoadDiagram() {
this.lazyInjector.getAll(sprotty_1.TYPES.ISelectionListener).forEach(listener => this.addListener(listener));
}
dispose() {
this.toDispose.dispose();
}
get onSelectionChanged() {
return this.onSelectionChangedEmitter.event;
}
addListener(listener) {
return this.onSelectionChanged(change => listener.selectionChanged(change.root, change.selectedElements, change.deselectedElements));
}
modelRootChanged(root) {
this.updateSelection(root, [], []);
}
updateSelection(newRoot, select, deselect) {
if (newRoot === undefined && select.length === 0 && deselect.length === 0) {
return;
}
const prevRoot = this.root;
const prevSelectedElementIDs = new Set(this.selectedElementIDs);
this.root = newRoot;
// We only select elements that are not part of the deselection
const toSelect = [...select].filter(selectId => deselect.indexOf(selectId) === -1);
// We only need to deselect elements that are not part of the selection
// If an element is part of both the select and deselect, it's state is not changed
const toDeselect = [...deselect].filter(deselectId => select.indexOf(deselectId) === -1 && this.selectedElementIDs.has(deselectId));
// update selected element ids
toDeselect.forEach(toDeselectId => this.selectedElementIDs.delete(toDeselectId));
toSelect.forEach(toSelectId => this.selectedElementIDs.add(toSelectId));
// check if the newly or previously selected elements still exist in the updated root
const deselectedElementIDs = new Set(toDeselect);
for (const id of this.selectedElementIDs) {
const element = newRoot.index.getById(id);
if (element === undefined) {
// element to be selected does not exist in the root...
this.selectedElementIDs.delete(id);
if (prevRoot === null || prevRoot === void 0 ? void 0 : prevRoot.index.getById(id)) {
// ...but existed in the previous root, so we want to consider it deselected
deselectedElementIDs.add(id);
}
}
}
// only send out changes if there actually are changes, i.e., any of the selected elements ids has changed
const selectionChanged = prevSelectedElementIDs.size !== this.selectedElementIDs.size ||
![...prevSelectedElementIDs].every(value => this.selectedElementIDs.has(value));
if (selectionChanged) {
// aggregate to feedback action handling all elements as only the last feedback is restored
this.dispatchFeedback([
SelectFeedbackAction.create({
selectedElementsIDs: [...this.selectedElementIDs],
deselectedElementsIDs: [...deselectedElementIDs]
})
]);
// notify listeners after the feedback action
this.notifyListeners(this.root, this.selectedElementIDs, deselectedElementIDs);
}
}
dispatchFeedback(actions) {
this.feedbackDispatcher.registerFeedback(this, actions);
}
notifyListeners(root, selectedElementIDs, deselectedElementIDs) {
this.onSelectionChangedEmitter.fire({
root,
selectedElements: Array.from(selectedElementIDs),
deselectedElements: Array.from(deselectedElementIDs)
});
}
getModelRoot() {
return this.root;
}
getSelectedElements() {
return !this.root ? [] : (0, gmodel_util_1.getElements)(this.root.index, Array.from(this.selectedElementIDs), sprotty_1.isSelectable);
}
/**
* QUERY METHODS
*/
getSelectedElementIDs() {
return [...this.selectedElementIDs];
}
hasSelectedElements() {
return this.selectedElementIDs.size > 0;
}
isSingleSelection() {
return this.selectedElementIDs.size === 1;
}
isMultiSelection() {
return this.selectedElementIDs.size > 1;
}
};
exports.SelectionService = SelectionService;
__decorate([
(0, inversify_1.inject)(sprotty_1.TYPES.IFeedbackActionDispatcher),
__metadata("design:type", Object)
], SelectionService.prototype, "feedbackDispatcher", void 0);
__decorate([
(0, inversify_1.inject)(sprotty_1.LazyInjector),
__metadata("design:type", Object)
], SelectionService.prototype, "lazyInjector", void 0);
__decorate([
(0, inversify_1.inject)(sprotty_1.TYPES.ILogger),
__metadata("design:type", Object)
], SelectionService.prototype, "logger", void 0);
__decorate([
(0, inversify_1.postConstruct)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], SelectionService.prototype, "initialize", null);
__decorate([
(0, inversify_1.preDestroy)(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", void 0)
], SelectionService.prototype, "dispose", null);
exports.SelectionService = SelectionService = __decorate([
(0, inversify_1.injectable)()
], SelectionService);
/**
* Handles a {@link SelectAction} and propagates the new selection to the {@link SelectionService}.
* Other tools might be selection-sensitive which means {@link SelectAction}s must be processed as fast as possible.
* Handling the action with a command ensures that the action is processed before the next render tick.
*/
let SelectCommand = class SelectCommand extends sprotty_1.Command {
constructor(action, selectionService) {
super();
this.action = action;
this.selectionService = selectionService;
this.selected = [];
this.deselected = [];
}
execute(context) {
const model = context.root;
const selectionGuard = (element) => element instanceof sprotty_1.GChildElement && (0, sprotty_1.isSelectable)(element);
const selectedElements = (0, gmodel_util_1.getElements)(model.index, this.action.selectedElementsIDs, selectionGuard);
const deselectedElements = this.action.deselectAll
? this.selectionService.getSelectedElements()
: (0, gmodel_util_1.getElements)(model.index, this.action.deselectedElementsIDs, selectionGuard);
this.selectionService.updateSelection(model, (0, sprotty_1.pluck)(selectedElements, 'id'), (0, sprotty_1.pluck)(deselectedElements, 'id'));
return model;
}
// Basically no-op since client-side undo is not supported in GLSP.
undo(context) {
return context.root;
}
// Basically no-op since client-side redo is not supported in GLSP.
redo(context) {
return context.root;
}
};
exports.SelectCommand = SelectCommand;
SelectCommand.KIND = sprotty_1.SprottySelectCommand.KIND;
exports.SelectCommand = SelectCommand = __decorate([
(0, inversify_1.injectable)(),
__param(0, (0, inversify_1.inject)(sprotty_1.TYPES.Action)),
__param(1, (0, inversify_1.inject)(SelectionService)),
__metadata("design:paramtypes", [Object, SelectionService])
], SelectCommand);
/**
* Handles a {@link SelectAllAction} and propagates the new selection to the {@link SelectionService}.
* Other tools might be selection-sensitive which means {@link SelectionAllAction}s must be processed as fast as possible.
* Handling the action with a command ensures that the action is processed before the next render tick.
*/
let SelectAllCommand = class SelectAllCommand extends sprotty_1.Command {
constructor(action, selectionService) {
super();
this.action = action;
this.selectionService = selectionService;
this.previousSelection = new Map();
}
execute(context) {
const model = context.root;
const selectionGuard = (element) => element instanceof sprotty_1.GChildElement && (0, sprotty_1.isSelectable)(element);
const selectables = (0, gmodel_util_1.getMatchingElements)(model.index, selectionGuard);
const selectableIds = (0, sprotty_1.pluck)(selectables, 'id');
if (this.action.select) {
this.selectionService.updateSelection(model, selectableIds, []);
}
else {
this.selectionService.updateSelection(model, [], selectableIds);
}
return model;
}
// Basically no-op since client-side undo is not supported in GLSP.
undo(context) {
return context.root;
}
// Basically no-op since client-side redo is not supported in GLSP.
redo(context) {
return context.root;
}
};
exports.SelectAllCommand = SelectAllCommand;
SelectAllCommand.KIND = sprotty_1.SprottySelectAllCommand.KIND;
exports.SelectAllCommand = SelectAllCommand = __decorate([
(0, inversify_1.injectable)(),
__param(0, (0, inversify_1.inject)(sprotty_1.TYPES.Action)),
__param(1, (0, inversify_1.inject)(SelectionService)),
__metadata("design:paramtypes", [Object, SelectionService])
], SelectAllCommand);
var SelectFeedbackAction;
(function (SelectFeedbackAction) {
SelectFeedbackAction.KIND = 'selectFeedback';
function is(object) {
return sprotty_1.Action.hasKind(object, SelectFeedbackAction.KIND) && (0, sprotty_1.hasArrayProp)(object, 'selectedElementsIDs') && (0, sprotty_1.hasArrayProp)(object, 'deselectedElementsIDs');
}
SelectFeedbackAction.is = is;
function create(options) {
return { ...sprotty_1.SelectAction.create(options), kind: SelectFeedbackAction.KIND };
}
SelectFeedbackAction.create = create;
function addSelection(selectedElementsIDs) {
return { ...sprotty_1.SelectAction.addSelection(selectedElementsIDs), kind: SelectFeedbackAction.KIND };
}
SelectFeedbackAction.addSelection = addSelection;
function removeSelection(deselectedElementsIDs) {
return { ...sprotty_1.SelectAction.removeSelection(deselectedElementsIDs), kind: SelectFeedbackAction.KIND };
}
SelectFeedbackAction.removeSelection = removeSelection;
function setSelection(selectedElementsIDs) {
return { ...sprotty_1.SelectAction.setSelection(selectedElementsIDs), kind: SelectFeedbackAction.KIND };
}
SelectFeedbackAction.setSelection = setSelection;
})(SelectFeedbackAction || (exports.SelectFeedbackAction = SelectFeedbackAction = {}));
//# sourceMappingURL=selection-service.js.map