feat(ui): add visual indicator when input field is added to form

This commit is contained in:
psychedelicious
2025-07-30 21:24:12 +10:00
parent 3835fd2f72
commit 7a3c2332dd
4 changed files with 39 additions and 3 deletions

View File

@@ -3,7 +3,7 @@ import type { Selector } from '@reduxjs/toolkit';
import { createSelector } from '@reduxjs/toolkit';
import type { RootState } from 'app/store/store';
import { $templates } from 'features/nodes/store/nodesSlice';
import { selectEdges, selectNodes } from 'features/nodes/store/selectors';
import { selectEdges, selectNodeFieldElements, selectNodes } from 'features/nodes/store/selectors';
import type { InvocationNode, InvocationTemplate } from 'features/nodes/types/invocation';
import { getNeedsUpdate } from 'features/nodes/util/node/nodeUpdate';
import type { PropsWithChildren } from 'react';
@@ -27,6 +27,7 @@ type InvocationNodeContextValue = {
buildSelectOutputFieldTemplateSafe: (
fieldName: string
) => Selector<RootState, InvocationTemplate['outputs'][string] | null>;
buildSelectIsInputFieldAddedToForm: (fieldName: string) => Selector<RootState, boolean>;
selectNodeOrThrow: Selector<RootState, InvocationNode>;
selectNodeDataOrThrow: Selector<RootState, InvocationNode['data']>;
@@ -181,6 +182,15 @@ export const InvocationNodeContextProvider = memo(({ nodeId, children }: PropsWi
})
);
const buildSelectIsInputFieldAddedToForm = (fieldName: string) =>
getSelectorFromCache(cache, `buildSelectIsInputFieldAddedToForm-${fieldName}`, () =>
createSelector(selectNodeFieldElements, (nodeFieldElements) => {
return nodeFieldElements.some(
(el) => el.data.fieldIdentifier.nodeId === nodeId && el.data.fieldIdentifier.fieldName === fieldName
);
})
);
const selectNodeNeedsUpdate = getSelectorFromCache(cache, 'selectNodeNeedsUpdate', () =>
createSelector([selectNodeDataSafe, selectNodeTemplateSafe], (data, template) => {
if (!data || !template) {
@@ -202,6 +212,7 @@ export const InvocationNodeContextProvider = memo(({ nodeId, children }: PropsWi
buildSelectInputFieldSafe,
buildSelectInputFieldTemplateSafe,
buildSelectOutputFieldTemplateSafe,
buildSelectIsInputFieldAddedToForm,
selectNodeOrThrow,
selectNodeDataOrThrow,

View File

@@ -1,5 +1,5 @@
import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Input, Text, Tooltip } from '@invoke-ai/ui-library';
import { Icon, Input, Text, Tooltip } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useEditable } from 'common/hooks/useEditable';
import { InputFieldTooltipContent } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldTooltipContent';
@@ -8,6 +8,7 @@ import {
useIsConnectionInProgress,
useIsConnectionStartField,
} from 'features/nodes/hooks/useFieldConnectionState';
import { useInputFieldIsAddedToForm } from 'features/nodes/hooks/useInputFieldIsAddedToForm';
import { useInputFieldIsConnected } from 'features/nodes/hooks/useInputFieldIsConnected';
import { useInputFieldTemplateTitleOrThrow } from 'features/nodes/hooks/useInputFieldTemplateTitleOrThrow';
import { useInputFieldUserTitleSafe } from 'features/nodes/hooks/useInputFieldUserTitleSafe';
@@ -16,9 +17,13 @@ import { HANDLE_TOOLTIP_OPEN_DELAY, NO_FIT_ON_DOUBLE_CLICK_CLASS } from 'feature
import type { MouseEvent } from 'react';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
import { PiLinkBold } from 'react-icons/pi';
const labelSx: SystemStyleObject = {
p: 0,
display: 'flex',
gap: 1,
alignItems: 'center',
fontWeight: 'semibold',
textAlign: 'left',
color: 'base.300',
@@ -28,6 +33,9 @@ const labelSx: SystemStyleObject = {
'&[data-is-invalid="true"]': {
color: 'error.300',
},
'&[data-is-added-to-form="true"]': {
color: 'blue.300',
},
'&[data-is-disabled="true"]': {
opacity: 0.5,
},
@@ -47,6 +55,7 @@ export const InputFieldTitle = memo((props: Props) => {
const fieldTemplateTitle = useInputFieldTemplateTitleOrThrow(fieldName);
const { t } = useTranslation();
const isConnected = useInputFieldIsConnected(fieldName);
const isAddedToForm = useInputFieldIsAddedToForm(fieldName);
const isConnectionStartField = useIsConnectionStartField(nodeId, fieldName, 'target');
const isConnectionInProgress = useIsConnectionInProgress();
const connectionError = useConnectionErrorTKey(nodeId, fieldName, 'target');
@@ -93,9 +102,11 @@ export const InputFieldTitle = memo((props: Props) => {
noOfLines={1}
data-is-invalid={isInvalid}
data-is-disabled={isDisabled}
data-is-added-to-form={isAddedToForm}
onDoubleClick={onDoubleClick}
>
{editable.value}
{isAddedToForm && <Icon as={PiLinkBold} color="blue.200" ml={1} />}
</Text>
</Tooltip>
);

View File

@@ -2,6 +2,7 @@ import { Flex, ListItem, Text, UnorderedList } from '@invoke-ai/ui-library';
import { startCase } from 'es-toolkit/compat';
import { useInputFieldErrors } from 'features/nodes/hooks/useInputFieldErrors';
import { useInputFieldInstance } from 'features/nodes/hooks/useInputFieldInstance';
import { useInputFieldIsAddedToForm } from 'features/nodes/hooks/useInputFieldIsAddedToForm';
import { useInputFieldTemplateOrThrow } from 'features/nodes/hooks/useInputFieldTemplateOrThrow';
import { useFieldTypeName } from 'features/nodes/hooks/usePrettyFieldType';
import { memo, useMemo } from 'react';
@@ -19,6 +20,7 @@ export const InputFieldTooltipContent = memo(({ fieldName }: Props) => {
const fieldTemplate = useInputFieldTemplateOrThrow(fieldName);
const fieldTypeName = useFieldTypeName(fieldTemplate.type);
const fieldErrors = useInputFieldErrors(fieldName);
const isAddedToForm = useInputFieldIsAddedToForm(fieldName);
const fieldTitle = useMemo(() => {
if (fieldInstance.label && fieldTemplate.title) {
@@ -34,7 +36,10 @@ export const InputFieldTooltipContent = memo(({ fieldName }: Props) => {
return (
<Flex flexDir="column">
<Text fontWeight="semibold">{fieldTitle}</Text>
<Text fontWeight="semibold">
{fieldTitle}
{isAddedToForm && ' (added to form)'}
</Text>
<Text opacity={0.7} fontStyle="oblique 5deg">
{fieldTemplate.description}
</Text>

View File

@@ -0,0 +1,9 @@
import { useAppSelector } from 'app/store/storeHooks';
import { useInvocationNodeContext } from 'features/nodes/components/flow/nodes/Invocation/context';
import { useMemo } from 'react';
export const useInputFieldIsAddedToForm = (fieldName: string) => {
const ctx = useInvocationNodeContext();
const selector = useMemo(() => ctx.buildSelectIsInputFieldAddedToForm(fieldName), [ctx, fieldName]);
return useAppSelector(selector);
};