Feat/studio clock (#60)

This commit is contained in:
Carlos Valente
2021-12-13 21:29:56 +01:00
committed by GitHub
parent 627cce7ba6
commit 80c2e9a8ba
26 changed files with 1099 additions and 135 deletions
+15 -4
View File
@@ -10,13 +10,9 @@ const withSocket = (Component) => {
const WrappedComponent = (props) => {
const {
data: eventsData,
status: eventsDataStatus,
isError: eventsDataIsError,
} = useFetch(EVENTS_TABLE, fetchAllEvents);
const {
data: genData,
status: genDataStatus,
isError: genDataIsError,
} = useFetch(EVENT_TABLE, fetchEvent);
const [publicEvents, setPublicEvents] = useState([]);
@@ -58,6 +54,7 @@ const withSocket = (Component) => {
presenterNext: '',
});
const [selectedId, setSelectedId] = useState(null);
const [nextId, setNextId] = useState(null);
const [publicSelectedId, setPublicSelectedId] = useState(null);
const [general, setGeneral] = useState({
title: '',
@@ -67,6 +64,7 @@ const withSocket = (Component) => {
endMessage: '',
});
const [playback, setPlayback] = useState(null);
const [onAir, setOnAir] = useState(false);
// Ask for update on load
useEffect(() => {
@@ -96,6 +94,9 @@ const withSocket = (Component) => {
socket.on('playstate', (data) => {
setPlayback(data);
});
socket.on('onAir', (data) => {
setOnAir(data);
});
// Handle titles
socket.on('titles', (data) => {
@@ -112,6 +113,9 @@ const withSocket = (Component) => {
socket.on('publicselected-id', (data) => {
setPublicSelectedId(data);
});
socket.on('next-id', (data) => {
setNextId(data);
});
// Ask for up to date data
socket.emit('get-messages');
@@ -124,6 +128,7 @@ const withSocket = (Component) => {
// ask for playstate
socket.emit('get-playstate');
socket.emit('get-onAir')
// Ask for up titles
socket.emit('get-titles');
@@ -131,6 +136,7 @@ const withSocket = (Component) => {
// Ask for up selected
socket.emit('get-selected-id');
socket.emit('get-next-id');
// Clear listeners
return () => {
@@ -139,9 +145,11 @@ const withSocket = (Component) => {
socket.off('messages-lower');
socket.off('timer');
socket.off('playstate');
socket.off('onAir');
socket.off('titles');
socket.off('publictitles');
socket.off('selected-id');
socket.emit('next-id');
};
}, [socket]);
@@ -224,6 +232,7 @@ const withSocket = (Component) => {
...timer,
finished: playback === 'start' && timer.running <= 0 && timer.startedAt,
clock: stringFromMillis(timer.clock),
clockNoSeconds: stringFromMillis(timer.clock, false),
playstate: playback,
};
@@ -240,7 +249,9 @@ const withSocket = (Component) => {
backstageEvents={backstageEvents}
selectedId={selectedId}
publicSelectedId={publicSelectedId}
nextId={nextId}
general={general}
onAir={onAir}
/>
);
};
@@ -6,6 +6,7 @@ import NavLogo from 'common/components/nav/NavLogo';
import { useEffect, useState } from 'react';
import { AnimatePresence, motion } from 'framer-motion';
import TitleSide from 'common/components/views/TitleSide';
import {getEventsWithDelay} from "../../../common/utils/eventsManager";
export default function StageManager(props) {
const { publ, title, time, backstageEvents, selectedId, general } = props;
@@ -20,23 +21,9 @@ export default function StageManager(props) {
useEffect(() => {
if (backstageEvents == null) return;
let events = [...backstageEvents];
setFilteredEvents(getEventsWithDelay(backstageEvents));
// Add running delay
let delay = 0;
for (const e of events) {
if (e.type === 'block') delay = 0;
else if (e.type === 'delay') delay = delay + e.duration;
else if (e.type === 'event' && delay > 0) {
e.timeStart += delay;
e.timeEnd += delay;
}
}
// filter just events
let filtered = events.filter((e) => e.type === 'event');
setFilteredEvents(filtered);
console.log('hhh', getEventsWithDelay(backstageEvents))
}, [backstageEvents]);
// Format messages
@@ -11,7 +11,7 @@ export default function PresenterView(props) {
// Set window title
useEffect(() => {
document.title = 'ontime - Speaker Screen';
document.title = 'ontime - Presenter Screen';
}, []);
const showOverlay = pres.text !== '' && pres.visible;
@@ -1,6 +1,7 @@
import { AnimatePresence, motion } from 'framer-motion';
import { useEffect, useState } from 'react';
import style from './LowerClean.module.css';
import NavLogo from "../../../../common/components/nav/NavLogo";
export default function LowerClean(props) {
const { lower, title, options } = props;
@@ -86,6 +87,9 @@ export default function LowerClean(props) {
fontSize: `${sizeMultiplier}vh`,
}}
>
<NavLogo isHidden />
<AnimatePresence>
{showLower && (
<motion.div
@@ -1,6 +1,7 @@
import { AnimatePresence, motion } from 'framer-motion';
import { useEffect, useState } from 'react';
import style from './LowerLines.module.css';
import NavLogo from "../../../../common/components/nav/NavLogo";
export default function LowerLines(props) {
const { lower, title, options } = props;
@@ -126,6 +127,9 @@ export default function LowerLines(props) {
fontSize: `${sizeMultiplier}vh`,
}}
>
<NavLogo isHidden />
<AnimatePresence>
{showLower && (
<motion.div
@@ -0,0 +1,87 @@
import style from "./StudioClock.module.scss";
import useFitText from "use-fit-text";
import NavLogo from "../../../common/components/nav/NavLogo";
import {useEffect, useState} from "react";
import {formatDisplay} from "../../../common/utils/dateConfig";
import {formatEventList, trimEventlist} from "../../../common/utils/eventsManager";
export default function StudioClock(props) {
const { title, time, backstageEvents, selectedId, nextId, onAir } = props;
const { fontSize, ref } = useFitText({maxFontSize:500});
const [hoursNow, minutesNow] = time.clockNoSeconds.split(':');
const [schedule, setSchedule] = useState([]);
const hoursIndicators = [...Array(12).keys()];
const minutesIndicators = [...Array(60).keys()];
const MAX_TITLES = 8;
// Set window title
useEffect(() => {
document.title = 'ontime - Studio Clock';
}, []);
// Prepare event list
// Todo: useMemo()
useEffect(() => {
if (backstageEvents == null) return;
const events = backstageEvents.filter((e) => e.type === 'event');
let e = trimEventlist(events, selectedId, MAX_TITLES);
e = formatEventList(e, selectedId, nextId);
setSchedule(e);
}, [backstageEvents, selectedId, nextId]);
return (
<div className={style.container}>
<NavLogo />
<div className={style.clockContainer}>
<div className={style.time}>{time.clockNoSeconds}</div>
<div
ref={ref}
className={style.nextTitle}
style={{fontSize, height:'100px', width:'100%', maxWidth:'680px'}}
>
{title.titleNext}
</div>
<div className={time.running > 0 ? style.nextCountdown: style.nextCountdown__overtime}>
{selectedId != null && formatDisplay(time.running)}
</div>
<div className={style.indicators}>
{hoursIndicators.map(i => (
<div
key = {i}
className={i <= hoursNow ? style.hours__active : style.hours}
style={{
transform: `rotate(${360/12*i-90}deg) translateX(380px)`
}}/>
)
)}
{minutesIndicators.map(i => (
<div
key={i}
className={i <= minutesNow ? style.min__active : style.min}
style={{
transform: `rotate(${360/60*i-90}deg) translateX(415px)`
}}/>
)
)}
</div>
</div>
<div className={style.scheduleContainer}>
<div className={onAir ? style.onAir : style.onAir__idle}>ON AIR</div>
<div className={style.schedule}>
<ul>
{schedule.map((s) => (
<li key={s.id} className={s.isNow ? style.now : s.isNext ? style.next : ''}>
<div className={s.isNow ? style.decorator__active : s.isNext ? style.decorator__next : style.decorator}/>{`${s.time} ${s.title}`}
</li>
))
}
</ul>
</div>
</div>
</div>
);
}
@@ -0,0 +1,190 @@
@font-face {
font-family: "digital-clock";
src: local('digital-7'), url('./../../../assets/fonts/digital-7.mono.ttf') format('truetype') ;
}
.container {
margin: 0;
box-sizing: border-box; /* reset */
overflow: hidden;
width: 100%; /* restrict the page width to viewport */
height: 100vh;
padding: 1vw;
background: #111;
display: grid;
grid-template-columns: 1000px 1fr;
gap: 50px;
grid-template-areas: "clck schd";
/* =============== CLOCK STUFF ==================*/
$clock-size: 900px;
$size-hours: 20px;
$half-hours: 10px;
$size-min: 18px;
$half-min: 9px;
$red-active: #c53030;
$red-idle: darken($red-active, 30%);
$cyan-active: #0ff;
$cyan-idle: #0aa;
.clockContainer {
display: grid;
place-content: center;
grid-area: clck;
width: $clock-size;
height: $clock-size;
text-align: center;
position: relative;
margin: auto;
font-family: digital-clock, monospace;
text-transform: uppercase;
.time {
margin-top: 175px;
color: $red-active;
font-size: 275px;
line-height: 0.8em;
text-shadow: rgb(180,0,0) 0 0 20px;
}
.nextTitle:after,
.nextCountdown:after,
.nextCountdown__overtime:after {
content: '\200b';
}
.nextTitle {
color:$cyan-idle;
line-height: 100px;
}
.nextCountdown,
.nextCountdown__overtime {
font-size: 100px;
line-height: 1em;
}
.nextCountdown {
color: $cyan-active;
text-shadow: rgb(0,100,100) 0 0 20px;
}
.nextCountdown::before {
content: '-';
}
.nextCountdown__overtime {
color: darken($red-active, 10%);
}
.indicators {
position: absolute;
width: 100%;
height: 100%;
.min,
.min__active,
.hours,
.hours__active {
border-radius: 50%;
position: absolute;
background: $red-idle;
}
.min,
.min__active {
min-height: $size-min;
width: $size-min;
top: calc(50% - #{$half_min});
left: calc(50% - #{$half_min});
}
.hours,
.hours__active{
min-height:$size-hours;
width: $size-hours;
top: calc(50% - #{$half_hours});
left: calc(50% - #{$half_hours});
}
.min__active,
.hours__active {
background: $red-active;
box-shadow: 0 0 10px 2px rgba(255,0,0,0.25);
}
}
}
/* ============= SCHEDULE STUFF =================*/
.scheduleContainer {
grid-area: schd;
margin: 50px 0;
padding-right: 50px;
font-family: digital-clock, monospace;
text-transform: uppercase;
.onAir,
.onAir__idle{
padding-bottom: 50px;
font-size: 190px;
line-height: 0.9em;
letter-spacing: 0.05em;
}
.onAir {
color: $red-active;
text-shadow: rgb(150,0,0) 0 0 20px;
}
.onAir__idle {
color: $red-idle;
}
.schedule {
ul {
color: $cyan-idle;
line-height: 1em;
list-style: none;
font-size: 40px;
}
li {
padding-bottom: 0.5em;
display: flex;
align-items: center;
gap: 20px;
}
.now {
color: $cyan-active;
text-shadow: rgb(0,100,100) 0 0 20px;
}
.next {
color: $red-active;
text-shadow: rgb(100,0,0) 0 0 20px;
}
.decorator,
.decorator__active,
.decorator__next {
aspect-ratio: 1;
border-radius: 50%;
background: $red-idle;
min-height: $size-hours;
max-height: $size-hours;
width: $size-hours;
}
.decorator__active {
background: $cyan-active;
box-shadow: 0 0 10px 2px rgba(0,255,255,0.25);
}
.decorator__next{
background: $red-active;
box-shadow: 0 0 10px 2px rgba(255,0,0,0.25);
}
}
}
}
@media only screen and (max-width: 1600px) {
.container {
display: grid;
grid-template-areas: "clck";
grid-template-columns: 100%;
place-content: center;
}
.scheduleContainer{
display: none;
}
}