mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(canvas): implement Phase 7.2 - functional canvas instance manager
- Extended NavigationApi with getDockviewApi() and getContainer() methods to allow component access to dockview APIs - Enhanced CanvasInstanceManager to create dockview panels dynamically when "Add Canvas" is clicked - Added selectCanvasInstances selector for future use - CanvasInstanceManager now creates both Redux state and corresponding dockview panel - New panels are automatically activated when created - Maximum 3 canvas limit enforced This completes the core "Add new canvas" functionality specified in Phase 7.2. 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,11 @@ export const selectActiveCanvasId = (state: RootState) => state.canvases.activeI
|
||||
*/
|
||||
export const selectCanvasCount = (state: RootState) => Object.keys(state.canvases.instances).length;
|
||||
|
||||
/**
|
||||
* Selects all canvas instances
|
||||
*/
|
||||
export const selectCanvasInstances = (state: RootState) => state.canvases.instances;
|
||||
|
||||
/**
|
||||
* Legacy selector for backward compatibility - selects the active canvas
|
||||
* @deprecated Use selectActiveCanvas instead
|
||||
|
||||
@@ -2,6 +2,12 @@ import { Flex, IconButton, Text } from '@invoke-ai/ui-library';
|
||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||
import { canvasInstanceAdded } from 'features/controlLayers/store/canvasesSlice';
|
||||
import { selectCanvasCount } from 'features/controlLayers/store/selectors';
|
||||
import type { DockviewPanelParameters } from 'features/ui/layouts/auto-layout-context';
|
||||
import { navigationApi } from 'features/ui/layouts/navigation-api';
|
||||
import {
|
||||
DOCKVIEW_TAB_CANVAS_WORKSPACE_ID,
|
||||
WORKSPACE_PANEL_ID
|
||||
} from 'features/ui/layouts/shared';
|
||||
import { nanoid } from 'nanoid';
|
||||
import { memo, useCallback } from 'react';
|
||||
import { PiPlus } from 'react-icons/pi';
|
||||
@@ -16,18 +22,38 @@ export const CanvasInstanceManager = memo(({ maxCanvases = 3 }: CanvasInstanceMa
|
||||
|
||||
const addCanvas = useCallback(() => {
|
||||
if (canvasCount >= maxCanvases) {
|
||||
return;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const canvasId = nanoid();
|
||||
const canvasName = `Canvas ${canvasCount + 1}`;
|
||||
|
||||
// For now, just add to Redux. The dockview panel creation will be handled
|
||||
// by other parts of the system that have access to the dockview API
|
||||
// Add to Redux first
|
||||
dispatch(canvasInstanceAdded({ canvasId, name: canvasName }));
|
||||
|
||||
// TODO: Trigger panel creation through a global event or state change
|
||||
// that the dockview can listen to
|
||||
// Get the dockview API and create the panel
|
||||
const dockviewApi = navigationApi.getDockviewApi('canvas', 'main');
|
||||
if (dockviewApi) {
|
||||
const panelId = `${WORKSPACE_PANEL_ID}_${canvasId}`;
|
||||
|
||||
// Create the dockview panel
|
||||
dockviewApi.addPanel<DockviewPanelParameters>({
|
||||
id: panelId,
|
||||
component: WORKSPACE_PANEL_ID,
|
||||
title: canvasName,
|
||||
tabComponent: DOCKVIEW_TAB_CANVAS_WORKSPACE_ID,
|
||||
params: {
|
||||
tab: 'canvas',
|
||||
canvasId,
|
||||
focusRegion: 'canvas',
|
||||
i18nKey: 'ui.panels.canvas',
|
||||
},
|
||||
});
|
||||
|
||||
// Activate the new panel
|
||||
const newPanel = dockviewApi.getPanel(panelId);
|
||||
newPanel?.api.setActive();
|
||||
}
|
||||
}, [canvasCount, maxCanvases, dispatch]);
|
||||
|
||||
const canCanAddCanvas = canvasCount < maxCanvases;
|
||||
|
||||
@@ -60,6 +60,11 @@ export class NavigationApi {
|
||||
*/
|
||||
private panels: Map<string, PanelType> = new Map();
|
||||
|
||||
/**
|
||||
* Map of registered containers (DockviewApi/GridviewApi), keyed by tab and container ID
|
||||
*/
|
||||
private containers: Map<string, DockviewApi | GridviewApi> = new Map();
|
||||
|
||||
/**
|
||||
* Map of waiters for panel registration.
|
||||
*/
|
||||
@@ -219,6 +224,9 @@ export class NavigationApi {
|
||||
}
|
||||
|
||||
const key = this._getContainerKey(tab, id);
|
||||
|
||||
// Store the container API for later access
|
||||
this.containers.set(key, api);
|
||||
|
||||
const stored = this._app.storage.get(key);
|
||||
if (stored) {
|
||||
@@ -707,19 +715,49 @@ export class NavigationApi {
|
||||
.map((key) => key.substring(prefix.length));
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a registered container API by tab and container ID.
|
||||
* @param tab - The tab the container belongs to
|
||||
* @param id - The container ID
|
||||
* @returns The DockviewApi or GridviewApi instance, or undefined if not found
|
||||
*/
|
||||
getContainer = (tab: TabName, id: string): DockviewApi | GridviewApi | undefined => {
|
||||
const key = this._getContainerKey(tab, id);
|
||||
return this.containers.get(key);
|
||||
};
|
||||
|
||||
/**
|
||||
* Get a registered DockviewApi by tab and container ID.
|
||||
* @param tab - The tab the container belongs to
|
||||
* @param id - The container ID
|
||||
* @returns The DockviewApi instance, or undefined if not found or not a DockviewApi
|
||||
*/
|
||||
getDockviewApi = (tab: TabName, id: string): DockviewApi | undefined => {
|
||||
const container = this.getContainer(tab, id);
|
||||
return container instanceof DockviewApi ? container : undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Unregister all panels for a tab. Any pending waiters for these panels will be rejected.
|
||||
* @param tab - The tab to unregister panels for
|
||||
*/
|
||||
unregisterTab = (tab: TabName): void => {
|
||||
const prefix = this._getPanelPrefix(tab);
|
||||
const keysToDelete = Array.from(this.panels.keys()).filter((key) => key.startsWith(prefix));
|
||||
const panelPrefix = this._getPanelPrefix(tab);
|
||||
const panelKeysToDelete = Array.from(this.panels.keys()).filter((key) => key.startsWith(panelPrefix));
|
||||
|
||||
for (const key of keysToDelete) {
|
||||
for (const key of panelKeysToDelete) {
|
||||
this.panels.delete(key);
|
||||
}
|
||||
|
||||
const promiseKeysToDelete = Array.from(this.waiters.keys()).filter((key) => key.startsWith(prefix));
|
||||
// Clean up containers for this tab
|
||||
const containerPrefix = this._getContainerPrefix(tab);
|
||||
const containerKeysToDelete = Array.from(this.containers.keys()).filter((key) => key.startsWith(containerPrefix));
|
||||
|
||||
for (const key of containerKeysToDelete) {
|
||||
this.containers.delete(key);
|
||||
}
|
||||
|
||||
const promiseKeysToDelete = Array.from(this.waiters.keys()).filter((key) => key.startsWith(panelPrefix));
|
||||
for (const key of promiseKeysToDelete) {
|
||||
const waiter = this.waiters.get(key);
|
||||
if (waiter) {
|
||||
|
||||
Reference in New Issue
Block a user