Webview: bootstrap layout

This commit is contained in:
Johannes Kirschbauer
2024-05-29 16:40:20 +02:00
parent 3f5ca3f5fa
commit 5caba68a54
17 changed files with 407 additions and 96 deletions

View File

@@ -1,59 +1,18 @@
import { Match, Switch, createSignal, type Component } from "solid-js";
import { createSignal, type Component } from "solid-js";
import { CountProvider } from "./Config";
// import { Nested } from "./nested";
import { Layout } from "./layout/layout";
import cx from "classnames";
import { Nested } from "./nested";
import { Route, Router } from "./Routes";
type Route = "home" | "machines";
// Global state
const [route, setRoute] = createSignal<Route>("machines");
export { route, setRoute };
const App: Component = () => {
const [route, setRoute] = createSignal<Route>("home");
return (
<CountProvider>
<Layout>
<div class="col-span-1">
<div class={cx("text-zinc-500")}>Navigation</div>
<ul>
<li>
<button
onClick={() => setRoute("home")}
classList={{ "bg-blue-500": route() === "home" }}
>
Home
</button>
</li>
<li>
{" "}
<button
onClick={() => setRoute("machines")}
classList={{ "bg-blue-500": route() === "machines" }}
>
Machines
</button>
</li>
</ul>
</div>
<div class="col-span-7">
<div>{route()}</div>
<Switch fallback={<p>{route()} not found</p>}>
<Match when={route() == "home"}>
<Nested />
</Match>
<Match when={route() == "machines"}>
<div class="grid grid-cols-3 gap-2">
<div class="h-10 w-20 bg-red-500">red</div>
<div class="h-10 w-20 bg-green-500">green</div>
<div class="h-10 w-20 bg-blue-500">blue</div>
<div class="h-10 w-20 bg-yellow-500">yellow</div>
<div class="h-10 w-20 bg-purple-500">purple</div>
<div class="h-10 w-20 bg-cyan-500">cyan</div>
<div class="h-10 w-20 bg-pink-500">pink</div>
</div>
</Match>
</Switch>
</div>
<Router route={route} />
</Layout>
</CountProvider>
);

View File

@@ -0,0 +1,32 @@
import { Accessor, For, Match, Switch } from "solid-js";
import { MachineListView } from "./routes/machines/view";
import { colors } from "./routes/colors/view";
export type Route = keyof typeof routes;
export const routes = {
machines: {
child: MachineListView,
label: "Machines",
icon: "devices_other",
},
colors: {
child: colors,
label: "Colors",
icon: "color_lens",
},
};
interface RouterProps {
route: Accessor<Route>;
}
export const Router = (props: RouterProps) => {
const { route } = props;
return (
<Switch fallback={<p>route {route()} not found</p>}>
<For each={Object.entries(routes)}>
{([key, { child }]) => <Match when={route() === key}>{child}</Match>}
</For>
</Switch>
);
};

View File

@@ -0,0 +1,33 @@
import { Accessor, For, Setter } from "solid-js";
import { Route, routes } from "./Routes";
interface SidebarProps {
route: Accessor<Route>;
setRoute: Setter<Route>;
}
export const Sidebar = (props: SidebarProps) => {
const { route, setRoute } = props;
return (
<aside class="min-h-screen w-80 bg-base-100">
<div class="sticky top-0 z-20 hidden items-center gap-2 bg-base-100/90 px-4 py-2 shadow-sm backdrop-blur lg:flex">
Icon
</div>
<ul class="menu px-4 py-0">
<For each={Object.entries(routes)}>
{([key, { label, icon }]) => (
<li>
<button
onClick={() => setRoute(key as Route)}
class="group"
classList={{ "bg-blue-500": route() === key }}
>
<span class="material-icons">{icon}</span>
{label}
</button>
</li>
)}
</For>
</ul>
</aside>
);
};

View File

@@ -1,3 +1,5 @@
@import 'material-icons/iconfont/filled.css';
/* List of icons: https://marella.me/material-icons/demo/ */
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -3,7 +3,7 @@ import { render } from "solid-js/web";
import "./index.css";
import App from "./App";
import { getFakeResponse } from "../mock";
const root = document.getElementById("app");
window.clan = window.clan || {};
@@ -14,5 +14,26 @@ if (import.meta.env.DEV && !(root instanceof HTMLElement)) {
);
}
console.log(import.meta.env);
if (import.meta.env.DEV) {
console.log("Development mode");
window.webkit = window.webkit || {
messageHandlers: {
gtk: {
postMessage: (postMessage) => {
const { method, data } = postMessage;
console.debug("Python API call", { method, data });
setTimeout(() => {
const mock = getFakeResponse(method, data);
console.log("mock", { mock });
window.clan[method](JSON.stringify(mock));
}, 1000);
},
},
},
};
}
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
render(() => <App />, root!);

View File

@@ -0,0 +1,19 @@
export const Header = () => {
return (
<div class="navbar bg-base-100">
<div class="flex-none">
<button class="btn btn-square btn-ghost">
<span class="material-icons">home</span>
</button>
</div>
<div class="flex-1">
<a class="btn btn-ghost text-xl">Clan</a>
</div>
<div class="flex-none">
<button class="btn btn-square btn-ghost">
<span class="material-icons">menu</span>
</button>
</div>
</div>
);
};

View File

@@ -1,9 +1,35 @@
import { Component, JSXElement } from "solid-js";
import { Header } from "./header";
import { Sidebar } from "../Sidebar";
import { route, setRoute } from "../App";
interface LayoutProps {
children: JSXElement;
}
export const Layout: Component<LayoutProps> = (props) => {
return <div class="grid grid-cols-8">{props.children}</div>;
return (
<>
<div class="drawer bg-base-100 lg:drawer-open">
<input
id="toplevel-drawer"
type="checkbox"
class="drawer-toggle hidden"
/>
<div class="drawer-content">
<Header />
{props.children}
</div>
<div class="drawer-side z-40">
<label
for="toplevel-drawer"
aria-label="close sidebar"
class="drawer-overlay"
></label>
<Sidebar route={route} setRoute={setRoute} />
</div>
</div>
</>
);
};

View File

@@ -1,39 +0,0 @@
import { For, Match, Switch, createEffect, type Component } from "solid-js";
import { useCountContext } from "./Config";
export const Nested: Component = () => {
const [{ machines, loading }, { getMachines }] = useCountContext();
const list = () => Object.values(machines());
createEffect(() => {
console.log("1", list());
});
createEffect(() => {
console.log("2", machines());
});
return (
<div>
<button onClick={() => getMachines()} class="btn btn-primary">
Get machines
</button>
<div></div>
<Switch>
<Match when={loading()}>Loading...</Match>
<Match when={!loading() && Object.entries(machines()).length === 0}>
No machines found
</Match>
<Match when={!loading()}>
<For each={list()}>
{(entry, i) => (
<li>
{i() + 1}: {entry.machine_name}{" "}
{entry.machine_description || "No description"}
</li>
)}
</For>
</Match>
</Switch>
</div>
);
};

View File

@@ -0,0 +1,13 @@
export const colors = () => {
return (
<div class="grid grid-cols-3 gap-2">
<div class="h-10 w-20 bg-red-500">red</div>
<div class="h-10 w-20 bg-green-500">green</div>
<div class="h-10 w-20 bg-blue-500">blue</div>
<div class="h-10 w-20 bg-yellow-500">yellow</div>
<div class="h-10 w-20 bg-purple-500">purple</div>
<div class="h-10 w-20 bg-cyan-500">cyan</div>
<div class="h-10 w-20 bg-pink-500">pink</div>
</div>
);
};

View File

@@ -0,0 +1,76 @@
import { For, Match, Switch, createEffect, type Component } from "solid-js";
import { useCountContext } from "../../Config";
import { route } from "@/src/App";
export const MachineListView: Component = () => {
const [{ machines, loading }, { getMachines }] = useCountContext();
const list = () => Object.values(machines());
createEffect(() => {
if (route() === "machines") getMachines();
});
return (
<div class="max-w-screen-lg">
<div class="tooltip" data-tip="Refresh ">
<button class="btn btn-ghost" onClick={() => getMachines()}>
<span class="material-icons ">refresh</span>
</button>
</div>
<Switch>
<Match when={loading()}>
{/* Loading skeleton */}
<li>
<div class="card card-side m-2 bg-base-100 shadow-lg">
<figure class="pl-2">
<div class="skeleton size-12"></div>
</figure>
<div class="card-body">
<h2 class="card-title">
<div class="skeleton h-12 w-80"></div>
</h2>
<div class="skeleton h-8 w-72"></div>
</div>
</div>
</li>
</Match>
<Match when={!loading() && Object.entries(machines()).length === 0}>
No machines found
</Match>
<Match when={!loading()}>
<For each={list()}>
{(entry) => (
<li>
<div class="card card-side m-2 bg-base-100 shadow-lg">
<figure class="pl-2">
<span class="material-icons content-center text-5xl">
devices_other
</span>
</figure>
<div class="card-body flex-row justify-between">
<div class="flex flex-col">
<h2 class="card-title">{entry.machine_name}</h2>
<p
classList={{
"text-gray-400": !entry.machine_description,
"text-gray-600": !!entry.machine_description,
}}
>
{entry.machine_description || "No description"}
</p>
</div>
<div>
<button class="btn btn-ghost">
<span class="material-icons">more_vert</span>
</button>
</div>
</div>
</div>
</li>
)}
</For>
</Match>
</Switch>
</div>
);
};