refactor: reduce menu bar rerenders (#7589)
* reduce menu bar rerenders * imports cleanup * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
8983516381
commit
2c2a348a2f
3 changed files with 73 additions and 31 deletions
|
|
@ -4,7 +4,6 @@ import { useCustomNavigate } from "@/customization/hooks/use-custom-navigate";
|
||||||
import useAddFlow from "@/hooks/flows/use-add-flow";
|
import useAddFlow from "@/hooks/flows/use-add-flow";
|
||||||
import useSaveFlow from "@/hooks/flows/use-save-flow";
|
import useSaveFlow from "@/hooks/flows/use-save-flow";
|
||||||
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
import useUploadFlow from "@/hooks/flows/use-upload-flow";
|
||||||
import { customStringify } from "@/utils/reactflowUtils";
|
|
||||||
import { useHotkeys } from "react-hotkeys-hook";
|
import { useHotkeys } from "react-hotkeys-hook";
|
||||||
|
|
||||||
import IconComponent from "@/components/common/genericIconComponent";
|
import IconComponent from "@/components/common/genericIconComponent";
|
||||||
|
|
@ -22,6 +21,7 @@ import { UPLOAD_ERROR_ALERT } from "@/constants/alerts_constants";
|
||||||
import { SAVED_HOVER } from "@/constants/constants";
|
import { SAVED_HOVER } from "@/constants/constants";
|
||||||
import { useGetRefreshFlowsQuery } from "@/controllers/API/queries/flows/use-get-refresh-flows-query";
|
import { useGetRefreshFlowsQuery } from "@/controllers/API/queries/flows/use-get-refresh-flows-query";
|
||||||
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
|
import { useGetFoldersQuery } from "@/controllers/API/queries/folders/use-get-folders";
|
||||||
|
import { useUnsavedChanges } from "@/hooks/useUnsavedChanges";
|
||||||
import ExportModal from "@/modals/exportModal";
|
import ExportModal from "@/modals/exportModal";
|
||||||
import FlowLogsModal from "@/modals/flowLogsModal";
|
import FlowLogsModal from "@/modals/flowLogsModal";
|
||||||
import FlowSettingsModal from "@/modals/flowSettingsModal";
|
import FlowSettingsModal from "@/modals/flowSettingsModal";
|
||||||
|
|
@ -33,6 +33,7 @@ import { useShortcutsStore } from "@/stores/shortcuts";
|
||||||
import { swatchColors } from "@/utils/styleUtils";
|
import { swatchColors } from "@/utils/styleUtils";
|
||||||
import { cn, getNumberFromString } from "@/utils/utils";
|
import { cn, getNumberFromString } from "@/utils/utils";
|
||||||
import { useQueryClient } from "@tanstack/react-query";
|
import { useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { useShallow } from "zustand/react/shallow";
|
||||||
|
|
||||||
export const MenuBar = ({}: {}): JSX.Element => {
|
export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
const shortcuts = useShortcutsStore((state) => state.shortcuts);
|
||||||
|
|
@ -50,18 +51,36 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
const saveFlow = useSaveFlow();
|
const saveFlow = useSaveFlow();
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
const autoSaving = useFlowsManagerStore((state) => state.autoSaving);
|
||||||
const currentFlow = useFlowStore((state) => state.currentFlow);
|
const {
|
||||||
const currentSavedFlow = useFlowsManagerStore((state) => state.currentFlow);
|
currentFlowName,
|
||||||
const updatedAt = currentSavedFlow?.updated_at;
|
currentFlowId,
|
||||||
|
currentFlowFolderId,
|
||||||
|
currentFlowIcon,
|
||||||
|
currentFlowGradient,
|
||||||
|
} = useFlowStore(
|
||||||
|
useShallow((state) => ({
|
||||||
|
currentFlowName: state.currentFlow?.name,
|
||||||
|
currentFlowId: state.currentFlow?.id,
|
||||||
|
currentFlowFolderId: state.currentFlow?.folder_id,
|
||||||
|
currentFlowIcon: state.currentFlow?.icon,
|
||||||
|
currentFlowGradient: state.currentFlow?.gradient,
|
||||||
|
})),
|
||||||
|
);
|
||||||
|
const { updated_at: updatedAt } = useFlowsManagerStore(
|
||||||
|
useShallow((state) => ({
|
||||||
|
updated_at: state.currentFlow?.updated_at,
|
||||||
|
})),
|
||||||
|
);
|
||||||
const onFlowPage = useFlowStore((state) => state.onFlowPage);
|
const onFlowPage = useFlowStore((state) => state.onFlowPage);
|
||||||
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
const setCurrentFlow = useFlowsManagerStore((state) => state.setCurrentFlow);
|
||||||
const stopBuilding = useFlowStore((state) => state.stopBuilding);
|
const stopBuilding = useFlowStore((state) => state.stopBuilding);
|
||||||
const [editingName, setEditingName] = useState(false);
|
const [editingName, setEditingName] = useState(false);
|
||||||
const [flowName, setFlowName] = useState(currentFlow?.name ?? "");
|
const [flowName, setFlowName] = useState(currentFlowName ?? "");
|
||||||
const [isInvalidName, setIsInvalidName] = useState(false);
|
const [isInvalidName, setIsInvalidName] = useState(false);
|
||||||
const nameInputRef = useRef<HTMLInputElement>(null);
|
const nameInputRef = useRef<HTMLInputElement>(null);
|
||||||
const [inputWidth, setInputWidth] = useState<number>(0);
|
const [inputWidth, setInputWidth] = useState<number>(0);
|
||||||
const measureRef = useRef<HTMLSpanElement>(null);
|
const measureRef = useRef<HTMLSpanElement>(null);
|
||||||
|
const changesNotSaved = useUnsavedChanges();
|
||||||
|
|
||||||
const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery();
|
const { data: folders, isFetched: isFoldersFetched } = useGetFoldersQuery();
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
|
|
@ -73,9 +92,9 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
flows.forEach((flow) => {
|
flows.forEach((flow) => {
|
||||||
tempNameList.push(flow.name);
|
tempNameList.push(flow.name);
|
||||||
});
|
});
|
||||||
setNameList(tempNameList.filter((name) => name !== currentFlow?.name));
|
setNameList(tempNameList.filter((name) => name !== currentFlowName));
|
||||||
}
|
}
|
||||||
}, [flows, currentFlow?.name]);
|
}, [flows, currentFlowName]);
|
||||||
|
|
||||||
useGetRefreshFlowsQuery(
|
useGetRefreshFlowsQuery(
|
||||||
{
|
{
|
||||||
|
|
@ -86,13 +105,10 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
);
|
);
|
||||||
|
|
||||||
const currentFolder = useMemo(
|
const currentFolder = useMemo(
|
||||||
() => folders?.find((f) => f.id === currentFlow?.folder_id),
|
() => folders?.find((f) => f.id === currentFlowFolderId),
|
||||||
[folders, currentFlow?.folder_id],
|
[folders, currentFlowFolderId],
|
||||||
);
|
);
|
||||||
|
|
||||||
const changesNotSaved =
|
|
||||||
customStringify(currentFlow) !== customStringify(currentSavedFlow);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (measureRef.current) {
|
if (measureRef.current) {
|
||||||
setInputWidth(measureRef.current.offsetWidth);
|
setInputWidth(measureRef.current.offsetWidth);
|
||||||
|
|
@ -163,26 +179,29 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
(e: React.KeyboardEvent<HTMLInputElement>) => {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
setEditingName(false);
|
setEditingName(false);
|
||||||
setFlowName(currentFlow?.name ?? "");
|
setFlowName(currentFlowName ?? "");
|
||||||
setIsInvalidName(false);
|
setIsInvalidName(false);
|
||||||
}
|
}
|
||||||
if (e.key === "Enter") {
|
if (e.key === "Enter") {
|
||||||
nameInputRef.current?.blur();
|
nameInputRef.current?.blur();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[currentFlow?.name],
|
[currentFlowName],
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleNameSubmit = useCallback(() => {
|
const handleNameSubmit = useCallback(() => {
|
||||||
if (
|
if (
|
||||||
flowName.trim() !== "" &&
|
flowName.trim() !== "" &&
|
||||||
flowName !== currentFlow?.name &&
|
flowName !== currentFlowName &&
|
||||||
!isInvalidName
|
!isInvalidName
|
||||||
) {
|
) {
|
||||||
|
// Get a one-time snapshot of currentFlow using get()
|
||||||
|
const currentFlowSnapshot = useFlowStore.getState().currentFlow;
|
||||||
|
|
||||||
const newFlow = {
|
const newFlow = {
|
||||||
...currentFlow!,
|
...currentFlowSnapshot!,
|
||||||
name: flowName,
|
name: flowName,
|
||||||
id: currentFlow!.id,
|
id: currentFlowId!,
|
||||||
};
|
};
|
||||||
setCurrentFlow(newFlow);
|
setCurrentFlow(newFlow);
|
||||||
saveFlow(newFlow)
|
saveFlow(newFlow)
|
||||||
|
|
@ -194,22 +213,23 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
title: "Error updating flow name",
|
title: "Error updating flow name",
|
||||||
list: [(error as Error).message],
|
list: [(error as Error).message],
|
||||||
});
|
});
|
||||||
setFlowName(currentFlow?.name ?? "");
|
setFlowName(currentFlowName ?? "");
|
||||||
});
|
});
|
||||||
} else if (isInvalidName) {
|
} else if (isInvalidName) {
|
||||||
setErrorData({
|
setErrorData({
|
||||||
title: "Invalid flow name",
|
title: "Invalid flow name",
|
||||||
list: ["Name already exists"],
|
list: ["Name already exists"],
|
||||||
});
|
});
|
||||||
setFlowName(currentFlow?.name ?? "");
|
setFlowName(currentFlowName ?? "");
|
||||||
} else {
|
} else {
|
||||||
setFlowName(currentFlow?.name ?? "");
|
setFlowName(currentFlowName ?? "");
|
||||||
}
|
}
|
||||||
setEditingName(false);
|
setEditingName(false);
|
||||||
setIsInvalidName(false);
|
setIsInvalidName(false);
|
||||||
}, [
|
}, [
|
||||||
flowName,
|
flowName,
|
||||||
currentFlow,
|
currentFlowName,
|
||||||
|
currentFlowId,
|
||||||
setCurrentFlow,
|
setCurrentFlow,
|
||||||
saveFlow,
|
saveFlow,
|
||||||
setSuccessData,
|
setSuccessData,
|
||||||
|
|
@ -218,10 +238,10 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
]);
|
]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (currentFlow && !editingName) {
|
if (currentFlowName && !editingName) {
|
||||||
setFlowName(currentFlow.name);
|
setFlowName(currentFlowName);
|
||||||
}
|
}
|
||||||
}, [currentFlow, editingName]);
|
}, [currentFlowName, editingName]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (measureRef.current) {
|
if (measureRef.current) {
|
||||||
|
|
@ -230,12 +250,12 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
}, [flowName]);
|
}, [flowName]);
|
||||||
|
|
||||||
const swatchIndex =
|
const swatchIndex =
|
||||||
(currentFlow?.gradient && !isNaN(parseInt(currentFlow?.gradient))
|
(currentFlowGradient && !isNaN(parseInt(currentFlowGradient))
|
||||||
? parseInt(currentFlow?.gradient)
|
? parseInt(currentFlowGradient)
|
||||||
: getNumberFromString(currentFlow?.gradient ?? currentFlow?.id ?? "")) %
|
: getNumberFromString(currentFlowGradient ?? currentFlowId ?? "")) %
|
||||||
swatchColors.length;
|
swatchColors.length;
|
||||||
|
|
||||||
return currentFlow && onFlowPage ? (
|
return currentFlowName && onFlowPage ? (
|
||||||
<div
|
<div
|
||||||
className="flex w-full items-center justify-center gap-2"
|
className="flex w-full items-center justify-center gap-2"
|
||||||
data-testid="menu_bar_wrapper"
|
data-testid="menu_bar_wrapper"
|
||||||
|
|
@ -270,7 +290,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
</div>
|
</div>
|
||||||
<div className={cn(`flex rounded p-1`, swatchColors[swatchIndex])}>
|
<div className={cn(`flex rounded p-1`, swatchColors[swatchIndex])}>
|
||||||
<IconComponent
|
<IconComponent
|
||||||
name={currentFlow?.icon ?? "Workflow"}
|
name={currentFlowIcon ?? "Workflow"}
|
||||||
className="h-3.5 w-3.5"
|
className="h-3.5 w-3.5"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -305,7 +325,7 @@ export const MenuBar = ({}: {}): JSX.Element => {
|
||||||
onKeyDown={handleKeyDown}
|
onKeyDown={handleKeyDown}
|
||||||
onFocus={() => {
|
onFocus={() => {
|
||||||
setEditingName(true);
|
setEditingName(true);
|
||||||
setFlowName(currentFlow.name);
|
setFlowName(currentFlowName);
|
||||||
}}
|
}}
|
||||||
onBlur={handleNameSubmit}
|
onBlur={handleNameSubmit}
|
||||||
value={flowName}
|
value={flowName}
|
||||||
|
|
|
||||||
18
src/frontend/src/hooks/useUnsavedChanges.ts
Normal file
18
src/frontend/src/hooks/useUnsavedChanges.ts
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
import useFlowStore from "../stores/flowStore";
|
||||||
|
import useFlowsManagerStore from "../stores/flowsManagerStore";
|
||||||
|
import { customStringify } from "../utils/reactflowUtils";
|
||||||
|
|
||||||
|
export function useUnsavedChanges() {
|
||||||
|
const currentFlow = useFlowStore((state) => state.currentFlow);
|
||||||
|
const savedFlow = useFlowsManagerStore((state) => state.currentFlow);
|
||||||
|
|
||||||
|
if (!currentFlow || !savedFlow) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((currentFlow?.data?.nodes?.length ?? 0) > 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return customStringify(currentFlow) !== customStringify(savedFlow);
|
||||||
|
}
|
||||||
|
|
@ -18,8 +18,12 @@ export default function FlowSettingsModal({
|
||||||
flowData,
|
flowData,
|
||||||
details,
|
details,
|
||||||
}: FlowSettingsPropsType): JSX.Element {
|
}: FlowSettingsPropsType): JSX.Element {
|
||||||
|
if (!open) return <></>;
|
||||||
|
|
||||||
const saveFlow = useSaveFlow();
|
const saveFlow = useSaveFlow();
|
||||||
const currentFlow = useFlowStore((state) => state.currentFlow);
|
const currentFlow = useFlowStore((state) =>
|
||||||
|
flowData ? undefined : state.currentFlow,
|
||||||
|
);
|
||||||
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
|
const setCurrentFlow = useFlowStore((state) => state.setCurrentFlow);
|
||||||
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
const setSuccessData = useAlertStore((state) => state.setSuccessData);
|
||||||
const flows = useFlowsManagerStore((state) => state.flows);
|
const flows = useFlowsManagerStore((state) => state.flows);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue