diff --git a/public/activeConfigs.json b/public/activeConfigs.json index e41f3842363d5d08665d490c5293d62a986e3bec..ff2e1686554fd46e6add52275a7946190f0c7dbb 100644 --- a/public/activeConfigs.json +++ b/public/activeConfigs.json @@ -1,4 +1,4 @@ [ - "config/default.json", - "config/foobar.json" + "config/foobar.json", + "config/default.json" ] diff --git a/public/config/default.json b/public/config/default.json index 4b5dab5bf149958b2081b2813e30481cf0a65bc9..feefa3a9443db5ddb20f444c04737316cb6f81f6 100644 --- a/public/config/default.json +++ b/public/config/default.json @@ -1,27 +1,26 @@ { - "priority": 0, + "id": "default", "schedule": { "always": true }, - "layout": { - "type": "xsplit", - "cut": 35, - "left": { - "type": "panel", - "name": "departure", - "config": "oh14" + "panels": [ + { + "type": "test", + "position": { + "x": 0, + "y": 0, + "w": 2, + "h": 2 + } }, - "right": { - "type": "ysplit", - "cut": 30, - "up": { - "type": "panel", - "name": "clock" - }, - "down": { - "type": "panel", - "name": "mensaPlan" + { + "type": "demo", + "position": { + "x": 0, + "y": 0, + "w": 2, + "h": 2 } } - } + ] } diff --git a/public/config/foobar.json b/public/config/foobar.json index 8d222fed87a5d1e4b981f78e863bcfd807070dd8..6d50b2a69636cd2baf601c7906de00aab36477f8 100644 --- a/public/config/foobar.json +++ b/public/config/foobar.json @@ -1,31 +1,22 @@ { - "priority": 1, + "id": "foobar", "schedule": { - "time": [ + "times": [ { - "from": "26.06.2023" + "from": "2023-11-25T16:26", + "to": "2023-11-25T16:27" } ] }, - "layout": { - "type": "xsplit", - "cut": 35, - "left": { - "type": "panel", - "name": "departure", - "config": "oh14" - }, - "right": { - "type": "ysplit", - "cut": 30, - "up": { - "type": "panel", - "name": "clock" - }, - "down": { - "type": "panel", - "name": "mensaPlan" + "panels": [ + { + "type": "foobar", + "position": { + "x": 0, + "y": 0, + "w": 2, + "h": 2 } } - } + ] } diff --git a/public/schema/layout.json b/public/schema/layout.json new file mode 100644 index 0000000000000000000000000000000000000000..6c15cfddaa0b3bff75a0ead0a8e0472949fe077c --- /dev/null +++ b/public/schema/layout.json @@ -0,0 +1,35 @@ +{ + "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" + } + } + } + } + } + ] + } + } +} diff --git a/src/App.tsx b/src/App.tsx index a40e6444d05bf9b3713e83a8f5524dc6dfb0503f..2c3ec2a675a3561b15042596d63724e74ad33240 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,62 +1,37 @@ 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 LayoutElement from "./layout/LayoutElement"; +import useLayout from "./hooks/useLayout"; +import {PanelRenderers} from "./panels/_Panels"; 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} />*/} + <div className={"overflow-hidden w-screen h-screen bg-zinc-950 text-white"} style={{fontFamily: "Inter"}}> <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}/> + { + layout.panels.map(panel => { + const Renderer = PanelRenderers[panel.type]; + + return ( + <Renderer definition={panel.config} /> + ) + }) + } </ScreenWrapper> </div> ); diff --git a/src/hooks/useLayout.ts b/src/hooks/useLayout.ts index a1ce6d0362fa2004dae7fe182272a0d31f6c0bf3..7c7ad62ad2d4dc8646f37300f154d90d67c1ed32 100644 --- a/src/hooks/useLayout.ts +++ b/src/hooks/useLayout.ts @@ -1,16 +1,22 @@ -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(() => { LayoutService.init().then(() => { const refresh = () => { + console.log("Resync") const activeLayout = LayoutService.getActiveLayout(); - if(layout?.id !== activeLayout.id) { + console.log("Determined", activeLayout.id); + + if(currentId.current !== activeLayout.id) { + console.log("Switching from", currentId.current, "to", activeLayout.id) + currentId.current = activeLayout.id; setLayout(activeLayout); } }; diff --git a/src/index.css b/src/index.css index b5c61c956711f981a41e95f7fcf0038436cfbb22..76927df400fec01dbecac51e5c7a22eaec3c337d 100644 --- a/src/index.css +++ b/src/index.css @@ -1,3 +1,10 @@ @tailwind base; @tailwind components; @tailwind utilities; + +.layout-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(max(45px, 45px), 1fr)); + grid-template-rows: repeat(auto-fill, minmax(max(45px, 45px), 1fr)); + grid-gap: 30px; +} diff --git a/src/layout/HSplit.tsx b/src/layout/HSplit.tsx deleted file mode 100644 index 8a3cd2f8d2944fcd7b16b074dfd6eec653df0748..0000000000000000000000000000000000000000 --- a/src/layout/HSplit.tsx +++ /dev/null @@ -1,26 +0,0 @@ -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; diff --git a/src/layout/LayoutElement.tsx b/src/layout/LayoutElement.tsx index 8bc4765d84ef7efc22ebb9ac1926bcb61d60cc0d..59a4e431632611bbe54335fdd60dce4527e1ff9f 100644 --- a/src/layout/LayoutElement.tsx +++ b/src/layout/LayoutElement.tsx @@ -1,9 +1,12 @@ import React from 'react'; -import {Layout} from "../types/LayoutConfig"; -import config from "tailwindcss/defaultConfig"; -const LayoutElement = (props: {config: Layout}) => { - // switch (config.type)s +const LayoutElement = (props: {children: any}) => { + // const LayoutElement = (props: {config: Layout}) => { + return ( + <div className={"absolute"} style={{gridRowStart: 5, gridRowEnd: "span 2", gridColumnStart: 2, gridColumnEnd: "span 5"}}> + {props.children} + </div> + ); }; export default LayoutElement; diff --git a/src/layout/VSplit.tsx b/src/layout/VSplit.tsx deleted file mode 100644 index af94e6bed41acfbe252960429b7f335603f6510b..0000000000000000000000000000000000000000 --- a/src/layout/VSplit.tsx +++ /dev/null @@ -1,26 +0,0 @@ -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; diff --git a/src/meta/ScreenWrapper.tsx b/src/meta/ScreenWrapper.tsx index 137ca10ffd8d082e7a5dcb1e722ba6ad89da6333..78040b2b67ab3cc5b101c6a966080a4281d3fbdf 100644 --- a/src/meta/ScreenWrapper.tsx +++ b/src/meta/ScreenWrapper.tsx @@ -5,7 +5,7 @@ const ScreenWrapper = (props: {children: any}) => { return ( <div className={"h-full w-full flex flex-col"} style={{backgroundImage: "/logo512.png"}}> <ScreenBar /> - <div className={"flex-1 px-6 pb-6"}> + <div className={"layout-grid flex-1 px-8 pb-10"}> {props.children} </div> </div> diff --git a/src/panels/_Panels.tsx b/src/panels/_Panels.tsx new file mode 100644 index 0000000000000000000000000000000000000000..386d19f1218e5a05385b50c2391a2488d080b0df --- /dev/null +++ b/src/panels/_Panels.tsx @@ -0,0 +1,38 @@ +/* + * Hello dear future contributor, + * if you desire to create a panel, this file is for you. Here, you'll need to register your panel both with the + * typings system and with the rendering system. + */ +import React from "react"; +import FahrplanPanel from "./Fahrplan/FahrplanPanel"; +import {PanelDefinition} from "../types/LayoutConfig"; + +/* + * First, please claim a unique id for your panel here. Convention is that it is all lowercase, in snake-case to be + * precise. So if you want to call your panel "My awesome Panel", please claim "my-awesome-panel". Add it by adding + * `| "my-awesome-panel"` before the semicolon in the type below this comment. + */ +export type PanelTypes = "fahrplan"; + +/* + * Next, add your renderer. You'll get the definition that was written in the layout config as a prop. If you'd like to + * provide custom settings, you may add an object with these settings as the generic into the PanelDefinition. + * It will then be available as `definition.config`. + */ +export const PanelRenderers: {[panelType: string]: React.FC<any & {definition: PanelDefinition<any>}>} = { + "fahrplan": FahrplanPanel, + "test": () => ( + <h1>Test!</h1> + ), + "demo": () => ( + <p>Demo!</p> + ), + "foobar": () => ( + <p>FooBar!</p> + ) +}; + + +/* + * That should have been it. Now, have fun implementing your renderer! + */ diff --git a/src/services/LayoutService.ts b/src/services/LayoutService.ts index 0720b747c895ab46e60aa185f9d0ab7f3f42fc69..2b90cad305f707c48d656b7f6a33e6a20948ee2f 100644 --- a/src/services/LayoutService.ts +++ b/src/services/LayoutService.ts @@ -1,5 +1,13 @@ import {LayoutConfig} from "../types/LayoutConfig"; +const NO_LAYOUT_CONFIG: LayoutConfig = { + id: "", + schedule: { + always: true + }, + panels: [] +} + export class LayoutService { static configs: LayoutConfig[] = []; @@ -25,22 +33,14 @@ export class LayoutService { return config.schedule.times.reduce((accu, curr) => { if(accu) return true; - return (curr.from <= now && curr.to >= now); + return (new Date(curr.from) <= now && new Date(curr.to) >= now); }, false); }); + console.log(activeConfigs) + /* ToDo: This is not great, as it assumes there is always an active layout. If you don't configure this correctly, consider yourself warned now and don't blame me */ - return activeConfigs.reduce((accu, curr) => { - if(!accu) { - return curr; - } - - if(accu.priority < curr.priority) { - return curr; - } - - return accu; - }) + return activeConfigs.at(0) ?? NO_LAYOUT_CONFIG; } } diff --git a/src/types/LayoutConfig.ts b/src/types/LayoutConfig.ts index ca73c85bec07390ef82662f30cd4c1a581d37ab9..3508cce007a2dff5008ed263d19b694a2d32545a 100644 --- a/src/types/LayoutConfig.ts +++ b/src/types/LayoutConfig.ts @@ -1,8 +1,9 @@ +import {PanelTypes} from "../panels/_Panels"; + export type LayoutConfig = { id: string, - priority: number, schedule: LayoutSchedule, - layout: Layout + panels: PanelDefinition<any>[] } type LayoutSchedule = { @@ -10,29 +11,18 @@ type LayoutSchedule = { } | { always?: false, times: { - from: Date, - to: Date + from: string, + to: string }[] } -export type Layout = XSplitLayout | YSplitLayout | Panel; - -type XSplitLayout = { - type: "xsplit", - cut: number, - left: Layout, - right: Layout -} - -type YSplitLayout = { - type: "ysplit", - cut: number, - left: Layout, - right: Layout -} - -interface Panel { - type: "panel", - name: string, - config: Object +export type PanelDefinition<T> = { + type: PanelTypes, + position: { + x: number, + y: number, + w: number, + h: number + }, + config: T }