diff --git a/src/frontend/src/App.tsx b/src/frontend/src/App.tsx index 4f3187b58..97d019d45 100644 --- a/src/frontend/src/App.tsx +++ b/src/frontend/src/App.tsx @@ -18,10 +18,10 @@ import { import { AuthContext } from "./contexts/authContext"; import { FlowsContext } from "./contexts/flowsContext"; import { locationContext } from "./contexts/locationContext"; -import { typesContext } from "./contexts/typesContext"; import { getVersion } from "./controllers/API"; import Router from "./routes"; import useAlertStore from "./stores/alertStore"; +import { useTypesStore } from "./stores/typesStore"; export default function App() { let { setCurrent, setShowSideBar, setIsStackedOpen } = @@ -43,8 +43,7 @@ export default function App() { const successOpen = useAlertStore((state) => state.successOpen); const setSuccessOpen = useAlertStore((state) => state.setSuccessOpen); const loading = useAlertStore((state) => state.loading); - - const { fetchError } = useContext(typesContext); + const fetchError = useAlertStore((state) => state.fetchError); // Initialize state variable for the list of alerts const [alertsList, setAlertsList] = useState< @@ -132,14 +131,15 @@ export default function App() { const { isAuthenticated } = useContext(AuthContext); const { refreshFlows, setVersion } = useContext(FlowsContext); - const { getTypes } = useContext(typesContext); + const getTypes = useTypesStore((state) => state.getTypes); useEffect(() => { // If the user is authenticated, fetch the types. This code is important to check if the user is auth because of the execution order of the useEffect hooks. if (isAuthenticated === true) { // get data from db - refreshFlows(); - getTypes(); + getTypes().then(() => { + refreshFlows(); + }); } getVersion().then((data) => { @@ -156,16 +156,14 @@ export default function App() { }} FallbackComponent={CrashErrorComponent} > - {loading ? ( + {fetchError ? ( + + ) : loading ? (
- {fetchError ? ( - - ) : ( - - )} +
) : ( <> diff --git a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx index 8e73c40e5..6994276ca 100644 --- a/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/components/parameterComponent/index.tsx @@ -27,7 +27,6 @@ import { TOOLTIP_EMPTY, } from "../../../../constants/constants"; import { FlowsContext } from "../../../../contexts/flowsContext"; -import { typesContext } from "../../../../contexts/typesContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import { postCustomComponentUpdate } from "../../../../controllers/API"; import useAlertStore from "../../../../stores/alertStore"; @@ -48,6 +47,7 @@ import { nodeNames, } from "../../../../utils/styleUtils"; import { classNames, groupByFamily } from "../../../../utils/utils"; +import { useTypesStore } from "../../../../stores/typesStore"; export default function ParameterComponent({ left, @@ -78,14 +78,15 @@ export default function ParameterComponent({ const groupedEdge = useRef(null); - const { setFilterEdge } = useContext(typesContext); + const setFilterEdge = useTypesStore((state) => state.setFilterEdge); + let disabled = edges.some( (edge) => edge.targetHandle === scapedJSONStringfy(proxy ? { ...id, proxy } : id) ) ?? false; - const { data: myData } = useContext(typesContext); + const myData = useTypesStore((state) => state.data); const { takeSnapshot } = useContext(undoRedoContext); diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index f3b47836f..3a90c9ce4 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -7,7 +7,6 @@ import InputComponent from "../../components/inputComponent"; import { Textarea } from "../../components/ui/textarea"; import { priorityFields } from "../../constants/constants"; import { useSSE } from "../../contexts/SSEContext"; -import { typesContext } from "../../contexts/typesContext"; import { undoRedoContext } from "../../contexts/undoRedoContext"; import NodeToolbarComponent from "../../pages/FlowPage/components/nodeToolbarComponent"; import useFlowStore from "../../stores/flowStore"; @@ -17,6 +16,7 @@ import { handleKeyDown, scapedJSONStringfy } from "../../utils/reactflowUtils"; import { nodeColors, nodeIconsLucide } from "../../utils/styleUtils"; import { classNames, cn, getFieldTitle } from "../../utils/utils"; import ParameterComponent from "./components/parameterComponent"; +import { useTypesStore } from "../../stores/typesStore"; export default function GenericNode({ data, @@ -29,7 +29,7 @@ export default function GenericNode({ xPos: number; yPos: number; }): JSX.Element { - const { types } = useContext(typesContext); + const types = useTypesStore((state) => state.types); const deleteNode = useFlowStore((state) => state.deleteNode); const setNode = useFlowStore((state) => state.setNode); const name = nodeIconsLucide[data.type] ? data.type : types[data.type]; diff --git a/src/frontend/src/contexts/flowsContext.tsx b/src/frontend/src/contexts/flowsContext.tsx index 489699135..4c1750fce 100644 --- a/src/frontend/src/contexts/flowsContext.tsx +++ b/src/frontend/src/contexts/flowsContext.tsx @@ -35,7 +35,7 @@ import { getRandomDescription, getRandomName, } from "../utils/utils"; -import { typesContext } from "./typesContext"; +import { useTypesStore } from "../stores/typesStore"; const uid = new ShortUniqueId({ length: 5 }); @@ -76,8 +76,7 @@ export function FlowsProvider({ children }: { children: ReactNode }) { const [isLoading, setIsLoading] = useState(false); const [flows, setFlows] = useState>([]); const [tabsState, setTabsState] = useState({}); - const { setData } = useContext(typesContext); - + const setData = useTypesStore((state) => state.setData); const nodes = useFlowStore((state) => state.nodes); const edges = useFlowStore((state) => state.edges); const reactFlowInstance = useFlowStore((state) => state.reactFlowInstance); diff --git a/src/frontend/src/contexts/index.tsx b/src/frontend/src/contexts/index.tsx index 63870e94d..0bc1913fb 100644 --- a/src/frontend/src/contexts/index.tsx +++ b/src/frontend/src/contexts/index.tsx @@ -8,7 +8,6 @@ import { AuthProvider } from "./authContext"; import { FlowsProvider } from "./flowsContext"; import { LocationProvider } from "./locationContext"; -import { TypesProvider } from "./typesContext"; import { UndoRedoProvider } from "./undoRedoContext"; export default function ContextWrapper({ children }: { children: ReactNode }) { @@ -19,16 +18,14 @@ export default function ContextWrapper({ children }: { children: ReactNode }) { - - - - - - {children} - - - - + + + + + {children} + + + diff --git a/src/frontend/src/contexts/typesContext.tsx b/src/frontend/src/contexts/typesContext.tsx deleted file mode 100644 index 16deecf00..000000000 --- a/src/frontend/src/contexts/typesContext.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import _ from "lodash"; -import { - createContext, - ReactNode, - useContext, - useEffect, - useState, -} from "react"; -import { getAll, getHealth } from "../controllers/API"; -import useAlertStore from "../stores/alertStore"; -import { APIKindType } from "../types/api"; -import { typesContextType } from "../types/typesContext"; -import { AuthContext } from "./authContext"; - -//context to share types adn functions from nodes to flow - -const initialValue: typesContextType = { - types: {}, - setTypes: () => {}, - templates: {}, - setTemplates: () => {}, - data: {}, - setData: () => {}, - getTypes: () => {}, - setFetchError: () => {}, - fetchError: false, - setFilterEdge: (filter) => {}, - getFilterEdge: [], -}; - -export const typesContext = createContext(initialValue); - -export function TypesProvider({ children }: { children: ReactNode }) { - const [types, setTypes] = useState({}); - const [templates, setTemplates] = useState({}); - const [data, setData] = useState({}); - const [fetchError, setFetchError] = useState(false); - const setLoading = useAlertStore((state) => state.setLoading); - const [getFilterEdge, setFilterEdge] = useState([]); - - async function getTypes(): Promise { - // We will keep a flag to handle the case where the component is unmounted before the API call resolves. - let isMounted = true; - try { - const result = await getAll(); - // Make sure to only update the state if the component is still mounted. - if (isMounted && result?.status === 200) { - setLoading(false); - let { data } = _.cloneDeep(result); - setData((old) => ({ ...old, ...data })); - setTemplates( - Object.keys(data).reduce((acc, curr) => { - Object.keys(data[curr]).forEach((c: keyof APIKindType) => { - //prevent wrong overwriting of the component template by a group of the same type - if (!data[curr][c].flow) acc[c] = data[curr][c]; - }); - return acc; - }, {}) - ); - // Set the types by reducing over the keys of the result data and updating the accumulator. - setTypes( - // Reverse the keys so the tool world does not overlap - Object.keys(data) - .reverse() - .reduce((acc, curr) => { - Object.keys(data[curr]).forEach((c: keyof APIKindType) => { - acc[c] = curr; - // Add the base classes to the accumulator as well. - data[curr][c].base_classes?.forEach((b) => { - acc[b] = curr; - }); - }); - return acc; - }, {}) - ); - } - } catch (error) { - console.error("An error has occurred while fetching types."); - console.log(error); - await getHealth().catch((e) => { - setFetchError(true); - }); - } - } - - return ( - - {children} - - ); -} diff --git a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx index 2a2544997..d5d9f0f26 100644 --- a/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/PageComponent/index.tsx @@ -25,7 +25,6 @@ import Chat from "../../../../components/chatComponent"; import Loading from "../../../../components/ui/loading"; import { FlowsContext } from "../../../../contexts/flowsContext"; import { locationContext } from "../../../../contexts/locationContext"; -import { typesContext } from "../../../../contexts/typesContext"; import { undoRedoContext } from "../../../../contexts/undoRedoContext"; import useAlertStore from "../../../../stores/alertStore"; import useFlowStore from "../../../../stores/flowStore"; @@ -42,6 +41,7 @@ import { cn, getRandomName, isWrappedWithClass } from "../../../../utils/utils"; import ConnectionLineComponent from "../ConnectionLineComponent"; import SelectionMenu from "../SelectionMenuComponent"; import ExtraSidebar from "../extraSidebarComponent"; +import { useTypesStore } from "../../../../stores/typesStore"; const nodeTypes = { genericNode: GenericNode, @@ -55,7 +55,9 @@ export default function Page({ view?: boolean; }): JSX.Element { let { uploadFlow, saveFlow } = useContext(FlowsContext); - const { types, templates, setFilterEdge } = useContext(typesContext); + const types = useTypesStore((state) => state.types); + const templates = useTypesStore((state) => state.templates); + const setFilterEdge = useTypesStore((state) => state.setFilterEdge); const reactFlowWrapper = useRef(null); const [lastCopiedSelection, setLastCopiedSelection] = useState<{ diff --git a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx index 070c3e962..710b9fd9a 100644 --- a/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/extraSidebarComponent/index.tsx @@ -5,7 +5,6 @@ import IconComponent from "../../../../components/genericIconComponent"; import { Input } from "../../../../components/ui/input"; import { Separator } from "../../../../components/ui/separator"; import { FlowsContext } from "../../../../contexts/flowsContext"; -import { typesContext } from "../../../../contexts/typesContext"; import ApiModal from "../../../../modals/ApiModal"; import ExportModal from "../../../../modals/exportModal"; import ShareModal from "../../../../modals/shareModal"; @@ -25,10 +24,13 @@ import { } from "../../../../utils/utils"; import DisclosureComponent from "../DisclosureComponent"; import SidebarDraggableComponent from "./sideBarDraggableComponent"; +import { useTypesStore } from "../../../../stores/typesStore"; export default function ExtraSidebar(): JSX.Element { - const { data, templates, getFilterEdge, setFilterEdge } = - useContext(typesContext); + const data = useTypesStore((state) => state.data); + const templates = useTypesStore((state) => state.templates); + const getFilterEdge = useTypesStore((state) => state.getFilterEdge); + const setFilterEdge = useTypesStore((state) => state.setFilterEdge); const { flows, tabId, uploadFlow, saveFlow } = useContext(FlowsContext); const hasStore = useStoreStore((state) => state.hasStore); diff --git a/src/frontend/src/stores/alertStore.ts b/src/frontend/src/stores/alertStore.ts index 63c58e28c..a5c2ae41a 100644 --- a/src/frontend/src/stores/alertStore.ts +++ b/src/frontend/src/stores/alertStore.ts @@ -21,6 +21,10 @@ const useAlertStore = create((set, get) => ({ notificationCenter: false, notificationList: [], loading: true, + fetchError: false, + setFetchError: (newState: boolean) => { + set({ fetchError: newState }); + }, setErrorData: (newState: { title: string; list?: Array }) => { if (newState.title && newState.title !== "") { set({ diff --git a/src/frontend/src/stores/typesStore.ts b/src/frontend/src/stores/typesStore.ts new file mode 100644 index 000000000..f28791ba0 --- /dev/null +++ b/src/frontend/src/stores/typesStore.ts @@ -0,0 +1,52 @@ +import { create } from "zustand"; +import { getAll, getHealth } from "../controllers/API"; +import { APIDataType, APIKindType } from "../types/api"; +import { TypesStoreType } from "../types/zustand/types"; +import useAlertStore from "./alertStore"; +import { templatesGenerator, typesGenerator } from "../utils/reactflowUtils"; + +export const useTypesStore = create((set, get) => ({ + types: {}, + templates: {}, + data: {}, + getFilterEdge: [], + getTypes: () => { + return new Promise(async (resolve, reject) => { + getAll() + .then((response) => { + const data = response.data; + useAlertStore.setState({ loading: false }); + set((old) => ({ + types: typesGenerator(data), + data: { ...old.data, ...data }, + templates: templatesGenerator(data), + })); + resolve(); + }) + .catch((error) => { + console.error("An error has occurred while fetching types."); + console.log(error); + getHealth().catch((e) => { + useAlertStore.setState({ + fetchError: true, + loading: false, + }); + reject(); + }); + }); + }); + }, + setTypes: (newState: {}) => { + set({ types: newState }); + }, + setTemplates: (newState: {}) => { + set({ templates: newState }); + }, + setData: (change: APIDataType | ((old: APIDataType) => APIDataType)) => { + let newChange = typeof change === "function" ? change(get().data) : change; + set({ data: newChange }); + }, + setFilterEdge: (newState) => { + set({ getFilterEdge: newState }); + }, +})); diff --git a/src/frontend/src/types/api/index.ts b/src/frontend/src/types/api/index.ts index 38cd59bda..16c840e37 100644 --- a/src/frontend/src/types/api/index.ts +++ b/src/frontend/src/types/api/index.ts @@ -2,8 +2,8 @@ import { Edge, Node, Viewport } from "reactflow"; import { FlowType } from "../flow"; //kind and class are just representative names to represent the actual structure of the object received by the API export type APIDataType = { [key: string]: APIKindType }; -export type APIObjectType = { kind: APIKindType; [key: string]: APIKindType }; -export type APIKindType = { class: APIClassType; [key: string]: APIClassType }; +export type APIObjectType = { [key: string]: APIKindType }; +export type APIKindType = { [key: string]: APIClassType }; export type APITemplateType = { [key: string]: TemplateVariableType; }; diff --git a/src/frontend/src/types/components/index.ts b/src/frontend/src/types/components/index.ts index 314150b4a..986260665 100644 --- a/src/frontend/src/types/components/index.ts +++ b/src/frontend/src/types/components/index.ts @@ -3,8 +3,8 @@ import { ReactFlowJsonObject, XYPosition } from "reactflow"; import { APIClassType, APITemplateType, TemplateVariableType } from "../api"; import { ChatMessageType } from "../chat"; import { FlowStyleType, FlowType, NodeDataType, NodeType } from "../flow/index"; -import { typesContextType } from "../typesContext"; import { sourceHandleType, targetHandleType } from "./../flow/index"; +import { TypesStoreType } from "../zustand/types"; export type InputComponentType = { autoFocus?: boolean; onBlur?: (event: React.FocusEvent) => void; @@ -48,7 +48,6 @@ export type ParameterComponentType = { required?: boolean; name?: string; tooltipTitle: string | undefined; - dataContext?: typesContextType; optionalHandle?: Array | null; info?: string; proxy?: { field: string; id: string }; diff --git a/src/frontend/src/types/typesContext/index.ts b/src/frontend/src/types/typesContext/index.ts index e0d9644ef..c7314fea0 100644 --- a/src/frontend/src/types/typesContext/index.ts +++ b/src/frontend/src/types/typesContext/index.ts @@ -1,24 +1,5 @@ import { Edge, Node } from "reactflow"; import { AlertItemType } from "../alerts"; -import { APIClassType, APIDataType } from "../api"; - -const types: { [char: string]: string } = {}; -const template: { [char: string]: APIClassType } = {}; -const data: { [char: string]: string } = {}; - -export type typesContextType = { - types: typeof types; - setTypes: (newState: {}) => void; - templates: typeof template; - setTemplates: (newState: {}) => void; - data: APIDataType; - setData: (newState: {}) => void; - getTypes: () => void; - fetchError: boolean; - setFetchError: (newState: boolean) => void; - setFilterEdge: (newState) => void; - getFilterEdge: any[]; -}; export type alertContextType = { errorData: { title: string; list?: Array }; diff --git a/src/frontend/src/types/zustand/alert/index.ts b/src/frontend/src/types/zustand/alert/index.ts index 1e273cc4e..cdecff157 100644 --- a/src/frontend/src/types/zustand/alert/index.ts +++ b/src/frontend/src/types/zustand/alert/index.ts @@ -20,4 +20,6 @@ export type AlertStoreType = { removeFromNotificationList: (index: string) => void; loading: boolean; setLoading: (newState: boolean) => void; + fetchError: boolean; + setFetchError: (newState: boolean) => void; }; diff --git a/src/frontend/src/types/zustand/types/index.ts b/src/frontend/src/types/zustand/types/index.ts new file mode 100644 index 000000000..904e3058e --- /dev/null +++ b/src/frontend/src/types/zustand/types/index.ts @@ -0,0 +1,13 @@ +import { APIClassType, APIDataType } from "../../api"; + +export type TypesStoreType = { + types: { [char: string]: string }; + setTypes: (newState: {}) => void; + templates: { [char: string]: APIClassType }; + setTemplates: (newState: {}) => void; + data: APIDataType; + setData: (newState: {}) => void; + getTypes: () => Promise; + setFilterEdge: (newState) => void; + getFilterEdge: any[]; +} \ No newline at end of file diff --git a/src/frontend/src/utils/reactflowUtils.ts b/src/frontend/src/utils/reactflowUtils.ts index 550443455..d9dcfe2ec 100644 --- a/src/frontend/src/utils/reactflowUtils.ts +++ b/src/frontend/src/utils/reactflowUtils.ts @@ -12,7 +12,12 @@ import { LANGFLOW_SUPPORTED_TYPES, specialCharsRegex, } from "../constants/constants"; -import { APITemplateType, TemplateVariableType } from "../types/api"; +import { + APIKindType, + APIObjectType, + APITemplateType, + TemplateVariableType, +} from "../types/api"; import { FlowType, NodeDataType, @@ -1138,3 +1143,28 @@ export function removeFileNameFromComponents(flow: FlowType) { } }); } + +export function typesGenerator(data: APIObjectType) { + return Object.keys(data) + .reverse() + .reduce((acc, curr) => { + Object.keys(data[curr]).forEach((c: keyof APIKindType) => { + acc[c] = curr; + // Add the base classes to the accumulator as well. + data[curr][c].base_classes?.forEach((b) => { + acc[b] = curr; + }); + }); + return acc; + }, {}); +} + +export function templatesGenerator(data: APIObjectType) { + return Object.keys(data).reduce((acc, curr) => { + Object.keys(data[curr]).forEach((c: keyof APIKindType) => { + //prevent wrong overwriting of the component template by a group of the same type + if (!data[curr][c].flow) acc[c] = data[curr][c]; + }); + return acc; + }, {}); +}