Merge pull request 'Webview: migrate create clan form to async api' (#1757) from hsjobeki/clan-core:hsjobeki-main into main
This commit is contained in:
@@ -7,6 +7,7 @@ import { BlockDevicesView } from "./routes/blockdevices/view";
|
|||||||
import { Flash } from "./routes/flash/view";
|
import { Flash } from "./routes/flash/view";
|
||||||
import { Settings } from "./routes/settings";
|
import { Settings } from "./routes/settings";
|
||||||
import { Welcome } from "./routes/welcome";
|
import { Welcome } from "./routes/welcome";
|
||||||
|
import { Deploy } from "./routes/deploy";
|
||||||
|
|
||||||
export type Route = keyof typeof routes;
|
export type Route = keyof typeof routes;
|
||||||
|
|
||||||
@@ -51,6 +52,11 @@ export const routes = {
|
|||||||
label: "welcome",
|
label: "welcome",
|
||||||
icon: "settings",
|
icon: "settings",
|
||||||
},
|
},
|
||||||
|
deploy: {
|
||||||
|
child: Deploy,
|
||||||
|
label: "deploy",
|
||||||
|
icon: "content_copy",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
interface RouterProps {
|
interface RouterProps {
|
||||||
|
|||||||
@@ -1,6 +1,21 @@
|
|||||||
|
import { createQuery } from "@tanstack/solid-query";
|
||||||
import { activeURI, setRoute } from "../App";
|
import { activeURI, setRoute } from "../App";
|
||||||
|
import { callApi } from "../api";
|
||||||
|
import { Show } from "solid-js";
|
||||||
|
|
||||||
export const Header = () => {
|
export const Header = () => {
|
||||||
|
const { isLoading, data } = createQuery(() => ({
|
||||||
|
queryKey: [`${activeURI()}:meta`],
|
||||||
|
queryFn: async () => {
|
||||||
|
const currUri = activeURI();
|
||||||
|
if (currUri) {
|
||||||
|
const result = await callApi("show_clan_meta", { uri: currUri });
|
||||||
|
if (result.status === "error") throw new Error("Failed to fetch data");
|
||||||
|
return result.data;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="navbar bg-base-100">
|
<div class="navbar bg-base-100">
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
@@ -14,7 +29,16 @@ export const Header = () => {
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<a class="text-xl">{activeURI()}</a>
|
<div class="tooltip tooltip-right" data-tip={data?.name || activeURI()}>
|
||||||
|
<div class="avatar placeholder online mx-4">
|
||||||
|
<div class="w-10 rounded-full bg-slate-700 text-neutral-content">
|
||||||
|
<span class="text-xl">C</span>
|
||||||
|
<Show when={data?.name}>
|
||||||
|
{(name) => <span class="text-xl">{name()}</span>}
|
||||||
|
</Show>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-none">
|
<div class="flex-none">
|
||||||
<span class="tooltip tooltip-bottom" data-tip="Settings">
|
<span class="tooltip tooltip-bottom" data-tip="Settings">
|
||||||
|
|||||||
@@ -1,26 +1,14 @@
|
|||||||
import { OperationResponse, pyApi } from "@/src/api";
|
import { OperationResponse, callApi, pyApi } from "@/src/api";
|
||||||
import {
|
import { Show } from "solid-js";
|
||||||
For,
|
|
||||||
JSX,
|
|
||||||
Match,
|
|
||||||
Show,
|
|
||||||
Switch,
|
|
||||||
createEffect,
|
|
||||||
createSignal,
|
|
||||||
} from "solid-js";
|
|
||||||
import {
|
import {
|
||||||
SubmitHandler,
|
SubmitHandler,
|
||||||
createForm,
|
createForm,
|
||||||
required,
|
required,
|
||||||
custom,
|
reset,
|
||||||
} from "@modular-forms/solid";
|
} from "@modular-forms/solid";
|
||||||
import toast from "solid-toast";
|
import toast from "solid-toast";
|
||||||
import { setActiveURI, setRoute } from "@/src/App";
|
import { setActiveURI, setRoute } from "@/src/App";
|
||||||
|
|
||||||
interface ClanDetailsProps {
|
|
||||||
directory: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
type CreateForm = Meta & {
|
type CreateForm = Meta & {
|
||||||
template_url: string;
|
template_url: string;
|
||||||
};
|
};
|
||||||
@@ -28,69 +16,44 @@ type CreateForm = Meta & {
|
|||||||
export const ClanForm = () => {
|
export const ClanForm = () => {
|
||||||
const [formStore, { Form, Field }] = createForm<CreateForm>({
|
const [formStore, { Form, Field }] = createForm<CreateForm>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
|
name: "",
|
||||||
|
description: "",
|
||||||
template_url: "git+https://git.clan.lol/clan/clan-core#templates.minimal",
|
template_url: "git+https://git.clan.lol/clan/clan-core#templates.minimal",
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSubmit: SubmitHandler<CreateForm> = async (values, event) => {
|
const handleSubmit: SubmitHandler<CreateForm> = async (values, event) => {
|
||||||
const { template_url, ...meta } = values;
|
const { template_url, ...meta } = values;
|
||||||
pyApi.open_file.dispatch({
|
|
||||||
file_request: {
|
|
||||||
mode: "save",
|
|
||||||
},
|
|
||||||
|
|
||||||
op_key: "create_clan",
|
const response = await callApi("open_file", {
|
||||||
|
file_request: { mode: "save" },
|
||||||
});
|
});
|
||||||
|
|
||||||
// await new Promise<void>((done) => {
|
if (response.status !== "success") {
|
||||||
// pyApi.open_file.receive((r) => {
|
toast.error("Cannot select clan directory");
|
||||||
// if (r.op_key !== "create_clan") {
|
return;
|
||||||
// done();
|
}
|
||||||
// return;
|
const target_dir = response?.data;
|
||||||
// }
|
if (!target_dir) {
|
||||||
// if (r.status !== "success") {
|
toast.error("Cannot select clan directory");
|
||||||
// toast.error("Cannot select clan directory");
|
return;
|
||||||
// done();
|
}
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// const target_dir = r?.data;
|
|
||||||
// if (!target_dir) {
|
|
||||||
// toast.error("Cannot select clan directory");
|
|
||||||
// done();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// console.log({ formStore });
|
await toast.promise(
|
||||||
|
(async () => {
|
||||||
// toast.promise(
|
await callApi("create_clan", {
|
||||||
// new Promise<void>((resolve, reject) => {
|
options: { directory: target_dir, meta, template_url },
|
||||||
// pyApi.create_clan.receive((r) => {
|
});
|
||||||
// done();
|
setActiveURI(target_dir);
|
||||||
// if (r.status === "error") {
|
setRoute("machines");
|
||||||
// reject();
|
})(),
|
||||||
// console.error(r.errors);
|
{
|
||||||
// return;
|
loading: "Creating clan...",
|
||||||
// }
|
success: "Clan Successfully Created",
|
||||||
// resolve();
|
error: "Failed to create clan",
|
||||||
|
}
|
||||||
// // Navigate to the new clan
|
);
|
||||||
// setCurrClanURI(target_dir);
|
reset(formStore);
|
||||||
// setRoute("machines");
|
|
||||||
// });
|
|
||||||
|
|
||||||
// pyApi.create_clan.dispatch({
|
|
||||||
// options: { directory: target_dir, meta, template_url },
|
|
||||||
// op_key: "create_clan",
|
|
||||||
// });
|
|
||||||
// }),
|
|
||||||
// {
|
|
||||||
// loading: "Creating clan...",
|
|
||||||
// success: "Clan Successfully Created",
|
|
||||||
// error: "Failed to create clan",
|
|
||||||
// }
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -135,6 +98,7 @@ export const ClanForm = () => {
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
|
disabled={formStore.submitting}
|
||||||
required
|
required
|
||||||
placeholder="Clan Name"
|
placeholder="Clan Name"
|
||||||
class="input input-bordered"
|
class="input input-bordered"
|
||||||
@@ -158,6 +122,7 @@ export const ClanForm = () => {
|
|||||||
|
|
||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
|
disabled={formStore.submitting}
|
||||||
required
|
required
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Some words about your clan"
|
placeholder="Some words about your clan"
|
||||||
@@ -188,6 +153,7 @@ export const ClanForm = () => {
|
|||||||
<input
|
<input
|
||||||
{...props}
|
{...props}
|
||||||
required
|
required
|
||||||
|
disabled={formStore.submitting}
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Template to use"
|
placeholder="Template to use"
|
||||||
class="input input-bordered"
|
class="input input-bordered"
|
||||||
|
|||||||
6
pkgs/webview-ui/app/src/routes/deploy/index.tsx
Normal file
6
pkgs/webview-ui/app/src/routes/deploy/index.tsx
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { callApi } from "@/src/api";
|
||||||
|
import { createQuery } from "@tanstack/solid-query";
|
||||||
|
|
||||||
|
export const Deploy = () => {
|
||||||
|
return <div>Deloy view</div>;
|
||||||
|
};
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { route } from "@/src/App";
|
import { route } from "@/src/App";
|
||||||
import { OperationArgs, OperationResponse, pyApi } from "@/src/api";
|
import { OperationArgs, OperationResponse, callApi, pyApi } from "@/src/api";
|
||||||
import { SubmitHandler, createForm, required } from "@modular-forms/solid";
|
import { SubmitHandler, createForm, required } from "@modular-forms/solid";
|
||||||
|
import { createQuery } from "@tanstack/solid-query";
|
||||||
import { For, createSignal } from "solid-js";
|
import { For, createSignal } from "solid-js";
|
||||||
import { effect } from "solid-js/web";
|
import { effect } from "solid-js/web";
|
||||||
|
|
||||||
@@ -28,36 +29,35 @@ type BlockDevices = Extract<
|
|||||||
export const Flash = () => {
|
export const Flash = () => {
|
||||||
const [formStore, { Form, Field }] = createForm<FlashFormValues>({});
|
const [formStore, { Form, Field }] = createForm<FlashFormValues>({});
|
||||||
|
|
||||||
const [devices, setDevices] = createSignal<BlockDevices>([]);
|
const {
|
||||||
// pyApi.show_block_devices.receive((r) => {
|
data: devices,
|
||||||
// console.log("block devices", r);
|
refetch: loadDevices,
|
||||||
// if (r.status === "success") {
|
isFetching,
|
||||||
// setDevices(r.data.blockdevices);
|
} = createQuery(() => ({
|
||||||
// }
|
queryKey: ["TanStack Query"],
|
||||||
// });
|
queryFn: async () => {
|
||||||
|
const result = await callApi("show_block_devices", {});
|
||||||
|
if (result.status === "error") throw new Error("Failed to fetch data");
|
||||||
|
return result.data;
|
||||||
|
},
|
||||||
|
staleTime: 1000 * 60 * 1, // 1 minutes
|
||||||
|
}));
|
||||||
|
|
||||||
const handleSubmit: SubmitHandler<FlashFormValues> = (values, event) => {
|
const handleSubmit = async (values: FlashFormValues) => {
|
||||||
// pyApi.open_file.dispatch({ file_request: { mode: "save" } });
|
// TODO: Rework Flash machine API
|
||||||
// pyApi.open_file.receive((r) => {
|
// Its unusable in its current state
|
||||||
// if (r.status === "success") {
|
// await callApi("flash_machine", {
|
||||||
// if (r.data) {
|
// machine: {
|
||||||
// pyApi.create_clan.dispatch({
|
// name: "",
|
||||||
// options: { directory: r.data, meta: values },
|
// },
|
||||||
// });
|
// disks: {values.disk },
|
||||||
// }
|
// dry_run: true,
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// });
|
// });
|
||||||
console.log("submit", values);
|
console.log("submit", values);
|
||||||
};
|
};
|
||||||
|
|
||||||
// effect(() => {
|
|
||||||
// if (route() === "flash") {
|
|
||||||
// pyApi.show_block_devices.dispatch({});
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
return (
|
return (
|
||||||
<div class="">
|
<div class="px-2">
|
||||||
<Form onSubmit={handleSubmit}>
|
<Form onSubmit={handleSubmit}>
|
||||||
<Field
|
<Field
|
||||||
name="machine.flake"
|
name="machine.flake"
|
||||||
@@ -70,7 +70,7 @@ export const Flash = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="grow"
|
class="grow"
|
||||||
placeholder="Clan URI"
|
placeholder="machine.flake"
|
||||||
required
|
required
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -96,7 +96,7 @@ export const Flash = () => {
|
|||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
class="grow"
|
class="grow"
|
||||||
placeholder="Machine Name"
|
placeholder="machine.name"
|
||||||
required
|
required
|
||||||
{...props}
|
{...props}
|
||||||
/>
|
/>
|
||||||
@@ -115,9 +115,15 @@ export const Flash = () => {
|
|||||||
{(field, props) => (
|
{(field, props) => (
|
||||||
<>
|
<>
|
||||||
<label class="form-control input-bordered flex w-full items-center gap-2">
|
<label class="form-control input-bordered flex w-full items-center gap-2">
|
||||||
<select required class="select w-full" {...props}>
|
<select
|
||||||
|
required
|
||||||
|
class="select select-bordered w-full"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
{/* <span class="material-icons">devices</span> */}
|
{/* <span class="material-icons">devices</span> */}
|
||||||
<For each={devices()}>
|
<option disabled>Select a disk</option>
|
||||||
|
|
||||||
|
<For each={devices?.blockdevices}>
|
||||||
{(device) => (
|
{(device) => (
|
||||||
<option value={device.name}>
|
<option value={device.name}>
|
||||||
{device.name} / {device.size} bytes
|
{device.name} / {device.size} bytes
|
||||||
@@ -126,6 +132,11 @@ export const Flash = () => {
|
|||||||
</For>
|
</For>
|
||||||
</select>
|
</select>
|
||||||
<div class="label">
|
<div class="label">
|
||||||
|
{isFetching && (
|
||||||
|
<span class="label-text-alt">
|
||||||
|
<span class="loading loading-bars"></span>
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
{field.error && (
|
{field.error && (
|
||||||
<span class="label-text-alt font-bold text-error">
|
<span class="label-text-alt font-bold text-error">
|
||||||
{field.error}
|
{field.error}
|
||||||
|
|||||||
Reference in New Issue
Block a user