diff --git a/client/src/App.jsx b/client/src/App.jsx index 9caca88b1..bd59ac247 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -20,6 +20,8 @@ const Public = lazy(() => import('./features/viewers/foh/Public') ); const Lower = lazy(() => import('./features/viewers/lower/Lower')); +const Lower = lazy(() => import('./features/viewers/production/Lower')); +const Pip = lazy(() => import('./features/viewers/production/Pip')); const queryClient = new QueryClient(); // Seemed to cause issues @@ -33,6 +35,7 @@ const SSpeakerSimple = withSocket(PresenterSimple); const SStageManager = withSocket(StageManager); const SPublic = withSocket(Public); const SLowerThird = withSocket(Lower); +const SPip = withSocket(Pip); function App() { return ( @@ -48,6 +51,7 @@ function App() { {/* */} + diff --git a/client/src/common/components/nav/NavLogo.jsx b/client/src/common/components/nav/NavLogo.jsx index 67dbbc56b..be4583504 100644 --- a/client/src/common/components/nav/NavLogo.jsx +++ b/client/src/common/components/nav/NavLogo.jsx @@ -64,6 +64,14 @@ export default function NavLogo() { > Lower Thirds + } + > + PIP + )} diff --git a/client/src/common/components/views/Paginator.jsx b/client/src/common/components/views/Paginator.jsx index 4f707b61e..f3f1e9cc0 100644 --- a/client/src/common/components/views/Paginator.jsx +++ b/client/src/common/components/views/Paginator.jsx @@ -5,23 +5,17 @@ import { useInterval } from '../../../app/hooks/useInterval'; export default function Paginator(props) { const { events, selectedId } = props; - const LIMIT_PER_PAGE = 7; - const SCROLL_TIME = 5000; + const LIMIT_PER_PAGE = props.limit || 7; + const SCROLL_TIME = (props.time * 1000) || 5000; const SCROLL_PAST = false; const [numEvents, setNumEvents] = useState(0); const [page, setPage] = useState([]); const [pages, setPages] = useState(0); const [selPage, setSelPage] = useState(0); - - // Keep track of order - // 0 - events before - // 1 - running event - // 2 - future event - let selectedYet = 0; + const [selected, setSelected] = useState(-1); useEffect(() => { if (events == null) return; - // how many events in list let n = events.length; setNumEvents(n); @@ -37,7 +31,19 @@ export default function Paginator(props) { setPage(e); // if array is completely in past, show depending on SCROLL_PAST - }, [events, selPage]); + }, [events, selPage, LIMIT_PER_PAGE]); + + useEffect(() => { + // maybe nothing is selected + if (events == null || selectedId == null) return; + + // which one is selected + const s = events.filter((e) => e.id === selectedId); + + if (s.length < 1) return; + + setSelected(s[0].order); + }, [events, selectedId]); // every SCROLL_TIME go to the next array useInterval(() => { @@ -49,25 +55,24 @@ export default function Paginator(props) { return ( <> - {pages > 1 && ( -
- {[...Array(pages)].map((p, i) => ( +
+ {pages > 1 && + [...Array(pages)].map((p, i) => (
))} -
- )} - +
{page.map((e) => { - if (e.id === selectedId) selectedYet = 1; - else if (selectedYet === 1) selectedYet = 2; + let selectedState = 0; + if (e.order === selected) selectedState = 1; + else if (e.order > selected) selectedState = 2; return ( diff --git a/client/src/features/viewers/lower/Lower.jsx b/client/src/features/viewers/production/Lower.jsx similarity index 100% rename from client/src/features/viewers/lower/Lower.jsx rename to client/src/features/viewers/production/Lower.jsx diff --git a/client/src/features/viewers/lower/Lower.module.css b/client/src/features/viewers/production/Lower.module.css similarity index 100% rename from client/src/features/viewers/lower/Lower.module.css rename to client/src/features/viewers/production/Lower.module.css diff --git a/client/src/features/viewers/production/Pip.jsx b/client/src/features/viewers/production/Pip.jsx new file mode 100644 index 000000000..01b357d78 --- /dev/null +++ b/client/src/features/viewers/production/Pip.jsx @@ -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 ( +
+ + +
{general.title}
+ +
+
Today
+
+ +
+
+ +
+ + {size} +
+ + + {showInfo && ( + +
Info
+
+
{general.backstageInfo}
+
+
+ {general.url != null && general.url !== '' && ( + + )} +
+
+ )} +
+ +
+
Time Now
+
{time.clock}
+
+ +
+
Stage Timer
+
{stageTimer}
+
+
+ ); +} diff --git a/client/src/features/viewers/production/Pip.module.css b/client/src/features/viewers/production/Pip.module.css new file mode 100644 index 000000000..48b0f5189 --- /dev/null +++ b/client/src/features/viewers/production/Pip.module.css @@ -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; +}