UNPKG

@theia/core

Version:

Theia is a cloud & desktop IDE framework implemented in TypeScript.

348 lines • 16 kB
"use strict"; // ***************************************************************************** // Copyright (C) 2017 TypeFox 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 // ***************************************************************************** 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); }; var __param = (this && this.__param) || function (paramIndex, decorator) { return function (target, key) { decorator(target, key, paramIndex); } }; var ShellLayoutRestorer_1; Object.defineProperty(exports, "__esModule", { value: true }); exports.ShellLayoutRestorer = exports.RESET_LAYOUT = exports.ApplicationShellLayoutMigration = exports.ApplicationShellLayoutMigrationError = exports.StatefulWidget = void 0; const inversify_1 = require("inversify"); const widget_manager_1 = require("../widget-manager"); const storage_service_1 = require("../storage-service"); const logger_1 = require("../../common/logger"); const command_1 = require("../../common/command"); const theming_1 = require("../theming"); const contribution_provider_1 = require("../../common/contribution-provider"); const application_shell_1 = require("./application-shell"); const common_frontend_contribution_1 = require("../common-frontend-contribution"); const window_service_1 = require("../window/window-service"); const frontend_application_state_1 = require("../../common/frontend-application-state"); const common_1 = require("../../common"); var StatefulWidget; (function (StatefulWidget) { function is(arg) { return (0, common_1.isObject)(arg) && (0, common_1.isFunction)(arg.storeState) && (0, common_1.isFunction)(arg.restoreState); } StatefulWidget.is = is; })(StatefulWidget = exports.StatefulWidget || (exports.StatefulWidget = {})); var ApplicationShellLayoutMigrationError; (function (ApplicationShellLayoutMigrationError) { const code = 'ApplicationShellLayoutMigrationError'; function create(message) { return Object.assign(new Error(`Could not migrate layout to version ${application_shell_1.applicationShellLayoutVersion}.` + (message ? '\n' + message : '')), { code }); } ApplicationShellLayoutMigrationError.create = create; function is(error) { return !!error && 'code' in error && error['code'] === code; } ApplicationShellLayoutMigrationError.is = is; })(ApplicationShellLayoutMigrationError = exports.ApplicationShellLayoutMigrationError || (exports.ApplicationShellLayoutMigrationError = {})); exports.ApplicationShellLayoutMigration = Symbol('ApplicationShellLayoutMigration'); exports.RESET_LAYOUT = command_1.Command.toLocalizedCommand({ id: 'reset.layout', category: common_frontend_contribution_1.CommonCommands.VIEW_CATEGORY, label: 'Reset Workbench Layout' }, 'theia/core/resetWorkbenchLayout', common_frontend_contribution_1.CommonCommands.VIEW_CATEGORY_KEY); let ShellLayoutRestorer = ShellLayoutRestorer_1 = class ShellLayoutRestorer { constructor(widgetManager, logger, storageService) { this.widgetManager = widgetManager; this.logger = logger; this.storageService = storageService; this.storageKey = 'layout'; this.shouldStoreLayout = true; } registerCommands(commands) { commands.registerCommand(exports.RESET_LAYOUT, { execute: async () => this.resetLayout() }); } async resetLayout() { if (await this.windowService.isSafeToShutDown(frontend_application_state_1.StopReason.Reload)) { this.logger.info('>>> Resetting layout...'); this.shouldStoreLayout = false; this.storageService.setData(this.storageKey, undefined); this.themeService.reset(); this.logger.info('<<< The layout has been successfully reset.'); this.windowService.reload(); } } storeLayout(app) { if (this.shouldStoreLayout) { try { this.logger.info('>>> Storing the layout...'); const layoutData = app.shell.getLayoutData(); const serializedLayoutData = this.deflate(layoutData); this.storageService.setData(this.storageKey, serializedLayoutData); this.logger.info('<<< The layout has been successfully stored.'); } catch (error) { this.storageService.setData(this.storageKey, undefined); this.logger.error('Error during serialization of layout data', error); } } } async restoreLayout(app) { this.logger.info('>>> Restoring the layout state...'); const serializedLayoutData = await this.storageService.getData(this.storageKey); if (serializedLayoutData === undefined) { this.logger.info('<<< Nothing to restore.'); return false; } const layoutData = await this.inflate(serializedLayoutData); await app.shell.setLayoutData(layoutData); this.logger.info('<<< The layout has been successfully restored.'); return true; } isWidgetProperty(propertyName) { return propertyName === 'widget'; } isWidgetsProperty(propertyName) { return propertyName === 'widgets'; } /** * Turns the layout data to a string representation. */ deflate(data) { return JSON.stringify(data, (property, value) => { if (this.isWidgetProperty(property)) { const description = this.convertToDescription(value); return description; } else if (this.isWidgetsProperty(property)) { const descriptions = []; for (const widget of value) { const description = this.convertToDescription(widget); if (description) { descriptions.push(description); } } return descriptions; } return value; }); } convertToDescription(widget) { const desc = this.widgetManager.getDescription(widget); if (desc) { if (StatefulWidget.is(widget)) { const innerState = widget.storeState(); return innerState ? { constructionOptions: desc, innerWidgetState: this.deflate(innerState) } : undefined; } else { return { constructionOptions: desc, innerWidgetState: undefined }; } } } /** * Creates the layout data from its string representation. */ async inflate(layoutData) { const parseContext = new ShellLayoutRestorer_1.ParseContext(); const layout = this.parse(layoutData, parseContext); const layoutVersion = Number(layout.version); if (typeof layoutVersion !== 'number' || Number.isNaN(layoutVersion)) { throw new Error('could not resolve a layout version'); } if (layoutVersion !== application_shell_1.applicationShellLayoutVersion) { if (layoutVersion < application_shell_1.applicationShellLayoutVersion) { console.warn(`Layout version ${layoutVersion} is behind current layout version ${application_shell_1.applicationShellLayoutVersion}, trying to migrate...`); } else { console.warn(`Layout version ${layoutVersion} is ahead current layout version ${application_shell_1.applicationShellLayoutVersion}, trying to load anyway...`); } console.info(`Please use '${exports.RESET_LAYOUT.label}' command if the layout looks bogus.`); } const migrations = this.migrations.getContributions() .filter(m => m.layoutVersion > layoutVersion && m.layoutVersion <= application_shell_1.applicationShellLayoutVersion) .sort((m, m2) => m.layoutVersion - m2.layoutVersion); if (migrations.length) { console.info(`Found ${migrations.length} migrations from layout version ${layoutVersion} to version ${application_shell_1.applicationShellLayoutVersion}, migrating...`); } const context = { layout, layoutVersion, migrations }; await this.fireWillInflateLayout(context); await parseContext.inflate(context); return layout; } async fireWillInflateLayout(context) { for (const migration of context.migrations) { if (migration.onWillInflateLayout) { // don't catch exceptions, if one migration fails all should fail. await migration.onWillInflateLayout(context); } } } parse(layoutData, parseContext) { return JSON.parse(layoutData, (property, value) => { if (this.isWidgetsProperty(property)) { const widgets = parseContext.filteredArray(); const descs = value; for (let i = 0; i < descs.length; i++) { parseContext.push(async (context) => { widgets[i] = await this.convertToWidget(descs[i], context); }); } return widgets; } else if ((0, common_1.isObject)(value) && !Array.isArray(value)) { const copy = {}; for (const p in value) { if (this.isWidgetProperty(p)) { parseContext.push(async (context) => { copy[p] = await this.convertToWidget(value[p], context); }); } else { copy[p] = value[p]; } } return copy; } return value; }); } async fireWillInflateWidget(desc, context) { for (const migration of context.migrations) { if (migration.onWillInflateWidget) { // don't catch exceptions, if one migration fails all should fail. const migrated = await migration.onWillInflateWidget(desc, context); if (migrated) { if ((0, common_1.isObject)(migrated.innerWidgetState)) { // in order to inflate nested widgets migrated.innerWidgetState = JSON.stringify(migrated.innerWidgetState); } desc = migrated; } } } return desc; } async convertToWidget(desc, context) { if (!desc.constructionOptions) { return undefined; } try { desc = await this.fireWillInflateWidget(desc, context); const widget = await this.widgetManager.getOrCreateWidget(desc.constructionOptions.factoryId, desc.constructionOptions.options); if (StatefulWidget.is(widget) && desc.innerWidgetState !== undefined) { try { let oldState; if (typeof desc.innerWidgetState === 'string') { const parseContext = new ShellLayoutRestorer_1.ParseContext(); oldState = this.parse(desc.innerWidgetState, parseContext); await parseContext.inflate(Object.assign(Object.assign({}, context), { parent: widget })); } else { oldState = desc.innerWidgetState; } widget.restoreState(oldState); } catch (e) { if (ApplicationShellLayoutMigrationError.is(e)) { throw e; } this.logger.warn(`Couldn't restore widget state for ${widget.id}. Error: ${e} `); } } if (widget.isDisposed) { return undefined; } return widget; } catch (e) { if (ApplicationShellLayoutMigrationError.is(e)) { throw e; } this.logger.warn(`Couldn't restore widget for ${desc.constructionOptions.factoryId}. Error: ${e} `); return undefined; } } }; __decorate([ (0, inversify_1.inject)(contribution_provider_1.ContributionProvider), (0, inversify_1.named)(exports.ApplicationShellLayoutMigration), __metadata("design:type", Object) ], ShellLayoutRestorer.prototype, "migrations", void 0); __decorate([ (0, inversify_1.inject)(window_service_1.WindowService), __metadata("design:type", Object) ], ShellLayoutRestorer.prototype, "windowService", void 0); __decorate([ (0, inversify_1.inject)(theming_1.ThemeService), __metadata("design:type", theming_1.ThemeService) ], ShellLayoutRestorer.prototype, "themeService", void 0); ShellLayoutRestorer = ShellLayoutRestorer_1 = __decorate([ (0, inversify_1.injectable)(), __param(0, (0, inversify_1.inject)(widget_manager_1.WidgetManager)), __param(1, (0, inversify_1.inject)(logger_1.ILogger)), __param(2, (0, inversify_1.inject)(storage_service_1.StorageService)), __metadata("design:paramtypes", [widget_manager_1.WidgetManager, Object, Object]) ], ShellLayoutRestorer); exports.ShellLayoutRestorer = ShellLayoutRestorer; (function (ShellLayoutRestorer) { class ParseContext { constructor() { this.toInflate = []; this.toFilter = []; } /** * Returns an array, which will be filtered from undefined elements * after resolving promises, that create widgets. */ filteredArray() { const array = []; this.toFilter.push(array); return array; } push(toInflate) { this.toInflate.push(toInflate); } async inflate(context) { const pending = []; while (this.toInflate.length) { pending.push(this.toInflate.pop()(context)); } await Promise.all(pending); if (this.toFilter.length) { this.toFilter.forEach(array => { for (let i = 0; i < array.length; i++) { if (array[i] === undefined) { array.splice(i--, 1); } } }); } } } ShellLayoutRestorer.ParseContext = ParseContext; })(ShellLayoutRestorer = exports.ShellLayoutRestorer || (exports.ShellLayoutRestorer = {})); exports.ShellLayoutRestorer = ShellLayoutRestorer; //# sourceMappingURL=shell-layout-restorer.js.map