diff --git a/client/src/App.jsx b/client/src/App.jsx index fd461f721..725681327 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -17,7 +17,9 @@ const StageManager = lazy(() => import('./features/viewers/backstage/StageManager') ); const Public = lazy(() => import('./features/viewers/foh/Public')); -const Lower = lazy(() => import('./features/viewers/production/Lower')); +const Lower = lazy(() => + import('./features/viewers/production/lower/LowerWrapper') +); const Pip = lazy(() => import('./features/viewers/production/Pip')); const queryClient = new QueryClient(); diff --git a/client/src/features/viewers/production/lower/LowerClean.jsx b/client/src/features/viewers/production/lower/LowerClean.jsx new file mode 100644 index 000000000..780a3e1ef --- /dev/null +++ b/client/src/features/viewers/production/lower/LowerClean.jsx @@ -0,0 +1,131 @@ +import { AnimatePresence, motion } from 'framer-motion'; +import { useEffect, useState } from 'react'; +import style from './LowerClean.module.css'; + +export default function LowerClean(props) { + const { lower, title, options } = props; + const defaults = { + size: 1, + transitionIn: 3, + textColour: '#fffffa', + posX: 50, + posY: 700, + }; + + const [showLower, setShowLower] = useState(true); + // Unmount if fadeOut + useEffect(() => { + if (!options.fadeOut) return; + + // Calculate time + const fadeOutTime = + (parseInt(options.fadeOut) + + (options.transitionIn || defaults.transitionIn)) * + 1000; + if (isNaN(fadeOutTime)) return; + + const timeout = setTimeout(() => { + setShowLower(false); + }, fadeOutTime); + + return () => clearTimeout(timeout); + }, [options.fadeOut, options.transitionIn, defaults.transitionIn]); + + // calculate transition times + useEffect(() => {}); + // Format messages + const showLowerMessage = lower.text !== '' && lower.visible; + + // motion + // transition segments + const t = options.transitionIn || defaults.transitionIn; + const quarter = t / 4; + const third = t / 3; + const half = t / 2; + + const lowerThirdVariants = { + hidden: { + opacity: 0, + }, + visible: { + opacity: 1, + transition: { + duration: third, + }, + }, + exit: { + opacity: 0, + transition: { + duration: third, + }, + }, + }; + + const titleVariants = { + hidden: { + opacity: 0, + }, + visible: { + opacity: 1, + transition: { + delay: quarter, + duration: half, + }, + }, + }; + + const sizeMultiplier = (options.size || 1) * 4; + + return ( +
+ + {showLower && ( + + + {title.titleNow} + + + {title.presenterNow} + + + )} + + + + {showLowerMessage && ( + +
{lower.text}
+
+ )} +
+
+ ); +} diff --git a/client/src/features/viewers/production/lower/LowerClean.module.css b/client/src/features/viewers/production/lower/LowerClean.module.css new file mode 100644 index 000000000..409a473c3 --- /dev/null +++ b/client/src/features/viewers/production/lower/LowerClean.module.css @@ -0,0 +1,27 @@ +.lowerThird { + margin: 0; + box-sizing: border-box; /* reset */ + overflow: hidden; + width: 100%; /* restrict the page width to viewport */ + height: 100%; + + color: #fffffa; + font-size: 4vh; +} + +.lowerContainer { + position: absolute; + padding: 1vh 2vh; + border-radius: 1vh; +} + +.messageContainer { + position: absolute; + bottom: 2vh; + width: 100%; + text-align: center; +} + +.message { + font-size: 3.5vh; +} diff --git a/client/src/features/viewers/production/Lower.jsx b/client/src/features/viewers/production/lower/LowerLines.jsx similarity index 69% rename from client/src/features/viewers/production/Lower.jsx rename to client/src/features/viewers/production/lower/LowerLines.jsx index 7fe81f8ff..7c2c28d66 100644 --- a/client/src/features/viewers/production/Lower.jsx +++ b/client/src/features/viewers/production/lower/LowerLines.jsx @@ -1,36 +1,26 @@ import { AnimatePresence, motion } from 'framer-motion'; import { useEffect, useState } from 'react'; -import style from './Lower.module.css'; +import style from './LowerLines.module.css'; -export default function Lower(props) { - const { lower, title, time, general } = props; +export default function LowerLines(props) { + const { lower, title, options } = props; + const defaults = { + size: 1, + transitionIn: 3, + textColour: '#fffffa', + bgColour: '#00000033', + }; const [showLower, setShowLower] = useState(true); - // Set window title + // Unmount if fadeOut useEffect(() => { - document.title = 'ontime - Lower Thirds'; - }, []); - - // TODO: sanitize data - // getting config from URL: preset, size, transition, bg, text, key - // eg. http://localhost:3000/lower?bg=ff2&text=f00&size=0.6&transition=5 - let params = new URLSearchParams(props.location.search); - - const preset = params.get('preset') ? params.get('preset') : 1; - - const size = params.get('size') ? params.get('size') : 1; - const transitionIn = params.get('transition') ? params.get('transition') : 3; - const textColour = params.get('text') ? `#${params.get('text')}` : '#fffffa'; - const bgColour = params.get('bg') ? `#${params.get('bg')}` : '#00000033'; - const key = params.get('key') ? `#${params.get('key')}` : 'null'; - const fadeOut = params.get('fadeout') ? `${params.get('fadeout')}` : 'null'; - - useEffect(() => { - if (!fadeOut) return; + if (!options.fadeOut) return; // Calculate time - let transitionTime = parseInt(transitionIn) || 0; - let fadeOutTime = (parseInt(fadeOut) + transitionTime) * 1000; + const fadeOutTime = + (parseInt(options.fadeOut) + + (options.transitionIn || defaults.transitionIn)) * + 1000; if (isNaN(fadeOutTime)) return; const timeout = setTimeout(() => { @@ -38,18 +28,19 @@ export default function Lower(props) { }, fadeOutTime); return () => clearTimeout(timeout); - }, []); + }, [options.fadeOut, options.transitionIn, defaults.transitionIn]); // Format messages const showLowerMessage = lower.text !== '' && lower.visible; - // transition segments - const eight = transitionIn / 8; - const quarter = transitionIn / 4; - const third = transitionIn / 3; - const half = transitionIn / 2; - // motion + // transition segments + const t = options.transitionIn || defaults.transitionIn; + const eight = t / 8; + const quarter = t / 4; + const third = t / 3; + const half = t / 2; + const lowerThirdVariants = { hidden: { opacity: 0, @@ -120,20 +111,22 @@ export default function Lower(props) { }, }; + const sizeMultiplier = (options.size || 1) * 4; + return (
{showLower && ( { + document.title = 'ontime - Lower Thirds'; + }, []); + + // TODO: sanitize data + // getting config from URL: preset, size, transition, bg, text, key + // eg. http://localhost:3000/lower?bg=ff2&text=f00&size=0.6&transition=5 + // Check for user options + useEffect(() => { + // get parameters + const params = new URLSearchParams(props.location.search); + // create aux + let options = {}; + + // preset: selector + // Should be a number 1-n + let p = parseInt(params.get('preset')); + if (!isNaN(p)) setPreset(p); + + // size: multiplier + // Should be a number 0.0-n + let s = params.get('size'); + if (s) options.size = s; + + // transitionIn: seconds + // Should be a number 0-n + let t = parseInt(params.get('transition')); + if (!isNaN(t)) options.transitionIn = t; + + // textColour: string + // Should be a hex string '#ffffff' + let c = params.get('text'); + if (c) options.textColour = `#${c}`; + + // bgColour: string + // Should be a hex string '#ffffff' + let b = params.get('bg'); + if (b) options.bgColour = `#${b}`; + + // key: string + // Should be a hex string '#00FF00' with key colour + let k = params.get('key'); + if (k) options.keyColour = `#${k}`; + + // fadeOut: seconds + // Should be a number 0-n + let f = parseInt(params.get('fadeout')); + if (!isNaN(f)) options.fadeOut = f; + + // x: pixels + // Should be a number 0-n + let x = parseInt(params.get('x')); + if (!isNaN(x)) options.posX = x; + + // y: pixels + // Should be a number 0-n + let y = parseInt(params.get('y')); + if (!isNaN(y)) options.posY = y; + + setLowerOptions({ + ...options, + set: true, + }); + }, [props.location.search]); + + // Defer rendering until we have data ready + if (!lowerOptions.set) return null; + + switch (preset) { + case 0: + return ; + case 1: + return ; + default: + return ; + } +}