Merge pull request 'UI/treewide: replace all {button,icon} component, other minor fixes' (#2503) from hsjobeki/clan-core:hsjobeki-main into main

This commit is contained in:
clan-bot
2024-11-27 09:13:09 +00:00
27 changed files with 287 additions and 321 deletions

View File

@@ -230,10 +230,18 @@ def construct_value(
raise ClanError(msg, location=f"{loc}") raise ClanError(msg, location=f"{loc}")
return field_value return field_value
# Enums
if origin is Enum: if origin is Enum:
if field_value not in origin.__members__: if field_value not in origin.__members__:
msg = f"Expected one of {', '.join(origin.__members__)}, got {field_value}" msg = f"Expected one of {', '.join(origin.__members__)}, got {field_value}"
raise ClanError(msg, location=f"{loc}") raise ClanError(msg, location=f"{loc}")
return origin.__members__[field_value] # type: ignore
if isinstance(t, type) and issubclass(t, Enum):
if field_value not in t.__members__:
msg = f"Expected one of {', '.join(t.__members__)}, got {field_value}"
raise ClanError(msg, location=f"{loc}")
return t.__members__[field_value] # type: ignore
if origin is Annotated: if origin is Annotated:
(base_type,) = get_args(t) (base_type,) = get_args(t)

View File

@@ -163,7 +163,7 @@ class ClanError(Exception):
exception_msg += self.msg exception_msg += self.msg
if self.description: if self.description:
exception_msg = f" - {self.description}" exception_msg += f" - {self.description}"
super().__init__(exception_msg) super().__init__(exception_msg)

View File

@@ -102,8 +102,11 @@ class Machine:
or self.deployment.get("deploymentAddress") or self.deployment.get("deploymentAddress")
) )
if val is None: if val is None:
msg = f"the 'clan.core.networking.targetHost' nixos option is not set for machine '{self.name}'" msg = f"'TargetHost' is not set for machine '{self.name}'"
raise ClanError(msg) raise ClanError(
msg,
description="See https://docs.clan.lol/getting-started/deploy/#setting-the-target-host for more information.",
)
return val return val
@property @property

View File

@@ -35,6 +35,8 @@ import {
import cx from "classnames"; import cx from "classnames";
import { Label } from "../base/label"; import { Label } from "../base/label";
import { SelectInput } from "../fields/Select"; import { SelectInput } from "../fields/Select";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
function generateDefaults(schema: JSONSchema7): unknown { function generateDefaults(schema: JSONSchema7): unknown {
switch (schema.type) { switch (schema.type) {
@@ -385,15 +387,25 @@ export function ListValueDisplay<T extends FieldValues, R extends ResponseData>(
<div class="flex w-full items-end gap-2 px-4"> <div class="flex w-full items-end gap-2 px-4">
{props.children} {props.children}
<div class="ml-4 min-w-fit pb-4"> <div class="ml-4 min-w-fit pb-4">
<button class="btn" onClick={moveItemBy(1)} disabled={topMost()}> <Button
variant="light"
</button> onClick={moveItemBy(1)}
<button class="btn" onClick={moveItemBy(-1)} disabled={bottomMost()}> disabled={topMost()}
startIcon={<Icon icon="ArrowBottom" />}
</button> class="h-12"
<button class="btn btn-error" onClick={removeItem}> ></Button>
x <Button
</button> variant="light"
onClick={moveItemBy(-1)}
disabled={bottomMost()}
class="h-12"
startIcon={<Icon icon="ArrowTop" />}
></Button>
<Button
class="h-12"
startIcon={<Icon icon="Trash" />}
onClick={removeItem}
></Button>
</div> </div>
</div> </div>
</div> </div>
@@ -641,7 +653,14 @@ export function ArrayFields<T extends FieldValues, R extends ResponseData>(
}} }}
// Button for adding new items // Button for adding new items
components={{ components={{
before: <button class="btn">Add </button>, before: (
<Button
variant="light"
endIcon={<Icon icon={"Plus"} />}
>
Add
</Button>
),
}} }}
// Add the new item to the FieldArray // Add the new item to the FieldArray
handleSubmit={(values, event) => { handleSubmit={(values, event) => {
@@ -827,8 +846,10 @@ export function ObjectFields<T extends FieldValues, R extends ResponseData>(
<span class="text-xl font-semibold"> <span class="text-xl font-semibold">
{key} {key}
</span> </span>
<button <Button
class="btn btn-warning btn-sm ml-auto" variant="light"
class="ml-auto"
size="s"
type="button" type="button"
onClick={(_e) => { onClick={(_e) => {
const copy = { const copy = {
@@ -845,8 +866,8 @@ export function ObjectFields<T extends FieldValues, R extends ResponseData>(
); );
}} }}
> >
Remove <Icon icon="Trash" />
</button> </Button>
</div> </div>
), ),
}} }}

View File

@@ -1,73 +0,0 @@
import { For, Show } from "solid-js";
import { activeURI } from "./App";
import { createQuery } from "@tanstack/solid-query";
import { callApi } from "./api";
import { A, RouteSectionProps } from "@solidjs/router";
import { AppRoute, routes } from "./index";
export const Sidebar = (props: RouteSectionProps) => {
const clanQuery = createQuery(() => ({
queryKey: [activeURI(), "meta"],
queryFn: async () => {
const curr = activeURI();
if (curr) {
const result = await callApi("show_clan_meta", { uri: curr });
if (result.status === "error") throw new Error("Failed to fetch data");
return result.data;
}
},
}));
return (
<aside class="w-80 rounded-xl border border-slate-900 bg-slate-800 pb-10">
<div class="m-4 flex flex-col text-center capitalize text-white">
<span class="text-lg">{clanQuery.data?.name}</span>
<span class="text-sm">{clanQuery.data?.description}</span>
<RouteMenu class="menu px-4 py-2" routes={routes} />
</div>
</aside>
);
};
const RouteMenu = (props: {
class?: string;
routes: AppRoute[];
prefix?: string;
}) => (
<ul class={props?.class}>
<For each={props.routes.filter((r) => !r.hidden)}>
{(route) => (
<li>
<Show
when={route.children}
fallback={
<A href={[props.prefix, route.path].filter(Boolean).join("")}>
<button class="text-white">
{route.icon && (
<span class="material-icons">{route.icon}</span>
)}
{route.label}
</button>
</A>
}
>
{(children) => (
<details id={`disclosure-${route.label}`} open={true}>
<summary class="group">
{route.icon && (
<span class="material-icons">{route.icon}</span>
)}
{route.label}
</summary>
<RouteMenu
routes={children()}
prefix={[props.prefix, route.path].filter(Boolean).join("")}
/>
</details>
)}
</Show>
</li>
)}
</For>
</ul>
);

View File

@@ -9,7 +9,7 @@ export const BackButton = () => {
variant="light" variant="light"
class="w-fit" class="w-fit"
onClick={() => navigate(-1)} onClick={() => navigate(-1)}
startIcon={<Icon icon="ArrowLeft" />} startIcon={<Icon icon="CaretRight" />}
></Button> ></Button>
); );
}; };

View File

@@ -5,6 +5,7 @@ import { activeURI } from "../App";
import toast from "solid-toast"; import toast from "solid-toast";
import { A, useNavigate } from "@solidjs/router"; import { A, useNavigate } from "@solidjs/router";
import { RndThumbnail } from "./noiseThumbnail"; import { RndThumbnail } from "./noiseThumbnail";
import Icon from "./icon";
type MachineDetails = SuccessQuery<"list_inventory_machines">["data"][string]; type MachineDetails = SuccessQuery<"list_inventory_machines">["data"][string];
@@ -138,7 +139,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
<div> <div>
<Menu <Menu
popoverid={`menu-${props.name}`} popoverid={`menu-${props.name}`}
label={<span class="material-icons">more_vert</span>} label={<Icon icon={"Expand"} />}
> >
<ul class="menu z-[1] w-52 rounded-box bg-base-100 p-2 shadow"> <ul class="menu z-[1] w-52 rounded-box bg-base-100 p-2 shadow">
<li> <li>

View File

@@ -9,6 +9,7 @@ import {
shift, shift,
} from "@floating-ui/dom"; } from "@floating-ui/dom";
import cx from "classnames"; import cx from "classnames";
import { Button } from "./button";
interface MenuProps { interface MenuProps {
/** /**
@@ -53,7 +54,8 @@ export const Menu = (props: MenuProps) => {
return ( return (
<div> <div>
<button <Button
variant="light"
popovertarget={props.popoverid} popovertarget={props.popoverid}
popovertargetaction="toggle" popovertargetaction="toggle"
ref={setReference} ref={setReference}
@@ -64,7 +66,7 @@ export const Menu = (props: MenuProps) => {
{...props.buttonProps} {...props.buttonProps}
> >
{props.label} {props.label}
</button> </Button>
<div <div
popover="auto" popover="auto"
id={props.popoverid} id={props.popoverid}

View File

@@ -6,7 +6,7 @@ export const SidebarFlyout = () => {
<div class="sidebar__flyout"> <div class="sidebar__flyout">
<div class="sidebar__flyout__inner"> <div class="sidebar__flyout__inner">
<List gapSize="small"> <List gapSize="small">
<SidebarListItem href="/settings" title="Settings" /> <SidebarListItem href="/clans" title="Settings" />
</List> </List>
</div> </div>
</div> </div>

View File

@@ -29,7 +29,7 @@ export const SidebarHeader = (props: SidebarHeader) => {
color="primary" color="primary"
inverted={true} inverted={true}
> >
{clanName.slice(0, 1)} {clanName.slice(0, 1).toUpperCase()}
</Typography> </Typography>
</div> </div>
); );

View File

@@ -8,11 +8,7 @@
/* Sidebar Structure */ /* Sidebar Structure */
.sidebar { .sidebar {
min-width: theme(width.72); @apply bg-inv-2 h-full border border-solid border-inv-2 min-w-72 rounded-xl;
height: 100%;
background-color: var(--clr-bg-inv-2);
border: 1px solid var(--clr-border-inv-2);
border-radius: theme(borderRadius.xl);
} }
.sidebar__body { .sidebar__body {

View File

@@ -61,6 +61,7 @@ export const Button = (props: ButtonProps) => {
"inline-flex items-center flex-shrink gap-2 justify-center", "inline-flex items-center flex-shrink gap-2 justify-center",
// Styles // Styles
"border border-solid", "border border-solid",
"p-4",
sizePaddings[local.size || "default"], sizePaddings[local.size || "default"],
// Colors // Colors
variantColors[local.variant || "dark"], variantColors[local.variant || "dark"],

View File

@@ -1,4 +1,4 @@
import { Component, JSX } from "solid-js"; import { Component, JSX, splitProps } from "solid-js";
import ArrowBottom from "@/icons/arrow-bottom.svg"; import ArrowBottom from "@/icons/arrow-bottom.svg";
import ArrowLeft from "@/icons/arrow-left.svg"; import ArrowLeft from "@/icons/arrow-left.svg";
import ArrowRight from "@/icons/arrow-right.svg"; import ArrowRight from "@/icons/arrow-right.svg";
@@ -81,10 +81,18 @@ const Icon: Component<IconProps> = (props) => {
Trash, Trash,
Update, Update,
}; };
const [local, iconProps] = splitProps(props, ["icon"]);
const IconComponent = icons[props.icon]; const IconComponent = icons[local.icon];
return IconComponent ? ( return IconComponent ? (
<IconComponent width={16} height={16} viewBox="0 0 48 48" /> <IconComponent
width={16}
height={16}
viewBox="0 0 48 48"
// @ts-expect-error: dont know, fix this type nit later
ref={iconProps.ref}
{...iconProps}
/>
) : null; ) : null;
}; };

View File

@@ -136,9 +136,10 @@ export const routes: AppRoute[] = [
{ {
path: "/welcome", path: "/welcome",
label: "", label: "",
hidden: true, hidden: false,
component: () => <Welcome />, component: () => <Welcome />,
}, },
{ {
path: "/api_testing", path: "/api_testing",
label: "api_testing", label: "api_testing",

View File

@@ -4,7 +4,8 @@ import { callApi } from "../api";
import { Accessor, Show } from "solid-js"; import { Accessor, Show } from "solid-js";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import ArrowBottom from "@/icons/arrow-bottom.svg"; import Icon from "../components/icon";
import { Button } from "../components/button";
interface HeaderProps { interface HeaderProps {
clan_dir: Accessor<string | null>; clan_dir: Accessor<string | null>;
@@ -28,7 +29,7 @@ export const Header = (props: HeaderProps) => {
return ( return (
<div class="navbar"> <div class="navbar">
<div class="flex-none"> <div class="flex-none">
<span class="tooltip tooltip-bottom" data-tip="Menu"> <span class="tooltip tooltip-bottom lg:hidden" data-tip="Menu">
<label <label
class="btn btn-square btn-ghost drawer-button" class="btn btn-square btn-ghost drawer-button"
for="toplevel-drawer" for="toplevel-drawer"
@@ -37,44 +38,16 @@ export const Header = (props: HeaderProps) => {
</label> </label>
</span> </span>
</div> </div>
<div class="flex-1"> <div class="flex-1"></div>
<Show when={query.isLoading && !query.data}>
<div class="skeleton mx-4 size-11 rounded-full"></div>
<span class="flex flex-col gap-2">
<div class="skeleton h-3 w-32"></div>
<div class="skeleton h-3 w-40"></div>
</span>
</Show>
<Show when={query.data}>
{(meta) => (
<div class="tooltip tooltip-right" data-tip={activeURI()}>
<div class="avatar placeholder online mx-4">
<div class="w-10 rounded-full bg-slate-700 text-3xl text-neutral-content">
<ArrowBottom />
</div>
</div>
</div>
)}
</Show>
<span class="flex flex-col">
<Show when={query.data}>
{(meta) => [
<span class="text-primary-800">{meta().name}</span>,
<span class="text-neutral">{meta()?.description}</span>,
]}
</Show>
</span>
</div>
<div class="flex-none"> <div class="flex-none">
<Show when={activeURI()}> <Show when={activeURI()}>
{(d) => ( {(d) => (
<span class="tooltip tooltip-bottom" data-tip="Clan Settings"> <span class="tooltip tooltip-bottom" data-tip="Clan Settings">
<button <Button
class="link" variant="light"
onClick={() => navigate(`/clans/${window.btoa(d())}`)} onClick={() => navigate(`/clans/${window.btoa(d())}`)}
> startIcon={<Icon icon="Settings" />}
<span class="material-icons">settings</span> ></Button>
</button>
</span> </span>
)} )}
</Show> </Show>

View File

@@ -18,14 +18,14 @@ export const Layout: Component<RouteSectionProps> = (props) => {
}); });
return ( return (
<div class="h-screen w-full p-4 bg-def-3"> <div class="h-screen w-full p-4 bg-def-2">
<div class="drawer h-full lg:drawer-open "> <div class="drawer h-full lg:drawer-open ">
<input <input
id="toplevel-drawer" id="toplevel-drawer"
type="checkbox" type="checkbox"
class="drawer-toggle hidden" class="drawer-toggle hidden"
/> />
<div class="drawer-content overflow-x-hidden overflow-y-scroll"> <div class="drawer-content overflow-x-hidden overflow-y-scroll p-2">
<Show when={props.location.pathname !== "welcome"}> <Show when={props.location.pathname !== "welcome"}>
<Header clan_dir={activeURI} /> <Header clan_dir={activeURI} />
</Show> </Show>

View File

@@ -2,6 +2,8 @@ import { callApi } from "@/src/api";
import { Component, For, Show } from "solid-js"; import { Component, For, Show } from "solid-js";
import { createQuery } from "@tanstack/solid-query"; import { createQuery } from "@tanstack/solid-query";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
export const BlockDevicesView: Component = () => { export const BlockDevicesView: Component = () => {
const { const {
@@ -22,9 +24,10 @@ export const BlockDevicesView: Component = () => {
return ( return (
<div> <div>
<div class="tooltip tooltip-bottom" data-tip="Refresh"> <div class="tooltip tooltip-bottom" data-tip="Refresh">
<button class="btn btn-ghost" onClick={() => loadDevices()}> <Button
<span class="material-icons ">refresh</span> onClick={() => loadDevices()}
</button> startIcon={<Icon icon="Reload" />}
></Button>
</div> </div>
<div class="flex max-w-screen-lg flex-col gap-4"> <div class="flex max-w-screen-lg flex-col gap-4">
{isFetching ? ( {isFetching ? (

View File

@@ -10,6 +10,8 @@ import toast from "solid-toast";
import { setActiveURI, setClanList } from "@/src/App"; import { setActiveURI, setClanList } from "@/src/App";
import { TextInput } from "@/src/components/TextInput"; import { TextInput } from "@/src/components/TextInput";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
type CreateForm = Meta & { type CreateForm = Meta & {
template: string; template: string;
@@ -176,13 +178,13 @@ export const CreateClan = () => {
</Field> </Field>
{ {
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<button <Button
class="btn btn-primary"
type="submit" type="submit"
disabled={formStore.submitting} disabled={formStore.submitting}
endIcon={<Icon icon="Plus" />}
> >
Create Create
</button> </Button>
</div> </div>
} }
</div> </div>

View File

@@ -1,16 +1,7 @@
import { import { callApi, ClanServiceInstance, SuccessQuery } from "@/src/api";
callApi,
ClanService,
ClanServiceInstance,
SuccessQuery,
} from "@/src/api";
import { BackButton } from "@/src/components/BackButton"; import { BackButton } from "@/src/components/BackButton";
import { useParams } from "@solidjs/router"; import { useParams } from "@solidjs/router";
import { import { createQuery, useQueryClient } from "@tanstack/solid-query";
createQuery,
QueryClient,
useQueryClient,
} from "@tanstack/solid-query";
import { createSignal, For, Match, Switch } from "solid-js"; import { createSignal, For, Match, Switch } from "solid-js";
import { Show } from "solid-js"; import { Show } from "solid-js";
import { import {
@@ -25,6 +16,8 @@ import {
import { TextInput } from "@/src/components/TextInput"; import { TextInput } from "@/src/components/TextInput";
import toast from "solid-toast"; import toast from "solid-toast";
import { get_single_service, set_single_service } from "@/src/api/inventory"; import { get_single_service, set_single_service } from "@/src/api/inventory";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
interface AdminModuleFormProps { interface AdminModuleFormProps {
admin: AdminData; admin: AdminData;
@@ -157,13 +150,12 @@ const EditClanForm = (props: EditClanFormProps) => {
</Field> </Field>
{ {
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<button <Button
class="btn btn-primary"
type="submit" type="submit"
disabled={formStore.submitting || !formStore.dirty} disabled={formStore.submitting || !formStore.dirty}
> >
Save Save
</button> </Button>
</div> </div>
} }
</div> </div>
@@ -307,41 +299,39 @@ const AdminModuleForm = (props: AdminModuleFormProps) => {
</> </>
)} )}
</Field> </Field>
<button <Button
class="btn btn-ghost col-span-1 self-end" variant="light"
class="col-span-1 self-end"
startIcon={<Icon icon="Trash" />}
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
setKeys((c) => c.filter((_, i) => i !== idx())); setKeys((c) => c.filter((_, i) => i !== idx()));
setValue(formStore, `allowedKeys.${idx()}.name`, ""); setValue(formStore, `allowedKeys.${idx()}.name`, "");
setValue(formStore, `allowedKeys.${idx()}.value`, ""); setValue(formStore, `allowedKeys.${idx()}.value`, "");
}} }}
> ></Button>
<span class="material-icons">delete</span>
</button>
</> </>
)} )}
</For> </For>
<div class="my-2 flex w-full gap-2"> <div class="my-2 flex w-full gap-2">
<button <Button
class="btn btn-square btn-ghost" variant="light"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
setKeys((c) => [...c, 1]); setKeys((c) => [...c, 1]);
}} }}
> startIcon={<Icon icon="Plus" />}
<span class="material-icons">add</span> ></Button>
</button>
</div> </div>
</div> </div>
{ {
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<button <Button
class="btn btn-primary"
type="submit" type="submit"
disabled={formStore.submitting || !formStore.dirty} disabled={formStore.submitting || !formStore.dirty}
> >
Save Save
</button> </Button>
</div> </div>
} }
</div> </div>

View File

@@ -6,6 +6,8 @@ import { useFloating } from "@/src/floating";
import { autoUpdate, flip, hide, offset, shift } from "@floating-ui/dom"; import { autoUpdate, flip, hide, offset, shift } from "@floating-ui/dom";
import { useNavigate, A } from "@solidjs/router"; import { useNavigate, A } from "@solidjs/router";
import { registerClan } from "@/src/hooks"; import { registerClan } from "@/src/hooks";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
interface ClanItemProps { interface ClanItemProps {
clan_dir: string; clan_dir: string;
@@ -61,33 +63,32 @@ const ClanItem = (props: ClanItemProps) => {
<div class="stat"> <div class="stat">
<div class="stat-figure text-primary-800"> <div class="stat-figure text-primary-800">
<div class="join"> <div class="join">
<button <Button
class="join-item btn-sm" size="s"
variant="light"
class="join-item"
onClick={() => navigate(`/clans/${window.btoa(clan_dir)}`)} onClick={() => navigate(`/clans/${window.btoa(clan_dir)}`)}
> endIcon={<Icon icon="Settings" />}
<span class="material-icons">edit</span> ></Button>
</button> <Button
<button size="s"
class=" join-item btn-sm" variant="light"
classList={{ class="join-item "
"btn btn-ghost btn-outline": activeURI() !== clan_dir,
"badge badge-primary": activeURI() === clan_dir,
}}
disabled={activeURI() === clan_dir}
onClick={() => { onClick={() => {
setActiveURI(clan_dir); setActiveURI(clan_dir);
}} }}
> >
{activeURI() === clan_dir ? "active" : "select"} {activeURI() === clan_dir ? "active" : "select"}
</button> </Button>
<button <Button
size="s"
variant="light"
popovertarget={`clan-delete-popover-${clan_dir}`} popovertarget={`clan-delete-popover-${clan_dir}`}
popovertargetaction="toggle" popovertargetaction="toggle"
ref={setReference} ref={setReference}
class="btn btn-ghost btn-outline join-item btn-sm" class="btn btn-ghost btn-outline join-item"
> endIcon={<Icon icon="Trash" />}
Remove ></Button>
</button>
<div <div
popover="auto" popover="auto"
role="tooltip" role="tooltip"
@@ -100,9 +101,14 @@ const ClanItem = (props: ClanItemProps) => {
}} }}
class="m-0 bg-transparent" class="m-0 bg-transparent"
> >
<button class="btn bg-[#ffdd2c]" onClick={handleRemove}> <Button
size="s"
onClick={handleRemove}
variant="dark"
endIcon={<Icon icon="Trash" />}
>
Remove from App Remove from App
</button> </Button>
</div> </div>
</div> </div>
</div> </div>
@@ -139,19 +145,19 @@ export const ClanList = () => {
<div class="label-text text-2xl text-neutral">Registered Clans</div> <div class="label-text text-2xl text-neutral">Registered Clans</div>
<div class="flex gap-2"> <div class="flex gap-2">
<span class="tooltip tooltip-top" data-tip="Register clan"> <span class="tooltip tooltip-top" data-tip="Register clan">
<button class="btn btn-square btn-ghost" onClick={registerClan}> <Button
<span class="material-icons">post_add</span> variant="light"
</button> onClick={registerClan}
startIcon={<Icon icon="List" />}
></Button>
</span> </span>
<span class="tooltip tooltip-top" data-tip="Create new clan"> <span class="tooltip tooltip-top" data-tip="Create new clan">
<button <Button
class="btn btn-square btn-ghost"
onClick={() => { onClick={() => {
navigate("create"); navigate("create");
}} }}
> startIcon={<Icon icon="Plus" />}
<span class="material-icons">add</span> ></Button>
</button>
</span> </span>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,7 @@
import { callApi } from "@/src/api"; import { callApi } from "@/src/api";
import { Button } from "@/src/components/button";
import { FileInput } from "@/src/components/FileInput"; import { FileInput } from "@/src/components/FileInput";
import Icon from "@/src/components/icon";
import { SelectInput } from "@/src/components/SelectInput"; import { SelectInput } from "@/src/components/SelectInput";
import { TextInput } from "@/src/components/TextInput"; import { TextInput } from "@/src/components/TextInput";
import { import {
@@ -209,15 +211,15 @@ export const Flash = () => {
{(field, props) => ( {(field, props) => (
<SelectInput <SelectInput
topRightLabel={ topRightLabel={
<button <Button
class="btn btn-ghost btn-sm" size="s"
variant="light"
onClick={(e) => { onClick={(e) => {
e.preventDefault(); e.preventDefault();
deviceQuery.refetch(); deviceQuery.refetch();
}} }}
> startIcon={<Icon icon="Reload" />}
<span class="material-icons text-sm">refresh</span> ></Button>
</button>
} }
formStore={formStore} formStore={formStore}
selectProps={props} selectProps={props}
@@ -285,17 +287,19 @@ export const Flash = () => {
adornment={{ adornment={{
position: "end", position: "end",
content: ( content: (
<button <Button
variant="light"
type="button" type="button"
class="flex justify-center opacity-70" class="flex justify-center opacity-70"
onClick={() => togglePasswordVisibility(index())} onClick={() => togglePasswordVisibility(index())}
> startIcon={
<span class="material-icons"> passwordVisibility()[index()] ? (
{passwordVisibility()[index()] <Icon icon="EyeClose" />
? "visibility_off" ) : (
: "visibility"} <Icon icon="EyeOpen" />
</span> )
</button> }
></Button>
), ),
}} }}
required required
@@ -304,25 +308,27 @@ export const Flash = () => {
)} )}
</Field> </Field>
<div class="col-span-1 self-end"> <div class="col-span-1 self-end">
<button <Button
type="button" type="button"
class="btn btn-ghost " variant="light"
class="h-12"
onClick={() => removeWifiNetwork(index())} onClick={() => removeWifiNetwork(index())}
> startIcon={<Icon icon="Trash" />}
<span class="material-icons">delete</span> ></Button>
</button>
</div> </div>
</div> </div>
)} )}
</For> </For>
<div class=""> <div class="">
<button <Button
type="button" type="button"
class="btn btn-ghost btn-sm" size="s"
variant="light"
onClick={addWifiNetwork} onClick={addWifiNetwork}
startIcon={<Icon icon="Plus" />}
> >
<span class="material-icons">add</span>Add WiFi Network Add WiFi Network
</button> </Button>
</div> </div>
</div> </div>
@@ -434,18 +440,20 @@ export const Flash = () => {
<hr></hr> <hr></hr>
<div class="mt-2 flex justify-end pt-2"> <div class="mt-2 flex justify-end pt-2">
<button <Button
class="btn btn-error self-end" class="self-end"
type="submit" type="submit"
disabled={formStore.submitting} disabled={formStore.submitting}
> startIcon={
{formStore.submitting ? ( formStore.submitting ? (
<span class="loading loading-spinner"></span> <Icon icon="Load" />
) : ( ) : (
<span class="material-icons">bolt</span> <Icon icon="Flash" />
)} )
}
>
{formStore.submitting ? "Flashing..." : "Flash Installer"} {formStore.submitting ? "Flashing..." : "Flash Installer"}
</button> </Button>
</div> </div>
</Form> </Form>
</div> </div>

View File

@@ -1,5 +1,7 @@
import { type Component, createSignal, For, Show } from "solid-js"; import { type Component, createSignal, For, Show } from "solid-js";
import { OperationResponse, pyApi } from "@/src/api"; import { OperationResponse, pyApi } from "@/src/api";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
type ServiceModel = Extract< type ServiceModel = Extract<
OperationResponse<"show_mdns">, OperationResponse<"show_mdns">,
@@ -12,12 +14,11 @@ export const HostList: Component = () => {
return ( return (
<div> <div>
<div class="tooltip tooltip-bottom" data-tip="Refresh install targets"> <div class="tooltip tooltip-bottom" data-tip="Refresh install targets">
<button <Button
class="btn btn-ghost" variant="light"
onClick={() => pyApi.show_mdns.dispatch({})} onClick={() => pyApi.show_mdns.dispatch({})}
> startIcon={<Icon icon="Update" />}
<span class="material-icons ">refresh</span> ></Button>
</button>
</div> </div>
<div class="flex flex-wrap gap-2"> <div class="flex flex-wrap gap-2">
<Show when={services()}> <Show when={services()}>

View File

@@ -1,5 +1,7 @@
import { callApi, OperationArgs } from "@/src/api"; import { callApi, OperationArgs } from "@/src/api";
import { activeURI } from "@/src/App"; import { activeURI } from "@/src/App";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
import { TextInput } from "@/src/components/TextInput"; import { TextInput } from "@/src/components/TextInput";
import { createForm, required, reset } from "@modular-forms/solid"; import { createForm, required, reset } from "@modular-forms/solid";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
@@ -108,26 +110,21 @@ export function CreateMachine() {
)} )}
</Field> </Field>
<div class="mt-12 flex justify-end"> <div class="mt-12 flex justify-end">
<button <Button
class="btn btn-primary"
type="submit" type="submit"
classList={{ disabled={formStore.submitting}
"btn-disabled": formStore.submitting, startIcon={
}} formStore.submitting ? (
> <Icon icon="Load" />
<Switch ) : (
fallback={ <Icon icon="Plus" />
<> )
<span class="loading loading-spinner loading-sm"></span>
Creating
</>
} }
> >
<Match when={!formStore.submitting}> <Switch fallback={<>Creating</>}>
<span class="material-icons">add</span>Create <Match when={!formStore.submitting}>Create</Match>
</Match>
</Switch> </Switch>
</button> </Button>
</div> </div>
</Form> </Form>
</div> </div>

View File

@@ -3,7 +3,9 @@ import { set_single_disk_id } from "@/src/api/disk";
import { get_iwd_service } from "@/src/api/wifi"; import { get_iwd_service } from "@/src/api/wifi";
import { activeURI } from "@/src/App"; import { activeURI } from "@/src/App";
import { BackButton } from "@/src/components/BackButton"; import { BackButton } from "@/src/components/BackButton";
import { Button } from "@/src/components/button";
import { FileInput } from "@/src/components/FileInput"; import { FileInput } from "@/src/components/FileInput";
import Icon from "@/src/components/icon";
import { SelectInput } from "@/src/components/SelectInput"; import { SelectInput } from "@/src/components/SelectInput";
import { TextInput } from "@/src/components/TextInput"; import { TextInput } from "@/src/components/TextInput";
import { selectSshKeys } from "@/src/hooks"; import { selectSshKeys } from "@/src/hooks";
@@ -192,13 +194,15 @@ const InstallMachine = (props: InstallMachineProps) => {
</Match> </Match>
</Switch> </Switch>
<div class=""> <div class="">
<button <Button
class="btn btn-ghost btn-sm btn-wide" variant="light"
size="s"
class="w-full"
onclick={generateReport} onclick={generateReport}
> >
<span class="material-icons">manage_search</span> <span class="material-icons">manage_search</span>
Generate report Generate report
</button> </Button>
</div> </div>
</div> </div>
</div> </div>
@@ -252,25 +256,32 @@ const InstallMachine = (props: InstallMachineProps) => {
<Show <Show
when={confirmDisk()} when={confirmDisk()}
fallback={ fallback={
<button <Button
class="btn btn-primary btn-wide" class="btn btn-primary btn-wide"
onClick={handleDiskConfirm} onClick={handleDiskConfirm}
disabled={!hasDisk()} disabled={!hasDisk()}
startIcon={<Icon icon="Flash" />}
> >
<span class="material-icons">check</span>
Confirm Disk Confirm Disk
</button> </Button>
} }
> >
<button class="btn btn-primary btn-wide" type="submit"> <Button
<span class="material-icons">send_to_mobile</span> class="w-full"
type="submit"
startIcon={<Icon icon="Flash" />}
>
Install Install
</button> </Button>
</Show> </Show>
<form method="dialog"> <form method="dialog">
<button onClick={() => setConfirmDisk(false)} class="btn"> <Button
variant="light"
onClick={() => setConfirmDisk(false)}
class="btn"
>
Close Close
</button> </Button>
</form> </form>
</div> </div>
</Form> </Form>
@@ -519,13 +530,12 @@ const MachineForm = (props: MachineDetailsProps) => {
{ {
<div class="card-actions justify-end"> <div class="card-actions justify-end">
<button <Button
class="btn btn-primary"
type="submit" type="submit"
disabled={formStore.submitting || !formStore.dirty} disabled={formStore.submitting || !formStore.dirty}
> >
Save Save
</button> </Button>
</div> </div>
} }
</div> </div>
@@ -550,15 +560,19 @@ const MachineForm = (props: MachineDetailsProps) => {
device. device.
</span> </span>
<div class="tooltip w-fit" data-tip="Machine must be online"> <div class="tooltip w-fit" data-tip="Machine must be online">
<button <Button
class="btn btn-primary btn-sm btn-wide" class="w-full"
disabled={!online()} // disabled={!online()}
// @ts-expect-error: This string method is not supported by ts onClick={() => {
onClick="install_modal.showModal()" const modal = document.getElementById(
"install_modal",
) as HTMLDialogElement | null;
modal?.showModal();
}}
endIcon={<Icon icon="Flash" />}
> >
<span class="material-icons">send_to_mobile</span>
Install Install
</button> </Button>
</div> </div>
<dialog id="install_modal" class="modal backdrop:bg-transparent"> <dialog id="install_modal" class="modal backdrop:bg-transparent">
@@ -577,14 +591,14 @@ const MachineForm = (props: MachineDetailsProps) => {
process. process.
</span> </span>
<div class="tooltip w-fit" data-tip="Machine must be online"> <div class="tooltip w-fit" data-tip="Machine must be online">
<button <Button
class="btn btn-primary btn-sm btn-wide" class="w-full"
disabled={!online()} disabled={!online()}
onClick={() => handleUpdate()} onClick={() => handleUpdate()}
endIcon={<Icon icon="Update" />}
> >
<span class="material-icons">update</span>
Update Update
</button> </Button>
</div> </div>
</div> </div>
</div> </div>
@@ -761,41 +775,38 @@ function WifiModule(props: MachineWifiProps) {
/> />
)} )}
</Field> </Field>
<button class="btn btn-ghost self-end"> <Button
<span variant="light"
class="material-icons" class="self-end"
onClick={(e) => { type="button"
e.preventDefault(); onClick={() => {
setNets((c) => c.filter((_, i) => i !== idx())); setNets((c) => c.filter((_, i) => i !== idx()));
setValue(formStore, `networks.${idx()}.ssid`, undefined); setValue(formStore, `networks.${idx()}.ssid`, undefined);
setValue(formStore, `networks.${idx()}.password`, undefined); setValue(formStore, `networks.${idx()}.password`, undefined);
}} }}
> startIcon={<Icon icon="Trash" />}
delete ></Button>
</span>
</button>
</div> </div>
)} )}
</For> </For>
<button <Button
class="btn btn-ghost btn-sm my-1 flex items-center justify-center" class="btn btn-ghost btn-sm my-1 flex items-center justify-center"
onClick={(e) => { onClick={(e) => {
e.preventDefault();
setNets([...nets(), 1]); setNets([...nets(), 1]);
}} }}
type="button"
startIcon={<Icon icon="Plus" />}
> >
<span class="material-icons">add</span>
Add Network Add Network
</button> </Button>
{ {
<div class="card-actions mt-4 justify-end"> <div class="card-actions mt-4 justify-end">
<button <Button
class="btn btn-primary"
type="submit" type="submit"
disabled={formStore.submitting || !formStore.dirty} disabled={formStore.submitting || !formStore.dirty}
> >
Save Save
</button> </Button>
</div> </div>
} }
</Form> </Form>

View File

@@ -9,6 +9,8 @@ import {
useQueryClient, useQueryClient,
} from "@tanstack/solid-query"; } from "@tanstack/solid-query";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
type MachinesModel = Extract< type MachinesModel = Extract<
OperationResponse<"list_inventory_machines">, OperationResponse<"list_inventory_machines">,
@@ -80,14 +82,18 @@ export const MachineListView: Component = () => {
<div> <div>
<div class="tooltip tooltip-bottom" data-tip="Open Clan"></div> <div class="tooltip tooltip-bottom" data-tip="Open Clan"></div>
<div class="tooltip tooltip-bottom" data-tip="Refresh"> <div class="tooltip tooltip-bottom" data-tip="Refresh">
<button class="btn btn-ghost" onClick={() => refresh()}> <Button
<span class="material-icons ">refresh</span> variant="light"
</button> onClick={() => refresh()}
startIcon={<Icon icon="Reload" />}
></Button>
</div> </div>
<div class="tooltip tooltip-bottom" data-tip="Create machine"> <div class="tooltip tooltip-bottom" data-tip="Create machine">
<button class="btn btn-ghost" onClick={() => navigate("create")}> <Button
<span class="material-icons ">add</span> variant="light"
</button> onClick={() => navigate("create")}
startIcon={<Icon icon="Plus" />}
></Button>
</div> </div>
<Switch> <Switch>
<Match when={inventoryQuery.isLoading}> <Match when={inventoryQuery.isLoading}>
@@ -117,12 +123,13 @@ export const MachineListView: Component = () => {
<span class="text-lg text-neutral"> <span class="text-lg text-neutral">
No machines defined yet. Click below to define one. No machines defined yet. Click below to define one.
</span> </span>
<button <Button
class="btn btn-square btn-ghost size-28 overflow-hidden p-2" variant="light"
class="size-28 overflow-hidden p-2"
onClick={() => navigate("/machines/create")} onClick={() => navigate("/machines/create")}
> >
<span class="material-icons text-6xl font-light">add</span> <span class="material-icons text-6xl font-light">add</span>
</button> </Button>
</div> </div>
</Match> </Match>
<Match when={!inventoryQuery.isLoading}> <Match when={!inventoryQuery.isLoading}>

View File

@@ -25,6 +25,8 @@ import {
SubmitHandler, SubmitHandler,
} from "@modular-forms/solid"; } from "@modular-forms/solid";
import { DynForm } from "@/src/Form/form"; import { DynForm } from "@/src/Form/form";
import { Button } from "@/src/components/button";
import Icon from "@/src/components/icon";
export const ModuleDetails = () => { export const ModuleDetails = () => {
const params = useParams(); const params = useParams();
@@ -116,10 +118,9 @@ const Details = (props: DetailsProps) => {
<SolidMarkdown>{props.data.readme}</SolidMarkdown> <SolidMarkdown>{props.data.readme}</SolidMarkdown>
</div> </div>
<div class="my-2 flex w-full gap-2"> <div class="my-2 flex w-full gap-2">
<button class="btn btn-primary" onClick={add}> <Button variant="light" onClick={add} startIcon={<Icon icon="Plus" />}>
<span class="material-icons ">add</span>
Add to Clan Add to Clan
</button> </Button>
{/* Add -> Select (required) roles, assign Machine */} {/* Add -> Select (required) roles, assign Machine */}
</div> </div>
<ModuleForm id={props.id} /> <ModuleForm id={props.id} />
@@ -192,7 +193,7 @@ export const ModuleForm = (props: { id: string }) => {
handleSubmit={handleSubmit} handleSubmit={handleSubmit}
schema={schema} schema={schema}
components={{ components={{
after: <button class="btn btn-primary">Submit</button>, after: <Button>Submit</Button>,
}} }}
/> />
</div> </div>

View File

@@ -1,4 +1,5 @@
import { setActiveURI } from "@/src/App"; import { setActiveURI } from "@/src/App";
import { Button } from "@/src/components/button";
import { registerClan } from "@/src/hooks"; import { registerClan } from "@/src/hooks";
import { useNavigate } from "@solidjs/router"; import { useNavigate } from "@solidjs/router";
@@ -11,14 +12,12 @@ export const Welcome = () => {
<h1 class="text-5xl font-bold">Welcome to Clan</h1> <h1 class="text-5xl font-bold">Welcome to Clan</h1>
<p class="py-6">Own the services you use.</p> <p class="py-6">Own the services you use.</p>
<div class="flex flex-col items-start gap-2"> <div class="flex flex-col items-start gap-2">
<button <Button class="w-full" onClick={() => navigate("/clans/create")}>
class="btn btn-primary w-full"
onClick={() => navigate("/clans/create")}
>
Build your own Build your own
</button> </Button>
<button <Button
class="link w-full text-right text-primary-800" variant="light"
class="!link w-full text-right !text-primary-800"
onClick={async () => { onClick={async () => {
const uri = await registerClan(); const uri = await registerClan();
if (uri) { if (uri) {
@@ -28,7 +27,7 @@ export const Welcome = () => {
}} }}
> >
Or select folder Or select folder
</button> </Button>
</div> </div>
</div> </div>
</div> </div>