Skip to content

Commit

Permalink
feat(ui): move layers/gallery tab state into redux so it persists acr…
Browse files Browse the repository at this point in the history
…oss sessions/refreshes, make gallery the default
  • Loading branch information
Mary Hipp authored and Mary Hipp committed Oct 15, 2024
1 parent fe87c19 commit 44cc25a
Show file tree
Hide file tree
Showing 11 changed files with 64 additions and 57 deletions.
3 changes: 2 additions & 1 deletion invokeai/frontend/web/src/app/hooks/useStudioInitAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { parseAndRecallAllMetadata } from 'features/metadata/util/handlers';
import { $isWorkflowListMenuIsOpen } from 'features/nodes/store/workflowListMenu';
import { $isStylePresetsMenuOpen, activeStylePresetIdChanged } from 'features/stylePresets/store/stylePresetSlice';
import { toast } from 'features/toast/toast';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { activeTabCanvasRightPanelChanged, setActiveTab } from 'features/ui/store/uiSlice';
import { useGetAndLoadLibraryWorkflow } from 'features/workflowLibrary/hooks/useGetAndLoadLibraryWorkflow';
import { useCallback, useEffect, useRef } from 'react';
import { useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -140,6 +140,7 @@ export const useStudioInitAction = (action?: StudioInitAction) => {
case 'generation':
// Go to the canvas tab, open the image viewer, and enable send-to-gallery mode
store.dispatch(setActiveTab('canvas'));
store.dispatch(activeTabCanvasRightPanelChanged('gallery'));
store.dispatch(settingsSendToCanvasChanged(false));
$imageViewer.set(true);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,22 @@ import { Alert, AlertDescription, AlertIcon, AlertTitle, Button, Flex } from '@i
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useBoolean } from 'common/hooks/useBoolean';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import {
selectCanvasRightPanelGalleryTab,
selectCanvasRightPanelLayersTab,
} from 'features/controlLayers/store/ephemeral';
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { useCurrentDestination } from 'features/queue/hooks/useCurrentDestination';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { activeTabCanvasRightPanelChanged, setActiveTab } from 'features/ui/store/uiSlice';
import { AnimatePresence, motion } from 'framer-motion';
import type { PropsWithChildren, ReactNode } from 'react';
import { useCallback, useMemo } from 'react';
import { Trans, useTranslation } from 'react-i18next';

const ActivateImageViewerButton = (props: PropsWithChildren) => {
const imageViewer = useImageViewer();
const dispatch = useAppDispatch();
const onClick = useCallback(() => {
imageViewer.open();
selectCanvasRightPanelGalleryTab();
}, [imageViewer]);
dispatch(activeTabCanvasRightPanelChanged('gallery'));
}, [imageViewer, dispatch]);
return (
<Button onClick={onClick} size="sm" variant="link" color="base.50">
{props.children}
Expand Down Expand Up @@ -60,7 +57,7 @@ const ActivateCanvasButton = (props: PropsWithChildren) => {
const imageViewer = useImageViewer();
const onClick = useCallback(() => {
dispatch(setActiveTab('canvas'));
selectCanvasRightPanelLayersTab();
dispatch(activeTabCanvasRightPanelChanged('layers'));
imageViewer.close();
}, [dispatch, imageViewer]);
return (
Expand Down
Original file line number Diff line number Diff line change
@@ -1,31 +1,49 @@
import { useDndContext } from '@dnd-kit/core';
import { Box, Button, Spacer, Tab, TabList, TabPanel, TabPanels, Tabs } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useAppSelector } from 'app/store/storeHooks';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { CanvasLayersPanelContent } from 'features/controlLayers/components/CanvasLayersPanelContent';
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import {
$canvasRightPanelTabIndex,
selectCanvasRightPanelGalleryTab,
selectCanvasRightPanelLayersTab,
} from 'features/controlLayers/store/ephemeral';
import { selectEntityCountActive } from 'features/controlLayers/store/selectors';
import GalleryPanelContent from 'features/gallery/components/GalleryPanelContent';
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import { selectActiveTabCanvasRightPanel } from 'features/ui/store/uiSelectors';
import { activeTabCanvasRightPanelChanged } from 'features/ui/store/uiSlice';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';

export const CanvasRightPanel = memo(() => {
const { t } = useTranslation();
const tabIndex = useStore($canvasRightPanelTabIndex);
const activeTab = useAppSelector(selectActiveTabCanvasRightPanel);
const imageViewer = useImageViewer();
const dispatch = useAppDispatch();

const tabIndex = useMemo(() => {
if (activeTab === 'gallery') {
return 1;
} else {
return 0;
}
}, [activeTab]);

const onClickViewerToggleButton = useCallback(() => {
if ($canvasRightPanelTabIndex.get() !== 1) {
$canvasRightPanelTabIndex.set(1);
if (activeTab !== 'gallery') {
dispatch(activeTabCanvasRightPanelChanged('gallery'));
}
imageViewer.toggle();
}, [imageViewer]);
}, [imageViewer, activeTab, dispatch]);

const onChangeTab = useCallback(
(index: number) => {
if (index === 0) {
dispatch(activeTabCanvasRightPanelChanged('layers'));
} else {
dispatch(activeTabCanvasRightPanelChanged('gallery'));
}
},
[dispatch]
);

useRegisteredHotkeys({
id: 'toggleViewer',
category: 'viewer',
Expand All @@ -34,7 +52,7 @@ export const CanvasRightPanel = memo(() => {
});

return (
<Tabs index={tabIndex} onChange={$canvasRightPanelTabIndex.set} w="full" h="full" display="flex" flexDir="column">
<Tabs index={tabIndex} onChange={onChangeTab} w="full" h="full" display="flex" flexDir="column">
<TabList alignItems="center">
<PanelTabs />
<Spacer />
Expand Down Expand Up @@ -63,22 +81,23 @@ const PanelTabs = memo(() => {
const activeEntityCount = useAppSelector(selectEntityCountActive);
const tabTimeout = useRef<number | null>(null);
const dndCtx = useDndContext();
const dispatch = useAppDispatch();

const onOnMouseOverLayersTab = useCallback(() => {
tabTimeout.current = window.setTimeout(() => {
if (dndCtx.active) {
selectCanvasRightPanelLayersTab();
dispatch(activeTabCanvasRightPanelChanged('layers'));
}
}, 300);
}, [dndCtx.active]);
}, [dndCtx.active, dispatch]);

const onOnMouseOverGalleryTab = useCallback(() => {
tabTimeout.current = window.setTimeout(() => {
if (dndCtx.active) {
selectCanvasRightPanelGalleryTab();
dispatch(activeTabCanvasRightPanelChanged('gallery'));
}
}, 300);
}, [dndCtx.active]);
}, [dndCtx.active, dispatch]);

const onMouseOut = useCallback(() => {
if (tabTimeout.current) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,12 @@ import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { buildUseBoolean } from 'common/hooks/useBoolean';
import { newCanvasSessionRequested, newGallerySessionRequested } from 'features/controlLayers/store/actions';
import {
selectCanvasRightPanelGalleryTab,
selectCanvasRightPanelLayersTab,
} from 'features/controlLayers/store/ephemeral';
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import {
selectSystemShouldConfirmOnNewSession,
shouldConfirmOnNewSessionToggled,
} from 'features/system/store/systemSlice';
import { activeTabCanvasRightPanelChanged } from 'features/ui/store/uiSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';

Expand All @@ -27,7 +24,7 @@ export const useNewGallerySession = () => {
const newGallerySessionImmediate = useCallback(() => {
dispatch(newGallerySessionRequested());
imageViewer.open();
selectCanvasRightPanelGalleryTab();
dispatch(activeTabCanvasRightPanelChanged('gallery'));
}, [dispatch, imageViewer]);

const newGallerySessionWithDialog = useCallback(() => {
Expand All @@ -50,7 +47,7 @@ export const useNewCanvasSession = () => {
const newCanvasSessionImmediate = useCallback(() => {
dispatch(newCanvasSessionRequested());
imageViewer.close();
selectCanvasRightPanelLayersTab();
dispatch(activeTabCanvasRightPanelChanged('layers'));
}, [dispatch, imageViewer]);

const newCanvasSessionWithDialog = useCallback(() => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,19 @@
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { useCanvasIsBusy } from 'features/controlLayers/hooks/useCanvasIsBusy';
import { entityDeleted } from 'features/controlLayers/store/canvasSlice';
import { $canvasRightPanelTab } from 'features/controlLayers/store/ephemeral';
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { selectActiveTab, selectActiveTabCanvasRightPanel } from 'features/ui/store/uiSelectors';
import { useCallback, useMemo } from 'react';

export function useCanvasDeleteLayerHotkey() {
useAssertSingleton(useCanvasDeleteLayerHotkey.name);
const dispatch = useAppDispatch();
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
const isBusy = useCanvasIsBusy();
const canvasRightPanelTab = useStore($canvasRightPanelTab);
const canvasRightPanelTab = useAppSelector(selectActiveTabCanvasRightPanel);
const appTab = useAppSelector(selectActiveTab);

const imageViewer = useImageViewer();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,9 @@
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
import { atom, computed } from 'nanostores';
import { atom } from 'nanostores';

// Ephemeral state for canvas - not persisted across sessions.

/**
* The global canvas manager instance.
*/
export const $canvasManager = atom<CanvasManager | null>(null);

/**
* The index of the active tab in the canvas right panel.
*/
export const $canvasRightPanelTabIndex = atom(0);
/**
* The name of the active tab in the canvas right panel.
*/
export const $canvasRightPanelTab = computed($canvasRightPanelTabIndex, (index) =>
index === 0 ? 'layers' : 'gallery'
);
export const selectCanvasRightPanelLayersTab = () => $canvasRightPanelTabIndex.set(0);
export const selectCanvasRightPanelGalleryTab = () => $canvasRightPanelTabIndex.set(1);
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { useStore } from '@nanostores/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { useIsRegionFocused } from 'common/hooks/focus';
import { useAssertSingleton } from 'common/hooks/useAssertSingleton';
import { $canvasRightPanelTab } from 'features/controlLayers/store/ephemeral';
import { imagesToDeleteSelected } from 'features/deleteImageModal/store/slice';
import { useGalleryNavigation } from 'features/gallery/hooks/useGalleryNavigation';
import { useGalleryPagination } from 'features/gallery/hooks/useGalleryPagination';
import { selectListImagesQueryArgs } from 'features/gallery/store/gallerySelectors';
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
import { selectActiveTab } from 'features/ui/store/uiSelectors';
import { selectActiveTab, selectActiveTabCanvasRightPanel } from 'features/ui/store/uiSelectors';
import { useMemo } from 'react';
import { useListImagesQuery } from 'services/api/endpoints/images';

Expand All @@ -22,7 +20,7 @@ export const useGalleryHotkeys = () => {
const selection = useAppSelector((s) => s.gallery.selection);
const queryArgs = useAppSelector(selectListImagesQueryArgs);
const queryResult = useListImagesQuery(queryArgs);
const canvasRightPanelTab = useStore($canvasRightPanelTab);
const canvasRightPanelTab = useAppSelector(selectActiveTabCanvasRightPanel);
const appTab = useAppSelector(selectActiveTab);
const isWorkflowsFocused = useIsRegionFocused('workflows');
const isGalleryFocused = useIsRegionFocused('gallery');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,8 @@ import {
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { selectSendToCanvas, settingsSendToCanvasChanged } from 'features/controlLayers/store/canvasSettingsSlice';
import { selectIsStaging } from 'features/controlLayers/store/canvasStagingAreaSlice';
import { selectCanvasRightPanelLayersTab } from 'features/controlLayers/store/ephemeral';
import { useImageViewer } from 'features/gallery/components/ImageViewer/useImageViewer';
import { setActiveTab } from 'features/ui/store/uiSlice';
import { activeTabCanvasRightPanelChanged, setActiveTab } from 'features/ui/store/uiSlice';
import type { ChangeEvent, PropsWithChildren } from 'react';
import { memo, useCallback, useMemo } from 'react';
import { Trans, useTranslation } from 'react-i18next';
Expand Down Expand Up @@ -186,7 +185,7 @@ const ActivateCanvasButton = (props: PropsWithChildren) => {
const imageViewer = useImageViewer();
const onClick = useCallback(() => {
dispatch(setActiveTab('canvas'));
selectCanvasRightPanelLayersTab();
dispatch(activeTabCanvasRightPanelChanged('layers'));
imageViewer.close();
}, [dispatch, imageViewer]);
return (
Expand Down
1 change: 1 addition & 0 deletions invokeai/frontend/web/src/features/ui/store/uiSelectors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ import { selectUiSlice } from 'features/ui/store/uiSlice';
export const selectActiveTab = createSelector(selectUiSlice, (ui) => ui.activeTab);
export const selectShouldShowImageDetails = createSelector(selectUiSlice, (ui) => ui.shouldShowImageDetails);
export const selectShouldShowProgressInViewer = createSelector(selectUiSlice, (ui) => ui.shouldShowProgressInViewer);
export const selectActiveTabCanvasRightPanel = createSelector(selectUiSlice, (ui) => ui.activeTabCanvasRightPanel);
7 changes: 6 additions & 1 deletion invokeai/frontend/web/src/features/ui/store/uiSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@ import { newSessionRequested } from 'features/controlLayers/store/actions';
import { workflowLoadRequested } from 'features/nodes/store/actions';
import { atom } from 'nanostores';

import type { TabName, UIState } from './uiTypes';
import type { CanvasRightPanelTabName, TabName, UIState } from './uiTypes';

const initialUIState: UIState = {
_version: 3,
activeTab: 'canvas',
activeTabCanvasRightPanel: 'gallery',
shouldShowImageDetails: false,
shouldShowProgressInViewer: true,
accordions: {},
Expand All @@ -24,6 +25,9 @@ export const uiSlice = createSlice({
setActiveTab: (state, action: PayloadAction<TabName>) => {
state.activeTab = action.payload;
},
activeTabCanvasRightPanelChanged: (state, action: PayloadAction<CanvasRightPanelTabName>) => {
state.activeTabCanvasRightPanel = action.payload;
},
setShouldShowImageDetails: (state, action: PayloadAction<boolean>) => {
state.shouldShowImageDetails = action.payload;
},
Expand Down Expand Up @@ -54,6 +58,7 @@ export const uiSlice = createSlice({

export const {
setActiveTab,
activeTabCanvasRightPanelChanged,
setShouldShowImageDetails,
setShouldShowProgressInViewer,
accordionStateChanged,
Expand Down
5 changes: 5 additions & 0 deletions invokeai/frontend/web/src/features/ui/store/uiTypes.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export type TabName = 'canvas' | 'upscaling' | 'workflows' | 'models' | 'queue';
export type CanvasRightPanelTabName = 'layers' | 'gallery';

export interface UIState {
/**
Expand All @@ -9,6 +10,10 @@ export interface UIState {
* The currently active tab.
*/
activeTab: TabName;
/**
* The currently active right panel canvas tab
*/
activeTabCanvasRightPanel: CanvasRightPanelTabName;
/**
* Whether or not to show image details, e.g. metadata, workflow, etc.
*/
Expand Down

0 comments on commit 44cc25a

Please sign in to comment.