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}
+
+
+
+
+
+ {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;
+}