diff --git a/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/WorkflowsLaunchpadPanel.tsx b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/WorkflowsLaunchpadPanel.tsx
new file mode 100644
index 0000000000..e048d94d6c
--- /dev/null
+++ b/invokeai/frontend/web/src/features/controlLayers/components/SimpleSession/WorkflowsLaunchpadPanel.tsx
@@ -0,0 +1,13 @@
+import { Flex, Heading } from '@invoke-ai/ui-library';
+import { memo } from 'react';
+
+export const WorkflowsLaunchpadPanel = memo(() => {
+ return (
+
+
+ Go deep with Workflows.
+
+
+ );
+});
+WorkflowsLaunchpadPanel.displayName = 'WorkflowsLaunchpadPanel';
diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx
index 168af9dff9..96a4c5cd0b 100644
--- a/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/NodeEditor.tsx
@@ -1,4 +1,5 @@
import type { SystemStyleObject } 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';
@@ -29,21 +30,23 @@ const NodeEditor = () => {
const { t } = useTranslation();
return (
-
- {data && (
- <>
-
-
-
-
-
-
-
- >
- )}
-
- {isLoading && }
-
+
+
+ {data && (
+ <>
+
+
+
+
+
+
+
+ >
+ )}
+
+ {isLoading && }
+
+
);
};
diff --git a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx
index dd114a22aa..5a20867115 100644
--- a/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/sidePanel/WorkflowsTabLeftPanel.tsx
@@ -8,6 +8,7 @@ import { PublishWorkflowPanelContent } from 'features/nodes/components/sidePanel
import { ActiveWorkflowDescription } from 'features/nodes/components/sidePanel/WorkflowListMenu/ActiveWorkflowDescription';
import { ActiveWorkflowNameAndActions } from 'features/nodes/components/sidePanel/WorkflowListMenu/ActiveWorkflowNameAndActions';
import { selectWorkflowMode } from 'features/nodes/store/workflowLibrarySlice';
+import QueueControls from 'features/queue/components/QueueControls';
import { memo } from 'react';
import { ViewModeLeftPanelContent } from './viewMode/ViewModeLeftPanelContent';
@@ -18,13 +19,16 @@ const WorkflowsTabLeftPanel = () => {
const isInPublishFlow = useStore($isInPublishFlow);
return (
-
- {isInPublishFlow && }
- {!isInPublishFlow && }
- {!isInPublishFlow && !isPublished && mode === 'view' && }
- {!isInPublishFlow && !isPublished && mode === 'view' && }
- {!isInPublishFlow && !isPublished && mode === 'edit' && }
- {isPublished && }
+
+
+
+ {isInPublishFlow && }
+ {!isInPublishFlow && }
+ {!isInPublishFlow && !isPublished && mode === 'view' && }
+ {!isInPublishFlow && !isPublished && mode === 'view' && }
+ {!isInPublishFlow && !isPublished && mode === 'edit' && }
+ {isPublished && }
+
);
};
diff --git a/invokeai/frontend/web/src/features/queue/components/QueueTabContent.tsx b/invokeai/frontend/web/src/features/queue/components/QueueTabContent.tsx
index 73a8dfc401..2dae5e6ebe 100644
--- a/invokeai/frontend/web/src/features/queue/components/QueueTabContent.tsx
+++ b/invokeai/frontend/web/src/features/queue/components/QueueTabContent.tsx
@@ -1,7 +1,5 @@
import { Box, Flex } from '@invoke-ai/ui-library';
-import { useAppSelector } from 'app/store/storeHooks';
import { useFeatureStatus } from 'features/system/hooks/useFeatureStatus';
-import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { memo } from 'react';
import InvocationCacheStatus from './InvocationCacheStatus';
@@ -11,18 +9,9 @@ import QueueTabQueueControls from './QueueTabQueueControls';
const QueueTabContent = () => {
const isInvocationCacheEnabled = useFeatureStatus('invocationCache');
- const activeTabName = useAppSelector(selectActiveTab);
return (
-
+
diff --git a/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts b/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts
index 9844fc81c3..781397e794 100644
--- a/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts
+++ b/invokeai/frontend/web/src/features/system/components/HotkeysModal/useHotkeyData.ts
@@ -78,11 +78,12 @@ export const useHotkeyData = (): HotkeysData => {
addHotkey('app', 'invokeFront', ['mod+shift+enter']);
addHotkey('app', 'cancelQueueItem', ['shift+x']);
addHotkey('app', 'clearQueue', ['mod+shift+x']);
- addHotkey('app', 'selectCanvasTab', ['1']);
- addHotkey('app', 'selectUpscalingTab', ['2']);
- addHotkey('app', 'selectWorkflowsTab', ['3']);
- addHotkey('app', 'selectModelsTab', ['4'], isModelManagerEnabled);
- addHotkey('app', 'selectQueueTab', isModelManagerEnabled ? ['5'] : ['4']);
+ addHotkey('app', 'selectGenerateTab', ['1']);
+ addHotkey('app', 'selectCanvasTab', ['2']);
+ addHotkey('app', 'selectUpscalingTab', ['3']);
+ addHotkey('app', 'selectWorkflowsTab', ['4']);
+ addHotkey('app', 'selectModelsTab', ['5'], isModelManagerEnabled);
+ addHotkey('app', 'selectQueueTab', isModelManagerEnabled ? ['6'] : ['5']);
addHotkey('app', 'focusPrompt', ['alt+a']);
addHotkey('app', 'toggleLeftPanel', ['t', 'o']);
addHotkey('app', 'toggleRightPanel', ['g']);
diff --git a/invokeai/frontend/web/src/features/ui/components/AppContent.tsx b/invokeai/frontend/web/src/features/ui/components/AppContent.tsx
index 2e94ba0990..3cd6b73435 100644
--- a/invokeai/frontend/web/src/features/ui/components/AppContent.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/AppContent.tsx
@@ -8,11 +8,16 @@ import { VerticalNavBar } from 'features/ui/components/VerticalNavBar';
import { CanvasTabAutoLayout } from 'features/ui/layouts/canvas-tab-auto-layout';
import { GenerateTabAutoLayout } from 'features/ui/layouts/generate-tab-auto-layout';
import { UpscalingTabAutoLayout } from 'features/ui/layouts/upscaling-tab-auto-layout';
+import { WorkflowsTabAutoLayout } from 'features/ui/layouts/workflows-tab-auto-layout';
import { selectActiveTabIndex } from 'features/ui/store/uiSelectors';
import { $isLeftPanelOpen, $isRightPanelOpen } from 'features/ui/store/uiSlice';
import type { CSSProperties } from 'react';
import { memo } from 'react';
+import { TabMountGate } from './TabMountGate';
+import ModelManagerTab from './tabs/ModelManagerTab';
+import QueueTab from './tabs/QueueTab';
+
const panelStyles: CSSProperties = { position: 'relative', height: '100%', width: '100%', minWidth: 0 };
const onLeftPanelCollapse = (isCollapsed: boolean) => $isLeftPanelOpen.set(!isCollapsed);
@@ -103,15 +108,36 @@ export const AppContent = memo(() => {
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
);
diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/WorkflowsTabContent.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/WorkflowsTabContent.tsx
index c924855b5a..d1e506afff 100644
--- a/invokeai/frontend/web/src/features/ui/components/tabs/WorkflowsTabContent.tsx
+++ b/invokeai/frontend/web/src/features/ui/components/tabs/WorkflowsTabContent.tsx
@@ -1,4 +1,3 @@
-import { ReactFlowProvider } from '@xyflow/react';
import { useAppSelector } from 'app/store/storeHooks';
import { ImageViewer } from 'features/gallery/components/ImageViewer/ImageViewer';
import NodeEditor from 'features/nodes/components/NodeEditor';
@@ -9,11 +8,7 @@ export const WorkflowsMainPanel = memo(() => {
const mode = useAppSelector(selectWorkflowMode);
if (mode === 'edit') {
- return (
-
-
-
- );
+ return ;
}
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
new file mode 100644
index 0000000000..c94bd5f081
--- /dev/null
+++ b/invokeai/frontend/web/src/features/ui/layouts/workflows-tab-auto-layout.tsx
@@ -0,0 +1,203 @@
+import type { GridviewApi, IDockviewReactProps, IGridviewReactProps } from 'dockview';
+import { DockviewReact, GridviewReact, Orientation } from 'dockview';
+import { WorkflowsLaunchpadPanel } from 'features/controlLayers/components/SimpleSession/WorkflowsLaunchpadPanel';
+import { BoardsPanel } from 'features/gallery/components/BoardsListPanelContent';
+import { GalleryPanel } from 'features/gallery/components/Gallery';
+import { GenerationProgressPanel } from 'features/gallery/components/ImageViewer/GenerationProgressPanel';
+import { ImageViewerPanel } from 'features/gallery/components/ImageViewer/ImageViewerPanel';
+import NodeEditor from 'features/nodes/components/NodeEditor';
+import WorkflowsTabLeftPanel from 'features/nodes/components/sidePanel/WorkflowsTabLeftPanel';
+import { AutoLayoutProvider } from 'features/ui/layouts/auto-layout-context';
+import { TabWithoutCloseButton } from 'features/ui/layouts/TabWithoutCloseButton';
+import { LEFT_PANEL_MIN_SIZE_PX, RIGHT_PANEL_MIN_SIZE_PX } from 'features/ui/store/uiSlice';
+import { dockviewTheme } from 'features/ui/styles/theme';
+import { atom } from 'nanostores';
+import { memo, useCallback, useRef, useState } from 'react';
+
+import { useOnFirstVisible } 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'] = {
+ [LAUNCHPAD_PANEL_ID]: WorkflowsLaunchpadPanel,
+ [WORKSPACE_PANEL_ID]: NodeEditor,
+ [VIEWER_PANEL_ID]: ImageViewerPanel,
+ [PROGRESS_PANEL_ID]: GenerationProgressPanel,
+};
+
+const onReadyMainPanel: IDockviewReactProps['onReady'] = (event) => {
+ const { api } = event;
+ api.addPanel({
+ id: LAUNCHPAD_PANEL_ID,
+ component: LAUNCHPAD_PANEL_ID,
+ title: 'Launchpad',
+ });
+ api.addPanel({
+ id: WORKSPACE_PANEL_ID,
+ component: WORKSPACE_PANEL_ID,
+ title: 'Workflow Editor',
+ position: {
+ direction: 'within',
+ referencePanel: LAUNCHPAD_PANEL_ID,
+ },
+ });
+ api.addPanel({
+ id: VIEWER_PANEL_ID,
+ component: VIEWER_PANEL_ID,
+ title: 'Image Viewer',
+ position: {
+ direction: 'within',
+ referencePanel: LAUNCHPAD_PANEL_ID,
+ },
+ });
+ api.addPanel({
+ id: PROGRESS_PANEL_ID,
+ component: PROGRESS_PANEL_ID,
+ title: 'Generation Progress',
+ position: {
+ direction: 'within',
+ referencePanel: LAUNCHPAD_PANEL_ID,
+ },
+ });
+
+ api.getPanel(LAUNCHPAD_PANEL_ID)?.api.setActive();
+
+ const disposables = [
+ api.onWillShowOverlay((e) => {
+ if (e.kind === 'header_space' || e.kind === 'tab') {
+ return;
+ }
+ e.preventDefault();
+ }),
+ ];
+
+ return () => {
+ disposables.forEach((disposable) => {
+ disposable.dispose();
+ });
+ };
+};
+
+const MainPanel = memo(() => {
+ return (
+
+ );
+});
+MainPanel.displayName = 'MainPanel';
+
+const LEFT_PANEL_ID = 'left';
+const MAIN_PANEL_ID = 'main';
+const BOARDS_PANEL_ID = 'boards';
+const GALLERY_PANEL_ID = 'gallery';
+
+export const gridviewComponents: IGridviewReactProps['components'] = {
+ [LEFT_PANEL_ID]: WorkflowsTabLeftPanel,
+ [MAIN_PANEL_ID]: MainPanel,
+ [BOARDS_PANEL_ID]: BoardsPanel,
+ [GALLERY_PANEL_ID]: GalleryPanel,
+};
+
+export const initializeLayout = (api: GridviewApi) => {
+ api.addPanel({
+ id: MAIN_PANEL_ID,
+ component: MAIN_PANEL_ID,
+ // priority: LayoutPriority.High,
+ });
+ api.addPanel({
+ id: LEFT_PANEL_ID,
+ component: LEFT_PANEL_ID,
+ minimumWidth: LEFT_PANEL_MIN_SIZE_PX,
+ position: {
+ direction: 'left',
+ referencePanel: MAIN_PANEL_ID,
+ },
+ // priority: LayoutPriority.High,
+ });
+ api.addPanel({
+ id: GALLERY_PANEL_ID,
+ component: GALLERY_PANEL_ID,
+ minimumWidth: RIGHT_PANEL_MIN_SIZE_PX,
+ minimumHeight: 232,
+ position: {
+ direction: 'right',
+ referencePanel: MAIN_PANEL_ID,
+ },
+ // priority: LayoutPriority.High,
+ });
+ api.addPanel({
+ id: BOARDS_PANEL_ID,
+ component: BOARDS_PANEL_ID,
+ minimumHeight: 36,
+ position: {
+ direction: 'above',
+ referencePanel: GALLERY_PANEL_ID,
+ },
+ // priority: LayoutPriority.High,
+ });
+ api.getPanel(LEFT_PANEL_ID)?.api.setSize({ width: LEFT_PANEL_MIN_SIZE_PX });
+ api.getPanel(BOARDS_PANEL_ID)?.api.setSize({ height: 256, width: RIGHT_PANEL_MIN_SIZE_PX });
+ api.getPanel(MAIN_PANEL_ID)?.api.setActive();
+};
+
+export const WorkflowsTabAutoLayout = memo(() => {
+ const ref = useRef(null);
+ const $api = useState(() => atom(null))[0];
+ const onReady = useCallback(
+ (event) => {
+ $api.set(event.api);
+ initializeLayout(event.api);
+ },
+ [$api]
+ );
+ const resizeMainPanelOnFirstVisible = useCallback(() => {
+ const api = $api.get();
+ if (!api) {
+ return;
+ }
+ const mainPanel = api.getPanel(MAIN_PANEL_ID);
+ if (!mainPanel) {
+ return;
+ }
+ if (mainPanel.width !== 0) {
+ return;
+ }
+ let count = 0;
+ const setSize = () => {
+ if (count++ > 50) {
+ return;
+ }
+ mainPanel.api.setSize({ width: Number.MAX_SAFE_INTEGER });
+ if (mainPanel.width === 0) {
+ requestAnimationFrame(setSize);
+ return;
+ }
+ };
+ setSize();
+ }, [$api]);
+ useOnFirstVisible(ref, resizeMainPanelOnFirstVisible);
+
+ return (
+
+
+
+ );
+});
+WorkflowsTabAutoLayout.displayName = 'WorkflowsTabAutoLayout';