refactor(ui): even more better focus handling

This commit is contained in:
psychedelicious
2025-07-01 13:32:45 +10:00
parent e817631ba3
commit fa72a97794
22 changed files with 312 additions and 190 deletions

View File

@@ -1,6 +1,5 @@
import { Divider, Flex } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { CanvasAddEntityButtons } from 'features/controlLayers/components/CanvasAddEntityButtons';
import { CanvasEntityList } from 'features/controlLayers/components/CanvasEntityList/CanvasEntityList';
import { EntityListSelectedEntityActionBar } from 'features/controlLayers/components/CanvasEntityList/EntityListSelectedEntityActionBar';
@@ -15,14 +14,14 @@ export const CanvasLayersPanel = memo(() => {
return (
<CanvasManagerProviderGate>
<FocusRegionWrapper region="layers" as={Flex} flexDir="column" gap={2} w="full" h="full" p={2}>
<Flex flexDir="column" gap={2} w="full" h="full">
<EntityListSelectedEntityActionBar />
<Divider py={0} />
<ParamDenoisingStrength />
<Divider py={0} />
{!hasEntities && <CanvasAddEntityButtons />}
{hasEntities && <CanvasEntityList />}
</FocusRegionWrapper>
</Flex>
</CanvasManagerProviderGate>
);
});

View File

@@ -1,5 +1,4 @@
import { Button, Flex, Grid, Heading, Text } from '@invoke-ai/ui-library';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
import { WORKSPACE_PANEL_ID } from 'features/ui/layouts/shared';
import { memo, useCallback } from 'react';
@@ -18,7 +17,7 @@ export const CanvasLaunchpadPanel = memo(() => {
ctx.focusPanel(WORKSPACE_PANEL_ID);
}, [ctx]);
return (
<FocusRegionWrapper region="launchpad" as={Flex} flexDir="column" h="full" w="full" alignItems="center" gap={2}>
<Flex flexDir="column" h="full" w="full" alignItems="center" gap={2}>
<Flex flexDir="column" w="full" gap={4} px={14} maxW={768} pt="20vh">
<Heading mb={4}>{t('ui.launchpad.canvasTitle')}</Heading>
<Flex flexDir="column" gap={8}>
@@ -44,7 +43,7 @@ export const CanvasLaunchpadPanel = memo(() => {
<LaunchpadUseALayoutImageButton extraAction={focusCanvas} />
</Flex>
</Flex>
</FocusRegionWrapper>
</Flex>
);
});
CanvasLaunchpadPanel.displayName = 'CanvasLaunchpadPanel';

View File

@@ -1,6 +1,5 @@
import { Alert, Button, Flex, Grid, Heading, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { InitialStateMainModelPicker } from 'features/controlLayers/components/SimpleSession/InitialStateMainModelPicker';
import { LaunchpadAddStyleReference } from 'features/controlLayers/components/SimpleSession/LaunchpadAddStyleReference';
import { setActiveTab } from 'features/ui/store/uiSlice';
@@ -15,7 +14,7 @@ export const GenerateLaunchpadPanel = memo(() => {
}, [dispatch]);
return (
<FocusRegionWrapper region="launchpad" as={Flex} flexDir="column" h="full" w="full" alignItems="center" gap={2}>
<Flex flexDir="column" h="full" w="full" alignItems="center" gap={2}>
<Flex flexDir="column" w="full" gap={4} px={14} maxW={768} pt="20vh">
<Heading mb={4}>Generate images from text prompts.</Heading>
<Flex flexDir="column" gap={8}>
@@ -47,7 +46,7 @@ export const GenerateLaunchpadPanel = memo(() => {
</Alert>
</Flex>
</Flex>
</FocusRegionWrapper>
</Flex>
);
});
GenerateLaunchpadPanel.displayName = 'GenerateLaunchpad';

View File

@@ -1,5 +1,4 @@
import { Button, Flex, Heading, Icon, Text } from '@invoke-ai/ui-library';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { useWorkflowLibraryModal } from 'features/nodes/store/workflowLibraryModal';
import { useLoadWorkflowWithDialog } from 'features/workflowLibrary/components/LoadWorkflowConfirmationAlertDialog';
import { useNewWorkflow } from 'features/workflowLibrary/components/NewWorkflowConfirmationAlertDialog';
@@ -46,7 +45,7 @@ export const WorkflowsLaunchpadPanel = memo(() => {
});
return (
<FocusRegionWrapper region="launchpad" as={Flex} flexDir="column" h="full" w="full" alignItems="center" gap={2}>
<Flex flexDir="column" h="full" w="full" alignItems="center" gap={2}>
<Flex flexDir="column" w="full" gap={4} px={14} maxW={768} pt="20vh">
<Heading>{t('ui.launchpad.workflowsTitle')}</Heading>
@@ -102,7 +101,7 @@ export const WorkflowsLaunchpadPanel = memo(() => {
</LaunchpadButton>
</Flex>
</Flex>
</FocusRegionWrapper>
</Flex>
);
});

View File

@@ -1,7 +1,6 @@
import { Box, Button, Collapse, Divider, Flex, IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { useDisclosure } from 'common/hooks/useBoolean';
import { BoardsListWrapper } from 'features/gallery/components/Boards/BoardsList/BoardsListWrapper';
import { BoardsSearch } from 'features/gallery/components/Boards/BoardsList/BoardsSearch';
@@ -46,7 +45,7 @@ export const BoardsPanel = memo(() => {
}, [boardSearchText.length, searchDisclosure, collapsibleApi, dispatch]);
return (
<FocusRegionWrapper region="boards" as={Flex} flexDir="column" w="full" h="full" p={2}>
<Flex flexDir="column" w="full" h="full">
<Flex alignItems="center" justifyContent="space-between" w="full">
<Flex flexGrow={1} flexBasis={0}>
<Button
@@ -82,7 +81,7 @@ export const BoardsPanel = memo(() => {
</Collapse>
<Divider pt={2} />
<BoardsListWrapper />
</FocusRegionWrapper>
</Flex>
);
});
BoardsPanel.displayName = 'BoardsPanel';

View File

@@ -2,7 +2,6 @@ import { Box, Button, ButtonGroup, Collapse, Divider, Flex, IconButton, Spacer }
import { useStore } from '@nanostores/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { useDisclosure } from 'common/hooks/useBoolean';
import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useGallerySearchTerm';
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
@@ -68,17 +67,7 @@ export const GalleryPanel = memo(() => {
const boardName = useBoardName(selectedBoardId);
return (
<FocusRegionWrapper
region="gallery"
as={Flex}
flexDirection="column"
alignItems="center"
justifyContent="space-between"
h="full"
w="full"
minH={0}
p={2}
>
<Flex flexDirection="column" alignItems="center" justifyContent="space-between" h="full" w="full" minH={0}>
<Flex gap={2} fontSize="sm" alignItems="center" w="full">
<Button
size="sm"
@@ -134,7 +123,7 @@ export const GalleryPanel = memo(() => {
<Flex w="full" h="full" pt={2}>
<NewGallery />
</Flex>
</FocusRegionWrapper>
</Flex>
);
});
GalleryPanel.displayName = 'Gallery';
GalleryPanel.displayName = 'GalleryPanel';

View File

@@ -1,23 +1,15 @@
import { Flex } from '@invoke-ai/ui-library';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { ProgressImage } from 'features/gallery/components/ImageViewer/ProgressImage';
import { memo } from 'react';
import { ProgressIndicator } from './ProgressIndicator';
export const GenerationProgressPanel = memo(() => (
<FocusRegionWrapper
region="progress"
as={Flex}
position="relative"
flexDir="column"
w="full"
h="full"
overflow="hidden"
p={2}
>
<ProgressImage />
<ProgressIndicator position="absolute" top={6} right={6} size={8} />
</FocusRegionWrapper>
));
export const GenerationProgressPanel = memo(() => {
return (
<Flex position="relative" flexDir="column" w="full" h="full" overflow="hidden">
<ProgressImage />
<ProgressIndicator position="absolute" top={6} right={6} size={8} />
</Flex>
);
});
GenerationProgressPanel.displayName = 'GenerationProgressPanel';

View File

@@ -1,5 +1,4 @@
import { Divider, Flex } from '@invoke-ai/ui-library';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import { ViewerToolbar } from 'features/gallery/components/ImageViewer/ViewerToolbar';
import { memo } from 'react';
@@ -9,11 +8,11 @@ import { ImageViewerContextProvider } from './context';
export const ImageViewerPanel = memo(() => {
return (
<ImageViewerContextProvider>
<FocusRegionWrapper region="viewer" as={Flex} flexDir="column" w="full" h="full" overflow="hidden" p={2} gap={2}>
<Flex flexDir="column" w="full" h="full" overflow="hidden" gap={2}>
<ViewerToolbar />
<Divider />
<ImageViewer />
</FocusRegionWrapper>
</Flex>
</ImageViewerContextProvider>
);
});

View File

@@ -1,6 +1,5 @@
import { Flex } from '@invoke-ai/ui-library';
import { ReactFlowProvider } from '@xyflow/react';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
import { AddNodeCmdk } from 'features/nodes/components/flow/AddNodeCmdk/AddNodeCmdk';
import { TopCenterPanel } from 'features/nodes/components/flow/panels/TopPanel/TopCenterPanel';
@@ -22,9 +21,7 @@ const NodeEditor = () => {
return (
<ReactFlowProvider>
<FocusRegionWrapper
region="workflows"
as={Flex}
<Flex
bg="base.900"
display="flex"
position="relative"
@@ -46,7 +43,7 @@ const NodeEditor = () => {
)}
<WorkflowEditorSettings />
{isLoading && <IAINoContentFallback label={t('nodes.loadingNodes')} icon={PiFlowArrowBold} />}
</FocusRegionWrapper>
</Flex>
</ReactFlowProvider>
);
};

View File

@@ -1,7 +1,6 @@
import { Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { EditModeLeftPanelContent } from 'features/nodes/components/sidePanel/EditModeLeftPanelContent';
import { PublishedWorkflowPanelContent } from 'features/nodes/components/sidePanel/PublishedWorkflowPanelContent';
import { $isInPublishFlow, useIsWorkflowPublished } from 'features/nodes/components/sidePanel/workflow/publish';
@@ -20,7 +19,7 @@ const WorkflowsTabLeftPanel = () => {
const isInPublishFlow = useStore($isInPublishFlow);
return (
<FocusRegionWrapper region="settings" as={Flex} flexDir="column" w="full" h="full" gap={2} p={2}>
<Flex flexDir="column" w="full" h="full" gap={2}>
<QueueControls />
<Flex w="full" h="full" gap={2} flexDir="column">
{isInPublishFlow && <PublishWorkflowPanelContent />}
@@ -30,7 +29,7 @@ const WorkflowsTabLeftPanel = () => {
{!isInPublishFlow && !isPublished && mode === 'edit' && <EditModeLeftPanelContent />}
{isPublished && <PublishedWorkflowPanelContent />}
</Flex>
</FocusRegionWrapper>
</Flex>
);
};

View File

@@ -1,17 +1,16 @@
import { Box, Flex } from '@invoke-ai/ui-library';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import QueueControls from 'features/queue/components/QueueControls';
import { ParametersPanelCanvas } from 'features/ui/components/ParametersPanels/ParametersPanelCanvas';
import { memo } from 'react';
export const CanvasTabLeftPanel = memo(() => {
return (
<FocusRegionWrapper region="settings" as={Flex} flexDir="column" w="full" h="full" gap={2} p={2}>
<Flex flexDir="column" w="full" h="full" gap={2}>
<QueueControls />
<Box position="relative" w="full" h="full">
<ParametersPanelCanvas />
</Box>
</FocusRegionWrapper>
</Flex>
);
});
CanvasTabLeftPanel.displayName = 'CanvasTabLeftPanel';

View File

@@ -1,6 +1,5 @@
import { ContextMenu, Divider, Flex, IconButton, Menu, MenuButton, MenuList } from '@invoke-ai/ui-library';
import { useAppSelector } from 'app/store/storeHooks';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import { CanvasAlertsInvocationProgress } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsInvocationProgress';
import { CanvasAlertsPreserveMask } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsPreserveMask';
import { CanvasAlertsSelectedEntityStatus } from 'features/controlLayers/components/CanvasAlerts/CanvasAlertsSelectedEntityStatus';
@@ -56,9 +55,7 @@ export const CanvasWorkspacePanel = memo(() => {
}, []);
return (
<FocusRegionWrapper
region="canvas"
as={Flex}
<Flex
borderRadius="base"
position="relative"
flexDirection="column"
@@ -68,7 +65,6 @@ export const CanvasWorkspacePanel = memo(() => {
alignItems="center"
justifyContent="center"
overflow="hidden"
p={2}
>
<CanvasManagerProviderGate>
<CanvasToolbar />
@@ -136,7 +132,7 @@ export const CanvasWorkspacePanel = memo(() => {
<CanvasManagerProviderGate>
<CanvasDropArea />
</CanvasManagerProviderGate>
</FocusRegionWrapper>
</Flex>
);
});
CanvasWorkspacePanel.displayName = 'CanvasPanel';
CanvasWorkspacePanel.displayName = 'CanvasWorkspacePanel';

View File

@@ -1,17 +1,16 @@
import { Box, Flex } from '@invoke-ai/ui-library';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import QueueControls from 'features/queue/components/QueueControls';
import { ParametersPanelGenerate } from 'features/ui/components/ParametersPanels/ParametersPanelGenerate';
import { memo } from 'react';
export const GenerateTabLeftPanel = memo(() => {
return (
<FocusRegionWrapper region="settings" as={Flex} flexDir="column" w="full" h="full" gap={2} p={2}>
<Flex flexDir="column" w="full" h="full" gap={2}>
<QueueControls />
<Box position="relative" w="full" h="full">
<ParametersPanelGenerate />
</Box>
</FocusRegionWrapper>
</Flex>
);
});
GenerateTabLeftPanel.displayName = 'GenerateTabLeftPanel';

View File

@@ -1,9 +1,12 @@
import { Flex, Text } from '@invoke-ai/ui-library';
import { setFocusedRegion } from 'common/hooks/focus';
import { useCallbackOnDragEnter } from 'common/hooks/useCallbackOnDragEnter';
import type { IDockviewPanelHeaderProps } from 'dockview';
import { memo, useCallback, useRef } from 'react';
export const TabWithoutCloseButton = memo((props: IDockviewPanelHeaderProps) => {
import type { PanelParameters } from './auto-layout-context';
export const TabWithoutCloseButton = memo((props: IDockviewPanelHeaderProps<PanelParameters>) => {
const ref = useRef<HTMLDivElement>(null);
const setActive = useCallback(() => {
if (!props.api.isActive) {
@@ -13,8 +16,12 @@ export const TabWithoutCloseButton = memo((props: IDockviewPanelHeaderProps) =>
useCallbackOnDragEnter(setActive, ref, 300);
const onPointerDown = useCallback(() => {
setFocusedRegion(props.params.focusRegion);
}, [props.params.focusRegion]);
return (
<Flex ref={ref} alignItems="center" h="full">
<Flex ref={ref} alignItems="center" h="full" onPointerDown={onPointerDown}>
<Text userSelect="none" px={4}>
{props.api.title ?? props.api.id}
</Text>

View File

@@ -1,31 +1,40 @@
import { Flex, Text } from '@invoke-ai/ui-library';
import { setFocusedRegion } from 'common/hooks/focus';
import { useCallbackOnDragEnter } from 'common/hooks/useCallbackOnDragEnter';
import type { IDockviewPanelHeaderProps } from 'dockview';
import ProgressBar from 'features/system/components/ProgressBar';
import { memo, useCallback, useRef } from 'react';
import { useIsGenerationInProgress } from 'services/api/endpoints/queue';
export const TabWithoutCloseButtonAndWithProgressIndicator = memo((props: IDockviewPanelHeaderProps) => {
const isGenerationInProgress = useIsGenerationInProgress();
import type { PanelParameters } from './auto-layout-context';
const ref = useRef<HTMLDivElement>(null);
const setActive = useCallback(() => {
if (!props.api.isActive) {
props.api.setActive();
}
}, [props.api]);
export const TabWithoutCloseButtonAndWithProgressIndicator = memo(
(props: IDockviewPanelHeaderProps<PanelParameters>) => {
const isGenerationInProgress = useIsGenerationInProgress();
useCallbackOnDragEnter(setActive, ref, 300);
const ref = useRef<HTMLDivElement>(null);
const setActive = useCallback(() => {
if (!props.api.isActive) {
props.api.setActive();
}
}, [props.api]);
return (
<Flex ref={ref} position="relative" alignItems="center" h="full">
<Text userSelect="none" px={4}>
{props.api.title ?? props.api.id}
</Text>
{isGenerationInProgress && (
<ProgressBar position="absolute" bottom={0} left={0} right={0} h={1} borderRadius="none" />
)}
</Flex>
);
});
useCallbackOnDragEnter(setActive, ref, 300);
const onPointerDown = useCallback(() => {
setFocusedRegion(props.params.focusRegion);
}, [props.params.focusRegion]);
return (
<Flex ref={ref} position="relative" alignItems="center" h="full" onPointerDown={onPointerDown}>
<Text userSelect="none" px={4}>
{props.api.title ?? props.api.id}
</Text>
{isGenerationInProgress && (
<ProgressBar position="absolute" bottom={0} left={0} right={0} h={1} borderRadius="none" />
)}
</Flex>
);
}
);
TabWithoutCloseButtonAndWithProgressIndicator.displayName = 'TabWithoutCloseButtonAndWithProgressIndicator';

View File

@@ -1,17 +1,16 @@
import { Box, Flex } from '@invoke-ai/ui-library';
import { FocusRegionWrapper } from 'common/components/FocusRegionWrapper';
import QueueControls from 'features/queue/components/QueueControls';
import { ParametersPanelUpscale } from 'features/ui/components/ParametersPanels/ParametersPanelUpscale';
import { memo } from 'react';
export const UpscalingTabLeftPanel = memo(() => {
return (
<FocusRegionWrapper region="settings" as={Flex} flexDir="column" w="full" h="full" gap={2} p={2}>
<Flex flexDir="column" w="full" h="full" gap={2}>
<QueueControls />
<Box position="relative" w="full" h="full">
<ParametersPanelUpscale />
</Box>
</FocusRegionWrapper>
</Flex>
);
});
UpscalingTabLeftPanel.displayName = 'UpscalingTabLeftPanel';

View File

@@ -1,12 +1,14 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks';
import type { DockviewApi, GridviewApi } from 'dockview';
import { AutoLayoutPanelContainer } from 'common/components/FocusRegionWrapper';
import type { FocusRegionName } from 'common/hooks/focus';
import type { DockviewApi, GridviewApi, IDockviewPanelProps, IGridviewPanelProps } from 'dockview';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import type { TabName } from 'features/ui/store/uiTypes';
import type { WritableAtom } from 'nanostores';
import { atom } from 'nanostores';
import type { PropsWithChildren, RefObject } from 'react';
import type { FunctionComponent, PropsWithChildren, RefObject } from 'react';
import { createContext, memo, useCallback, useContext, useMemo, useState } from 'react';
import { LEFT_PANEL_ID, LEFT_PANEL_MIN_SIZE_PX, RIGHT_PANEL_ID, RIGHT_PANEL_MIN_SIZE_PX } from './shared';
@@ -208,3 +210,21 @@ export const PanelHotkeysLogical = memo(() => {
return null;
});
PanelHotkeysLogical.displayName = 'PanelHotkeysLogical';
export type PanelParameters = {
focusRegion: FocusRegionName;
};
export type AutoLayoutGridviewComponents = Record<string, FunctionComponent<IGridviewPanelProps<PanelParameters>>>;
export type AutoLayoutDockviewComponents = Record<string, FunctionComponent<IDockviewPanelProps<PanelParameters>>>;
export type RootLayoutGridviewComponents = Record<string, FunctionComponent<IGridviewPanelProps<PanelParameters>>>;
export type PanelProps = IDockviewPanelProps<PanelParameters> | IGridviewPanelProps<PanelParameters>;
export const withPanelContainer = (Component: FunctionComponent) =>
memo((props: PanelProps) => {
return (
<AutoLayoutPanelContainer {...props}>
<Component />
</AutoLayoutPanelContainer>
);
});

View File

@@ -15,7 +15,18 @@ import { GenerationProgressPanel } from 'features/gallery/components/ImageViewer
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
import { FloatingCanvasLeftPanelButtons } from 'features/ui/components/FloatingLeftPanelButtons';
import { FloatingRightPanelButtons } from 'features/ui/components/FloatingRightPanelButtons';
import { AutoLayoutProvider, PanelHotkeysLogical, useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
import type {
AutoLayoutDockviewComponents,
AutoLayoutGridviewComponents,
PanelParameters,
RootLayoutGridviewComponents,
} from 'features/ui/layouts/auto-layout-context';
import {
AutoLayoutProvider,
PanelHotkeysLogical,
useAutoLayoutContext,
withPanelContainer,
} from 'features/ui/layouts/auto-layout-context';
import { TabWithoutCloseButton } from 'features/ui/layouts/TabWithoutCloseButton';
import { dockviewTheme } from 'features/ui/styles/theme';
import { atom } from 'nanostores';
@@ -23,7 +34,6 @@ import { memo, useCallback, useRef, useState } from 'react';
import { CanvasTabLeftPanel } from './CanvasTabLeftPanel';
import { CanvasWorkspacePanel } from './CanvasWorkspacePanel';
import { registerFocusListener } from './layout-focus-bridge';
import {
BOARD_PANEL_DEFAULT_HEIGHT_PX,
BOARD_PANEL_MIN_HEIGHT_PX,
@@ -56,45 +66,51 @@ const tabComponents = {
[TAB_WITH_LAUNCHPAD_ICON_ID]: TabWithLaunchpadIcon,
};
const centerPanelComponents: IDockviewReactProps['components'] = {
[LAUNCHPAD_PANEL_ID]: CanvasLaunchpadPanel,
[WORKSPACE_PANEL_ID]: CanvasWorkspacePanel,
[VIEWER_PANEL_ID]: ImageViewerPanel,
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
const centerPanelComponents: AutoLayoutDockviewComponents = {
[LAUNCHPAD_PANEL_ID]: withPanelContainer(CanvasLaunchpadPanel),
[WORKSPACE_PANEL_ID]: withPanelContainer(CanvasWorkspacePanel),
[VIEWER_PANEL_ID]: withPanelContainer(ImageViewerPanel),
[PROGRESS_PANEL_ID]: withPanelContainer(GenerationProgressPanel),
};
const initializeCenterPanelLayout = (api: DockviewApi) => {
const launchpadPanel = api.addPanel({
const launchpadPanel = api.addPanel<PanelParameters>({
id: LAUNCHPAD_PANEL_ID,
component: LAUNCHPAD_PANEL_ID,
title: 'Launchpad',
tabComponent: TAB_WITH_LAUNCHPAD_ICON_ID,
params: {
focusRegion: 'launchpad',
},
});
registerFocusListener(launchpadPanel, 'launchpad');
const workspacePanel = api.addPanel({
const workspacePanel = api.addPanel<PanelParameters>({
id: WORKSPACE_PANEL_ID,
component: WORKSPACE_PANEL_ID,
title: 'Canvas',
tabComponent: DEFAULT_TAB_ID,
params: {
focusRegion: 'canvas',
},
position: {
direction: 'within',
referencePanel: launchpadPanel.id,
},
});
registerFocusListener(workspacePanel, 'canvas');
const viewerPanel = api.addPanel({
const viewerPanel = api.addPanel<PanelParameters>({
id: VIEWER_PANEL_ID,
component: VIEWER_PANEL_ID,
title: 'Image Viewer',
tabComponent: DEFAULT_TAB_ID,
params: {
focusRegion: 'viewer',
},
position: {
direction: 'within',
referencePanel: launchpadPanel.id,
},
});
registerFocusListener(viewerPanel, 'viewer');
return { launchpadPanel, workspacePanel, viewerPanel } satisfies Record<string, IDockviewPanel>;
};
@@ -145,42 +161,48 @@ const CenterPanel = memo(() => {
});
CenterPanel.displayName = 'CenterPanel';
const rightPanelComponents: IGridviewReactProps['components'] = {
[BOARDS_PANEL_ID]: BoardsPanel,
[GALLERY_PANEL_ID]: GalleryPanel,
[LAYERS_PANEL_ID]: CanvasLayersPanel,
const rightPanelComponents: AutoLayoutGridviewComponents = {
[BOARDS_PANEL_ID]: withPanelContainer(BoardsPanel),
[GALLERY_PANEL_ID]: withPanelContainer(GalleryPanel),
[LAYERS_PANEL_ID]: withPanelContainer(CanvasLayersPanel),
};
export const initializeRightPanelLayout = (api: GridviewApi) => {
const galleryPanel = api.addPanel({
const galleryPanel = api.addPanel<PanelParameters>({
id: GALLERY_PANEL_ID,
component: GALLERY_PANEL_ID,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
minimumHeight: GALLERY_PANEL_MIN_HEIGHT_PX,
params: {
focusRegion: 'gallery',
},
});
registerFocusListener(galleryPanel, 'gallery');
const layersPanel = api.addPanel({
const layersPanel = api.addPanel<PanelParameters>({
id: LAYERS_PANEL_ID,
component: LAYERS_PANEL_ID,
minimumHeight: LAYERS_PANEL_MIN_HEIGHT_PX,
params: {
focusRegion: 'layers',
},
position: {
direction: 'below',
referencePanel: galleryPanel.id,
},
});
registerFocusListener(layersPanel, 'layers');
const boardsPanel = api.addPanel({
const boardsPanel = api.addPanel<PanelParameters>({
id: BOARDS_PANEL_ID,
component: BOARDS_PANEL_ID,
minimumHeight: BOARD_PANEL_MIN_HEIGHT_PX,
params: {
focusRegion: 'boards',
},
position: {
direction: 'above',
referencePanel: galleryPanel.id,
},
});
registerFocusListener(boardsPanel, 'boards');
boardsPanel.api.setSize({ height: BOARD_PANEL_DEFAULT_HEIGHT_PX, width: RIGHT_PANEL_MIN_SIZE_PX });
return { galleryPanel, layersPanel, boardsPanel } satisfies Record<string, IGridviewPanel>;
@@ -208,16 +230,18 @@ const RightPanel = memo(() => {
});
RightPanel.displayName = 'RightPanel';
const leftPanelComponents: IGridviewReactProps['components'] = {
[SETTINGS_PANEL_ID]: CanvasTabLeftPanel,
const leftPanelComponents: AutoLayoutGridviewComponents = {
[SETTINGS_PANEL_ID]: withPanelContainer(CanvasTabLeftPanel),
};
export const initializeLeftPanelLayout = (api: GridviewApi) => {
const settingsPanel = api.addPanel({
const settingsPanel = api.addPanel<PanelParameters>({
id: SETTINGS_PANEL_ID,
component: SETTINGS_PANEL_ID,
params: {
focusRegion: 'settings',
},
});
registerFocusListener(settingsPanel, 'settings');
return { settingsPanel } satisfies Record<string, IGridviewPanel>;
};
@@ -244,7 +268,7 @@ const LeftPanel = memo(() => {
});
LeftPanel.displayName = 'LeftPanel';
export const rootPanelComponents: IGridviewReactProps['components'] = {
export const rootPanelComponents: RootLayoutGridviewComponents = {
[LEFT_PANEL_ID]: LeftPanel,
[MAIN_PANEL_ID]: CenterPanel,
[RIGHT_PANEL_ID]: RightPanel,

View File

@@ -14,7 +14,18 @@ import { GenerationProgressPanel } from 'features/gallery/components/ImageViewer
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
import { FloatingLeftPanelButtons } from 'features/ui/components/FloatingLeftPanelButtons';
import { FloatingRightPanelButtons } from 'features/ui/components/FloatingRightPanelButtons';
import { AutoLayoutProvider, PanelHotkeysLogical, useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
import type {
AutoLayoutDockviewComponents,
AutoLayoutGridviewComponents,
PanelParameters,
RootLayoutGridviewComponents,
} from 'features/ui/layouts/auto-layout-context';
import {
AutoLayoutProvider,
PanelHotkeysLogical,
useAutoLayoutContext,
withPanelContainer,
} from 'features/ui/layouts/auto-layout-context';
import { TabWithoutCloseButton } from 'features/ui/layouts/TabWithoutCloseButton';
import { dockviewTheme } from 'features/ui/styles/theme';
import { atom } from 'nanostores';
@@ -51,26 +62,32 @@ const tabComponents = {
[TAB_WITH_LAUNCHPAD_ICON_ID]: TabWithLaunchpadIcon,
};
const centerPanelComponents: IDockviewReactProps['components'] = {
[LAUNCHPAD_PANEL_ID]: GenerateLaunchpadPanel,
[VIEWER_PANEL_ID]: ImageViewerPanel,
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
const centerPanelComponents: AutoLayoutDockviewComponents = {
[LAUNCHPAD_PANEL_ID]: withPanelContainer(GenerateLaunchpadPanel),
[VIEWER_PANEL_ID]: withPanelContainer(ImageViewerPanel),
[PROGRESS_PANEL_ID]: withPanelContainer(GenerationProgressPanel),
};
const initializeCenterPanelLayout = (api: DockviewApi) => {
const launchpadPanel = api.addPanel({
const launchpadPanel = api.addPanel<PanelParameters>({
id: LAUNCHPAD_PANEL_ID,
component: LAUNCHPAD_PANEL_ID,
title: 'Launchpad',
tabComponent: TAB_WITH_LAUNCHPAD_ICON_ID,
params: {
focusRegion: 'launchpad',
},
});
registerFocusListener(launchpadPanel, 'launchpad');
const viewerPanel = api.addPanel({
const viewerPanel = api.addPanel<PanelParameters>({
id: VIEWER_PANEL_ID,
component: VIEWER_PANEL_ID,
title: 'Image Viewer',
tabComponent: TAB_WITH_PROGRESS_INDICATOR_ID,
params: {
focusRegion: 'viewer',
},
position: {
direction: 'within',
referencePanel: launchpadPanel.id,
@@ -127,24 +144,30 @@ const CenterPanel = memo(() => {
});
CenterPanel.displayName = 'CenterPanel';
const rightPanelComponents: IGridviewReactProps['components'] = {
[BOARDS_PANEL_ID]: BoardsPanel,
[GALLERY_PANEL_ID]: GalleryPanel,
const rightPanelComponents: AutoLayoutGridviewComponents = {
[BOARDS_PANEL_ID]: withPanelContainer(BoardsPanel),
[GALLERY_PANEL_ID]: withPanelContainer(GalleryPanel),
};
export const initializeRightPanelLayout = (api: GridviewApi) => {
const galleryPanel = api.addPanel({
const galleryPanel = api.addPanel<PanelParameters>({
id: GALLERY_PANEL_ID,
component: GALLERY_PANEL_ID,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
minimumHeight: GALLERY_PANEL_MIN_HEIGHT_PX,
params: {
focusRegion: 'gallery',
},
});
registerFocusListener(galleryPanel, 'gallery');
const boardsPanel = api.addPanel({
const boardsPanel = api.addPanel<PanelParameters>({
id: BOARDS_PANEL_ID,
component: BOARDS_PANEL_ID,
minimumHeight: BOARD_PANEL_MIN_HEIGHT_PX,
params: {
focusRegion: 'boards',
},
position: {
direction: 'above',
referencePanel: galleryPanel.id,
@@ -179,14 +202,17 @@ const RightPanel = memo(() => {
});
RightPanel.displayName = 'RightPanel';
const leftPanelComponents: IGridviewReactProps['components'] = {
[SETTINGS_PANEL_ID]: GenerateTabLeftPanel,
const leftPanelComponents: AutoLayoutGridviewComponents = {
[SETTINGS_PANEL_ID]: withPanelContainer(GenerateTabLeftPanel),
};
export const initializeLeftPanelLayout = (api: GridviewApi) => {
const settingsPanel = api.addPanel({
const settingsPanel = api.addPanel<PanelParameters>({
id: SETTINGS_PANEL_ID,
component: SETTINGS_PANEL_ID,
params: {
focusRegion: 'settings',
},
});
registerFocusListener(settingsPanel, 'settings');
@@ -215,20 +241,20 @@ const LeftPanel = memo(() => {
});
LeftPanel.displayName = 'LeftPanel';
export const rootPanelComponents: IGridviewReactProps['components'] = {
export const rootPanelComponents: RootLayoutGridviewComponents = {
[LEFT_PANEL_ID]: LeftPanel,
[MAIN_PANEL_ID]: CenterPanel,
[RIGHT_PANEL_ID]: RightPanel,
};
export const initializeRootPanelLayout = (layoutApi: GridviewApi) => {
const mainPanel = layoutApi.addPanel({
const mainPanel = layoutApi.addPanel<PanelParameters>({
id: MAIN_PANEL_ID,
component: MAIN_PANEL_ID,
priority: LayoutPriority.High,
});
const leftPanel = layoutApi.addPanel({
const leftPanel = layoutApi.addPanel<PanelParameters>({
id: LEFT_PANEL_ID,
component: LEFT_PANEL_ID,
minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
@@ -238,7 +264,7 @@ export const initializeRootPanelLayout = (layoutApi: GridviewApi) => {
},
});
const rightPanel = layoutApi.addPanel({
const rightPanel = layoutApi.addPanel<PanelParameters>({
id: RIGHT_PANEL_ID,
component: RIGHT_PANEL_ID,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,

View File

@@ -14,13 +14,23 @@ import { GenerationProgressPanel } from 'features/gallery/components/ImageViewer
import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
import { FloatingLeftPanelButtons } from 'features/ui/components/FloatingLeftPanelButtons';
import { FloatingRightPanelButtons } from 'features/ui/components/FloatingRightPanelButtons';
import { AutoLayoutProvider, PanelHotkeysLogical, useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
import type {
AutoLayoutDockviewComponents,
AutoLayoutGridviewComponents,
PanelParameters,
RootLayoutGridviewComponents,
} from 'features/ui/layouts/auto-layout-context';
import {
AutoLayoutProvider,
PanelHotkeysLogical,
useAutoLayoutContext,
withPanelContainer,
} from 'features/ui/layouts/auto-layout-context';
import { TabWithoutCloseButton } from 'features/ui/layouts/TabWithoutCloseButton';
import { dockviewTheme } from 'features/ui/styles/theme';
import { atom } from 'nanostores';
import { memo, useCallback, useRef, useState } from 'react';
import { registerFocusListener } from './layout-focus-bridge';
import {
BOARD_PANEL_DEFAULT_HEIGHT_PX,
BOARD_PANEL_MIN_HEIGHT_PX,
@@ -51,32 +61,36 @@ const tabComponents = {
[TAB_WITH_LAUNCHPAD_ICON_ID]: TabWithLaunchpadIcon,
};
const centerComponents: IDockviewReactProps['components'] = {
[LAUNCHPAD_PANEL_ID]: UpscalingLaunchpadPanel,
[VIEWER_PANEL_ID]: ImageViewerPanel,
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
const centerComponents: AutoLayoutDockviewComponents = {
[LAUNCHPAD_PANEL_ID]: withPanelContainer(UpscalingLaunchpadPanel),
[VIEWER_PANEL_ID]: withPanelContainer(ImageViewerPanel),
[PROGRESS_PANEL_ID]: withPanelContainer(GenerationProgressPanel),
};
const initializeCenterPanelLayout = (api: DockviewApi) => {
const launchpadPanel = api.addPanel({
const launchpadPanel = api.addPanel<PanelParameters>({
id: LAUNCHPAD_PANEL_ID,
component: LAUNCHPAD_PANEL_ID,
title: 'Launchpad',
tabComponent: TAB_WITH_LAUNCHPAD_ICON_ID,
params: {
focusRegion: 'launchpad',
},
});
registerFocusListener(launchpadPanel, 'launchpad');
const viewerPanel = api.addPanel({
const viewerPanel = api.addPanel<PanelParameters>({
id: VIEWER_PANEL_ID,
component: VIEWER_PANEL_ID,
title: 'Image Viewer',
tabComponent: TAB_WITH_PROGRESS_INDICATOR_ID,
params: {
focusRegion: 'viewer',
},
position: {
direction: 'within',
referencePanel: launchpadPanel.id,
},
});
registerFocusListener(viewerPanel, 'viewer');
return { launchpadPanel, viewerPanel } satisfies Record<string, IDockviewPanel>;
};
@@ -127,30 +141,34 @@ const CenterPanel = memo(() => {
});
CenterPanel.displayName = 'CenterPanel';
const rightPanelComponents: IGridviewReactProps['components'] = {
[BOARDS_PANEL_ID]: BoardsPanel,
[GALLERY_PANEL_ID]: GalleryPanel,
const rightPanelComponents: AutoLayoutGridviewComponents = {
[BOARDS_PANEL_ID]: withPanelContainer(BoardsPanel),
[GALLERY_PANEL_ID]: withPanelContainer(GalleryPanel),
};
export const initializeRightPanelLayout = (api: GridviewApi) => {
const galleryPanel = api.addPanel({
const galleryPanel = api.addPanel<PanelParameters>({
id: GALLERY_PANEL_ID,
component: GALLERY_PANEL_ID,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
minimumHeight: GALLERY_PANEL_MIN_HEIGHT_PX,
params: {
focusRegion: 'gallery',
},
});
registerFocusListener(galleryPanel, 'gallery');
const boardsPanel = api.addPanel({
const boardsPanel = api.addPanel<PanelParameters>({
id: BOARDS_PANEL_ID,
component: BOARDS_PANEL_ID,
minimumHeight: BOARD_PANEL_MIN_HEIGHT_PX,
params: {
focusRegion: 'boards',
},
position: {
direction: 'above',
referencePanel: galleryPanel.id,
},
});
registerFocusListener(boardsPanel, 'boards');
boardsPanel.api.setSize({ height: BOARD_PANEL_DEFAULT_HEIGHT_PX, width: RIGHT_PANEL_MIN_SIZE_PX });
@@ -175,16 +193,18 @@ const RightPanel = memo(() => {
});
RightPanel.displayName = 'RightPanel';
const leftPanelComponents: IGridviewReactProps['components'] = {
[SETTINGS_PANEL_ID]: UpscalingTabLeftPanel,
const leftPanelComponents: AutoLayoutGridviewComponents = {
[SETTINGS_PANEL_ID]: withPanelContainer(UpscalingTabLeftPanel),
};
export const initializeLeftPanelLayout = (api: GridviewApi) => {
const settingsPanel = api.addPanel({
const settingsPanel = api.addPanel<PanelParameters>({
id: SETTINGS_PANEL_ID,
component: SETTINGS_PANEL_ID,
params: {
focusRegion: 'settings',
},
});
registerFocusListener(settingsPanel, 'settings');
return { settingsPanel } satisfies Record<string, IGridviewPanel>;
};
@@ -211,7 +231,7 @@ const LeftPanel = memo(() => {
});
LeftPanel.displayName = 'LeftPanel';
export const rootPanelComponents: IGridviewReactProps['components'] = {
export const rootPanelComponents: RootLayoutGridviewComponents = {
[LEFT_PANEL_ID]: LeftPanel,
[MAIN_PANEL_ID]: CenterPanel,
[RIGHT_PANEL_ID]: RightPanel,

View File

@@ -16,7 +16,18 @@ import NodeEditor from 'features/nodes/components/NodeEditor';
import WorkflowsTabLeftPanel from 'features/nodes/components/sidePanel/WorkflowsTabLeftPanel';
import { FloatingLeftPanelButtons } from 'features/ui/components/FloatingLeftPanelButtons';
import { FloatingRightPanelButtons } from 'features/ui/components/FloatingRightPanelButtons';
import { AutoLayoutProvider, PanelHotkeysLogical, useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
import type {
AutoLayoutDockviewComponents,
AutoLayoutGridviewComponents,
PanelParameters,
RootLayoutGridviewComponents,
} from 'features/ui/layouts/auto-layout-context';
import {
AutoLayoutProvider,
PanelHotkeysLogical,
useAutoLayoutContext,
withPanelContainer,
} from 'features/ui/layouts/auto-layout-context';
import { TabWithoutCloseButton } from 'features/ui/layouts/TabWithoutCloseButton';
import { dockviewTheme } from 'features/ui/styles/theme';
import { atom } from 'nanostores';
@@ -53,45 +64,51 @@ const tabComponents = {
[TAB_WITH_LAUNCHPAD_ICON_ID]: TabWithLaunchpadIcon,
};
const centerPanelComponents: IDockviewReactProps['components'] = {
[LAUNCHPAD_PANEL_ID]: WorkflowsLaunchpadPanel,
[WORKSPACE_PANEL_ID]: NodeEditor,
[VIEWER_PANEL_ID]: ImageViewerPanel,
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
const centerPanelComponents: AutoLayoutDockviewComponents = {
[LAUNCHPAD_PANEL_ID]: withPanelContainer(WorkflowsLaunchpadPanel),
[WORKSPACE_PANEL_ID]: withPanelContainer(NodeEditor),
[VIEWER_PANEL_ID]: withPanelContainer(ImageViewerPanel),
[PROGRESS_PANEL_ID]: withPanelContainer(GenerationProgressPanel),
};
const initializeCenterPanelLayout = (api: DockviewApi) => {
const launchpadPanel = api.addPanel({
const launchpadPanel = api.addPanel<PanelParameters>({
id: LAUNCHPAD_PANEL_ID,
component: LAUNCHPAD_PANEL_ID,
title: 'Launchpad',
tabComponent: TAB_WITH_LAUNCHPAD_ICON_ID,
params: {
focusRegion: 'launchpad',
},
});
registerFocusListener(launchpadPanel, 'launchpad');
const workspacePanel = api.addPanel({
const workspacePanel = api.addPanel<PanelParameters>({
id: WORKSPACE_PANEL_ID,
component: WORKSPACE_PANEL_ID,
title: 'Workflow Editor',
tabComponent: DEFAULT_TAB_ID,
params: {
focusRegion: 'workflows',
},
position: {
direction: 'within',
referencePanel: launchpadPanel.id,
},
});
registerFocusListener(workspacePanel, 'workflows');
const viewerPanel = api.addPanel({
const viewerPanel = api.addPanel<PanelParameters>({
id: VIEWER_PANEL_ID,
component: VIEWER_PANEL_ID,
title: 'Image Viewer',
tabComponent: TAB_WITH_PROGRESS_INDICATOR_ID,
params: {
focusRegion: 'viewer',
},
position: {
direction: 'within',
referencePanel: launchpadPanel.id,
},
});
registerFocusListener(viewerPanel, 'viewer');
return { launchpadPanel, workspacePanel, viewerPanel } satisfies Record<string, IDockviewPanel>;
};
@@ -142,30 +159,34 @@ const CenterPanel = memo(() => {
});
CenterPanel.displayName = 'CenterPanel';
const rightPanelComponents: IGridviewReactProps['components'] = {
[BOARDS_PANEL_ID]: BoardsPanel,
[GALLERY_PANEL_ID]: GalleryPanel,
const rightPanelComponents: AutoLayoutGridviewComponents = {
[BOARDS_PANEL_ID]: withPanelContainer(BoardsPanel),
[GALLERY_PANEL_ID]: withPanelContainer(GalleryPanel),
};
export const initializeRightPanelLayout = (api: GridviewApi) => {
const galleryPanel = api.addPanel({
const galleryPanel = api.addPanel<PanelParameters>({
id: GALLERY_PANEL_ID,
component: GALLERY_PANEL_ID,
minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
minimumHeight: GALLERY_PANEL_MIN_HEIGHT_PX,
params: {
focusRegion: 'gallery',
},
});
registerFocusListener(galleryPanel, 'gallery');
const boardsPanel = api.addPanel({
const boardsPanel = api.addPanel<PanelParameters>({
id: BOARDS_PANEL_ID,
component: BOARDS_PANEL_ID,
minimumHeight: BOARD_PANEL_MIN_HEIGHT_PX,
params: {
focusRegion: 'boards',
},
position: {
direction: 'above',
referencePanel: galleryPanel.id,
},
});
registerFocusListener(boardsPanel, 'boards');
boardsPanel.api.setSize({ height: BOARD_PANEL_DEFAULT_HEIGHT_PX, width: RIGHT_PANEL_MIN_SIZE_PX });
@@ -194,14 +215,17 @@ const RightPanel = memo(() => {
});
RightPanel.displayName = 'RightPanel';
const leftPanelComponents: IGridviewReactProps['components'] = {
[SETTINGS_PANEL_ID]: WorkflowsTabLeftPanel,
const leftPanelComponents: AutoLayoutGridviewComponents = {
[SETTINGS_PANEL_ID]: withPanelContainer(WorkflowsTabLeftPanel),
};
export const initializeLeftPanelLayout = (api: GridviewApi) => {
const settingsPanel = api.addPanel({
const settingsPanel = api.addPanel<PanelParameters>({
id: SETTINGS_PANEL_ID,
component: SETTINGS_PANEL_ID,
params: {
focusRegion: 'settings',
},
});
registerFocusListener(settingsPanel, 'settings');
@@ -230,7 +254,7 @@ const LeftPanel = memo(() => {
});
LeftPanel.displayName = 'LeftPanel';
export const rootPanelComponents: IGridviewReactProps['components'] = {
export const rootPanelComponents: RootLayoutGridviewComponents = {
[LEFT_PANEL_ID]: LeftPanel,
[MAIN_PANEL_ID]: CenterPanel,
[RIGHT_PANEL_ID]: RightPanel,