remove input field from form

This commit is contained in:
Attila Cseh
2025-08-22 12:36:28 +02:00
committed by psychedelicious
parent e34ed199c9
commit 2496ac19c4
6 changed files with 71 additions and 42 deletions

View File

@@ -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",

View File

@@ -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 ? <PiMinusBold /> : <PiPlusBold />;
}, [isAddedToRoot]);
const onClick = useCallback(() => {
return isAddedToRoot ? removeNodeFieldFromRoot() : addNodeFieldToRoot();
}, [isAddedToRoot, addNodeFieldToRoot, removeNodeFieldFromRoot]);
return (
<IconButton
variant="ghost"
tooltip={description}
aria-label={description}
icon={icon}
pointerEvents="auto"
size="xs"
onClick={onClick}
/>
);
});
InputFieldAddRemoveFormRoot.displayName = 'InputFieldAddRemoveFormRoot';

View File

@@ -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 (
<IconButton
variant="ghost"
tooltip={t('workflows.builder.addToForm')}
aria-label={t('workflows.builder.addToForm')}
icon={<PiPlusBold />}
pointerEvents="auto"
size="xs"
onClick={addNodeFieldToRoot}
isDisabled={isAddedToRoot}
/>
);
});
InputFieldAddToFormRoot.displayName = 'InputFieldAddToFormRoot';

View File

@@ -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
<Flex className="direct-field-action-buttons">
<InputFieldDescriptionPopover nodeId={nodeId} fieldName={fieldName} />
<InputFieldResetToDefaultValueIconButton nodeId={nodeId} fieldName={fieldName} />
<InputFieldAddToFormRoot nodeId={nodeId} fieldName={fieldName} />
<InputFieldAddRemoveFormRoot nodeId={nodeId} fieldName={fieldName} />
</Flex>
</Flex>
<InputFieldRenderer nodeId={nodeId} fieldName={fieldName} />

View File

@@ -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 };
};

View File

@@ -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
)
);