UNPKG

14 kBJavaScriptView Raw
1"use strict";
2// *****************************************************************************
3// Copyright (C) 2017 TypeFox 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.FrontendApplication = void 0;
19const tslib_1 = require("tslib");
20const inversify_1 = require("inversify");
21const common_1 = require("../common");
22const keybinding_1 = require("./keybinding");
23const widgets_1 = require("./widgets");
24const application_shell_1 = require("./shell/application-shell");
25const shell_layout_restorer_1 = require("./shell/shell-layout-restorer");
26const frontend_application_state_1 = require("./frontend-application-state");
27const browser_1 = require("./browser");
28const core_preferences_1 = require("./core-preferences");
29const window_service_1 = require("./window/window-service");
30const tooltip_service_1 = require("./tooltip-service");
31const frontend_application_contribution_1 = require("./frontend-application-contribution");
32const TIMER_WARNING_THRESHOLD = 100;
33let FrontendApplication = class FrontendApplication {
34 constructor(commands, menus, keybindings, layoutRestorer, contributions, _shell, stateService) {
35 this.commands = commands;
36 this.menus = menus;
37 this.keybindings = keybindings;
38 this.layoutRestorer = layoutRestorer;
39 this.contributions = contributions;
40 this._shell = _shell;
41 this.stateService = stateService;
42 }
43 get shell() {
44 return this._shell;
45 }
46 /**
47 * Start the frontend application.
48 *
49 * Start up consists of the following steps:
50 * - start frontend contributions
51 * - attach the application shell to the host element
52 * - initialize the application shell layout
53 * - reveal the application shell if it was hidden by a startup indicator
54 */
55 async start() {
56 const startup = this.backendStopwatch.start('frontend');
57 await this.measure('startContributions', () => this.startContributions(), 'Start frontend contributions', false);
58 this.stateService.state = 'started_contributions';
59 const host = await this.getHost();
60 this.attachShell(host);
61 this.attachTooltip(host);
62 await (0, browser_1.animationFrame)();
63 this.stateService.state = 'attached_shell';
64 await this.measure('initializeLayout', () => this.initializeLayout(), 'Initialize the workbench layout', false);
65 this.stateService.state = 'initialized_layout';
66 await this.fireOnDidInitializeLayout();
67 await this.measure('revealShell', () => this.revealShell(host), 'Replace loading indicator with ready workbench UI (animation)', false);
68 this.registerEventListeners();
69 this.stateService.state = 'ready';
70 startup.then(idToken => this.backendStopwatch.stop(idToken, 'Frontend application start', []));
71 }
72 /**
73 * Return a promise to the host element to which the application shell is attached.
74 */
75 getHost() {
76 if (document.body) {
77 return Promise.resolve(document.body);
78 }
79 return new Promise(resolve => window.addEventListener('load', () => resolve(document.body), { once: true }));
80 }
81 /**
82 * Return an HTML element that indicates the startup phase, e.g. with an animation or a splash screen.
83 */
84 getStartupIndicator(host) {
85 const startupElements = host.getElementsByClassName('theia-preload');
86 return startupElements.length === 0 ? undefined : startupElements[0];
87 }
88 /**
89 * Register global event listeners.
90 */
91 registerEventListeners() {
92 this.windowsService.onUnload(() => {
93 this.stateService.state = 'closing_window';
94 this.layoutRestorer.storeLayout(this);
95 this.stopContributions();
96 });
97 window.addEventListener('resize', () => this.shell.update());
98 this.keybindings.registerEventListeners(window);
99 document.addEventListener('touchmove', event => { event.preventDefault(); }, { passive: false });
100 // Prevent forward/back navigation by scrolling in OS X
101 if (common_1.isOSX) {
102 document.body.addEventListener('wheel', browser_1.preventNavigation, { passive: false });
103 }
104 // Prevent the default browser behavior when dragging and dropping files into the window.
105 document.addEventListener('dragenter', event => {
106 if (event.dataTransfer) {
107 event.dataTransfer.dropEffect = 'none';
108 }
109 event.preventDefault();
110 }, false);
111 document.addEventListener('dragover', event => {
112 if (event.dataTransfer) {
113 event.dataTransfer.dropEffect = 'none';
114 }
115 event.preventDefault();
116 }, false);
117 document.addEventListener('drop', event => {
118 event.preventDefault();
119 }, false);
120 }
121 /**
122 * Attach the application shell to the host element. If a startup indicator is present, the shell is
123 * inserted before that indicator so it is not visible yet.
124 */
125 attachShell(host) {
126 const ref = this.getStartupIndicator(host);
127 widgets_1.Widget.attach(this.shell, host, ref);
128 }
129 /**
130 * Attach the tooltip container to the host element.
131 */
132 attachTooltip(host) {
133 this.tooltipService.attachTo(host);
134 }
135 /**
136 * If a startup indicator is present, it is first hidden with the `theia-hidden` CSS class and then
137 * removed after a while. The delay until removal is taken from the CSS transition duration.
138 */
139 revealShell(host) {
140 const startupElem = this.getStartupIndicator(host);
141 if (startupElem) {
142 return new Promise(resolve => {
143 window.requestAnimationFrame(() => {
144 startupElem.classList.add('theia-hidden');
145 const preloadStyle = window.getComputedStyle(startupElem);
146 const transitionDuration = (0, browser_1.parseCssTime)(preloadStyle.transitionDuration, 0);
147 window.setTimeout(() => {
148 const parent = startupElem.parentElement;
149 if (parent) {
150 parent.removeChild(startupElem);
151 }
152 resolve();
153 }, transitionDuration);
154 });
155 });
156 }
157 else {
158 return Promise.resolve();
159 }
160 }
161 /**
162 * Initialize the shell layout either using the layout restorer service or, if no layout has
163 * been stored, by creating the default layout.
164 */
165 async initializeLayout() {
166 if (!await this.restoreLayout()) {
167 // Fallback: Create the default shell layout
168 await this.createDefaultLayout();
169 }
170 await this.shell.pendingUpdates;
171 }
172 /**
173 * Try to restore the shell layout from the storage service. Resolves to `true` if successful.
174 */
175 async restoreLayout() {
176 try {
177 return await this.layoutRestorer.restoreLayout(this);
178 }
179 catch (error) {
180 if (shell_layout_restorer_1.ApplicationShellLayoutMigrationError.is(error)) {
181 console.warn(error.message);
182 console.info('Initializing the default layout instead...');
183 }
184 else {
185 console.error('Could not restore layout', error);
186 }
187 return false;
188 }
189 }
190 /**
191 * Let the frontend application contributions initialize the shell layout. Override this
192 * method in order to create an application-specific custom layout.
193 */
194 async createDefaultLayout() {
195 for (const contribution of this.contributions.getContributions()) {
196 if (contribution.initializeLayout) {
197 await this.measure(contribution.constructor.name + '.initializeLayout', () => contribution.initializeLayout(this));
198 }
199 }
200 }
201 async fireOnDidInitializeLayout() {
202 for (const contribution of this.contributions.getContributions()) {
203 if (contribution.onDidInitializeLayout) {
204 await this.measure(contribution.constructor.name + '.onDidInitializeLayout', () => contribution.onDidInitializeLayout(this));
205 }
206 }
207 }
208 /**
209 * Initialize and start the frontend application contributions.
210 */
211 async startContributions() {
212 for (const contribution of this.contributions.getContributions()) {
213 if (contribution.initialize) {
214 try {
215 await this.measure(contribution.constructor.name + '.initialize', () => contribution.initialize());
216 }
217 catch (error) {
218 console.error('Could not initialize contribution', error);
219 }
220 }
221 }
222 for (const contribution of this.contributions.getContributions()) {
223 if (contribution.configure) {
224 try {
225 await this.measure(contribution.constructor.name + '.configure', () => contribution.configure(this));
226 }
227 catch (error) {
228 console.error('Could not configure contribution', error);
229 }
230 }
231 }
232 /**
233 * FIXME:
234 * - decouple commands & menus
235 * - consider treat commands, keybindings and menus as frontend application contributions
236 */
237 await this.measure('commands.onStart', () => this.commands.onStart());
238 await this.measure('keybindings.onStart', () => this.keybindings.onStart());
239 await this.measure('menus.onStart', () => this.menus.onStart());
240 for (const contribution of this.contributions.getContributions()) {
241 if (contribution.onStart) {
242 try {
243 await this.measure(contribution.constructor.name + '.onStart', () => contribution.onStart(this));
244 }
245 catch (error) {
246 console.error('Could not start contribution', error);
247 }
248 }
249 }
250 }
251 /**
252 * Stop the frontend application contributions. This is called when the window is unloaded.
253 */
254 stopContributions() {
255 console.info('>>> Stopping frontend contributions...');
256 for (const contribution of this.contributions.getContributions()) {
257 if (contribution.onStop) {
258 try {
259 contribution.onStop(this);
260 }
261 catch (error) {
262 console.error('Could not stop contribution', error);
263 }
264 }
265 }
266 console.info('<<< All frontend contributions have been stopped.');
267 }
268 async measure(name, fn, message = `Frontend ${name}`, threshold = true) {
269 return this.stopwatch.startAsync(name, message, fn, threshold ? { thresholdMillis: TIMER_WARNING_THRESHOLD, defaultLogLevel: common_1.LogLevel.DEBUG } : {});
270 }
271};
272(0, tslib_1.__decorate)([
273 (0, inversify_1.inject)(core_preferences_1.CorePreferences),
274 (0, tslib_1.__metadata)("design:type", Object)
275], FrontendApplication.prototype, "corePreferences", void 0);
276(0, tslib_1.__decorate)([
277 (0, inversify_1.inject)(window_service_1.WindowService),
278 (0, tslib_1.__metadata)("design:type", Object)
279], FrontendApplication.prototype, "windowsService", void 0);
280(0, tslib_1.__decorate)([
281 (0, inversify_1.inject)(tooltip_service_1.TooltipService),
282 (0, tslib_1.__metadata)("design:type", Object)
283], FrontendApplication.prototype, "tooltipService", void 0);
284(0, tslib_1.__decorate)([
285 (0, inversify_1.inject)(common_1.Stopwatch),
286 (0, tslib_1.__metadata)("design:type", common_1.Stopwatch)
287], FrontendApplication.prototype, "stopwatch", void 0);
288(0, tslib_1.__decorate)([
289 (0, inversify_1.inject)(common_1.BackendStopwatch),
290 (0, tslib_1.__metadata)("design:type", Object)
291], FrontendApplication.prototype, "backendStopwatch", void 0);
292FrontendApplication = (0, tslib_1.__decorate)([
293 (0, inversify_1.injectable)(),
294 (0, tslib_1.__param)(0, (0, inversify_1.inject)(common_1.CommandRegistry)),
295 (0, tslib_1.__param)(1, (0, inversify_1.inject)(common_1.MenuModelRegistry)),
296 (0, tslib_1.__param)(2, (0, inversify_1.inject)(keybinding_1.KeybindingRegistry)),
297 (0, tslib_1.__param)(3, (0, inversify_1.inject)(shell_layout_restorer_1.ShellLayoutRestorer)),
298 (0, tslib_1.__param)(4, (0, inversify_1.inject)(common_1.ContributionProvider)),
299 (0, tslib_1.__param)(4, (0, inversify_1.named)(frontend_application_contribution_1.FrontendApplicationContribution)),
300 (0, tslib_1.__param)(5, (0, inversify_1.inject)(application_shell_1.ApplicationShell)),
301 (0, tslib_1.__param)(6, (0, inversify_1.inject)(frontend_application_state_1.FrontendApplicationStateService)),
302 (0, tslib_1.__metadata)("design:paramtypes", [common_1.CommandRegistry,
303 common_1.MenuModelRegistry,
304 keybinding_1.KeybindingRegistry,
305 shell_layout_restorer_1.ShellLayoutRestorer, Object, application_shell_1.ApplicationShell,
306 frontend_application_state_1.FrontendApplicationStateService])
307], FrontendApplication);
308exports.FrontendApplication = FrontendApplication;
309//# sourceMappingURL=frontend-application.js.map
\No newline at end of file