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:
parent
e2e07696fc
commit
3b9426eb72
@ -60,6 +60,7 @@
|
||||
"@chakra-ui/styled-system": "^2.9.0",
|
||||
"@chakra-ui/theme-tools": "^2.0.16",
|
||||
"@dagrejs/graphlib": "^2.1.12",
|
||||
"@dnd-kit/core": "^6.0.8",
|
||||
"@emotion/react": "^11.10.6",
|
||||
"@emotion/styled": "^11.10.6",
|
||||
"@floating-ui/react-dom": "^2.0.0",
|
||||
|
@ -0,0 +1,45 @@
|
||||
import {
|
||||
DndContext,
|
||||
DragEndEvent,
|
||||
DragOverlay,
|
||||
DragStartEvent,
|
||||
} from '@dnd-kit/core';
|
||||
import { PropsWithChildren, memo, useCallback, useState } from 'react';
|
||||
import OverlayDragImage from './OverlayDragImage';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { isImageDTO } from 'services/types/guards';
|
||||
|
||||
type ImageDndContextProps = PropsWithChildren;
|
||||
|
||||
const ImageDndContext = (props: ImageDndContextProps) => {
|
||||
const [draggedImage, setDraggedImage] = useState<ImageDTO | null>(null);
|
||||
|
||||
const handleDragStart = useCallback((event: DragStartEvent) => {
|
||||
const dragData = event.active.data.current;
|
||||
if (dragData && 'image' in dragData && isImageDTO(dragData.image)) {
|
||||
setDraggedImage(dragData.image);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const handleDragEnd = useCallback(
|
||||
(event: DragEndEvent) => {
|
||||
const handleDrop = event.over?.data.current?.handleDrop;
|
||||
if (handleDrop && typeof handleDrop === 'function' && draggedImage) {
|
||||
handleDrop(draggedImage);
|
||||
}
|
||||
setDraggedImage(null);
|
||||
},
|
||||
[draggedImage]
|
||||
);
|
||||
|
||||
return (
|
||||
<DndContext onDragStart={handleDragStart} onDragEnd={handleDragEnd}>
|
||||
{props.children}
|
||||
<DragOverlay dropAnimation={null}>
|
||||
{draggedImage && <OverlayDragImage image={draggedImage} />}
|
||||
</DragOverlay>
|
||||
</DndContext>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ImageDndContext);
|
@ -0,0 +1,23 @@
|
||||
import { Image } from '@chakra-ui/react';
|
||||
import { memo } from 'react';
|
||||
import { ImageDTO } from 'services/api';
|
||||
|
||||
type OverlayDragImageProps = {
|
||||
image: ImageDTO;
|
||||
};
|
||||
|
||||
const OverlayDragImage = (props: OverlayDragImageProps) => {
|
||||
return (
|
||||
<Image
|
||||
sx={{
|
||||
maxW: 32,
|
||||
maxH: 32,
|
||||
borderRadius: 'base',
|
||||
shadow: 'dark-lg',
|
||||
}}
|
||||
src={props.image.thumbnail_url}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(OverlayDragImage);
|
@ -16,6 +16,7 @@ import { PartialAppConfig } from 'app/types/invokeai';
|
||||
import '../../i18n';
|
||||
import { socketMiddleware } from 'services/events/middleware';
|
||||
import { Middleware } from '@reduxjs/toolkit';
|
||||
import ImageDndContext from './ImageDnd/ImageDndContext';
|
||||
|
||||
const App = lazy(() => import('./App'));
|
||||
const ThemeLocaleProvider = lazy(() => import('./ThemeLocaleProvider'));
|
||||
@ -69,11 +70,13 @@ const InvokeAIUI = ({
|
||||
<Provider store={store}>
|
||||
<React.Suspense fallback={<Loading />}>
|
||||
<ThemeLocaleProvider>
|
||||
<App
|
||||
config={config}
|
||||
headerComponent={headerComponent}
|
||||
setIsReady={setIsReady}
|
||||
/>
|
||||
<ImageDndContext>
|
||||
<App
|
||||
config={config}
|
||||
headerComponent={headerComponent}
|
||||
setIsReady={setIsReady}
|
||||
/>
|
||||
</ImageDndContext>
|
||||
</ThemeLocaleProvider>
|
||||
</React.Suspense>
|
||||
</Provider>
|
||||
|
@ -2,12 +2,10 @@ import { initialImageChanged } from 'features/parameters/store/generationSlice';
|
||||
import { t } from 'i18next';
|
||||
import { addToast } from 'features/system/store/systemSlice';
|
||||
import { startAppListening } from '..';
|
||||
import {
|
||||
initialImageSelected,
|
||||
isImageDTO,
|
||||
} from 'features/parameters/store/actions';
|
||||
import { initialImageSelected } from 'features/parameters/store/actions';
|
||||
import { makeToast } from 'app/components/Toaster';
|
||||
import { selectImagesById } from 'features/gallery/store/imagesSlice';
|
||||
import { isImageDTO } from 'services/types/guards';
|
||||
|
||||
export const addInitialImageSelectedListener = () => {
|
||||
startAppListening({
|
||||
|
@ -13,6 +13,7 @@ import galleryReducer from 'features/gallery/store/gallerySlice';
|
||||
import imagesReducer from 'features/gallery/store/imagesSlice';
|
||||
import lightboxReducer from 'features/lightbox/store/lightboxSlice';
|
||||
import generationReducer from 'features/parameters/store/generationSlice';
|
||||
import controlNetReducer from 'features/controlNet/store/controlNetSlice';
|
||||
import postprocessingReducer from 'features/parameters/store/postprocessingSlice';
|
||||
import systemReducer from 'features/system/store/systemSlice';
|
||||
// import sessionReducer from 'features/system/store/sessionSlice';
|
||||
@ -45,6 +46,7 @@ const allReducers = {
|
||||
ui: uiReducer,
|
||||
hotkeys: hotkeysReducer,
|
||||
images: imagesReducer,
|
||||
controlNet: controlNetReducer,
|
||||
// session: sessionReducer,
|
||||
};
|
||||
|
||||
@ -62,6 +64,7 @@ const rememberedKeys: (keyof typeof allReducers)[] = [
|
||||
'postprocessing',
|
||||
'system',
|
||||
'ui',
|
||||
'controlNet',
|
||||
// 'hotkeys',
|
||||
// 'config',
|
||||
];
|
||||
|
@ -1,17 +0,0 @@
|
||||
import { Checkbox, CheckboxProps } from '@chakra-ui/react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
type IAICheckboxProps = CheckboxProps & {
|
||||
label: string | ReactNode;
|
||||
};
|
||||
|
||||
const IAICheckbox = (props: IAICheckboxProps) => {
|
||||
const { label, ...rest } = props;
|
||||
return (
|
||||
<Checkbox colorScheme="accent" {...rest}>
|
||||
{label}
|
||||
</Checkbox>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IAICheckbox);
|
@ -0,0 +1,25 @@
|
||||
import {
|
||||
Checkbox,
|
||||
CheckboxProps,
|
||||
FormControl,
|
||||
FormControlProps,
|
||||
FormLabel,
|
||||
} from '@chakra-ui/react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
type IAIFullCheckboxProps = CheckboxProps & {
|
||||
label: string | ReactNode;
|
||||
formControlProps?: FormControlProps;
|
||||
};
|
||||
|
||||
const IAIFullCheckbox = (props: IAIFullCheckboxProps) => {
|
||||
const { label, formControlProps, ...rest } = props;
|
||||
return (
|
||||
<FormControl {...formControlProps}>
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<Checkbox colorScheme="accent" {...rest} />
|
||||
</FormControl>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IAIFullCheckbox);
|
@ -0,0 +1,19 @@
|
||||
import { Checkbox, CheckboxProps, Text } from '@chakra-ui/react';
|
||||
import { memo, ReactNode } from 'react';
|
||||
|
||||
type IAISimpleCheckboxProps = CheckboxProps & {
|
||||
label: string | ReactNode;
|
||||
};
|
||||
|
||||
const IAISimpleCheckbox = (props: IAISimpleCheckboxProps) => {
|
||||
const { label, ...rest } = props;
|
||||
return (
|
||||
<Checkbox colorScheme="accent" {...rest}>
|
||||
<Text color="base.200" fontSize="md">
|
||||
{label}
|
||||
</Text>
|
||||
</Checkbox>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IAISimpleCheckbox);
|
@ -23,9 +23,9 @@ const ImageMetadataOverlay = ({ image }: ImageMetadataOverlayProps) => {
|
||||
flexDirection: 'column',
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
insetInlineStart: 0,
|
||||
p: 2,
|
||||
alignItems: 'flex-end',
|
||||
alignItems: 'flex-start',
|
||||
gap: 2,
|
||||
}}
|
||||
>
|
||||
|
@ -2,7 +2,7 @@ import { ButtonGroup, Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIColorPicker from 'common/components/IAIColorPicker';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
@ -117,12 +117,12 @@ const IAICanvasMaskOptions = () => {
|
||||
}
|
||||
>
|
||||
<Flex direction="column" gap={2}>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={`${t('unifiedCanvas.enableMask')} (H)`}
|
||||
isChecked={isMaskEnabled}
|
||||
onChange={handleToggleEnableMask}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.preserveMaskedArea')}
|
||||
isChecked={shouldPreserveMaskedArea}
|
||||
onChange={(e) =>
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
@ -102,50 +102,50 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
}
|
||||
>
|
||||
<Flex direction="column" gap={2}>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showIntermediates')}
|
||||
isChecked={shouldShowIntermediates}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldShowIntermediates(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showGrid')}
|
||||
isChecked={shouldShowGrid}
|
||||
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.snapToGrid')}
|
||||
isChecked={shouldSnapToGrid}
|
||||
onChange={handleChangeShouldSnapToGrid}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.darkenOutsideSelection')}
|
||||
isChecked={shouldDarkenOutsideBoundingBox}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldDarkenOutsideBoundingBox(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.autoSaveToGallery')}
|
||||
isChecked={shouldAutoSave}
|
||||
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.saveBoxRegionOnly')}
|
||||
isChecked={shouldCropToBoundingBoxOnSave}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.limitStrokesToBox')}
|
||||
isChecked={shouldRestrictStrokesToBox}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldRestrictStrokesToBox(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showCanvasDebugInfo')}
|
||||
isChecked={shouldShowCanvasDebugInfo}
|
||||
onChange={(e) =>
|
||||
@ -153,7 +153,7 @@ const IAICanvasSettingsButtonPopover = () => {
|
||||
}
|
||||
/>
|
||||
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.antialiasing')}
|
||||
isChecked={shouldAntialias}
|
||||
onChange={(e) => dispatch(setShouldAntialias(e.target.checked))}
|
||||
|
@ -1,7 +1,32 @@
|
||||
import { memo } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { ControlNetProcessorNode } from '../store/types';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import CannyProcessor from './processors/CannyProcessor';
|
||||
import {
|
||||
ControlNet,
|
||||
ControlNetModel,
|
||||
controlNetBeginStepPctChanged,
|
||||
controlNetEndStepPctChanged,
|
||||
controlNetImageChanged,
|
||||
controlNetModelChanged,
|
||||
controlNetProcessedImageChanged,
|
||||
controlNetRemoved,
|
||||
controlNetToggled,
|
||||
controlNetWeightChanged,
|
||||
isControlNetImageProcessedToggled,
|
||||
} from '../store/controlNetSlice';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import ParamControlNetIsEnabled from './parameters/ParamControlNetIsEnabled';
|
||||
import ParamControlNetModel from './parameters/ParamControlNetModel';
|
||||
import ParamControlNetWeight from './parameters/ParamControlNetWeight';
|
||||
import ParamControlNetBeginStepPct from './parameters/ParamControlNetBeginStepPct';
|
||||
import ParamControlNetEndStepPct from './parameters/ParamControlNetEndStepPct';
|
||||
import { Flex, HStack, VStack } from '@chakra-ui/react';
|
||||
import IAISelectableImage from './parameters/IAISelectableImage';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
|
||||
export type ControlNetProcessorProps = {
|
||||
controlNetId: string;
|
||||
@ -16,11 +41,83 @@ const renderProcessorComponent = (props: ControlNetProcessorProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const ControlNet = () => {
|
||||
type ControlNetProps = {
|
||||
controlNet: ControlNet;
|
||||
};
|
||||
|
||||
const ControlNet = (props: ControlNetProps) => {
|
||||
const {
|
||||
controlNetId,
|
||||
isEnabled,
|
||||
model,
|
||||
weight,
|
||||
beginStepPct,
|
||||
endStepPct,
|
||||
controlImage,
|
||||
isControlImageProcessed,
|
||||
processedControlImage,
|
||||
} = props.controlNet;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleControlImageChanged = useCallback(
|
||||
(controlImage: ImageDTO) => {
|
||||
dispatch(controlNetImageChanged({ controlNetId, controlImage }));
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
|
||||
const handleControlImageReset = useCallback(() => {
|
||||
dispatch(controlNetImageChanged({ controlNetId, controlImage: null }));
|
||||
}, [controlNetId, dispatch]);
|
||||
|
||||
const handleControlNetRemoved = useCallback(() => {
|
||||
dispatch(controlNetRemoved(controlNetId));
|
||||
}, [controlNetId, dispatch]);
|
||||
|
||||
const handleIsControlImageProcessedToggled = useCallback(() => {
|
||||
dispatch(
|
||||
isControlNetImageProcessedToggled({
|
||||
controlNetId,
|
||||
})
|
||||
);
|
||||
}, [controlNetId, dispatch]);
|
||||
|
||||
const handleProcessedControlImageChanged = useCallback(
|
||||
(processedControlImage: ImageDTO | null) => {
|
||||
dispatch(
|
||||
controlNetProcessedImageChanged({
|
||||
controlNetId,
|
||||
processedControlImage,
|
||||
})
|
||||
);
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<h1>ControlNet</h1>
|
||||
</div>
|
||||
<Flex sx={{ flexDir: 'column', gap: 3, pb: 4 }}>
|
||||
<IAIButton onClick={handleControlNetRemoved}>Remove ControlNet</IAIButton>
|
||||
<IAISelectableImage
|
||||
image={controlImage}
|
||||
onChange={handleControlImageChanged}
|
||||
onReset={handleControlImageReset}
|
||||
resetIconSize="sm"
|
||||
/>
|
||||
<ParamControlNetModel controlNetId={controlNetId} model={model} />
|
||||
<ParamControlNetIsEnabled
|
||||
controlNetId={controlNetId}
|
||||
isEnabled={isEnabled}
|
||||
/>
|
||||
<ParamControlNetWeight controlNetId={controlNetId} weight={weight} />
|
||||
<ParamControlNetBeginStepPct
|
||||
controlNetId={controlNetId}
|
||||
beginStepPct={beginStepPct}
|
||||
/>
|
||||
<ParamControlNetEndStepPct
|
||||
controlNetId={controlNetId}
|
||||
endStepPct={endStepPct}
|
||||
/>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -0,0 +1,200 @@
|
||||
import {
|
||||
Box,
|
||||
Flex,
|
||||
Icon,
|
||||
IconButtonProps,
|
||||
Image,
|
||||
Text,
|
||||
} from '@chakra-ui/react';
|
||||
import { useDroppable } from '@dnd-kit/core';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import ImageMetadataOverlay from 'common/components/ImageMetadataOverlay';
|
||||
import { useGetUrl } from 'common/util/getUrl';
|
||||
import ImageFallbackSpinner from 'features/gallery/components/ImageFallbackSpinner';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
import { SyntheticEvent } from 'react';
|
||||
import { memo, useRef } from 'react';
|
||||
import { FaImage, FaUndo } from 'react-icons/fa';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
|
||||
type IAISelectableImageProps = {
|
||||
image: ImageDTO | null | undefined;
|
||||
onChange: (image: ImageDTO) => void;
|
||||
onReset?: () => void;
|
||||
onError?: (event: SyntheticEvent<HTMLImageElement>) => void;
|
||||
resetIconSize?: IconButtonProps['size'];
|
||||
};
|
||||
|
||||
const IAISelectableImage = (props: IAISelectableImageProps) => {
|
||||
const { image, onChange, onReset, onError, resetIconSize = 'md' } = props;
|
||||
const droppableId = useRef(uuidv4());
|
||||
const { getUrl } = useGetUrl();
|
||||
const { isOver, setNodeRef, active } = useDroppable({
|
||||
id: droppableId.current,
|
||||
data: {
|
||||
handleDrop: onChange,
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
width: 'full',
|
||||
height: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
position: 'relative',
|
||||
}}
|
||||
ref={setNodeRef}
|
||||
>
|
||||
{image && (
|
||||
<Flex sx={{ position: 'relative' }}>
|
||||
<Image
|
||||
src={getUrl(image.image_url)}
|
||||
fallbackStrategy="beforeLoadOrError"
|
||||
fallback={<ImageFallbackSpinner />}
|
||||
onError={onError}
|
||||
sx={{
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
/>
|
||||
<ImageMetadataOverlay image={image} />
|
||||
<AnimatePresence>
|
||||
{active && <DropOverlay isOver={isOver} />}
|
||||
</AnimatePresence>
|
||||
</Flex>
|
||||
)}
|
||||
{!image && (
|
||||
<>
|
||||
<Flex
|
||||
sx={{
|
||||
p: 8,
|
||||
bg: 'base.850',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
as={FaImage}
|
||||
sx={{
|
||||
boxSize: 24,
|
||||
color: 'base.500',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
<AnimatePresence>
|
||||
{active && <DropOverlay isOver={isOver} />}
|
||||
</AnimatePresence>
|
||||
</>
|
||||
)}
|
||||
{image && onReset && (
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<IAIIconButton
|
||||
size={resetIconSize}
|
||||
aria-label="Reset Image"
|
||||
icon={<FaUndo />}
|
||||
onClick={onReset}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(IAISelectableImage);
|
||||
|
||||
type DropOverlayProps = {
|
||||
isOver: boolean;
|
||||
};
|
||||
|
||||
const DropOverlay = (props: DropOverlayProps) => {
|
||||
const { isOver } = props;
|
||||
return (
|
||||
<motion.div
|
||||
key="statusText"
|
||||
initial={{
|
||||
opacity: 0,
|
||||
}}
|
||||
animate={{
|
||||
opacity: 1,
|
||||
transition: { duration: 0.1 },
|
||||
}}
|
||||
exit={{
|
||||
opacity: 0,
|
||||
transition: { duration: 0.1 },
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
bg: 'base.900',
|
||||
opacity: isOver ? 0.9 : 0.7,
|
||||
borderRadius: 'base',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.15s',
|
||||
}}
|
||||
/>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
opacity: isOver ? 1 : 0.9,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.15s',
|
||||
}}
|
||||
>
|
||||
<Text sx={{ fontSize: '2xl', fontWeight: 600, color: 'base.50' }}>
|
||||
Drop Image
|
||||
</Text>
|
||||
</Flex>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
left: 0,
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
opacity: isOver ? 1 : 0.7,
|
||||
borderWidth: 2,
|
||||
borderColor: 'base.500',
|
||||
borderRadius: 'base',
|
||||
borderStyle: 'dashed',
|
||||
transitionProperty: 'common',
|
||||
transitionDuration: '0.15s',
|
||||
}}
|
||||
></Flex>
|
||||
</Flex>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
@ -0,0 +1,44 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { controlNetBeginStepPctChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type ParamControlNetBeginStepPctProps = {
|
||||
controlNetId: string;
|
||||
beginStepPct: number;
|
||||
};
|
||||
|
||||
const ParamControlNetBeginStepPct = (
|
||||
props: ParamControlNetBeginStepPctProps
|
||||
) => {
|
||||
const { controlNetId, beginStepPct } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleBeginStepPctChanged = useCallback(
|
||||
(beginStepPct: number) => {
|
||||
dispatch(controlNetBeginStepPctChanged({ controlNetId, beginStepPct }));
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
|
||||
const handleBeginStepPctReset = () => {
|
||||
dispatch(controlNetBeginStepPctChanged({ controlNetId, beginStepPct: 0 }));
|
||||
};
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label="Begin Step %"
|
||||
value={beginStepPct}
|
||||
onChange={handleBeginStepPctChanged}
|
||||
withInput
|
||||
withReset
|
||||
handleReset={handleBeginStepPctReset}
|
||||
withSliderMarks
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetBeginStepPct);
|
@ -0,0 +1,42 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { controlNetEndStepPctChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type ParamControlNetEndStepPctProps = {
|
||||
controlNetId: string;
|
||||
endStepPct: number;
|
||||
};
|
||||
|
||||
const ParamControlNetEndStepPct = (props: ParamControlNetEndStepPctProps) => {
|
||||
const { controlNetId, endStepPct } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleEndStepPctChanged = useCallback(
|
||||
(endStepPct: number) => {
|
||||
dispatch(controlNetEndStepPctChanged({ controlNetId, endStepPct }));
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
|
||||
const handleEndStepPctReset = () => {
|
||||
dispatch(controlNetEndStepPctChanged({ controlNetId, endStepPct: 0 }));
|
||||
};
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label="End Step %"
|
||||
value={endStepPct}
|
||||
onChange={handleEndStepPctChanged}
|
||||
withInput
|
||||
withReset
|
||||
handleReset={handleEndStepPctReset}
|
||||
withSliderMarks
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetEndStepPct);
|
@ -0,0 +1,28 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAIFullCheckbox from 'common/components/IAIFullCheckbox';
|
||||
import { controlNetToggled } from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type ParamControlNetIsEnabledProps = {
|
||||
controlNetId: string;
|
||||
isEnabled: boolean;
|
||||
};
|
||||
|
||||
const ParamControlNetIsEnabled = (props: ParamControlNetIsEnabledProps) => {
|
||||
const { controlNetId, isEnabled } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleIsEnabledChanged = useCallback(() => {
|
||||
dispatch(controlNetToggled(controlNetId));
|
||||
}, [dispatch, controlNetId]);
|
||||
|
||||
return (
|
||||
<IAIFullCheckbox
|
||||
label="Enabled"
|
||||
isChecked={isEnabled}
|
||||
onChange={handleIsEnabledChanged}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetIsEnabled);
|
@ -0,0 +1,38 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAICustomSelect from 'common/components/IAICustomSelect';
|
||||
import {
|
||||
CONTROLNET_MODELS,
|
||||
ControlNetModel,
|
||||
controlNetModelChanged,
|
||||
} from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type ParamIsControlNetModelProps = {
|
||||
controlNetId: string;
|
||||
model: ControlNetModel;
|
||||
};
|
||||
|
||||
const ParamIsControlNetModel = (props: ParamIsControlNetModelProps) => {
|
||||
const { controlNetId, model } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleModelChanged = useCallback(
|
||||
(val: string | null | undefined) => {
|
||||
// TODO: do not cast
|
||||
const model = val as ControlNetModel;
|
||||
dispatch(controlNetModelChanged({ controlNetId, model }));
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
|
||||
return (
|
||||
<IAICustomSelect
|
||||
label="Model"
|
||||
items={CONTROLNET_MODELS}
|
||||
selectedItem={model}
|
||||
setSelectedItem={handleModelChanged}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamIsControlNetModel);
|
@ -0,0 +1,42 @@
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import { controlNetWeightChanged } from 'features/controlNet/store/controlNetSlice';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
type ParamControlNetWeightProps = {
|
||||
controlNetId: string;
|
||||
weight: number;
|
||||
};
|
||||
|
||||
const ParamControlNetWeight = (props: ParamControlNetWeightProps) => {
|
||||
const { controlNetId, weight } = props;
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleWeightChanged = useCallback(
|
||||
(weight: number) => {
|
||||
dispatch(controlNetWeightChanged({ controlNetId, weight }));
|
||||
},
|
||||
[controlNetId, dispatch]
|
||||
);
|
||||
|
||||
const handleWeightReset = () => {
|
||||
dispatch(controlNetWeightChanged({ controlNetId, weight: 1 }));
|
||||
};
|
||||
|
||||
return (
|
||||
<IAISlider
|
||||
label="Weight"
|
||||
value={weight}
|
||||
onChange={handleWeightChanged}
|
||||
withInput
|
||||
withReset
|
||||
handleReset={handleWeightReset}
|
||||
withSliderMarks
|
||||
min={0}
|
||||
max={1}
|
||||
step={0.01}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParamControlNetWeight);
|
@ -4,8 +4,6 @@ import { memo, useCallback, useState } from 'react';
|
||||
import ControlNetProcessButton from './common/ControlNetProcessButton';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { controlNetImageProcessed } from 'features/controlNet/store/actions';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import ControlNetProcessorImage from './common/ControlNetProcessorImage';
|
||||
import { ControlNetProcessorProps } from '../ControlNet';
|
||||
|
||||
export const CANNY_PROCESSOR = 'canny_processor';
|
||||
|
@ -1,33 +0,0 @@
|
||||
import { Flex, Image } from '@chakra-ui/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectImagesById } from 'features/gallery/store/imagesSlice';
|
||||
import { DragEvent, memo, useCallback } from 'react';
|
||||
import { ImageDTO } from 'services/api';
|
||||
|
||||
type ControlNetProcessorImageProps = {
|
||||
image: ImageDTO | undefined;
|
||||
setImage: (image: ImageDTO) => void;
|
||||
};
|
||||
|
||||
const ControlNetProcessorImage = (props: ControlNetProcessorImageProps) => {
|
||||
const { image, setImage } = props;
|
||||
const state = useAppSelector((state) => state);
|
||||
const handleDrop = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
const name = e.dataTransfer.getData('invokeai/imageName');
|
||||
const droppedImage = selectImagesById(state, name);
|
||||
if (droppedImage) {
|
||||
setImage(droppedImage);
|
||||
}
|
||||
},
|
||||
[setImage, state]
|
||||
);
|
||||
|
||||
if (!image) {
|
||||
return <Flex onDrop={handleDrop}>Upload Image</Flex>;
|
||||
}
|
||||
|
||||
return <Image src={image.image_url} />;
|
||||
};
|
||||
|
||||
export default memo(ControlNetProcessorImage);
|
@ -1,5 +1,10 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import {
|
||||
$CombinedState,
|
||||
PayloadAction,
|
||||
createSelector,
|
||||
} from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { RootState } from 'app/store/store';
|
||||
import { ImageDTO } from 'services/api';
|
||||
|
||||
export const CONTROLNET_MODELS = [
|
||||
@ -11,22 +16,22 @@ export const CONTROLNET_MODELS = [
|
||||
'lllyasviel/sd-controlnet-scribble',
|
||||
'lllyasviel/sd-controlnet-normal',
|
||||
'lllyasviel/sd-controlnet-mlsd',
|
||||
] as const;
|
||||
];
|
||||
|
||||
export const CONTROLNET_PROCESSORS = [
|
||||
'canny',
|
||||
'contentShuffle',
|
||||
'hed',
|
||||
'lineart',
|
||||
'lineartAnime',
|
||||
'mediapipeFace',
|
||||
'midasDepth',
|
||||
'mlsd',
|
||||
'normalBae',
|
||||
'openpose',
|
||||
'pidi',
|
||||
'zoeDepth',
|
||||
] as const;
|
||||
// export const CONTROLNET_PROCESSORS = [
|
||||
// 'canny',
|
||||
// 'contentShuffle',
|
||||
// 'hed',
|
||||
// 'lineart',
|
||||
// 'lineartAnime',
|
||||
// 'mediapipeFace',
|
||||
// 'midasDepth',
|
||||
// 'mlsd',
|
||||
// 'normalBae',
|
||||
// 'openpose',
|
||||
// 'pidi',
|
||||
// 'zoeDepth',
|
||||
// ] as const;
|
||||
|
||||
export type ControlNetModel = (typeof CONTROLNET_MODELS)[number];
|
||||
|
||||
@ -37,6 +42,7 @@ export const initialControlNet: Omit<ControlNet, 'controlNetId'> = {
|
||||
beginStepPct: 0,
|
||||
endStepPct: 1,
|
||||
controlImage: null,
|
||||
isControlImageProcessed: false,
|
||||
processedControlImage: null,
|
||||
};
|
||||
|
||||
@ -48,6 +54,7 @@ export type ControlNet = {
|
||||
beginStepPct: number;
|
||||
endStepPct: number;
|
||||
controlImage: ImageDTO | null;
|
||||
isControlImageProcessed: boolean;
|
||||
processedControlImage: ImageDTO | null;
|
||||
};
|
||||
|
||||
@ -63,15 +70,14 @@ export const controlNetSlice = createSlice({
|
||||
name: 'controlNet',
|
||||
initialState: initialControlNetState,
|
||||
reducers: {
|
||||
controlNetAddedFromModel: (
|
||||
controlNetAdded: (
|
||||
state,
|
||||
action: PayloadAction<{ controlNetId: string; model: ControlNetModel }>
|
||||
action: PayloadAction<{ controlNetId: string }>
|
||||
) => {
|
||||
const { controlNetId, model } = action.payload;
|
||||
const { controlNetId } = action.payload;
|
||||
state.controlNets[controlNetId] = {
|
||||
...initialControlNet,
|
||||
controlNetId,
|
||||
model,
|
||||
};
|
||||
},
|
||||
controlNetAddedFromImage: (
|
||||
@ -96,11 +102,24 @@ export const controlNetSlice = createSlice({
|
||||
},
|
||||
controlNetImageChanged: (
|
||||
state,
|
||||
action: PayloadAction<{ controlNetId: string; controlImage: ImageDTO }>
|
||||
action: PayloadAction<{
|
||||
controlNetId: string;
|
||||
controlImage: ImageDTO | null;
|
||||
}>
|
||||
) => {
|
||||
const { controlNetId, controlImage } = action.payload;
|
||||
state.controlNets[controlNetId].controlImage = controlImage;
|
||||
},
|
||||
isControlNetImageProcessedToggled: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
controlNetId: string;
|
||||
}>
|
||||
) => {
|
||||
const { controlNetId } = action.payload;
|
||||
state.controlNets[controlNetId].isControlImageProcessed =
|
||||
!state.controlNets[controlNetId].isControlImageProcessed;
|
||||
},
|
||||
controlNetProcessedImageChanged: (
|
||||
state,
|
||||
action: PayloadAction<{
|
||||
@ -144,10 +163,11 @@ export const controlNetSlice = createSlice({
|
||||
});
|
||||
|
||||
export const {
|
||||
controlNetAddedFromModel,
|
||||
controlNetAdded,
|
||||
controlNetAddedFromImage,
|
||||
controlNetRemoved,
|
||||
controlNetImageChanged,
|
||||
isControlNetImageProcessedToggled,
|
||||
controlNetProcessedImageChanged,
|
||||
controlNetToggled,
|
||||
controlNetModelChanged,
|
||||
@ -157,3 +177,5 @@ export const {
|
||||
} = controlNetSlice.actions;
|
||||
|
||||
export default controlNetSlice.reducer;
|
||||
|
||||
export const controlNetSelector = (state: RootState) => state.controlNet;
|
||||
|
@ -39,6 +39,8 @@ import {
|
||||
} from '../store/actions';
|
||||
import { useAppToaster } from 'app/components/Toaster';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { useDraggable } from '@dnd-kit/core';
|
||||
import { CSS } from '@dnd-kit/utilities';
|
||||
|
||||
export const selector = createSelector(
|
||||
[gallerySelector, systemSelector, lightboxSelector, activeTabNameSelector],
|
||||
@ -117,6 +119,13 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
const { recallBothPrompts, recallSeed, recallAllParameters } =
|
||||
useRecallParameters();
|
||||
|
||||
const { attributes, listeners, setNodeRef } = useDraggable({
|
||||
id: image_name,
|
||||
data: {
|
||||
image,
|
||||
},
|
||||
});
|
||||
|
||||
const handleMouseOver = () => setIsHovered(true);
|
||||
const handleMouseOut = () => setIsHovered(false);
|
||||
|
||||
@ -212,7 +221,12 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box
|
||||
ref={setNodeRef}
|
||||
{...listeners}
|
||||
{...attributes}
|
||||
sx={{ w: 'full', h: 'full' }}
|
||||
>
|
||||
<ContextMenu<HTMLDivElement>
|
||||
menuProps={{ size: 'sm', isLazy: true }}
|
||||
renderMenu={() => (
|
||||
@ -291,8 +305,8 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
onMouseOver={handleMouseOver}
|
||||
onMouseOut={handleMouseOut}
|
||||
userSelect="none"
|
||||
draggable={true}
|
||||
onDragStart={handleDragStart}
|
||||
// draggable={true}
|
||||
// onDragStart={handleDragStart}
|
||||
onClick={handleSelectImage}
|
||||
ref={ref}
|
||||
sx={{
|
||||
@ -373,7 +387,7 @@ const HoverableImage = memo((props: HoverableImageProps) => {
|
||||
onClose={onDeleteDialogClose}
|
||||
handleDelete={handleDelete}
|
||||
/>
|
||||
</>
|
||||
</Box>
|
||||
);
|
||||
}, memoEqualityCheck);
|
||||
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
@ -233,7 +233,7 @@ const ImageGalleryContent = () => {
|
||||
withReset
|
||||
handleReset={() => dispatch(setGalleryImageMinimumWidth(64))}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('gallery.maintainAspectRatio')}
|
||||
isChecked={galleryImageObjectFit === 'contain'}
|
||||
onChange={() =>
|
||||
@ -244,14 +244,14 @@ const ImageGalleryContent = () => {
|
||||
)
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('gallery.autoSwitchNewImages')}
|
||||
isChecked={shouldAutoSwitchToNewImages}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldAutoSwitchToNewImages(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('gallery.singleColumnLayout')}
|
||||
isChecked={shouldUseSingleGalleryColumn}
|
||||
onChange={(e: ChangeEvent<HTMLInputElement>) =>
|
||||
|
@ -1,39 +1,26 @@
|
||||
import { Box, Image } from '@chakra-ui/react';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import SelectImagePlaceholder from 'common/components/SelectImagePlaceholder';
|
||||
import { useGetUrl } from 'common/util/getUrl';
|
||||
import useGetImageByName from 'features/gallery/hooks/useGetImageByName';
|
||||
|
||||
import { fieldValueChanged } from 'features/nodes/store/nodesSlice';
|
||||
import {
|
||||
ImageInputFieldTemplate,
|
||||
ImageInputFieldValue,
|
||||
} from 'features/nodes/types/types';
|
||||
import { DragEvent, memo, useCallback, useState } from 'react';
|
||||
import { memo, useCallback } from 'react';
|
||||
|
||||
import { FieldComponentProps } from './types';
|
||||
import IAISelectableImage from 'features/controlNet/components/parameters/IAISelectableImage';
|
||||
import { ImageDTO } from 'services/api';
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
|
||||
const ImageInputFieldComponent = (
|
||||
props: FieldComponentProps<ImageInputFieldValue, ImageInputFieldTemplate>
|
||||
) => {
|
||||
const { nodeId, field } = props;
|
||||
|
||||
const getImageByName = useGetImageByName();
|
||||
const dispatch = useAppDispatch();
|
||||
const [url, setUrl] = useState<string | undefined>(field.value?.image_url);
|
||||
const { getUrl } = useGetUrl();
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(e: DragEvent<HTMLDivElement>) => {
|
||||
const name = e.dataTransfer.getData('invokeai/imageName');
|
||||
const image = getImageByName(name);
|
||||
|
||||
if (!image) {
|
||||
return;
|
||||
}
|
||||
|
||||
setUrl(image.image_url);
|
||||
|
||||
const handleChange = useCallback(
|
||||
(image: ImageDTO) => {
|
||||
dispatch(
|
||||
fieldValueChanged({
|
||||
nodeId,
|
||||
@ -42,13 +29,20 @@ const ImageInputFieldComponent = (
|
||||
})
|
||||
);
|
||||
},
|
||||
[getImageByName, dispatch, field.name, nodeId]
|
||||
[dispatch, field.name, nodeId]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box onDrop={handleDrop}>
|
||||
<Image src={getUrl(url)} fallback={<SelectImagePlaceholder />} />
|
||||
</Box>
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<IAISelectableImage image={field.value} onChange={handleChange} />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -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'
|
||||
);
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
} from '@chakra-ui/react';
|
||||
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIInput from 'common/components/IAIInput';
|
||||
import IAINumberInput from 'common/components/IAINumberInput';
|
||||
import React from 'react';
|
||||
@ -74,12 +74,12 @@ export default function AddCheckpointModel() {
|
||||
return (
|
||||
<VStack gap={2} alignItems="flex-start">
|
||||
<Flex columnGap={4}>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
isChecked={!addManually}
|
||||
label={t('modelManager.scanForModels')}
|
||||
onChange={() => setAddmanually(!addManually)}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('modelManager.addManually')}
|
||||
isChecked={addManually}
|
||||
onChange={() => setAddmanually(!addManually)}
|
||||
|
@ -24,7 +24,7 @@ import { useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import * as InvokeAI from 'app/types/invokeai';
|
||||
import IAISlider from 'common/components/IAISlider';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
|
||||
export default function MergeModels() {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -286,7 +286,7 @@ export default function MergeModels() {
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('modelManager.ignoreMismatch')}
|
||||
isChecked={modelMergeForce}
|
||||
onChange={(e) => setModelMergeForce(e.target.checked)}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import React from 'react';
|
||||
|
||||
@ -81,13 +81,13 @@ function SearchModelEntry({
|
||||
borderRadius={4}
|
||||
>
|
||||
<Flex gap={4} alignItems="center" justifyContent="space-between">
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
value={model.name}
|
||||
label={<Text fontWeight={500}>{model.name}</Text>}
|
||||
isChecked={modelsToAdd.includes(model.name)}
|
||||
isDisabled={existingModels.includes(model.location)}
|
||||
onChange={foundModelsChangeHandler}
|
||||
></IAICheckbox>
|
||||
></IAISimpleCheckbox>
|
||||
{existingModels.includes(model.location) && (
|
||||
<Badge colorScheme="accent">{t('modelManager.modelExists')}</Badge>
|
||||
)}
|
||||
@ -324,7 +324,7 @@ export default function SearchModels() {
|
||||
>
|
||||
{t('modelManager.deselectAll')}
|
||||
</IAIButton>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('modelManager.showExisting')}
|
||||
isChecked={shouldShowExistingModelsInSearch}
|
||||
onChange={() =>
|
||||
|
@ -8,6 +8,7 @@ import ParamNoiseCollapse from 'features/parameters/components/Parameters/Noise/
|
||||
import ParamSymmetryCollapse from 'features/parameters/components/Parameters/Symmetry/ParamSymmetryCollapse';
|
||||
import ParamSeamlessCollapse from 'features/parameters/components/Parameters/Seamless/ParamSeamlessCollapse';
|
||||
import ImageToImageTabCoreParameters from './ImageToImageTabCoreParameters';
|
||||
import ParamControlNetCollapse from 'features/parameters/components/Parameters/ControlNet/ParamControlNetCollapse';
|
||||
|
||||
const ImageToImageTabParameters = () => {
|
||||
return (
|
||||
@ -17,6 +18,7 @@ const ImageToImageTabParameters = () => {
|
||||
<ProcessButtons />
|
||||
<ImageToImageTabCoreParameters />
|
||||
<ParamSeedCollapse />
|
||||
<ParamControlNetCollapse />
|
||||
<ParamVariationCollapse />
|
||||
<ParamNoiseCollapse />
|
||||
<ParamSymmetryCollapse />
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -14,7 +14,7 @@ export default function UnifiedCanvasDarkenOutsideSelection() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.betaDarkenOutside')}
|
||||
isChecked={shouldDarkenOutsideBoundingBox}
|
||||
onChange={(e) =>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setIsMaskEnabled } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -16,7 +16,7 @@ export default function UnifiedCanvasEnableMask() {
|
||||
dispatch(setIsMaskEnabled(!isMaskEnabled));
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={`${t('unifiedCanvas.enableMask')} (H)`}
|
||||
isChecked={isMaskEnabled}
|
||||
onChange={handleToggleEnableMask}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldRestrictStrokesToBox } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -14,7 +14,7 @@ export default function UnifiedCanvasLimitStrokesToBox() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.betaLimitToBox')}
|
||||
isChecked={shouldRestrictStrokesToBox}
|
||||
onChange={(e) =>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldPreserveMaskedArea } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -13,7 +13,7 @@ export default function UnifiedCanvasPreserveMask() {
|
||||
);
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.betaPreserveMasked')}
|
||||
isChecked={shouldPreserveMaskedArea}
|
||||
onChange={(e) => dispatch(setShouldPreserveMaskedArea(e.target.checked))}
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
import IAIPopover from 'common/components/IAIPopover';
|
||||
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
|
||||
@ -73,33 +73,33 @@ const UnifiedCanvasSettings = () => {
|
||||
}
|
||||
>
|
||||
<Flex direction="column" gap={2}>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showIntermediates')}
|
||||
isChecked={shouldShowIntermediates}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldShowIntermediates(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.autoSaveToGallery')}
|
||||
isChecked={shouldAutoSave}
|
||||
onChange={(e) => dispatch(setShouldAutoSave(e.target.checked))}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.saveBoxRegionOnly')}
|
||||
isChecked={shouldCropToBoundingBoxOnSave}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldCropToBoundingBoxOnSave(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showCanvasDebugInfo')}
|
||||
isChecked={shouldShowCanvasDebugInfo}
|
||||
onChange={(e) =>
|
||||
dispatch(setShouldShowCanvasDebugInfo(e.target.checked))
|
||||
}
|
||||
/>
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.antialiasing')}
|
||||
isChecked={shouldAntialias}
|
||||
onChange={(e) => dispatch(setShouldAntialias(e.target.checked))}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldShowGrid } from 'features/canvas/store/canvasSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
@ -13,7 +13,7 @@ export default function UnifiedCanvasShowGrid() {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={t('unifiedCanvas.showGrid')}
|
||||
isChecked={shouldShowGrid}
|
||||
onChange={(e) => dispatch(setShouldShowGrid(e.target.checked))}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { RootState } from 'app/store/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import IAICheckbox from 'common/components/IAICheckbox';
|
||||
import IAISimpleCheckbox from 'common/components/IAISimpleCheckbox';
|
||||
import { setShouldSnapToGrid } from 'features/canvas/store/canvasSlice';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
@ -17,7 +17,7 @@ export default function UnifiedCanvasSnapToGrid() {
|
||||
dispatch(setShouldSnapToGrid(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAICheckbox
|
||||
<IAISimpleCheckbox
|
||||
label={`${t('unifiedCanvas.snapToGrid')} (N)`}
|
||||
isChecked={shouldSnapToGrid}
|
||||
onChange={handleChangeShouldSnapToGrid}
|
||||
|
@ -10,8 +10,25 @@ import {
|
||||
ImageField,
|
||||
LatentsOutput,
|
||||
ResourceOrigin,
|
||||
ImageDTO,
|
||||
} from 'services/api';
|
||||
|
||||
export const isImageDTO = (obj: unknown): obj is ImageDTO => {
|
||||
return (
|
||||
isObject(obj) &&
|
||||
'image_name' in obj &&
|
||||
isString(obj?.image_name) &&
|
||||
'thumbnail_url' in obj &&
|
||||
isString(obj?.thumbnail_url) &&
|
||||
'image_url' in obj &&
|
||||
isString(obj?.image_url) &&
|
||||
'image_origin' in obj &&
|
||||
isString(obj?.image_origin) &&
|
||||
'created_at' in obj &&
|
||||
isString(obj?.created_at)
|
||||
);
|
||||
};
|
||||
|
||||
export const isImageOutput = (
|
||||
output: GraphExecutionState['results'][string]
|
||||
): output is ImageOutput => output.type === 'image_output';
|
||||
|
@ -937,6 +937,29 @@
|
||||
gonzales-pe "^4.3.0"
|
||||
node-source-walk "^5.0.1"
|
||||
|
||||
"@dnd-kit/accessibility@^3.0.0":
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz#3ccbefdfca595b0a23a5dc57d3de96bc6935641c"
|
||||
integrity sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@dnd-kit/core@^6.0.8":
|
||||
version "6.0.8"
|
||||
resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.0.8.tgz#040ae13fea9787ee078e5f0361f3b49b07f3f005"
|
||||
integrity sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==
|
||||
dependencies:
|
||||
"@dnd-kit/accessibility" "^3.0.0"
|
||||
"@dnd-kit/utilities" "^3.2.1"
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@dnd-kit/utilities@^3.2.1":
|
||||
version "3.2.1"
|
||||
resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.1.tgz#53f9e2016fd2506ec49e404c289392cfff30332a"
|
||||
integrity sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==
|
||||
dependencies:
|
||||
tslib "^2.0.0"
|
||||
|
||||
"@emotion/babel-plugin@^11.10.8":
|
||||
version "11.10.8"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.10.8.tgz#bae325c902937665d00684038fd5294223ef9e1d"
|
||||
|
Loading…
Reference in New Issue
Block a user