mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): add bookmark for quick switch
This commit is contained in:
@@ -1654,7 +1654,8 @@
|
|||||||
"storeNotInitialized": "Store is not initialized"
|
"storeNotInitialized": "Store is not initialized"
|
||||||
},
|
},
|
||||||
"controlLayers": {
|
"controlLayers": {
|
||||||
"saveCanvasToGallery": "Save Canvas To Gallery",
|
"bookmarkedForQuickSwitch": "Bookmarked for Quick Switch",
|
||||||
|
"notBookmarkedForQuickSwitch": "Not Bookmarked for Quick Switch",
|
||||||
"saveBboxToGallery": "Save Bbox To Gallery",
|
"saveBboxToGallery": "Save Bbox To Gallery",
|
||||||
"savedToGalleryOk": "Saved to Gallery",
|
"savedToGalleryOk": "Saved to Gallery",
|
||||||
"savedToGalleryError": "Error saving to gallery",
|
"savedToGalleryError": "Error saving to gallery",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Flex } from '@invoke-ai/ui-library';
|
import { Flex } from '@invoke-ai/ui-library';
|
||||||
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
|
||||||
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
|
||||||
|
import { CanvasEntityIsBookmarkedForQuickSwitchToggle } from 'features/controlLayers/components/common/CanvasEntityIsBookmarkedForQuickSwitchToggle';
|
||||||
import { CanvasEntityIsLockedToggle } from 'features/controlLayers/components/common/CanvasEntityIsLockedToggle';
|
import { CanvasEntityIsLockedToggle } from 'features/controlLayers/components/common/CanvasEntityIsLockedToggle';
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
@@ -10,6 +11,7 @@ export const CanvasEntityHeaderCommonActions = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Flex alignSelf="stretch">
|
<Flex alignSelf="stretch">
|
||||||
|
<CanvasEntityIsBookmarkedForQuickSwitchToggle />
|
||||||
{entityIdentifier.type !== 'ip_adapter' && <CanvasEntityIsLockedToggle />}
|
{entityIdentifier.type !== 'ip_adapter' && <CanvasEntityIsLockedToggle />}
|
||||||
<CanvasEntityEnabledToggle />
|
<CanvasEntityEnabledToggle />
|
||||||
<CanvasEntityDeleteButton />
|
<CanvasEntityDeleteButton />
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
import { IconButton } from '@invoke-ai/ui-library';
|
||||||
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import { useEntityIsBookmarkedForQuickSwitch } from 'features/controlLayers/hooks/useEntityIsBookmarkedForQuickSwitch';
|
||||||
|
import { entityIsBookmarkedForQuickSwitchChanged } from 'features/controlLayers/store/canvasSlice';
|
||||||
|
import { memo, useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiBookmarkSimpleBold, PiBookmarkSimpleFill } from 'react-icons/pi';
|
||||||
|
|
||||||
|
export const CanvasEntityIsBookmarkedForQuickSwitchToggle = memo(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
|
const isBookmarked = useEntityIsBookmarkedForQuickSwitch(entityIdentifier);
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const onClick = useCallback(() => {
|
||||||
|
if (isBookmarked) {
|
||||||
|
dispatch(entityIsBookmarkedForQuickSwitchChanged({ entityIdentifier: null }));
|
||||||
|
} else {
|
||||||
|
dispatch(entityIsBookmarkedForQuickSwitchChanged({ entityIdentifier }));
|
||||||
|
}
|
||||||
|
}, [dispatch, entityIdentifier, isBookmarked]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IconButton
|
||||||
|
size="sm"
|
||||||
|
aria-label={t(
|
||||||
|
isBookmarked ? 'controlLayers.bookmarkedForQuickSwitch' : 'controlLayers.notBookmarkedForQuickSwitch'
|
||||||
|
)}
|
||||||
|
tooltip={t(isBookmarked ? 'controlLayers.bookmarkedForQuickSwitch' : 'controlLayers.notBookmarkedForQuickSwitch')}
|
||||||
|
variant="link"
|
||||||
|
alignSelf="stretch"
|
||||||
|
icon={isBookmarked ? <PiBookmarkSimpleFill /> : <PiBookmarkSimpleBold />}
|
||||||
|
onClick={onClick}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
CanvasEntityIsBookmarkedForQuickSwitchToggle.displayName = 'CanvasEntityIsBookmarkedForQuickSwitchToggle';
|
||||||
@@ -6,6 +6,7 @@ import { entitySelected } from 'features/controlLayers/store/canvasSlice';
|
|||||||
import {
|
import {
|
||||||
selectCanvasSlice,
|
selectCanvasSlice,
|
||||||
selectEntity,
|
selectEntity,
|
||||||
|
selectQuickSwitchEntityIdentifier,
|
||||||
selectSelectedEntityIdentifier,
|
selectSelectedEntityIdentifier,
|
||||||
} from 'features/controlLayers/store/selectors';
|
} from 'features/controlLayers/store/selectors';
|
||||||
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
@@ -20,51 +21,63 @@ export const useCanvasEntityQuickSwitchHotkey = () => {
|
|||||||
|
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const selectedEntityBuffer = useStore($selectedEntityBuffer);
|
const selectedEntityBuffer = useStore($selectedEntityBuffer);
|
||||||
const selectedEntity = useAppSelector(selectSelectedEntityIdentifier);
|
const quickSwitchEntityIdentifier = useAppSelector(selectQuickSwitchEntityIdentifier);
|
||||||
const selectDoesBufferExist = useMemo(
|
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
|
||||||
|
const selectBufferEntityIdentifier = useMemo(
|
||||||
() =>
|
() =>
|
||||||
createSelector(selectCanvasSlice, (canvas) => {
|
createSelector(selectCanvasSlice, (canvas) =>
|
||||||
if (!selectedEntityBuffer) {
|
selectedEntityBuffer ? (selectEntity(canvas, selectedEntityBuffer) ?? null) : null
|
||||||
return true;
|
),
|
||||||
}
|
|
||||||
const bufferEntity = selectEntity(canvas, selectedEntityBuffer);
|
|
||||||
if (bufferEntity) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}),
|
|
||||||
[selectedEntityBuffer]
|
[selectedEntityBuffer]
|
||||||
);
|
);
|
||||||
const doesBufferExist = useAppSelector(selectDoesBufferExist);
|
const bufferEntityIdentifier = useAppSelector(selectBufferEntityIdentifier);
|
||||||
|
|
||||||
const quickSwitch = useCallback(() => {
|
const quickSwitch = useCallback(() => {
|
||||||
// If there is no selected entity or buffer, we should not do anything
|
if (quickSwitchEntityIdentifier !== null) {
|
||||||
if (selectedEntity === null && selectedEntityBuffer === null) {
|
// If there is a quick switch entity, we should switch between it and the buffer
|
||||||
return;
|
if (quickSwitchEntityIdentifier.id !== selectedEntityIdentifier?.id) {
|
||||||
|
// The quick switch entity is not selected - select it
|
||||||
|
dispatch(entitySelected({ entityIdentifier: quickSwitchEntityIdentifier }));
|
||||||
|
$selectedEntityBuffer.set(selectedEntityIdentifier);
|
||||||
|
} else if (bufferEntityIdentifier !== null) {
|
||||||
|
// The quick switch entity is already selected - select the buffer
|
||||||
|
dispatch(entitySelected({ entityIdentifier: bufferEntityIdentifier }));
|
||||||
|
$selectedEntityBuffer.set(quickSwitchEntityIdentifier);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No quick switch entity, so we should switch between buffer and selected entity
|
||||||
|
// If there is no selected entity or buffer, we should not do anything
|
||||||
|
if (selectedEntityIdentifier === null && bufferEntityIdentifier === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If there is no selected entity but we do have a buffer, we should select the buffer
|
||||||
|
if (selectedEntityIdentifier === null && bufferEntityIdentifier !== null) {
|
||||||
|
dispatch(entitySelected({ entityIdentifier: bufferEntityIdentifier }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If there is a selected entity but no buffer, we should buffer the selected entity
|
||||||
|
if (selectedEntityIdentifier !== null && bufferEntityIdentifier === null) {
|
||||||
|
$selectedEntityBuffer.set(selectedEntityIdentifier);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// If there is a selected entity and a buffer, and they are different, we should swap the selected entity and the buffer
|
||||||
|
if (
|
||||||
|
selectedEntityIdentifier !== null &&
|
||||||
|
bufferEntityIdentifier !== null &&
|
||||||
|
selectedEntityIdentifier.id !== bufferEntityIdentifier.id
|
||||||
|
) {
|
||||||
|
$selectedEntityBuffer.set(selectedEntityIdentifier);
|
||||||
|
dispatch(entitySelected({ entityIdentifier: bufferEntityIdentifier }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// If there is no selected entity but we do have a buffer, we should select the buffer
|
}, [bufferEntityIdentifier, dispatch, quickSwitchEntityIdentifier, selectedEntityIdentifier]);
|
||||||
if (selectedEntity === null && selectedEntityBuffer !== null) {
|
|
||||||
dispatch(entitySelected({ entityIdentifier: selectedEntityBuffer }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If there is a selected entity but no buffer, we should buffer the selected entity
|
|
||||||
if (selectedEntity !== null && selectedEntityBuffer === null) {
|
|
||||||
$selectedEntityBuffer.set(selectedEntity);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// If there is a selected entity and a buffer, and they are different, we should swap the selected entity and the buffer
|
|
||||||
if (selectedEntity !== null && selectedEntityBuffer !== null && selectedEntity.id !== selectedEntityBuffer.id) {
|
|
||||||
$selectedEntityBuffer.set(selectedEntity);
|
|
||||||
dispatch(entitySelected({ entityIdentifier: selectedEntityBuffer }));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}, [dispatch, selectedEntity, selectedEntityBuffer]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!doesBufferExist) {
|
if (!bufferEntityIdentifier) {
|
||||||
$selectedEntityBuffer.set(null);
|
$selectedEntityBuffer.set(null);
|
||||||
}
|
}
|
||||||
}, [doesBufferExist]);
|
}, [bufferEntityIdentifier]);
|
||||||
|
|
||||||
useHotkeys('q', quickSwitch, { enabled: true, preventDefault: true }, [quickSwitch]);
|
useHotkeys('q', quickSwitch, { enabled: true, preventDefault: true }, [quickSwitch]);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { selectCanvasSlice } from 'features/controlLayers/store/selectors';
|
||||||
|
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
|
||||||
|
export const useEntityIsBookmarkedForQuickSwitch = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||||
|
const selectIsBookmarkedForQuickSwitch = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(selectCanvasSlice, (canvas) => {
|
||||||
|
return canvas.quickSwitchEntityIdentifier?.id === entityIdentifier.id;
|
||||||
|
}),
|
||||||
|
[entityIdentifier]
|
||||||
|
);
|
||||||
|
const isBookmarkedForQuickSwitch = useAppSelector(selectIsBookmarkedForQuickSwitch);
|
||||||
|
|
||||||
|
return isBookmarkedForQuickSwitch;
|
||||||
|
};
|
||||||
@@ -84,6 +84,7 @@ const getRGMaskFill = (state: CanvasState): RgbColor => {
|
|||||||
const initialState: CanvasState = {
|
const initialState: CanvasState = {
|
||||||
_version: 3,
|
_version: 3,
|
||||||
selectedEntityIdentifier: null,
|
selectedEntityIdentifier: null,
|
||||||
|
quickSwitchEntityIdentifier: null,
|
||||||
rasterLayers: {
|
rasterLayers: {
|
||||||
isHidden: false,
|
isHidden: false,
|
||||||
entities: [],
|
entities: [],
|
||||||
@@ -791,6 +792,22 @@ export const canvasSlice = createSlice({
|
|||||||
}
|
}
|
||||||
state.selectedEntityIdentifier = entityIdentifier;
|
state.selectedEntityIdentifier = entityIdentifier;
|
||||||
},
|
},
|
||||||
|
entityIsBookmarkedForQuickSwitchChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ entityIdentifier: CanvasEntityIdentifier | null }>
|
||||||
|
) => {
|
||||||
|
const { entityIdentifier } = action.payload;
|
||||||
|
if (!entityIdentifier) {
|
||||||
|
state.quickSwitchEntityIdentifier = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
// Cannot select a non-existent entity
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
state.quickSwitchEntityIdentifier = entityIdentifier;
|
||||||
|
},
|
||||||
entityNameChanged: (state, action: PayloadAction<EntityIdentifierPayload<{ name: string | null }>>) => {
|
entityNameChanged: (state, action: PayloadAction<EntityIdentifierPayload<{ name: string | null }>>) => {
|
||||||
const { entityIdentifier, name } = action.payload;
|
const { entityIdentifier, name } = action.payload;
|
||||||
const entity = selectEntity(state, entityIdentifier);
|
const entity = selectEntity(state, entityIdentifier);
|
||||||
@@ -1105,6 +1122,7 @@ export const {
|
|||||||
canvasClearHistory,
|
canvasClearHistory,
|
||||||
// All entities
|
// All entities
|
||||||
entitySelected,
|
entitySelected,
|
||||||
|
entityIsBookmarkedForQuickSwitchChanged,
|
||||||
entityNameChanged,
|
entityNameChanged,
|
||||||
entityReset,
|
entityReset,
|
||||||
entityIsEnabledToggled,
|
entityIsEnabledToggled,
|
||||||
|
|||||||
@@ -181,6 +181,11 @@ export const selectSelectedEntityIdentifier = createSelector(
|
|||||||
(canvas) => canvas.selectedEntityIdentifier
|
(canvas) => canvas.selectedEntityIdentifier
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const selectQuickSwitchEntityIdentifier = createSelector(
|
||||||
|
selectCanvasSlice,
|
||||||
|
(canvas) => canvas.quickSwitchEntityIdentifier
|
||||||
|
);
|
||||||
|
|
||||||
export const selectIsSelectedEntityDrawable = createSelector(
|
export const selectIsSelectedEntityDrawable = createSelector(
|
||||||
selectSelectedEntityIdentifier,
|
selectSelectedEntityIdentifier,
|
||||||
(selectedEntityIdentifier) => {
|
(selectedEntityIdentifier) => {
|
||||||
|
|||||||
@@ -688,6 +688,7 @@ export type StagingAreaImage = {
|
|||||||
export type CanvasState = {
|
export type CanvasState = {
|
||||||
_version: 3;
|
_version: 3;
|
||||||
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
selectedEntityIdentifier: CanvasEntityIdentifier | null;
|
||||||
|
quickSwitchEntityIdentifier: CanvasEntityIdentifier | null;
|
||||||
inpaintMasks: {
|
inpaintMasks: {
|
||||||
isHidden: boolean;
|
isHidden: boolean;
|
||||||
entities: CanvasInpaintMaskState[];
|
entities: CanvasInpaintMaskState[];
|
||||||
|
|||||||
Reference in New Issue
Block a user