breaking: convert offset > over-under

change offset direction and relabel to over-under
This commit is contained in:
Carlos Valente
2025-07-14 11:12:52 +02:00
parent 92a34cf135
commit 1e1f547ce1
18 changed files with 89 additions and 72 deletions
@@ -1,8 +1,8 @@
import { ViewSettings } from 'ontime-types';
export const viewsSettingsPlaceholder: ViewSettings = {
dangerColor: '#ED3333',
dangerColor: '#ff7300',
normalColor: '#ffffffcc',
overrideStyles: false,
warningColor: '#FFAB33',
warningColor: '#ffa528',
};
+25
View File
@@ -0,0 +1,25 @@
import { MaybeNumber } from 'ontime-types';
import { millisToString } from 'ontime-utils';
import { enDash } from './styleUtils';
/**
* Formats offset text
*/
export function getOffsetText(offset: MaybeNumber): string {
if (offset === null) {
return enDash;
}
let offsetText = '';
if (offset < 0) offsetText += '-';
if (offset > 0) offsetText += '+';
offsetText += millisToString(Math.abs(offset));
return offsetText;
}
export function getOffsetState(offset: MaybeNumber): 'ahead' | 'behind' | 'muted' | null {
if (offset === null) return null;
if (offset === 0) return 'muted';
return offset < 0 ? 'behind' : 'ahead';
}
@@ -42,8 +42,8 @@ export default function TimerPreview() {
const overrideColour = (() => {
// override fallback colours from starter project
if (phase === TimerPhase.Warning) return data.warningColor ?? '#FFAB33';
if (phase === TimerPhase.Danger) return data.dangerColor ?? '#ED3333';
if (phase === TimerPhase.Warning) return data.warningColor ?? '#ffa528';
if (phase === TimerPhase.Danger) return data.dangerColor ?? '#ff7300';
return data.normalColor ?? '#FFFC';
})();
@@ -2,7 +2,7 @@ import { memo, PropsWithChildren } from 'react';
import { useIsMobileScreen } from '../../common/hooks/useIsMobileScreen';
import { ClockOverview, MetadataTimes, RuntimeOverview, StartTimes, TimerOverview } from './composite/TimeElements';
import { ClockOverview, MetadataTimes, OffsetOverview, StartTimes, TimerOverview } from './composite/TimeElements';
import TitleOverview from './composite/TitleOverview';
import { OverviewWrapper } from './OverviewWrapper';
@@ -18,7 +18,7 @@ function CuesheetMobile({ children }: PropsWithChildren) {
return (
<OverviewWrapper navElements={children}>
<TimerOverview />
<RuntimeOverview />
<OffsetOverview />
</OverviewWrapper>
);
}
@@ -29,7 +29,7 @@ function CuesheetDesktop({ children }: PropsWithChildren) {
<TitleOverview />
<StartTimes />
<TimerOverview />
<RuntimeOverview />
<OffsetOverview />
<MetadataTimes />
<ClockOverview />
</OverviewWrapper>
@@ -1,6 +1,6 @@
import { memo, PropsWithChildren } from 'react';
import { ClockOverview, MetadataTimes, ProgressOverview, RuntimeOverview, StartTimes } from './composite/TimeElements';
import { ClockOverview, MetadataTimes, OffsetOverview, ProgressOverview, StartTimes } from './composite/TimeElements';
import TitleOverview from './composite/TitleOverview';
import { OverviewWrapper } from './OverviewWrapper';
@@ -11,7 +11,7 @@ function EditorOverview({ children }: PropsWithChildren) {
<TitleOverview />
<StartTimes />
<ProgressOverview />
<RuntimeOverview />
<OffsetOverview />
<MetadataTimes />
<ClockOverview />
</OverviewWrapper>
@@ -13,9 +13,10 @@ import {
useTimer,
} from '../../../common/hooks/useSocket';
import { useEntry } from '../../../common/hooks-query/useRundown';
import { getOffsetState, getOffsetText } from '../../../common/utils/offset';
import { cx, enDash, timerPlaceholder } from '../../../common/utils/styleUtils';
import { formatTime, useTimeUntilStart } from '../../../common/utils/time';
import { calculateEndAndDaySpan, formattedTime, getOffsetText } from '../overview.utils';
import { calculateEndAndDaySpan, formattedTime } from '../overview.utils';
import { TimeColumn } from './TimeLayout';
@@ -193,14 +194,16 @@ export function ProgressOverview() {
return <TimeColumn label='Progress' value={progressText} />;
}
export function RuntimeOverview() {
export function OffsetOverview() {
const { offset, playback } = useRuntimePlaybackOverview();
const isPlaying = isPlaybackActive(playback);
const offsetText = getOffsetText(isPlaying ? offset : null);
const offsetClasses = cx([style.offset, isPlaying && (offset < 0 ? style.behind : style.ahead)]);
const correctedOffset = offset * -1;
const offsetState = getOffsetState(correctedOffset);
const offsetClasses = cx([style.offset, offsetState && style[offsetState]]);
const offsetText = getOffsetText(isPlaying ? correctedOffset : null);
return <TimeColumn label='Offset' value={offsetText} className={offsetClasses} testId='offset' />;
return <TimeColumn label='Over / under' value={offsetText} className={offsetClasses} testId='offset' />;
}
export function ClockOverview() {
@@ -1,7 +1,7 @@
import { MaybeNumber, TimerType } from 'ontime-types';
import { dayInMs, millisToString } from 'ontime-utils';
import { enDash, timerPlaceholder, timerPlaceholderMin } from '../../common/utils/styleUtils';
import { timerPlaceholder, timerPlaceholderMin } from '../../common/utils/styleUtils';
/**
* Encapsulates the logic for formatting time in overview
@@ -24,15 +24,3 @@ export function calculateEndAndDaySpan(end: MaybeNumber): [MaybeNumber, number]
return [end, 0];
}
/**
* Formats offset text
* @param offset
* @returns
*/
export function getOffsetText(offset: MaybeNumber): string {
if (offset === null) {
return enDash;
}
return millisToString(offset, { fallback: enDash });
}
@@ -53,7 +53,8 @@ export default function BlockEditor({ block }: BlockEditorProps) {
);
const isEditor = window.location.pathname.includes('editor');
const planOffset = typeof block.targetDuration !== 'number' ? null : block.targetDuration - block.duration;
const planOffset = typeof block.targetDuration !== 'number' ? null : block.duration - block.targetDuration;
const planOffsetLabel = planOffset !== null && planOffset > 0 ? 'behind' : 'ahead';
return (
<div className={style.content}>
@@ -94,11 +95,8 @@ export default function BlockEditor({ block }: BlockEditorProps) {
</div>
<div>
<Editor.Label htmlFor='eventId'>Plan offset</Editor.Label>
{
// TODO: update remote data
// TODO: remove tab index
}
<TextLikeInput delayed={Boolean(planOffset)} className={style.textLikeInput}>
<TextLikeInput offset={planOffsetLabel} className={style.textLikeInput} tabIndex={-1}>
{planOffset !== null && planOffset > 0 ? '+' : ''}
{millisToString(planOffset, { fallback: enDash })}
</TextLikeInput>
</div>
@@ -103,7 +103,7 @@ function DurationInput({
onClick={handleFakeFocus}
onFocus={handleFakeFocus}
muted={!lockedValue}
delayed={delayed}
offset={delayed ? 'behind' : undefined}
ref={textRef}
>
{children}
@@ -14,7 +14,11 @@
overflow: hidden;
&.delayed {
&.ahead {
color: $playback-ahead;
}
&.behind {
color: $ontime-delay-text;
}
@@ -5,29 +5,30 @@ import { cx } from '../../../../common/utils/styleUtils';
import style from './TextLikeInput.module.scss';
interface TextLikeInputProps extends HTMLAttributes<HTMLSpanElement> {
delayed?: boolean;
offset?: 'ahead' | 'behind';
muted?: boolean;
}
const TextLikeInput = forwardRef((props: PropsWithChildren<TextLikeInputProps>, textRef) => {
const { delayed, muted, children, className, ...elementProps } = props;
const ref = useRef<HTMLDivElement | null>(null);
const classes = cx([style.textInput, delayed && style.delayed, muted && style.muted, className]);
const TextLikeInput = forwardRef(
({ offset, muted, children, className, ...elementProps }: PropsWithChildren<TextLikeInputProps>, textRef) => {
const ref = useRef<HTMLDivElement | null>(null);
const classes = cx([style.textInput, offset && style[offset], muted && style.muted, className]);
useImperativeHandle(textRef, () => {
return {
focusParentElement() {
ref.current?.parentElement?.focus();
},
};
});
useImperativeHandle(textRef, () => {
return {
focusParentElement() {
ref.current?.parentElement?.focus();
},
};
});
return (
<div className={classes} {...elementProps} tabIndex={0} ref={ref}>
{children}
</div>
);
});
return (
<div className={classes} tabIndex={0} {...elementProps} ref={ref}>
{children}
</div>
);
},
);
TextLikeInput.displayName = 'TextLikeInput';
@@ -105,7 +105,7 @@ function TimeInputDuration({
onClick={handleFakeFocus}
onFocus={handleFakeFocus}
muted={!lockedValue}
delayed={delayed}
offset={delayed ? 'behind' : undefined}
ref={textRef}
>
{children}
@@ -2,6 +2,7 @@ import { OntimeEvent, Playback, Runtime, TimerPhase, TimerState, ViewSettings }
import { millisToString } from 'ontime-utils';
import { useAuxTimersTime } from '../../common/hooks/useSocket';
import { getOffsetState } from '../../common/utils/offset';
import { cx } from '../../common/utils/styleUtils';
import { useTranslation } from '../../translation/TranslationProvider';
import { getTimerColour } from '../utils/presentation.utils';
@@ -45,6 +46,8 @@ export default function StudioTimers({
time.phase === TimerPhase.Danger,
);
const offsetState = getOffsetState(runtime.offset);
return (
<div className='studio__timers'>
<div className='card' id='card-schedule'>
@@ -54,15 +57,8 @@ export default function StudioTimers({
<div className='runtime-timer'>{schedule.actualStart}</div>
</div>
<div>
<div className='label center'>Offset</div>
<div
className={cx([
'runtime-timer',
'center',
!eventNow && 'muted',
runtime.offset <= 0 ? 'behind' : 'ahead',
])}
>
<div className='label center'>Over / under</div>
<div className={cx(['runtime-timer', 'center', !eventNow && 'muted', offsetState && offsetState])}>
{schedule.offset}
</div>
</div>
@@ -1,14 +1,16 @@
import { OntimeEvent, Runtime, TimerState } from 'ontime-types';
import { millisToString } from 'ontime-utils';
import { getOffsetText } from '../../common/utils/offset';
import { formatTime } from '../../common/utils/time';
const timeFormat = { format12: 'h:mm a', format24: 'HH:mm' };
export function getFormattedScheduleTimes(runtime: Runtime) {
const correctedOffset = runtime.offset !== null ? runtime.offset * -1 : null;
return {
actualStart: formatTime(runtime.actualStart, timeFormat),
expectedEnd: formatTime(runtime.expectedEnd, timeFormat),
offset: millisToString(runtime.offset),
offset: getOffsetText(correctedOffset),
};
}
+2 -2
View File
@@ -33,8 +33,8 @@ export const dbModel: DatabaseModel = {
viewSettings: {
overrideStyles: false,
normalColor: '#ffffffcc',
warningColor: '#FFAB33',
dangerColor: '#ED3333',
warningColor: '#ffa528',
dangerColor: '#ff7300',
},
urlPresets: [],
customFields: {},
+2 -2
View File
@@ -532,10 +532,10 @@ export const demoDb: DatabaseModel = {
language: 'en',
},
viewSettings: {
dangerColor: '#ED3333',
dangerColor: '#ff7300',
normalColor: '#ffffffcc',
overrideStyles: false,
warningColor: '#FFAB33',
warningColor: '#ffa528',
},
customFields: {
Song: {
+2 -2
View File
@@ -467,10 +467,10 @@
"language": "en"
},
"viewSettings": {
"dangerColor": "#ED3333",
"dangerColor": "#ff7300",
"normalColor": "#ffffffcc",
"overrideStyles": false,
"warningColor": "#FFAB33"
"warningColor": "#ffa528"
},
"customFields": {
"song": {
+2 -2
View File
@@ -484,10 +484,10 @@
"language": "en"
},
"viewSettings": {
"dangerColor": "#ED3333",
"dangerColor": "#ff7300",
"normalColor": "#ffffffcc",
"overrideStyles": false,
"warningColor": "#FFAB33"
"warningColor": "#ffa528"
},
"customFields": {
"Song": {