Merge pull request 'UI/install: add loading animation' (#4723) from install-ui into main

Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/4723
This commit is contained in:
hsjobeki
2025-08-13 12:19:23 +00:00
3 changed files with 69 additions and 33 deletions

View File

@@ -22,7 +22,7 @@ export interface ButtonProps
startIcon?: IconVariant;
endIcon?: IconVariant;
class?: string;
onAction?: Action;
loading?: boolean;
}
const iconSizes: Record<Size, string> = {
@@ -40,31 +40,12 @@ export const Button = (props: ButtonProps) => {
"startIcon",
"endIcon",
"class",
"onAction",
"loading",
]);
const size = local.size || "default";
const hierarchy = local.hierarchy || "primary";
const [loading, setLoading] = createSignal(false);
const onClick = async () => {
if (!local.onAction) {
console.error("this should not be possible");
return;
}
setLoading(true);
try {
await local.onAction();
} catch (error) {
console.error("Error while executing action", error);
}
setLoading(false);
};
const iconSize = iconSizes[local.size || "default"];
const loadingClass =
@@ -81,16 +62,19 @@ export const Button = (props: ButtonProps) => {
hierarchy,
{
icon: local.icon,
loading: loading(),
loading: props.loading,
ghost: local.ghost,
},
)}
onClick={local.onAction ? onClick : undefined}
onClick={props.onClick}
{...other}
>
<Loader
hierarchy={hierarchy}
class={cx({ [idleClass]: !loading(), [loadingClass]: loading() })}
class={cx({
[idleClass]: !props.loading,
[loadingClass]: props.loading,
})}
/>
{local.startIcon && (

View File

@@ -99,8 +99,12 @@ const welcome = (props: {
}) => {
const navigate = useNavigate();
const [loading, setLoading] = createSignal(false);
const selectFolder = async () => {
setLoading(true);
const uri = await selectClanFolder();
setLoading(false);
navigateToClan(navigate, uri);
};
@@ -148,7 +152,12 @@ const welcome = (props: {
</Typography>
<Divider orientation="horizontal" />
</div>
<Button hierarchy="primary" ghost={true} onAction={selectFolder}>
<Button
hierarchy="primary"
ghost={true}
loading={loading()}
onClick={selectFolder}
>
Select folder
</Button>
</div>

View File

@@ -4,6 +4,7 @@ import {
createForm,
FieldValues,
getError,
getValue,
SubmitHandler,
valiForm,
} from "@modular-forms/solid";
@@ -13,7 +14,7 @@ import { getStepStore, useStepper } from "@/src/hooks/stepper";
import { InstallSteps, InstallStoreType, PromptValues } from "../install";
import { TextInput } from "@/src/components/Form/TextInput";
import { Alert } from "@/src/components/Alert/Alert";
import { For, Match, Show, Switch } from "solid-js";
import { createSignal, For, Match, Show, Switch } from "solid-js";
import { Divider } from "@/src/components/Divider/Divider";
import { Orienter } from "@/src/components/Form/Orienter";
import { Button } from "@/src/components/Button/Button";
@@ -29,6 +30,7 @@ import {
import { useClanURI } from "@/src/hooks/clan";
import { useApiClient } from "@/src/hooks/ApiClient";
import { ProcessMessage, useNotifyOrigin } from "@/src/hooks/notify";
import { Loader } from "@/src/components/Loader/Loader";
export const InstallHeader = (props: { machineName: string }) => {
return (
@@ -58,8 +60,9 @@ const ConfigureAddress = () => {
},
});
const [isReachable, setIsReachable] = createSignal<string | null>(null);
const client = useApiClient();
const clanUri = useClanURI();
// TODO: push values to the parent form Store
const handleSubmit: SubmitHandler<ConfigureAdressForm> = async (
values,
@@ -72,6 +75,24 @@ const ConfigureAddress = () => {
stepSignal.next();
};
const tryReachable = async () => {
const address = getValue(formStore, "targetHost");
if (!address) {
return;
}
const call = client.fetch("check_machine_ssh_login", {
remote: {
address,
},
});
const result = await call.result;
console.log("SSH login check result:", result);
if (result.status === "success") {
setIsReachable(address);
}
};
return (
<Form onSubmit={handleSubmit} class="h-full">
<StepLayout
@@ -98,12 +119,28 @@ const ConfigureAddress = () => {
)}
</Field>
</Fieldset>
<Button
disabled={!getValue(formStore, "targetHost")}
endIcon="ArrowRight"
onClick={tryReachable}
hierarchy="secondary"
>
Test Connection
</Button>
</div>
}
footer={
<div class="flex justify-between">
<BackButton />
<NextButton type="submit">Next</NextButton>
<NextButton
type="submit"
disabled={
!isReachable() ||
isReachable() !== getValue(formStore, "targetHost")
}
>
Next
</NextButton>
</div>
}
/>
@@ -157,15 +194,18 @@ const CheckHardware = () => {
Hardware Report
</Typography>
<Button
disabled={hardwareQuery.isLoading}
hierarchy="secondary"
startIcon="Report"
onClick={handleUpdateSummary}
class="flex gap-3"
loading={hardwareQuery.isFetching}
>
Update hardware report
</Button>
</Orienter>
<Divider orientation="horizontal" />
<Show when={hardwareQuery.isLoading}>Loading...</Show>
<Show when={hardwareQuery.data}>
{(d) => (
<Alert
@@ -545,13 +585,16 @@ const InstallSummary = () => {
<StepLayout
body={
<div class="flex flex-col gap-4">
<Fieldset legend="Address Configuration">
<Fieldset legend="Machine">
<Orienter orientation="horizontal">
{/* TOOD: Display the values emited from previous steps */}
<Display label="Target" value="flash-installer.local" />
<Display label="Name" value={store.install.machineName} />
</Orienter>
<Divider orientation="horizontal" />
<Orienter orientation="horizontal">
<Display label="Address" value={store.install.targetHost} />
</Orienter>
</Fieldset>
<Fieldset legend="Disk Configuration">
<Fieldset legend="Disk">
<Orienter orientation="horizontal">
<Display label="Disk Schema" value="Single" />
</Orienter>