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