From 2e2c327a10ba3537d914e04f0d0e1a2f43ac1faf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Niklas=20Schr=C3=B6tler?= <niklas@allround.digital> Date: Sat, 25 Nov 2023 16:32:37 +0100 Subject: [PATCH] Implemented new project structure This aims to improve the contribution experience by separating panels out as much as possible --- public/activeConfigs.json | 4 +-- public/config/default.json | 37 ++++++++++--------- public/config/foobar.json | 35 +++++++----------- public/schema/layout.json | 35 ++++++++++++++++++ src/App.tsx | 67 +++++++++++------------------------ src/hooks/useLayout.ts | 10 ++++-- src/index.css | 7 ++++ src/layout/HSplit.tsx | 26 -------------- src/layout/LayoutElement.tsx | 11 +++--- src/layout/VSplit.tsx | 26 -------------- src/meta/ScreenWrapper.tsx | 2 +- src/panels/_Panels.tsx | 38 ++++++++++++++++++++ src/services/LayoutService.ts | 24 ++++++------- src/types/LayoutConfig.ts | 38 ++++++++------------ 14 files changed, 176 insertions(+), 184 deletions(-) create mode 100644 public/schema/layout.json delete mode 100644 src/layout/HSplit.tsx delete mode 100644 src/layout/VSplit.tsx create mode 100644 src/panels/_Panels.tsx diff --git a/public/activeConfigs.json b/public/activeConfigs.json index e41f384..ff2e168 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 4b5dab5..feefa3a 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 8d222fe..6d50b2a 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 0000000..6c15cfd --- /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 a40e644..2c3ec2a 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 a1ce6d0..7c7ad62 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 b5c61c9..76927df 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 8a3cd2f..0000000 --- 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 8bc4765..59a4e43 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 af94e6b..0000000 --- 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 137ca10..78040b2 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 0000000..386d19f --- /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 0720b74..2b90cad 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 ca73c85..3508cce 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 } -- GitLab