Procházet zdrojové kódy

ui updates and dark theme

Timothy Pomeroy před 2 roky
rodič
revize
71cca519ce

+ 2 - 44
app/api/automation/[id]/route.js

@@ -1,43 +1,13 @@
-import { pick } from "lodash";
 import { NextResponse } from "next/server";
-import { WLEDClient } from "wled-client";
 
 import automation from "data/automation.json";
 
 const get = (url) => {
-  // console.log("get", url);
+  console.log("get", url);
   return fetch(url, { cache: "no-store" })
     .then((resp) => resp && resp.text())
     .then((result) => result);
 };
-const post = (url, value) => {
-  // console.log("post", url, JSON.stringify(value));
-  return fetch(url, {
-    method: "POST",
-    cache: "no-store",
-    headers: {
-      Accept: "application/json",
-      "Content-Type": "application/json"
-    },
-    body: JSON.stringify(value)
-  })
-    .then((resp) => resp && resp.json())
-    .then((result) => result);
-};
-
-// const update = async (client, { ps, ...value }) => {
-//   console.log("update", client, JSON.stringify(value));
-//   let url = `http://${client}/json/state`;
-//   if (ps) {
-//     await post(url, { ps: ps, v: true });
-//     await sleep(1000);
-//   }
-//   let resp = await post(url, { ...value, v: true });
-//   // console.log("->", "resp", resp);
-//   let result = pick(resp, ["ps", ...Object.keys(value)]);
-//   console.log("->", result);
-//   return result;
-// };
 
 const update = ({ id, client, on, bri, ...rest }) => {
   let url = `http://${client}/win`;
@@ -46,7 +16,7 @@ const update = ({ id, client, on, bri, ...rest }) => {
   else url += `&T=0`;
   if (bri) url += `&A=${bri}`;
   return get(url)
-    .then((_) => ({
+    .then((resp) => ({
       ps: id,
       on: on,
       bri: bri
@@ -54,18 +24,6 @@ const update = ({ id, client, on, bri, ...rest }) => {
     .catch((err) => ({ error: err.message }));
 };
 
-const action = async ({ id, client, on, ...rest }) => {
-  let wled = new WLEDClient(client); // setup a connection to the client
-  // await wled.init(); // init the connection
-  await wled.setPreset(id); // set the preset
-  if (on) await wled.turnOn(); // turn off the lights
-  else await wled.turnOff(); // turn off the lights
-  await wled.refreshState();
-  return {
-    ...pick(wled?.state || {}, ["on", "brightness", "presetId"])
-  };
-};
-
 export async function GET(req, { params }) {
   let promises = [];
   let id = params?.id || null;

+ 1 - 1
app/api/power/[id]/route.js

@@ -45,7 +45,7 @@ export async function GET(req, { params }) {
   }
   return Promise.allSettled(promises)
     .then((results) => {
-      console.log("results", results);
+      // console.log("results", results);
       return NextResponse.json(results?.map((o) => o?.value));
     })
     .catch((err) => {

+ 41 - 0
app/api/preset/[id]/route.js

@@ -0,0 +1,41 @@
+import { NextResponse } from "next/server";
+
+import automation from "data/automation.json";
+
+const get = (url) => {
+  return fetch(url, { cache: "no-store" })
+    .then((resp) => resp && resp.text())
+    .then((result) => result);
+};
+
+const update = ({ id, client, ...rest }) => {
+  let url = `http://${client}/win`;
+  if (id) url += `&PL=${id}`;
+  return get(url)
+    .then((resp) => ({
+      ps: id
+    }))
+    .catch((err) => ({ error: err.message }));
+};
+
+export async function GET(req, { params }) {
+  let promises = [];
+  let id = params?.id || null;
+  try {
+    if (!id) throw new Error("No id specified");
+    let clients = (automation?.[1] && Object.keys(automation?.[1])) || [];
+    if (!clients) throw new Error("No clients for id", id);
+    for (let client of clients) {
+      promises.push(update({ id, client }));
+    }
+  } catch (err) {
+    return NextResponse.json({ error: err?.message }, { status: 500 });
+  }
+  return Promise.allSettled(promises)
+    .then((results) => {
+      return NextResponse.json(results?.map((o) => o?.value));
+    })
+    .catch((err) => {
+      return NextResponse.json({ error: err?.message }, { status: 500 });
+    });
+}

+ 47 - 0
app/api/state/route.js

@@ -0,0 +1,47 @@
+import { NextResponse } from "next/server";
+
+import automation from "data/automation.json";
+
+import { pick } from "lib/utils";
+
+const get = (url) => {
+  return fetch(url, {
+    headers: { "Content-Type": "application/json" },
+    redirect: "follow",
+    cache: "no-store",
+    mode: "cors"
+  })
+    .then((resp) => resp && resp.json())
+    .then((result) => result);
+};
+
+const status = ({ client }) => {
+  let url = `http://${client}/json/state`;
+  return get(url)
+    .then((resp) => resp?.state || resp || {})
+    .then((state) => pick(state, ["on", "bri", "ps", "pl", "udpn"]))
+    .then((state) => ({ ...state, client }))
+    .catch((err) => ({ error: err.message }));
+};
+
+export async function GET(req, { params }) {
+  let promises = [];
+  let id = 1;
+  try {
+    if (!id) throw new Error("No id specified");
+    let clients = (automation?.[id] && Object.keys(automation?.[id])) || [];
+    if (!clients) throw new Error("No clients for id", id);
+    for (let client of clients) {
+      promises.push(status({ id, client, ...automation?.[id]?.[client] }));
+    }
+  } catch (err) {
+    return NextResponse.json({ error: err?.message }, { status: 500 });
+  }
+  return Promise.allSettled(promises)
+    .then((results) => {
+      return NextResponse.json(results?.map((o) => o?.value));
+    })
+    .catch((err) => {
+      return NextResponse.json({ error: err?.message }, { status: 500 });
+    });
+}

+ 184 - 4
app/globals.scss

@@ -1,6 +1,7 @@
 $prefix: "ty" !default;
 
-// Color
+$hawkband-color: #001529;
+
 $primary-color: #002e5d !default;
 
 $white-color: #fff !default;
@@ -29,17 +30,83 @@ $magenta-color: #eb2f96 !default;
 // @import "~tiny-ui/dist/styles/index.min.css";
 @import "~tiny-ui/components/style/index.scss";
 
+.gradient-animation {
+  background: linear-gradient(270deg, #0d6efd, #6f42c1);
+  background-size: 400% 400%;
+  -webkit-animation: gradient 30s ease infinite;
+  -moz-animation: gradient 30s ease infinite;
+  animation: gradient 30s ease infinite;
+}
+
+@-webkit-keyframes gradient {
+  0% {
+    background-position: 0% 50%;
+  }
+  50% {
+    background-position: 100% 50%;
+  }
+  100% {
+    background-position: 0% 50%;
+  }
+}
+@-moz-keyframes gradient {
+  0% {
+    background-position: 0% 50%;
+  }
+  50% {
+    background-position: 100% 50%;
+  }
+  100% {
+    background-position: 0% 50%;
+  }
+}
+@keyframes gradient {
+  0% {
+    background-position: 0% 50%;
+  }
+  50% {
+    background-position: 100% 50%;
+  }
+  100% {
+    background-position: 0% 50%;
+  }
+}
+
 body {
-  background: $gray-500;
+  background: $hawkband-color;
+  color: $white-color;
+
   .ty-layout-header {
     height: auto;
+    &:after {
+      content: " ";
+      display: block;
+      height: 3px;
+      width: 100%;
+      background: linear-gradient(270deg, #0d6efd, #6f42c1);
+      background-size: 400% 400%;
+      -webkit-animation: gradient 18s ease infinite;
+      -moz-animation: gradient 18s ease infinite;
+      animation: gradient 18s ease infinite;
+    }
   }
+
   .ty-layout-content {
-    background-color: $gray-500;
+    background-color: $hawkband-color !important;
+    .ty-row {
+      background-color: transparent !important;
+      color: $white-color;
+    }
   }
+
   .ty-layout-footer {
-    background-color: $gray-500;
+    background: linear-gradient(270deg, #0d6efd, #6f42c1);
+    background-size: 400% 400%;
+    -webkit-animation: gradient 30s ease infinite;
+    -moz-animation: gradient 30s ease infinite;
+    animation: gradient 30s ease infinite;
   }
+
   .ty-menu_dark {
     .ty-menu-item {
       color: $white-color;
@@ -48,9 +115,122 @@ body {
       }
     }
   }
+
   .ty-steps-item.ty-steps-item_label-vertical {
     .ty-steps-item__content {
       width: 100%;
     }
   }
+  .ty-steps {
+    .ty-steps-item__title,
+    .ty-steps-item__desc {
+      color: $white-color;
+    }
+    .ty-steps-item_wait {
+      .ty-steps-item__icon {
+        color: rgba(0, 0, 0, 0.75);
+      }
+      .ty-steps-item__title,
+      .ty-steps-item__desc {
+        color: rgba(255, 255, 255, 0.45);
+      }
+    }
+    .ty-steps-item_process {
+      .ty-steps-item__icon {
+        background-color: $blue-color !important;
+        color: $white-color !important;
+      }
+    }
+  }
+
+  .ty-collapse_borderless {
+    background-color: $hawkband-color;
+    color: $white-color;
+    .ty-collapse-item {
+      border-bottom: 1px solid $blue-color;
+      color: inherit;
+      .ty-collapse-item__header {
+        &:hover {
+          background-color: $blue-color;
+        }
+        color: inherit;
+      }
+      .ty-collapse-item__content {
+        border-top: 1px solid $blue-color;
+        background-color: inherit;
+        color: inherit;
+      }
+    }
+  }
+
+  .ty-btn_default {
+    color: $white-color;
+    background-color: $blue-color;
+    border-color: rgba(255, 255, 255, 0.9);
+    &:hover {
+      color: $white-color;
+      border-color: rgba(255, 255, 255, 0.75);
+      background-color: rgba(13, 110, 253, 0.75);
+    }
+    &:active {
+      color: $white-color;
+      border-color: rgba(255, 255, 255, 0.5);
+      background-color: rgba(13, 110, 253, 0.5);
+    }
+    &:focus {
+      color: $white-color;
+      border-color: rgba(255, 255, 255, 0.75);
+      background-color: $purple-color;
+    }
+  }
+
+  .ty-overlay {
+    .ty-modal {
+      .ty-modal__body {
+        color: #000;
+        .ty-transfer {
+          .ty-transfer-panel {
+            width: 210px;
+          }
+        }
+      }
+    }
+  }
+
+  .commandbar {
+    display: block;
+    position: sticky;
+    top: calc(3.5rem + 2px);
+    padding: 0.5rem 0 0.5rem 0;
+    margin-bottom: 1rem;
+    background-image: linear-gradient(
+      to bottom,
+      rgba(0, 21, 41, 1),
+      rgba(0, 21, 41, 0)
+    );
+    text-align: center;
+  }
+
+  .notification {
+    margin: 0 0 1rem 0;
+  }
+
+  @media only screen and (max-width: 551px) {
+    .ty-notification-container {
+      font-size: 0.7rem;
+      width: 250px;
+      .ty-notification {
+        font-size: inherit;
+        .ty-notification__title {
+          font-size: 1rem;
+        }
+        .ty-tag {
+          font-size: inherit;
+        }
+      }
+    }
+    .notification {
+      display: none;
+    }
+  }
 }

+ 2 - 2
app/layout.js

@@ -5,13 +5,13 @@ const inter = Inter({ subsets: ["latin"] });
 
 export const metadata = {
   title: "Hawkband WLED",
-  description: "Hawkband WLED command and control",
+  description: "Hawkband WLED command and control"
 };
 
 export default function RootLayout({ children }) {
   return (
     <html lang="en">
-      <body className={inter.className}>{children}</body>
+      <body className={inter?.className}>{children}</body>
     </html>
   );
 }

+ 43 - 5
app/page.js

@@ -9,6 +9,8 @@ import automation from "data/automation.json";
 import config from "data/config.json";
 import presets from "data/presets.json";
 
+import { isFunction } from "lib/utils";
+
 export default function Home() {
   const [names, setNames] = useState(null);
   const [clients, setClients] = useState(null);
@@ -25,7 +27,40 @@ export default function Home() {
     setNames(n);
   }, []);
 
-  const handleAutomation = (id) => {
+  const handlePreset = (id, cb) => {
+    setBusy(true);
+    fetch(`/api/preset/${id}`)
+      .then((resp) => resp && resp.json())
+      .then((result) => {
+        setBusy(false);
+        Notification({
+          title: `Preset set to ${id}`,
+          description: (
+            <>
+              {result?.map((o, i) => (
+                <div key={i} className="notification">
+                  <strong>
+                    {names[i]} ({clients[i]})
+                  </strong>
+                  {o &&
+                    Object.keys(o)?.map((v, k) => (
+                      <Tag key={k}>
+                        <strong>{v}: </strong> {JSON.stringify(o[v])}
+                      </Tag>
+                    ))}
+                </div>
+              ))}
+            </>
+          )
+        });
+        if (isFunction(cb)) cb();
+      })
+      .catch((err) => {
+        setBusy(false);
+      });
+  };
+
+  const handleAutomation = (id, cb) => {
     setBusy(true);
     fetch(`/api/automation/${id}`)
       .then((resp) => resp && resp.json())
@@ -36,7 +71,7 @@ export default function Home() {
           description: (
             <>
               {result?.map((o, i) => (
-                <div key={i} style={{ margin: "0 0 1rem 0" }}>
+                <div key={i} className="notification">
                   <strong>
                     {names[i]} ({clients[i]})
                   </strong>
@@ -51,13 +86,14 @@ export default function Home() {
             </>
           )
         });
+        if (isFunction(cb)) cb();
       })
       .catch((err) => {
         setBusy(false);
       });
   };
 
-  const handlePower = (value) => {
+  const handlePower = (value, cb) => {
     setBusy(true);
     fetch(`/api/power/${value}`)
       .then((resp) => resp && resp.json())
@@ -68,7 +104,7 @@ export default function Home() {
           description: (
             <>
               {result?.map((o, i) => (
-                <div key={i} style={{ margin: "0 0 1rem 0" }}>
+                <div key={i} className="notification">
                   <strong>
                     {names[i]} ({clients[i]})
                   </strong>
@@ -83,6 +119,7 @@ export default function Home() {
             </>
           )
         });
+        if (isFunction(cb)) cb();
       })
       .catch((err) => {
         setBusy(false);
@@ -91,7 +128,7 @@ export default function Home() {
 
   return (
     <Layout page="home">
-      <Row style={{ backgroundColor: "#fff" }}>
+      <Row>
         <Col style={{ margin: "2rem" }}>
           <Automation
             automation={automation}
@@ -100,6 +137,7 @@ export default function Home() {
             presets={presets}
             onAutomation={handleAutomation}
             onPower={handlePower}
+            onPreset={handlePreset}
             busy={busy}
           />
         </Col>

+ 51 - 18
components/Automation.js

@@ -1,7 +1,12 @@
+"use client";
+
 import { useEffect, useState } from "react";
+import useSWR from "swr";
 import { Button, Icon, Steps } from "tiny-ui";
 
-import { useLocalStorage } from "lib/state";
+import { fetcher, isArray, isEmpty } from "lib/utils";
+
+import { useScreenSize } from "components";
 
 const { Step } = Steps;
 
@@ -12,10 +17,14 @@ const Automation = ({
   automation,
   onAutomation,
   onPower,
+  onPreset,
   busy
 }) => {
-  const [current, setCurrent] = useLocalStorage("preset", -1, 3600);
+  const [current, setCurrent] = useState(-1);
+  const [preset, setPreset] = useState();
   const [total, setTotal] = useState();
+  const { data, mutate } = useSWR("/api/state", fetcher);
+  const screenSize = useScreenSize();
 
   const getTitle = (indx) => presets?.[indx]?.name || indx;
   const getName = (v) => {
@@ -37,11 +46,24 @@ const Automation = ({
   }, [automation]);
 
   useEffect(() => {
-    if (current > -1 && current < total) {
-      onAutomation(current + 1);
+    if (current > -1 && current < total && (!preset || preset !== current)) {
+      onAutomation(current + 1, () => {
+        mutate();
+      });
     }
   }, [current, total]);
 
+  useEffect(() => {
+    if (preset && preset !== current) setCurrent(preset);
+  }, [preset]);
+
+  useEffect(() => {
+    if (isArray(data) && !isEmpty(data)) {
+      let ps = [...new Set(data?.map((o) => o?.ps))];
+      if (ps.length === 1 && ps?.[0] !== 200) setPreset(ps?.[0] - 1);
+    }
+  }, [data]);
+
   let steps = Object.keys(automation).map((o, i) => {
     return (
       <Step
@@ -56,7 +78,9 @@ const Automation = ({
                   round
                   icon={<Icon name="loader-circle" />}
                   onClick={() => {
-                    onAutomation(o);
+                    onAutomation(o, () => {
+                      mutate();
+                    });
                   }}
                 />
               </div>
@@ -70,8 +94,8 @@ const Automation = ({
 
   return (
     <>
-      <div style={{ position: "sticky", top: "60px", marginBottom: "2rem" }}>
-        <Button.Group size="lg" round>
+      <div className="commandbar">
+        <Button.Group size={screenSize?.width > 550 ? "lg" : "md"} round>
           <Button
             title="Previous"
             icon={<Icon name="arrow-left" />}
@@ -85,7 +109,9 @@ const Automation = ({
             title="Reapply"
             icon={<Icon name="loader-circle" />}
             onClick={() => {
-              onAutomation(current + 1);
+              onAutomation(current + 1, () => {
+                mutate();
+              });
             }}
           />
           <Button
@@ -97,12 +123,23 @@ const Automation = ({
               setCurrent(next);
             }}
           />
-        </Button.Group>
-        <Button.Group size="md" round>
+          <Button
+            title="Initial state"
+            icon={<Icon name="no-idea" />}
+            onClick={() => {
+              setCurrent(-1);
+              setPreset(null);
+              onPreset(200, () => {
+                mutate();
+              });
+            }}
+          />
           <Button
             title="Power on"
             onClick={() => {
-              onPower("on");
+              onPower("on", () => {
+                mutate();
+              });
             }}
           >
             On
@@ -110,18 +147,14 @@ const Automation = ({
           <Button
             title="Power off"
             onClick={() => {
-              onPower("off");
+              onPower("off", () => {
+                mutate();
+              });
             }}
           >
             Off
           </Button>
         </Button.Group>
-        {(busy && (
-          <Button.Group size="md" round>
-            <Button title="Busy" icon={<Icon spin name="loader-3quarter" />} />
-          </Button.Group>
-        )) ||
-          null}
       </div>
       <div>
         <Steps

+ 3 - 2
components/Layout.js

@@ -6,6 +6,7 @@ const { Header, Footer, Content } = _Layout;
 const layoutStyle = {
   marginBottom: "2rem"
 };
+
 const headerStyle = {
   position: "sticky",
   top: "0"
@@ -18,8 +19,8 @@ const contentStyle = {
 
 const footerStyle = {
   textAlign: "right",
-  fontSize: ".77rem",
-  color: "#777"
+  fontSize: ".8rem",
+  color: "rgba(255,255,255,0.75)"
 };
 
 const Layout = ({ page, children, ...rest }) => {

+ 2 - 0
components/Management.js

@@ -1,3 +1,5 @@
+"use client";
+
 import { useEffect, useRef, useState } from "react";
 import styled from "styled-components";
 import {

+ 7 - 2
components/Menu.js

@@ -2,19 +2,24 @@ import Link from "next/link";
 
 import { Image, Menu as _Menu } from "tiny-ui";
 
+import { useScreenSize } from "components";
+
 const MENU = [
-  { url: "/", text: "Powerline" },
   { url: "/", text: "Automation" },
   { url: "/admin", text: "Administration" }
 ];
 
 const Menu = ({ page, ...rest }) => {
-  const items = MENU?.map((o, i) => (
+  const screenSize = useScreenSize();
+
+  let items = MENU?.map((o, i) => (
     <_Menu.Item key={i}>
       <Link href={o?.url}>{o?.text}</Link>
     </_Menu.Item>
   ));
   if (!items) return null;
+  if (screenSize?.width < 551)
+    items = <_Menu.SubMenu title="Powerline">{items}</_Menu.SubMenu>;
   return (
     <_Menu theme="dark">
       <_Menu.Item>

+ 0 - 1
components/Notification.js

@@ -5,7 +5,6 @@ const Notification = ({ type, ...props }) => {
   if (type && ["info", "success", "warning", "error"].includes(type)) {
     _Notification[type](payload);
   } else if (type && ["loader", "loading"].includes(type)) {
-    payload.duration = 1500;
     payload.icon = (
       <Icon name="loader" size={32} style={{ marginRight: "1rem" }} spin />
     );

+ 30 - 0
components/ScreenSize.js

@@ -0,0 +1,30 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+const useScreenSize = () => {
+  const [screenSize, setScreenSize] = useState({
+    width: null,
+    height: null
+  });
+
+  useEffect(() => {
+    const handleResize = () => {
+      setScreenSize({
+        width: window?.innerWidth,
+        height: window?.innerHeight
+      });
+    };
+    if (window) {
+      handleResize();
+      window.addEventListener("resize", handleResize);
+      return () => {
+        window.removeEventListener("resize", handleResize);
+      };
+    }
+  }, []);
+
+  return screenSize;
+};
+
+export default useScreenSize;

+ 1 - 0
components/index.js

@@ -3,4 +3,5 @@ export { default as Layout } from "./Layout";
 export { default as Management } from "./Management";
 export { default as Menu } from "./Menu";
 export { default as Notification } from "./Notification";
+export { default as useScreenSize } from "./ScreenSize";
 export { default as WLED } from "./WLED";

+ 55 - 271
data/development/presets.json

@@ -18,21 +18,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            30,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 30, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 57,
         "effectSpeed": 145,
@@ -157,21 +145,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 57,
         "effectSpeed": 255,
@@ -296,21 +272,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 57,
         "effectSpeed": 255,
@@ -435,21 +399,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 0,
         "effectSpeed": 255,
@@ -574,21 +526,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 87,
         "effectSpeed": 255,
@@ -713,21 +653,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            255,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [255, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 87,
         "effectSpeed": 255,
@@ -852,21 +780,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            183,
-            38,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [183, 38, 255],
+          [0, 0, 0]
         ],
         "effectId": 87,
         "effectSpeed": 255,
@@ -991,21 +907,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 57,
         "effectSpeed": 255,
@@ -1130,21 +1034,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            30,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 30, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 18,
         "effectSpeed": 255,
@@ -1269,21 +1161,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 96,
         "effectSpeed": 255,
@@ -1408,21 +1288,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            255,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [255, 0, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 88,
         "effectSpeed": 255,
@@ -1547,21 +1415,9 @@
         "cct": 127,
         "name": "1 Powerline",
         "colors": [
-          [
-            0,
-            30,
-            255
-          ],
-          [
-            255,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 30, 255],
+          [255, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 45,
         "effectSpeed": 255,
@@ -1686,21 +1542,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 87,
         "effectSpeed": 83,
@@ -1825,21 +1669,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            8,
-            255,
-            0
-          ],
-          [
-            255,
-            200,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [8, 255, 0],
+          [255, 200, 0],
+          [0, 0, 0]
         ],
         "effectId": 74,
         "effectSpeed": 22,
@@ -1964,21 +1796,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            8,
-            255,
-            0
-          ],
-          [
-            255,
-            200,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [8, 255, 0],
+          [255, 200, 0],
+          [0, 0, 0]
         ],
         "effectId": 7,
         "effectSpeed": 255,
@@ -2103,21 +1923,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            8,
-            255,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [8, 255, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 2,
         "effectSpeed": 0,
@@ -2242,21 +2050,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            8,
-            255,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [8, 255, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 2,
         "effectSpeed": 0,
@@ -2383,21 +2179,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 0,
         "effectSpeed": 255,
@@ -2512,4 +2296,4 @@
     "name": "999_Next",
     "label": "2"
   }
-}
+}

+ 55 - 271
data/production/presets.json

@@ -18,21 +18,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            30,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 30, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 57,
         "effectSpeed": 145,
@@ -157,21 +145,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 57,
         "effectSpeed": 255,
@@ -296,21 +272,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 57,
         "effectSpeed": 255,
@@ -435,21 +399,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 0,
         "effectSpeed": 255,
@@ -574,21 +526,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 87,
         "effectSpeed": 255,
@@ -713,21 +653,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            255,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [255, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 87,
         "effectSpeed": 255,
@@ -852,21 +780,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            183,
-            38,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [183, 38, 255],
+          [0, 0, 0]
         ],
         "effectId": 87,
         "effectSpeed": 255,
@@ -991,21 +907,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 57,
         "effectSpeed": 255,
@@ -1130,21 +1034,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            30,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 30, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 18,
         "effectSpeed": 255,
@@ -1269,21 +1161,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            255
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 96,
         "effectSpeed": 255,
@@ -1408,21 +1288,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            255,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [255, 0, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 88,
         "effectSpeed": 255,
@@ -1547,21 +1415,9 @@
         "cct": 127,
         "name": "1 Powerline",
         "colors": [
-          [
-            0,
-            30,
-            255
-          ],
-          [
-            255,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 30, 255],
+          [255, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 45,
         "effectSpeed": 255,
@@ -1686,21 +1542,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 87,
         "effectSpeed": 83,
@@ -1825,21 +1669,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            8,
-            255,
-            0
-          ],
-          [
-            255,
-            200,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [8, 255, 0],
+          [255, 200, 0],
+          [0, 0, 0]
         ],
         "effectId": 74,
         "effectSpeed": 22,
@@ -1964,21 +1796,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            8,
-            255,
-            0
-          ],
-          [
-            255,
-            200,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [8, 255, 0],
+          [255, 200, 0],
+          [0, 0, 0]
         ],
         "effectId": 7,
         "effectSpeed": 255,
@@ -2103,21 +1923,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            8,
-            255,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [8, 255, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 2,
         "effectSpeed": 0,
@@ -2242,21 +2050,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            8,
-            255,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [8, 255, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 2,
         "effectSpeed": 0,
@@ -2383,21 +2179,9 @@
         "cct": 127,
         "name": "Powerline",
         "colors": [
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ],
-          [
-            0,
-            0,
-            0
-          ]
+          [0, 0, 0],
+          [0, 0, 0],
+          [0, 0, 0]
         ],
         "effectId": 0,
         "effectSpeed": 255,
@@ -2512,4 +2296,4 @@
     "name": "999_Next",
     "label": "2"
   }
-}
+}

+ 104 - 0
data/staging/automation.json

@@ -0,0 +1,104 @@
+{
+  "1": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": false, "bri": 1 },
+    "10.10.10.102": { "on": false, "bri": 1 },
+    "10.10.10.103": { "on": false, "bri": 1 }
+  },
+  "2": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "3": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "4": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "5": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "6": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "7": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "8": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "9": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "10": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "11": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "12": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "13": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "14": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "15": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "16": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  },
+  "17": {
+    "10.10.10.100": { "on": true, "bri": 255 },
+    "10.10.10.101": { "on": true, "bri": 255 },
+    "10.10.10.102": { "on": true, "bri": 255 },
+    "10.10.10.103": { "on": true, "bri": 255 }
+  }
+}

+ 6 - 0
data/staging/config.json

@@ -0,0 +1,6 @@
+{
+  "10.10.10.100": "Stage",
+  "10.10.10.101": "Yellow tower",
+  "10.10.10.102": "Blue tower",
+  "10.10.10.103": "Red tower"
+}

+ 2299 - 0
data/staging/presets.json

@@ -0,0 +1,2299 @@
+{
+  "1": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 30, 255],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 57,
+        "effectSpeed": 145,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "1 part 1 opening "
+  },
+  "2": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 57,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "2 part 1 3/4"
+  },
+  "3": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 57,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "3 part 1 set 19"
+  },
+  "4": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 0,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "4 part 1 end"
+  },
+  "5": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 87,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "5 part 2 opening "
+  },
+  "6": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [255, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 87,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "6 part 2 ww feature"
+  },
+  "7": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 0, 255],
+          [183, 38, 255],
+          [0, 0, 0]
+        ],
+        "effectId": 87,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 3,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "7 part 2 set 41"
+  },
+  "8": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 57,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "7A part 2 going to circles"
+  },
+  "9": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 30, 255],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 18,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "8 part 2 set 50 circles"
+  },
+  "10": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 0, 255],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 96,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "9 part 2 riffle toss"
+  },
+  "11": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [255, 0, 0],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 88,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "10 part 2 letter F"
+  },
+  "12": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "1 Powerline",
+        "colors": [
+          [0, 30, 255],
+          [255, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 45,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 3,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "10A crazy 8s"
+  },
+  "13": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 0, 0],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 87,
+        "effectSpeed": 83,
+        "effectIntensity": 91,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "11 part 2 ending "
+  },
+  "14": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [8, 255, 0],
+          [255, 200, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 74,
+        "effectSpeed": 22,
+        "effectIntensity": 46,
+        "paletteId": 3,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "12 part 3 opening "
+  },
+  "15": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [8, 255, 0],
+          [255, 200, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 7,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 3,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "13 part 3 bolt hit"
+  },
+  "16": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [8, 255, 0],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 2,
+        "effectSpeed": 0,
+        "effectIntensity": 0,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "14 part 3 ending "
+  },
+  "17": {
+    "on": true,
+    "brightness": 255,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [8, 255, 0],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 2,
+        "effectSpeed": 0,
+        "effectIntensity": 0,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ],
+    "name": "15 end of part 3"
+  },
+  "200": {
+    "name": "888_Poweroff",
+    "label": "1",
+    "on": true,
+    "brightness": 1,
+    "transition": 7,
+    "mainSegment": 0,
+    "segments": [
+      {
+        "id": 0,
+        "start": 0,
+        "stop": 500,
+        "grouping": 1,
+        "spacing": 0,
+        "of": 0,
+        "on": true,
+        "freeze": false,
+        "brightness": 255,
+        "cct": 127,
+        "name": "Powerline",
+        "colors": [
+          [0, 0, 0],
+          [0, 0, 0],
+          [0, 0, 0]
+        ],
+        "effectId": 0,
+        "effectSpeed": 255,
+        "effectIntensity": 255,
+        "paletteId": 2,
+        "selected": true,
+        "reverse": false,
+        "mirror": false
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      },
+      {
+        "stop": 0
+      }
+    ]
+  },
+  "249": {
+    "win": "win&P1=1&P2=17PL=~-",
+    "name": "999_Previous",
+    "label": "3"
+  },
+  "250": {
+    "win": "win&P1=1&P2=17PL=~",
+    "name": "999_Next",
+    "label": "2"
+  }
+}

+ 3 - 0
lib/utils.js

@@ -10,6 +10,8 @@ const {
   pick
 } = require("lodash");
 
+const fetcher = (url) => fetch(url).then((res) => res.json());
+
 const parse = (value) => {
   try {
     return JSON.parse(value);
@@ -447,6 +449,7 @@ module.exports = {
   btoa,
   capitalizeFirstLetter,
   clone,
+  fetcher,
   firstOfList,
   formatCurrency,
   hasDocument,

+ 1 - 0
package.json

@@ -23,6 +23,7 @@
     "react": "18.2.0",
     "react-dom": "18.2.0",
     "styled-components": "^6.0.8",
+    "swr": "^2.2.4",
     "tiny-ui": "^0.0.95",
     "utf-8-validate": "^6.0.3",
     "wled-client": "^0.22.1"

+ 14 - 1
yarn.lock

@@ -1631,7 +1631,7 @@ classnames@^2.3.1:
   resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.3.2.tgz#351d813bf0137fcc6a76a16b88208d2560a0d924"
   integrity sha512-CSbhY4cFEJRe6/GQzIk5qXZ4Jeg5pcsP7b5peFSDpffpe1cqjASH/n9UTjBwOp6XpMSTwQ8Za2K5V02ueA7Tmw==
 
-client-only@0.0.1:
+client-only@0.0.1, client-only@^0.0.1:
   version "0.0.1"
   resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
   integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
@@ -3475,6 +3475,14 @@ supports-preserve-symlinks-flag@^1.0.0:
   resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
   integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
 
+swr@^2.2.4:
+  version "2.2.4"
+  resolved "https://registry.yarnpkg.com/swr/-/swr-2.2.4.tgz#03ec4c56019902fbdc904d78544bd7a9a6fa3f07"
+  integrity sha512-njiZ/4RiIhoOlAaLYDqwz5qH/KZXVilRLvomrx83HjzCWTfa+InyfAjv05PSFxnmLzZkNO9ZfvgoqzAaEI4sGQ==
+  dependencies:
+    client-only "^0.0.1"
+    use-sync-external-store "^1.2.0"
+
 tapable@^2.2.0:
   version "2.2.1"
   resolved "https://registry.yarnpkg.com/tapable/-/tapable-2.2.1.tgz#1967a73ef4060a82f12ab96af86d52fdb76eeca0"
@@ -3633,6 +3641,11 @@ uri-js@^4.2.2:
   dependencies:
     punycode "^2.1.0"
 
+use-sync-external-store@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
+  integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
+
 utf-8-validate@^6.0.3:
   version "6.0.3"
   resolved "https://registry.yarnpkg.com/utf-8-validate/-/utf-8-validate-6.0.3.tgz#7d8c936d854e86b24d1d655f138ee27d2636d777"