diff --git a/client/src/App.jsx b/client/src/App.jsx index 1aa7cb1b1..20d7f2d82 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -19,18 +19,17 @@ const SStageManager = withSocket(StageManager); function App() { return ( -
- - - - - - - - - - -
+ +
+ + + + + + + +
+
); } diff --git a/client/src/features/viewers/ViewWrapper.jsx b/client/src/features/viewers/ViewWrapper.jsx index ab98ada94..96034b1df 100644 --- a/client/src/features/viewers/ViewWrapper.jsx +++ b/client/src/features/viewers/ViewWrapper.jsx @@ -1,9 +1,25 @@ import { useEffect, useState } from 'react'; +import { useQuery } from 'react-query'; +import { eventsNamespace, fetchAllEvents } from '../../app/api/eventsApi'; +import { fetchSettings, settingsNamespace } from '../../app/api/settingsApi'; import { useSocket } from '../../app/context/socketContext'; import { stringFromMillis } from '../../common/dateConfig'; const withSocket = (Component) => { const WrappedComponent = (props) => { + const { + data: eventsData, + status: eventsDataStatus, + isError: eventsDataIsError, + } = useQuery(eventsNamespace, fetchAllEvents); + const { + data: genData, + status: genDataStatus, + isError: genDataIsError, + } = useQuery(settingsNamespace, fetchSettings); + + const [events, setEvents] = useState([]); + const socket = useSocket(); const [pres, setPres] = useState({ text: '', @@ -31,6 +47,13 @@ const withSocket = (Component) => { subtitleNext: '', presenterNext: '', }); + const [selectedId, setSelectedId] = useState({}); + const [general, setGeneral] = useState({ + title: '', + url: '', + publicInfo: '', + backstageInfo: '', + }); // Ask for update on load useEffect(() => { @@ -56,11 +79,16 @@ const withSocket = (Component) => { setTimer({ ...data }); }); - // Handle timer + // Handle titles socket.on('titles', (data) => { setTitles({ ...data }); }); + // Handle selected event + socket.on('selected-id', (data) => { + setSelectedId(data); + }); + // Ask for up to date data socket.emit('get-messages'); @@ -70,6 +98,9 @@ const withSocket = (Component) => { // Ask for up titles socket.emit('get-titles'); + // Ask for up selected + socket.emit('get-selected-id'); + // Clear listeners return () => { socket.off('messages-public'); @@ -77,9 +108,23 @@ const withSocket = (Component) => { socket.off('messages-lower'); socket.off('timer'); socket.off('titles'); + socket.off('selected-id'); }; }, [socket]); + // Filter events only to pass down + useEffect(() => { + if (eventsData == null) return; + const e = eventsData.filter((d) => d.type === 'event'); + setEvents(e); + }, [eventsData]); + + // Set general data + useEffect(() => { + if (genData == null) return; + setGeneral(genData); + }, [genData]); + /********************************************/ /*** + titleManager ***/ /*** WRAP INFORMATION RELATED TO TITLES ***/ @@ -114,6 +159,9 @@ const withSocket = (Component) => { lower={lower} title={titleManager} time={timeManager} + events={events} + selectedId={selectedId} + general={general} /> ); }; diff --git a/client/src/features/viewers/backstage/StageManager.jsx b/client/src/features/viewers/backstage/StageManager.jsx index 4f4caab7e..ce36a83ea 100644 --- a/client/src/features/viewers/backstage/StageManager.jsx +++ b/client/src/features/viewers/backstage/StageManager.jsx @@ -1,19 +1,27 @@ import QRCode from 'react-qr-code'; import { formatDisplay } from '../../../common/dateConfig'; import style from './StageManager.module.css'; +import TodayItem from './TodayItem'; export default function StageManager(props) { - const { pres, publ, lower, title, time } = props; + const { pres, publ, lower, title, time, events, selectedId, general } = props; + // Format messages const showPubl = publ.text !== '' && publ.visible; const stageTimer = time.currentSeconds != null && !isNaN(time.currentSeconds) ? formatDisplay(time.currentSeconds, true) : ''; + // Keep track of order + // 0 - events before + // 1 - running event + // 2 - future event + let selectedYet = 0; + return (
-
A hamburger life
+
{general.title}
Now
{title.titleNow}
@@ -29,32 +37,20 @@ export default function StageManager(props) {
Today
-
-
10:30
-
10:45
-
A dress to the nation
-
-
-
10:30
-
10:45
-
A dress to the nation
-
-
-
10:30
-
10:45
-
A dress to the nation
-
-
-
10:30
-
10:45
-
Are burguers real
-
-
-
10:30
-
10:45
-
Up to midnight
-
+ {events.map((e) => { + if (e.id === selectedId) selectedYet = 1; + else if (selectedYet === 1) selectedYet = 2; + return ( + + ); + })}
+
@@ -84,16 +80,16 @@ export default function StageManager(props) {
Info
-
Water in the public bathroom
-
Wifi Password: bemywifi
-
Have a nice day
+
{general.backstageInfo}
- + {general.url != null && general.url !== '' && ( + + )}
diff --git a/client/src/features/viewers/backstage/StageManager.module.css b/client/src/features/viewers/backstage/StageManager.module.css index 4960ae05e..c12b1d2ff 100644 --- a/client/src/features/viewers/backstage/StageManager.module.css +++ b/client/src/features/viewers/backstage/StageManager.module.css @@ -1,3 +1,5 @@ +@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@300;400;600;800&display=swap'); + .container__gray, .container__grayFinished { margin: 0; @@ -30,8 +32,9 @@ .eventTitle { grid-area: titl; - font-size: 4vw; + font-size: 3vw; font-weight: 600; + text-decoration: underline #ff7597 0.5vh; padding-top: 0.2vh; padding-left: 1vw; } @@ -206,15 +209,18 @@ grid-area: info; display: grid; + grid-template-rows: 3vh minmax(0, 1fr); grid-template-columns: 3fr 1fr; grid-template-areas: - 'titl qr' - 'info qr'; + 'titl .' + 'binf qr'; + gap: 0.5vw; } .infoMessages { - grid-area: info; + grid-area: binf; font-size: 1.5vw; + white-space: pre-line; } .qr { @@ -236,6 +242,7 @@ } .clock { + font-family: 'Open Sans', sans-serif; font-size: 3vw; line-height: 3vw; text-align: center; diff --git a/client/src/features/viewers/backstage/TodayItem.jsx b/client/src/features/viewers/backstage/TodayItem.jsx new file mode 100644 index 000000000..891d11de6 --- /dev/null +++ b/client/src/features/viewers/backstage/TodayItem.jsx @@ -0,0 +1,21 @@ +import { stringFromMillis } from '../../../common/dateConfig'; +import style from './StageManager.module.css'; +export default function TodayItem(props) { + const { selected, timeStart, timeEnd, title } = props; + + // Format timers + const start = timeStart ? stringFromMillis(timeStart, false) : ''; + const end = timeEnd ? stringFromMillis(timeEnd, false) : ''; + + // select styling + let selectStyle = style.entryPast; + if (selected === 1) selectStyle = style.entryNow; + else if (selected === 2) selectStyle = style.entryFuture; + return ( +
+
{start}
+
{end}
+
{title}
+
+ ); +} diff --git a/server/controllers/settingsController.js b/server/controllers/settingsController.js new file mode 100644 index 000000000..066e3557d --- /dev/null +++ b/server/controllers/settingsController.js @@ -0,0 +1,143 @@ +// get database +const db = require('../app.js').db; +const table = 'settings'; + +function getSettings() { + return db.get(table).value(); +} + +// Create controller for GET request to 'settings' +// Returns ACK message +exports.getAll = async (req, res) => { + const settings = getSettings(); + if (settings) res.json(settings); + else res.sendStatus(400); +}; + +// Create controller for POST request to 'settings' +// Returns ACK message +exports.post = async (req, res) => { + if (!req.body) { + res.status(400).send(`No object found in request`); + return; + } + // TODO: validate data + try { + db.get(table) + .assign({ ...req.body }) + .write(); + res.sendStatus(200); + } catch (error) { + res.status(400).send(error); + } +}; + +// Create controller for GET request to 'settings/title' +// Returns ACK message +exports.titleGet = async (req, res) => { + const settings = getSettings(); + + if (settings) res.json(settings.title); + else res.sendStatus(400); +}; + +// Create controller for POST request to 'settings/title' +// Returns ACK message +exports.titlePost = async (req, res) => { + if (!req.body) { + res.status(400).send(`No object found in request`); + return; + } + + // TODO: validate data + try { + db.get(table).assign({ title: req.body.title }).write(); + res.sendStatus(200); + } catch (error) { + res.status(400).send(error); + } +}; + +// Create controller for GET request to '/settings/url' +// Returns ACK message +exports.urlGet = async (req, res) => { + let event = getEventOptions(); + + if (event) res.json(event.url); + else res.sendStatus(400); +}; + +// Create controller for POST request to '/settings/url' +// Returns ACK message +exports.urlPost = async (req, res) => { + if (!req.body) { + res.status(400).send(`No object found in request`); + return; + } + + // TODO: validate data + try { + db.get(table).assign({ url: req.body.url }).write(); + res.sendStatus(200); + } catch (error) { + res.status(400).send(error); + } +}; + +// Create controller for GET request to 'settings/publicInfo' +// Returns ACK message +exports.publicInfoGet = async (req, res) => { + const settings = getSettings(); + + if (settings) res.json(settings.publicInfo); + else res.sendStatus(400); +}; + +// Create controller for POST request to '/settings/publicInfo' +// Returns ACK message +exports.publicInfoPost = async (req, res) => { + if (!req.body) { + res.status(400).send(`No object found in request`); + return; + } + + // TODO: validate data + try { + db.get(table).assign({ publicInfo: req.body.publicInfo }).write(); + res.sendStatus(200); + } catch (error) { + res.status(400).send(error); + } +}; + +// Create controller for GET request to 'settings/backstageInfo' +// Returns ACK message +exports.backstageInfoGet = async (req, res) => { + const settings = getSettings(); + + if (settings) res.json(settings.backstageInfo); + else res.sendStatus(400); +}; + +// Create controller for POST request to '/settings/info' +// Returns ACK message +exports.backstageInfoPost = async (req, res) => { + if (!req.body) { + res.status(400).send(`No object found in request`); + return; + } + + // TODO: validate data + try { + db.get(table).assign({ backstageInfo: req.body.backstageInfo }).write(); + res.sendStatus(200); + } catch (error) { + res.status(400).send(error); + } +}; + +// Create controller for GET request to 'settings/osc' +// Returns ACK message +exports.osc = async (req, res) => { + res.send('Not yet implemented').status(500); +};