| // 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 m from 'mithril'; |
| |
| import {classNames} from '../base/classnames'; |
| import {raf} from '../core/raf_scheduler'; |
| import {VERSION} from '../gen/perfetto_version'; |
| |
| import {globals} from './globals'; |
| import {taskTracker} from './task_tracker'; |
| |
| export const DISMISSED_PANNING_HINT_KEY = 'dismissedPanningHint'; |
| |
| class Progress implements m.ClassComponent { |
| view(_vnode: m.Vnode): m.Children { |
| const classes = classNames(this.isLoading() && 'progress-anim'); |
| return m('.progress', {class: classes}); |
| } |
| |
| private isLoading(): boolean { |
| const engine = globals.getCurrentEngine(); |
| return ( |
| (engine && !engine.ready) || |
| globals.numQueuedQueries > 0 || |
| taskTracker.hasPendingTasks() |
| ); |
| } |
| } |
| |
| class NewVersionNotification implements m.ClassComponent { |
| view() { |
| return m( |
| '.new-version-toast', |
| `Updated to ${VERSION} and ready for offline use!`, |
| m( |
| 'button.notification-btn.preferred', |
| { |
| onclick: () => { |
| globals.newVersionAvailable = false; |
| raf.scheduleFullRedraw(); |
| }, |
| }, |
| 'Dismiss', |
| ), |
| ); |
| } |
| } |
| |
| class HelpPanningNotification implements m.ClassComponent { |
| view() { |
| const dismissed = localStorage.getItem(DISMISSED_PANNING_HINT_KEY); |
| // Do not show the help notification in embedded mode because local storage |
| // does not persist for iFrames. The host is responsible for communicating |
| // to users that they can press '?' for help. |
| if ( |
| globals.embeddedMode || |
| dismissed === 'true' || |
| !globals.showPanningHint |
| ) { |
| return; |
| } |
| return m( |
| '.helpful-hint', |
| m( |
| '.hint-text', |
| 'Are you trying to pan? Use the WASD keys or hold shift to click ' + |
| "and drag. Press '?' for more help.", |
| ), |
| m( |
| 'button.hint-dismiss-button', |
| { |
| onclick: () => { |
| globals.showPanningHint = false; |
| localStorage.setItem(DISMISSED_PANNING_HINT_KEY, 'true'); |
| raf.scheduleFullRedraw(); |
| }, |
| }, |
| 'Dismiss', |
| ), |
| ); |
| } |
| } |
| |
| class TraceErrorIcon implements m.ClassComponent { |
| view() { |
| if (globals.embeddedMode) return; |
| |
| const mode = globals.state.omniboxState.mode; |
| |
| const errors = globals.traceErrors; |
| if ((!Boolean(errors) && !globals.metricError) || mode === 'COMMAND') { |
| return; |
| } |
| const message = Boolean(errors) |
| ? `${errors} import or data loss errors detected.` |
| : `Metric error detected.`; |
| return m( |
| 'a.error', |
| {href: '#!/info'}, |
| m( |
| 'i.material-icons', |
| { |
| title: message + ` Click for more info.`, |
| }, |
| 'announcement', |
| ), |
| ); |
| } |
| } |
| |
| export interface TopbarAttrs { |
| omnibox: m.Children; |
| } |
| |
| export class Topbar implements m.ClassComponent<TopbarAttrs> { |
| view({attrs}: m.Vnode<TopbarAttrs>) { |
| const {omnibox} = attrs; |
| return m( |
| '.topbar', |
| {class: globals.state.sidebarVisible ? '' : 'hide-sidebar'}, |
| globals.newVersionAvailable ? m(NewVersionNotification) : omnibox, |
| m(Progress), |
| m(HelpPanningNotification), |
| m(TraceErrorIcon), |
| ); |
| } |
| } |