import React from 'react'; import { List, AutoSizer } from "react-virtualized"; import cn from 'classnames'; import { NoContent, IconButton } from 'UI'; import { percentOf } from 'App/utils'; import { formatMs } from 'App/date'; import BarRow from './BarRow'; import stl from './timeTable.module.css'; import autoscrollStl from '../autoscroll.module.css'; //aaa type Timed = { time: number, } type Durationed = { duration: number, } type CanBeRed = { //+isRed: boolean, isRed: () => boolean, } type Row = Timed & Durationed & CanBeRed type Line = { color: string, // Maybe use typescript? hint?: string, onClick?: any, } & Timed type Column = { label: string, width: number, referenceLines?: Array, style?: Object, } & RenderOrKey // type RenderOrKey = { // Disjoint? // render: Row => React.Node // } | { // dataKey: string, // } type RenderOrKey = { render?: (row: Row) => React.ReactNode, key?: string, } | { dataKey: string, } type Props = { className?: string, rows: Array, children: Array } type TimeLineInfo = { timestart: number, timewidth: number, } type State = TimeLineInfo & typeof initialState; //const TABLE_HEIGHT = 195; let _additionalHeight = 0; const ROW_HEIGHT = 32; //const VISIBLE_COUNT = Math.ceil(TABLE_HEIGHT/ROW_HEIGHT); const TIME_SECTIONS_COUNT = 8; const ZERO_TIMEWIDTH = 1000; function formatTime(ms) { if(ms < 0) return ""; return formatMs(ms); } function computeTimeLine(rows: Array, firstVisibleRowIndex: number, visibleCount): TimeLineInfo { const visibleRows = rows.slice(firstVisibleRowIndex, firstVisibleRowIndex + visibleCount + _additionalHeight); let timestart = visibleRows.length > 0 ? Math.min(...visibleRows.map(r => r.time)) : 0; const timeend = visibleRows.length > 0 ? Math.max(...visibleRows.map(r => r.time + r.duration)) : 0; let timewidth = timeend - timestart; const offset = timewidth / 70; if (timestart >= offset) { timestart -= offset; } timewidth *= 1.5; // += offset; if (timewidth === 0) { timewidth = ZERO_TIMEWIDTH; } return { timestart, timewidth, }; } const initialState = { firstVisibleRowIndex: 0, } export default class TimeTable extends React.PureComponent { state = { ...computeTimeLine(this.props.rows, initialState.firstVisibleRowIndex, this.visibleCount), ...initialState, } get tableHeight() { return this.props.tableHeight || 195; } get visibleCount() { return Math.ceil(this.tableHeight/ROW_HEIGHT); } scroller = React.createRef(); autoScroll = true; // componentDidMount() { // if (this.scroller.current != null) { // this.scroller.current.scrollToRow(this.props.rows.length - 1); // } // } componentDidUpdate(prevProps: any, prevState: any) { // if (prevProps.rows.length !== this.props.rows.length && // this.autoScroll && // this.scroller.current != null) { // this.scroller.current.scrollToRow(this.props.rows.length); // } if (prevState.firstVisibleRowIndex !== this.state.firstVisibleRowIndex || (this.props.rows.length <= (this.visibleCount + _additionalHeight) && prevProps.rows.length !== this.props.rows.length)) { this.setState({ ...computeTimeLine(this.props.rows, this.state.firstVisibleRowIndex, this.visibleCount), }); } if (this.props.activeIndex >= 0 && prevProps.activeIndex !== this.props.activeIndex && this.scroller.current != null) { this.scroller.current.scrollToRow(this.props.activeIndex); } } onScroll = ({ scrollTop, scrollHeight, clientHeight }: { scrollTop: number, scrollHeight: number, clientHeight: number }):void => { const firstVisibleRowIndex = Math.floor(scrollTop / ROW_HEIGHT + 0.33); if (this.state.firstVisibleRowIndex !== firstVisibleRowIndex) { this.autoScroll = (scrollHeight - clientHeight - scrollTop) < ROW_HEIGHT / 2; this.setState({ firstVisibleRowIndex }); } } renderRow = ({ index, key, style: rowStyle }: any) => { const { activeIndex } = this.props; const { children: columns, rows, renderPopup, hoverable, onRowClick, } = this.props; const { timestart, timewidth, } = this.state; const row = rows[ index ]; return (
onRowClick(row, index) : null } id="table-row" > { columns.map(({ dataKey, render, width }) => (
{ render ? render(row) : (row[ dataKey ] || {"empty"}) }
))}
); } onPrevClick = () => { let prevRedIndex = -1; for (let i = this.state.firstVisibleRowIndex-1; i >= 0; i--) { if (this.props.rows[ i ].isRed()) { prevRedIndex = i; break; } } if (this.scroller.current != null) { this.scroller.current.scrollToRow(prevRedIndex); } } onNextClick = () => { let prevRedIndex = -1; for (let i = this.state.firstVisibleRowIndex+1; i < this.props.rows.length; i++) { if (this.props.rows[ i ].isRed()) { prevRedIndex = i; break; } } if (this.scroller.current != null) { this.scroller.current.scrollToRow(prevRedIndex); } } render() { const { className, rows, children: columns, navigation=false, referenceLines = [], additionalHeight = 0, activeIndex, } = this.props; const { timewidth, timestart, } = this.state; _additionalHeight = additionalHeight; const sectionDuration = Math.round(timewidth / TIME_SECTIONS_COUNT); const timeColumns = []; if (timewidth > 0) { for (let i = 0; i < TIME_SECTIONS_COUNT; i++) { timeColumns.push(timestart + i * sectionDuration); } } const visibleRefLines = referenceLines.filter(({ time }) => time > timestart && time < timestart + timewidth); const columnsSumWidth = columns.reduce((sum, { width }) => sum + width, 0); return (
{ navigation &&
}
{ columns.map(({ label, width }) => (
{ label }
)) }
{ timeColumns.map((time, i) => (
{ formatTime(time) }
)) }
{ timeColumns.map((_, index) => (
)) } { visibleRefLines.map(({ time, color, onClick }) => (
))}
{({ width }) => ( )}
); } }