mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip img2img ui
This commit is contained in:
parent
38474fa9d4
commit
568f0aad71
@ -97,7 +97,8 @@
|
||||
"statusMergedModels": "Models Merged",
|
||||
"pinOptionsPanel": "Pin Options Panel",
|
||||
"loading": "Loading",
|
||||
"loadingInvokeAI": "Loading Invoke AI"
|
||||
"loadingInvokeAI": "Loading Invoke AI",
|
||||
"random": "Random"
|
||||
},
|
||||
"gallery": {
|
||||
"generations": "Generations",
|
||||
|
@ -34,10 +34,9 @@ const IAISwitch = (props: Props) => {
|
||||
display="flex"
|
||||
gap={4}
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
{...formControlProps}
|
||||
>
|
||||
<FormLabel my={1} {...formLabelProps}>
|
||||
<FormLabel my={1} flexGrow={1} {...formLabelProps}>
|
||||
{label}
|
||||
</FormLabel>
|
||||
<Switch {...rest} />
|
||||
|
@ -37,28 +37,6 @@ const ImageToImageOverlay = ({
|
||||
position: 'absolute',
|
||||
}}
|
||||
>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: 0,
|
||||
right: 0,
|
||||
p: 2,
|
||||
}}
|
||||
>
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
isDisabled={!isImageToImageEnabled}
|
||||
icon={<FaUndo />}
|
||||
aria-label={t('accessibility.reset')}
|
||||
onClick={handleResetInitialImage}
|
||||
/>
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
isDisabled={!isImageToImageEnabled}
|
||||
icon={<FaUpload />}
|
||||
aria-label={t('common.upload')}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
|
@ -0,0 +1,62 @@
|
||||
import {
|
||||
Box,
|
||||
ButtonGroup,
|
||||
Collapse,
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
Image,
|
||||
Spacer,
|
||||
Text,
|
||||
useDisclosure,
|
||||
VStack,
|
||||
} from '@chakra-ui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import ImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageFit';
|
||||
import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaUndo, FaUpload } from 'react-icons/fa';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import { RootState } from 'app/store';
|
||||
import { useCallback } from 'react';
|
||||
import { clearInitialImage } from 'features/parameters/store/generationSlice';
|
||||
|
||||
const ImageToImageSettingsHeader = () => {
|
||||
const isImageToImageEnabled = useAppSelector(
|
||||
(state: RootState) => state.generation.isImageToImageEnabled
|
||||
);
|
||||
const dispatch = useAppDispatch();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const handleResetInitialImage = useCallback(() => {
|
||||
dispatch(clearInitialImage());
|
||||
}, [dispatch]);
|
||||
|
||||
return (
|
||||
<Flex w="full" alignItems="center">
|
||||
<Text size="sm" fontWeight={500} color="base.300">
|
||||
Image to Image
|
||||
</Text>
|
||||
<Spacer />
|
||||
<ButtonGroup>
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
icon={<FaUndo />}
|
||||
aria-label={t('accessibility.reset')}
|
||||
onClick={handleResetInitialImage}
|
||||
/>
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
icon={<FaUpload />}
|
||||
aria-label={t('common.upload')}
|
||||
/>
|
||||
</ButtonGroup>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default ImageToImageSettingsHeader;
|
@ -3,7 +3,17 @@ import { FaImage } from 'react-icons/fa';
|
||||
|
||||
const SelectImagePlaceholder = () => {
|
||||
return (
|
||||
<Flex sx={{ h: 36, alignItems: 'center', justifyContent: 'center' }}>
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
bg: 'base.800',
|
||||
borderRadius: 'base',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
aspectRatio: '1/1',
|
||||
}}
|
||||
>
|
||||
<Icon color="base.400" boxSize={32} as={FaImage}></Icon>
|
||||
</Flex>
|
||||
);
|
||||
|
@ -1,7 +1,14 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { isEqual } from 'lodash';
|
||||
|
||||
import { ButtonGroup, Flex, FlexProps, Link, useToast } from '@chakra-ui/react';
|
||||
import {
|
||||
ButtonGroup,
|
||||
Flex,
|
||||
FlexProps,
|
||||
FormControl,
|
||||
Link,
|
||||
useToast,
|
||||
} from '@chakra-ui/react';
|
||||
import { runESRGAN, runFacetool } from 'app/socketio/actions';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
@ -446,6 +453,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
<IAIPopover
|
||||
triggerComponent={
|
||||
<IAIIconButton
|
||||
isDisabled={!selectedImage}
|
||||
aria-label={`${t('parameters.sendTo')}...`}
|
||||
icon={<FaShareAlt />}
|
||||
/>
|
||||
@ -487,7 +495,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
|
||||
{t('parameters.copyImageToLink')}
|
||||
</IAIButton>
|
||||
|
||||
<Link download={true} href={getUrl(selectedImage?.url)}>
|
||||
<Link download={true} href={getUrl(selectedImage?.url ?? '')}>
|
||||
<IAIButton leftIcon={<FaDownload />} size="sm" w="100%">
|
||||
{t('parameters.downloadImage')}
|
||||
</IAIButton>
|
||||
|
@ -1,8 +1,17 @@
|
||||
import { Flex, Icon } from '@chakra-ui/react';
|
||||
import { Box, Collapse, Flex, Icon, useDisclosure } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { systemSelector } from 'features/system/store/systemSelectors';
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings';
|
||||
import { isEqual } from 'lodash';
|
||||
import { useState } from 'react';
|
||||
import {
|
||||
AnimatePresence,
|
||||
motion,
|
||||
useMotionValue,
|
||||
useTransform,
|
||||
} from 'framer-motion';
|
||||
|
||||
import { MdPhoto } from 'react-icons/md';
|
||||
import {
|
||||
@ -33,31 +42,38 @@ export const currentImageDisplaySelector = createSelector(
|
||||
*/
|
||||
const CurrentImageDisplay = () => {
|
||||
const { hasAnImageToDisplay } = useAppSelector(currentImageDisplaySelector);
|
||||
const [shouldHideImageToImage, setShouldHideImageToImage] = useState(false);
|
||||
const w = useMotionValue(0);
|
||||
const width = useTransform(w, [0, 100], [`0px`, `100px`]);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
sx={{
|
||||
position: 'relative',
|
||||
flexDirection: 'column',
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
rowGap: 4,
|
||||
borderRadius: 'base',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
{hasAnImageToDisplay ? (
|
||||
<>
|
||||
<CurrentImageButtons />
|
||||
<Box sx={{ position: 'absolute', top: 0 }}>
|
||||
<CurrentImageButtons />
|
||||
</Box>
|
||||
<Flex
|
||||
sx={{
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: 4,
|
||||
}}
|
||||
>
|
||||
{hasAnImageToDisplay ? (
|
||||
<CurrentImagePreview />
|
||||
</>
|
||||
) : (
|
||||
<Flex
|
||||
sx={{
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
) : (
|
||||
<Icon
|
||||
as={MdPhoto}
|
||||
sx={{
|
||||
@ -65,8 +81,8 @@ const CurrentImageDisplay = () => {
|
||||
color: 'base.500',
|
||||
}}
|
||||
/>
|
||||
</Flex>
|
||||
)}
|
||||
)}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
@ -27,7 +27,7 @@ export default function InvokeAccordionItem({
|
||||
{header}
|
||||
</Box>
|
||||
{additionalHeaderComponents}
|
||||
{feature && <GuideIcon feature={feature} />}
|
||||
{/* {feature && <GuideIcon feature={feature} />} */}
|
||||
<AccordionIcon />
|
||||
</Flex>
|
||||
</AccordionButton>
|
||||
|
@ -12,10 +12,6 @@ export default function ImageFit() {
|
||||
(state: RootState) => state.generation.shouldFitToWidthHeight
|
||||
);
|
||||
|
||||
const isImageToImageEnabled = useAppSelector(
|
||||
(state: RootState) => state.generation.isImageToImageEnabled
|
||||
);
|
||||
|
||||
const handleChangeFit = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
dispatch(setShouldFitToWidthHeight(e.target.checked));
|
||||
|
||||
@ -23,7 +19,6 @@ export default function ImageFit() {
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
isDisabled={!isImageToImageEnabled}
|
||||
label={t('parameters.imageFit')}
|
||||
isChecked={shouldFitToWidthHeight}
|
||||
onChange={handleChangeFit}
|
||||
|
@ -1,14 +1,33 @@
|
||||
import { Flex, Image, VStack } from '@chakra-ui/react';
|
||||
import {
|
||||
Box,
|
||||
ButtonGroup,
|
||||
Collapse,
|
||||
Flex,
|
||||
Heading,
|
||||
HStack,
|
||||
Image,
|
||||
Spacer,
|
||||
useDisclosure,
|
||||
VStack,
|
||||
} from '@chakra-ui/react';
|
||||
import { motion } from 'framer-motion';
|
||||
|
||||
import IAIButton from 'common/components/IAIButton';
|
||||
import ImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageFit';
|
||||
import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength';
|
||||
import IAIIconButton from 'common/components/IAIIconButton';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import InitialImagePreview from './InitialImagePreview';
|
||||
import { useState } from 'react';
|
||||
import { FaUndo, FaUpload } from 'react-icons/fa';
|
||||
import ImageToImageSettingsHeader from 'common/components/ImageToImageSettingsHeader';
|
||||
|
||||
export default function ImageToImageSettings() {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<VStack gap={2} alignItems="stretch">
|
||||
<VStack gap={2} w="full" alignItems="stretch">
|
||||
<ImageToImageSettingsHeader />
|
||||
<InitialImagePreview />
|
||||
<ImageToImageStrength label={t('parameters.img2imgStrength')} />
|
||||
<ImageFit />
|
||||
|
@ -3,12 +3,15 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { isImageToImageEnabledChanged } from 'features/parameters/store/generationSlice';
|
||||
import { ChangeEvent } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export default function ImageToImageToggle() {
|
||||
const isImageToImageEnabled = useAppSelector(
|
||||
(state: RootState) => state.generation.isImageToImageEnabled
|
||||
);
|
||||
|
||||
const { t } = useTranslation();
|
||||
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
|
||||
@ -16,6 +19,7 @@ export default function ImageToImageToggle() {
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label={t('common.img2img')}
|
||||
isChecked={isImageToImageEnabled}
|
||||
width="auto"
|
||||
onChange={handleChange}
|
||||
|
@ -87,47 +87,44 @@ const InitialImagePreview = () => {
|
||||
}}
|
||||
onDrop={handleDrop}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
height: 'full',
|
||||
width: 'full',
|
||||
opacity: isImageToImageEnabled ? 1 : 0.5,
|
||||
filter: isImageToImageEnabled ? 'none' : 'auto',
|
||||
blur: '5px',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{initialImage?.url && (
|
||||
<>
|
||||
<Image
|
||||
sx={{
|
||||
fit: 'contain',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
src={getUrl(initialImage?.url)}
|
||||
onError={onError}
|
||||
onLoad={() => {
|
||||
setIsLoaded(true);
|
||||
}}
|
||||
fallback={
|
||||
<Flex
|
||||
sx={{ h: 36, alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
<Spinner color="grey" w="5rem" h="5rem" />
|
||||
</Flex>
|
||||
}
|
||||
{initialImage?.url && (
|
||||
<Box
|
||||
sx={{
|
||||
height: 'full',
|
||||
width: 'full',
|
||||
opacity: isImageToImageEnabled ? 1 : 0.5,
|
||||
filter: isImageToImageEnabled ? 'none' : 'auto',
|
||||
blur: '5px',
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
sx={{
|
||||
fit: 'contain',
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
src={getUrl(initialImage?.url)}
|
||||
onError={onError}
|
||||
onLoad={() => {
|
||||
setIsLoaded(true);
|
||||
}}
|
||||
fallback={
|
||||
<Flex
|
||||
sx={{ h: 36, alignItems: 'center', justifyContent: 'center' }}
|
||||
>
|
||||
<Spinner color="grey" w="5rem" h="5rem" />
|
||||
</Flex>
|
||||
}
|
||||
/>
|
||||
{isLoaded && (
|
||||
<ImageToImageOverlay
|
||||
setIsLoaded={setIsLoaded}
|
||||
image={initialImage}
|
||||
/>
|
||||
{isLoaded && (
|
||||
<ImageToImageOverlay
|
||||
setIsLoaded={setIsLoaded}
|
||||
image={initialImage}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{!initialImage?.url && <SelectImagePlaceholder />}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
)}
|
||||
{!initialImage?.url && <SelectImagePlaceholder />}
|
||||
{!isImageToImageEnabled && (
|
||||
<Flex
|
||||
sx={{
|
||||
|
@ -5,6 +5,7 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Switch } from '@chakra-ui/react';
|
||||
|
||||
export default function RandomizeSeed() {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -18,8 +19,8 @@ export default function RandomizeSeed() {
|
||||
dispatch(setShouldRandomizeSeed(e.target.checked));
|
||||
|
||||
return (
|
||||
<IAISwitch
|
||||
label={t('parameters.randomizeSeed')}
|
||||
<Switch
|
||||
aria-label={t('parameters.randomizeSeed')}
|
||||
isChecked={shouldRandomizeSeed}
|
||||
onChange={handleChangeShouldRandomizeSeed}
|
||||
/>
|
||||
|
@ -10,7 +10,6 @@ import Threshold from './Threshold';
|
||||
const SeedSettings = () => {
|
||||
return (
|
||||
<VStack gap={2} alignItems="stretch">
|
||||
<RandomizeSeed />
|
||||
<Seed />
|
||||
<Threshold />
|
||||
<Perlin />
|
||||
|
@ -3,8 +3,10 @@ import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
|
||||
import { RootState } from 'app/store';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import randomInt from 'common/util/randomInt';
|
||||
import { IAIIconButton } from 'exports';
|
||||
import { setSeed } from 'features/parameters/store/generationSlice';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { FaRandom } from 'react-icons/fa';
|
||||
|
||||
export default function ShuffleSeed() {
|
||||
const dispatch = useAppDispatch();
|
||||
@ -17,13 +19,20 @@ export default function ShuffleSeed() {
|
||||
dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)));
|
||||
|
||||
return (
|
||||
<Button
|
||||
<IAIIconButton
|
||||
size="sm"
|
||||
isDisabled={shouldRandomizeSeed}
|
||||
aria-label={t('parameters.shuffle')}
|
||||
tooltip={t('parameters.shuffle')}
|
||||
icon={<FaRandom />}
|
||||
onClick={handleClickRandomizeSeed}
|
||||
padding="0 1.5rem"
|
||||
>
|
||||
<p>{t('parameters.shuffle')}</p>
|
||||
</Button>
|
||||
/>
|
||||
// <Button
|
||||
// size="sm"
|
||||
// onClick={handleClickRandomizeSeed}
|
||||
// padding="0 1.5rem"
|
||||
// >
|
||||
// <p>{t('parameters.shuffle')}</p>
|
||||
// </Button>
|
||||
);
|
||||
}
|
||||
|
@ -0,0 +1,32 @@
|
||||
import { memo, useState } from 'react';
|
||||
import { AnimatePresence, motion } from 'framer-motion';
|
||||
|
||||
import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { RootState } from 'app/store';
|
||||
import { Box } from '@chakra-ui/react';
|
||||
|
||||
const AnimatedImageToImagePanel = () => {
|
||||
const isImageToImageEnabled = useAppSelector(
|
||||
(state: RootState) => state.generation.isImageToImageEnabled
|
||||
);
|
||||
|
||||
return (
|
||||
<AnimatePresence>
|
||||
{isImageToImageEnabled && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scaleX: 0, width: 0 }}
|
||||
animate={{ opacity: 1, scaleX: 1, width: '28rem' }}
|
||||
exit={{ opacity: 0, scaleX: 0, width: 0 }}
|
||||
transition={{ type: 'spring', bounce: 0, duration: 0.35 }}
|
||||
>
|
||||
<Box sx={{ h: 'full', w: 'full', pl: 4 }}>
|
||||
<ImageToImageSettings />
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(AnimatedImageToImagePanel);
|
@ -23,7 +23,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { ResourceKey } from 'i18next';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import NodeEditor from 'features/nodes/components/NodeEditor';
|
||||
import LinearWorkarea from './tabs/Linear/LinearWorkarea';
|
||||
import LinearWorkspace from './tabs/Linear/LinearWorkspace';
|
||||
import { FaImage } from 'react-icons/fa';
|
||||
|
||||
export interface InvokeTabInfo {
|
||||
@ -41,7 +41,7 @@ const buildTabs = (disabledTabs: InvokeTabName[]): InvokeTabInfo[] => {
|
||||
{
|
||||
id: 'linear',
|
||||
icon: <Icon as={FaImage} sx={tabIconStyles} />,
|
||||
workarea: <LinearWorkarea />,
|
||||
workarea: <LinearWorkspace />,
|
||||
},
|
||||
{
|
||||
id: 'unifiedCanvas',
|
||||
|
@ -0,0 +1,143 @@
|
||||
import { Box, Flex, useOutsideClick } from '@chakra-ui/react';
|
||||
import { Slide } from '@chakra-ui/react';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppDispatch, useAppSelector } from 'app/storeHooks';
|
||||
import { uiSelector } from 'features/ui/store/uiSelectors';
|
||||
import { isEqual } from 'lodash';
|
||||
import { memo, PropsWithChildren, useRef } from 'react';
|
||||
import PinParametersPanelButton from 'features/ui/components/PinParametersPanelButton';
|
||||
import {
|
||||
setShouldShowParametersPanel,
|
||||
toggleParametersPanel,
|
||||
togglePinParametersPanel,
|
||||
} from 'features/ui/store/uiSlice';
|
||||
import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
|
||||
import Scrollable from 'features/ui/components/common/Scrollable';
|
||||
import { useLangDirection } from 'features/ui/hooks/useDirection';
|
||||
import { useHotkeys } from 'react-hotkeys-hook';
|
||||
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
|
||||
import { generationSelector } from 'features/parameters/store/generationSelectors';
|
||||
import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel';
|
||||
|
||||
const parametersSlideSelector = createSelector(
|
||||
[uiSelector, generationSelector],
|
||||
(ui, generation) => {
|
||||
const { shouldPinParametersPanel, shouldShowParametersPanel } = ui;
|
||||
const { isImageToImageEnabled } = generation;
|
||||
|
||||
return {
|
||||
shouldPinParametersPanel,
|
||||
shouldShowParametersPanel,
|
||||
isImageToImageEnabled,
|
||||
};
|
||||
},
|
||||
{
|
||||
memoizeOptions: {
|
||||
resultEqualityCheck: isEqual,
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
type ParametersSlideProps = PropsWithChildren;
|
||||
|
||||
const ParametersSlide = (props: ParametersSlideProps) => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const {
|
||||
shouldShowParametersPanel,
|
||||
isImageToImageEnabled,
|
||||
shouldPinParametersPanel,
|
||||
} = useAppSelector(parametersSlideSelector);
|
||||
|
||||
const langDirection = useLangDirection();
|
||||
|
||||
const outsideClickRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const closeParametersPanel = () => {
|
||||
dispatch(setShouldShowParametersPanel(false));
|
||||
};
|
||||
|
||||
useOutsideClick({
|
||||
ref: outsideClickRef,
|
||||
handler: () => {
|
||||
closeParametersPanel();
|
||||
},
|
||||
enabled: shouldShowParametersPanel && !shouldPinParametersPanel,
|
||||
});
|
||||
|
||||
useHotkeys(
|
||||
'o',
|
||||
() => {
|
||||
dispatch(toggleParametersPanel());
|
||||
shouldPinParametersPanel && dispatch(requestCanvasRescale());
|
||||
},
|
||||
[shouldPinParametersPanel]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'esc',
|
||||
() => {
|
||||
dispatch(setShouldShowParametersPanel(false));
|
||||
},
|
||||
{
|
||||
enabled: () => !shouldPinParametersPanel,
|
||||
preventDefault: true,
|
||||
},
|
||||
[shouldPinParametersPanel]
|
||||
);
|
||||
|
||||
useHotkeys(
|
||||
'shift+o',
|
||||
() => {
|
||||
dispatch(togglePinParametersPanel());
|
||||
dispatch(requestCanvasRescale());
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
return (
|
||||
<Slide
|
||||
direction={langDirection === 'rtl' ? 'right' : 'left'}
|
||||
in={shouldShowParametersPanel}
|
||||
motionProps={{ initial: false }}
|
||||
style={{ zIndex: 99 }}
|
||||
>
|
||||
<Flex
|
||||
sx={{
|
||||
boxShadow: '0 0 4rem 0 rgba(0, 0, 0, 0.8)',
|
||||
pl: 4,
|
||||
py: 4,
|
||||
h: 'full',
|
||||
w: 'min',
|
||||
bg: 'base.900',
|
||||
borderInlineEndWidth: 4,
|
||||
borderInlineEndColor: 'base.800',
|
||||
}}
|
||||
>
|
||||
<Flex ref={outsideClickRef} position="relative" height="full" pr={4}>
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
width: '28rem',
|
||||
flexShrink: 0,
|
||||
}}
|
||||
>
|
||||
<Flex
|
||||
paddingTop={1.5}
|
||||
paddingBottom={4}
|
||||
justifyContent="space-between"
|
||||
alignItems="center"
|
||||
>
|
||||
<InvokeAILogoComponent />
|
||||
<PinParametersPanelButton />
|
||||
</Flex>
|
||||
<Scrollable>{props.children}</Scrollable>
|
||||
</Flex>
|
||||
<AnimatedImageToImagePanel />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Slide>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(ParametersSlide);
|
@ -1,10 +1,12 @@
|
||||
import { Flex } from '@chakra-ui/react';
|
||||
import { Feature } from 'app/features';
|
||||
import IAISwitch from 'common/components/IAISwitch';
|
||||
import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings';
|
||||
import ImageToImageToggle from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle';
|
||||
import OutputSettings from 'features/parameters/components/AdvancedParameters/Output/OutputSettings';
|
||||
import SymmetrySettings from 'features/parameters/components/AdvancedParameters/Output/SymmetrySettings';
|
||||
import SymmetryToggle from 'features/parameters/components/AdvancedParameters/Output/SymmetryToggle';
|
||||
import RandomizeSeed from 'features/parameters/components/AdvancedParameters/Seed/RandomizeSeed';
|
||||
import SeedSettings from 'features/parameters/components/AdvancedParameters/Seed/SeedSettings';
|
||||
import GenerateVariationsToggle from 'features/parameters/components/AdvancedParameters/Variations/GenerateVariations';
|
||||
import VariationsSettings from 'features/parameters/components/AdvancedParameters/Variations/VariationsSettings';
|
||||
@ -15,58 +17,75 @@ import ParametersAccordion, {
|
||||
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
|
||||
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
|
||||
import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
|
||||
import { memo } from 'react';
|
||||
import { memo, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
|
||||
|
||||
const LinearParameters = () => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const linearAccordions: ParametersAccordionItems = {
|
||||
general: {
|
||||
name: 'general',
|
||||
header: `${t('parameters.general')}`,
|
||||
feature: undefined,
|
||||
content: <MainSettings />,
|
||||
},
|
||||
seed: {
|
||||
name: 'seed',
|
||||
header: `${t('parameters.seed')}`,
|
||||
feature: Feature.SEED,
|
||||
content: <SeedSettings />,
|
||||
},
|
||||
imageToImage: {
|
||||
name: 'imageToImage',
|
||||
header: `${t('parameters.imageToImage')}`,
|
||||
feature: undefined,
|
||||
content: <ImageToImageSettings />,
|
||||
additionalHeaderComponents: <ImageToImageToggle />,
|
||||
},
|
||||
variations: {
|
||||
name: 'variations',
|
||||
header: `${t('parameters.variations')}`,
|
||||
feature: Feature.VARIATIONS,
|
||||
content: <VariationsSettings />,
|
||||
additionalHeaderComponents: <GenerateVariationsToggle />,
|
||||
},
|
||||
symmetry: {
|
||||
name: 'symmetry',
|
||||
header: `${t('parameters.symmetry')}`,
|
||||
content: <SymmetrySettings />,
|
||||
additionalHeaderComponents: <SymmetryToggle />,
|
||||
},
|
||||
other: {
|
||||
name: 'other',
|
||||
header: `${t('parameters.otherOptions')}`,
|
||||
feature: Feature.OTHER,
|
||||
content: <OutputSettings />,
|
||||
},
|
||||
};
|
||||
const linearAccordions: ParametersAccordionItems = useMemo(
|
||||
() => ({
|
||||
// general: {
|
||||
// name: 'general',
|
||||
// header: `${t('parameters.general')}`,
|
||||
// feature: undefined,
|
||||
// content: <MainSettings />,
|
||||
// },
|
||||
seed: {
|
||||
name: 'seed',
|
||||
header: `${t('parameters.seed')}`,
|
||||
feature: Feature.SEED,
|
||||
content: <SeedSettings />,
|
||||
additionalHeaderComponents: <RandomizeSeed />,
|
||||
},
|
||||
// imageToImage: {
|
||||
// name: 'imageToImage',
|
||||
// header: `${t('parameters.imageToImage')}`,
|
||||
// feature: undefined,
|
||||
// content: <ImageToImageSettings />,
|
||||
// additionalHeaderComponents: <ImageToImageToggle />,
|
||||
// },
|
||||
variations: {
|
||||
name: 'variations',
|
||||
header: `${t('parameters.variations')}`,
|
||||
feature: Feature.VARIATIONS,
|
||||
content: <VariationsSettings />,
|
||||
additionalHeaderComponents: <GenerateVariationsToggle />,
|
||||
},
|
||||
symmetry: {
|
||||
name: 'symmetry',
|
||||
header: `${t('parameters.symmetry')}`,
|
||||
content: <SymmetrySettings />,
|
||||
additionalHeaderComponents: <SymmetryToggle />,
|
||||
},
|
||||
other: {
|
||||
name: 'other',
|
||||
header: `${t('parameters.otherOptions')}`,
|
||||
feature: Feature.OTHER,
|
||||
content: <OutputSettings />,
|
||||
},
|
||||
}),
|
||||
[t]
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex flexDir="column" gap={2}>
|
||||
<PromptInput />
|
||||
<NegativePromptInput />
|
||||
<ProcessButtons />
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
gap: 2,
|
||||
bg: 'base.800',
|
||||
p: 4,
|
||||
borderRadius: 'base',
|
||||
}}
|
||||
>
|
||||
<MainSettings />
|
||||
<ImageToImageToggle />
|
||||
</Flex>
|
||||
<ParametersAccordion accordionItems={linearAccordions} />
|
||||
</Flex>
|
||||
);
|
||||
|
@ -1,11 +0,0 @@
|
||||
import InvokeWorkarea from 'features/ui/components/InvokeWorkarea';
|
||||
import LinearContent from './LinearContent';
|
||||
import LinearParameters from './LinearParameters';
|
||||
|
||||
export default function LinearWorkarea() {
|
||||
return (
|
||||
<InvokeWorkarea parametersPanelContent={<LinearParameters />}>
|
||||
<LinearContent />
|
||||
</InvokeWorkarea>
|
||||
);
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import { memo } from 'react';
|
||||
import LinearContent from './LinearContent';
|
||||
import LinearParameters from './LinearParameters';
|
||||
import PinParametersPanelButton from '../../PinParametersPanelButton';
|
||||
import { RootState } from 'app/store';
|
||||
import Scrollable from '../../common/Scrollable';
|
||||
import ParametersSlide from '../../common/ParametersSlide';
|
||||
import AnimatedImageToImagePanel from 'features/parameters/components/AnimatedImageToImagePanel';
|
||||
|
||||
const LinearWorkspace = () => {
|
||||
const shouldPinParametersPanel = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldPinParametersPanel
|
||||
);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
flexDirection={{ base: 'column-reverse', xl: 'row' }}
|
||||
w="full"
|
||||
h="full"
|
||||
gap={4}
|
||||
>
|
||||
{shouldPinParametersPanel ? (
|
||||
<Flex sx={{ flexDirection: 'row-reverse' }}>
|
||||
<AnimatedImageToImagePanel />
|
||||
<Flex
|
||||
sx={{
|
||||
flexDirection: 'column',
|
||||
width: '28rem',
|
||||
flexShrink: 0,
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
<Scrollable>
|
||||
<LinearParameters />
|
||||
</Scrollable>
|
||||
<PinParametersPanelButton
|
||||
sx={{ position: 'absolute', top: 0, insetInlineEnd: 0 }}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
) : (
|
||||
<ParametersSlide>
|
||||
<LinearParameters />
|
||||
</ParametersSlide>
|
||||
)}
|
||||
<LinearContent />
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(LinearWorkspace);
|
@ -1,26 +1,77 @@
|
||||
import { RootState } from 'app/store';
|
||||
// import { RootState } from 'app/store';
|
||||
// import { useAppSelector } from 'app/storeHooks';
|
||||
// import InvokeWorkarea from 'features/ui/components/InvokeWorkarea';
|
||||
// import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
// import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta';
|
||||
// import UnifiedCanvasContent from './UnifiedCanvasContent';
|
||||
// import UnifiedCanvasParameters from './UnifiedCanvasParameters';
|
||||
|
||||
// export default function UnifiedCanvasWorkarea() {
|
||||
// const shouldUseCanvasBetaLayout = useAppSelector(
|
||||
// (state: RootState) => state.ui.shouldUseCanvasBetaLayout
|
||||
// );
|
||||
|
||||
// const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
// return (
|
||||
// <InvokeWorkarea parametersPanelContent={<UnifiedCanvasParameters />}>
|
||||
// {activeTabName === 'unifiedCanvas' &&
|
||||
// (shouldUseCanvasBetaLayout ? (
|
||||
// <UnifiedCanvasContentBeta />
|
||||
// ) : (
|
||||
// <UnifiedCanvasContent />
|
||||
// ))}
|
||||
// </InvokeWorkarea>
|
||||
// );
|
||||
// }
|
||||
import { Box, Flex } from '@chakra-ui/react';
|
||||
import { useAppSelector } from 'app/storeHooks';
|
||||
import InvokeWorkarea from 'features/ui/components/InvokeWorkarea';
|
||||
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
|
||||
import { memo } from 'react';
|
||||
import PinParametersPanelButton from '../../PinParametersPanelButton';
|
||||
import { RootState } from 'app/store';
|
||||
import Scrollable from '../../common/Scrollable';
|
||||
import ParametersSlide from '../../common/ParametersSlide';
|
||||
import UnifiedCanvasParameters from './UnifiedCanvasParameters';
|
||||
import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta';
|
||||
import UnifiedCanvasContent from './UnifiedCanvasContent';
|
||||
import UnifiedCanvasParameters from './UnifiedCanvasParameters';
|
||||
|
||||
export default function UnifiedCanvasWorkarea() {
|
||||
const CanvasWorkspace = () => {
|
||||
const shouldPinParametersPanel = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldPinParametersPanel
|
||||
);
|
||||
|
||||
const shouldUseCanvasBetaLayout = useAppSelector(
|
||||
(state: RootState) => state.ui.shouldUseCanvasBetaLayout
|
||||
);
|
||||
|
||||
const activeTabName = useAppSelector(activeTabNameSelector);
|
||||
|
||||
return (
|
||||
<InvokeWorkarea parametersPanelContent={<UnifiedCanvasParameters />}>
|
||||
{activeTabName === 'unifiedCanvas' &&
|
||||
(shouldUseCanvasBetaLayout ? (
|
||||
<UnifiedCanvasContentBeta />
|
||||
) : (
|
||||
<UnifiedCanvasContent />
|
||||
))}
|
||||
</InvokeWorkarea>
|
||||
<Flex
|
||||
flexDirection={{ base: 'column-reverse', xl: 'row' }}
|
||||
w="full"
|
||||
h="full"
|
||||
gap={4}
|
||||
>
|
||||
{shouldPinParametersPanel ? (
|
||||
<Box width="28rem" flexShrink={0} position="relative">
|
||||
<Scrollable>
|
||||
<UnifiedCanvasParameters />
|
||||
</Scrollable>
|
||||
<PinParametersPanelButton
|
||||
sx={{ position: 'absolute', top: 0, insetInlineEnd: 0 }}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<ParametersSlide>
|
||||
<UnifiedCanvasParameters />
|
||||
</ParametersSlide>
|
||||
)}
|
||||
{shouldUseCanvasBetaLayout ? (
|
||||
<UnifiedCanvasContentBeta />
|
||||
) : (
|
||||
<UnifiedCanvasContent />
|
||||
)}
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default memo(CanvasWorkspace);
|
||||
|
14
invokeai/frontend/web/src/features/ui/hooks/useDirection.ts
Normal file
14
invokeai/frontend/web/src/features/ui/hooks/useDirection.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { useTheme } from '@chakra-ui/react';
|
||||
import { useMemo } from 'react';
|
||||
import { LangDirection } from '../components/common/ResizableDrawer/types';
|
||||
|
||||
export const useLangDirection = () => {
|
||||
const theme = useTheme();
|
||||
|
||||
const langDirection = useMemo(
|
||||
() => theme.direction as LangDirection,
|
||||
[theme.direction]
|
||||
);
|
||||
|
||||
return langDirection;
|
||||
};
|
Loading…
Reference in New Issue
Block a user