Skip to content

Commit 6329c5f

Browse files
author
Attila Cseh
committed
locate in gallery image context menu
1 parent f3554b4 commit 6329c5f

File tree

7 files changed

+136
-23
lines changed

7 files changed

+136
-23
lines changed

invokeai/frontend/web/public/locales/en.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"deletedImagesCannotBeRestored": "Deleted images cannot be restored.",
3939
"hideBoards": "Hide Boards",
4040
"loading": "Loading...",
41+
"locateInGalery": "Locate in Gallery",
4142
"menuItemAutoAdd": "Auto-add to this Board",
4243
"move": "Move",
4344
"movingImagesToBoard_one": "Moving {{count}} image to board:",

invokeai/frontend/web/src/features/gallery/components/Gallery.tsx

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,7 @@ import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useG
77
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
88
import { galleryViewChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
99
import { useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
10-
import {
11-
GALLERY_PANEL_DEFAULT_HEIGHT_PX,
12-
GALLERY_PANEL_ID,
13-
GALLERY_PANEL_MIN_EXPANDED_HEIGHT_PX,
14-
GALLERY_PANEL_MIN_HEIGHT_PX,
15-
} from 'features/ui/layouts/shared';
16-
import { useCollapsibleGridviewPanel } from 'features/ui/layouts/use-collapsible-gridview-panel';
10+
import { useGalleryPanel } from 'features/ui/layouts/use-gallery-panel';
1711
import type { CSSProperties } from 'react';
1812
import { memo, useCallback } from 'react';
1913
import { useTranslation } from 'react-i18next';
@@ -34,16 +28,8 @@ export const GalleryPanel = memo(() => {
3428
const { t } = useTranslation();
3529
const dispatch = useAppDispatch();
3630
const { tab } = useAutoLayoutContext();
37-
const collapsibleApi = useCollapsibleGridviewPanel(
38-
tab,
39-
GALLERY_PANEL_ID,
40-
'vertical',
41-
GALLERY_PANEL_DEFAULT_HEIGHT_PX,
42-
GALLERY_PANEL_MIN_HEIGHT_PX,
43-
GALLERY_PANEL_MIN_EXPANDED_HEIGHT_PX
44-
);
45-
const isCollapsed = useStore(collapsibleApi.$isCollapsed);
46-
31+
const galleryPanel = useGalleryPanel(tab);
32+
const isCollapsed = useStore(galleryPanel.$isCollapsed);
4733
const galleryView = useAppSelector(selectGalleryView);
4834
const initialSearchTerm = useAppSelector(selectSearchTerm);
4935
const searchDisclosure = useDisclosure(!!initialSearchTerm);
@@ -58,11 +44,11 @@ export const GalleryPanel = memo(() => {
5844

5945
const handleClickSearch = useCallback(() => {
6046
onResetSearchTerm();
61-
if (!searchDisclosure.isOpen && collapsibleApi.$isCollapsed.get()) {
62-
collapsibleApi.expand();
47+
if (!searchDisclosure.isOpen && galleryPanel.$isCollapsed.get()) {
48+
galleryPanel.expand();
6349
}
6450
searchDisclosure.toggle();
65-
}, [collapsibleApi, onResetSearchTerm, searchDisclosure]);
51+
}, [galleryPanel, onResetSearchTerm, searchDisclosure]);
6652

6753
const selectedBoardId = useAppSelector(selectSelectedBoardId);
6854
const boardName = useBoardName(selectedBoardId);
@@ -73,7 +59,7 @@ export const GalleryPanel = memo(() => {
7359
<Button
7460
size="sm"
7561
variant="ghost"
76-
onClick={collapsibleApi.toggle}
62+
onClick={galleryPanel.toggle}
7763
leftIcon={isCollapsed ? <PiCaretDownBold /> : <PiCaretUpBold />}
7864
noOfLines={1}
7965
>
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { MenuItem } from '@invoke-ai/ui-library';
2+
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
3+
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
4+
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
5+
import { navigationApi } from 'features/ui/layouts/navigation-api';
6+
import { useGalleryPanel } from 'features/ui/layouts/use-gallery-panel';
7+
import { selectActiveTab } from 'features/ui/store/uiSelectors';
8+
import { memo, useCallback, useMemo } from 'react';
9+
import { useTranslation } from 'react-i18next';
10+
import { PiCrosshair } from 'react-icons/pi';
11+
12+
export const ImageMenuItemLocateInGalery = memo(() => {
13+
const { t } = useTranslation();
14+
const dispatch = useAppDispatch();
15+
const imageDTO = useImageDTOContext();
16+
const activeTab = useAppSelector(selectActiveTab);
17+
const galleryPanel = useGalleryPanel(activeTab);
18+
19+
const isGalleryImage = useMemo(() => {
20+
return !!imageDTO.board_id;
21+
}, [imageDTO]);
22+
23+
const onClick = useCallback(() => {
24+
navigationApi.expandRightPanel();
25+
galleryPanel.expand();
26+
dispatch(boardIdSelected({ boardId: imageDTO.board_id ?? 'none', selectedImageName: imageDTO.image_name }));
27+
}, [dispatch, galleryPanel, imageDTO]);
28+
29+
return (
30+
<MenuItem icon={<PiCrosshair />} onClickCapture={onClick} isDisabled={!isGalleryImage}>
31+
{t('boards.locateInGalery')}
32+
</MenuItem>
33+
);
34+
});
35+
36+
ImageMenuItemLocateInGalery.displayName = 'ImageMenuItemLocateInGalery';

invokeai/frontend/web/src/features/gallery/components/ImageContextMenu/SingleSelectionMenuItems.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ImageMenuItemCopy } from 'features/gallery/components/ImageContextMenu/
66
import { ImageMenuItemDelete } from 'features/gallery/components/ImageContextMenu/ImageMenuItemDelete';
77
import { ImageMenuItemDownload } from 'features/gallery/components/ImageContextMenu/ImageMenuItemDownload';
88
import { ImageMenuItemLoadWorkflow } from 'features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow';
9+
import { ImageMenuItemLocateInGalery } from 'features/gallery/components/ImageContextMenu/ImageMenuItemLocateInGalery';
910
import { ImageMenuItemMetadataRecallActionsCanvasGenerateTabs } from 'features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActionsCanvasGenerateTabs';
1011
import { ImageMenuItemNewCanvasFromImageSubMenu } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu';
1112
import { ImageMenuItemNewLayerFromImageSubMenu } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu';
@@ -55,6 +56,7 @@ const SingleSelectionMenuItems = ({ imageDTO }: SingleSelectionMenuItemsProps) =
5556
<MenuDivider />
5657
<ImageMenuItemChangeBoard />
5758
<ImageMenuItemStarUnstar />
59+
<ImageMenuItemLocateInGalery />
5860
</ImageDTOContextProvider>
5961
);
6062
};

invokeai/frontend/web/src/features/ui/layouts/navigation-api.ts

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,35 @@ export class NavigationApi {
448448
return this.panels.get(key);
449449
};
450450

451+
/**
452+
* Expand the left panel in the currently active tab.
453+
*
454+
* This method will not wait for the panel to be registered.
455+
*
456+
* @returns True if the panel was expanded, false if it was not found or an error occurred
457+
*/
458+
expandLeftPanel = (): boolean => {
459+
const activeTab = this._app?.activeTab.get() ?? null;
460+
if (!activeTab) {
461+
log.warn('No active tab found to expand left panel');
462+
return false;
463+
}
464+
const leftPanel = this.getPanel(activeTab, LEFT_PANEL_ID);
465+
if (!leftPanel) {
466+
log.warn(`Left panel not found in active tab "${activeTab}"`);
467+
return false;
468+
}
469+
470+
if (!(leftPanel instanceof GridviewPanel)) {
471+
log.error(`Right panels must be instances of GridviewPanel`);
472+
return false;
473+
}
474+
475+
this._expandPanel(leftPanel, LEFT_PANEL_MIN_SIZE_PX);
476+
477+
return true;
478+
};
479+
451480
/**
452481
* Toggle the left panel in the currently active tab.
453482
*
@@ -481,6 +510,35 @@ export class NavigationApi {
481510
return true;
482511
};
483512

513+
/**
514+
* Expand the right panel in the currently active tab.
515+
*
516+
* This method will not wait for the panel to be registered.
517+
*
518+
* @returns True if the panel was expanded, false if it was not found or an error occurred
519+
*/
520+
expandRightPanel = (): boolean => {
521+
const activeTab = this._app?.activeTab.get() ?? null;
522+
if (!activeTab) {
523+
log.warn('No active tab found to expand right panel');
524+
return false;
525+
}
526+
const rightPanel = this.getPanel(activeTab, RIGHT_PANEL_ID);
527+
if (!rightPanel) {
528+
log.warn(`Right panel not found in active tab "${activeTab}"`);
529+
return false;
530+
}
531+
532+
if (!(rightPanel instanceof GridviewPanel)) {
533+
log.error(`Right panels must be instances of GridviewPanel`);
534+
return false;
535+
}
536+
537+
this._expandPanel(rightPanel, RIGHT_PANEL_MIN_SIZE_PX);
538+
539+
return true;
540+
};
541+
484542
/**
485543
* Toggle the right panel in the currently active tab.
486544
*

invokeai/frontend/web/src/features/ui/layouts/use-collapsible-gridview-panel.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,15 @@ export const useCollapsibleGridviewPanel = (
2828
const lastExpandedSizeRef = useRef<number>(0);
2929
const collapse = useCallback(() => {
3030
const panel = navigationApi.getPanel(tab, panelId);
31-
3231
if (!panel || !(panel instanceof GridviewPanel)) {
3332
return;
3433
}
3534

35+
const isCollapsed = getIsCollapsed(panel, orientation, collapsedSize);
36+
if (isCollapsed) {
37+
return;
38+
}
39+
3640
lastExpandedSizeRef.current = orientation === 'vertical' ? panel.height : panel.width;
3741

3842
if (orientation === 'vertical') {
@@ -48,6 +52,11 @@ export const useCollapsibleGridviewPanel = (
4852
return;
4953
}
5054

55+
const isCollapsed = getIsCollapsed(panel, orientation, collapsedSize);
56+
if (!isCollapsed) {
57+
return;
58+
}
59+
5160
let newSize = lastExpandedSizeRef.current || defaultSize;
5261
if (minExpandedSize && newSize < minExpandedSize) {
5362
newSize = minExpandedSize;
@@ -58,14 +67,15 @@ export const useCollapsibleGridviewPanel = (
5867
} else {
5968
panel.api.setSize({ width: newSize });
6069
}
61-
}, [defaultSize, minExpandedSize, orientation, panelId, tab]);
70+
}, [defaultSize, minExpandedSize, orientation, collapsedSize, panelId, tab]);
6271

6372
const toggle = useCallback(() => {
6473
const panel = navigationApi.getPanel(tab, panelId);
6574
if (!panel || !(panel instanceof GridviewPanel)) {
6675
return;
6776
}
6877
const isCollapsed = getIsCollapsed(panel, orientation, collapsedSize);
78+
6979
if (isCollapsed) {
7080
expand();
7181
} else {
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import type { TabName } from 'features/ui/store/uiTypes';
2+
3+
import {
4+
GALLERY_PANEL_DEFAULT_HEIGHT_PX,
5+
GALLERY_PANEL_ID,
6+
GALLERY_PANEL_MIN_EXPANDED_HEIGHT_PX,
7+
GALLERY_PANEL_MIN_HEIGHT_PX,
8+
} from './shared';
9+
import { useCollapsibleGridviewPanel } from './use-collapsible-gridview-panel';
10+
11+
export const useGalleryPanel = (tab: TabName) => {
12+
return useCollapsibleGridviewPanel(
13+
tab,
14+
GALLERY_PANEL_ID,
15+
'vertical',
16+
GALLERY_PANEL_DEFAULT_HEIGHT_PX,
17+
GALLERY_PANEL_MIN_HEIGHT_PX,
18+
GALLERY_PANEL_MIN_EXPANDED_HEIGHT_PX
19+
);
20+
};

0 commit comments

Comments
 (0)