chrome-devtools-frontend
Version:
Chrome DevTools UI
288 lines (251 loc) • 9.19 kB
text/typescript
// Copyright 2020 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/kit/kit.js';
import * as i18n from '../../../core/i18n/i18n.js';
import * as Buttons from '../../../ui/components/buttons/buttons.js';
import * as UI from '../../../ui/legacy/legacy.js';
import * as Lit from '../../../ui/lit/lit.js';
import * as VisualLogging from '../../../ui/visual_logging/visual_logging.js';
import linearMemoryNavigatorStyles from './linearMemoryNavigator.css.js';
const UIStrings = {
/**
* @description Tooltip text that appears when hovering over a valid memory address (for example, 0x0) in the address line in the Memory inspector panel.
*/
enterAddress: 'Enter address',
/**
* @description Tooltip text that appears when hovering over the button to go back in history in the Memory inspector panel.
*/
goBackInAddressHistory: 'Go back in address history',
/**
* @description Tooltip text that appears when hovering over the button to go forward in history in the Memory inspector panel.
*/
goForwardInAddressHistory: 'Go forward in address history',
/**
* @description Tooltip text that appears when hovering over the page back icon in the Memory inspector panel.
*/
previousPage: 'Previous page',
/**
* @description Tooltip text that appears when hovering over the next page icon in the Memory inspector panel.
*/
nextPage: 'Next page',
/**
* @description Tooltip text that appears when hovering over the refresh button in the Memory inspector panel.
*/
refresh: 'Refresh',
} as const;
const str_ =
i18n.i18n.registerUIStrings('panels/linear_memory_inspector/components/LinearMemoryNavigator.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
const {render, html, Directives: {ifDefined}} = Lit;
export const enum Navigation {
BACKWARD = 'Backward',
FORWARD = 'Forward',
}
export interface LinearMemoryNavigatorData {
address: string;
mode: Mode;
canGoBackInHistory: boolean;
canGoForwardInHistory: boolean;
valid: boolean;
error: string|undefined;
}
export const enum Mode {
EDIT = 'Edit',
SUBMITTED = 'Submitted',
INVALID_SUBMIT = 'InvalidSubmit',
}
export interface ViewInput extends LinearMemoryNavigatorData {
onAddressChange?: (address: string, mode: Mode) => void;
onNavigatePage?: (navigation: Navigation) => void;
onNavigateHistory?: (navigation: Navigation) => void;
onRefreshRequest?: () => void;
}
export type View = (input: ViewInput, output: undefined, target: HTMLElement|ShadowRoot) => void;
export const DEFAULT_VIEW: View = (input, _output, target) => {
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
const result = html`
<style>${linearMemoryNavigatorStyles}</style>
<div class="navigator">
<div class="navigator-item">
${createButton({icon: 'undo', title: i18nString(UIStrings.goBackInAddressHistory),
onClick: () => input.onNavigateHistory?.(Navigation.BACKWARD), enabled: input.canGoBackInHistory,
jslogContext:'linear-memory-inspector.history-back'})}
${createButton({icon: 'redo', title: i18nString(UIStrings.goForwardInAddressHistory),
onClick: () => input.onNavigateHistory?.(Navigation.FORWARD), enabled: input.canGoForwardInHistory,
jslogContext:'linear-memory-inspector.history-forward'})}
</div>
<div class="navigator-item">
${createButton({icon: 'chevron-left', title: i18nString(UIStrings.previousPage),
onClick: () => input.onNavigatePage?.(Navigation.BACKWARD), enabled: true,
jslogContext:'linear-memory-inspector.previous-page'})}
${createAddressInput(input)}
${createButton({icon: 'chevron-right', title: i18nString(UIStrings.nextPage),
onClick: () => input.onNavigatePage?.(Navigation.FORWARD), enabled: true,
jslogContext:'linear-memory-inspector.next-page'})}
</div>
${createButton({icon: 'refresh', title: i18nString(UIStrings.refresh),
onClick: () => input.onRefreshRequest?.(), enabled: true,
jslogContext:'linear-memory-inspector.refresh'})}
</div>
`;
render(result, target);
// clang-format on
};
function createAddressInput(data: ViewInput): Lit.TemplateResult {
const classMap = {
'address-input': true,
invalid: !data.valid,
};
return html`<input
class=${Lit.Directives.classMap(classMap)}
data-input="true"
.value=${data.address}
jslog=${VisualLogging.textField('linear-memory-inspector.address').track({
change: true,
})}
title=${
ifDefined(
data.valid ? i18nString(UIStrings.enterAddress) : data.error,
)}
@change=${(e: Event) => data.onAddressChange?.((e.target as HTMLInputElement).value, Mode.SUBMITTED)}
@input=${(e: Event) => data.onAddressChange?.((e.target as HTMLInputElement).value, Mode.EDIT)}
${Lit.Directives.ref((el: Element|undefined) => {
if (el) {
const inputEl = el as HTMLInputElement;
if (data.mode === Mode.SUBMITTED) {
inputEl.blur();
} else if (data.mode === Mode.INVALID_SUBMIT) {
inputEl.select();
}
}
})}
/>`;
}
function createButton(data: {
icon: string,
title: string,
onClick: () => void,
enabled: boolean,
jslogContext: string,
}): Lit.TemplateResult {
return html`
<devtools-button class="navigator-button"
.data=${{variant: Buttons.Button.Variant.ICON,
iconName: data.icon,
disabled: !data.enabled} as Buttons.Button.ButtonData}
jslog=${VisualLogging.action().track({click: true, keydown: 'Enter'}).context(data.jslogContext)}
title=${data.title}
@click=${data.onClick}
></devtools-button>`;
}
export class LinearMemoryNavigator extends UI.Widget.Widget {
readonly #view: View;
#address = '0';
#error: string|undefined = undefined;
#valid = true;
#canGoBackInHistory = false;
#canGoForwardInHistory = false;
#mode = Mode.SUBMITTED;
#onRefreshRequest?: () => void;
#onAddressChange?: (address: string, mode: Mode) => void;
#onNavigatePage?: (navigation: Navigation) => void;
#onNavigateHistory?: (navigation: Navigation) => void;
get onRefreshRequest(): (() => void)|undefined {
return this.#onRefreshRequest;
}
set onRefreshRequest(callback: (() => void)|undefined) {
this.#onRefreshRequest = callback;
this.performUpdate();
}
get onAddressChange(): ((address: string, mode: Mode) => void)|undefined {
return this.#onAddressChange;
}
set onAddressChange(callback: ((address: string, mode: Mode) => void)|undefined) {
this.#onAddressChange = callback;
this.performUpdate();
}
get onNavigatePage(): ((navigation: Navigation) => void)|undefined {
return this.#onNavigatePage;
}
set onNavigatePage(callback: ((navigation: Navigation) => void)|undefined) {
this.#onNavigatePage = callback;
this.performUpdate();
}
get onNavigateHistory(): ((navigation: Navigation) => void)|undefined {
return this.#onNavigateHistory;
}
set onNavigateHistory(callback: ((navigation: Navigation) => void)|undefined) {
this.#onNavigateHistory = callback;
this.performUpdate();
}
constructor(element?: HTMLElement, view: View = DEFAULT_VIEW) {
super(element);
this.#view = view;
if (!this.element.shadowRoot) {
this.element.attachShadow({mode: 'open'});
}
}
get address(): string {
return this.#address;
}
set address(address: string) {
this.#address = address;
this.requestUpdate();
}
get error(): string|undefined {
return this.#error;
}
set error(error: string|undefined) {
this.#error = error;
this.requestUpdate();
}
get valid(): boolean {
return this.#valid;
}
set valid(valid: boolean) {
this.#valid = valid;
this.requestUpdate();
}
get canGoBackInHistory(): boolean {
return this.#canGoBackInHistory;
}
set canGoBackInHistory(canGoBackInHistory: boolean) {
this.#canGoBackInHistory = canGoBackInHistory;
this.requestUpdate();
}
get canGoForwardInHistory(): boolean {
return this.#canGoForwardInHistory;
}
set canGoForwardInHistory(canGoForwardInHistory: boolean) {
this.#canGoForwardInHistory = canGoForwardInHistory;
this.requestUpdate();
}
get mode(): Mode {
return this.#mode;
}
set mode(mode: Mode) {
this.#mode = mode;
this.requestUpdate();
}
override performUpdate(): void {
const shadowRoot = this.element.shadowRoot;
if (!shadowRoot) {
return;
}
const viewInput: ViewInput = {
address: this.#address,
error: this.#error,
valid: this.#valid,
canGoBackInHistory: this.#canGoBackInHistory,
canGoForwardInHistory: this.#canGoForwardInHistory,
mode: this.#mode,
onAddressChange: this.onAddressChange,
onNavigatePage: this.onNavigatePage,
onNavigateHistory: this.onNavigateHistory,
onRefreshRequest: this.onRefreshRequest,
};
this.#view(viewInput, undefined, shadowRoot);
}
}