blob: 6e1acdb73f1bd08762b94a2a4d12ed315e2fd12d [file] [log] [blame]
// Copyright (C) 2018 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, deleteQuery} from '../common/actions';
import {QueryResponse} from '../common/queries';
import {EngineConfig} from '../common/state';
import {globals} from './globals';
const QUERY_ID = 'quicksearch';
let selResult = 0;
let numResults = 0;
let mode: 'search'|'command' = 'search';
function clearOmniboxResults() {
globals.queryResults.delete(QUERY_ID);
globals.dispatch(deleteQuery(QUERY_ID));
}
function onKeyDown(e: Event) {
e.stopPropagation();
const key = (e as KeyboardEvent).key;
// Avoid that the global 'a', 'd', 'w', 's' handler sees these keystrokes.
// TODO: this seems a bug in the pan_and_zoom_handler.ts.
if (key === 'ArrowUp' || key === 'ArrowDown') {
e.preventDefault();
return;
}
const txt = (e.target as HTMLInputElement);
if (key === ':' && txt.value === '') {
mode = 'command';
globals.rafScheduler.scheduleFullRedraw();
e.preventDefault();
return;
}
if (key === 'Escape' && mode === 'command') {
txt.value = '';
mode = 'search';
globals.rafScheduler.scheduleFullRedraw();
return;
}
if (key === 'Backspace' && txt.value.length === 0 && mode === 'command') {
mode = 'search';
globals.rafScheduler.scheduleFullRedraw();
return;
}
}
function onKeyUp(e: Event) {
e.stopPropagation();
const key = (e as KeyboardEvent).key;
const txt = e.target as HTMLInputElement;
if (key === 'ArrowUp' || key === 'ArrowDown') {
selResult += (key === 'ArrowUp') ? -1 : 1;
selResult = Math.max(selResult, 0);
selResult = Math.min(selResult, numResults - 1);
e.preventDefault();
globals.rafScheduler.scheduleFullRedraw();
return;
}
if (txt.value.length <= 0 || key === 'Escape') {
clearOmniboxResults();
globals.rafScheduler.scheduleFullRedraw();
return;
}
if (mode === 'search') {
const name = txt.value.replace(/'/g, '\\\'').replace(/[*]/g, '%');
const query = `select str from strings where str like '%${name}%' limit 10`;
globals.dispatch(
Actions.executeQuery({engineId: '0', queryId: QUERY_ID, query}));
}
if (mode === 'command' && key === 'Enter') {
globals.dispatch(Actions.executeQuery(
{engineId: '0', queryId: 'command', query: txt.value}));
}
}
class Omnibox implements m.ClassComponent {
oncreate(vnode: m.VnodeDOM) {
const txt = vnode.dom.querySelector('input') as HTMLInputElement;
txt.addEventListener('blur', clearOmniboxResults);
txt.addEventListener('keydown', onKeyDown);
txt.addEventListener('keyup', onKeyUp);
}
view() {
const msgTTL = globals.state.status.timestamp + 3 - Date.now() / 1e3;
let enginesAreBusy = false;
for (const engine of Object.values(globals.state.engines)) {
enginesAreBusy = enginesAreBusy || !engine.ready;
}
if (msgTTL > 0 || enginesAreBusy) {
setTimeout(
() => globals.rafScheduler.scheduleFullRedraw(), msgTTL * 1000);
return m(
`.omnibox.message-mode`,
m(`input[placeholder=${globals.state.status.msg}][readonly]`));
}
// TODO(primiano): handle query results here.
const results = [];
const resp = globals.queryResults.get(QUERY_ID) as QueryResponse;
if (resp !== undefined) {
numResults = resp.rows ? resp.rows.length : 0;
for (let i = 0; i < resp.rows.length; i++) {
const clazz = (i === selResult) ? '.selected' : '';
results.push(m(`div${clazz}`, resp.rows[i][resp.columns[0]]));
}
}
const placeholder = {
search: 'Search or type : to enter command mode',
command: 'e.g., select * from sched left join thread using(utid) limit 10'
};
const commandMode = mode === 'command';
return m(
`.omnibox${commandMode ? '.command-mode' : ''}`,
m(`input[placeholder=${placeholder[mode]}]`),
m('.omnibox-results', results));
}
}
export class Topbar implements m.ClassComponent {
view() {
const progBar = [];
const engine: EngineConfig = globals.state.engines['0'];
if (globals.state.queries[QUERY_ID] !== undefined ||
(engine !== undefined && !engine.ready)) {
progBar.push(m('.progress'));
}
return m('.topbar', m(Omnibox), ...progBar);
}
}