mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
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:
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -28,7 +28,6 @@ const InitialImageDisplay = () => {
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
<InitialImageButtons />
|
||||
<InitialImagePreview />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
@ -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'
|
||||
);
|
||||
|
Reference in New Issue
Block a user