Merge pull request 'feat(ui): add a clan context provider' (#3744) from feat/clan-uri-context into main
Reviewed-on: https://git.clan.lol/clan/clan-core/pulls/3744
This commit is contained in:
@@ -1,41 +0,0 @@
|
|||||||
import { createSignal } from "solid-js";
|
|
||||||
import { makePersisted } from "@solid-primitives/storage";
|
|
||||||
import { callApi } from "./api";
|
|
||||||
|
|
||||||
const [activeURI, setActiveURI] = makePersisted(
|
|
||||||
createSignal<string | null>(null),
|
|
||||||
{
|
|
||||||
name: "activeURI",
|
|
||||||
storage: localStorage,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
export { activeURI, setActiveURI };
|
|
||||||
|
|
||||||
const [clanList, setClanList] = makePersisted(createSignal<string[]>([]), {
|
|
||||||
name: "clanList",
|
|
||||||
storage: localStorage,
|
|
||||||
});
|
|
||||||
|
|
||||||
export { clanList, setClanList };
|
|
||||||
|
|
||||||
(async function () {
|
|
||||||
const curr = activeURI();
|
|
||||||
if (curr) {
|
|
||||||
const result = await callApi("show_clan_meta", {
|
|
||||||
flake: { identifier: curr },
|
|
||||||
});
|
|
||||||
console.log("refetched meta for ", curr);
|
|
||||||
if (result.status === "error") {
|
|
||||||
result.errors.forEach((error) => {
|
|
||||||
if (error.description === "clan directory does not exist") {
|
|
||||||
setActiveURI(null);
|
|
||||||
setClanList((clans) => clans.filter((clan) => clan !== curr));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})();
|
|
||||||
|
|
||||||
// ensure to null out activeURI on startup if the clan was deleted
|
|
||||||
// => throws user back to the view for selecting a clan
|
|
||||||
@@ -1,14 +1,12 @@
|
|||||||
import { For, createEffect, Show, type JSX, children } from "solid-js";
|
import { For, type JSX, Show } from "solid-js";
|
||||||
import { A, RouteSectionProps } from "@solidjs/router";
|
import { RouteSectionProps } from "@solidjs/router";
|
||||||
import { activeURI } from "@/src/App";
|
import { AppRoute, routes } from "@/src";
|
||||||
import { createQuery } from "@tanstack/solid-query";
|
|
||||||
import { callApi } from "@/src/api";
|
|
||||||
import { AppRoute, routes } from "@/src/index";
|
|
||||||
import { SidebarHeader } from "./SidebarHeader";
|
import { SidebarHeader } from "./SidebarHeader";
|
||||||
import { SidebarListItem } from "./SidebarListItem";
|
import { SidebarListItem } from "./SidebarListItem";
|
||||||
import { Typography } from "../Typography";
|
import { Typography } from "../Typography";
|
||||||
import "./css/sidebar.css";
|
import "./css/sidebar.css";
|
||||||
import Icon, { IconVariant } from "../icon";
|
import Icon, { IconVariant } from "../icon";
|
||||||
|
import { clanMetaQuery } from "@/src/queries/clan-meta";
|
||||||
|
|
||||||
export const SidebarSection = (props: {
|
export const SidebarSection = (props: {
|
||||||
title: string;
|
title: string;
|
||||||
@@ -42,26 +40,7 @@ export const SidebarSection = (props: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const Sidebar = (props: RouteSectionProps) => {
|
export const Sidebar = (props: RouteSectionProps) => {
|
||||||
createEffect(() => {
|
const query = clanMetaQuery();
|
||||||
console.log("machines");
|
|
||||||
console.log(routes);
|
|
||||||
});
|
|
||||||
|
|
||||||
const query = createQuery(() => ({
|
|
||||||
queryKey: [activeURI(), "meta"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const curr = activeURI();
|
|
||||||
if (curr) {
|
|
||||||
const result = await callApi("show_clan_meta", {
|
|
||||||
flake: { identifier: curr },
|
|
||||||
});
|
|
||||||
console.log("refetched meta for ", curr);
|
|
||||||
if (result.status === "error") throw new Error("Failed to fetch data");
|
|
||||||
|
|
||||||
return result.data;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="sidebar">
|
<div class="sidebar">
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
import { createSignal, For, Setter, Show } from "solid-js";
|
import { createSignal, Setter } from "solid-js";
|
||||||
import { callApi, SuccessQuery } from "../../api";
|
import { callApi, SuccessQuery } from "../../api";
|
||||||
|
|
||||||
import { activeURI } from "../../App";
|
|
||||||
import toast from "solid-toast";
|
|
||||||
import { A, useNavigate } from "@solidjs/router";
|
import { A, useNavigate } from "@solidjs/router";
|
||||||
import { RndThumbnail } from "../noiseThumbnail";
|
import { RndThumbnail } from "../noiseThumbnail";
|
||||||
|
|
||||||
import { Filter } from "../../routes/machines";
|
import { Filter } from "../../routes/machines";
|
||||||
import { Typography } from "../Typography";
|
import { Typography } from "../Typography";
|
||||||
import "./css/index.css";
|
import "./css/index.css";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
type MachineDetails = SuccessQuery<"list_inv_machines">["data"][string];
|
type MachineDetails = SuccessQuery<"list_inv_machines">["data"][string];
|
||||||
|
|
||||||
@@ -28,6 +27,8 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
// Later only updates
|
// Later only updates
|
||||||
const [updating, setUpdating] = createSignal<boolean>(false);
|
const [updating, setUpdating] = createSignal<boolean>(false);
|
||||||
|
|
||||||
|
const { activeClanURI } = useClanContext();
|
||||||
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleInstall = async () => {
|
const handleInstall = async () => {
|
||||||
@@ -35,7 +36,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const active_clan = activeURI();
|
const active_clan = activeClanURI();
|
||||||
if (!active_clan) {
|
if (!active_clan) {
|
||||||
console.error("No active clan selected");
|
console.error("No active clan selected");
|
||||||
return;
|
return;
|
||||||
@@ -70,7 +71,7 @@ export const MachineListItem = (props: MachineListItemProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const active_clan = activeURI();
|
const active_clan = activeClanURI();
|
||||||
if (!active_clan) {
|
if (!active_clan) {
|
||||||
console.error("No active clan selected");
|
console.error("No active clan selected");
|
||||||
return;
|
return;
|
||||||
|
|||||||
65
pkgs/clan-app/ui/src/contexts/clan.tsx
Normal file
65
pkgs/clan-app/ui/src/contexts/clan.tsx
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { createContext, createEffect, JSX, useContext } from "solid-js";
|
||||||
|
import { callApi } from "@/src/api";
|
||||||
|
import {
|
||||||
|
activeClanURI,
|
||||||
|
addClanURI,
|
||||||
|
clanURIs,
|
||||||
|
removeClanURI,
|
||||||
|
setActiveClanURI,
|
||||||
|
store,
|
||||||
|
} from "@/src/stores/clan";
|
||||||
|
import { redirect } from "@solidjs/router";
|
||||||
|
|
||||||
|
// Create the context
|
||||||
|
interface ClanContextType {
|
||||||
|
activeClanURI: typeof activeClanURI;
|
||||||
|
setActiveClanURI: typeof setActiveClanURI;
|
||||||
|
clanURIs: typeof clanURIs;
|
||||||
|
addClanURI: typeof addClanURI;
|
||||||
|
removeClanURI: typeof removeClanURI;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ClanContext = createContext<ClanContextType>({
|
||||||
|
activeClanURI,
|
||||||
|
setActiveClanURI,
|
||||||
|
clanURIs,
|
||||||
|
addClanURI,
|
||||||
|
removeClanURI,
|
||||||
|
});
|
||||||
|
|
||||||
|
interface ClanProviderProps {
|
||||||
|
children: JSX.Element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ClanProvider(props: ClanProviderProps) {
|
||||||
|
// redirect to welcome if there's no active clan and no clan URIs
|
||||||
|
createEffect(async () => {
|
||||||
|
if (!store.activeClanURI && store.clanURIs.length == 0) {
|
||||||
|
redirect("/welcome");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ClanContext.Provider
|
||||||
|
value={{
|
||||||
|
activeClanURI,
|
||||||
|
setActiveClanURI,
|
||||||
|
clanURIs,
|
||||||
|
addClanURI,
|
||||||
|
removeClanURI,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{props.children}
|
||||||
|
</ClanContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export a hook that provides access to the context
|
||||||
|
export function useClanContext() {
|
||||||
|
const context = useContext(ClanContext);
|
||||||
|
if (!context) {
|
||||||
|
throw new Error("useClanContext must be used within a ClanProvider");
|
||||||
|
}
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -1,18 +1,17 @@
|
|||||||
import { callApi } from "../api";
|
import { callApi } from "../api";
|
||||||
import { setActiveURI, setClanList } from "../App";
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
export const registerClan = async () => {
|
export const registerClan = async () => {
|
||||||
|
const { setActiveClanURI, addClanURI } = useClanContext();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const loc = await callApi("open_file", {
|
const loc = await callApi("open_file", {
|
||||||
file_request: { mode: "select_folder" },
|
file_request: { mode: "select_folder" },
|
||||||
});
|
});
|
||||||
if (loc.status === "success" && loc.data) {
|
if (loc.status === "success" && loc.data) {
|
||||||
const data = loc.data[0];
|
const data = loc.data[0];
|
||||||
setClanList((s) => {
|
addClanURI(data);
|
||||||
const res = new Set([...s, data]);
|
setActiveClanURI(data);
|
||||||
return Array.from(res);
|
|
||||||
});
|
|
||||||
setActiveURI(data);
|
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -5,12 +5,12 @@ import { Navigate, RouteDefinition, Router } from "@solidjs/router";
|
|||||||
import "./index.css";
|
import "./index.css";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/solid-query";
|
||||||
import {
|
import {
|
||||||
|
CreateMachine,
|
||||||
MachineDetails,
|
MachineDetails,
|
||||||
MachineListView,
|
MachineListView,
|
||||||
CreateMachine,
|
|
||||||
} from "./routes/machines";
|
} from "./routes/machines";
|
||||||
import { Layout } from "./layout/layout";
|
import { Layout } from "./layout/layout";
|
||||||
import { ClanList, CreateClan, ClanDetails } from "./routes/clans";
|
import { ClanDetails, ClanList, CreateClan } from "./routes/clans";
|
||||||
import { Flash } from "./routes/flash/view";
|
import { Flash } from "./routes/flash/view";
|
||||||
import { HostList } from "./routes/hosts/view";
|
import { HostList } from "./routes/hosts/view";
|
||||||
import { Welcome } from "./routes/welcome";
|
import { Welcome } from "./routes/welcome";
|
||||||
@@ -21,9 +21,9 @@ import { ModuleDetails as AddModule } from "./routes/modules/add";
|
|||||||
import { ApiTester } from "./api_test";
|
import { ApiTester } from "./api_test";
|
||||||
import { IconVariant } from "./components/icon";
|
import { IconVariant } from "./components/icon";
|
||||||
import { Components } from "./routes/components";
|
import { Components } from "./routes/components";
|
||||||
import { activeURI } from "./App";
|
import { VarsPage } from "./routes/machines/install/vars-step";
|
||||||
import { VarsPage, VarsForm } from "./routes/machines/install/vars-step";
|
|
||||||
import { ThreePlayground } from "./three";
|
import { ThreePlayground } from "./three";
|
||||||
|
import { ClanProvider } from "./contexts/clan";
|
||||||
|
|
||||||
export const client = new QueryClient();
|
export const client = new QueryClient();
|
||||||
|
|
||||||
@@ -186,7 +186,9 @@ render(
|
|||||||
<Toaster position="top-right" containerClassName="z-[9999]" />
|
<Toaster position="top-right" containerClassName="z-[9999]" />
|
||||||
</Portal>
|
</Portal>
|
||||||
<QueryClientProvider client={client}>
|
<QueryClientProvider client={client}>
|
||||||
<Router root={Layout}>{routes}</Router>
|
<ClanProvider>
|
||||||
|
<Router root={Layout}>{routes}</Router>
|
||||||
|
</ClanProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import { Component, createEffect } from "solid-js";
|
import { Component, createEffect } from "solid-js";
|
||||||
import { Sidebar } from "@/src/components/Sidebar";
|
import { Sidebar } from "@/src/components/Sidebar";
|
||||||
import { clanList } from "../App";
|
|
||||||
import { RouteSectionProps, useNavigate } from "@solidjs/router";
|
import { RouteSectionProps, useNavigate } from "@solidjs/router";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
export const Layout: Component<RouteSectionProps> = (props) => {
|
export const Layout: Component<RouteSectionProps> = (props) => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { clanURIs } = useClanContext();
|
||||||
createEffect(() => {
|
createEffect(() => {
|
||||||
console.log(
|
console.log(
|
||||||
"empty ClanList, redirect to welcome page",
|
"empty ClanList, redirect to welcome page",
|
||||||
clanList().length === 0,
|
clanURIs().length === 0,
|
||||||
);
|
);
|
||||||
if (clanList().length === 0) {
|
if (clanURIs().length === 0) {
|
||||||
navigate("/welcome");
|
navigate("/welcome");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
37
pkgs/clan-app/ui/src/queries/clan-meta.ts
Normal file
37
pkgs/clan-app/ui/src/queries/clan-meta.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import { useQuery } from "@tanstack/solid-query";
|
||||||
|
import { callApi } from "@/src/api";
|
||||||
|
import { activeClanURI, removeClanURI } from "@/src/stores/clan";
|
||||||
|
|
||||||
|
export const clanMetaQuery = (uri: string | undefined = undefined) =>
|
||||||
|
useQuery(() => {
|
||||||
|
const clanURI = uri || activeClanURI();
|
||||||
|
const enabled = !!clanURI;
|
||||||
|
|
||||||
|
return {
|
||||||
|
enabled,
|
||||||
|
queryKey: [clanURI, "meta"],
|
||||||
|
queryFn: async () => {
|
||||||
|
console.log("fetching clan meta", clanURI);
|
||||||
|
|
||||||
|
const result = await callApi("show_clan_meta", {
|
||||||
|
flake: { identifier: clanURI! },
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("result", result);
|
||||||
|
|
||||||
|
if (result.status === "error") {
|
||||||
|
// check if the clan directory no longer exists
|
||||||
|
// remove from the clan list if not
|
||||||
|
result.errors.forEach((error) => {
|
||||||
|
if (error.description === "clan directory does not exist") {
|
||||||
|
removeClanURI(clanURI!);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new Error("Failed to fetch data");
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
@@ -6,7 +6,7 @@ export interface ModulesFilter {
|
|||||||
features: string[];
|
features: string[];
|
||||||
}
|
}
|
||||||
export const createModulesQuery = (
|
export const createModulesQuery = (
|
||||||
uri: string | null,
|
uri: string | undefined,
|
||||||
filter?: ModulesFilter,
|
filter?: ModulesFilter,
|
||||||
) =>
|
) =>
|
||||||
createQuery(() => ({
|
createQuery(() => ({
|
||||||
@@ -34,7 +34,7 @@ export const createModulesQuery = (
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const tagsQuery = (uri: string | null) =>
|
export const tagsQuery = (uri: string | undefined) =>
|
||||||
createQuery<string[]>(() => ({
|
createQuery<string[]>(() => ({
|
||||||
queryKey: [uri, "tags"],
|
queryKey: [uri, "tags"],
|
||||||
placeholderData: [],
|
placeholderData: [],
|
||||||
@@ -55,7 +55,7 @@ export const tagsQuery = (uri: string | null) =>
|
|||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
export const machinesQuery = (uri: string | null) =>
|
export const machinesQuery = (uri: string | undefined) =>
|
||||||
createQuery<string[]>(() => ({
|
createQuery<string[]>(() => ({
|
||||||
queryKey: [uri, "machines"],
|
queryKey: [uri, "machines"],
|
||||||
placeholderData: [],
|
placeholderData: [],
|
||||||
|
|||||||
@@ -7,11 +7,11 @@ import {
|
|||||||
SubmitHandler,
|
SubmitHandler,
|
||||||
} from "@modular-forms/solid";
|
} from "@modular-forms/solid";
|
||||||
import toast from "solid-toast";
|
import toast from "solid-toast";
|
||||||
import { activeURI, setActiveURI, setClanList } from "@/src/App";
|
|
||||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate } from "@solidjs/router";
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
type CreateForm = Meta & {
|
type CreateForm = Meta & {
|
||||||
template: string;
|
template: string;
|
||||||
@@ -27,6 +27,8 @@ export const CreateClan = () => {
|
|||||||
});
|
});
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const { setActiveClanURI, addClanURI } = useClanContext();
|
||||||
|
|
||||||
const handleSubmit: SubmitHandler<CreateForm> = async (values, event) => {
|
const handleSubmit: SubmitHandler<CreateForm> = async (values, event) => {
|
||||||
const { template, ...meta } = values;
|
const { template, ...meta } = values;
|
||||||
const response = await callApi("open_file", {
|
const response = await callApi("open_file", {
|
||||||
@@ -74,8 +76,10 @@ export const CreateClan = () => {
|
|||||||
|
|
||||||
if (r.status === "success") {
|
if (r.status === "success") {
|
||||||
toast.success("Clan Successfully Created");
|
toast.success("Clan Successfully Created");
|
||||||
setActiveURI(target_dir[0]);
|
|
||||||
setClanList((list) => [...list, target_dir[0]]);
|
addClanURI(target_dir[0]);
|
||||||
|
setActiveClanURI(target_dir[0]);
|
||||||
|
|
||||||
navigate("/machines");
|
navigate("/machines");
|
||||||
reset(formStore);
|
reset(formStore);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import toast from "solid-toast";
|
|||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
import { Header } from "@/src/layout/header";
|
import { Header } from "@/src/layout/header";
|
||||||
|
import { clanMetaQuery } from "@/src/queries/clan-meta";
|
||||||
|
|
||||||
interface EditClanFormProps {
|
interface EditClanFormProps {
|
||||||
initial: GeneralData;
|
initial: GeneralData;
|
||||||
@@ -44,7 +45,7 @@ const EditClanForm = (props: EditClanFormProps) => {
|
|||||||
error: "Failed to update clan",
|
error: "Failed to update clan",
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: [props.directory, "meta"],
|
queryKey: [props.directory, "meta"],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -142,16 +143,7 @@ export const ClanDetails = () => {
|
|||||||
const params = useParams();
|
const params = useParams();
|
||||||
const clan_dir = window.atob(params.id);
|
const clan_dir = window.atob(params.id);
|
||||||
// Fetch general meta data
|
// Fetch general meta data
|
||||||
const clanQuery = createQuery(() => ({
|
const clanQuery = clanMetaQuery(clan_dir);
|
||||||
queryKey: [clan_dir, "inventory", "meta"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const result = await callApi("show_clan_meta", {
|
|
||||||
flake: { identifier: clan_dir },
|
|
||||||
});
|
|
||||||
if (result.status === "error") throw new Error("Failed to fetch data");
|
|
||||||
return result.data;
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
import { callApi } from "@/src/api";
|
import { createSignal, For, Show } from "solid-js";
|
||||||
import { activeURI, clanList, setActiveURI, setClanList } from "@/src/App";
|
|
||||||
import { createSignal, For, Match, Setter, Show, Switch } from "solid-js";
|
|
||||||
import { createQuery } from "@tanstack/solid-query";
|
|
||||||
import { useFloating } from "@/src/floating";
|
import { useFloating } from "@/src/floating";
|
||||||
import { autoUpdate, flip, hide, offset, shift } from "@floating-ui/dom";
|
import { autoUpdate, flip, hide, offset, shift } from "@floating-ui/dom";
|
||||||
import { useNavigate, A } from "@solidjs/router";
|
import { A, useNavigate } from "@solidjs/router";
|
||||||
import { registerClan } from "@/src/hooks";
|
import { registerClan } from "@/src/hooks";
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
import { clanURIs, setActiveClanURI } from "@/src/stores/clan";
|
||||||
|
import { clanMetaQuery } from "@/src/queries/clan-meta";
|
||||||
|
|
||||||
interface ClanItemProps {
|
interface ClanItemProps {
|
||||||
clan_dir: string;
|
clan_dir: string;
|
||||||
@@ -15,20 +15,14 @@ interface ClanItemProps {
|
|||||||
const ClanItem = (props: ClanItemProps) => {
|
const ClanItem = (props: ClanItemProps) => {
|
||||||
const { clan_dir } = props;
|
const { clan_dir } = props;
|
||||||
|
|
||||||
const details = createQuery(() => ({
|
const details = clanMetaQuery(clan_dir);
|
||||||
queryKey: [clan_dir, "meta"],
|
|
||||||
queryFn: async () => {
|
|
||||||
const result = await callApi("show_clan_meta", {
|
|
||||||
flake: { identifier: clan_dir },
|
|
||||||
});
|
|
||||||
if (result.status === "error") throw new Error("Failed to fetch data");
|
|
||||||
return result.data;
|
|
||||||
},
|
|
||||||
}));
|
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [reference, setReference] = createSignal<HTMLElement>();
|
const [reference, setReference] = createSignal<HTMLElement>();
|
||||||
const [floating, setFloating] = createSignal<HTMLElement>();
|
const [floating, setFloating] = createSignal<HTMLElement>();
|
||||||
|
|
||||||
|
const { activeClanURI, removeClanURI } = useClanContext();
|
||||||
|
|
||||||
// `position` is a reactive object.
|
// `position` is a reactive object.
|
||||||
const position = useFloating(reference, floating, {
|
const position = useFloating(reference, floating, {
|
||||||
placement: "top",
|
placement: "top",
|
||||||
@@ -50,15 +44,7 @@ const ClanItem = (props: ClanItemProps) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handleRemove = () => {
|
const handleRemove = () => {
|
||||||
setClanList((s) =>
|
removeClanURI(clan_dir);
|
||||||
s.filter((v, idx) => {
|
|
||||||
if (v == clan_dir) {
|
|
||||||
setActiveURI(clanList()[idx - 1] || clanList()[idx + 1] || null);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -77,10 +63,10 @@ const ClanItem = (props: ClanItemProps) => {
|
|||||||
variant="light"
|
variant="light"
|
||||||
class=" "
|
class=" "
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setActiveURI(clan_dir);
|
setActiveClanURI(clan_dir);
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{activeURI() === clan_dir ? "active" : "select"}
|
{activeClanURI() === clan_dir ? "active" : "select"}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
size="s"
|
size="s"
|
||||||
@@ -117,7 +103,7 @@ const ClanItem = (props: ClanItemProps) => {
|
|||||||
<div
|
<div
|
||||||
class=""
|
class=""
|
||||||
classList={{
|
classList={{
|
||||||
"": activeURI() === clan_dir,
|
"": activeClanURI() === clan_dir,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{clan_dir}
|
{clan_dir}
|
||||||
@@ -164,7 +150,7 @@ export const ClanList = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class=" shadow">
|
<div class=" shadow">
|
||||||
<For each={clanList()}>
|
<For each={clanURIs()}>
|
||||||
{(value) => <ClanItem clan_dir={value} />}
|
{(value) => <ClanItem clan_dir={value} />}
|
||||||
</For>
|
</For>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
import { callApi } from "@/src/api";
|
import { callApi } from "@/src/api";
|
||||||
import { activeURI } from "@/src/App";
|
import { useQuery } from "@tanstack/solid-query";
|
||||||
import { createQuery } from "@tanstack/solid-query";
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
import { createEffect } from "solid-js";
|
|
||||||
|
|
||||||
export function DiskView() {
|
export function DiskView() {
|
||||||
const query = createQuery(() => ({
|
const { activeClanURI } = useClanContext();
|
||||||
queryKey: ["disk", activeURI()],
|
|
||||||
|
const query = useQuery(() => ({
|
||||||
|
queryKey: ["disk", activeClanURI()],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const currUri = activeURI();
|
const currUri = activeClanURI();
|
||||||
if (currUri) {
|
if (currUri) {
|
||||||
// Example of calling an API
|
// Example of calling an API
|
||||||
const result = await callApi("get_inventory", {
|
const result = await callApi("get_inventory", {
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { callApi, OperationArgs } from "@/src/api";
|
import { callApi, OperationArgs } from "@/src/api";
|
||||||
import { activeURI } from "@/src/App";
|
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||||
@@ -13,16 +12,19 @@ import { MachineAvatar } from "./avatar";
|
|||||||
import { DynForm } from "@/src/Form/form";
|
import { DynForm } from "@/src/Form/form";
|
||||||
import Fieldset from "@/src/Form/fieldset";
|
import Fieldset from "@/src/Form/fieldset";
|
||||||
import Accordion from "@/src/components/accordion";
|
import Accordion from "@/src/components/accordion";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
type CreateMachineForm = OperationArgs<"create_machine">;
|
type CreateMachineForm = OperationArgs<"create_machine">;
|
||||||
|
|
||||||
export function CreateMachine() {
|
export function CreateMachine() {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { activeClanURI } = useClanContext();
|
||||||
|
|
||||||
const [formStore, { Form, Field }] = createForm<CreateMachineForm>({
|
const [formStore, { Form, Field }] = createForm<CreateMachineForm>({
|
||||||
initialValues: {
|
initialValues: {
|
||||||
opts: {
|
opts: {
|
||||||
clan_dir: {
|
clan_dir: {
|
||||||
identifier: activeURI() || "",
|
identifier: activeClanURI() || "",
|
||||||
},
|
},
|
||||||
machine: {
|
machine: {
|
||||||
tags: ["all"],
|
tags: ["all"],
|
||||||
@@ -39,7 +41,7 @@ export function CreateMachine() {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const handleSubmit = async (values: CreateMachineForm) => {
|
const handleSubmit = async (values: CreateMachineForm) => {
|
||||||
const active_dir = activeURI();
|
const active_dir = activeClanURI();
|
||||||
if (!active_dir) {
|
if (!active_dir) {
|
||||||
toast.error("Open a clan to create the machine within");
|
toast.error("Open a clan to create the machine within");
|
||||||
return;
|
return;
|
||||||
@@ -60,9 +62,10 @@ export function CreateMachine() {
|
|||||||
toast.success(`Successfully created ${values.opts.machine.name}`);
|
toast.success(`Successfully created ${values.opts.machine.name}`);
|
||||||
reset(formStore);
|
reset(formStore);
|
||||||
|
|
||||||
queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: [activeURI(), "list_inv_machines"],
|
queryKey: [active_dir, "list_inv_machines"],
|
||||||
});
|
});
|
||||||
|
|
||||||
navigate("/machines");
|
navigate("/machines");
|
||||||
} else {
|
} else {
|
||||||
toast.error(
|
toast.error(
|
||||||
|
|||||||
@@ -7,10 +7,9 @@ import {
|
|||||||
setValue,
|
setValue,
|
||||||
} from "@modular-forms/solid";
|
} from "@modular-forms/solid";
|
||||||
import { useNavigate, useParams, useSearchParams } from "@solidjs/router";
|
import { useNavigate, useParams, useSearchParams } from "@solidjs/router";
|
||||||
import { createQuery, useQueryClient } from "@tanstack/solid-query";
|
import { createQuery, useQuery, useQueryClient } from "@tanstack/solid-query";
|
||||||
import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js";
|
import { createEffect, createSignal, For, Match, Show, Switch } from "solid-js";
|
||||||
|
|
||||||
import { activeURI } from "@/src/App";
|
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||||
@@ -29,9 +28,11 @@ import cx from "classnames";
|
|||||||
import { VarsStep, VarsValues } from "./install/vars-step";
|
import { VarsStep, VarsValues } from "./install/vars-step";
|
||||||
import Fieldset from "@/src/Form/fieldset";
|
import Fieldset from "@/src/Form/fieldset";
|
||||||
import {
|
import {
|
||||||
FileSelectorField,
|
|
||||||
type FileDialogOptions,
|
type FileDialogOptions,
|
||||||
|
FileSelectorField,
|
||||||
} from "@/src/components/fileSelect";
|
} from "@/src/components/fileSelect";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
type MachineFormInterface = MachineData & {
|
type MachineFormInterface = MachineData & {
|
||||||
sshKey?: File;
|
sshKey?: File;
|
||||||
disk?: string;
|
disk?: string;
|
||||||
@@ -81,7 +82,9 @@ interface InstallMachineProps {
|
|||||||
machine: MachineData;
|
machine: MachineData;
|
||||||
}
|
}
|
||||||
const InstallMachine = (props: InstallMachineProps) => {
|
const InstallMachine = (props: InstallMachineProps) => {
|
||||||
const curr = activeURI();
|
const { activeClanURI } = useClanContext();
|
||||||
|
|
||||||
|
const curr = activeClanURI();
|
||||||
const { name } = props;
|
const { name } = props;
|
||||||
if (!curr || !name) {
|
if (!curr || !name) {
|
||||||
return <span>No Clan selected</span>;
|
return <span>No Clan selected</span>;
|
||||||
@@ -95,7 +98,7 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
|
|
||||||
const handleInstall = async (values: AllStepsValues) => {
|
const handleInstall = async (values: AllStepsValues) => {
|
||||||
console.log("Installing", values);
|
console.log("Installing", values);
|
||||||
const curr_uri = activeURI();
|
const curr_uri = activeClanURI();
|
||||||
|
|
||||||
const target = values["1"].target;
|
const target = values["1"].target;
|
||||||
const diskValues = values["2"];
|
const diskValues = values["2"];
|
||||||
@@ -257,7 +260,7 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
// @ts-expect-error: This cannot be undefined in this context.
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
machine_id={props.name}
|
machine_id={props.name}
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
dir={activeURI()}
|
dir={activeClanURI()}
|
||||||
handleNext={(data) => {
|
handleNext={(data) => {
|
||||||
const prev = getValue(formStore, "1");
|
const prev = getValue(formStore, "1");
|
||||||
setValue(formStore, "1", { ...prev, ...data });
|
setValue(formStore, "1", { ...prev, ...data });
|
||||||
@@ -277,7 +280,7 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
// @ts-expect-error: This cannot be undefined in this context.
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
machine_id={props.name}
|
machine_id={props.name}
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
dir={activeURI()}
|
dir={activeClanURI()}
|
||||||
footer={<Footer />}
|
footer={<Footer />}
|
||||||
handleNext={(data) => {
|
handleNext={(data) => {
|
||||||
const prev = getValue(formStore, "2");
|
const prev = getValue(formStore, "2");
|
||||||
@@ -297,7 +300,7 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
// @ts-expect-error: This cannot be undefined in this context.
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
machine_id={props.name}
|
machine_id={props.name}
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
dir={activeURI()}
|
dir={activeClanURI()}
|
||||||
handleNext={(data) => {
|
handleNext={(data) => {
|
||||||
const prev = getValue(formStore, "3");
|
const prev = getValue(formStore, "3");
|
||||||
setValue(formStore, "3", { ...prev, ...data });
|
setValue(formStore, "3", { ...prev, ...data });
|
||||||
@@ -312,7 +315,7 @@ const InstallMachine = (props: InstallMachineProps) => {
|
|||||||
// @ts-expect-error: This cannot be undefined in this context.
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
machine_id={props.name}
|
machine_id={props.name}
|
||||||
// @ts-expect-error: This cannot be undefined in this context.
|
// @ts-expect-error: This cannot be undefined in this context.
|
||||||
dir={activeURI()}
|
dir={activeClanURI()}
|
||||||
handleNext={() => handleNext()}
|
handleNext={() => handleNext()}
|
||||||
// @ts-expect-error: This cannot be known.
|
// @ts-expect-error: This cannot be known.
|
||||||
initial={getValues(formStore)}
|
initial={getValues(formStore)}
|
||||||
@@ -394,11 +397,12 @@ const MachineForm = (props: MachineDetailsProps) => {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
const { activeClanURI } = useClanContext();
|
||||||
|
|
||||||
const handleSubmit = async (values: MachineFormInterface) => {
|
const handleSubmit = async (values: MachineFormInterface) => {
|
||||||
console.log("submitting", values);
|
console.log("submitting", values);
|
||||||
|
|
||||||
const curr_uri = activeURI();
|
const curr_uri = activeClanURI();
|
||||||
if (!curr_uri) {
|
if (!curr_uri) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -418,18 +422,18 @@ const MachineForm = (props: MachineDetailsProps) => {
|
|||||||
),
|
),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
queryClient.invalidateQueries({
|
await queryClient.invalidateQueries({
|
||||||
queryKey: [activeURI(), "machine", machineName(), "get_machine_details"],
|
queryKey: [curr_uri, "machine", machineName(), "get_machine_details"],
|
||||||
});
|
});
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const generatorsQuery = createQuery(() => ({
|
const generatorsQuery = createQuery(() => ({
|
||||||
queryKey: [activeURI(), machineName(), "generators"],
|
queryKey: [activeClanURI(), machineName(), "generators"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const machine_name = machineName();
|
const machine_name = machineName();
|
||||||
const base_dir = activeURI();
|
const base_dir = activeClanURI();
|
||||||
if (!machine_name || !base_dir) {
|
if (!machine_name || !base_dir) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
@@ -460,7 +464,7 @@ const MachineForm = (props: MachineDetailsProps) => {
|
|||||||
if (isUpdating()) {
|
if (isUpdating()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const curr_uri = activeURI();
|
const curr_uri = activeClanURI();
|
||||||
if (!curr_uri) {
|
if (!curr_uri) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -697,10 +701,12 @@ const MachineForm = (props: MachineDetailsProps) => {
|
|||||||
|
|
||||||
export const MachineDetails = () => {
|
export const MachineDetails = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const genericQuery = createQuery(() => ({
|
const { activeClanURI } = useClanContext();
|
||||||
queryKey: [activeURI(), "machine", params.id, "get_machine_details"],
|
|
||||||
|
const genericQuery = useQuery(() => ({
|
||||||
|
queryKey: [activeClanURI(), "machine", params.id, "get_machine_details"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const curr = activeURI();
|
const curr = activeClanURI();
|
||||||
if (curr) {
|
if (curr) {
|
||||||
const result = await callApi("get_machine_details", {
|
const result = await callApi("get_machine_details", {
|
||||||
machine: {
|
machine: {
|
||||||
|
|||||||
@@ -1,19 +1,17 @@
|
|||||||
import { callApi } from "@/src/api";
|
import { callApi } from "@/src/api";
|
||||||
import { activeURI } from "@/src/App";
|
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
import { InputError, InputLabel } from "@/src/components/inputBase";
|
import { InputError, InputLabel } from "@/src/components/inputBase";
|
||||||
import { FieldLayout } from "@/src/Form/fields/layout";
|
import { FieldLayout } from "@/src/Form/fields/layout";
|
||||||
import {
|
import {
|
||||||
createForm,
|
createForm,
|
||||||
SubmitHandler,
|
|
||||||
FieldValues,
|
FieldValues,
|
||||||
validate,
|
|
||||||
required,
|
|
||||||
getValue,
|
getValue,
|
||||||
submit,
|
required,
|
||||||
setValue,
|
setValue,
|
||||||
FormStore,
|
submit,
|
||||||
|
SubmitHandler,
|
||||||
|
validate,
|
||||||
} from "@modular-forms/solid";
|
} from "@modular-forms/solid";
|
||||||
import { createEffect, createSignal, JSX, Match, Switch } from "solid-js";
|
import { createEffect, createSignal, JSX, Match, Switch } from "solid-js";
|
||||||
import { TextInput } from "@/src/Form/fields";
|
import { TextInput } from "@/src/Form/fields";
|
||||||
@@ -21,9 +19,10 @@ import { createQuery } from "@tanstack/solid-query";
|
|||||||
import { Badge } from "@/src/components/badge";
|
import { Badge } from "@/src/components/badge";
|
||||||
import { Group } from "@/src/components/group";
|
import { Group } from "@/src/components/group";
|
||||||
import {
|
import {
|
||||||
FileSelectorField,
|
|
||||||
type FileDialogOptions,
|
type FileDialogOptions,
|
||||||
|
FileSelectorField,
|
||||||
} from "@/src/components/fileSelect";
|
} from "@/src/components/fileSelect";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
export type HardwareValues = FieldValues & {
|
export type HardwareValues = FieldValues & {
|
||||||
report: boolean;
|
report: boolean;
|
||||||
@@ -76,8 +75,10 @@ export const HWStep = (props: StepProps<HardwareValues>) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { activeClanURI } = useClanContext();
|
||||||
|
|
||||||
const generateReport = async (e: Event) => {
|
const generateReport = async (e: Event) => {
|
||||||
const curr_uri = activeURI();
|
const curr_uri = activeClanURI();
|
||||||
if (!curr_uri) return;
|
if (!curr_uri) return;
|
||||||
|
|
||||||
await validate(formStore, "target");
|
await validate(formStore, "target");
|
||||||
|
|||||||
@@ -12,10 +12,10 @@ import { For, JSX, Match, Show, Switch } from "solid-js";
|
|||||||
import { TextInput } from "@/src/Form/fields";
|
import { TextInput } from "@/src/Form/fields";
|
||||||
import toast from "solid-toast";
|
import toast from "solid-toast";
|
||||||
import { useNavigate, useParams, useSearchParams } from "@solidjs/router";
|
import { useNavigate, useParams, useSearchParams } from "@solidjs/router";
|
||||||
import { activeURI } from "@/src/App";
|
|
||||||
import { StepProps } from "./hardware-step";
|
import { StepProps } from "./hardware-step";
|
||||||
import { BackButton } from "@/src/components/BackButton";
|
import { BackButton } from "@/src/components/BackButton";
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
export type VarsValues = FieldValues & Record<string, Record<string, string>>;
|
export type VarsValues = FieldValues & Record<string, Record<string, string>>;
|
||||||
|
|
||||||
@@ -206,6 +206,7 @@ export const VarsStep = (props: VarsStepProps) => {
|
|||||||
export const VarsPage = () => {
|
export const VarsPage = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { activeClanURI } = useClanContext();
|
||||||
const [searchParams, setSearchParams] = useSearchParams();
|
const [searchParams, setSearchParams] = useSearchParams();
|
||||||
const handleNext = (values: VarsValues) => {
|
const handleNext = (values: VarsValues) => {
|
||||||
if (searchParams?.action === "update") {
|
if (searchParams?.action === "update") {
|
||||||
@@ -234,7 +235,7 @@ export const VarsPage = () => {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* VarsStep component */}
|
{/* VarsStep component */}
|
||||||
<Show when={activeURI()}>
|
<Show when={activeClanURI()}>
|
||||||
{(uri) => (
|
{(uri) => (
|
||||||
<VarsStep
|
<VarsStep
|
||||||
machine_id={params.id}
|
machine_id={params.id}
|
||||||
|
|||||||
@@ -1,21 +1,13 @@
|
|||||||
import {
|
import { type Component, createSignal, For, Match, Switch } from "solid-js";
|
||||||
type Component,
|
|
||||||
createSignal,
|
|
||||||
For,
|
|
||||||
Match,
|
|
||||||
Show,
|
|
||||||
Switch,
|
|
||||||
} from "solid-js";
|
|
||||||
import { activeURI } from "@/src/App";
|
|
||||||
import { callApi, OperationResponse } from "@/src/api";
|
import { callApi, OperationResponse } from "@/src/api";
|
||||||
import toast from "solid-toast";
|
|
||||||
import { MachineListItem } from "@/src/components/machine-list-item";
|
import { MachineListItem } from "@/src/components/machine-list-item";
|
||||||
import { createQuery, useQueryClient } from "@tanstack/solid-query";
|
import { useQuery, useQueryClient } from "@tanstack/solid-query";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate } from "@solidjs/router";
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
import { Header } from "@/src/layout/header";
|
import { Header } from "@/src/layout/header";
|
||||||
import { makePersisted } from "@solid-primitives/storage";
|
import { makePersisted } from "@solid-primitives/storage";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
type MachinesModel = Extract<
|
type MachinesModel = Extract<
|
||||||
OperationResponse<"list_inv_machines">,
|
OperationResponse<"list_inv_machines">,
|
||||||
@@ -30,19 +22,22 @@ export const MachineListView: Component = () => {
|
|||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
const [filter, setFilter] = createSignal<Filter>({ tags: [] });
|
const [filter, setFilter] = createSignal<Filter>({ tags: [] });
|
||||||
|
const { activeClanURI } = useClanContext();
|
||||||
|
|
||||||
const inventoryQuery = createQuery<MachinesModel>(() => ({
|
const inventoryQuery = useQuery<MachinesModel>(() => ({
|
||||||
queryKey: [activeURI(), "list_inv_machines"],
|
queryKey: [activeClanURI(), "list_inv_machines"],
|
||||||
placeholderData: {},
|
placeholderData: {},
|
||||||
enabled: !!activeURI(),
|
enabled: !!activeClanURI(),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const uri = activeURI();
|
console.log("fetching inventory", activeClanURI());
|
||||||
|
const uri = activeClanURI();
|
||||||
if (uri) {
|
if (uri) {
|
||||||
const response = await callApi("list_inv_machines", {
|
const response = await callApi("list_inv_machines", {
|
||||||
flake: {
|
flake: {
|
||||||
identifier: uri,
|
identifier: uri,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
console.log("response", response);
|
||||||
if (response.status === "error") {
|
if (response.status === "error") {
|
||||||
console.error("Failed to fetch data");
|
console.error("Failed to fetch data");
|
||||||
} else {
|
} else {
|
||||||
@@ -54,9 +49,18 @@ export const MachineListView: Component = () => {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
queryClient.invalidateQueries({
|
const clanURI = activeClanURI();
|
||||||
|
|
||||||
|
// do nothing if there is no active URI
|
||||||
|
if (!clanURI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("refreshing", clanURI);
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
// Invalidates the cache for of all types of machine list at once
|
// Invalidates the cache for of all types of machine list at once
|
||||||
queryKey: [activeURI(), "list_inv_machines"],
|
queryKey: [clanURI, "list_inv_machines"],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { activeURI } from "@/src/App";
|
|
||||||
import { BackButton } from "@/src/components/BackButton";
|
import { BackButton } from "@/src/components/BackButton";
|
||||||
import { createModulesQuery, machinesQuery, tagsQuery } from "@/src/queries";
|
import { createModulesQuery, machinesQuery, tagsQuery } from "@/src/queries";
|
||||||
import { useParams } from "@solidjs/router";
|
import { useParams } from "@solidjs/router";
|
||||||
@@ -6,10 +5,12 @@ import { For, Match, Switch } from "solid-js";
|
|||||||
import { ModuleInfo } from "./list";
|
import { ModuleInfo } from "./list";
|
||||||
import { createForm, FieldValues, SubmitHandler } from "@modular-forms/solid";
|
import { createForm, FieldValues, SubmitHandler } from "@modular-forms/solid";
|
||||||
import { SelectInput } from "@/src/Form/fields/Select";
|
import { SelectInput } from "@/src/Form/fields/Select";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
export const ModuleDetails = () => {
|
export const ModuleDetails = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const modulesQuery = createModulesQuery(activeURI());
|
const { activeClanURI } = useClanContext();
|
||||||
|
const modulesQuery = createModulesQuery(activeClanURI());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
@@ -32,8 +33,9 @@ interface AddModuleProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const AddModule = (props: AddModuleProps) => {
|
export const AddModule = (props: AddModuleProps) => {
|
||||||
const tags = tagsQuery(activeURI());
|
const { activeClanURI } = useClanContext();
|
||||||
const machines = machinesQuery(activeURI());
|
const tags = tagsQuery(activeClanURI());
|
||||||
|
const machines = machinesQuery(activeClanURI());
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div>Add to your clan</div>
|
<div>Add to your clan</div>
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
import { activeURI } from "@/src/App";
|
|
||||||
import { BackButton } from "@/src/components/BackButton";
|
import { BackButton } from "@/src/components/BackButton";
|
||||||
import { createModulesQuery } from "@/src/queries";
|
import { createModulesQuery } from "@/src/queries";
|
||||||
import { useParams, useNavigate } from "@solidjs/router";
|
import { useNavigate, useParams } from "@solidjs/router";
|
||||||
import { createEffect, For, Match, Switch } from "solid-js";
|
import { createEffect, For, Match, Switch } from "solid-js";
|
||||||
import { SolidMarkdown } from "solid-markdown";
|
|
||||||
import { ModuleInfo } from "./list";
|
import { ModuleInfo } from "./list";
|
||||||
import { createQuery } from "@tanstack/solid-query";
|
import { createQuery } from "@tanstack/solid-query";
|
||||||
import { JSONSchema7 } from "json-schema";
|
import { JSONSchema7 } from "json-schema";
|
||||||
@@ -11,10 +9,13 @@ import { SubmitHandler } from "@modular-forms/solid";
|
|||||||
import { DynForm } from "@/src/Form/form";
|
import { DynForm } from "@/src/Form/form";
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
import { activeClanURI } from "@/src/stores/clan";
|
||||||
|
|
||||||
export const ModuleDetails = () => {
|
export const ModuleDetails = () => {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const modulesQuery = createModulesQuery(activeURI());
|
const { activeClanURI } = useClanContext();
|
||||||
|
const modulesQuery = createModulesQuery(activeClanURI());
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div class="p-1">
|
<div class="p-1">
|
||||||
@@ -143,7 +144,7 @@ export const ModuleForm = (props: { id: string }) => {
|
|||||||
// TODO: Fetch the synced schema for all the modules at runtime
|
// TODO: Fetch the synced schema for all the modules at runtime
|
||||||
// We use static schema file at build time for now. (Different versions might have different schema at runtime)
|
// We use static schema file at build time for now. (Different versions might have different schema at runtime)
|
||||||
const schemaQuery = createQuery(() => ({
|
const schemaQuery = createQuery(() => ({
|
||||||
queryKey: [activeURI(), "modules_schema"],
|
queryKey: [activeClanURI(), "modules_schema"],
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const moduleSchema = await import(
|
const moduleSchema = await import(
|
||||||
"../../../api/modules_schemas.json"
|
"../../../api/modules_schemas.json"
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
import { SuccessData } from "@/src/api";
|
import { SuccessData } from "@/src/api";
|
||||||
import { activeURI } from "@/src/App";
|
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import { Header } from "@/src/layout/header";
|
import { Header } from "@/src/layout/header";
|
||||||
import { createModulesQuery } from "@/src/queries";
|
import { createModulesQuery } from "@/src/queries";
|
||||||
@@ -11,6 +10,7 @@ import { makePersisted } from "@solid-primitives/storage";
|
|||||||
import { useQueryClient } from "@tanstack/solid-query";
|
import { useQueryClient } from "@tanstack/solid-query";
|
||||||
import cx from "classnames";
|
import cx from "classnames";
|
||||||
import Icon from "@/src/components/icon";
|
import Icon from "@/src/components/icon";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
export type ModuleInfo = SuccessData<"list_modules">["localModules"][string];
|
export type ModuleInfo = SuccessData<"list_modules">["localModules"][string];
|
||||||
|
|
||||||
@@ -110,7 +110,8 @@ const ModuleItem = (props: {
|
|||||||
|
|
||||||
export const ModuleList = () => {
|
export const ModuleList = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const modulesQuery = createModulesQuery(activeURI(), {
|
const { activeClanURI } = useClanContext();
|
||||||
|
const modulesQuery = createModulesQuery(activeClanURI(), {
|
||||||
features: ["inventory"],
|
features: ["inventory"],
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -120,9 +121,16 @@ export const ModuleList = () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const refresh = async () => {
|
const refresh = async () => {
|
||||||
queryClient.invalidateQueries({
|
const clanURI = activeClanURI();
|
||||||
|
|
||||||
|
// do nothing if there is no active URI
|
||||||
|
if (!clanURI) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
// Invalidates the cache for of all types of machine list at once
|
// Invalidates the cache for of all types of machine list at once
|
||||||
queryKey: [activeURI(), "list_modules"],
|
queryKey: [clanURI, "list_modules"],
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { setActiveURI } from "@/src/App";
|
|
||||||
import { Button } from "@/src/components/button";
|
import { Button } from "@/src/components/button";
|
||||||
import { registerClan } from "@/src/hooks";
|
import { registerClan } from "@/src/hooks";
|
||||||
import { useNavigate } from "@solidjs/router";
|
import { useNavigate } from "@solidjs/router";
|
||||||
|
import { useClanContext } from "@/src/contexts/clan";
|
||||||
|
|
||||||
export const Welcome = () => {
|
export const Welcome = () => {
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
const { setActiveClanURI } = useClanContext();
|
||||||
return (
|
return (
|
||||||
<div class="min-h-[calc(100vh-10rem)]">
|
<div class="min-h-[calc(100vh-10rem)]">
|
||||||
<div class="mb-32 text-center">
|
<div class="mb-32 text-center">
|
||||||
@@ -21,7 +22,7 @@ export const Welcome = () => {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
const uri = await registerClan();
|
const uri = await registerClan();
|
||||||
if (uri) {
|
if (uri) {
|
||||||
setActiveURI(uri);
|
setActiveClanURI(uri);
|
||||||
navigate("/machines");
|
navigate("/machines");
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
89
pkgs/clan-app/ui/src/stores/clan.tsx
Normal file
89
pkgs/clan-app/ui/src/stores/clan.tsx
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import { createStore, produce } from "solid-js/store";
|
||||||
|
import { makePersisted } from "@solid-primitives/storage";
|
||||||
|
|
||||||
|
interface ClanStoreType {
|
||||||
|
clanURIs: string[];
|
||||||
|
activeClanURI?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const [store, setStore] = makePersisted(
|
||||||
|
createStore<ClanStoreType>({
|
||||||
|
clanURIs: [],
|
||||||
|
}),
|
||||||
|
{
|
||||||
|
name: "clanStore",
|
||||||
|
storage: localStorage,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the active clan URI from the store.
|
||||||
|
*
|
||||||
|
* @function
|
||||||
|
* @returns {string} The URI of the active clan.
|
||||||
|
*/
|
||||||
|
const activeClanURI = (): string | undefined => store.activeClanURI;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the active Clan URI in the store.
|
||||||
|
*
|
||||||
|
* @param {string} uri - The URI to be set as the active Clan URI.
|
||||||
|
*/
|
||||||
|
const setActiveClanURI = (uri: string) => setStore("activeClanURI", uri);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the current list of clan URIs from the store.
|
||||||
|
*
|
||||||
|
* @function clanURIs
|
||||||
|
* @returns {*} The clan URIs from the store.
|
||||||
|
*/
|
||||||
|
const clanURIs = (): string[] => store.clanURIs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new clan URI to the list of clan URIs in the store.
|
||||||
|
*
|
||||||
|
* @param {string} uri - The URI of the clan to be added.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
const addClanURI = (uri: string) =>
|
||||||
|
setStore("clanURIs", store.clanURIs.length, uri);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a specified URI from the clan URI list and updates the active clan URI.
|
||||||
|
*
|
||||||
|
* This function modifies the store in the following ways:
|
||||||
|
* - Removes the specified URI from the `clanURIs` array.
|
||||||
|
* - Clears the `activeClanURI` if the removed URI matches the currently active URI.
|
||||||
|
* - Sets a new active clan URI to the last URI in the `clanURIs` array if the active clan URI is undefined
|
||||||
|
* and there are remaining clan URIs in the list.
|
||||||
|
*
|
||||||
|
* @param {string} uri - The URI to be removed from the clan list.
|
||||||
|
*/
|
||||||
|
const removeClanURI = (uri: string) => {
|
||||||
|
setStore(
|
||||||
|
produce((state) => {
|
||||||
|
// remove from the clan list
|
||||||
|
state.clanURIs = state.clanURIs.filter((el) => el !== uri);
|
||||||
|
|
||||||
|
// clear active clan uri if it's the one being removed
|
||||||
|
if (state.activeClanURI === uri) {
|
||||||
|
state.activeClanURI = undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// select a new active URI if at least one remains
|
||||||
|
if (!state.activeClanURI && state.clanURIs.length > 0) {
|
||||||
|
state.activeClanURI = state.clanURIs[state.clanURIs.length - 1];
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export {
|
||||||
|
store,
|
||||||
|
setStore,
|
||||||
|
activeClanURI,
|
||||||
|
setActiveClanURI,
|
||||||
|
clanURIs,
|
||||||
|
addClanURI,
|
||||||
|
removeClanURI,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user