feat(ui): reworked layout (wip)

This commit is contained in:
psychedelicious
2024-09-09 21:53:36 +10:00
parent b67c369bdb
commit 3ed29a16a8
52 changed files with 656 additions and 628 deletions

View File

@@ -1,7 +1,6 @@
import { Flex } from '@invoke-ai/ui-library';
import IAIDroppable from 'common/components/IAIDroppable';
import type { AddControlLayerFromImageDropData, AddRasterLayerFromImageDropData } from 'features/dnd/types';
import { useIsImageViewerOpen } from 'features/gallery/components/ImageViewer/useImageViewer';
import { memo } from 'react';
const addRasterLayerFromImageDropData: AddRasterLayerFromImageDropData = {
@@ -15,12 +14,6 @@ const addControlLayerFromImageDropData: AddControlLayerFromImageDropData = {
};
export const CanvasDropArea = memo(() => {
const isImageViewerOpen = useIsImageViewerOpen();
if (isImageViewerOpen) {
return null;
}
return (
<>
<Flex position="absolute" top={0} right={0} bottom="50%" left={0} gap={2} pointerEvents="none">

View File

@@ -1,24 +0,0 @@
import { Flex } from '@invoke-ai/ui-library';
import type { Meta, StoryObj } from '@storybook/react';
import { CanvasEditor } from 'features/controlLayers/components/CanvasEditor';
const meta: Meta<typeof CanvasEditor> = {
title: 'Feature/ControlLayers',
tags: ['autodocs'],
component: CanvasEditor,
};
export default meta;
type Story = StoryObj<typeof CanvasEditor>;
const Component = () => {
return (
<Flex w={1500} h={1500}>
<CanvasEditor />
</Flex>
);
};
export const Default: Story = {
render: Component,
};

View File

@@ -6,7 +6,7 @@ import { memo } from 'react';
export const EntityListGlobalActionBar = memo(() => {
return (
<Flex w="full" py={1} px={1} gap={2} alignItems="center">
<Flex w="full" gap={2} alignItems="center">
<EntityListGlobalActionBarDenoisingStrength />
<Spacer />
<Flex>

View File

@@ -8,7 +8,7 @@ import { memo } from 'react';
export const EntityListSelectedEntityActionBar = memo(() => {
return (
<Flex w="full" py={1} px={1} gap={2} alignItems="center">
<Flex w="full" gap={2} alignItems="center" ps={1}>
<EntityListSelectedEntityActionBarOpacity />
<Spacer />
<EntityListSelectedEntityActionBarFill />

View File

@@ -2,7 +2,6 @@ import { Divider, Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { CanvasAddEntityButtons } from 'features/controlLayers/components/CanvasAddEntityButtons';
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityList';
import { EntityListGlobalActionBar } from 'features/controlLayers/components/CanvasEntityList/EntityListGlobalActionBar';
import { EntityListSelectedEntityActionBar } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBar';
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { selectHasEntities } from 'features/controlLayers/store/selectors';
@@ -14,8 +13,6 @@ export const CanvasPanelContent = memo(() => {
return (
<CanvasManagerProviderGate>
<Flex flexDir="column" gap={2} w="full" h="full">
<EntityListGlobalActionBar />
<Divider py={0} />
<EntityListSelectedEntityActionBar />
<Divider py={0} />
{!hasEntities && <CanvasAddEntityButtons />}

View File

@@ -0,0 +1,83 @@
import { useDndContext } from '@dnd-kit/core';
import { Box, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
import { CanvasPanelContent } from 'features/controlLayers/components/CanvasPanelContent';
import { CanvasSendToToggle } from 'features/controlLayers/components/CanvasSendToToggle';
import { selectSendToCanvas } from 'features/controlLayers/store/canvasSettingsSlice';
import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent';
import { memo, useCallback, useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
export const CanvasRightPanelContent = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const [tab, setTab] = useState(0);
useScopeOnFocus('gallery', ref);
return (
<Tabs index={tab} onChange={setTab} w="full" h="full" display="flex" flexDir="column">
<TabList alignItems="center">
<PanelTabs setTab={setTab} />
<Spacer />
<CanvasSendToToggle />
</TabList>
<TabPanels w="full" h="full">
<TabPanel w="full" h="full" p={0} pt={2}>
<GalleryPanelContent />
</TabPanel>
<TabPanel w="full" h="full" p={0} pt={2}>
<CanvasPanelContent />
</TabPanel>
</TabPanels>
</Tabs>
);
});
CanvasRightPanelContent.displayName = 'CanvasRightPanelContent';
const PanelTabs = memo(({ setTab }: { setTab: (val: number) => void }) => {
const { t } = useTranslation();
const sendToCanvas = useAppSelector(selectSendToCanvas);
const tabTimeout = useRef<number | null>(null);
const dndCtx = useDndContext();
const onOnMouseOverLayersTab = useCallback(() => {
tabTimeout.current = window.setTimeout(() => {
if (dndCtx.active) {
setTab(1);
}
}, 300);
}, [dndCtx.active, setTab]);
const onOnMouseOverGalleryTab = useCallback(() => {
tabTimeout.current = window.setTimeout(() => {
if (dndCtx.active) {
setTab(0);
}
}, 300);
}, [dndCtx.active, setTab]);
const onMouseOut = useCallback(() => {
if (tabTimeout.current) {
clearTimeout(tabTimeout.current);
}
}, []);
return (
<>
<Tab position="relative" onMouseOver={onOnMouseOverGalleryTab} onMouseOut={onMouseOut}>
{t('gallery.gallery')}
{!sendToCanvas && (
<Box position="absolute" top={2} right={2} h={2} w={2} bg="invokeYellow.300" borderRadius="full" />
)}
</Tab>
<Tab position="relative" onMouseOver={onOnMouseOverLayersTab} onMouseOut={onMouseOut}>
{t('controlLayers.layer_other')}
{sendToCanvas && (
<Box position="absolute" top={2} right={2} h={2} w={2} bg="invokeYellow.300" borderRadius="full" />
)}
</Tab>
</>
);
});
PanelTabs.displayName = 'PanelTabs';

View File

@@ -1,64 +1,76 @@
import { Flex, Text } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { IconSwitch } from 'common/components/IconSwitch';
import {
selectCanvasSettingsSlice,
settingsSendToCanvasChanged,
} from 'features/controlLayers/store/canvasSettingsSlice';
Button,
Flex,
Icon,
Popover,
PopoverArrow,
PopoverBody,
PopoverContent,
PopoverTrigger,
Text,
} from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectSendToCanvas, settingsSendToCanvasChanged } from 'features/controlLayers/store/canvasSettingsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiImageBold, PiPaintBrushBold } from 'react-icons/pi';
const TooltipSendToGallery = memo(() => {
const { t } = useTranslation();
return (
<Flex flexDir="column">
<Text fontWeight="semibold">{t('controlLayers.sendToGallery')}</Text>
<Text fontWeight="normal">{t('controlLayers.sendToGalleryDesc')}</Text>
</Flex>
);
});
TooltipSendToGallery.displayName = 'TooltipSendToGallery';
const TooltipSendToCanvas = memo(() => {
const { t } = useTranslation();
return (
<Flex flexDir="column">
<Text fontWeight="semibold">{t('controlLayers.sendToCanvas')}</Text>
<Text fontWeight="normal">{t('controlLayers.sendToCanvasDesc')}</Text>
</Flex>
);
});
TooltipSendToCanvas.displayName = 'TooltipSendToCanvas';
const selectSendToCanvas = createSelector(selectCanvasSettingsSlice, (canvasSettings) => canvasSettings.sendToCanvas);
import { PiCaretDownBold, PiCheckBold } from 'react-icons/pi';
export const CanvasSendToToggle = memo(() => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const sendToCanvas = useAppSelector(selectSendToCanvas);
const dispatch = useAppDispatch();
const onChange = useCallback(
(isChecked: boolean) => {
dispatch(settingsSendToCanvasChanged(isChecked));
},
[dispatch]
);
const enableSendToCanvas = useCallback(() => {
dispatch(settingsSendToCanvasChanged(true));
}, [dispatch]);
const disableSendToCanvas = useCallback(() => {
dispatch(settingsSendToCanvasChanged(false));
}, [dispatch]);
return (
<IconSwitch
isChecked={sendToCanvas}
onChange={onChange}
iconUnchecked={<PiImageBold />}
tooltipUnchecked={<TooltipSendToGallery />}
iconChecked={<PiPaintBrushBold />}
tooltipChecked={<TooltipSendToCanvas />}
ariaLabel="Toggle canvas mode"
/>
<Popover isLazy>
<PopoverTrigger>
<Button
size="sm"
variant="link"
data-testid="toggle-viewer-menu-button"
pointerEvents="auto"
rightIcon={<PiCaretDownBold />}
>
{sendToCanvas ? t('controlLayers.sendingToCanvas') : t('controlLayers.sendingToGallery')}
</Button>
</PopoverTrigger>
<PopoverContent p={2} pointerEvents="auto">
<PopoverArrow />
<PopoverBody>
<Flex flexDir="column">
<Button onClick={disableSendToCanvas} variant="ghost" h="auto" w="auto" p={2}>
<Flex gap={2} w="full">
<Icon as={PiCheckBold} visibility={!sendToCanvas ? 'visible' : 'hidden'} />
<Flex flexDir="column" gap={2} alignItems="flex-start">
<Text fontWeight="semibold">{t('controlLayers.sendToGallery')}</Text>
<Text fontWeight="normal" variant="subtext">
{t('controlLayers.sendToGalleryDesc')}
</Text>
</Flex>
</Flex>
</Button>
<Button onClick={enableSendToCanvas} variant="ghost" h="auto" w="auto" p={2}>
<Flex gap={2} w="full">
<Icon as={PiCheckBold} visibility={sendToCanvas ? 'visible' : 'hidden'} />
<Flex flexDir="column" gap={2} alignItems="flex-start">
<Text fontWeight="semibold">{t('controlLayers.sendToCanvas')}</Text>
<Text fontWeight="normal" variant="subtext">
{t('controlLayers.sendToCanvasDesc')}
</Text>
</Flex>
</Flex>
</Button>
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
);
});

View File

@@ -11,7 +11,7 @@ import { Transform } from 'features/controlLayers/components/Transform/Transform
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { memo, useRef } from 'react';
export const CanvasEditor = memo(() => {
export const CanvasTabContent = memo(() => {
const ref = useRef<HTMLDivElement>(null);
useScopeOnFocus('canvas', ref);
@@ -48,4 +48,4 @@ export const CanvasEditor = memo(() => {
);
});
CanvasEditor.displayName = 'CanvasEditor';
CanvasTabContent.displayName = 'CanvasTabContent';

View File

@@ -85,7 +85,7 @@ export const IPAdapterImagePreview = memo(
/>
{controlImage && (
<Flex position="absolute" flexDir="column" top={1} insetInlineEnd={1} gap={1}>
<Flex position="absolute" flexDir="column" top={2} insetInlineEnd={2} gap={1}>
<IAIDndImageIcon
onClick={handleResetControlImage}
icon={<PiArrowCounterClockwiseBold size={16} />}

View File

@@ -13,8 +13,6 @@ import { useCanvasEntityQuickSwitchHotkey } from 'features/controlLayers/hooks/u
import { useCanvasResetLayerHotkey } from 'features/controlLayers/hooks/useCanvasResetLayerHotkey';
import { useCanvasUndoRedoHotkeys } from 'features/controlLayers/hooks/useCanvasUndoRedoHotkeys';
import { useNextPrevEntityHotkeys } from 'features/controlLayers/hooks/useNextPrevEntity';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
import { ViewerToggle } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
import { memo } from 'react';
export const CanvasToolbar = memo(() => {
@@ -27,7 +25,6 @@ export const CanvasToolbar = memo(() => {
return (
<CanvasManagerProviderGate>
<Flex w="full" gap={2} alignItems="center">
<ToggleProgressButton />
<ToolChooser />
<Spacer />
<ToolSettings />
@@ -38,7 +35,6 @@ export const CanvasToolbar = memo(() => {
<ToolColorPicker />
<CanvasToolbarSaveToGalleryButton />
<CanvasSettingsPopover />
<ViewerToggle />
</Flex>
</CanvasManagerProviderGate>
);