From b8c5419dba21c7c002fc061ee4301f557000d10f Mon Sep 17 00:00:00 2001 From: Johannes Kirschbauer Date: Sat, 4 Nov 2023 13:53:30 +0100 Subject: [PATCH 1/2] select modules.Prefetched schema depends only on modules --- pkgs/ui/src/app/layout.tsx | 42 +++++++-- pkgs/ui/src/app/machines/layout.tsx | 16 +++- .../createMachineForm/clanModules.tsx | 86 +++++++++++++++++++ .../createMachineForm/customConfig.tsx | 48 ++--------- .../components/createMachineForm/index.tsx | 19 +++- .../createMachineForm/interfaces.ts | 4 + .../ui/src/components/hooks/useAppContext.tsx | 46 +++++++--- pkgs/ui/src/components/hooks/useMachines.tsx | 21 ++--- pkgs/ui/src/components/sidebar/index.tsx | 4 +- pkgs/ui/src/components/table/nodeTable.tsx | 9 +- 10 files changed, 211 insertions(+), 84 deletions(-) create mode 100644 pkgs/ui/src/components/createMachineForm/clanModules.tsx diff --git a/pkgs/ui/src/app/layout.tsx b/pkgs/ui/src/app/layout.tsx index a67038c67..793a0817b 100644 --- a/pkgs/ui/src/app/layout.tsx +++ b/pkgs/ui/src/app/layout.tsx @@ -6,6 +6,8 @@ import { Button, CssBaseline, IconButton, + MenuItem, + Select, ThemeProvider, useMediaQuery, } from "@mui/material"; @@ -51,7 +53,7 @@ export default function RootLayout({ Clan.lol - + @@ -62,18 +64,42 @@ export default function RootLayout({ {(appState) => { const showSidebarDerived = Boolean( - showSidebar && - !appState.isLoading && - appState.data.isJoined, + showSidebar && !appState.isLoading && appState.data.isJoined ); return ( <>
- setShowSidebar(false)} - /> + + setShowSidebar(false)} + clanSelect={ + appState.data.clanName && ( + + ) + } + /> +
- {children} - + <> + {clanName && ( + + {children} + + )} + ); } diff --git a/pkgs/ui/src/components/createMachineForm/clanModules.tsx b/pkgs/ui/src/components/createMachineForm/clanModules.tsx new file mode 100644 index 000000000..6ad3f3e2b --- /dev/null +++ b/pkgs/ui/src/components/createMachineForm/clanModules.tsx @@ -0,0 +1,86 @@ +import { setMachineSchema } from "@/api/machine/machine"; +import { useListClanModules } from "@/api/modules/modules"; +import Box from "@mui/material/Box"; +import Chip from "@mui/material/Chip"; +import FormControl from "@mui/material/FormControl"; +import InputLabel from "@mui/material/InputLabel"; +import MenuItem from "@mui/material/MenuItem"; +import OutlinedInput from "@mui/material/OutlinedInput"; +import Select, { SelectChangeEvent } from "@mui/material/Select"; +import { useEffect } from "react"; +import { CreateMachineForm, FormStepContentProps } from "./interfaces"; + +const ITEM_HEIGHT = 48; +const ITEM_PADDING_TOP = 8; +const MenuProps = { + PaperProps: { + style: { + maxHeight: ITEM_HEIGHT * 4.5 + ITEM_PADDING_TOP, + width: 250, + }, + }, +}; + +type ClanModulesProps = FormStepContentProps; + +export default function ClanModules(props: ClanModulesProps) { + const { clanName, formHooks } = props; + const { data, isLoading } = useListClanModules(clanName); + + const selectedModules = formHooks.watch("modules"); + + useEffect(() => { + setMachineSchema(clanName, "example_machine", { + imports: [], + }).then((response) => { + if (response.statusText == "OK") { + formHooks.setValue("schema", response.data.schema); + } + }); + formHooks.setValue("modules", []); + }, [clanName, formHooks]); + + const handleChange = ( + event: SelectChangeEvent + ) => { + const { + target: { value }, + } = event; + const newValue = typeof value === "string" ? value.split(",") : value; + formHooks.setValue("modules", newValue); + setMachineSchema(clanName, "example_machine", { + imports: selectedModules, + }).then((response) => { + if (response.statusText == "OK") { + formHooks.setValue("schema", response.data.schema); + } + }); + }; + return ( +
+ + Modules + + +
+ ); +} diff --git a/pkgs/ui/src/components/createMachineForm/customConfig.tsx b/pkgs/ui/src/components/createMachineForm/customConfig.tsx index ea83371e9..c1b276625 100644 --- a/pkgs/ui/src/components/createMachineForm/customConfig.tsx +++ b/pkgs/ui/src/components/createMachineForm/customConfig.tsx @@ -1,10 +1,8 @@ "use client"; -import { useGetMachineSchema } from "@/api/machine/machine"; import { Check, Error } from "@mui/icons-material"; import { Box, Button, - LinearProgress, List, ListItem, ListItemIcon, @@ -22,41 +20,18 @@ import { TranslatableString, } from "@rjsf/utils"; import validator from "@rjsf/validator-ajv8"; -import { JSONSchema7 } from "json-schema"; import { useMemo, useRef } from "react"; import toast from "react-hot-toast"; import { FormStepContentProps } from "./interfaces"; +type ValueType = { default: any }; interface PureCustomConfigProps extends FormStepContentProps { - schema: JSONSchema7; initialValues: any; } export function CustomConfig(props: FormStepContentProps) { - const { formHooks } = props; - const { data, isLoading, error } = useGetMachineSchema( - "defaultFlake", - "mama", - ); - // const { data, isLoading, error } = { data: {data:{schema: { - // title: 'Test form', - // type: 'object', - // properties: { - // name: { - // type: 'string', - // }, - // age: { - // type: 'number', - // }, - // }, - // }}}, isLoading: false, error: undefined } - const schema = useMemo(() => { - if (!isLoading && !error?.message && data?.data) { - return data?.data.schema; - } - return {}; - }, [data, isLoading, error]); + const { formHooks, clanName } = props; + const schema = formHooks.watch("schema"); - type ValueType = { default: any }; const initialValues = useMemo( () => Object.entries(schema?.properties || {}).reduce((acc, [key, value]) => { @@ -69,18 +44,14 @@ export function CustomConfig(props: FormStepContentProps) { } return acc; }, {}), - [schema], + [schema] ); - return isLoading ? ( - - ) : error?.message ? ( -
{error?.message}
- ) : ( + return ( ); } @@ -115,9 +86,9 @@ function ErrorList< } function PureCustomConfig(props: PureCustomConfigProps) { - const { schema, formHooks } = props; + const { formHooks } = props; const { setValue, watch } = formHooks; - + const schema = watch("schema"); console.log({ schema }); const configData = watch("config") as IChangeEvent; @@ -125,7 +96,6 @@ function PureCustomConfig(props: PureCustomConfigProps) { console.log({ configData }); const setConfig = (data: IChangeEvent) => { - console.log({ data }); setValue("config", data); }; @@ -139,7 +109,7 @@ function PureCustomConfig(props: PureCustomConfigProps) { message: "invalid config", }); toast.error( - "Configuration is invalid. Please check the highlighted fields for details.", + "Configuration is invalid. Please check the highlighted fields for details." ); } else { formHooks.clearErrors("config"); diff --git a/pkgs/ui/src/components/createMachineForm/index.tsx b/pkgs/ui/src/components/createMachineForm/index.tsx index cf1515ff9..c2d2ab3c7 100644 --- a/pkgs/ui/src/components/createMachineForm/index.tsx +++ b/pkgs/ui/src/components/createMachineForm/index.tsx @@ -1,6 +1,7 @@ import { Box, Button, + LinearProgress, MobileStepper, Step, StepLabel, @@ -10,14 +11,20 @@ import { } from "@mui/material"; import React, { useState } from "react"; import { useForm } from "react-hook-form"; +import { useAppState } from "../hooks/useAppContext"; +import ClanModules from "./clanModules"; import { CustomConfig } from "./customConfig"; import { CreateMachineForm, FormStep } from "./interfaces"; export function CreateMachineForm() { + const { + data: { clanName }, + } = useAppState(); const formHooks = useForm({ defaultValues: { name: "", config: {}, + modules: [], }, }); const { handleSubmit, reset } = formHooks; @@ -34,12 +41,20 @@ export function CreateMachineForm() { { id: "modules", label: "Modules", - content:
, + content: clanName ? ( + + ) : ( + + ), }, { id: "config", label: "Customize", - content: , + content: clanName ? ( + + ) : ( + + ), }, { id: "save", diff --git a/pkgs/ui/src/components/createMachineForm/interfaces.ts b/pkgs/ui/src/components/createMachineForm/interfaces.ts index c9e14c263..3cac40b18 100644 --- a/pkgs/ui/src/components/createMachineForm/interfaces.ts +++ b/pkgs/ui/src/components/createMachineForm/interfaces.ts @@ -1,3 +1,4 @@ +import { JSONSchema7 } from "json-schema"; import { ReactElement } from "react"; import { UseFormReturn } from "react-hook-form"; @@ -6,6 +7,8 @@ export type StepId = "template" | "modules" | "config" | "save"; export type CreateMachineForm = { name: string; config: any; + modules: string[]; + schema: JSONSchema7; }; export type FormHooks = UseFormReturn; @@ -18,6 +21,7 @@ export type FormStep = { export interface FormStepContentProps { formHooks: FormHooks; + clanName: string; } export type FormStepContent = ReactElement; diff --git a/pkgs/ui/src/components/hooks/useAppContext.tsx b/pkgs/ui/src/components/hooks/useAppContext.tsx index b17e69ec3..38c803d0a 100644 --- a/pkgs/ui/src/components/hooks/useAppContext.tsx +++ b/pkgs/ui/src/components/hooks/useAppContext.tsx @@ -1,11 +1,15 @@ -import { AxiosError } from "axios"; +import { useListAllFlakes } from "@/api/flake/flake"; +import { FlakeListResponse } from "@/api/model"; +import { AxiosError, AxiosResponse } from "axios"; import React, { Dispatch, ReactNode, SetStateAction, createContext, + useEffect, useState, } from "react"; +import { KeyedMutator } from "swr"; type AppContextType = { // data: AxiosResponse<{}, any> | undefined; @@ -15,19 +19,16 @@ type AppContextType = { error: AxiosError | undefined; setAppState: Dispatch>; - // mutate: KeyedMutator>; + mutate: KeyedMutator>; swrKey: string | false | Record; }; -// const initialState = { -// isLoading: true, -// } as const; - export const AppContext = createContext({} as AppContextType); type AppState = { isJoined?: boolean; clanName?: string; + flakes?: FlakeListResponse["flakes"]; }; interface AppContextProviderProps { @@ -35,14 +36,33 @@ interface AppContextProviderProps { } export const WithAppState = (props: AppContextProviderProps) => { const { children } = props; - const { isLoading, error, swrKey } = { - isLoading: false, - error: undefined, - swrKey: "default", - }; - + const { + isLoading, + error, + swrKey, + data: flakesResponse, + mutate, + } = useListAllFlakes({ + swr: { + revalidateIfStale: false, + revalidateOnFocus: false, + revalidateOnReconnect: false, + }, + }); const [data, setAppState] = useState({ isJoined: false }); + useEffect(() => { + if (!isLoading && !error && flakesResponse) { + const { + data: { flakes }, + } = flakesResponse; + if (flakes.length >= 1) { + setAppState((c) => ({ ...c, clanName: flakes[0], isJoined: true })); + } + setAppState((c) => ({ ...c, flakes })); + } + }, [flakesResponse, error, isLoading]); + return ( { isLoading, error, swrKey, - // mutate, + mutate, }} > {children} diff --git a/pkgs/ui/src/components/hooks/useMachines.tsx b/pkgs/ui/src/components/hooks/useMachines.tsx index 7ba233f80..19a9bb83d 100644 --- a/pkgs/ui/src/components/hooks/useMachines.tsx +++ b/pkgs/ui/src/components/hooks/useMachines.tsx @@ -34,7 +34,6 @@ type MachineContextType = swrKey: string | false | Record; } | { - flakeName: string; isLoading: true; data: readonly []; }; @@ -44,13 +43,10 @@ const initialState = { data: [], } as const; -export function CreateMachineContext(flakeName: string) { - return useMemo(() => { - return createContext({ - ...initialState, - flakeName, - }); - }, [flakeName]); +export function CreateMachineContext() { + return createContext({ + ...initialState, + }); } interface MachineContextProviderProps { @@ -58,6 +54,8 @@ interface MachineContextProviderProps { flakeName: string; } +const MachineContext = CreateMachineContext(); + export const MachineContextProvider = (props: MachineContextProviderProps) => { const { children, flakeName } = props; const { @@ -74,14 +72,12 @@ export const MachineContextProvider = (props: MachineContextProviderProps) => { if (!isLoading && !error && !isValidating && rawData) { const { machines } = rawData.data; return machines.filter((m) => - filters.every((f) => m[f.name] === f.value), + filters.every((f) => m[f.name] === f.value) ); } return []; }, [isLoading, error, isValidating, rawData, filters]); - const MachineContext = CreateMachineContext(flakeName); - return ( { ); }; -export const useMachines = (flakeName: string) => - React.useContext(CreateMachineContext(flakeName)); +export const useMachines = () => React.useContext(MachineContext); diff --git a/pkgs/ui/src/components/sidebar/index.tsx b/pkgs/ui/src/components/sidebar/index.tsx index 5c95e93e5..8e801f09d 100644 --- a/pkgs/ui/src/components/sidebar/index.tsx +++ b/pkgs/ui/src/components/sidebar/index.tsx @@ -75,9 +75,10 @@ const showSidebar = tw`lg:translate-x-0`; interface SidebarProps { show: boolean; onClose: () => void; + clanSelect: React.ReactNode; } export function Sidebar(props: SidebarProps) { - const { show, onClose } = props; + const { show, onClose, clanSelect } = props; return (
+
{clanSelect}
([]); const tableData = useMemo(() => { - const tableData = machines.data.map((machine) => { + const tableData = machines.map((machine) => { return { name: machine.name, status: machine.status }; }); setFilteredList(tableData); return tableData; - }, [machines.data]); + }, [machines]); const handleChangePage = (event: unknown, newPage: number) => { setPage(newPage); @@ -42,7 +43,7 @@ export function NodeTable() { setPage(0); }; - if (machines.isLoading) { + if (isLoading) { return ( Date: Sat, 4 Nov 2023 14:02:32 +0100 Subject: [PATCH 2/2] format stuff --- pkgs/ui/src/app/layout.tsx | 4 +++- pkgs/ui/src/components/createMachineForm/clanModules.tsx | 2 +- pkgs/ui/src/components/createMachineForm/customConfig.tsx | 4 ++-- pkgs/ui/src/components/hooks/useMachines.tsx | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) diff --git a/pkgs/ui/src/app/layout.tsx b/pkgs/ui/src/app/layout.tsx index 793a0817b..b9d3fa532 100644 --- a/pkgs/ui/src/app/layout.tsx +++ b/pkgs/ui/src/app/layout.tsx @@ -64,7 +64,9 @@ export default function RootLayout({ {(appState) => { const showSidebarDerived = Boolean( - showSidebar && !appState.isLoading && appState.data.isJoined + showSidebar && + !appState.isLoading && + appState.data.isJoined, ); return ( <> diff --git a/pkgs/ui/src/components/createMachineForm/clanModules.tsx b/pkgs/ui/src/components/createMachineForm/clanModules.tsx index 6ad3f3e2b..cf99fec68 100644 --- a/pkgs/ui/src/components/createMachineForm/clanModules.tsx +++ b/pkgs/ui/src/components/createMachineForm/clanModules.tsx @@ -41,7 +41,7 @@ export default function ClanModules(props: ClanModulesProps) { }, [clanName, formHooks]); const handleChange = ( - event: SelectChangeEvent + event: SelectChangeEvent, ) => { const { target: { value }, diff --git a/pkgs/ui/src/components/createMachineForm/customConfig.tsx b/pkgs/ui/src/components/createMachineForm/customConfig.tsx index c1b276625..f845995bb 100644 --- a/pkgs/ui/src/components/createMachineForm/customConfig.tsx +++ b/pkgs/ui/src/components/createMachineForm/customConfig.tsx @@ -44,7 +44,7 @@ export function CustomConfig(props: FormStepContentProps) { } return acc; }, {}), - [schema] + [schema], ); return ( @@ -109,7 +109,7 @@ function PureCustomConfig(props: PureCustomConfigProps) { message: "invalid config", }); toast.error( - "Configuration is invalid. Please check the highlighted fields for details." + "Configuration is invalid. Please check the highlighted fields for details.", ); } else { formHooks.clearErrors("config"); diff --git a/pkgs/ui/src/components/hooks/useMachines.tsx b/pkgs/ui/src/components/hooks/useMachines.tsx index 19a9bb83d..62a9bd908 100644 --- a/pkgs/ui/src/components/hooks/useMachines.tsx +++ b/pkgs/ui/src/components/hooks/useMachines.tsx @@ -72,7 +72,7 @@ export const MachineContextProvider = (props: MachineContextProviderProps) => { if (!isLoading && !error && !isValidating && rawData) { const { machines } = rawData.data; return machines.filter((m) => - filters.every((f) => m[f.name] === f.value) + filters.every((f) => m[f.name] === f.value), ); } return [];