refactor: compound timeline

This commit is contained in:
Carlos Valente
2024-10-06 22:43:17 +02:00
committed by Carlos Valente
parent f11218a757
commit bad9dab0a0
9 changed files with 89 additions and 131 deletions
@@ -1,39 +1,40 @@
@use '../../../theme/viewerDefs' as *;
$timeline-entry-height: 20px;
$lane-height: 120px;
$timeline-height: 1rem;
$view-background: $ui-black;
.timeline {
flex: 1;
font-weight: 600;
color: $ui-white;
background-color: $ui-black;
}
.timelineEvents {
background-color: $view-background;
box-sizing: content-box;
// create progress background
box-shadow: inset 0 1rem 0 0 $gray-1100;
position: relative;
height: 100%;
}
.column {
display: flex;
flex-direction: column;
position: absolute;
border-left: 1px solid $ui-black;
border-left: 1px solid $view-background;
// avoiding content being larger than the view
height: calc(100% - 3rem);
}
// decorate timeline element
&::before {
content: '';
position: absolute;
box-sizing: content-box;
top: -$timeline-height;
left: 0;
right: 0;
height: $timeline-height;
background-color: $white-40;
// generate combined timeline
.timelineBlock {
height: $timeline-height;
background: $white-60;
width: 100%;
&[data-status='done'] {
background: $active-red;
}
&[data-status='live'] {
background: linear-gradient(to right, $active-red var(--progress, 0%), $white-60 var(--progress, 0%));
}
}
@@ -66,7 +67,8 @@ $timeline-height: 1rem;
line-height: 1rem;
background-color: var(--lighter, $viewer-card-bg-color);
border-bottom: 2px solid $ui-black;
border-bottom: 2px solid $view-background;
border-top: 2px solid $view-background;
box-shadow: 0 0.25rem 0 0 var(--color, $gray-300);
&[data-status='done'] {
@@ -4,7 +4,6 @@ import { isOntimeEvent, isPlayableEvent, MaybeNumber, OntimeRundown } from 'onti
import { checkIsNextDay, dayInMs, getLastEvent, MILLIS_PER_HOUR } from 'ontime-utils';
import TimelineMarkers from './timeline-markers/TimelineMarkers';
import TimelineProgressBar from './timeline-progress-bar/TimelineProgressBar';
import { getElementPosition, getEndHour, getStartHour } from './timeline.utils';
import { ProgressStatus, TimelineEntry } from './TimelineEntry';
@@ -39,57 +38,54 @@ function Timeline(props: TimelineProps) {
return (
<div className={style.timeline}>
<TimelineMarkers startHour={startHour} endHour={endHour} />
<TimelineProgressBar startHour={startHour} endHour={endHour} />
<div className={style.timelineEvents}>
{rundown.map((event) => {
// for now we dont render delays and blocks
if (!isOntimeEvent(event) || !isPlayableEvent(event)) {
return null;
}
{rundown.map((event) => {
// for now we dont render delays and blocks
if (!isOntimeEvent(event) || !isPlayableEvent(event)) {
return null;
}
// keep track of progress of rundown
if (eventStatus === 'live') {
eventStatus = 'future';
}
if (event.id === selectedEventId) {
eventStatus = 'live';
}
// keep track of progress of rundown
if (eventStatus === 'live') {
eventStatus = 'future';
}
if (event.id === selectedEventId) {
eventStatus = 'live';
}
// we only need to check for next day if we have a previous event
if (
previousEventStartTime !== null &&
checkIsNextDay(previousEventStartTime, event.timeStart, event.duration)
) {
elapsedDays++;
}
const normalisedStart = event.timeStart + elapsedDays * dayInMs;
// we only need to check for next day if we have a previous event
if (
previousEventStartTime !== null &&
checkIsNextDay(previousEventStartTime, event.timeStart, event.duration)
) {
elapsedDays++;
}
const normalisedStart = event.timeStart + elapsedDays * dayInMs;
const { left: elementLeftPosition, width: elementWidth } = getElementPosition(
startHour * MILLIS_PER_HOUR,
endHour * MILLIS_PER_HOUR,
normalisedStart + (event.delay ?? 0),
event.duration,
screenWidth,
);
const { left: elementLeftPosition, width: elementWidth } = getElementPosition(
startHour * MILLIS_PER_HOUR,
endHour * MILLIS_PER_HOUR,
normalisedStart + (event.delay ?? 0),
event.duration,
screenWidth,
);
// prepare values for next iteration
previousEventStartTime = normalisedStart;
// prepare values for next iteration
previousEventStartTime = normalisedStart;
return (
<TimelineEntry
key={event.id}
colour={event.colour}
delay={event.delay ?? 0}
duration={event.duration}
left={elementLeftPosition}
status={eventStatus}
start={normalisedStart} // dataset solves issues related to crossing midnight
title={event.title}
width={elementWidth}
/>
);
})}
</div>
return (
<TimelineEntry
key={event.id}
colour={event.colour}
delay={event.delay ?? 0}
duration={event.duration}
left={elementLeftPosition}
status={eventStatus}
start={normalisedStart} // dataset solves issues related to crossing midnight
title={event.title}
width={elementWidth}
/>
);
})}
</div>
);
}
@@ -1,4 +1,5 @@
import { useTimelineStatus } from '../../../common/hooks/useSocket';
import { useTimelineStatus, useTimer } from '../../../common/hooks/useSocket';
import { getProgress } from '../../../common/utils/getProgress';
import { alpha, cx } from '../../../common/utils/styleUtils';
import { formatDuration, formatTime } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
@@ -48,6 +49,7 @@ export function TimelineEntry(props: TimelineEntryProps) {
width: `${width}px`,
}}
>
{status === 'live' ? <ActiveBlock /> : <div data-status={status} className={style.timelineBlock} />}
<div
className={contentClasses}
data-status={status}
@@ -77,7 +79,7 @@ interface TimelineEntryStatusProps {
status: ProgressStatus;
}
// we isolate this component to avoid isolate re-renders provoked by the clock changes
// extract component to isolate re-renders provoked by the clock changes
function TimelineEntryStatus(props: TimelineEntryStatusProps) {
const { delay, start, status } = props;
const { clock, offset } = useTimelineStatus();
@@ -93,3 +95,10 @@ function TimelineEntryStatus(props: TimelineEntryStatusProps) {
return <div className={style.status}>{statusText}</div>;
}
/** Generates a block level progress bar */
function ActiveBlock() {
const { current, duration } = useTimer();
const progress = getProgress(current, duration);
return <div data-status='live' className={style.timelineBlock} style={{ '--progress': `${progress}%` }} />;
}
@@ -56,12 +56,7 @@ describe('getCSSPosition()', () => {
describe('makeTimelineSections', () => {
it('creates an array between the hours given, end excluded', () => {
const result = makeTimelineSections(11, 17);
expect(result).toEqual(['11:00', '12:00', '13:00', '14:00', '15:00', '16:00']);
});
it('wraps around midnight', () => {
const result = makeTimelineSections(22, 26);
expect(result).toEqual(['22:00', '23:00', '00:00', '01:00']);
expect(result).toEqual([11, 12, 13, 14, 15, 16]);
});
});
@@ -1,16 +1,17 @@
.markers {
position: absolute;
left: 0;
width: 100%;
color: $ui-white;
height: 100%;
display: flex;
height: 1rem;
line-height: 1rem;
margin-bottom: 0.25rem;
font-size: calc(1rem - 2px);
justify-content: space-evenly;
& > span {
flex-grow: 1;
border-left: 1px solid $white-7;
height: 100vh;
&:not(:first-child) {
border-left: 1px solid $white-7;
}
}
}
@@ -7,6 +7,7 @@ interface TimelineMarkersProps {
endHour: number;
}
/** Creates a line for every hour in the timeline */
export default function TimelineMarkers(props: TimelineMarkersProps) {
const { startHour, endHour } = props;
@@ -14,9 +15,9 @@ export default function TimelineMarkers(props: TimelineMarkersProps) {
return (
<div className={style.markers}>
{elements.map((tag, index) => {
return <span key={`${index}-${tag}`}>{tag}</span>;
})}
{elements.map((tag) => (
<span key={tag} />
))}
</div>
);
}
@@ -1,19 +0,0 @@
.progressBar {
width: 100%;
height: 1rem;
position: relative;
background-color: $gray-1000;
}
.progress {
height: 100%;
position: absolute;
left: 0;
top: 0;
z-index: 2;
background-color: $active-red;
transition-duration: 0.3s;
transition-property: width;
}
@@ -1,25 +0,0 @@
import { MILLIS_PER_HOUR } from 'ontime-utils';
import { useClock } from '../../../../common/hooks/useSocket';
import { getRelativePositionX } from '../timeline.utils';
import style from './TimelineProgressBar.module.scss';
interface ProgressBarProps {
startHour: number;
endHour: number;
}
export default function ProgressBar(props: ProgressBarProps) {
const { startHour, endHour } = props;
// TODO: how to account for days?
const { clock } = useClock();
const width = getRelativePositionX(startHour * MILLIS_PER_HOUR, endHour * MILLIS_PER_HOUR, clock);
return (
<div className={style.progressBar}>
<div className={style.progress} style={{ width: `${width}%` }} />
</div>
);
}
@@ -9,8 +9,6 @@ import {
getTimeFromPrevious,
isNewLatest,
MILLIS_PER_HOUR,
millisToString,
removeSeconds,
} from 'ontime-utils';
import { clamp } from '../../../common/utils/math';
@@ -71,7 +69,7 @@ export function getEndHour(endTime: number): number {
export function makeTimelineSections(firstHour: number, lastHour: number) {
const timelineSections = [];
for (let i = firstHour; i < lastHour; i++) {
timelineSections.push(removeSeconds(millisToString((i % 24) * MILLIS_PER_HOUR)));
timelineSections.push(i);
}
return timelineSections;
}