[go: nahoru, domu]

blob: 96b1698dc5915a42839648fb85d1b37e015ae6e8 [file] [log] [blame]
Tim van der Lippe0830b3d2019-10-03 13:20:071// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Paul Lewis17e384e2020-01-08 15:46:515import * as Common from '../common/common.js';
Paul Lewis9950e182019-12-16 16:06:076import {ContextMenu} from './ContextMenu.js'; // eslint-disable-line no-unused-vars
7import {Icon} from './Icon.js';
8import {Events as TabbedPaneEvents, TabbedPane} from './TabbedPane.js';
9import {Toolbar, ToolbarItem, ToolbarMenuButton} from './Toolbar.js'; // eslint-disable-line no-unused-vars
10import {ProvidedView, TabbedViewLocation, View, ViewLocation, ViewLocationResolver, widgetSymbol,} from './View.js'; // eslint-disable-line no-unused-vars
11import {VBox, Widget} from './Widget.js'; // eslint-disable-line no-unused-vars
12
Tim van der Lippe0830b3d2019-10-03 13:20:0713/**
14 * @unrestricted
15 */
Paul Lewis9950e182019-12-16 16:06:0716export class ViewManager {
Tim van der Lippe0830b3d2019-10-03 13:20:0717 constructor() {
Paul Lewis9950e182019-12-16 16:06:0718 /** @type {!Map<string, !View>} */
Tim van der Lippe0830b3d2019-10-03 13:20:0719 this._views = new Map();
20 /** @type {!Map<string, string>} */
21 this._locationNameByViewId = new Map();
22
23 for (const extension of self.runtime.extensions('view')) {
24 const descriptor = extension.descriptor();
Paul Lewis9950e182019-12-16 16:06:0725 this._views.set(descriptor['id'], new ProvidedView(extension));
Tim van der Lippe0830b3d2019-10-03 13:20:0726 this._locationNameByViewId.set(descriptor['id'], descriptor['location']);
27 }
28 }
29
30 /**
Paul Lewis9950e182019-12-16 16:06:0731 * @param {!Array<!ToolbarItem>} toolbarItems
Jack Lyncha29e3002019-11-04 19:59:2032 * @return {?Element}
Tim van der Lippe0830b3d2019-10-03 13:20:0733 */
Jack Lyncha29e3002019-11-04 19:59:2034 static _createToolbar(toolbarItems) {
Tim van der Lippe0830b3d2019-10-03 13:20:0735 if (!toolbarItems.length) {
Jack Lyncha29e3002019-11-04 19:59:2036 return null;
Tim van der Lippe0830b3d2019-10-03 13:20:0737 }
Paul Lewis9950e182019-12-16 16:06:0738 const toolbar = new Toolbar('');
Tim van der Lippe0830b3d2019-10-03 13:20:0739 for (const item of toolbarItems) {
40 toolbar.appendToolbarItem(item);
41 }
Jack Lyncha29e3002019-11-04 19:59:2042 return toolbar.element;
Tim van der Lippe0830b3d2019-10-03 13:20:0743 }
44
45 /**
Paul Lewis9950e182019-12-16 16:06:0746 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:0747 * @return {!Promise}
48 */
49 revealView(view) {
50 const location = /** @type {?_Location} */ (view[_Location.symbol]);
51 if (!location) {
52 return Promise.resolve();
53 }
54 location._reveal();
55 return location.showView(view);
56 }
57
58 /**
59 * @param {string} viewId
Paul Lewis9950e182019-12-16 16:06:0760 * @return {?View}
Tim van der Lippe0830b3d2019-10-03 13:20:0761 */
62 view(viewId) {
63 return this._views.get(viewId);
64 }
65
66 /**
67 * @param {string} viewId
Paul Lewis9950e182019-12-16 16:06:0768 * @return {?Widget}
Tim van der Lippe0830b3d2019-10-03 13:20:0769 */
70 materializedWidget(viewId) {
71 const view = this.view(viewId);
Paul Lewis9950e182019-12-16 16:06:0772 return view ? view[widgetSymbol] : null;
Tim van der Lippe0830b3d2019-10-03 13:20:0773 }
74
75 /**
76 * @param {string} viewId
77 * @param {boolean=} userGesture
78 * @param {boolean=} omitFocus
79 * @return {!Promise}
80 */
81 showView(viewId, userGesture, omitFocus) {
82 const view = this._views.get(viewId);
83 if (!view) {
84 console.error('Could not find view for id: \'' + viewId + '\' ' + new Error().stack);
85 return Promise.resolve();
86 }
87
88 const locationName = this._locationNameByViewId.get(viewId);
89
90 const location = view[_Location.symbol];
91 if (location) {
92 location._reveal();
93 return location.showView(view, undefined, userGesture, omitFocus);
94 }
95
96 return this.resolveLocation(locationName).then(location => {
97 if (!location) {
98 throw new Error('Could not resolve location for view: ' + viewId);
99 }
100 location._reveal();
101 return location.showView(view, undefined, userGesture, omitFocus);
102 });
103 }
104
105 /**
106 * @param {string=} location
107 * @return {!Promise<?_Location>}
108 */
109 resolveLocation(location) {
110 if (!location) {
111 return /** @type {!Promise<?_Location>} */ (Promise.resolve(null));
112 }
113
Paul Lewis9950e182019-12-16 16:06:07114 const resolverExtensions =
115 self.runtime.extensions(ViewLocationResolver).filter(extension => extension.descriptor()['name'] === location);
Tim van der Lippe0830b3d2019-10-03 13:20:07116 if (!resolverExtensions.length) {
117 throw new Error('Unresolved location: ' + location);
118 }
119 const resolverExtension = resolverExtensions[0];
120 return resolverExtension.instance().then(
121 resolver => /** @type {?_Location} */ (resolver.resolveLocation(location)));
122 }
123
124 /**
125 * @param {function()=} revealCallback
126 * @param {string=} location
127 * @param {boolean=} restoreSelection
128 * @param {boolean=} allowReorder
129 * @param {?string=} defaultTab
Paul Lewis9950e182019-12-16 16:06:07130 * @return {!TabbedViewLocation}
Tim van der Lippe0830b3d2019-10-03 13:20:07131 */
132 createTabbedLocation(revealCallback, location, restoreSelection, allowReorder, defaultTab) {
Paul Lewis9950e182019-12-16 16:06:07133 return new _TabbedLocation(this, revealCallback, location, restoreSelection, allowReorder, defaultTab);
Tim van der Lippe0830b3d2019-10-03 13:20:07134 }
135
136 /**
137 * @param {function()=} revealCallback
138 * @param {string=} location
Paul Lewis9950e182019-12-16 16:06:07139 * @return {!ViewLocation}
Tim van der Lippe0830b3d2019-10-03 13:20:07140 */
141 createStackLocation(revealCallback, location) {
142 return new _StackLocation(this, revealCallback, location);
143 }
144
145 /**
146 * @param {string} location
147 * @return {boolean}
148 */
149 hasViewsForLocation(location) {
150 return !!this._viewsForLocation(location).length;
151 }
152
153 /**
154 * @param {string} location
Paul Lewis9950e182019-12-16 16:06:07155 * @return {!Array<!View>}
Tim van der Lippe0830b3d2019-10-03 13:20:07156 */
157 _viewsForLocation(location) {
158 const result = [];
159 for (const id of this._views.keys()) {
160 if (this._locationNameByViewId.get(id) === location) {
161 result.push(this._views.get(id));
162 }
163 }
164 return result;
165 }
166}
167
168
169/**
170 * @unrestricted
171 */
Paul Lewis9950e182019-12-16 16:06:07172export class ContainerWidget extends VBox {
Tim van der Lippe0830b3d2019-10-03 13:20:07173 /**
Paul Lewis9950e182019-12-16 16:06:07174 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:07175 */
176 constructor(view) {
177 super();
178 this.element.classList.add('flex-auto', 'view-container', 'overflow-auto');
179 this._view = view;
180 this.element.tabIndex = -1;
Junyi Xiao57f03352019-10-21 22:45:51181 UI.ARIAUtils.markAsTabpanel(this.element);
182 UI.ARIAUtils.setAccessibleName(this.element, ls`${view.title()} panel`);
Tim van der Lippe0830b3d2019-10-03 13:20:07183 this.setDefaultFocusedElement(this.element);
184 }
185
186 /**
187 * @return {!Promise}
188 */
189 _materialize() {
190 if (this._materializePromise) {
191 return this._materializePromise;
192 }
193 const promises = [];
194 // TODO(crbug.com/1006759): Transform to async-await
Jack Lyncha29e3002019-11-04 19:59:20195 promises.push(this._view.toolbarItems().then(toolbarItems => {
Paul Lewis9950e182019-12-16 16:06:07196 const toolbarElement = ViewManager._createToolbar(toolbarItems);
Jack Lyncha29e3002019-11-04 19:59:20197 if (toolbarElement) {
198 this.element.insertBefore(toolbarElement, this.element.firstChild);
199 }
200 }));
Tim van der Lippe0830b3d2019-10-03 13:20:07201 promises.push(this._view.widget().then(widget => {
202 // Move focus from |this| to loaded |widget| if any.
203 const shouldFocus = this.element.hasFocus();
204 this.setDefaultFocusedElement(null);
Paul Lewis9950e182019-12-16 16:06:07205 this._view[widgetSymbol] = widget;
Tim van der Lippe0830b3d2019-10-03 13:20:07206 widget.show(this.element);
207 if (shouldFocus) {
208 widget.focus();
209 }
210 }));
211 this._materializePromise = Promise.all(promises);
212 return this._materializePromise;
213 }
214
215 /**
216 * @override
217 */
218 wasShown() {
219 this._materialize().then(() => {
220 this._wasShownForTest();
221 });
222 }
223
224 _wasShownForTest() {
225 // This method is sniffed in tests.
226 }
227}
228
229/**
230 * @unrestricted
231 */
Paul Lewis9950e182019-12-16 16:06:07232export class _ExpandableContainerWidget extends VBox {
Tim van der Lippe0830b3d2019-10-03 13:20:07233 /**
Paul Lewis9950e182019-12-16 16:06:07234 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:07235 */
236 constructor(view) {
237 super(true);
238 this.element.classList.add('flex-none');
239 this.registerRequiredCSS('ui/viewContainers.css');
240
241 this._titleElement = createElementWithClass('div', 'expandable-view-title');
Jack Lynchb56d2dc2019-11-04 18:20:44242 UI.ARIAUtils.markAsButton(this._titleElement);
Paul Lewis9950e182019-12-16 16:06:07243 this._titleExpandIcon = Icon.create('smallicon-triangle-right', 'title-expand-icon');
Tim van der Lippe0830b3d2019-10-03 13:20:07244 this._titleElement.appendChild(this._titleExpandIcon);
Jack Lynch202351c2019-10-17 18:31:15245 const titleText = view.title();
246 this._titleElement.createTextChild(titleText);
247 UI.ARIAUtils.setAccessibleName(this._titleElement, titleText);
Michael Liao6988c2a2019-12-10 22:36:32248 UI.ARIAUtils.setExpanded(this._titleElement, false);
Tim van der Lippe0830b3d2019-10-03 13:20:07249 this._titleElement.tabIndex = 0;
Jack Lynchf53ecf52019-11-04 19:57:08250 self.onInvokeElement(this._titleElement, this._toggleExpanded.bind(this));
Tim van der Lippe0830b3d2019-10-03 13:20:07251 this._titleElement.addEventListener('keydown', this._onTitleKeyDown.bind(this), false);
252 this.contentElement.insertBefore(this._titleElement, this.contentElement.firstChild);
253
Jack Lynchb56d2dc2019-11-04 18:20:44254 UI.ARIAUtils.setControls(this._titleElement, this.contentElement.createChild('slot'));
Tim van der Lippe0830b3d2019-10-03 13:20:07255 this._view = view;
Paul Lewis9950e182019-12-16 16:06:07256 view[_ExpandableContainerWidget._symbol] = this;
Tim van der Lippe0830b3d2019-10-03 13:20:07257 }
258
259 /**
260 * @return {!Promise}
261 */
262 _materialize() {
263 if (this._materializePromise) {
264 return this._materializePromise;
265 }
266 // TODO(crbug.com/1006759): Transform to async-await
267 const promises = [];
Jack Lyncha29e3002019-11-04 19:59:20268 promises.push(this._view.toolbarItems().then(toolbarItems => {
Paul Lewis9950e182019-12-16 16:06:07269 const toolbarElement = ViewManager._createToolbar(toolbarItems);
Jack Lyncha29e3002019-11-04 19:59:20270 if (toolbarElement) {
271 this._titleElement.appendChild(toolbarElement);
272 }
273 }));
Tim van der Lippe0830b3d2019-10-03 13:20:07274 promises.push(this._view.widget().then(widget => {
275 this._widget = widget;
Paul Lewis9950e182019-12-16 16:06:07276 this._view[widgetSymbol] = widget;
Tim van der Lippe0830b3d2019-10-03 13:20:07277 widget.show(this.element);
278 }));
279 this._materializePromise = Promise.all(promises);
280 return this._materializePromise;
281 }
282
283 /**
284 * @return {!Promise}
285 */
286 _expand() {
287 if (this._titleElement.classList.contains('expanded')) {
288 return this._materialize();
289 }
290 this._titleElement.classList.add('expanded');
291 UI.ARIAUtils.setExpanded(this._titleElement, true);
292 this._titleExpandIcon.setIconType('smallicon-triangle-down');
293 return this._materialize().then(() => this._widget.show(this.element));
294 }
295
296 _collapse() {
297 if (!this._titleElement.classList.contains('expanded')) {
298 return;
299 }
300 this._titleElement.classList.remove('expanded');
301 UI.ARIAUtils.setExpanded(this._titleElement, false);
302 this._titleExpandIcon.setIconType('smallicon-triangle-right');
303 this._materialize().then(() => this._widget.detach());
304 }
305
Jack Lynchf53ecf52019-11-04 19:57:08306 /**
307 * @param {!Event} event
308 */
309 _toggleExpanded(event) {
310 if (event.type === 'keydown' && event.target !== this._titleElement) {
311 return;
312 }
Tim van der Lippe0830b3d2019-10-03 13:20:07313 if (this._titleElement.classList.contains('expanded')) {
314 this._collapse();
315 } else {
316 this._expand();
317 }
318 }
319
320 /**
321 * @param {!Event} event
322 */
323 _onTitleKeyDown(event) {
Jack Lynchf53ecf52019-11-04 19:57:08324 if (event.target !== this._titleElement) {
325 return;
326 }
327 if (event.key === 'ArrowLeft') {
Tim van der Lippe0830b3d2019-10-03 13:20:07328 this._collapse();
329 } else if (event.key === 'ArrowRight') {
330 if (!this._titleElement.classList.contains('expanded')) {
331 this._expand();
332 } else if (this._widget) {
333 this._widget.focus();
334 }
335 }
336 }
337}
338
339_ExpandableContainerWidget._symbol = Symbol('container');
340
341/**
342 * @unrestricted
343 */
Tim van der Lippec96ccd92019-11-29 16:23:54344class _Location {
Tim van der Lippe0830b3d2019-10-03 13:20:07345 /**
Paul Lewis9950e182019-12-16 16:06:07346 * @param {!ViewManager} manager
347 * @param {!Widget} widget
Tim van der Lippe0830b3d2019-10-03 13:20:07348 * @param {function()=} revealCallback
349 */
350 constructor(manager, widget, revealCallback) {
351 this._manager = manager;
352 this._revealCallback = revealCallback;
353 this._widget = widget;
354 }
355
356 /**
Paul Lewis9950e182019-12-16 16:06:07357 * @return {!Widget}
Tim van der Lippe0830b3d2019-10-03 13:20:07358 */
359 widget() {
360 return this._widget;
361 }
362
363 _reveal() {
364 if (this._revealCallback) {
365 this._revealCallback();
366 }
367 }
368}
369
370_Location.symbol = Symbol('location');
371
372/**
Paul Lewis9950e182019-12-16 16:06:07373 * @implements {TabbedViewLocation}
Tim van der Lippe0830b3d2019-10-03 13:20:07374 * @unrestricted
375 */
376export class _TabbedLocation extends _Location {
377 /**
Paul Lewis9950e182019-12-16 16:06:07378 * @param {!ViewManager} manager
Tim van der Lippe0830b3d2019-10-03 13:20:07379 * @param {function()=} revealCallback
380 * @param {string=} location
381 * @param {boolean=} restoreSelection
382 * @param {boolean=} allowReorder
383 * @param {?string=} defaultTab
384 */
385 constructor(manager, revealCallback, location, restoreSelection, allowReorder, defaultTab) {
Paul Lewis9950e182019-12-16 16:06:07386 const tabbedPane = new TabbedPane();
Tim van der Lippe0830b3d2019-10-03 13:20:07387 if (allowReorder) {
388 tabbedPane.setAllowTabReorder(true);
389 }
390
391 super(manager, tabbedPane, revealCallback);
392 this._tabbedPane = tabbedPane;
393 this._allowReorder = allowReorder;
394
Paul Lewis9950e182019-12-16 16:06:07395 this._tabbedPane.addEventListener(TabbedPaneEvents.TabSelected, this._tabSelected, this);
396 this._tabbedPane.addEventListener(TabbedPaneEvents.TabClosed, this._tabClosed, this);
Paul Lewis17e384e2020-01-08 15:46:51397 // Note: go via self.Common for globally-namespaced singletons.
398 this._closeableTabSetting = self.Common.settings.createSetting(location + '-closeableTabs', {});
399 // Note: go via self.Common for globally-namespaced singletons.
400 this._tabOrderSetting = self.Common.settings.createSetting(location + '-tabOrder', {});
Paul Lewis9950e182019-12-16 16:06:07401 this._tabbedPane.addEventListener(TabbedPaneEvents.TabOrderChanged, this._persistTabOrder, this);
Tim van der Lippe0830b3d2019-10-03 13:20:07402 if (restoreSelection) {
Paul Lewis17e384e2020-01-08 15:46:51403 // Note: go via self.Common for globally-namespaced singletons.
404 this._lastSelectedTabSetting = self.Common.settings.createSetting(location + '-selectedTab', '');
Tim van der Lippe0830b3d2019-10-03 13:20:07405 }
406 this._defaultTab = defaultTab;
407
Paul Lewis9950e182019-12-16 16:06:07408 /** @type {!Map.<string, !View>} */
Tim van der Lippe0830b3d2019-10-03 13:20:07409 this._views = new Map();
410
411 if (location) {
412 this.appendApplicableItems(location);
413 }
414 }
415
416 /**
417 * @override
Paul Lewis9950e182019-12-16 16:06:07418 * @return {!Widget}
Tim van der Lippe0830b3d2019-10-03 13:20:07419 */
420 widget() {
421 return this._tabbedPane;
422 }
423
424 /**
425 * @override
Paul Lewis9950e182019-12-16 16:06:07426 * @return {!TabbedPane}
Tim van der Lippe0830b3d2019-10-03 13:20:07427 */
428 tabbedPane() {
429 return this._tabbedPane;
430 }
431
432 /**
433 * @override
Paul Lewis9950e182019-12-16 16:06:07434 * @return {!ToolbarMenuButton}
Tim van der Lippe0830b3d2019-10-03 13:20:07435 */
436 enableMoreTabsButton() {
Paul Lewis9950e182019-12-16 16:06:07437 const moreTabsButton = new ToolbarMenuButton(this._appendTabsToMenu.bind(this));
Tim van der Lippe0830b3d2019-10-03 13:20:07438 this._tabbedPane.leftToolbar().appendToolbarItem(moreTabsButton);
439 this._tabbedPane.disableOverflowMenu();
440 return moreTabsButton;
441 }
442
443 /**
444 * @override
445 * @param {string} locationName
446 */
447 appendApplicableItems(locationName) {
448 const views = this._manager._viewsForLocation(locationName);
449 if (this._allowReorder) {
450 let i = 0;
451 const persistedOrders = this._tabOrderSetting.get();
452 const orders = new Map();
453 for (const view of views) {
Paul Lewis9950e182019-12-16 16:06:07454 orders.set(view.viewId(), persistedOrders[view.viewId()] || (++i) * _TabbedLocation.orderStep);
Tim van der Lippe0830b3d2019-10-03 13:20:07455 }
456 views.sort((a, b) => orders.get(a.viewId()) - orders.get(b.viewId()));
457 }
458
459 for (const view of views) {
460 const id = view.viewId();
461 this._views.set(id, view);
462 view[_Location.symbol] = this;
463 if (view.isTransient()) {
464 continue;
465 }
466 if (!view.isCloseable()) {
467 this._appendTab(view);
468 } else if (this._closeableTabSetting.get()[id]) {
469 this._appendTab(view);
470 }
471 }
472 if (this._defaultTab && this._tabbedPane.hasTab(this._defaultTab)) {
473 this._tabbedPane.selectTab(this._defaultTab);
474 } else if (this._lastSelectedTabSetting && this._tabbedPane.hasTab(this._lastSelectedTabSetting.get())) {
475 this._tabbedPane.selectTab(this._lastSelectedTabSetting.get());
476 }
477 }
478
479 /**
Paul Lewis9950e182019-12-16 16:06:07480 * @param {!ContextMenu} contextMenu
Tim van der Lippe0830b3d2019-10-03 13:20:07481 */
482 _appendTabsToMenu(contextMenu) {
483 const views = Array.from(this._views.values());
484 views.sort((viewa, viewb) => viewa.title().localeCompare(viewb.title()));
485 for (const view of views) {
Paul Lewis17e384e2020-01-08 15:46:51486 const title = Common.UIString.UIString(view.title());
Tim van der Lippe0830b3d2019-10-03 13:20:07487 contextMenu.defaultSection().appendItem(title, this.showView.bind(this, view, undefined, true));
488 }
489 }
490
491 /**
Paul Lewis9950e182019-12-16 16:06:07492 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:07493 * @param {number=} index
494 */
495 _appendTab(view, index) {
496 this._tabbedPane.appendTab(
Paul Lewis9950e182019-12-16 16:06:07497 view.viewId(), view.title(), new ContainerWidget(view), undefined, false,
Tim van der Lippe0830b3d2019-10-03 13:20:07498 view.isCloseable() || view.isTransient(), index);
499 }
500
501 /**
502 * @override
Paul Lewis9950e182019-12-16 16:06:07503 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:07504 * @param {?UI.View=} insertBefore
505 */
506 appendView(view, insertBefore) {
507 if (this._tabbedPane.hasTab(view.viewId())) {
508 return;
509 }
510 const oldLocation = view[_Location.symbol];
511 if (oldLocation && oldLocation !== this) {
512 oldLocation.removeView(view);
513 }
514 view[_Location.symbol] = this;
515 this._manager._views.set(view.viewId(), view);
516 this._views.set(view.viewId(), view);
517 let index = undefined;
518 const tabIds = this._tabbedPane.tabIds();
519 if (this._allowReorder) {
520 const orderSetting = this._tabOrderSetting.get();
521 const order = orderSetting[view.viewId()];
522 for (let i = 0; order && i < tabIds.length; ++i) {
523 if (orderSetting[tabIds[i]] && orderSetting[tabIds[i]] > order) {
524 index = i;
525 break;
526 }
527 }
528 } else if (insertBefore) {
529 for (let i = 0; i < tabIds.length; ++i) {
530 if (tabIds[i] === insertBefore.viewId()) {
531 index = i;
532 break;
533 }
534 }
535 }
536 this._appendTab(view, index);
537
538 if (view.isCloseable()) {
539 const tabs = this._closeableTabSetting.get();
540 const tabId = view.viewId();
541 if (!tabs[tabId]) {
542 tabs[tabId] = true;
543 this._closeableTabSetting.set(tabs);
544 }
545 }
546 this._persistTabOrder();
547 }
548
549 /**
550 * @override
Paul Lewis9950e182019-12-16 16:06:07551 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:07552 * @param {?UI.View=} insertBefore
553 * @param {boolean=} userGesture
554 * @param {boolean=} omitFocus
555 * @return {!Promise}
556 */
557 showView(view, insertBefore, userGesture, omitFocus) {
558 this.appendView(view, insertBefore);
559 this._tabbedPane.selectTab(view.viewId(), userGesture);
560 if (!omitFocus) {
561 this._tabbedPane.focus();
562 }
Paul Lewis9950e182019-12-16 16:06:07563 const widget = /** @type {!ContainerWidget} */ (this._tabbedPane.tabView(view.viewId()));
Tim van der Lippe0830b3d2019-10-03 13:20:07564 return widget._materialize();
565 }
566
567 /**
Paul Lewis9950e182019-12-16 16:06:07568 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:07569 * @override
570 */
571 removeView(view) {
572 if (!this._tabbedPane.hasTab(view.viewId())) {
573 return;
574 }
575
576 delete view[_Location.symbol];
577 this._manager._views.delete(view.viewId());
578 this._tabbedPane.closeTab(view.viewId());
579 this._views.delete(view.viewId());
580 }
581
582 /**
583 * @param {!Common.Event} event
584 */
585 _tabSelected(event) {
586 const tabId = /** @type {string} */ (event.data.tabId);
587 if (this._lastSelectedTabSetting && event.data['isUserGesture']) {
588 this._lastSelectedTabSetting.set(tabId);
589 }
590 }
591
592 /**
593 * @param {!Common.Event} event
594 */
595 _tabClosed(event) {
596 const id = /** @type {string} */ (event.data['tabId']);
597 const tabs = this._closeableTabSetting.get();
598 if (tabs[id]) {
599 delete tabs[id];
600 this._closeableTabSetting.set(tabs);
601 }
602 this._views.get(id).disposeView();
603 }
604
605 _persistTabOrder() {
606 const tabIds = this._tabbedPane.tabIds();
607 const tabOrders = {};
608 for (let i = 0; i < tabIds.length; i++) {
Paul Lewis9950e182019-12-16 16:06:07609 tabOrders[tabIds[i]] = (i + 1) * _TabbedLocation.orderStep;
Tim van der Lippe0830b3d2019-10-03 13:20:07610 }
611
612 const oldTabOrder = this._tabOrderSetting.get();
613 const oldTabArray = Object.keys(oldTabOrder);
614 oldTabArray.sort((a, b) => oldTabOrder[a] - oldTabOrder[b]);
615 let lastOrder = 0;
616 for (const key of oldTabArray) {
617 if (key in tabOrders) {
618 lastOrder = tabOrders[key];
619 continue;
620 }
621 tabOrders[key] = ++lastOrder;
622 }
623 this._tabOrderSetting.set(tabOrders);
624 }
625}
626
627_TabbedLocation.orderStep = 10; // Keep in sync with descriptors.
628
629/**
Paul Lewis9950e182019-12-16 16:06:07630 * @implements {ViewLocation}
Tim van der Lippe0830b3d2019-10-03 13:20:07631 * @unrestricted
632 */
Tim van der Lippec96ccd92019-11-29 16:23:54633class _StackLocation extends _Location {
Tim van der Lippe0830b3d2019-10-03 13:20:07634 /**
Paul Lewis9950e182019-12-16 16:06:07635 * @param {!ViewManager} manager
Tim van der Lippe0830b3d2019-10-03 13:20:07636 * @param {function()=} revealCallback
637 * @param {string=} location
638 */
639 constructor(manager, revealCallback, location) {
Paul Lewis9950e182019-12-16 16:06:07640 const vbox = new VBox();
Tim van der Lippe0830b3d2019-10-03 13:20:07641 super(manager, vbox, revealCallback);
642 this._vbox = vbox;
643
Paul Lewis9950e182019-12-16 16:06:07644 /** @type {!Map<string, !_ExpandableContainerWidget>} */
Tim van der Lippe0830b3d2019-10-03 13:20:07645 this._expandableContainers = new Map();
646
647 if (location) {
648 this.appendApplicableItems(location);
649 }
650 }
651
652 /**
653 * @override
Paul Lewis9950e182019-12-16 16:06:07654 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:07655 * @param {?UI.View=} insertBefore
656 */
657 appendView(view, insertBefore) {
658 const oldLocation = view[_Location.symbol];
659 if (oldLocation && oldLocation !== this) {
660 oldLocation.removeView(view);
661 }
662
663 let container = this._expandableContainers.get(view.viewId());
664 if (!container) {
665 view[_Location.symbol] = this;
666 this._manager._views.set(view.viewId(), view);
Paul Lewis9950e182019-12-16 16:06:07667 container = new _ExpandableContainerWidget(view);
Tim van der Lippe0830b3d2019-10-03 13:20:07668 let beforeElement = null;
669 if (insertBefore) {
Paul Lewis9950e182019-12-16 16:06:07670 const beforeContainer = insertBefore[_ExpandableContainerWidget._symbol];
Tim van der Lippe0830b3d2019-10-03 13:20:07671 beforeElement = beforeContainer ? beforeContainer.element : null;
672 }
673 container.show(this._vbox.contentElement, beforeElement);
674 this._expandableContainers.set(view.viewId(), container);
675 }
676 }
677
678 /**
679 * @override
Paul Lewis9950e182019-12-16 16:06:07680 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:07681 * @param {?UI.View=} insertBefore
682 * @return {!Promise}
683 */
684 showView(view, insertBefore) {
685 this.appendView(view, insertBefore);
686 const container = this._expandableContainers.get(view.viewId());
687 return container._expand();
688 }
689
690 /**
Paul Lewis9950e182019-12-16 16:06:07691 * @param {!View} view
Tim van der Lippe0830b3d2019-10-03 13:20:07692 * @override
693 */
694 removeView(view) {
695 const container = this._expandableContainers.get(view.viewId());
696 if (!container) {
697 return;
698 }
699
700 container.detach();
701 this._expandableContainers.delete(view.viewId());
702 delete view[_Location.symbol];
703 this._manager._views.delete(view.viewId());
704 }
705
706 /**
707 * @override
708 * @param {string} locationName
709 */
710 appendApplicableItems(locationName) {
711 for (const view of this._manager._viewsForLocation(locationName)) {
712 this.appendView(view);
713 }
714 }
715}