mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-13 17:14:56 -05:00
refactor(ui): canvas flow (wip)
This commit is contained in:
@@ -2,7 +2,6 @@ import type { TooltipProps } from '@invoke-ai/ui-library';
|
||||
import { Divider, Flex, ListItem, Text, Tooltip, UnorderedList } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { selectIterations } from 'features/controlLayers/store/paramsSlice';
|
||||
import { selectDynamicPromptsIsLoading } from 'features/dynamicPrompts/store/dynamicPromptsSlice';
|
||||
import { selectAutoAddBoardId } from 'features/gallery/store/gallerySelectors';
|
||||
@@ -237,32 +236,14 @@ StyledDivider.displayName = 'StyledDivider';
|
||||
|
||||
const AddingToText = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const sendToCanvas = useAppSelector(selectSendToCanvas);
|
||||
const autoAddBoardId = useAppSelector(selectAutoAddBoardId);
|
||||
const autoAddBoardName = useBoardName(autoAddBoardId);
|
||||
|
||||
const addingTo = useMemo(() => {
|
||||
if (sendToCanvas) {
|
||||
return t('controlLayers.stagingOnCanvas');
|
||||
}
|
||||
return t('parameters.invoke.addingImagesTo');
|
||||
}, [sendToCanvas, t]);
|
||||
|
||||
const destination = useMemo(() => {
|
||||
if (sendToCanvas) {
|
||||
return t('queue.canvas');
|
||||
}
|
||||
if (autoAddBoardName) {
|
||||
return autoAddBoardName;
|
||||
}
|
||||
return t('boards.uncategorized');
|
||||
}, [autoAddBoardName, sendToCanvas, t]);
|
||||
|
||||
return (
|
||||
<Text fontStyle="oblique 10deg">
|
||||
{addingTo}{' '}
|
||||
{t('parameters.invoke.addingImagesTo')}{' '}
|
||||
<Text as="span" fontWeight="semibold">
|
||||
{destination}
|
||||
{autoAddBoardName || t('boards.uncategorized')}
|
||||
</Text>
|
||||
</Text>
|
||||
);
|
||||
|
||||
@@ -1,28 +1,17 @@
|
||||
import { Flex, Spacer } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { ClearQueueIconButton } from 'features/queue/components/ClearQueueIconButton';
|
||||
import { QueueActionsMenuButton } from 'features/queue/components/QueueActionsMenuButton';
|
||||
import { SendToToggle } from 'features/queue/components/SendToToggle';
|
||||
import ProgressBar from 'features/system/components/ProgressBar';
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
import { memo } from 'react';
|
||||
|
||||
import { InvokeButton } from './InvokeQueueBackButton';
|
||||
|
||||
const QueueControls = () => {
|
||||
const tab = useAppSelector(selectActiveTab);
|
||||
|
||||
return (
|
||||
<Flex w="full" position="relative" borderRadius="base" gap={2} flexDir="column">
|
||||
<Flex gap={2}>
|
||||
<InvokeButton />
|
||||
<Spacer />
|
||||
{tab === 'canvas' && (
|
||||
<CanvasManagerProviderGate>
|
||||
<SendToToggle />
|
||||
</CanvasManagerProviderGate>
|
||||
)}
|
||||
<QueueActionsMenuButton />
|
||||
<ClearQueueIconButton />
|
||||
</Flex>
|
||||
|
||||
@@ -1,205 +0,0 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
chakra,
|
||||
Flex,
|
||||
Icon,
|
||||
Popover,
|
||||
PopoverArrow,
|
||||
PopoverBody,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
Portal,
|
||||
Text,
|
||||
useCheckbox,
|
||||
useToken,
|
||||
} from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { selectSendToCanvas, settingsSendToCanvasChanged } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { $imageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
import { activeTabCanvasRightPanelChanged, setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import type { ChangeEvent, PropsWithChildren } from 'react';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { Trans, useTranslation } from 'react-i18next';
|
||||
import { PiImageBold, PiPaintBrushBold } from 'react-icons/pi';
|
||||
|
||||
const getSx = (padding: string | number): SystemStyleObject => ({
|
||||
bg: 'base.700',
|
||||
w: '72px',
|
||||
cursor: 'pointer',
|
||||
'&[data-checked]': {
|
||||
'.thumb': {
|
||||
left: `calc(100% - ${padding})`,
|
||||
transform: 'translateX(-100%)',
|
||||
bg: 'invokeGreen.300',
|
||||
},
|
||||
'.unchecked-icon': {
|
||||
color: 'base.50',
|
||||
opacity: 0.4,
|
||||
},
|
||||
'.checked-icon': {
|
||||
color: 'base.900',
|
||||
opacity: 1,
|
||||
},
|
||||
},
|
||||
'&[data-disabled]': {
|
||||
bg: 'base.700',
|
||||
'.thumb': {
|
||||
bg: 'base.500',
|
||||
},
|
||||
'.unchecked-icon': {
|
||||
color: 'base.800',
|
||||
},
|
||||
'.checked-icon': {
|
||||
color: 'base.800',
|
||||
},
|
||||
},
|
||||
'.thumb': {
|
||||
transition: 'left 0.1s ease-in-out, transform 0.1s ease-in-out',
|
||||
left: padding,
|
||||
transform: 'translateX(0)',
|
||||
bg: 'invokeBlue.400',
|
||||
shadow: 'md',
|
||||
},
|
||||
'.unchecked-icon': {
|
||||
color: 'base.900',
|
||||
opacity: 1,
|
||||
},
|
||||
'.checked-icon': {
|
||||
color: 'base.50',
|
||||
opacity: 0.4,
|
||||
},
|
||||
});
|
||||
|
||||
export const SendToToggle = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const sendToCanvas = useAppSelector(selectSendToCanvas);
|
||||
const isStaging = useAppSelector(selectIsStaging);
|
||||
|
||||
const gap = useToken('space', 1);
|
||||
const sx = useMemo(() => getSx(gap), [gap]);
|
||||
|
||||
const onChange = useCallback(
|
||||
(e: ChangeEvent<HTMLInputElement>) => {
|
||||
dispatch(settingsSendToCanvasChanged(e.target.checked));
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const { getCheckboxProps, getInputProps, htmlProps } = useCheckbox({
|
||||
onChange,
|
||||
isChecked: sendToCanvas,
|
||||
isDisabled: isStaging,
|
||||
});
|
||||
|
||||
return (
|
||||
<Popover trigger="hover">
|
||||
<PopoverTrigger>
|
||||
<chakra.label {...htmlProps}>
|
||||
<Flex
|
||||
position="relative"
|
||||
borderRadius="base"
|
||||
alignItems="center"
|
||||
justifyContent="space-between"
|
||||
h="full"
|
||||
p={gap}
|
||||
gap={gap}
|
||||
sx={sx}
|
||||
{...getCheckboxProps()}
|
||||
>
|
||||
<input {...getInputProps()} hidden />
|
||||
<Box className="thumb" position="absolute" borderRadius="base" w={10} top={gap} bottom={gap} />
|
||||
<Flex w={10} h="full" alignItems="center" justifyContent="center" pos="relative">
|
||||
<Icon
|
||||
className="unchecked-icon"
|
||||
w={6}
|
||||
h={6}
|
||||
as={PiImageBold}
|
||||
aria-label={t('controlLayers.sendToGallery')}
|
||||
/>
|
||||
</Flex>
|
||||
<Flex w={10} h="full" alignItems="center" justifyContent="center" pos="relative">
|
||||
<Icon
|
||||
className="checked-icon"
|
||||
w={6}
|
||||
h={6}
|
||||
as={PiPaintBrushBold}
|
||||
aria-label={t('controlLayers.sendToCanvas')}
|
||||
/>
|
||||
</Flex>
|
||||
</Flex>
|
||||
</chakra.label>
|
||||
</PopoverTrigger>
|
||||
<Portal>
|
||||
<PopoverContent maxW={296} p={2} bg="base.200" color="base.800">
|
||||
<PopoverArrow
|
||||
bg="base.200"
|
||||
left={sendToCanvas ? '18px !important' : '-18px !important'}
|
||||
transitionProperty="all"
|
||||
transitionDuration="0.2s"
|
||||
/>
|
||||
<PopoverBody>
|
||||
<TooltipContent />
|
||||
</PopoverBody>
|
||||
</PopoverContent>
|
||||
</Portal>
|
||||
</Popover>
|
||||
);
|
||||
});
|
||||
|
||||
SendToToggle.displayName = 'SendToToggle';
|
||||
|
||||
const TooltipContent = memo(() => {
|
||||
const { t } = useTranslation();
|
||||
const sendToCanvas = useAppSelector(selectSendToCanvas);
|
||||
const isStaging = useAppSelector(selectIsStaging);
|
||||
|
||||
if (isStaging) {
|
||||
return (
|
||||
<Flex flexDir="column">
|
||||
<Text fontWeight="semibold">{t('controlLayers.sendingToCanvas')}</Text>
|
||||
<Text fontWeight="normal">
|
||||
<Trans i18nKey="controlLayers.viewProgressOnCanvas" components={{ Btn: <ActivateCanvasButton /> }} />
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Flex flexDir="column">
|
||||
<Text fontWeight="semibold">
|
||||
{sendToCanvas ? t('controlLayers.sendToCanvas') : t('controlLayers.sendToGallery')}
|
||||
</Text>
|
||||
<Text fontWeight="normal">
|
||||
{sendToCanvas ? t('controlLayers.sendToCanvasDesc') : t('controlLayers.sendToGalleryDesc')}
|
||||
</Text>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
TooltipContent.displayName = 'TooltipContent';
|
||||
|
||||
const ActivateCanvasButton = memo((props: PropsWithChildren) => {
|
||||
const dispatch = useAppDispatch();
|
||||
const onClick = useCallback(() => {
|
||||
dispatch(setActiveTab('canvas'));
|
||||
dispatch(activeTabCanvasRightPanelChanged('layers'));
|
||||
$imageViewer.set(false);
|
||||
}, [dispatch]);
|
||||
return (
|
||||
<Button
|
||||
onClick={onClick}
|
||||
size="sm"
|
||||
variant="link"
|
||||
color="base.800"
|
||||
_hover={{ color: 'base.900', textDecoration: 'underline' }}
|
||||
>
|
||||
{props.children}
|
||||
</Button>
|
||||
);
|
||||
});
|
||||
|
||||
ActivateCanvasButton.displayName = 'ActivateCanvasButton';
|
||||
Reference in New Issue
Block a user