Implemented types context at Zustand and at the whole project

This commit is contained in:
Lucas Oliveira 2024-01-05 21:07:18 -03:00
commit 03d7020185
16 changed files with 143 additions and 168 deletions

View file

@ -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 ? (
<FetchErrorComponent
description={FETCH_ERROR_DESCRIPION}
message={FETCH_ERROR_MESSAGE}
></FetchErrorComponent>
) : loading ? (
<div className="loading-page-panel">
{fetchError ? (
<FetchErrorComponent
description={FETCH_ERROR_DESCRIPION}
message={FETCH_ERROR_MESSAGE}
></FetchErrorComponent>
) : (
<LoadingComponent remSize={50} />
)}
<LoadingComponent remSize={50} />
</div>
) : (
<>

View file

@ -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);

View file

@ -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];

View file

@ -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<Array<FlowType>>([]);
const [tabsState, setTabsState] = useState<FlowsState>({});
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);

View file

@ -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 }) {
<AuthProvider>
<TooltipProvider>
<ReactFlowProvider>
<TypesProvider>
<LocationProvider>
<ApiInterceptor />
<SSEProvider>
<FlowsProvider>
<UndoRedoProvider>{children}</UndoRedoProvider>
</FlowsProvider>
</SSEProvider>
</LocationProvider>
</TypesProvider>
<LocationProvider>
<ApiInterceptor />
<SSEProvider>
<FlowsProvider>
<UndoRedoProvider>{children}</UndoRedoProvider>
</FlowsProvider>
</SSEProvider>
</LocationProvider>
</ReactFlowProvider>
</TooltipProvider>
</AuthProvider>

View file

@ -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<typesContextType>(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<void> {
// 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 (
<typesContext.Provider
value={{
types,
setTypes,
setTemplates,
templates,
data,
setData,
getTypes,
fetchError,
setFetchError,
setFilterEdge,
getFilterEdge,
}}
>
{children}
</typesContext.Provider>
);
}

View file

@ -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<HTMLDivElement>(null);
const [lastCopiedSelection, setLastCopiedSelection] = useState<{

View file

@ -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);

View file

@ -21,6 +21,10 @@ const useAlertStore = create<AlertStoreType>((set, get) => ({
notificationCenter: false,
notificationList: [],
loading: true,
fetchError: false,
setFetchError: (newState: boolean) => {
set({ fetchError: newState });
},
setErrorData: (newState: { title: string; list?: Array<string> }) => {
if (newState.title && newState.title !== "") {
set({

View file

@ -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<TypesStoreType>((set, get) => ({
types: {},
templates: {},
data: {},
getFilterEdge: [],
getTypes: () => {
return new Promise<void>(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 });
},
}));

View file

@ -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;
};

View file

@ -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<HTMLInputElement>) => void;
@ -48,7 +48,6 @@ export type ParameterComponentType = {
required?: boolean;
name?: string;
tooltipTitle: string | undefined;
dataContext?: typesContextType;
optionalHandle?: Array<String> | null;
info?: string;
proxy?: { field: string; id: string };

View file

@ -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<string> };

View file

@ -20,4 +20,6 @@ export type AlertStoreType = {
removeFromNotificationList: (index: string) => void;
loading: boolean;
setLoading: (newState: boolean) => void;
fetchError: boolean;
setFetchError: (newState: boolean) => void;
};

View file

@ -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<void>;
setFilterEdge: (newState) => void;
getFilterEdge: any[];
}

View file

@ -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;
}, {});
}