1// Copyright (C) 2018 The Android Open Source Project 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15import m from 'mithril'; 16 17import {classNames} from '../base/classnames'; 18import {raf} from '../core/raf_scheduler'; 19 20import {globals} from './globals'; 21import {taskTracker} from './task_tracker'; 22 23export const DISMISSED_PANNING_HINT_KEY = 'dismissedPanningHint'; 24 25class Progress implements m.ClassComponent { 26 view(_vnode: m.Vnode): m.Children { 27 const classes = classNames(this.isLoading() && 'progress-anim'); 28 return m('.progress', {class: classes}); 29 } 30 31 private isLoading(): boolean { 32 const engine = globals.getCurrentEngine(); 33 return ( 34 (engine && !engine.ready) || 35 globals.numQueuedQueries > 0 || 36 taskTracker.hasPendingTasks() 37 ); 38 } 39} 40 41class HelpPanningNotification implements m.ClassComponent { 42 view() { 43 const dismissed = localStorage.getItem(DISMISSED_PANNING_HINT_KEY); 44 // Do not show the help notification in embedded mode because local storage 45 // does not persist for iFrames. The host is responsible for communicating 46 // to users that they can press '?' for help. 47 if ( 48 globals.embeddedMode || 49 dismissed === 'true' || 50 !globals.showPanningHint 51 ) { 52 return; 53 } 54 return m( 55 '.helpful-hint', 56 m( 57 '.hint-text', 58 'Are you trying to pan? Use the WASD keys or hold shift to click ' + 59 "and drag. Press '?' for more help.", 60 ), 61 m( 62 'button.hint-dismiss-button', 63 { 64 onclick: () => { 65 globals.showPanningHint = false; 66 localStorage.setItem(DISMISSED_PANNING_HINT_KEY, 'true'); 67 raf.scheduleFullRedraw(); 68 }, 69 }, 70 'Dismiss', 71 ), 72 ); 73 } 74} 75 76class TraceErrorIcon implements m.ClassComponent { 77 view() { 78 if (globals.embeddedMode) return; 79 80 const mode = globals.state.omniboxState.mode; 81 82 const errors = globals.traceErrors; 83 if ((!Boolean(errors) && !globals.metricError) || mode === 'COMMAND') { 84 return; 85 } 86 const message = Boolean(errors) 87 ? `${errors} import or data loss errors detected.` 88 : `Metric error detected.`; 89 return m( 90 'a.error', 91 {href: '#!/info'}, 92 m( 93 'i.material-icons', 94 { 95 title: message + ` Click for more info.`, 96 }, 97 'announcement', 98 ), 99 ); 100 } 101} 102 103export interface TopbarAttrs { 104 omnibox: m.Children; 105} 106 107export class Topbar implements m.ClassComponent<TopbarAttrs> { 108 view({attrs}: m.Vnode<TopbarAttrs>) { 109 const {omnibox} = attrs; 110 return m( 111 '.topbar', 112 {class: globals.state.sidebarVisible ? '' : 'hide-sidebar'}, 113 omnibox, 114 m(Progress), 115 m(HelpPanningNotification), 116 m(TraceErrorIcon), 117 ); 118 } 119} 120