diff --git a/pkgs/webview-ui/app/eslint.config.mjs b/pkgs/webview-ui/app/eslint.config.mjs index a18d3867..68dc5215 100644 --- a/pkgs/webview-ui/app/eslint.config.mjs +++ b/pkgs/webview-ui/app/eslint.config.mjs @@ -19,6 +19,7 @@ export default tseslint.config( "error", { callees: ["cx"], + whitelist: ["material-icons"], }, ], }, diff --git a/pkgs/webview-ui/app/gtk.webview.js b/pkgs/webview-ui/app/gtk.webview.js index fefa23db..875f89de 100644 --- a/pkgs/webview-ui/app/gtk.webview.js +++ b/pkgs/webview-ui/app/gtk.webview.js @@ -25,9 +25,12 @@ fs.readFile(manifestPath, { encoding: "utf8" }, (err, data) => { // Add linked stylesheets assets.forEach((asset) => { - asset.css.forEach((cssEntry) => { - htmlContent += `\n `; - }); + console.log(asset); + if (asset.src === "index.html") { + asset.css.forEach((cssEntry) => { + htmlContent += `\n `; + }); + } }); htmlContent += ` diff --git a/pkgs/webview-ui/app/mock.ts b/pkgs/webview-ui/app/mock.ts new file mode 100644 index 00000000..dab41ba0 --- /dev/null +++ b/pkgs/webview-ui/app/mock.ts @@ -0,0 +1,16 @@ +import { JSONSchemaFaker } from "json-schema-faker"; +import { schema } from "./api/index"; +import { OperationNames } from "./src/message"; + +const faker = JSONSchemaFaker; + +faker.option({ + alwaysFakeOptionals: true, +}); + +const getFakeResponse = (method: OperationNames, data: any) => { + const fakeData = faker.generate(schema.properties[method].properties.return); + return fakeData; +}; + +export { getFakeResponse }; diff --git a/pkgs/webview-ui/app/package-lock.json b/pkgs/webview-ui/app/package-lock.json index 5d575bcf..db9f0b4c 100644 --- a/pkgs/webview-ui/app/package-lock.json +++ b/pkgs/webview-ui/app/package-lock.json @@ -9,10 +9,12 @@ "version": "0.0.1", "license": "MIT", "dependencies": { + "material-icons": "^1.13.12", "solid-js": "^1.8.11" }, "devDependencies": { "@eslint/js": "^9.3.0", + "@tailwindcss/typography": "^0.5.13", "@types/node": "^20.12.12", "@typescript-eslint/parser": "^7.10.0", "autoprefixer": "^10.4.19", @@ -20,6 +22,7 @@ "daisyui": "^4.11.1", "eslint": "^8.57.0", "eslint-plugin-tailwindcss": "^3.17.0", + "json-schema-faker": "^0.5.6", "json-schema-to-ts": "^3.1.0", "postcss": "^8.4.38", "prettier": "^3.2.5", @@ -1482,6 +1485,34 @@ "solid-js": "^1.6.12" } }, + "node_modules/@tailwindcss/typography": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@tailwindcss/typography/-/typography-0.5.13.tgz", + "integrity": "sha512-ADGcJ8dX21dVVHIwTRgzrcunY6YY9uSlAHHGVKvkA+vLc5qLwEszvKts40lx7z0qc4clpjclwLeK5rVCV2P/uw==", + "dev": true, + "dependencies": { + "lodash.castarray": "^4.4.0", + "lodash.isplainobject": "^4.0.6", + "lodash.merge": "^4.6.2", + "postcss-selector-parser": "6.0.10" + }, + "peerDependencies": { + "tailwindcss": ">=3.0.0 || insiders" + } + }, + "node_modules/@tailwindcss/typography/node_modules/postcss-selector-parser": { + "version": "6.0.10", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz", + "integrity": "sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -1990,6 +2021,12 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-me-maybe": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.2.tgz", + "integrity": "sha512-HpX65o1Hnr9HH25ojC1YGs7HCQLq0GCOibSaWER0eNpgJ/Z1MZv2mTc7+xh6WOPxbRVcmgbv4hGU+uSQ/2xFZQ==", + "dev": true + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2623,6 +2660,19 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/esquery": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", @@ -2802,6 +2852,12 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/format-util": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/format-util/-/format-util-1.0.5.tgz", + "integrity": "sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg==", + "dev": true + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3197,6 +3253,53 @@ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true }, + "node_modules/json-schema-faker": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/json-schema-faker/-/json-schema-faker-0.5.6.tgz", + "integrity": "sha512-u/cFC26/GDxh2vPiAC8B8xVvpXAW+QYtG2mijEbKrimCk8IHtiwQBjCE8TwvowdhALWq9IcdIWZ+/8ocXvdL3Q==", + "dev": true, + "dependencies": { + "json-schema-ref-parser": "^6.1.0", + "jsonpath-plus": "^7.2.0" + }, + "bin": { + "jsf": "bin/gen.cjs" + } + }, + "node_modules/json-schema-ref-parser": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/json-schema-ref-parser/-/json-schema-ref-parser-6.1.0.tgz", + "integrity": "sha512-pXe9H1m6IgIpXmE5JSb8epilNTGsmTb2iPohAXpOdhqGFbQjNeHHsZxU+C8w6T81GZxSPFLeUoqDJmzxx5IGuw==", + "deprecated": "Please switch to @apidevtools/json-schema-ref-parser", + "dev": true, + "dependencies": { + "call-me-maybe": "^1.0.1", + "js-yaml": "^3.12.1", + "ono": "^4.0.11" + } + }, + "node_modules/json-schema-ref-parser/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/json-schema-ref-parser/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/json-schema-to-ts": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.0.tgz", @@ -3234,6 +3337,15 @@ "node": ">=6" } }, + "node_modules/jsonpath-plus": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz", + "integrity": "sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -3314,6 +3426,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/lodash.castarray": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.castarray/-/lodash.castarray-4.4.0.tgz", + "integrity": "sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==", + "dev": true + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "dev": true + }, "node_modules/lodash.merge": { "version": "4.6.2", "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", @@ -3366,6 +3490,11 @@ "semver": "bin/semver" } }, + "node_modules/material-icons": { + "version": "1.13.12", + "resolved": "https://registry.npmjs.org/material-icons/-/material-icons-1.13.12.tgz", + "integrity": "sha512-/2YoaB79IjUK2B2JB+vIXXYGtBfHb/XG66LvoKVM5ykHW7yfrV5SP6d7KLX6iijY6/G9GqwgtPQ/sbhFnOURVA==" + }, "node_modules/merge-anything": { "version": "5.1.7", "resolved": "https://registry.npmjs.org/merge-anything/-/merge-anything-5.1.7.tgz", @@ -3551,6 +3680,15 @@ "wrappy": "1" } }, + "node_modules/ono": { + "version": "4.0.11", + "resolved": "https://registry.npmjs.org/ono/-/ono-4.0.11.tgz", + "integrity": "sha512-jQ31cORBFE6td25deYeD80wxKBMj+zBmHTrVxnc6CKhx8gho6ipmWM5zj/oeoqioZ99yqBls9Z/9Nss7J26G2g==", + "dev": true, + "dependencies": { + "format-util": "^1.0.3" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4289,6 +4427,12 @@ "node": ">=0.10.0" } }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", diff --git a/pkgs/webview-ui/app/package.json b/pkgs/webview-ui/app/package.json index 07d82612..5f92b7d9 100644 --- a/pkgs/webview-ui/app/package.json +++ b/pkgs/webview-ui/app/package.json @@ -13,12 +13,15 @@ "license": "MIT", "devDependencies": { "@eslint/js": "^9.3.0", + "@tailwindcss/typography": "^0.5.13", "@types/node": "^20.12.12", "@typescript-eslint/parser": "^7.10.0", "autoprefixer": "^10.4.19", "classnames": "^2.5.1", "daisyui": "^4.11.1", "eslint": "^8.57.0", + "eslint-plugin-tailwindcss": "^3.17.0", + "json-schema-faker": "^0.5.6", "json-schema-to-ts": "^3.1.0", "postcss": "^8.4.38", "prettier": "^3.2.5", @@ -27,10 +30,10 @@ "typescript": "^5.4.5", "typescript-eslint": "^7.10.0", "vite": "^5.0.11", - "vite-plugin-solid": "^2.8.2", - "eslint-plugin-tailwindcss": "^3.17.0" + "vite-plugin-solid": "^2.8.2" }, "dependencies": { + "material-icons": "^1.13.12", "solid-js": "^1.8.11" } } diff --git a/pkgs/webview-ui/app/src/App.tsx b/pkgs/webview-ui/app/src/App.tsx index 50e04034..cad9e312 100644 --- a/pkgs/webview-ui/app/src/App.tsx +++ b/pkgs/webview-ui/app/src/App.tsx @@ -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("machines"); + +export { route, setRoute }; const App: Component = () => { - const [route, setRoute] = createSignal("home"); return ( -
-
Navigation
-
    -
  • - -
  • -
  • - {" "} - -
  • -
-
- -
-
{route()}
- {route()} not found

}> - - - - -
-
red
-
green
-
blue
-
yellow
-
purple
-
cyan
-
pink
-
-
-
-
+
); diff --git a/pkgs/webview-ui/app/src/Routes.tsx b/pkgs/webview-ui/app/src/Routes.tsx new file mode 100644 index 00000000..a726ed4e --- /dev/null +++ b/pkgs/webview-ui/app/src/Routes.tsx @@ -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; +} +export const Router = (props: RouterProps) => { + const { route } = props; + return ( + route {route()} not found

}> + + {([key, { child }]) => {child}} + +
+ ); +}; diff --git a/pkgs/webview-ui/app/src/Sidebar.tsx b/pkgs/webview-ui/app/src/Sidebar.tsx new file mode 100644 index 00000000..c39389d3 --- /dev/null +++ b/pkgs/webview-ui/app/src/Sidebar.tsx @@ -0,0 +1,33 @@ +import { Accessor, For, Setter } from "solid-js"; +import { Route, routes } from "./Routes"; + +interface SidebarProps { + route: Accessor; + setRoute: Setter; +} +export const Sidebar = (props: SidebarProps) => { + const { route, setRoute } = props; + return ( + + ); +}; diff --git a/pkgs/webview-ui/app/src/index.css b/pkgs/webview-ui/app/src/index.css index b5c61c95..0fd274ce 100644 --- a/pkgs/webview-ui/app/src/index.css +++ b/pkgs/webview-ui/app/src/index.css @@ -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; diff --git a/pkgs/webview-ui/app/src/index.tsx b/pkgs/webview-ui/app/src/index.tsx index 0bf36444..b2f81429 100644 --- a/pkgs/webview-ui/app/src/index.tsx +++ b/pkgs/webview-ui/app/src/index.tsx @@ -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(() => , root!); diff --git a/pkgs/webview-ui/app/src/layout/header.tsx b/pkgs/webview-ui/app/src/layout/header.tsx new file mode 100644 index 00000000..f0f55b79 --- /dev/null +++ b/pkgs/webview-ui/app/src/layout/header.tsx @@ -0,0 +1,19 @@ +export const Header = () => { + return ( + + ); +}; diff --git a/pkgs/webview-ui/app/src/layout/layout.tsx b/pkgs/webview-ui/app/src/layout/layout.tsx index 127eb5ff..ab794262 100644 --- a/pkgs/webview-ui/app/src/layout/layout.tsx +++ b/pkgs/webview-ui/app/src/layout/layout.tsx @@ -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 = (props) => { - return
{props.children}
; + return ( + <> +
+ +
+
+ + {props.children} +
+
+ + +
+
+ + ); }; diff --git a/pkgs/webview-ui/app/src/nested.tsx b/pkgs/webview-ui/app/src/nested.tsx deleted file mode 100644 index ebbd0bca..00000000 --- a/pkgs/webview-ui/app/src/nested.tsx +++ /dev/null @@ -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 ( -
- -
- - Loading... - - No machines found - - - - {(entry, i) => ( -
  • - {i() + 1}: {entry.machine_name}{" "} - {entry.machine_description || "No description"} -
  • - )} -
    -
    -
    -
    - ); -}; diff --git a/pkgs/webview-ui/app/src/routes/colors/view.tsx b/pkgs/webview-ui/app/src/routes/colors/view.tsx new file mode 100644 index 00000000..465c1856 --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/colors/view.tsx @@ -0,0 +1,13 @@ +export const colors = () => { + return ( +
    +
    red
    +
    green
    +
    blue
    +
    yellow
    +
    purple
    +
    cyan
    +
    pink
    +
    + ); +}; diff --git a/pkgs/webview-ui/app/src/routes/machines/view.tsx b/pkgs/webview-ui/app/src/routes/machines/view.tsx new file mode 100644 index 00000000..eb8f5108 --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/machines/view.tsx @@ -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 ( +
    +
    + +
    + + + {/* Loading skeleton */} +
  • +
    +
    +
    +
    +
    +

    +
    +

    +
    +
    +
    +
  • +
    + + No machines found + + + + {(entry) => ( +
  • +
    +
    + + devices_other + +
    +
    +
    +

    {entry.machine_name}

    +

    + {entry.machine_description || "No description"} +

    +
    +
    + +
    +
    +
    +
  • + )} +
    +
    +
    +
    + ); +}; diff --git a/pkgs/webview-ui/app/tailwind.config.js b/pkgs/webview-ui/app/tailwind.config.js index 3bcabba4..e428eb80 100644 --- a/pkgs/webview-ui/app/tailwind.config.js +++ b/pkgs/webview-ui/app/tailwind.config.js @@ -1,9 +1,11 @@ +const typography = require("@tailwindcss/typography"); const daisyui = require("daisyui"); + /** @type {import('tailwindcss').Config} */ module.exports = { content: ["./src/**/*.{js,jsx,ts,tsx}"], theme: { extend: {}, }, - plugins: [daisyui], + plugins: [typography, daisyui], }; diff --git a/pkgs/webview-ui/flake-module.nix b/pkgs/webview-ui/flake-module.nix index 79776579..83d5e019 100644 --- a/pkgs/webview-ui/flake-module.nix +++ b/pkgs/webview-ui/flake-module.nix @@ -11,7 +11,7 @@ npmDeps = pkgs.fetchNpmDeps { src = ./app; - hash = "sha256-E0++hupVKnDqmLk7ljoMcqcI4w+DIMlfRYRPbKUsT2c="; + hash = "sha256-4ZurUbY5uMq7KeKnYRJ1+/Go9WoURFOpeZgLE0S6WZI="; }; # The prepack script runs the build script, which we'd rather do in the build phase. npmPackFlags = [ "--ignore-scripts" ];