Over under (#771)

* feat: schedule offset
This commit is contained in:
Carlos Valente
2024-02-16 21:04:58 +01:00
committed by GitHub
parent 436a34aaee
commit 1a420a1ddd
34 changed files with 437 additions and 157 deletions
@@ -5,6 +5,7 @@ import { IoSunny } from '@react-icons/all-files/io5/IoSunny';
import { IoSunnyOutline } from '@react-icons/all-files/io5/IoSunnyOutline';
import { setMessage, useMessageControl } from '../../../common/hooks/useSocket';
import { enDash } from '../../../common/utils/styleUtils';
import InputRow from './InputRow';
@@ -64,7 +65,7 @@ export default function MessageControl() {
</div>
<InputRow
label='External Message'
placeholder='-'
placeholder={enDash}
readonly
text={message.external.text || ''}
visible={message.external.visible || false}
@@ -1,7 +1,7 @@
import { MaybeNumber } from 'ontime-types';
import { millisToString } from 'ontime-utils';
import { cx } from '../../../../common/utils/styleUtils';
import { cx, timerPlaceholder } from '../../../../common/utils/styleUtils';
import style from './TimerDisplay.module.scss';
@@ -11,16 +11,17 @@ interface TimerDisplayProps {
/**
* Displays time in ms in formatted timetag
* Typically used in production views
*/
export default function TimerDisplay(props: TimerDisplayProps) {
const { time } = props;
if (time == null) {
return <div className={style.timer}>-- : -- : --</div>;
return <div className={style.timer}>{timerPlaceholder}</div>;
}
const isNegative = time < 0;
const display = millisToString(Math.abs(time), { fallback: '-- : -- : --' });
const display = millisToString(Math.abs(time), { fallback: timerPlaceholder });
const classes = cx([style.timer, isNegative ? style.finished : null]);
return <div className={classes}>{display}</div>;
@@ -1,12 +1,6 @@
$table-font-size: calc(1rem - 2px);
$table-header-font-size: calc(1rem - 3px);
@mixin ellipsis-overflow() {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.cuesheetContainer {
grid-area: table;
display: flex;
@@ -10,7 +10,7 @@ interface DelayRowProps {
function DelayRow(props: DelayRowProps) {
const { duration } = props;
const delayTime = millisToDelayString(duration);
const delayTime = millisToDelayString(duration, 'expanded');
return (
<tr className={style.delayRow}>
@@ -8,7 +8,7 @@ import { Playback, ProjectData } from 'ontime-types';
import PlaybackIcon from '../../../common/components/playback-icon/PlaybackIcon';
import useFullscreen from '../../../common/hooks/useFullscreen';
import useProjectData from '../../../common/hooks-query/useProjectData';
import { cx } from '../../../common/utils/styleUtils';
import { cx, enDash } from '../../../common/utils/styleUtils';
import { tooltipDelayFast } from '../../../ontimeConfig';
import { useCuesheetSettings } from '../store/CuesheetSettings';
@@ -42,15 +42,15 @@ export default function CuesheetTableHeader({ handleExport, featureData }: Cuesh
const selected = !featureData.numEvents
? 'No events'
: `Event ${featureData.selectedEventIndex != null ? featureData.selectedEventIndex + 1 : '-'}/${
featureData.numEvents ? featureData.numEvents : '-'
: `Event ${featureData.selectedEventIndex != null ? featureData.selectedEventIndex + 1 : enDash}/${
featureData.numEvents ? featureData.numEvents : enDash
}`;
return (
<div className={style.header}>
<div className={style.event}>
<div className={style.title}>{project?.title || '-'}</div>
<div className={style.eventNow}>{featureData?.titleNow || '-'}</div>
<div className={style.title}>{project?.title || enDash}</div>
<div className={style.eventNow}>{featureData?.titleNow || enDash}</div>
</div>
<div className={style.playback}>
<div className={style.playbackLabel}>{selected}</div>
@@ -3,6 +3,8 @@ import { UseFormRegister } from 'react-hook-form';
import { IconButton, Input, InputGroup, InputRightElement } from '@chakra-ui/react';
import { IoEyeOutline } from '@react-icons/all-files/io5/IoEyeOutline';
import { enDash } from '../../../common/utils/styleUtils';
interface FormInput {
[key: string]: string;
}
@@ -21,7 +23,7 @@ export default function ModalPinInput({ register, formName, isDisabled }: ModalP
type={isVisible ? 'text' : 'password'}
maxLength={4}
{...register(formName)}
placeholder='-'
placeholder={enDash}
isDisabled={isDisabled}
/>
<InputRightElement>
@@ -2,54 +2,26 @@
grid-area: overview;
display: flex;
align-items: center;
justify-content: start;
justify-content: space-between;
font-size: $inner-section-text-size;
gap: 2rem;
padding-left: 1rem;
padding-right: 0.5rem;
}
.titles {
flex: 1;
padding: 0 1rem;
}
.title {
font-size: 1.5rem;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@include ellipsis-overflow;
}
.description {
font-size: 1rem;
color: $label-gray;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
@include ellipsis-overflow;
}
.inline {
display: flex;
align-items: center;
gap: 0.25rem;
.ahead {
color: $green-500;
}
@mixin indicator($bg-color) {
&::before {
content: '';
background-color: $bg-color;
display: inline-flex;
height: 0.75em;
width: 0.75em;
vertical-align: middle;
margin-right: 0.25rem;
}
}
.start {
@include indicator($green-500);
}
.end {
@include indicator($red-500);
.behind {
color: $ontime-delay-text;
}
+42 -37
View File
@@ -1,34 +1,39 @@
import { Tooltip } from '@chakra-ui/react';
import { MaybeNumber } from 'ontime-types';
import { millisToString } from 'ontime-utils';
import ErrorBoundary from '../../common/components/error-boundary/ErrorBoundary';
import PlaybackIcon from '../../common/components/playback-icon/PlaybackIcon';
import { useRuntimeOverview } from '../../common/hooks/useSocket';
import { useRuntimeOverview, useRuntimePlaybackOverview } from '../../common/hooks/useSocket';
import useProjectData from '../../common/hooks-query/useProjectData';
import { formatTime } from '../../common/utils/time';
import { enDash, timerPlaceholder } from '../../common/utils/styleUtils';
import styles from './Overview.module.scss';
import { TimeColumn, TimeRow } from './composite/TimeLayout';
import style from './Overview.module.scss';
/**
* Encapsulates the logic for formatting time in overview
* @param time
* @returns
*/
function formattedTime(time: MaybeNumber) {
return millisToString(time, { fallback: timerPlaceholder });
}
export default function Overview() {
const { plannedEnd, plannedStart, actualStart, expectedEnd } = useRuntimeOverview();
return (
<div className={styles.overview}>
<div className={style.overview}>
<ErrorBoundary>
<TitlesOverview />
<div className={styles.clocks}>
<Tooltip label='Planned start'>
<div className={styles.start}>Planned start</div>
</Tooltip>
<Tooltip label='Actual start'>
<div className={styles.start}>Actual start</div>
</Tooltip>
<div className={style.column}>
<TimeRow label='Planned start' value={formattedTime(plannedStart)} className={style.start} />
<TimeRow label='Actual start' value={formattedTime(actualStart)} className={style.start} />
</div>
<RuntimeOverview />
<div className={styles.clocks}>
<Tooltip label='Planned end'>
<div className={styles.end}>Planned end</div>
</Tooltip>
<Tooltip label='Expected end'>
<div className={styles.end}>Expected end</div>
</Tooltip>
<div className={style.column}>
<TimeRow label='Planned end' value={formattedTime(plannedEnd)} className={style.end} />
<TimeRow label='Expected end' value={formattedTime(expectedEnd)} className={style.end} />
</div>
</ErrorBoundary>
</div>
@@ -39,31 +44,31 @@ function TitlesOverview() {
const { data } = useProjectData();
return (
<div className={styles.titles}>
<div className={styles.title}>{data.title}</div>
<div className={styles.description}>{data.description}</div>
<div className={style.titles}>
<div className={style.title}>{data.title}</div>
<div className={style.description}>{data.description}</div>
</div>
);
}
function RuntimeOverview() {
const { playback, clock, numEvents, selectedEventIndex } = useRuntimeOverview();
const { clock, numEvents, selectedEventIndex, offset } = useRuntimePlaybackOverview();
const current = selectedEventIndex !== null ? selectedEventIndex + 1 : '-';
const ofTotal = numEvents || '-';
const current = selectedEventIndex !== null ? selectedEventIndex + 1 : enDash;
const ofTotal = numEvents || enDash;
const progressText = numEvents ? `${current} of ${ofTotal}` : '';
const display = formatTime(clock);
const isAhead = offset <= 0;
let offsetText = millisToString(Math.abs(offset), { fallback: enDash });
if (offsetText !== enDash) {
offsetText = isAhead ? `+${offsetText}` : `${enDash}${offsetText}`;
}
return (
<div className={styles.clocks}>
<div className={styles.inline}>
<PlaybackIcon state={playback} skipTooltip />
{display}
</div>
<div className={styles.inline}>
<span>{`(${current} / ${ofTotal})`}</span>
Over / Under
</div>
</div>
<>
<TimeColumn label='Progress' value={progressText} />
<TimeColumn label='Offset' value={offsetText} className={isAhead ? style.ahead : style.behind} />
<TimeColumn label='Time now' value={formattedTime(clock)} />
</>
);
}
@@ -0,0 +1,40 @@
.label {
color: $label-gray;
font-size: calc(1rem - 2px);
width: 10em; // a number large enough to force right alignment
}
.clock {
text-align: left;
font-size: 1.5rem;
letter-spacing: 0.5px;
min-width: 5em;
&::after {
content: '\200b';
}
}
.column {
display: flex;
flex-direction: column;
.label {
line-height: 0.9em;
}
}
.row {
display: flex;
align-items: center;
gap: 0.5rem;
.label {
text-align: right;
}
.clock {
font-size: 1.25rem;
}
}
@@ -0,0 +1,27 @@
import { cx } from '../../../common/utils/styleUtils';
import style from './TimeLayout.module.scss';
interface TimeLayoutProps {
label: string;
value: string;
className?: string;
}
export function TimeColumn({ label, value, className }: TimeLayoutProps) {
return (
<div className={style.column}>
<span className={style.label}>{label}</span>
<span className={cx([style.clock, className])}>{value}</span>
</div>
);
}
export function TimeRow({ label, value, className }: TimeLayoutProps) {
return (
<div className={style.row}>
<span className={style.label}>{label}</span>
<span className={cx([style.clock, className])}>{value}</span>
</div>
);
}
@@ -64,9 +64,9 @@ const EventEditorTimes = (props: EventEditorTimesProps) => {
const hasDelay = delay !== 0;
const delayLabel = hasDelay
? `Event is ${millisToDelayString(delay)}. New schedule ${millisToString(timeStart + delay)}${millisToString(
timeEnd + delay,
)}`
? `Event is ${millisToDelayString(delay, 'expanded')}. New schedule ${millisToString(
timeStart + delay,
)}${millisToString(timeEnd + delay)}`
: '';
return (
@@ -10,6 +10,7 @@ import ViewParamsEditor from '../../../common/components/view-params-editor/View
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { OverridableOptions } from '../../../common/models/View.types';
import { timerPlaceholder } from '../../../common/utils/styleUtils';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
import { getTimerByType } from '../common/viewerUtils';
@@ -153,7 +154,7 @@ export default function MinimalTimer(props: MinimalTimerProps) {
: viewSettings.normalColor;
const stageTimer = getTimerByType(time);
let display = millisToString(stageTimer, { fallback: '-- : -- : --' });
let display = millisToString(stageTimer, { fallback: timerPlaceholder });
if (stageTimer !== null) {
if (hideTimerSeconds) {
display = removeSeconds(display);
@@ -12,6 +12,7 @@ import { getTimerOptions } from '../../../common/components/view-params-editor/c
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { ViewExtendedTimer } from '../../../common/models/TimeManager.type';
import { timerPlaceholder } from '../../../common/utils/styleUtils';
import { formatTime, getDefaultFormat } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
@@ -115,7 +116,7 @@ export default function Timer(props: TimerProps) {
: viewSettings.normalColor;
const stageTimer = getTimerByType(time);
let display = millisToString(stageTimer, { fallback: '-- : -- : --' });
let display = millisToString(stageTimer, { fallback: timerPlaceholder });
if (stageTimer !== null) {
if (hideTimerSeconds) {
display = removeSeconds(display);