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",
|
"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",
|
||||||
|
@ -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} />
|
||||||
|
@ -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',
|
||||||
|
@ -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 = () => {
|
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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -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>
|
||||||
|
@ -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}
|
||||||
|
@ -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 />
|
||||||
|
@ -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}
|
||||||
|
@ -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={{
|
||||||
|
@ -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}
|
||||||
/>
|
/>
|
||||||
|
@ -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 />
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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 { 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',
|
||||||
|
@ -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 { 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>
|
||||||
);
|
);
|
||||||
|
@ -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 { 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);
|
||||||
|
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…
x
Reference in New Issue
Block a user