chrome-devtools-frontend
Version:
Chrome DevTools UI
155 lines (139 loc) • 5.11 kB
text/typescript
// Copyright 2026 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import '../../../ui/legacy/components/data_grid/data_grid.js';
import * as Host from '../../../core/host/host.js';
import * as i18n from '../../../core/i18n/i18n.js';
import type * as TextUtils from '../../../models/text_utils/text_utils.js';
import * as UI from '../../../ui/legacy/legacy.js';
import {html, render} from '../../../ui/lit/lit.js';
const UIStrings = {
/**
* @description Text in Crash Report Context Items View of the Application panel
*/
key: 'Key',
/**
* @description Text in Crash Report Context Items View of the Application panel
*/
value: 'Value',
/**
* @description Context menu item to copy the key of a context entry
*/
copyKey: 'Copy key',
/**
* @description Context menu item to copy the value of a context entry
*/
copyValue: 'Copy value',
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/application/components/CrashReportContextGrid.ts', UIStrings);
export const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
export interface CrashReportContextGridData {
entries: Array<{key: string, value: string}>;
selectedKey?: string;
filters?: TextUtils.TextUtils.ParsedFilter[];
}
export interface ViewInput {
entries: Array<{key: string, value: string}>;
selectedKey?: string;
onSelect: (key: string) => void;
onContextMenu: (e: Event, key: string, value: string) => void;
}
export const DEFAULT_VIEW = (input: ViewInput, output: undefined, target: HTMLElement|ShadowRoot): void => {
// clang-format off
render(
html`
<style>
:host {
display: block;
}
div {
overflow: auto;
}
td {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
</style>
<style>${UI.inspectorCommonStyles}</style>
<div>
<devtools-data-grid striped inline>
<table>
<thead>
<tr>
<th id="key" weight="50">${i18nString(UIStrings.key)}</th>
<th id="value" weight="50">${i18nString(UIStrings.value)}</th>
</tr>
</thead>
<tbody>
${input.entries.map(entry => html`
<tr class=${input.selectedKey === entry.key ? 'selected' : ''}
@select=${() => input.onSelect(entry.key)}
@contextmenu=${(e: Event) => input.onContextMenu(e, entry.key, entry.value)}>
<td title=${entry.key}>${entry.key}</td>
<td title=${entry.value}>${entry.value}</td>
</tr>
`)}
</tbody>
</table>
</devtools-data-grid>
</div>
`,
target);
// clang-format on
};
type View = (input: ViewInput, output: undefined, target: HTMLElement|ShadowRoot) => void;
export class CrashReportContextGrid extends UI.Widget.Widget {
#entries: Array<{key: string, value: string}> = [];
#filteredEntries: Array<{key: string, value: string}> = [];
#selectedKey?: string;
#filters: TextUtils.TextUtils.ParsedFilter[] = [];
readonly #view: View;
constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
super(element, {useShadowDom: true});
this.#view = view;
}
set data(data: CrashReportContextGridData) {
this.#entries = data.entries;
this.#selectedKey = data.selectedKey;
this.#filters = data.filters || [];
this.requestUpdate();
}
#computeFilteredEntries(): void {
if (this.#filters.length === 0) {
this.#filteredEntries = this.#entries;
return;
}
this.#filteredEntries = this.#entries.filter(entry => {
return this.#filters.every(filter => {
const regex = filter.regex;
if (!regex) {
return true;
}
const matches = regex.test(entry.key) || regex.test(entry.value);
return filter.negative ? !matches : matches;
});
});
}
#onContextMenu(e: Event, key: string, value: string): void {
const customEvent = e as CustomEvent<UI.ContextMenu.ContextMenu>;
const contextMenu = customEvent.detail;
contextMenu.defaultSection().appendItem(i18nString(UIStrings.copyKey), () => {
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(key);
}, {jslogContext: 'copy-key'});
contextMenu.defaultSection().appendItem(i18nString(UIStrings.copyValue), () => {
Host.InspectorFrontendHost.InspectorFrontendHostInstance.copyText(value);
}, {jslogContext: 'copy-value'});
}
override performUpdate(): void {
this.#computeFilteredEntries();
this.#view(
{
entries: this.#filteredEntries,
selectedKey: this.#selectedKey,
onSelect: (key: string) => this.element.dispatchEvent(new CustomEvent('select', {detail: key})),
onContextMenu: (e: Event, key: string, value: string) => this.#onContextMenu(e, key, value),
},
undefined, this.contentElement);
}
}