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