refactor: finish view directory migration

This commit is contained in:
Carlos Valente
2025-12-21 13:19:27 +01:00
committed by Carlos Valente
parent 2f3cf9825a
commit 08b8e73393
28 changed files with 32 additions and 32 deletions
@@ -0,0 +1,25 @@
/**
* encapsulate logic related to showing a clock timer
*/
import { MaybeNumber } from 'ontime-types';
import { formatTime } from '../../../common/utils/time';
import { FORMAT_12, FORMAT_24 } from '../../../viewerConfig';
import SuperscriptTime from '../superscript-time/SuperscriptTime';
interface ClockTimeProps {
value: MaybeNumber;
preferredFormat12?: string;
preferredFormat24?: string;
className?: string;
}
export default function ClockTime(props: ClockTimeProps) {
const { value, preferredFormat12 = FORMAT_12, preferredFormat24 = FORMAT_24, className } = props;
// TODO: should we get the params from URL here to see if the user is overriding the default?
const formattedTime = formatTime(value, { format12: preferredFormat12, format24: preferredFormat24 });
return <SuperscriptTime className={className} time={formattedTime} />;
}
@@ -0,0 +1,30 @@
/**
* encapsulate logic related to showing a running timer
*/
import { MaybeNumber } from 'ontime-types';
import { removeLeadingZero, removeSeconds } from 'ontime-utils';
import { formattedTime } from '../../../features/overview/overview.utils';
interface RunningTimeProps {
value: MaybeNumber;
hideSeconds?: boolean;
hideLeadingZero?: boolean;
className?: string;
}
export default function RunningTime(props: RunningTimeProps) {
const { value, hideSeconds, hideLeadingZero, className } = props;
let display = formattedTime(value, hideSeconds || hideLeadingZero ? 2 : 3);
if (hideLeadingZero) {
display = removeLeadingZero(display);
}
if (hideSeconds) {
display = removeSeconds(display);
}
return <div className={className}>{display}</div>;
}
@@ -5,7 +5,7 @@ import { getOffsetState } from '../../../common/utils/offset';
import { ExtendedEntry } from '../../../common/utils/rundownMetadata';
import { cx } from '../../../common/utils/styleUtils';
import { formatTime, getExpectedTimesFromExtendedEvent } from '../../../common/utils/time';
import SuperscriptPeriod from '../../../features/viewers/common/superscript-time/SuperscriptPeriod';
import SuperscriptPeriod from '../superscript-time/SuperscriptPeriod';
import { useScheduleOptions } from './schedule.options';
@@ -4,7 +4,7 @@ import { useSearchParams } from 'react-router';
import { SelectOption } from '../../../common/components/select/Select';
import { OptionTitle } from '../../../common/components/view-params-editor/constants';
import type { ViewOption } from '../../../common/components/view-params-editor/viewParams.types';
import { isStringBoolean } from '../../../features/viewers/common/viewUtils';
import { isStringBoolean } from '../viewUtils';
export const getScheduleOptions = (customFieldOptions: SelectOption[]): ViewOption => ({
title: OptionTitle.Schedule,
@@ -0,0 +1,23 @@
import './SuperscriptTime.scss';
interface SuperscriptPeriodProps {
time: string;
className?: string;
}
/**
* Receives a time string and formats periods (am/pm) as superscript
* @example 12:00 AM -> AM becomes a superscript
* @example 12:00:10 -> no formatting changes applied
*/
export default function SuperscriptPeriod({ time, className }: SuperscriptPeriodProps) {
// we assume anything after space is a period tag
const [timeString, period] = time.split(' ');
return (
<div className={className}>
{timeString}
{period && <sup className='period'>{period}</sup>}
</div>
);
}
@@ -0,0 +1,8 @@
sup.period {
top: -1em;
font-size: 0.5em;
}
.subscript {
font-size: 0.75em;
}
@@ -0,0 +1,39 @@
import { CSSProperties } from 'react';
import './SuperscriptTime.scss';
interface SuperscriptTimeProps {
time: string;
className?: string;
style?: CSSProperties;
}
/**
* When the timer includes seconds, we want to split it from the rest
*/
function getTimerParts(time: string) {
if (time.length !== 8) {
return [time, ''];
}
return [time.slice(0, 5), time.slice(5)];
}
/**
* Receives a time string and formats it with a subscript or superscript
* @example 12:00 AM -> AM becomes a superscript
* @example 12:00:10 -> the seconds become a subscript
*/
export default function SuperscriptTime({ time, className, style }: SuperscriptTimeProps) {
// we assume anything after space is a period tag
const [timeString, period] = time.split(' ');
const [mainTime, subscript] = getTimerParts(timeString);
return (
<div className={className} style={style}>
{mainTime}
{subscript && <span className='subscript'>{subscript}</span>}
{period && <sup className='period'>{period}</sup>}
</div>
);
}
+125
View File
@@ -0,0 +1,125 @@
import { MaybeNumber, MaybeString, OntimeEvent, TimerState, TimerType } from 'ontime-types';
import { MILLIS_PER_MINUTE, MILLIS_PER_SECOND, millisToString, removeLeadingZero, removeSeconds } from 'ontime-utils';
import { timerPlaceholder, timerPlaceholderMin } from '../../common/utils/styleUtils';
import { formatTime } from '../../common/utils/time';
/**
* Gathers all options that affect which timer is displayed and selects the correct data source to display
* it also handles edge cases such as freezing on end
*/
export function getTimerByType(
freezeEnd: boolean,
timerTypeNow: TimerType,
clock: number,
timerObject: Pick<TimerState, 'current' | 'elapsed'>,
timerTypeOverride?: TimerType,
): number | null {
if (!timerObject) {
return null;
}
const viewTimerType = timerTypeOverride ?? timerTypeNow;
switch (viewTimerType) {
case TimerType.CountDown:
if (timerObject.current === null) {
return null;
}
return freezeEnd ? Math.max(timerObject.current, 0) : timerObject.current;
case TimerType.CountUp:
return timerObject.elapsed;
case TimerType.Clock:
return clock;
case TimerType.None:
return null;
default: {
viewTimerType satisfies never;
return null;
}
}
}
/**
* Parses a string to semantically verify if it represents a true value
* Used in the context of parsing search params and local storage items which can be strings or null
*/
export function isStringBoolean(text: string | null) {
if (text === null) {
return false;
}
return text?.toLowerCase() === 'true' || text === '1';
}
/**
* Prepares a colour string for use in views
* Colours in params do not have the #prefix
*/
export function makeColourString(hex: string | null): string | undefined {
if (!hex) {
return undefined;
}
// ensure the hex starts with a #
return hex.startsWith('#') ? hex : `#${hex}`;
}
/**
* Retrieves a dynamic property from an event
* Considers custom fields
*/
export function getPropertyValue(event: OntimeEvent | null, property: MaybeString): string | undefined {
if (!event || typeof property !== 'string' || property === 'none') {
return undefined;
}
if (property.startsWith('custom-')) {
const field = property.split('custom-')[1];
return event.custom?.[field];
}
return event[property as keyof OntimeEvent] as string;
}
type FormattingOptions = {
removeSeconds: boolean;
removeLeadingZero: boolean;
};
export function getFormattedTimer(
timer: MaybeNumber,
timerType: TimerType,
localisedMinutes: string,
options: FormattingOptions,
): string {
if (timer == null || timerType === TimerType.None) {
return options.removeSeconds ? timerPlaceholderMin : timerPlaceholder;
}
if (timerType === TimerType.Clock) {
return formatTime(timer);
}
let timeToParse = timer;
if (options.removeSeconds) {
const isNegative = timeToParse < -MILLIS_PER_SECOND && timerType !== TimerType.CountUp;
if (isNegative) {
// in negative numbers, we need to round down
timeToParse -= MILLIS_PER_MINUTE;
}
}
let display = millisToString(timeToParse, { direction: timerType });
if (options.removeLeadingZero) {
display = removeLeadingZero(display);
}
if (options.removeSeconds) {
display = formatDisplayWithMinutes(display, localisedMinutes);
}
return display;
}
function formatDisplayWithMinutes(display: string, localisedMinutes: string): string {
display = removeSeconds(display);
return display.length < 3 ? `${display} ${localisedMinutes}` : display;
}