Migrate ui/ActionRegistration.js to .ts
Bug: 1134103
Change-Id: I05c38b489df32c87c770f10c2b930b21be005f41
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2566441
Commit-Queue: Andres Olivares <[email protected]>
Reviewed-by: Tim van der Lippe <[email protected]>
diff --git a/front_end/ui/ActionRegistration.ts b/front_end/ui/ActionRegistration.ts
new file mode 100644
index 0000000..33879a9
--- /dev/null
+++ b/front_end/ui/ActionRegistration.ts
@@ -0,0 +1,352 @@
+// Copyright 2019 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 '../common/common.js';
+import {ls} from '../platform/platform.js';
+import * as Root from '../root/root.js';
+
+import {Context} from './Context.js';
+
+class ActionRuntimeExtensionDescriptor extends Root.Runtime.RuntimeExtensionDescriptor {
+ iconClass?: string;
+ toggledIconClass?: string;
+ toggleWithRedColor?: boolean;
+ toggleable?: boolean;
+
+ constructor() {
+ super();
+ }
+}
+
+export interface Action extends Common.EventTarget.EventTarget {
+ id(): string;
+
+ execute(): Promise<boolean>;
+
+ icon(): string|undefined;
+
+ toggledIcon(): string|undefined;
+
+ toggleWithRedColor(): boolean;
+
+ setEnabled(_enabled: boolean): void;
+
+ enabled(): boolean;
+
+ category(): string;
+
+ tags(): string|undefined;
+
+ toggleable(): boolean;
+
+ title(): string;
+
+ toggled(): boolean;
+
+ setToggled(_toggled: boolean): void
+}
+
+export class LegacyActionRegistration extends Common.ObjectWrapper.ObjectWrapper implements Action {
+ _extension: Root.Runtime.Extension;
+ _enabled: boolean;
+ _toggled: boolean;
+
+ constructor(extension: Root.Runtime.Extension) {
+ super();
+ this._extension = extension;
+ this._enabled = true;
+ this._toggled = false;
+ }
+
+ id(): string {
+ return this._actionDescriptor().actionId || '';
+ }
+
+ extension(): Root.Runtime.Extension {
+ return this._extension;
+ }
+
+ async execute(): Promise<boolean> {
+ if (!this._extension.canInstantiate()) {
+ return false;
+ }
+ const delegate = await this._extension.instance() as ActionDelegate;
+ const actionId = this.id();
+ return delegate.handleAction(Context.instance(), actionId);
+ }
+
+ icon(): string {
+ return this._actionDescriptor().iconClass || '';
+ }
+
+ toggledIcon(): string {
+ return this._actionDescriptor().toggledIconClass || '';
+ }
+
+ toggleWithRedColor(): boolean {
+ return !!this._actionDescriptor().toggleWithRedColor;
+ }
+
+ setEnabled(enabled: boolean) {
+ if (this._enabled === enabled) {
+ return;
+ }
+
+ this._enabled = enabled;
+ this.dispatchEventToListeners(Events.Enabled, enabled);
+ }
+
+ enabled(): boolean {
+ return this._enabled;
+ }
+
+ category(): string {
+ return ls`${this._actionDescriptor().category || ''}`;
+ }
+
+ tags(): string {
+ const keys = this._actionDescriptor().tags || '';
+ // Get localized keys and separate by null character to prevent fuzzy matching from matching across them.
+ const keyList = keys.split(',');
+ let key = '';
+ keyList.forEach(k => {
+ key += (ls(k.trim()) + '\0');
+ });
+ return key;
+ }
+
+ toggleable(): boolean {
+ return !!this._actionDescriptor().toggleable;
+ }
+
+ title(): string {
+ let title = this._extension.title() || '';
+ const options = this._actionDescriptor().options;
+ if (options) {
+ for (const pair of options) {
+ if (pair.value !== this._toggled) {
+ title = ls`${pair.title}`;
+ }
+ }
+ }
+ return title;
+ }
+
+ toggled(): boolean {
+ return this._toggled;
+ }
+
+ setToggled(toggled: boolean) {
+ console.assert(this.toggleable(), 'Shouldn\'t be toggling an untoggleable action', this.id());
+ if (this._toggled === toggled) {
+ return;
+ }
+
+ this._toggled = toggled;
+ this.dispatchEventToListeners(Events.Toggled, toggled);
+ }
+
+ _actionDescriptor(): ActionRuntimeExtensionDescriptor {
+ return this._extension.descriptor() as ActionRuntimeExtensionDescriptor;
+ }
+}
+
+export interface ActionDelegate {
+ handleAction(_context: Context, _actionId: string): boolean;
+}
+
+export class PreRegisteredAction extends Common.ObjectWrapper.ObjectWrapper implements Action {
+ _enabled = true;
+ _toggled = false;
+ _actionRegistration: ActionRegistration;
+ constructor(actionRegistration: ActionRegistration) {
+ super();
+ this._actionRegistration = actionRegistration;
+ }
+
+ id(): string {
+ return this._actionRegistration.actionId;
+ }
+
+ async execute(): Promise<boolean> {
+ if (!this._actionRegistration.loadActionDelegate) {
+ return false;
+ }
+ const delegate = await this._actionRegistration.loadActionDelegate();
+ const actionId = this.id();
+ return delegate.handleAction(Context.instance(), actionId);
+ }
+
+ icon(): string|undefined {
+ return this._actionRegistration.iconClass;
+ }
+
+ toggledIcon(): string|undefined {
+ return this._actionRegistration.toggledIconClass;
+ }
+
+ toggleWithRedColor(): boolean {
+ return !!this._actionRegistration.toggleWithRedColor;
+ }
+
+ setEnabled(enabled: boolean) {
+ if (this._enabled === enabled) {
+ return;
+ }
+
+ this._enabled = enabled;
+ this.dispatchEventToListeners(Events.Enabled, enabled);
+ }
+
+ enabled(): boolean {
+ return this._enabled;
+ }
+
+ category(): string {
+ return this._actionRegistration.category;
+ }
+
+ tags(): string|undefined {
+ return this._actionRegistration.tags;
+ }
+
+ toggleable(): boolean {
+ return !!this._actionRegistration.toggleable;
+ }
+
+ title(): string {
+ let title = this._actionRegistration.title || '';
+ const options = this._actionRegistration.options;
+ if (options) {
+ // Actions with an 'options' property don't have a title field. Instead, the displayed
+ // title is taken from the 'title' property of the option that is not active. Only one of the
+ // two options can be active at a given moment and the 'toggled' property of the action along
+ // with the 'value' of the options are used to determine which one it is.
+
+ for (const pair of options) {
+ if (pair.value !== this._toggled) {
+ title = pair.title;
+ }
+ }
+ }
+ return title;
+ }
+
+ toggled(): boolean {
+ return this._toggled;
+ }
+
+ setToggled(toggled: boolean) {
+ console.assert(this.toggleable(), 'Shouldn\'t be toggling an untoggleable action', this.id());
+ if (this._toggled === toggled) {
+ return;
+ }
+
+ this._toggled = toggled;
+ this.dispatchEventToListeners(Events.Toggled, toggled);
+ }
+
+ options(): undefined|Array<ExtensionOption> {
+ return this._actionRegistration.options;
+ }
+
+ contextTypes(): undefined|Array<unknown> {
+ if (this._actionRegistration.contextTypes) {
+ return this._actionRegistration.contextTypes();
+ }
+ return undefined;
+ }
+
+ canInstantiate(): boolean {
+ return !!this._actionRegistration.loadActionDelegate;
+ }
+
+ bindings(): Array<Binding>|undefined {
+ return this._actionRegistration.bindings;
+ }
+
+ experiment(): string|undefined {
+ return this._actionRegistration.experiment;
+ }
+
+ condition(): string|undefined {
+ return this._actionRegistration.condition;
+ }
+}
+
+const registeredActionExtensions: Array<PreRegisteredAction> = [];
+
+const actionIdSet = new Set<string>();
+
+export function registerActionExtension(registration: ActionRegistration) {
+ const actionId = registration.actionId;
+ if (actionIdSet.has(actionId)) {
+ throw new Error(`Duplicate Action id '${actionId}': ${new Error().stack}`);
+ }
+ actionIdSet.add(actionId);
+ registeredActionExtensions.push(new PreRegisteredAction(registration));
+}
+
+export function getRegisteredActionExtensions(): Array<PreRegisteredAction> {
+ return registeredActionExtensions.filter(
+ action =>
+ Root.Runtime.Runtime.isDescriptorEnabled({experiment: action.experiment(), condition: action.condition()}));
+}
+
+export const enum Platform {
+ All = 'All platforms',
+ Mac = 'mac',
+ WindowsLinux = 'windows,linux',
+ Android = 'Android',
+}
+
+export const Events = {
+ Enabled: Symbol('Enabled'),
+ Toggled: Symbol('Toggled'),
+};
+
+export const ActionCategory = {
+ ELEMENTS: ls`Elements`,
+ SCREENSHOT: ls`Screenshot`,
+};
+
+type ActionCategory = typeof ActionCategory[keyof typeof ActionCategory];
+
+export const enum IconClass {
+ LARGEICON_NODE_SEARCH = 'largeicon-node-search',
+}
+
+export const enum KeybindSet {
+ DEVTOOLS_DEFAULT = 'devToolsDefault',
+ VS_CODE = 'vsCode',
+}
+
+export interface ExtensionOption {
+ value: boolean;
+ title: string;
+ text?: string;
+}
+
+export interface Binding {
+ platform?: Platform;
+ shortcut: string;
+ keybindSets?: Array<KeybindSet>;
+}
+
+export interface ActionRegistration {
+ actionId: string;
+ category: ActionCategory;
+ title?: string;
+ iconClass?: string;
+ toggledIconClass?: string;
+ toggleWithRedColor?: boolean;
+ tags?: string;
+ toggleable?: boolean;
+ loadActionDelegate?: () => Promise<ActionDelegate>;
+ contextTypes?: () => Array<unknown>;
+ options?: Array<ExtensionOption>;
+ bindings?: Array<Binding>;
+ experiment?: Root.Runtime.ExperimentName;
+ condition?: Root.Runtime.ConditionName;
+}