diff --git a/apps/client/src/common/components/title-card/TitleCard.scss b/apps/client/src/common/components/title-card/TitleCard.scss index e9d0b130a..2b92c799f 100644 --- a/apps/client/src/common/components/title-card/TitleCard.scss +++ b/apps/client/src/common/components/title-card/TitleCard.scss @@ -1,25 +1,37 @@ @use '../../../theme/viewerDefs' as *; .title-card { + position: relative; display: flex; flex-direction: column; - gap: 0.5rem; + gap: 0.25rem; } -.inline { - display: flex; +.title-card__title, +.title-card__secondary { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .title-card__title { font-weight: 600; - font-size: clamp(32px, 3.5vw, 50px); + font-size: clamp(1.5rem, 3vw, 3rem); color: var(--color-override, $viewer-color); - line-height: 1.1em; + line-height: 1.2em; +} + +.title-card__secondary { + font-size: clamp(1rem, 2vw, 2.25rem); + color: var(--secondary-color-override, $viewer-secondary-color); + line-height: 1.2em; } .title-card__label { + position: absolute; + right: 1rem; + top: 0.5rem; font-size: clamp(1rem, 1.5vw, 1.5rem); - font-weight: 400; color: var(--secondary-color-override, $viewer-secondary-color); margin-left: auto; text-transform: uppercase; @@ -28,13 +40,3 @@ color: var(--accent-color-override, $accent-color); } } - -.title-card__secondary { - font-size: clamp(1.5rem, 2vw, 2.25rem); - color: var(--secondary-color-override, $viewer-secondary-color); - line-height: 1.1em; - - &::after { - content: '\200b'; - } -} diff --git a/apps/client/src/common/components/title-card/TitleCard.tsx b/apps/client/src/common/components/title-card/TitleCard.tsx index 7e9220fb3..e2d79d587 100644 --- a/apps/client/src/common/components/title-card/TitleCard.tsx +++ b/apps/client/src/common/components/title-card/TitleCard.tsx @@ -1,28 +1,32 @@ +import { ForwardedRef, forwardRef } from 'react'; + import { useTranslation } from '../../../translation/TranslationProvider'; import './TitleCard.scss'; interface TitleCardProps { - label: 'now' | 'next'; title: string; + label?: 'now' | 'next'; secondary?: string; + className?: string; } -export default function TitleCard(props: TitleCardProps) { - const { label, title, secondary } = props; +const TitleCard = forwardRef((props: TitleCardProps, ref: ForwardedRef) => { + const { label, title, secondary, className = '' } = props; const { getLocalizedString } = useTranslation(); const accent = label === 'now'; return ( -
-
- {title} - - {getLocalizedString(`common.${label}`)} - -
+
+ {title} + + {label && getLocalizedString(`common.${label}`)} +
{secondary}
); -} +}); + +TitleCard.displayName = 'TitleCard'; +export default TitleCard; diff --git a/apps/client/src/features/operator/operator-event/OperatorEvent.module.scss b/apps/client/src/features/operator/operator-event/OperatorEvent.module.scss index 5c574fab3..94e6982cb 100644 --- a/apps/client/src/features/operator/operator-event/OperatorEvent.module.scss +++ b/apps/client/src/features/operator/operator-event/OperatorEvent.module.scss @@ -65,12 +65,15 @@ font-size: 1.5rem; letter-spacing: 0.5px; color: $ui-white; + + @include ellipsis-text; } .secondaryField { grid-area: secondary; font-size: 1.25rem; letter-spacing: 0.5px; + @include ellipsis-text; } .schedule { diff --git a/apps/client/src/features/rundown/event-editor/composite/EventTextInput.tsx b/apps/client/src/features/rundown/event-editor/composite/EventTextInput.tsx index 9eab0e63c..218db4f3e 100644 --- a/apps/client/src/features/rundown/event-editor/composite/EventTextInput.tsx +++ b/apps/client/src/features/rundown/event-editor/composite/EventTextInput.tsx @@ -6,14 +6,14 @@ import { EditorUpdateFields } from '../EventEditor'; import style from '../EventEditor.module.scss'; -interface CountedTextInputProps extends InputProps { +interface EventTextInputProps extends InputProps { field: EditorUpdateFields; label: string; initialValue: string; submitHandler: (field: EditorUpdateFields, value: string) => void; } -export default function EventTextInput(props: CountedTextInputProps) { +export default function EventTextInput(props: EventTextInputProps) { const { field, label, initialValue, submitHandler, maxLength } = props; const ref = useRef(null); const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]); @@ -34,7 +34,7 @@ export default function EventTextInput(props: CountedTextInputProps) { variant='ontime-filled' data-testid='input-textfield' value={value} - maxLength={maxLength || 50} + maxLength={maxLength || 100} onChange={onChange} onBlur={onBlur} onKeyDown={onKeyDown} diff --git a/apps/client/src/features/viewers/backstage/Backstage.tsx b/apps/client/src/features/viewers/backstage/Backstage.tsx index 3565430fb..afad7e9c5 100644 --- a/apps/client/src/features/viewers/backstage/Backstage.tsx +++ b/apps/client/src/features/viewers/backstage/Backstage.tsx @@ -25,6 +25,8 @@ import { getPropertyValue } from '../common/viewUtils'; import './Backstage.scss'; +export const MotionTitleCard = motion(TitleCard); + interface BackstageProps { customFields: CustomFields; isMirrored: boolean; @@ -124,7 +126,7 @@ export default function Backstage(props: BackstageProps) { animate='visible' exit='exit' > - +
{getLocalizedString('common.started_at')}
@@ -151,16 +153,17 @@ export default function Backstage(props: BackstageProps) { {eventNext && ( - - - + label='next' + title={eventNext.title} + secondary={secondaryTextNext} + /> )}
diff --git a/apps/client/src/features/viewers/countdown/Countdown.tsx b/apps/client/src/features/viewers/countdown/Countdown.tsx index 3713b5903..b67acdfce 100644 --- a/apps/client/src/features/viewers/countdown/Countdown.tsx +++ b/apps/client/src/features/viewers/countdown/Countdown.tsx @@ -1,7 +1,6 @@ import { useEffect, useState } from 'react'; import { useSearchParams } from 'react-router-dom'; import { OntimeEvent, OntimeRundownEntry, Playback, Settings, SupportedEvent, ViewSettings } from 'ontime-types'; -import { millisToString, removeLeadingZero } from 'ontime-utils'; import { overrideStylesURL } from '../../../common/api/constants'; import { getCountdownOptions } from '../../../common/components/view-params-editor/constants'; @@ -12,6 +11,7 @@ import { ViewExtendedTimer } from '../../../common/models/TimeManager.type'; import { formatTime, getDefaultFormat } from '../../../common/utils/time'; import { useTranslation } from '../../../translation/TranslationProvider'; import SuperscriptTime from '../common/superscript-time/SuperscriptTime'; +import { getFormattedTimer, isStringBoolean } from '../common/viewUtils'; import { fetchTimerData, TimerMessage } from './countdown.helpers'; import CountdownSelect from './CountdownSelect'; @@ -84,24 +84,17 @@ export default function Countdown(props: CountdownProps) { const standby = time.playback !== Playback.Play && time.playback !== Playback.Roll && selectedId === follow?.id; const finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt; const isRunningFinished = finished && runningMessage === TimerMessage.running; - const isSelected = runningMessage === TimerMessage.running; const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : ''; const clock = formatTime(time.clock); const startTime = follow === null ? '...' : formatTime(follow.timeStart + delay); const endTime = follow === null ? '...' : formatTime(follow.timeEnd + delay); - const formatTimer = (): string => { - if (runningMessage === TimerMessage.ended) { - return formatTime(runningTimer, { format12: 'hh:mm a', format24: 'HH:mm' }); - } - let formattedTime = millisToString(isSelected ? runningTimer : runningTimer + delay); - if (isSelected || runningMessage === TimerMessage.waiting) { - formattedTime = removeLeadingZero(formattedTime); - } - return formattedTime; - }; - const formattedTimer = formatTimer(); + const hideSeconds = searchParams.get('hideTimerSeconds'); + const formattedTimer = getFormattedTimer(runningTimer, time.timerType, getLocalizedString('common.minutes'), { + removeSeconds: isStringBoolean(hideSeconds), + removeLeadingZero: false, + }); const defaultFormat = getDefaultFormat(settings?.timeFormat); const timeOption = getCountdownOptions(defaultFormat); diff --git a/apps/client/src/features/viewers/lower-thirds/LowerThird.scss b/apps/client/src/features/viewers/lower-thirds/LowerThird.scss index 8a5797803..7929fa994 100644 --- a/apps/client/src/features/viewers/lower-thirds/LowerThird.scss +++ b/apps/client/src/features/viewers/lower-thirds/LowerThird.scss @@ -72,6 +72,8 @@ font-family: var(--lowerThird-font-family-override), Lato, Arial, sans-serif; text-align: var(--lowerThird-text-align-override, left); white-space: nowrap; + @include ellipsis-text(); + &::after { content: '\200b'; } diff --git a/apps/client/src/features/viewers/public/Public.tsx b/apps/client/src/features/viewers/public/Public.tsx index 4a588e1cc..4d3a2a627 100644 --- a/apps/client/src/features/viewers/public/Public.tsx +++ b/apps/client/src/features/viewers/public/Public.tsx @@ -21,6 +21,8 @@ import { getPropertyValue } from '../common/viewUtils'; import './Public.scss'; +export const MotionTitleCard = motion(TitleCard); + interface BackstageProps { customFields: CustomFields; isMirrored: boolean; @@ -83,31 +85,33 @@ export default function Public(props: BackstageProps) {
{publicEventNow && ( - - - + label='now' + title={publicEventNow.title} + secondary={secondaryTextNow} + /> )} {publicEventNext && ( - - - + label='next' + title={publicEventNext.title} + secondary={secondaryTextNext} + /> )}
diff --git a/apps/client/src/features/viewers/studio/StudioClock.scss b/apps/client/src/features/viewers/studio/StudioClock.scss index c5c4b1084..6e239f915 100644 --- a/apps/client/src/features/viewers/studio/StudioClock.scss +++ b/apps/client/src/features/viewers/studio/StudioClock.scss @@ -168,7 +168,6 @@ $orange-active: #f60; display: flex; align-items: start; gap: 0.5em; - white-space: nowrap; &--now { color: var(--studio-active-label, $red-active); diff --git a/apps/client/src/features/viewers/studio/StudioClock.tsx b/apps/client/src/features/viewers/studio/StudioClock.tsx index e48f985df..17bce990b 100644 --- a/apps/client/src/features/viewers/studio/StudioClock.tsx +++ b/apps/client/src/features/viewers/studio/StudioClock.tsx @@ -36,7 +36,7 @@ export default function StudioClock(props: StudioClockProps) { // TODO: can we prevent the Flash of Unstyled Content on the 7segment fonts? // deferring rendering seems to affect styling (font and useFitText) useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL); - const { fontSize: titleFontSize, ref: titleRef } = useFitText({ maxFontSize: 500 }); + const { fontSize: titleFontSize, ref: titleRef } = useFitText({ minFontSize: 150, maxFontSize: 500 }); const activeIndicators = [...Array(12).keys()]; const secondsIndicators = [...Array(60).keys()]; diff --git a/apps/client/src/features/viewers/timer/Timer.scss b/apps/client/src/features/viewers/timer/Timer.scss index e6a73db53..8c0e42786 100644 --- a/apps/client/src/features/viewers/timer/Timer.scss +++ b/apps/client/src/features/viewers/timer/Timer.scss @@ -15,7 +15,7 @@ padding: min(2vh, 16px) clamp(16px, 10vw, 64px); display: grid; - grid-template-columns: auto; + grid-template-columns: 100%; grid-template-rows: auto 1fr auto auto auto; grid-template-areas: 'clock' diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx index 1c96c7b31..dc7e767b5 100644 --- a/apps/client/src/features/viewers/timer/Timer.tsx +++ b/apps/client/src/features/viewers/timer/Timer.tsx @@ -43,6 +43,8 @@ const titleVariants = { }, }; +export const MotionTitleCard = motion(TitleCard); + interface TimerProps { customFields: CustomFields; eventNext: OntimeEvent | null; @@ -211,31 +213,33 @@ export default function Timer(props: TimerProps) { <> {eventNow?.title && ( - - - + label='now' + title={mainFieldNow} + secondary={secondaryTextNow} + /> )} {eventNext?.title && ( - - - + label='next' + title={mainFieldNext} + secondary={secondaryTextNext} + className='event next' + /> )} diff --git a/apps/client/src/theme/_mixins.scss b/apps/client/src/theme/_mixins.scss index 70e6fa226..059503c5c 100644 --- a/apps/client/src/theme/_mixins.scss +++ b/apps/client/src/theme/_mixins.scss @@ -1,4 +1,4 @@ -@use "ontimeStyles" as *; +@use 'ontimeStyles' as *; //////////////////////////////////// general app elements @@ -15,3 +15,9 @@ color: $ontime-color; } } + +@mixin ellipsis-text { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} diff --git a/apps/client/vite.config.js b/apps/client/vite.config.js index e64be901d..a9ef6f7ca 100644 --- a/apps/client/vite.config.js +++ b/apps/client/vite.config.js @@ -61,6 +61,7 @@ export default defineConfig({ additionalData: ` @use './src/theme/ontimeColours' as *; @use './src/theme/ontimeStyles' as *; + @use './src/theme/mixins' as *; `, }, },