diff --git a/pkgs/clan-cli/clan_cli/api/__init__.py b/pkgs/clan-cli/clan_cli/api/__init__.py index cd8e46b7..5af194ce 100644 --- a/pkgs/clan-cli/clan_cli/api/__init__.py +++ b/pkgs/clan-cli/clan_cli/api/__init__.py @@ -55,6 +55,34 @@ class _MethodRegistry: self._orig: dict[str, Callable[[Any], Any]] = {} self._registry: dict[str, Callable[[Any], Any]] = {} + def register_abstract(self, fn: Callable[..., T]) -> Callable[..., T]: + @wraps(fn) + def wrapper( + *args: Any, op_key: str | None = None, **kwargs: Any + ) -> ApiResponse[T]: + raise NotImplementedError( + f"""{fn.__name__} - The platform didn't implement this function. + +--- +# Example + +The function 'open_file()' depends on the platform. + +def open_file(file_request: FileRequest) -> str | None: + # In GTK we open a file dialog window + # In Android we open a file picker dialog + # and so on. + pass + +# At runtime the clan-app must override platform specific functions +API.register(open_file) +--- + """ + ) + + self.register(wrapper) + return fn + def register(self, fn: Callable[..., T]) -> Callable[..., T]: self._orig[fn.__name__] = fn diff --git a/pkgs/clan-cli/clan_cli/api/directory.py b/pkgs/clan-cli/clan_cli/api/directory.py index e37b13f5..c7f95b87 100644 --- a/pkgs/clan-cli/clan_cli/api/directory.py +++ b/pkgs/clan-cli/clan_cli/api/directory.py @@ -28,13 +28,13 @@ class FileRequest: filters: FileFilter | None = None -@API.register +@API.register_abstract def open_file(file_request: FileRequest) -> str | None: """ Abstract api method to open a file dialog window. It must return the name of the selected file or None if no file was selected. """ - raise NotImplementedError("Each specific platform should implement this function.") + pass @dataclass diff --git a/pkgs/clan-cli/clan_cli/flash.py b/pkgs/clan-cli/clan_cli/flash.py index c03e64a6..f846068a 100644 --- a/pkgs/clan-cli/clan_cli/flash.py +++ b/pkgs/clan-cli/clan_cli/flash.py @@ -11,6 +11,8 @@ from pathlib import Path from tempfile import TemporaryDirectory from typing import Any +from clan_cli.api import API + from .clan_uri import FlakeId from .cmd import Log, run from .completions import add_dynamic_completer, complete_machines @@ -22,6 +24,7 @@ from .nix import nix_shell log = logging.getLogger(__name__) +@API.register def flash_machine( machine: Machine, *, diff --git a/pkgs/webview-ui/app/src/Routes.tsx b/pkgs/webview-ui/app/src/Routes.tsx index 12af6621..1beebdfa 100644 --- a/pkgs/webview-ui/app/src/Routes.tsx +++ b/pkgs/webview-ui/app/src/Routes.tsx @@ -4,6 +4,7 @@ import { colors } from "./routes/colors/view"; import { clan } from "./routes/clan/view"; import { HostList } from "./routes/hosts/view"; import { BlockDevicesView } from "./routes/blockdevices/view"; +import { Flash } from "./routes/flash/view"; export type Route = keyof typeof routes; @@ -23,6 +24,11 @@ export const routes = { label: "hosts", icon: "devices_other", }, + flash: { + child: Flash, + label: "create_flash_installer", + icon: "devices_other", + }, blockdevices: { child: BlockDevicesView, label: "blockdevices", diff --git a/pkgs/webview-ui/app/src/routes/flash/view.tsx b/pkgs/webview-ui/app/src/routes/flash/view.tsx new file mode 100644 index 00000000..5b4c4578 --- /dev/null +++ b/pkgs/webview-ui/app/src/routes/flash/view.tsx @@ -0,0 +1,144 @@ +import { route } from "@/src/App"; +import { OperationArgs, OperationResponse, pyApi } from "@/src/api"; +import { SubmitHandler, createForm, required } from "@modular-forms/solid"; +import { For, createSignal } from "solid-js"; +import { effect } from "solid-js/web"; + +// type FlashMachineArgs = { +// machine: Omit["machine"], "cached_deployment">; +// } & Omit, "machine">, "system_config">; + +// type FlashMachineArgs = OperationArgs<"flash_machine">; + +// type k = keyof FlashMachineArgs; +// eslint-disable-next-line @typescript-eslint/consistent-type-definitions +type FlashFormValues = { + machine: { + name: string; + flake: string; + }; + disk: string; +}; + +type BlockDevices = Extract< + OperationResponse<"show_block_devices">, + { status: "success" } +>["data"]["blockdevices"]; +export const Flash = () => { + const [formStore, { Form, Field }] = createForm({}); + + const [devices, setDevices] = createSignal([]); + pyApi.show_block_devices.receive((r) => { + console.log("block devices", r); + if (r.status === "success") { + setDevices(r.data.blockdevices); + } + }); + + const handleSubmit: SubmitHandler = (values, event) => { + // pyApi.open_file.dispatch({ file_request: { mode: "save" } }); + // pyApi.open_file.receive((r) => { + // if (r.status === "success") { + // if (r.data) { + // pyApi.create_clan.dispatch({ + // options: { directory: r.data, meta: values }, + // }); + // } + // return; + // } + // }); + console.log("submit", values); + }; + + effect(() => { + if (route() === "flash") { + pyApi.show_block_devices.dispatch({}); + } + }); + return ( +
+
+ + {(field, props) => ( + <> + +
+ {field.error && ( + + {field.error} + + )} +
+ + )} +
+ + {(field, props) => ( + <> + +
+ {field.error && ( + + {field.error} + + )} +
+ + )} +
+ + {(field, props) => ( + <> + + + )} + + +
+
+ ); +};