UNPKG

9.23 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2022 STMicroelectronics, Ericsson, ARM, EclipseSource and others.
4//
5// This program and the accompanying materials are made available under the
6// terms of the Eclipse Public License v. 2.0 which is available at
7// http://www.eclipse.org/legal/epl-2.0.
8//
9// This Source Code may also be made available under the following Secondary
10// Licenses when the conditions for such availability set forth in the Eclipse
11// Public License v. 2.0 are satisfied: GNU General Public License, version 2
12// with the GNU Classpath Exception which is available at
13// https://www.gnu.org/software/classpath/license.html.
14//
15// SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0
16// *****************************************************************************
17Object.defineProperty(exports, "__esModule", { value: true });
18exports.SecondaryWindowHandler = void 0;
19const tslib_1 = require("tslib");
20const debounce = require("lodash.debounce");
21const inversify_1 = require("inversify");
22const widgets_1 = require("./widgets");
23const message_service_1 = require("../common/message-service");
24const event_1 = require("../common/event");
25const secondary_window_service_1 = require("./window/secondary-window-service");
26const keybinding_1 = require("./keybinding");
27/** Widget to be contained directly in a secondary window. */
28class SecondaryWindowRootWidget extends widgets_1.Widget {
29 constructor() {
30 super();
31 this.layout = new widgets_1.BoxLayout();
32 }
33 addWidget(widget) {
34 this.layout.addWidget(widget);
35 widgets_1.BoxPanel.setStretch(widget, 1);
36 }
37}
38/**
39 * Offers functionality to move a widget out of the main window to a newly created window.
40 * Widgets must explicitly implement the `ExtractableWidget` interface to support this.
41 *
42 * This handler manages the opened secondary windows and sets up messaging between them and the Theia main window.
43 * In addition, it provides access to the extracted widgets and provides notifications when widgets are added to or removed from this handler.
44 *
45 */
46let SecondaryWindowHandler = class SecondaryWindowHandler {
47 constructor() {
48 /** List of widgets in secondary windows. */
49 this._widgets = [];
50 this.onWillAddWidgetEmitter = new event_1.Emitter();
51 /** Subscribe to get notified when a widget is added to this handler, i.e. the widget was moved to an secondary window . */
52 this.onWillAddWidget = this.onWillAddWidgetEmitter.event;
53 this.onDidAddWidgetEmitter = new event_1.Emitter();
54 /** Subscribe to get notified when a widget is added to this handler, i.e. the widget was moved to an secondary window . */
55 this.onDidAddWidget = this.onDidAddWidgetEmitter.event;
56 this.onWillRemoveWidgetEmitter = new event_1.Emitter();
57 /** Subscribe to get notified when a widget is removed from this handler, i.e. the widget's window was closed or the widget was disposed. */
58 this.onWillRemoveWidget = this.onWillRemoveWidgetEmitter.event;
59 this.onDidRemoveWidgetEmitter = new event_1.Emitter();
60 /** Subscribe to get notified when a widget is removed from this handler, i.e. the widget's window was closed or the widget was disposed. */
61 this.onDidRemoveWidget = this.onDidRemoveWidgetEmitter.event;
62 }
63 /** @returns List of widgets in secondary windows. */
64 get widgets() {
65 // Create new array in case the original changes while this is used.
66 return [...this._widgets];
67 }
68 /**
69 * Sets up message forwarding from the main window to secondary windows.
70 * Does nothing if this service has already been initialized.
71 *
72 * @param shell The `ApplicationShell` that widgets will be moved out from.
73 */
74 init(shell) {
75 if (this.applicationShell) {
76 // Already initialized
77 return;
78 }
79 this.applicationShell = shell;
80 }
81 /**
82 * Moves the given widget to a new window.
83 *
84 * @param widget the widget to extract
85 */
86 moveWidgetToSecondaryWindow(widget) {
87 if (!this.applicationShell) {
88 console.error('Widget cannot be extracted because the WidgetExtractionHandler has not been initialized.');
89 return;
90 }
91 if (!widget.isExtractable) {
92 console.error('Widget is not extractable.', widget.id);
93 return;
94 }
95 const newWindow = this.secondaryWindowService.createSecondaryWindow(widget, this.applicationShell);
96 if (!newWindow) {
97 this.messageService.error('The widget could not be moved to a secondary window because the window creation failed. Please make sure to allow popups.');
98 return;
99 }
100 const mainWindowTitle = document.title;
101 newWindow.addEventListener('load', () => {
102 this.keybindings.registerEventListeners(newWindow);
103 // Use the widget's title as the window title
104 // Even if the widget's label were malicious, this should be safe against XSS because the HTML standard defines this is inserted via a text node.
105 // See https://html.spec.whatwg.org/multipage/dom.html#document.title
106 newWindow.document.title = `${widget.title.label}${mainWindowTitle}`;
107 const element = newWindow.document.getElementById('widget-host');
108 if (!element) {
109 console.error('Could not find dom element to attach to in secondary window');
110 return;
111 }
112 this.onWillAddWidgetEmitter.fire([widget, newWindow]);
113 widget.secondaryWindow = newWindow;
114 const rootWidget = new SecondaryWindowRootWidget();
115 rootWidget.addClass('secondary-widget-root');
116 widgets_1.Widget.attach(rootWidget, element);
117 rootWidget.addWidget(widget);
118 widget.show();
119 widget.update();
120 this.addWidget(widget, newWindow);
121 // Close the window if the widget is disposed, e.g. by a command closing all widgets.
122 widget.disposed.connect(() => {
123 this.onWillRemoveWidgetEmitter.fire([widget, newWindow]);
124 this.removeWidget(widget, newWindow);
125 if (!newWindow.closed) {
126 newWindow.close();
127 }
128 });
129 // debounce to avoid rapid updates while resizing the secondary window
130 const updateWidget = debounce(() => {
131 rootWidget.update();
132 }, 100);
133 newWindow.addEventListener('resize', () => {
134 updateWidget();
135 });
136 widget.activate();
137 });
138 }
139 /**
140 * If the given widget is tracked by this handler, activate it and focus its secondary window.
141 *
142 * @param widgetId The widget to activate specified by its id
143 * @returns The activated `ExtractableWidget` or `undefined` if the given widget id is unknown to this handler.
144 */
145 activateWidget(widgetId) {
146 const trackedWidget = this.revealWidget(widgetId);
147 trackedWidget === null || trackedWidget === void 0 ? void 0 : trackedWidget.activate();
148 return trackedWidget;
149 }
150 /**
151 * If the given widget is tracked by this handler, reveal it by focussing its secondary window.
152 *
153 * @param widgetId The widget to reveal specified by its id
154 * @returns The revealed `ExtractableWidget` or `undefined` if the given widget id is unknown to this handler.
155 */
156 revealWidget(widgetId) {
157 const trackedWidget = this._widgets.find(w => w.id === widgetId);
158 if (trackedWidget) {
159 this.secondaryWindowService.focus(trackedWidget.secondaryWindow);
160 return trackedWidget;
161 }
162 return undefined;
163 }
164 addWidget(widget, win) {
165 if (!this._widgets.includes(widget)) {
166 this._widgets.push(widget);
167 this.onDidAddWidgetEmitter.fire([widget, win]);
168 }
169 }
170 removeWidget(widget, win) {
171 const index = this._widgets.indexOf(widget);
172 if (index > -1) {
173 this._widgets.splice(index, 1);
174 this.onDidRemoveWidgetEmitter.fire([widget, win]);
175 }
176 }
177};
178(0, tslib_1.__decorate)([
179 (0, inversify_1.inject)(keybinding_1.KeybindingRegistry),
180 (0, tslib_1.__metadata)("design:type", keybinding_1.KeybindingRegistry)
181], SecondaryWindowHandler.prototype, "keybindings", void 0);
182(0, tslib_1.__decorate)([
183 (0, inversify_1.inject)(message_service_1.MessageService),
184 (0, tslib_1.__metadata)("design:type", message_service_1.MessageService)
185], SecondaryWindowHandler.prototype, "messageService", void 0);
186(0, tslib_1.__decorate)([
187 (0, inversify_1.inject)(secondary_window_service_1.SecondaryWindowService),
188 (0, tslib_1.__metadata)("design:type", Object)
189], SecondaryWindowHandler.prototype, "secondaryWindowService", void 0);
190SecondaryWindowHandler = (0, tslib_1.__decorate)([
191 (0, inversify_1.injectable)()
192], SecondaryWindowHandler);
193exports.SecondaryWindowHandler = SecondaryWindowHandler;
194//# sourceMappingURL=secondary-window-handler.js.map
\No newline at end of file