@eclipse-glsp/theia-integration
Version:
Glue code to integrate GLSP clients into Eclipse Theia
374 lines • 15.3 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);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.GLSPDiagramWidget = exports.GLSPDiagramWidgetOptions = void 0;
exports.isDiagramWidgetContainer = isDiagramWidgetContainer;
exports.getDiagramWidget = getDiagramWidget;
/********************************************************************************
* Copyright (c) 2017-2026 TypeFox and others.
* Modifications: (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
********************************************************************************/
// based on: https://github.com/eclipse-sprotty/sprotty-theia/blob/v0.12.0/src/theia/diagram-widget.ts
const client_1 = require("@eclipse-glsp/client");
const core_1 = require("@theia/core");
const browser_1 = require("@theia/core/lib/browser");
const selection_service_1 = require("@theia/core/lib/common/selection-service");
const inversify_1 = require("@theia/core/shared/inversify");
const lodash_1 = require("lodash");
const glsp_saveable_1 = require("./glsp-saveable");
var GLSPDiagramWidgetOptions;
(function (GLSPDiagramWidgetOptions) {
function is(object) {
return (browser_1.NavigatableWidgetOptions.is(object) &&
(0, client_1.hasStringProp)(object, 'uri') &&
(0, client_1.hasStringProp)(object, 'diagramType') &&
(0, client_1.hasStringProp)(object, 'label') &&
(0, client_1.hasStringProp)(object, 'iconClass') &&
(0, client_1.hasStringProp)(object, 'editMode'));
}
GLSPDiagramWidgetOptions.is = is;
})(GLSPDiagramWidgetOptions || (exports.GLSPDiagramWidgetOptions = GLSPDiagramWidgetOptions = {}));
function isDiagramWidgetContainer(widget) {
return !!widget && 'diagramWidget' in widget && widget.diagramWidget instanceof GLSPDiagramWidget;
}
class GLSPDiagramWidget extends browser_1.BaseWidget {
constructor() {
super(...arguments);
this.storeViewportStateOnClose = true;
}
configure(options, diContainer) {
this._options = options;
this._diContainer = diContainer;
this.title.closable = true;
this.title.label = options.label;
this.title.iconClass = options.iconClass;
this.id = this.createWidgetId();
this.saveable = new glsp_saveable_1.GLSPSaveable(this.actionDispatcher, this.diContainer.get(client_1.EditorContextService));
this.title.caption = this.uri.path.fsPath();
this.toDispose.push(this.saveable);
}
createWidgetId() {
return `${this.diagramType}:${this.options.uri}`;
}
onAfterAttach(msg) {
if (!this.diagramContainer) {
// Create the container and initialize its content upon first attachment
this.createContainer();
this.initializeDiagram();
}
super.onAfterAttach(msg);
this.restoreViewportDataFromStorageService();
this.disposed.connect(() => {
this.diContainer.unbindAll();
});
this.node.dataset['uri'] = this.uri.toString();
if (this.diContainer.isBound(client_1.TYPES.ICopyPasteHandler)) {
this.copyPasteHandler = this.diContainer.get(client_1.TYPES.ICopyPasteHandler);
this.addClipboardListener(this.node, 'copy', e => this.handleCopy(e));
this.addClipboardListener(this.node, 'paste', e => this.handlePaste(e));
this.addClipboardListener(this.node, 'cut', e => this.handleCut(e));
}
this.addEventListener(this.node, 'mouseenter', e => this.handleMouseEnter(e));
this.addEventListener(this.node, 'mouseleave', e => this.handleMouseLeave(e));
}
createContainer() {
this.diagramContainer = document.createElement('div');
this.diagramContainer.id = this.viewerOptions.baseDiv;
this.node.appendChild(this.diagramContainer);
const hiddenContainer = document.createElement('div');
hiddenContainer.id = this.viewerOptions.hiddenDiv;
document.body.appendChild(hiddenContainer);
}
async initializeDiagram() {
// Filter options to only contain defined primitive values
this.requestModelOptions = this.getRequestModelOptions();
const loader = this.diContainer.get(client_1.DiagramLoader);
return loader.load({ requestModelOptions: this.requestModelOptions });
}
getRequestModelOptions() {
const definedOptions = (0, lodash_1.pickBy)(this.options, v => v !== undefined && typeof v !== 'object');
return {
sourceUri: this.uri.path.fsPath(),
...definedOptions
};
}
getBoundsInPage(element) {
const bounds = element.getBoundingClientRect();
return {
x: bounds.left,
y: bounds.top,
width: bounds.width,
height: bounds.height
};
}
onResize(msg) {
super.onResize(msg);
const newBounds = this.getBoundsInPage(this.node);
this.actionDispatcher.dispatch(client_1.InitializeCanvasBoundsAction.create(newBounds));
}
onActivateRequest(msg) {
this.makeFocusable(this.node.querySelector(`#${this.viewerOptions.baseDiv} svg`));
super.onActivateRequest(msg);
this.focusDiagram();
this.updateGlobalSelection();
}
makeFocusable(element) {
// eslint-disable-next-line no-null/no-null
if (element !== null) {
const tabindex = element.getAttribute('tabindex');
// eslint-disable-next-line no-null/no-null
if (tabindex === null) {
// use -1 to make focusable but not reachable via keyboard navigation
element.setAttribute('tabindex', '-1');
}
}
}
focusDiagram() {
const svgElement = this.node.querySelector(`#${this.viewerOptions.baseDiv} svg`);
if (svgElement) {
svgElement.focus();
}
else {
const tabindex = this.node.getAttribute('tabindex');
if (tabindex) {
this.node.setAttribute('tabindex', '-1');
}
this.node.focus();
}
}
async updateGlobalSelection() {
this.getSelectedElementIds().then((selectedElementsIDs) => this.actionDispatcher.dispatch(client_1.SelectAction.create({ selectedElementsIDs })));
}
/**
* We cannot activate the widget before the SVG element is there, as it takes the focus.
* This should happen within two animation frames, as the action dispatcher issues
* a SetModelCommand in the constructor. OTOH, shell.activateWidget() is synchronous. So
* after creating the widget and before activating it, we use this method to wait for the
* SVG to be appended to the DOM.
*/
async getSvgElement() {
return new Promise(resolve => {
let frames = 0;
const waitForSvg = () => {
requestAnimationFrame(() => {
const svgElement = this.node.querySelector(`#${this.viewerOptions.baseDiv} svg`);
if (svgElement) {
resolve(svgElement);
}
else if (++frames < 5) {
waitForSvg();
}
else {
resolve(undefined);
}
});
};
waitForSvg();
});
}
onBeforeDetach(msg) {
var _a;
this.storeViewportDataInStorageService();
(_a = document.querySelector(`#${this.viewerOptions.hiddenDiv}`)) === null || _a === void 0 ? void 0 : _a.remove();
super.onBeforeDetach(msg);
}
onCloseRequest(msg) {
super.onCloseRequest(msg);
this.clearGlobalSelection();
}
reloadModel() {
return this.actionDispatcher.dispatch(client_1.RequestModelAction.create({ options: this.requestModelOptions }));
}
handleMouseEnter(e) {
this.node.classList.add('mouse-enter');
this.node.classList.remove('mouse-leave');
}
handleMouseLeave(e) {
this.node.classList.add('mouse-leave');
this.node.classList.remove('mouse-enter');
}
handleCopy(e) {
if (this.copyPasteHandler) {
this.copyPasteHandler.handleCopy(e);
}
}
handleCut(e) {
if (this.copyPasteHandler) {
this.copyPasteHandler.handleCut(e);
}
}
handlePaste(e) {
if (this.copyPasteHandler) {
this.copyPasteHandler.handlePaste(e);
}
}
listenToFocusState(shell) {
this.toDispose.push(shell.onDidChangeActiveWidget(event => {
const focusedWidget = event.newValue;
if (this.hasFocus && focusedWidget && !this.isThisWidget(focusedWidget)) {
this.actionDispatcher.dispatch(client_1.FocusStateChangedAction.create(false));
}
else if (!this.hasFocus && this.isThisWidget(focusedWidget)) {
this.actionDispatcher.dispatch(client_1.FocusStateChangedAction.create(true));
}
}));
}
isThisWidget(widget) {
if (!widget) {
return false;
}
const diagramWidget = getDiagramWidget(widget);
return diagramWidget !== undefined && diagramWidget.id === this.id;
}
get hasFocus() {
let focusTracker;
if (this.diContainer.isBound(client_1.FocusTracker)) {
focusTracker = this.diContainer.get(client_1.FocusTracker);
}
if (focusTracker) {
return focusTracker.hasFocus;
}
return undefined;
}
async getSelectedElementIds() {
const editorContextService = this.diContainer.get(client_1.EditorContextService);
return editorContextService.selectedElements.map(element => element.id);
}
async clearGlobalSelection() {
this.theiaSelectionService.selection = undefined;
}
storeState() {
// the viewport is stored in the application layout
// so there is no need to keep it in the storage
this.removeViewportDataFromStorageService();
return { ...this.options, ...this.getViewportData() };
}
restoreState(oldState) {
if (GLSPDiagramWidgetOptions.is(oldState)) {
this._options = oldState;
}
if (isViewportDataContainer(oldState)) {
this.setViewportData(oldState);
}
}
getResourceUri() {
return this.uri;
}
createMoveToUri(resourceUri) {
return this.uri.withPath(resourceUri.path);
}
storeViewportDataInStorageService() {
if (!this.storeViewportStateOnClose) {
return;
}
const viewportData = this.getViewportData();
if (viewportData) {
this.storage.setData(this.viewportStorageId, viewportData);
}
}
async restoreViewportDataFromStorageService() {
if (!this.storeViewportStateOnClose) {
return;
}
const viewportData = await this.storage.getData(this.viewportStorageId);
if (viewportData) {
this.setViewportData(viewportData);
}
}
async removeViewportDataFromStorageService() {
return this.storage.setData(this.viewportStorageId, undefined);
}
getViewportData() {
let viewportData = undefined;
if ((0, client_1.isViewport)(this.editorContext.modelRoot)) {
viewportData = {
elementId: this.editorContext.modelRoot.id,
viewportData: {
scroll: this.editorContext.modelRoot.scroll,
zoom: this.editorContext.modelRoot.zoom
}
};
}
return viewportData;
}
async setViewportData(viewportData) {
if (this.actionDispatcher instanceof client_1.GLSPActionDispatcher) {
const restoreViewportAction = client_1.SetViewportAction.create(viewportData.elementId, viewportData.viewportData, { animate: true });
return this.actionDispatcher.dispatchOnceModelInitialized(restoreViewportAction);
}
}
get viewportStorageId() {
return this.options.diagramType + ':' + this.options.uri;
}
get actionDispatcher() {
return this.diContainer.get(client_1.TYPES.IActionDispatcher);
}
get editorContext() {
return this.diContainer.get(client_1.EditorContextService);
}
get viewerOptions() {
return this.diContainer.get(client_1.TYPES.ViewerOptions);
}
get modelSource() {
return this.diContainer.get(client_1.GLSPModelSource);
}
get clientId() {
return this.modelSource.clientId;
}
get uri() {
return new core_1.URI(this.options.uri);
}
get diagramType() {
return this.options.diagramType;
}
get options() {
return this._options;
}
get diContainer() {
return this._diContainer;
}
}
exports.GLSPDiagramWidget = GLSPDiagramWidget;
__decorate([
(0, inversify_1.inject)(browser_1.StorageService),
__metadata("design:type", Object)
], GLSPDiagramWidget.prototype, "storage", void 0);
__decorate([
(0, inversify_1.inject)(selection_service_1.SelectionService),
__metadata("design:type", selection_service_1.SelectionService)
], GLSPDiagramWidget.prototype, "theiaSelectionService", void 0);
function isViewportDataContainer(obj) {
return obj !== undefined && obj['elementId'] !== undefined && obj['viewportData'] !== undefined;
}
function getDiagramWidget(widgetOrShell) {
var _a;
const widget = widgetOrShell instanceof browser_1.ApplicationShell ? ((_a = widgetOrShell.activeWidget) !== null && _a !== void 0 ? _a : widgetOrShell.currentWidget) : widgetOrShell;
if (widget instanceof GLSPDiagramWidget) {
return widget;
}
else if (isDiagramWidgetContainer(widget)) {
return widget.diagramWidget;
}
return undefined;
}
//# sourceMappingURL=glsp-diagram-widget.js.map