feat: Timeline UI (#2830)

This commit is contained in:
JohnMark Sill
2022-02-27 08:04:12 -06:00
committed by GitHub
parent 4004048add
commit 3e07d4eddb
54 changed files with 1950 additions and 50 deletions

View File

@@ -0,0 +1,73 @@
import { TimelineEvent } from '../../components/Timeline/TimelineEvent';
import { TimelineEventBlock } from '../../components/Timeline/TimelineEventBlock';
import { epochToLong, longToDate } from '../dateUtil';
export const checkEventForOverlap = (firstEvent: TimelineEvent, secondEvent: TimelineEvent) => {
if (secondEvent.startTime < firstEvent.endTime && secondEvent.startTime > firstEvent.startTime) {
return true;
}
return false;
};
export const getTimelineEventBlocksFromTimelineEvents = (events: TimelineEvent[], xOffset: number): TimelineEventBlock[] => {
const firstEvent = events[0];
const firstEventTime = longToDate(firstEvent.start_time);
return events
.map((e, index) => {
const startTime = longToDate(e.start_time);
const endTime = e.end_time ? longToDate(e.end_time) : new Date();
const seconds = Math.round(Math.abs(endTime.getTime() - startTime.getTime()) / 1000);
const positionX = Math.round(Math.abs(startTime.getTime() - firstEventTime.getTime()) / 1000 + xOffset);
return {
...e,
startTime,
endTime,
width: seconds,
positionX,
index,
} as TimelineEventBlock;
})
.reduce((rowMap, current) => {
for (let i = 0; i < rowMap.length; i++) {
const row = rowMap[i] ?? [];
const lastItem = row[row.length - 1];
if (lastItem) {
const isOverlap = checkEventForOverlap(lastItem, current);
if (isOverlap) {
continue;
}
}
rowMap[i] = [...row, current];
return rowMap;
}
rowMap.push([current]);
return rowMap;
}, [] as TimelineEventBlock[][])
.flatMap((rows, rowPosition) => {
rows.forEach((eventBlock) => {
const OFFSET_DISTANCE_IN_PIXELS = 10;
eventBlock.yOffset = OFFSET_DISTANCE_IN_PIXELS * rowPosition;
});
return rows;
})
.sort((a, b) => a.startTime.getTime() - b.startTime.getTime());
}
export const findLargestYOffsetInBlocks = (blocks: TimelineEventBlock[]): number => {
return blocks.reduce((largestYOffset, current) => {
if (current.yOffset > largestYOffset) {
return current.yOffset
}
return largestYOffset;
}, 0)
};
export const getTimelineWidthFromBlocks = (blocks: TimelineEventBlock[], offset: number): number => {
const firstBlock = blocks[0];
if (firstBlock) {
const startTimeEpoch = firstBlock.startTime.getTime();
const endTimeEpoch = Date.now();
const timelineDurationLong = epochToLong(endTimeEpoch - startTimeEpoch);
return timelineDurationLong + offset * 2
}
}

16
web/src/utils/dateUtil.ts Normal file
View File

@@ -0,0 +1,16 @@
export const longToDate = (long: number): Date => new Date(long * 1000);
export const epochToLong = (date: number): number => date / 1000;
export const dateToLong = (date: Date): number => epochToLong(date.getTime());
const getDateTimeYesterday = (dateTime: Date): Date => {
const twentyFourHoursInMilliseconds = 24 * 60 * 60 * 1000;
return new Date(dateTime.getTime() - twentyFourHoursInMilliseconds);
}
const getNowYesterday = (): Date => {
return getDateTimeYesterday(new Date());
}
export const getNowYesterdayInLong = (): number => {
return dateToLong(getNowYesterday());
};

View File

@@ -0,0 +1 @@
export const isNullOrUndefined = (object?: unknown): boolean => object === null || object === undefined;

View File

@@ -0,0 +1,15 @@
import { TimelineEvent } from '../../components/Timeline/TimelineEvent';
export const getColorFromTimelineEvent = (event: TimelineEvent) => {
const { label } = event;
if (label === 'car') {
return 'bg-red-400';
} else if (label === 'person') {
return 'bg-blue-400';
} else if (label === 'dog') {
return 'bg-green-400';
}
// unknown label
return 'bg-gray-400';
};

View File

@@ -0,0 +1,3 @@
export const convertRemToPixels = (rem: number): number => {
return rem * parseFloat(getComputedStyle(document.documentElement).fontSize);
}