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/FieldLinearViewToggle.tsx new file mode 100644 index 0000000000..0dac7996eb --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/FieldLinearViewToggle.tsx @@ -0,0 +1,74 @@ +import { IconButton } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { + selectWorkflowSlice, + workflowExposedFieldAdded, + workflowExposedFieldRemoved, +} from 'features/nodes/store/workflowSlice'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiMinusBold, PiPlusBold } from 'react-icons/pi'; + +type Props = { + nodeId: string; + fieldName: string; + isHovered: boolean; +}; + +const FieldLinearViewToggle = ({ nodeId, fieldName, isHovered }: Props) => { + const dispatch = useAppDispatch(); + const { t } = useTranslation(); + + const selectIsExposed = useMemo( + () => + createSelector(selectWorkflowSlice, (workflow) => { + return Boolean(workflow.exposedFields.find((f) => f.nodeId === nodeId && f.fieldName === fieldName)); + }), + [fieldName, nodeId] + ); + + const isExposed = useAppSelector(selectIsExposed); + + const handleExposeField = useCallback(() => { + dispatch(workflowExposedFieldAdded({ nodeId, fieldName })); + }, [dispatch, fieldName, nodeId]); + + const handleUnexposeField = useCallback(() => { + dispatch(workflowExposedFieldRemoved({ nodeId, fieldName })); + }, [dispatch, fieldName, nodeId]); + + const ToggleButton = useMemo(() => { + if (!isHovered) { + return null; + } else if (!isExposed) { + return ( + } + onClick={handleExposeField} + pointerEvents="auto" + size="xs" + /> + ); + } else if (isExposed) { + return ( + } + onClick={handleUnexposeField} + pointerEvents="auto" + size="xs" + /> + ); + } + }, [isHovered, handleExposeField, handleUnexposeField, isExposed, t]); + + return ToggleButton; +}; + +export default memo(FieldLinearViewToggle); diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx index b33b65dfb5..bb45f4e3e3 100644 --- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputField.tsx @@ -4,12 +4,13 @@ import { useDoesInputHaveValue } from 'features/nodes/hooks/useDoesInputHaveValu import { useFieldInputInstance } from 'features/nodes/hooks/useFieldInputInstance'; import { useFieldInputTemplate } from 'features/nodes/hooks/useFieldInputTemplate'; import type { PropsWithChildren } from 'react'; -import { memo, useMemo } from 'react'; +import { memo, useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import EditableFieldTitle from './EditableFieldTitle'; import FieldContextMenu from './FieldContextMenu'; import FieldHandle from './FieldHandle'; +import FieldLinearViewToggle from './FieldLinearViewToggle'; import InputFieldRenderer from './InputFieldRenderer'; interface Props { @@ -22,6 +23,7 @@ const InputField = ({ nodeId, fieldName }: Props) => { const fieldTemplate = useFieldInputTemplate(nodeId, fieldName); const fieldInstance = useFieldInputInstance(nodeId, fieldName); const doesFieldHaveValue = useDoesInputHaveValue(nodeId, fieldName); + const [isHovered, setIsHovered] = useState(false); const { isConnected, isConnectionInProgress, isConnectionStartField, connectionError, shouldDim } = useConnectionState({ nodeId, fieldName, kind: 'input' }); @@ -46,6 +48,14 @@ const InputField = ({ nodeId, fieldName }: Props) => { return false; }, [fieldTemplate, isConnected, doesFieldHaveValue]); + const handleMouseOver = useCallback(() => { + setIsHovered(true); + }, []); + + const handleMouseOut = useCallback(() => { + setIsHovered(false); + }, []); + if (!fieldTemplate || !fieldInstance) { return ( @@ -87,17 +97,20 @@ const InputField = ({ nodeId, fieldName }: Props) => { return ( - + {(ref) => ( - + + + + )}