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  }