| // Copyright (C) 2020 The Android Open Source Project |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| |
| import * as m from 'mithril'; |
| |
| import {Actions} from '../common/actions'; |
| |
| import {globals} from './globals'; |
| import {createPage} from './pages'; |
| import {QueryTable} from './query_table'; |
| |
| const INPUT_PLACEHOLDER = 'Enter query and press Cmd/Ctrl + Enter'; |
| const INPUT_MIN_LINES = 2; |
| const INPUT_MAX_LINES = 10; |
| const INPUT_LINE_HEIGHT_EM = 1.2; |
| const TAB_SPACES = 2; |
| const QUERY_ID = 'analyze-page-query'; |
| |
| class QueryInput implements m.ClassComponent { |
| // How many lines to display if the user hasn't resized the input box. |
| displayLines = INPUT_MIN_LINES; |
| |
| static onKeyDown(e: Event) { |
| const event = e as KeyboardEvent; |
| const target = e.target as HTMLTextAreaElement; |
| |
| if (event.code === 'Enter' && (event.metaKey || event.ctrlKey)) { |
| event.preventDefault(); |
| const query = target.value; |
| if (!query) return; |
| globals.dispatch( |
| Actions.executeQuery({engineId: '0', queryId: QUERY_ID, query})); |
| } |
| |
| if (event.code === 'Tab') { |
| // Handle tabs to insert spaces. |
| event.preventDefault(); |
| const whitespace = ' '.repeat(TAB_SPACES); |
| const {selectionStart, selectionEnd} = target; |
| target.value = target.value.substring(0, selectionStart) + whitespace + |
| target.value.substring(selectionEnd); |
| target.selectionEnd = selectionStart + TAB_SPACES; |
| } |
| } |
| |
| onInput(textareaValue: string) { |
| const textareaLines = textareaValue.split('\n').length; |
| const clampedNumLines = |
| Math.min(Math.max(textareaLines, INPUT_MIN_LINES), INPUT_MAX_LINES); |
| this.displayLines = clampedNumLines; |
| globals.dispatch(Actions.setAnalyzePageQuery({query: textareaValue})); |
| globals.rafScheduler.scheduleFullRedraw(); |
| } |
| |
| // This method exists because unfortunatley setting custom properties on an |
| // element's inline style attribue doesn't seem to work in mithril, even |
| // though the docs claim so. |
| setHeightBeforeResize(node: HTMLElement) { |
| // +2em for some extra breathing space to account for padding. |
| const heightEm = this.displayLines * INPUT_LINE_HEIGHT_EM + 2; |
| // We set a height based on the number of lines that we want to display by |
| // default. If the user resizes the textbox using the resize handle in the |
| // bottom-right corner, this height is overridden. |
| node.style.setProperty('--height-before-resize', `${heightEm}em`); |
| // TODO(dproy): The resized height is lost if user navigates away from the |
| // page and comes back. |
| } |
| |
| oncreate(vnode: m.VnodeDOM) { |
| // This makes sure query persists if user navigates to other pages and comes |
| // back to analyze page. |
| const existingQuery = globals.state.analyzePageQuery; |
| const textarea = vnode.dom as HTMLTextAreaElement; |
| if (existingQuery) { |
| textarea.value = existingQuery; |
| this.onInput(existingQuery); |
| } |
| |
| this.setHeightBeforeResize(textarea); |
| } |
| |
| onupdate(vnode: m.VnodeDOM) { |
| this.setHeightBeforeResize(vnode.dom as HTMLElement); |
| } |
| |
| view() { |
| return m('textarea.query-input', { |
| placeholder: INPUT_PLACEHOLDER, |
| onkeydown: (e: Event) => QueryInput.onKeyDown(e), |
| oninput: (e: Event) => |
| this.onInput((e.target as HTMLTextAreaElement).value), |
| }); |
| } |
| } |
| |
| |
| export const AnalyzePage = createPage({ |
| view() { |
| return m( |
| '.analyze-page', |
| m(QueryInput), |
| m(QueryTable, {queryId: QUERY_ID}), |
| ); |
| } |
| }); |