improvement: hide seconds (#675)

* feat: optional timer seconds
This commit is contained in:
Carlos Valente
2023-12-31 12:13:05 +01:00
committed by GitHub
parent a5d8b91f34
commit 9a4d659b04
29 changed files with 223 additions and 125 deletions
@@ -1,5 +1,3 @@
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
//@ts-nocheck -- working on it
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { FormControl, Input, Switch } from '@chakra-ui/react';
@@ -59,7 +57,6 @@ export default function OscSettings() {
};
const resetForm = () => {
// @ts-expect-error -- we know the types dont match
reset(data);
};
@@ -1,5 +1,6 @@
import { useEffect, useState } from 'react';
import QRCode from 'react-qr-code';
import { useSearchParams } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { Message, OntimeEvent, ProjectData, Settings, SupportedEvent, ViewSettings } from 'ontime-types';
import { formatDisplay } from 'ontime-utils';
@@ -16,17 +17,13 @@ import ViewParamsEditor from '../../../common/components/view-params-editor/View
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatTime } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
import { titleVariants } from '../common/animation';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import './Backstage.scss';
const formatOptions = {
showSeconds: true,
format: 'hh:mm:ss a',
};
interface BackstageProps {
isMirrored: boolean;
publ: Message;
@@ -43,6 +40,9 @@ interface BackstageProps {
export default function Backstage(props: BackstageProps) {
const { isMirrored, publ, eventNow, eventNext, time, backstageEvents, selectedId, general, viewSettings, settings } =
props;
const [searchParams] = useSearchParams();
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const { getLocalizedString } = useTranslation();
const [blinkClass, setBlinkClass] = useState(false);
@@ -68,6 +68,12 @@ export default function Backstage(props: BackstageProps) {
return null;
}
const hideSeconds = isStringBoolean(searchParams.get('hideSeconds'));
const formatOptions = {
showSeconds: !hideSeconds,
format: 'hh:mm:ss a',
};
const clock = formatTime(time.clock, formatOptions);
const startedAt = formatTime(time.startedAt, formatOptions);
const isNegative = (time.current ?? 0) < 0;
@@ -0,0 +1,21 @@
import { removePrependedZero } from '../viewerUtils.js';
describe('removePrependedZero', () => {
it('should remove "00:" from the start of the string', () => {
const timer = '00:10:10';
const result = removePrependedZero(timer);
expect(result).toBe('10:10');
});
it('should remove "00:0:" from the start of the string', () => {
const timer = '00:01:10';
const result = removePrependedZero(timer);
expect(result).toBe('1:10');
});
it('should not modify the string if it does not start with "00:" or "00:0:"', () => {
const timer = '10:10:10';
const result = removePrependedZero(timer);
expect(result).toBe(timer);
});
});
@@ -8,6 +8,9 @@ interface SuperscriptTimeProps {
style?: CSSProperties;
}
/**
* @description receives a string like 12:00 AM and adds the period part to the superscript
*/
export default function SuperscriptTime(props: SuperscriptTimeProps) {
const { time, className, style } = props;
@@ -1,43 +1,39 @@
import { TimerType } from 'ontime-types';
import { formatDisplay } from 'ontime-utils';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatTime } from '../../../common/utils/time';
const formatOptions = {
showSeconds: true,
format: 'hh:mm:ss a',
};
import type { TimeManagerType } from '../../../common/models/TimeManager.type';
type TimerTypeParams = Pick<TimeManagerType, 'timerType' | 'current' | 'elapsed' | 'clock'>;
export function getTimerByType(timerObject?: TimerTypeParams): string | number | null {
let timer = null;
export function getTimerByType(timerObject?: TimerTypeParams): number | null {
if (!timerObject) {
return timer;
return null;
}
if (timerObject.timerType === TimerType.CountDown || timerObject.timerType === TimerType.TimeToEnd) {
timer = timerObject.current;
} else if (timerObject.timerType === TimerType.CountUp) {
timer = timerObject.elapsed;
} else if (timerObject.timerType === TimerType.Clock) {
timer = formatTime(timerObject.clock, formatOptions);
switch (timerObject.timerType) {
case TimerType.CountDown:
case TimerType.TimeToEnd:
return timerObject.current;
case TimerType.CountUp:
return Math.abs(timerObject.elapsed ?? 0);
case TimerType.Clock:
return timerObject.clock;
default: {
const exhaustiveCheck: never = timerObject.timerType;
return exhaustiveCheck;
}
}
}
/**
* Receives a string such as 00:10:10 and removes the hours field if it is 00
* @param timer
*/
export const removePrependedZero = (timer: string): string => {
if (timer.startsWith('00:0')) {
return timer.slice(4);
}
if (timer.startsWith('00:')) {
return timer.slice(3);
}
return timer;
}
export function formatTimerDisplay(timer?: string | number | null): string {
let display = '';
if (typeof timer === 'string') {
display = timer;
} else if (timer === null || typeof timer === 'undefined' || isNaN(timer)) {
display = '-- : -- : --';
} else {
display = formatDisplay(timer, true);
}
return display;
}
};
@@ -5,11 +5,12 @@ import { formatDisplay } from 'ontime-utils';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import { getTimeOption } from '../../../common/components/view-params-editor/constants';
import { getCountdownOptions } from '../../../common/components/view-params-editor/constants';
import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatTime } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
@@ -18,11 +19,6 @@ import CountdownSelect from './CountdownSelect';
import './Countdown.scss';
const formatOptions = {
showSeconds: true,
format: 'hh:mm:ss a',
};
const formatOptionsFinished = {
showSeconds: false,
format: 'hh:mm a',
@@ -99,6 +95,12 @@ export default function Countdown(props: CountdownProps) {
const isSelected = runningMessage === TimerMessage.running;
const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : '';
const hideSeconds = isStringBoolean(searchParams.get('hideSeconds'));
const formatOptions = {
showSeconds: !hideSeconds,
format: 'hh:mm:ss a',
};
const clock = formatTime(time.clock, formatOptions);
const startTime = follow === null ? '...' : formatTime(follow.timeStart + delay, formatOptions);
const endTime = follow === null ? '...' : formatTime(follow.timeEnd + delay, formatOptions);
@@ -110,12 +112,12 @@ export default function Countdown(props: CountdownProps) {
isSelected || runningMessage === TimerMessage.waiting,
);
const timeOption = getTimeOption(settings?.timeFormat ?? '24');
const timeOption = getCountdownOptions(settings?.timeFormat ?? '24');
return (
<div className={`countdown ${isMirrored ? 'mirror' : ''}`} data-testid='countdown-view'>
<NavigationMenu />
<ViewParamsEditor paramFields={[timeOption]} />
<ViewParamsEditor paramFields={timeOption} />
{follow === null ? (
<CountdownSelect events={backstageEvents} />
) : (
@@ -9,8 +9,10 @@ import ViewParamsEditor from '../../../common/components/view-params-editor/View
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { OverridableOptions } from '../../../common/models/View.types';
import { formatTime } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { formatTimerDisplay, getTimerByType } from '../common/viewerUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
import { getTimerByType, removePrependedZero } from '../common/viewerUtils';
import './MinimalTimer.scss';
@@ -24,6 +26,7 @@ interface MinimalTimerProps {
export default function MinimalTimer(props: MinimalTimerProps) {
const { isMirrored, pres, time, viewSettings } = props;
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const { getLocalizedString } = useTranslation();
const [searchParams] = useSearchParams();
useEffect(() => {
@@ -35,9 +38,8 @@ export default function MinimalTimer(props: MinimalTimerProps) {
return null;
}
// get config from url: key, text, font, size, hideovertime
// eg. http://localhost:3000/minimal?key=f00&text=fff
// Check for user options
// TODO: this should be tied to the params
// USER OPTIONS
const userOptions: OverridableOptions = {
size: 1,
};
@@ -126,10 +128,12 @@ export default function MinimalTimer(props: MinimalTimerProps) {
const hideEndMessage = searchParams.get('hideendmessage');
userOptions.hideEndMessage = isStringBoolean(hideEndMessage);
const hideTimerSeconds = searchParams.get('hideTimerSeconds');
userOptions.hideTimerSeconds = isStringBoolean(hideTimerSeconds);
const showOverlay = pres.text !== '' && pres.visible;
const isPlaying = time.playback !== Playback.Pause;
const isNegative =
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
const showEndMessage = (time.current ?? 0) < 0 && viewSettings.endMessage && !hideEndMessage;
const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt;
const showFinished = finished && !userOptions?.hideOvertime && (time.timerType !== TimerType.Clock || showEndMessage);
@@ -149,9 +153,16 @@ export default function MinimalTimer(props: MinimalTimerProps) {
: viewSettings.normalColor;
const stageTimer = getTimerByType(time);
let display = formatTimerDisplay(stageTimer);
if (isNegative) {
display = `-${display}`;
let display = '-- : -- : --';
if (stageTimer !== null) {
display = formatTime(stageTimer, {
showSeconds: !userOptions.hideTimerSeconds,
format: 'hh:mm:ss a',
});
display = removePrependedZero(display);
if (display.length < 3) {
display = `${display} ${getLocalizedString('common.minutes')}`;
}
}
const stageTimerCharacters = display.replace('/:/g', '').length;
@@ -1,5 +1,6 @@
import { useEffect } from 'react';
import QRCode from 'react-qr-code';
import { useSearchParams } from 'react-router-dom';
import { AnimatePresence, motion } from 'framer-motion';
import { Message, OntimeEvent, ProjectData, Settings, ViewSettings } from 'ontime-types';
@@ -14,17 +15,13 @@ import ViewParamsEditor from '../../../common/components/view-params-editor/View
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatTime } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
import { titleVariants } from '../common/animation';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import './Public.scss';
const formatOptions = {
showSeconds: true,
format: 'hh:mm:ss a',
};
interface BackstageProps {
isMirrored: boolean;
publ: Message;
@@ -51,6 +48,9 @@ export default function Public(props: BackstageProps) {
viewSettings,
settings,
} = props;
const [searchParams] = useSearchParams();
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const { getLocalizedString } = useTranslation();
@@ -64,6 +64,13 @@ export default function Public(props: BackstageProps) {
}
const showPublicMessage = publ.text && publ.visible;
const hideSeconds = isStringBoolean(searchParams.get('hideSeconds'));
const formatOptions = {
showSeconds: !hideSeconds,
format: 'hh:mm:ss a',
};
const clock = formatTime(time.clock, formatOptions);
const qrSize = Math.max(window.innerWidth / 15, 128);
@@ -107,7 +107,7 @@ $orange-active: #f60;
}
}
.ampmIndicator {
.clock__ampm {
font-family: monospace;
font-size: calc(var(--clock-size) / 20);
position: relative;
@@ -14,17 +14,13 @@ import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { secondsInMillis } from '../../../common/utils/dateConfig';
import { formatTime, resolveTimeFormat } from '../../../common/utils/time';
import { mth } from '../../../common/utils/timeConstants';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { trimRundown } from './studioClock.utils';
import './StudioClock.scss';
const formatOptions = {
showSeconds: false,
format: 'hh:mm',
};
interface StudioClockProps {
isMirrored: boolean;
eventNext: OntimeEvent | null;
@@ -51,15 +47,17 @@ export default function StudioClock(props: StudioClockProps) {
const MAX_TITLES = 11;
const [searchParams] = useSearchParams();
const showSeconds = searchParams.get('seconds');
const timeFormat = resolveTimeFormat();
formatOptions.showSeconds = Boolean(showSeconds);
formatOptions.format = `hh:mm${formatOptions.showSeconds ? ':ss' : ''}`;
useEffect(() => {
document.title = 'ontime - Studio Clock';
}, []);
const hideSeconds = isStringBoolean(searchParams.get('hideSeconds'));
const formatOptions = {
showSeconds: !hideSeconds,
format: 'hh:mm:ss',
};
const clock = formatTime(time.clock, formatOptions);
const secondsNow = secondsInMillis(time.clock);
const isNegative = (time.current ?? 0) < 0;
@@ -70,14 +68,15 @@ export default function StudioClock(props: StudioClockProps) {
const delayed = backstageEvents.filter((event) => isOntimeEvent(event)) as OntimeEvent[];
const trimmedRundown = trimRundown(delayed, selectedId, MAX_TITLES);
const isAm = time.clock / (mth * 12) > 12;
const timeFormat = resolveTimeFormat();
return (
<div className={`studio-clock ${isMirrored ? 'mirror' : ''}`} data-testid='studio-view'>
<NavigationMenu />
<ViewParamsEditor paramFields={studioClockOptions} />
<div className='clock-container'>
{timeFormat == '12' && <div className='ampmIndicator'>{isAm ? 'am' : 'pm'}</div>}
<div className={`studio-timer ${showSeconds ? 'studio-timer--with-seconds' : ''}`}>{clock}</div>
{timeFormat == '12' && <div className='clock__ampm'>{isAm ? 'am' : 'pm'}</div>}
<div className={`studio-timer ${formatOptions.showSeconds ? 'studio-timer--with-seconds' : ''}`}>{clock}</div>
<div
ref={titleRef}
className='next-title'
@@ -15,15 +15,10 @@ import { formatTime } from '../../../common/utils/time';
import { isStringBoolean } from '../../../common/utils/viewUtils';
import { useTranslation } from '../../../translation/TranslationProvider';
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { formatTimerDisplay, getTimerByType } from '../common/viewerUtils';
import { getTimerByType, removePrependedZero } from '../common/viewerUtils';
import './Timer.scss';
const formatOptions = {
showSeconds: true,
format: 'hh:mm:ss a',
};
// motion
const titleVariants = {
hidden: {
@@ -72,6 +67,8 @@ export default function Timer(props: TimerProps) {
hideCards: false,
hideProgress: false,
hideMessage: false,
hideTimerSeconds: false,
hideClockSeconds: false,
};
const hideClock = searchParams.get('hideClock');
@@ -86,12 +83,19 @@ export default function Timer(props: TimerProps) {
const hideMessage = searchParams.get('hideMessage');
userOptions.hideMessage = isStringBoolean(hideMessage);
const clock = formatTime(time.clock, formatOptions);
const hideClockSeconds = searchParams.get('hideClockSeconds');
userOptions.hideClockSeconds = isStringBoolean(hideClockSeconds);
const clock = formatTime(time.clock, {
showSeconds: !userOptions.hideClockSeconds,
format: 'hh:mm:ss a',
});
const hideTimerSeconds = searchParams.get('hideTimerSeconds');
userOptions.hideTimerSeconds = isStringBoolean(hideTimerSeconds);
const showOverlay = pres.text !== '' && pres.visible;
const isPlaying = time.playback !== Playback.Pause;
const isNegative =
(time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt;
const totalTime = (time.duration ?? 0) + (time.addedTime ?? 0);
@@ -113,11 +117,18 @@ export default function Timer(props: TimerProps) {
: viewSettings.normalColor;
const stageTimer = getTimerByType(time);
let display = formatTimerDisplay(stageTimer);
const stageTimerCharacters = display.replace('/:/g', '').length;
if (isNegative) {
display = `-${display}`;
let display = '-- : -- : --';
if (stageTimer !== null) {
display = formatTime(stageTimer, {
showSeconds: !userOptions.hideTimerSeconds,
format: 'hh:mm:ss a',
});
display = removePrependedZero(display);
if (display.length < 3) {
display = `${display} ${getLocalizedString('common.minutes')}`;
}
}
const stageTimerCharacters = display.replace('/:/g', '').length;
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''} ${showBlackout ? 'blackout' : ''}`;
let timerFontSize = 89 / (stageTimerCharacters - 1);