feat(ui): add bookmark for quick switch

This commit is contained in:
psychedelicious
2024-09-01 22:34:05 +10:00
parent 0c81a435f4
commit ea062ab01a
8 changed files with 132 additions and 36 deletions

View File

@@ -1654,7 +1654,8 @@
"storeNotInitialized": "Store is not initialized"
},
"controlLayers": {
"saveCanvasToGallery": "Save Canvas To Gallery",
"bookmarkedForQuickSwitch": "Bookmarked for Quick Switch",
"notBookmarkedForQuickSwitch": "Not Bookmarked for Quick Switch",
"saveBboxToGallery": "Save Bbox To Gallery",
"savedToGalleryOk": "Saved to Gallery",
"savedToGalleryError": "Error saving to gallery",

View File

@@ -1,6 +1,7 @@
import { Flex } from '@invoke-ai/ui-library';
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
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 { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { memo } from 'react';
@@ -10,6 +11,7 @@ export const CanvasEntityHeaderCommonActions = memo(() => {
return (
<Flex alignSelf="stretch">
<CanvasEntityIsBookmarkedForQuickSwitchToggle />
{entityIdentifier.type !== 'ip_adapter' && <CanvasEntityIsLockedToggle />}
<CanvasEntityEnabledToggle />
<CanvasEntityDeleteButton />

View File

@@ -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';

View File

@@ -6,6 +6,7 @@ import { entitySelected } from 'features/controlLayers/store/canvasSlice';
import {
selectCanvasSlice,
selectEntity,
selectQuickSwitchEntityIdentifier,
selectSelectedEntityIdentifier,
} from 'features/controlLayers/store/selectors';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
@@ -20,51 +21,63 @@ export const useCanvasEntityQuickSwitchHotkey = () => {
const dispatch = useAppDispatch();
const selectedEntityBuffer = useStore($selectedEntityBuffer);
const selectedEntity = useAppSelector(selectSelectedEntityIdentifier);
const selectDoesBufferExist = useMemo(
const quickSwitchEntityIdentifier = useAppSelector(selectQuickSwitchEntityIdentifier);
const selectedEntityIdentifier = useAppSelector(selectSelectedEntityIdentifier);
const selectBufferEntityIdentifier = useMemo(
() =>
createSelector(selectCanvasSlice, (canvas) => {
if (!selectedEntityBuffer) {
return true;
}
const bufferEntity = selectEntity(canvas, selectedEntityBuffer);
if (bufferEntity) {
return true;
}
return false;
}),
createSelector(selectCanvasSlice, (canvas) =>
selectedEntityBuffer ? (selectEntity(canvas, selectedEntityBuffer) ?? null) : null
),
[selectedEntityBuffer]
);
const doesBufferExist = useAppSelector(selectDoesBufferExist);
const bufferEntityIdentifier = useAppSelector(selectBufferEntityIdentifier);
const quickSwitch = useCallback(() => {
// If there is no selected entity or buffer, we should not do anything
if (selectedEntity === null && selectedEntityBuffer === null) {
return;
if (quickSwitchEntityIdentifier !== null) {
// If there is a quick switch entity, we should switch between it and the buffer
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
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]);
}, [bufferEntityIdentifier, dispatch, quickSwitchEntityIdentifier, selectedEntityIdentifier]);
useEffect(() => {
if (!doesBufferExist) {
if (!bufferEntityIdentifier) {
$selectedEntityBuffer.set(null);
}
}, [doesBufferExist]);
}, [bufferEntityIdentifier]);
useHotkeys('q', quickSwitch, { enabled: true, preventDefault: true }, [quickSwitch]);
};

View File

@@ -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;
};

View File

@@ -84,6 +84,7 @@ const getRGMaskFill = (state: CanvasState): RgbColor => {
const initialState: CanvasState = {
_version: 3,
selectedEntityIdentifier: null,
quickSwitchEntityIdentifier: null,
rasterLayers: {
isHidden: false,
entities: [],
@@ -791,6 +792,22 @@ export const canvasSlice = createSlice({
}
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 }>>) => {
const { entityIdentifier, name } = action.payload;
const entity = selectEntity(state, entityIdentifier);
@@ -1105,6 +1122,7 @@ export const {
canvasClearHistory,
// All entities
entitySelected,
entityIsBookmarkedForQuickSwitchChanged,
entityNameChanged,
entityReset,
entityIsEnabledToggled,

View File

@@ -181,6 +181,11 @@ export const selectSelectedEntityIdentifier = createSelector(
(canvas) => canvas.selectedEntityIdentifier
);
export const selectQuickSwitchEntityIdentifier = createSelector(
selectCanvasSlice,
(canvas) => canvas.quickSwitchEntityIdentifier
);
export const selectIsSelectedEntityDrawable = createSelector(
selectSelectedEntityIdentifier,
(selectedEntityIdentifier) => {

View File

@@ -688,6 +688,7 @@ export type StagingAreaImage = {
export type CanvasState = {
_version: 3;
selectedEntityIdentifier: CanvasEntityIdentifier | null;
quickSwitchEntityIdentifier: CanvasEntityIdentifier | null;
inpaintMasks: {
isHidden: boolean;
entities: CanvasInpaintMaskState[];