UNPKG

15.2 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// *****************************************************************************
17var ShellLayoutRestorer_1;
18Object.defineProperty(exports, "__esModule", { value: true });
19exports.ShellLayoutRestorer = exports.RESET_LAYOUT = exports.ApplicationShellLayoutMigration = exports.ApplicationShellLayoutMigrationError = exports.StatefulWidget = void 0;
20const tslib_1 = require("tslib");
21const inversify_1 = require("inversify");
22const widget_manager_1 = require("../widget-manager");
23const storage_service_1 = require("../storage-service");
24const logger_1 = require("../../common/logger");
25const command_1 = require("../../common/command");
26const theming_1 = require("../theming");
27const contribution_provider_1 = require("../../common/contribution-provider");
28const application_shell_1 = require("./application-shell");
29const common_frontend_contribution_1 = require("../common-frontend-contribution");
30const window_service_1 = require("../window/window-service");
31const frontend_application_state_1 = require("../../common/frontend-application-state");
32const common_1 = require("../../common");
33var StatefulWidget;
34(function (StatefulWidget) {
35 function is(arg) {
36 return (0, common_1.isObject)(arg) && (0, common_1.isFunction)(arg.storeState) && (0, common_1.isFunction)(arg.restoreState);
37 }
38 StatefulWidget.is = is;
39})(StatefulWidget = exports.StatefulWidget || (exports.StatefulWidget = {}));
40var ApplicationShellLayoutMigrationError;
41(function (ApplicationShellLayoutMigrationError) {
42 const code = 'ApplicationShellLayoutMigrationError';
43 function create(message) {
44 return Object.assign(new Error(`Could not migrate layout to version ${application_shell_1.applicationShellLayoutVersion}.` + (message ? '\n' + message : '')), { code });
45 }
46 ApplicationShellLayoutMigrationError.create = create;
47 function is(error) {
48 return !!error && 'code' in error && error['code'] === code;
49 }
50 ApplicationShellLayoutMigrationError.is = is;
51})(ApplicationShellLayoutMigrationError = exports.ApplicationShellLayoutMigrationError || (exports.ApplicationShellLayoutMigrationError = {}));
52exports.ApplicationShellLayoutMigration = Symbol('ApplicationShellLayoutMigration');
53exports.RESET_LAYOUT = command_1.Command.toLocalizedCommand({
54 id: 'reset.layout',
55 category: common_frontend_contribution_1.CommonCommands.VIEW_CATEGORY,
56 label: 'Reset Workbench Layout'
57}, 'theia/core/resetWorkbenchLayout', common_frontend_contribution_1.CommonCommands.VIEW_CATEGORY_KEY);
58let ShellLayoutRestorer = ShellLayoutRestorer_1 = class ShellLayoutRestorer {
59 constructor(widgetManager, logger, storageService) {
60 this.widgetManager = widgetManager;
61 this.logger = logger;
62 this.storageService = storageService;
63 this.storageKey = 'layout';
64 this.shouldStoreLayout = true;
65 }
66 registerCommands(commands) {
67 commands.registerCommand(exports.RESET_LAYOUT, {
68 execute: async () => this.resetLayout()
69 });
70 }
71 async resetLayout() {
72 if (await this.windowService.isSafeToShutDown(frontend_application_state_1.StopReason.Reload)) {
73 this.logger.info('>>> Resetting layout...');
74 this.shouldStoreLayout = false;
75 this.storageService.setData(this.storageKey, undefined);
76 this.themeService.reset();
77 this.logger.info('<<< The layout has been successfully reset.');
78 this.windowService.reload();
79 }
80 }
81 storeLayout(app) {
82 if (this.shouldStoreLayout) {
83 try {
84 this.logger.info('>>> Storing the layout...');
85 const layoutData = app.shell.getLayoutData();
86 const serializedLayoutData = this.deflate(layoutData);
87 this.storageService.setData(this.storageKey, serializedLayoutData);
88 this.logger.info('<<< The layout has been successfully stored.');
89 }
90 catch (error) {
91 this.storageService.setData(this.storageKey, undefined);
92 this.logger.error('Error during serialization of layout data', error);
93 }
94 }
95 }
96 async restoreLayout(app) {
97 this.logger.info('>>> Restoring the layout state...');
98 const serializedLayoutData = await this.storageService.getData(this.storageKey);
99 if (serializedLayoutData === undefined) {
100 this.logger.info('<<< Nothing to restore.');
101 return false;
102 }
103 const layoutData = await this.inflate(serializedLayoutData);
104 await app.shell.setLayoutData(layoutData);
105 this.logger.info('<<< The layout has been successfully restored.');
106 return true;
107 }
108 isWidgetProperty(propertyName) {
109 return propertyName === 'widget';
110 }
111 isWidgetsProperty(propertyName) {
112 return propertyName === 'widgets';
113 }
114 /**
115 * Turns the layout data to a string representation.
116 */
117 deflate(data) {
118 return JSON.stringify(data, (property, value) => {
119 if (this.isWidgetProperty(property)) {
120 const description = this.convertToDescription(value);
121 return description;
122 }
123 else if (this.isWidgetsProperty(property)) {
124 const descriptions = [];
125 for (const widget of value) {
126 const description = this.convertToDescription(widget);
127 if (description) {
128 descriptions.push(description);
129 }
130 }
131 return descriptions;
132 }
133 return value;
134 });
135 }
136 convertToDescription(widget) {
137 const desc = this.widgetManager.getDescription(widget);
138 if (desc) {
139 if (StatefulWidget.is(widget)) {
140 const innerState = widget.storeState();
141 return innerState ? {
142 constructionOptions: desc,
143 innerWidgetState: this.deflate(innerState)
144 } : undefined;
145 }
146 else {
147 return {
148 constructionOptions: desc,
149 innerWidgetState: undefined
150 };
151 }
152 }
153 }
154 /**
155 * Creates the layout data from its string representation.
156 */
157 async inflate(layoutData) {
158 const parseContext = new ShellLayoutRestorer_1.ParseContext();
159 const layout = this.parse(layoutData, parseContext);
160 const layoutVersion = Number(layout.version);
161 if (typeof layoutVersion !== 'number' || Number.isNaN(layoutVersion)) {
162 throw new Error('could not resolve a layout version');
163 }
164 if (layoutVersion !== application_shell_1.applicationShellLayoutVersion) {
165 if (layoutVersion < application_shell_1.applicationShellLayoutVersion) {
166 console.warn(`Layout version ${layoutVersion} is behind current layout version ${application_shell_1.applicationShellLayoutVersion}, trying to migrate...`);
167 }
168 else {
169 console.warn(`Layout version ${layoutVersion} is ahead current layout version ${application_shell_1.applicationShellLayoutVersion}, trying to load anyway...`);
170 }
171 console.info(`Please use '${exports.RESET_LAYOUT.label}' command if the layout looks bogus.`);
172 }
173 const migrations = this.migrations.getContributions()
174 .filter(m => m.layoutVersion > layoutVersion && m.layoutVersion <= application_shell_1.applicationShellLayoutVersion)
175 .sort((m, m2) => m.layoutVersion - m2.layoutVersion);
176 if (migrations.length) {
177 console.info(`Found ${migrations.length} migrations from layout version ${layoutVersion} to version ${application_shell_1.applicationShellLayoutVersion}, migrating...`);
178 }
179 const context = { layout, layoutVersion, migrations };
180 await this.fireWillInflateLayout(context);
181 await parseContext.inflate(context);
182 return layout;
183 }
184 async fireWillInflateLayout(context) {
185 for (const migration of context.migrations) {
186 if (migration.onWillInflateLayout) {
187 // don't catch exceptions, if one migration fails all should fail.
188 await migration.onWillInflateLayout(context);
189 }
190 }
191 }
192 parse(layoutData, parseContext) {
193 return JSON.parse(layoutData, (property, value) => {
194 if (this.isWidgetsProperty(property)) {
195 const widgets = parseContext.filteredArray();
196 const descs = value;
197 for (let i = 0; i < descs.length; i++) {
198 parseContext.push(async (context) => {
199 widgets[i] = await this.convertToWidget(descs[i], context);
200 });
201 }
202 return widgets;
203 }
204 else if ((0, common_1.isObject)(value) && !Array.isArray(value)) {
205 const copy = {};
206 for (const p in value) {
207 if (this.isWidgetProperty(p)) {
208 parseContext.push(async (context) => {
209 copy[p] = await this.convertToWidget(value[p], context);
210 });
211 }
212 else {
213 copy[p] = value[p];
214 }
215 }
216 return copy;
217 }
218 return value;
219 });
220 }
221 async fireWillInflateWidget(desc, context) {
222 for (const migration of context.migrations) {
223 if (migration.onWillInflateWidget) {
224 // don't catch exceptions, if one migration fails all should fail.
225 const migrated = await migration.onWillInflateWidget(desc, context);
226 if (migrated) {
227 if ((0, common_1.isObject)(migrated.innerWidgetState)) {
228 // in order to inflate nested widgets
229 migrated.innerWidgetState = JSON.stringify(migrated.innerWidgetState);
230 }
231 desc = migrated;
232 }
233 }
234 }
235 return desc;
236 }
237 async convertToWidget(desc, context) {
238 if (!desc.constructionOptions) {
239 return undefined;
240 }
241 try {
242 desc = await this.fireWillInflateWidget(desc, context);
243 const widget = await this.widgetManager.getOrCreateWidget(desc.constructionOptions.factoryId, desc.constructionOptions.options);
244 if (StatefulWidget.is(widget) && desc.innerWidgetState !== undefined) {
245 try {
246 let oldState;
247 if (typeof desc.innerWidgetState === 'string') {
248 const parseContext = new ShellLayoutRestorer_1.ParseContext();
249 oldState = this.parse(desc.innerWidgetState, parseContext);
250 await parseContext.inflate({ ...context, parent: widget });
251 }
252 else {
253 oldState = desc.innerWidgetState;
254 }
255 widget.restoreState(oldState);
256 }
257 catch (e) {
258 if (ApplicationShellLayoutMigrationError.is(e)) {
259 throw e;
260 }
261 this.logger.warn(`Couldn't restore widget state for ${widget.id}. Error: ${e} `);
262 }
263 }
264 if (widget.isDisposed) {
265 return undefined;
266 }
267 return widget;
268 }
269 catch (e) {
270 if (ApplicationShellLayoutMigrationError.is(e)) {
271 throw e;
272 }
273 this.logger.warn(`Couldn't restore widget for ${desc.constructionOptions.factoryId}. Error: ${e} `);
274 return undefined;
275 }
276 }
277};
278(0, tslib_1.__decorate)([
279 (0, inversify_1.inject)(contribution_provider_1.ContributionProvider),
280 (0, inversify_1.named)(exports.ApplicationShellLayoutMigration),
281 (0, tslib_1.__metadata)("design:type", Object)
282], ShellLayoutRestorer.prototype, "migrations", void 0);
283(0, tslib_1.__decorate)([
284 (0, inversify_1.inject)(window_service_1.WindowService),
285 (0, tslib_1.__metadata)("design:type", Object)
286], ShellLayoutRestorer.prototype, "windowService", void 0);
287(0, tslib_1.__decorate)([
288 (0, inversify_1.inject)(theming_1.ThemeService),
289 (0, tslib_1.__metadata)("design:type", theming_1.ThemeService)
290], ShellLayoutRestorer.prototype, "themeService", void 0);
291ShellLayoutRestorer = ShellLayoutRestorer_1 = (0, tslib_1.__decorate)([
292 (0, inversify_1.injectable)(),
293 (0, tslib_1.__param)(0, (0, inversify_1.inject)(widget_manager_1.WidgetManager)),
294 (0, tslib_1.__param)(1, (0, inversify_1.inject)(logger_1.ILogger)),
295 (0, tslib_1.__param)(2, (0, inversify_1.inject)(storage_service_1.StorageService)),
296 (0, tslib_1.__metadata)("design:paramtypes", [widget_manager_1.WidgetManager, Object, Object])
297], ShellLayoutRestorer);
298exports.ShellLayoutRestorer = ShellLayoutRestorer;
299(function (ShellLayoutRestorer) {
300 class ParseContext {
301 constructor() {
302 this.toInflate = [];
303 this.toFilter = [];
304 }
305 /**
306 * Returns an array, which will be filtered from undefined elements
307 * after resolving promises, that create widgets.
308 */
309 filteredArray() {
310 const array = [];
311 this.toFilter.push(array);
312 return array;
313 }
314 push(toInflate) {
315 this.toInflate.push(toInflate);
316 }
317 async inflate(context) {
318 const pending = [];
319 while (this.toInflate.length) {
320 pending.push(this.toInflate.pop()(context));
321 }
322 await Promise.all(pending);
323 if (this.toFilter.length) {
324 this.toFilter.forEach(array => {
325 for (let i = 0; i < array.length; i++) {
326 if (array[i] === undefined) {
327 array.splice(i--, 1);
328 }
329 }
330 });
331 }
332 }
333 }
334 ShellLayoutRestorer.ParseContext = ParseContext;
335})(ShellLayoutRestorer = exports.ShellLayoutRestorer || (exports.ShellLayoutRestorer = {}));
336exports.ShellLayoutRestorer = ShellLayoutRestorer;
337//# sourceMappingURL=shell-layout-restorer.js.map
\No newline at end of file