Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • fix/sitzungstermin
  • main
  • marquee
  • master
  • punctuation-workaround
5 results

Target

Select target project
  • julianbohnenkaemper/infoscreen
  • acul/infoscreen-new
  • tudo-fsinfo/infoscreen/infoscreen
  • alexr/infoscreen
  • fabianvanrissenbeck/infoscreen
  • evysgarden/infoscreen
  • falk-pages/infoscreen
  • smmokerg/infoscreen
  • smmivog2/infoscreen-fussball
  • smmivog2/infoscreen-update-preisliste
10 results
Select Git revision
  • 1-issue-czi-wtf
  • master
  • update-deps
3 results
Show changes
Showing
with 248 additions and 120 deletions
File suppressed by a .gitattributes entry or the file's encoding is unsupported.
File added
File added
File added
File added
File added
File added
File added
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="https://static.fachschaften.org/icon/tight/color/fsorg-icon-color-tight.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000" />
<meta <meta
...@@ -24,7 +24,7 @@ ...@@ -24,7 +24,7 @@
work correctly both with client-side routing and a non-root public URL. work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>React App</title> <title>Infoscreen</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
......
{
"type": "object",
"properties": {
"schedule": {
"oneOf": [
{
"type": "object",
"properties": {
"always": {
"type": "boolean"
}
}
},
{
"type": "object",
"properties": {
"time": {
"type": "array",
"items": {
"from": {
"type": "string",
"format": "date-time"
},
"to": {
"type": "string",
"format": "date-time"
}
}
}
}
}
]
}
}
}
sonar.projectKey=Fachschaft-Informatik-Infoscreen
sonar.qualitygate.wait=true
import React from 'react'; import React from 'react';
import './App.css'; import './App.css';
import VSplit from "./layout/VSplit";
import HSplit from "./layout/HSplit";
import ScreenWrapper from "./meta/ScreenWrapper"; import ScreenWrapper from "./meta/ScreenWrapper";
import PanelWrapper from "./meta/PanelWrapper"; import useLayout from "./hooks/useLayout";
import PanelTitle from "./meta/PanelTitle"; import {PanelRenderers} from "./panels/_Panels";
import PanelContent from "./meta/PanelContent"; import LayoutElement from "./layout/LayoutElement";
import FahrplanPanel from "./panels/Fahrplan/FahrplanPanel"; import {ErrorBoundary} from "react-error-boundary";
import ErrorPanel from "./panels/Error/ErrorPanel";
function App() { function App() {
// const layout = useLayout(); const layout = useLayout();
// if(!layout) { if(!layout) {
// return ( return (
// <div> <div>
// Loading... Loading...
// </div> </div>
// ) )
// } }
return ( return (
<div className={"overflow-hidden w-screen h-screen bg-zinc-950 text-white"} style={{fontFamily: "Inter"}}> <div className={"overflow-hidden w-screen h-screen bg-zinc-950 text-white text-def"} style={{fontFamily: "Inter"}}>
{/*<LayoutElement config={layout} />*/} <ScreenWrapper backgroundImage={layout.background}>
<ScreenWrapper> {
<VSplit left={( layout.panels.map(panel => {
<HSplit top={( try {
<FahrplanPanel /> const Renderer = PanelRenderers[panel.type];
)} bottom={(
<PanelWrapper> if(!Renderer) {
<PanelTitle title={"Als nächstes im CZI"} /> console.error(`Rendering a panel of type ${panel.type} at (${panel.position.x}, ${panel.position.y}) failed. Config:`, panel.config)
<PanelContent> return (
<p>Next up</p> <LayoutElement config={panel}>
</PanelContent> <ErrorPanel
</PanelWrapper> message={`Unable to find a panel definition for type "${panel.type}"`}
)} split={.9}/> />
)} right={( </LayoutElement>
<HSplit top={( )
<PanelWrapper> }
<p>News</p>
</PanelWrapper> return (
)} bottom={( <LayoutElement config={panel}>
<HSplit top={( <ErrorBoundary fallback={
<PanelWrapper> <ErrorPanel
<PanelTitle title={"Mensa Öffnungszeiten"} /> message={`Panel of type ${panel.type} failed to render. Please check the config you provided.`}
<PanelContent> />
<p>Mensa Öffnungszeiten</p> }>
</PanelContent> <Renderer definition={panel.config} />
</PanelWrapper> </ErrorBoundary>
)} bottom={( </LayoutElement>
<PanelWrapper> )
<PanelTitle title={"Mensaplan Hauptmensa"} /> } catch (e) {
<PanelContent> return (
<p>Mensa Plan</p> <ErrorPanel/>
</PanelContent> );
</PanelWrapper> }
)} split={.4}/> })
)} split={.9}/> }
)} split={.35}/>
</ScreenWrapper> </ScreenWrapper>
</div> </div>
); );
......
import {useEffect, useState} from "react"; import {useEffect, useRef, useState} from "react";
import {LayoutConfig} from "../types/LayoutConfig"; import {LayoutConfig} from "../types/LayoutConfig";
import {LayoutService} from "../services/LayoutService"; import {LayoutService} from "../services/LayoutService";
export default function useLayout(): LayoutConfig | null { export default function useLayout(): LayoutConfig | null {
const currentId = useRef<string | null>(null);
const [layout, setLayout] = useState<LayoutConfig | null>(null); const [layout, setLayout] = useState<LayoutConfig | null>(null);
useEffect(() => { useEffect(() => {
...@@ -10,7 +11,9 @@ export default function useLayout(): LayoutConfig | null { ...@@ -10,7 +11,9 @@ export default function useLayout(): LayoutConfig | null {
const refresh = () => { const refresh = () => {
const activeLayout = LayoutService.getActiveLayout(); const activeLayout = LayoutService.getActiveLayout();
if(layout?.id !== activeLayout.id) { if(currentId.current !== activeLayout.id) {
console.log("Switching from", currentId.current, "to", activeLayout.id)
currentId.current = activeLayout.id;
setLayout(activeLayout); setLayout(activeLayout);
} }
}; };
......
@tailwind base; @tailwind base;
@tailwind components; @tailwind components;
@tailwind utilities; @tailwind utilities;
.layout-grid {
display: grid;
grid-template-columns: repeat(auto-fill, 45px);
grid-template-rows: repeat(auto-fill, 45px);
grid-gap: 30px;
}
.bg-lower-gradient {
background-image: linear-gradient(180deg, rgba(24, 24, 27, 0) 0%, rgba(24, 24, 27, 0.70) 60%, rgba(24, 24, 27, 1.00) 100%)
}
* {
cursor: none;
}
.progress-bar {
justify-content: center;
border-radius: 10px;
padding: 0 5px;
display: flex;
height: 10px;
width: 100%;;
}
.progress-bar-fill {
border-radius: 10px;
height: 10px;
width: 0;
animation-timing-function: linear;
}
.progress-bar-animate {
animation-name: progress-bar-animation;
}
@keyframes progress-bar-animation {
0% { width: 0; }
100% { width: 100%; }
}
/* inter-200 - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Inter';
font-style: normal;
font-weight: 200;
src: url('./../public/fonts/inter/inter-v13-latin-200.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* inter-300 - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Inter';
font-style: normal;
font-weight: 300;
src: url('./../public/fonts/inter/inter-v13-latin-300.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* inter-regular - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Inter';
font-style: normal;
font-weight: 400;
src: url('./../public/fonts/inter/inter-v13-latin-regular.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* inter-500 - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Inter';
font-style: normal;
font-weight: 500;
src: url('./../public/fonts/inter/inter-v13-latin-500.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* inter-600 - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Inter';
font-style: normal;
font-weight: 600;
src: url('./../public/fonts/inter/inter-v13-latin-600.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* inter-700 - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Inter';
font-style: normal;
font-weight: 700;
src: url('./../public/fonts/inter/inter-v13-latin-700.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
/* inter-800 - latin_latin-ext */
@font-face {
font-display: swap; /* Check https://developer.mozilla.org/en-US/docs/Web/CSS/@font-face/font-display for other options. */
font-family: 'Inter';
font-style: normal;
font-weight: 800;
src: url('./../public/fonts/inter/inter-v13-latin-800.woff2') format('woff2'); /* Chrome 36+, Opera 23+, Firefox 39+, Safari 12+, iOS 10+ */
}
import React from 'react';
const HSplit = (props: {
top: any,
bottom: any,
split?: number
}) => {
const split = props.split ?? .5;
if(split <= 0 || split >= 1) {
throw new Error("HSplit has an invalid split");
}
return (
<div className={"flex flex-col w-full h-full gap-6"}>
<div style={{height: `${split * 100}%`}}>
{props.top}
</div>
<div className={"flex-1"}>
{props.bottom}
</div>
</div>
);
};
export default HSplit;
import React from 'react'; import React from 'react';
import {Layout} from "../types/LayoutConfig"; import {PanelDefinition} from "../types/LayoutConfig";
import config from "tailwindcss/defaultConfig";
const LayoutElement = (props: {config: Layout}) => { const LayoutElement = (props: {children: any, config: PanelDefinition<any>}) => {
// switch (config.type)s return (
<div
style={{
gridColumnStart: props.config.position.x + 1,
gridColumnEnd: props.config.position.x + props.config.position.w + 1,
gridRowStart: props.config.position.y + 1,
gridRowEnd: props.config.position.y + props.config.position.h + 1
}}
>
{props.children}
</div>
);
}; };
export default LayoutElement; export default LayoutElement;
import React from 'react';
const VSplit = (props: {
left: any,
right: any,
split?: number
}) => {
const split = props.split ?? .5;
if(split <= 0 || split >= 1) {
throw new Error("VSplit has an invalid split");
}
return (
<div className={"flex flex-row w-full h-full gap-6"}>
<div style={{width: `${split * 100}%`}}>
{props.left}
</div>
<div className={"flex-1"}>
{props.right}
</div>
</div>
);
};
export default VSplit;
import React from 'react'; import React from 'react';
import classNames from "../util/classNames";
const PanelContent = (props: {padding?: boolean, children: any}) => { const PanelContent = (props: {padding?: boolean, className?: string, children: any}) => {
return ( return (
<div className={"flex-1 " + ((props.padding ?? true) ? "px-5 pb-4" : "")}> <div className={classNames(
"flex-1",
(props.padding ?? true) ? "px-6 pb-5" : "",
props.className
)}>
{props.children} {props.children}
</div> </div>
); );
......
import React from 'react'; import React from "react";
const PanelTitle = (props: {title: string}) => { const PanelTitle = (props: { title: string; info?: string }) => {
return ( return (
<div className={"px-5 py-4 text-zinc-400"}> <div
<h2>{props.title}</h2> className={
</div> "px-6 pt-4 pb-2 text-zinc-400 flex flex-row justify-between"
); }
>
<h2>{props.title}</h2>
{props.info && <p>{props.info}</p>}
</div>
);
}; };
export default PanelTitle; export default PanelTitle;
const ProgressBar = (props: { duration: number, idName: string, callbackFunction: () => void }) => {
const runCallback = () => {
props.callbackFunction();
const el = document.getElementById(props.idName);
if (el) {
el.classList.remove('progress-bar-animate');
void el.offsetWidth;
el.classList.add('progress-bar-animate');
}
}
return (
<div className="progress-bar">
<div
id={props.idName}
className={'progress-bar-fill progress-bar-animate bg-zinc-700'}
style={{ animationDuration: `${props.duration}ms` }}
onAnimationEnd={() => runCallback()}
/>
</div>
);
};
export default ProgressBar;