1// Copyright (C) 2023 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 {copyToClipboard} from '../../base/clipboard'; 18import {Icons} from '../../base/semantic_icons'; 19import {Duration, duration} from '../../base/time'; 20import { 21 DurationPrecision, 22 durationPrecision, 23 setDurationPrecision, 24 TimestampFormat, 25 timestampFormat, 26} from '../../core/timestamp_format'; 27import {raf} from '../../core/raf_scheduler'; 28import {Anchor} from '../../widgets/anchor'; 29import {MenuDivider, MenuItem, PopupMenu2} from '../../widgets/menu'; 30 31import {menuItemForFormat} from './timestamp'; 32 33interface DurationWidgetAttrs { 34 dur: duration; 35 extraMenuItems?: m.Child[]; 36} 37 38export class DurationWidget implements m.ClassComponent<DurationWidgetAttrs> { 39 view({attrs}: m.Vnode<DurationWidgetAttrs>) { 40 const {dur} = attrs; 41 if (dur === -1n) { 42 return '(Did not end)'; 43 } 44 return m( 45 PopupMenu2, 46 { 47 trigger: m(Anchor, renderDuration(dur)), 48 }, 49 m(MenuItem, { 50 icon: Icons.Copy, 51 label: `Copy raw value`, 52 onclick: () => { 53 copyToClipboard(dur.toString()); 54 }, 55 }), 56 m( 57 MenuItem, 58 { 59 label: 'Set time format', 60 }, 61 menuItemForFormat(TimestampFormat.Timecode, 'Timecode'), 62 menuItemForFormat(TimestampFormat.UTC, 'Realtime (UTC)'), 63 menuItemForFormat(TimestampFormat.TraceTz, 'Realtime (Trace TZ)'), 64 menuItemForFormat(TimestampFormat.Seconds, 'Seconds'), 65 menuItemForFormat(TimestampFormat.Raw, 'Raw'), 66 menuItemForFormat( 67 TimestampFormat.RawLocale, 68 'Raw (with locale-specific formatting)', 69 ), 70 ), 71 m( 72 MenuItem, 73 { 74 label: 'Duration precision', 75 disabled: !durationPrecisionHasEffect(), 76 title: 'Not configurable with current time format', 77 }, 78 menuItemForPrecision(DurationPrecision.Full, 'Full'), 79 menuItemForPrecision(DurationPrecision.HumanReadable, 'Human readable'), 80 ), 81 attrs.extraMenuItems ? [m(MenuDivider), attrs.extraMenuItems] : null, 82 ); 83 } 84} 85 86function menuItemForPrecision( 87 value: DurationPrecision, 88 label: string, 89): m.Children { 90 return m(MenuItem, { 91 label, 92 active: value === durationPrecision(), 93 onclick: () => { 94 setDurationPrecision(value); 95 raf.scheduleFullRedraw(); 96 }, 97 }); 98} 99 100function durationPrecisionHasEffect(): boolean { 101 switch (timestampFormat()) { 102 case TimestampFormat.Timecode: 103 case TimestampFormat.UTC: 104 case TimestampFormat.TraceTz: 105 return true; 106 default: 107 return false; 108 } 109} 110 111export function renderDuration(dur: duration): string { 112 const fmt = timestampFormat(); 113 switch (fmt) { 114 case TimestampFormat.UTC: 115 case TimestampFormat.TraceTz: 116 case TimestampFormat.Timecode: 117 return renderFormattedDuration(dur); 118 case TimestampFormat.Raw: 119 return dur.toString(); 120 case TimestampFormat.RawLocale: 121 return dur.toLocaleString(); 122 case TimestampFormat.Seconds: 123 return Duration.formatSeconds(dur); 124 default: 125 const x: never = fmt; 126 throw new Error(`Invalid format ${x}`); 127 } 128} 129 130function renderFormattedDuration(dur: duration): string { 131 const fmt = durationPrecision(); 132 switch (fmt) { 133 case DurationPrecision.HumanReadable: 134 return Duration.humanise(dur); 135 case DurationPrecision.Full: 136 return Duration.format(dur); 137 default: 138 const x: never = fmt; 139 throw new Error(`Invalid format ${x}`); 140 } 141} 142