mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-14 11:53:49 +00:00
refactor: timeline design review
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { RefObject, useCallback, useEffect } from 'react';
|
||||
import { EntryId } from 'ontime-types';
|
||||
|
||||
function scrollToComponent<ComponentRef extends HTMLElement, ScrollRef extends HTMLElement>(
|
||||
componentRef: RefObject<ComponentRef> | null,
|
||||
@@ -26,7 +27,7 @@ interface UseHorizontalFollowComponentOptions {
|
||||
followRef: RefObject<HTMLElement | null>;
|
||||
scrollRef: RefObject<HTMLElement | null>;
|
||||
doFollow: boolean;
|
||||
hasSelectedElement?: boolean;
|
||||
selectedEventId: EntryId | null;
|
||||
leftOffset?: number;
|
||||
setScrollFlag?: (newValue: boolean) => void;
|
||||
}
|
||||
@@ -39,7 +40,7 @@ export default function useHorizontalFollowComponent({
|
||||
followRef,
|
||||
scrollRef,
|
||||
doFollow,
|
||||
hasSelectedElement,
|
||||
selectedEventId,
|
||||
leftOffset = 0,
|
||||
setScrollFlag,
|
||||
}: UseHorizontalFollowComponentOptions) {
|
||||
@@ -52,25 +53,25 @@ export default function useHorizontalFollowComponent({
|
||||
// Use requestAnimationFrame to ensure the component is fully loaded
|
||||
window.requestAnimationFrame(() => {
|
||||
scrollToComponent(
|
||||
hasSelectedElement ? (followRef as RefObject<HTMLElement>) : null,
|
||||
selectedEventId !== null ? (followRef as RefObject<HTMLElement>) : null,
|
||||
scrollRef as RefObject<HTMLElement>,
|
||||
leftOffset,
|
||||
);
|
||||
setScrollFlag?.(false);
|
||||
});
|
||||
}, [followRef, scrollRef, doFollow, hasSelectedElement, leftOffset, setScrollFlag]);
|
||||
}, [followRef, scrollRef, doFollow, leftOffset, setScrollFlag, selectedEventId]);
|
||||
|
||||
const scrollToRefComponent = useCallback(
|
||||
(componentRef = followRef, containerRef = scrollRef, offset = leftOffset) => {
|
||||
if (containerRef.current) {
|
||||
scrollToComponent(
|
||||
hasSelectedElement ? (componentRef as RefObject<HTMLElement>) : null,
|
||||
selectedEventId !== null ? (componentRef as RefObject<HTMLElement>) : null,
|
||||
containerRef as RefObject<HTMLElement>,
|
||||
offset,
|
||||
);
|
||||
}
|
||||
},
|
||||
[followRef, scrollRef, hasSelectedElement, leftOffset],
|
||||
[followRef, scrollRef, leftOffset, selectedEventId],
|
||||
);
|
||||
|
||||
return scrollToRefComponent;
|
||||
|
||||
@@ -28,6 +28,13 @@ $timeline-color: color-mix(in srgb, transparent 60%, var(--background-color-over
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.maybeInline {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5em;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
// generate combined timeline
|
||||
.timelineBlock {
|
||||
height: $timeline-height;
|
||||
@@ -65,7 +72,7 @@ $timeline-color: color-mix(in srgb, transparent 60%, var(--background-color-over
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2rem;
|
||||
gap: 1rem;
|
||||
padding-top: 0.25rem;
|
||||
padding-inline-start: 0.25rem;
|
||||
overflow: hidden;
|
||||
@@ -76,32 +83,15 @@ $timeline-color: color-mix(in srgb, transparent 60%, var(--background-color-over
|
||||
border-top: 2px solid var(--background-color-override, $viewer-background-color);
|
||||
box-shadow: 0 0.25rem 0 0 var(--color, $gray-300);
|
||||
|
||||
text-transform: capitalize;
|
||||
white-space: normal;
|
||||
|
||||
&[data-status='done'] {
|
||||
opacity: $opacity-disabled;
|
||||
}
|
||||
|
||||
&[data-status='live'] {
|
||||
box-shadow: 0 0.25rem 0 0 $active-red;
|
||||
}
|
||||
}
|
||||
|
||||
.delay {
|
||||
margin-top: -2rem;
|
||||
margin-bottom: -1rem;
|
||||
}
|
||||
|
||||
.timeOverview {
|
||||
padding-top: 0.25rem;
|
||||
padding-inline-start: 0.25em;
|
||||
text-transform: capitalize;
|
||||
white-space: normal;
|
||||
height: 6rem;
|
||||
|
||||
&[data-status='done'] {
|
||||
opacity: $opacity-disabled;
|
||||
}
|
||||
|
||||
&[data-status='live'] {
|
||||
.status {
|
||||
color: $active-red;
|
||||
}
|
||||
@@ -114,6 +104,22 @@ $timeline-color: color-mix(in srgb, transparent 60%, var(--background-color-over
|
||||
}
|
||||
}
|
||||
|
||||
.delay {
|
||||
color: $delay-color;
|
||||
}
|
||||
|
||||
.timeOverview {
|
||||
padding-top: 0.25rem;
|
||||
padding-inline-start: 0.25em;
|
||||
height: 3rem;
|
||||
}
|
||||
|
||||
.cross {
|
||||
text-decoration: line-through;
|
||||
text-decoration-thickness: 2px;
|
||||
text-decoration-color: $delay-color;
|
||||
}
|
||||
|
||||
.separeLeft {
|
||||
border-left: 1px solid var(--color, $gray-300);
|
||||
}
|
||||
|
||||
@@ -38,7 +38,7 @@ function Timeline({ firstStart, rundown, selectedEventId, totalDuration }: Timel
|
||||
followRef: selectedRef,
|
||||
scrollRef: scrollContainerRef,
|
||||
doFollow: autosize,
|
||||
hasSelectedElement: selectedEventId !== null,
|
||||
selectedEventId: selectedEventId,
|
||||
// No offset when hiding past events to ensure content starts at 0
|
||||
leftOffset: hidePast ? 0 : screenWidth / 6,
|
||||
});
|
||||
@@ -92,6 +92,7 @@ function Timeline({ firstStart, rundown, selectedEventId, totalDuration }: Timel
|
||||
colour={event.colour}
|
||||
delay={event.delay ?? 0}
|
||||
duration={event.duration}
|
||||
hasLink={Boolean(event.linkStart)}
|
||||
left={position.left}
|
||||
status={statusMap[event.id]}
|
||||
start={event.timeStart + (event.dayOffset ?? 0) * dayInMs}
|
||||
|
||||
@@ -16,6 +16,7 @@ interface TimelineEntryProps {
|
||||
colour: string;
|
||||
delay: number;
|
||||
duration: number;
|
||||
hasLink: boolean;
|
||||
left: number;
|
||||
status: ProgressStatus;
|
||||
start: number;
|
||||
@@ -29,21 +30,31 @@ const formatOptions = {
|
||||
format24: 'HH:mm',
|
||||
};
|
||||
|
||||
export function TimelineEntry({ colour, delay, duration, left, status, start, title, width, ref }: TimelineEntryProps) {
|
||||
export function TimelineEntry({
|
||||
colour,
|
||||
delay,
|
||||
duration,
|
||||
hasLink,
|
||||
left,
|
||||
status,
|
||||
start,
|
||||
title,
|
||||
width,
|
||||
ref,
|
||||
}: TimelineEntryProps) {
|
||||
const formattedStartTime = formatTime(start, formatOptions);
|
||||
const formattedDuration = formatDuration(duration);
|
||||
const delayedStart = start + delay;
|
||||
const hasDelay = delay > 0;
|
||||
|
||||
const lighterColour = alpha(colour, 0.7);
|
||||
const columnClasses = cx([style.column, width < 40 && style.smallArea]);
|
||||
const contentClasses = cx([style.content, width < 20 && style.hide]);
|
||||
const showTitle = width > 25;
|
||||
const smallArea = width < 40;
|
||||
|
||||
return (
|
||||
<div
|
||||
ref={ref}
|
||||
className={columnClasses}
|
||||
className={cx([style.column, smallArea && style.smallArea])}
|
||||
style={{
|
||||
'--color': colour,
|
||||
'--lighter': lighterColour ?? '',
|
||||
@@ -53,24 +64,27 @@ export function TimelineEntry({ colour, delay, duration, left, status, start, ti
|
||||
>
|
||||
{status === 'live' ? <ActiveBlock /> : <div data-status={status} className={style.timelineBlock} />}
|
||||
<div
|
||||
className={contentClasses}
|
||||
className={cx([style.content, width < 20 && style.hide, !hasLink && style.separeLeft])}
|
||||
data-status={status}
|
||||
style={{
|
||||
'--color': colour,
|
||||
}}
|
||||
>
|
||||
<div className={hasDelay ? style.cross : undefined}>{formattedStartTime}</div>
|
||||
{hasDelay && <div className={style.delay}>{formatTime(delayedStart, formatOptions)}</div>}
|
||||
{showTitle && <div>{title}</div>}
|
||||
</div>
|
||||
<div className={style.timeOverview} data-status={status}>
|
||||
{status !== 'done' && (
|
||||
<div className={style.maybeInline}>
|
||||
<div className={cx([hasDelay && style.cross])}>{formattedStartTime}</div>
|
||||
{hasDelay && <div className={style.delay}>{formatTime(delayedStart, formatOptions)}</div>}
|
||||
{smallArea && <TimelineEntryStatus delay={delay} start={start} status={status} />}
|
||||
</div>
|
||||
{showTitle && (
|
||||
<>
|
||||
<div className={style.duration}>{formattedDuration}</div>
|
||||
<TimelineEntryStatus delay={delay} start={start} status={status} />
|
||||
{!smallArea && <TimelineEntryStatus delay={delay} start={start} status={status} />}
|
||||
<div>{title}</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
<div className={style.timeOverview} data-status={status}>
|
||||
{status !== 'done' && <div className={style.duration}>{formattedDuration}</div>}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user