From 011910a08c25a05d5b925dccd3cad5f27e19bdc1 Mon Sep 17 00:00:00 2001
From: psychedelicious <4822129+psychedelicious@users.noreply.github.com>
Date: Mon, 20 Jan 2025 16:11:55 +1100
Subject: [PATCH] refactor(ui): continued reorg of components & hooks
---
.../listeners/workflowLoadRequested.ts | 2 +-
.../features/nodes/components/flow/Flow.tsx | 6 +-
.../flow/nodes/Invocation/InvocationNode.tsx | 12 +--
.../nodes/Invocation/InvocationNodeFooter.tsx | 4 +-
...ea.tsx => InvocationNodeNotesTextarea.tsx} | 20 ++---
.../InvocationNodeStatusIndicator.tsx | 4 +-
.../Invocation/SaveToGalleryCheckbox.tsx | 6 +-
.../nodes/Invocation/UseCacheCheckbox.tsx | 2 +-
.../fields/FieldResetToDefaultValueButton.tsx | 16 ----
...eldResetToInitialLinearViewValueButton.tsx | 16 ----
.../fields/FieldResetValueButton.tsx | 25 ------
.../Invocation/fields/FieldTooltipContent.tsx | 63 --------------
...putFieldAddRemoveLinearViewIconButton.tsx} | 19 ++---
...inear.tsx => InputFieldEditModeLinear.tsx} | 33 +++-----
...wNodes.tsx => InputFieldEditModeNodes.tsx} | 55 ++++++------
.../Invocation/fields/InputFieldGate.tsx | 8 +-
... InputFieldLinearViewConfigIconButton.tsx} | 12 +--
...tton.tsx => InputFieldNotesIconButton.tsx} | 8 +-
.../Invocation/fields/InputFieldRenderer.tsx | 19 ++---
...nputFieldResetToDefaultValueIconButton.tsx | 30 +++++++
...nputFieldResetToInitialValueIconButton.tsx | 30 +++++++
...ableFieldTitle.tsx => InputFieldTitle.tsx} | 84 +++++++++----------
.../Invocation/fields/InputFieldTooltip.tsx | 49 +++++++++++
.../fields/InputFieldUnknownPlaceholder.tsx | 6 +-
.../Invocation/fields/InputFieldViewMode.tsx | 31 +++++++
.../fields/InputFieldViewSimple.tsx | 31 -------
.../Invocation/fields/InputFieldWrapper.tsx | 15 +---
.../Invocation/fields/OutputFieldGate.tsx | 4 +-
.../fields/OutputFieldNodesEditorView.tsx | 37 ++++----
.../Invocation/fields/OutputFieldTitle.tsx | 43 ++++++++++
.../Invocation/fields/OutputFieldTooltip.tsx | 29 +++++++
.../fields/OutputFieldUnknownPlaceholder.tsx | 6 +-
.../Invocation/fields/OutputFieldWrapper.tsx | 13 +--
.../ImageFieldCollectionInputComponent.tsx | 4 +-
.../NumberFieldCollectionInputComponent.tsx | 4 +-
.../StringFieldCollectionInputComponent.tsx | 4 +-
.../flow/nodes/common/NodeWrapper.tsx | 4 +-
...ntent.tsx => EditModeLeftPanelContent.tsx} | 4 +-
.../sidePanel/WorkflowsTabLeftPanel.tsx | 8 +-
.../inspector/InspectorDetailsTab.tsx | 6 +-
.../inspector/InspectorOutputsTab.tsx | 4 +-
...ntent.tsx => ViewModeLeftPanelContent.tsx} | 14 ++--
.../sidePanel/workflow/WorkflowLinearTab.tsx | 4 +-
.../features/nodes/hooks/useBatchGroupId.ts | 27 +++---
.../nodes/hooks/useConnectionState.ts | 59 -------------
.../features/nodes/hooks/useFieldTemplate.ts | 36 --------
.../nodes/hooks/useFieldTemplateTitle.ts | 8 --
.../hooks/useInputFieldConnectionState.ts | 35 ++++++++
...tValue.ts => useInputFieldDefaultValue.ts} | 10 +--
...=> useInputFieldInitialLinearViewValue.ts} | 6 +-
...utInstance.ts => useInputFieldInstance.ts} | 11 ++-
...ists.ts => useInputFieldInstanceExists.ts} | 2 +-
.../nodes/hooks/useInputFieldIsConnected.ts | 21 +++++
...IsExposed.ts => useInputFieldIsExposed.ts} | 2 +-
...IsInvalid.ts => useInputFieldIsInvalid.ts} | 16 ++--
...useFieldLabel.ts => useInputFieldLabel.ts} | 2 +-
...ig.ts => useInputFieldLinearViewConfig.ts} | 2 +-
...FieldInputName.ts => useInputFieldName.ts} | 2 +-
...Names.ts => useInputFieldNamesByStatus.ts} | 2 +-
...useFieldNotes.ts => useInputFieldNotes.ts} | 2 +-
...utTemplate.ts => useInputFieldTemplate.ts} | 2 +-
...ists.ts => useInputFieldTemplateExists.ts} | 2 +-
.../nodes/hooks/useInputFieldTemplateTitle.ts | 15 ++++
...useFieldValue.ts => useInputFieldValue.ts} | 2 +-
.../{useCopyPaste.ts => useNodeCopyPaste.ts} | 2 +-
...utionState.ts => useNodeExecutionState.ts} | 2 +-
...mageOutput.ts => useNodeHasImageOutput.ts} | 2 +-
.../nodes/hooks/useNodeIsInvocationNode.ts | 20 +++++
.../src/features/nodes/hooks/useNodeNotes.ts | 22 +++++
.../hooks/useOutputFieldConnectionState.ts | 35 ++++++++
.../nodes/hooks/useOutputFieldIsConnected.ts | 21 +++++
...eldOutputName.ts => useOutputFieldName.ts} | 2 +-
...tTemplate.ts => useOutputFieldTemplate.ts} | 2 +-
...sts.ts => useOutputFieldTemplateExists.ts} | 2 +-
.../nodes/hooks/usePrettyFieldType.ts | 5 +-
.../src/features/nodes/hooks/useWithFooter.ts | 4 +-
.../services/events/onInvocationComplete.tsx | 2 +-
.../src/services/events/setEventListeners.tsx | 2 +-
78 files changed, 634 insertions(+), 547 deletions(-)
rename invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/{NotesTextarea.tsx => InvocationNodeNotesTextarea.tsx} (64%)
delete mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetToDefaultValueButton.tsx
delete mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetToInitialLinearViewValueButton.tsx
delete mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetValueButton.tsx
delete mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx
rename invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/{FieldLinearViewToggle.tsx => InputFieldAddRemoveLinearViewIconButton.tsx} (71%)
rename invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/{InputFieldViewLinear.tsx => InputFieldEditModeLinear.tsx} (61%)
rename invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/{InputFieldViewNodes.tsx => InputFieldEditModeNodes.tsx} (51%)
rename invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/{FieldLinearViewConfigIconButton.tsx => InputFieldLinearViewConfigIconButton.tsx} (81%)
rename invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/{FieldNotesIconButton.tsx => InputFieldNotesIconButton.tsx} (86%)
create mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToDefaultValueIconButton.tsx
create mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToInitialValueIconButton.tsx
rename invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/{EditableFieldTitle.tsx => InputFieldTitle.tsx} (73%)
create mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTooltip.tsx
create mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewMode.tsx
delete mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewSimple.tsx
create mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldTitle.tsx
create mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldTooltip.tsx
rename invokeai/frontend/web/src/features/nodes/components/sidePanel/{LinearViewLeftPanelContent.tsx => EditModeLeftPanelContent.tsx} (92%)
rename invokeai/frontend/web/src/features/nodes/components/sidePanel/viewMode/{SimpleViewLeftPanelContent.tsx => ViewModeLeftPanelContent.tsx} (76%)
delete mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useConnectionState.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useFieldTemplate.ts
delete mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useFieldTemplateTitle.ts
create mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useInputFieldConnectionState.ts
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldDefaultValue.ts => useInputFieldDefaultValue.ts} (64%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldInitialLinearViewValue.ts => useInputFieldInitialLinearViewValue.ts} (83%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldInputInstance.ts => useInputFieldInstance.ts} (55%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldInputInstanceExists.ts => useInputFieldInstanceExists.ts} (90%)
create mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsConnected.ts
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldIsExposed.ts => useInputFieldIsExposed.ts} (87%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldIsInvalid.ts => useInputFieldIsInvalid.ts} (81%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldLabel.ts => useInputFieldLabel.ts} (84%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldLinearViewConfig.ts => useInputFieldLinearViewConfig.ts} (88%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldInputName.ts => useInputFieldName.ts} (93%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldNames.ts => useInputFieldNamesByStatus.ts} (95%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldNotes.ts => useInputFieldNotes.ts} (89%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldInputTemplate.ts => useInputFieldTemplate.ts} (89%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldInputTemplateExists.ts => useInputFieldTemplateExists.ts} (92%)
create mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplateTitle.ts
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldValue.ts => useInputFieldValue.ts} (90%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useCopyPaste.ts => useNodeCopyPaste.ts} (99%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useExecutionState.ts => useNodeExecutionState.ts} (97%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useHasImageOutput.ts => useNodeHasImageOutput.ts} (89%)
create mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useNodeIsInvocationNode.ts
create mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useNodeNotes.ts
create mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldConnectionState.ts
create mode 100644 invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldIsConnected.ts
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldOutputName.ts => useOutputFieldName.ts} (93%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldOutputTemplate.ts => useOutputFieldTemplate.ts} (89%)
rename invokeai/frontend/web/src/features/nodes/hooks/{useFieldOutputTemplateExists.ts => useOutputFieldTemplateExists.ts} (92%)
diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested.ts
index 8d39448ad2..cde399e666 100644
--- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested.ts
+++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/workflowLoadRequested.ts
@@ -1,6 +1,6 @@
import { logger } from 'app/logging/logger';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
-import { $nodeExecutionStates } from 'features/nodes/hooks/useExecutionState';
+import { $nodeExecutionStates } from 'features/nodes/hooks/useNodeExecutionState';
import { workflowLoaded, workflowLoadRequested } from 'features/nodes/store/actions';
import { $templates } from 'features/nodes/store/nodesSlice';
import { $needsFit } from 'features/nodes/store/reactFlowInstance';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx
index aaeea376a1..aef01fbdff 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/Flow.tsx
@@ -3,9 +3,9 @@ import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
import { useConnection } from 'features/nodes/hooks/useConnection';
-import { useCopyPaste } from 'features/nodes/hooks/useCopyPaste';
-import { useSyncExecutionState } from 'features/nodes/hooks/useExecutionState';
import { useIsValidConnection } from 'features/nodes/hooks/useIsValidConnection';
+import { useNodeCopyPaste } from 'features/nodes/hooks/useNodeCopyPaste';
+import { useSyncExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { useWorkflowWatcher } from 'features/nodes/hooks/useWorkflowWatcher';
import {
$addNodeCmdk,
@@ -208,7 +208,7 @@ export const Flow = memo(() => {
// #endregion
- const { copySelection, pasteSelection, pasteSelectionWithEdges } = useCopyPaste();
+ const { copySelection, pasteSelection, pasteSelectionWithEdges } = useNodeCopyPaste();
useRegisteredHotkeys({
id: 'copySelection',
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx
index ef98639050..fe3c292ad7 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx
@@ -3,12 +3,12 @@ import NodeWrapper from 'features/nodes/components/flow/nodes/common/NodeWrapper
import { InputFieldGate } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldGate';
import { OutputFieldGate } from 'features/nodes/components/flow/nodes/Invocation/fields/OutputFieldGate';
import { OutputFieldNodesEditorView } from 'features/nodes/components/flow/nodes/Invocation/fields/OutputFieldNodesEditorView';
-import { useFieldNames } from 'features/nodes/hooks/useFieldNames';
+import { useInputFieldNamesByStatus } from 'features/nodes/hooks/useInputFieldNamesByStatus';
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
import { useWithFooter } from 'features/nodes/hooks/useWithFooter';
import { memo } from 'react';
-import { InputFieldViewNodes } from './fields/InputFieldViewNodes';
+import { InputFieldEditModeNodes } from './fields/InputFieldEditModeNodes';
import InvocationNodeFooter from './InvocationNodeFooter';
import InvocationNodeHeader from './InvocationNodeHeader';
@@ -21,7 +21,7 @@ type Props = {
};
const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
- const fieldNames = useFieldNames(nodeId);
+ const fieldNames = useInputFieldNamesByStatus(nodeId);
const withFooter = useWithFooter(nodeId);
const outputFieldNames = useOutputFieldNames(nodeId);
@@ -44,7 +44,7 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
{fieldNames.connectionFields.map((fieldName, i) => (
-
+
))}
@@ -58,12 +58,12 @@ const InvocationNode = ({ nodeId, isOpen, label, type, selected }: Props) => {
{fieldNames.anyOrDirectFields.map((fieldName) => (
-
+
))}
{fieldNames.missingFields.map((fieldName) => (
-
+
))}
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeFooter.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeFooter.tsx
index c1ff625d25..467e2b1bb6 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeFooter.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeFooter.tsx
@@ -1,6 +1,6 @@
import type { ChakraProps } from '@invoke-ai/ui-library';
import { Flex, FormControlGroup } from '@invoke-ai/ui-library';
-import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
+import { useNodeHasImageOutput } from 'features/nodes/hooks/useNodeHasImageOutput';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { memo } from 'react';
@@ -15,7 +15,7 @@ type Props = {
const props: ChakraProps = { w: 'unset' };
const InvocationNodeFooter = ({ nodeId }: Props) => {
- const hasImageOutput = useHasImageOutput(nodeId);
+ const hasImageOutput = useNodeHasImageOutput(nodeId);
const isCacheEnabled = useFeatureStatus('invocationCache');
return (
{
+type Props = {
+ nodeId: string;
+};
+
+export const InvocationNodeNotesTextarea = memo(({ nodeId }: Props) => {
const dispatch = useAppDispatch();
- const node = useNode(nodeId);
const { t } = useTranslation();
+ const notes = useInvocationNodeNotes(nodeId);
const handleNotesChanged = useCallback(
(e: ChangeEvent) => {
dispatch(nodeNotesChanged({ nodeId, notes: e.target.value }));
},
[dispatch, nodeId]
);
- if (!isInvocationNode(node)) {
- return null;
- }
return (
{t('nodes.notes')}
-
+
);
-};
+});
-export default memo(NotesTextarea);
+InvocationNodeNotesTextarea.displayName = 'InvocationNodeNotesTextarea';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx
index b58f6fe8ba..a740a2ee3d 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeStatusIndicator.tsx
@@ -1,6 +1,6 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Badge, CircularProgress, Flex, Icon, Image, Text, Tooltip } from '@invoke-ai/ui-library';
-import { useExecutionState } from 'features/nodes/hooks/useExecutionState';
+import { useNodeExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { DRAG_HANDLE_CLASSNAME } from 'features/nodes/types/constants';
import type { NodeExecutionState } from 'features/nodes/types/invocation';
import { zNodeStatus } from 'features/nodes/types/invocation';
@@ -22,7 +22,7 @@ const circleStyles: SystemStyleObject = {
};
const InvocationNodeStatusIndicator = ({ nodeId }: Props) => {
- const nodeExecutionState = useExecutionState(nodeId);
+ const nodeExecutionState = useNodeExecutionState(nodeId);
if (!nodeExecutionState) {
return null;
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/SaveToGalleryCheckbox.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/SaveToGalleryCheckbox.tsx
index 270d137d50..4ef07a0485 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/SaveToGalleryCheckbox.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/SaveToGalleryCheckbox.tsx
@@ -1,7 +1,7 @@
import { Checkbox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
-import { useHasImageOutput } from 'features/nodes/hooks/useHasImageOutput';
import { useIsIntermediate } from 'features/nodes/hooks/useIsIntermediate';
+import { useNodeHasImageOutput } from 'features/nodes/hooks/useNodeHasImageOutput';
import { nodeIsIntermediateChanged } from 'features/nodes/store/nodesSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
@@ -10,7 +10,7 @@ import { useTranslation } from 'react-i18next';
const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
- const hasImageOutput = useHasImageOutput(nodeId);
+ const hasImageOutput = useNodeHasImageOutput(nodeId);
const isIntermediate = useIsIntermediate(nodeId);
const handleChange = useCallback(
(e: ChangeEvent) => {
@@ -30,7 +30,7 @@ const SaveToGalleryCheckbox = ({ nodeId }: { nodeId: string }) => {
return (
- {t('nodes.saveToGallery')}
+ {t('nodes.saveToGallery')}
);
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/UseCacheCheckbox.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/UseCacheCheckbox.tsx
index e8069bf38d..853d718a85 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/UseCacheCheckbox.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/UseCacheCheckbox.tsx
@@ -23,7 +23,7 @@ const UseCacheCheckbox = ({ nodeId }: { nodeId: string }) => {
const { t } = useTranslation();
return (
- {t('invocationCache.useCache')}
+ {t('invocationCache.useCache')}
);
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetToDefaultValueButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetToDefaultValueButton.tsx
deleted file mode 100644
index 9d944a1c81..0000000000
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetToDefaultValueButton.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { FieldResetValueButton } from 'features/nodes/components/flow/nodes/Invocation/fields/FieldResetValueButton';
-import { useFieldDefaultValue } from 'features/nodes/hooks/useFieldDefaultValue';
-import { memo } from 'react';
-
-type Props = {
- nodeId: string;
- fieldName: string;
-};
-
-const FieldResetToDefaultValueButton = ({ nodeId, fieldName }: Props) => {
- const { isValueChanged, resetToDefaultValue } = useFieldDefaultValue(nodeId, fieldName);
-
- return ;
-};
-
-export default memo(FieldResetToDefaultValueButton);
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetToInitialLinearViewValueButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetToInitialLinearViewValueButton.tsx
deleted file mode 100644
index d0c056c933..0000000000
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetToInitialLinearViewValueButton.tsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { FieldResetValueButton } from 'features/nodes/components/flow/nodes/Invocation/fields/FieldResetValueButton';
-import { useFieldInitialLinearViewValue } from 'features/nodes/hooks/useFieldInitialLinearViewValue';
-import { memo } from 'react';
-
-type Props = {
- nodeId: string;
- fieldName: string;
-};
-
-const FieldResetToInitialLinearViewValueButton = ({ nodeId, fieldName }: Props) => {
- const { isValueChanged, resetToInitialLinearViewValue } = useFieldInitialLinearViewValue(nodeId, fieldName);
-
- return ;
-};
-
-export default memo(FieldResetToInitialLinearViewValueButton);
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetValueButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetValueButton.tsx
deleted file mode 100644
index c9d1405b40..0000000000
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldResetValueButton.tsx
+++ /dev/null
@@ -1,25 +0,0 @@
-import type { IconButtonProps } from '@invoke-ai/ui-library';
-import { IconButton } from '@invoke-ai/ui-library';
-import { memo } from 'react';
-import { useTranslation } from 'react-i18next';
-import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
-
-type Props = Omit;
-
-export const FieldResetValueButton = memo((props: Props) => {
- const { t } = useTranslation();
-
- return (
- }
- pointerEvents="auto"
- size="xs"
- {...props}
- />
- );
-});
-
-FieldResetValueButton.displayName = 'FieldResetValueButton';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx
deleted file mode 100644
index a8925f5b3a..0000000000
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldTooltipContent.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { Flex, Text } from '@invoke-ai/ui-library';
-import { useFieldInputInstance } from 'features/nodes/hooks/useFieldInputInstance';
-import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
-import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
-import { isFieldInputInstance, isFieldInputTemplate } from 'features/nodes/types/field';
-import { startCase } from 'lodash-es';
-import { memo, useMemo } from 'react';
-import { useTranslation } from 'react-i18next';
-interface Props {
- nodeId: string;
- fieldName: string;
- kind: 'inputs' | 'outputs';
-}
-
-const FieldTooltipContent = ({ nodeId, fieldName, kind }: Props) => {
- const field = useFieldInputInstance(nodeId, fieldName);
- const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind);
- const isInputTemplate = isFieldInputTemplate(fieldTemplate);
- const fieldTypeName = useFieldTypeName(fieldTemplate?.type);
- const { t } = useTranslation();
- const fieldTitle = useMemo(() => {
- if (isFieldInputInstance(field)) {
- if (field.label && fieldTemplate?.title) {
- return `${field.label} (${fieldTemplate.title})`;
- }
-
- if (field.label && !fieldTemplate) {
- return field.label;
- }
-
- if (!field.label && fieldTemplate) {
- return fieldTemplate.title;
- }
-
- return t('nodes.unknownField');
- } else {
- return fieldTemplate?.title || t('nodes.unknownField');
- }
- }, [field, fieldTemplate, t]);
-
- return (
-
- {fieldTitle}
- {fieldTemplate && (
-
- {fieldTemplate.description}
-
- )}
- {fieldTypeName && (
-
- {t('parameters.type')}: {fieldTypeName}
-
- )}
- {isInputTemplate && (
-
- {t('common.input')}: {startCase(fieldTemplate.input)}
-
- )}
-
- );
-};
-
-export default memo(FieldTooltipContent);
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewToggle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddRemoveLinearViewIconButton.tsx
similarity index 71%
rename from invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewToggle.tsx
rename to invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddRemoveLinearViewIconButton.tsx
index be7951497a..9ffe0afa75 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewToggle.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddRemoveLinearViewIconButton.tsx
@@ -1,11 +1,8 @@
import { IconButton } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
-import { useFieldIsExposed } from 'features/nodes/hooks/useFieldIsExposed';
-import { useFieldValue } from 'features/nodes/hooks/useFieldValue';
-import {
- workflowExposedFieldAdded,
- workflowExposedFieldRemoved,
-} from 'features/nodes/store/workflowSlice';
+import { useInputFieldIsExposed } from 'features/nodes/hooks/useInputFieldIsExposed';
+import { useInputFieldValue } from 'features/nodes/hooks/useInputFieldValue';
+import { workflowExposedFieldAdded, workflowExposedFieldRemoved } from 'features/nodes/store/workflowSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiMinusBold, PiPlusBold } from 'react-icons/pi';
@@ -15,11 +12,11 @@ type Props = {
fieldName: string;
};
-const FieldLinearViewToggle = ({ nodeId, fieldName }: Props) => {
+export const InputFieldAddRemoveLinearViewIconButton = memo(({ nodeId, fieldName }: Props) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const value = useFieldValue(nodeId, fieldName);
- const isExposed = useFieldIsExposed(nodeId, fieldName);
+ const value = useInputFieldValue(nodeId, fieldName);
+ const isExposed = useInputFieldIsExposed(nodeId, fieldName);
const handleExposeField = useCallback(() => {
dispatch(workflowExposedFieldAdded({ nodeId, fieldName, value }));
@@ -54,6 +51,6 @@ const FieldLinearViewToggle = ({ nodeId, fieldName }: Props) => {
/>
);
}
-};
+});
-export default memo(FieldLinearViewToggle);
+InputFieldAddRemoveLinearViewIconButton.displayName = 'InputFieldAddRemoveLinearViewIconButton';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewLinear.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeLinear.tsx
similarity index 61%
rename from invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewLinear.tsx
rename to invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeLinear.tsx
index 9bf5e1c5b7..5708ea5b07 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewLinear.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeLinear.tsx
@@ -1,20 +1,18 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
-import { Box, Circle, Flex, Icon, IconButton, Spacer, Tooltip } from '@invoke-ai/ui-library';
+import { Box, Circle, Flex, IconButton, Spacer } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { DndListDropIndicator } from 'features/dnd/DndListDropIndicator';
-import { FieldNotesIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/FieldNotesIconButton';
-import FieldResetToInitialLinearViewValueButton from 'features/nodes/components/flow/nodes/Invocation/fields/FieldResetToInitialLinearViewValueButton';
+import { InputFieldNotesIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldNotesIconButton';
+import InputFieldResetToInitialValueIconButton from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToInitialValueIconButton';
import { useLinearViewFieldDnd } from 'features/nodes/components/sidePanel/workflow/useLinearViewFieldDnd';
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import { workflowExposedFieldRemoved } from 'features/nodes/store/workflowSlice';
-import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
import { memo, useCallback, useRef } from 'react';
import { useTranslation } from 'react-i18next';
-import { PiInfoBold, PiTrashSimpleBold } from 'react-icons/pi';
+import { PiTrashSimpleBold } from 'react-icons/pi';
-import EditableFieldTitle from './EditableFieldTitle';
-import FieldTooltipContent from './FieldTooltipContent';
-import InputFieldRenderer from './InputFieldRenderer';
+import { InputFieldRenderer } from './InputFieldRenderer';
+import { InputFieldTitle } from './InputFieldTitle';
type Props = {
nodeId: string;
@@ -34,7 +32,7 @@ const sx = {
transitionProperty: 'common',
} satisfies SystemStyleObject;
-export const InputFieldViewLinear = memo(({ nodeId, fieldName }: Props) => {
+export const InputFieldEditModeLinear = memo(({ nodeId, fieldName }: Props) => {
const dispatch = useAppDispatch();
const { isMouseOverNode, handleMouseOut, handleMouseOver } = useMouseOverNode(nodeId);
const { t } = useTranslation();
@@ -59,20 +57,11 @@ export const InputFieldViewLinear = memo(({ nodeId, fieldName }: Props) => {
>
-
+
{isMouseOverNode && }
-
-
- }
- openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
- placement="top"
- >
-
-
-
-
+
+
{
);
});
-InputFieldViewLinear.displayName = 'InputFieldViewLinear';
+InputFieldEditModeLinear.displayName = 'InputFieldEditModeLinear';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewNodes.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeNodes.tsx
similarity index 51%
rename from invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewNodes.tsx
rename to invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeNodes.tsx
index 5a1d32ba6a..80f766e7ee 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewNodes.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeNodes.tsx
@@ -1,16 +1,17 @@
import { Flex, FormControl } from '@invoke-ai/ui-library';
import { FieldHandle } from 'features/nodes/components/flow/nodes/Invocation/fields/FieldHandle';
-import { FieldLinearViewConfigIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewConfigIconButton';
-import { FieldNotesIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/FieldNotesIconButton';
-import FieldResetToDefaultValueButton from 'features/nodes/components/flow/nodes/Invocation/fields/FieldResetToDefaultValueButton';
-import { useConnectionState } from 'features/nodes/hooks/useConnectionState';
-import { useFieldInputTemplate } from 'features/nodes/hooks/useFieldInputTemplate';
-import { useFieldIsInvalid } from 'features/nodes/hooks/useFieldIsInvalid';
+import { InputFieldLinearViewConfigIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldLinearViewConfigIconButton';
+import { InputFieldNotesIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldNotesIconButton';
+import { InputFieldResetToDefaultValueIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToDefaultValueIconButton';
+import { useInputFieldConnectionState } from 'features/nodes/hooks/useInputFieldConnectionState';
+import { useInputFieldIsConnected } from 'features/nodes/hooks/useInputFieldIsConnected';
+import { useInputFieldIsInvalid } from 'features/nodes/hooks/useInputFieldIsInvalid';
+import { useInputFieldTemplate } from 'features/nodes/hooks/useInputFieldTemplate';
import { memo, useCallback, useState } from 'react';
-import EditableFieldTitle from './EditableFieldTitle';
-import FieldLinearViewToggle from './FieldLinearViewToggle';
-import InputFieldRenderer from './InputFieldRenderer';
+import { InputFieldAddRemoveLinearViewIconButton } from './InputFieldAddRemoveLinearViewIconButton';
+import { InputFieldRenderer } from './InputFieldRenderer';
+import { InputFieldTitle } from './InputFieldTitle';
import { InputFieldWrapper } from './InputFieldWrapper';
interface Props {
@@ -18,13 +19,15 @@ interface Props {
fieldName: string;
}
-export const InputFieldViewNodes = memo(({ nodeId, fieldName }: Props) => {
- const fieldTemplate = useFieldInputTemplate(nodeId, fieldName);
+export const InputFieldEditModeNodes = memo(({ nodeId, fieldName }: Props) => {
+ const fieldTemplate = useInputFieldTemplate(nodeId, fieldName);
const [isHovered, setIsHovered] = useState(false);
- const isInvalid = useFieldIsInvalid(nodeId, fieldName);
-
- const { isConnected, isConnectionInProgress, isConnectionStartField, validationResult, shouldDim } =
- useConnectionState(nodeId, fieldName, 'inputs');
+ const isInvalid = useInputFieldIsInvalid(nodeId, fieldName);
+ const isConnected = useInputFieldIsConnected(nodeId, fieldName);
+ const { isConnectionInProgress, isConnectionStartField, validationResult } = useInputFieldConnectionState(
+ nodeId,
+ fieldName
+ );
const onMouseEnter = useCallback(() => {
setIsHovered(true);
@@ -36,15 +39,13 @@ export const InputFieldViewNodes = memo(({ nodeId, fieldName }: Props) => {
if (fieldTemplate.input === 'connection' || isConnected) {
return (
-
+
-
@@ -60,7 +61,7 @@ export const InputFieldViewNodes = memo(({ nodeId, fieldName }: Props) => {
}
return (
-
+
{
>
-
+
{isHovered && (
<>
-
-
-
-
+
+
+
+
>
)}
@@ -99,4 +100,4 @@ export const InputFieldViewNodes = memo(({ nodeId, fieldName }: Props) => {
);
});
-InputFieldViewNodes.displayName = 'InputFieldViewNodes';
+InputFieldEditModeNodes.displayName = 'InputFieldEditModeNodes';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldGate.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldGate.tsx
index 29fd7bc7e2..5f43fc2977 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldGate.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldGate.tsx
@@ -1,6 +1,6 @@
import { InputFieldUnknownPlaceholder } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldUnknownPlaceholder';
-import { useFieldInputInstanceExists } from 'features/nodes/hooks/useFieldInputInstanceExists';
-import { useFieldInputTemplateExists } from 'features/nodes/hooks/useFieldInputTemplateExists';
+import { useInputFieldInstanceExists } from 'features/nodes/hooks/useInputFieldInstanceExists';
+import { useInputFieldTemplateExists } from 'features/nodes/hooks/useInputFieldTemplateExists';
import type { PropsWithChildren } from 'react';
import { memo } from 'react';
@@ -10,8 +10,8 @@ type Props = PropsWithChildren<{
}>;
export const InputFieldGate = memo(({ nodeId, fieldName, children }: Props) => {
- const hasInstance = useFieldInputInstanceExists(nodeId, fieldName);
- const hasTemplate = useFieldInputTemplateExists(nodeId, fieldName);
+ const hasInstance = useInputFieldInstanceExists(nodeId, fieldName);
+ const hasTemplate = useInputFieldTemplateExists(nodeId, fieldName);
if (!hasTemplate || !hasInstance) {
return ;
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewConfigIconButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldLinearViewConfigIconButton.tsx
similarity index 81%
rename from invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewConfigIconButton.tsx
rename to invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldLinearViewConfigIconButton.tsx
index fb70070f3a..b7659574ad 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewConfigIconButton.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldLinearViewConfigIconButton.tsx
@@ -8,8 +8,8 @@ import {
Select,
} from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
-import { useFieldIsExposed } from 'features/nodes/hooks/useFieldIsExposed';
-import { useFieldLinearViewConfig } from 'features/nodes/hooks/useFieldLinearViewConfig';
+import { useInputFieldIsExposed } from 'features/nodes/hooks/useInputFieldIsExposed';
+import { useInputFieldLinearViewConfig } from 'features/nodes/hooks/useInputFieldLinearViewConfig';
import { fieldLinearViewConfigChanged } from 'features/nodes/store/nodesSlice';
import type { FieldInputInstance } from 'features/nodes/types/field';
import type { ChangeEvent } from 'react';
@@ -37,11 +37,11 @@ const parseNotesDisplay = (
}
};
-export const FieldLinearViewConfigIconButton = memo(({ nodeId, fieldName }: Props) => {
+export const InputFieldLinearViewConfigIconButton = memo(({ nodeId, fieldName }: Props) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const isExposed = useFieldIsExposed(nodeId, fieldName);
- const linearViewConfig = useFieldLinearViewConfig(nodeId, fieldName);
+ const isExposed = useInputFieldIsExposed(nodeId, fieldName);
+ const linearViewConfig = useInputFieldLinearViewConfig(nodeId, fieldName);
const onChangeNotesDisplay = useCallback(
(e: ChangeEvent) => {
const notesDisplay = parseNotesDisplay(e.target.value);
@@ -82,4 +82,4 @@ export const FieldLinearViewConfigIconButton = memo(({ nodeId, fieldName }: Prop
);
});
-FieldLinearViewConfigIconButton.displayName = 'FieldLinearViewConfigIconButton';
+InputFieldLinearViewConfigIconButton.displayName = 'InputFieldLinearViewConfigIconButton';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldNotesIconButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldNotesIconButton.tsx
similarity index 86%
rename from invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldNotesIconButton.tsx
rename to invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldNotesIconButton.tsx
index 3f581e0be3..23508f22cc 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldNotesIconButton.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldNotesIconButton.tsx
@@ -10,7 +10,7 @@ import {
Textarea,
} from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
-import { useFieldNotes } from 'features/nodes/hooks/useFieldNotes';
+import { useInputFieldNotes } from 'features/nodes/hooks/useInputFieldNotes';
import { fieldNotesChanged } from 'features/nodes/store/nodesSlice';
import type { ChangeEvent } from 'react';
import { memo, useCallback } from 'react';
@@ -23,10 +23,10 @@ type Props = {
readOnly?: boolean;
};
-export const FieldNotesIconButton = memo(({ nodeId, fieldName, readOnly }: Props) => {
+export const InputFieldNotesIconButton = memo(({ nodeId, fieldName, readOnly }: Props) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
- const notes = useFieldNotes(nodeId, fieldName);
+ const notes = useInputFieldNotes(nodeId, fieldName);
const onChange = useCallback(
(e: ChangeEvent) => {
dispatch(fieldNotesChanged({ nodeId, fieldName, val: e.target.value }));
@@ -75,4 +75,4 @@ export const FieldNotesIconButton = memo(({ nodeId, fieldName, readOnly }: Props
);
});
-FieldNotesIconButton.displayName = 'FieldNotesIconButton';
+InputFieldNotesIconButton.displayName = 'InputFieldNotesIconButton';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer.tsx
index 998fc4ae41..6763794765 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer.tsx
@@ -5,8 +5,8 @@ import ModelIdentifierFieldInputComponent from 'features/nodes/components/flow/n
import { NumberFieldCollectionInputComponent } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldCollectionInputComponent';
import { StringFieldCollectionInputComponent } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/StringFieldCollectionInputComponent';
import { StringGeneratorFieldInputComponent } from 'features/nodes/components/flow/nodes/Invocation/fields/inputs/StringGeneratorFieldComponent';
-import { useFieldInputInstance } from 'features/nodes/hooks/useFieldInputInstance';
-import { useFieldInputTemplate } from 'features/nodes/hooks/useFieldInputTemplate';
+import { useInputFieldInstance } from 'features/nodes/hooks/useInputFieldInstance';
+import { useInputFieldTemplate } from 'features/nodes/hooks/useInputFieldTemplate';
import {
isBoardFieldInputInstance,
isBoardFieldInputTemplate,
@@ -110,9 +110,9 @@ type InputFieldProps = {
fieldName: string;
};
-const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
- const fieldInstance = useFieldInputInstance(nodeId, fieldName);
- const fieldTemplate = useFieldInputTemplate(nodeId, fieldName);
+export const InputFieldRenderer = memo(({ nodeId, fieldName }: InputFieldProps) => {
+ const fieldInstance = useInputFieldInstance(nodeId, fieldName);
+ const fieldTemplate = useInputFieldTemplate(nodeId, fieldName);
if (isStringFieldCollectionInputInstance(fieldInstance) && isStringFieldCollectionInputTemplate(fieldTemplate)) {
return ;
@@ -258,10 +258,7 @@ const InputFieldRenderer = ({ nodeId, fieldName }: InputFieldProps) => {
return ;
}
- if (fieldTemplate) {
- // Fallback for when there is no component for the type
- return null;
- }
-};
+ return null;
+});
-export default memo(InputFieldRenderer);
+InputFieldRenderer.displayName = 'InputFieldRenderer';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToDefaultValueIconButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToDefaultValueIconButton.tsx
new file mode 100644
index 0000000000..a147743af7
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToDefaultValueIconButton.tsx
@@ -0,0 +1,30 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useInputFieldDefaultValue } from 'features/nodes/hooks/useInputFieldDefaultValue';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
+
+type Props = {
+ nodeId: string;
+ fieldName: string;
+};
+
+export const InputFieldResetToDefaultValueIconButton = memo(({ nodeId, fieldName }: Props) => {
+ const { t } = useTranslation();
+ const { isValueChanged, resetToDefaultValue } = useInputFieldDefaultValue(nodeId, fieldName);
+
+ return (
+ }
+ pointerEvents="auto"
+ size="xs"
+ onClick={resetToDefaultValue}
+ isDisabled={!isValueChanged}
+ />
+ );
+});
+
+InputFieldResetToDefaultValueIconButton.displayName = 'InputFieldResetToDefaultValueIconButton';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToInitialValueIconButton.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToInitialValueIconButton.tsx
new file mode 100644
index 0000000000..3acfc24e7f
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToInitialValueIconButton.tsx
@@ -0,0 +1,30 @@
+import { IconButton } from '@invoke-ai/ui-library';
+import { useInputFieldInitialLinearViewValue } from 'features/nodes/hooks/useInputFieldInitialLinearViewValue';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+import { PiArrowCounterClockwiseBold } from 'react-icons/pi';
+
+type Props = {
+ nodeId: string;
+ fieldName: string;
+};
+
+const InputFieldResetToInitialValueIconButton = ({ nodeId, fieldName }: Props) => {
+ const { t } = useTranslation();
+ const { isValueChanged, resetToInitialLinearViewValue } = useInputFieldInitialLinearViewValue(nodeId, fieldName);
+
+ return (
+ }
+ pointerEvents="auto"
+ size="xs"
+ onClick={resetToInitialLinearViewValue}
+ isDisabled={!isValueChanged}
+ />
+ );
+};
+
+export default memo(InputFieldResetToInitialValueIconButton);
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/EditableFieldTitle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTitle.tsx
similarity index 73%
rename from invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/EditableFieldTitle.tsx
rename to invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTitle.tsx
index d7690669e0..fc4857594e 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/EditableFieldTitle.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTitle.tsx
@@ -1,37 +1,54 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
-import {
- Editable,
- EditableInput,
- EditablePreview,
- Flex,
- forwardRef,
- Tooltip,
- useEditableControls,
-} from '@invoke-ai/ui-library';
+import { Editable, EditableInput, EditablePreview, Flex, Tooltip, useEditableControls } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
-import { useFieldLabel } from 'features/nodes/hooks/useFieldLabel';
-import { useFieldTemplateTitle } from 'features/nodes/hooks/useFieldTemplateTitle';
+import { InputFieldTooltip } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldTooltip';
+import { useInputFieldLabel } from 'features/nodes/hooks/useInputFieldLabel';
+import { useInputFieldTemplateTitle } from 'features/nodes/hooks/useInputFieldTemplateTitle';
import { fieldLabelChanged } from 'features/nodes/store/nodesSlice';
import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
import type { MouseEvent } from 'react';
import { memo, useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
-import FieldTooltipContent from './FieldTooltipContent';
+const editablePreviewStyles: SystemStyleObject = {
+ p: 0,
+ fontWeight: 'semibold',
+ textAlign: 'left',
+ color: 'base.300',
+ _hover: {
+ fontWeight: 'semibold !important',
+ },
+ '&[data-is-invalid="true"]': {
+ color: 'error.300',
+ },
+ '&[data-is-disabled="true"]': {
+ opacity: 0.5,
+ },
+};
+
+const editableInputStyles: SystemStyleObject = {
+ p: 0,
+ w: 'full',
+ fontWeight: 'semibold',
+ color: 'base.100',
+ _focusVisible: {
+ p: 0,
+ textAlign: 'left',
+ boxShadow: 'none',
+ },
+};
interface Props {
nodeId: string;
fieldName: string;
- kind: 'inputs' | 'outputs';
isInvalid?: boolean;
- withTooltip?: boolean;
- shouldDim?: boolean;
+ isDisabled?: boolean;
}
-const EditableFieldTitle = forwardRef((props: Props, ref) => {
- const { nodeId, fieldName, kind, isInvalid = false, withTooltip = false, shouldDim = false } = props;
- const label = useFieldLabel(nodeId, fieldName);
- const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, kind);
+export const InputFieldTitle = memo((props: Props) => {
+ const { nodeId, fieldName, isInvalid, isDisabled } = props;
+ const label = useInputFieldLabel(nodeId, fieldName);
+ const fieldTemplateTitle = useInputFieldTemplateTitle(nodeId, fieldName);
const { t } = useTranslation();
const dispatch = useAppDispatch();
@@ -62,7 +79,6 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
onChange={handleChange}
onSubmit={handleSubmit}
as={Flex}
- ref={ref}
position="relative"
overflow="hidden"
alignItems="center"
@@ -71,15 +87,14 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
w="full"
>
: undefined}
+ label={}
openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
>
@@ -88,26 +103,7 @@ const EditableFieldTitle = forwardRef((props: Props, ref) => {
);
});
-const editableInputStyles: SystemStyleObject = {
- p: 0,
- w: 'full',
- fontWeight: 'semibold',
- color: 'base.100',
- _focusVisible: {
- p: 0,
- textAlign: 'left',
- boxShadow: 'none',
- },
-};
-const editablePreviewStyles: SystemStyleObject = {
- p: 0,
- textAlign: 'left',
- _hover: {
- fontWeight: 'semibold !important',
- },
-};
-
-export default memo(EditableFieldTitle);
+InputFieldTitle.displayName = 'InputFieldTitle';
const EditableControls = memo(() => {
const { isEditing, getEditButtonProps } = useEditableControls();
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTooltip.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTooltip.tsx
new file mode 100644
index 0000000000..324a83d7e3
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTooltip.tsx
@@ -0,0 +1,49 @@
+import { Flex, Text } from '@invoke-ai/ui-library';
+import { useInputFieldInstance } from 'features/nodes/hooks/useInputFieldInstance';
+import { useInputFieldTemplate } from 'features/nodes/hooks/useInputFieldTemplate';
+import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
+import { startCase } from 'lodash-es';
+import { memo, useMemo } from 'react';
+import { useTranslation } from 'react-i18next';
+
+interface Props {
+ nodeId: string;
+ fieldName: string;
+}
+
+export const InputFieldTooltip = memo(({ nodeId, fieldName }: Props) => {
+ const { t } = useTranslation();
+
+ const fieldInstance = useInputFieldInstance(nodeId, fieldName);
+ const fieldTemplate = useInputFieldTemplate(nodeId, fieldName);
+ const fieldTypeName = useFieldTypeName(fieldTemplate.type);
+
+ const fieldTitle = useMemo(() => {
+ if (fieldInstance.label && fieldTemplate.title) {
+ return `${fieldInstance.label} (${fieldTemplate.title})`;
+ }
+
+ if (fieldInstance.label && !fieldTemplate.title) {
+ return fieldInstance.label;
+ }
+
+ return fieldTemplate.title;
+ }, [fieldInstance, fieldTemplate]);
+
+ return (
+
+ {fieldTitle}
+
+ {fieldTemplate.description}
+
+
+ {t('parameters.type')}: {fieldTypeName}
+
+
+ {t('common.input')}: {startCase(fieldTemplate.input)}
+
+
+ );
+});
+
+InputFieldTooltip.displayName = 'FieldTooltipContent';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldUnknownPlaceholder.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldUnknownPlaceholder.tsx
index 1807673548..615034ea32 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldUnknownPlaceholder.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldUnknownPlaceholder.tsx
@@ -1,6 +1,6 @@
import { FormControl, FormLabel } from '@invoke-ai/ui-library';
import { InputFieldWrapper } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldWrapper';
-import { useFieldInputName } from 'features/nodes/hooks/useFieldInputName';
+import { useInputFieldName } from 'features/nodes/hooks/useInputFieldName';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -11,10 +11,10 @@ type Props = {
export const InputFieldUnknownPlaceholder = memo(({ nodeId, fieldName }: Props) => {
const { t } = useTranslation();
- const name = useFieldInputName(nodeId, fieldName);
+ const name = useInputFieldName(nodeId, fieldName);
return (
-
+
{t('nodes.unknownInput', { name })}
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewMode.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewMode.tsx
new file mode 100644
index 0000000000..e7c1e1e87f
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewMode.tsx
@@ -0,0 +1,31 @@
+import { Flex, FormLabel, Spacer } from '@invoke-ai/ui-library';
+import { InputFieldNotesIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldNotesIconButton';
+import { InputFieldRenderer } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer';
+import InputFieldResetToInitialValueIconButton from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToInitialValueIconButton';
+import { useInputFieldLabel } from 'features/nodes/hooks/useInputFieldLabel';
+import { useInputFieldTemplateTitle } from 'features/nodes/hooks/useInputFieldTemplateTitle';
+import { memo } from 'react';
+
+type Props = {
+ nodeId: string;
+ fieldName: string;
+};
+
+export const InputFieldViewMode = memo(({ nodeId, fieldName }: Props) => {
+ const label = useInputFieldLabel(nodeId, fieldName);
+ const fieldTemplateTitle = useInputFieldTemplateTitle(nodeId, fieldName);
+
+ return (
+
+
+ {label || fieldTemplateTitle}
+
+
+
+
+
+
+ );
+});
+
+InputFieldViewMode.displayName = 'InputFieldViewMode';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewSimple.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewSimple.tsx
deleted file mode 100644
index 0d3fcf463e..0000000000
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewSimple.tsx
+++ /dev/null
@@ -1,31 +0,0 @@
-import { Flex, FormLabel, Spacer } from '@invoke-ai/ui-library';
-import { FieldNotesIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/FieldNotesIconButton';
-import FieldResetToInitialLinearViewValueButton from 'features/nodes/components/flow/nodes/Invocation/fields/FieldResetToInitialLinearViewValueButton';
-import InputFieldRenderer from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldRenderer';
-import { useFieldLabel } from 'features/nodes/hooks/useFieldLabel';
-import { useFieldTemplateTitle } from 'features/nodes/hooks/useFieldTemplateTitle';
-import { memo } from 'react';
-
-type Props = {
- nodeId: string;
- fieldName: string;
-};
-
-export const InputFieldViewSimple = memo(({ nodeId, fieldName }: Props) => {
- const label = useFieldLabel(nodeId, fieldName);
- const fieldTemplateTitle = useFieldTemplateTitle(nodeId, fieldName, 'inputs');
-
- return (
-
-
- {label || fieldTemplateTitle}
-
-
-
-
-
-
- );
-});
-
-InputFieldViewSimple.displayName = 'InputFieldViewSimple';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldWrapper.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldWrapper.tsx
index d7cc9d5fa5..5c5046b086 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldWrapper.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldWrapper.tsx
@@ -12,21 +12,10 @@ const sx = {
transitionDuration: '0.1s',
w: 'full',
h: 'full',
- '&[data-should-dim="true"]': {
- opacity: 0.5,
- },
} satisfies SystemStyleObject;
-type InputFieldWrapperProps = PropsWithChildren<{
- shouldDim: boolean;
-}>;
-
-export const InputFieldWrapper = memo(({ shouldDim, children }: InputFieldWrapperProps) => {
- return (
-
- {children}
-
- );
+export const InputFieldWrapper = memo(({ children }: PropsWithChildren) => {
+ return {children};
});
InputFieldWrapper.displayName = 'InputFieldWrapper';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldGate.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldGate.tsx
index 81951959e2..2b50e35706 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldGate.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldGate.tsx
@@ -1,5 +1,5 @@
import { OutputFieldUnknownPlaceholder } from 'features/nodes/components/flow/nodes/Invocation/fields/OutputFieldUnknownPlaceholder';
-import { useFieldOutputTemplateExists } from 'features/nodes/hooks/useFieldOutputTemplateExists';
+import { useOutputFieldTemplateExists } from 'features/nodes/hooks/useOutputFieldTemplateExists';
import type { PropsWithChildren } from 'react';
import { memo } from 'react';
@@ -9,7 +9,7 @@ type Props = PropsWithChildren<{
}>;
export const OutputFieldGate = memo(({ nodeId, fieldName, children }: Props) => {
- const hasTemplate = useFieldOutputTemplateExists(nodeId, fieldName);
+ const hasTemplate = useOutputFieldTemplateExists(nodeId, fieldName);
if (!hasTemplate) {
return ;
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldNodesEditorView.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldNodesEditorView.tsx
index 85ee48cde6..6fac1f148f 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldNodesEditorView.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldNodesEditorView.tsx
@@ -1,37 +1,32 @@
-import { FormControl, FormLabel, Tooltip } from '@invoke-ai/ui-library';
import { FieldHandle } from 'features/nodes/components/flow/nodes/Invocation/fields/FieldHandle';
+import { OutputFieldTitle } from 'features/nodes/components/flow/nodes/Invocation/fields/OutputFieldTitle';
import { OutputFieldWrapper } from 'features/nodes/components/flow/nodes/Invocation/fields/OutputFieldWrapper';
-import { useConnectionState } from 'features/nodes/hooks/useConnectionState';
-import { useFieldOutputTemplate } from 'features/nodes/hooks/useFieldOutputTemplate';
-import { HANDLE_TOOLTIP_OPEN_DELAY } from 'features/nodes/types/constants';
+import { useOutputFieldConnectionState } from 'features/nodes/hooks/useOutputFieldConnectionState';
+import { useOutputFieldIsConnected } from 'features/nodes/hooks/useOutputFieldIsConnected';
+import { useOutputFieldTemplate } from 'features/nodes/hooks/useOutputFieldTemplate';
import type { PropsWithChildren } from 'react';
import { memo } from 'react';
-import FieldTooltipContent from './FieldTooltipContent';
-
type Props = PropsWithChildren<{
nodeId: string;
fieldName: string;
}>;
export const OutputFieldNodesEditorView = memo(({ nodeId, fieldName }: Props) => {
- const fieldTemplate = useFieldOutputTemplate(nodeId, fieldName);
-
- const { isConnected, isConnectionInProgress, isConnectionStartField, validationResult, shouldDim } =
- useConnectionState(nodeId, fieldName, 'outputs');
+ const fieldTemplate = useOutputFieldTemplate(nodeId, fieldName);
+ const isConnected = useOutputFieldIsConnected(nodeId, fieldName);
+ const { isConnectionInProgress, isConnectionStartField, validationResult } = useOutputFieldConnectionState(
+ nodeId,
+ fieldName
+ );
return (
-
- }
- openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
- placement="top"
- shouldWrapChildren
- >
-
- {fieldTemplate?.title}
-
-
+
+
;
+
+export const OutputFieldTitle = memo(({ nodeId, fieldName, isDisabled }: Props) => {
+ const fieldTemplate = useOutputFieldTemplate(nodeId, fieldName);
+
+ return (
+ }
+ openDelay={HANDLE_TOOLTIP_OPEN_DELAY}
+ placement="top"
+ shouldWrapChildren
+ >
+
+ {fieldTemplate.title}
+
+
+ );
+});
+
+OutputFieldTitle.displayName = 'OutputFieldTitle';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldTooltip.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldTooltip.tsx
new file mode 100644
index 0000000000..e034720e46
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldTooltip.tsx
@@ -0,0 +1,29 @@
+import { Flex, Text } from '@invoke-ai/ui-library';
+import { useOutputFieldTemplate } from 'features/nodes/hooks/useOutputFieldTemplate';
+import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
+import { memo } from 'react';
+import { useTranslation } from 'react-i18next';
+interface Props {
+ nodeId: string;
+ fieldName: string;
+}
+
+export const OutputFieldTooltip = memo(({ nodeId, fieldName }: Props) => {
+ const fieldTemplate = useOutputFieldTemplate(nodeId, fieldName);
+ const fieldTypeName = useFieldTypeName(fieldTemplate.type);
+ const { t } = useTranslation();
+
+ return (
+
+ {fieldTemplate.title}
+
+ {fieldTemplate.description}
+
+
+ {t('parameters.type')}: {fieldTypeName}
+
+
+ );
+});
+
+OutputFieldTooltip.displayName = 'OutputFieldTooltip';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldUnknownPlaceholder.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldUnknownPlaceholder.tsx
index 20e69a227a..9de792b9e5 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldUnknownPlaceholder.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldUnknownPlaceholder.tsx
@@ -1,6 +1,6 @@
import { FormControl, FormLabel } from '@invoke-ai/ui-library';
import { OutputFieldWrapper } from 'features/nodes/components/flow/nodes/Invocation/fields/OutputFieldWrapper';
-import { useFieldOutputName } from 'features/nodes/hooks/useFieldOutputName';
+import { useOutputFieldName } from 'features/nodes/hooks/useOutputFieldName';
import { memo } from 'react';
import { useTranslation } from 'react-i18next';
@@ -11,10 +11,10 @@ type Props = {
export const OutputFieldUnknownPlaceholder = memo(({ nodeId, fieldName }: Props) => {
const { t } = useTranslation();
- const name = useFieldOutputName(nodeId, fieldName);
+ const name = useOutputFieldName(nodeId, fieldName);
return (
-
+
{t('nodes.unknownOutput', { name })}
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldWrapper.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldWrapper.tsx
index fffbeb7c73..dd45d25368 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldWrapper.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/OutputFieldWrapper.tsx
@@ -11,19 +11,8 @@ const sx = {
transitionProperty: 'opacity',
transitionDuration: '0.1s',
justifyContent: 'flex-end',
- '&[data-should-dim="true"]': {
- opacity: 0.5,
- },
} satisfies SystemStyleObject;
-type OutputFieldWrapperProps = PropsWithChildren<{
- shouldDim: boolean;
-}>;
-
-export const OutputFieldWrapper = memo(({ shouldDim, children }: OutputFieldWrapperProps) => (
-
- {children}
-
-));
+export const OutputFieldWrapper = memo(({ children }: PropsWithChildren) => {children});
OutputFieldWrapper.displayName = 'OutputFieldWrapper';
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageFieldCollectionInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageFieldCollectionInputComponent.tsx
index d011a7ea76..8a36d3044c 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageFieldCollectionInputComponent.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/ImageFieldCollectionInputComponent.tsx
@@ -10,7 +10,7 @@ import { addImagesToNodeImageFieldCollectionDndTarget } from 'features/dnd/dnd';
import { DndDropTarget } from 'features/dnd/DndDropTarget';
import { DndImage } from 'features/dnd/DndImage';
import { DndImageIcon } from 'features/dnd/DndImageIcon';
-import { useFieldIsInvalid } from 'features/nodes/hooks/useFieldIsInvalid';
+import { useInputFieldIsInvalid } from 'features/nodes/hooks/useInputFieldIsInvalid';
import { fieldImageCollectionValueChanged } from 'features/nodes/store/nodesSlice';
import type { ImageField } from 'features/nodes/types/common';
import type { ImageFieldCollectionInputInstance, ImageFieldCollectionInputTemplate } from 'features/nodes/types/field';
@@ -39,7 +39,7 @@ export const ImageFieldCollectionInputComponent = memo(
const { nodeId, field } = props;
const store = useAppStore();
- const isInvalid = useFieldIsInvalid(nodeId, field.name);
+ const isInvalid = useInputFieldIsInvalid(nodeId, field.name);
const dndTargetData = useMemo(
() =>
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldCollectionInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldCollectionInputComponent.tsx
index 8f48f01206..f39abde93d 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldCollectionInputComponent.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/NumberFieldCollectionInputComponent.tsx
@@ -12,7 +12,7 @@ import {
import { NUMPY_RAND_MAX } from 'app/constants';
import { useAppStore } from 'app/store/nanostores/store';
import { getOverlayScrollbarsParams, overlayScrollbarsStyles } from 'common/components/OverlayScrollbars/constants';
-import { useFieldIsInvalid } from 'features/nodes/hooks/useFieldIsInvalid';
+import { useInputFieldIsInvalid } from 'features/nodes/hooks/useInputFieldIsInvalid';
import { fieldNumberCollectionValueChanged } from 'features/nodes/store/nodesSlice';
import type {
FloatFieldCollectionInputInstance,
@@ -48,7 +48,7 @@ export const NumberFieldCollectionInputComponent = memo(
const store = useAppStore();
const { t } = useTranslation();
- const isInvalid = useFieldIsInvalid(nodeId, field.name);
+ const isInvalid = useInputFieldIsInvalid(nodeId, field.name);
const isIntegerField = useMemo(() => fieldTemplate.type.name === 'IntegerField', [fieldTemplate.type]);
const onRemoveNumber = useCallback(
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringFieldCollectionInputComponent.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringFieldCollectionInputComponent.tsx
index 16e04af51c..ee85fc7d95 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringFieldCollectionInputComponent.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/inputs/StringFieldCollectionInputComponent.tsx
@@ -2,7 +2,7 @@ import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Button, Divider, Flex, FormLabel, Grid, GridItem, IconButton, Input } from '@invoke-ai/ui-library';
import { useAppStore } from 'app/store/nanostores/store';
import { getOverlayScrollbarsParams, overlayScrollbarsStyles } from 'common/components/OverlayScrollbars/constants';
-import { useFieldIsInvalid } from 'features/nodes/hooks/useFieldIsInvalid';
+import { useInputFieldIsInvalid } from 'features/nodes/hooks/useInputFieldIsInvalid';
import { fieldStringCollectionValueChanged } from 'features/nodes/store/nodesSlice';
import type {
StringFieldCollectionInputInstance,
@@ -32,7 +32,7 @@ export const StringFieldCollectionInputComponent = memo(
const { t } = useTranslation();
const store = useAppStore();
- const isInvalid = useFieldIsInvalid(nodeId, field.name);
+ const isInvalid = useInputFieldIsInvalid(nodeId, field.name);
const onRemoveString = useCallback(
(index: number) => {
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx
index 7ae3872864..18a7c05073 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx
@@ -2,7 +2,7 @@ import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, useGlobalMenuClose, useToken } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector, useAppStore } from 'app/store/storeHooks';
import NodeSelectionOverlay from 'common/components/NodeSelectionOverlay';
-import { useExecutionState } from 'features/nodes/hooks/useExecutionState';
+import { useNodeExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import { nodesChanged } from 'features/nodes/store/nodesSlice';
import { selectNodes } from 'features/nodes/store/selectors';
@@ -24,7 +24,7 @@ const NodeWrapper = (props: NodeWrapperProps) => {
const store = useAppStore();
const { isMouseOverNode, handleMouseOut, handleMouseOver } = useMouseOverNode(nodeId);
- const executionState = useExecutionState(nodeId);
+ const executionState = useNodeExecutionState(nodeId);
const isInProgress = executionState?.status === zNodeStatus.enum.IN_PROGRESS;
const [nodeInProgress, shadowsXl, shadowsBase] = useToken('shadows', [
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/LinearViewLeftPanelContent.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/EditModeLeftPanelContent.tsx
similarity index 92%
rename from invokeai/frontend/web/src/features/nodes/components/sidePanel/LinearViewLeftPanelContent.tsx
rename to invokeai/frontend/web/src/features/nodes/components/sidePanel/EditModeLeftPanelContent.tsx
index dc13454a98..4b4f8b86b9 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/LinearViewLeftPanelContent.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/EditModeLeftPanelContent.tsx
@@ -13,7 +13,7 @@ import WorkflowFieldsLinearViewPanel from './workflow/WorkflowPanel';
const panelGroupStyles: CSSProperties = { height: '100%', width: '100%' };
-export const LinearViewLeftPanelContent = memo(() => {
+export const EditModeLeftPanelContent = memo(() => {
const panelGroupRef = useRef(null);
const handleDoubleClickHandle = useCallback(() => {
@@ -46,4 +46,4 @@ export const LinearViewLeftPanelContent = memo(() => {
);
});
-LinearViewLeftPanelContent.displayName = 'LinearViewLeftPanelContent';
+EditModeLeftPanelContent.displayName = 'EditModeLeftPanelContent';
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx
index 68373a5dc1..84d630372d 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx
@@ -2,12 +2,12 @@ import 'reactflow/dist/style.css';
import { Box, Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
-import { LinearViewLeftPanelContent } from 'features/nodes/components/sidePanel/LinearViewLeftPanelContent';
+import { EditModeLeftPanelContent } from 'features/nodes/components/sidePanel/EditModeLeftPanelContent';
import { useWorkflowListMenu } from 'features/nodes/store/workflowListMenu';
import { selectWorkflowMode } from 'features/nodes/store/workflowSlice';
import { memo } from 'react';
-import { SimpleViewLeftPanelContent } from './viewMode/SimpleViewLeftPanelContent';
+import { ViewModeLeftPanelContent } from './viewMode/ViewModeLeftPanelContent';
import { WorkflowListMenu } from './WorkflowListMenu/WorkflowListMenu';
import { WorkflowListMenuTrigger } from './WorkflowListMenu/WorkflowListMenuTrigger';
@@ -21,8 +21,8 @@ const WorkflowsTabLeftPanel = () => {
{workflowListMenu.isOpen && }
- {mode === 'view' && }
- {mode === 'edit' && }
+ {mode === 'view' && }
+ {mode === 'edit' && }
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx
index 3ed2189ff7..f4796893aa 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorDetailsTab.tsx
@@ -4,7 +4,8 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
-import NotesTextarea from 'features/nodes/components/flow/nodes/Invocation/NotesTextarea';
+import { InvocationNodeNotesTextarea } from 'features/nodes/components/flow/nodes/Invocation/InvocationNodeNotesTextarea';
+import { useNodeIsInvocationNode } from 'features/nodes/hooks/useNodeIsInvocationNode';
import { useNodeNeedsUpdate } from 'features/nodes/hooks/useNodeNeedsUpdate';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode, selectNodesSlice } from 'features/nodes/store/selectors';
@@ -55,6 +56,7 @@ type ContentProps = {
const Content = memo((props: ContentProps) => {
const { t } = useTranslation();
const needsUpdate = useNodeNeedsUpdate(props.nodeId);
+ const isInvocationNode = useNodeIsInvocationNode(props.nodeId);
return (
@@ -74,7 +76,7 @@ const Content = memo((props: ContentProps) => {
-
+ {isInvocationNode && }
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx
index 048276f5c0..9f032c1249 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/inspector/InspectorOutputsTab.tsx
@@ -5,7 +5,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import DataViewer from 'features/gallery/components/ImageMetadataViewer/DataViewer';
-import { useExecutionState } from 'features/nodes/hooks/useExecutionState';
+import { useNodeExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectLastSelectedNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
@@ -35,7 +35,7 @@ const InspectorOutputsTab = () => {
[templates]
);
const data = useAppSelector(selector);
- const nes = useExecutionState(data?.nodeId);
+ const nes = useNodeExecutionState(data?.nodeId);
const { t } = useTranslation();
if (!data || !nes) {
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/viewMode/SimpleViewLeftPanelContent.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/viewMode/ViewModeLeftPanelContent.tsx
similarity index 76%
rename from invokeai/frontend/web/src/features/nodes/components/sidePanel/viewMode/SimpleViewLeftPanelContent.tsx
rename to invokeai/frontend/web/src/features/nodes/components/sidePanel/viewMode/ViewModeLeftPanelContent.tsx
index 05048dbc5f..25ee742020 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/viewMode/SimpleViewLeftPanelContent.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/viewMode/ViewModeLeftPanelContent.tsx
@@ -4,7 +4,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import ScrollableContent from 'common/components/OverlayScrollbars/ScrollableContent';
import { InputFieldGate } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldGate';
-import { InputFieldViewSimple } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewSimple';
+import { InputFieldViewMode } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewMode';
import { selectWorkflowSlice } from 'features/nodes/store/workflowSlice';
import { t } from 'i18next';
import { memo } from 'react';
@@ -14,20 +14,20 @@ import { EmptyState } from './EmptyState';
const selectExposedFields = createMemoizedSelector(selectWorkflowSlice, (workflow) => workflow.exposedFields);
-export const SimpleViewLeftPanelContent = memo(() => {
+export const ViewModeLeftPanelContent = memo(() => {
return (
-
+
);
});
-SimpleViewLeftPanelContent.displayName = 'SimpleViewLeftPanelContent';
+ViewModeLeftPanelContent.displayName = 'ViewModeLeftPanelContent';
-const SimpleViewLeftPanelContentInner = memo(() => {
+const ViewModeLeftPanelContentInner = memo(() => {
const { isLoading } = useGetOpenAPISchemaQuery();
const exposedFields = useAppSelector(selectExposedFields);
@@ -43,10 +43,10 @@ const SimpleViewLeftPanelContentInner = memo(() => {
<>
{exposedFields.map(({ nodeId, fieldName }) => (
-
+
))}
>
);
});
-SimpleViewLeftPanelContentInner.displayName = ' SimpleViewLeftPanelContentInner';
+ViewModeLeftPanelContentInner.displayName = ' ViewModeLeftPanelContentInner';
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx
index 80726470e7..7270bd7264 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/workflow/WorkflowLinearTab.tsx
@@ -10,8 +10,8 @@ import { colorTokenToCssVar } from 'common/util/colorTokenToCssVar';
import { deepClone } from 'common/util/deepClone';
import { singleWorkflowFieldDndSource } from 'features/dnd/dnd';
import { triggerPostMoveFlash } from 'features/dnd/util';
+import { InputFieldEditModeLinear } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeLinear';
import { InputFieldGate } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldGate';
-import { InputFieldViewLinear } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldViewLinear';
import { selectWorkflowSlice, workflowExposedFieldsReordered } from 'features/nodes/store/workflowSlice';
import type { FieldIdentifier } from 'features/nodes/types/field';
import { isEqual } from 'lodash-es';
@@ -143,7 +143,7 @@ const FieldListInnerContent = memo(({ fields }: { fields: FieldIdentifier[] }) =
<>
{fields.map(({ nodeId, fieldName }) => (
-
+
))}
>
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupId.ts b/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupId.ts
index 9ae03fa39a..a41f4ca04e 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupId.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useBatchGroupId.ts
@@ -1,19 +1,24 @@
-import { useNode } from 'features/nodes/hooks/useNode';
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppSelector } from 'app/store/storeHooks';
+import { selectNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { isBatchNode, isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
export const useBatchGroupId = (nodeId: string) => {
- const node = useNode(nodeId);
+ const selector = useMemo(() => {
+ return createSelector(selectNodesSlice, (nodes) => {
+ const node = selectNode(nodes, nodeId);
+ if (!isInvocationNode(node)) {
+ return;
+ }
+ if (!isBatchNode(node)) {
+ return;
+ }
+ return node.data.inputs['batch_group_id']?.value as string;
+ });
+ }, [nodeId]);
- const batchGroupId = useMemo(() => {
- if (!isInvocationNode(node)) {
- return;
- }
- if (!isBatchNode(node)) {
- return;
- }
- return node.data.inputs['batch_group_id']?.value as string;
- }, [node]);
+ const batchGroupId = useAppSelector(selector);
return batchGroupId;
};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useConnectionState.ts b/invokeai/frontend/web/src/features/nodes/hooks/useConnectionState.ts
deleted file mode 100644
index 87438a3c07..0000000000
--- a/invokeai/frontend/web/src/features/nodes/hooks/useConnectionState.ts
+++ /dev/null
@@ -1,59 +0,0 @@
-import { useStore } from '@nanostores/react';
-import { createSelector } from '@reduxjs/toolkit';
-import { useAppSelector } from 'app/store/storeHooks';
-import { $edgePendingUpdate, $pendingConnection, $templates } from 'features/nodes/store/nodesSlice';
-import { selectNodesSlice } from 'features/nodes/store/selectors';
-import { makeConnectionErrorSelector } from 'features/nodes/store/util/makeConnectionErrorSelector';
-import { useMemo } from 'react';
-
-export const useConnectionState = (nodeId: string, fieldName: string, kind: 'inputs' | 'outputs') => {
- const pendingConnection = useStore($pendingConnection);
- const templates = useStore($templates);
- const edgePendingUpdate = useStore($edgePendingUpdate);
-
- const selectIsConnected = useMemo(
- () =>
- createSelector(selectNodesSlice, (nodes) => {
- const firstConnectedEdge = nodes.edges.find((edge) => {
- return (
- (kind === 'inputs' ? edge.target : edge.source) === nodeId &&
- (kind === 'inputs' ? edge.targetHandle : edge.sourceHandle) === fieldName
- );
- });
- return firstConnectedEdge !== undefined;
- }),
- [fieldName, kind, nodeId]
- );
-
- const selectValidationResult = useMemo(
- () => makeConnectionErrorSelector(templates, nodeId, fieldName, kind === 'inputs' ? 'target' : 'source'),
- [templates, nodeId, fieldName, kind]
- );
-
- const isConnected = useAppSelector(selectIsConnected);
- const isConnectionInProgress = useMemo(() => Boolean(pendingConnection), [pendingConnection]);
- const isConnectionStartField = useMemo(() => {
- if (!pendingConnection) {
- return false;
- }
- return (
- pendingConnection.nodeId === nodeId &&
- pendingConnection.handleId === fieldName &&
- pendingConnection.fieldTemplate.fieldKind === { inputs: 'input', outputs: 'output' }[kind]
- );
- }, [fieldName, kind, nodeId, pendingConnection]);
- const validationResult = useAppSelector((s) => selectValidationResult(s, pendingConnection, edgePendingUpdate));
-
- const shouldDim = useMemo(
- () => Boolean(isConnectionInProgress && !validationResult.isValid && !isConnectionStartField),
- [validationResult, isConnectionInProgress, isConnectionStartField]
- );
-
- return {
- isConnected,
- isConnectionInProgress,
- isConnectionStartField,
- validationResult,
- shouldDim,
- };
-};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldTemplate.ts b/invokeai/frontend/web/src/features/nodes/hooks/useFieldTemplate.ts
deleted file mode 100644
index 1e1c7a4dd8..0000000000
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldTemplate.ts
+++ /dev/null
@@ -1,36 +0,0 @@
-import { useStore } from '@nanostores/react';
-import { createSelector } from '@reduxjs/toolkit';
-import { useAppSelector } from 'app/store/storeHooks';
-import { $templates } from 'features/nodes/store/nodesSlice';
-import { selectInvocationNodeType, selectNodesSlice } from 'features/nodes/store/selectors';
-import type { FieldInputTemplate, FieldOutputTemplate } from 'features/nodes/types/field';
-import { useMemo } from 'react';
-import { assert } from 'tsafe';
-
-export const useFieldTemplate = (
- nodeId: string,
- fieldName: string,
- kind: 'inputs' | 'outputs'
-): FieldInputTemplate | FieldOutputTemplate => {
- const templates = useStore($templates);
- const selectNodeType = useMemo(
- () => createSelector(selectNodesSlice, (nodes) => selectInvocationNodeType(nodes, nodeId)),
- [nodeId]
- );
- const nodeType = useAppSelector(selectNodeType);
- const fieldTemplate = useMemo(() => {
- const template = templates[nodeType];
- assert(template, `Template for node type ${nodeType} not found`);
- if (kind === 'inputs') {
- const fieldTemplate = template.inputs[fieldName];
- assert(fieldTemplate, `Field template for field ${fieldName} not found`);
- return fieldTemplate;
- } else {
- const fieldTemplate = template.outputs[fieldName];
- assert(fieldTemplate, `Field template for field ${fieldName} not found`);
- return fieldTemplate;
- }
- }, [fieldName, kind, nodeType, templates]);
-
- return fieldTemplate;
-};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldTemplateTitle.ts b/invokeai/frontend/web/src/features/nodes/hooks/useFieldTemplateTitle.ts
deleted file mode 100644
index 2370aa8e1d..0000000000
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldTemplateTitle.ts
+++ /dev/null
@@ -1,8 +0,0 @@
-import { useFieldTemplate } from 'features/nodes/hooks/useFieldTemplate';
-import { useMemo } from 'react';
-
-export const useFieldTemplateTitle = (nodeId: string, fieldName: string, kind: 'inputs' | 'outputs'): string => {
- const fieldTemplate = useFieldTemplate(nodeId, fieldName, kind);
- const fieldTemplateTitle = useMemo(() => fieldTemplate.title, [fieldTemplate]);
- return fieldTemplateTitle;
-};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldConnectionState.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldConnectionState.ts
new file mode 100644
index 0000000000..9690847858
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldConnectionState.ts
@@ -0,0 +1,35 @@
+import { useStore } from '@nanostores/react';
+import { useAppSelector } from 'app/store/storeHooks';
+import { $edgePendingUpdate, $pendingConnection, $templates } from 'features/nodes/store/nodesSlice';
+import { makeConnectionErrorSelector } from 'features/nodes/store/util/makeConnectionErrorSelector';
+import { useMemo } from 'react';
+
+export const useInputFieldConnectionState = (nodeId: string, fieldName: string) => {
+ const pendingConnection = useStore($pendingConnection);
+ const templates = useStore($templates);
+ const edgePendingUpdate = useStore($edgePendingUpdate);
+
+ const selectValidationResult = useMemo(
+ () => makeConnectionErrorSelector(templates, nodeId, fieldName, 'target'),
+ [templates, nodeId, fieldName]
+ );
+
+ const isConnectionInProgress = useMemo(() => Boolean(pendingConnection), [pendingConnection]);
+ const isConnectionStartField = useMemo(() => {
+ if (!pendingConnection) {
+ return false;
+ }
+ return (
+ pendingConnection.nodeId === nodeId &&
+ pendingConnection.handleId === fieldName &&
+ pendingConnection.fieldTemplate.fieldKind === 'input'
+ );
+ }, [fieldName, nodeId, pendingConnection]);
+ const validationResult = useAppSelector((s) => selectValidationResult(s, pendingConnection, edgePendingUpdate));
+
+ return {
+ isConnectionInProgress,
+ isConnectionStartField,
+ validationResult,
+ };
+};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldDefaultValue.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldDefaultValue.ts
similarity index 64%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldDefaultValue.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldDefaultValue.ts
index ea1d8f67d5..0f1f2aa722 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldDefaultValue.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldDefaultValue.ts
@@ -1,15 +1,15 @@
import { useAppDispatch } from 'app/store/storeHooks';
-import { useFieldInputTemplate } from 'features/nodes/hooks/useFieldInputTemplate';
-import { useFieldValue } from 'features/nodes/hooks/useFieldValue';
+import { useInputFieldValue } from 'features/nodes/hooks/useInputFieldValue';
+import { useInputFieldTemplate } from 'features/nodes/hooks/useInputFieldTemplate';
import { fieldValueReset } from 'features/nodes/store/nodesSlice';
import { isEqual } from 'lodash-es';
import { useCallback, useMemo } from 'react';
-export const useFieldDefaultValue = (nodeId: string, fieldName: string) => {
+export const useInputFieldDefaultValue = (nodeId: string, fieldName: string) => {
const dispatch = useAppDispatch();
- const value = useFieldValue(nodeId, fieldName);
- const fieldTemplate = useFieldInputTemplate(nodeId, fieldName);
+ const value = useInputFieldValue(nodeId, fieldName);
+ const fieldTemplate = useInputFieldTemplate(nodeId, fieldName);
const isValueChanged = useMemo(() => {
return !isEqual(value, fieldTemplate.default);
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInitialLinearViewValue.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldInitialLinearViewValue.ts
similarity index 83%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldInitialLinearViewValue.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldInitialLinearViewValue.ts
index 61a4460d08..e31dacfa26 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInitialLinearViewValue.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldInitialLinearViewValue.ts
@@ -1,12 +1,12 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
-import { useFieldValue } from 'features/nodes/hooks/useFieldValue';
+import { useInputFieldValue } from 'features/nodes/hooks/useInputFieldValue';
import { fieldValueReset } from 'features/nodes/store/nodesSlice';
import { selectWorkflowSlice } from 'features/nodes/store/workflowSlice';
import { isEqual } from 'lodash-es';
import { useCallback, useMemo } from 'react';
-export const useFieldInitialLinearViewValue = (nodeId: string, fieldName: string) => {
+export const useInputFieldInitialLinearViewValue = (nodeId: string, fieldName: string) => {
const dispatch = useAppDispatch();
const selectInitialLinearViewValue = useMemo(
() =>
@@ -18,7 +18,7 @@ export const useFieldInitialLinearViewValue = (nodeId: string, fieldName: string
[nodeId, fieldName]
);
const initialLinearViewValue = useAppSelector(selectInitialLinearViewValue);
- const value = useFieldValue(nodeId, fieldName);
+ const value = useInputFieldValue(nodeId, fieldName);
const isValueChanged = useMemo(() => !isEqual(value, initialLinearViewValue), [value, initialLinearViewValue]);
const resetToInitialLinearViewValue = useCallback(() => {
dispatch(fieldValueReset({ nodeId, fieldName, value: initialLinearViewValue }));
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputInstance.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldInstance.ts
similarity index 55%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldInputInstance.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldInstance.ts
index 37bc108a95..470832f7dd 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputInstance.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldInstance.ts
@@ -3,17 +3,20 @@ import { useAppSelector } from 'app/store/storeHooks';
import { selectFieldInputInstance, selectNodesSlice } from 'features/nodes/store/selectors';
import type { FieldInputInstance } from 'features/nodes/types/field';
import { useMemo } from 'react';
+import { assert } from 'tsafe';
-export const useFieldInputInstance = (nodeId: string, fieldName: string): FieldInputInstance | null => {
+export const useInputFieldInstance = (nodeId: string, fieldName: string): FieldInputInstance => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
- return selectFieldInputInstance(nodes, nodeId, fieldName);
+ const instance = selectFieldInputInstance(nodes, nodeId, fieldName);
+ assert(instance, `Instance for input field ${fieldName} not found`);
+ return instance;
}),
[fieldName, nodeId]
);
- const fieldData = useAppSelector(selector);
+ const instance = useAppSelector(selector);
- return fieldData;
+ return instance;
};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputInstanceExists.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldInstanceExists.ts
similarity index 90%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldInputInstanceExists.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldInstanceExists.ts
index 1e4c577d54..bc9702705c 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputInstanceExists.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldInstanceExists.ts
@@ -3,7 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { selectInvocationNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
-export const useFieldInputInstanceExists = (nodeId: string, fieldName: string) => {
+export const useInputFieldInstanceExists = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodesSlice) => {
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsConnected.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsConnected.ts
new file mode 100644
index 0000000000..bda4622db8
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsConnected.ts
@@ -0,0 +1,21 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppSelector } from 'app/store/storeHooks';
+import { selectNodesSlice } from 'features/nodes/store/selectors';
+import { useMemo } from 'react';
+
+export const useInputFieldIsConnected = (nodeId: string, fieldName: string) => {
+ const selector = useMemo(
+ () =>
+ createSelector(selectNodesSlice, (nodes) => {
+ const firstConnectedEdge = nodes.edges.find((edge) => {
+ return edge.target === nodeId && edge.targetHandle === fieldName;
+ });
+ return firstConnectedEdge !== undefined;
+ }),
+ [fieldName, nodeId]
+ );
+
+ const isConnected = useAppSelector(selector);
+
+ return isConnected;
+};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldIsExposed.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsExposed.ts
similarity index 87%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldIsExposed.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsExposed.ts
index 22bb6669c8..4245cdde71 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldIsExposed.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsExposed.ts
@@ -3,7 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { selectWorkflowSlice } from 'features/nodes/store/workflowSlice';
import { useMemo } from 'react';
-export const useFieldIsExposed = (nodeId: string, fieldName: string) => {
+export const useInputFieldIsExposed = (nodeId: string, fieldName: string) => {
const selectIsExposed = useMemo(
() =>
createSelector(selectWorkflowSlice, (workflow) => {
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldIsInvalid.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsInvalid.ts
similarity index 81%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldIsInvalid.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsInvalid.ts
index cf26b623c7..a6e52260e1 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldIsInvalid.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldIsInvalid.ts
@@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
-import { useConnectionState } from 'features/nodes/hooks/useConnectionState';
-import { useFieldInputTemplate } from 'features/nodes/hooks/useFieldInputTemplate';
+import { useInputFieldIsConnected } from 'features/nodes/hooks/useInputFieldIsConnected';
+import { useInputFieldTemplate } from 'features/nodes/hooks/useInputFieldTemplate';
import { selectFieldInputInstance, selectNodesSlice } from 'features/nodes/store/selectors';
import {
isFloatFieldCollectionInputInstance,
@@ -20,9 +20,9 @@ import {
} from 'features/nodes/types/fieldValidators';
import { useMemo } from 'react';
-export const useFieldIsInvalid = (nodeId: string, fieldName: string) => {
- const template = useFieldInputTemplate(nodeId, fieldName);
- const connectionState = useConnectionState(nodeId, fieldName, 'inputs');
+export const useInputFieldIsInvalid = (nodeId: string, fieldName: string) => {
+ const template = useInputFieldTemplate(nodeId, fieldName);
+ const isConnected = useInputFieldIsConnected(nodeId, fieldName);
const selectIsInvalid = useMemo(() => {
return createSelector(selectNodesSlice, (nodes) => {
@@ -35,11 +35,11 @@ export const useFieldIsInvalid = (nodeId: string, fieldName: string) => {
// 'connection' input fields have no data validation - only connection validation
if (template.input === 'connection') {
- return template.required && !connectionState.isConnected;
+ return template.required && !isConnected;
}
// 'any' input fields are valid if they are connected
- if (template.input === 'any' && connectionState.isConnected) {
+ if (template.input === 'any' && isConnected) {
return false;
}
@@ -77,7 +77,7 @@ export const useFieldIsInvalid = (nodeId: string, fieldName: string) => {
// Field looks OK
return false;
});
- }, [connectionState.isConnected, fieldName, nodeId, template]);
+ }, [nodeId, fieldName, template, isConnected]);
const isInvalid = useAppSelector(selectIsInvalid);
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldLabel.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldLabel.ts
similarity index 84%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldLabel.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldLabel.ts
index d7a7fe48f2..5719709007 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldLabel.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldLabel.ts
@@ -3,7 +3,7 @@ import { useAppSelector } from 'app/store/storeHooks';
import { selectFieldInputInstance, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
-export const useFieldLabel = (nodeId: string, fieldName: string): string | null => {
+export const useInputFieldLabel = (nodeId: string, fieldName: string): string | null => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodes) => {
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldLinearViewConfig.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldLinearViewConfig.ts
similarity index 88%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldLinearViewConfig.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldLinearViewConfig.ts
index f3b4397fc7..a5703fa11e 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldLinearViewConfig.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldLinearViewConfig.ts
@@ -4,7 +4,7 @@ import { selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
-export const useFieldLinearViewConfig = (nodeId: string, fieldName: string) => {
+export const useInputFieldLinearViewConfig = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodes) => {
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputName.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldName.ts
similarity index 93%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldInputName.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldName.ts
index 86165094ae..573edd89f9 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputName.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldName.ts
@@ -5,7 +5,7 @@ import { $templates } from 'features/nodes/store/nodesSlice';
import { selectInvocationNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
-export const useFieldInputName = (nodeId: string, fieldName: string) => {
+export const useInputFieldName = (nodeId: string, fieldName: string) => {
const templates = useStore($templates);
const selector = useMemo(
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldNames.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldNamesByStatus.ts
similarity index 95%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldNames.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldNamesByStatus.ts
index 19849fb296..4dba5a776a 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldNames.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldNamesByStatus.ts
@@ -20,7 +20,7 @@ const isAnyOrDirectInputField = (field: FieldInputTemplate) => {
);
};
-export const useFieldNames = (nodeId: string) => {
+export const useInputFieldNamesByStatus = (nodeId: string) => {
const template = useNodeTemplate(nodeId);
const node = useNodeData(nodeId);
const fieldNames = useMemo(() => {
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldNotes.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldNotes.ts
similarity index 89%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldNotes.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldNotes.ts
index 15a4021b3f..80ff92ebf4 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldNotes.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldNotes.ts
@@ -4,7 +4,7 @@ import { selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
-export const useFieldNotes = (nodeId: string, fieldName: string) => {
+export const useInputFieldNotes = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createSelector(selectNodesSlice, (nodes) => {
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputTemplate.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplate.ts
similarity index 89%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldInputTemplate.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplate.ts
index f1ce8a2883..34afde1e49 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputTemplate.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplate.ts
@@ -3,7 +3,7 @@ import type { FieldInputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react';
import { assert } from 'tsafe';
-export const useFieldInputTemplate = (nodeId: string, fieldName: string): FieldInputTemplate => {
+export const useInputFieldTemplate = (nodeId: string, fieldName: string): FieldInputTemplate => {
const template = useNodeTemplate(nodeId);
const fieldTemplate = useMemo(() => {
const _fieldTemplate = template.inputs[fieldName];
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputTemplateExists.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplateExists.ts
similarity index 92%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldInputTemplateExists.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplateExists.ts
index 75dd3f7a5e..b14fa1d6e4 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldInputTemplateExists.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplateExists.ts
@@ -5,7 +5,7 @@ import { $templates } from 'features/nodes/store/nodesSlice';
import { selectInvocationNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
-export const useFieldInputTemplateExists = (nodeId: string, fieldName: string) => {
+export const useInputFieldTemplateExists = (nodeId: string, fieldName: string) => {
const templates = useStore($templates);
const selector = useMemo(
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplateTitle.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplateTitle.ts
new file mode 100644
index 0000000000..319c219faf
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldTemplateTitle.ts
@@ -0,0 +1,15 @@
+import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
+import { useMemo } from 'react';
+import { assert } from 'tsafe';
+
+export const useInputFieldTemplateTitle = (nodeId: string, fieldName: string): string => {
+ const template = useNodeTemplate(nodeId);
+
+ const title = useMemo(() => {
+ const fieldTemplate = template.inputs[fieldName];
+ assert(fieldTemplate, `Template for input field ${fieldName} not found.`);
+ return fieldTemplate.title;
+ }, [fieldName, template.inputs]);
+
+ return title;
+};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldValue.ts b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldValue.ts
similarity index 90%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldValue.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useInputFieldValue.ts
index 7dfe27bcc5..d1b83b2217 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldValue.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useInputFieldValue.ts
@@ -4,7 +4,7 @@ import { selectNodesSlice } from 'features/nodes/store/selectors';
import { isInvocationNode } from 'features/nodes/types/invocation';
import { useMemo } from 'react';
-export const useFieldValue = (nodeId: string, fieldName: string) => {
+export const useInputFieldValue = (nodeId: string, fieldName: string) => {
const selector = useMemo(
() =>
createMemoizedSelector(selectNodesSlice, (nodes) => {
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useCopyPaste.ts b/invokeai/frontend/web/src/features/nodes/hooks/useNodeCopyPaste.ts
similarity index 99%
rename from invokeai/frontend/web/src/features/nodes/hooks/useCopyPaste.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useNodeCopyPaste.ts
index 3f561eafca..42799f09b8 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useCopyPaste.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useNodeCopyPaste.ts
@@ -166,6 +166,6 @@ const pasteSelectionWithEdges = () => {
const api = { copySelection, pasteSelection, pasteSelectionWithEdges };
-export const useCopyPaste = () => {
+export const useNodeCopyPaste = () => {
return api;
};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useExecutionState.ts b/invokeai/frontend/web/src/features/nodes/hooks/useNodeExecutionState.ts
similarity index 97%
rename from invokeai/frontend/web/src/features/nodes/hooks/useExecutionState.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useNodeExecutionState.ts
index a8755e67c4..30ea97a959 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useExecutionState.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useNodeExecutionState.ts
@@ -19,7 +19,7 @@ const initialNodeExecutionState: Omit = {
outputs: [],
};
-export const useExecutionState = (nodeId?: string) => {
+export const useNodeExecutionState = (nodeId?: string) => {
const executionStates = useStore($nodeExecutionStates, nodeId ? { keys: [nodeId] } : undefined);
const executionState = useMemo(() => (nodeId ? executionStates[nodeId] : undefined), [executionStates, nodeId]);
return executionState;
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useHasImageOutput.ts b/invokeai/frontend/web/src/features/nodes/hooks/useNodeHasImageOutput.ts
similarity index 89%
rename from invokeai/frontend/web/src/features/nodes/hooks/useHasImageOutput.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useNodeHasImageOutput.ts
index 1078b18cc6..5855f5542b 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useHasImageOutput.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useNodeHasImageOutput.ts
@@ -2,7 +2,7 @@ import { useNodeTemplate } from 'features/nodes/hooks/useNodeTemplate';
import { some } from 'lodash-es';
import { useMemo } from 'react';
-export const useHasImageOutput = (nodeId: string): boolean => {
+export const useNodeHasImageOutput = (nodeId: string): boolean => {
const template = useNodeTemplate(nodeId);
const hasImageOutput = useMemo(
() =>
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useNodeIsInvocationNode.ts b/invokeai/frontend/web/src/features/nodes/hooks/useNodeIsInvocationNode.ts
new file mode 100644
index 0000000000..c3acbf5763
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useNodeIsInvocationNode.ts
@@ -0,0 +1,20 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppSelector } from 'app/store/storeHooks';
+import { selectNode, selectNodesSlice } from 'features/nodes/store/selectors';
+import { isInvocationNode as _isInvocationNode } from 'features/nodes/types/invocation';
+import { useMemo } from 'react';
+
+export const useNodeIsInvocationNode = (nodeId: string): boolean => {
+ const selector = useMemo(
+ () =>
+ createSelector(selectNodesSlice, (nodes) => {
+ const node = selectNode(nodes, nodeId);
+ return _isInvocationNode(node);
+ }),
+ [nodeId]
+ );
+
+ const isInvocationNode = useAppSelector(selector);
+
+ return isInvocationNode;
+};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useNodeNotes.ts b/invokeai/frontend/web/src/features/nodes/hooks/useNodeNotes.ts
new file mode 100644
index 0000000000..20780aee10
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useNodeNotes.ts
@@ -0,0 +1,22 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppSelector } from 'app/store/storeHooks';
+import { selectNode, selectNodesSlice } from 'features/nodes/store/selectors';
+import { isInvocationNode } from 'features/nodes/types/invocation';
+import { useMemo } from 'react';
+import { assert } from 'tsafe';
+
+export const useInvocationNodeNotes = (nodeId: string): string => {
+ const selector = useMemo(
+ () =>
+ createSelector(selectNodesSlice, (nodes) => {
+ const node = selectNode(nodes, nodeId);
+ assert(isInvocationNode(node), `Node with id ${nodeId} is not an invocation node`);
+ return node.data.notes;
+ }),
+ [nodeId]
+ );
+
+ const notes = useAppSelector(selector);
+
+ return notes;
+};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldConnectionState.ts b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldConnectionState.ts
new file mode 100644
index 0000000000..4bd6606467
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldConnectionState.ts
@@ -0,0 +1,35 @@
+import { useStore } from '@nanostores/react';
+import { useAppSelector } from 'app/store/storeHooks';
+import { $edgePendingUpdate, $pendingConnection, $templates } from 'features/nodes/store/nodesSlice';
+import { makeConnectionErrorSelector } from 'features/nodes/store/util/makeConnectionErrorSelector';
+import { useMemo } from 'react';
+
+export const useOutputFieldConnectionState = (nodeId: string, fieldName: string) => {
+ const pendingConnection = useStore($pendingConnection);
+ const templates = useStore($templates);
+ const edgePendingUpdate = useStore($edgePendingUpdate);
+
+ const selectValidationResult = useMemo(
+ () => makeConnectionErrorSelector(templates, nodeId, fieldName, 'source'),
+ [templates, nodeId, fieldName]
+ );
+
+ const isConnectionInProgress = useMemo(() => Boolean(pendingConnection), [pendingConnection]);
+ const isConnectionStartField = useMemo(() => {
+ if (!pendingConnection) {
+ return false;
+ }
+ return (
+ pendingConnection.nodeId === nodeId &&
+ pendingConnection.handleId === fieldName &&
+ pendingConnection.fieldTemplate.fieldKind === 'output'
+ );
+ }, [fieldName, nodeId, pendingConnection]);
+ const validationResult = useAppSelector((s) => selectValidationResult(s, pendingConnection, edgePendingUpdate));
+
+ return {
+ isConnectionInProgress,
+ isConnectionStartField,
+ validationResult,
+ };
+};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldIsConnected.ts b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldIsConnected.ts
new file mode 100644
index 0000000000..c6c5f214ab
--- /dev/null
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldIsConnected.ts
@@ -0,0 +1,21 @@
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppSelector } from 'app/store/storeHooks';
+import { selectNodesSlice } from 'features/nodes/store/selectors';
+import { useMemo } from 'react';
+
+export const useOutputFieldIsConnected = (nodeId: string, fieldName: string) => {
+ const selector = useMemo(
+ () =>
+ createSelector(selectNodesSlice, (nodes) => {
+ const firstConnectedEdge = nodes.edges.find((edge) => {
+ return edge.source === nodeId && edge.sourceHandle === fieldName;
+ });
+ return firstConnectedEdge !== undefined;
+ }),
+ [fieldName, nodeId]
+ );
+
+ const isConnected = useAppSelector(selector);
+
+ return isConnected;
+};
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldOutputName.ts b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldName.ts
similarity index 93%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldOutputName.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldName.ts
index 5b151dce28..8b237484e2 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldOutputName.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldName.ts
@@ -5,7 +5,7 @@ import { $templates } from 'features/nodes/store/nodesSlice';
import { selectInvocationNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
-export const useFieldOutputName = (nodeId: string, fieldName: string) => {
+export const useOutputFieldName = (nodeId: string, fieldName: string) => {
const templates = useStore($templates);
const selector = useMemo(
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldOutputTemplate.ts b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldTemplate.ts
similarity index 89%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldOutputTemplate.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldTemplate.ts
index 0b55857f7a..d30b4a8bb5 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldOutputTemplate.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldTemplate.ts
@@ -3,7 +3,7 @@ import type { FieldOutputTemplate } from 'features/nodes/types/field';
import { useMemo } from 'react';
import { assert } from 'tsafe';
-export const useFieldOutputTemplate = (nodeId: string, fieldName: string): FieldOutputTemplate => {
+export const useOutputFieldTemplate = (nodeId: string, fieldName: string): FieldOutputTemplate => {
const template = useNodeTemplate(nodeId);
const fieldTemplate = useMemo(() => {
const _fieldTemplate = template.outputs[fieldName];
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useFieldOutputTemplateExists.ts b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldTemplateExists.ts
similarity index 92%
rename from invokeai/frontend/web/src/features/nodes/hooks/useFieldOutputTemplateExists.ts
rename to invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldTemplateExists.ts
index 336eddc12d..37962efb46 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useFieldOutputTemplateExists.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useOutputFieldTemplateExists.ts
@@ -5,7 +5,7 @@ import { $templates } from 'features/nodes/store/nodesSlice';
import { selectInvocationNode, selectNodesSlice } from 'features/nodes/store/selectors';
import { useMemo } from 'react';
-export const useFieldOutputTemplateExists = (nodeId: string, fieldName: string) => {
+export const useOutputFieldTemplateExists = (nodeId: string, fieldName: string) => {
const templates = useStore($templates);
const selector = useMemo(
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/usePrettyFieldType.ts b/invokeai/frontend/web/src/features/nodes/hooks/usePrettyFieldType.ts
index 7f531c3dba..892f0d73e1 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/usePrettyFieldType.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/usePrettyFieldType.ts
@@ -2,13 +2,10 @@ import { type FieldType, isCollection, isSingleOrCollection } from 'features/nod
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
-export const useFieldTypeName = (fieldType?: FieldType): string => {
+export const useFieldTypeName = (fieldType: FieldType): string => {
const { t } = useTranslation();
const name = useMemo(() => {
- if (!fieldType) {
- return '';
- }
const { name } = fieldType;
if (isCollection(fieldType)) {
return t('nodes.collectionFieldType', { name });
diff --git a/invokeai/frontend/web/src/features/nodes/hooks/useWithFooter.ts b/invokeai/frontend/web/src/features/nodes/hooks/useWithFooter.ts
index 6e00d374f6..e118f8901d 100644
--- a/invokeai/frontend/web/src/features/nodes/hooks/useWithFooter.ts
+++ b/invokeai/frontend/web/src/features/nodes/hooks/useWithFooter.ts
@@ -1,10 +1,10 @@
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
import { useMemo } from 'react';
-import { useHasImageOutput } from './useHasImageOutput';
+import { useNodeHasImageOutput } from './useNodeHasImageOutput';
export const useWithFooter = (nodeId: string) => {
- const hasImageOutput = useHasImageOutput(nodeId);
+ const hasImageOutput = useNodeHasImageOutput(nodeId);
const isCacheEnabled = useFeatureStatus('invocationCache');
const withFooter = useMemo(() => hasImageOutput || isCacheEnabled, [hasImageOutput, isCacheEnabled]);
return withFooter;
diff --git a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
index 70b127aa41..475e6b7b07 100644
--- a/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
+++ b/invokeai/frontend/web/src/services/events/onInvocationComplete.tsx
@@ -3,7 +3,7 @@ import type { AppDispatch, RootState } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
import { stagingAreaImageStaged } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { boardIdSelected, galleryViewChanged, imageSelected, offsetChanged } from 'features/gallery/store/gallerySlice';
-import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
+import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { zNodeStatus } from 'features/nodes/types/invocation';
import { CANVAS_OUTPUT_PREFIX } from 'features/nodes/util/graph/graphBuilderUtils';
import { boardsApi } from 'services/api/endpoints/boards';
diff --git a/invokeai/frontend/web/src/services/events/setEventListeners.tsx b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
index 4a0fa018c7..22fcd0dcbb 100644
--- a/invokeai/frontend/web/src/services/events/setEventListeners.tsx
+++ b/invokeai/frontend/web/src/services/events/setEventListeners.tsx
@@ -6,7 +6,7 @@ import { $bulkDownloadId } from 'app/store/nanostores/bulkDownloadId';
import { $queueId } from 'app/store/nanostores/queueId';
import type { AppStore } from 'app/store/store';
import { deepClone } from 'common/util/deepClone';
-import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useExecutionState';
+import { $nodeExecutionStates, upsertExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
import { zNodeStatus } from 'features/nodes/types/invocation';
import ErrorToastDescription, { getTitle } from 'features/toast/ErrorToastDescription';
import { toast } from 'features/toast/toast';