diff --git a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx
index cf3e1f12e..7937e8b5e 100644
--- a/apps/client/src/features/modals/settings-modal/AliasesForm.tsx
+++ b/apps/client/src/features/modals/settings-modal/AliasesForm.tsx
@@ -112,6 +112,7 @@ export default function AliasesForm() {
icon={
}
colorScheme='red'
isDisabled={disableInputs}
+ data-testid={`field__delete_${index}`}
/>
handleLinks(event, alias.alias)}
@@ -140,8 +143,14 @@ export default function AliasesForm() {
icon={}
colorScheme='red'
isDisabled={disableInputs}
+ data-testid={`field__test_${index}`}
+ />
+
-
);
})}
diff --git a/apps/client/src/features/viewers/ViewWrapper.tsx b/apps/client/src/features/viewers/ViewWrapper.tsx
index c51069d67..53503c8b9 100644
--- a/apps/client/src/features/viewers/ViewWrapper.tsx
+++ b/apps/client/src/features/viewers/ViewWrapper.tsx
@@ -14,6 +14,7 @@ type WithDataProps = {
pres: TimerMessage;
publ: Message;
lower: Message;
+ external: Message;
eventNow: OntimeEvent | null;
publicEventNow: OntimeEvent | null;
eventNext: OntimeEvent | null;
@@ -51,12 +52,12 @@ const withData = (Component: ComponentType
) => {
}, [rundownData]);
// websocket data
- const data = useStore(runtime);
const {
timer,
publicMessage,
timerMessage,
lowerMessage,
+ externalMessage,
playback,
onAir,
eventNext,
@@ -64,7 +65,7 @@ const withData =
(Component: ComponentType
) => {
publicEventNow,
eventNow,
loaded,
- } = data;
+ } = useStore(runtime);
const publicSelectedId = loaded.selectedPublicEventId;
const selectedId = loaded.selectedEventId;
const nextId = loaded.nextEventId;
@@ -92,6 +93,7 @@ const withData =
(Component: ComponentType
) => {
pres={timerMessage}
publ={publicMessage}
lower={lowerMessage}
+ external={externalMessage}
eventNow={eventNow}
publicEventNow={publicEventNow}
eventNext={eventNext}
diff --git a/apps/client/src/features/viewers/backstage/Backstage.scss b/apps/client/src/features/viewers/backstage/Backstage.scss
index 208ed1aee..45d549e50 100644
--- a/apps/client/src/features/viewers/backstage/Backstage.scss
+++ b/apps/client/src/features/viewers/backstage/Backstage.scss
@@ -60,6 +60,7 @@
font-weight: 600;
color: var(--secondary-color-override, $viewer-secondary-color);
letter-spacing: 0.05em;
+ line-height: 0.95em;
}
.message {
diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx
index 94fb848b1..01e1dab6f 100644
--- a/apps/client/src/features/viewers/backstage/Backstage.tsx
+++ b/apps/client/src/features/viewers/backstage/Backstage.tsx
@@ -18,6 +18,7 @@ import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatTime } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
import { titleVariants } from '../common/animation';
+import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import './Backstage.scss';
@@ -97,7 +98,7 @@ export default function Backstage(props: BackstageProps) {
{general.title}
{getLocalizedString('common.time_now')}
-
{clock}
+
@@ -128,12 +129,16 @@ export default function Backstage(props: BackstageProps) {
{getLocalizedString('common.started_at')}
-
{startedAt}
+
{getLocalizedString('common.expected_finish')}
-
{expectedFinish}
+ {isNegative ? (
+
{expectedFinish}
+ ) : (
+
+ )}
diff --git a/apps/client/src/features/viewers/clock/Clock.tsx b/apps/client/src/features/viewers/clock/Clock.tsx
index ef40764a2..871a053e8 100644
--- a/apps/client/src/features/viewers/clock/Clock.tsx
+++ b/apps/client/src/features/viewers/clock/Clock.tsx
@@ -10,6 +10,7 @@ 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 SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import './Clock.scss';
@@ -135,7 +136,8 @@ export default function Clock(props: ClockProps) {
>
-
- {clock}
-
+ />
);
}
diff --git a/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.scss b/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.scss
new file mode 100644
index 000000000..301b9d7da
--- /dev/null
+++ b/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.scss
@@ -0,0 +1,4 @@
+sup.period {
+ top: -1em;
+ font-size: 0.4em;
+}
diff --git a/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.tsx b/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.tsx
new file mode 100644
index 000000000..6a1b99054
--- /dev/null
+++ b/apps/client/src/features/viewers/common/superscript-time/SuperscriptTime.tsx
@@ -0,0 +1,23 @@
+import { CSSProperties } from 'react';
+
+import './SuperscriptTime.scss';
+
+interface SuperscriptTimeProps {
+ time: string;
+ className?: string;
+ style?: CSSProperties;
+}
+
+export default function SuperscriptTime(props: SuperscriptTimeProps) {
+ const { time, className, style } = props;
+
+ // we assume anything after space is a period tag
+ const [timeString, period] = time.split(' ');
+
+ return (
+
+ {timeString}
+ {period && {period}}
+
+ );
+}
diff --git a/apps/client/src/features/viewers/countdown/Countdown.scss b/apps/client/src/features/viewers/countdown/Countdown.scss
index 7859e460e..c2c33a57b 100644
--- a/apps/client/src/features/viewers/countdown/Countdown.scss
+++ b/apps/client/src/features/viewers/countdown/Countdown.scss
@@ -68,6 +68,7 @@
font-size: clamp(32px, 3.5vw, 50px);
color: var(--secondary-color-override, $viewer-secondary-color);
letter-spacing: 0.05em;
+ line-height: 0.95em;
}
}
@@ -135,6 +136,7 @@
font-size: clamp(32px, 3.5vw, 50px);
color: var(--secondary-color-override, $viewer-secondary-color);
letter-spacing: 0.05em;
+ line-height: 0.95em;
&--delayed {
color: $delay-color;
diff --git a/apps/client/src/features/viewers/countdown/Countdown.tsx b/apps/client/src/features/viewers/countdown/Countdown.tsx
index 441ded578..b6ac0658b 100644
--- a/apps/client/src/features/viewers/countdown/Countdown.tsx
+++ b/apps/client/src/features/viewers/countdown/Countdown.tsx
@@ -11,6 +11,7 @@ import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatTime } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
+import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { fetchTimerData, TimerMessage } from './countdown.helpers';
import CountdownSelect from './CountdownSelect';
@@ -118,26 +119,27 @@ export default function Countdown(props: CountdownProps) {
{getLocalizedString('common.time_now')}
-
{clock}
+
{runningMessage !== TimerMessage.unhandled && (
{getLocalizedString(`countdown.${runningMessage}`)}
)}
-
- {formattedTimer}
-
+
{follow?.title || 'Untitled Event'}
{getLocalizedString('common.start_time')}
-
{startTime}
+
{getLocalizedString('common.end_time')}
-
{endTime}
+
diff --git a/apps/client/src/features/viewers/countdown/CountdownSelect.tsx b/apps/client/src/features/viewers/countdown/CountdownSelect.tsx
index 056a05673..16aa5c9b6 100644
--- a/apps/client/src/features/viewers/countdown/CountdownSelect.tsx
+++ b/apps/client/src/features/viewers/countdown/CountdownSelect.tsx
@@ -9,6 +9,10 @@ import { sanitiseTitle } from './countdown.helpers';
import './Countdown.scss';
+const formatOptions = {
+ format: 'hh:mm a',
+};
+
interface CountdownSelectProps {
events: OntimeRundownEntry[];
}
@@ -31,8 +35,8 @@ export default function CountdownSelect(props: CountdownSelectProps) {
filteredEvents.map((event: OntimeEvent, counter: number) => {
const index = counter + 1;
const title = sanitiseTitle(event.title);
- const start = formatTime(event.timeStart, { format: 'hh:mm' });
- const end = formatTime(event.timeEnd, { format: 'hh:mm' });
+ const start = formatTime(event.timeStart, formatOptions);
+ const end = formatTime(event.timeEnd, formatOptions);
return (
diff --git a/apps/client/src/features/viewers/public/Public.scss b/apps/client/src/features/viewers/public/Public.scss
index 504cb24ce..d38f72110 100644
--- a/apps/client/src/features/viewers/public/Public.scss
+++ b/apps/client/src/features/viewers/public/Public.scss
@@ -58,6 +58,7 @@
font-size: clamp(32px, 3.5vw, 50px);
font-weight: 600;
letter-spacing: 0.05em;
+ line-height: 0.95em;
}
.message {
diff --git a/apps/client/src/features/viewers/public/Public.tsx b/apps/client/src/features/viewers/public/Public.tsx
index 78d97a4f4..a9b779e99 100644
--- a/apps/client/src/features/viewers/public/Public.tsx
+++ b/apps/client/src/features/viewers/public/Public.tsx
@@ -16,6 +16,7 @@ import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatTime } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
import { titleVariants } from '../common/animation';
+import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import './Public.scss';
@@ -63,7 +64,7 @@ export default function Public(props: BackstageProps) {
{general.title}
{getLocalizedString('common.time_now')}
-
{clock}
+
diff --git a/apps/client/src/features/viewers/studio/StudioClock.scss b/apps/client/src/features/viewers/studio/StudioClock.scss
index 6421a99f6..cfb997e49 100644
--- a/apps/client/src/features/viewers/studio/StudioClock.scss
+++ b/apps/client/src/features/viewers/studio/StudioClock.scss
@@ -13,7 +13,7 @@ $half-hours: min(1.5vh, 10px);
$size-min: min(2.5vh, 18px);
$half-min: min(1.25vh, 9px);
$red-active: #c53030;
-$red-idle: #300000;
+$red-idle: #000000;
$cyan-active: #0ff;
$cyan-idle: #0aa;
@@ -56,11 +56,10 @@ $cyan-idle: #0aa;
.hours {
border-radius: 50%;
position: absolute;
- background: $red-idle;
+ background: var(--studio-idle, $red-idle);
&--active {
- background: $red-active;
- box-shadow: 0 0 10px 2px rgba(255, 0, 0, 0.25);
+ background: var(--studio-active, $red-active);
}
}
@@ -80,7 +79,7 @@ $cyan-idle: #0aa;
}
.studio-timer {
- color: $red-active;
+ color: var(--studio-active, $red-active);
font-size: calc(#{$clock-size} / 3);
margin-top: calc(50% - calc(#{$clock-size} / 7));
line-height: 0.8em;
@@ -88,9 +87,8 @@ $cyan-idle: #0aa;
&--with-seconds {
font-size: calc(#{$clock-size} / 4.6);
margin-top: calc(50% - calc(#{$clock-size} / 11));
+ }
}
- }
-
.next-title:after,
@@ -100,14 +98,15 @@ $cyan-idle: #0aa;
}
.next-title {
- color: $cyan-idle;
+ color: var(--studio-idle-label, $cyan-idle);
text-align: center;
}
.next-countdown {
+ color: var(--studio-active-label, $cyan-active);
+
font-size: 10vh;
line-height: 1em;
- color: $cyan-active;
&--overtime {
color: darken($red-active, 10%);
@@ -131,16 +130,16 @@ $cyan-idle: #0aa;
padding-bottom: 2vh;
font-size: 15vh;
line-height: 0.9em;
- color: $red-active;
+ color: var(--studio-active, $red-active);
&--idle {
- color: $red-idle;
+ color: var(--studio-idle, $red-active);
}
}
.schedule {
ul {
- color: $cyan-idle;
+ color: var(--studio-idle-label, $cyan-idle);
font-size: 3.75vh;
line-height: 1em;
list-style: none;
@@ -154,18 +153,18 @@ $cyan-idle: #0aa;
}
.now {
- color: $cyan-active;
+ color: var(--studio-active-label, $cyan-active);
}
.next {
- color: $red-active;
+ color: var(--studio-active, $red-active);
}
.user-colour {
width: 0.35em;
height: 0.35em;
aspect-ratio: 1;
- background-color: $red-idle;
+ background-color: var(--studio-idle, $red-idle);
margin-right: 0.35em;
}
}
diff --git a/apps/client/src/features/viewers/timer/Timer.scss b/apps/client/src/features/viewers/timer/Timer.scss
index 5d53cd3cb..7cd00751e 100644
--- a/apps/client/src/features/viewers/timer/Timer.scss
+++ b/apps/client/src/features/viewers/timer/Timer.scss
@@ -48,6 +48,7 @@
font-size: clamp(32px, 3.5vw, 50px);
color: var(--secondary-color-override, $viewer-secondary-color);
letter-spacing: 0.05em;
+ line-height: 0.95em;
}
&--hidden {
@@ -78,6 +79,9 @@
justify-self: center;
align-self: center;
+ width: 100%;
+ overflow: hidden;
+
.end-message {
text-align: center;
font-size: 11.5vw;
@@ -89,7 +93,6 @@
.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;
@@ -97,6 +100,9 @@
letter-spacing: 0.05em;
font-weight: 600;
+ transition-property: font-size;
+ transition-duration: $viewer-transition-time;
+
&--paused {
opacity: $viewer-opacity-disabled;
transition: $viewer-transition-time;
@@ -108,6 +114,27 @@
}
}
+ .external {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ font-weight: 600;
+ text-align: center;
+ color: var(--external-color-override, $external-color);
+ letter-spacing: 0.5px;
+ line-height: 0.9em;
+ padding-bottom: 0.2em;
+ transition-property: opacity, height;
+ transition-duration: $viewer-transition-time;
+ border-top: 1px solid rgba(white, 0.1);
+
+ &--hidden {
+ opacity: 0;
+ height: 0;
+ }
+ }
+
.progress-container {
grid-area: progress;
width: 100%;
@@ -115,12 +142,11 @@
opacity: 1;
transition: $viewer-transition-time;
- &--paused {
- opacity: $viewer-opacity-disabled;
- transition: $viewer-transition-time;
+ &--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 aaccf4c63..f40f0c31e 100644
--- a/apps/client/src/features/viewers/timer/Timer.tsx
+++ b/apps/client/src/features/viewers/timer/Timer.tsx
@@ -1,6 +1,6 @@
import { useEffect } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
-import { OntimeEvent, Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
+import { Message, OntimeEvent, Playback, TimerMessage, TimerType, ViewSettings } from 'ontime-types';
import { overrideStylesURL } from '../../../common/api/apiConstants';
import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar';
@@ -12,6 +12,7 @@ import { useRuntimeStylesheet } from '../../../common/hooks/useRuntimeStylesheet
import { TimeManagerType } from '../../../common/models/TimeManager.type';
import { formatTime } from '../../../common/utils/time';
import { useTranslation } from '../../../translation/TranslationProvider';
+import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
import { formatTimerDisplay, getTimerByType } from '../common/viewerUtils';
import './Timer.scss';
@@ -40,6 +41,7 @@ const titleVariants = {
interface TimerProps {
isMirrored: boolean;
pres: TimerMessage;
+ external: Message;
eventNow: OntimeEvent | null;
eventNext: OntimeEvent | null;
time: TimeManagerType;
@@ -47,10 +49,9 @@ interface TimerProps {
}
export default function Timer(props: TimerProps) {
- const { isMirrored, pres, eventNow, eventNext, time, viewSettings } = props;
+ const { isMirrored, pres, eventNow, eventNext, time, viewSettings, external } = props;
const { shouldRender } = useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
const { getLocalizedString } = useTranslation();
-
useEffect(() => {
document.title = 'ontime - Timer';
}, []);
@@ -77,6 +78,7 @@ export default function Timer(props: TimerProps) {
const showBlinking = pres.timerBlink;
const showBlackout = pres.timerBlackout;
const showClock = time.timerType !== TimerType.Clock;
+ const showExternal = external.visible && external.text;
const timerColor =
showProgress && showDanger
@@ -93,7 +95,13 @@ export default function Timer(props: TimerProps) {
}
const baseClasses = `stage-timer ${isMirrored ? 'mirror' : ''} ${showBlackout ? 'blackout' : ''}`;
- const timerFontSize = 89 / (stageTimerCharacters - 1);
+ let timerFontSize = 89 / (stageTimerCharacters - 1);
+ // we need to shrink the timer if the external is going to be there
+ if (showExternal) {
+ timerFontSize *= 0.8;
+ }
+ const externalFontSize = timerFontSize * 0.4
+ const timerContainerClasses = `timer-container ${showBlinking ? (showOverlay ? '' : 'blink') : ''}`;
const timerClasses = `timer ${!isPlaying ? 'timer--paused' : ''} ${showFinished ? 'timer--finished' : ''}`;
return (
@@ -106,10 +114,10 @@ export default function Timer(props: TimerProps) {