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 249 additions and 121 deletions
File added
File added
File added
File added
File added
File added
File added
......@@ -2,7 +2,7 @@
<html lang="en">
<head>
<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="theme-color" content="#000000" />
<meta
......@@ -24,7 +24,7 @@
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`.
-->
<title>React App</title>
<title>Infoscreen</title>
</head>
<body>
<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 './App.css';
import VSplit from "./layout/VSplit";
import HSplit from "./layout/HSplit";
import ScreenWrapper from "./meta/ScreenWrapper";
import PanelWrapper from "./meta/PanelWrapper";
import PanelTitle from "./meta/PanelTitle";
import PanelContent from "./meta/PanelContent";
import FahrplanPanel from "./panels/Fahrplan/FahrplanPanel";
import useLayout from "./hooks/useLayout";
import {PanelRenderers} from "./panels/_Panels";
import LayoutElement from "./layout/LayoutElement";
import {ErrorBoundary} from "react-error-boundary";
import ErrorPanel from "./panels/Error/ErrorPanel";
function App() {
// const layout = useLayout();
const layout = useLayout();
// if(!layout) {
// return (
// <div>
// Loading...
// </div>
// )
// }
if(!layout) {
return (
<div>
Loading...
</div>
)
}
return (
<div className={"overflow-hidden w-screen h-screen bg-zinc-950 text-white"} style={{fontFamily: "Inter"}}>
{/*<LayoutElement config={layout} />*/}
<ScreenWrapper>
<VSplit left={(
<HSplit top={(
<FahrplanPanel />
)} bottom={(
<PanelWrapper>
<PanelTitle title={"Als nächstes im CZI"} />
<PanelContent>
<p>Next up</p>
</PanelContent>
</PanelWrapper>
)} split={.9}/>
)} right={(
<HSplit top={(
<PanelWrapper>
<p>News</p>
</PanelWrapper>
)} bottom={(
<HSplit top={(
<PanelWrapper>
<PanelTitle title={"Mensa Öffnungszeiten"} />
<PanelContent>
<p>Mensa Öffnungszeiten</p>
</PanelContent>
</PanelWrapper>
)} bottom={(
<PanelWrapper>
<PanelTitle title={"Mensaplan Hauptmensa"} />
<PanelContent>
<p>Mensa Plan</p>
</PanelContent>
</PanelWrapper>
)} split={.4}/>
)} split={.9}/>
)} split={.35}/>
<div className={"overflow-hidden w-screen h-screen bg-zinc-950 text-white text-def"} style={{fontFamily: "Inter"}}>
<ScreenWrapper backgroundImage={layout.background}>
{
layout.panels.map(panel => {
try {
const Renderer = PanelRenderers[panel.type];
if(!Renderer) {
console.error(`Rendering a panel of type ${panel.type} at (${panel.position.x}, ${panel.position.y}) failed. Config:`, panel.config)
return (
<LayoutElement config={panel}>
<ErrorPanel
message={`Unable to find a panel definition for type "${panel.type}"`}
/>
</LayoutElement>
)
}
return (
<LayoutElement config={panel}>
<ErrorBoundary fallback={
<ErrorPanel
message={`Panel of type ${panel.type} failed to render. Please check the config you provided.`}
/>
}>
<Renderer definition={panel.config} />
</ErrorBoundary>
</LayoutElement>
)
} catch (e) {
return (
<ErrorPanel/>
);
}
})
}
</ScreenWrapper>
</div>
);
......
import {useEffect, useState} from "react";
import {useEffect, useRef, useState} from "react";
import {LayoutConfig} from "../types/LayoutConfig";
import {LayoutService} from "../services/LayoutService";
export default function useLayout(): LayoutConfig | null {
const currentId = useRef<string | null>(null);
const [layout, setLayout] = useState<LayoutConfig | null>(null);
useEffect(() => {
......@@ -10,7 +11,9 @@ export default function useLayout(): LayoutConfig | null {
const refresh = () => {
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);
}
};
......
@tailwind base;
@tailwind components;
@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 {Layout} from "../types/LayoutConfig";
import config from "tailwindcss/defaultConfig";
import {PanelDefinition} from "../types/LayoutConfig";
const LayoutElement = (props: {config: Layout}) => {
// switch (config.type)s
const LayoutElement = (props: {children: any, config: PanelDefinition<any>}) => {
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;
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 classNames from "../util/classNames";
const PanelContent = (props: {padding?: boolean, children: any}) => {
const PanelContent = (props: {padding?: boolean, className?: string, children: any}) => {
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}
</div>
);
......
import React from 'react';
import React from "react";
const PanelTitle = (props: {title: string}) => {
return (
<div className={"px-5 py-4 text-zinc-400"}>
<h2>{props.title}</h2>
</div>
);
const PanelTitle = (props: { title: string; info?: string }) => {
return (
<div
className={
"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;
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;
......@@ -28,7 +28,7 @@ const ScreenBar = () => {
}, [])
return (
<div className={"flex flex-row justify-between px-8 py-6"}>
<div className={"flex flex-row justify-between px-8 py-6 bg-zinc-950 bg-opacity-70"}>
<p>{date}</p>
<p>{time}</p>
</div>
......