UNPKG

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