diff --git a/invokeai/frontend/web/src/features/gallery/components/GalleryTopBar.tsx b/invokeai/frontend/web/src/features/gallery/components/GalleryTopBar.tsx index 20634f7450..68037da8a2 100644 --- a/invokeai/frontend/web/src/features/gallery/components/GalleryTopBar.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/GalleryTopBar.tsx @@ -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 ( - + Boards diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx index aa68d5460e..f6753316b6 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/ImageMenuItemOpenInViewer.tsx @@ -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 ( { 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>(() => { store.dispatch(imageToCompareChanged(null)); - }, [store]); + autoLayoutContext.focusImageViewer(); + }, [autoLayoutContext, store]); const dataTestId = useMemo(() => getGalleryImageDataTestId(imageDTO.image_name), [imageDTO.image_name]); diff --git a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageOpenInViewerIconButton.tsx b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageOpenInViewerIconButton.tsx index bf58669bf6..ac694369b9 100644 --- a/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageOpenInViewerIconButton.tsx +++ b/invokeai/frontend/web/src/features/gallery/components/ImageGrid/GalleryImageOpenInViewerIconButton.tsx @@ -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 ( ; toggleLeftPanel: () => void; toggleRightPanel: () => void; toggleBothPanels: () => void; resetPanels: () => void; + focusImageViewer: () => void; + _$rootPanelApi: WritableAtom; + _$leftPanelApi: WritableAtom; + _$centerPanelApi: WritableAtom; + _$rightPanelApi: WritableAtom; }; const AutoLayoutContext = createContext(null); @@ -42,9 +53,22 @@ const getIsCollapsed = (api: GridviewApi, panelId: string) => { return panel.maximumWidth === 0; }; -export const AutoLayoutProvider = (props: PropsWithChildren<{ $api: Atom }>) => { +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 }>) => { + const { $rootApi, children } = props; + const $leftApi = useState(() => atom(null))[0]; + const $centerApi = useState(() => atom(null))[0]; + const $rightApi = useState(() => atom(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 { - const api = props.$api.get(); + const api = $rootApi.get(); if (!api) { return; } @@ -65,10 +89,10 @@ export const AutoLayoutProvider = (props: PropsWithChildren<{ $api: Atom { - const api = props.$api.get(); + const api = $rootApi.get(); if (!api) { return; } @@ -81,28 +105,50 @@ export const AutoLayoutProvider = (props: PropsWithChildren<{ $api: Atom { - 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( () => ({ - $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 {props.children}; + return {children}; }; export const useAutoLayoutContext = () => { diff --git a/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx index b77a196c56..b4e5971972 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/canvas-tab-auto-layout.tsx @@ -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( + (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 ( <> { disableFloatingGroups={true} dndEdges={false} defaultTabComponent={TabWithoutCloseButton} - components={mainPanelComponents} - onReady={onReadyMainPanel} + components={centerPanelComponents} + onReady={onReady} theme={dockviewTheme} /> @@ -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( + (event) => { + initializeRightPanelLayout(event.api); + ctx._$rightPanelApi.set(event.api); + }, + [ctx._$rightPanelApi] + ); return ( <> ); }); 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( + (event) => { + initializeLeftPanelLayout(event.api); + ctx._$leftPanelApi.set(event.api); + }, + [ctx._$leftPanelApi] + ); + return ( + <> + + + ); +}); +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(null); - const $api = useState(() => atom(null))[0]; + const $rootPanelApi = useState(() => atom(null))[0]; const onReady = useCallback( (event) => { - $api.set(event.api); - initializeRootLayout(event.api); + $rootPanelApi.set(event.api); + initializeRootPanelLayout(event.api); }, - [$api] + [$rootPanelApi] ); - useResizeMainPanelOnFirstVisit($api, ref); + useResizeMainPanelOnFirstVisit($rootPanelApi, ref); return ( - + diff --git a/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx index c954118928..ec87e58d46 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/generate-tab-auto-layout.tsx @@ -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( + (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 ( <> { disableFloatingGroups={true} dndEdges={false} defaultTabComponent={TabWithoutCloseButton} - components={mainPanelComponents} - onReady={onReadyMainPanel} + components={centerPanelComponents} + onReady={onReady} theme={dockviewTheme} /> @@ -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( + (event) => { + initializeRightPanelLayout(event.api); + ctx._$rightPanelApi.set(event.api); + }, + [ctx._$rightPanelApi] + ); return ( <> ); }); 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( + (event) => { + initializeLeftPanelLayout(event.api); + ctx._$leftPanelApi.set(event.api); + }, + [ctx._$leftPanelApi] + ); + return ( + <> + + + ); +}); +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(null); - const $api = useState(() => atom(null))[0]; + const $rootPanelApi = useState(() => atom(null))[0]; const onReady = useCallback( (event) => { - $api.set(event.api); - initializeRootLayout(event.api); + $rootPanelApi.set(event.api); + initializeRootPanelLayout(event.api); }, - [$api] + [$rootPanelApi] ); - useResizeMainPanelOnFirstVisit($api, ref); + useResizeMainPanelOnFirstVisit($rootPanelApi, ref); return ( - + diff --git a/invokeai/frontend/web/src/features/ui/layouts/shared.ts b/invokeai/frontend/web/src/features/ui/layouts/shared.ts index 894616faf9..6c160e295e 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/shared.ts +++ b/invokeai/frontend/web/src/features/ui/layouts/shared.ts @@ -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; diff --git a/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx index c013d5ae39..a0a57b96f2 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/upscaling-tab-auto-layout.tsx @@ -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( + (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 ( <> { disableFloatingGroups={true} dndEdges={false} defaultTabComponent={TabWithoutCloseButton} - components={dockviewComponents} - onReady={onReadyMainPanel} + components={centerComponents} + onReady={onReady} theme={dockviewTheme} /> @@ -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( + (event) => { + initializeLeftPanelLayout(event.api); + ctx._$leftPanelApi.set(event.api); + }, + [ctx._$leftPanelApi] + ); + return ( + <> + + + ); +}); +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(null); - const $api = useState(() => atom(null))[0]; + const $rootPanelApi = useState(() => atom(null))[0]; const onReady = useCallback( (event) => { - $api.set(event.api); - initializeRootLayout(event.api); + $rootPanelApi.set(event.api); + initializeRootPanelLayout(event.api); }, - [$api] + [$rootPanelApi] ); - useResizeMainPanelOnFirstVisit($api, ref); + useResizeMainPanelOnFirstVisit($rootPanelApi, ref); return ( - + diff --git a/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx b/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx index de9b98aae4..966e43cf88 100644 --- a/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx +++ b/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx @@ -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( + (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 ( <> { disableFloatingGroups={true} dndEdges={false} defaultTabComponent={TabWithoutCloseButton} - components={dockviewComponents} - onReady={onReadyMainPanel} + components={centerPanelComponents} + onReady={onReady} theme={dockviewTheme} /> @@ -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( + (event) => { + initializeRightPanelLayout(event.api); + ctx._$rightPanelApi.set(event.api); + }, + [ctx._$rightPanelApi] + ); return ( <> ); }); 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( + (event) => { + initializeLeftPanelLayout(event.api); + ctx._$leftPanelApi.set(event.api); + }, + [ctx._$leftPanelApi] + ); + return ( + <> + + + ); +}); +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(null); - const $api = useState(() => atom(null))[0]; + const $rootPanelApi = useState(() => atom(null))[0]; const onReady = useCallback( (event) => { - $api.set(event.api); - initializeRootLayout(event.api); + $rootPanelApi.set(event.api); + initializeRootPanelLayout(event.api); }, - [$api] + [$rootPanelApi] ); - useResizeMainPanelOnFirstVisit($api, ref); + useResizeMainPanelOnFirstVisit($rootPanelApi, ref); return ( - +