// Copyright (C) 2019 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 m from 'mithril'; import {showModal} from '../widgets/modal'; import {Spinner} from '../widgets/spinner'; import { KeyboardLayoutMap, nativeKeyboardLayoutMap, NotSupportedError, } from './keyboard_layout_map'; import {KeyMapping} from './pan_and_zoom_handler'; import {HotkeyGlyphs} from '../widgets/hotkey_glyphs'; import {assertExists} from '../base/logging'; import {AppImpl} from '../core/app_impl'; export function toggleHelp() { AppImpl.instance.analytics.logEvent('User Actions', 'Show help'); showModal({ title: 'Perfetto Help', content: () => m(KeyMappingsHelp), buttons: [], }); } function keycap(glyph: m.Children): m.Children { return m('.keycap', glyph); } // A fallback keyboard map based on the QWERTY keymap. Converts keyboard event // codes to their associated glyphs on an English QWERTY keyboard. class EnglishQwertyKeyboardLayoutMap implements KeyboardLayoutMap { get(code: string): string { // Converts 'KeyX' -> 'x' return code.replace(/^Key([A-Z])$/, '$1').toLowerCase(); } } class KeyMappingsHelp implements m.ClassComponent { private keyMap?: KeyboardLayoutMap; oninit() { nativeKeyboardLayoutMap() .then((keyMap: KeyboardLayoutMap) => { this.keyMap = keyMap; AppImpl.instance.scheduleFullRedraw('force'); }) .catch((e) => { if ( e instanceof NotSupportedError || String(e).includes('SecurityError') ) { // Keyboard layout is unavailable. Since showing the keyboard // mappings correct for the user's keyboard layout is a nice-to- // have, and users with non-QWERTY layouts are usually aware of the // fact that the are using non-QWERTY layouts, we resort to showing // English QWERTY mappings as a best-effort approach. // The alternative would be to show key mappings for all keyboard // layouts which is not feasible. this.keyMap = new EnglishQwertyKeyboardLayoutMap(); AppImpl.instance.scheduleFullRedraw('force'); } else { // Something unexpected happened. Either the browser doesn't conform // to the keyboard API spec, or the keyboard API spec has changed! throw e; } }); } view(): m.Children { return m( '.help', m('h2', 'Navigation'), m( 'table', m( 'tr', m( 'td', this.codeToKeycap(KeyMapping.KEY_ZOOM_IN), '/', this.codeToKeycap(KeyMapping.KEY_ZOOM_OUT), ), m('td', 'Zoom in/out'), ), m( 'tr', m( 'td', this.codeToKeycap(KeyMapping.KEY_PAN_LEFT), '/', this.codeToKeycap(KeyMapping.KEY_PAN_RIGHT), ), m('td', 'Pan left/right'), ), ), m('h2', 'Mouse Controls'), m( 'table', m('tr', m('td', 'Click'), m('td', 'Select event')), m('tr', m('td', 'Ctrl + Scroll wheel'), m('td', 'Zoom in/out')), m('tr', m('td', 'Click + Drag'), m('td', 'Select area')), m('tr', m('td', 'Shift + Click + Drag'), m('td', 'Pan left/right')), ), m('h2', 'Running commands from the viewer page'), m( 'table', m( 'tr', m('td', keycap('>'), ' in the (empty) search box'), m('td', 'Switch to command mode'), ), ), m('h2', 'Making SQL queries from the viewer page'), m( 'table', m( 'tr', m('td', keycap(':'), ' in the (empty) search box'), m('td', 'Switch to query mode'), ), m('tr', m('td', keycap('Enter')), m('td', 'Execute query')), m( 'tr', m('td', keycap('Ctrl'), ' + ', keycap('Enter')), m( 'td', 'Execute query and pin output ' + '(output will not be replaced by regular query input)', ), ), ), m('h2', 'Making SQL queries from the query page'), m( 'table', m( 'tr', m('td', keycap('Ctrl'), ' + ', keycap('Enter')), m('td', 'Execute query'), ), m( 'tr', m('td', keycap('Ctrl'), ' + ', keycap('Enter'), ' (with selection)'), m('td', 'Execute selection'), ), ), m('h2', 'Command Hotkeys'), m( 'table', AppImpl.instance.commands.commands .filter(({defaultHotkey}) => defaultHotkey) .sort((a, b) => a.name.localeCompare(b.name)) .map(({defaultHotkey, name}) => { return m( 'tr', m('td', m(HotkeyGlyphs, {hotkey: assertExists(defaultHotkey)})), m('td', name), ); }), ), ); } private codeToKeycap(code: string): m.Children { if (this.keyMap) { return keycap(this.keyMap.get(code)); } else { return keycap(m(Spinner)); } } }