mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-22 07:29:08 +00:00
refactor: remove size limits in title
This commit is contained in:
committed by
Carlos Valente
parent
cfc3aab893
commit
6c20cb5e99
@@ -1,25 +1,37 @@
|
|||||||
@use '../../../theme/viewerDefs' as *;
|
@use '../../../theme/viewerDefs' as *;
|
||||||
|
|
||||||
.title-card {
|
.title-card {
|
||||||
|
position: relative;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
gap: 0.5rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.inline {
|
.title-card__title,
|
||||||
display: flex;
|
.title-card__secondary {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
}
|
}
|
||||||
|
|
||||||
.title-card__title {
|
.title-card__title {
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: clamp(32px, 3.5vw, 50px);
|
font-size: clamp(1.5rem, 3vw, 3rem);
|
||||||
color: var(--color-override, $viewer-color);
|
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 {
|
.title-card__label {
|
||||||
|
position: absolute;
|
||||||
|
right: 1rem;
|
||||||
|
top: 0.5rem;
|
||||||
font-size: clamp(1rem, 1.5vw, 1.5rem);
|
font-size: clamp(1rem, 1.5vw, 1.5rem);
|
||||||
font-weight: 400;
|
|
||||||
color: var(--secondary-color-override, $viewer-secondary-color);
|
color: var(--secondary-color-override, $viewer-secondary-color);
|
||||||
margin-left: auto;
|
margin-left: auto;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -28,13 +40,3 @@
|
|||||||
color: var(--accent-color-override, $accent-color);
|
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';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,28 +1,32 @@
|
|||||||
|
import { ForwardedRef, forwardRef } from 'react';
|
||||||
|
|
||||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||||
|
|
||||||
import './TitleCard.scss';
|
import './TitleCard.scss';
|
||||||
|
|
||||||
interface TitleCardProps {
|
interface TitleCardProps {
|
||||||
label: 'now' | 'next';
|
|
||||||
title: string;
|
title: string;
|
||||||
|
label?: 'now' | 'next';
|
||||||
secondary?: string;
|
secondary?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function TitleCard(props: TitleCardProps) {
|
const TitleCard = forwardRef((props: TitleCardProps, ref: ForwardedRef<HTMLDivElement>) => {
|
||||||
const { label, title, secondary } = props;
|
const { label, title, secondary, className = '' } = props;
|
||||||
const { getLocalizedString } = useTranslation();
|
const { getLocalizedString } = useTranslation();
|
||||||
|
|
||||||
const accent = label === 'now';
|
const accent = label === 'now';
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='title-card'>
|
<div className={`title-card ${className}`} ref={ref}>
|
||||||
<div className='inline'>
|
<span className='title-card__title'>{title}</span>
|
||||||
<span className='title-card__title'>{title}</span>
|
<span className={accent ? 'title-card__label title-card__label--accent' : 'title-card__label'}>
|
||||||
<span className={accent ? 'title-card__label title-card__label--accent' : 'title-card__label'}>
|
{label && getLocalizedString(`common.${label}`)}
|
||||||
{getLocalizedString(`common.${label}`)}
|
</span>
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className='title-card__secondary'>{secondary}</div>
|
<div className='title-card__secondary'>{secondary}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
});
|
||||||
|
|
||||||
|
TitleCard.displayName = 'TitleCard';
|
||||||
|
export default TitleCard;
|
||||||
|
|||||||
@@ -65,12 +65,15 @@
|
|||||||
font-size: 1.5rem;
|
font-size: 1.5rem;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
color: $ui-white;
|
color: $ui-white;
|
||||||
|
|
||||||
|
@include ellipsis-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.secondaryField {
|
.secondaryField {
|
||||||
grid-area: secondary;
|
grid-area: secondary;
|
||||||
font-size: 1.25rem;
|
font-size: 1.25rem;
|
||||||
letter-spacing: 0.5px;
|
letter-spacing: 0.5px;
|
||||||
|
@include ellipsis-text;
|
||||||
}
|
}
|
||||||
|
|
||||||
.schedule {
|
.schedule {
|
||||||
|
|||||||
@@ -6,14 +6,14 @@ import { EditorUpdateFields } from '../EventEditor';
|
|||||||
|
|
||||||
import style from '../EventEditor.module.scss';
|
import style from '../EventEditor.module.scss';
|
||||||
|
|
||||||
interface CountedTextInputProps extends InputProps {
|
interface EventTextInputProps extends InputProps {
|
||||||
field: EditorUpdateFields;
|
field: EditorUpdateFields;
|
||||||
label: string;
|
label: string;
|
||||||
initialValue: string;
|
initialValue: string;
|
||||||
submitHandler: (field: EditorUpdateFields, value: string) => void;
|
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 { field, label, initialValue, submitHandler, maxLength } = props;
|
||||||
const ref = useRef<HTMLInputElement | null>(null);
|
const ref = useRef<HTMLInputElement | null>(null);
|
||||||
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
|
const submitCallback = useCallback((newValue: string) => submitHandler(field, newValue), [field, submitHandler]);
|
||||||
@@ -34,7 +34,7 @@ export default function EventTextInput(props: CountedTextInputProps) {
|
|||||||
variant='ontime-filled'
|
variant='ontime-filled'
|
||||||
data-testid='input-textfield'
|
data-testid='input-textfield'
|
||||||
value={value}
|
value={value}
|
||||||
maxLength={maxLength || 50}
|
maxLength={maxLength || 100}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
onBlur={onBlur}
|
onBlur={onBlur}
|
||||||
onKeyDown={onKeyDown}
|
onKeyDown={onKeyDown}
|
||||||
|
|||||||
@@ -25,6 +25,8 @@ import { getPropertyValue } from '../common/viewUtils';
|
|||||||
|
|
||||||
import './Backstage.scss';
|
import './Backstage.scss';
|
||||||
|
|
||||||
|
export const MotionTitleCard = motion(TitleCard);
|
||||||
|
|
||||||
interface BackstageProps {
|
interface BackstageProps {
|
||||||
customFields: CustomFields;
|
customFields: CustomFields;
|
||||||
isMirrored: boolean;
|
isMirrored: boolean;
|
||||||
@@ -124,7 +126,7 @@ export default function Backstage(props: BackstageProps) {
|
|||||||
animate='visible'
|
animate='visible'
|
||||||
exit='exit'
|
exit='exit'
|
||||||
>
|
>
|
||||||
<TitleCard label='now' title={eventNow.title} secondary={secondaryTextNow} />
|
<TitleCard title={eventNow.title} secondary={secondaryTextNow} />
|
||||||
<div className='timer-group'>
|
<div className='timer-group'>
|
||||||
<div className='aux-timers'>
|
<div className='aux-timers'>
|
||||||
<div className='aux-timers__label'>{getLocalizedString('common.started_at')}</div>
|
<div className='aux-timers__label'>{getLocalizedString('common.started_at')}</div>
|
||||||
@@ -151,16 +153,17 @@ export default function Backstage(props: BackstageProps) {
|
|||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{eventNext && (
|
{eventNext && (
|
||||||
<motion.div
|
<MotionTitleCard
|
||||||
className='event next'
|
className='event next'
|
||||||
key='next'
|
key='next'
|
||||||
variants={titleVariants}
|
variants={titleVariants}
|
||||||
initial='hidden'
|
initial='hidden'
|
||||||
animate='visible'
|
animate='visible'
|
||||||
exit='exit'
|
exit='exit'
|
||||||
>
|
label='next'
|
||||||
<TitleCard label='next' title={eventNext.title} secondary={secondaryTextNext} />
|
title={eventNext.title}
|
||||||
</motion.div>
|
secondary={secondaryTextNext}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useSearchParams } from 'react-router-dom';
|
import { useSearchParams } from 'react-router-dom';
|
||||||
import { OntimeEvent, OntimeRundownEntry, Playback, Settings, SupportedEvent, ViewSettings } from 'ontime-types';
|
import { OntimeEvent, OntimeRundownEntry, Playback, Settings, SupportedEvent, ViewSettings } from 'ontime-types';
|
||||||
import { millisToString, removeLeadingZero } from 'ontime-utils';
|
|
||||||
|
|
||||||
import { overrideStylesURL } from '../../../common/api/constants';
|
import { overrideStylesURL } from '../../../common/api/constants';
|
||||||
import { getCountdownOptions } from '../../../common/components/view-params-editor/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 { formatTime, getDefaultFormat } from '../../../common/utils/time';
|
||||||
import { useTranslation } from '../../../translation/TranslationProvider';
|
import { useTranslation } from '../../../translation/TranslationProvider';
|
||||||
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
import SuperscriptTime from '../common/superscript-time/SuperscriptTime';
|
||||||
|
import { getFormattedTimer, isStringBoolean } from '../common/viewUtils';
|
||||||
|
|
||||||
import { fetchTimerData, TimerMessage } from './countdown.helpers';
|
import { fetchTimerData, TimerMessage } from './countdown.helpers';
|
||||||
import CountdownSelect from './CountdownSelect';
|
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 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 finished = time.playback === Playback.Play && (time.current ?? 0) < 0 && time.startedAt;
|
||||||
const isRunningFinished = finished && runningMessage === TimerMessage.running;
|
const isRunningFinished = finished && runningMessage === TimerMessage.running;
|
||||||
const isSelected = runningMessage === TimerMessage.running;
|
|
||||||
const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : '';
|
const delayedTimerStyles = delay > 0 ? 'aux-timers__value--delayed' : '';
|
||||||
|
|
||||||
const clock = formatTime(time.clock);
|
const clock = formatTime(time.clock);
|
||||||
const startTime = follow === null ? '...' : formatTime(follow.timeStart + delay);
|
const startTime = follow === null ? '...' : formatTime(follow.timeStart + delay);
|
||||||
const endTime = follow === null ? '...' : formatTime(follow.timeEnd + delay);
|
const endTime = follow === null ? '...' : formatTime(follow.timeEnd + delay);
|
||||||
|
|
||||||
const formatTimer = (): string => {
|
const hideSeconds = searchParams.get('hideTimerSeconds');
|
||||||
if (runningMessage === TimerMessage.ended) {
|
const formattedTimer = getFormattedTimer(runningTimer, time.timerType, getLocalizedString('common.minutes'), {
|
||||||
return formatTime(runningTimer, { format12: 'hh:mm a', format24: 'HH:mm' });
|
removeSeconds: isStringBoolean(hideSeconds),
|
||||||
}
|
removeLeadingZero: false,
|
||||||
let formattedTime = millisToString(isSelected ? runningTimer : runningTimer + delay);
|
});
|
||||||
if (isSelected || runningMessage === TimerMessage.waiting) {
|
|
||||||
formattedTime = removeLeadingZero(formattedTime);
|
|
||||||
}
|
|
||||||
return formattedTime;
|
|
||||||
};
|
|
||||||
const formattedTimer = formatTimer();
|
|
||||||
|
|
||||||
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
const defaultFormat = getDefaultFormat(settings?.timeFormat);
|
||||||
const timeOption = getCountdownOptions(defaultFormat);
|
const timeOption = getCountdownOptions(defaultFormat);
|
||||||
|
|||||||
@@ -72,6 +72,8 @@
|
|||||||
font-family: var(--lowerThird-font-family-override), Lato, Arial, sans-serif;
|
font-family: var(--lowerThird-font-family-override), Lato, Arial, sans-serif;
|
||||||
text-align: var(--lowerThird-text-align-override, left);
|
text-align: var(--lowerThird-text-align-override, left);
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
|
@include ellipsis-text();
|
||||||
|
|
||||||
&::after {
|
&::after {
|
||||||
content: '\200b';
|
content: '\200b';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,8 @@ import { getPropertyValue } from '../common/viewUtils';
|
|||||||
|
|
||||||
import './Public.scss';
|
import './Public.scss';
|
||||||
|
|
||||||
|
export const MotionTitleCard = motion(TitleCard);
|
||||||
|
|
||||||
interface BackstageProps {
|
interface BackstageProps {
|
||||||
customFields: CustomFields;
|
customFields: CustomFields;
|
||||||
isMirrored: boolean;
|
isMirrored: boolean;
|
||||||
@@ -83,31 +85,33 @@ export default function Public(props: BackstageProps) {
|
|||||||
<div className='now-container'>
|
<div className='now-container'>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{publicEventNow && (
|
{publicEventNow && (
|
||||||
<motion.div
|
<MotionTitleCard
|
||||||
className='event now'
|
className='event now'
|
||||||
key='now'
|
key='now'
|
||||||
variants={titleVariants}
|
variants={titleVariants}
|
||||||
initial='hidden'
|
initial='hidden'
|
||||||
animate='visible'
|
animate='visible'
|
||||||
exit='exit'
|
exit='exit'
|
||||||
>
|
label='now'
|
||||||
<TitleCard label='now' title={publicEventNow.title} secondary={secondaryTextNow} />
|
title={publicEventNow.title}
|
||||||
</motion.div>
|
secondary={secondaryTextNow}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{publicEventNext && (
|
{publicEventNext && (
|
||||||
<motion.div
|
<MotionTitleCard
|
||||||
className='event next'
|
className='event next'
|
||||||
key='next'
|
key='next'
|
||||||
variants={titleVariants}
|
variants={titleVariants}
|
||||||
initial='hidden'
|
initial='hidden'
|
||||||
animate='visible'
|
animate='visible'
|
||||||
exit='exit'
|
exit='exit'
|
||||||
>
|
label='next'
|
||||||
<TitleCard label='next' title={publicEventNext.title} secondary={secondaryTextNext} />
|
title={publicEventNext.title}
|
||||||
</motion.div>
|
secondary={secondaryTextNext}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -168,7 +168,6 @@ $orange-active: #f60;
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: start;
|
align-items: start;
|
||||||
gap: 0.5em;
|
gap: 0.5em;
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&--now {
|
&--now {
|
||||||
color: var(--studio-active-label, $red-active);
|
color: var(--studio-active-label, $red-active);
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ export default function StudioClock(props: StudioClockProps) {
|
|||||||
// TODO: can we prevent the Flash of Unstyled Content on the 7segment fonts?
|
// TODO: can we prevent the Flash of Unstyled Content on the 7segment fonts?
|
||||||
// deferring rendering seems to affect styling (font and useFitText)
|
// deferring rendering seems to affect styling (font and useFitText)
|
||||||
useRuntimeStylesheet(viewSettings?.overrideStyles && overrideStylesURL);
|
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 activeIndicators = [...Array(12).keys()];
|
||||||
const secondsIndicators = [...Array(60).keys()];
|
const secondsIndicators = [...Array(60).keys()];
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
padding: min(2vh, 16px) clamp(16px, 10vw, 64px);
|
padding: min(2vh, 16px) clamp(16px, 10vw, 64px);
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto;
|
grid-template-columns: 100%;
|
||||||
grid-template-rows: auto 1fr auto auto auto;
|
grid-template-rows: auto 1fr auto auto auto;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
'clock'
|
'clock'
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ const titleVariants = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const MotionTitleCard = motion(TitleCard);
|
||||||
|
|
||||||
interface TimerProps {
|
interface TimerProps {
|
||||||
customFields: CustomFields;
|
customFields: CustomFields;
|
||||||
eventNext: OntimeEvent | null;
|
eventNext: OntimeEvent | null;
|
||||||
@@ -211,31 +213,33 @@ export default function Timer(props: TimerProps) {
|
|||||||
<>
|
<>
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{eventNow?.title && (
|
{eventNow?.title && (
|
||||||
<motion.div
|
<MotionTitleCard
|
||||||
className='event now'
|
className='event now'
|
||||||
key='now'
|
key='now'
|
||||||
variants={titleVariants}
|
variants={titleVariants}
|
||||||
initial='hidden'
|
initial='hidden'
|
||||||
animate='visible'
|
animate='visible'
|
||||||
exit='exit'
|
exit='exit'
|
||||||
>
|
label='now'
|
||||||
<TitleCard label='now' title={mainFieldNow} secondary={secondaryTextNow} />
|
title={mainFieldNow}
|
||||||
</motion.div>
|
secondary={secondaryTextNow}
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
|
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
{eventNext?.title && (
|
{eventNext?.title && (
|
||||||
<motion.div
|
<MotionTitleCard
|
||||||
className='event next'
|
|
||||||
key='next'
|
key='next'
|
||||||
variants={titleVariants}
|
variants={titleVariants}
|
||||||
initial='hidden'
|
initial='hidden'
|
||||||
animate='visible'
|
animate='visible'
|
||||||
exit='exit'
|
exit='exit'
|
||||||
>
|
label='next'
|
||||||
<TitleCard label='next' title={mainFieldNext} secondary={secondaryTextNext} />
|
title={mainFieldNext}
|
||||||
</motion.div>
|
secondary={secondaryTextNext}
|
||||||
|
className='event next'
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</AnimatePresence>
|
</AnimatePresence>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
@use "ontimeStyles" as *;
|
@use 'ontimeStyles' as *;
|
||||||
|
|
||||||
//////////////////////////////////// general app elements
|
//////////////////////////////////// general app elements
|
||||||
|
|
||||||
@@ -15,3 +15,9 @@
|
|||||||
color: $ontime-color;
|
color: $ontime-color;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@mixin ellipsis-text {
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export default defineConfig({
|
|||||||
additionalData: `
|
additionalData: `
|
||||||
@use './src/theme/ontimeColours' as *;
|
@use './src/theme/ontimeColours' as *;
|
||||||
@use './src/theme/ontimeStyles' as *;
|
@use './src/theme/ontimeStyles' as *;
|
||||||
|
@use './src/theme/mixins' as *;
|
||||||
`,
|
`,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user