mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-01 01:38:07 +08:00
Bug: 1134103 Change-Id: I2ed7656af264f60c45f1a4143c14203afdbf6f68 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2521638 Commit-Queue: Andres Olivares <andoli@chromium.org> Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
653 lines
23 KiB
JavaScript
653 lines
23 KiB
JavaScript
// Copyright 2020 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 * as Host from '../host/host.js';
|
||
import * as i18n from '../i18n/i18n.js';
|
||
import * as Root from '../root/root.js';
|
||
import * as UI from '../ui/ui.js';
|
||
|
||
export const UIStrings = {
|
||
/**
|
||
*@description Text for keyboard shortcuts
|
||
*/
|
||
shortcuts: 'Shortcuts',
|
||
/**
|
||
*@description Text appearing before a select control offering users their choice of keyboard shortcut presets.
|
||
*/
|
||
matchShortcutsFromPreset: 'Match shortcuts from preset',
|
||
/**
|
||
*@description Screen reader label for list of keyboard shortcuts in settings
|
||
*/
|
||
keyboardShortcutsList: 'Keyboard shortcuts list',
|
||
/**
|
||
*@description Screen reader label for an icon denoting a shortcut that has been changed from its default
|
||
*/
|
||
shortcutModified: 'Shortcut modified',
|
||
/**
|
||
*@description Screen reader label for an empty shortcut cell in custom shortcuts settings tab
|
||
*/
|
||
noShortcutForAction: 'No shortcut for action',
|
||
/**
|
||
*@description Link text in the settings pane to add another shortcut for an action
|
||
*/
|
||
addAShortcut: 'Add a shortcut',
|
||
/**
|
||
*@description Label for a button in the settings pane that confirms changes to a keyboard shortcut
|
||
*/
|
||
confirmChanges: 'Confirm changes',
|
||
/**
|
||
*@description Label for a button in the settings pane that discards changes to the shortcut being edited
|
||
*/
|
||
discardChanges: 'Discard changes',
|
||
/**
|
||
*@description Label for a button in the settings pane that removes a keyboard shortcut.
|
||
*/
|
||
removeShortcut: 'Remove shortcut',
|
||
/**
|
||
*@description Label for a button in the settings pane that edits a keyboard shortcut
|
||
*/
|
||
editShortcut: 'Edit shortcut',
|
||
/**
|
||
*@description Message shown in settings when the user inputs a modifier-only shortcut such as Ctrl+Shift.
|
||
*/
|
||
shortcutsCannotContainOnly: 'Shortcuts cannot contain only modifier keys.',
|
||
/**
|
||
*@description Messages shown in shortcuts settings when the user inputs a shortcut that is already in use.
|
||
*@example {Performance} PH1
|
||
*@example {Start/stop recording} PH2
|
||
*/
|
||
thisShortcutIsInUseByS: 'This shortcut is in use by {PH1}: {PH2}.',
|
||
/**
|
||
*@description Message shown in settings when to restore default shortcuts.
|
||
*/
|
||
RestoreDefaultShortcuts: 'Restore default shortcuts',
|
||
/**
|
||
*@description Message shown in settings to show the full list of keyboard shortcuts.
|
||
*/
|
||
FullListOfDevtoolsKeyboard: 'Full list of DevTools keyboard shortcuts and gestures',
|
||
/**
|
||
*@description Label for a button in the shortcut editor that resets all shortcuts for the current action.
|
||
*/
|
||
ResetShortcutsForAction: 'Reset shortcuts for action',
|
||
};
|
||
const str_ = i18n.i18n.registerUIStrings('settings/KeybindsSettingsTab.js', UIStrings);
|
||
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
||
|
||
/**
|
||
* @implements {UI.ListControl.ListDelegate<!KeybindsItem>}
|
||
*/
|
||
export class KeybindsSettingsTab extends UI.Widget.VBox {
|
||
constructor() {
|
||
super(true);
|
||
this.registerRequiredCSS('settings/keybindsSettingsTab.css', {enableLegacyPatching: true});
|
||
|
||
const header = this.contentElement.createChild('header');
|
||
header.createChild('h1').textContent = i18nString(UIStrings.shortcuts);
|
||
const keybindsSetSetting = Common.Settings.Settings.instance().moduleSetting('activeKeybindSet');
|
||
const userShortcutsSetting = Common.Settings.Settings.instance().moduleSetting('userShortcuts');
|
||
userShortcutsSetting.addChangeListener(this.update, this);
|
||
keybindsSetSetting.addChangeListener(this.update, this);
|
||
const keybindsSetSelect =
|
||
UI.SettingsUI.createControlForSetting(keybindsSetSetting, i18nString(UIStrings.matchShortcutsFromPreset));
|
||
if (keybindsSetSelect) {
|
||
keybindsSetSelect.classList.add('keybinds-set-select');
|
||
this.contentElement.appendChild(keybindsSetSelect);
|
||
}
|
||
|
||
/** @type {!UI.ListModel.ListModel<!KeybindsItem>} */
|
||
this._items = new UI.ListModel.ListModel();
|
||
this._list = new UI.ListControl.ListControl(this._items, this, UI.ListControl.ListMode.NonViewport);
|
||
this._items.replaceAll(this._createListItems());
|
||
UI.ARIAUtils.markAsList(this._list.element);
|
||
this.registerRequiredCSS('settings/keybindsSettingsTab.css', {enableLegacyPatching: true});
|
||
this.contentElement.appendChild(this._list.element);
|
||
UI.ARIAUtils.setAccessibleName(this._list.element, i18nString(UIStrings.keyboardShortcutsList));
|
||
const footer = this.contentElement.createChild('div');
|
||
footer.classList.add('keybinds-footer');
|
||
const docsLink = UI.UIUtils.createDocumentationLink(
|
||
'iterate/inspect-styles/shortcuts', i18nString(UIStrings.FullListOfDevtoolsKeyboard));
|
||
docsLink.classList.add('docs-link');
|
||
footer.appendChild(docsLink);
|
||
footer.appendChild(UI.UIUtils.createTextButton(i18nString(UIStrings.RestoreDefaultShortcuts), () => {
|
||
userShortcutsSetting.set([]);
|
||
keybindsSetSetting.set(UI.ShortcutRegistry.DefaultShortcutSetting);
|
||
}));
|
||
/** @type {?UI.ActionRegistration.Action} */
|
||
this._editingItem = null;
|
||
/** @type {?ShortcutListItem} */
|
||
this._editingRow = null;
|
||
|
||
this.update();
|
||
}
|
||
|
||
/**
|
||
* @override
|
||
* @param {!KeybindsItem} item
|
||
* @return {!Element}
|
||
*/
|
||
createElementForItem(item) {
|
||
let itemElement = document.createElement('div');
|
||
|
||
if (typeof item === 'string') {
|
||
UI.ARIAUtils.setLevel(itemElement, 1);
|
||
itemElement.classList.add('keybinds-category-header');
|
||
itemElement.textContent = item;
|
||
} else {
|
||
const listItem = new ShortcutListItem(item, this, item === this._editingItem);
|
||
itemElement = listItem.element;
|
||
UI.ARIAUtils.setLevel(itemElement, 2);
|
||
if (item === this._editingItem) {
|
||
this._editingRow = listItem;
|
||
}
|
||
}
|
||
|
||
itemElement.classList.add('keybinds-list-item');
|
||
UI.ARIAUtils.markAsListitem(itemElement);
|
||
itemElement.tabIndex = item === this._list.selectedItem() && item !== this._editingItem ? 0 : -1;
|
||
return itemElement;
|
||
}
|
||
|
||
/**
|
||
* @param {!UI.ActionRegistration.Action} item
|
||
* @param {!Map.<!UI.KeyboardShortcut.KeyboardShortcut, ?Array.<!UI.KeyboardShortcut.Descriptor>>} editedShortcuts
|
||
*/
|
||
commitChanges(item, editedShortcuts) {
|
||
for (const [originalShortcut, newDescriptors] of editedShortcuts) {
|
||
if (originalShortcut.type !== UI.KeyboardShortcut.Type.UnsetShortcut) {
|
||
UI.ShortcutRegistry.ShortcutRegistry.instance().removeShortcut(originalShortcut);
|
||
if (!newDescriptors) {
|
||
Host.userMetrics.actionTaken(Host.UserMetrics.Action.ShortcutRemoved);
|
||
}
|
||
}
|
||
if (newDescriptors) {
|
||
UI.ShortcutRegistry.ShortcutRegistry.instance().registerUserShortcut(
|
||
originalShortcut.changeKeys(/** @type {!Array.<!UI.KeyboardShortcut.Descriptor>} */ (newDescriptors))
|
||
.changeType(UI.KeyboardShortcut.Type.UserShortcut));
|
||
if (originalShortcut.type === UI.KeyboardShortcut.Type.UnsetShortcut) {
|
||
Host.userMetrics.actionTaken(Host.UserMetrics.Action.UserShortcutAdded);
|
||
} else {
|
||
Host.userMetrics.actionTaken(Host.UserMetrics.Action.ShortcutModified);
|
||
}
|
||
}
|
||
}
|
||
this.stopEditing(item);
|
||
}
|
||
|
||
/**
|
||
* This method will never be called.
|
||
* @override
|
||
* @param {!KeybindsItem} item
|
||
* @return {number}
|
||
*/
|
||
heightForItem(item) {
|
||
return 0;
|
||
}
|
||
|
||
|
||
/**
|
||
* @override
|
||
* @param {!KeybindsItem} item
|
||
* @returns {boolean}
|
||
*/
|
||
isItemSelectable(item) {
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @override
|
||
* @param {?KeybindsItem} from
|
||
* @param {?KeybindsItem} to
|
||
* @param {?HTMLElement} fromElement
|
||
* @param {?HTMLElement} toElement
|
||
*/
|
||
selectedItemChanged(from, to, fromElement, toElement) {
|
||
if (fromElement) {
|
||
fromElement.tabIndex = -1;
|
||
}
|
||
if (toElement) {
|
||
if (to === this._editingItem && this._editingRow) {
|
||
this._editingRow.focus();
|
||
} else {
|
||
toElement.tabIndex = 0;
|
||
if (this._list.element.hasFocus()) {
|
||
toElement.focus();
|
||
}
|
||
}
|
||
this.setDefaultFocusedElement(toElement);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @override
|
||
* @param {?Element} fromElement
|
||
* @param {?Element} toElement
|
||
* @return {boolean}
|
||
*/
|
||
updateSelectedItemARIA(fromElement, toElement) {
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @param {!UI.ActionRegistration.Action} action
|
||
*/
|
||
startEditing(action) {
|
||
if (this._editingItem) {
|
||
this.stopEditing(this._editingItem);
|
||
}
|
||
UI.UIUtils.markBeingEdited(this._list.element, true);
|
||
this._editingItem = action;
|
||
this._list.refreshItem(action);
|
||
}
|
||
|
||
/**
|
||
* @param {!UI.ActionRegistration.Action} action
|
||
*/
|
||
stopEditing(action) {
|
||
UI.UIUtils.markBeingEdited(this._list.element, false);
|
||
this._editingItem = null;
|
||
this._editingRow = null;
|
||
this._list.refreshItem(action);
|
||
this.focus();
|
||
}
|
||
|
||
/**
|
||
* @returns {!Array.<!KeybindsItem>}
|
||
*/
|
||
_createListItems() {
|
||
const actions = UI.ActionRegistry.ActionRegistry.instance().actions().sort((actionA, actionB) => {
|
||
if (actionA.category() < actionB.category()) {
|
||
return -1;
|
||
}
|
||
if (actionA.category() > actionB.category()) {
|
||
return 1;
|
||
}
|
||
if (actionA.id() < actionB.id()) {
|
||
return -1;
|
||
}
|
||
if (actionA.id() > actionB.id()) {
|
||
return 1;
|
||
}
|
||
return 0;
|
||
});
|
||
|
||
/** @type {!Array.<!KeybindsItem>} */
|
||
const items = [];
|
||
|
||
/** @type {string} */
|
||
let currentCategory;
|
||
actions.forEach(action => {
|
||
if (currentCategory !== action.category()) {
|
||
items.push(action.category());
|
||
}
|
||
items.push(action);
|
||
currentCategory = action.category();
|
||
});
|
||
return items;
|
||
}
|
||
|
||
/**
|
||
* @param {!Event} event
|
||
*/
|
||
onEscapeKeyPressed(event) {
|
||
const deepActiveElement = document.deepActiveElement();
|
||
if (this._editingRow && deepActiveElement && deepActiveElement.nodeName === 'INPUT') {
|
||
this._editingRow.onEscapeKeyPressed(event);
|
||
}
|
||
}
|
||
|
||
update() {
|
||
if (this._editingItem) {
|
||
this.stopEditing(this._editingItem);
|
||
}
|
||
this._list.refreshAllItems();
|
||
if (!this._list.selectedItem()) {
|
||
this._list.selectItem(this._items.at(0));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @override
|
||
*/
|
||
willHide() {
|
||
if (this._editingItem) {
|
||
this.stopEditing(this._editingItem);
|
||
}
|
||
}
|
||
}
|
||
|
||
export class ShortcutListItem {
|
||
/**
|
||
* @param {!UI.ActionRegistration.Action} item
|
||
* @param {!KeybindsSettingsTab} settingsTab
|
||
* @param {boolean=} isEditing
|
||
*/
|
||
constructor(item, settingsTab, isEditing) {
|
||
this._isEditing = !!isEditing;
|
||
this._settingsTab = settingsTab;
|
||
this._item = item;
|
||
this.element = document.createElement('div');
|
||
/** @type {!Map.<!UI.KeyboardShortcut.KeyboardShortcut, ?Array.<!UI.KeyboardShortcut.Descriptor>>} */
|
||
this._editedShortcuts = new Map();
|
||
/** @type {!Map.<!UI.KeyboardShortcut.KeyboardShortcut, !Element>} */
|
||
this._shortcutInputs = new Map();
|
||
/** @type {!Array.<!UI.KeyboardShortcut.KeyboardShortcut>} */
|
||
this._shortcuts = UI.ShortcutRegistry.ShortcutRegistry.instance().shortcutsForAction(item.id());
|
||
/** @type {?HTMLElement} */
|
||
this._elementToFocus = null;
|
||
/** @type {?HTMLButtonElement} */
|
||
this._confirmButton = null;
|
||
/** @type {?Element} */
|
||
this._addShortcutLinkContainer = null;
|
||
/** @type {?Element} */
|
||
this._errorMessageElement = null;
|
||
/** @type {?number} */
|
||
this._secondKeyTimeout = null;
|
||
|
||
this._update();
|
||
}
|
||
|
||
focus() {
|
||
if (this._elementToFocus) {
|
||
this._elementToFocus.focus();
|
||
}
|
||
}
|
||
|
||
_update() {
|
||
this.element.removeChildren();
|
||
this._elementToFocus = null;
|
||
this._shortcutInputs.clear();
|
||
|
||
this.element.classList.toggle('keybinds-editing', this._isEditing);
|
||
this.element.createChild('div', 'keybinds-action-name keybinds-list-text').textContent = this._item.title();
|
||
this._shortcuts.forEach(this._createShortcutRow, this);
|
||
if (this._shortcuts.length === 0) {
|
||
this._createEmptyInfo();
|
||
}
|
||
if (this._isEditing) {
|
||
this._setupEditor();
|
||
}
|
||
}
|
||
|
||
_createEmptyInfo() {
|
||
if (UI.ShortcutRegistry.ShortcutRegistry.instance().actionHasDefaultShortcut(this._item.id())) {
|
||
const icon = UI.Icon.Icon.create('largeicon-shortcut-changed', 'keybinds-modified');
|
||
UI.ARIAUtils.setAccessibleName(icon, i18nString(UIStrings.shortcutModified));
|
||
this.element.appendChild(icon);
|
||
}
|
||
if (!this._isEditing) {
|
||
const emptyElement = this.element.createChild('div', 'keybinds-shortcut keybinds-list-text');
|
||
UI.ARIAUtils.setAccessibleName(emptyElement, i18nString(UIStrings.noShortcutForAction));
|
||
if (Root.Runtime.experiments.isEnabled('keyboardShortcutEditor')) {
|
||
this.element.appendChild(this._createEditButton());
|
||
}
|
||
}
|
||
}
|
||
|
||
_setupEditor() {
|
||
this._addShortcutLinkContainer = this.element.createChild('div', 'keybinds-shortcut devtools-link');
|
||
const addShortcutLink =
|
||
/** @type {!HTMLDivElement} */ (this._addShortcutLinkContainer.createChild('span', 'devtools-link'));
|
||
addShortcutLink.textContent = i18nString(UIStrings.addAShortcut);
|
||
addShortcutLink.tabIndex = 0;
|
||
UI.ARIAUtils.markAsLink(addShortcutLink);
|
||
self.onInvokeElement(addShortcutLink, this._addShortcut.bind(this));
|
||
if (!this._elementToFocus) {
|
||
this._elementToFocus = addShortcutLink;
|
||
}
|
||
|
||
this._errorMessageElement = this.element.createChild('div', 'keybinds-info keybinds-error hidden');
|
||
UI.ARIAUtils.markAsAlert(this._errorMessageElement);
|
||
this.element.appendChild(this._createIconButton(
|
||
i18nString(UIStrings.ResetShortcutsForAction), 'largeicon-undo', '',
|
||
this._resetShortcutsToDefaults.bind(this)));
|
||
this._confirmButton = this._createIconButton(
|
||
i18nString(UIStrings.confirmChanges), 'largeicon-checkmark', 'keybinds-confirm-button',
|
||
() => this._settingsTab.commitChanges(this._item, this._editedShortcuts));
|
||
this.element.appendChild(this._confirmButton);
|
||
this.element.appendChild(this._createIconButton(
|
||
i18nString(UIStrings.discardChanges), 'largeicon-delete', 'keybinds-cancel-button',
|
||
() => this._settingsTab.stopEditing(this._item)));
|
||
this.element.addEventListener('keydown', event => {
|
||
if (isEscKey(event)) {
|
||
this._settingsTab.stopEditing(this._item);
|
||
event.consume(true);
|
||
}
|
||
});
|
||
}
|
||
|
||
_addShortcut() {
|
||
const shortcut =
|
||
new UI.KeyboardShortcut.KeyboardShortcut([], this._item.id(), UI.KeyboardShortcut.Type.UnsetShortcut);
|
||
this._shortcuts.push(shortcut);
|
||
this._update();
|
||
const shortcutInput = /** @type {!HTMLElement} */ (this._shortcutInputs.get(shortcut));
|
||
if (shortcutInput) {
|
||
shortcutInput.focus();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!UI.KeyboardShortcut.KeyboardShortcut} shortcut
|
||
* @param {number=} index
|
||
*/
|
||
_createShortcutRow(shortcut, index) {
|
||
if (this._editedShortcuts.has(shortcut) && !this._editedShortcuts.get(shortcut)) {
|
||
return;
|
||
}
|
||
/** @type {!UI.Icon.Icon} */
|
||
let icon;
|
||
if (shortcut.type !== UI.KeyboardShortcut.Type.UnsetShortcut && !shortcut.isDefault()) {
|
||
icon = UI.Icon.Icon.create('largeicon-shortcut-changed', 'keybinds-modified');
|
||
UI.ARIAUtils.setAccessibleName(icon, i18nString(UIStrings.shortcutModified));
|
||
this.element.appendChild(icon);
|
||
}
|
||
const shortcutElement = this.element.createChild('div', 'keybinds-shortcut keybinds-list-text');
|
||
if (this._isEditing) {
|
||
const shortcutInput = /** @type {!HTMLInputElement} */ (shortcutElement.createChild('input', 'harmony-input'));
|
||
shortcutInput.spellcheck = false;
|
||
this._shortcutInputs.set(shortcut, shortcutInput);
|
||
if (!this._elementToFocus) {
|
||
this._elementToFocus = shortcutInput;
|
||
}
|
||
shortcutInput.value = shortcut.title();
|
||
const userDescriptors = this._editedShortcuts.get(shortcut);
|
||
if (userDescriptors) {
|
||
shortcutInput.value = this._shortcutInputTextForDescriptors(userDescriptors);
|
||
}
|
||
shortcutInput.addEventListener('keydown', this._onShortcutInputKeyDown.bind(this, shortcut, shortcutInput));
|
||
shortcutInput.addEventListener('blur', () => {
|
||
if (this._secondKeyTimeout !== null) {
|
||
clearTimeout(this._secondKeyTimeout);
|
||
this._secondKeyTimeout = null;
|
||
}
|
||
});
|
||
shortcutElement.appendChild(this._createIconButton(
|
||
i18nString(UIStrings.removeShortcut), 'largeicon-trash-bin', 'keybinds-delete-button', () => {
|
||
const index = this._shortcuts.indexOf(shortcut);
|
||
if (!shortcut.isDefault()) {
|
||
this._shortcuts.splice(index, 1);
|
||
}
|
||
this._editedShortcuts.set(shortcut, null);
|
||
this._update();
|
||
this.focus();
|
||
this._validateInputs();
|
||
}));
|
||
} else {
|
||
const keys = shortcut.descriptors.flatMap(descriptor => descriptor.name.split(' + '));
|
||
keys.forEach(key => {
|
||
shortcutElement.createChild('span', 'keybinds-key').textContent = key;
|
||
});
|
||
if (Root.Runtime.experiments.isEnabled('keyboardShortcutEditor') && index === 0) {
|
||
this.element.appendChild(this._createEditButton());
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @return {!Element}
|
||
*/
|
||
_createEditButton() {
|
||
return this._createIconButton(
|
||
i18nString(UIStrings.editShortcut), 'largeicon-edit', 'keybinds-edit-button',
|
||
() => this._settingsTab.startEditing(this._item));
|
||
}
|
||
|
||
/**
|
||
* @param {string} label
|
||
* @param {string} iconName
|
||
* @param {string} className
|
||
* @param {function():void} listener
|
||
* @return {!HTMLButtonElement}
|
||
*/
|
||
_createIconButton(label, iconName, className, listener) {
|
||
const button = /** @type {!HTMLButtonElement}*/ (document.createElement('button'));
|
||
button.appendChild(UI.Icon.Icon.create(iconName));
|
||
button.addEventListener('click', listener);
|
||
UI.ARIAUtils.setAccessibleName(button, label);
|
||
if (className) {
|
||
button.classList.add(className);
|
||
}
|
||
return button;
|
||
}
|
||
|
||
/**
|
||
* @param {!UI.KeyboardShortcut.KeyboardShortcut} shortcut
|
||
* @param {!HTMLInputElement} shortcutInput
|
||
* @param {!Event} event
|
||
*/
|
||
_onShortcutInputKeyDown(shortcut, shortcutInput, event) {
|
||
if (/** @type {!KeyboardEvent} */ (event).key !== 'Tab') {
|
||
const eventDescriptor = this._descriptorForEvent(/** @type {!KeyboardEvent} */ (event));
|
||
const userDescriptors = this._editedShortcuts.get(shortcut) || [];
|
||
this._editedShortcuts.set(shortcut, userDescriptors);
|
||
const isLastKeyOfShortcut =
|
||
userDescriptors.length === 2 && UI.KeyboardShortcut.KeyboardShortcut.isModifier(userDescriptors[1].key);
|
||
const shouldClearOldShortcut = userDescriptors.length === 2 && !isLastKeyOfShortcut;
|
||
if (shouldClearOldShortcut) {
|
||
userDescriptors.splice(0, 2);
|
||
}
|
||
|
||
if (this._secondKeyTimeout) {
|
||
clearTimeout(this._secondKeyTimeout);
|
||
this._secondKeyTimeout = null;
|
||
userDescriptors.push(eventDescriptor);
|
||
} else if (isLastKeyOfShortcut) {
|
||
userDescriptors[1] = eventDescriptor;
|
||
} else if (!UI.KeyboardShortcut.KeyboardShortcut.isModifier(eventDescriptor.key)) {
|
||
userDescriptors[0] = eventDescriptor;
|
||
this._secondKeyTimeout = window.setTimeout(() => {
|
||
this._secondKeyTimeout = null;
|
||
}, UI.ShortcutRegistry.KeyTimeout);
|
||
} else {
|
||
userDescriptors[0] = eventDescriptor;
|
||
}
|
||
shortcutInput.value = this._shortcutInputTextForDescriptors(userDescriptors);
|
||
this._validateInputs();
|
||
event.consume(true);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param {!KeyboardEvent} event
|
||
*/
|
||
_descriptorForEvent(event) {
|
||
const userKey = UI.KeyboardShortcut.KeyboardShortcut.makeKeyFromEvent(/** @type {!KeyboardEvent} */ (event));
|
||
const codeAndModifiers = UI.KeyboardShortcut.KeyboardShortcut.keyCodeAndModifiersFromKey(userKey);
|
||
const key = UI.KeyboardShortcut.Keys[event.key] || UI.KeyboardShortcut.KeyBindings[event.key];
|
||
return UI.KeyboardShortcut.KeyboardShortcut.makeDescriptor(key || event.key, codeAndModifiers.modifiers);
|
||
}
|
||
|
||
/**
|
||
* @param {!Array.<!UI.KeyboardShortcut.Descriptor>} descriptors
|
||
* @return {string}
|
||
*/
|
||
_shortcutInputTextForDescriptors(descriptors) {
|
||
return descriptors.map(descriptor => descriptor.name).join(' ');
|
||
}
|
||
|
||
_resetShortcutsToDefaults() {
|
||
this._editedShortcuts.clear();
|
||
for (const shortcut of this._shortcuts) {
|
||
if (shortcut.type === UI.KeyboardShortcut.Type.UnsetShortcut) {
|
||
const index = this._shortcuts.indexOf(shortcut);
|
||
this._shortcuts.splice(index, 1);
|
||
} else if (shortcut.type === UI.KeyboardShortcut.Type.UserShortcut) {
|
||
this._editedShortcuts.set(shortcut, null);
|
||
}
|
||
}
|
||
const disabledDefaults = UI.ShortcutRegistry.ShortcutRegistry.instance().disabledDefaultsForAction(this._item.id());
|
||
disabledDefaults.forEach(shortcut => {
|
||
this._shortcuts.push(shortcut);
|
||
this._editedShortcuts.set(shortcut, shortcut.descriptors);
|
||
});
|
||
this._update();
|
||
this.focus();
|
||
}
|
||
|
||
/**
|
||
* @param {!Event} event
|
||
*/
|
||
onEscapeKeyPressed(event) {
|
||
const activeElement = document.deepActiveElement();
|
||
for (const [shortcut, shortcutInput] of this._shortcutInputs.entries()) {
|
||
if (activeElement === shortcutInput) {
|
||
this._onShortcutInputKeyDown(
|
||
/** @type {!UI.KeyboardShortcut.KeyboardShortcut} */ (shortcut),
|
||
/** @type {!HTMLInputElement} */ (shortcutInput), /** @type {!KeyboardEvent} */ (event));
|
||
}
|
||
}
|
||
}
|
||
|
||
_validateInputs() {
|
||
const confirmButton = this._confirmButton;
|
||
const errorMessageElement = this._errorMessageElement;
|
||
if (!confirmButton || !errorMessageElement) {
|
||
return;
|
||
}
|
||
|
||
confirmButton.disabled = false;
|
||
errorMessageElement.classList.add('hidden');
|
||
this._shortcutInputs.forEach((shortcutInput, shortcut) => {
|
||
const userDescriptors = this._editedShortcuts.get(shortcut);
|
||
if (!userDescriptors) {
|
||
return;
|
||
}
|
||
if (userDescriptors.some(descriptor => UI.KeyboardShortcut.KeyboardShortcut.isModifier(descriptor.key))) {
|
||
confirmButton.disabled = true;
|
||
shortcutInput.classList.add('error-input');
|
||
UI.ARIAUtils.setInvalid(shortcutInput, true);
|
||
errorMessageElement.classList.remove('hidden');
|
||
errorMessageElement.textContent = i18nString(UIStrings.shortcutsCannotContainOnly);
|
||
return;
|
||
}
|
||
const conflicts = UI.ShortcutRegistry.ShortcutRegistry.instance()
|
||
.actionsForDescriptors(userDescriptors)
|
||
.filter(actionId => actionId !== this._item.id());
|
||
if (conflicts.length) {
|
||
confirmButton.disabled = true;
|
||
shortcutInput.classList.add('error-input');
|
||
UI.ARIAUtils.setInvalid(shortcutInput, true);
|
||
errorMessageElement.classList.remove('hidden');
|
||
const action = UI.ActionRegistry.ActionRegistry.instance().action(conflicts[0]);
|
||
if (!action) {
|
||
return;
|
||
}
|
||
const actionTitle = action.title();
|
||
const actionCategory = action.category();
|
||
errorMessageElement.textContent =
|
||
i18nString(UIStrings.thisShortcutIsInUseByS, {PH1: actionCategory, PH2: actionTitle});
|
||
return;
|
||
}
|
||
shortcutInput.classList.remove('error-input');
|
||
UI.ARIAUtils.setInvalid(shortcutInput, false);
|
||
});
|
||
}
|
||
}
|
||
|
||
/** @typedef {string|!UI.ActionRegistration.Action} */
|
||
// @ts-ignore typedef
|
||
export let KeybindsItem;
|