feat(ui): waiting for necessary queries before dropping clan loader

This commit is contained in:
Brian McGee
2025-07-24 09:48:57 +01:00
parent 2299feb809
commit 694059d3ce
5 changed files with 63 additions and 29 deletions

View File

@@ -3,7 +3,7 @@ import { A } from "@solidjs/router";
import { Accordion } from "@kobalte/core/accordion";
import Icon from "../Icon/Icon";
import { Typography } from "@/src/components/Typography/Typography";
import { For, Suspense } from "solid-js";
import { For } from "solid-js";
import { MachineStatus } from "@/src/components/MachineStatus/MachineStatus";
import { buildMachinePath, useClanURI } from "@/src/hooks/clan";
import { useMachinesQuery } from "@/src/queries/queries";
@@ -89,21 +89,19 @@ export const SidebarBody = (props: SidebarProps) => {
</Accordion.Trigger>
</Accordion.Header>
<Accordion.Content class="content">
<Suspense fallback={"Loading..."}>
<nav>
<For each={Object.entries(machineList.data || {})}>
{([id, machine]) => (
<MachineRoute
clanURI={clanURI}
machineID={id}
name={machine.name || id}
status="Not Installed"
serviceCount={0}
/>
)}
</For>
</nav>
</Suspense>
<nav>
<For each={Object.entries(machineList.data || {})}>
{([id, machine]) => (
<MachineRoute
clanURI={clanURI}
machineID={id}
name={machine.name || id}
status="Not Installed"
serviceCount={0}
/>
)}
</For>
</nav>
</Accordion.Content>
</Accordion.Item>

View File

@@ -4,7 +4,7 @@ import { DropdownMenu } from "@kobalte/core/dropdown-menu";
import { useNavigate } from "@solidjs/router";
import { Typography } from "../Typography/Typography";
import { createSignal, For, Suspense } from "solid-js";
import { useAllClanDetailsQuery } from "@/src/queries/queries";
import { useClanListQuery } from "@/src/queries/queries";
import { navigateToClan, useClanURI } from "@/src/hooks/clan";
import { clanURIs } from "@/src/stores/clan";
@@ -15,7 +15,7 @@ export const SidebarHeader = () => {
// get information about the current active clan
const clanURI = useClanURI();
const allClans = useAllClanDetailsQuery(clanURIs());
const allClans = useClanListQuery(clanURIs());
const activeClan = () => allClans.find(({ data }) => data?.uri === clanURI);

View File

@@ -67,7 +67,7 @@ export const callApi = <K extends OperationNames>(
const op_key = backendOpts?.op_key ?? crypto.randomUUID();
let req: BackendSendType<OperationNames> = {
const req: BackendSendType<OperationNames> = {
body: args,
header: {
...backendOpts,

View File

@@ -3,8 +3,12 @@ import { callApi, SuccessData } from "../hooks/api";
import { encodeBase64 } from "@/src/hooks/clan";
export type ClanDetails = SuccessData<"get_clan_details">;
export type ClanDetailsWithURI = ClanDetails & { uri: string };
export type ListMachines = SuccessData<"list_machines">;
export type MachinesQueryResult = UseQueryResult<ListMachines>;
export type ClanListQueryResult = UseQueryResult<ClanDetailsWithURI>[];
export const useMachinesQuery = (clanURI: string) =>
useQuery<ListMachines>(() => ({
@@ -48,7 +52,7 @@ export const useClanDetailsQuery = (clanURI: string) =>
},
}));
export const useAllClanDetailsQuery = (clanURIs: string[]) =>
export const useClanListQuery = (clanURIs: string[]): ClanListQueryResult =>
useQueries(() => ({
queries: clanURIs.map((clanURI) => ({
queryKey: ["clans", encodeBase64(clanURI), "details"],

View File

@@ -15,9 +15,14 @@ import {
useClanURI,
} from "@/src/hooks/clan";
import { CubeScene } from "@/src/scene/cubes";
import { MachinesQueryResult, useMachinesQuery } from "@/src/queries/queries";
import {
ClanListQueryResult,
MachinesQueryResult,
useClanListQuery,
useMachinesQuery,
} from "@/src/queries/queries";
import { callApi } from "@/src/hooks/api";
import { store, setStore } from "@/src/stores/clan";
import { store, setStore, clanURIs } from "@/src/stores/clan";
import { produce } from "solid-js/store";
import { Button } from "@/src/components/Button/Button";
import { Splash } from "@/src/scene/splash";
@@ -42,10 +47,12 @@ export const Clan: Component<RouteSectionProps> = (props) => {
interface CreateFormValues extends FieldValues {
name: string;
}
interface MockProps {
onClose: () => void;
onSubmit: (formValues: CreateFormValues) => void;
}
const MockCreateMachine = (props: MockProps) => {
let container: Node;
@@ -173,7 +180,26 @@ const ClanSceneController = (props: RouteSectionProps) => {
return (
<SceneDataProvider clanURI={clanURI}>
{({ query }) => {
{({ clansQuery, machinesQuery }) => {
// a combination of the individual clan details query status and the machines query status
// the cube scene needs the machines query, the sidebar needs the clans query and machines query results
// so we wait on both before removing the loader to avoid any loading artefacts
const isLoading = (): boolean => {
// check the machines query first
if (machinesQuery.isLoading) {
return true;
}
// otherwise iterate the clans query and return early if we find a queries that is still loading
for (const query of clansQuery) {
if (query.isLoading) {
return true;
}
}
return false;
};
return (
<>
<Show when={showModal()}>
@@ -217,7 +243,7 @@ const ClanSceneController = (props: RouteSectionProps) => {
ghost
onClick={() => {
console.log("Refetching API");
query.refetch();
machinesQuery.refetch();
}}
>
Refetch API
@@ -225,7 +251,9 @@ const ClanSceneController = (props: RouteSectionProps) => {
</div>
{/* TODO: Add minimal display time */}
<div
class={cx({ "fade-out": !query.isLoading && loadingCooldown() })}
class={cx({
"fade-out": !machinesQuery.isLoading && loadingCooldown(),
})}
>
<Splash />
</div>
@@ -233,8 +261,8 @@ const ClanSceneController = (props: RouteSectionProps) => {
<CubeScene
selectedIds={selectedIds}
onSelect={onMachineSelect}
isLoading={query.isLoading}
cubesQuery={query}
isLoading={isLoading()}
cubesQuery={machinesQuery}
onCreate={onCreate}
sceneStore={() => {
const clanURI = useClanURI();
@@ -268,10 +296,14 @@ const ClanSceneController = (props: RouteSectionProps) => {
const SceneDataProvider = (props: {
clanURI: string;
children: (sceneData: { query: MachinesQueryResult }) => JSX.Element;
children: (sceneData: {
clansQuery: ClanListQueryResult;
machinesQuery: MachinesQueryResult;
}) => JSX.Element;
}) => {
const clansQuery = useClanListQuery(clanURIs());
const machinesQuery = useMachinesQuery(props.clanURI);
// This component can be used to provide scene data or context if needed
return props.children({ query: machinesQuery });
return props.children({ clansQuery, machinesQuery });
};