mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): add visual indicator when input field is added to form
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
Reference in New Issue
Block a user