diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index ab10276491..404c2013e4 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -702,9 +702,9 @@ }, "nodes": { "reloadSchema": "Reload Schema", - "saveNodes": "Save Nodes", - "loadNodes": "Load Nodes", - "clearNodes": "Clear Nodes", + "saveGraph": "Save Graph", + "loadGraph": "Load Graph (saved from Node Editor) (Do not copy-paste metadata)", + "clearGraph": "Clear Graph", "zoomInNodes": "Zoom In", "zoomOutNodes": "Zoom Out", "fitViewportNodes": "Fit View", diff --git a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx index 90f8039285..21076e16f5 100644 --- a/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/panels/TopCenterPanel.tsx @@ -2,11 +2,11 @@ import { HStack } from '@chakra-ui/react'; import CancelButton from 'features/parameters/components/ProcessButtons/CancelButton'; import { memo } from 'react'; import { Panel } from 'reactflow'; -import LoadNodesButton from '../ui/LoadNodesButton'; +import ClearGraphButton from '../ui/ClearGraphButton'; +import LoadGraphButton from '../ui/LoadGraphButton'; import NodeInvokeButton from '../ui/NodeInvokeButton'; import ReloadSchemaButton from '../ui/ReloadSchemaButton'; -import SaveNodesButton from '../ui/SaveNodesButton'; -import ClearNodesButton from '../ui/ClearNodesButton'; +import SaveGraphButton from '../ui/SaveGraphButton'; const TopCenterPanel = () => { return ( @@ -15,9 +15,9 @@ const TopCenterPanel = () => { - - - + + + ); diff --git a/invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx b/invokeai/frontend/web/src/features/nodes/components/ui/ClearGraphButton.tsx similarity index 87% rename from invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx rename to invokeai/frontend/web/src/features/nodes/components/ui/ClearGraphButton.tsx index 86d9d08a84..88fb60ee0f 100644 --- a/invokeai/frontend/web/src/features/nodes/components/ui/ClearNodesButton.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/ui/ClearGraphButton.tsx @@ -9,17 +9,17 @@ import { Text, useDisclosure, } from '@chakra-ui/react'; -import { makeToast } from 'features/system/util/makeToast'; import { RootState } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import IAIIconButton from 'common/components/IAIIconButton'; import { nodeEditorReset } from 'features/nodes/store/nodesSlice'; import { addToast } from 'features/system/store/systemSlice'; -import { memo, useRef, useCallback } from 'react'; +import { makeToast } from 'features/system/util/makeToast'; +import { memo, useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { FaTrash } from 'react-icons/fa'; -const ClearNodesButton = () => { +const ClearGraphButton = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const { isOpen, onOpen, onClose } = useDisclosure(); @@ -46,8 +46,8 @@ const ClearNodesButton = () => { <> } - tooltip={t('nodes.clearNodes')} - aria-label={t('nodes.clearNodes')} + tooltip={t('nodes.clearGraph')} + aria-label={t('nodes.clearGraph')} onClick={onOpen} isDisabled={nodes.length === 0} /> @@ -62,11 +62,11 @@ const ClearNodesButton = () => { - {t('nodes.clearNodes')} + {t('nodes.clearGraph')} - {t('common.clearNodes')} + {t('common.clearGraph')} @@ -83,4 +83,4 @@ const ClearNodesButton = () => { ); }; -export default memo(ClearNodesButton); +export default memo(ClearGraphButton); diff --git a/invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx b/invokeai/frontend/web/src/features/nodes/components/ui/LoadGraphButton.tsx similarity index 82% rename from invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx rename to invokeai/frontend/web/src/features/nodes/components/ui/LoadGraphButton.tsx index 2aa369bc11..437418e18a 100644 --- a/invokeai/frontend/web/src/features/nodes/components/ui/LoadNodesButton.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/ui/LoadGraphButton.tsx @@ -13,17 +13,28 @@ interface JsonFile { [key: string]: unknown; } -function validateInvokeAIGraph(jsonFile: JsonFile): boolean { +function sanityCheckInvokeAIGraph(jsonFile: JsonFile): boolean { const keys = ['nodes', 'edges', 'viewport']; for (const key of keys) { if (!(key in jsonFile)) { return false; } } + + if (!Array.isArray(jsonFile.nodes) || !Array.isArray(jsonFile.edges)) { + return false; + } + + for (const node of jsonFile.nodes) { + if (!('data' in node)) { + return false; + } + } + return true; } -const LoadNodesButton = () => { +const LoadGraphButton = () => { const { t } = useTranslation(); const dispatch = useAppDispatch(); const { fitView } = useReactFlow(); @@ -39,9 +50,9 @@ const LoadNodesButton = () => { try { const retrievedNodeTree = await JSON.parse(String(json)); - const isValidNodeTree = validateInvokeAIGraph(retrievedNodeTree); + const isSaneNodeTree = sanityCheckInvokeAIGraph(retrievedNodeTree); - if (isValidNodeTree) { + if (isSaneNodeTree) { dispatch(loadFileNodes(retrievedNodeTree.nodes)); dispatch(loadFileEdges(retrievedNodeTree.edges)); fitView(); @@ -93,8 +104,8 @@ const LoadNodesButton = () => { {(props) => ( } - tooltip={t('nodes.loadNodes')} - aria-label={t('nodes.loadNodes')} + tooltip={t('nodes.loadGraph')} + aria-label={t('nodes.loadGraph')} {...props} /> )} @@ -102,4 +113,4 @@ const LoadNodesButton = () => { ); }; -export default memo(LoadNodesButton); +export default memo(LoadGraphButton); diff --git a/invokeai/frontend/web/src/features/nodes/components/ui/SaveNodesButton.tsx b/invokeai/frontend/web/src/features/nodes/components/ui/SaveGraphButton.tsx similarity index 90% rename from invokeai/frontend/web/src/features/nodes/components/ui/SaveNodesButton.tsx rename to invokeai/frontend/web/src/features/nodes/components/ui/SaveGraphButton.tsx index 5833182456..42e545258e 100644 --- a/invokeai/frontend/web/src/features/nodes/components/ui/SaveNodesButton.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/ui/SaveGraphButton.tsx @@ -6,7 +6,7 @@ import { memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { FaSave } from 'react-icons/fa'; -const SaveNodesButton = () => { +const SaveGraphButton = () => { const { t } = useTranslation(); const editorInstance = useAppSelector( (state: RootState) => state.nodes.editorInstance @@ -37,12 +37,12 @@ const SaveNodesButton = () => { } fontSize={18} - tooltip={t('nodes.saveNodes')} - aria-label={t('nodes.saveNodes')} + tooltip={t('nodes.saveGraph')} + aria-label={t('nodes.saveGraph')} onClick={saveEditorToJSON} isDisabled={nodes.length === 0} /> ); }; -export default memo(SaveNodesButton); +export default memo(SaveGraphButton);