refactor: share image upload request construction
This commit is contained in:
+4
-21
@@ -3,6 +3,7 @@ import { createModuleLogger } from "./log_system/log_funcs.js";
|
||||
import { showErrorNotification } from "./utils/NotificationUtils.js";
|
||||
import { webSocketManager } from "./utils/WebSocketManager.js";
|
||||
import { scaleImageToFit, loadImage, blobToDataUrl, tensorToImageData, createImageFromImageData } from "./utils/ImageUtils.js";
|
||||
import { postImageBlob } from "./utils/ImageUploadUtils.js";
|
||||
const log = createModuleLogger('CanvasIO');
|
||||
export class CanvasIO {
|
||||
constructor(canvas) {
|
||||
@@ -112,14 +113,8 @@ export class CanvasIO {
|
||||
if (!blobWithoutMask)
|
||||
return;
|
||||
log.debug(`Created blob for image without mask, size: ${blobWithoutMask.size} bytes`);
|
||||
const formDataWithoutMask = new FormData();
|
||||
formDataWithoutMask.append("image", blobWithoutMask, fileNameWithoutMask);
|
||||
formDataWithoutMask.append("overwrite", "true");
|
||||
try {
|
||||
const response = await fetch("/upload/image", {
|
||||
method: "POST",
|
||||
body: formDataWithoutMask,
|
||||
});
|
||||
const response = await postImageBlob({ blob: blobWithoutMask, filename: fileNameWithoutMask }, fetch);
|
||||
log.debug(`Image without mask upload response: ${response.status}`);
|
||||
}
|
||||
catch (error) {
|
||||
@@ -131,14 +126,8 @@ export class CanvasIO {
|
||||
if (!blob)
|
||||
return;
|
||||
log.debug(`Created blob for main image, size: ${blob.size} bytes`);
|
||||
const formData = new FormData();
|
||||
formData.append("image", blob, fileName);
|
||||
formData.append("overwrite", "true");
|
||||
try {
|
||||
const resp = await fetch("/upload/image", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
const resp = await postImageBlob({ blob, filename: fileName }, fetch);
|
||||
log.debug(`Main image upload response: ${resp.status}`);
|
||||
if (resp.status === 200) {
|
||||
const maskFileName = fileName.replace('.png', '_mask.png');
|
||||
@@ -147,14 +136,8 @@ export class CanvasIO {
|
||||
if (!maskBlob)
|
||||
return;
|
||||
log.debug(`Created blob for mask, size: ${maskBlob.size} bytes`);
|
||||
const maskFormData = new FormData();
|
||||
maskFormData.append("image", maskBlob, maskFileName);
|
||||
maskFormData.append("overwrite", "true");
|
||||
try {
|
||||
const maskResp = await fetch("/upload/image", {
|
||||
method: "POST",
|
||||
body: maskFormData,
|
||||
});
|
||||
const maskResp = await postImageBlob({ blob: maskBlob, filename: maskFileName }, fetch);
|
||||
log.debug(`Mask upload response: ${maskResp.status}`);
|
||||
if (maskResp.status === 200) {
|
||||
const data = await resp.json();
|
||||
|
||||
@@ -4,6 +4,18 @@ import { createModuleLogger } from "../log_system/log_funcs.js";
|
||||
import { withErrorHandling, createValidationError, createNetworkError } from "../ErrorHandler.js";
|
||||
import { getFlattenedCanvasBlob, supportsFlattenedCanvasBlob } from './CanvasBlobUtils.js';
|
||||
const log = createModuleLogger('ImageUploadUtils');
|
||||
export async function postImageBlob(request, transport = (input, init) => api.fetchApi(input, init)) {
|
||||
const formData = new FormData();
|
||||
formData.append("image", request.blob, request.filename);
|
||||
formData.append("overwrite", (request.overwrite ?? true).toString());
|
||||
if (request.type !== undefined) {
|
||||
formData.append("type", request.type);
|
||||
}
|
||||
return transport("/upload/image", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
}
|
||||
async function getCanvasBlobForUpload(canvas, uploadOptions, config) {
|
||||
if (!canvas) {
|
||||
throw createValidationError("Canvas is required", { canvas });
|
||||
@@ -53,15 +65,12 @@ export const uploadImageBlob = withErrorHandling(async function (blob, options =
|
||||
type,
|
||||
overwrite
|
||||
});
|
||||
// Create FormData
|
||||
const formData = new FormData();
|
||||
formData.append("image", blob, filename);
|
||||
formData.append("overwrite", overwrite.toString());
|
||||
formData.append("type", type);
|
||||
// Upload to server
|
||||
const response = await api.fetchApi("/upload/image", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
const response = await postImageBlob({
|
||||
blob,
|
||||
filename,
|
||||
overwrite,
|
||||
type
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw createNetworkError(`Failed to upload image: ${response.statusText}`, {
|
||||
|
||||
+13
-21
@@ -3,6 +3,7 @@ import { createModuleLogger } from "./log_system/log_funcs.js";
|
||||
import { showErrorNotification } from "./utils/NotificationUtils.js";
|
||||
import { webSocketManager } from "./utils/WebSocketManager.js";
|
||||
import { scaleImageToFit, loadImage, blobToDataUrl, tensorToImageData, createImageFromImageData } from "./utils/ImageUtils.js";
|
||||
import { postImageBlob } from "./utils/ImageUploadUtils.js";
|
||||
import type { Canvas } from './Canvas';
|
||||
import type { Layer, Shape, AddMode } from './types';
|
||||
|
||||
@@ -153,15 +154,12 @@ export class CanvasIO {
|
||||
tempCanvas.toBlob(async (blobWithoutMask) => {
|
||||
if (!blobWithoutMask) return;
|
||||
log.debug(`Created blob for image without mask, size: ${blobWithoutMask.size} bytes`);
|
||||
const formDataWithoutMask = new FormData();
|
||||
formDataWithoutMask.append("image", blobWithoutMask, fileNameWithoutMask);
|
||||
formDataWithoutMask.append("overwrite", "true");
|
||||
|
||||
try {
|
||||
const response = await fetch("/upload/image", {
|
||||
method: "POST",
|
||||
body: formDataWithoutMask,
|
||||
});
|
||||
const response = await postImageBlob(
|
||||
{ blob: blobWithoutMask, filename: fileNameWithoutMask },
|
||||
fetch
|
||||
);
|
||||
log.debug(`Image without mask upload response: ${response.status}`);
|
||||
} catch (error) {
|
||||
log.error(`Error uploading image without mask:`, error);
|
||||
@@ -171,15 +169,12 @@ export class CanvasIO {
|
||||
tempCanvas.toBlob(async (blob) => {
|
||||
if (!blob) return;
|
||||
log.debug(`Created blob for main image, size: ${blob.size} bytes`);
|
||||
const formData = new FormData();
|
||||
formData.append("image", blob, fileName);
|
||||
formData.append("overwrite", "true");
|
||||
|
||||
try {
|
||||
const resp = await fetch("/upload/image", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
const resp = await postImageBlob(
|
||||
{ blob, filename: fileName },
|
||||
fetch
|
||||
);
|
||||
log.debug(`Main image upload response: ${resp.status}`);
|
||||
|
||||
if (resp.status === 200) {
|
||||
@@ -189,15 +184,12 @@ export class CanvasIO {
|
||||
maskCanvas.toBlob(async (maskBlob) => {
|
||||
if (!maskBlob) return;
|
||||
log.debug(`Created blob for mask, size: ${maskBlob.size} bytes`);
|
||||
const maskFormData = new FormData();
|
||||
maskFormData.append("image", maskBlob, maskFileName);
|
||||
maskFormData.append("overwrite", "true");
|
||||
|
||||
try {
|
||||
const maskResp = await fetch("/upload/image", {
|
||||
method: "POST",
|
||||
body: maskFormData,
|
||||
});
|
||||
const maskResp = await postImageBlob(
|
||||
{ blob: maskBlob, filename: maskFileName },
|
||||
fetch
|
||||
);
|
||||
log.debug(`Mask upload response: ${maskResp.status}`);
|
||||
|
||||
if (maskResp.status === 200) {
|
||||
|
||||
@@ -39,6 +39,32 @@ interface CanvasBlobUploadConfig {
|
||||
emptyBlobMessage: string;
|
||||
}
|
||||
|
||||
interface ImageBlobUploadRequest {
|
||||
blob: Blob;
|
||||
filename: string;
|
||||
overwrite?: boolean;
|
||||
type?: string;
|
||||
}
|
||||
|
||||
type ImageUploadTransport = (input: string, init: RequestInit) => Promise<Response>;
|
||||
|
||||
export async function postImageBlob(
|
||||
request: ImageBlobUploadRequest,
|
||||
transport: ImageUploadTransport = (input, init) => api.fetchApi(input, init)
|
||||
): Promise<Response> {
|
||||
const formData = new FormData();
|
||||
formData.append("image", request.blob, request.filename);
|
||||
formData.append("overwrite", (request.overwrite ?? true).toString());
|
||||
if (request.type !== undefined) {
|
||||
formData.append("type", request.type);
|
||||
}
|
||||
|
||||
return transport("/upload/image", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
});
|
||||
}
|
||||
|
||||
async function getCanvasBlobForUpload(
|
||||
canvas: any,
|
||||
uploadOptions: UploadImageOptions,
|
||||
@@ -104,16 +130,12 @@ export const uploadImageBlob = withErrorHandling(async function(blob: Blob, opti
|
||||
overwrite
|
||||
});
|
||||
|
||||
// Create FormData
|
||||
const formData = new FormData();
|
||||
formData.append("image", blob, filename);
|
||||
formData.append("overwrite", overwrite.toString());
|
||||
formData.append("type", type);
|
||||
|
||||
// Upload to server
|
||||
const response = await api.fetchApi("/upload/image", {
|
||||
method: "POST",
|
||||
body: formData,
|
||||
const response = await postImageBlob({
|
||||
blob,
|
||||
filename,
|
||||
overwrite,
|
||||
type
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
|
||||
@@ -24,7 +24,7 @@ test('output paths preserve their current render bounds and mask responsibilitie
|
||||
test('output rendering keeps persistence and transport in CanvasIO', () => {
|
||||
assert.match(canvasIOSource, /async _performSave\(fileName: string, outputMode: string\)/);
|
||||
assert.match(canvasIOSource, /tempCanvas\.toDataURL\('image\/png'\)/);
|
||||
assert.match(canvasIOSource, /fetch\("\/upload\/image"/);
|
||||
assert.equal((canvasIOSource.match(/postImageBlob\(/g) ?? []).length, 3);
|
||||
assert.match(canvasLayersSource, /async getFlattenedCanvasAsBlob\(\)/);
|
||||
assert.match(canvasLayersSource, /async getFlattenedMaskAsBlob\(\)/);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import test from 'node:test';
|
||||
|
||||
const [canvasIOSource, imageUploadSource] = await Promise.all([
|
||||
readFile(new URL('../src/CanvasIO.ts', import.meta.url), 'utf8'),
|
||||
readFile(new URL('../src/utils/ImageUploadUtils.ts', import.meta.url), 'utf8'),
|
||||
]);
|
||||
|
||||
test('current upload callers preserve their endpoint and form-data contracts', () => {
|
||||
assert.equal((canvasIOSource.match(/new FormData\(\)/g) ?? []).length, 0);
|
||||
assert.equal((canvasIOSource.match(/fetch\("\/upload\/image"/g) ?? []).length, 0);
|
||||
assert.equal((canvasIOSource.match(/postImageBlob\(/g) ?? []).length, 3);
|
||||
assert.equal((imageUploadSource.match(/new FormData\(\)/g) ?? []).length, 1);
|
||||
assert.equal((imageUploadSource.match(/api\.fetchApi\(/g) ?? []).length, 1);
|
||||
|
||||
assert.match(canvasIOSource, /postImageBlob\(\s*\{ blob: blobWithoutMask, filename: fileNameWithoutMask \},\s*fetch\s*\)/s);
|
||||
assert.match(canvasIOSource, /postImageBlob\(\s*\{ blob, filename: fileName \},\s*fetch\s*\)/s);
|
||||
assert.match(canvasIOSource, /postImageBlob\(\s*\{ blob: maskBlob, filename: maskFileName \},\s*fetch\s*\)/s);
|
||||
|
||||
assert.match(imageUploadSource, /export async function postImageBlob\(/);
|
||||
assert.match(imageUploadSource, /formData\.append\("image", request\.blob, request\.filename\)/);
|
||||
assert.match(imageUploadSource, /formData\.append\("overwrite", \(request\.overwrite \?\? true\)\.toString\(\)\)/);
|
||||
assert.match(imageUploadSource, /formData\.append\("type", request\.type\)/);
|
||||
assert.match(imageUploadSource, /return transport\("\/upload\/image"/);
|
||||
assert.match(imageUploadSource, /postImageBlob\(\{[\s\S]*?overwrite,[\s\S]*?type/s);
|
||||
});
|
||||
Reference in New Issue
Block a user