mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-13 06:14:58 -05:00
feat(ui): reworked layout (wip)
This commit is contained in:
@@ -1,11 +1,10 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||
import { CanvasEditor } from 'features/controlLayers/components/CanvasEditor';
|
||||
import { CanvasRightPanelContent } from 'features/controlLayers/components/CanvasRightPanel';
|
||||
import { CanvasTabContent } from 'features/controlLayers/components/CanvasTabContent';
|
||||
import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent';
|
||||
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
|
||||
import { useIsImageViewerOpen } from 'features/gallery/components/ImageViewer/useImageViewer';
|
||||
import NodeEditorPanelGroup from 'features/nodes/components/sidePanel/NodeEditorPanelGroup';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import FloatingGalleryButton from 'features/ui/components/FloatingGalleryButton';
|
||||
@@ -13,19 +12,23 @@ import FloatingParametersPanelButtons from 'features/ui/components/FloatingParam
|
||||
import ParametersPanelTextToImage from 'features/ui/components/ParametersPanels/ParametersPanelTextToImage';
|
||||
import { TabMountGate } from 'features/ui/components/TabMountGate';
|
||||
import ModelManagerTab from 'features/ui/components/tabs/ModelManagerTab';
|
||||
import NodesTab from 'features/ui/components/tabs/NodesTab';
|
||||
import QueueTab from 'features/ui/components/tabs/QueueTab';
|
||||
import { WorkflowsTabContent } from 'features/ui/components/tabs/WorkflowsTabContent';
|
||||
import { TabVisibilityGate } from 'features/ui/components/TabVisibilityGate';
|
||||
import { VerticalNavBar } from 'features/ui/components/VerticalNavBar';
|
||||
import type { UsePanelOptions } from 'features/ui/hooks/usePanel';
|
||||
import { usePanel } from 'features/ui/hooks/usePanel';
|
||||
import { usePanelStorage } from 'features/ui/hooks/usePanelStorage';
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
import {
|
||||
$isGalleryPanelOpen,
|
||||
$isParametersPanelOpen,
|
||||
selectUiSlice,
|
||||
TABS_WITH_GALLERY_PANEL,
|
||||
TABS_WITH_OPTIONS_PANEL,
|
||||
$isLeftPanelOpen,
|
||||
$isRightPanelOpen,
|
||||
LEFT_PANEL_MIN_SIZE_PCT,
|
||||
LEFT_PANEL_MIN_SIZE_PX,
|
||||
RIGHT_PANEL_MIN_SIZE_PCT,
|
||||
RIGHT_PANEL_MIN_SIZE_PX,
|
||||
selectWithLeftPanel,
|
||||
selectWithRightPanel,
|
||||
} from 'features/ui/store/uiSlice';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useMemo, useRef } from 'react';
|
||||
@@ -37,95 +40,75 @@ import ParametersPanelUpscale from './ParametersPanels/ParametersPanelUpscale';
|
||||
import ResizeHandle from './tabs/ResizeHandle';
|
||||
|
||||
const panelStyles: CSSProperties = { position: 'relative', height: '100%', width: '100%' };
|
||||
const GALLERY_MIN_SIZE_PX = 310;
|
||||
const GALLERY_MIN_SIZE_PCT = 20;
|
||||
const OPTIONS_PANEL_MIN_SIZE_PX = 430;
|
||||
const OPTIONS_PANEL_MIN_SIZE_PCT = 20;
|
||||
|
||||
const onGalleryPanelCollapse = (isCollapsed: boolean) => $isGalleryPanelOpen.set(!isCollapsed);
|
||||
const onParametersPanelCollapse = (isCollapsed: boolean) => $isParametersPanelOpen.set(!isCollapsed);
|
||||
|
||||
const selectShouldShowGalleryPanel = createSelector(selectUiSlice, (ui) =>
|
||||
TABS_WITH_GALLERY_PANEL.includes(ui.activeTab)
|
||||
);
|
||||
const selectShouldShowOptionsPanel = createSelector(selectUiSlice, (ui) =>
|
||||
TABS_WITH_OPTIONS_PANEL.includes(ui.activeTab)
|
||||
);
|
||||
const onLeftPanelCollapse = (isCollapsed: boolean) => $isLeftPanelOpen.set(!isCollapsed);
|
||||
const onRightPanelCollapse = (isCollapsed: boolean) => $isRightPanelOpen.set(!isCollapsed);
|
||||
|
||||
export const AppContent = memo(() => {
|
||||
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
const isImageViewerOpen = useIsImageViewerOpen();
|
||||
const shouldShowGalleryPanel = useAppSelector(selectShouldShowGalleryPanel);
|
||||
const shouldShowOptionsPanel = useAppSelector(selectShouldShowOptionsPanel);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useScopeOnFocus('gallery', ref);
|
||||
|
||||
const optionsPanelUsePanelOptions = useMemo<UsePanelOptions>(
|
||||
() => ({
|
||||
id: 'options-panel',
|
||||
unit: 'pixels',
|
||||
minSize: OPTIONS_PANEL_MIN_SIZE_PX,
|
||||
defaultSize: OPTIONS_PANEL_MIN_SIZE_PCT,
|
||||
panelGroupRef,
|
||||
panelGroupDirection: 'horizontal',
|
||||
onCollapse: onParametersPanelCollapse,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const galleryPanelUsePanelOptions = useMemo<UsePanelOptions>(
|
||||
() => ({
|
||||
id: 'gallery-panel',
|
||||
unit: 'pixels',
|
||||
minSize: GALLERY_MIN_SIZE_PX,
|
||||
defaultSize: GALLERY_MIN_SIZE_PCT,
|
||||
panelGroupRef,
|
||||
panelGroupDirection: 'horizontal',
|
||||
onCollapse: onGalleryPanelCollapse,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
|
||||
const panelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
const panelStorage = usePanelStorage();
|
||||
|
||||
const optionsPanel = usePanel(optionsPanelUsePanelOptions);
|
||||
const withLeftPanel = useAppSelector(selectWithLeftPanel);
|
||||
const leftPanelUsePanelOptions = useMemo<UsePanelOptions>(
|
||||
() => ({
|
||||
id: 'left-panel',
|
||||
unit: 'pixels',
|
||||
minSize: LEFT_PANEL_MIN_SIZE_PX,
|
||||
defaultSize: LEFT_PANEL_MIN_SIZE_PCT,
|
||||
panelGroupRef,
|
||||
panelGroupDirection: 'horizontal',
|
||||
onCollapse: onLeftPanelCollapse,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const leftPanel = usePanel(leftPanelUsePanelOptions);
|
||||
useHotkeys(['t', 'o'], leftPanel.toggle, { enabled: withLeftPanel }, [leftPanel.toggle, withLeftPanel]);
|
||||
|
||||
const galleryPanel = usePanel(galleryPanelUsePanelOptions);
|
||||
const withRightPanel = useAppSelector(selectWithRightPanel);
|
||||
const rightPanelUsePanelOptions = useMemo<UsePanelOptions>(
|
||||
() => ({
|
||||
id: 'right-panel',
|
||||
unit: 'pixels',
|
||||
minSize: RIGHT_PANEL_MIN_SIZE_PX,
|
||||
defaultSize: RIGHT_PANEL_MIN_SIZE_PCT,
|
||||
panelGroupRef,
|
||||
panelGroupDirection: 'horizontal',
|
||||
onCollapse: onRightPanelCollapse,
|
||||
}),
|
||||
[]
|
||||
);
|
||||
const rightPanel = usePanel(rightPanelUsePanelOptions);
|
||||
useHotkeys('g', rightPanel.toggle, { enabled: withRightPanel }, [rightPanel.toggle, withRightPanel]);
|
||||
|
||||
useHotkeys('g', galleryPanel.toggle, { enabled: shouldShowGalleryPanel }, [
|
||||
galleryPanel.toggle,
|
||||
shouldShowGalleryPanel,
|
||||
]);
|
||||
useHotkeys(['t', 'o'], optionsPanel.toggle, { enabled: shouldShowOptionsPanel }, [
|
||||
optionsPanel.toggle,
|
||||
shouldShowOptionsPanel,
|
||||
]);
|
||||
useHotkeys(
|
||||
'shift+r',
|
||||
() => {
|
||||
optionsPanel.reset();
|
||||
galleryPanel.reset();
|
||||
leftPanel.reset();
|
||||
rightPanel.reset();
|
||||
},
|
||||
[optionsPanel.reset, galleryPanel.reset]
|
||||
[leftPanel.reset, rightPanel.reset]
|
||||
);
|
||||
useHotkeys(
|
||||
'f',
|
||||
() => {
|
||||
if (optionsPanel.isCollapsed || galleryPanel.isCollapsed) {
|
||||
optionsPanel.expand();
|
||||
galleryPanel.expand();
|
||||
if (leftPanel.isCollapsed || rightPanel.isCollapsed) {
|
||||
leftPanel.expand();
|
||||
rightPanel.expand();
|
||||
} else {
|
||||
optionsPanel.collapse();
|
||||
galleryPanel.collapse();
|
||||
leftPanel.collapse();
|
||||
rightPanel.collapse();
|
||||
}
|
||||
},
|
||||
[
|
||||
optionsPanel.isCollapsed,
|
||||
galleryPanel.isCollapsed,
|
||||
optionsPanel.expand,
|
||||
galleryPanel.expand,
|
||||
optionsPanel.collapse,
|
||||
galleryPanel.collapse,
|
||||
leftPanel.isCollapsed,
|
||||
rightPanel.isCollapsed,
|
||||
leftPanel.expand,
|
||||
rightPanel.expand,
|
||||
leftPanel.collapse,
|
||||
rightPanel.collapse,
|
||||
]
|
||||
);
|
||||
|
||||
@@ -141,63 +124,100 @@ export const AppContent = memo(() => {
|
||||
style={panelStyles}
|
||||
storage={panelStorage}
|
||||
>
|
||||
<Panel order={0} collapsible style={panelStyles} {...optionsPanel.panelProps}>
|
||||
<Flex flexDir="column" w="full" h="full" gap={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
{withLeftPanel && (
|
||||
<>
|
||||
<Panel order={0} collapsible style={panelStyles} {...leftPanel.panelProps}>
|
||||
<TabMountGate tab="generation">
|
||||
<TabVisibilityGate tab="generation">
|
||||
<ParametersPanelTextToImage />
|
||||
<Flex flexDir="column" w="full" h="full" gap={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ParametersPanelTextToImage />
|
||||
</Box>
|
||||
</Flex>
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
<TabMountGate tab="upscaling">
|
||||
<TabVisibilityGate tab="upscaling">
|
||||
<ParametersPanelUpscale />
|
||||
<Flex flexDir="column" w="full" h="full" gap={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ParametersPanelUpscale />
|
||||
</Box>
|
||||
</Flex>
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
<TabMountGate tab="workflows">
|
||||
<TabVisibilityGate tab="workflows">
|
||||
<NodeEditorPanelGroup />
|
||||
<Flex flexDir="column" w="full" h="full" gap={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
<NodeEditorPanelGroup />
|
||||
</Box>
|
||||
</Flex>
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Panel>
|
||||
<ResizeHandle id="options-main-handle" orientation="vertical" {...optionsPanel.resizeHandleProps} />
|
||||
</Panel>
|
||||
<ResizeHandle id="left-main-handle" orientation="vertical" {...leftPanel.resizeHandleProps} />
|
||||
</>
|
||||
)}
|
||||
<Panel id="main-panel" order={1} minSize={20} style={panelStyles}>
|
||||
<TabMountGate tab="generation">
|
||||
<TabVisibilityGate tab="generation">
|
||||
<CanvasEditor />
|
||||
<CanvasTabContent />
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
<TabMountGate tab="upscaling">
|
||||
<TabVisibilityGate tab="upscaling">
|
||||
<ImageViewer />
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
{/* upscaling tab has no content of its own - uses image viewer only */}
|
||||
<TabMountGate tab="workflows">
|
||||
<TabVisibilityGate tab="workflows">
|
||||
<NodesTab />
|
||||
<WorkflowsTabContent />
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
<TabMountGate tab="gallery">
|
||||
<TabVisibilityGate tab="gallery">
|
||||
<ImageViewer />
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
<TabMountGate tab="models">
|
||||
<TabVisibilityGate tab="models">
|
||||
<ModelManagerTab />
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
<TabMountGate tab="queue">
|
||||
<TabVisibilityGate tab="queue">
|
||||
<QueueTab />
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
{isImageViewerOpen && <ImageViewer />}
|
||||
</Panel>
|
||||
<ResizeHandle id="main-gallery-handle" orientation="vertical" {...galleryPanel.resizeHandleProps} />
|
||||
<Panel order={2} style={panelStyles} collapsible {...galleryPanel.panelProps}>
|
||||
<GalleryPanelContent />
|
||||
</Panel>
|
||||
{withRightPanel && (
|
||||
<>
|
||||
<ResizeHandle id="main-right-handle" orientation="vertical" {...rightPanel.resizeHandleProps} />
|
||||
<Panel order={2} style={panelStyles} collapsible {...rightPanel.panelProps}>
|
||||
<RightPanelContent />
|
||||
</Panel>
|
||||
</>
|
||||
)}
|
||||
</PanelGroup>
|
||||
{shouldShowOptionsPanel && <FloatingParametersPanelButtons panelApi={optionsPanel} />}
|
||||
{shouldShowGalleryPanel && <FloatingGalleryButton panelApi={galleryPanel} />}
|
||||
<TabMountGate tab="models">
|
||||
<TabVisibilityGate tab="models">
|
||||
<ModelManagerTab />
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
<TabMountGate tab="models">
|
||||
<TabVisibilityGate tab="queue">
|
||||
<QueueTab />
|
||||
</TabVisibilityGate>
|
||||
</TabMountGate>
|
||||
{withLeftPanel && <FloatingParametersPanelButtons panelApi={leftPanel} />}
|
||||
{withRightPanel && <FloatingGalleryButton panelApi={rightPanel} />}
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
AppContent.displayName = 'AppContent';
|
||||
|
||||
const RightPanelContent = memo(() => {
|
||||
const tab = useAppSelector(selectActiveTab);
|
||||
|
||||
if (tab === 'generation') {
|
||||
return <CanvasRightPanelContent />;
|
||||
}
|
||||
|
||||
return <GalleryPanelContent />;
|
||||
});
|
||||
RightPanelContent.displayName = 'RightPanelContent';
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import type { ChakraProps } from '@invoke-ai/ui-library';
|
||||
import { Box, Flex, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||
import { CanvasPanelContent } from 'features/controlLayers/components/CanvasPanelContent';
|
||||
import { selectIsSDXL } from 'features/controlLayers/store/paramsSlice';
|
||||
import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice';
|
||||
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
|
||||
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
||||
import { CompositingSettingsAccordion } from 'features/settingsAccordions/components/CompositingSettingsAccordion/CompositingSettingsAccordion';
|
||||
@@ -17,47 +14,22 @@ import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePr
|
||||
import { $isMenuOpen } from 'features/stylePresets/store/isMenuOpen';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo, useCallback, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { memo } from 'react';
|
||||
|
||||
const overlayScrollbarsStyles: CSSProperties = {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
const baseStyles: ChakraProps['sx'] = {
|
||||
fontWeight: 'semibold',
|
||||
fontSize: 'sm',
|
||||
color: 'base.300',
|
||||
};
|
||||
|
||||
const selectedStyles: ChakraProps['sx'] = {
|
||||
borderColor: 'base.800',
|
||||
borderBottomColor: 'base.900',
|
||||
color: 'invokeBlue.300',
|
||||
};
|
||||
|
||||
const ParametersPanelTextToImage = () => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const isSDXL = useAppSelector(selectIsSDXL);
|
||||
const onChangeTabs = useCallback(
|
||||
(i: number) => {
|
||||
if (i === 1) {
|
||||
dispatch(isImageViewerOpenChanged(false));
|
||||
}
|
||||
},
|
||||
[dispatch]
|
||||
);
|
||||
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const isMenuOpen = useStore($isMenuOpen);
|
||||
|
||||
return (
|
||||
<Flex w="full" h="full" flexDir="column" gap={2}>
|
||||
<StylePresetMenuTrigger />
|
||||
<Flex w="full" h="full" position="relative">
|
||||
<Box position="absolute" top={0} left={0} right={0} bottom={0} ref={ref}>
|
||||
<Box position="absolute" top={0} left={0} right={0} bottom={0}>
|
||||
{isMenuOpen && (
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
@@ -68,43 +40,11 @@ const ParametersPanelTextToImage = () => {
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<Prompts />
|
||||
<Tabs
|
||||
defaultIndex={0}
|
||||
variant="enclosed"
|
||||
display="flex"
|
||||
flexDir="column"
|
||||
w="full"
|
||||
h="full"
|
||||
gap={2}
|
||||
onChange={onChangeTabs}
|
||||
>
|
||||
<TabList gap={2} fontSize="sm" borderColor="base.800" alignItems="center" w="full" pe={1}>
|
||||
<Tab sx={baseStyles} _selected={selectedStyles} data-testid="generation-tab-settings-tab-button">
|
||||
{t('common.settingsLabel')}
|
||||
</Tab>
|
||||
<Tab
|
||||
sx={baseStyles}
|
||||
_selected={selectedStyles}
|
||||
data-testid="generation-tab-control-layers-tab-button"
|
||||
>
|
||||
{t('controlLayers.layer_other')}
|
||||
</Tab>
|
||||
</TabList>
|
||||
<TabPanels w="full" h="full">
|
||||
<TabPanel p={0} w="full" h="full">
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<ImageSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
<CompositingSettingsAccordion />
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
<AdvancedSettingsAccordion />
|
||||
</Flex>
|
||||
</TabPanel>
|
||||
<TabPanel p={0} w="full" h="full">
|
||||
<CanvasPanelContent />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
<ImageSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
<CompositingSettingsAccordion />
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
<AdvancedSettingsAccordion />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Box>
|
||||
|
||||
@@ -8,7 +8,7 @@ import { TabMountGate } from 'features/ui/components/TabMountGate';
|
||||
import { memo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { MdZoomOutMap } from 'react-icons/md';
|
||||
import { PiFlowArrowBold } from 'react-icons/pi';
|
||||
import { PiFlowArrowBold, PiImageBold } from 'react-icons/pi';
|
||||
import { RiBox2Line, RiInputMethodLine, RiPlayList2Fill } from 'react-icons/ri';
|
||||
|
||||
import { TabButton } from './TabButton';
|
||||
@@ -36,6 +36,9 @@ export const VerticalNavBar = memo(() => {
|
||||
<TabMountGate tab="queue">
|
||||
<TabButton tab="queue" icon={<RiPlayList2Fill />} label={t('ui.tabs.queue')} />
|
||||
</TabMountGate>
|
||||
<TabMountGate tab="gallery">
|
||||
<TabButton tab="gallery" icon={<PiImageBold />} label={t('ui.tabs.gallery')} />
|
||||
</TabMountGate>
|
||||
</Flex>
|
||||
<Spacer />
|
||||
<StatusIndicator />
|
||||
|
||||
@@ -1,37 +0,0 @@
|
||||
import { Box } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useScopeOnFocus } from 'common/hooks/interactionScopes';
|
||||
import NodeEditor from 'features/nodes/components/NodeEditor';
|
||||
import { selectWorkflowMode } from 'features/nodes/store/workflowSlice';
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
import { memo, useRef } from 'react';
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
|
||||
const NodesTab = () => {
|
||||
const mode = useAppSelector(selectWorkflowMode);
|
||||
const activeTabName = useAppSelector(selectActiveTab);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
useScopeOnFocus('workflows', ref);
|
||||
|
||||
return (
|
||||
<Box
|
||||
display={activeTabName === 'workflows' ? undefined : 'none'}
|
||||
hidden={activeTabName !== 'workflows'}
|
||||
ref={ref}
|
||||
layerStyle="first"
|
||||
position="relative"
|
||||
w="full"
|
||||
h="full"
|
||||
p={2}
|
||||
borderRadius="base"
|
||||
>
|
||||
{mode === 'edit' && (
|
||||
<ReactFlowProvider>
|
||||
<NodeEditor />
|
||||
</ReactFlowProvider>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(NodesTab);
|
||||
@@ -0,0 +1,22 @@
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
|
||||
import NodeEditor from 'features/nodes/components/NodeEditor';
|
||||
import { selectWorkflowMode } from 'features/nodes/store/workflowSlice';
|
||||
import { memo } from 'react';
|
||||
import { ReactFlowProvider } from 'reactflow';
|
||||
|
||||
export const WorkflowsTabContent = memo(() => {
|
||||
const mode = useAppSelector(selectWorkflowMode);
|
||||
|
||||
if (mode === 'edit') {
|
||||
return (
|
||||
<ReactFlowProvider>
|
||||
<NodeEditor />
|
||||
</ReactFlowProvider>
|
||||
);
|
||||
}
|
||||
|
||||
return <ImageViewer />;
|
||||
});
|
||||
|
||||
WorkflowsTabContent.displayName = 'WorkflowsTabContent';
|
||||
@@ -107,6 +107,12 @@ export const usePanel = (arg: UsePanelOptions): UsePanelReturn => {
|
||||
}
|
||||
|
||||
const minSizePct = getSizeAsPercentage(arg.minSize, arg.panelGroupRef, arg.panelGroupDirection);
|
||||
|
||||
if (minSizePct > 100) {
|
||||
// This can happen when the panel is hidden
|
||||
return;
|
||||
}
|
||||
|
||||
_setMinSize(minSizePct);
|
||||
|
||||
if (arg.defaultSize && arg.defaultSize > minSizePct) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { PayloadAction } from '@reduxjs/toolkit';
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||
import type { PersistConfig, RootState } from 'app/store/store';
|
||||
import { workflowLoadRequested } from 'features/nodes/store/actions';
|
||||
import { atom } from 'nanostores';
|
||||
@@ -78,7 +78,14 @@ export const uiPersistConfig: PersistConfig<UIState> = {
|
||||
persistDenylist: ['shouldShowImageDetails'],
|
||||
};
|
||||
|
||||
export const $isGalleryPanelOpen = atom(true);
|
||||
export const $isParametersPanelOpen = atom(true);
|
||||
export const TABS_WITH_GALLERY_PANEL: TabName[] = ['generation', 'upscaling', 'workflows'] as const;
|
||||
export const TABS_WITH_OPTIONS_PANEL: TabName[] = ['generation', 'upscaling', 'workflows'] as const;
|
||||
export const LEFT_PANEL_MIN_SIZE_PX = 390;
|
||||
export const LEFT_PANEL_MIN_SIZE_PCT = 20;
|
||||
export const TABS_WITH_LEFT_PANEL: TabName[] = ['generation', 'upscaling', 'workflows'] as const;
|
||||
export const $isLeftPanelOpen = atom(true);
|
||||
export const selectWithLeftPanel = createSelector(selectUiSlice, (ui) => TABS_WITH_LEFT_PANEL.includes(ui.activeTab));
|
||||
|
||||
export const TABS_WITH_RIGHT_PANEL: TabName[] = ['generation', 'upscaling', 'workflows', 'gallery'] as const;
|
||||
export const RIGHT_PANEL_MIN_SIZE_PX = 390;
|
||||
export const RIGHT_PANEL_MIN_SIZE_PCT = 20;
|
||||
export const $isRightPanelOpen = atom(true);
|
||||
export const selectWithRightPanel = createSelector(selectUiSlice, (ui) => TABS_WITH_RIGHT_PANEL.includes(ui.activeTab));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export type TabName = 'generation' | 'upscaling' | 'workflows' | 'models' | 'queue';
|
||||
export type TabName = 'generation' | 'upscaling' | 'workflows' | 'models' | 'queue' | 'gallery';
|
||||
|
||||
export interface UIState {
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user