mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-02-13 05:34:58 -05:00
feat(ui): get layouts working
This commit is contained in:
@@ -14,7 +14,7 @@ export const CanvasLayersPanel = memo(() => {
|
||||
|
||||
return (
|
||||
<CanvasManagerProviderGate>
|
||||
<Flex flexDir="column" gap={2} w="full" h="full">
|
||||
<Flex flexDir="column" gap={2} w="full" h="full" p={2}>
|
||||
<EntityListSelectedEntityActionBar />
|
||||
<Divider py={0} />
|
||||
<ParamDenoisingStrength />
|
||||
|
||||
@@ -11,7 +11,7 @@ export const CanvasLaunchpadPanel = memo(() => {
|
||||
return (
|
||||
<Flex flexDir="column" h="full" w="full" alignItems="center" gap={2}>
|
||||
<Flex flexDir="column" w="full" h="full" gap={4} px={12} maxW={768} pt="20%">
|
||||
<Heading mb={4}>Get started with Invoke.</Heading>
|
||||
<Heading mb={4}>Edit and refine on Canvas.</Heading>
|
||||
<Flex flexDir="column" gap={8}>
|
||||
<Grid gridTemplateColumns="1fr 1fr" gap={8}>
|
||||
<InitialStateMainModelPicker />
|
||||
|
||||
@@ -16,7 +16,7 @@ export const GenerateLaunchpadPanel = memo(() => {
|
||||
return (
|
||||
<Flex flexDir="column" h="full" w="full" alignItems="center" justifyContent="center" gap={2}>
|
||||
<Flex flexDir="column" w="full" h="full" justifyContent="center" gap={4} px={12} maxW={768}>
|
||||
<Heading mb={4}>Get started with Invoke.</Heading>
|
||||
<Heading mb={4}>Generate images from text prompts.</Heading>
|
||||
<Flex flexDir="column" gap={8}>
|
||||
<Grid gridTemplateColumns="1fr 1fr" gap={8}>
|
||||
<InitialStateMainModelPicker />
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
import { Flex, Heading } from '@invoke-ai/ui-library';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const UpscalingLaunchpadPanel = memo(() => {
|
||||
return (
|
||||
<Flex flexDir="column" h="full" w="full" alignItems="center" justifyContent="center" gap={2}>
|
||||
<Flex flexDir="column" w="full" h="full" justifyContent="center" gap={4} px={12} maxW={768}>
|
||||
<Heading mb={4}>Upscale and add detail.</Heading>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
UpscalingLaunchpadPanel.displayName = 'UpscalingLaunchpadPanel';
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Divider, Flex, Heading } from '@invoke-ai/ui-library';
|
||||
import { Divider, Flex } from '@invoke-ai/ui-library';
|
||||
import { CanvasSettingsPopover } from 'features/controlLayers/components/Settings/CanvasSettingsPopover';
|
||||
import { ToolColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker';
|
||||
import { ToolSettings } from 'features/controlLayers/components/Tool/ToolSettings';
|
||||
@@ -29,10 +29,6 @@ export const CanvasToolbar = memo(() => {
|
||||
|
||||
return (
|
||||
<Flex w="full" gap={2} alignItems="center" px={2}>
|
||||
<Heading size="sm" me={2}>
|
||||
Canvas
|
||||
</Heading>
|
||||
<Divider orientation="vertical" />
|
||||
<ToolColorPicker />
|
||||
<ToolSettings />
|
||||
<Flex alignItems="center" h="full" flexGrow={1} justifyContent="flex-end">
|
||||
@@ -48,7 +44,6 @@ export const CanvasToolbar = memo(() => {
|
||||
<CanvasToolbarNewSessionMenuButton />
|
||||
<CanvasSettingsPopover />
|
||||
</Flex>
|
||||
<Divider orientation="vertical" />
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,8 @@ import type { PayloadAction, Selector } from '@reduxjs/toolkit';
|
||||
import { createSelector, createSlice } from '@reduxjs/toolkit';
|
||||
import type { RootState } from 'app/store/store';
|
||||
import type { AppConfig, NumericalParameterConfig, PartialAppConfig } from 'app/types/invokeai';
|
||||
import type { TabName } from 'features/ui/store/uiTypes';
|
||||
import { ALL_TABS } from 'features/ui/store/uiTypes';
|
||||
import { merge } from 'lodash-es';
|
||||
|
||||
const baseDimensionConfig: NumericalParameterConfig = {
|
||||
@@ -225,3 +227,12 @@ export const selectIsClientSideUploadEnabled = createConfigSelector((config) =>
|
||||
export const selectAllowPublishWorkflows = createConfigSelector((config) => config.allowPublishWorkflows);
|
||||
export const selectIsLocal = createSelector(selectConfigSlice, (config) => config.isLocal);
|
||||
export const selectShouldShowCredits = createConfigSelector((config) => config.shouldShowCredits);
|
||||
export const selectEnabledTabs = createConfigSelector((config) => {
|
||||
const enabledTabs: TabName[] = [];
|
||||
for (const tab of ALL_TABS) {
|
||||
if (!config.disabledTabs.includes(tab)) {
|
||||
enabledTabs.push(tab);
|
||||
}
|
||||
}
|
||||
return enabledTabs;
|
||||
});
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
import 'dockview/dist/styles/dockview.css';
|
||||
import 'features/ui/styles/dockview-theme-invoke.css';
|
||||
|
||||
import { Flex } from '@invoke-ai/ui-library';
|
||||
import { TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { useDndMonitor } from 'features/dnd/useDndMonitor';
|
||||
import { VerticalNavBar } from 'features/ui/components/VerticalNavBar';
|
||||
import { AutoLayout } from 'features/ui/layouts/AutoLayout';
|
||||
import { CanvasTabAutoLayout } from 'features/ui/layouts/canvas-tab-auto-layout';
|
||||
import { GenerateTabAutoLayout } from 'features/ui/layouts/generate-tab-auto-layout';
|
||||
import { UpscalingTabAutoLayout } from 'features/ui/layouts/upscaling-tab-auto-layout';
|
||||
import { selectActiveTabIndex } from 'features/ui/store/uiSelectors';
|
||||
import { $isLeftPanelOpen, $isRightPanelOpen } from 'features/ui/store/uiSlice';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo } from 'react';
|
||||
@@ -16,6 +20,7 @@ const onRightPanelCollapse = (isCollapsed: boolean) => $isRightPanelOpen.set(!is
|
||||
|
||||
export const AppContent = memo(() => {
|
||||
// const tab = useAppSelector(selectActiveTab);
|
||||
const tabIndex = useAppSelector(selectActiveTabIndex);
|
||||
// const imperativePanelGroupRef = useRef<ImperativePanelGroupHandle>(null);
|
||||
useDndMonitor();
|
||||
|
||||
@@ -93,10 +98,22 @@ export const AppContent = memo(() => {
|
||||
// });
|
||||
|
||||
return (
|
||||
<Flex id="invoke-app-tabs" w="full" h="full" p={0} overflow="hidden">
|
||||
<VerticalNavBar />
|
||||
<AutoLayout />
|
||||
</Flex>
|
||||
<Tabs index={tabIndex} display="flex" w="full" h="full" p={0} overflow="hidden">
|
||||
<TabList>
|
||||
<VerticalNavBar />
|
||||
</TabList>
|
||||
<TabPanels w="full" h="full" p={0}>
|
||||
<TabPanel w="full" h="full" p={0}>
|
||||
<GenerateTabAutoLayout />
|
||||
</TabPanel>
|
||||
<TabPanel w="full" h="full" p={0}>
|
||||
<CanvasTabAutoLayout />
|
||||
</TabPanel>
|
||||
<TabPanel w="full" h="full" p={0}>
|
||||
<UpscalingTabAutoLayout />
|
||||
</TabPanel>
|
||||
</TabPanels>
|
||||
</Tabs>
|
||||
);
|
||||
});
|
||||
AppContent.displayName = 'AppContent';
|
||||
|
||||
@@ -22,7 +22,7 @@ const overlayScrollbarsStyles: CSSProperties = {
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
const ParametersPanelTextToImage = () => {
|
||||
export const ParametersPanelCanvas = memo(() => {
|
||||
const isSDXL = useAppSelector(selectIsSDXL);
|
||||
const isCogview4 = useAppSelector(selectIsCogView4);
|
||||
const isStylePresetsMenuOpen = useStore($isStylePresetsMenuOpen);
|
||||
@@ -55,6 +55,6 @@ const ParametersPanelTextToImage = () => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default memo(ParametersPanelTextToImage);
|
||||
ParametersPanelCanvas.displayName = 'ParametersPanelCanvas';
|
||||
@@ -0,0 +1,58 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import { useStore } from '@nanostores/react';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import { overlayScrollbarsParams } from 'common/components/OverlayScrollbars/constants';
|
||||
import { selectIsCogView4, selectIsSDXL } from 'features/controlLayers/store/paramsSlice';
|
||||
import { Prompts } from 'features/parameters/components/Prompts/Prompts';
|
||||
import { useIsApiModel } from 'features/parameters/hooks/useIsApiModel';
|
||||
import { AdvancedSettingsAccordion } from 'features/settingsAccordions/components/AdvancedSettingsAccordion/AdvancedSettingsAccordion';
|
||||
import { GenerationSettingsAccordion } from 'features/settingsAccordions/components/GenerationSettingsAccordion/GenerationSettingsAccordion';
|
||||
import { ImageSettingsAccordion } from 'features/settingsAccordions/components/ImageSettingsAccordion/ImageSettingsAccordion';
|
||||
import { RefinerSettingsAccordion } from 'features/settingsAccordions/components/RefinerSettingsAccordion/RefinerSettingsAccordion';
|
||||
import { StylePresetMenu } from 'features/stylePresets/components/StylePresetMenu';
|
||||
import { StylePresetMenuTrigger } from 'features/stylePresets/components/StylePresetMenuTrigger';
|
||||
import { $isStylePresetsMenuOpen } from 'features/stylePresets/store/stylePresetSlice';
|
||||
import { OverlayScrollbarsComponent } from 'overlayscrollbars-react';
|
||||
import type { CSSProperties } from 'react';
|
||||
import { memo } from 'react';
|
||||
|
||||
const overlayScrollbarsStyles: CSSProperties = {
|
||||
height: '100%',
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
export const ParametersPanelGenerate = memo(() => {
|
||||
const isSDXL = useAppSelector(selectIsSDXL);
|
||||
const isCogview4 = useAppSelector(selectIsCogView4);
|
||||
const isStylePresetsMenuOpen = useStore($isStylePresetsMenuOpen);
|
||||
|
||||
const isApiModel = useIsApiModel();
|
||||
|
||||
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}>
|
||||
{isStylePresetsMenuOpen && (
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<StylePresetMenu />
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
)}
|
||||
<OverlayScrollbarsComponent defer style={overlayScrollbarsStyles} options={overlayScrollbarsParams.options}>
|
||||
<Flex gap={2} flexDirection="column" h="full" w="full">
|
||||
<Prompts />
|
||||
<ImageSettingsAccordion />
|
||||
<GenerationSettingsAccordion />
|
||||
{isSDXL && <RefinerSettingsAccordion />}
|
||||
{!isCogview4 && !isApiModel && <AdvancedSettingsAccordion />}
|
||||
</Flex>
|
||||
</OverlayScrollbarsComponent>
|
||||
</Box>
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
|
||||
ParametersPanelGenerate.displayName = 'ParametersPanelGenerate';
|
||||
@@ -17,7 +17,7 @@ const overlayScrollbarsStyles: CSSProperties = {
|
||||
width: '100%',
|
||||
};
|
||||
|
||||
const ParametersPanelUpscale = () => {
|
||||
export const ParametersPanelUpscale = memo(() => {
|
||||
const isStylePresetsMenuOpen = useStore($isStylePresetsMenuOpen);
|
||||
|
||||
return (
|
||||
@@ -44,6 +44,6 @@ const ParametersPanelUpscale = () => {
|
||||
</Flex>
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
});
|
||||
|
||||
export default memo(ParametersPanelUpscale);
|
||||
ParametersPanelUpscale.displayName = 'ParametersPanelUpscale';
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { SystemStyleObject } from '@invoke-ai/ui-library';
|
||||
import { IconButton, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { IconButton, Tab, Tooltip } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { useCallbackOnDragEnter } from 'common/hooks/useCallbackOnDragEnter';
|
||||
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||
@@ -26,13 +26,14 @@ export const TabButton = memo(({ tab, icon, label }: { tab: TabName; icon: React
|
||||
return (
|
||||
<Tooltip label={label} placement="end">
|
||||
<IconButton
|
||||
as={Tab}
|
||||
p={0}
|
||||
ref={ref}
|
||||
onClick={selectTab}
|
||||
icon={icon}
|
||||
size="md"
|
||||
fontSize="24px"
|
||||
variant="appTab"
|
||||
variant="link"
|
||||
data-selected={activeTabName === tab}
|
||||
aria-label={label}
|
||||
data-testid={label}
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import { ParametersPanelCanvas } from 'features/ui/components/ParametersPanels/ParametersPanelCanvas';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const CanvasTabLeftPanel = memo(() => {
|
||||
return (
|
||||
<Flex flexDir="column" w="full" h="full" gap={2} py={2} pe={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ParametersPanelCanvas />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
CanvasTabLeftPanel.displayName = 'CanvasTabLeftPanel';
|
||||
@@ -66,6 +66,7 @@ export const CanvasWorkspacePanel = memo(() => {
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
overflow="hidden"
|
||||
p={2}
|
||||
>
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasToolbar />
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import { ParametersPanelGenerate } from 'features/ui/components/ParametersPanels/ParametersPanelGenerate';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const GenerateTabLeftPanel = memo(() => {
|
||||
return (
|
||||
<Flex flexDir="column" w="full" h="full" gap={2} py={2} pe={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ParametersPanelGenerate />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
GenerateTabLeftPanel.displayName = 'GenerateTabLeftPanel';
|
||||
@@ -0,0 +1,16 @@
|
||||
import { Box, Flex } from '@invoke-ai/ui-library';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import { ParametersPanelUpscale } from 'features/ui/components/ParametersPanels/ParametersPanelUpscale';
|
||||
import { memo } from 'react';
|
||||
|
||||
export const UpscalingTabLeftPanel = memo(() => {
|
||||
return (
|
||||
<Flex flexDir="column" w="full" h="full" gap={2} py={2} pe={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ParametersPanelUpscale />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
UpscalingTabLeftPanel.displayName = 'UpscalingTabLeftPanel';
|
||||
@@ -1,225 +1,71 @@
|
||||
import { Box, ContextMenu, Divider, Flex, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
|
||||
import { $isLayoutLoading } from 'app/store/nanostores/globalIsLoading';
|
||||
import { useAppSelector } from 'app/store/storeHooks';
|
||||
import type { GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
|
||||
import { DockviewReact, GridviewReact, Orientation } from 'dockview';
|
||||
import { CanvasAlertsInvocationProgress } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress';
|
||||
import { CanvasAlertsPreserveMask } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsPreserveMask';
|
||||
import { CanvasAlertsSelectedEntityStatus } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSelectedEntityStatus';
|
||||
import { CanvasContextMenuGlobalMenuItems } from 'features/controlLayers/components/CanvasContextMenu/CanvasContextMenuGlobalMenuItems';
|
||||
import { CanvasContextMenuSelectedEntityMenuItems } from 'features/controlLayers/components/CanvasContextMenu/CanvasContextMenuSelectedEntityMenuItems';
|
||||
import { CanvasDropArea } from 'features/controlLayers/components/CanvasDropArea';
|
||||
import { CanvasLayersPanel } from 'features/controlLayers/components/CanvasLayersPanelContent';
|
||||
import { Filter } from 'features/controlLayers/components/Filters/Filter';
|
||||
import { CanvasHUD } from 'features/controlLayers/components/HUD/CanvasHUD';
|
||||
import { InvokeCanvasComponent } from 'features/controlLayers/components/InvokeCanvasComponent';
|
||||
import { SelectObject } from 'features/controlLayers/components/SelectObject/SelectObject';
|
||||
import { CanvasSessionContextProvider } from 'features/controlLayers/components/SimpleSession/context';
|
||||
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel';
|
||||
import { StagingAreaItemsList } from 'features/controlLayers/components/SimpleSession/StagingAreaItemsList';
|
||||
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
||||
import { CanvasToolbar } from 'features/controlLayers/components/Toolbar/CanvasToolbar';
|
||||
import { Transform } from 'features/controlLayers/components/Transform/Transform';
|
||||
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||
import { selectDynamicGrid, selectShowHUD } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { selectCanvasSessionId } from 'features/controlLayers/store/canvasStagingAreaSlice';
|
||||
import { CanvasLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/CanvasLaunchpadPanel';
|
||||
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
|
||||
import { GalleryPanel } from 'features/gallery/components/Gallery';
|
||||
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer2';
|
||||
import { ProgressImage } from 'features/gallery/components/ImageViewer/ProgressImage2';
|
||||
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar2';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import ParametersPanelTextToImage from 'features/ui/components/ParametersPanels/ParametersPanelTextToImage';
|
||||
import { GenerationProgressPanel } from 'features/gallery/components/ImageViewer/GenerationProgressPanel';
|
||||
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
|
||||
import { AutoLayoutProvider } from 'features/ui/layouts/auto-layout-context';
|
||||
import { components } from 'features/ui/layouts/components';
|
||||
import { TabWithoutCloseButton } from 'features/ui/layouts/TabWithoutCloseButton';
|
||||
import { LEFT_PANEL_MIN_SIZE_PX, RIGHT_PANEL_MIN_SIZE_PX } from 'features/ui/store/uiSlice';
|
||||
import { dockviewTheme } from 'features/ui/styles/theme';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { PiDotsThreeOutlineVerticalFill } from 'react-icons/pi';
|
||||
import { atom } from 'nanostores';
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
|
||||
const MenuContent = memo(() => {
|
||||
return (
|
||||
<CanvasManagerProviderGate>
|
||||
<MenuList>
|
||||
<CanvasContextMenuSelectedEntityMenuItems />
|
||||
<CanvasContextMenuGlobalMenuItems />
|
||||
</MenuList>
|
||||
</CanvasManagerProviderGate>
|
||||
);
|
||||
});
|
||||
MenuContent.displayName = 'MenuContent';
|
||||
import { CanvasTabLeftPanel } from './CanvasTabLeftPanel';
|
||||
import { CanvasWorkspacePanel } from './CanvasWorkspacePanel';
|
||||
import { useOnFirstVisible } from './use-on-first-visible';
|
||||
|
||||
const canvasBgSx = {
|
||||
position: 'relative',
|
||||
w: 'full',
|
||||
h: 'full',
|
||||
borderRadius: 'base',
|
||||
overflow: 'hidden',
|
||||
bg: 'base.900',
|
||||
'&[data-dynamic-grid="true"]': {
|
||||
bg: 'base.850',
|
||||
},
|
||||
};
|
||||
|
||||
export const CanvasWorkspacePanel = memo(() => {
|
||||
const dynamicGrid = useAppSelector(selectDynamicGrid);
|
||||
const showHUD = useAppSelector(selectShowHUD);
|
||||
const canvasId = useAppSelector(selectCanvasSessionId);
|
||||
|
||||
const renderMenu = useCallback(() => {
|
||||
return <MenuContent />;
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Flex
|
||||
tabIndex={-1}
|
||||
borderRadius="base"
|
||||
position="relative"
|
||||
flexDirection="column"
|
||||
height="full"
|
||||
width="full"
|
||||
gap={2}
|
||||
alignItems="center"
|
||||
justifyContent="center"
|
||||
overflow="hidden"
|
||||
>
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasToolbar />
|
||||
</CanvasManagerProviderGate>
|
||||
<Divider />
|
||||
<ContextMenu<HTMLDivElement> renderMenu={renderMenu} withLongPress={false}>
|
||||
{(ref) => (
|
||||
<Flex ref={ref} sx={canvasBgSx} data-dynamic-grid={dynamicGrid}>
|
||||
<InvokeCanvasComponent />
|
||||
<CanvasManagerProviderGate>
|
||||
<Flex
|
||||
position="absolute"
|
||||
flexDir="column"
|
||||
top={1}
|
||||
insetInlineStart={1}
|
||||
pointerEvents="none"
|
||||
gap={2}
|
||||
alignItems="flex-start"
|
||||
>
|
||||
{showHUD && <CanvasHUD />}
|
||||
<CanvasAlertsSelectedEntityStatus />
|
||||
<CanvasAlertsPreserveMask />
|
||||
<CanvasAlertsInvocationProgress />
|
||||
</Flex>
|
||||
<Flex position="absolute" top={1} insetInlineEnd={1}>
|
||||
<Menu>
|
||||
<MenuButton as={IconButton} icon={<PiDotsThreeOutlineVerticalFill />} colorScheme="base" />
|
||||
<MenuContent />
|
||||
</Menu>
|
||||
</Flex>
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
)}
|
||||
</ContextMenu>
|
||||
{canvasId !== null && (
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasSessionContextProvider type="advanced" id={canvasId}>
|
||||
<Flex
|
||||
position="absolute"
|
||||
flexDir="column"
|
||||
bottom={4}
|
||||
gap={2}
|
||||
align="center"
|
||||
justify="center"
|
||||
left={4}
|
||||
right={4}
|
||||
>
|
||||
<Flex position="relative" maxW="full" w="full" h={108}>
|
||||
<StagingAreaItemsList />
|
||||
</Flex>
|
||||
<Flex gap={2}>
|
||||
<StagingAreaToolbar />
|
||||
</Flex>
|
||||
</Flex>
|
||||
</CanvasSessionContextProvider>
|
||||
</CanvasManagerProviderGate>
|
||||
)}
|
||||
<Flex position="absolute" bottom={4}>
|
||||
<CanvasManagerProviderGate>
|
||||
<Filter />
|
||||
<Transform />
|
||||
<SelectObject />
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasDropArea />
|
||||
</CanvasManagerProviderGate>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
CanvasWorkspacePanel.displayName = 'CanvasPanel';
|
||||
|
||||
const LayersPanelContent = memo(() => (
|
||||
<CanvasManagerProviderGate>
|
||||
<CanvasLayersPanel />
|
||||
</CanvasManagerProviderGate>
|
||||
));
|
||||
LayersPanelContent.displayName = 'LayersPanelContent';
|
||||
|
||||
const ViewerPanelContent = memo(() => (
|
||||
<Flex flexDir="column" w="full" h="full" overflow="hidden" p={2} gap={2}>
|
||||
<ViewerToolbar />
|
||||
<Divider />
|
||||
<ImageViewer />
|
||||
</Flex>
|
||||
));
|
||||
ViewerPanelContent.displayName = 'ViewerPanelContent';
|
||||
|
||||
const ProgressPanelContent = memo(() => (
|
||||
<Flex flexDir="column" w="full" h="full" overflow="hidden" p={2}>
|
||||
<ProgressImage />
|
||||
</Flex>
|
||||
));
|
||||
ProgressPanelContent.displayName = 'ProgressPanelContent';
|
||||
const LAUNCHPAD_PANEL_ID = 'launchpad';
|
||||
const WORKSPACE_PANEL_ID = 'workspace';
|
||||
const VIEWER_PANEL_ID = 'viewer';
|
||||
const PROGRESS_PANEL_ID = 'progress';
|
||||
|
||||
const mainPanelComponents: IDockviewReactProps['components'] = {
|
||||
canvasLaunchpad: GenerateLaunchpadPanel,
|
||||
canvas: CanvasWorkspacePanel,
|
||||
viewer: ViewerPanelContent,
|
||||
progress: ProgressPanelContent,
|
||||
[LAUNCHPAD_PANEL_ID]: CanvasLaunchpadPanel,
|
||||
[WORKSPACE_PANEL_ID]: CanvasWorkspacePanel,
|
||||
[VIEWER_PANEL_ID]: ImageViewerPanel,
|
||||
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
|
||||
};
|
||||
|
||||
const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
|
||||
const { api } = event;
|
||||
api.addPanel({
|
||||
id: 'canvasLaunchpad',
|
||||
component: 'canvasLaunchpad',
|
||||
title: 'canvasLaunchpad',
|
||||
id: LAUNCHPAD_PANEL_ID,
|
||||
component: LAUNCHPAD_PANEL_ID,
|
||||
title: 'Launchpad',
|
||||
});
|
||||
api.addPanel({
|
||||
id: 'canvas',
|
||||
component: 'canvas',
|
||||
id: WORKSPACE_PANEL_ID,
|
||||
component: WORKSPACE_PANEL_ID,
|
||||
title: 'Canvas',
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: 'canvasLaunchpad',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
},
|
||||
});
|
||||
api.addPanel({
|
||||
id: 'viewer',
|
||||
component: 'viewer',
|
||||
id: VIEWER_PANEL_ID,
|
||||
component: VIEWER_PANEL_ID,
|
||||
title: 'Image Viewer',
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: 'canvasLaunchpad',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
},
|
||||
});
|
||||
api.addPanel({
|
||||
id: 'progress',
|
||||
component: 'progress',
|
||||
id: PROGRESS_PANEL_ID,
|
||||
component: PROGRESS_PANEL_ID,
|
||||
title: 'Generation Progress',
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: 'canvasLaunchpad',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
},
|
||||
});
|
||||
|
||||
api.getPanel(LAUNCHPAD_PANEL_ID)?.api.setActive();
|
||||
|
||||
const disposables = [
|
||||
api.onWillShowOverlay((e) => {
|
||||
if (e.kind === 'header_space' || e.kind === 'tab') {
|
||||
@@ -238,102 +84,122 @@ const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
|
||||
|
||||
const MainPanel = memo(() => {
|
||||
return (
|
||||
<Flex w="full" h="full">
|
||||
<DockviewReact
|
||||
disableDnd={true}
|
||||
locked={true}
|
||||
disableFloatingGroups={true}
|
||||
dndEdges={false}
|
||||
defaultTabComponent={TabWithoutCloseButton}
|
||||
components={components}
|
||||
onReady={onReadyMainPanel}
|
||||
theme={dockviewTheme}
|
||||
/>
|
||||
</Flex>
|
||||
<DockviewReact
|
||||
disableDnd={true}
|
||||
locked={true}
|
||||
disableFloatingGroups={true}
|
||||
dndEdges={false}
|
||||
defaultTabComponent={TabWithoutCloseButton}
|
||||
components={mainPanelComponents}
|
||||
onReady={onReadyMainPanel}
|
||||
theme={dockviewTheme}
|
||||
/>
|
||||
);
|
||||
});
|
||||
MainPanel.displayName = 'MainPanel';
|
||||
|
||||
const Left = memo(() => {
|
||||
return (
|
||||
<Flex flexDir="column" w="full" h="full" gap={2} py={2} pe={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ParametersPanelTextToImage />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
Left.displayName = 'Left';
|
||||
|
||||
const Null = () => null;
|
||||
const LEFT_PANEL_ID = 'left';
|
||||
const MAIN_PANEL_ID = 'main';
|
||||
const BOARDS_PANEL_ID = 'boards';
|
||||
const GALLERY_PANEL_ID = 'gallery';
|
||||
const LAYERS_PANEL_ID = 'layers';
|
||||
|
||||
export const canvasTabComponents: IGridviewReactProps['components'] = {
|
||||
left: Left,
|
||||
main: MainPanel,
|
||||
boards: BoardsPanel,
|
||||
gallery: GalleryPanel,
|
||||
layers: LayersPanelContent,
|
||||
[LEFT_PANEL_ID]: CanvasTabLeftPanel,
|
||||
[MAIN_PANEL_ID]: MainPanel,
|
||||
[BOARDS_PANEL_ID]: BoardsPanel,
|
||||
[GALLERY_PANEL_ID]: GalleryPanel,
|
||||
[LAYERS_PANEL_ID]: CanvasLayersPanel,
|
||||
};
|
||||
|
||||
export const initializeCanvasTabLayout = (api: GridviewApi) => {
|
||||
const main = api.addPanel({
|
||||
id: 'main',
|
||||
component: 'main',
|
||||
minimumWidth: 256,
|
||||
export const initializeLayout = (api: GridviewApi) => {
|
||||
api.addPanel({
|
||||
id: MAIN_PANEL_ID,
|
||||
component: MAIN_PANEL_ID,
|
||||
});
|
||||
const left = api.addPanel({
|
||||
id: 'left',
|
||||
component: 'left',
|
||||
api.addPanel({
|
||||
id: LEFT_PANEL_ID,
|
||||
component: LEFT_PANEL_ID,
|
||||
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
|
||||
position: {
|
||||
direction: 'left',
|
||||
referencePanel: 'main',
|
||||
referencePanel: MAIN_PANEL_ID,
|
||||
},
|
||||
});
|
||||
api.addPanel({
|
||||
id: 'gallery',
|
||||
component: 'gallery',
|
||||
id: GALLERY_PANEL_ID,
|
||||
component: GALLERY_PANEL_ID,
|
||||
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
|
||||
minimumHeight: 232,
|
||||
position: {
|
||||
direction: 'right',
|
||||
referencePanel: 'main',
|
||||
referencePanel: MAIN_PANEL_ID,
|
||||
},
|
||||
});
|
||||
api.addPanel({
|
||||
id: 'layers',
|
||||
component: 'layers',
|
||||
id: LAYERS_PANEL_ID,
|
||||
component: LAYERS_PANEL_ID,
|
||||
minimumHeight: 256,
|
||||
position: {
|
||||
direction: 'below',
|
||||
referencePanel: 'gallery',
|
||||
referencePanel: GALLERY_PANEL_ID,
|
||||
},
|
||||
});
|
||||
const boards = api.addPanel({
|
||||
id: 'boards',
|
||||
component: 'boards',
|
||||
api.addPanel({
|
||||
id: BOARDS_PANEL_ID,
|
||||
component: BOARDS_PANEL_ID,
|
||||
minimumHeight: 36,
|
||||
position: {
|
||||
direction: 'above',
|
||||
referencePanel: 'gallery',
|
||||
referencePanel: GALLERY_PANEL_ID,
|
||||
},
|
||||
});
|
||||
left.api.setSize({ width: LEFT_PANEL_MIN_SIZE_PX });
|
||||
boards.api.setSize({ height: 256, width: RIGHT_PANEL_MIN_SIZE_PX });
|
||||
api.getPanel(LEFT_PANEL_ID)?.api.setSize({ width: LEFT_PANEL_MIN_SIZE_PX });
|
||||
api.getPanel(BOARDS_PANEL_ID)?.api.setSize({ height: 256, width: RIGHT_PANEL_MIN_SIZE_PX });
|
||||
api.getPanel(MAIN_PANEL_ID)?.api.setActive();
|
||||
};
|
||||
|
||||
export const CanvasTabAutoLayout = memo(() => {
|
||||
const [api, setApi] = useState<GridviewApi | null>(null);
|
||||
const onReady = useCallback<IGridviewReactProps['onReady']>((event) => {
|
||||
$isLayoutLoading.set(true);
|
||||
setApi(event.api);
|
||||
initializeCanvasTabLayout(event.api);
|
||||
$isLayoutLoading.set(false);
|
||||
}, []);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const $api = useState(() => atom<GridviewApi | null>(null))[0];
|
||||
const onReady = useCallback<IGridviewReactProps['onReady']>(
|
||||
(event) => {
|
||||
$api.set(event.api);
|
||||
initializeLayout(event.api);
|
||||
},
|
||||
[$api]
|
||||
);
|
||||
const resizeMainPanelOnFirstVisible = useCallback(() => {
|
||||
const api = $api.get();
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
const mainPanel = api.getPanel(MAIN_PANEL_ID);
|
||||
if (!mainPanel) {
|
||||
return;
|
||||
}
|
||||
if (mainPanel.width !== 0) {
|
||||
return;
|
||||
}
|
||||
let count = 0;
|
||||
const setSize = () => {
|
||||
if (count++ > 50) {
|
||||
return;
|
||||
}
|
||||
mainPanel.api.setSize({ width: Number.MAX_SAFE_INTEGER });
|
||||
if (mainPanel.width === 0) {
|
||||
requestAnimationFrame(setSize);
|
||||
return;
|
||||
}
|
||||
};
|
||||
setSize();
|
||||
}, [$api]);
|
||||
useOnFirstVisible(ref, resizeMainPanelOnFirstVisible);
|
||||
|
||||
return (
|
||||
<AutoLayoutProvider api={api}>
|
||||
<AutoLayoutProvider $api={$api}>
|
||||
<GridviewReact
|
||||
ref={ref}
|
||||
className="dockview-theme-invoke"
|
||||
components={canvasTabComponents}
|
||||
onReady={onReady}
|
||||
|
||||
@@ -1,69 +1,58 @@
|
||||
import { Box, Divider, Flex } from '@invoke-ai/ui-library';
|
||||
import { $isLayoutLoading } from 'app/store/nanostores/globalIsLoading';
|
||||
import type { GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
|
||||
import { DockviewReact, GridviewReact, Orientation } from 'dockview';
|
||||
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel';
|
||||
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
|
||||
import { GalleryPanel } from 'features/gallery/components/Gallery';
|
||||
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer2';
|
||||
import { ProgressImage } from 'features/gallery/components/ImageViewer/ProgressImage2';
|
||||
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar2';
|
||||
import QueueControls from 'features/queue/components/QueueControls';
|
||||
import ParametersPanelTextToImage from 'features/ui/components/ParametersPanels/ParametersPanelTextToImage';
|
||||
import { GenerationProgressPanel } from 'features/gallery/components/ImageViewer/GenerationProgressPanel';
|
||||
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
|
||||
import { AutoLayoutProvider } from 'features/ui/layouts/auto-layout-context';
|
||||
import { TabWithoutCloseButton } from 'features/ui/layouts/TabWithoutCloseButton';
|
||||
import { LEFT_PANEL_MIN_SIZE_PX, RIGHT_PANEL_MIN_SIZE_PX } from 'features/ui/store/uiSlice';
|
||||
import { dockviewTheme } from 'features/ui/styles/theme';
|
||||
import { memo, useCallback, useState } from 'react';
|
||||
import { atom } from 'nanostores';
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
|
||||
const ViewerPanelContent = memo(() => (
|
||||
<Flex flexDir="column" w="full" h="full" overflow="hidden" p={2} gap={2}>
|
||||
<ViewerToolbar />
|
||||
<Divider />
|
||||
<ImageViewer />
|
||||
</Flex>
|
||||
));
|
||||
ViewerPanelContent.displayName = 'ViewerPanelContent';
|
||||
import { GenerateTabLeftPanel } from './GenerateTabLeftPanel';
|
||||
import { useOnFirstVisible } from './use-on-first-visible';
|
||||
|
||||
const ProgressPanelContent = memo(() => (
|
||||
<Flex flexDir="column" w="full" h="full" overflow="hidden" p={2}>
|
||||
<ProgressImage />
|
||||
</Flex>
|
||||
));
|
||||
ProgressPanelContent.displayName = 'ProgressPanelContent';
|
||||
const LAUNCHPAD_PANEL_ID = 'launchpad';
|
||||
const VIEWER_PANEL_ID = 'viewer';
|
||||
const PROGRESS_PANEL_ID = 'progress';
|
||||
|
||||
const mainPanelComponents: IDockviewReactProps['components'] = {
|
||||
welcome: GenerateLaunchpadPanel,
|
||||
viewer: ViewerPanelContent,
|
||||
progress: ProgressPanelContent,
|
||||
[LAUNCHPAD_PANEL_ID]: GenerateLaunchpadPanel,
|
||||
[VIEWER_PANEL_ID]: ImageViewerPanel,
|
||||
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
|
||||
};
|
||||
|
||||
const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
|
||||
const { api } = event;
|
||||
api.addPanel({
|
||||
id: 'welcome',
|
||||
component: 'welcome',
|
||||
id: LAUNCHPAD_PANEL_ID,
|
||||
component: LAUNCHPAD_PANEL_ID,
|
||||
title: 'Launchpad',
|
||||
});
|
||||
api.addPanel({
|
||||
id: 'viewer',
|
||||
component: 'viewer',
|
||||
id: VIEWER_PANEL_ID,
|
||||
component: VIEWER_PANEL_ID,
|
||||
title: 'Image Viewer',
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: 'welcome',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
},
|
||||
});
|
||||
api.addPanel({
|
||||
id: 'progress',
|
||||
component: 'progress',
|
||||
id: PROGRESS_PANEL_ID,
|
||||
component: PROGRESS_PANEL_ID,
|
||||
title: 'Generation Progress',
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: 'welcome',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
},
|
||||
});
|
||||
|
||||
api.getPanel(LAUNCHPAD_PANEL_ID)?.api.setActive();
|
||||
|
||||
const disposables = [
|
||||
api.onWillShowOverlay((e) => {
|
||||
if (e.kind === 'header_space' || e.kind === 'tab') {
|
||||
@@ -82,90 +71,111 @@ const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
|
||||
|
||||
const MainPanel = memo(() => {
|
||||
return (
|
||||
<Flex w="full" h="full">
|
||||
<DockviewReact
|
||||
disableDnd={true}
|
||||
locked={true}
|
||||
disableFloatingGroups={true}
|
||||
dndEdges={false}
|
||||
defaultTabComponent={TabWithoutCloseButton}
|
||||
components={mainPanelComponents}
|
||||
onReady={onReadyMainPanel}
|
||||
theme={dockviewTheme}
|
||||
/>
|
||||
</Flex>
|
||||
<DockviewReact
|
||||
disableDnd={true}
|
||||
locked={true}
|
||||
disableFloatingGroups={true}
|
||||
dndEdges={false}
|
||||
defaultTabComponent={TabWithoutCloseButton}
|
||||
components={mainPanelComponents}
|
||||
onReady={onReadyMainPanel}
|
||||
theme={dockviewTheme}
|
||||
/>
|
||||
);
|
||||
});
|
||||
MainPanel.displayName = 'MainPanel';
|
||||
|
||||
export const GenerateLeftPanel = memo(() => {
|
||||
return (
|
||||
<Flex flexDir="column" w="full" h="full" gap={2} py={2} pe={2}>
|
||||
<QueueControls />
|
||||
<Box position="relative" w="full" h="full">
|
||||
<ParametersPanelTextToImage />
|
||||
</Box>
|
||||
</Flex>
|
||||
);
|
||||
});
|
||||
GenerateLeftPanel.displayName = 'GenerateLeftPanel';
|
||||
const LEFT_PANEL_ID = 'left';
|
||||
const MAIN_PANEL_ID = 'main';
|
||||
const BOARDS_PANEL_ID = 'boards';
|
||||
const GALLERY_PANEL_ID = 'gallery';
|
||||
|
||||
export const generateTabComponents: IGridviewReactProps['components'] = {
|
||||
left: GenerateLeftPanel,
|
||||
main: MainPanel,
|
||||
boards: BoardsPanel,
|
||||
gallery: GalleryPanel,
|
||||
[LEFT_PANEL_ID]: GenerateTabLeftPanel,
|
||||
[MAIN_PANEL_ID]: MainPanel,
|
||||
[BOARDS_PANEL_ID]: BoardsPanel,
|
||||
[GALLERY_PANEL_ID]: GalleryPanel,
|
||||
};
|
||||
|
||||
export const initializeGenerateTabLayout = (api: GridviewApi) => {
|
||||
const main = api.addPanel({
|
||||
id: 'main',
|
||||
component: 'main',
|
||||
minimumWidth: 256,
|
||||
export const initializeLayout = (api: GridviewApi) => {
|
||||
api.addPanel({
|
||||
id: MAIN_PANEL_ID,
|
||||
component: MAIN_PANEL_ID,
|
||||
});
|
||||
const left = api.addPanel({
|
||||
id: 'left',
|
||||
component: 'left',
|
||||
api.addPanel({
|
||||
id: LEFT_PANEL_ID,
|
||||
component: LEFT_PANEL_ID,
|
||||
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
|
||||
position: {
|
||||
direction: 'left',
|
||||
referencePanel: 'main',
|
||||
referencePanel: MAIN_PANEL_ID,
|
||||
},
|
||||
});
|
||||
api.addPanel({
|
||||
id: 'gallery',
|
||||
component: 'gallery',
|
||||
id: GALLERY_PANEL_ID,
|
||||
component: GALLERY_PANEL_ID,
|
||||
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
|
||||
minimumHeight: 232,
|
||||
position: {
|
||||
direction: 'right',
|
||||
referencePanel: 'main',
|
||||
referencePanel: MAIN_PANEL_ID,
|
||||
},
|
||||
});
|
||||
const boards = api.addPanel({
|
||||
id: 'boards',
|
||||
component: 'boards',
|
||||
api.addPanel({
|
||||
id: BOARDS_PANEL_ID,
|
||||
component: BOARDS_PANEL_ID,
|
||||
minimumHeight: 36,
|
||||
position: {
|
||||
direction: 'above',
|
||||
referencePanel: 'gallery',
|
||||
referencePanel: GALLERY_PANEL_ID,
|
||||
},
|
||||
});
|
||||
left.api.setSize({ width: LEFT_PANEL_MIN_SIZE_PX });
|
||||
boards.api.setSize({ height: 256, width: RIGHT_PANEL_MIN_SIZE_PX });
|
||||
api.getPanel(LEFT_PANEL_ID)?.api.setSize({ width: LEFT_PANEL_MIN_SIZE_PX });
|
||||
api.getPanel(BOARDS_PANEL_ID)?.api.setSize({ height: 256, width: RIGHT_PANEL_MIN_SIZE_PX });
|
||||
api.getPanel(MAIN_PANEL_ID)?.api.setActive();
|
||||
};
|
||||
|
||||
export const GenerateTabAutoLayout = memo(() => {
|
||||
const [api, setApi] = useState<GridviewApi | null>(null);
|
||||
const onReady = useCallback<IGridviewReactProps['onReady']>((event) => {
|
||||
$isLayoutLoading.set(true);
|
||||
setApi(event.api);
|
||||
initializeGenerateTabLayout(event.api);
|
||||
$isLayoutLoading.set(false);
|
||||
}, []);
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const $api = useState(() => atom<GridviewApi | null>(null))[0];
|
||||
const onReady = useCallback<IGridviewReactProps['onReady']>(
|
||||
(event) => {
|
||||
$api.set(event.api);
|
||||
initializeLayout(event.api);
|
||||
},
|
||||
[$api]
|
||||
);
|
||||
const resizeMainPanelOnFirstVisible = useCallback(() => {
|
||||
const api = $api.get();
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
const mainPanel = api.getPanel(MAIN_PANEL_ID);
|
||||
if (!mainPanel) {
|
||||
return;
|
||||
}
|
||||
if (mainPanel.width !== 0) {
|
||||
return;
|
||||
}
|
||||
let count = 0;
|
||||
const setSize = () => {
|
||||
if (count++ > 50) {
|
||||
return;
|
||||
}
|
||||
mainPanel.api.setSize({ width: Number.MAX_SAFE_INTEGER });
|
||||
if (mainPanel.width === 0) {
|
||||
requestAnimationFrame(setSize);
|
||||
return;
|
||||
}
|
||||
};
|
||||
setSize();
|
||||
}, [$api]);
|
||||
useOnFirstVisible(ref, resizeMainPanelOnFirstVisible);
|
||||
|
||||
return (
|
||||
<AutoLayoutProvider api={api}>
|
||||
<AutoLayoutProvider $api={$api}>
|
||||
<GridviewReact
|
||||
ref={ref}
|
||||
className="dockview-theme-invoke"
|
||||
components={generateTabComponents}
|
||||
onReady={onReady}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
import type { GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
|
||||
import { DockviewReact, GridviewReact, Orientation } from 'dockview';
|
||||
import { UpscalingLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/UpscalingLaunchpadPanel';
|
||||
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
|
||||
import { GalleryPanel } from 'features/gallery/components/Gallery';
|
||||
import { GenerationProgressPanel } from 'features/gallery/components/ImageViewer/GenerationProgressPanel';
|
||||
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
|
||||
import { AutoLayoutProvider } from 'features/ui/layouts/auto-layout-context';
|
||||
import { TabWithoutCloseButton } from 'features/ui/layouts/TabWithoutCloseButton';
|
||||
import { LEFT_PANEL_MIN_SIZE_PX, RIGHT_PANEL_MIN_SIZE_PX } from 'features/ui/store/uiSlice';
|
||||
import { dockviewTheme } from 'features/ui/styles/theme';
|
||||
import { atom } from 'nanostores';
|
||||
import { memo, useCallback, useRef, useState } from 'react';
|
||||
|
||||
import { UpscalingTabLeftPanel } from './UpscalingTabLeftPanel';
|
||||
import { useOnFirstVisible } from './use-on-first-visible';
|
||||
|
||||
const LAUNCHPAD_PANEL_ID = 'launchpad';
|
||||
const VIEWER_PANEL_ID = 'viewer';
|
||||
const PROGRESS_PANEL_ID = 'progress';
|
||||
|
||||
const dockviewComponents: IDockviewReactProps['components'] = {
|
||||
[LAUNCHPAD_PANEL_ID]: UpscalingLaunchpadPanel,
|
||||
[VIEWER_PANEL_ID]: ImageViewerPanel,
|
||||
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
|
||||
};
|
||||
|
||||
const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
|
||||
const { api } = event;
|
||||
api.addPanel({
|
||||
id: LAUNCHPAD_PANEL_ID,
|
||||
component: LAUNCHPAD_PANEL_ID,
|
||||
title: 'Launchpad',
|
||||
});
|
||||
api.addPanel({
|
||||
id: VIEWER_PANEL_ID,
|
||||
component: VIEWER_PANEL_ID,
|
||||
title: 'Image Viewer',
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
},
|
||||
});
|
||||
api.addPanel({
|
||||
id: PROGRESS_PANEL_ID,
|
||||
component: PROGRESS_PANEL_ID,
|
||||
title: 'Generation Progress',
|
||||
position: {
|
||||
direction: 'within',
|
||||
referencePanel: LAUNCHPAD_PANEL_ID,
|
||||
},
|
||||
});
|
||||
|
||||
api.getPanel(LAUNCHPAD_PANEL_ID)?.api.setActive();
|
||||
|
||||
const disposables = [
|
||||
api.onWillShowOverlay((e) => {
|
||||
if (e.kind === 'header_space' || e.kind === 'tab') {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
}),
|
||||
];
|
||||
|
||||
return () => {
|
||||
disposables.forEach((disposable) => {
|
||||
disposable.dispose();
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
const MainPanel = memo(() => {
|
||||
return (
|
||||
<DockviewReact
|
||||
disableDnd={true}
|
||||
locked={true}
|
||||
disableFloatingGroups={true}
|
||||
dndEdges={false}
|
||||
defaultTabComponent={TabWithoutCloseButton}
|
||||
components={dockviewComponents}
|
||||
onReady={onReadyMainPanel}
|
||||
theme={dockviewTheme}
|
||||
/>
|
||||
);
|
||||
});
|
||||
MainPanel.displayName = 'MainPanel';
|
||||
|
||||
const LEFT_PANEL_ID = 'left';
|
||||
const MAIN_PANEL_ID = 'main';
|
||||
const BOARDS_PANEL_ID = 'boards';
|
||||
const GALLERY_PANEL_ID = 'gallery';
|
||||
|
||||
export const gridviewComponents: IGridviewReactProps['components'] = {
|
||||
[LEFT_PANEL_ID]: UpscalingTabLeftPanel,
|
||||
[MAIN_PANEL_ID]: MainPanel,
|
||||
[BOARDS_PANEL_ID]: BoardsPanel,
|
||||
[GALLERY_PANEL_ID]: GalleryPanel,
|
||||
};
|
||||
|
||||
export const initializeLayout = (api: GridviewApi) => {
|
||||
api.addPanel({
|
||||
id: MAIN_PANEL_ID,
|
||||
component: MAIN_PANEL_ID,
|
||||
// priority: LayoutPriority.High,
|
||||
});
|
||||
api.addPanel({
|
||||
id: LEFT_PANEL_ID,
|
||||
component: LEFT_PANEL_ID,
|
||||
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
|
||||
position: {
|
||||
direction: 'left',
|
||||
referencePanel: MAIN_PANEL_ID,
|
||||
},
|
||||
// priority: LayoutPriority.High,
|
||||
});
|
||||
api.addPanel({
|
||||
id: GALLERY_PANEL_ID,
|
||||
component: GALLERY_PANEL_ID,
|
||||
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
|
||||
minimumHeight: 232,
|
||||
position: {
|
||||
direction: 'right',
|
||||
referencePanel: MAIN_PANEL_ID,
|
||||
},
|
||||
// priority: LayoutPriority.High,
|
||||
});
|
||||
api.addPanel({
|
||||
id: BOARDS_PANEL_ID,
|
||||
component: BOARDS_PANEL_ID,
|
||||
minimumHeight: 36,
|
||||
position: {
|
||||
direction: 'above',
|
||||
referencePanel: GALLERY_PANEL_ID,
|
||||
},
|
||||
// priority: LayoutPriority.High,
|
||||
});
|
||||
api.getPanel(LEFT_PANEL_ID)?.api.setSize({ width: LEFT_PANEL_MIN_SIZE_PX });
|
||||
api.getPanel(BOARDS_PANEL_ID)?.api.setSize({ height: 256, width: RIGHT_PANEL_MIN_SIZE_PX });
|
||||
api.getPanel(MAIN_PANEL_ID)?.api.setActive();
|
||||
};
|
||||
|
||||
export const UpscalingTabAutoLayout = memo(() => {
|
||||
const ref = useRef<HTMLDivElement>(null);
|
||||
const $api = useState(() => atom<GridviewApi | null>(null))[0];
|
||||
const onReady = useCallback<IGridviewReactProps['onReady']>(
|
||||
(event) => {
|
||||
$api.set(event.api);
|
||||
initializeLayout(event.api);
|
||||
},
|
||||
[$api]
|
||||
);
|
||||
const resizeMainPanelOnFirstVisible = useCallback(() => {
|
||||
const api = $api.get();
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
const mainPanel = api.getPanel(MAIN_PANEL_ID);
|
||||
if (!mainPanel) {
|
||||
return;
|
||||
}
|
||||
if (mainPanel.width !== 0) {
|
||||
return;
|
||||
}
|
||||
let count = 0;
|
||||
const setSize = () => {
|
||||
if (count++ > 50) {
|
||||
return;
|
||||
}
|
||||
mainPanel.api.setSize({ width: Number.MAX_SAFE_INTEGER });
|
||||
if (mainPanel.width === 0) {
|
||||
requestAnimationFrame(setSize);
|
||||
return;
|
||||
}
|
||||
};
|
||||
setSize();
|
||||
}, [$api]);
|
||||
useOnFirstVisible(ref, resizeMainPanelOnFirstVisible);
|
||||
|
||||
return (
|
||||
<AutoLayoutProvider $api={$api}>
|
||||
<GridviewReact
|
||||
ref={ref}
|
||||
className="dockview-theme-invoke"
|
||||
components={gridviewComponents}
|
||||
onReady={onReady}
|
||||
orientation={Orientation.VERTICAL}
|
||||
/>
|
||||
</AutoLayoutProvider>
|
||||
);
|
||||
});
|
||||
UpscalingTabAutoLayout.displayName = 'UpscalingTabAutoLayout';
|
||||
@@ -0,0 +1,25 @@
|
||||
import { addAppListener } from 'app/store/middleware/listenerMiddleware';
|
||||
import { useAppDispatch } from 'app/store/storeHooks';
|
||||
import { setActiveTab } from 'features/ui/store/uiSlice';
|
||||
import type { TabName } from 'features/ui/store/uiTypes';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useOnFirstVisitToTab = (tab: TabName, cb: () => void) => {
|
||||
const dispatch = useAppDispatch();
|
||||
useEffect(() => {
|
||||
dispatch(
|
||||
addAppListener({
|
||||
predicate: (action) => {
|
||||
if (!setActiveTab.match(action)) {
|
||||
return false;
|
||||
}
|
||||
return action.payload === tab;
|
||||
},
|
||||
effect: (_, api) => {
|
||||
cb();
|
||||
api.unsubscribe();
|
||||
},
|
||||
})
|
||||
);
|
||||
}, [cb, dispatch, tab]);
|
||||
};
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { RefObject } from 'react';
|
||||
import { useEffect } from 'react';
|
||||
|
||||
export const useOnFirstVisible = (elementRef: RefObject<HTMLElement>, callback: () => void): void => {
|
||||
useEffect(() => {
|
||||
const element = elementRef.current;
|
||||
if (!element) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Find the parent element that has display: none
|
||||
const findParentWithDisplay = (el: HTMLElement): HTMLElement | null => {
|
||||
let parent = el.parentElement;
|
||||
while (parent) {
|
||||
const computedStyle = window.getComputedStyle(parent);
|
||||
if (computedStyle.display === 'none') {
|
||||
return parent;
|
||||
}
|
||||
parent = parent.parentElement;
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const targetParent = findParentWithDisplay(element);
|
||||
if (!targetParent) {
|
||||
return;
|
||||
}
|
||||
|
||||
const observerCallback = () => {
|
||||
if (window.getComputedStyle(targetParent).display === 'none') {
|
||||
return;
|
||||
}
|
||||
observer.disconnect();
|
||||
callback();
|
||||
};
|
||||
const observer = new MutationObserver(observerCallback);
|
||||
|
||||
observer.observe(targetParent, {
|
||||
attributes: true,
|
||||
attributeFilter: ['hidden', 'style', 'class'],
|
||||
});
|
||||
|
||||
observerCallback();
|
||||
return () => {
|
||||
observer.disconnect();
|
||||
};
|
||||
}, [elementRef, callback]);
|
||||
};
|
||||
@@ -1,7 +1,11 @@
|
||||
import { createSelector } from '@reduxjs/toolkit';
|
||||
import { selectEnabledTabs } from 'features/system/store/configSlice';
|
||||
import { selectUiSlice } from 'features/ui/store/uiSlice';
|
||||
|
||||
export const selectActiveTab = createSelector(selectUiSlice, (ui) => ui.activeTab);
|
||||
export const selectActiveTabIndex = createSelector(selectActiveTab, selectEnabledTabs, (activeTab, enabledTabs) => {
|
||||
return enabledTabs.indexOf(activeTab);
|
||||
});
|
||||
export const selectShouldShowImageDetails = createSelector(selectUiSlice, (ui) => ui.shouldShowImageDetails);
|
||||
export const selectShouldShowProgressInViewer = createSelector(selectUiSlice, (ui) => ui.shouldShowProgressInViewer);
|
||||
export const selectActiveTabCanvasRightPanel = createSelector(selectUiSlice, (ui) => ui.activeTabCanvasRightPanel);
|
||||
|
||||
@@ -2,6 +2,7 @@ import { deepClone } from 'common/util/deepClone';
|
||||
import { z } from 'zod';
|
||||
|
||||
const zTabName = z.enum(['generate', 'canvas', 'upscaling', 'workflows', 'models', 'queue']);
|
||||
export const ALL_TABS = zTabName.options;
|
||||
export type TabName = z.infer<typeof zTabName>;
|
||||
const zCanvasRightPanelTabName = z.enum(['layers', 'gallery']);
|
||||
export type CanvasRightPanelTabName = z.infer<typeof zCanvasRightPanelTabName>;
|
||||
|
||||
Reference in New Issue
Block a user