refactor(ui): canvas flow (wip)

This commit is contained in:
psychedelicious
2025-05-27 16:20:10 +10:00
parent c0428ee7ef
commit ce5ae83689
32 changed files with 98 additions and 452 deletions

View File

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

View File

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

View File

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