From aa4b56baf201d223dea1e6c1760e55ab74e63e3c Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 21 Sep 2023 14:50:55 +1000 Subject: [PATCH] feat(ui): enable control adapters on image drop - Dropping/uploading an image on control adapter enables it (controlnet & ip adapter) - The image components are always enabled to allow this --- .../listeners/imageDropped.ts | 9 +++++++ .../listeners/imageUploaded.ts | 9 +++++++ .../controlNet/components/ControlNet.tsx | 22 +++++++++++------ .../components/ControlNetImagePreview.tsx | 8 ++----- .../ipAdapter/ParamIPAdapterFeatureToggle.tsx | 13 ++++++---- .../ipAdapter/ParamIPAdapterImage.tsx | 24 ++++++++++--------- .../ipAdapter/ParamIPAdapterModelSelect.tsx | 4 ++++ .../controlNet/store/controlNetSlice.ts | 16 ++++++------- .../ControlNet/ParamControlNetCollapse.tsx | 7 +++--- 9 files changed, 72 insertions(+), 40 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts index e8b4aa9210..d38a20a917 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageDropped.ts @@ -4,7 +4,9 @@ import { parseify } from 'common/util/serialize'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { controlNetImageChanged, + controlNetIsEnabledChanged, ipAdapterImageChanged, + isIPAdapterEnabledChanged, } from 'features/controlNet/store/controlNetSlice'; import { TypesafeDraggableData, @@ -99,6 +101,12 @@ export const addImageDroppedListener = () => { controlNetId, }) ); + dispatch( + controlNetIsEnabledChanged({ + controlNetId, + isEnabled: true, + }) + ); return; } @@ -111,6 +119,7 @@ export const addImageDroppedListener = () => { activeData.payload.imageDTO ) { dispatch(ipAdapterImageChanged(activeData.payload.imageDTO)); + dispatch(isIPAdapterEnabledChanged(true)); return; } diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts index 1c7caaeb2f..b27c922342 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/imageUploaded.ts @@ -3,7 +3,9 @@ import { logger } from 'app/logging/logger'; import { setInitialCanvasImage } from 'features/canvas/store/canvasSlice'; import { controlNetImageChanged, + controlNetIsEnabledChanged, ipAdapterImageChanged, + isIPAdapterEnabledChanged, } from 'features/controlNet/store/controlNetSlice'; import { fieldImageValueChanged } from 'features/nodes/store/nodesSlice'; import { initialImageChanged } from 'features/parameters/store/generationSlice'; @@ -87,6 +89,12 @@ export const addImageUploadedFulfilledListener = () => { if (postUploadAction?.type === 'SET_CONTROLNET_IMAGE') { const { controlNetId } = postUploadAction; + dispatch( + controlNetIsEnabledChanged({ + controlNetId, + isEnabled: true, + }) + ); dispatch( controlNetImageChanged({ controlNetId, @@ -104,6 +112,7 @@ export const addImageUploadedFulfilledListener = () => { if (postUploadAction?.type === 'SET_IP_ADAPTER_IMAGE') { dispatch(ipAdapterImageChanged(imageDTO)); + dispatch(isIPAdapterEnabledChanged(true)); dispatch( addToast({ ...DEFAULT_UPLOADED_TOAST, diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx index 13ceaf4173..3d00359d18 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNet.tsx @@ -1,12 +1,12 @@ import { Box, Flex } from '@chakra-ui/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { memo, useCallback } from 'react'; +import { ChangeEvent, memo, useCallback } from 'react'; import { FaCopy, FaTrash } from 'react-icons/fa'; import { ControlNetConfig, controlNetDuplicated, controlNetRemoved, - controlNetToggled, + controlNetIsEnabledChanged, } from '../store/controlNetSlice'; import ParamControlNetModel from './parameters/ParamControlNetModel'; import ParamControlNetWeight from './parameters/ParamControlNetWeight'; @@ -77,9 +77,17 @@ const ControlNet = (props: ControlNetProps) => { ); }, [controlNetId, dispatch]); - const handleToggleIsEnabled = useCallback(() => { - dispatch(controlNetToggled({ controlNetId })); - }, [controlNetId, dispatch]); + const handleToggleIsEnabled = useCallback( + (e: ChangeEvent) => { + dispatch( + controlNetIsEnabledChanged({ + controlNetId, + isEnabled: e.target.checked, + }) + ); + }, + [controlNetId, dispatch] + ); return ( { sx={{ w: 'full', minW: 0, - opacity: isEnabled ? 1 : 0.5, - pointerEvents: isEnabled ? 'auto' : 'none', + // opacity: isEnabled ? 1 : 0.5, + // pointerEvents: isEnabled ? 'auto' : 'none', transitionProperty: 'common', transitionDuration: '0.1s', }} diff --git a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx index 02c65c1c83..0b1e0dab87 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ControlNetImagePreview.tsx @@ -13,6 +13,7 @@ import { import { setHeight, setWidth } from 'features/parameters/store/generationSlice'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { memo, useCallback, useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; import { FaRulerVertical, FaSave, FaUndo } from 'react-icons/fa'; import { useAddImageToBoardMutation, @@ -26,7 +27,6 @@ import { ControlNetConfig, controlNetImageChanged, } from '../store/controlNetSlice'; -import { useTranslation } from 'react-i18next'; type Props = { controlNet: ControlNetConfig; @@ -52,7 +52,6 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => { controlImage: controlImageName, processedControlImage: processedControlImageName, processorType, - isEnabled, controlNetId, } = controlNet; @@ -172,15 +171,13 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => { h: isSmall ? 28 : 366, // magic no touch alignItems: 'center', justifyContent: 'center', - pointerEvents: isEnabled ? 'auto' : 'none', - opacity: isEnabled ? 1 : 0.5, }} > @@ -202,7 +199,6 @@ const ControlNetImagePreview = ({ isSmall, controlNet }: Props) => { droppableData={droppableData} imageDTO={processedControlImage} isUploadDisabled={true} - isDropDisabled={!isEnabled} /> diff --git a/invokeai/frontend/web/src/features/controlNet/components/ipAdapter/ParamIPAdapterFeatureToggle.tsx b/invokeai/frontend/web/src/features/controlNet/components/ipAdapter/ParamIPAdapterFeatureToggle.tsx index c077e7a824..7fae538cf0 100644 --- a/invokeai/frontend/web/src/features/controlNet/components/ipAdapter/ParamIPAdapterFeatureToggle.tsx +++ b/invokeai/frontend/web/src/features/controlNet/components/ipAdapter/ParamIPAdapterFeatureToggle.tsx @@ -3,8 +3,8 @@ import { stateSelector } from 'app/store/store'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import IAISwitch from 'common/components/IAISwitch'; -import { isIPAdapterEnableToggled } from 'features/controlNet/store/controlNetSlice'; -import { memo, useCallback } from 'react'; +import { isIPAdapterEnabledChanged } from 'features/controlNet/store/controlNetSlice'; +import { ChangeEvent, memo, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; const selector = createSelector( @@ -22,9 +22,12 @@ const ParamIPAdapterFeatureToggle = () => { const dispatch = useAppDispatch(); const { t } = useTranslation(); - const handleChange = useCallback(() => { - dispatch(isIPAdapterEnableToggled()); - }, [dispatch]); + const handleChange = useCallback( + (e: ChangeEvent) => { + dispatch(isIPAdapterEnabledChanged(e.target.checked)); + }, + [dispatch] + ); return ( { + const { ipAdapterInfo } = controlNet; + return { ipAdapterInfo }; + }, + defaultSelectorOptions +); + const ParamIPAdapterImage = () => { - const ipAdapterInfo = useAppSelector( - (state: RootState) => state.controlNet.ipAdapterInfo - ); - - const isIPAdapterEnabled = useAppSelector( - (state: RootState) => state.controlNet.isIPAdapterEnabled - ); - + const { ipAdapterInfo } = useAppSelector(selector); const dispatch = useAppDispatch(); const { t } = useTranslation(); @@ -71,8 +75,6 @@ const ParamIPAdapterImage = () => { droppableData={droppableData} draggableData={draggableData} postUploadAction={postUploadAction} - isUploadDisabled={!isIPAdapterEnabled} - isDropDisabled={!isIPAdapterEnabled} dropLabel={t('toast.setIPAdapterImage')} noContentFallback={ { + const isEnabled = useAppSelector( + (state: RootState) => state.controlNet.isIPAdapterEnabled + ); const ipAdapterModel = useAppSelector( (state: RootState) => state.controlNet.ipAdapterInfo.model ); @@ -90,6 +93,7 @@ const ParamIPAdapterModelSelect = () => { data={data} onChange={handleValueChanged} sx={{ width: '100%' }} + disabled={!isEnabled} /> ); }; diff --git a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts index 3fe57f4a84..70c459f0a4 100644 --- a/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts +++ b/invokeai/frontend/web/src/features/controlNet/store/controlNetSlice.ts @@ -146,16 +146,16 @@ export const controlNetSlice = createSlice({ const { controlNetId } = action.payload; delete state.controlNets[controlNetId]; }, - controlNetToggled: ( + controlNetIsEnabledChanged: ( state, - action: PayloadAction<{ controlNetId: string }> + action: PayloadAction<{ controlNetId: string; isEnabled: boolean }> ) => { - const { controlNetId } = action.payload; + const { controlNetId, isEnabled } = action.payload; const cn = state.controlNets[controlNetId]; if (!cn) { return; } - cn.isEnabled = !cn.isEnabled; + cn.isEnabled = isEnabled; }, controlNetImageChanged: ( state, @@ -377,8 +377,8 @@ export const controlNetSlice = createSlice({ controlNetReset: () => { return { ...initialControlNetState }; }, - isIPAdapterEnableToggled: (state) => { - state.isIPAdapterEnabled = !state.isIPAdapterEnabled; + isIPAdapterEnabledChanged: (state, action: PayloadAction) => { + state.isIPAdapterEnabled = action.payload; }, ipAdapterImageChanged: (state, action: PayloadAction) => { state.ipAdapterInfo.adapterImage = action.payload; @@ -450,7 +450,7 @@ export const { controlNetRemoved, controlNetImageChanged, controlNetProcessedImageChanged, - controlNetToggled, + controlNetIsEnabledChanged, controlNetModelChanged, controlNetWeightChanged, controlNetBeginStepPctChanged, @@ -461,7 +461,7 @@ export const { controlNetProcessorTypeChanged, controlNetReset, controlNetAutoConfigToggled, - isIPAdapterEnableToggled, + isIPAdapterEnabledChanged, ipAdapterImageChanged, ipAdapterWeightChanged, ipAdapterModelChanged, diff --git a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx index cd276713c3..844bda8b3d 100644 --- a/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx +++ b/invokeai/frontend/web/src/features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse.tsx @@ -23,17 +23,18 @@ import { v4 as uuidv4 } from 'uuid'; const selector = createSelector( [stateSelector], ({ controlNet }) => { - const { controlNets, isEnabled, isIPAdapterEnabled } = controlNet; + const { controlNets, isEnabled, isIPAdapterEnabled, ipAdapterInfo } = + controlNet; const validControlNets = getValidControlNets(controlNets); - + const isIPAdapterValid = ipAdapterInfo.model && ipAdapterInfo.adapterImage; let activeLabel = undefined; if (isEnabled && validControlNets.length > 0) { activeLabel = `${validControlNets.length} ControlNet`; } - if (isIPAdapterEnabled) { + if (isIPAdapterEnabled && isIPAdapterValid) { if (activeLabel) { activeLabel = `${activeLabel}, IP Adapter`; } else {