// 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 './icons.html.js'; import './option.js'; import 'chrome://resources/cr_elements/icons.html.js'; import 'chrome://resources/polymer/v3_0/iron-icon/iron-icon.js'; import 'chrome://resources/cr_elements/cr_shared_vars.css.js'; import {addWebUIListener} from 'chrome://resources/js/cr.m.js'; import {Debouncer, DomRepeatEvent, enqueueDebouncer, microTask, PolymerElement} from 'chrome://resources/polymer/v3_0/polymer/polymer_bundled.min.js'; import {getTemplate} from './app.html.js'; import {BrowserProxy, BrowserProxyImpl} from './browser_proxy.js'; import {Action, Option, ViewModel} from './types.js'; export interface CommanderAppElement { $: { input: HTMLInputElement, inputRow: HTMLElement, }; } export class CommanderAppElement extends PolymerElement { static get is() { return 'commander-app'; } static get template() { return getTemplate(); } static get properties() { return { options_: Array, focusedIndex_: { type: Number, notify: true, observer: 'onFocusedIndexChanged_', }, promptText_: String, resultSetId_: Number, }; } private options_: Option[]; private focusedIndex_: number; private promptText_: string|null; private browserProxy_: BrowserProxy; private resultSetId_: number|null; private savedInput_: string; private showNoResults_: boolean; private debouncer_: Debouncer|null; constructor() { super(); this.browserProxy_ = BrowserProxyImpl.getInstance(); } override ready() { super.ready(); addWebUIListener('view-model-updated', this.onViewModelUpdated_.bind(this)); addWebUIListener('initialize', this.initialize_.bind(this)); this.initialize_(); } /** * Resets the UI for a new session. */ private initialize_() { this.options_ = []; this.$.input.value = ''; this.$.input.focus(); this.focusedIndex_ = -1; this.resultSetId_ = null; this.promptText_ = null; this.savedInput_ = ''; this.showNoResults_ = false; } private onKeydown_(e: KeyboardEvent) { if (e.key === 'Escape') { this.browserProxy_.dismiss(); return; } if (e.key === 'ArrowUp') { e.preventDefault(); this.focusedIndex_ = (this.focusedIndex_ + this.options_.length - 1) % this.options_.length; } else if (e.key === 'ArrowDown') { e.preventDefault(); this.focusedIndex_ = (this.focusedIndex_ + 1) % this.options_.length; } else if (e.key === 'Enter') { if (this.focusedIndex_ >= 0 && this.focusedIndex_ < this.options_.length) { this.notifySelectedAtIndex_(this.focusedIndex_); } } else if ( this.promptText_ && e.key === 'Backspace' && this.$.input.value === '') { this.browserProxy_.promptCancelled(); this.promptText_ = null; this.$.input.value = this.savedInput_; e.preventDefault(); this.onInput_(); } } private onInput_() { this.browserProxy_.textChanged(this.$.input.value); } private onViewModelUpdated_(viewModel: ViewModel) { if (viewModel.action === Action.DISPLAY_RESULTS) { this.options_ = viewModel.options || []; this.resultSetId_ = viewModel.resultSetId; this.showNoResults_ = this.resultSetId_ != null && this.$.input.value !== '' && this.options_.length === 0; if (this.options_.length > 0) { this.focusedIndex_ = 0; } } else if (viewModel.action === Action.PROMPT) { this.showNoResults_ = false; this.options_ = []; this.resultSetId_ = viewModel.resultSetId; this.promptText_ = viewModel.promptText || null; this.savedInput_ = this.$.input.value; this.$.input.value = ''; this.onInput_(); } } private onDomChange_() { this.debouncer_ = Debouncer.debounce(this.debouncer_, microTask, () => { this.browserProxy_.heightChanged(document.body.offsetHeight); }); enqueueDebouncer(this.debouncer_); } /** * Called when a result option is clicked via mouse. */ private onOptionClick_(e: DomRepeatEvent