Refactor: improve code modularity

This commit is contained in:
MangoPig
2026-06-29 03:06:18 +01:00
parent c47eae1381
commit fc5ef23af8
87 changed files with 5442 additions and 4769 deletions
@@ -0,0 +1,469 @@
// Path: Frontend/src/components/bootstrap/BootstrapWizard/BootstrapWizard.hook.ts
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js";
import { createStore } from "solid-js/store";
import {
bootstrapPersonaDefinitions,
bootstrapStepDefinitions,
defaultAdminForm,
defaultInstanceForm,
defaultModeForm,
defaultStructureForm,
organizationalStructureDefaults,
personalStructureDefaults,
type AdminForm,
type BootstrapPersona,
type BootstrapPersonaDefinition,
type BootstrapStepDefinition,
type BootstrapStepKey,
type InstanceForm,
type ModeForm,
type StructureForm,
} from "./BootstrapWizard.data";
import { submitBootstrapStepRequest } from "./bootstrapWizard.api";
type AppShellBootstrapAdapter = {
installation: () => { isBootstrapped?: boolean; materializationStatus?: string; materializationError?: string } | undefined;
status: () => string;
reload: () => Promise<void>;
};
export type BootstrapSubmissionState = {
status: "idle" | "submitting" | "success" | "error";
error: string;
};
export type MaterializationState = "not_started" | "pending" | "running" | "succeeded" | "failed";
export type FieldTooltipState = {
text: string;
left: number;
top: number;
placement: "top" | "bottom";
};
const initialSubmissionState = (): BootstrapSubmissionState => ({
status: "idle",
error: "",
});
const materializationPollIntervalMs = 2000;
export const useBootstrapWizard = (appShellData: AppShellBootstrapAdapter) => {
const [instanceForm, setInstanceForm] = createStore<InstanceForm>({ ...defaultInstanceForm });
const [modeForm, setModeForm] = createStore<ModeForm>({ ...defaultModeForm });
const [adminForm, setAdminForm] = createStore<AdminForm>({ ...defaultAdminForm });
const [structureForm, setStructureForm] = createStore<StructureForm>({ ...defaultStructureForm });
const [selectedPersona, setSelectedPersona] = createSignal<BootstrapPersona>("enthusiast");
const [hasChosenPersona, setHasChosenPersona] = createSignal(false);
const [stepState, setStepState] = createStore<Record<BootstrapStepKey, BootstrapSubmissionState>>({
persona: initialSubmissionState(),
instance: initialSubmissionState(),
mode: initialSubmissionState(),
admin: initialSubmissionState(),
structure: initialSubmissionState(),
});
const [isBootstrapStateResolved, setIsBootstrapStateResolved] = createSignal(false);
const [isBootstrapComplete, setIsBootstrapComplete] = createSignal(false);
const [isWizardOpen, setIsWizardOpen] = createSignal(false);
const [isFinishingBootstrapFlow, setIsFinishingBootstrapFlow] = createSignal(false);
const [currentStepIndex, setCurrentStepIndex] = createSignal(0);
const [fieldTooltip, setFieldTooltip] = createSignal<FieldTooltipState | null>(null);
const installation = createMemo(() => appShellData.installation());
const materializationState = createMemo<MaterializationState>(() => {
const status = installation()?.materializationStatus;
switch (status) {
case "pending":
case "running":
case "failed":
case "succeeded":
case "not_started":
return status;
default:
return installation()?.isBootstrapped ? "succeeded" : "not_started";
}
});
const isBootstrapPersisted = createMemo(() => installation()?.isBootstrapped ?? false);
const isMaterializationInFlight = createMemo(() => materializationState() === "pending" || materializationState() === "running");
const hasMaterializationFailed = createMemo(() => materializationState() === "failed");
const showBootstrapFinishingState = createMemo(() => isFinishingBootstrapFlow() && (isMaterializationInFlight() || hasMaterializationFailed()));
const materializationStatusLabel = createMemo(() => {
switch (materializationState()) {
case "pending":
return "Materialization queued";
case "running":
return "Materialization running";
case "failed":
return "Materialization failed";
case "succeeded":
return "Ready";
default:
return "Not started";
}
});
const materializationMessage = createMemo(() => {
if (isMaterializationInFlight()) {
return "Your bootstrap is saved. The worker is still creating the POSIX skeleton and rebuilding the app shell index.";
}
if (hasMaterializationFailed()) {
return installation()?.materializationError || "Bootstrap saved, but background materialization did not finish cleanly.";
}
return "";
});
const personaDefinition = createMemo<BootstrapPersonaDefinition>(() => bootstrapPersonaDefinitions.find((persona) => persona.id === selectedPersona()) ?? bootstrapPersonaDefinitions[0]!);
const selectedPersonaIsAvailable = createMemo(() => personaDefinition().isAvailable);
const usesCondensedBootstrapFlow = createMemo(() => selectedPersona() === "personal" || selectedPersona() === "team");
const activeBootstrapSteps = createMemo<readonly BootstrapStepDefinition[]>(() => {
if (usesCondensedBootstrapFlow()) {
return [bootstrapStepDefinitions[0]!, bootstrapStepDefinitions[2]!, bootstrapStepDefinitions[3]!];
}
return bootstrapStepDefinitions;
});
const activeWizardSteps = createMemo(() => activeBootstrapSteps().filter((step) => step.id !== "persona"));
createEffect(() => {
const defaults = personaDefinition().defaults;
setInstanceForm({
protocol: defaults.protocol,
access: defaults.access,
host: defaults.host,
});
setModeForm("mode", defaults.mode);
setStructureForm({
departmentName: defaults.departmentName,
teamName: defaults.teamName,
projectName: defaults.projectName,
});
});
createEffect(() => {
if (modeForm.mode === "personal") {
setStructureForm("departmentName", personalStructureDefaults.departmentName);
setStructureForm("teamName", personalStructureDefaults.teamName);
return;
}
if (structureForm.departmentName === personalStructureDefaults.departmentName) {
setStructureForm("departmentName", organizationalStructureDefaults.departmentName);
}
if (structureForm.teamName === personalStructureDefaults.teamName) {
setStructureForm("teamName", organizationalStructureDefaults.teamName);
}
});
const resetWizardState = (): void => {
setSelectedPersona("enthusiast");
setHasChosenPersona(false);
setInstanceForm({ ...defaultInstanceForm });
setModeForm({ ...defaultModeForm });
setAdminForm({ ...defaultAdminForm });
setStructureForm({ ...defaultStructureForm });
setStepState({
persona: initialSubmissionState(),
instance: initialSubmissionState(),
mode: initialSubmissionState(),
admin: initialSubmissionState(),
structure: initialSubmissionState(),
});
setCurrentStepIndex(0);
setIsFinishingBootstrapFlow(false);
};
createEffect(() => {
const shellStatus = appShellData.status();
if (shellStatus === "idle" || shellStatus === "loading") {
return;
}
if (shellStatus !== "success") {
return;
}
if (!isBootstrapPersisted()) {
setIsFinishingBootstrapFlow(false);
resetWizardState();
}
setIsBootstrapComplete(isBootstrapPersisted() && !isMaterializationInFlight());
setIsWizardOpen(!isBootstrapPersisted() || showBootstrapFinishingState());
setIsBootstrapStateResolved(true);
});
createEffect(() => {
if (!isFinishingBootstrapFlow()) {
return;
}
if (isMaterializationInFlight() || hasMaterializationFailed()) {
return;
}
setIsFinishingBootstrapFlow(false);
setIsWizardOpen(false);
});
createEffect(() => {
if (!isBootstrapPersisted() || !isMaterializationInFlight()) {
return;
}
let cancelled = false;
let timeoutId: number | undefined;
const scheduleReload = (): void => {
timeoutId = window.setTimeout(async () => {
if (cancelled) {
return;
}
await appShellData.reload();
if (!cancelled && isBootstrapPersisted() && isMaterializationInFlight()) {
scheduleReload();
}
}, materializationPollIntervalMs);
};
scheduleReload();
onCleanup(() => {
cancelled = true;
if (timeoutId !== undefined) {
window.clearTimeout(timeoutId);
}
});
});
const bootstrapNamePlaceholder = (): string => personaDefinition().defaults.namePlaceholder;
const bootstrapStepCount = createMemo(() => activeWizardSteps().length);
const currentStep = createMemo<BootstrapStepDefinition>(() => activeBootstrapSteps()[currentStepIndex()] ?? activeBootstrapSteps()[0] ?? bootstrapStepDefinitions[0]!);
const currentWizardStepIndex = createMemo(() => {
const visibleIndex = activeWizardSteps().findIndex((step) => step.id === currentStep().id);
return visibleIndex >= 0 ? visibleIndex : 0;
});
const wizardProgressPercent = createMemo(() => {
const totalSteps = bootstrapStepCount();
const activeIndex = Math.max(currentWizardStepIndex(), 0);
if (totalSteps <= 1) {
return 100;
}
return (activeIndex / (totalSteps - 1)) * 100;
});
const wizardProgressFillWidth = createMemo(() => {
if (currentStepIndex() <= 0) {
return `${wizardProgressPercent()}%`;
}
return `calc(${wizardProgressPercent()}% + ((var(--control-size-md) - var(--space-2)) / 2))`;
});
const currentStepState = createMemo<BootstrapSubmissionState>(() => stepState[currentStep().id]);
const isFirstStep = (): boolean => currentStepIndex() === 0;
const isLastStep = (): boolean => currentStepIndex() === activeBootstrapSteps().length - 1;
const canDismissWizard = (): boolean => isBootstrapPersisted() && !isMaterializationInFlight();
createEffect(() => {
setCurrentStepIndex((index) => Math.min(index, activeBootstrapSteps().length - 1));
});
const submitStep = async (step: BootstrapStepKey, payload: unknown): Promise<boolean> => {
setStepState(step, { status: "submitting", error: "" });
try {
await submitBootstrapStepRequest(step, payload);
setStepState(step, {
status: "success",
error: "",
});
return true;
} catch (error) {
setStepState(step, {
status: "error",
error: error instanceof Error ? error.message : `Bootstrap ${step} request failed.`,
});
return false;
}
};
const payloadForStep = (step: BootstrapStepKey): unknown => {
switch (step) {
case "instance":
return instanceForm;
case "mode":
return modeForm;
case "admin":
return adminForm;
case "structure":
return structureForm;
}
};
const applyPersonaSelection = (persona: BootstrapPersona): void => {
const definition = bootstrapPersonaDefinitions.find((candidate) => candidate.id === persona);
if (!definition?.isAvailable) {
return;
}
setSelectedPersona(persona);
setHasChosenPersona(true);
setStepState("persona", {
status: "success",
error: "",
});
setCurrentStepIndex((index) => Math.min(index + 1, activeBootstrapSteps().length - 1));
};
const statusLabel = (state: BootstrapSubmissionState): string => {
switch (state.status) {
case "submitting":
return "Sending";
case "error":
return "Request failed";
default:
return "";
}
};
const submitCurrentStep = async (): Promise<void> => {
const step = currentStep().id;
if (step === "persona") {
applyPersonaSelection(selectedPersona());
return;
}
if (step === "mode" && usesCondensedBootstrapFlow() && stepState.instance.status !== "success") {
const didPersistInstanceDefaults = await submitStep("instance", instanceForm);
if (!didPersistInstanceDefaults) {
return;
}
}
const didSucceed = await submitStep(step, payloadForStep(step));
if (!didSucceed) {
return;
}
if (step === "admin" && usesCondensedBootstrapFlow()) {
const didPersistStructureDefaults = await submitStep("structure", structureForm);
if (!didPersistStructureDefaults) {
return;
}
}
if (isLastStep()) {
await appShellData.reload();
const shouldShowFinishingState = isBootstrapPersisted() && (isMaterializationInFlight() || hasMaterializationFailed());
setIsFinishingBootstrapFlow(shouldShowFinishingState);
setIsBootstrapComplete(isBootstrapPersisted() && !isMaterializationInFlight());
setIsWizardOpen(!isBootstrapPersisted() || shouldShowFinishingState);
setIsBootstrapStateResolved(true);
return;
}
setCurrentStepIndex((index) => Math.min(index + 1, activeBootstrapSteps().length - 1));
};
const showFieldTooltip = (target: HTMLElement, text: string): void => {
const rect = target.getBoundingClientRect();
const placement = rect.top > 96 ? "top" : "bottom";
const viewportPadding = 20;
const left = Math.min(Math.max(rect.left + rect.width / 2, viewportPadding), window.innerWidth - viewportPadding);
const top = placement === "top" ? rect.top - 10 : rect.bottom + 10;
setFieldTooltip({ text, left, top, placement });
};
const hideFieldTooltip = (): void => {
setFieldTooltip(null);
};
const stepStatusLabel = (step: BootstrapStepDefinition): string => {
const state = stepState[step.id];
if (state.status === "success") {
return "Done";
}
if (state.status === "error") {
return "Needs retry";
}
return "";
};
return {
instanceForm,
setInstanceForm,
modeForm,
setModeForm,
adminForm,
setAdminForm,
structureForm,
setStructureForm,
selectedPersona,
setSelectedPersona,
hasChosenPersona,
stepState,
isBootstrapStateResolved,
isBootstrapComplete,
isWizardOpen,
setIsWizardOpen,
isFinishingBootstrapFlow,
setIsFinishingBootstrapFlow,
fieldTooltip,
materializationState,
isMaterializationInFlight,
hasMaterializationFailed,
showBootstrapFinishingState,
materializationStatusLabel,
materializationMessage,
personaDefinition,
selectedPersonaIsAvailable,
usesCondensedBootstrapFlow,
activeWizardSteps,
bootstrapNamePlaceholder,
bootstrapStepCount,
currentStep,
currentWizardStepIndex,
wizardProgressFillWidth,
currentStepState,
isFirstStep,
canDismissWizard,
resetWizardState,
handleCurrentStepSubmit: (event: SubmitEvent & { currentTarget: HTMLFormElement; target: Element }): void => {
event.preventDefault();
void submitCurrentStep();
},
applyPersonaSelection,
statusLabel,
showFieldTooltip,
hideFieldTooltip,
stepStatusLabel,
navigateBack: (): void => {
setCurrentStepIndex((index) => Math.max(index - 1, 0));
},
navigateToVisibleStep: (index: number): void => {
setCurrentStepIndex(index + 1);
},
};
};