mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
locate in gallery image context menu
This commit is contained in:
committed by
psychedelicious
parent
9c1eb263a8
commit
19cd6eed08
@@ -38,6 +38,7 @@
|
|||||||
"deletedImagesCannotBeRestored": "Deleted images cannot be restored.",
|
"deletedImagesCannotBeRestored": "Deleted images cannot be restored.",
|
||||||
"hideBoards": "Hide Boards",
|
"hideBoards": "Hide Boards",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
|
"locateInGalery": "Locate in Gallery",
|
||||||
"menuItemAutoAdd": "Auto-add to this Board",
|
"menuItemAutoAdd": "Auto-add to this Board",
|
||||||
"move": "Move",
|
"move": "Move",
|
||||||
"movingImagesToBoard_one": "Moving {{count}} image to board:",
|
"movingImagesToBoard_one": "Moving {{count}} image to board:",
|
||||||
|
|||||||
@@ -7,13 +7,7 @@ import { useGallerySearchTerm } from 'features/gallery/components/ImageGrid/useG
|
|||||||
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
|
import { selectSelectedBoardId } from 'features/gallery/store/gallerySelectors';
|
||||||
import { galleryViewChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
import { galleryViewChanged, selectGallerySlice } from 'features/gallery/store/gallerySlice';
|
||||||
import { useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
|
import { useAutoLayoutContext } from 'features/ui/layouts/auto-layout-context';
|
||||||
import {
|
import { useGalleryPanel } from 'features/ui/layouts/use-gallery-panel';
|
||||||
GALLERY_PANEL_DEFAULT_HEIGHT_PX,
|
|
||||||
GALLERY_PANEL_ID,
|
|
||||||
GALLERY_PANEL_MIN_EXPANDED_HEIGHT_PX,
|
|
||||||
GALLERY_PANEL_MIN_HEIGHT_PX,
|
|
||||||
} from 'features/ui/layouts/shared';
|
|
||||||
import { useCollapsibleGridviewPanel } from 'features/ui/layouts/use-collapsible-gridview-panel';
|
|
||||||
import type { CSSProperties } from 'react';
|
import type { CSSProperties } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@@ -34,16 +28,8 @@ export const GalleryPanel = memo(() => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { tab } = useAutoLayoutContext();
|
const { tab } = useAutoLayoutContext();
|
||||||
const collapsibleApi = useCollapsibleGridviewPanel(
|
const galleryPanel = useGalleryPanel(tab);
|
||||||
tab,
|
const isCollapsed = useStore(galleryPanel.$isCollapsed);
|
||||||
GALLERY_PANEL_ID,
|
|
||||||
'vertical',
|
|
||||||
GALLERY_PANEL_DEFAULT_HEIGHT_PX,
|
|
||||||
GALLERY_PANEL_MIN_HEIGHT_PX,
|
|
||||||
GALLERY_PANEL_MIN_EXPANDED_HEIGHT_PX
|
|
||||||
);
|
|
||||||
const isCollapsed = useStore(collapsibleApi.$isCollapsed);
|
|
||||||
|
|
||||||
const galleryView = useAppSelector(selectGalleryView);
|
const galleryView = useAppSelector(selectGalleryView);
|
||||||
const initialSearchTerm = useAppSelector(selectSearchTerm);
|
const initialSearchTerm = useAppSelector(selectSearchTerm);
|
||||||
const searchDisclosure = useDisclosure(!!initialSearchTerm);
|
const searchDisclosure = useDisclosure(!!initialSearchTerm);
|
||||||
@@ -58,11 +44,11 @@ export const GalleryPanel = memo(() => {
|
|||||||
|
|
||||||
const handleClickSearch = useCallback(() => {
|
const handleClickSearch = useCallback(() => {
|
||||||
onResetSearchTerm();
|
onResetSearchTerm();
|
||||||
if (!searchDisclosure.isOpen && collapsibleApi.$isCollapsed.get()) {
|
if (!searchDisclosure.isOpen && galleryPanel.$isCollapsed.get()) {
|
||||||
collapsibleApi.expand();
|
galleryPanel.expand();
|
||||||
}
|
}
|
||||||
searchDisclosure.toggle();
|
searchDisclosure.toggle();
|
||||||
}, [collapsibleApi, onResetSearchTerm, searchDisclosure]);
|
}, [galleryPanel, onResetSearchTerm, searchDisclosure]);
|
||||||
|
|
||||||
const selectedBoardId = useAppSelector(selectSelectedBoardId);
|
const selectedBoardId = useAppSelector(selectSelectedBoardId);
|
||||||
const boardName = useBoardName(selectedBoardId);
|
const boardName = useBoardName(selectedBoardId);
|
||||||
@@ -73,7 +59,7 @@ export const GalleryPanel = memo(() => {
|
|||||||
<Button
|
<Button
|
||||||
size="sm"
|
size="sm"
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
onClick={collapsibleApi.toggle}
|
onClick={galleryPanel.toggle}
|
||||||
leftIcon={isCollapsed ? <PiCaretDownBold /> : <PiCaretUpBold />}
|
leftIcon={isCollapsed ? <PiCaretDownBold /> : <PiCaretUpBold />}
|
||||||
noOfLines={1}
|
noOfLines={1}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { MenuItem } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { useImageDTOContext } from 'features/gallery/contexts/ImageDTOContext';
|
||||||
|
import { boardIdSelected } from 'features/gallery/store/gallerySlice';
|
||||||
|
import { navigationApi } from 'features/ui/layouts/navigation-api';
|
||||||
|
import { useGalleryPanel } from 'features/ui/layouts/use-gallery-panel';
|
||||||
|
import { selectActiveTab } from 'features/ui/store/uiSelectors';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiCrosshair } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const ImageMenuItemLocateInGalery = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const imageDTO = useImageDTOContext();
|
||||||
|
const activeTab = useAppSelector(selectActiveTab);
|
||||||
|
const galleryPanel = useGalleryPanel(activeTab);
|
||||||
|
|
||||||
|
const isGalleryImage = useMemo(() => {
|
||||||
|
return !!imageDTO.board_id;
|
||||||
|
}, [imageDTO]);
|
||||||
|
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
navigationApi.expandRightPanel();
|
||||||
|
galleryPanel.expand();
|
||||||
|
dispatch(boardIdSelected({ boardId: imageDTO.board_id ?? 'none', selectedImageName: imageDTO.image_name }));
|
||||||
|
}, [dispatch, galleryPanel, imageDTO]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MenuItem icon={<PiCrosshair />} onClickCapture={onClick} isDisabled={!isGalleryImage}>
|
||||||
|
{t('boards.locateInGalery')}
|
||||||
|
</MenuItem>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
ImageMenuItemLocateInGalery.displayName = 'ImageMenuItemLocateInGalery';
|
||||||
@@ -6,6 +6,7 @@ import { ImageMenuItemCopy } from 'features/gallery/components/ImageContextMenu/
|
|||||||
import { ImageMenuItemDelete } from 'features/gallery/components/ImageContextMenu/ImageMenuItemDelete';
|
import { ImageMenuItemDelete } from 'features/gallery/components/ImageContextMenu/ImageMenuItemDelete';
|
||||||
import { ImageMenuItemDownload } from 'features/gallery/components/ImageContextMenu/ImageMenuItemDownload';
|
import { ImageMenuItemDownload } from 'features/gallery/components/ImageContextMenu/ImageMenuItemDownload';
|
||||||
import { ImageMenuItemLoadWorkflow } from 'features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow';
|
import { ImageMenuItemLoadWorkflow } from 'features/gallery/components/ImageContextMenu/ImageMenuItemLoadWorkflow';
|
||||||
|
import { ImageMenuItemLocateInGalery } from 'features/gallery/components/ImageContextMenu/ImageMenuItemLocateInGalery';
|
||||||
import { ImageMenuItemMetadataRecallActionsCanvasGenerateTabs } from 'features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActionsCanvasGenerateTabs';
|
import { ImageMenuItemMetadataRecallActionsCanvasGenerateTabs } from 'features/gallery/components/ImageContextMenu/ImageMenuItemMetadataRecallActionsCanvasGenerateTabs';
|
||||||
import { ImageMenuItemNewCanvasFromImageSubMenu } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu';
|
import { ImageMenuItemNewCanvasFromImageSubMenu } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewCanvasFromImageSubMenu';
|
||||||
import { ImageMenuItemNewLayerFromImageSubMenu } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu';
|
import { ImageMenuItemNewLayerFromImageSubMenu } from 'features/gallery/components/ImageContextMenu/ImageMenuItemNewLayerFromImageSubMenu';
|
||||||
@@ -55,6 +56,7 @@ const SingleSelectionMenuItems = ({ imageDTO }: SingleSelectionMenuItemsProps) =
|
|||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<ImageMenuItemChangeBoard />
|
<ImageMenuItemChangeBoard />
|
||||||
<ImageMenuItemStarUnstar />
|
<ImageMenuItemStarUnstar />
|
||||||
|
<ImageMenuItemLocateInGalery />
|
||||||
</ImageDTOContextProvider>
|
</ImageDTOContextProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -448,6 +448,35 @@ export class NavigationApi {
|
|||||||
return this.panels.get(key);
|
return this.panels.get(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand the left panel in the currently active tab.
|
||||||
|
*
|
||||||
|
* This method will not wait for the panel to be registered.
|
||||||
|
*
|
||||||
|
* @returns True if the panel was expanded, false if it was not found or an error occurred
|
||||||
|
*/
|
||||||
|
expandLeftPanel = (): boolean => {
|
||||||
|
const activeTab = this._app?.activeTab.get() ?? null;
|
||||||
|
if (!activeTab) {
|
||||||
|
log.warn('No active tab found to expand left panel');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const leftPanel = this.getPanel(activeTab, LEFT_PANEL_ID);
|
||||||
|
if (!leftPanel) {
|
||||||
|
log.warn(`Left panel not found in active tab "${activeTab}"`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(leftPanel instanceof GridviewPanel)) {
|
||||||
|
log.error(`Right panels must be instances of GridviewPanel`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._expandPanel(leftPanel, LEFT_PANEL_MIN_SIZE_PX);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the left panel in the currently active tab.
|
* Toggle the left panel in the currently active tab.
|
||||||
*
|
*
|
||||||
@@ -481,6 +510,35 @@ export class NavigationApi {
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expand the right panel in the currently active tab.
|
||||||
|
*
|
||||||
|
* This method will not wait for the panel to be registered.
|
||||||
|
*
|
||||||
|
* @returns True if the panel was expanded, false if it was not found or an error occurred
|
||||||
|
*/
|
||||||
|
expandRightPanel = (): boolean => {
|
||||||
|
const activeTab = this._app?.activeTab.get() ?? null;
|
||||||
|
if (!activeTab) {
|
||||||
|
log.warn('No active tab found to expand right panel');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const rightPanel = this.getPanel(activeTab, RIGHT_PANEL_ID);
|
||||||
|
if (!rightPanel) {
|
||||||
|
log.warn(`Right panel not found in active tab "${activeTab}"`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!(rightPanel instanceof GridviewPanel)) {
|
||||||
|
log.error(`Right panels must be instances of GridviewPanel`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this._expandPanel(rightPanel, RIGHT_PANEL_MIN_SIZE_PX);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Toggle the right panel in the currently active tab.
|
* Toggle the right panel in the currently active tab.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -28,11 +28,15 @@ export const useCollapsibleGridviewPanel = (
|
|||||||
const lastExpandedSizeRef = useRef<number>(0);
|
const lastExpandedSizeRef = useRef<number>(0);
|
||||||
const collapse = useCallback(() => {
|
const collapse = useCallback(() => {
|
||||||
const panel = navigationApi.getPanel(tab, panelId);
|
const panel = navigationApi.getPanel(tab, panelId);
|
||||||
|
|
||||||
if (!panel || !(panel instanceof GridviewPanel)) {
|
if (!panel || !(panel instanceof GridviewPanel)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isCollapsed = getIsCollapsed(panel, orientation, collapsedSize);
|
||||||
|
if (isCollapsed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
lastExpandedSizeRef.current = orientation === 'vertical' ? panel.height : panel.width;
|
lastExpandedSizeRef.current = orientation === 'vertical' ? panel.height : panel.width;
|
||||||
|
|
||||||
if (orientation === 'vertical') {
|
if (orientation === 'vertical') {
|
||||||
@@ -48,6 +52,11 @@ export const useCollapsibleGridviewPanel = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isCollapsed = getIsCollapsed(panel, orientation, collapsedSize);
|
||||||
|
if (!isCollapsed) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let newSize = lastExpandedSizeRef.current || defaultSize;
|
let newSize = lastExpandedSizeRef.current || defaultSize;
|
||||||
if (minExpandedSize && newSize < minExpandedSize) {
|
if (minExpandedSize && newSize < minExpandedSize) {
|
||||||
newSize = minExpandedSize;
|
newSize = minExpandedSize;
|
||||||
@@ -58,7 +67,7 @@ export const useCollapsibleGridviewPanel = (
|
|||||||
} else {
|
} else {
|
||||||
panel.api.setSize({ width: newSize });
|
panel.api.setSize({ width: newSize });
|
||||||
}
|
}
|
||||||
}, [defaultSize, minExpandedSize, orientation, panelId, tab]);
|
}, [defaultSize, minExpandedSize, orientation, collapsedSize, panelId, tab]);
|
||||||
|
|
||||||
const toggle = useCallback(() => {
|
const toggle = useCallback(() => {
|
||||||
const panel = navigationApi.getPanel(tab, panelId);
|
const panel = navigationApi.getPanel(tab, panelId);
|
||||||
@@ -66,6 +75,7 @@ export const useCollapsibleGridviewPanel = (
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const isCollapsed = getIsCollapsed(panel, orientation, collapsedSize);
|
const isCollapsed = getIsCollapsed(panel, orientation, collapsedSize);
|
||||||
|
|
||||||
if (isCollapsed) {
|
if (isCollapsed) {
|
||||||
expand();
|
expand();
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import type { TabName } from 'features/ui/store/uiTypes';
|
||||||
|
|
||||||
|
import {
|
||||||
|
GALLERY_PANEL_DEFAULT_HEIGHT_PX,
|
||||||
|
GALLERY_PANEL_ID,
|
||||||
|
GALLERY_PANEL_MIN_EXPANDED_HEIGHT_PX,
|
||||||
|
GALLERY_PANEL_MIN_HEIGHT_PX,
|
||||||
|
} from './shared';
|
||||||
|
import { useCollapsibleGridviewPanel } from './use-collapsible-gridview-panel';
|
||||||
|
|
||||||
|
export const useGalleryPanel = (tab: TabName) => {
|
||||||
|
return useCollapsibleGridviewPanel(
|
||||||
|
tab,
|
||||||
|
GALLERY_PANEL_ID,
|
||||||
|
'vertical',
|
||||||
|
GALLERY_PANEL_DEFAULT_HEIGHT_PX,
|
||||||
|
GALLERY_PANEL_MIN_HEIGHT_PX,
|
||||||
|
GALLERY_PANEL_MIN_EXPANDED_HEIGHT_PX
|
||||||
|
);
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user