Skip to content
Snippets Groups Projects
Commit ea3c0619 authored by xaralis's avatar xaralis
Browse files

feat: when protocol url is available, load protocol

parent 6b2601e3
No related branches found
No related tags found
No related merge requests found
Pipeline #1960 passed
...@@ -1421,6 +1421,11 @@ ...@@ -1421,6 +1421,11 @@
"prop-types": "^15.7.2" "prop-types": "^15.7.2"
} }
}, },
"@rooks/use-interval": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/@rooks/use-interval/-/use-interval-4.5.0.tgz",
"integrity": "sha512-As0DueIAGLJLYATKPPOCDGqoIlwbhPAcYP14TNTHaAj9/ODdvUYFXAP3jFCRzDNpjXCIgSe4oBuzVVmM526n+Q=="
},
"@rooks/use-window-size": { "@rooks/use-window-size": {
"version": "4.5.0", "version": "4.5.0",
"resolved": "https://registry.npmjs.org/@rooks/use-window-size/-/use-window-size-4.5.0.tgz", "resolved": "https://registry.npmjs.org/@rooks/use-window-size/-/use-window-size-4.5.0.tgz",
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@react-keycloak/web": "^2.1.4", "@react-keycloak/web": "^2.1.4",
"@rooks/use-interval": "^4.5.0",
"@rooks/use-window-size": "^4.5.0", "@rooks/use-window-size": "^4.5.0",
"@sentry/react": "^5.29.2", "@sentry/react": "^5.29.2",
"classnames": "^2.2.6", "classnames": "^2.2.6",
......
...@@ -11,6 +11,7 @@ import Footer from "components/Footer"; ...@@ -11,6 +11,7 @@ import Footer from "components/Footer";
import Navbar from "components/Navbar"; import Navbar from "components/Navbar";
import Home from "pages/Home"; import Home from "pages/Home";
import Program from "pages/Program"; import Program from "pages/Program";
import Protocol from "pages/Protocol";
import { AuthStore, PostStore } from "stores"; import { AuthStore, PostStore } from "stores";
import { updateWindowPosts } from "utils"; import { updateWindowPosts } from "utils";
...@@ -82,12 +83,12 @@ const LoadingComponent = ( ...@@ -82,12 +83,12 @@ const LoadingComponent = (
const NotFound = () => ( const NotFound = () => (
<article className="container container--default py-8 lg:py-24"> <article className="container container--default py-8 lg:py-24">
<h1 className="head-alt-base lg:head-alt-lg mb-8"> <h1 className="head-alt-base lg:head-alt-lg mb-8">
404ka: Tahle stránka tu není 404ka: tak tahle stránka tu není
</h1> </h1>
<p className="text-base lg:text-xl mb-8"> <p className="text-base lg:text-xl mb-8">
Dostali jste se na takzvanou „<strong>čtyřystačtyřku</strong>“, což Dostal/a ses na takzvanou „<strong>čtyřystačtyřku</strong>“, což znamená,
znamená, že stránka, kterou jste se pokusili navštívit, na tomhle webu že stránka, kterou jsi se pokusil/a navštívit, na tomhle webu není.
není. Zkontroluj, zda máš správný odkaz.
</p> </p>
<Button routerTo="/" className="text-base lg:text-xl" hoverActive fullwidth> <Button routerTo="/" className="text-base lg:text-xl" hoverActive fullwidth>
Přejít na hlavní stránku Přejít na hlavní stránku
...@@ -104,6 +105,7 @@ const BaseApp = () => { ...@@ -104,6 +105,7 @@ const BaseApp = () => {
<Switch> <Switch>
<Route exact path="/" children={<Home />} /> <Route exact path="/" children={<Home />} />
<Route exact path="/program" children={<Program />} /> <Route exact path="/program" children={<Program />} />
<Route exact path="/protocol" children={<Protocol />} />
<Route component={NotFound} /> <Route component={NotFound} />
</Switch> </Switch>
<Footer /> <Footer />
......
import isArray from "lodash/isArray"; import isArray from "lodash/isArray";
import { createAsyncAction, errorResult, successResult } from "pullstate"; import { createAsyncAction, errorResult, successResult } from "pullstate";
import baseFetch from "unfetch";
import { fetch } from "api"; import { fetch } from "api";
import { markdownConverter } from "markdown";
import { GlobalInfoStore } from "stores"; import { GlobalInfoStore } from "stores";
export const loadConfig = createAsyncAction( export const loadConfig = createAsyncAction(
...@@ -30,7 +32,37 @@ export const loadConfig = createAsyncAction( ...@@ -30,7 +32,37 @@ export const loadConfig = createAsyncAction(
if (rawConfigItem.id === "websocket_url") { if (rawConfigItem.id === "websocket_url") {
state.websocketUrl = rawConfigItem.value; state.websocketUrl = rawConfigItem.value;
} }
if (rawConfigItem.id === "record_url") {
state.protocolUrl = rawConfigItem.value;
}
});
}); });
}
},
}
);
export const loadProtocol = createAsyncAction(
async () => {
const { protocolUrl } = GlobalInfoStore.getRawState();
try {
const resp = await baseFetch(protocolUrl);
if (resp.status !== 200) {
return errorResult([], `Unexpected status code ${resp.status}`);
}
return successResult(await resp.text());
} catch (err) {
return errorResult([], err.toString());
}
},
{
postActionHook: ({ result }) => {
if (!result.error) {
GlobalInfoStore.update((state) => {
state.protocol = markdownConverter.makeHtml(result.payload);
}); });
} }
}, },
......
...@@ -12,6 +12,7 @@ const Button = ({ ...@@ -12,6 +12,7 @@ const Button = ({
loading = false, loading = false,
children, children,
routerTo, routerTo,
bodyProps = {},
...props ...props
}) => { }) => {
const btnClass = classNames( const btnClass = classNames(
...@@ -30,7 +31,9 @@ const Button = ({ ...@@ -30,7 +31,9 @@ const Button = ({
const inner = ( const inner = (
<div className="btn__body-wrap"> <div className="btn__body-wrap">
<div className={bodyClass}>{children}</div> <div className={bodyClass} {...bodyProps}>
{children}
</div>
{!!icon && ( {!!icon && (
<div className="btn__icon"> <div className="btn__icon">
<i className={icon}></i> <i className={icon}></i>
......
...@@ -30,6 +30,9 @@ const Footer = () => { ...@@ -30,6 +30,9 @@ const Footer = () => {
<li> <li>
<NavLink to="/program">Program</NavLink> <NavLink to="/program">Program</NavLink>
</li> </li>
<li>
<NavLink to="/protocol">Zápis</NavLink>
</li>
</ul> </ul>
</div> </div>
</div> </div>
......
...@@ -100,6 +100,11 @@ const Navbar = () => { ...@@ -100,6 +100,11 @@ const Navbar = () => {
Program Program
</NavLink> </NavLink>
</li> </li>
<li className="navbar-menu__item">
<NavLink className="navbar-menu__link" to="/protocol">
Zápis
</NavLink>
</li>
</ul> </ul>
</div> </div>
<div className="navbar__actions navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto self-start flex flex-row items-center"> <div className="navbar__actions navbar__section navbar__section--expandable container-padding--zero lg:container-padding--auto self-start flex flex-row items-center">
......
...@@ -25,7 +25,7 @@ const Schedule = () => { ...@@ -25,7 +25,7 @@ const Schedule = () => {
); );
return ( return (
<article className="container container--wide py-8 lg:py-24"> <article className="container container--default py-8 lg:py-24">
<h1 className="head-alt-md lg:head-alt-lg mb-8">Program zasedání</h1> <h1 className="head-alt-md lg:head-alt-lg mb-8">Program zasedání</h1>
<div className="flex flex-col"> <div className="flex flex-col">
{scheduleIds.map((id) => { {scheduleIds.map((id) => {
......
import React, { useCallback, useState } from "react";
import useInterval from "@rooks/use-interval";
import { loadProtocol } from "actions/global-info";
import Button from "components/Button";
import ErrorMessage from "components/ErrorMessage";
import { useActionState } from "hooks";
import { GlobalInfoStore } from "stores";
const Protocol = () => {
const { protocolUrl, protocol } = GlobalInfoStore.useState();
const [protocolLoading, protocolLoadError] = useActionState(loadProtocol);
const [progressPercent, setProgressPercent] = useState(0);
const [paused, setPaused] = useState(false);
const forceLoad = useCallback(async () => {
try {
setPaused(true);
setProgressPercent(1);
await loadProtocol.run();
} finally {
setPaused(false);
}
}, [setPaused, setProgressPercent]);
const tick = useCallback(async () => {
if (paused) {
return;
}
if (progressPercent % 100 === 0) {
forceLoad();
} else {
setProgressPercent(progressPercent + 1);
}
}, [forceLoad, paused, progressPercent, setProgressPercent]);
useInterval(tick, 100, true);
const htmlContent = protocol
? {
__html: protocol,
}
: null;
const progressStyle = {
position: "absolute",
width: `${progressPercent}%`,
height: "100%",
left: "0",
background:
"linear-gradient(142deg, rgba(2,0,36,1) 0%, rgba(51,51,51,1) 0%, rgba(255,255,255,1) 100%)",
opacity: "0.4",
};
return (
<article className="container container--default py-8 lg:py-24">
<h1 className="head-alt-md lg:head-alt-lg mb-8">Zápis z jednání</h1>
<div className="flex items-start">
<div className="lg:w-2/3">
{!protocolUrl && (
<ErrorMessage>Zápis momentálně není k dispozici.</ErrorMessage>
)}
{protocolLoadError && (
<ErrorMessage>
Při stahování záznamu z jednání došlo k problému:{" "}
{protocolLoadError.toString()}
</ErrorMessage>
)}
{protocolUrl && <></>}
{htmlContent && (
<div
className="leading-tight text-sm lg:text-base content-block"
dangerouslySetInnerHTML={htmlContent}
></div>
)}
</div>
<div className="hidden lg:block card elevation-10 w-1/3">
<div className="lg:card__body content-block">
<h2>Jak to funguje?</h2>
<p>
Zápis se aktualizuje automaticky každých 10 sekund. Pokud chceš
aktualizaci vynutit ručně, můžeš využít tlačítko níže.
</p>
<Button
icon="ico--refresh"
loading={protocolLoading}
className="btn--fullwidth"
onClick={forceLoad}
color="black"
bodyProps={{
style: {
position: "relative",
},
}}
>
<span style={progressStyle}></span>
<span style={{ position: "relative" }}>
{protocolLoading ? "Aktualizace..." : "Aktualizovat"}
</span>
</Button>
</div>
</div>
</div>
</article>
);
};
export default Protocol;
...@@ -8,6 +8,8 @@ const globalInfoStoreInitial = { ...@@ -8,6 +8,8 @@ const globalInfoStoreInitial = {
onlineUsers: 0, onlineUsers: 0,
websocketUrl: null, websocketUrl: null,
streamUrl: null, streamUrl: null,
protocolUrl: null,
protocol: null,
}; };
export const GlobalInfoStore = new Store(globalInfoStoreInitial); export const GlobalInfoStore = new Store(globalInfoStoreInitial);
......
...@@ -4,6 +4,8 @@ declare namespace CF2021 { ...@@ -4,6 +4,8 @@ declare namespace CF2021 {
onlineUsers: number; onlineUsers: number;
websocketUrl: string; websocketUrl: string;
streamUrl?: string; streamUrl?: string;
protocolUrl?: string;
protocol?: string;
} }
interface ProgramScheduleEntry { interface ProgramScheduleEntry {
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment