Skip to content

Commit

Permalink
tweaks
Browse files Browse the repository at this point in the history
  • Loading branch information
cpvalente committed Jul 22, 2024
1 parent e90508b commit 0ab3ed8
Show file tree
Hide file tree
Showing 18 changed files with 131 additions and 206 deletions.
16 changes: 8 additions & 8 deletions apps/client/src/common/utils/time.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { MaybeNumber, Settings, TimeFormat } from 'ontime-types';
import { formatFromMillis } from 'ontime-utils';
import { formatFromMillis, MILLIS_PER_HOUR, MILLIS_PER_MINUTE, MILLIS_PER_SECOND } from 'ontime-utils';

import { FORMAT_12, FORMAT_24 } from '../../viewerConfig';
import { APP_SETTINGS } from '../api/constants';
Expand All @@ -9,17 +9,17 @@ import { ontimeQueryClient } from '../queryClient';
* Returns current time in milliseconds
* @returns {number}
*/
export const nowInMillis = () => {
export function nowInMillis(): number {
const now = new Date();

// extract milliseconds since midnight
let elapsed = now.getHours() * 3600000;
elapsed += now.getMinutes() * 60000;
elapsed += now.getSeconds() * 1000;
let elapsed = now.getHours() * MILLIS_PER_HOUR;
elapsed += now.getMinutes() * MILLIS_PER_MINUTE;
elapsed += now.getSeconds() * MILLIS_PER_SECOND;
elapsed += now.getMilliseconds();

return elapsed;
};
}

/**
* @description Resolves format from url and store
Expand Down Expand Up @@ -107,8 +107,8 @@ export function formatDuration(duration: number): string {
return '0h 0m';
}

const hours = Math.floor(duration / 3600000);
const minutes = Math.floor((duration % 3600000) / 60000);
const hours = Math.floor(duration / MILLIS_PER_HOUR);
const minutes = Math.floor((duration % MILLIS_PER_HOUR) / MILLIS_PER_MINUTE);
let result = '';
if (hours > 0) {
result += `${hours}h `;
Expand Down
84 changes: 36 additions & 48 deletions apps/client/src/features/viewers/timeline/Timeline.module.scss
Original file line number Diff line number Diff line change
@@ -1,74 +1,62 @@
@use '../../../theme/viewerDefs' as *;

$timeline-entry-height: 20px;
$lane-height: 120px;

.timeline {
flex: 1;
font-weight: 600;
color: $ui-white;
}

.timelineEvents {
position: relative;
top: 0.5rem;
height: 100%;
}

.entryColumn {
.column {
display: flex;
flex-direction: column;
position: absolute;
background-color: var(--lighter, $viewer-card-bg-color);
border-bottom: 0.25rem solid var(--color, $ui-white);
border-right: 1px solid $ui-black;

min-height: 100px;
}

.entryContent {
padding-top: var(--top, 0);
padding-bottom: 0.5rem;
border-inline: 1px solid $ui-black;
// avoiding content being larger than the view
height: calc(100% - 3rem);
}

.entryText {
position: relative;
z-index: 2;
color: $ui-white;
padding-inline: 0.5em;
width: fit-content;
white-space: nowrap;

&.textBg {
background-color: var(--bg, $black-10);
}
.content {
flex: 1;
display: flex;
flex-direction: column;
gap: 1rem;
padding-top: 0.25rem;
padding-inline-start: 0.25rem;

&.lastElement {
white-space: normal;
}
background-color: var(--lighter, $viewer-card-bg-color);
border-bottom: 2px solid $ui-black;
box-shadow: 0 0.25rem 0 0 var(--color, $ui-white);
}

.start,
.title {
font-weight: 600;
}
.timeOverview {
padding-top: 0.25rem;
padding-inline-start: 0.25em;
text-transform: capitalize;
white-space: normal;
height: 6rem;

// for elapsed events, we can hide some stuff
[data-status='finished'] {
color: $gray-500;
.status {
display: none;
&[data-status='done'] {
opacity: $opacity-disabled;
}
}

[data-status='live'] {
font-weight: 600;
color: $ui-white;

.status {
color: $active-red;
&[data-status='live'] {
.status {
color: $active-red;
}
}
}

[data-status='future'] {
font-weight: 600;
color: $gray-300;

.status {
color: $green-500;
&[data-status='future'] {
.status {
color: $green-500;
}
}
}
25 changes: 4 additions & 21 deletions apps/client/src/features/viewers/timeline/Timeline.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,17 @@
import { memo } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useViewportSize } from '@mantine/hooks';
import { isOntimeEvent, MaybeNumber } from 'ontime-types';
import { dayInMs, getFirstEventNormal, getLastEventNormal, MILLIS_PER_HOUR } from 'ontime-utils';

import useRundown from '../../../common/hooks-query/useRundown';
import { isStringBoolean } from '../../viewers/common/viewUtils';

import TimelineMarkers from './timeline-markers/TimelineMarkers';
import ProgressBar from './timeline-progress-bar/TimelineProgressBar';
import { getElementPosition, getEndHour, getEstimatedWidth, getLaneLevel, getStartHour } from './timeline.utils';
import { getElementPosition, getEndHour, getStartHour } from './timeline.utils';
import { ProgressStatus, TimelineEntry } from './TimelineEntry';

import style from './Timeline.module.scss';

export default memo(Timeline);

function useTimeline() {
const { data } = useRundown();
if (data.revision === -1) {
Expand Down Expand Up @@ -44,12 +40,12 @@ interface TimelineProps {
selectedEventId: string | null;
}

export default memo(Timeline);

function Timeline(props: TimelineProps) {
const { selectedEventId } = props;
const { width: screenWidth } = useViewportSize();
const timelineData = useTimeline();
const [searchParams] = useSearchParams();
const fullHeight = isStringBoolean(searchParams.get('fullHeight'));

if (timelineData === null) {
return null;
Expand All @@ -59,9 +55,7 @@ function Timeline(props: TimelineProps) {

let hasTimelinePassedMidnight = false;
let previousEventStartTime: MaybeNumber = null;
let eventStatus: ProgressStatus = 'finished';
// a list of the right most element for each lane
const rightMostElements: Record<number, number> = {};
let eventStatus: ProgressStatus = 'done';

return (
<div className={style.timeline}>
Expand Down Expand Up @@ -97,28 +91,17 @@ function Timeline(props: TimelineProps) {
event.duration,
screenWidth,
);
const estimatedWidth = getEstimatedWidth(event.title);
const estimatedRightPosition = elementLeftPosition + estimatedWidth;
const laneLevel = getLaneLevel(rightMostElements, elementLeftPosition);

if (rightMostElements[laneLevel] === undefined || rightMostElements[laneLevel] < estimatedRightPosition) {
rightMostElements[laneLevel] = estimatedRightPosition;
}

return (
<TimelineEntry
key={eventId}
colour={event.colour}
duration={event.duration}
isLast={eventId === order[order.length - 1]}
lane={laneLevel}
left={elementLeftPosition}
status={eventStatus}
start={event.timeStart}
title={event.title}
width={elementWidth}
mayGrow={elementWidth < estimatedWidth}
fullHeight={fullHeight}
/>
);
})}
Expand Down
72 changes: 19 additions & 53 deletions apps/client/src/features/viewers/timeline/TimelineEntry.tsx
Original file line number Diff line number Diff line change
@@ -1,66 +1,40 @@
import { useLayoutEffect, useRef, useState } from 'react';

import { useTimelineStatus } from '../../../common/hooks/useSocket';
import { alpha, cx } from '../../../common/utils/styleUtils';
import { alpha } from '../../../common/utils/styleUtils';
import { formatDuration, formatTime } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';

import { getStatusLabel } from './timeline.utils';

import style from './Timeline.module.scss';

export type ProgressStatus = 'finished' | 'live' | 'future';
export type ProgressStatus = 'done' | 'live' | 'future';

interface TimelineEntry {
interface TimelineEntryProps {
colour: string;
duration: number;
isLast: boolean;
lane: number;
left: number;
status: ProgressStatus;
start: number;
title: string;
width: number;

mayGrow: boolean;
fullHeight: boolean;
}

const laneHeight = 120;
const formatOptions = {
format12: 'hh:mm a',
format24: 'HH:mm',
};

export function TimelineEntry(props: TimelineEntry) {
const { colour, duration, isLast, lane, left, status, start, title, width, mayGrow, fullHeight } = props;

const elementRef = useRef<HTMLDivElement>(null);
const [rightOffset, setRightOffset] = useState(0);

useLayoutEffect(() => {
if (!isLast || !elementRef.current) {
return;
}
const screenWidth = window.innerWidth;
const b = elementRef.current.getBoundingClientRect();
const right = b.x + b.width;
const offset = right > screenWidth ? right - screenWidth : 0;
setRightOffset(offset);
}, [isLast, title]);
export function TimelineEntry(props: TimelineEntryProps) {
const { colour, duration, left, status, start, title, width } = props;

const formattedStartTime = formatTime(start, formatOptions);
const formattedDuration = formatDuration(duration);

const lighterColour = alpha(colour, 0.7);
const alphaColour = alpha(colour, 0.6);
const columnClasses = cx([style.entryColumn, fullHeight && style.fullHeight]);
const contentClasses = cx([style.entryContent]);
const textBgClasses = cx([style.entryText, mayGrow && style.textBg, isLast && style.lastElement]);

return (
<div
className={columnClasses}
className={style.column}
style={{
'--color': colour,
'--lighter': lighterColour ?? '',
Expand All @@ -69,27 +43,18 @@ export function TimelineEntry(props: TimelineEntry) {
}}
>
<div
className={contentClasses}
className={style.content}
data-status={status}
style={{
'--color': colour,
'--top': `${lane * laneHeight}px`,
}}
>
<div
className={textBgClasses}
ref={isLast ? elementRef : null}
style={{
'--bg': alphaColour ?? '',
right: `${rightOffset}px`,
width: isLast ? `${Math.max(width, 100)}px` : 'fit-content',
}}
>
<div className={style.start}>{formattedStartTime}</div>
<div className={style.title}>{title}</div>
<div className={style.duration}>{formattedDuration}</div>
<TimelineEntryStatus status={status} start={start} />
</div>
<div>{formattedStartTime}</div>
<div>{title}</div>
</div>
<div className={style.timeOverview} data-status={status}>
<div className={style.duration}>{formattedDuration}</div>
<TimelineEntryStatus status={status} start={start} />
</div>
</div>
);
Expand All @@ -99,19 +64,20 @@ interface TimelineEntryStatusProps {
status: ProgressStatus;
start: number;
}
// we isolate this component to avoid re-rendering too many elements

// we isolate this component to avoid isolate re-renders provoked by the clock changes
function TimelineEntryStatus(props: TimelineEntryStatusProps) {
const { status, start } = props;
const { clock, offset } = useTimelineStatus();
const { getLocalizedString } = useTranslation();

let statusText = getStatusLabel(start - clock + offset, status);
if (statusText === 'live') {
statusText = getLocalizedString('timeline.live').toUpperCase();
statusText = getLocalizedString('timeline.live');
} else if (statusText === 'pending') {
statusText = getLocalizedString('timeline.pending').toUpperCase();
} else if (statusText === 'finished') {
statusText = getLocalizedString('timeline.finished').toUpperCase();
statusText = getLocalizedString('timeline.due');
} else if (statusText === 'done') {
statusText = getLocalizedString('timeline.done');
}

return <div className={style.status}>{statusText}</div>;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.progress {
.timeline {
width: 100vw;
height: 100vh;

Expand All @@ -12,7 +12,6 @@

.title {
padding-inline: 2rem;
text-align: left;
font-size: 3.5rem;
}

Expand Down
Loading

0 comments on commit 0ab3ed8

Please sign in to comment.