mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): fix canvas saving
- fix "bounding box region only" not being respected when saving - add toasts for each action - improve workflow `take()` predicates to use the requestId
This commit is contained in:
parent
cc22427f25
commit
229de2dbb8
@ -28,6 +28,13 @@ export const addCanvasCopiedToClipboardListener = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
copyBlobToClipboard(blob);
|
copyBlobToClipboard(blob);
|
||||||
|
|
||||||
|
dispatch(
|
||||||
|
addToast({
|
||||||
|
title: 'Canvas Copied to Clipboard',
|
||||||
|
status: 'success',
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -27,7 +27,8 @@ export const addCanvasDownloadedAsImageListener = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
downloadBlob(blob, 'mergedCanvas.png');
|
downloadBlob(blob, 'canvas.png');
|
||||||
|
dispatch(addToast({ title: 'Canvas Downloaded', status: 'success' }));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -1,22 +1,20 @@
|
|||||||
import { canvasMerged } from 'features/canvas/store/actions';
|
import { canvasMerged } from 'features/canvas/store/actions';
|
||||||
import { startAppListening } from '..';
|
import { startAppListening } from '..';
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { imageUploaded } from 'services/thunks/image';
|
import { imageUploaded } from 'services/thunks/image';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
|
import { setMergedCanvas } from 'features/canvas/store/canvasSlice';
|
||||||
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
|
||||||
|
import { getFullBaseLayerBlob } from 'features/canvas/util/getFullBaseLayerBlob';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'canvasCopiedToClipboardListener' });
|
const moduleLog = log.child({ namespace: 'canvasCopiedToClipboardListener' });
|
||||||
|
export const MERGED_CANVAS_FILENAME = 'mergedCanvas.png';
|
||||||
|
|
||||||
export const addCanvasMergedListener = () => {
|
export const addCanvasMergedListener = () => {
|
||||||
startAppListening({
|
startAppListening({
|
||||||
actionCreator: canvasMerged,
|
actionCreator: canvasMerged,
|
||||||
effect: async (action, { dispatch, getState, take }) => {
|
effect: async (action, { dispatch, getState, take }) => {
|
||||||
const state = getState();
|
const blob = await getFullBaseLayerBlob();
|
||||||
|
|
||||||
const blob = await getBaseLayerBlob(state, true);
|
|
||||||
|
|
||||||
if (!blob) {
|
if (!blob) {
|
||||||
moduleLog.error('Problem getting base layer blob');
|
moduleLog.error('Problem getting base layer blob');
|
||||||
@ -48,12 +46,12 @@ export const addCanvasMergedListener = () => {
|
|||||||
relativeTo: canvasBaseLayer.getParent(),
|
relativeTo: canvasBaseLayer.getParent(),
|
||||||
});
|
});
|
||||||
|
|
||||||
const filename = `mergedCanvas_${uuidv4()}.png`;
|
const imageUploadedRequest = dispatch(
|
||||||
|
|
||||||
dispatch(
|
|
||||||
imageUploaded({
|
imageUploaded({
|
||||||
formData: {
|
formData: {
|
||||||
file: new File([blob], filename, { type: 'image/png' }),
|
file: new File([blob], MERGED_CANVAS_FILENAME, {
|
||||||
|
type: 'image/png',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
imageCategory: 'general',
|
imageCategory: 'general',
|
||||||
isIntermediate: true,
|
isIntermediate: true,
|
||||||
@ -61,9 +59,11 @@ export const addCanvasMergedListener = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [{ payload }] = await take(
|
const [{ payload }] = await take(
|
||||||
(action): action is ReturnType<typeof imageUploaded.fulfilled> =>
|
(
|
||||||
imageUploaded.fulfilled.match(action) &&
|
uploadedImageAction
|
||||||
action.meta.arg.formData.file.name === filename
|
): uploadedImageAction is ReturnType<typeof imageUploaded.fulfilled> =>
|
||||||
|
imageUploaded.fulfilled.match(uploadedImageAction) &&
|
||||||
|
uploadedImageAction.meta.requestId === imageUploadedRequest.requestId
|
||||||
);
|
);
|
||||||
|
|
||||||
const mergedCanvasImage = payload;
|
const mergedCanvasImage = payload;
|
||||||
|
@ -4,9 +4,10 @@ import { log } from 'app/logging/useLogger';
|
|||||||
import { imageUploaded } from 'services/thunks/image';
|
import { imageUploaded } from 'services/thunks/image';
|
||||||
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
import { getBaseLayerBlob } from 'features/canvas/util/getBaseLayerBlob';
|
||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { v4 as uuidv4 } from 'uuid';
|
|
||||||
import { imageUpserted } from 'features/gallery/store/imagesSlice';
|
import { imageUpserted } from 'features/gallery/store/imagesSlice';
|
||||||
|
|
||||||
|
export const SAVED_CANVAS_FILENAME = 'savedCanvas.png';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'canvasSavedToGalleryListener' });
|
const moduleLog = log.child({ namespace: 'canvasSavedToGalleryListener' });
|
||||||
|
|
||||||
export const addCanvasSavedToGalleryListener = () => {
|
export const addCanvasSavedToGalleryListener = () => {
|
||||||
@ -15,7 +16,7 @@ export const addCanvasSavedToGalleryListener = () => {
|
|||||||
effect: async (action, { dispatch, getState, take }) => {
|
effect: async (action, { dispatch, getState, take }) => {
|
||||||
const state = getState();
|
const state = getState();
|
||||||
|
|
||||||
const blob = await getBaseLayerBlob(state, true);
|
const blob = await getBaseLayerBlob(state);
|
||||||
|
|
||||||
if (!blob) {
|
if (!blob) {
|
||||||
moduleLog.error('Problem getting base layer blob');
|
moduleLog.error('Problem getting base layer blob');
|
||||||
@ -29,12 +30,12 @@ export const addCanvasSavedToGalleryListener = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const filename = `mergedCanvas_${uuidv4()}.png`;
|
const imageUploadedRequest = dispatch(
|
||||||
|
|
||||||
dispatch(
|
|
||||||
imageUploaded({
|
imageUploaded({
|
||||||
formData: {
|
formData: {
|
||||||
file: new File([blob], filename, { type: 'image/png' }),
|
file: new File([blob], SAVED_CANVAS_FILENAME, {
|
||||||
|
type: 'image/png',
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
imageCategory: 'general',
|
imageCategory: 'general',
|
||||||
isIntermediate: false,
|
isIntermediate: false,
|
||||||
@ -42,9 +43,11 @@ export const addCanvasSavedToGalleryListener = () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const [{ payload: uploadedImageDTO }] = await take(
|
const [{ payload: uploadedImageDTO }] = await take(
|
||||||
(action): action is ReturnType<typeof imageUploaded.fulfilled> =>
|
(
|
||||||
imageUploaded.fulfilled.match(action) &&
|
uploadedImageAction
|
||||||
action.meta.arg.formData.file.name === filename
|
): uploadedImageAction is ReturnType<typeof imageUploaded.fulfilled> =>
|
||||||
|
imageUploaded.fulfilled.match(uploadedImageAction) &&
|
||||||
|
uploadedImageAction.meta.requestId === imageUploadedRequest.requestId
|
||||||
);
|
);
|
||||||
|
|
||||||
dispatch(imageUpserted(uploadedImageDTO));
|
dispatch(imageUpserted(uploadedImageDTO));
|
||||||
|
@ -3,6 +3,8 @@ import { imageUploaded } from 'services/thunks/image';
|
|||||||
import { addToast } from 'features/system/store/systemSlice';
|
import { addToast } from 'features/system/store/systemSlice';
|
||||||
import { log } from 'app/logging/useLogger';
|
import { log } from 'app/logging/useLogger';
|
||||||
import { imageUpserted } from 'features/gallery/store/imagesSlice';
|
import { imageUpserted } from 'features/gallery/store/imagesSlice';
|
||||||
|
import { SAVED_CANVAS_FILENAME } from './canvasSavedToGallery';
|
||||||
|
import { MERGED_CANVAS_FILENAME } from './canvasMerged';
|
||||||
|
|
||||||
const moduleLog = log.child({ namespace: 'image' });
|
const moduleLog = log.child({ namespace: 'image' });
|
||||||
|
|
||||||
@ -19,9 +21,22 @@ export const addImageUploadedFulfilledListener = () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const state = getState();
|
const originalFileName = action.meta.arg.formData.file.name;
|
||||||
|
|
||||||
dispatch(imageUpserted(image));
|
dispatch(imageUpserted(image));
|
||||||
|
|
||||||
|
if (originalFileName === SAVED_CANVAS_FILENAME) {
|
||||||
|
dispatch(
|
||||||
|
addToast({ title: 'Canvas Saved to Gallery', status: 'success' })
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (originalFileName === MERGED_CANVAS_FILENAME) {
|
||||||
|
dispatch(addToast({ title: 'Canvas Merged', status: 'success' }));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
dispatch(addToast({ title: 'Image Uploaded', status: 'success' }));
|
dispatch(addToast({ title: 'Image Uploaded', status: 'success' }));
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -2,10 +2,10 @@ import { getCanvasBaseLayer } from './konvaInstanceProvider';
|
|||||||
import { RootState } from 'app/store/store';
|
import { RootState } from 'app/store/store';
|
||||||
import { konvaNodeToBlob } from './konvaNodeToBlob';
|
import { konvaNodeToBlob } from './konvaNodeToBlob';
|
||||||
|
|
||||||
export const getBaseLayerBlob = async (
|
/**
|
||||||
state: RootState,
|
* Get the canvas base layer blob, with or without bounding box according to `shouldCropToBoundingBoxOnSave`
|
||||||
withoutBoundingBox?: boolean
|
*/
|
||||||
) => {
|
export const getBaseLayerBlob = async (state: RootState) => {
|
||||||
const canvasBaseLayer = getCanvasBaseLayer();
|
const canvasBaseLayer = getCanvasBaseLayer();
|
||||||
|
|
||||||
if (!canvasBaseLayer) {
|
if (!canvasBaseLayer) {
|
||||||
@ -24,15 +24,14 @@ export const getBaseLayerBlob = async (
|
|||||||
|
|
||||||
const absPos = clonedBaseLayer.getAbsolutePosition();
|
const absPos = clonedBaseLayer.getAbsolutePosition();
|
||||||
|
|
||||||
const boundingBox =
|
const boundingBox = shouldCropToBoundingBoxOnSave
|
||||||
shouldCropToBoundingBoxOnSave && !withoutBoundingBox
|
? {
|
||||||
? {
|
x: boundingBoxCoordinates.x + absPos.x,
|
||||||
x: boundingBoxCoordinates.x + absPos.x,
|
y: boundingBoxCoordinates.y + absPos.y,
|
||||||
y: boundingBoxCoordinates.y + absPos.y,
|
width: boundingBoxDimensions.width,
|
||||||
width: boundingBoxDimensions.width,
|
height: boundingBoxDimensions.height,
|
||||||
height: boundingBoxDimensions.height,
|
}
|
||||||
}
|
: clonedBaseLayer.getClientRect();
|
||||||
: clonedBaseLayer.getClientRect();
|
|
||||||
|
|
||||||
return konvaNodeToBlob(clonedBaseLayer, boundingBox);
|
return konvaNodeToBlob(clonedBaseLayer, boundingBox);
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,19 @@
|
|||||||
|
import { getCanvasBaseLayer } from './konvaInstanceProvider';
|
||||||
|
import { konvaNodeToBlob } from './konvaNodeToBlob';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the canvas base layer blob, without bounding box
|
||||||
|
*/
|
||||||
|
export const getFullBaseLayerBlob = async () => {
|
||||||
|
const canvasBaseLayer = getCanvasBaseLayer();
|
||||||
|
|
||||||
|
if (!canvasBaseLayer) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const clonedBaseLayer = canvasBaseLayer.clone();
|
||||||
|
|
||||||
|
clonedBaseLayer.scale({ x: 1, y: 1 });
|
||||||
|
|
||||||
|
return konvaNodeToBlob(clonedBaseLayer, clonedBaseLayer.getClientRect());
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user