u
This commit is contained in:
20
src/utils/notify.js
Normal file
20
src/utils/notify.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import { enqueueSnackbar } from 'notistack';
|
||||
import { backendErrorFrom } from './backendError';
|
||||
|
||||
/** Show error toast and log to console for debugging. */
|
||||
export function notifyError(err) {
|
||||
const normalized = backendErrorFrom(err);
|
||||
console.error('[error]', normalized.message, {
|
||||
status: normalized.status,
|
||||
path: normalized.path,
|
||||
method: normalized.method,
|
||||
err,
|
||||
});
|
||||
enqueueSnackbar(normalized.message, { variant: 'error' });
|
||||
return normalized;
|
||||
}
|
||||
|
||||
export function notifyErrorMessage(message) {
|
||||
console.error('[error]', message);
|
||||
enqueueSnackbar(message, { variant: 'error' });
|
||||
}
|
||||
@@ -1,6 +1,9 @@
|
||||
const MAX_EDGE = 1000;
|
||||
const AVIF_MIME = 'image/avif';
|
||||
const AVIF_QUALITY = 0.8;
|
||||
const AVIF_QUALITY = 80;
|
||||
|
||||
let encodeAvif;
|
||||
let resizeImage;
|
||||
|
||||
function scaledDimensions(width, height) {
|
||||
const scale = Math.min(1, MAX_EDGE / width, MAX_EDGE / height);
|
||||
@@ -10,8 +13,43 @@ function scaledDimensions(width, height) {
|
||||
};
|
||||
}
|
||||
|
||||
async function getAvifEncoder() {
|
||||
if (!encodeAvif) {
|
||||
const { default: encode } = await import('@jsquash/avif/encode');
|
||||
encodeAvif = encode;
|
||||
}
|
||||
return encodeAvif;
|
||||
}
|
||||
|
||||
async function getResizer() {
|
||||
if (!resizeImage) {
|
||||
const { default: resize } = await import('@jsquash/resize');
|
||||
resizeImage = resize;
|
||||
}
|
||||
return resizeImage;
|
||||
}
|
||||
|
||||
async function decodeToImageData(input) {
|
||||
const bitmap = await createImageBitmap(input);
|
||||
try {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = bitmap.width;
|
||||
canvas.height = bitmap.height;
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
throw new Error('Canvas nicht verfügbar.');
|
||||
}
|
||||
ctx.drawImage(bitmap, 0, 0);
|
||||
return ctx.getImageData(0, 0, bitmap.width, bitmap.height);
|
||||
} finally {
|
||||
bitmap.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resize to fit within 1000×1000 (aspect ratio kept, no upscale) and encode as AVIF.
|
||||
* Resize to fit within 1000×1000 (aspect ratio kept, no upscale).
|
||||
* Encodes as AVIF via @jsquash/avif (WASM).
|
||||
* @param {Blob|File} input
|
||||
* @returns {Promise<File>}
|
||||
*/
|
||||
@@ -23,39 +61,21 @@ export async function prepareArticlePhoto(input) {
|
||||
throw new Error('Kein Bild.');
|
||||
}
|
||||
|
||||
const bitmap = await createImageBitmap(input);
|
||||
try {
|
||||
const { width, height } = scaledDimensions(bitmap.width, bitmap.height);
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = width;
|
||||
canvas.height = height;
|
||||
const originalImageData = await decodeToImageData(input);
|
||||
const { width, height } = scaledDimensions(
|
||||
originalImageData.width,
|
||||
originalImageData.height,
|
||||
);
|
||||
|
||||
const ctx = canvas.getContext('2d');
|
||||
if (!ctx) {
|
||||
throw new Error('Canvas nicht verfügbar.');
|
||||
}
|
||||
ctx.drawImage(bitmap, 0, 0, width, height);
|
||||
const resize = await getResizer();
|
||||
const imageData = await resize(originalImageData, { width, height });
|
||||
|
||||
const avifBlob = await new Promise((resolve, reject) => {
|
||||
canvas.toBlob(
|
||||
(blob) => {
|
||||
if (!blob || blob.type !== AVIF_MIME) {
|
||||
reject(new Error('AVIF wird von diesem Browser nicht unterstützt.'));
|
||||
return;
|
||||
}
|
||||
resolve(blob);
|
||||
},
|
||||
AVIF_MIME,
|
||||
AVIF_QUALITY,
|
||||
);
|
||||
});
|
||||
const encode = await getAvifEncoder();
|
||||
const avifBuffer = await encode(imageData, { quality: AVIF_QUALITY });
|
||||
|
||||
const baseName = input instanceof File && input.name
|
||||
? input.name.replace(/\.[^.]+$/, '')
|
||||
: 'photo';
|
||||
const baseName = input instanceof File && input.name
|
||||
? input.name.replace(/\.[^.]+$/, '')
|
||||
: 'photo';
|
||||
|
||||
return new File([avifBlob], `${baseName}.avif`, { type: AVIF_MIME });
|
||||
} finally {
|
||||
bitmap.close();
|
||||
}
|
||||
return new File([avifBuffer], `${baseName}.avif`, { type: AVIF_MIME });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user