diff --git a/apps/client/src/common/components/timer-display/TimerDisplay.scss b/apps/client/src/common/components/timer-display/TimerDisplay.scss
index 698d9ddae..59db74db7 100644
--- a/apps/client/src/common/components/timer-display/TimerDisplay.scss
+++ b/apps/client/src/common/components/timer-display/TimerDisplay.scss
@@ -3,18 +3,12 @@
.timer {
font-family: var(--font-family-override, $viewer-font-family);
color: var(--timer-color-override, $timer-color);
- font-size: 20vw;
line-height: 0.9em;
text-align: center;
- letter-spacing: 0.05em;
+ letter-spacing: 0.1em;
font-weight: 600;
-
- &--small {
- font-size: 3.75em;
- text-align: center;
- letter-spacing: 0.1em;
- }
-
+ font-size: 3.75em;
+
&--finished {
color: $timer-finished-color;
}
diff --git a/apps/client/src/common/components/timer-display/TimerDisplay.tsx b/apps/client/src/common/components/timer-display/TimerDisplay.tsx
index de28d1667..7af63835f 100644
--- a/apps/client/src/common/components/timer-display/TimerDisplay.tsx
+++ b/apps/client/src/common/components/timer-display/TimerDisplay.tsx
@@ -6,9 +6,6 @@ import './TimerDisplay.scss';
interface TimerDisplayProps {
time?: number | null;
- small?: boolean;
- hideZeroHours?: boolean;
- className?: string;
}
/**
@@ -17,15 +14,19 @@ interface TimerDisplayProps {
* @constructor
*/
const TimerDisplay = (props: TimerDisplayProps) => {
- const { time, small, hideZeroHours, className = '' } = props;
+ const { time } = props;
- const display =
- (time === null || typeof time === 'undefined' || isNaN(time))
- ? '-- : -- : --'
- : formatDisplay(millisToSeconds(time), hideZeroHours);
+ let display = '';
+
+ if (time === null || typeof time === 'undefined' || isNaN(time)) {
+ display = '-- : -- : --';
+ } else {
+ display = formatDisplay(millisToSeconds(time));
+ }
const isNegative = (time ?? 0) < 0;
- const classes = `timer ${small ? 'timer--small' : ''} ${isNegative ? 'timer--finished' : ''} ${className}`;
+
+ const classes = `timer ${isNegative ? 'timer--finished' : ''}`;
return
{display}
;
};
diff --git a/apps/client/src/common/models/TimeManager.type.ts b/apps/client/src/common/models/TimeManager.type.ts
index b96ddc92a..7e1860395 100644
--- a/apps/client/src/common/models/TimeManager.type.ts
+++ b/apps/client/src/common/models/TimeManager.type.ts
@@ -1,9 +1,12 @@
-import { Playback } from 'ontime-types';
+import { Playback, TimerType } from 'ontime-types';
export type TimeManagerType = {
clock: number;
current: null | number;
elapsed: null | number;
+ duration: null | number;
+ timerBehaviour?: string;
+ timerType: TimerType;
expectedFinish: null | number;
addedTime: number;
startedAt: null | number;
diff --git a/apps/client/src/features/control/playback/PlaybackTimer.tsx b/apps/client/src/features/control/playback/PlaybackTimer.tsx
index c76ab82e0..c4dd8e2dd 100644
--- a/apps/client/src/features/control/playback/PlaybackTimer.tsx
+++ b/apps/client/src/features/control/playback/PlaybackTimer.tsx
@@ -44,7 +44,7 @@ export default function PlaybackTimer(props: PlaybackTimerProps) {
-
+
{isWaiting ? (
diff --git a/apps/client/src/features/event-editor/EventEditor.tsx b/apps/client/src/features/event-editor/EventEditor.tsx
index 50a336ed3..5ffc5d8c2 100644
--- a/apps/client/src/features/event-editor/EventEditor.tsx
+++ b/apps/client/src/features/event-editor/EventEditor.tsx
@@ -2,7 +2,7 @@ import { useCallback, useContext, useEffect, useState } from 'react';
import { Button, Select, Switch } from '@chakra-ui/react';
import { IoBan } from '@react-icons/all-files/io5/IoBan';
import { useAtom } from 'jotai';
-import { OntimeEvent } from 'ontime-types';
+import { OntimeEvent, TimerType } from 'ontime-types';
import { editorEventId } from '../../common/atoms/LocalEventSettings';
import CopyTag from '../../common/components/copy-tag/CopyTag';
@@ -84,7 +84,8 @@ export default function EventEditor() {
[emitError, event, updateEvent],
);
- const timerValidationHandler = useCallback((entry: TimeEntryField, val: number) => {
+ const timerValidationHandler = useCallback(
+ (entry: TimeEntryField, val: number) => {
if (!event) {
return;
}
@@ -97,7 +98,15 @@ export default function EventEditor() {
[event, emitWarning],
);
- const togglePublic = useCallback((currentValue: boolean) => {
+ const handleChange = useCallback(
+ (field: string, value: string) => {
+ updateEvent({ id: event.id, [field]: value });
+ },
+ [event, updateEvent],
+ );
+
+ const togglePublic = useCallback(
+ (currentValue: boolean) => {
if (!event) {
return;
}
@@ -111,9 +120,7 @@ export default function EventEditor() {
}
const delayed = delay !== 0;
- const addedTime = delayed
- ? `${delay >= 0 ? '+' : '-'} ${millisToMinutes(Math.abs(delay))} minutes`
- : null;
+ const addedTime = delayed ? `${delay >= 0 ? '+' : '-'} ${millisToMinutes(Math.abs(delay))} minutes` : null;
const newStart = delayed ? `New start ${stringFromMillis(event.timeStart + delay)}` : null;
const newEnd = delayed ? `New end ${stringFromMillis(event.timeEnd + delay)}` : null;
@@ -162,26 +169,32 @@ export default function EventEditor() {
/>
-
-
@@ -194,11 +207,7 @@ export default function EventEditor() {
-
+
@@ -209,30 +218,15 @@ export default function EventEditor() {
-
- }
- onClick={() => handleSubmit('colour', '')}
- variant='ontime-subtle'
- size='sm'
- >
+
+ } onClick={() => handleSubmit('colour', '')} variant='ontime-subtle' size='sm'>
Clear colour
-
+
diff --git a/apps/client/src/features/viewers/common/viewerUtils.ts b/apps/client/src/features/viewers/common/viewerUtils.ts
new file mode 100644
index 000000000..6b2b9f858
--- /dev/null
+++ b/apps/client/src/features/viewers/common/viewerUtils.ts
@@ -0,0 +1,44 @@
+import { TimerType } from 'ontime-types';
+
+import { TimeManagerType } from '../../../common/models/TimeManager.type';
+import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
+import { formatTime } from '../../../common/utils/time';
+
+const formatOptions = {
+ showSeconds: true,
+ format: 'hh:mm:ss a',
+};
+
+type TimerTypeParams = Pick;
+
+export function getTimerByType(timerObject?: TimerTypeParams): string | number | null {
+ let timer = null;
+ if (!timerObject) {
+ return timer;
+ }
+
+ if (timerObject.timerType === TimerType.CountDown) {
+ timer = timerObject.current;
+ } else if (timerObject.timerType === TimerType.CountUp) {
+ //todo: fix counting down issue in backend later
+ timer = (timerObject.elapsed ?? 0) < 0 ? 0 : timerObject.elapsed;
+ } else if (timerObject.timerType === TimerType.Clock) {
+ timer = formatTime(timerObject.clock, formatOptions);
+ }
+
+ 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(millisToSeconds(timer), true);
+ }
+
+ return display;
+}
diff --git a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx
index 0d232c076..fd2ababe0 100644
--- a/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx
+++ b/apps/client/src/features/viewers/minimal-timer/MinimalTimer.tsx
@@ -1,7 +1,7 @@
import { useEffect } from 'react';
import { useSearchParams } from 'react-router-dom';
import { useAtom } from 'jotai';
-import { EventData, Message, ViewSettings } from 'ontime-types';
+import { EventData, Message, TimerType, ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
@@ -9,7 +9,7 @@ import NavigationMenu from '../../../common/components/navigation-menu/Navigatio
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { OverridableOptions } from '../../../common/models/View.types';
-import { formatDisplay, millisToSeconds } from '../../../common/utils/dateConfig';
+import { formatTimerDisplay, getTimerByType } from '../common/viewerUtils';
import './MinimalTimer.scss';
@@ -128,23 +128,24 @@ export default function MinimalTimer(props: MinimalTimerProps) {
const showOverlay = pres.text !== '' && pres.visible;
const isPlaying = time.playback !== 'pause';
- const isNegative = (time.current ?? 0) < 0;
- const showFinished = isNegative && !userOptions?.hideOvertime;
+ const isNegative =
+ (time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
const showEndMessage = time.current < 0 && general.endMessage && !hideEndMessage;
+ const showFinished =
+ time.finished && !userOptions?.hideOvertime && (time.timerType !== TimerType.Clock || showEndMessage);
+
+ const stageTimer = getTimerByType(time);
+ let display = formatTimerDisplay(stageTimer);
+ if (isNegative) {
+ display = `-${display}`;
+ }
+ const stageTimerCharacters = display.replace('/:/g', '').length;
+
+ const timerFontSize = (89 / (stageTimerCharacters - 1)) * (userOptions.size || 1);
+ const timerClasseNames = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
const baseClasses = `minimal-timer ${isMirrored ? 'mirror' : ''}`;
- let stageTimer;
- if (time.current === null) {
- stageTimer = '- - : - -';
- } else {
- stageTimer = formatDisplay(Math.abs(millisToSeconds(time.current)), true);
- if (time.current < 0) {
- stageTimer = `-${stageTimer}`;
- }
- }
- const stageTimerCharacters = stageTimer.replace('/:/g', '').length;
-
return (
{general.endMessage}
) : (
- {stageTimer}
+ {display}
)}
diff --git a/apps/client/src/features/viewers/timer/Timer.scss b/apps/client/src/features/viewers/timer/Timer.scss
index 6a12c2ab7..665add342 100644
--- a/apps/client/src/features/viewers/timer/Timer.scss
+++ b/apps/client/src/features/viewers/timer/Timer.scss
@@ -35,6 +35,7 @@
grid-area: clock;
margin-left: auto;
font-weight: 600;
+ transition: opacity $viewer-transition-time;
.label {
font-size: clamp(16px, 1.5vw, 24px);
@@ -47,6 +48,10 @@
color: var(--secondary-color-override, $viewer-secondary-color);
letter-spacing: 0.05em;
}
+
+ &--hidden {
+ opacity: 0;
+ }
}
/* =================== TITLES ===================*/
@@ -71,7 +76,6 @@
grid-area: timer;
justify-self: center;
align-self: center;
- color: var(--timer-color-override, $timer-color);
.end-message {
text-align: center;
@@ -84,6 +88,22 @@
.timer {
opacity: 1;
+ font-size: 20vw;
+ font-family: var(--font-family-override, $viewer-font-family);
+ color: var(--timer-color-override, $timer-color);
+ line-height: 0.9em;
+ text-align: center;
+ letter-spacing: 0.05em;
+ font-weight: 600;
+
+ &--paused {
+ opacity: $viewer-opacity-disabled;
+ transition: $viewer-transition-time;
+ }
+
+ &--finished {
+ color: $timer-finished-color;
+ }
}
}
@@ -93,13 +113,12 @@
margin: 0 auto;
opacity: 1;
transition: $viewer-transition-time;
- }
- .timer--paused,
- .progress-container--paused {
+ &--paused {
opacity: $viewer-opacity-disabled;
transition: $viewer-transition-time;
}
+}
/* =================== OVERLAY ===================*/
diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx
index 9e3758a75..457f7d7a9 100644
--- a/apps/client/src/features/viewers/timer/Timer.tsx
+++ b/apps/client/src/features/viewers/timer/Timer.tsx
@@ -1,16 +1,17 @@
import { useEffect } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import { useAtom } from 'jotai';
+import { TimerType } from 'ontime-types';
import PropTypes from 'prop-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import { mirrorViewersAtom } from '../../../common/atoms/ViewerSettings';
import NavigationMenu from '../../../common/components/navigation-menu/NavigationMenu';
import ProgressBar from '../../../common/components/progress-bar/ProgressBar';
-import TimerDisplay from '../../../common/components/timer-display/TimerDisplay';
import TitleCard from '../../../common/components/title-card/TitleCard';
import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet';
import { formatTime } from '../../../common/utils/time';
+import { formatTimerDisplay, getTimerByType } from '../common/viewerUtils';
import './Timer.scss';
@@ -61,29 +62,49 @@ export default function Timer(props) {
const clock = formatTime(time.clock, formatOptions);
const showOverlay = pres.text !== '' && pres.visible;
const isPlaying = time.playback !== 'pause';
- const normalisedTime = time.current === null ? null : Math.max(time.current, 0);
+ const isNegative =
+ (time.current ?? 0) < 0 && time.timerType !== TimerType.Clock && time.timerType !== TimerType.CountUp;
+
+ const showEndMessage = time.current < 0 && general.endMessage;
const showProgress = time.playback !== 'stop';
+ const showFinished = time.finished && (time.timerType !== TimerType.Clock || showEndMessage);
+ const showClock = time.timerType !== TimerType.Clock;
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''}`;
+ const stageTimer = getTimerByType(time);
+ let display = formatTimerDisplay(stageTimer);
+ const stageTimerCharacters = display.replace('/:/g', '').length;
+ if (isNegative) {
+ display = `-${display}`;
+ }
+
+ const timerFontSize = 100 / stageTimerCharacters;
+ const timerClasseNames = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
+
return (
-
+
-
+
- {time.finished ? (
-
- {!general.endMessage ? : general.endMessage}
-
+ {showEndMessage ? (
+
{general.endMessage}
) : (
-
+
+ {display}
+
)}
diff --git a/apps/server/src/models/eventsDefinition.js b/apps/server/src/models/eventsDefinition.js
deleted file mode 100644
index 1eaec867c..000000000
--- a/apps/server/src/models/eventsDefinition.js
+++ /dev/null
@@ -1,35 +0,0 @@
-export const event = {
- title: '',
- subtitle: '',
- presenter: '',
- note: '',
- timeType: 'start-end',
- timeStart: 0,
- timeEnd: 0,
- duration: 0,
- isPublic: false,
- skip: false,
- colour: '',
- user0: '',
- user1: '',
- user2: '',
- user3: '',
- user4: '',
- user5: '',
- user6: '',
- user7: '',
- user8: '',
- user9: '',
- type: 'event',
- revision: 0,
-};
-
-export const delay = {
- duration: 0,
- type: 'delay',
- revision: 0,
-};
-
-export const block = {
- type: 'block',
-};
diff --git a/apps/server/src/models/eventsDefinition.ts b/apps/server/src/models/eventsDefinition.ts
new file mode 100644
index 000000000..769b076d3
--- /dev/null
+++ b/apps/server/src/models/eventsDefinition.ts
@@ -0,0 +1,38 @@
+import { OntimeBlock, OntimeDelay, OntimeEvent, SupportedEvent, TimerType } from 'ontime-types';
+
+export const event: Omit
= {
+ title: '',
+ subtitle: '',
+ presenter: '',
+ note: '',
+ timerBehaviour: 'start-end',
+ timerType: TimerType.CountDown,
+ timeStart: 0,
+ timeEnd: 0,
+ duration: 0,
+ isPublic: false,
+ skip: false,
+ colour: '',
+ user0: '',
+ user1: '',
+ user2: '',
+ user3: '',
+ user4: '',
+ user5: '',
+ user6: '',
+ user7: '',
+ user8: '',
+ user9: '',
+ type: SupportedEvent.Event,
+ revision: 0,
+};
+
+export const delay: Omit = {
+ duration: 0,
+ type: SupportedEvent.Delay,
+ revision: 0,
+};
+
+export const block: Omit = {
+ type: SupportedEvent.Block,
+};
diff --git a/apps/server/src/services/TimerService.ts b/apps/server/src/services/TimerService.ts
index 96f1f4aff..a68055453 100644
--- a/apps/server/src/services/TimerService.ts
+++ b/apps/server/src/services/TimerService.ts
@@ -1,4 +1,4 @@
-import { TimerLifeCycle } from 'ontime-types';
+import { TimerLifeCycle, TimerType } from 'ontime-types';
import { eventStore } from '../stores/EventStore.js';
import { PlaybackService } from './PlaybackService.js';
@@ -27,6 +27,7 @@ export class TimerService {
secondaryTimer: number | null;
selectedEventId: string | null;
duration: number | null;
+ timerType: TimerType | null;
};
/**
@@ -91,6 +92,7 @@ export class TimerService {
secondaryTimer: null,
selectedEventId: null,
duration: null,
+ timerType: null,
};
this.loadedTimerId = null;
this._pausedInterval = 0;
@@ -121,6 +123,7 @@ export class TimerService {
// update relevant information and force update
this.timer.duration = timer.duration;
+ this.timer.timerType = timer.timerType;
// this might not be ideal
this.timer.finishedAt = null;
@@ -138,7 +141,8 @@ export class TimerService {
* @param {number} timer.timeStart
* @param {number} timer.timeEnd
* @param {number} timer.duration
- * @param {string} timer.timeType
+ * @param {string} timer.timerBehaviour
+ * @param {string} timer.timerType
* @param {boolean} timer.skip
*/
load(timer) {
@@ -152,6 +156,7 @@ export class TimerService {
this.timer.duration = timer.duration;
this.timer.current = timer.duration;
this.playback = 'armed';
+ this.timer.timerType = timer.timerType;
this._pausedInterval = 0;
this._pausedAt = 0;
diff --git a/apps/server/src/utils/parser.js b/apps/server/src/utils/parser.js
index 9e663c979..100fd86a4 100644
--- a/apps/server/src/utils/parser.js
+++ b/apps/server/src/utils/parser.js
@@ -310,7 +310,8 @@ export const validateEvent = (eventArgs) => {
presenter: makeString(e.presenter, d.presenter),
timeStart: start,
timeEnd: end,
- timeType: 'start-end',
+ timerBehaviour: makeString(e.timerBehaviour, d.timerBehaviour),
+ timerType: makeString(e.timerType, d.timerType),
duration: validateDuration(start, end),
isPublic: typeof e.isPublic === 'boolean' ? e.isPublic : d.isPublic,
skip: typeof e.skip === 'boolean' ? e.skip : d.skip,
diff --git a/packages/types/src/definitions/TimerType.type.ts b/packages/types/src/definitions/TimerType.type.ts
index 69150eeae..25446c941 100644
--- a/packages/types/src/definitions/TimerType.type.ts
+++ b/packages/types/src/definitions/TimerType.type.ts
@@ -1,4 +1,4 @@
-export enum TimerTypeType {
+export enum TimerType {
CountDown = 'count-down',
CountUp = 'count-up',
Clock = 'clock'
diff --git a/packages/types/src/definitions/core/OntimeEvent.type.ts b/packages/types/src/definitions/core/OntimeEvent.type.ts
index ed73eb658..6d2fa4732 100644
--- a/packages/types/src/definitions/core/OntimeEvent.type.ts
+++ b/packages/types/src/definitions/core/OntimeEvent.type.ts
@@ -1,3 +1,5 @@
+import { TimerType } from '../TimerType.type.js';
+
export enum SupportedEvent {
Event = 'event',
Delay = 'delay',
@@ -26,7 +28,8 @@ export type OntimeEvent = OntimeBaseEvent & {
subtitle: string;
presenter: string;
note: string;
- timeType?: string;
+ timerBehaviour: 'start-end',
+ timerType: TimerType,
timeStart: number;
timeEnd: number;
duration: number;
diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts
index 68ee07488..f07fd7389 100644
--- a/packages/types/src/index.ts
+++ b/packages/types/src/index.ts
@@ -14,7 +14,7 @@ import { OSCSettings, OscSubscription } from './definitions/core/OscSettings.typ
import { Playback } from './definitions/runtime/Playback.type.js';
import { Settings } from './definitions/core/Settings.type.js';
import { TimerLifeCycle } from './definitions/core/TimerLifecycle.type.js';
-import { TimerTypeType } from './definitions/TimerType.type.js';
+import { TimerType } from './definitions/TimerType.type.js';
import { UserFields } from './definitions/core/UserFields.type.js';
import { ViewSettings } from './definitions/core/Views.type.js';
@@ -22,10 +22,10 @@ import { ViewSettings } from './definitions/core/Views.type.js';
export type { DatabaseModel };
// ---> Rundown
-export type { TimerTypeType };
+export { TimerType };
+export { SupportedEvent };
export type { OntimeBaseEvent, OntimeBlock, OntimeDelay, OntimeEvent };
export type { OntimeRundown, OntimeRundownEntry };
-export { SupportedEvent };
// ---> Event
export type { EventData };