feat(ui): improve controlnet image style

css is terrible
This commit is contained in:
psychedelicious 2023-06-07 15:42:17 +10:00
parent 0843028e6e
commit 6c2b39d1df
4 changed files with 78 additions and 37 deletions

View File

@ -3,7 +3,6 @@ import {
DragEndEvent, DragEndEvent,
DragOverlay, DragOverlay,
DragStartEvent, DragStartEvent,
KeyboardSensor,
MouseSensor, MouseSensor,
TouchSensor, TouchSensor,
pointerWithin, pointerWithin,
@ -15,6 +14,7 @@ import OverlayDragImage from './OverlayDragImage';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api';
import { isImageDTO } from 'services/types/guards'; import { isImageDTO } from 'services/types/guards';
import { snapCenterToCursor } from '@dnd-kit/modifiers'; import { snapCenterToCursor } from '@dnd-kit/modifiers';
import { AnimatePresence, motion } from 'framer-motion';
type ImageDndContextProps = PropsWithChildren; type ImageDndContextProps = PropsWithChildren;
@ -62,7 +62,25 @@ const ImageDndContext = (props: ImageDndContextProps) => {
> >
{props.children} {props.children}
<DragOverlay dropAnimation={null} modifiers={[snapCenterToCursor]}> <DragOverlay dropAnimation={null} modifiers={[snapCenterToCursor]}>
{draggedImage && <OverlayDragImage image={draggedImage} />} <AnimatePresence>
{draggedImage && (
<motion.div
layout
key="overlay-drag-image"
initial={{
opacity: 0,
scale: 0.7,
}}
animate={{
opacity: 1,
scale: 1,
transition: { duration: 0.1 },
}}
>
<OverlayDragImage image={draggedImage} />
</motion.div>
)}
</AnimatePresence>
</DragOverlay> </DragOverlay>
</DndContext> </DndContext>
); );

View File

@ -1,4 +1,11 @@
import { Box, Flex, Icon, IconButtonProps, Image } from '@chakra-ui/react'; import {
Box,
ChakraProps,
Flex,
Icon,
IconButtonProps,
Image,
} from '@chakra-ui/react';
import { useDraggable, useDroppable } from '@dnd-kit/core'; import { useDraggable, useDroppable } from '@dnd-kit/core';
import { useCombinedRefs } from '@dnd-kit/utilities'; import { useCombinedRefs } from '@dnd-kit/utilities';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
@ -31,6 +38,7 @@ type IAIDndImageProps = {
payloadImage?: ImageDTO | null | undefined; payloadImage?: ImageDTO | null | undefined;
minSize?: number; minSize?: number;
postUploadAction?: PostUploadAction; postUploadAction?: PostUploadAction;
imageSx?: ChakraProps['sx'];
}; };
const IAIDndImage = (props: IAIDndImageProps) => { const IAIDndImage = (props: IAIDndImageProps) => {
@ -49,6 +57,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
payloadImage, payloadImage,
minSize = 24, minSize = 24,
postUploadAction, postUploadAction,
imageSx,
} = props; } = props;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const dndId = useRef(uuidv4()); const dndId = useRef(uuidv4());
@ -56,7 +65,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
const { const {
isOver, isOver,
setNodeRef: setDroppableRef, setNodeRef: setDroppableRef,
active, active: isDropActive,
} = useDroppable({ } = useDroppable({
id: dndId.current, id: dndId.current,
disabled: isDropDisabled, disabled: isDropDisabled,
@ -69,6 +78,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
attributes, attributes,
listeners, listeners,
setNodeRef: setDraggableRef, setNodeRef: setDraggableRef,
isDragging,
} = useDraggable({ } = useDraggable({
id: dndId.current, id: dndId.current,
data: { data: {
@ -84,8 +94,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
return; return;
} }
console.log(postUploadAction);
dispatch( dispatch(
imageUploaded({ imageUploaded({
formData: { file }, formData: { file },
@ -141,7 +149,6 @@ const IAIDndImage = (props: IAIDndImageProps) => {
sx={{ sx={{
w: 'full', w: 'full',
h: 'full', h: 'full',
position: 'relative',
alignItems: 'center', alignItems: 'center',
justifyContent: 'center', justifyContent: 'center',
}} }}
@ -157,6 +164,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
maxW: 'full', maxW: 'full',
maxH: 'full', maxH: 'full',
borderRadius: 'base', borderRadius: 'base',
...imageSx,
}} }}
/> />
{withMetadataOverlay && <ImageMetadataOverlay image={image} />} {withMetadataOverlay && <ImageMetadataOverlay image={image} />}
@ -179,7 +187,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
</Box> </Box>
)} )}
<AnimatePresence> <AnimatePresence>
{active && <IAIDropOverlay isOver={isOver} />} {isDropActive && <IAIDropOverlay isOver={isOver} />}
</AnimatePresence> </AnimatePresence>
</Flex> </Flex>
)} )}
@ -209,7 +217,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
/> />
</Flex> </Flex>
<AnimatePresence> <AnimatePresence>
{active && <IAIDropOverlay isOver={isOver} />} {isDropActive && <IAIDropOverlay isOver={isOver} />}
</AnimatePresence> </AnimatePresence>
</> </>
)} )}

View File

@ -20,6 +20,8 @@ import {
Tab, Tab,
TabPanel, TabPanel,
Box, Box,
VStack,
ChakraProps,
} from '@chakra-ui/react'; } from '@chakra-ui/react';
import { FaCopy, FaPlus, FaTrash, FaWrench } from 'react-icons/fa'; import { FaCopy, FaPlus, FaTrash, FaWrench } from 'react-icons/fa';
@ -35,6 +37,8 @@ import IAIButton from 'common/components/IAIButton';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons'; import { ChevronDownIcon, ChevronUpIcon } from '@chakra-ui/icons';
const expandedControlImageSx: ChakraProps['sx'] = { maxH: 96 };
type ControlNetProps = { type ControlNetProps = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
}; };
@ -53,7 +57,7 @@ const ControlNet = (props: ControlNetProps) => {
processorType, processorType,
} = props.controlNet; } = props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const [shouldShowAdvanced, onToggleAdvanced] = useToggle(false); const [isExpanded, toggleIsExpanded] = useToggle(false);
const handleDelete = useCallback(() => { const handleDelete = useCallback(() => {
dispatch(controlNetRemoved({ controlNetId })); dispatch(controlNetRemoved({ controlNetId }));
@ -116,16 +120,14 @@ const ControlNet = (props: ControlNetProps) => {
<IAIIconButton <IAIIconButton
size="sm" size="sm"
aria-label="Expand" aria-label="Expand"
onClick={onToggleAdvanced} onClick={toggleIsExpanded}
variant="link" variant="link"
icon={ icon={
<ChevronUpIcon <ChevronUpIcon
sx={{ sx={{
boxSize: 4, boxSize: 4,
color: 'base.300', color: 'base.300',
transform: shouldShowAdvanced transform: isExpanded ? 'rotate(0deg)' : 'rotate(180deg)',
? 'rotate(0deg)'
: 'rotate(180deg)',
transitionProperty: 'common', transitionProperty: 'common',
transitionDuration: 'normal', transitionDuration: 'normal',
}} }}
@ -135,7 +137,7 @@ const ControlNet = (props: ControlNetProps) => {
</Flex> </Flex>
{isEnabled && ( {isEnabled && (
<> <>
<Flex sx={{ gap: 4 }}> <Flex sx={{ gap: 4, w: 'full' }}>
<Flex <Flex
sx={{ sx={{
flexDir: 'column', flexDir: 'column',
@ -143,7 +145,7 @@ const ControlNet = (props: ControlNetProps) => {
w: 'full', w: 'full',
h: 24, h: 24,
paddingInlineStart: 1, paddingInlineStart: 1,
paddingInlineEnd: shouldShowAdvanced ? 1 : 0, paddingInlineEnd: isExpanded ? 1 : 0,
pb: 2, pb: 2,
justifyContent: 'space-between', justifyContent: 'space-between',
}} }}
@ -160,7 +162,7 @@ const ControlNet = (props: ControlNetProps) => {
mini mini
/> />
</Flex> </Flex>
{!shouldShowAdvanced && ( {!isExpanded && (
<Flex <Flex
sx={{ sx={{
alignItems: 'center', alignItems: 'center',
@ -174,10 +176,13 @@ const ControlNet = (props: ControlNetProps) => {
</Flex> </Flex>
)} )}
</Flex> </Flex>
{shouldShowAdvanced && ( {isExpanded && (
<> <>
<Box pt={2}> <Box mt={2}>
<ControlNetImagePreview controlNet={props.controlNet} /> <ControlNetImagePreview
controlNet={props.controlNet}
imageSx={expandedControlImageSx}
/>
</Box> </Box>
<ParamControlNetProcessorSelect <ParamControlNetProcessorSelect
controlNetId={controlNetId} controlNetId={controlNetId}

View File

@ -1,4 +1,4 @@
import { memo, useCallback, useRef } from 'react'; import { memo, useCallback, useState } from 'react';
import { ImageDTO } from 'services/api'; import { ImageDTO } from 'services/api';
import { import {
ControlNetConfig, ControlNetConfig,
@ -6,13 +6,14 @@ import {
controlNetSelector, controlNetSelector,
} from '../store/controlNetSlice'; } from '../store/controlNetSlice';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { Box } from '@chakra-ui/react'; import { Box, ChakraProps, Flex } from '@chakra-ui/react';
import IAIDndImage from 'common/components/IAIDndImage'; import IAIDndImage from 'common/components/IAIDndImage';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions'; import { defaultSelectorOptions } from 'app/store/util/defaultMemoizeOptions';
import { AnimatePresence, motion } from 'framer-motion'; import { AnimatePresence, motion } from 'framer-motion';
import { IAIImageFallback } from 'common/components/IAIImageFallback'; import { IAIImageFallback } from 'common/components/IAIImageFallback';
import { useHoverDirty } from 'react-use'; import IAIIconButton from 'common/components/IAIIconButton';
import { FaUndo } from 'react-icons/fa';
const selector = createSelector( const selector = createSelector(
controlNetSelector, controlNetSelector,
@ -25,22 +26,24 @@ const selector = createSelector(
type Props = { type Props = {
controlNet: ControlNetConfig; controlNet: ControlNetConfig;
imageSx?: ChakraProps['sx'];
}; };
const ControlNetImagePreview = (props: Props) => { const ControlNetImagePreview = (props: Props) => {
const { imageSx } = props;
const { controlNetId, controlImage, processedControlImage, processorType } = const { controlNetId, controlImage, processedControlImage, processorType } =
props.controlNet; props.controlNet;
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { pendingControlImages } = useAppSelector(selector); const { pendingControlImages } = useAppSelector(selector);
const containerRef = useRef<HTMLDivElement>(null);
const isMouseOverImage = useHoverDirty(containerRef); const [isMouseOverImage, setIsMouseOverImage] = useState(false);
const handleDrop = useCallback( const handleDrop = useCallback(
(droppedImage: ImageDTO) => { (droppedImage: ImageDTO) => {
if (controlImage?.image_name === droppedImage.image_name) { if (controlImage?.image_name === droppedImage.image_name) {
return; return;
} }
setIsMouseOverImage(false);
dispatch( dispatch(
controlNetImageChanged({ controlNetId, controlImage: droppedImage }) controlNetImageChanged({ controlNetId, controlImage: droppedImage })
); );
@ -48,6 +51,14 @@ const ControlNetImagePreview = (props: Props) => {
[controlImage, controlNetId, dispatch] [controlImage, controlNetId, dispatch]
); );
const handleMouseEnter = useCallback(() => {
setIsMouseOverImage(true);
}, []);
const handleMouseLeave = useCallback(() => {
setIsMouseOverImage(false);
}, []);
const shouldShowProcessedImageBackdrop = const shouldShowProcessedImageBackdrop =
Number(controlImage?.width) > Number(processedControlImage?.width) || Number(controlImage?.width) > Number(processedControlImage?.width) ||
Number(controlImage?.height) > Number(processedControlImage?.height); Number(controlImage?.height) > Number(processedControlImage?.height);
@ -61,8 +72,9 @@ const ControlNetImagePreview = (props: Props) => {
return ( return (
<Box <Box
ref={containerRef} onMouseEnter={handleMouseEnter}
sx={{ position: 'relative', w: 'full', h: 'full', aspectRatio: '1/1' }} onMouseLeave={handleMouseLeave}
sx={{ position: 'relative', w: 'full', h: 'full' }}
> >
<IAIDndImage <IAIDndImage
image={controlImage} image={controlImage}
@ -72,10 +84,12 @@ const ControlNetImagePreview = (props: Props) => {
)} )}
isUploadDisabled={Boolean(controlImage)} isUploadDisabled={Boolean(controlImage)}
postUploadAction={{ type: 'SET_CONTROLNET_IMAGE', controlNetId }} postUploadAction={{ type: 'SET_CONTROLNET_IMAGE', controlNetId }}
imageSx={imageSx}
/> />
<AnimatePresence> <AnimatePresence>
{shouldShowProcessedImage && ( {shouldShowProcessedImage && (
<motion.div <motion.div
style={{ width: '100%' }}
initial={{ initial={{
opacity: 0, opacity: 0,
}} }}
@ -88,18 +102,13 @@ const ControlNetImagePreview = (props: Props) => {
transition: { duration: 0.1 }, transition: { duration: 0.1 },
}} }}
> >
<Box <>
sx={{
position: 'absolute',
w: 'full',
h: 'full',
top: 0,
insetInlineStart: 0,
}}
>
{shouldShowProcessedImageBackdrop && ( {shouldShowProcessedImageBackdrop && (
<Box <Box
sx={{ sx={{
position: 'absolute',
top: 0,
insetInlineStart: 0,
w: 'full', w: 'full',
h: 'full', h: 'full',
bg: 'base.900', bg: 'base.900',
@ -121,9 +130,10 @@ const ControlNetImagePreview = (props: Props) => {
onDrop={handleDrop} onDrop={handleDrop}
payloadImage={controlImage} payloadImage={controlImage}
isUploadDisabled={true} isUploadDisabled={true}
imageSx={imageSx}
/> />
</Box> </Box>
</Box> </>
</motion.div> </motion.div>
)} )}
</AnimatePresence> </AnimatePresence>