feat(ui): standardize auto layout structure

This commit is contained in:
psychedelicious
2025-06-20 16:25:59 +10:00
parent 01953cf057
commit 852badc90b
10 changed files with 452 additions and 236 deletions

View File

@@ -1,48 +1,31 @@
import { Button, Flex, IconButton } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useBoardSearchDisclosure } from 'features/gallery/components/Boards/BoardsList/BoardsSearch';
import { BoardsSettingsPopover } from 'features/gallery/components/Boards/BoardsSettingsPopover';
import { GalleryHeader } from 'features/gallery/components/GalleryHeader';
import { selectBoardSearchText } from 'features/gallery/store/gallerySelectors';
import { boardSearchTextChanged } from 'features/gallery/store/gallerySlice';
import { useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
import { useCollapsibleGridviewPanel } from 'features/ui/layouts/use-collapsible-gridview-panel';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiCaretDownBold, PiCaretUpBold, PiMagnifyingGlassBold } from 'react-icons/pi';
import { PiMagnifyingGlassBold } from 'react-icons/pi';
export const GalleryTopBar = memo(() => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const boardSearchText = useAppSelector(selectBoardSearchText);
const boardSearchDisclosure = useBoardSearchDisclosure();
const { $api } = useAutoLayoutContext();
const api = useStore($api);
const boardsPanel = useCollapsibleGridviewPanel(api, 'Boards', 'vertical', 256);
const isBoardsPanelCollapsed = useStore(boardsPanel.$isCollapsed);
const onClickBoardSearch = useCallback(() => {
if (boardSearchText.length) {
dispatch(boardSearchTextChanged(''));
}
if (!boardSearchDisclosure.isOpen && boardsPanel.$isCollapsed.get()) {
boardsPanel.expand();
}
boardSearchDisclosure.toggle();
}, [boardSearchText.length, boardSearchDisclosure, dispatch, boardsPanel]);
}, [boardSearchText.length, boardSearchDisclosure, dispatch]);
return (
<Flex alignItems="center" justifyContent="space-between" w="full">
<Flex flexGrow={1} flexBasis={0}>
<Button
size="sm"
variant="ghost"
onClick={boardsPanel.toggle}
rightIcon={isBoardsPanelCollapsed ? <PiCaretDownBold /> : <PiCaretUpBold />}
>
{isBoardsPanelCollapsed ? t('boards.viewBoards') : t('boards.hideBoards')}
</Button>
<Text>Boards</Text>
</Flex>
<Flex>
<GalleryHeader />

View File

@@ -1,16 +1,20 @@
import { useAppDispatch } from 'app/store/storeHooks';
import { IconMenuItem } from 'common/components/IconMenuItem';
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowsOutBold } from 'react-icons/pi';
export const ImageMenuItemOpenInViewer = memo(() => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const imageDTO = useImageDTOContext();
const onClick = useCallback(() => {
// TODO
imageDTO.image_name;
}, [imageDTO]);
dispatch(imageToCompareChanged(null));
dispatch(imageSelected(imageDTO));
// TODO: figure out how to select the closest image viewer...
}, [dispatch, imageDTO]);
return (
<IconMenuItem

View File

@@ -16,6 +16,7 @@ import { useImageContextMenu } from 'features/gallery/components/ImageContextMen
import { GalleryImageHoverIcons } from 'features/gallery/components/ImageGrid/GalleryImageHoverIcons';
import { getGalleryImageDataTestId } from 'features/gallery/components/ImageGrid/getGalleryImageDataTestId';
import { imageToCompareChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
import { useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
import type { MouseEventHandler } from 'react';
import { memo, useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
import type { ImageDTO } from 'services/api/types';
@@ -83,6 +84,7 @@ interface Props {
export const GalleryImage = memo(({ imageDTO }: Props) => {
const store = useAppStore();
const autoLayoutContext = useAutoLayoutContext();
const [isDragging, setIsDragging] = useState(false);
const [dragPreviewState, setDragPreviewState] = useState<
DndDragPreviewSingleImageState | DndDragPreviewMultipleImageState | null
@@ -203,7 +205,8 @@ export const GalleryImage = memo(({ imageDTO }: Props) => {
const onDoubleClick = useCallback<MouseEventHandler<HTMLDivElement>>(() => {
store.dispatch(imageToCompareChanged(null));
}, [store]);
autoLayoutContext.focusImageViewer();
}, [autoLayoutContext, store]);
const dataTestId = useMemo(() => getGalleryImageDataTestId(imageDTO.image_name), [imageDTO.image_name]);

View File

@@ -1,4 +1,7 @@
import { useAppDispatch } from 'app/store/storeHooks';
import { DndImageIcon } from 'features/dnd/DndImageIcon';
import { imageSelected, imageToCompareChanged } from 'features/gallery/store/gallerySlice';
import { useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { PiArrowsOutBold } from 'react-icons/pi';
@@ -9,12 +12,15 @@ type Props = {
};
export const GalleryImageOpenInViewerIconButton = memo(({ imageDTO }: Props) => {
const dispatch = useAppDispatch();
const { focusImageViewer } = useAutoLayoutContext();
const { t } = useTranslation();
const onClick = useCallback(() => {
// TODO
imageDTO.image_name;
}, [imageDTO]);
dispatch(imageToCompareChanged(null));
dispatch(imageSelected(imageDTO));
focusImageViewer();
}, [dispatch, focusImageViewer, imageDTO]);
return (
<DndImageIcon

View File

@@ -1,17 +1,28 @@
import type { GridviewApi } from 'dockview';
import type { DockviewApi, GridviewApi } from 'dockview';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import type { Atom } from 'nanostores';
import type { WritableAtom } from 'nanostores';
import { atom } from 'nanostores';
import type { PropsWithChildren } from 'react';
import { createContext, memo, useCallback, useContext, useMemo } 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';
import {
LEFT_PANEL_ID,
LEFT_PANEL_MIN_SIZE_PX,
RIGHT_PANEL_ID,
RIGHT_PANEL_MIN_SIZE_PX,
VIEWER_PANEL_ID,
} from './shared';
type AutoLayoutContextValue = {
$api: Atom<GridviewApi | null>;
toggleLeftPanel: () => void;
toggleRightPanel: () => void;
toggleBothPanels: () => void;
resetPanels: () => void;
focusImageViewer: () => void;
_$rootPanelApi: WritableAtom<GridviewApi | null>;
_$leftPanelApi: WritableAtom<GridviewApi | null>;
_$centerPanelApi: WritableAtom<DockviewApi | null>;
_$rightPanelApi: WritableAtom<GridviewApi | null>;
};
const AutoLayoutContext = createContext<AutoLayoutContextValue | null>(null);
@@ -42,9 +53,22 @@ const getIsCollapsed = (api: GridviewApi, panelId: string) => {
return panel.maximumWidth === 0;
};
export const AutoLayoutProvider = (props: PropsWithChildren<{ $api: Atom<GridviewApi | null> }>) => {
const activatePanel = (api: GridviewApi | DockviewApi, panelId: string) => {
const panel = api.getPanel(panelId);
if (!panel) {
return;
}
panel.api.setActive();
};
export const AutoLayoutProvider = (props: PropsWithChildren<{ $rootApi: WritableAtom<GridviewApi | null> }>) => {
const { $rootApi, children } = props;
const $leftApi = useState(() => atom<GridviewApi | null>(null))[0];
const $centerApi = useState(() => atom<DockviewApi | null>(null))[0];
const $rightApi = useState(() => atom<GridviewApi | null>(null))[0];
const toggleLeftPanel = useCallback(() => {
const api = props.$api.get();
const api = $rootApi.get();
if (!api) {
return;
}
@@ -53,10 +77,10 @@ export const AutoLayoutProvider = (props: PropsWithChildren<{ $api: Atom<Gridvie
} else {
collapsePanel(api, LEFT_PANEL_ID);
}
}, [props.$api]);
}, [$rootApi]);
const toggleRightPanel = useCallback(() => {
const api = props.$api.get();
const api = $rootApi.get();
if (!api) {
return;
}
@@ -65,10 +89,10 @@ export const AutoLayoutProvider = (props: PropsWithChildren<{ $api: Atom<Gridvie
} else {
collapsePanel(api, RIGHT_PANEL_ID);
}
}, [props.$api]);
}, [$rootApi]);
const toggleBothPanels = useCallback(() => {
const api = props.$api.get();
const api = $rootApi.get();
if (!api) {
return;
}
@@ -81,28 +105,50 @@ export const AutoLayoutProvider = (props: PropsWithChildren<{ $api: Atom<Gridvie
collapsePanel(api, RIGHT_PANEL_ID);
}
});
}, [props.$api]);
}, [$rootApi]);
const resetPanels = useCallback(() => {
const api = props.$api.get();
const api = $rootApi.get();
if (!api) {
return;
}
expandPanel(api, LEFT_PANEL_ID, LEFT_PANEL_MIN_SIZE_PX);
expandPanel(api, RIGHT_PANEL_ID, RIGHT_PANEL_MIN_SIZE_PX);
}, [props.$api]);
}, [$rootApi]);
const focusImageViewer = useCallback(() => {
const api = $centerApi.get();
if (!api) {
return;
}
activatePanel(api, VIEWER_PANEL_ID);
}, [$centerApi]);
const value = useMemo<AutoLayoutContextValue>(
() => ({
$api: props.$api,
toggleLeftPanel,
toggleRightPanel,
toggleBothPanels,
resetPanels,
focusImageViewer,
_$rootPanelApi: $rootApi,
_$leftPanelApi: $leftApi,
_$centerPanelApi: $centerApi,
_$rightPanelApi: $rightApi,
}),
[props.$api, resetPanels, toggleBothPanels, toggleLeftPanel, toggleRightPanel]
[
$centerApi,
$leftApi,
$rightApi,
$rootApi,
focusImageViewer,
resetPanels,
toggleBothPanels,
toggleLeftPanel,
toggleRightPanel,
]
);
return <AutoLayoutContext.Provider value={value}>{props.children}</AutoLayoutContext.Provider>;
return <AutoLayoutContext.Provider value={value}>{children}</AutoLayoutContext.Provider>;
};
export const useAutoLayoutContext = () => {

View File

@@ -1,4 +1,4 @@
import type { GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import type { DockviewApi, GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { DockviewReact, GridviewReact, LayoutPriority, Orientation } from 'dockview';
import { CanvasLayersPanel } from 'features/controlLayers/components/CanvasLayersPanelContent';
import { CanvasLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/CanvasLaunchpadPanel';
@@ -8,7 +8,7 @@ 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 } from 'features/ui/layouts/auto-layout-context';
import { AutoLayoutProvider, PanelHotkeysLogical, useAutoLayoutContext } 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';
@@ -17,28 +17,30 @@ import { memo, useCallback, useRef, useState } from 'react';
import { CanvasTabLeftPanel } from './CanvasTabLeftPanel';
import { CanvasWorkspacePanel } from './CanvasWorkspacePanel';
import {
BOARDS_PANEL_ID,
GALLERY_PANEL_ID,
LAUNCHPAD_PANEL_ID,
LAYERS_PANEL_ID,
LEFT_PANEL_ID,
LEFT_PANEL_MIN_SIZE_PX,
MAIN_PANEL_ID,
PROGRESS_PANEL_ID,
RIGHT_PANEL_ID,
RIGHT_PANEL_MIN_SIZE_PX,
SETTINGS_PANEL_ID,
VIEWER_PANEL_ID,
WORKSPACE_PANEL_ID,
} from './shared';
import { useResizeMainPanelOnFirstVisit } from './use-on-first-visible';
const LAUNCHPAD_PANEL_ID = 'launchpad';
const WORKSPACE_PANEL_ID = 'workspace';
const VIEWER_PANEL_ID = 'viewer';
const PROGRESS_PANEL_ID = 'progress';
const mainPanelComponents: IDockviewReactProps['components'] = {
const centerPanelComponents: IDockviewReactProps['components'] = {
[LAUNCHPAD_PANEL_ID]: CanvasLaunchpadPanel,
[WORKSPACE_PANEL_ID]: CanvasWorkspacePanel,
[VIEWER_PANEL_ID]: ImageViewerPanel,
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
};
const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
const { api } = event;
const initializeCenterPanelLayout = (api: DockviewApi) => {
api.addPanel({
id: LAUNCHPAD_PANEL_ID,
component: LAUNCHPAD_PANEL_ID,
@@ -73,24 +75,31 @@ const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
});
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(() => {
const CenterPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IDockviewReactProps['onReady']>(
(event) => {
initializeCenterPanelLayout(event.api);
ctx._$centerPanelApi.set(event.api);
const disposables = [
event.api.onWillShowOverlay((e) => {
if (e.kind === 'header_space' || e.kind === 'tab') {
return;
}
e.preventDefault();
}),
];
return () => {
disposables.forEach((disposable) => {
disposable.dispose();
});
};
},
[ctx._$centerPanelApi]
);
return (
<>
<DockviewReact
@@ -99,8 +108,8 @@ const MainPanel = memo(() => {
disableFloatingGroups={true}
dndEdges={false}
defaultTabComponent={TabWithoutCloseButton}
components={mainPanelComponents}
onReady={onReadyMainPanel}
components={centerPanelComponents}
onReady={onReady}
theme={dockviewTheme}
/>
<FloatingCanvasLeftPanelButtons />
@@ -109,11 +118,7 @@ const MainPanel = memo(() => {
</>
);
});
MainPanel.displayName = 'MainPanel';
const BOARDS_PANEL_ID = 'boards';
const GALLERY_PANEL_ID = 'gallery';
const LAYERS_PANEL_ID = 'layers';
CenterPanel.displayName = 'CenterPanel';
const rightPanelComponents: IGridviewReactProps['components'] = {
[BOARDS_PANEL_ID]: BoardsPanel,
@@ -121,7 +126,7 @@ const rightPanelComponents: IGridviewReactProps['components'] = {
[LAYERS_PANEL_ID]: CanvasLayersPanel,
};
export const initializeRightLayout = (api: GridviewApi) => {
export const initializeRightPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: GALLERY_PANEL_ID,
component: GALLERY_PANEL_ID,
@@ -149,31 +154,68 @@ export const initializeRightLayout = (api: GridviewApi) => {
api.getPanel(BOARDS_PANEL_ID)?.api.setSize({ height: 256, width: RIGHT_PANEL_MIN_SIZE_PX });
};
const onReadyRightPanel: IGridviewReactProps['onReady'] = (event) => {
initializeRightLayout(event.api);
};
const RightPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
initializeRightPanelLayout(event.api);
ctx._$rightPanelApi.set(event.api);
},
[ctx._$rightPanelApi]
);
return (
<>
<GridviewReact
className="dockview-theme-invoke"
orientation={Orientation.VERTICAL}
components={rightPanelComponents}
onReady={onReadyRightPanel}
onReady={onReady}
/>
</>
);
});
RightPanel.displayName = 'RightPanel';
export const rootComponents: IGridviewReactProps['components'] = {
[LEFT_PANEL_ID]: CanvasTabLeftPanel,
[MAIN_PANEL_ID]: MainPanel,
const leftPanelComponents: IGridviewReactProps['components'] = {
[SETTINGS_PANEL_ID]: CanvasTabLeftPanel,
};
export const initializeLeftPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: SETTINGS_PANEL_ID,
component: SETTINGS_PANEL_ID,
});
};
const LeftPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
initializeLeftPanelLayout(event.api);
ctx._$leftPanelApi.set(event.api);
},
[ctx._$leftPanelApi]
);
return (
<>
<GridviewReact
className="dockview-theme-invoke"
orientation={Orientation.VERTICAL}
components={leftPanelComponents}
onReady={onReady}
/>
</>
);
});
LeftPanel.displayName = 'LeftPanel';
export const rootPanelComponents: IGridviewReactProps['components'] = {
[LEFT_PANEL_ID]: LeftPanel,
[MAIN_PANEL_ID]: CenterPanel,
[RIGHT_PANEL_ID]: RightPanel,
};
export const initializeRootLayout = (api: GridviewApi) => {
export const initializeRootPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: MAIN_PANEL_ID,
component: MAIN_PANEL_ID,
@@ -204,22 +246,22 @@ export const initializeRootLayout = (api: GridviewApi) => {
export const CanvasTabAutoLayout = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const $api = useState(() => atom<GridviewApi | null>(null))[0];
const $rootPanelApi = useState(() => atom<GridviewApi | null>(null))[0];
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
$api.set(event.api);
initializeRootLayout(event.api);
$rootPanelApi.set(event.api);
initializeRootPanelLayout(event.api);
},
[$api]
[$rootPanelApi]
);
useResizeMainPanelOnFirstVisit($api, ref);
useResizeMainPanelOnFirstVisit($rootPanelApi, ref);
return (
<AutoLayoutProvider $api={$api}>
<AutoLayoutProvider $rootApi={$rootPanelApi}>
<GridviewReact
ref={ref}
className="dockview-theme-invoke"
components={rootComponents}
components={rootPanelComponents}
onReady={onReady}
orientation={Orientation.VERTICAL}
/>

View File

@@ -1,4 +1,4 @@
import type { GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import type { DockviewApi, GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { DockviewReact, GridviewReact, LayoutPriority, Orientation } from 'dockview';
import { GenerateLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/GenerateLaunchpadPanel';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
@@ -7,7 +7,7 @@ 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 } from 'features/ui/layouts/auto-layout-context';
import { AutoLayoutProvider, PanelHotkeysLogical, useAutoLayoutContext } 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';
@@ -15,26 +15,27 @@ import { memo, useCallback, useRef, useState } from 'react';
import { GenerateTabLeftPanel } from './GenerateTabLeftPanel';
import {
BOARDS_PANEL_ID,
GALLERY_PANEL_ID,
LAUNCHPAD_PANEL_ID,
LEFT_PANEL_ID,
LEFT_PANEL_MIN_SIZE_PX,
MAIN_PANEL_ID,
PROGRESS_PANEL_ID,
RIGHT_PANEL_ID,
RIGHT_PANEL_MIN_SIZE_PX,
SETTINGS_PANEL_ID,
VIEWER_PANEL_ID,
} from './shared';
import { useResizeMainPanelOnFirstVisit } from './use-on-first-visible';
const LAUNCHPAD_PANEL_ID = 'launchpad';
const VIEWER_PANEL_ID = 'viewer';
const PROGRESS_PANEL_ID = 'progress';
const mainPanelComponents: IDockviewReactProps['components'] = {
const centerPanelComponents: IDockviewReactProps['components'] = {
[LAUNCHPAD_PANEL_ID]: GenerateLaunchpadPanel,
[VIEWER_PANEL_ID]: ImageViewerPanel,
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
};
const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
const { api } = event;
const initializeCenterPanelLayout = (api: DockviewApi) => {
api.addPanel({
id: LAUNCHPAD_PANEL_ID,
component: LAUNCHPAD_PANEL_ID,
@@ -60,24 +61,31 @@ const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
});
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(() => {
const CenterPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IDockviewReactProps['onReady']>(
(event) => {
initializeCenterPanelLayout(event.api);
ctx._$centerPanelApi.set(event.api);
const disposables = [
event.api.onWillShowOverlay((e) => {
if (e.kind === 'header_space' || e.kind === 'tab') {
return;
}
e.preventDefault();
}),
];
return () => {
disposables.forEach((disposable) => {
disposable.dispose();
});
};
},
[ctx._$centerPanelApi]
);
return (
<>
<DockviewReact
@@ -86,8 +94,8 @@ const MainPanel = memo(() => {
disableFloatingGroups={true}
dndEdges={false}
defaultTabComponent={TabWithoutCloseButton}
components={mainPanelComponents}
onReady={onReadyMainPanel}
components={centerPanelComponents}
onReady={onReady}
theme={dockviewTheme}
/>
<FloatingLeftPanelButtons />
@@ -96,17 +104,14 @@ const MainPanel = memo(() => {
</>
);
});
MainPanel.displayName = 'MainPanel';
const BOARDS_PANEL_ID = 'boards';
const GALLERY_PANEL_ID = 'gallery';
CenterPanel.displayName = 'CenterPanel';
const rightPanelComponents: IGridviewReactProps['components'] = {
[BOARDS_PANEL_ID]: BoardsPanel,
[GALLERY_PANEL_ID]: GalleryPanel,
};
export const initializeRightLayout = (api: GridviewApi) => {
export const initializeRightPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: GALLERY_PANEL_ID,
component: GALLERY_PANEL_ID,
@@ -125,31 +130,68 @@ export const initializeRightLayout = (api: GridviewApi) => {
api.getPanel(BOARDS_PANEL_ID)?.api.setSize({ height: 256, width: RIGHT_PANEL_MIN_SIZE_PX });
};
const onReadyRightPanel: IGridviewReactProps['onReady'] = (event) => {
initializeRightLayout(event.api);
};
const RightPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
initializeRightPanelLayout(event.api);
ctx._$rightPanelApi.set(event.api);
},
[ctx._$rightPanelApi]
);
return (
<>
<GridviewReact
className="dockview-theme-invoke"
orientation={Orientation.VERTICAL}
components={rightPanelComponents}
onReady={onReadyRightPanel}
onReady={onReady}
/>
</>
);
});
RightPanel.displayName = 'RightPanel';
export const rootComponents: IGridviewReactProps['components'] = {
[LEFT_PANEL_ID]: GenerateTabLeftPanel,
[MAIN_PANEL_ID]: MainPanel,
const leftPanelComponents: IGridviewReactProps['components'] = {
[SETTINGS_PANEL_ID]: GenerateTabLeftPanel,
};
export const initializeLeftPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: SETTINGS_PANEL_ID,
component: SETTINGS_PANEL_ID,
});
};
const LeftPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
initializeLeftPanelLayout(event.api);
ctx._$leftPanelApi.set(event.api);
},
[ctx._$leftPanelApi]
);
return (
<>
<GridviewReact
className="dockview-theme-invoke"
orientation={Orientation.VERTICAL}
components={leftPanelComponents}
onReady={onReady}
/>
</>
);
});
LeftPanel.displayName = 'LeftPanel';
export const rootPanelComponents: IGridviewReactProps['components'] = {
[LEFT_PANEL_ID]: LeftPanel,
[MAIN_PANEL_ID]: CenterPanel,
[RIGHT_PANEL_ID]: RightPanel,
};
export const initializeRootLayout = (api: GridviewApi) => {
export const initializeRootPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: MAIN_PANEL_ID,
component: MAIN_PANEL_ID,
@@ -180,22 +222,22 @@ export const initializeRootLayout = (api: GridviewApi) => {
export const GenerateTabAutoLayout = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const $api = useState(() => atom<GridviewApi | null>(null))[0];
const $rootPanelApi = useState(() => atom<GridviewApi | null>(null))[0];
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
$api.set(event.api);
initializeRootLayout(event.api);
$rootPanelApi.set(event.api);
initializeRootPanelLayout(event.api);
},
[$api]
[$rootPanelApi]
);
useResizeMainPanelOnFirstVisit($api, ref);
useResizeMainPanelOnFirstVisit($rootPanelApi, ref);
return (
<AutoLayoutProvider $api={$api}>
<AutoLayoutProvider $rootApi={$rootPanelApi}>
<GridviewReact
ref={ref}
className="dockview-theme-invoke"
components={rootComponents}
components={rootPanelComponents}
onReady={onReady}
orientation={Orientation.VERTICAL}
/>

View File

@@ -2,5 +2,16 @@ export const LEFT_PANEL_ID = 'left';
export const MAIN_PANEL_ID = 'main';
export const RIGHT_PANEL_ID = 'right';
export const LAUNCHPAD_PANEL_ID = 'launchpad';
export const WORKSPACE_PANEL_ID = 'workspace';
export const VIEWER_PANEL_ID = 'viewer';
export const PROGRESS_PANEL_ID = 'progress';
export const BOARDS_PANEL_ID = 'boards';
export const GALLERY_PANEL_ID = 'gallery';
export const LAYERS_PANEL_ID = 'layers';
export const SETTINGS_PANEL_ID = 'settings';
export const LEFT_PANEL_MIN_SIZE_PX = 420;
export const RIGHT_PANEL_MIN_SIZE_PX = 420;

View File

@@ -1,4 +1,4 @@
import type { GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import type { DockviewApi, GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { DockviewReact, GridviewReact, LayoutPriority, Orientation } from 'dockview';
import { UpscalingLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/UpscalingLaunchpadPanel';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
@@ -7,34 +7,35 @@ 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 } from 'features/ui/layouts/auto-layout-context';
import { AutoLayoutProvider, PanelHotkeysLogical, useAutoLayoutContext } 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 {
BOARDS_PANEL_ID,
GALLERY_PANEL_ID,
LAUNCHPAD_PANEL_ID,
LEFT_PANEL_ID,
LEFT_PANEL_MIN_SIZE_PX,
MAIN_PANEL_ID,
PROGRESS_PANEL_ID,
RIGHT_PANEL_ID,
RIGHT_PANEL_MIN_SIZE_PX,
SETTINGS_PANEL_ID,
VIEWER_PANEL_ID,
} from './shared';
import { UpscalingTabLeftPanel } from './UpscalingTabLeftPanel';
import { useResizeMainPanelOnFirstVisit } from './use-on-first-visible';
const LAUNCHPAD_PANEL_ID = 'launchpad';
const VIEWER_PANEL_ID = 'viewer';
const PROGRESS_PANEL_ID = 'progress';
const dockviewComponents: IDockviewReactProps['components'] = {
const centerComponents: IDockviewReactProps['components'] = {
[LAUNCHPAD_PANEL_ID]: UpscalingLaunchpadPanel,
[VIEWER_PANEL_ID]: ImageViewerPanel,
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
};
const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
const { api } = event;
const initializeCenterLayout = (api: DockviewApi) => {
api.addPanel({
id: LAUNCHPAD_PANEL_ID,
component: LAUNCHPAD_PANEL_ID,
@@ -60,24 +61,30 @@ const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
});
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 CenterPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IDockviewReactProps['onReady']>(
(event) => {
initializeCenterLayout(event.api);
ctx._$centerPanelApi.set(event.api);
const disposables = [
event.api.onWillShowOverlay((e) => {
if (e.kind === 'header_space' || e.kind === 'tab') {
return;
}
e.preventDefault();
}),
];
const MainPanel = memo(() => {
return () => {
disposables.forEach((disposable) => {
disposable.dispose();
});
};
},
[ctx._$centerPanelApi]
);
return (
<>
<DockviewReact
@@ -86,8 +93,8 @@ const MainPanel = memo(() => {
disableFloatingGroups={true}
dndEdges={false}
defaultTabComponent={TabWithoutCloseButton}
components={dockviewComponents}
onReady={onReadyMainPanel}
components={centerComponents}
onReady={onReady}
theme={dockviewTheme}
/>
<FloatingLeftPanelButtons />
@@ -96,17 +103,14 @@ const MainPanel = memo(() => {
</>
);
});
MainPanel.displayName = 'MainPanel';
const BOARDS_PANEL_ID = 'boards';
const GALLERY_PANEL_ID = 'gallery';
CenterPanel.displayName = 'CenterPanel';
const rightPanelComponents: IGridviewReactProps['components'] = {
[BOARDS_PANEL_ID]: BoardsPanel,
[GALLERY_PANEL_ID]: GalleryPanel,
};
export const initializeRightLayout = (api: GridviewApi) => {
export const initializeRightPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: GALLERY_PANEL_ID,
component: GALLERY_PANEL_ID,
@@ -126,7 +130,7 @@ export const initializeRightLayout = (api: GridviewApi) => {
};
const onReadyRightPanel: IGridviewReactProps['onReady'] = (event) => {
initializeRightLayout(event.api);
initializeRightPanelLayout(event.api);
};
const RightPanel = memo(() => {
@@ -143,13 +147,46 @@ const RightPanel = memo(() => {
});
RightPanel.displayName = 'RightPanel';
export const rootComponents: IGridviewReactProps['components'] = {
[LEFT_PANEL_ID]: UpscalingTabLeftPanel,
[MAIN_PANEL_ID]: MainPanel,
const leftPanelComponents: IGridviewReactProps['components'] = {
[SETTINGS_PANEL_ID]: UpscalingTabLeftPanel,
};
export const initializeLeftPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: SETTINGS_PANEL_ID,
component: SETTINGS_PANEL_ID,
});
};
const LeftPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
initializeLeftPanelLayout(event.api);
ctx._$leftPanelApi.set(event.api);
},
[ctx._$leftPanelApi]
);
return (
<>
<GridviewReact
className="dockview-theme-invoke"
orientation={Orientation.VERTICAL}
components={leftPanelComponents}
onReady={onReady}
/>
</>
);
});
LeftPanel.displayName = 'LeftPanel';
export const rootPanelComponents: IGridviewReactProps['components'] = {
[LEFT_PANEL_ID]: LeftPanel,
[MAIN_PANEL_ID]: CenterPanel,
[RIGHT_PANEL_ID]: RightPanel,
};
export const initializeRootLayout = (api: GridviewApi) => {
export const initializeRootPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: MAIN_PANEL_ID,
component: MAIN_PANEL_ID,
@@ -180,22 +217,22 @@ export const initializeRootLayout = (api: GridviewApi) => {
export const UpscalingTabAutoLayout = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const $api = useState(() => atom<GridviewApi | null>(null))[0];
const $rootPanelApi = useState(() => atom<GridviewApi | null>(null))[0];
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
$api.set(event.api);
initializeRootLayout(event.api);
$rootPanelApi.set(event.api);
initializeRootPanelLayout(event.api);
},
[$api]
[$rootPanelApi]
);
useResizeMainPanelOnFirstVisit($api, ref);
useResizeMainPanelOnFirstVisit($rootPanelApi, ref);
return (
<AutoLayoutProvider $api={$api}>
<AutoLayoutProvider $rootApi={$rootPanelApi}>
<GridviewReact
ref={ref}
className="dockview-theme-invoke"
components={rootComponents}
components={rootPanelComponents}
onReady={onReady}
orientation={Orientation.VERTICAL}
/>

View File

@@ -1,4 +1,4 @@
import type { GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import type { DockviewApi, GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
import { DockviewReact, GridviewReact, LayoutPriority, Orientation } from 'dockview';
import { WorkflowsLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/WorkflowsLaunchpadPanel';
import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
@@ -9,35 +9,36 @@ 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 } from 'features/ui/layouts/auto-layout-context';
import { AutoLayoutProvider, PanelHotkeysLogical, useAutoLayoutContext } 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 {
BOARDS_PANEL_ID,
GALLERY_PANEL_ID,
LAUNCHPAD_PANEL_ID,
LEFT_PANEL_ID,
LEFT_PANEL_MIN_SIZE_PX,
MAIN_PANEL_ID,
PROGRESS_PANEL_ID,
RIGHT_PANEL_ID,
RIGHT_PANEL_MIN_SIZE_PX,
SETTINGS_PANEL_ID,
VIEWER_PANEL_ID,
WORKSPACE_PANEL_ID,
} from './shared';
import { useResizeMainPanelOnFirstVisit } from './use-on-first-visible';
const LAUNCHPAD_PANEL_ID = 'launchpad';
const WORKSPACE_PANEL_ID = 'workspace';
const VIEWER_PANEL_ID = 'viewer';
const PROGRESS_PANEL_ID = 'progress';
const dockviewComponents: IDockviewReactProps['components'] = {
const centerPanelComponents: IDockviewReactProps['components'] = {
[LAUNCHPAD_PANEL_ID]: WorkflowsLaunchpadPanel,
[WORKSPACE_PANEL_ID]: NodeEditor,
[VIEWER_PANEL_ID]: ImageViewerPanel,
[PROGRESS_PANEL_ID]: GenerationProgressPanel,
};
const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
const { api } = event;
const initializeCenterPanelLayout = (api: DockviewApi) => {
api.addPanel({
id: LAUNCHPAD_PANEL_ID,
component: LAUNCHPAD_PANEL_ID,
@@ -72,24 +73,31 @@ const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
});
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(() => {
const CenterPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IDockviewReactProps['onReady']>(
(event) => {
initializeCenterPanelLayout(event.api);
ctx._$centerPanelApi.set(event.api);
const disposables = [
event.api.onWillShowOverlay((e) => {
if (e.kind === 'header_space' || e.kind === 'tab') {
return;
}
e.preventDefault();
}),
];
return () => {
disposables.forEach((disposable) => {
disposable.dispose();
});
};
},
[ctx._$centerPanelApi]
);
return (
<>
<DockviewReact
@@ -98,8 +106,8 @@ const MainPanel = memo(() => {
disableFloatingGroups={true}
dndEdges={false}
defaultTabComponent={TabWithoutCloseButton}
components={dockviewComponents}
onReady={onReadyMainPanel}
components={centerPanelComponents}
onReady={onReady}
theme={dockviewTheme}
/>
<FloatingLeftPanelButtons />
@@ -108,17 +116,14 @@ const MainPanel = memo(() => {
</>
);
});
MainPanel.displayName = 'MainPanel';
const BOARDS_PANEL_ID = 'boards';
const GALLERY_PANEL_ID = 'gallery';
CenterPanel.displayName = 'CenterPanel';
const rightPanelComponents: IGridviewReactProps['components'] = {
[BOARDS_PANEL_ID]: BoardsPanel,
[GALLERY_PANEL_ID]: GalleryPanel,
};
export const initializeRightLayout = (api: GridviewApi) => {
export const initializeRightPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: GALLERY_PANEL_ID,
component: GALLERY_PANEL_ID,
@@ -137,31 +142,68 @@ export const initializeRightLayout = (api: GridviewApi) => {
api.getPanel(BOARDS_PANEL_ID)?.api.setSize({ height: 256, width: RIGHT_PANEL_MIN_SIZE_PX });
};
const onReadyRightPanel: IGridviewReactProps['onReady'] = (event) => {
initializeRightLayout(event.api);
};
const RightPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
initializeRightPanelLayout(event.api);
ctx._$rightPanelApi.set(event.api);
},
[ctx._$rightPanelApi]
);
return (
<>
<GridviewReact
className="dockview-theme-invoke"
orientation={Orientation.VERTICAL}
components={rightPanelComponents}
onReady={onReadyRightPanel}
onReady={onReady}
/>
</>
);
});
RightPanel.displayName = 'RightPanel';
export const rootComponents: IGridviewReactProps['components'] = {
[LEFT_PANEL_ID]: WorkflowsTabLeftPanel,
[MAIN_PANEL_ID]: MainPanel,
const leftPanelComponents: IGridviewReactProps['components'] = {
[SETTINGS_PANEL_ID]: WorkflowsTabLeftPanel,
};
export const initializeLeftPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: SETTINGS_PANEL_ID,
component: SETTINGS_PANEL_ID,
});
};
const LeftPanel = memo(() => {
const ctx = useAutoLayoutContext();
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
initializeLeftPanelLayout(event.api);
ctx._$leftPanelApi.set(event.api);
},
[ctx._$leftPanelApi]
);
return (
<>
<GridviewReact
className="dockview-theme-invoke"
orientation={Orientation.VERTICAL}
components={leftPanelComponents}
onReady={onReady}
/>
</>
);
});
LeftPanel.displayName = 'LeftPanel';
export const rootPanelComponents: IGridviewReactProps['components'] = {
[LEFT_PANEL_ID]: LeftPanel,
[MAIN_PANEL_ID]: CenterPanel,
[RIGHT_PANEL_ID]: RightPanel,
};
export const initializeRootLayout = (api: GridviewApi) => {
export const initializeRootPanelLayout = (api: GridviewApi) => {
api.addPanel({
id: MAIN_PANEL_ID,
component: MAIN_PANEL_ID,
@@ -192,22 +234,22 @@ export const initializeRootLayout = (api: GridviewApi) => {
export const WorkflowsTabAutoLayout = memo(() => {
const ref = useRef<HTMLDivElement>(null);
const $api = useState(() => atom<GridviewApi | null>(null))[0];
const $rootPanelApi = useState(() => atom<GridviewApi | null>(null))[0];
const onReady = useCallback<IGridviewReactProps['onReady']>(
(event) => {
$api.set(event.api);
initializeRootLayout(event.api);
$rootPanelApi.set(event.api);
initializeRootPanelLayout(event.api);
},
[$api]
[$rootPanelApi]
);
useResizeMainPanelOnFirstVisit($api, ref);
useResizeMainPanelOnFirstVisit($rootPanelApi, ref);
return (
<AutoLayoutProvider $api={$api}>
<AutoLayoutProvider $rootApi={$rootPanelApi}>
<GridviewReact
ref={ref}
className="dockview-theme-invoke"
components={rootComponents}
components={rootPanelComponents}
onReady={onReady}
orientation={Orientation.VERTICAL}
/>