mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-10 13:44:39 +08:00
Move and rename files. NOAUTOREVERT=true NOPRESUBMIT=true NOTREECHECKS=true Bug: 768828 TBR=darin@chromium.org NOTRY=true Change-Id: I66d3b155808bc5bdbf237b80208e1e552bcf7f28 Reviewed-on: https://chromium-review.googlesource.com/1001153 Reviewed-by: Blink Reformat <blink-reformat@chromium.org> Commit-Queue: Blink Reformat <blink-reformat@chromium.org> Cr-Original-Commit-Position: refs/heads/master@{#549061} Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src Cr-Mirrored-Commit: 0aee4434a4dba42a42abaea9bfbc0cd196a63bc1
422 lines
12 KiB
JavaScript
422 lines
12 KiB
JavaScript
// Copyright 2015 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.
|
|
/**
|
|
* @template T
|
|
*/
|
|
UI.ListWidget = class extends UI.VBox {
|
|
/**
|
|
* @param {!UI.ListWidget.Delegate<T>} delegate
|
|
*/
|
|
constructor(delegate) {
|
|
super(true);
|
|
this.registerRequiredCSS('ui/listWidget.css');
|
|
this._delegate = delegate;
|
|
|
|
this._list = this.contentElement.createChild('div', 'list');
|
|
this.element.tabIndex = -1;
|
|
|
|
this._lastSeparator = false;
|
|
/** @type {?UI.ElementFocusRestorer} */
|
|
this._focusRestorer = null;
|
|
/** @type {!Array<T>} */
|
|
this._items = [];
|
|
/** @type {!Array<boolean>} */
|
|
this._editable = [];
|
|
/** @type {!Array<!Element>} */
|
|
this._elements = [];
|
|
/** @type {?UI.ListWidget.Editor<T>} */
|
|
this._editor = null;
|
|
/** @type {?T} */
|
|
this._editItem = null;
|
|
/** @type {?Element} */
|
|
this._editElement = null;
|
|
|
|
/** @type {?Element} */
|
|
this._emptyPlaceholder = null;
|
|
|
|
this._updatePlaceholder();
|
|
}
|
|
|
|
clear() {
|
|
this._items = [];
|
|
this._editable = [];
|
|
this._elements = [];
|
|
this._lastSeparator = false;
|
|
this._list.removeChildren();
|
|
this._updatePlaceholder();
|
|
this._stopEditing();
|
|
}
|
|
|
|
/**
|
|
* @param {!T} item
|
|
* @param {boolean} editable
|
|
*/
|
|
appendItem(item, editable) {
|
|
if (this._lastSeparator && this._items.length)
|
|
this._list.appendChild(createElementWithClass('div', 'list-separator'));
|
|
this._lastSeparator = false;
|
|
|
|
this._items.push(item);
|
|
this._editable.push(editable);
|
|
|
|
const element = this._list.createChild('div', 'list-item');
|
|
element.appendChild(this._delegate.renderItem(item, editable));
|
|
if (editable) {
|
|
element.classList.add('editable');
|
|
element.appendChild(this._createControls(item, element));
|
|
}
|
|
this._elements.push(element);
|
|
this._updatePlaceholder();
|
|
}
|
|
|
|
appendSeparator() {
|
|
this._lastSeparator = true;
|
|
}
|
|
|
|
/**
|
|
* @param {number} index
|
|
*/
|
|
removeItem(index) {
|
|
if (this._editItem === this._items[index])
|
|
this._stopEditing();
|
|
|
|
const element = this._elements[index];
|
|
|
|
const previous = element.previousElementSibling;
|
|
const previousIsSeparator = previous && previous.classList.contains('list-separator');
|
|
|
|
const next = element.nextElementSibling;
|
|
const nextIsSeparator = next && next.classList.contains('list-separator');
|
|
|
|
if (previousIsSeparator && (nextIsSeparator || !next))
|
|
previous.remove();
|
|
if (nextIsSeparator && !previous)
|
|
next.remove();
|
|
element.remove();
|
|
|
|
this._elements.splice(index, 1);
|
|
this._items.splice(index, 1);
|
|
this._editable.splice(index, 1);
|
|
this._updatePlaceholder();
|
|
}
|
|
|
|
/**
|
|
* @param {number} index
|
|
* @param {!T} item
|
|
*/
|
|
addNewItem(index, item) {
|
|
this._startEditing(item, null, this._elements[index] || null);
|
|
}
|
|
|
|
/**
|
|
* @param {?Element} element
|
|
*/
|
|
setEmptyPlaceholder(element) {
|
|
this._emptyPlaceholder = element;
|
|
this._updatePlaceholder();
|
|
}
|
|
|
|
/**
|
|
* @param {!T} item
|
|
* @param {!Element} element
|
|
* @return {!Element}
|
|
*/
|
|
_createControls(item, element) {
|
|
const controls = createElementWithClass('div', 'controls-container fill');
|
|
controls.createChild('div', 'controls-gradient');
|
|
|
|
const buttons = controls.createChild('div', 'controls-buttons');
|
|
|
|
const toolbar = new UI.Toolbar('', buttons);
|
|
|
|
const editButton = new UI.ToolbarButton(Common.UIString('Edit'), 'largeicon-edit');
|
|
editButton.addEventListener(UI.ToolbarButton.Events.Click, onEditClicked.bind(this));
|
|
toolbar.appendToolbarItem(editButton);
|
|
|
|
const removeButton = new UI.ToolbarButton(Common.UIString('Remove'), 'largeicon-trash-bin');
|
|
removeButton.addEventListener(UI.ToolbarButton.Events.Click, onRemoveClicked.bind(this));
|
|
toolbar.appendToolbarItem(removeButton);
|
|
|
|
return controls;
|
|
|
|
/**
|
|
* @this {UI.ListWidget}
|
|
*/
|
|
function onEditClicked() {
|
|
const index = this._elements.indexOf(element);
|
|
const insertionPoint = this._elements[index + 1] || null;
|
|
this._startEditing(item, element, insertionPoint);
|
|
}
|
|
|
|
/**
|
|
* @this {UI.ListWidget}
|
|
*/
|
|
function onRemoveClicked() {
|
|
const index = this._elements.indexOf(element);
|
|
this.element.focus();
|
|
this._delegate.removeItemRequested(this._items[index], index);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @override
|
|
*/
|
|
wasShown() {
|
|
super.wasShown();
|
|
this._stopEditing();
|
|
}
|
|
|
|
_updatePlaceholder() {
|
|
if (!this._emptyPlaceholder)
|
|
return;
|
|
|
|
if (!this._elements.length && !this._editor)
|
|
this._list.appendChild(this._emptyPlaceholder);
|
|
else
|
|
this._emptyPlaceholder.remove();
|
|
}
|
|
|
|
/**
|
|
* @param {!T} item
|
|
* @param {?Element} element
|
|
* @param {?Element} insertionPoint
|
|
*/
|
|
_startEditing(item, element, insertionPoint) {
|
|
if (element && this._editElement === element)
|
|
return;
|
|
|
|
this._stopEditing();
|
|
this._focusRestorer = new UI.ElementFocusRestorer(this.element);
|
|
|
|
this._list.classList.add('list-editing');
|
|
this._editItem = item;
|
|
this._editElement = element;
|
|
if (element)
|
|
element.classList.add('hidden');
|
|
|
|
const index = element ? this._elements.indexOf(element) : -1;
|
|
this._editor = this._delegate.beginEdit(item);
|
|
this._updatePlaceholder();
|
|
this._list.insertBefore(this._editor.element, insertionPoint);
|
|
this._editor.beginEdit(
|
|
item, index, element ? Common.UIString('Save') : Common.UIString('Add'), this._commitEditing.bind(this),
|
|
this._stopEditing.bind(this));
|
|
}
|
|
|
|
_commitEditing() {
|
|
const editItem = this._editItem;
|
|
const isNew = !this._editElement;
|
|
const editor = /** @type {!UI.ListWidget.Editor<T>} */ (this._editor);
|
|
this._stopEditing();
|
|
this._delegate.commitEdit(editItem, editor, isNew);
|
|
}
|
|
|
|
_stopEditing() {
|
|
this._list.classList.remove('list-editing');
|
|
if (this._focusRestorer)
|
|
this._focusRestorer.restore();
|
|
if (this._editElement)
|
|
this._editElement.classList.remove('hidden');
|
|
if (this._editor && this._editor.element.parentElement)
|
|
this._editor.element.remove();
|
|
|
|
this._editor = null;
|
|
this._editItem = null;
|
|
this._editElement = null;
|
|
this._updatePlaceholder();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* @template T
|
|
* @interface
|
|
*/
|
|
UI.ListWidget.Delegate = function() {};
|
|
|
|
UI.ListWidget.Delegate.prototype = {
|
|
/**
|
|
* @param {!T} item
|
|
* @param {boolean} editable
|
|
* @return {!Element}
|
|
*/
|
|
renderItem(item, editable) {},
|
|
|
|
/**
|
|
* @param {!T} item
|
|
* @param {number} index
|
|
*/
|
|
removeItemRequested(item, index) {},
|
|
|
|
/**
|
|
* @param {!T} item
|
|
* @return {!UI.ListWidget.Editor<T>}
|
|
*/
|
|
beginEdit(item) {},
|
|
|
|
/**
|
|
* @param {!T} item
|
|
* @param {!UI.ListWidget.Editor<T>} editor
|
|
* @param {boolean} isNew
|
|
*/
|
|
commitEdit(item, editor, isNew) {}
|
|
};
|
|
|
|
/**
|
|
* @template T
|
|
*/
|
|
UI.ListWidget.Editor = class {
|
|
constructor() {
|
|
this.element = createElementWithClass('div', 'editor-container');
|
|
this.element.addEventListener('keydown', onKeyDown.bind(null, isEscKey, this._cancelClicked.bind(this)), false);
|
|
this.element.addEventListener('keydown', onKeyDown.bind(null, isEnterKey, this._commitClicked.bind(this)), false);
|
|
|
|
this._contentElement = this.element.createChild('div', 'editor-content');
|
|
|
|
const buttonsRow = this.element.createChild('div', 'editor-buttons');
|
|
this._commitButton = UI.createTextButton('', this._commitClicked.bind(this), '', true /* primary */);
|
|
buttonsRow.appendChild(this._commitButton);
|
|
this._cancelButton = UI.createTextButton(Common.UIString('Cancel'), this._cancelClicked.bind(this));
|
|
this._cancelButton.addEventListener(
|
|
'keydown', onKeyDown.bind(null, isEnterKey, this._cancelClicked.bind(this)), false);
|
|
buttonsRow.appendChild(this._cancelButton);
|
|
|
|
/**
|
|
* @param {function(!Event):boolean} predicate
|
|
* @param {function()} callback
|
|
* @param {!Event} event
|
|
*/
|
|
function onKeyDown(predicate, callback, event) {
|
|
if (predicate(event)) {
|
|
event.consume(true);
|
|
callback();
|
|
}
|
|
}
|
|
|
|
/** @type {!Array<!HTMLInputElement|!HTMLSelectElement>} */
|
|
this._controls = [];
|
|
/** @type {!Map<string, !HTMLInputElement|!HTMLSelectElement>} */
|
|
this._controlByName = new Map();
|
|
/** @type {!Array<function(!T, number, (!HTMLInputElement|!HTMLSelectElement)):boolean>} */
|
|
this._validators = [];
|
|
|
|
/** @type {?function()} */
|
|
this._commit = null;
|
|
/** @type {?function()} */
|
|
this._cancel = null;
|
|
/** @type {?T} */
|
|
this._item = null;
|
|
/** @type {number} */
|
|
this._index = -1;
|
|
}
|
|
|
|
/**
|
|
* @return {!Element}
|
|
*/
|
|
contentElement() {
|
|
return this._contentElement;
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {string} type
|
|
* @param {string} title
|
|
* @param {function(!T, number, (!HTMLInputElement|!HTMLSelectElement)):boolean} validator
|
|
* @return {!HTMLInputElement}
|
|
*/
|
|
createInput(name, type, title, validator) {
|
|
const input = /** @type {!HTMLInputElement} */ (UI.createInput('', type));
|
|
input.placeholder = title;
|
|
input.addEventListener('input', this._validateControls.bind(this, false), false);
|
|
input.addEventListener('blur', this._validateControls.bind(this, false), false);
|
|
this._controlByName.set(name, input);
|
|
this._controls.push(input);
|
|
this._validators.push(validator);
|
|
return input;
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @param {!Array<string>} options
|
|
* @param {function(!T, number, (!HTMLInputElement|!HTMLSelectElement)):boolean} validator
|
|
* @return {!HTMLSelectElement}
|
|
*/
|
|
createSelect(name, options, validator) {
|
|
const select = /** @type {!HTMLSelectElement} */ (createElementWithClass('select', 'chrome-select'));
|
|
for (let index = 0; index < options.length; ++index) {
|
|
const option = select.createChild('option');
|
|
option.value = options[index];
|
|
option.textContent = options[index];
|
|
}
|
|
select.addEventListener('input', this._validateControls.bind(this, false), false);
|
|
select.addEventListener('blur', this._validateControls.bind(this, false), false);
|
|
this._controlByName.set(name, select);
|
|
this._controls.push(select);
|
|
this._validators.push(validator);
|
|
return select;
|
|
}
|
|
|
|
/**
|
|
* @param {string} name
|
|
* @return {!HTMLInputElement|!HTMLSelectElement}
|
|
*/
|
|
control(name) {
|
|
return /** @type {!HTMLInputElement|!HTMLSelectElement} */ (this._controlByName.get(name));
|
|
}
|
|
|
|
/**
|
|
* @param {boolean} forceValid
|
|
*/
|
|
_validateControls(forceValid) {
|
|
let allValid = true;
|
|
for (let index = 0; index < this._controls.length; ++index) {
|
|
const input = this._controls[index];
|
|
const valid = this._validators[index].call(null, this._item, this._index, input);
|
|
input.classList.toggle('error-input', !valid && !forceValid);
|
|
allValid &= valid;
|
|
}
|
|
this._commitButton.disabled = !allValid;
|
|
}
|
|
|
|
/**
|
|
* @param {!T} item
|
|
* @param {number} index
|
|
* @param {string} commitButtonTitle
|
|
* @param {function()} commit
|
|
* @param {function()} cancel
|
|
*/
|
|
beginEdit(item, index, commitButtonTitle, commit, cancel) {
|
|
this._commit = commit;
|
|
this._cancel = cancel;
|
|
this._item = item;
|
|
this._index = index;
|
|
|
|
this._commitButton.textContent = commitButtonTitle;
|
|
this.element.scrollIntoViewIfNeeded(false);
|
|
if (this._controls.length)
|
|
this._controls[0].focus();
|
|
this._validateControls(true);
|
|
}
|
|
|
|
_commitClicked() {
|
|
if (this._commitButton.disabled)
|
|
return;
|
|
|
|
const commit = this._commit;
|
|
this._commit = null;
|
|
this._cancel = null;
|
|
this._item = null;
|
|
this._index = -1;
|
|
commit();
|
|
}
|
|
|
|
_cancelClicked() {
|
|
const cancel = this._cancel;
|
|
this._commit = null;
|
|
this._cancel = null;
|
|
this._item = null;
|
|
this._index = -1;
|
|
cancel();
|
|
}
|
|
};
|