go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/milo/ui/src/common/components/timestamp.tsx (about) 1 // Copyright 2023 The LUCI Authors. 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 15 import { html, render } from 'lit'; 16 import { DateTime } from 'luxon'; 17 18 import { 19 HideTooltipEventDetail, 20 ShowTooltipEventDetail, 21 } from '@/common/components/tooltip'; 22 import { displayDuration, LONG_TIME_FORMAT } from '@/common/tools/time_utils'; 23 24 export interface TimeZoneConfig { 25 label: string; 26 zone: string; 27 } 28 29 export const DEFAULT_EXTRA_ZONE_CONFIGS = [ 30 { 31 label: 'PT', 32 zone: 'America/Los_Angeles', 33 }, 34 { 35 label: 'UTC', 36 zone: 'utc', 37 }, 38 ]; 39 40 function renderTooltip( 41 datetime: DateTime, 42 format: string, 43 extraZones: readonly TimeZoneConfig[], 44 ) { 45 const now = DateTime.now(); 46 return html` 47 <table> 48 <tr> 49 <td colspan="2">${displayDuration(now.diff(datetime))} ago</td> 50 </tr> 51 ${extraZones.map( 52 (tz) => html` 53 <tr> 54 <td>${tz.label}:</td> 55 <td>${datetime.setZone(tz.zone).toFormat(format)}</td> 56 </tr> 57 `, 58 )} 59 </table> 60 `; 61 } 62 63 export interface TimestampProps { 64 readonly datetime: DateTime | undefined; 65 /** 66 * Defaults to `LONG_TIME_FORMAT`; 67 */ 68 readonly format?: string; 69 readonly extra?: { 70 readonly zones?: readonly TimeZoneConfig[]; 71 /** 72 * Defaults to `format`. 73 */ 74 readonly format?: string; 75 }; 76 } 77 78 /** 79 * Renders a timestamp. 80 * Shows duration and addition timezone on hover. 81 */ 82 export function Timestamp(props: TimestampProps) { 83 if (props.datetime === undefined) { 84 return <span>No timestamp available</span>; 85 } 86 87 const datetime = props.datetime; 88 const format = props.format ?? LONG_TIME_FORMAT; 89 const extraZones = props.extra?.zones ?? DEFAULT_EXTRA_ZONE_CONFIGS; 90 const extraFormat = props.extra?.format ?? format; 91 92 function onShowTooltip(target: HTMLElement) { 93 const tooltip = document.createElement('div'); 94 render(renderTooltip(datetime, extraFormat, extraZones), tooltip); 95 96 window.dispatchEvent( 97 new CustomEvent<ShowTooltipEventDetail>('show-tooltip', { 98 detail: { 99 tooltip, 100 targetRect: target.getBoundingClientRect(), 101 gapSize: 2, 102 }, 103 }), 104 ); 105 } 106 107 function onHideTooltip() { 108 window.dispatchEvent( 109 new CustomEvent<HideTooltipEventDetail>('hide-tooltip', { 110 detail: { delay: 50 }, 111 }), 112 ); 113 } 114 115 return ( 116 <span 117 onMouseOver={(e) => onShowTooltip(e.target as HTMLElement)} 118 onFocus={(e) => onShowTooltip(e.target as HTMLElement)} 119 onMouseOut={onHideTooltip} 120 onBlur={onHideTooltip} 121 > 122 {datetime.toFormat(format)} 123 </span> 124 ); 125 }