feat(ui): wip img2img ui

This commit is contained in:
psychedelicious 2023-04-24 14:48:46 +10:00
parent 38474fa9d4
commit 568f0aad71
23 changed files with 569 additions and 170 deletions

View File

@ -97,7 +97,8 @@
"statusMergedModels": "Models Merged", "statusMergedModels": "Models Merged",
"pinOptionsPanel": "Pin Options Panel", "pinOptionsPanel": "Pin Options Panel",
"loading": "Loading", "loading": "Loading",
"loadingInvokeAI": "Loading Invoke AI" "loadingInvokeAI": "Loading Invoke AI",
"random": "Random"
}, },
"gallery": { "gallery": {
"generations": "Generations", "generations": "Generations",

View File

@ -34,10 +34,9 @@ const IAISwitch = (props: Props) => {
display="flex" display="flex"
gap={4} gap={4}
alignItems="center" alignItems="center"
justifyContent="space-between"
{...formControlProps} {...formControlProps}
> >
<FormLabel my={1} {...formLabelProps}> <FormLabel my={1} flexGrow={1} {...formLabelProps}>
{label} {label}
</FormLabel> </FormLabel>
<Switch {...rest} /> <Switch {...rest} />

View File

@ -37,28 +37,6 @@ const ImageToImageOverlay = ({
position: 'absolute', 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 <Flex
sx={{ sx={{
position: 'absolute', position: 'absolute',

View File

@ -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;

View File

@ -3,7 +3,17 @@ import { FaImage } from 'react-icons/fa';
const SelectImagePlaceholder = () => { const SelectImagePlaceholder = () => {
return ( 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> <Icon color="base.400" boxSize={32} as={FaImage}></Icon>
</Flex> </Flex>
); );

View File

@ -1,7 +1,14 @@
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { isEqual } from 'lodash'; 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 { runESRGAN, runFacetool } from 'app/socketio/actions';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAIButton from 'common/components/IAIButton'; import IAIButton from 'common/components/IAIButton';
@ -446,6 +453,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
<IAIPopover <IAIPopover
triggerComponent={ triggerComponent={
<IAIIconButton <IAIIconButton
isDisabled={!selectedImage}
aria-label={`${t('parameters.sendTo')}...`} aria-label={`${t('parameters.sendTo')}...`}
icon={<FaShareAlt />} icon={<FaShareAlt />}
/> />
@ -487,7 +495,7 @@ const CurrentImageButtons = (props: CurrentImageButtonsProps) => {
{t('parameters.copyImageToLink')} {t('parameters.copyImageToLink')}
</IAIButton> </IAIButton>
<Link download={true} href={getUrl(selectedImage?.url)}> <Link download={true} href={getUrl(selectedImage?.url ?? '')}>
<IAIButton leftIcon={<FaDownload />} size="sm" w="100%"> <IAIButton leftIcon={<FaDownload />} size="sm" w="100%">
{t('parameters.downloadImage')} {t('parameters.downloadImage')}
</IAIButton> </IAIButton>

View File

@ -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 { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/storeHooks'; import { useAppSelector } from 'app/storeHooks';
import { systemSelector } from 'features/system/store/systemSelectors'; 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 { isEqual } from 'lodash';
import { useState } from 'react';
import {
AnimatePresence,
motion,
useMotionValue,
useTransform,
} from 'framer-motion';
import { MdPhoto } from 'react-icons/md'; import { MdPhoto } from 'react-icons/md';
import { import {
@ -33,31 +42,38 @@ export const currentImageDisplaySelector = createSelector(
*/ */
const CurrentImageDisplay = () => { const CurrentImageDisplay = () => {
const { hasAnImageToDisplay } = useAppSelector(currentImageDisplaySelector); const { hasAnImageToDisplay } = useAppSelector(currentImageDisplaySelector);
const [shouldHideImageToImage, setShouldHideImageToImage] = useState(false);
const w = useMotionValue(0);
const width = useTransform(w, [0, 100], [`0px`, `100px`]);
return ( return (
<Flex <Flex
sx={{ sx={{
position: 'relative',
flexDirection: 'column', flexDirection: 'column',
height: '100%', height: '100%',
width: '100%', width: '100%',
rowGap: 4, rowGap: 4,
borderRadius: 'base', borderRadius: 'base',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Box sx={{ position: 'absolute', top: 0 }}>
<CurrentImageButtons />
</Box>
<Flex
sx={{
w: 'full',
h: 'full',
alignItems: 'center',
justifyContent: 'center',
gap: 4,
}} }}
> >
{hasAnImageToDisplay ? ( {hasAnImageToDisplay ? (
<>
<CurrentImageButtons />
<CurrentImagePreview /> <CurrentImagePreview />
</>
) : ( ) : (
<Flex
sx={{
alignItems: 'center',
justifyContent: 'center',
width: '100%',
height: '100%',
}}
>
<Icon <Icon
as={MdPhoto} as={MdPhoto}
sx={{ sx={{
@ -65,9 +81,9 @@ const CurrentImageDisplay = () => {
color: 'base.500', color: 'base.500',
}} }}
/> />
</Flex>
)} )}
</Flex> </Flex>
</Flex>
); );
}; };

View File

@ -27,7 +27,7 @@ export default function InvokeAccordionItem({
{header} {header}
</Box> </Box>
{additionalHeaderComponents} {additionalHeaderComponents}
{feature && <GuideIcon feature={feature} />} {/* {feature && <GuideIcon feature={feature} />} */}
<AccordionIcon /> <AccordionIcon />
</Flex> </Flex>
</AccordionButton> </AccordionButton>

View File

@ -12,10 +12,6 @@ export default function ImageFit() {
(state: RootState) => state.generation.shouldFitToWidthHeight (state: RootState) => state.generation.shouldFitToWidthHeight
); );
const isImageToImageEnabled = useAppSelector(
(state: RootState) => state.generation.isImageToImageEnabled
);
const handleChangeFit = (e: ChangeEvent<HTMLInputElement>) => const handleChangeFit = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldFitToWidthHeight(e.target.checked)); dispatch(setShouldFitToWidthHeight(e.target.checked));
@ -23,7 +19,6 @@ export default function ImageFit() {
return ( return (
<IAISwitch <IAISwitch
isDisabled={!isImageToImageEnabled}
label={t('parameters.imageFit')} label={t('parameters.imageFit')}
isChecked={shouldFitToWidthHeight} isChecked={shouldFitToWidthHeight}
onChange={handleChangeFit} onChange={handleChangeFit}

View File

@ -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 ImageFit from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageFit';
import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength'; import ImageToImageStrength from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageStrength';
import IAIIconButton from 'common/components/IAIIconButton';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import InitialImagePreview from './InitialImagePreview'; 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() { export default function ImageToImageSettings() {
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<VStack gap={2} alignItems="stretch"> <VStack gap={2} w="full" alignItems="stretch">
<ImageToImageSettingsHeader />
<InitialImagePreview /> <InitialImagePreview />
<ImageToImageStrength label={t('parameters.img2imgStrength')} /> <ImageToImageStrength label={t('parameters.img2imgStrength')} />
<ImageFit /> <ImageFit />

View File

@ -3,12 +3,15 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { isImageToImageEnabledChanged } from 'features/parameters/store/generationSlice'; import { isImageToImageEnabledChanged } from 'features/parameters/store/generationSlice';
import { ChangeEvent } from 'react'; import { ChangeEvent } from 'react';
import { useTranslation } from 'react-i18next';
export default function ImageToImageToggle() { export default function ImageToImageToggle() {
const isImageToImageEnabled = useAppSelector( const isImageToImageEnabled = useAppSelector(
(state: RootState) => state.generation.isImageToImageEnabled (state: RootState) => state.generation.isImageToImageEnabled
); );
const { t } = useTranslation();
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const handleChange = (e: ChangeEvent<HTMLInputElement>) => const handleChange = (e: ChangeEvent<HTMLInputElement>) =>
@ -16,6 +19,7 @@ export default function ImageToImageToggle() {
return ( return (
<IAISwitch <IAISwitch
label={t('common.img2img')}
isChecked={isImageToImageEnabled} isChecked={isImageToImageEnabled}
width="auto" width="auto"
onChange={handleChange} onChange={handleChange}

View File

@ -87,6 +87,7 @@ const InitialImagePreview = () => {
}} }}
onDrop={handleDrop} onDrop={handleDrop}
> >
{initialImage?.url && (
<Box <Box
sx={{ sx={{
height: 'full', height: 'full',
@ -97,8 +98,6 @@ const InitialImagePreview = () => {
position: 'relative', position: 'relative',
}} }}
> >
{initialImage?.url && (
<>
<Image <Image
sx={{ sx={{
fit: 'contain', fit: 'contain',
@ -123,11 +122,9 @@ const InitialImagePreview = () => {
image={initialImage} image={initialImage}
/> />
)} )}
</>
)}
{!initialImage?.url && <SelectImagePlaceholder />}
</Box> </Box>
)}
{!initialImage?.url && <SelectImagePlaceholder />}
{!isImageToImageEnabled && ( {!isImageToImageEnabled && (
<Flex <Flex
sx={{ sx={{

View File

@ -5,6 +5,7 @@ import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import IAISwitch from 'common/components/IAISwitch'; import IAISwitch from 'common/components/IAISwitch';
import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice'; import { setShouldRandomizeSeed } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Switch } from '@chakra-ui/react';
export default function RandomizeSeed() { export default function RandomizeSeed() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -18,8 +19,8 @@ export default function RandomizeSeed() {
dispatch(setShouldRandomizeSeed(e.target.checked)); dispatch(setShouldRandomizeSeed(e.target.checked));
return ( return (
<IAISwitch <Switch
label={t('parameters.randomizeSeed')} aria-label={t('parameters.randomizeSeed')}
isChecked={shouldRandomizeSeed} isChecked={shouldRandomizeSeed}
onChange={handleChangeShouldRandomizeSeed} onChange={handleChangeShouldRandomizeSeed}
/> />

View File

@ -10,7 +10,6 @@ import Threshold from './Threshold';
const SeedSettings = () => { const SeedSettings = () => {
return ( return (
<VStack gap={2} alignItems="stretch"> <VStack gap={2} alignItems="stretch">
<RandomizeSeed />
<Seed /> <Seed />
<Threshold /> <Threshold />
<Perlin /> <Perlin />

View File

@ -3,8 +3,10 @@ import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from 'app/constants';
import { RootState } from 'app/store'; import { RootState } from 'app/store';
import { useAppDispatch, useAppSelector } from 'app/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/storeHooks';
import randomInt from 'common/util/randomInt'; import randomInt from 'common/util/randomInt';
import { IAIIconButton } from 'exports';
import { setSeed } from 'features/parameters/store/generationSlice'; import { setSeed } from 'features/parameters/store/generationSlice';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { FaRandom } from 'react-icons/fa';
export default function ShuffleSeed() { export default function ShuffleSeed() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -17,13 +19,20 @@ export default function ShuffleSeed() {
dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX))); dispatch(setSeed(randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)));
return ( return (
<Button <IAIIconButton
size="sm" size="sm"
isDisabled={shouldRandomizeSeed} isDisabled={shouldRandomizeSeed}
aria-label={t('parameters.shuffle')}
tooltip={t('parameters.shuffle')}
icon={<FaRandom />}
onClick={handleClickRandomizeSeed} onClick={handleClickRandomizeSeed}
padding="0 1.5rem" />
> // <Button
<p>{t('parameters.shuffle')}</p> // size="sm"
</Button> // onClick={handleClickRandomizeSeed}
// padding="0 1.5rem"
// >
// <p>{t('parameters.shuffle')}</p>
// </Button>
); );
} }

View File

@ -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);

View File

@ -23,7 +23,7 @@ import { useTranslation } from 'react-i18next';
import { ResourceKey } from 'i18next'; import { ResourceKey } from 'i18next';
import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale'; import { requestCanvasRescale } from 'features/canvas/store/thunks/requestCanvasScale';
import NodeEditor from 'features/nodes/components/NodeEditor'; 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'; import { FaImage } from 'react-icons/fa';
export interface InvokeTabInfo { export interface InvokeTabInfo {
@ -41,7 +41,7 @@ const buildTabs = (disabledTabs: InvokeTabName[]): InvokeTabInfo[] => {
{ {
id: 'linear', id: 'linear',
icon: <Icon as={FaImage} sx={tabIconStyles} />, icon: <Icon as={FaImage} sx={tabIconStyles} />,
workarea: <LinearWorkarea />, workarea: <LinearWorkspace />,
}, },
{ {
id: 'unifiedCanvas', id: 'unifiedCanvas',

View File

@ -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);

View File

@ -1,10 +1,12 @@
import { Flex } from '@chakra-ui/react'; import { Flex } from '@chakra-ui/react';
import { Feature } from 'app/features'; import { Feature } from 'app/features';
import IAISwitch from 'common/components/IAISwitch';
import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings'; import ImageToImageSettings from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageSettings';
import ImageToImageToggle from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle'; import ImageToImageToggle from 'features/parameters/components/AdvancedParameters/ImageToImage/ImageToImageToggle';
import OutputSettings from 'features/parameters/components/AdvancedParameters/Output/OutputSettings'; import OutputSettings from 'features/parameters/components/AdvancedParameters/Output/OutputSettings';
import SymmetrySettings from 'features/parameters/components/AdvancedParameters/Output/SymmetrySettings'; import SymmetrySettings from 'features/parameters/components/AdvancedParameters/Output/SymmetrySettings';
import SymmetryToggle from 'features/parameters/components/AdvancedParameters/Output/SymmetryToggle'; 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 SeedSettings from 'features/parameters/components/AdvancedParameters/Seed/SeedSettings';
import GenerateVariationsToggle from 'features/parameters/components/AdvancedParameters/Variations/GenerateVariations'; import GenerateVariationsToggle from 'features/parameters/components/AdvancedParameters/Variations/GenerateVariations';
import VariationsSettings from 'features/parameters/components/AdvancedParameters/Variations/VariationsSettings'; import VariationsSettings from 'features/parameters/components/AdvancedParameters/Variations/VariationsSettings';
@ -15,32 +17,35 @@ import ParametersAccordion, {
import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons'; import ProcessButtons from 'features/parameters/components/ProcessButtons/ProcessButtons';
import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput'; import NegativePromptInput from 'features/parameters/components/PromptInput/NegativePromptInput';
import PromptInput from 'features/parameters/components/PromptInput/PromptInput'; import PromptInput from 'features/parameters/components/PromptInput/PromptInput';
import { memo } from 'react'; import { memo, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { PARAMETERS_PANEL_WIDTH } from 'theme/util/constants';
const LinearParameters = () => { const LinearParameters = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const linearAccordions: ParametersAccordionItems = { const linearAccordions: ParametersAccordionItems = useMemo(
general: { () => ({
name: 'general', // general: {
header: `${t('parameters.general')}`, // name: 'general',
feature: undefined, // header: `${t('parameters.general')}`,
content: <MainSettings />, // feature: undefined,
}, // content: <MainSettings />,
// },
seed: { seed: {
name: 'seed', name: 'seed',
header: `${t('parameters.seed')}`, header: `${t('parameters.seed')}`,
feature: Feature.SEED, feature: Feature.SEED,
content: <SeedSettings />, content: <SeedSettings />,
additionalHeaderComponents: <RandomizeSeed />,
}, },
imageToImage: { // imageToImage: {
name: 'imageToImage', // name: 'imageToImage',
header: `${t('parameters.imageToImage')}`, // header: `${t('parameters.imageToImage')}`,
feature: undefined, // feature: undefined,
content: <ImageToImageSettings />, // content: <ImageToImageSettings />,
additionalHeaderComponents: <ImageToImageToggle />, // additionalHeaderComponents: <ImageToImageToggle />,
}, // },
variations: { variations: {
name: 'variations', name: 'variations',
header: `${t('parameters.variations')}`, header: `${t('parameters.variations')}`,
@ -60,13 +65,27 @@ const LinearParameters = () => {
feature: Feature.OTHER, feature: Feature.OTHER,
content: <OutputSettings />, content: <OutputSettings />,
}, },
}; }),
[t]
);
return ( return (
<Flex flexDir="column" gap={2}> <Flex flexDir="column" gap={2}>
<PromptInput /> <PromptInput />
<NegativePromptInput /> <NegativePromptInput />
<ProcessButtons /> <ProcessButtons />
<Flex
sx={{
flexDirection: 'column',
gap: 2,
bg: 'base.800',
p: 4,
borderRadius: 'base',
}}
>
<MainSettings />
<ImageToImageToggle />
</Flex>
<ParametersAccordion accordionItems={linearAccordions} /> <ParametersAccordion accordionItems={linearAccordions} />
</Flex> </Flex>
); );

View File

@ -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>
);
}

View File

@ -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);

View File

@ -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 { useAppSelector } from 'app/storeHooks';
import InvokeWorkarea from 'features/ui/components/InvokeWorkarea'; import { memo } from 'react';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; 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 UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta';
import UnifiedCanvasContent from './UnifiedCanvasContent'; 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( const shouldUseCanvasBetaLayout = useAppSelector(
(state: RootState) => state.ui.shouldUseCanvasBetaLayout (state: RootState) => state.ui.shouldUseCanvasBetaLayout
); );
const activeTabName = useAppSelector(activeTabNameSelector);
return ( return (
<InvokeWorkarea parametersPanelContent={<UnifiedCanvasParameters />}> <Flex
{activeTabName === 'unifiedCanvas' && flexDirection={{ base: 'column-reverse', xl: 'row' }}
(shouldUseCanvasBetaLayout ? ( 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 /> <UnifiedCanvasContentBeta />
) : ( ) : (
<UnifiedCanvasContent /> <UnifiedCanvasContent />
))} )}
</InvokeWorkarea> </Flex>
); );
} };
export default memo(CanvasWorkspace);

View 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;
};