diff --git a/apps/client/src/common/components/fit-text/FitText.tsx b/apps/client/src/common/components/fit-text/FitText.tsx new file mode 100644 index 000000000..3d5aa8d8d --- /dev/null +++ b/apps/client/src/common/components/fit-text/FitText.tsx @@ -0,0 +1,63 @@ +/** + * Copied from + * https://github.com/namhong2001/react-textfit/blob/master/index.tsx + */ + +import { HTMLAttributes, PropsWithChildren, useCallback, useEffect, useRef } from 'react'; + +import { bsearch } from './fitText.utils'; + +interface FitTextProps extends HTMLAttributes { + mode?: 'single' | 'multi'; + min?: number; // inclusive + max?: number; // inclusive +} + +export function FitText(props: PropsWithChildren) { + const { children, mode = 'multi', min = 16, max = 256, ...elementProps } = props; + const ref = useRef(null); + + const isOverflown = useCallback(() => { + const el = ref.current; + if (!el) return false; + return el.scrollHeight > el.clientHeight || el.scrollWidth > el.clientWidth; + }, []); + + const setFontSize = useCallback(() => { + const el = ref.current; + if (!el) return; + + const originVisibility = el.style.visibility; + + el.style.visibility = 'hidden'; + const fontSize = bsearch(min, max + 1, (mid) => { + el.style.fontSize = `${mid}px`; + return !isOverflown(); + }); + el.style.fontSize = `${fontSize}px`; + el.style.visibility = originVisibility; + }, [isOverflown, min, max]); + + useEffect(() => { + const el = ref.current; + if (!el) return; + + setFontSize(); + const observer = new ResizeObserver(setFontSize); + observer.observe(el); + + return () => observer.disconnect(); + }, [children, mode, setFontSize]); + + return ( +
+ {children} +
+ ); +} diff --git a/apps/client/src/common/components/fit-text/fitText.utils.ts b/apps/client/src/common/components/fit-text/fitText.utils.ts new file mode 100644 index 000000000..1ed75db75 --- /dev/null +++ b/apps/client/src/common/components/fit-text/fitText.utils.ts @@ -0,0 +1,18 @@ +/** + * @param low inclusive, must be true on predicate function + * @param high exclusive, + * @param predicate predicate function + */ +export const bsearch = (low: number, high: number, predicate: (mid: number) => boolean): number => { + while (low < high) { + const mid = Math.floor((low + high) / 2); + if (mid === low) break; + + if (predicate(mid)) { + low = mid; + } else { + high = mid; + } + } + return low; +}; diff --git a/apps/client/src/features/viewers/timer/Timer.scss b/apps/client/src/features/viewers/timer/Timer.scss index 8c0e42786..a0daf0b2a 100644 --- a/apps/client/src/features/viewers/timer/Timer.scss +++ b/apps/client/src/features/viewers/timer/Timer.scss @@ -167,36 +167,28 @@ .message-overlay { position: fixed; - width: 100%; - height: 100%; top: 0; left: 0; right: 0; bottom: 0; - background-color: $viewer-overlay-bg-color; - z-index: -1; + padding: 2vw; + background: var(--background-color-override, $viewer-background-color); opacity: 0; - transition: $viewer-transition-time; + transition: opacity $viewer-transition-time; + z-index: 2; &--active { opacity: 1; - transition: $viewer-transition-time; - transition-property: opacity; - z-index: 2; } } .message { - width: inherit; - padding: 2vw; - position: absolute; - top: 50%; - left: 50%; - color: $viewer-color; - transform: translate(-50%, -50%); - -ms-transform: translate(-50%, -50%); - font-size: 15vw; - line-height: 30vh; + display: grid; + place-content: center; + height: 100%; + width: 100%; + + color: var(--color-override, $viewer-color); text-align: center; font-weight: 600; } diff --git a/apps/client/src/features/viewers/timer/Timer.tsx b/apps/client/src/features/viewers/timer/Timer.tsx index 6edba99d9..3f1945671 100644 --- a/apps/client/src/features/viewers/timer/Timer.tsx +++ b/apps/client/src/features/viewers/timer/Timer.tsx @@ -13,6 +13,7 @@ import { } from 'ontime-types'; import { overrideStylesURL } from '../../../common/api/constants'; +import { FitText } from '../../../common/components/fit-text/FitText'; import MultiPartProgressBar from '../../../common/components/multi-part-progress-bar/MultiPartProgressBar'; import TitleCard from '../../../common/components/title-card/TitleCard'; import ViewParamsEditor from '../../../common/components/view-params-editor/ViewParamsEditor'; @@ -163,7 +164,9 @@ export default function Timer(props: TimerProps) {
{!userOptions.hideMessage && (
-
{pres.text}
+ + {pres.text} +
)} diff --git a/apps/client/src/theme/_viewerDefs.scss b/apps/client/src/theme/_viewerDefs.scss index 67bc2614d..9f39347e4 100644 --- a/apps/client/src/theme/_viewerDefs.scss +++ b/apps/client/src/theme/_viewerDefs.scss @@ -19,7 +19,6 @@ $viewer-background-color: $ui-black; // --background-color-override $viewer-color: rgba(white, 80%); // --color-override $viewer-secondary-color: rgba(white, 45%); // --secondary-color-override $viewer-card-bg-color: rgba(white, 7%); // --card-background-color-override -$viewer-overlay-bg-color: rgba(black, 90%); $element-border-radius: 8px; // Properties related to timer