mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
wip
This commit is contained in:
parent
7b93b5e928
commit
2c979d1b68
@ -49,6 +49,7 @@
|
||||
"back": "Back",
|
||||
"batch": "Batch Manager",
|
||||
"cancel": "Cancel",
|
||||
"clickToEdit": "Click to Edit",
|
||||
"close": "Close",
|
||||
"on": "On",
|
||||
"communityLabel": "Community",
|
||||
@ -853,7 +854,7 @@
|
||||
"noConnectionData": "No connection data",
|
||||
"noConnectionInProgress": "No connection in progress",
|
||||
"node": "Node",
|
||||
"nodeOutputs": "Node Outputs",
|
||||
"nodeOutputs": "Node Results",
|
||||
"nodeSearch": "Search for nodes",
|
||||
"nodeTemplate": "Node Template",
|
||||
"nodeType": "Node Type",
|
||||
@ -863,9 +864,9 @@
|
||||
"noMatchingNodes": "No matching nodes",
|
||||
"noNodeSelected": "No node selected",
|
||||
"nodeOpacity": "Node Opacity",
|
||||
"noOutputRecorded": "No outputs recorded",
|
||||
"noOutputRecorded": "No results recorded",
|
||||
"noOutputSchemaName": "No output schema name found in ref object",
|
||||
"notes": "Notes",
|
||||
"notes": "Node Notes",
|
||||
"notesDescription": "Add notes about your workflow",
|
||||
"oNNXModelField": "ONNX Model",
|
||||
"oNNXModelFieldDescription": "ONNX model field.",
|
||||
@ -943,7 +944,12 @@
|
||||
"workflowValidation": "Workflow Validation Error",
|
||||
"workflowVersion": "Version",
|
||||
"zoomInNodes": "Zoom In",
|
||||
"zoomOutNodes": "Zoom Out"
|
||||
"zoomOutNodes": "Zoom Out",
|
||||
"tabDetails": "Details",
|
||||
"tabNotes": "Notes",
|
||||
"tabResults": "Results",
|
||||
"tabData": "Data",
|
||||
"tabTemplate": "Template"
|
||||
},
|
||||
"parameters": {
|
||||
"aspectRatio": "Aspect Ratio",
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { FormControl, FormLabel } from '@chakra-ui/react';
|
||||
import { FormControl, FormLabel, Flex } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAITextarea from 'common/components/IAITextarea';
|
||||
import { useNodeData } from 'features/nodes/hooks/useNodeData';
|
||||
import { useNodeNotes } from 'features/nodes/hooks/useNodeNotes';
|
||||
import { nodeNotesChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { isInvocationNodeData } from 'features/nodes/types/types';
|
||||
import { isNil } from 'lodash-es';
|
||||
import { ChangeEvent, memo, useCallback } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const data = useNodeData(nodeId);
|
||||
const notes = useNodeNotes(nodeId);
|
||||
const { t } = useTranslation();
|
||||
const handleNotesChanged = useCallback(
|
||||
(e: ChangeEvent<HTMLTextAreaElement>) => {
|
||||
@ -17,16 +17,17 @@ const NotesTextarea = ({ nodeId }: { nodeId: string }) => {
|
||||
},
|
||||
[dispatch, nodeId]
|
||||
);
|
||||
if (!isInvocationNodeData(data)) {
|
||||
if (isNil(notes)) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<FormControl>
|
||||
<FormControl as={Flex} sx={{ flexDir: 'column', h: 'full' }}>
|
||||
<FormLabel>{t('nodes.notes')}</FormLabel>
|
||||
<IAITextarea
|
||||
value={data?.notes}
|
||||
value={notes}
|
||||
onChange={handleNotesChanged}
|
||||
rows={10}
|
||||
resize="none"
|
||||
h="full"
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
|
@ -28,6 +28,10 @@ const NodeTitle = ({ nodeId, title }: Props) => {
|
||||
const [localTitle, setLocalTitle] = useState('');
|
||||
const handleSubmit = useCallback(
|
||||
async (newTitle: string) => {
|
||||
if (!newTitle.trim()) {
|
||||
setLocalTitle(label || templateTitle || t('nodes.problemSettingTitle'));
|
||||
return;
|
||||
}
|
||||
dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
|
||||
setLocalTitle(
|
||||
label || title || templateTitle || t('nodes.problemSettingTitle')
|
||||
|
@ -22,9 +22,8 @@ import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaSync } from 'react-icons/fa';
|
||||
import { Node } from 'reactflow';
|
||||
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
|
||||
import ScrollableContent from '../ScrollableContent';
|
||||
import EditableNodeTitle from './details/EditableNodeTitle';
|
||||
import InputFields from './details/InputFields';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
@ -82,42 +81,23 @@ const Content = (props: {
|
||||
sx={{
|
||||
flexDir: 'column',
|
||||
position: 'relative',
|
||||
p: 1,
|
||||
gap: 2,
|
||||
w: 'full',
|
||||
}}
|
||||
>
|
||||
<EditableNodeTitle nodeId={props.node.data.id} />
|
||||
<HStack>
|
||||
<FormControl>
|
||||
<FormLabel>Node Type</FormLabel>
|
||||
<FormLabel>Type</FormLabel>
|
||||
<Text fontSize="sm" fontWeight={600}>
|
||||
{props.template.title}
|
||||
{props.template.title} ({props.template.type})
|
||||
</Text>
|
||||
</FormControl>
|
||||
<Flex
|
||||
flexDir="row"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
w="full"
|
||||
>
|
||||
<FormControl isInvalid={needsUpdate}>
|
||||
<FormLabel>Node Version</FormLabel>
|
||||
<FormControl>
|
||||
<FormLabel>Description</FormLabel>
|
||||
<Text fontSize="sm" fontWeight={600}>
|
||||
{props.node.data.version}
|
||||
{props.template.description}
|
||||
</Text>
|
||||
</FormControl>
|
||||
{needsUpdate && (
|
||||
<IAIIconButton
|
||||
aria-label={t('nodes.updateNode')}
|
||||
tooltip={t('nodes.updateNode')}
|
||||
icon={<FaSync />}
|
||||
onClick={updateNode}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
</HStack>
|
||||
<NotesTextarea nodeId={props.node.data.id} />
|
||||
<InputFields nodeId={props.node.id} />
|
||||
</Flex>
|
||||
</ScrollableContent>
|
||||
</Box>
|
||||
|
@ -0,0 +1,43 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
|
||||
import { isInvocationNode } from 'features/nodes/types/types';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import NotesTextarea from '../../flow/nodes/Invocation/NotesTextarea';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
({ nodes }) => {
|
||||
const lastSelectedNodeId =
|
||||
nodes.selectedNodes[nodes.selectedNodes.length - 1];
|
||||
|
||||
const lastSelectedNode = nodes.nodes.find(
|
||||
(node) => node.id === lastSelectedNodeId
|
||||
);
|
||||
|
||||
if (!isInvocationNode(lastSelectedNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return lastSelectedNode.id;
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const InspectorNotesTab = () => {
|
||||
const nodeId = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
if (!nodeId) {
|
||||
return (
|
||||
<IAINoContentFallback label={t('nodes.noNodeSelected')} icon={null} />
|
||||
);
|
||||
}
|
||||
|
||||
return <NotesTextarea nodeId={nodeId} />;
|
||||
};
|
||||
|
||||
export default memo(InspectorNotesTab);
|
@ -6,13 +6,41 @@ import {
|
||||
TabPanels,
|
||||
Tabs,
|
||||
} from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { isInvocationNode } from 'features/nodes/types/types';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import InspectorDataTab from './InspectorDataTab';
|
||||
import InspectorOutputsTab from './InspectorOutputsTab';
|
||||
import InspectorTemplateTab from './InspectorTemplateTab';
|
||||
import InspectorDetailsTab from './InspectorDetailsTab';
|
||||
import InspectorNotesTab from './InspectorNotesTab';
|
||||
import InspectorResultsTab from './InspectorResultsTab';
|
||||
import InspectorTemplateTab from './InspectorTemplateTab';
|
||||
import EditableNodeTitle from './details/EditableNodeTitle';
|
||||
|
||||
const selector = createSelector(
|
||||
stateSelector,
|
||||
({ nodes }) => {
|
||||
const lastSelectedNodeId =
|
||||
nodes.selectedNodes[nodes.selectedNodes.length - 1];
|
||||
|
||||
const lastSelectedNode = nodes.nodes.find(
|
||||
(node) => node.id === lastSelectedNodeId
|
||||
);
|
||||
|
||||
if (!isInvocationNode(lastSelectedNode)) {
|
||||
return;
|
||||
}
|
||||
|
||||
return lastSelectedNode.id;
|
||||
},
|
||||
defaultSelectorOptions
|
||||
);
|
||||
const InspectorPanel = () => {
|
||||
const { t } = useTranslation();
|
||||
const nodeId = useAppSelector(selector);
|
||||
return (
|
||||
<Flex
|
||||
layerStyle="first"
|
||||
@ -25,15 +53,17 @@ const InspectorPanel = () => {
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
<EditableNodeTitle nodeId={nodeId} />
|
||||
<Tabs
|
||||
variant="line"
|
||||
sx={{ display: 'flex', flexDir: 'column', w: 'full', h: 'full' }}
|
||||
>
|
||||
<TabList>
|
||||
<Tab>Details</Tab>
|
||||
<Tab>Outputs</Tab>
|
||||
<Tab>Data</Tab>
|
||||
<Tab>Template</Tab>
|
||||
<Tab>{t('nodes.tabDetails')}</Tab>
|
||||
<Tab>{t('nodes.tabNotes')}</Tab>
|
||||
<Tab>{t('nodes.tabResults')}</Tab>
|
||||
<Tab>{t('nodes.tabData')}</Tab>
|
||||
<Tab>{t('nodes.tabTemplate')}</Tab>
|
||||
</TabList>
|
||||
|
||||
<TabPanels>
|
||||
@ -41,7 +71,10 @@ const InspectorPanel = () => {
|
||||
<InspectorDetailsTab />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<InspectorOutputsTab />
|
||||
<InspectorNotesTab />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<InspectorResultsTab />
|
||||
</TabPanel>
|
||||
<TabPanel>
|
||||
<InspectorDataTab />
|
||||
|
@ -39,7 +39,7 @@ const selector = createSelector(
|
||||
defaultSelectorOptions
|
||||
);
|
||||
|
||||
const InspectorOutputsTab = () => {
|
||||
const InspectorResultsTab = () => {
|
||||
const { node, template, nes } = useAppSelector(selector);
|
||||
const { t } = useTranslation();
|
||||
|
||||
@ -91,6 +91,6 @@ const InspectorOutputsTab = () => {
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InspectorOutputsTab);
|
||||
export default memo(InspectorResultsTab);
|
||||
|
||||
const getKey = (result: AnyResult, i: number) => `${result.type}-${i}`;
|
@ -3,20 +3,89 @@ import {
|
||||
EditableInput,
|
||||
EditablePreview,
|
||||
Flex,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import { useNodeLabel } from 'features/nodes/hooks/useNodeLabel';
|
||||
import { useNodeTemplateTitle } from 'features/nodes/hooks/useNodeTemplateTitle';
|
||||
import { useNodeVersion } from 'features/nodes/hooks/useNodeVersion';
|
||||
import { nodeLabelChanged } from 'features/nodes/store/nodesSlice';
|
||||
import { memo, useCallback, useEffect, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaSync } from 'react-icons/fa';
|
||||
|
||||
type Props = {
|
||||
nodeId: string;
|
||||
title?: string;
|
||||
type EditableNodeTitleProps = {
|
||||
nodeId?: string;
|
||||
};
|
||||
|
||||
const EditableNodeTitle = ({ nodeId, title }: Props) => {
|
||||
const EditableNodeTitle = (props: EditableNodeTitleProps) => {
|
||||
if (!props.nodeId) {
|
||||
return (
|
||||
<Text
|
||||
sx={{
|
||||
fontWeight: 600,
|
||||
px: 1,
|
||||
color: 'base.700',
|
||||
_dark: { color: 'base.200' },
|
||||
}}
|
||||
>
|
||||
No node selected
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
justifyContent: 'space-between',
|
||||
alignItems: 'center',
|
||||
px: 1,
|
||||
color: 'base.700',
|
||||
_dark: { color: 'base.200' },
|
||||
}}
|
||||
>
|
||||
<EditableTitle nodeId={props.nodeId} />
|
||||
<Version nodeId={props.nodeId} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
type VersionProps = {
|
||||
nodeId: string;
|
||||
};
|
||||
|
||||
const Version = memo(({ nodeId }: VersionProps) => {
|
||||
const { version, needsUpdate, updateNode } = useNodeVersion(nodeId);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Flex alignItems="center" gap={1}>
|
||||
<Text variant={needsUpdate ? 'error' : 'subtext'} fontWeight={600}>
|
||||
v{version}
|
||||
</Text>
|
||||
{needsUpdate && (
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
aria-label={t('nodes.updateNode')}
|
||||
tooltip={t('nodes.updateNode')}
|
||||
icon={<FaSync />}
|
||||
variant="link"
|
||||
onClick={updateNode}
|
||||
/>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
Version.displayName = 'Version';
|
||||
|
||||
type EditableTitleProps = {
|
||||
nodeId: string;
|
||||
};
|
||||
|
||||
const EditableTitle = memo(({ nodeId }: EditableTitleProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const label = useNodeLabel(nodeId);
|
||||
const templateTitle = useNodeTemplateTitle(nodeId);
|
||||
@ -25,12 +94,14 @@ const EditableNodeTitle = ({ nodeId, title }: Props) => {
|
||||
const [localTitle, setLocalTitle] = useState('');
|
||||
const handleSubmit = useCallback(
|
||||
async (newTitle: string) => {
|
||||
if (!newTitle.trim()) {
|
||||
setLocalTitle(label || templateTitle || t('nodes.problemSettingTitle'));
|
||||
return;
|
||||
}
|
||||
dispatch(nodeLabelChanged({ nodeId, label: newTitle }));
|
||||
setLocalTitle(
|
||||
label || title || templateTitle || t('nodes.problemSettingTitle')
|
||||
);
|
||||
setLocalTitle(label || templateTitle || t('nodes.problemSettingTitle'));
|
||||
},
|
||||
[dispatch, nodeId, title, templateTitle, label, t]
|
||||
[dispatch, nodeId, templateTitle, label, t]
|
||||
);
|
||||
|
||||
const handleChange = useCallback((newTitle: string) => {
|
||||
@ -39,36 +110,28 @@ const EditableNodeTitle = ({ nodeId, title }: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
// Another component may change the title; sync local title with global state
|
||||
setLocalTitle(
|
||||
label || title || templateTitle || t('nodes.problemSettingTitle')
|
||||
);
|
||||
}, [label, templateTitle, title, t]);
|
||||
setLocalTitle(label || templateTitle || t('nodes.problemSettingTitle'));
|
||||
}, [label, templateTitle, t]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Editable
|
||||
as={Flex}
|
||||
value={localTitle}
|
||||
onChange={handleChange}
|
||||
onSubmit={handleSubmit}
|
||||
w="full"
|
||||
fontWeight={600}
|
||||
>
|
||||
<EditablePreview noOfLines={1} />
|
||||
<EditablePreview p={0} fontWeight={600} noOfLines={1} />
|
||||
<EditableInput
|
||||
p={0}
|
||||
className="nodrag"
|
||||
fontWeight={700}
|
||||
_focusVisible={{ boxShadow: 'none' }}
|
||||
/>
|
||||
</Editable>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
EditableTitle.displayName = 'EditableTitle';
|
||||
|
||||
export default memo(EditableNodeTitle);
|
||||
|
@ -0,0 +1,20 @@
|
||||
import { FormControl, FormLabel, Text } from '@chakra-ui/react';
|
||||
import { useNodeInputFields } from 'features/nodes/hooks/useNodeInputFields';
|
||||
import { memo } from 'react';
|
||||
|
||||
type Props = { nodeId: string };
|
||||
const InputFields = ({ nodeId }: Props) => {
|
||||
const inputs = useNodeInputFields(nodeId);
|
||||
return (
|
||||
<div>
|
||||
{inputs?.map(({ fieldData, fieldTemplate }) => (
|
||||
<FormControl key={fieldData.id}>
|
||||
<FormLabel>{fieldData.label || fieldTemplate.title}</FormLabel>
|
||||
<Text>{fieldData.type}</Text>
|
||||
</FormControl>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(InputFields);
|
@ -0,0 +1,56 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { useMemo } from 'react';
|
||||
import {
|
||||
InputFieldTemplate,
|
||||
InputFieldValue,
|
||||
isInvocationNode,
|
||||
} from '../types/types';
|
||||
|
||||
export const useNodeInputFields = (
|
||||
nodeId: string
|
||||
): { fieldData: InputFieldValue; fieldTemplate: InputFieldTemplate }[] => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
stateSelector,
|
||||
({ nodes }) => {
|
||||
const node = nodes.nodes.find((node) => node.id === nodeId);
|
||||
if (!isInvocationNode(node)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const template = nodes.nodeTemplates[node.data.type];
|
||||
|
||||
if (!template) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const inputs = Object.values(node.data.inputs).reduce<
|
||||
{
|
||||
fieldData: InputFieldValue;
|
||||
fieldTemplate: InputFieldTemplate;
|
||||
}[]
|
||||
>((acc, fieldData) => {
|
||||
const fieldTemplate = template.inputs[fieldData.name];
|
||||
if (fieldTemplate) {
|
||||
acc.push({
|
||||
fieldData,
|
||||
fieldTemplate,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return inputs;
|
||||
},
|
||||
defaultSelectorOptions
|
||||
),
|
||||
[nodeId]
|
||||
);
|
||||
|
||||
const inputs = useAppSelector(selector);
|
||||
return inputs;
|
||||
};
|
@ -0,0 +1,28 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { useMemo } from 'react';
|
||||
import { isInvocationNode } from '../types/types';
|
||||
|
||||
export const useNodeNotes = (nodeId: string) => {
|
||||
const selector = useMemo(
|
||||
() =>
|
||||
createSelector(
|
||||
stateSelector,
|
||||
({ nodes }) => {
|
||||
const node = nodes.nodes.find((node) => node.id === nodeId);
|
||||
if (!isInvocationNode(node)) {
|
||||
return;
|
||||
}
|
||||
return node.data.notes;
|
||||
},
|
||||
defaultSelectorOptions
|
||||
),
|
||||
[nodeId]
|
||||
);
|
||||
|
||||
const nodeNotes = useAppSelector(selector);
|
||||
|
||||
return nodeNotes;
|
||||
};
|
@ -1,10 +1,12 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { stateSelector } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
|
||||
import { satisfies } from 'compare-versions';
|
||||
import { cloneDeep, defaultsDeep } from 'lodash-es';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Node } from 'reactflow';
|
||||
import { AnyInvocationType } from 'services/events/types';
|
||||
import { nodeReplaced } from '../store/nodesSlice';
|
||||
@ -16,8 +18,6 @@ import {
|
||||
isInvocationNode,
|
||||
zParsedSemver,
|
||||
} from '../types/types';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const getNeedsUpdate = (
|
||||
node?: Node<NodeData>,
|
||||
@ -115,5 +115,17 @@ export const useNodeVersion = (nodeId: string) => {
|
||||
dispatch(nodeReplaced({ nodeId: updatedNode.id, node: updatedNode }));
|
||||
}, [dispatch, node, nodeTemplate, t, toast]);
|
||||
|
||||
return { needsUpdate, mayUpdate, updateNode: _updateNode };
|
||||
const version = useMemo(() => {
|
||||
if (!isInvocationNode(node)) {
|
||||
return '';
|
||||
}
|
||||
return node.data.version;
|
||||
}, [node]);
|
||||
|
||||
return {
|
||||
needsUpdate,
|
||||
mayUpdate,
|
||||
updateNode: _updateNode,
|
||||
version,
|
||||
};
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user