mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 14:14:17 +00:00
PIP + style consistency
small style changes to make screens consistent
This commit is contained in:
@@ -0,0 +1,183 @@
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useEffect, useState } from 'react';
|
||||
import style from './Lower.module.css';
|
||||
|
||||
export default function Lower(props) {
|
||||
const { lower, title, time, general } = props;
|
||||
const [showLower, setShowLower] = useState(true);
|
||||
|
||||
// Set window title
|
||||
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;
|
||||
|
||||
// Calculate time
|
||||
let transitionTime = parseInt(transitionIn) || 0;
|
||||
let fadeOutTime = (parseInt(fadeOut) + transitionTime) * 1000;
|
||||
if (isNaN(fadeOutTime)) return;
|
||||
|
||||
const timeout = setTimeout(() => {
|
||||
setShowLower(false);
|
||||
}, fadeOutTime);
|
||||
|
||||
return () => clearTimeout(timeout);
|
||||
}, []);
|
||||
|
||||
// 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
|
||||
const lowerThirdVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
duration: third,
|
||||
},
|
||||
},
|
||||
exit: {
|
||||
opacity: 0,
|
||||
x: -1000,
|
||||
transition: {
|
||||
duration: third,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const titleContainerVariants = {
|
||||
hidden: {
|
||||
left: -1000,
|
||||
},
|
||||
visible: {
|
||||
left: 0,
|
||||
transition: {
|
||||
duration: third,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const titleVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
delay: quarter,
|
||||
duration: half,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const subtitleContainerVariants = {
|
||||
hidden: {
|
||||
left: -1000,
|
||||
},
|
||||
visible: {
|
||||
left: 0,
|
||||
transition: {
|
||||
delay: eight,
|
||||
duration: third,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const subtitleVariants = {
|
||||
hidden: {
|
||||
opacity: 0,
|
||||
},
|
||||
visible: {
|
||||
opacity: 1,
|
||||
transition: {
|
||||
delay: third,
|
||||
duration: third * 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
className={style.lowerThird}
|
||||
style={{
|
||||
backgroundColor: key,
|
||||
color: textColour,
|
||||
fontSize: `${size * 4}vh`,
|
||||
}}
|
||||
>
|
||||
<AnimatePresence>
|
||||
{showLower && (
|
||||
<motion.div
|
||||
className={style.lowerContainer}
|
||||
style={{ backgroundColor: bgColour }}
|
||||
variants={lowerThirdVariants}
|
||||
initial='hidden'
|
||||
animate='visible'
|
||||
exit='exit'
|
||||
>
|
||||
<motion.div
|
||||
className={style.titleContainer}
|
||||
variants={titleContainerVariants}
|
||||
>
|
||||
<motion.div className={style.title} variants={titleVariants}>
|
||||
{title.titleNow}
|
||||
</motion.div>
|
||||
<div className={style.titleDecor} />
|
||||
</motion.div>
|
||||
<motion.div
|
||||
className={style.subtitleContainer}
|
||||
variants={subtitleContainerVariants}
|
||||
>
|
||||
<div className={style.subDecor} />
|
||||
<motion.div
|
||||
className={style.subtitle}
|
||||
variants={subtitleVariants}
|
||||
>
|
||||
{title.presenterNow}
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<AnimatePresence>
|
||||
{showLowerMessage && (
|
||||
<motion.div
|
||||
className={style.messageContainer}
|
||||
key='modal'
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
exit={{ scaleY: 0, opacity: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<div className={style.message}>{lower.text}</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
.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;
|
||||
top: 75vh;
|
||||
background-color: #00000033;
|
||||
|
||||
padding: 1vh 1vw 1vh 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 45vw;
|
||||
border-radius: 0 1vh 1vh 0;
|
||||
}
|
||||
|
||||
.messageContainer,
|
||||
.messageContainerHidden {
|
||||
position: absolute;
|
||||
bottom: 2vh;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.titleContainer,
|
||||
.subtitleContainer {
|
||||
display: grid;
|
||||
grid-template-columns: auto max-content;
|
||||
grid-template-areas: 'decor text';
|
||||
align-items: center;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.titleContainer,
|
||||
.subtitleContainer {
|
||||
margin-bottom: 1vh;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.titleDecor,
|
||||
.subDecor {
|
||||
grid-area: decor;
|
||||
height: 4vh;
|
||||
background-color: #ff6969;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
rgba(255, 105, 105, 1) 0%,
|
||||
rgba(255, 132, 132, 1) 100%
|
||||
);
|
||||
}
|
||||
|
||||
.titleDecor,
|
||||
.subDecor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.title,
|
||||
.subtitle {
|
||||
grid-area: text;
|
||||
padding-left: 1vw;
|
||||
justify-self: right;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.messageContainer {
|
||||
background-color: #00000033;
|
||||
}
|
||||
|
||||
.message {
|
||||
font-size: 3.5vh;
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
import QRCode from 'react-qr-code';
|
||||
import style from './Pip.module.css';
|
||||
import Paginator from '../../../common/components/views/Paginator';
|
||||
import NavLogo from '../../../common/components/nav/NavLogo';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import { formatDisplay } from '../../../common/dateConfig';
|
||||
import { ReactComponent as Emptyimage } from '../../../assets/images/empty.svg';
|
||||
|
||||
export default function Pip(props) {
|
||||
const { time, backstageEvents, selectedId, general } = props;
|
||||
const [size, setSize] = useState('');
|
||||
const ref = useRef(null);
|
||||
|
||||
useEffect(() => {
|
||||
const h = ref.current.clientHeight;
|
||||
const w = ref.current.clientWidth;
|
||||
setSize(`${w} x ${h}`);
|
||||
}, []);
|
||||
|
||||
// Set window title
|
||||
useEffect(() => {
|
||||
document.title = 'ontime - Pip';
|
||||
}, []);
|
||||
|
||||
// Format messages
|
||||
const showInfo =
|
||||
general.backstageInfo !== '' && general.backstageInfo != null;
|
||||
|
||||
const stageTimer =
|
||||
time.currentSeconds != null && !isNaN(time.currentSeconds)
|
||||
? formatDisplay(time.currentSeconds, true)
|
||||
: '';
|
||||
|
||||
return (
|
||||
<div className={style.container__gray}>
|
||||
<NavLogo />
|
||||
|
||||
<div className={style.eventTitle}>{general.title}</div>
|
||||
|
||||
<div className={style.todayContainer}>
|
||||
<div className={style.label}>Today</div>
|
||||
<div className={style.entriesContainer}>
|
||||
<Paginator
|
||||
selectedId={selectedId}
|
||||
events={backstageEvents}
|
||||
limit={15}
|
||||
time={20}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={style.pip} ref={ref}>
|
||||
<Emptyimage className={style.empty} />
|
||||
<span className={style.piptext}>{size}</span>
|
||||
</div>
|
||||
|
||||
<AnimatePresence>
|
||||
{showInfo && (
|
||||
<motion.div className={style.infoContainer}>
|
||||
<div className={style.label}>Info</div>
|
||||
<div className={style.infoMessages}>
|
||||
<div className={style.info}>{general.backstageInfo}</div>
|
||||
</div>
|
||||
<div className={style.qr}>
|
||||
{general.url != null && general.url !== '' && (
|
||||
<QRCode
|
||||
value='www.carlosvalente.com'
|
||||
size={window.innerWidth / 12}
|
||||
level='L'
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
<div className={style.clockContainer}>
|
||||
<div className={style.label}>Time Now</div>
|
||||
<div className={style.clock}>{time.clock}</div>
|
||||
</div>
|
||||
|
||||
<div className={style.countdownContainer}>
|
||||
<div className={style.label}>Stage Timer</div>
|
||||
<div className={style.clock}>{stageTimer}</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,137 @@
|
||||
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;800&display=swap');
|
||||
|
||||
.container__gray,
|
||||
.container__grayFinished {
|
||||
margin: 0;
|
||||
box-sizing: border-box; /* reset */
|
||||
overflow: hidden;
|
||||
width: 100%; /* restrict the page width to viewport */
|
||||
|
||||
background: linear-gradient(90deg, #252525 0%, #121212 100%);
|
||||
height: 100vh;
|
||||
color: #fffd;
|
||||
font-weight: 300;
|
||||
display: grid;
|
||||
grid-template-columns: 20vw 20vw 18vw 3vw 1fr;
|
||||
grid-template-rows: 60vh 1fr 1fr 1fr;
|
||||
grid-template-areas:
|
||||
' pip pip pip . schd'
|
||||
' titl titl titl . schd'
|
||||
' info info clck . schd'
|
||||
' info info time . schd';
|
||||
gap: 1vw;
|
||||
padding: 1vw;
|
||||
}
|
||||
|
||||
.label {
|
||||
font-size: 1.3vw;
|
||||
color: #ff7597;
|
||||
}
|
||||
|
||||
.eventTitle {
|
||||
grid-area: titl;
|
||||
font-size: 3vw;
|
||||
font-weight: 600;
|
||||
text-decoration: underline #ff7597 0.5vh;
|
||||
padding-top: 0.2vh;
|
||||
padding-left: 1vw;
|
||||
}
|
||||
|
||||
.pip {
|
||||
grid-area: pip;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
border: 1px solid rgba(255, 255, 2555, 0.07);
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
display: grid;
|
||||
place-content: center;
|
||||
}
|
||||
|
||||
.empty {
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.piptext {
|
||||
color: rgba(255, 255, 255, 0.13);
|
||||
font-weight: 600;
|
||||
font-size: 4vh;
|
||||
}
|
||||
|
||||
|
||||
/* =================== TITLES ===================*/
|
||||
|
||||
.infoContainer > div {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.infoContainer,
|
||||
.clockContainer,
|
||||
.countdownContainer {
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
padding: 1vh 1vw;
|
||||
}
|
||||
|
||||
.todayContainer {
|
||||
background-color: rgba(255, 255, 255, 0.07);
|
||||
padding: 2.5vh 2vw;
|
||||
}
|
||||
|
||||
.infoContainer,
|
||||
.todayContainer {
|
||||
border-radius: 1vw;
|
||||
}
|
||||
|
||||
.infoContainer {
|
||||
grid-area: info;
|
||||
display: grid;
|
||||
|
||||
grid-template-rows: 3vh minmax(0, 1fr);
|
||||
grid-template-columns: 3fr 1fr;
|
||||
grid-template-areas:
|
||||
'titl qr'
|
||||
'binf qr';
|
||||
gap: 0.5vw;
|
||||
}
|
||||
|
||||
.infoMessages {
|
||||
grid-area: binf;
|
||||
font-size: 1.5vw;
|
||||
line-height: 2vw;
|
||||
white-space: pre-line;
|
||||
}
|
||||
|
||||
.qr {
|
||||
align-self: center;
|
||||
justify-self: center;
|
||||
grid-area: qr;
|
||||
}
|
||||
|
||||
/* =================== SCHEDULE ===================*/
|
||||
|
||||
.todayContainer {
|
||||
grid-area: schd;
|
||||
display: grid;
|
||||
grid-template-rows: 5vh 1fr 3vh;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
/* =================== MAIN ===================*/
|
||||
|
||||
.clockContainer {
|
||||
grid-area: time;
|
||||
border-radius: 0 0 1vw 1vw;
|
||||
}
|
||||
|
||||
.countdownContainer {
|
||||
grid-area: clck;
|
||||
border-radius: 1vw 1vw 0 0;
|
||||
}
|
||||
|
||||
.clock {
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-size: 3vw;
|
||||
line-height: 3vw;
|
||||
text-align: center;
|
||||
letter-spacing: 0.25vw;
|
||||
color: #ddd;
|
||||
}
|
||||
Reference in New Issue
Block a user