feat(ui): revise image viewer

- Viewer only exists on Generation tab
- Viewer defaults to open
- When clicking the Control Layers tab on the left panel, close the viewer (i.e. open the CL editor)
- Do not switch to editor when adding layers (this is handled by clicking the Control Layers tab)
- Do not open viewer when single-clicking images in gallery
- _Do_ open viewer when _double_-clicking images in gallery
- Do not change viewer state when switching between app tabs (this no longer makes sense; the viewer only exists on generation tab)
- Change the button to a drop down menu that states what you are currently doing, e.g. Viewing vs Editing
This commit is contained in:
psychedelicious
2024-05-08 17:38:18 +10:00
committed by Kent Keirsey
parent e8023c44b0
commit e8e764be20
17 changed files with 249 additions and 187 deletions

View File

@ -142,9 +142,11 @@
"blue": "Blue", "blue": "Blue",
"alpha": "Alpha", "alpha": "Alpha",
"selected": "Selected", "selected": "Selected",
"viewer": "Viewer",
"tab": "Tab", "tab": "Tab",
"close": "Close" "viewing": "Viewing",
"viewingDesc": "Review images in a large gallery view",
"editing": "Editing",
"editingDesc": "Edit on the Control Layers canvas"
}, },
"controlnet": { "controlnet": {
"controlAdapter_one": "Control Adapter", "controlAdapter_one": "Control Adapter",
@ -365,10 +367,7 @@
"bulkDownloadRequestFailed": "Problem Preparing Download", "bulkDownloadRequestFailed": "Problem Preparing Download",
"bulkDownloadFailed": "Download Failed", "bulkDownloadFailed": "Download Failed",
"problemDeletingImages": "Problem Deleting Images", "problemDeletingImages": "Problem Deleting Images",
"problemDeletingImagesDesc": "One or more images could not be deleted", "problemDeletingImagesDesc": "One or more images could not be deleted"
"switchTo": "Switch to {{ tab }} (Z)",
"openFloatingViewer": "Open Floating Viewer",
"closeFloatingViewer": "Close Floating Viewer"
}, },
"hotkeys": { "hotkeys": {
"searchHotkeys": "Search Hotkeys", "searchHotkeys": "Search Hotkeys",

View File

@ -1,7 +1,7 @@
import { createAction } from '@reduxjs/toolkit'; import { createAction } from '@reduxjs/toolkit';
import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors'; import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
import { isImageViewerOpenChanged, selectionChanged } from 'features/gallery/store/gallerySlice'; import { selectionChanged } from 'features/gallery/store/gallerySlice';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types'; import type { ImageDTO } from 'services/api/types';
import { imagesSelectors } from 'services/api/util'; import { imagesSelectors } from 'services/api/util';
@ -62,7 +62,6 @@ export const addGalleryImageClickedListener = (startAppListening: AppStartListen
} else { } else {
dispatch(selectionChanged([imageDTO])); dispatch(selectionChanged([imageDTO]));
} }
dispatch(isImageViewerOpenChanged(true));
}, },
}); });
}; };

View File

@ -70,6 +70,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
onMouseOver, onMouseOver,
onMouseOut, onMouseOut,
dataTestId, dataTestId,
...rest
} = props; } = props;
const [isHovered, setIsHovered] = useState(false); const [isHovered, setIsHovered] = useState(false);
@ -138,6 +139,7 @@ const IAIDndImage = (props: IAIDndImageProps) => {
minH={minSize ? minSize : undefined} minH={minSize ? minSize : undefined}
userSelect="none" userSelect="none"
cursor={isDragDisabled || !imageDTO ? 'default' : 'pointer'} cursor={isDragDisabled || !imageDTO ? 'default' : 'pointer'}
{...rest}
> >
{imageDTO && ( {imageDTO && (
<Flex <Flex

View File

@ -22,7 +22,6 @@ import {
} from 'features/canvas/store/canvasSlice'; } from 'features/canvas/store/canvasSlice';
import type { CanvasLayer } from 'features/canvas/store/canvasTypes'; import type { CanvasLayer } from 'features/canvas/store/canvasTypes';
import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes'; import { LAYER_NAMES_DICT } from 'features/canvas/store/canvasTypes';
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton';
import { memo, useCallback, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -221,10 +220,6 @@ const IAICanvasToolbar = () => {
return ( return (
<Flex w="full" gap={2} alignItems="center"> <Flex w="full" gap={2} alignItems="center">
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineEnd="auto" />
</Flex>
<Flex flex={1} gap={2} justifyContent="center">
<Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}> <Tooltip label={`${t('unifiedCanvas.layer')} (Q)`}>
<FormControl isDisabled={isStaging} w="5rem"> <FormControl isDisabled={isStaging} w="5rem">
<Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} /> <Combobox value={value} options={LAYER_NAMES_DICT} onChange={handleChangeLayer} />
@ -316,12 +311,6 @@ const IAICanvasToolbar = () => {
<IAICanvasSettingsButtonPopover /> <IAICanvasSettingsButtonPopover />
</ButtonGroup> </ButtonGroup>
</Flex> </Flex>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto">
<ViewerButton />
</Flex>
</Flex>
</Flex>
); );
}; };

View File

@ -4,7 +4,7 @@ import { BrushSize } from 'features/controlLayers/components/BrushSize';
import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover'; import ControlLayersSettingsPopover from 'features/controlLayers/components/ControlLayersSettingsPopover';
import { ToolChooser } from 'features/controlLayers/components/ToolChooser'; import { ToolChooser } from 'features/controlLayers/components/ToolChooser';
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup'; import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton'; import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
import { memo } from 'react'; import { memo } from 'react';
export const ControlLayersToolbar = memo(() => { export const ControlLayersToolbar = memo(() => {
@ -21,7 +21,7 @@ export const ControlLayersToolbar = memo(() => {
</Flex> </Flex>
<Flex flex={1} justifyContent="center"> <Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto"> <Flex gap={2} marginInlineStart="auto">
<ViewerButton /> <ViewerToggleMenu />
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>

View File

@ -11,6 +11,7 @@ import type { GallerySelectionDraggableData, ImageDraggableData, TypesafeDraggab
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId'; import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
import { useMultiselect } from 'features/gallery/hooks/useMultiselect'; import { useMultiselect } from 'features/gallery/hooks/useMultiselect';
import { useScrollIntoView } from 'features/gallery/hooks/useScrollIntoView'; import { useScrollIntoView } from 'features/gallery/hooks/useScrollIntoView';
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
import type { MouseEvent } from 'react'; import type { MouseEvent } from 'react';
import { memo, useCallback, useMemo, useState } from 'react'; import { memo, useCallback, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@ -102,6 +103,10 @@ const GalleryImage = (props: HoverableImageProps) => {
setIsHovered(true); setIsHovered(true);
}, []); }, []);
const onDoubleClick = useCallback(() => {
dispatch(isImageViewerOpenChanged(true));
}, [dispatch]);
const handleMouseOut = useCallback(() => { const handleMouseOut = useCallback(() => {
setIsHovered(false); setIsHovered(false);
}, []); }, []);
@ -143,6 +148,7 @@ const GalleryImage = (props: HoverableImageProps) => {
> >
<IAIDndImage <IAIDndImage
onClick={handleClick} onClick={handleClick}
onDoubleClick={onDoubleClick}
imageDTO={imageDTO} imageDTO={imageDTO}
draggableData={draggableData} draggableData={draggableData}
isSelected={isSelected} isSelected={isSelected}

View File

@ -1,39 +0,0 @@
import { Button } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import type { InvokeTabName } from 'features/ui/store/tabMap';
import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowsDownUpBold } from 'react-icons/pi';
import { useImageViewer } from './useImageViewer';
const TAB_NAME_TO_TKEY_SHORT: Record<InvokeTabName, string> = {
generation: 'controlLayers.controlLayers',
canvas: 'ui.tabs.canvas',
workflows: 'ui.tabs.workflows',
models: 'ui.tabs.models',
queue: 'ui.tabs.queue',
};
export const EditorButton = () => {
const { t } = useTranslation();
const { onClose } = useImageViewer();
const activeTabName = useAppSelector(activeTabNameSelector);
const tooltip = useMemo(
() => t('gallery.switchTo', { tab: t(TAB_NAME_TO_TKEY_SHORT[activeTabName]) }),
[t, activeTabName]
);
return (
<Button
aria-label={tooltip}
tooltip={tooltip}
onClick={onClose}
variant="outline"
leftIcon={<PiArrowsDownUpBold />}
>
{t(TAB_NAME_TO_TKEY_SHORT[activeTabName])}
</Button>
);
};

View File

@ -10,7 +10,7 @@ import { useHotkeys } from 'react-hotkeys-hook';
import CurrentImageButtons from './CurrentImageButtons'; import CurrentImageButtons from './CurrentImageButtons';
import CurrentImagePreview from './CurrentImagePreview'; import CurrentImagePreview from './CurrentImagePreview';
import { EditorButton } from './EditorButton'; import { ViewerToggleMenu } from './ViewerToggleMenu';
const VIEWER_ENABLED_TABS: InvokeTabName[] = ['canvas', 'generation', 'workflows']; const VIEWER_ENABLED_TABS: InvokeTabName[] = ['canvas', 'generation', 'workflows'];
@ -60,7 +60,7 @@ export const ImageViewer = memo(() => {
</Flex> </Flex>
<Flex flex={1} justifyContent="center"> <Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto"> <Flex gap={2} marginInlineStart="auto">
<EditorButton /> <ViewerToggleMenu />
</Flex> </Flex>
</Flex> </Flex>
</Flex> </Flex>

View File

@ -0,0 +1,45 @@
import { Flex } from '@invoke-ai/ui-library';
import { ToggleMetadataViewerButton } from 'features/gallery/components/ImageViewer/ToggleMetadataViewerButton';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
import { memo } from 'react';
import CurrentImageButtons from './CurrentImageButtons';
import CurrentImagePreview from './CurrentImagePreview';
export const ImageViewerWorkflows = memo(() => {
return (
<Flex
layerStyle="first"
borderRadius="base"
position="absolute"
flexDirection="column"
top={0}
right={0}
bottom={0}
left={0}
p={2}
rowGap={4}
alignItems="center"
justifyContent="center"
zIndex={10} // reactflow puts its minimap at 5, so we need to be above that
>
<Flex w="full" gap={2}>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineEnd="auto">
<ToggleProgressButton />
<ToggleMetadataViewerButton />
</Flex>
</Flex>
<Flex flex={1} gap={2} justifyContent="center">
<CurrentImageButtons />
</Flex>
<Flex flex={1} justifyContent="center">
<Flex gap={2} marginInlineStart="auto" />
</Flex>
</Flex>
<CurrentImagePreview />
</Flex>
);
});
ImageViewerWorkflows.displayName = 'ImageViewerWorkflows';

View File

@ -1,25 +0,0 @@
import { Button } from '@invoke-ai/ui-library';
import { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowsDownUpBold } from 'react-icons/pi';
import { useImageViewer } from './useImageViewer';
export const ViewerButton = () => {
const { t } = useTranslation();
const { onOpen } = useImageViewer();
const tooltip = useMemo(() => t('gallery.switchTo', { tab: t('common.viewer') }), [t]);
return (
<Button
aria-label={tooltip}
tooltip={tooltip}
onClick={onOpen}
variant="outline"
pointerEvents="auto"
leftIcon={<PiArrowsDownUpBold />}
>
{t('common.viewer')}
</Button>
);
};

View File

@ -0,0 +1,67 @@
import {
Button,
Flex,
Icon,
Popover,
PopoverArrow,
PopoverBody,
PopoverContent,
PopoverTrigger,
Text,
} from '@invoke-ai/ui-library';
import { useTranslation } from 'react-i18next';
import { PiCaretDownBold, PiCheckBold, PiEyeBold, PiPencilBold } from 'react-icons/pi';
import { useImageViewer } from './useImageViewer';
export const ViewerToggleMenu = () => {
const { t } = useTranslation();
const { isOpen, onClose, onOpen } = useImageViewer();
return (
<Popover>
<PopoverTrigger>
<Button variant="outline">
<Flex gap={3} w="full" alignItems="center">
{isOpen ? <Icon as={PiEyeBold} /> : <Icon as={PiPencilBold} />}
<Text fontSize="md">{isOpen ? t('common.viewing') : t('common.editing')}</Text>
<Icon as={PiCaretDownBold} />
</Flex>
</Button>
</PopoverTrigger>
<PopoverContent p={2}>
<PopoverArrow />
<PopoverBody>
<Flex flexDir="column">
<Button onClick={onOpen} variant="ghost" h="auto" w="auto" p={2}>
<Flex gap={2} w="full">
<Icon as={PiCheckBold} visibility={isOpen ? 'visible' : 'hidden'} />
<Flex flexDir="column" gap={2} alignItems="flex-start">
<Text fontWeight="semibold" color="base.100">
{t('common.viewing')}
</Text>
<Text fontWeight="normal" color="base.300">
{t('common.viewingDesc')}
</Text>
</Flex>
</Flex>
</Button>
<Button onClick={onClose} variant="ghost" h="auto" w="auto" p={2}>
<Flex gap={2} w="full">
<Icon as={PiCheckBold} visibility={isOpen ? 'hidden' : 'visible'} />
<Flex flexDir="column" gap={2} alignItems="flex-start">
<Text fontWeight="semibold" color="base.100">
{t('common.editing')}
</Text>
<Text fontWeight="normal" color="base.300">
{t('common.editingDesc')}
</Text>
</Flex>
</Flex>
</Button>
</Flex>
</PopoverBody>
</PopoverContent>
</Popover>
);
};

View File

@ -1,8 +1,6 @@
import type { PayloadAction } from '@reduxjs/toolkit'; import type { PayloadAction } from '@reduxjs/toolkit';
import { createSlice, isAnyOf } from '@reduxjs/toolkit'; import { createSlice, isAnyOf } from '@reduxjs/toolkit';
import type { PersistConfig, RootState } from 'app/store/store'; import type { PersistConfig, RootState } from 'app/store/store';
import { rgLayerAdded } from 'features/controlLayers/store/controlLayersSlice';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { uniqBy } from 'lodash-es'; import { uniqBy } from 'lodash-es';
import { boardsApi } from 'services/api/endpoints/boards'; import { boardsApi } from 'services/api/endpoints/boards';
import { imagesApi } from 'services/api/endpoints/images'; import { imagesApi } from 'services/api/endpoints/images';
@ -23,7 +21,7 @@ const initialGalleryState: GalleryState = {
boardSearchText: '', boardSearchText: '',
limit: INITIAL_IMAGE_LIMIT, limit: INITIAL_IMAGE_LIMIT,
offset: 0, offset: 0,
isImageViewerOpen: false, isImageViewerOpen: true,
}; };
export const gallerySlice = createSlice({ export const gallerySlice = createSlice({
@ -83,12 +81,6 @@ export const gallerySlice = createSlice({
}, },
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(setActiveTab, (state) => {
state.isImageViewerOpen = false;
});
builder.addCase(rgLayerAdded, (state) => {
state.isImageViewerOpen = false;
});
builder.addMatcher(isAnyBoardDeleted, (state, action) => { builder.addMatcher(isAnyBoardDeleted, (state, action) => {
const deletedBoardId = action.meta.arg.originalArgs; const deletedBoardId = action.meta.arg.originalArgs;
if (deletedBoardId === state.selectedBoardId) { if (deletedBoardId === state.selectedBoardId) {

View File

@ -1,6 +1,5 @@
import { Flex, Spacer } from '@invoke-ai/ui-library'; import { Flex, Spacer } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { ViewerButton } from 'features/gallery/components/ImageViewer/ViewerButton';
import AddNodeButton from 'features/nodes/components/flow/panels/TopPanel/AddNodeButton'; import AddNodeButton from 'features/nodes/components/flow/panels/TopPanel/AddNodeButton';
import ClearFlowButton from 'features/nodes/components/flow/panels/TopPanel/ClearFlowButton'; import ClearFlowButton from 'features/nodes/components/flow/panels/TopPanel/ClearFlowButton';
import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton'; import SaveWorkflowButton from 'features/nodes/components/flow/panels/TopPanel/SaveWorkflowButton';
@ -23,7 +22,6 @@ const TopCenterPanel = () => {
<ClearFlowButton /> <ClearFlowButton />
<SaveWorkflowButton /> <SaveWorkflowButton />
<WorkflowLibraryMenu /> <WorkflowLibraryMenu />
<ViewerButton />
</Flex> </Flex>
); );
}; };

View File

@ -4,7 +4,6 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { $customNavComponent } from 'app/store/nanostores/customNavComponent'; import { $customNavComponent } from 'app/store/nanostores/customNavComponent';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent'; import ImageGalleryContent from 'features/gallery/components/ImageGalleryContent';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import NodeEditorPanelGroup from 'features/nodes/components/sidePanel/NodeEditorPanelGroup'; import NodeEditorPanelGroup from 'features/nodes/components/sidePanel/NodeEditorPanelGroup';
import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent'; import InvokeAILogoComponent from 'features/system/components/InvokeAILogoComponent';
import SettingsMenu from 'features/system/components/SettingsModal/SettingsMenu'; import SettingsMenu from 'features/system/components/SettingsModal/SettingsMenu';
@ -255,9 +254,8 @@ const InvokeTabs = () => {
</> </>
)} )}
<Panel id="main-panel" order={1} minSize={20}> <Panel id="main-panel" order={1} minSize={20}>
<TabPanels w="full" h="full" position="relative"> <TabPanels w="full" h="full">
{tabPanels} {tabPanels}
<ImageViewer />
</TabPanels> </TabPanels>
</Panel> </Panel>
{shouldShowGalleryPanel && ( {shouldShowGalleryPanel && (

View File

@ -1,8 +1,9 @@
import type { ChakraProps } from '@invoke-ai/ui-library'; import type { ChakraProps } from '@invoke-ai/ui-library';
import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library'; import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants'; import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent'; import { ControlLayersPanelContent } from 'features/controlLayers/components/ControlLayersPanelContent';
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
import { Prompts } from 'features/parameters/components/Prompts/Prompts'; import { Prompts } from 'features/parameters/components/Prompts/Prompts';
import QueueControls from 'features/queue/components/QueueControls'; import QueueControls from 'features/queue/components/QueueControls';
import { SDXLPrompts } from 'features/sdxl/components/SDXLPrompts/SDXLPrompts'; import { SDXLPrompts } from 'features/sdxl/components/SDXLPrompts/SDXLPrompts';
@ -15,7 +16,7 @@ import { RefinerSettingsAccordion } from 'features/settingsAccordions/components
import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; import { activeTabNameSelector } from 'features/ui/store/uiSelectors';
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react'; import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
import type { CSSProperties } from 'react'; import type { CSSProperties } from 'react';
import { memo, useMemo } from 'react'; import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
const overlayScrollbarsStyles: CSSProperties = { const overlayScrollbarsStyles: CSSProperties = {
@ -37,6 +38,7 @@ const selectedStyles: ChakraProps['sx'] = {
const ParametersPanelTextToImage = () => { const ParametersPanelTextToImage = () => {
const { t } = useTranslation(); const { t } = useTranslation();
const dispatch = useAppDispatch();
const activeTabName = useAppSelector(activeTabNameSelector); const activeTabName = useAppSelector(activeTabNameSelector);
const controlLayersCount = useAppSelector((s) => s.controlLayers.present.layers.length); const controlLayersCount = useAppSelector((s) => s.controlLayers.present.layers.length);
const controlLayersTitle = useMemo(() => { const controlLayersTitle = useMemo(() => {
@ -46,6 +48,14 @@ const ParametersPanelTextToImage = () => {
return `${t('controlLayers.controlLayers')} (${controlLayersCount})`; return `${t('controlLayers.controlLayers')} (${controlLayersCount})`;
}, [controlLayersCount, t]); }, [controlLayersCount, t]);
const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl'); const isSDXL = useAppSelector((s) => s.generation.model?.base === 'sdxl');
const onChangeTabs = useCallback(
(i: number) => {
if (i === 1) {
dispatch(isImageViewerOpenChanged(false));
}
},
[dispatch]
);
return ( return (
<Flex w="full" h="full" flexDir="column" gap={2}> <Flex w="full" h="full" flexDir="column" gap={2}>
@ -55,7 +65,15 @@ const ParametersPanelTextToImage = () => {
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}> <OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
<Flex gap={2} flexDirection="column" h="full" w="full"> <Flex gap={2} flexDirection="column" h="full" w="full">
{isSDXL ? <SDXLPrompts /> : <Prompts />} {isSDXL ? <SDXLPrompts /> : <Prompts />}
<Tabs variant="enclosed" display="flex" flexDir="column" w="full" h="full" gap={2}> <Tabs
variant="enclosed"
display="flex"
flexDir="column"
w="full"
h="full"
gap={2}
onChange={onChangeTabs}
>
<TabList gap={2} fontSize="sm" borderColor="base.800"> <TabList gap={2} fontSize="sm" borderColor="base.800">
<Tab sx={baseStyles} _selected={selectedStyles}> <Tab sx={baseStyles} _selected={selectedStyles}>
{t('common.settingsLabel')} {t('common.settingsLabel')}

View File

@ -1,9 +1,20 @@
import { Box } from '@invoke-ai/ui-library'; import { Box } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { ImageViewerWorkflows } from 'features/gallery/components/ImageViewer/ImageViewerWorkflows';
import NodeEditor from 'features/nodes/components/NodeEditor'; import NodeEditor from 'features/nodes/components/NodeEditor';
import { memo } from 'react'; import { memo } from 'react';
import { ReactFlowProvider } from 'reactflow'; import { ReactFlowProvider } from 'reactflow';
const NodesTab = () => { const NodesTab = () => {
const mode = useAppSelector((s) => s.workflow.mode);
if (mode === 'view') {
return (
<Box layerStyle="first" position="relative" w="full" h="full" p={2} borderRadius="base">
<ImageViewerWorkflows />
</Box>
);
}
return ( return (
<Box layerStyle="first" position="relative" w="full" h="full" p={2} borderRadius="base"> <Box layerStyle="first" position="relative" w="full" h="full" p={2} borderRadius="base">
<ReactFlowProvider> <ReactFlowProvider>

View File

@ -1,11 +1,13 @@
import { Box } from '@invoke-ai/ui-library'; import { Box } from '@invoke-ai/ui-library';
import { ControlLayersEditor } from 'features/controlLayers/components/ControlLayersEditor'; import { ControlLayersEditor } from 'features/controlLayers/components/ControlLayersEditor';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import { memo } from 'react'; import { memo } from 'react';
const TextToImageTab = () => { const TextToImageTab = () => {
return ( return (
<Box layerStyle="first" position="relative" w="full" h="full" p={2} borderRadius="base"> <Box layerStyle="first" position="relative" w="full" h="full" p={2} borderRadius="base">
<ControlLayersEditor /> <ControlLayersEditor />
<ImageViewer />
</Box> </Box>
); );
}; };