diff --git a/inventory.json b/inventory.json index 207630e57..478f177c7 100644 --- a/inventory.json +++ b/inventory.json @@ -1,6 +1,6 @@ { "meta": { - "name": "Minimal inventory" + "name": "clan-core" }, "machines": { "minimal-inventory-machine": { diff --git a/pkgs/clan-app/README.md b/pkgs/clan-app/README.md index 7d69788be..29880562b 100644 --- a/pkgs/clan-app/README.md +++ b/pkgs/clan-app/README.md @@ -28,7 +28,6 @@ Follow the instructions below to set up your development environment and start t This will start the application in debug mode and link it to the web server running at `http://localhost:3000`. - # clan app (old) Provides users with the simple functionality to manage their locally registered clans. diff --git a/pkgs/clan-app/clan_app/views/webview.py b/pkgs/clan-app/clan_app/views/webview.py index 887742be5..5900fdf3b 100644 --- a/pkgs/clan-app/clan_app/views/webview.py +++ b/pkgs/clan-app/clan_app/views/webview.py @@ -21,7 +21,7 @@ log = logging.getLogger(__name__) def sanitize_string(s: str) -> str: - return s.replace("\\", "\\\\").replace('"', '\\"') + return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n") def dataclass_to_dict(obj: Any) -> Any: diff --git a/pkgs/clan-cli/clan_cli/clan/create.py b/pkgs/clan-cli/clan_cli/clan/create.py index e198ffa0e..29cb94a42 100644 --- a/pkgs/clan-cli/clan_cli/clan/create.py +++ b/pkgs/clan-cli/clan_cli/clan/create.py @@ -1,12 +1,12 @@ # !/usr/bin/env python3 import argparse -import json import os from dataclasses import dataclass, fields from pathlib import Path from clan_cli.api import API from clan_cli.arg_actions import AppendOptionAction +from clan_cli.inventory import Inventory, InventoryMeta from ..cmd import CmdOut, run from ..errors import ClanError @@ -24,19 +24,12 @@ class CreateClanResponse: flake_update: CmdOut -@dataclass -class ClanMetaInfo: - name: str - description: str | None - icon: str | None - - @dataclass class CreateOptions: directory: Path | str # Metadata for the clan # Metadata can be shown with `clan show` - meta: ClanMetaInfo | None = None + meta: InventoryMeta | None = None # URL to the template to use. Defaults to the "minimal" template template_url: str = minimal_template_url @@ -70,13 +63,7 @@ def create_clan(options: CreateOptions) -> CreateClanResponse: ) out = run(command, cwd=directory) - # Write meta.json file if meta is provided - if options.meta is not None: - meta_file = Path(directory / "clan/meta.json") - meta_file.parent.mkdir(parents=True, exist_ok=True) - with open(meta_file, "w") as f: - json.dump(options.meta.__dict__, f) - + ## Begin: setup git command = nix_shell(["nixpkgs#git"], ["git", "init"]) out = run(command, cwd=directory) cmd_responses["git init"] = out @@ -94,6 +81,14 @@ def create_clan(options: CreateOptions) -> CreateClanResponse: ) out = run(command, cwd=directory) cmd_responses["git config"] = out + ## End: setup git + + # Write inventory.json file + inventory = Inventory.load_file(directory) + if options.meta is not None: + inventory.meta = options.meta + # Persist creates a commit message for each change + inventory.persist(directory, "Init inventory") command = ["nix", "flake", "update"] out = run(command, cwd=directory) @@ -118,7 +113,7 @@ def register_create_parser(parser: argparse.ArgumentParser) -> None: parser.add_argument( "--meta", - help=f"""Metadata to set for the clan. Available options are: {", ".join([f.name for f in fields(ClanMetaInfo)]) }""", + help=f"""Metadata to set for the clan. Available options are: {", ".join([f.name for f in fields(InventoryMeta)]) }""", nargs=2, metavar=("name", "value"), action=AppendOptionAction, diff --git a/pkgs/clan-cli/clan_cli/clan/show.py b/pkgs/clan-cli/clan_cli/clan/show.py index dbae165b6..670646523 100644 --- a/pkgs/clan-cli/clan_cli/clan/show.py +++ b/pkgs/clan-cli/clan_cli/clan/show.py @@ -5,8 +5,8 @@ from pathlib import Path from urllib.parse import urlparse from clan_cli.api import API -from clan_cli.clan.create import ClanMetaInfo from clan_cli.errors import ClanCmdError, ClanError +from clan_cli.inventory import InventoryMeta from ..cmd import run_no_stdout from ..nix import nix_eval @@ -15,10 +15,10 @@ log = logging.getLogger(__name__) @API.register -def show_clan_meta(uri: str | Path) -> ClanMetaInfo: +def show_clan_meta(uri: str | Path) -> InventoryMeta: cmd = nix_eval( [ - f"{uri}#clanInternals.meta", + f"{uri}#clanInternals.inventory.meta", "--json", ] ) @@ -27,11 +27,11 @@ def show_clan_meta(uri: str | Path) -> ClanMetaInfo: try: proc = run_no_stdout(cmd) res = proc.stdout.strip() - except ClanCmdError: + except ClanCmdError as e: raise ClanError( - "Clan might not have meta attributes", + "Evaluation failed on meta attribute", location=f"show_clan {uri}", - description="Evaluation failed on clanInternals.meta attribute", + description=str(e.cmd), ) clan_meta = json.loads(res) @@ -61,7 +61,7 @@ def show_clan_meta(uri: str | Path) -> ClanMetaInfo: description="Icon path must be a URL or a relative path.", ) - return ClanMetaInfo( + return InventoryMeta( name=clan_meta.get("name"), description=clan_meta.get("description", None), icon=icon_path, @@ -73,8 +73,8 @@ def show_command(args: argparse.Namespace) -> None: meta = show_clan_meta(flake_path) print(f"Name: {meta.name}") - print(f"Description: {meta.description or ''}") - print(f"Icon: {meta.icon or ''}") + print(f"Description: {meta.description or '-'}") + print(f"Icon: {meta.icon or '-'}") def register_parser(parser: argparse.ArgumentParser) -> None: diff --git a/pkgs/clan-cli/clan_cli/clan/update.py b/pkgs/clan-cli/clan_cli/clan/update.py index 4dcc33d77..3f73756ed 100644 --- a/pkgs/clan-cli/clan_cli/clan/update.py +++ b/pkgs/clan-cli/clan_cli/clan/update.py @@ -1,35 +1,20 @@ -import json from dataclasses import dataclass -from pathlib import Path from clan_cli.api import API -from clan_cli.clan.create import ClanMetaInfo -from clan_cli.errors import ClanError +from clan_cli.inventory import Inventory, InventoryMeta @dataclass class UpdateOptions: directory: str - meta: ClanMetaInfo | None = None + meta: InventoryMeta @API.register -def update_clan_meta(options: UpdateOptions) -> ClanMetaInfo: - meta_file = Path(options.directory) / Path("clan/meta.json") - if not meta_file.exists(): - raise ClanError( - "File not found", - description=f"Could not find {meta_file} to update.", - location="update_clan_meta", - ) +def update_clan_meta(options: UpdateOptions) -> InventoryMeta: + inventory = Inventory.load_file(options.directory) + inventory.meta = options.meta - meta_content: dict[str, str] = {} - with open(meta_file) as f: - meta_content = json.load(f) + inventory.persist(options.directory, "Update clan meta") - meta_content = {**meta_content, **options.meta.__dict__} - - with open(meta_file) as f: - json.dump(meta_content, f) - - return ClanMetaInfo(**meta_content) + return inventory.meta diff --git a/pkgs/clan-cli/clan_cli/inventory/__init__.py b/pkgs/clan-cli/clan_cli/inventory/__init__.py index f774fd70a..00fb00972 100644 --- a/pkgs/clan-cli/clan_cli/inventory/__init__.py +++ b/pkgs/clan-cli/clan_cli/inventory/__init__.py @@ -99,14 +99,23 @@ class Service: ) +@dataclass +class InventoryMeta: + name: str + description: str | None = None + icon: str | None = None + + @dataclass class Inventory: + meta: InventoryMeta machines: dict[str, Machine] services: dict[str, dict[str, Service]] @staticmethod def from_dict(d: dict[str, Any]) -> "Inventory": return Inventory( + meta=InventoryMeta(**d.get("meta", {})), machines={ name: Machine.from_dict(machine) for name, machine in d.get("machines", {}).items() @@ -126,7 +135,9 @@ class Inventory: @staticmethod def load_file(flake_dir: str | Path) -> "Inventory": - inventory = Inventory(machines={}, services={}) + inventory = Inventory( + machines={}, services={}, meta=InventoryMeta(name="New Clan") + ) inventory_file = Inventory.get_path(flake_dir) if inventory_file.exists(): with open(inventory_file) as f: diff --git a/pkgs/clan-cli/tests/test_vars.py b/pkgs/clan-cli/tests/test_vars.py index bf41ee272..4abeaff69 100644 --- a/pkgs/clan-cli/tests/test_vars.py +++ b/pkgs/clan-cli/tests/test_vars.py @@ -1,6 +1,8 @@ import os from collections import defaultdict +from collections.abc import Callable from pathlib import Path +from typing import Any import pytest from age_keys import SopsSetup @@ -14,7 +16,7 @@ def def_value() -> defaultdict: # allows defining nested dictionary in a single line -nested_dict = lambda: defaultdict(def_value) +nested_dict: Callable[[], dict[str, Any]] = lambda: defaultdict(def_value) @pytest.mark.impure diff --git a/pkgs/webview-ui/app/package-lock.json b/pkgs/webview-ui/app/package-lock.json index c1bef99a9..e0e9e98f1 100644 --- a/pkgs/webview-ui/app/package-lock.json +++ b/pkgs/webview-ui/app/package-lock.json @@ -10,8 +10,10 @@ "license": "MIT", "dependencies": { "@modular-forms/solid": "^0.21.0", + "@solid-primitives/storage": "^3.7.1", "@tanstack/solid-query": "^5.44.0", "material-icons": "^1.13.12", + "nanoid": "^5.0.7", "solid-js": "^1.8.11", "solid-toast": "^0.5.0" }, @@ -1498,6 +1500,26 @@ "solid-js": "^1.6.12" } }, + "node_modules/@solid-primitives/storage": { + "version": "3.7.1", + "resolved": "https://registry.npmjs.org/@solid-primitives/storage/-/storage-3.7.1.tgz", + "integrity": "sha512-tAmZKQg44RjDjrtWO/5hCOrktQspn/yVV0ySb7yKr7B3CVQlTQtldw3W8UetytJSD9podb9cplvvkq75fgpB1Q==", + "dependencies": { + "@solid-primitives/utils": "^6.2.3" + }, + "peerDependencies": { + "@tauri-apps/plugin-store": "*", + "solid-js": "^1.6.12" + }, + "peerDependenciesMeta": { + "@tauri-apps/plugin-store": { + "optional": true + }, + "solid-start": { + "optional": true + } + } + }, "node_modules/@solid-primitives/styles": { "version": "0.0.111", "resolved": "https://registry.npmjs.org/@solid-primitives/styles/-/styles-0.0.111.tgz", @@ -1515,7 +1537,6 @@ "version": "6.2.3", "resolved": "https://registry.npmjs.org/@solid-primitives/utils/-/utils-6.2.3.tgz", "integrity": "sha512-CqAwKb2T5Vi72+rhebSsqNZ9o67buYRdEJrIFzRXz3U59QqezuuxPsyzTSVCacwS5Pf109VRsgCJQoxKRoECZQ==", - "dev": true, "peerDependencies": { "solid-js": "^1.6.12" } @@ -4177,10 +4198,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", - "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "dev": true, + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-5.0.7.tgz", + "integrity": "sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==", "funding": [ { "type": "github", @@ -4188,10 +4208,10 @@ } ], "bin": { - "nanoid": "bin/nanoid.cjs" + "nanoid": "bin/nanoid.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": "^18 || >=20" } }, "node_modules/natural-compare": { @@ -4753,6 +4773,24 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, + "node_modules/postcss/node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/pkgs/webview-ui/app/package.json b/pkgs/webview-ui/app/package.json index 0d0f87975..c40899c91 100644 --- a/pkgs/webview-ui/app/package.json +++ b/pkgs/webview-ui/app/package.json @@ -39,8 +39,10 @@ }, "dependencies": { "@modular-forms/solid": "^0.21.0", + "@solid-primitives/storage": "^3.7.1", "@tanstack/solid-query": "^5.44.0", "material-icons": "^1.13.12", + "nanoid": "^5.0.7", "solid-js": "^1.8.11", "solid-toast": "^0.5.0" } diff --git a/pkgs/webview-ui/app/src/App.tsx b/pkgs/webview-ui/app/src/App.tsx index 4c15d3627..98061def5 100644 --- a/pkgs/webview-ui/app/src/App.tsx +++ b/pkgs/webview-ui/app/src/App.tsx @@ -1,24 +1,35 @@ import { createSignal, type Component } from "solid-js"; -import { MachineProvider } from "./Config"; import { Layout } from "./layout/layout"; import { Route, Router } from "./Routes"; import { Toaster } from "solid-toast"; +import { effect } from "solid-js/web"; +import { makePersisted } from "@solid-primitives/storage"; // Some global state const [route, setRoute] = createSignal("machines"); export { route, setRoute }; -const [currClanURI, setCurrClanURI] = createSignal(null); -export { currClanURI, setCurrClanURI }; +const [activeURI, setActiveURI] = createSignal(null); +export { activeURI, setActiveURI }; + +const [clanList, setClanList] = makePersisted(createSignal([]), { + name: "clanList", + storage: localStorage, +}); + +export { clanList, setClanList }; const App: Component = () => { + effect(() => { + if (clanList().length === 0) { + setRoute("welcome"); + } + }); return [ , - - - - - , + + + , ]; }; diff --git a/pkgs/webview-ui/app/src/Config.tsx b/pkgs/webview-ui/app/src/Config.tsx deleted file mode 100644 index e73629381..000000000 --- a/pkgs/webview-ui/app/src/Config.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { - createSignal, - createContext, - useContext, - JSXElement, - createEffect, -} from "solid-js"; -import { OperationResponse, pyApi } from "./api"; -import { currClanURI } from "./App"; - -export const makeMachineContext = () => { - const [machines, setMachines] = - createSignal>(); - const [loading, setLoading] = createSignal(false); - - pyApi.list_machines.receive((machines) => { - setLoading(false); - setMachines(machines); - }); - - createEffect(() => { - console.log("The state is now", machines()); - }); - - return [ - { loading, machines }, - { - getMachines: () => { - const clan_dir = currClanURI(); - - if (clan_dir) { - setLoading(true); - pyApi.list_machines.dispatch({ - debug: true, - flake_url: clan_dir, - }); - } - // When the gtk function sends its data the loading state will be set to false - }, - }, - ] as const; - // `as const` forces tuple type inference -}; -type MachineContextType = ReturnType; - -export const MachineContext = createContext([ - { - loading: () => false, - - // eslint-disable-next-line - machines: () => undefined, - }, - { - // eslint-disable-next-line - getMachines: () => {}, - }, -]); - -export const useMachineContext = () => useContext(MachineContext); - -export function MachineProvider(props: { children: JSXElement }) { - return ( - - {props.children} - - ); -} diff --git a/pkgs/webview-ui/app/src/Routes.tsx b/pkgs/webview-ui/app/src/Routes.tsx index 1beebdfa8..d0c8a27b2 100644 --- a/pkgs/webview-ui/app/src/Routes.tsx +++ b/pkgs/webview-ui/app/src/Routes.tsx @@ -1,17 +1,19 @@ import { Accessor, For, Match, Switch } from "solid-js"; import { MachineListView } from "./routes/machines/view"; import { colors } from "./routes/colors/view"; -import { clan } from "./routes/clan/view"; +import { CreateClan } from "./routes/clan/view"; import { HostList } from "./routes/hosts/view"; import { BlockDevicesView } from "./routes/blockdevices/view"; import { Flash } from "./routes/flash/view"; +import { Settings } from "./routes/settings"; +import { Welcome } from "./routes/welcome"; export type Route = keyof typeof routes; export const routes = { - clan: { - child: clan, - label: "Clan", + createClan: { + child: CreateClan, + label: "Create Clan", icon: "groups", }, machines: { @@ -39,6 +41,16 @@ export const routes = { label: "Colors", icon: "color_lens", }, + settings: { + child: Settings, + label: "Settings", + icon: "settings", + }, + welcome: { + child: Welcome, + label: "welcome", + icon: "settings", + }, }; interface RouterProps { diff --git a/pkgs/webview-ui/app/src/api.ts b/pkgs/webview-ui/app/src/api.ts index fd4d5dec9..ac919b440 100644 --- a/pkgs/webview-ui/app/src/api.ts +++ b/pkgs/webview-ui/app/src/api.ts @@ -1,5 +1,6 @@ import { FromSchema } from "json-schema-to-ts"; import { schema } from "@/api"; +import { nanoid } from "nanoid"; export type API = FromSchema; @@ -41,47 +42,44 @@ const operations = schema.properties; const operationNames = Object.keys(operations) as OperationNames[]; type ObserverRegistry = { - [K in OperationNames]: ((response: OperationResponse) => void)[]; + [K in OperationNames]: Record< + string, + (response: OperationResponse) => void + >; }; -const obs: ObserverRegistry = operationNames.reduce( +const registry: ObserverRegistry = operationNames.reduce( (acc, opName) => ({ ...acc, - [opName]: [], + [opName]: {}, }), {} as ObserverRegistry ); -interface ReceiveOptions { - /** - * Calls only the registered function that has the same key as used with dispatch - * - */ - fnKey: string; -} function createFunctions( operationName: K ): { dispatch: (args: OperationArgs) => void; - receive: (fn: (response: OperationResponse) => void) => void; + receive: (fn: (response: OperationResponse) => void, id: string) => void; } { return { dispatch: (args: OperationArgs) => { - // console.log( - // `Operation: ${String(operationName)}, Arguments: ${JSON.stringify(args)}` - // ); // Send the data to the gtk app window.webkit.messageHandlers.gtk.postMessage({ method: operationName, data: args, }); }, - receive: ( - fn: (response: OperationResponse) => void - // options?: ReceiveOptions - ) => { - obs[operationName].push(fn); + receive: (fn: (response: OperationResponse) => void, id: string) => { + // @ts-expect-error: This should work although typescript doesn't let us write + registry[operationName][id] = fn; + window.clan[operationName] = (s: string) => { - obs[operationName].forEach((f) => deserialize(f)(s)); + const f = (response: OperationResponse) => { + if (response.op_key === id) { + registry[operationName][id](response); + } + }; + deserialize(f)(s); }; }, }; @@ -90,16 +88,51 @@ function createFunctions( type PyApi = { [K in OperationNames]: { dispatch: (args: OperationArgs) => void; - receive: (fn: (response: OperationResponse) => void) => void; + receive: (fn: (response: OperationResponse) => void, id: string) => void; }; }; +function download(filename: string, text: string) { + const element = document.createElement("a"); + element.setAttribute( + "href", + "data:text/plain;charset=utf-8," + encodeURIComponent(text) + ); + element.setAttribute("download", filename); + + element.style.display = "none"; + document.body.appendChild(element); + + element.click(); + + document.body.removeChild(element); +} + +export const callApi = ( + method: K, + args: OperationArgs +) => { + return new Promise>((resolve, reject) => { + const id = nanoid(); + pyApi[method].receive((response) => { + if (response.status === "error") { + reject(response); + } + resolve(response); + }, id); + + pyApi[method].dispatch({ ...args, op_key: id }); + }); +}; + const deserialize = (fn: (response: T) => void) => (str: string) => { try { fn(JSON.parse(str) as T); } catch (e) { + console.log("Error parsing JSON: ", e); + console.log({ download: () => download("error.json", str) }); console.error(str); alert(`Error parsing JSON: ${e}`); } diff --git a/pkgs/webview-ui/app/src/components/MachineListItem.tsx b/pkgs/webview-ui/app/src/components/MachineListItem.tsx index bb779f6ae..05ee9b61f 100644 --- a/pkgs/webview-ui/app/src/components/MachineListItem.tsx +++ b/pkgs/webview-ui/app/src/components/MachineListItem.tsx @@ -1,6 +1,5 @@ import { Match, Show, Switch, createSignal } from "solid-js"; import { ErrorData, SuccessData, pyApi } from "../api"; -import { currClanURI } from "../App"; type MachineDetails = SuccessData<"list_machines">["data"][string]; @@ -23,51 +22,51 @@ const [deploymentInfo, setDeploymentInfo] = createSignal({}); const [errors, setErrors] = createSignal({}); -pyApi.show_machine_hardware_info.receive((r) => { - const { op_key } = r; - if (r.status === "error") { - console.error(r.errors); - if (op_key) { - setHwInfo((d) => ({ ...d, [op_key]: { system: null } })); - } - return; - } - if (op_key) { - setHwInfo((d) => ({ ...d, [op_key]: r.data })); - } -}); +// pyApi.show_machine_hardware_info.receive((r) => { +// const { op_key } = r; +// if (r.status === "error") { +// console.error(r.errors); +// if (op_key) { +// setHwInfo((d) => ({ ...d, [op_key]: { system: null } })); +// } +// return; +// } +// if (op_key) { +// setHwInfo((d) => ({ ...d, [op_key]: r.data })); +// } +// }); -pyApi.show_machine_deployment_target.receive((r) => { - const { op_key } = r; - if (r.status === "error") { - console.error(r.errors); - if (op_key) { - setDeploymentInfo((d) => ({ ...d, [op_key]: null })); - } - return; - } - if (op_key) { - setDeploymentInfo((d) => ({ ...d, [op_key]: r.data })); - } -}); +// pyApi.show_machine_deployment_target.receive((r) => { +// const { op_key } = r; +// if (r.status === "error") { +// console.error(r.errors); +// if (op_key) { +// setDeploymentInfo((d) => ({ ...d, [op_key]: null })); +// } +// return; +// } +// if (op_key) { +// setDeploymentInfo((d) => ({ ...d, [op_key]: r.data })); +// } +// }); export const MachineListItem = (props: MachineListItemProps) => { const { name, info } = props; - const clan_dir = currClanURI(); - if (clan_dir) { - pyApi.show_machine_hardware_info.dispatch({ - op_key: name, - clan_dir, - machine_name: name, - }); + // const clan_dir = currClanURI(); + // if (clan_dir) { + // pyApi.show_machine_hardware_info.dispatch({ + // op_key: name, + // clan_dir, + // machine_name: name, + // }); - pyApi.show_machine_deployment_target.dispatch({ - op_key: name, - clan_dir, - machine_name: name, - }); - } + // pyApi.show_machine_deployment_target.dispatch({ + // op_key: name, + // clan_dir, + // machine_name: name, + // }); + // } return (
  • diff --git a/pkgs/webview-ui/app/src/index.css b/pkgs/webview-ui/app/src/index.css index 0fd274ce6..169082015 100644 --- a/pkgs/webview-ui/app/src/index.css +++ b/pkgs/webview-ui/app/src/index.css @@ -3,3 +3,9 @@ @tailwind base; @tailwind components; @tailwind utilities; + + +html { + overflow-x: hidden; + overflow-y: scroll; +} \ No newline at end of file diff --git a/pkgs/webview-ui/app/src/layout/header.tsx b/pkgs/webview-ui/app/src/layout/header.tsx index a2b81d56d..badb57a34 100644 --- a/pkgs/webview-ui/app/src/layout/header.tsx +++ b/pkgs/webview-ui/app/src/layout/header.tsx @@ -1,4 +1,4 @@ -import { currClanURI } from "../App"; +import { activeURI, setRoute } from "../App"; export const Header = () => { return ( @@ -14,12 +14,12 @@ export const Header = () => {
    - -
    diff --git a/pkgs/webview-ui/app/src/layout/layout.tsx b/pkgs/webview-ui/app/src/layout/layout.tsx index ab7942626..66bea404d 100644 --- a/pkgs/webview-ui/app/src/layout/layout.tsx +++ b/pkgs/webview-ui/app/src/layout/layout.tsx @@ -1,13 +1,17 @@ -import { Component, JSXElement } from "solid-js"; +import { Component, JSXElement, Show } from "solid-js"; import { Header } from "./header"; import { Sidebar } from "../Sidebar"; import { route, setRoute } from "../App"; +import { effect } from "solid-js/web"; interface LayoutProps { children: JSXElement; } export const Layout: Component = (props) => { + effect(() => { + console.log(route()); + }); return ( <>
    @@ -17,11 +21,16 @@ export const Layout: Component = (props) => { class="drawer-toggle hidden" />
    -
    + +
    + {props.children}
    -
    +