diff --git a/src/App.jsx b/src/App.jsx index 225ee33807383ca265ee4d90afe80d939d6afbad..6a9b2098105e34447e3b8f41da573a1c9852ddea 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,6 +3,7 @@ import { BrowserRouter as Router, Route, Switch } from "react-router-dom"; import { KeycloakProvider } from "@react-keycloak/web"; import * as Sentry from "@sentry/react"; +import { loadConfig } from "actions/global-info"; import { initializeWSChannel } from "actions/ws"; import Footer from "components/Footer"; import Navbar from "components/Navbar"; @@ -75,6 +76,7 @@ const LoadingComponent = ( const BaseApp = () => { initializeWSChannel.read(); + loadConfig.read(); return ( <Router> diff --git a/src/actions/global-info.js b/src/actions/global-info.js new file mode 100644 index 0000000000000000000000000000000000000000..6e8c268fe43fd701446c53e45dc8d7ae69506586 --- /dev/null +++ b/src/actions/global-info.js @@ -0,0 +1,35 @@ +import isArray from "lodash/isArray"; +import { createAsyncAction, errorResult, successResult } from "pullstate"; + +import { fetch } from "api"; +import { GlobalInfoStore } from "stores"; + +export const loadConfig = createAsyncAction( + async () => { + try { + const resp = await fetch("/config"); + const payload = await resp.json(); + + if (!isArray(payload)) { + return errorResult([], "Unexpected response format"); + } + + return successResult(payload); + } catch (err) { + return errorResult([], err.toString()); + } + }, + { + postActionHook: ({ result }) => { + if (!result.error) { + GlobalInfoStore.update((state) => { + result.payload.forEach((rawConfigItem) => { + if (rawConfigItem.id === "stream_url") { + state.streamUrl = rawConfigItem.value; + } + }); + }); + } + }, + } +); diff --git a/src/pages/Home.css b/src/pages/Home.css new file mode 100644 index 0000000000000000000000000000000000000000..d8210b5879d6f84c1ff834d223d9523238a5902d --- /dev/null +++ b/src/pages/Home.css @@ -0,0 +1,14 @@ +/* auto-size iframe according to aspect ratio while keeping the 100% height */ +.iframe-container { + position: relative; + padding-bottom: 56.25%; /* 16:9 */ + height: 0; +} + +.iframe-container iframe { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; +} diff --git a/src/pages/Home.jsx b/src/pages/Home.jsx index aaa2ec1246f6bc8882a93525358ea8212018db6f..4d3dc7e525853d034f3e79bf9a4debe6e257710f 100644 --- a/src/pages/Home.jsx +++ b/src/pages/Home.jsx @@ -17,7 +17,9 @@ import AnnouncementsContainer from "containers/AnnoucementsContainer"; import PostFilters from "containers/PostFilters"; import PostsContainer from "containers/PostsContainer"; import { useActionConfirm } from "hooks"; -import { AuthStore, ProgramStore } from "stores"; +import { AuthStore, GlobalInfoStore, ProgramStore } from "stores"; + +import "./Home.css"; const NotYetStarted = ({ startAt }) => ( <article className="container container--wide py-8 md:py-16 lg:py-32"> @@ -94,6 +96,7 @@ const Home = () => { scheduleIds, } = ProgramStore.useState(); const { isAuthenticated, user } = AuthStore.useState(); + const { streamUrl } = GlobalInfoStore.useState(); const programEntry = currentId ? programEntries[currentId] : null; const [showProgramEditModal, setShowProgramEditModal] = useState(false); const [ @@ -191,15 +194,25 @@ const Home = () => { )} </div> - <iframe - width="100%" - height="500" - src="https://www.youtube.com/embed/73jJLspL8o8" - frameBorder="0" - allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" - allowFullScreen="" - title="Video stream" - ></iframe> + {streamUrl && ( + <div className="iframe-container"> + <iframe + width="560" + height="315" + src={streamUrl} + frameBorder="0" + allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" + allowFullScreen="" + title="Video stream" + /> + </div> + )} + {!streamUrl && ( + <p> + Server neposlal informaci o aktuálním streamu. Vyčkejte na + aktualizaci. + </p> + )} </section> <section className="cf2021__notifications"> diff --git a/src/stores.js b/src/stores.js index f4e1511b998a6e84afec4917c1608a2f1e30ad06..c7db0904befc1db704ab16065c90966069734d95 100644 --- a/src/stores.js +++ b/src/stores.js @@ -1,6 +1,14 @@ import memoize from "lodash/memoize"; import { Store } from "pullstate"; +/** @type {CF2021.GlobalInfoStorePayload} */ +const globalInfoStoreInitial = { + onlineUsers: 0, + streamUrl: null, +}; + +export const GlobalInfoStore = new Store(globalInfoStoreInitial); + /** @type {CF2021.AuthStorePayload} */ const authStoreInitial = { isAuthenticated: false, diff --git a/typings/cf2021.d.ts b/typings/cf2021.d.ts index fbed0e076d90270ed75dfc7abd2134f954a2252f..33bb981157b5b9f7df0b58814fcfbc76d6028104 100644 --- a/typings/cf2021.d.ts +++ b/typings/cf2021.d.ts @@ -1,4 +1,9 @@ declare namespace CF2021 { + export interface GlobalInfoStorePayload { + onlineUsers: number; + streamUrl?: string; + } + interface ProgramScheduleEntry { id: number; number: string;