feat(ui): controlnet/image dnd wip

Implement `dnd-kit` for image drag and drop
- vastly simplifies logic bc we can drag and drop non-serializable data (like an `ImageDTO`)
- also much prettier
- also will fix conflicts with file upload via OS drag and drop, bc `dnd-kit` does not use native HTML drag and drop API
- Implemented for Init image, controlnet, and node editor so far

More progress on the ControlNet UI
This commit is contained in:
psychedelicious
2023-06-01 18:27:39 +10:00
parent e2e07696fc
commit 3b9426eb72
42 changed files with 853 additions and 234 deletions

View File

@ -1,61 +1,59 @@
import { Flex, Text, useDisclosure } from '@chakra-ui/react';
import { Flex, useDisclosure } from '@chakra-ui/react';
import { useTranslation } from 'react-i18next';
import IAICollapse from 'common/components/IAICollapse';
import { memo, useCallback, useState } from 'react';
import IAICustomSelect from 'common/components/IAICustomSelect';
import { memo, useCallback } from 'react';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaPlus } from 'react-icons/fa';
import CannyProcessor from 'features/controlNet/components/processors/CannyProcessor';
import ControlNet from 'features/controlNet/components/ControlNet';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { createSelector } from '@reduxjs/toolkit';
import {
controlNetAdded,
controlNetSelector,
} from 'features/controlNet/store/controlNetSlice';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { map } from 'lodash-es';
import { v4 as uuidv4 } from 'uuid';
const CONTROLNET_MODELS = [
'lllyasviel/sd-controlnet-canny',
'lllyasviel/sd-controlnet-depth',
'lllyasviel/sd-controlnet-hed',
'lllyasviel/sd-controlnet-seg',
'lllyasviel/sd-controlnet-openpose',
'lllyasviel/sd-controlnet-scribble',
'lllyasviel/sd-controlnet-normal',
'lllyasviel/sd-controlnet-mlsd',
];
const selector = createSelector(
controlNetSelector,
(controlNet) => {
const { controlNets } = controlNet;
return { controlNets };
},
defaultSelectorOptions
);
const ParamControlNetCollapse = () => {
const { t } = useTranslation();
const { isOpen, onToggle } = useDisclosure();
const [model, setModel] = useState<string>(CONTROLNET_MODELS[0]);
const { controlNets } = useAppSelector(selector);
const dispatch = useAppDispatch();
const handleSetControlNet = useCallback(
(model: string | null | undefined) => {
if (model) {
setModel(model);
}
},
[]
);
const handleClickedAddControlNet = useCallback(() => {
dispatch(controlNetAdded({ controlNetId: uuidv4() }));
}, [dispatch]);
return (
<ControlNet />
// <IAICollapse
// label={'ControlNet'}
// // label={t('parameters.seamCorrectionHeader')}
// isOpen={isOpen}
// onToggle={onToggle}
// >
// <Flex sx={{ alignItems: 'flex-end' }}>
// <IAICustomSelect
// label="ControlNet Model"
// items={CONTROLNET_MODELS}
// selectedItem={model}
// setSelectedItem={handleSetControlNet}
// />
// <IAIIconButton
// size="sm"
// aria-label="Add ControlNet"
// icon={<FaPlus />}
// />
// </Flex>
// <CannyProcessor />
// </IAICollapse>
<IAICollapse
label={'ControlNet'}
// label={t('parameters.seamCorrectionHeader')}
isOpen={isOpen}
onToggle={onToggle}
>
<Flex sx={{ alignItems: 'flex-end' }}>
<IAIIconButton
size="sm"
aria-label="Add ControlNet"
onClick={handleClickedAddControlNet}
icon={<FaPlus />}
/>
</Flex>
{map(controlNets, (c) => (
<ControlNet key={c.controlNetId} controlNet={c} />
))}
</IAICollapse>
);
};

View File

@ -28,7 +28,6 @@ const InitialImageDisplay = () => {
gap: 4,
}}
>
<InitialImageButtons />
<InitialImagePreview />
</Flex>
</Flex>

View File

@ -2,7 +2,10 @@ import { Flex, Icon, Image } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useGetUrl } from 'common/util/getUrl';
import { clearInitialImage } from 'features/parameters/store/generationSlice';
import {
clearInitialImage,
initialImageChanged,
} from 'features/parameters/store/generationSlice';
import { DragEvent, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
@ -13,6 +16,8 @@ import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinn
import { FaImage } from 'react-icons/fa';
import { configSelector } from '../../../../system/store/configSelectors';
import { useAppToaster } from 'app/components/Toaster';
import IAISelectableImage from 'features/controlNet/components/parameters/IAISelectableImage';
import { ImageDTO } from 'services/api';
const selector = createSelector(
[generationSelector],
@ -51,14 +56,17 @@ const InitialImagePreview = () => {
}
}, [dispatch, t, toaster, shouldFetchImages]);
const handleDrop = useCallback(
(e: DragEvent<HTMLDivElement>) => {
const name = e.dataTransfer.getData('invokeai/imageName');
dispatch(initialImageSelected(name));
const handleChange = useCallback(
(image: ImageDTO) => {
dispatch(initialImageChanged(image));
},
[dispatch]
);
const handleReset = useCallback(() => {
dispatch(clearInitialImage());
}, [dispatch]);
return (
<Flex
sx={{
@ -68,9 +76,14 @@ const InitialImagePreview = () => {
alignItems: 'center',
justifyContent: 'center',
}}
onDrop={handleDrop}
// onDrop={handleDrop}
>
{initialImage?.image_url && (
<IAISelectableImage
image={initialImage}
onChange={handleChange}
onReset={handleReset}
/>
{/* {initialImage?.image_url && (
<>
<Image
src={getUrl(initialImage?.image_url)}
@ -97,7 +110,7 @@ const InitialImagePreview = () => {
color: 'base.500',
}}
/>
)}
)} */}
</Flex>
);
};

View File

@ -7,25 +7,6 @@ export type ImageNameAndOrigin = {
image_origin: ResourceOrigin;
};
export const isImageDTO = (image: any): image is ImageDTO => {
return (
image &&
isObject(image) &&
'image_name' in image &&
image?.image_name !== undefined &&
'image_origin' in image &&
image?.image_origin !== undefined &&
'image_url' in image &&
image?.image_url !== undefined &&
'thumbnail_url' in image &&
image?.thumbnail_url !== undefined &&
'image_category' in image &&
image?.image_category !== undefined &&
'created_at' in image &&
image?.created_at !== undefined
);
};
export const initialImageSelected = createAction<ImageDTO | string | undefined>(
'generation/initialImageSelected'
);