From 7fe6c53b374f354aa841f3ba247ea6f834a0d2fb Mon Sep 17 00:00:00 2001 From: anovazzi1 Date: Wed, 14 Aug 2024 12:04:25 -0300 Subject: [PATCH] refactor: improve node toolbar code (#3311) * remove repeated code * refactor: remove useless code * feat: sort input parameters in GenericNode renderInputParameter * refactor: remove unused code in GenericNode component * refactor: add NodeName component for displaying and editing node names * refactor: add NodeDescription component for displaying and editing node descriptions * fix import and add autofocus on nodeName * feat: add NodeStatus component for displaying and managing node status * [autofix.ci] apply automated fixes * refactor: remove unused code in GenericNode component * fix bugs on minimize * [autofix.ci] apply automated fixes * refactor: remove unused code and handle count in GenericNodeToolbar component * refactor: Add useShortcuts hook for handling keyboard shortcuts in nodeToolbarComponent * refactor: Add keyboard shortcuts handling to nodeToolbarComponent need to test * refactor: Update FreezeAllVertices function in NodeToolbarComponent * feat: Add getNodeLength function to calculate the length of a node's template fields * refactor: Update RenderIcons component to use navigator.platform for detecting macOS * refactor: Add ShortcutDisplay component to nodeToolbarComponent * refactor: Update nodeToolbarComponent to remove RenderIcons and add ShortcutDisplay * refactor: Improve keyboard shortcuts handling in nodeToolbarComponent * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> --- .../src/CustomNodes/GenericNode/index.tsx | 12 - .../components/renderIconComponent/index.tsx | 4 +- .../hooks/use-shortcuts.tsx | 115 ++++++++ .../components/nodeToolbarComponent/index.tsx | 275 ++++++------------ .../shortcutDisplay/index.tsx | 36 +++ src/frontend/src/types/components/index.ts | 3 - src/frontend/src/utils/utils.ts | 24 +- 7 files changed, 266 insertions(+), 203 deletions(-) create mode 100644 src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/hooks/use-shortcuts.tsx create mode 100644 src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/shortcutDisplay/index.tsx diff --git a/src/frontend/src/CustomNodes/GenericNode/index.tsx b/src/frontend/src/CustomNodes/GenericNode/index.tsx index e6dde996e..707000c02 100644 --- a/src/frontend/src/CustomNodes/GenericNode/index.tsx +++ b/src/frontend/src/CustomNodes/GenericNode/index.tsx @@ -52,7 +52,6 @@ export default function GenericNode({ const takeSnapshot = useFlowsManagerStore((state) => state.takeSnapshot); const [isOutdated, setIsOutdated] = useState(false); const [isUserEdited, setIsUserEdited] = useState(false); - const [handles, setHandles] = useState(0); const [borderColor, setBorderColor] = useState(""); const [showNode, setShowNode] = useState(data.showNode ?? true); @@ -108,15 +107,6 @@ export default function GenericNode({ checkNodeIconFragment, ); - function countHandles(): void { - const count = countHandlesFn(data); - setHandles(count); - } - - useEffect(() => { - countHandles(); - }, [data, data.node]); - useEffect(() => { setShowNode(data.showNode ?? true); }, [data.showNode]); @@ -228,7 +218,6 @@ export default function GenericNode({ })); }} setShowState={setShowNode} - numberOfHandles={handles} numberOfOutputHandles={shownOutputs.length ?? 0} showNode={showNode} openAdvancedModal={false} @@ -244,7 +233,6 @@ export default function GenericNode({ takeSnapshot, setNode, setShowNode, - handles, showNode, updateNodeCode, isOutdated, diff --git a/src/frontend/src/components/renderIconComponent/index.tsx b/src/frontend/src/components/renderIconComponent/index.tsx index b75d948a0..ec17cc187 100644 --- a/src/frontend/src/components/renderIconComponent/index.tsx +++ b/src/frontend/src/components/renderIconComponent/index.tsx @@ -1,12 +1,12 @@ import ForwardedIconComponent from "../genericIconComponent"; export default function RenderIcons({ - isMac, + isMac = navigator.platform.toUpperCase().includes("MAC"), hasShift, filteredShortcut, shortcutWPlus, }: { - isMac: boolean; + isMac?: boolean; hasShift: boolean; filteredShortcut: string[]; shortcutWPlus: string[]; diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/hooks/use-shortcuts.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/hooks/use-shortcuts.tsx new file mode 100644 index 000000000..f9d49219d --- /dev/null +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/hooks/use-shortcuts.tsx @@ -0,0 +1,115 @@ +import { useShortcutsStore } from "@/stores/shortcuts"; +import { useHotkeys } from "react-hotkeys-hook"; +import isWrappedWithClass from "../../PageComponent/utils/is-wrapped-with-class"; + +export default function useShortcuts({ + showOverrideModal, + showModalAdvanced, + openModal, + showconfirmShare, + FreezeAllVertices, + Freeze, + downloadFunction, + displayDocs, + saveComponent, + showAdvance, + handleCodeModal, + shareComponent, + ungroup, + minimizeFunction, +}: { + showOverrideModal: boolean; + showModalAdvanced: boolean; + openModal: boolean; + showconfirmShare: boolean; + FreezeAllVertices: () => void; + Freeze: () => void; + downloadFunction: () => void; + displayDocs: () => void; + saveComponent: () => void; + showAdvance: () => void; + handleCodeModal: () => void; + shareComponent: () => void; + ungroup: () => void; + minimizeFunction: () => void; +}) { + const advanced = useShortcutsStore((state) => state.advanced); + const minimize = useShortcutsStore((state) => state.minimize); + const component = useShortcutsStore((state) => state.component); + const save = useShortcutsStore((state) => state.save); + const docs = useShortcutsStore((state) => state.docs); + const code = useShortcutsStore((state) => state.code); + const group = useShortcutsStore((state) => state.group); + const download = useShortcutsStore((state) => state.download); + const freeze = useShortcutsStore((state) => state.freeze); + const freezeAll = useShortcutsStore((state) => state.FreezePath); + + function handleFreezeAll(e: KeyboardEvent) { + if (isWrappedWithClass(e, "noflow")) return; + e.preventDefault(); + FreezeAllVertices(); + } + + function handleFreeze(e: KeyboardEvent) { + if (isWrappedWithClass(e, "noflow")) return; + e.preventDefault(); + Freeze(); + } + + function handleDownloadWShortcut(e: KeyboardEvent) { + e.preventDefault(); + downloadFunction(); + } + + function handleDocsWShortcut(e: KeyboardEvent) { + e.preventDefault(); + displayDocs(); + } + + function handleSaveWShortcut(e: KeyboardEvent) { + if (isWrappedWithClass(e, "noflow") && !showOverrideModal) return; + e.preventDefault(); + saveComponent(); + } + + function handleAdvancedWShortcut(e: KeyboardEvent) { + //check if there is another modal open + if (isWrappedWithClass(e, "noflow") && !showModalAdvanced) return; + e.preventDefault(); + showAdvance(); + } + + function handleCodeWShortcut(e: KeyboardEvent) { + if (isWrappedWithClass(e, "noflow") && !openModal) return; + e.preventDefault(); + handleCodeModal(); + } + + function handleShareWShortcut(e: KeyboardEvent) { + if (isWrappedWithClass(e, "noflow") && !showconfirmShare) return; + e.preventDefault(); + shareComponent(); + } + function handleGroupWShortcut(e: KeyboardEvent) { + if (isWrappedWithClass(e, "noflow")) return; + e.preventDefault(); + ungroup(); + } + + function handleMinimizeWShortcut(e: KeyboardEvent) { + if (isWrappedWithClass(e, "noflow")) return; + e.preventDefault(); + minimizeFunction(); + } + + useHotkeys(minimize, handleMinimizeWShortcut, { preventDefault: true }); + useHotkeys(group, handleGroupWShortcut, { preventDefault: true }); + useHotkeys(component, handleShareWShortcut, { preventDefault: true }); + useHotkeys(code, handleCodeWShortcut, { preventDefault: true }); + useHotkeys(advanced, handleAdvancedWShortcut, { preventDefault: true }); + useHotkeys(save, handleSaveWShortcut, { preventDefault: true }); + useHotkeys(docs, handleDocsWShortcut, { preventDefault: true }); + useHotkeys(download, handleDownloadWShortcut, { preventDefault: true }); + useHotkeys(freeze, handleFreeze); + useHotkeys(freezeAll, handleFreezeAll); +} diff --git a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx index 743c5acd4..fab90a2e2 100644 --- a/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx +++ b/src/frontend/src/pages/FlowPage/components/nodeToolbarComponent/index.tsx @@ -1,3 +1,4 @@ +import { countHandlesFn } from "@/CustomNodes/helpers/count-handles"; import useHandleOnNewValue from "@/CustomNodes/hooks/use-handle-new-value"; import useHandleNodeClass from "@/CustomNodes/hooks/use-handle-node-class"; import { usePostRetrieveVertexOrder } from "@/controllers/API/queries/vertex"; @@ -5,11 +6,9 @@ import useAddFlow from "@/hooks/flows/use-add-flow"; import { APIClassType } from "@/types/api"; import _, { cloneDeep } from "lodash"; import { useEffect, useState } from "react"; -import { useHotkeys } from "react-hotkeys-hook"; import { useUpdateNodeInternals } from "reactflow"; import CodeAreaComponent from "../../../../components/codeAreaComponent"; import IconComponent from "../../../../components/genericIconComponent"; -import RenderIcons from "../../../../components/renderIconComponent"; import ShadTooltip from "../../../../components/shadTooltipComponent"; import { Select, @@ -34,15 +33,20 @@ import { expandGroupNode, updateFlowPosition, } from "../../../../utils/reactflowUtils"; -import { classNames, cn } from "../../../../utils/utils"; -import isWrappedWithClass from "../PageComponent/utils/is-wrapped-with-class"; +import { + classNames, + cn, + getNodeLength, + openInNewTab, +} from "../../../../utils/utils"; +import useShortcuts from "./hooks/use-shortcuts"; +import ShortcutDisplay from "./shortcutDisplay"; import ToolbarSelectItem from "./toolbarSelectItem"; export default function NodeToolbarComponent({ data, deleteNode, setShowNode, - numberOfHandles, numberOfOutputHandles, showNode, name = "code", @@ -58,36 +62,23 @@ export default function NodeToolbarComponent({ const [flowComponent, setFlowComponent] = useState( createFlowComponent(cloneDeep(data), version), ); - const preventDefault = true; - const isMac = navigator.platform.toUpperCase().includes("MAC"); - const nodeLength = Object.keys(data.node!.template).filter( - (templateField) => - templateField.charAt(0) !== "_" && - data.node?.template[templateField]?.show && - (data.node.template[templateField]?.type === "str" || - data.node.template[templateField]?.type === "bool" || - data.node.template[templateField]?.type === "float" || - data.node.template[templateField]?.type === "code" || - data.node.template[templateField]?.type === "prompt" || - data.node.template[templateField]?.type === "file" || - data.node.template[templateField]?.type === "Any" || - data.node.template[templateField]?.type === "int" || - data.node.template[templateField]?.type === "dict" || - data.node.template[templateField]?.type === "NestedDict"), - ).length; + const nodeLength = getNodeLength(data); const updateFreezeStatus = useFlowStore((state) => state.updateFreezeStatus); - const hasStore = useStoreStore((state) => state.hasStore); const hasApiKey = useStoreStore((state) => state.hasApiKey); const validApiKey = useStoreStore((state) => state.validApiKey); const shortcuts = useShortcutsStore((state) => state.shortcuts); const unselectAll = useFlowStore((state) => state.unselectAll); const currentFlowId = useFlowsManagerStore((state) => state.currentFlowId); + const [openModal, setOpenModal] = useState(false); + const isGroup = data.node?.flow ? true : false; + const frozen = data.node?.frozen ?? false; + const addFlow = useAddFlow(); - function handleMinimizeWShortcut(e: KeyboardEvent) { - if (isWrappedWithClass(e, "noflow")) return; - e.preventDefault(); + const isMinimal = countHandlesFn(data) <= 1 && numberOfOutputHandles <= 1; + + function minimize() { if (isMinimal) { setShowState((show) => !show); setShowNode(data.showNode ?? true ? false : true); @@ -100,54 +91,48 @@ export default function NodeToolbarComponent({ return; } - function handleGroupWShortcut(e: KeyboardEvent) { - if (isWrappedWithClass(e, "noflow")) return; - e.preventDefault(); + function handleungroup() { if (isGroup) { - handleSelectChange("ungroup"); + takeSnapshot(); + expandGroupNode( + data.id, + updateFlowPosition(getNodePosition(data.id), data.node?.flow!), + data.node!.template, + nodes, + edges, + setNodes, + setEdges, + data.node?.outputs, + ); } } - function handleShareWShortcut(e: KeyboardEvent) { - if (isWrappedWithClass(e, "noflow") && !showconfirmShare) return; - e.preventDefault(); + function shareComponent() { if (hasApiKey || hasStore) { setShowconfirmShare((state) => !state); } } - function handleCodeWShortcut(e: KeyboardEvent) { - if (isWrappedWithClass(e, "noflow") && !openModal) return; - e.preventDefault(); - if (hasCode) return setOpenModal((state) => !state); - setNoticeData({ title: `You can not access ${data.id} code` }); + function handleCodeModal() { + if (!hasCode) + setNoticeData({ title: `You can not access ${data.id} code` }); + setOpenModal((state) => !state); } - function handleAdvancedWShortcut(e: KeyboardEvent) { - if (isWrappedWithClass(e, "noflow") && !showModalAdvanced) return; - e.preventDefault(); - setShowModalAdvanced((state) => !state); - } - - function handleSaveWShortcut(e: KeyboardEvent) { - if (isWrappedWithClass(e, "noflow") && !showOverrideModal) return; - e.preventDefault(); + function saveComponent() { if (isSaved) { setShowOverrideModal((state) => !state); return; } - if (hasCode && !isSaved) { - addFlow({ - flow: flowComponent, - override: false, - }); - setSuccessData({ title: `${data.id} saved successfully` }); - return; - } + addFlow({ + flow: flowComponent, + override: false, + }); + setSuccessData({ title: `${data.id} saved successfully` }); + return; } - function handleDocsWShortcut(e: KeyboardEvent) { - e.preventDefault(); + function openDocs() { if (data.node?.documentation) { return openInNewTab(data.node?.documentation); } @@ -156,14 +141,7 @@ export default function NodeToolbarComponent({ }); } - function handleDownloadWShortcut(e: KeyboardEvent) { - e.preventDefault(); - downloadNode(flowComponent!); - } - - function handleFreeze(e: KeyboardEvent) { - if (isWrappedWithClass(e, "noflow")) return; - e.preventDefault(); + const freezeFunction = () => { setNode(data.id, (old) => ({ ...old, data: { @@ -174,45 +152,31 @@ export default function NodeToolbarComponent({ }, }, })); - } + }; - function handleFreezeAll(e: KeyboardEvent) { - if (isWrappedWithClass(e, "noflow")) return; - e.preventDefault(); - FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id }); - } + useShortcuts({ + showOverrideModal, + showModalAdvanced, + openModal, + showconfirmShare, + FreezeAllVertices: () => { + FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id }); + }, + Freeze: freezeFunction, + downloadFunction: () => downloadNode(flowComponent!), + displayDocs: openDocs, + saveComponent, + showAdvance: () => setShowModalAdvanced((state) => !state), + handleCodeModal, + shareComponent, + ungroup: handleungroup, + minimizeFunction: minimize, + }); - const advanced = useShortcutsStore((state) => state.advanced); - const minimize = useShortcutsStore((state) => state.minimize); - const component = useShortcutsStore((state) => state.component); - const save = useShortcutsStore((state) => state.save); - const docs = useShortcutsStore((state) => state.docs); - const code = useShortcutsStore((state) => state.code); - const group = useShortcutsStore((state) => state.group); - const download = useShortcutsStore((state) => state.download); - const freeze = useShortcutsStore((state) => state.freeze); - const freezeAll = useShortcutsStore((state) => state.FreezePath); - - useHotkeys(minimize, handleMinimizeWShortcut, { preventDefault }); - useHotkeys(group, handleGroupWShortcut, { preventDefault }); - useHotkeys(component, handleShareWShortcut, { preventDefault }); - useHotkeys(code, handleCodeWShortcut, { preventDefault }); - useHotkeys(advanced, handleAdvancedWShortcut, { preventDefault }); - useHotkeys(save, handleSaveWShortcut, { preventDefault }); - useHotkeys(docs, handleDocsWShortcut, { preventDefault }); - useHotkeys(download, handleDownloadWShortcut, { preventDefault }); - useHotkeys(freeze, handleFreeze); - useHotkeys(freezeAll, handleFreezeAll); - - const isMinimal = numberOfHandles <= 1 && numberOfOutputHandles <= 1; - const isGroup = data.node?.flow ? true : false; - - const frozen = data.node?.frozen ?? false; const paste = useFlowStore((state) => state.paste); const nodes = useFlowStore((state) => state.nodes); const edges = useFlowStore((state) => state.edges); const setNodes = useFlowStore((state) => state.setNodes); - const setEdges = useFlowStore((state) => state.setEdges); const getNodePosition = useFlowStore((state) => state.getNodePosition); const flows = useFlowsManagerStore((state) => state.flows); @@ -226,10 +190,6 @@ export default function NodeToolbarComponent({ }, }); - const openInNewTab = (url) => { - window.open(url, "_blank", "noreferrer"); - }; - useEffect(() => { if (!showModalAdvanced) { onCloseAdvancedModal!(false); @@ -259,25 +219,10 @@ export default function NodeToolbarComponent({ const handleSelectChange = (event) => { switch (event) { case "save": - if (isSaved) { - return setShowOverrideModal(true); - } - addFlow({ - flow: flowComponent, - override: false, - }); + saveComponent(); break; case "freeze": - setNode(data.id, (old) => ({ - ...old, - data: { - ...old.data, - node: { - ...old.data.node, - frozen: old.data?.node?.frozen ? false : true, - }, - }, - })); + freezeFunction(); break; case "freezeAll": FreezeAllVertices({ flowId: currentFlowId, stopNodeId: data.id }); @@ -290,10 +235,10 @@ export default function NodeToolbarComponent({ break; case "show": takeSnapshot(); - setShowNode(data.showNode ?? true ? false : true); + minimize(); break; case "Share": - if (hasApiKey || hasStore) setShowconfirmShare(true); + shareComponent(); break; case "Download": downloadNode(flowComponent!); @@ -305,7 +250,7 @@ export default function NodeToolbarComponent({ }); break; case "documentation": - if (data.node?.documentation) openInNewTab(data.node?.documentation); + openDocs(); break; case "disabled": break; @@ -313,17 +258,7 @@ export default function NodeToolbarComponent({ unselectAll(); break; case "ungroup": - takeSnapshot(); - expandGroupNode( - data.id, - updateFlowPosition(getNodePosition(data.id), data.node?.flow!), - data.node!.template, - nodes, - edges, - setNodes, - setEdges, - data.node?.outputs, - ); + handleungroup(); break; case "override": setShowOverrideModal(true); @@ -359,42 +294,6 @@ export default function NodeToolbarComponent({ Object.values(flow).includes(data.node?.display_name!), ); - function displayShortcut({ - name, - shortcut, - }: { - name: string; - shortcut: string; - }): JSX.Element { - let hasShift: boolean = false; - const fixedShortcut = shortcut?.split("+"); - fixedShortcut.forEach((key) => { - if (key.toLowerCase().includes("shift")) { - hasShift = true; - } - }); - const filteredShortcut = fixedShortcut.filter( - (key) => !key.toLowerCase().includes("shift"), - ); - let shortcutWPlus: string[] = []; - if (!hasShift) shortcutWPlus = filteredShortcut.join("+").split(" "); - return ( -
- {name} - - - -
- ); - } - const setNode = useFlowStore((state) => state.setNode); const { handleOnNewValue: handleOnNewValueHook } = useHandleOnNewValue({ @@ -413,7 +312,6 @@ export default function NodeToolbarComponent({ handleNodeClassHook(newNodeClass, type); }; - const [openModal, setOpenModal] = useState(false); const hasCode = Object.keys(data.node!.template).includes("code"); const [deleteIsFocus, setDeleteIsFocus] = useState(false); @@ -423,11 +321,13 @@ export default function NodeToolbarComponent({ {hasCode && ( name.split(" ")[0].toLowerCase() === "code", - )!, - )} + content={ + name.split(" ")[0].toLowerCase() === "code", + )!} + /> + } side="top" >