UNPKG

chrome-devtools-frontend

Version:
324 lines (285 loc) • 13.3 kB
// Copyright 2022 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. import * as Common from '../../core/common/common.js'; import * as i18n from '../../core/i18n/i18n.js'; import type * as Protocol from '../../generated/protocol.js'; import * as DataGrid from '../../ui/legacy/components/data_grid/data_grid.js'; import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js'; import * as UI from '../../ui/legacy/legacy.js'; import * as LegacyWrapper from '../../ui/components/legacy_wrapper/legacy_wrapper.js'; import * as ApplicationComponents from './components/components.js'; import {SharedStorageForOrigin} from './SharedStorageModel.js'; import {StorageItemsView} from './StorageItemsView.js'; const UIStrings = { /** *@description Text in SharedStorage Items View of the Application panel */ sharedStorage: 'Shared Storage', /** *@description Text representing the name of a value stored in the "Shared Storage Items" table */ key: 'Key', /** *@description Text for the value of something */ value: 'Value', /** *@description Name for the "Shared Storage Items" table that shows the content of the Shared Storage. */ sharedStorageItems: 'Shared Storage Items', /** *@description Text for announcing that the "Shared Storage Items" table was cleared, that is, all * entries were deleted. */ sharedStorageItemsCleared: 'Shared Storage items cleared', /** *@description Text for announcing that the filtered "Shared Storage Items" table was cleared, that is, * all filtered entries were deleted. */ sharedStorageFilteredItemsCleared: 'Shared Storage filtered items cleared', /** *@description Text in SharedStorage Items View of the Application panel */ selectAValueToPreview: 'Select a value to preview', /** *@description Text for announcing a Shared Storage key/value item has been deleted */ sharedStorageItemDeleted: 'The storage item was deleted.', /** *@description Text for announcing a Shared Storage key/value item has been edited */ sharedStorageItemEdited: 'The storage item was edited.', /** *@description Text for announcing a Shared Storage key/value item edit request has been canceled */ sharedStorageItemEditCanceled: 'The storage item edit was canceled.', /** *@description Text for announcing number of entries after filtering *@example {5} PH1 */ sharedStorageNumberEntries: 'Number of entries shown in table: {PH1}', }; const str_ = i18n.i18n.registerUIStrings('panels/application/SharedStorageItemsView.ts', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); interface WrappedEntry { key: string; value: string; } export namespace SharedStorageItemsDispatcher { // TODO(crbug.com/1167717): Make this a const enum. // eslint-disable-next-line rulesdir/const_enum export enum Events { FilteredItemsCleared = 'FilteredItemsCleared', ItemDeleted = 'ItemDeleted', ItemEdited = 'ItemEdited', ItemsCleared = 'ItemsCleared', ItemsRefreshed = 'ItemsRefreshed', } export interface ItemDeletedEvent { key: string; } export interface ItemEditedEvent { columnIdentifier: string; oldText: string; newText: string; } export type EventTypes = { [Events.FilteredItemsCleared]: void, [Events.ItemDeleted]: ItemDeletedEvent, [Events.ItemEdited]: ItemEditedEvent, [Events.ItemsCleared]: void, [Events.ItemsRefreshed]: void, }; } export class SharedStorageItemsView extends StorageItemsView { #sharedStorage: SharedStorageForOrigin; readonly outerSplitWidget: UI.SplitWidget.SplitWidget; readonly innerSplitWidget: UI.SplitWidget.SplitWidget; #metadataView: LegacyWrapper.LegacyWrapper .LegacyWrapper<UI.Widget.VBox, ApplicationComponents.SharedStorageMetadataView.SharedStorageMetadataView>; readonly dataGrid: DataGrid.DataGrid.DataGridImpl<Protocol.Storage.SharedStorageEntry>; #noDisplayView: UI.Widget.VBox; #eventListeners: Common.EventTarget.EventDescriptor[]; readonly sharedStorageItemsDispatcher: Common.ObjectWrapper.ObjectWrapper<SharedStorageItemsDispatcher.EventTypes>; constructor(sharedStorage: SharedStorageForOrigin) { super(i18nString(UIStrings.sharedStorage), 'sharedStoragePanel'); this.#sharedStorage = sharedStorage; this.element.classList.add('storage-view', 'table'); const columns = ([ {id: 'key', title: i18nString(UIStrings.key), sortable: false, editable: true, longText: true, weight: 50}, {id: 'value', title: i18nString(UIStrings.value), sortable: false, editable: true, longText: true, weight: 50}, ] as DataGrid.DataGrid.ColumnDescriptor[]); this.dataGrid = new DataGrid.DataGrid.DataGridImpl({ displayName: i18nString(UIStrings.sharedStorageItems), columns, editCallback: this.#editingCallback.bind(this), deleteCallback: this.#deleteCallback.bind(this), refreshCallback: this.refreshItems.bind(this), }); this.dataGrid.addEventListener(DataGrid.DataGrid.Events.SelectedNode, event => { void this.#previewEntry(event.data); }); this.dataGrid.addEventListener(DataGrid.DataGrid.Events.DeselectedNode, () => { void this.#previewEntry(null); }); this.dataGrid.setStriped(true); this.dataGrid.setName('SharedStorageItemsView'); const dataGridWidget = this.dataGrid.asWidget(); dataGridWidget.setMinimumSize(0, 100); this.#metadataView = LegacyWrapper.LegacyWrapper.legacyWrapper( UI.Widget.VBox, new ApplicationComponents.SharedStorageMetadataView.SharedStorageMetadataView( sharedStorage, sharedStorage.securityOrigin)); this.#metadataView.setMinimumSize(0, 275); const innerResizer = this.#metadataView.element.createChild('div', 'metadata-view-resizer'); this.innerSplitWidget = new UI.SplitWidget.SplitWidget( /* isVertical: */ false, /* secondIsSidebar: */ false, 'sharedStorageInnerSplitViewState'); this.innerSplitWidget.setSidebarWidget(this.#metadataView); this.innerSplitWidget.setMainWidget(dataGridWidget); this.innerSplitWidget.installResizer(innerResizer); this.#noDisplayView = new UI.Widget.VBox(); this.#noDisplayView.setMinimumSize(0, 25); const outerResizer = this.#noDisplayView.element.createChild('div', 'preview-panel-resizer'); this.outerSplitWidget = new UI.SplitWidget.SplitWidget( /* isVertical: */ false, /* secondIsSidebar: */ true, 'sharedStorageOuterSplitViewState'); this.outerSplitWidget.show(this.element); this.outerSplitWidget.setMainWidget(this.innerSplitWidget); this.outerSplitWidget.setSidebarWidget(this.#noDisplayView); this.outerSplitWidget.installResizer(outerResizer); this.#noDisplayView.contentElement.classList.add('placeholder'); const noDisplayDiv = this.#noDisplayView.contentElement.createChild('div'); noDisplayDiv.textContent = i18nString(UIStrings.selectAValueToPreview); this.#eventListeners = []; Common.EventTarget.removeEventListeners(this.#eventListeners); this.#sharedStorage = sharedStorage; this.#eventListeners = [ this.#sharedStorage.addEventListener( SharedStorageForOrigin.Events.SharedStorageChanged, this.#sharedStorageChanged, this), ]; this.sharedStorageItemsDispatcher = new Common.ObjectWrapper.ObjectWrapper<SharedStorageItemsDispatcher.EventTypes>(); } // Use `createView()` instead of the constructor to create a view, so that entries can be awaited asynchronously. static async createView(sharedStorage: SharedStorageForOrigin): Promise<SharedStorageItemsView> { const view = new SharedStorageItemsView(sharedStorage); await view.updateEntriesOnly(); return view; } async updateEntriesOnly(): Promise<void> { if (!this.isShowing()) { return; } const entries = await this.#sharedStorage.getEntries(); if (entries) { this.#showSharedStorageItems(entries); } } async #sharedStorageChanged(): Promise<void> { await this.refreshItems(); } override async refreshItems(): Promise<void> { if (!this.isShowing()) { return; } await this.#metadataView.getComponent().render(); await this.updateEntriesOnly(); this.sharedStorageItemsDispatcher.dispatchEventToListeners(SharedStorageItemsDispatcher.Events.ItemsRefreshed); } override async deleteSelectedItem(): Promise<void> { if (!this.dataGrid.selectedNode) { return; } await this.#deleteCallback(this.dataGrid.selectedNode); } override async deleteAllItems(): Promise<void> { if (!this.hasFilter()) { await this.#sharedStorage.clear(); await this.refreshItems(); this.sharedStorageItemsDispatcher.dispatchEventToListeners(SharedStorageItemsDispatcher.Events.ItemsCleared); UI.ARIAUtils.alert(i18nString(UIStrings.sharedStorageItemsCleared)); return; } await Promise.all(this.dataGrid.rootNode() .children.filter(node => node.data.key) .map(node => this.#sharedStorage.deleteEntry(node.data.key))); await this.refreshItems(); this.sharedStorageItemsDispatcher.dispatchEventToListeners( SharedStorageItemsDispatcher.Events.FilteredItemsCleared); UI.ARIAUtils.alert(i18nString(UIStrings.sharedStorageFilteredItemsCleared)); } async #editingCallback( editingNode: DataGrid.DataGrid.DataGridNode<Protocol.Storage.SharedStorageEntry>, columnIdentifier: string, oldText: string, newText: string): Promise<void> { if (columnIdentifier === 'key' && newText === '') { // The Shared Storage backend does not currently allow '' as a key, so we only set a new entry with a new key if its new key is nonempty. await this.refreshItems(); UI.ARIAUtils.alert(i18nString(UIStrings.sharedStorageItemEditCanceled)); return; } if (columnIdentifier === 'key') { await this.#sharedStorage.deleteEntry(oldText); await this.#sharedStorage.setEntry(newText, editingNode.data.value || '', false); } else { // The Shared Storage backend does not currently allow '' as a key, so we use ' ' as the default key instead. await this.#sharedStorage.setEntry(editingNode.data.key || ' ', newText, false); } await this.refreshItems(); this.sharedStorageItemsDispatcher.dispatchEventToListeners( SharedStorageItemsDispatcher.Events.ItemEdited, {columnIdentifier, oldText, newText} as SharedStorageItemsDispatcher.ItemEditedEvent); UI.ARIAUtils.alert(i18nString(UIStrings.sharedStorageItemEdited)); } #showSharedStorageItems(items: Protocol.Storage.SharedStorageEntry[]): void { const rootNode = this.dataGrid.rootNode(); const [selectedKey] = rootNode.children.filter(node => node.selected).map(node => node.data.key); rootNode.removeChildren(); let selectedNode: DataGrid.DataGrid.DataGridNode<Protocol.Storage.SharedStorageEntry>|null = null; const filteredItems = (item: Protocol.Storage.SharedStorageEntry): string => `${item.key} ${item.value}`; const filteredList = this.filter(items, filteredItems); for (const item of filteredList) { const node = new DataGrid.DataGrid.DataGridNode({key: item.key, value: item.value}, false); node.selectable = true; rootNode.appendChild(node); if (!selectedNode || item.key === selectedKey) { selectedNode = node; } } if (selectedNode) { selectedNode.selected = true; } this.dataGrid.addCreationNode(false); this.setCanDeleteSelected(Boolean(selectedNode)); UI.ARIAUtils.alert(i18nString(UIStrings.sharedStorageNumberEntries, {PH1: filteredList.length})); } async #deleteCallback(node: DataGrid.DataGrid.DataGridNode<Protocol.Storage.SharedStorageEntry>): Promise<void> { if (!node || node.isCreationNode || !this.#sharedStorage) { return; } const key = node.data.key; await this.#sharedStorage.deleteEntry(key); await this.refreshItems(); this.sharedStorageItemsDispatcher.dispatchEventToListeners( SharedStorageItemsDispatcher.Events.ItemDeleted, {key} as SharedStorageItemsDispatcher.ItemDeletedEvent); UI.ARIAUtils.alert(i18nString(UIStrings.sharedStorageItemDeleted)); } async #previewEntry(entry: DataGrid.DataGrid.DataGridNode<Protocol.Storage.SharedStorageEntry>|null): Promise<void> { const key = entry?.data?.key; const value = entry?.data?.value; const wrappedEntry = key && {key: key as string, value: value as string || ''} as WrappedEntry; if (wrappedEntry) { const preview = SourceFrame.JSONView.JSONView.createViewSync(wrappedEntry); // Selection could've changed while the preview was loaded if (entry.selected) { this.outerSplitWidget.setSidebarWidget(preview); } } else { this.outerSplitWidget.setSidebarWidget(this.#noDisplayView); } } getEntriesForTesting(): Array<Protocol.Storage.SharedStorageEntry> { return this.dataGrid.rootNode() .children.filter(node => node.data.key) .map(node => (node.data as Protocol.Storage.SharedStorageEntry)); } }