@theia/core
Version:
Theia is a cloud & desktop IDE framework implemented in TypeScript.
348 lines • 16 kB
JavaScript
// *****************************************************************************
// 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
;