UNPKG

@eclipse-glsp/theia-integration

Version:

Glue code to integrate GLSP clients into Eclipse Theia

390 lines 16.2 kB
"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-2018 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 browser_1 = require("@theia/core/lib/browser"); const selection_service_1 = require("@theia/core/lib/common/selection-service"); const uri_1 = require("@theia/core/lib/common/uri"); const inversify_1 = require("@theia/core/shared/inversify"); const browser_2 = require("@theia/editor/lib/browser"); 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.updateSaveable(); this.title.caption = this.uri.path.fsPath(); this.toDispose.push(this.editorPreferences.onPreferenceChanged(() => this.updateSaveable())); 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); } /** * Note: Manually updating the autosave settings is only necessary when using Theia < 1.50.0. * Since Theia 1.50.0, the autosave settings are generically handled and no longer the responsibility * of the `Saveable` implementation. See https://github.com/eclipse-theia/theia/pull/13683 */ updateSaveable() { this.saveable.autoSave = this.editorPreferences['files.autoSave']; this.saveable.autoSaveDelay = this.editorPreferences['files.autoSaveDelay']; } 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 uri_1.default(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_2.EditorPreferences), __metadata("design:type", Object) ], GLSPDiagramWidget.prototype, "editorPreferences", void 0); __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