Webview: init 'open clan' workflow

This commit is contained in:
Johannes Kirschbauer
2024-06-11 16:28:02 +02:00
parent b4a3b2937a
commit 6532e2acff
9 changed files with 288 additions and 15 deletions

View File

@@ -1,10 +1,16 @@
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";
export type Route = keyof typeof routes;
export const routes = {
clan: {
child: clan,
label: "Clan",
icon: "groups",
},
machines: {
child: MachineListView,
label: "Machines",

View File

@@ -87,11 +87,4 @@ operationNames.forEach((opName) => {
pyApi[name] = createFunctions(name);
});
pyApi.open_file.receive((r) => {
const { status } = r;
if (status === "error") return console.error(r.errors);
console.log(r.data);
alert(r.data);
});
export { pyApi };

View File

@@ -0,0 +1,161 @@
import { OperationResponse, pyApi } from "@/src/api";
import {
For,
JSX,
Match,
Show,
Switch,
createEffect,
createSignal,
} from "solid-js";
import cx from "classnames";
interface ClanDetailsProps {
directory: string;
}
interface MetaFieldsProps {
meta: ClanMeta;
actions: JSX.Element;
editable?: boolean;
directory?: string;
}
const fn = (e: SubmitEvent) => {
e.preventDefault();
console.log(e.currentTarget);
};
export const EditMetaFields = (props: MetaFieldsProps) => {
const { meta, editable, actions, directory } = props;
const [editing, setEditing] = createSignal<
keyof MetaFieldsProps["meta"] | null
>(null);
return (
<div class="card card-compact w-96 bg-base-100 shadow-xl">
<figure>
<img
src="https://www.shutterstock.com/image-vector/modern-professional-ninja-mascot-logo-260nw-1729854862.jpg"
alt="Clan Logo"
/>
</figure>
<div class="card-body">
<form onSubmit={fn}>
<h2 class="card-title justify-between">
<input
classList={{
[cx("text-slate-600")]: editing() !== "name",
}}
readOnly={editing() !== "name"}
class="w-full"
autofocus
onBlur={() => setEditing(null)}
type="text"
value={meta?.name}
onInput={(e) => {
console.log(e.currentTarget.value);
}}
/>
<Show when={editable}>
<button class="btn btn-square btn-ghost btn-sm">
<span
class="material-icons"
onClick={() => {
if (editing() !== "name") setEditing("name");
else {
setEditing(null);
}
}}
>
<Show when={editing() !== "name"} fallback="check">
edit
</Show>
</span>
</button>
</Show>
</h2>
<div class="flex gap-1 align-middle leading-8">
<i class="material-icons">description</i>
<span>{meta?.description || "No description"}</span>
</div>
<Show when={directory}>
<div class="flex gap-1 align-middle leading-8">
<i class="material-icons">folder</i>
<span>{directory}</span>
</div>
</Show>
{actions}
</form>
</div>
</div>
);
};
type ClanMeta = Extract<
OperationResponse<"show_clan_meta">,
{ status: "success" }
>["data"];
export const ClanDetails = (props: ClanDetailsProps) => {
const { directory } = props;
const [, setLoading] = createSignal(false);
const [errors, setErrors] = createSignal<
| Extract<
OperationResponse<"show_clan_meta">,
{ status: "error" }
>["errors"]
| null
>(null);
const [data, setData] = createSignal<ClanMeta>();
const loadMeta = () => {
pyApi.show_clan_meta.dispatch({ uri: directory });
setLoading(true);
};
createEffect(() => {
loadMeta();
pyApi.show_clan_meta.receive((response) => {
setLoading(false);
if (response.status === "error") {
setErrors(response.errors);
return console.error(response.errors);
}
setData(response.data);
});
});
return (
<Switch fallback={"loading"}>
<Match when={data()}>
<EditMetaFields
directory={directory}
// @ts-expect-error: TODO: figure out how solid allows type narrowing this
meta={data()}
actions={
<div class="card-actions justify-between">
<button class="btn btn-link" onClick={() => loadMeta()}>
Refresh
</button>
<button class="btn btn-primary">Open</button>
</div>
}
/>
</Match>
<Match when={errors()}>
<button class="btn btn-link" onClick={() => loadMeta()}>
Retry
</button>
<For each={errors()}>
{(item) => (
<div class="flex flex-col gap-3">
<span class="bg-red-400 text-white">{item.message}</span>
<span class="bg-red-400 text-white">{item.description}</span>
<span class="bg-red-400 text-white">{item.location}</span>
</div>
)}
</For>
</Match>
</Switch>
);
};

View File

@@ -0,0 +1,82 @@
import { pyApi } from "@/src/api";
import { Match, Switch, createEffect, createSignal } from "solid-js";
import toast from "solid-toast";
import { ClanDetails, EditMetaFields } from "./clanDetails";
export const clan = () => {
const [mode, setMode] = createSignal<"init" | "open" | "create">("init");
const [clanDir, setClanDir] = createSignal<string | null>(null);
createEffect(() => {
console.log(mode());
});
return (
<div>
<Switch fallback={"invalid"}>
<Match when={mode() === "init"}>
<div class="flex gap-2">
<button class="btn btn-square" onclick={() => setMode("create")}>
<span class="material-icons">add</span>
</button>
<button
class="btn btn-square"
onclick={() => {
pyApi.open_file.dispatch({
file_request: {
mode: "select_folder",
title: "Open Clan",
},
});
pyApi.open_file.receive((r) => {
// There are two error cases to handle
if (r.status !== "success") {
console.error(r.errors);
toast.error("Error opening clan");
return;
}
// User didn't select anything
if (!r.data) {
setMode("init");
return;
}
setClanDir(r.data);
setMode("open");
});
}}
>
<span class="material-icons">folder_open</span>
</button>
</div>
</Match>
<Match when={mode() === "open"}>
<ClanDetails directory={clanDir() || ""} />
</Match>
<Match when={mode() === "create"}>
<EditMetaFields
actions={
<div class="card-actions justify-end">
<button
class="btn btn-primary"
onClick={() => {
pyApi.open_file.dispatch({
file_request: { mode: "save" },
});
}}
>
Save
</button>
</div>
}
meta={{
name: "New Clan",
description: "nice description",
icon: "select icon",
}}
editable
/>
</Match>
</Switch>
</div>
);
};