Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 | 22x 22x 22x 22x 22x 22x 22x 22x 154x 242x 22x 1x 1x 1x 22x 22x 52x 52x 52x 24x 24x 52x 24x 24x 24x 52x 26x 25x 1x 1x 9x 9x 9x 9x 4x 52x 52x 52x 1x 1x 1x 52x 4x | /* * Copyright 2021 Harness Inc. All rights reserved. * Use of this source code is governed by the PolyForm Shield 1.0.0 license * that can be found in the licenses directory at the root of this repository, also available at * https://polyformproject.org/wp-content/uploads/2020/06/PolyForm-Shield-1.0.0.txt. */ import React, { useState, useEffect, useRef, useMemo, CSSProperties } from 'react' import { extendMoment, DateRange } from 'moment-range' import classnames from 'classnames' import styles from './TimelineView.module.scss' const moment = extendMoment(require('moment')) // eslint-disable-line const DAY_HOUR_MINUTE = 'ddd h:mm A' const MONTH_DAY_MINUTE = 'MMM D, h:mm A' export interface TimelineBarProps { startDate: string | number | Date endDate: string | number | Date className?: string style?: CSSProperties columnWidth?: number } const TIME_UNITS = [ { unit: 'minutes', step: 1, format: 'h:mm:ss A' }, { unit: 'minutes', step: 2, format: 'h:mm A' }, { unit: 'minutes', step: 5, format: 'h:mm A' }, { unit: 'minutes', step: 10, format: 'h:mm A' }, { unit: 'minutes', step: 15, format: 'h:mm A' }, { unit: 'minutes', step: 30, format: 'h:mm A' }, { unit: 'hours', step: 1, format: DAY_HOUR_MINUTE }, { unit: 'hours', step: 2, format: DAY_HOUR_MINUTE }, { unit: 'hours', step: 4, format: DAY_HOUR_MINUTE }, { unit: 'hours', step: 12, format: MONTH_DAY_MINUTE }, { unit: 'days', step: 1, format: MONTH_DAY_MINUTE }, { unit: 'days', step: 2, format: MONTH_DAY_MINUTE }, { unit: 'weeks', step: 1, format: MONTH_DAY_MINUTE }, { unit: 'months', step: 1, format: 'MMM' }, { unit: 'months', step: 4, format: 'MMM' }, { unit: 'years', step: 1, format: 'YYYY' } ] const HOURS_STARTING_INDEX = TIME_UNITS.findIndex(({ unit }) => unit === 'hours') const DAYS_STARTING_INDEX = TIME_UNITS.findIndex(({ unit }) => unit === 'days') /** * This method finds the best starting point for certain range. * If the range is too wide, the goal is to avoid recreating increments of small units. * It's just performance optimization and it would work if starting point was always zero. */ const findOptimalStartingIndex = (range: DateRange): number => { let start = 0 Iif (range.diff('days') > 0) { /** Start with hours */ start = HOURS_STARTING_INDEX if (range.diff('days') > 10) { /** start with days */ start = DAYS_STARTING_INDEX } } return start } const MIN_COL_SIZE = 70 export function TimelineBar({ startDate, endDate, className, style, columnWidth = MIN_COL_SIZE }: TimelineBarProps) { const ref = useRef<HTMLDivElement>(null) const [size, setSize] = useState(0) const onResize = () => { Eif (ref.current) { setSize(ref.current.getBoundingClientRect().width) } } useEffect(() => { onResize() window.addEventListener('resize', onResize) return () => window.removeEventListener('resize', onResize) }, []) const items = useMemo(() => { if (size === 0) { return [] } const range = moment.range(moment(startDate), moment(endDate)) for (let i = findOptimalStartingIndex(range); i < TIME_UNITS.length; i++) { const timeUnit = TIME_UNITS[i] const rangeItems = Array.from(range.by(timeUnit.unit as any, { step: timeUnit.step })) const colSize = size / rangeItems.length if (colSize >= columnWidth) { return rangeItems.map((item: any) => ({ formattedValue: item.format(timeUnit.format), originalValue: item })) } } return [] }, [size, startDate, endDate, columnWidth]) let lastItemCoeff = 0 let estimatedLastColumnSize = 0 if (items.length >= 2) { const limit = moment(endDate) Iif (limit.isAfter(items[items.length - 1].originalValue)) { const a = items[items.length - 1].originalValue.diff(items[items.length - 2].originalValue) const b = limit.diff(items[items.length - 1].originalValue) lastItemCoeff = b / a } Iif (lastItemCoeff > 0) { estimatedLastColumnSize = (size / (items.length - 1 + lastItemCoeff)) * lastItemCoeff } } return ( <div ref={ref} style={style} className={classnames(styles.timelineBar, className)}> {items.map((item, i) => ( <div key={i} style={ i === items.length - 1 ? { flex: lastItemCoeff, visibility: estimatedLastColumnSize > 55 ? 'visible' : 'hidden' } : undefined } className={styles.timelineBarItem} > {item.formattedValue} </div> ))} </div> ) } |