From 316fc27d4ecc5090c6bfcbe175b1f33221c55fdf Mon Sep 17 00:00:00 2001 From: Attila Cseh Date: Fri, 22 Aug 2025 12:36:28 +0200 Subject: [PATCH] remove input field from form --- invokeai/frontend/web/public/locales/en.json | 1 + .../fields/InputFieldAddRemoveFormRoot.tsx | 41 +++++++++++++++++++ .../fields/InputFieldAddToFormRoot.tsx | 30 -------------- .../fields/InputFieldEditModeNodes.tsx | 4 +- ...root.ts => use-add-remove-form-element.ts} | 28 +++++++++---- .../web/src/features/nodes/store/selectors.ts | 9 ++-- 6 files changed, 71 insertions(+), 42 deletions(-) create mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddRemoveFormRoot.tsx delete mode 100644 invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddToFormRoot.tsx rename invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/{use-add-node-field-to-root.ts => use-add-remove-form-element.ts} (51%) diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index a586aaa4aa5..9ede72d7941 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1950,6 +1950,7 @@ "zoomToNode": "Zoom to Node", "nodeFieldTooltip": "To add a node field, click the small plus sign button on the field in the Workflow Editor, or drag the field by its name into the form.", "addToForm": "Add to Form", + "removeFromForm": "Remove from Form", "label": "Label", "showDescription": "Show Description", "showShuffle": "Show Shuffle", diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddRemoveFormRoot.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddRemoveFormRoot.tsx new file mode 100644 index 00000000000..83fb3fa3453 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddRemoveFormRoot.tsx @@ -0,0 +1,41 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { useAddRemoveFormElement } from 'features/nodes/components/sidePanel/builder/use-add-remove-form-element'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiMinusBold, PiPlusBold } from 'react-icons/pi'; + +type Props = { + nodeId: string; + fieldName: string; +}; + +export const InputFieldAddRemoveFormRoot = memo(({ nodeId, fieldName }: Props) => { + const { t } = useTranslation(); + const { isAddedToRoot, addNodeFieldToRoot, removeNodeFieldFromRoot } = useAddRemoveFormElement(nodeId, fieldName); + + const description = useMemo(() => { + return isAddedToRoot ? t('workflows.builder.removeFromForm') : t('workflows.builder.addToForm'); + }, [isAddedToRoot, t]); + + const icon = useMemo(() => { + return isAddedToRoot ? : ; + }, [isAddedToRoot]); + + const onClick = useCallback(() => { + return isAddedToRoot ? removeNodeFieldFromRoot() : addNodeFieldToRoot(); + }, [isAddedToRoot, addNodeFieldToRoot, removeNodeFieldFromRoot]); + + return ( + + ); +}); + +InputFieldAddRemoveFormRoot.displayName = 'InputFieldAddRemoveFormRoot'; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddToFormRoot.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddToFormRoot.tsx deleted file mode 100644 index 7e225a9a669..00000000000 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddToFormRoot.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { IconButton } from '@invoke-ai/ui-library'; -import { useAddNodeFieldToRoot } from 'features/nodes/components/sidePanel/builder/use-add-node-field-to-root'; -import { memo } from 'react'; -import { useTranslation } from 'react-i18next'; -import { PiPlusBold } from 'react-icons/pi'; - -type Props = { - nodeId: string; - fieldName: string; -}; - -export const InputFieldAddToFormRoot = memo(({ nodeId, fieldName }: Props) => { - const { t } = useTranslation(); - const { isAddedToRoot, addNodeFieldToRoot } = useAddNodeFieldToRoot(nodeId, fieldName); - - return ( - } - pointerEvents="auto" - size="xs" - onClick={addNodeFieldToRoot} - isDisabled={isAddedToRoot} - /> - ); -}); - -InputFieldAddToFormRoot.displayName = 'InputFieldAddToFormRoot'; diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeNodes.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeNodes.tsx index 61825e0abd2..1597f4ede16 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeNodes.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldEditModeNodes.tsx @@ -1,6 +1,5 @@ import type { SystemStyleObject } from '@invoke-ai/ui-library'; import { Flex, Spacer } from '@invoke-ai/ui-library'; -import { InputFieldAddToFormRoot } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldAddToFormRoot'; import { InputFieldDescriptionPopover } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldDescriptionPopover'; import { InputFieldHandle } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldHandle'; import { InputFieldResetToDefaultValueIconButton } from 'features/nodes/components/flow/nodes/Invocation/fields/InputFieldResetToDefaultValueIconButton'; @@ -12,6 +11,7 @@ import { NO_DRAG_CLASS } from 'features/nodes/types/constants'; import type { FieldInputTemplate } from 'features/nodes/types/field'; import { memo, useRef } from 'react'; +import { InputFieldAddRemoveFormRoot } from './InputFieldAddRemoveFormRoot'; import { InputFieldRenderer } from './InputFieldRenderer'; import { InputFieldTitle } from './InputFieldTitle'; import { InputFieldWrapper } from './InputFieldWrapper'; @@ -113,7 +113,7 @@ const DirectField = memo(({ nodeId, fieldName, isInvalid, isConnected, fieldTemp - + diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/use-add-node-field-to-root.ts b/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/use-add-remove-form-element.ts similarity index 51% rename from invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/use-add-node-field-to-root.ts rename to invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/use-add-remove-form-element.ts index 0a3057a6356..e7e32345862 100644 --- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/use-add-node-field-to-root.ts +++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/builder/use-add-remove-form-element.ts @@ -1,21 +1,24 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useInputFieldInstance } from 'features/nodes/hooks/useInputFieldInstance'; import { useInputFieldTemplateOrThrow } from 'features/nodes/hooks/useInputFieldTemplateOrThrow'; -import { formElementAdded } from 'features/nodes/store/nodesSlice'; -import { buildSelectWorkflowFormNodeExists, selectFormRootElementId } from 'features/nodes/store/selectors'; +import { formElementAdded, formElementRemoved } from 'features/nodes/store/nodesSlice'; +import { buildSelectWorkflowFormNodeElement, selectFormRootElementId } from 'features/nodes/store/selectors'; import { buildNodeFieldElement } from 'features/nodes/types/workflow'; import { useCallback, useMemo } from 'react'; -export const useAddNodeFieldToRoot = (nodeId: string, fieldName: string) => { +export const useAddRemoveFormElement = (nodeId: string, fieldName: string) => { const dispatch = useAppDispatch(); const rootElementId = useAppSelector(selectFormRootElementId); const fieldTemplate = useInputFieldTemplateOrThrow(fieldName); const field = useInputFieldInstance(fieldName); - const selectWorkflowFormNodeExists = useMemo( - () => buildSelectWorkflowFormNodeExists(nodeId, fieldName), + const selectWorkflowFormNodeElement = useMemo( + () => buildSelectWorkflowFormNodeElement(nodeId, fieldName), [nodeId, fieldName] ); - const isAddedToRoot = useAppSelector(selectWorkflowFormNodeExists); + const workflowFormNodeElement = useAppSelector(selectWorkflowFormNodeElement); + const isAddedToRoot = useMemo(() => { + return !!workflowFormNodeElement; + }, [workflowFormNodeElement]); const addNodeFieldToRoot = useCallback(() => { const element = buildNodeFieldElement(nodeId, fieldName, fieldTemplate.type); @@ -28,5 +31,16 @@ export const useAddNodeFieldToRoot = (nodeId: string, fieldName: string) => { ); }, [nodeId, fieldName, fieldTemplate.type, dispatch, rootElementId, field.value]); - return { isAddedToRoot, addNodeFieldToRoot }; + const removeNodeFieldFromRoot = useCallback(() => { + if (!workflowFormNodeElement) { + return; + } + dispatch( + formElementRemoved({ + id: workflowFormNodeElement.id, + }) + ); + }, [workflowFormNodeElement, dispatch]); + + return { isAddedToRoot, addNodeFieldToRoot, removeNodeFieldFromRoot }; }; diff --git a/invokeai/frontend/web/src/features/nodes/store/selectors.ts b/invokeai/frontend/web/src/features/nodes/store/selectors.ts index 0a1ef2f0ff7..f3c35dc220f 100644 --- a/invokeai/frontend/web/src/features/nodes/store/selectors.ts +++ b/invokeai/frontend/web/src/features/nodes/store/selectors.ts @@ -103,7 +103,10 @@ export const selectWorkflowFormNodeFieldFieldIdentifiersDeduped = createSelector ); export const buildSelectElement = (id: string) => createNodesSelector((workflow) => workflow.form?.elements[id]); -export const buildSelectWorkflowFormNodeExists = (nodeId: string, fieldName: string) => - createSelector(selectWorkflowFormNodeFieldFieldIdentifiersDeduped, (identifiers) => - identifiers.some((identifier) => identifier.nodeId === nodeId && identifier.fieldName === fieldName) +export const buildSelectWorkflowFormNodeElement = (nodeId: string, fieldName: string) => + createSelector(selectNodeFieldElements, (elements) => + elements.find( + (element) => + element.data.fieldIdentifier.nodeId === nodeId && element.data.fieldIdentifier.fieldName === fieldName + ) );