fix(frontend): support non-image files
This commit is contained in:
parent
1c3604c7fb
commit
dda2ad6bcd
|
@ -24,7 +24,14 @@ export type UploaderFeatures = {
|
||||||
crop?: boolean;
|
crop?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
const COMPRESSION_SUPPORTED_TYPES = [
|
const THUMBNAIL_SUPPORTED_TYPES = [
|
||||||
|
'image/jpeg',
|
||||||
|
'image/png',
|
||||||
|
'image/webp',
|
||||||
|
'image/svg+xml',
|
||||||
|
];
|
||||||
|
|
||||||
|
const IMAGE_COMPRESSION_SUPPORTED_TYPES = [
|
||||||
'image/jpeg',
|
'image/jpeg',
|
||||||
'image/png',
|
'image/png',
|
||||||
'image/webp',
|
'image/webp',
|
||||||
|
@ -45,6 +52,13 @@ const IMAGE_EDITING_SUPPORTED_TYPES = [
|
||||||
|
|
||||||
const WATERMARK_SUPPORTED_TYPES = IMAGE_EDITING_SUPPORTED_TYPES;
|
const WATERMARK_SUPPORTED_TYPES = IMAGE_EDITING_SUPPORTED_TYPES;
|
||||||
|
|
||||||
|
const IMAGE_PREPROCESS_NEEDED_TYPES = [
|
||||||
|
...WATERMARK_SUPPORTED_TYPES,
|
||||||
|
...IMAGE_COMPRESSION_SUPPORTED_TYPES,
|
||||||
|
...CROPPING_SUPPORTED_TYPES,
|
||||||
|
...IMAGE_EDITING_SUPPORTED_TYPES,
|
||||||
|
];
|
||||||
|
|
||||||
const mimeTypeMap = {
|
const mimeTypeMap = {
|
||||||
'image/webp': 'webp',
|
'image/webp': 'webp',
|
||||||
'image/jpeg': 'jpg',
|
'image/jpeg': 'jpg',
|
||||||
|
@ -56,7 +70,7 @@ export type UploaderItem = {
|
||||||
name: string;
|
name: string;
|
||||||
uploadName?: string;
|
uploadName?: string;
|
||||||
progress: { max: number; value: number } | null;
|
progress: { max: number; value: number } | null;
|
||||||
thumbnail: string;
|
thumbnail: string | null;
|
||||||
preprocessing: boolean;
|
preprocessing: boolean;
|
||||||
uploading: boolean;
|
uploading: boolean;
|
||||||
uploaded: Misskey.entities.DriveFile | null;
|
uploaded: Misskey.entities.DriveFile | null;
|
||||||
|
@ -121,7 +135,7 @@ export function useUploader(options: {
|
||||||
id,
|
id,
|
||||||
name: prefer.s.keepOriginalFilename ? filename : id + extension,
|
name: prefer.s.keepOriginalFilename ? filename : id + extension,
|
||||||
progress: null,
|
progress: null,
|
||||||
thumbnail: window.URL.createObjectURL(file),
|
thumbnail: THUMBNAIL_SUPPORTED_TYPES.includes(file.type) ? window.URL.createObjectURL(file) : null,
|
||||||
preprocessing: false,
|
preprocessing: false,
|
||||||
uploading: false,
|
uploading: false,
|
||||||
aborted: false,
|
aborted: false,
|
||||||
|
@ -144,7 +158,7 @@ export function useUploader(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeItem(item: UploaderItem) {
|
function removeItem(item: UploaderItem) {
|
||||||
URL.revokeObjectURL(item.thumbnail);
|
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||||
items.value.splice(items.value.indexOf(item), 1);
|
items.value.splice(items.value.indexOf(item), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -196,7 +210,7 @@ export function useUploader(options: {
|
||||||
text: i18n.ts.cropImage,
|
text: i18n.ts.cropImage,
|
||||||
action: async () => {
|
action: async () => {
|
||||||
const cropped = await os.cropImageFile(item.file, { aspectRatio: null });
|
const cropped = await os.cropImageFile(item.file, { aspectRatio: null });
|
||||||
URL.revokeObjectURL(item.thumbnail);
|
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||||
items.value.splice(items.value.indexOf(item), 1, {
|
items.value.splice(items.value.indexOf(item), 1, {
|
||||||
...item,
|
...item,
|
||||||
file: markRaw(cropped),
|
file: markRaw(cropped),
|
||||||
|
@ -225,7 +239,7 @@ export function useUploader(options: {
|
||||||
image: item.file,
|
image: item.file,
|
||||||
}, {
|
}, {
|
||||||
ok: (file) => {
|
ok: (file) => {
|
||||||
URL.revokeObjectURL(item.thumbnail);
|
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||||
items.value.splice(items.value.indexOf(item), 1, {
|
items.value.splice(items.value.indexOf(item), 1, {
|
||||||
...item,
|
...item,
|
||||||
file: markRaw(file),
|
file: markRaw(file),
|
||||||
|
@ -295,7 +309,7 @@ export function useUploader(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
COMPRESSION_SUPPORTED_TYPES.includes(item.file.type) &&
|
IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(item.file.type) &&
|
||||||
!item.preprocessing &&
|
!item.preprocessing &&
|
||||||
!item.uploading &&
|
!item.uploading &&
|
||||||
!item.uploaded
|
!item.uploaded
|
||||||
|
@ -461,10 +475,25 @@ export function useUploader(options: {
|
||||||
async function preprocess(item: UploaderItem): Promise<void> {
|
async function preprocess(item: UploaderItem): Promise<void> {
|
||||||
item.preprocessing = true;
|
item.preprocessing = true;
|
||||||
|
|
||||||
let file: Blob | File = item.file;
|
try {
|
||||||
const imageBitmap = await window.createImageBitmap(file);
|
if (IMAGE_PREPROCESS_NEEDED_TYPES.includes(item.file.type)) {
|
||||||
|
await preprocessForImage(item);
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to preprocess image', err);
|
||||||
|
|
||||||
const needsWatermark = item.watermarkPresetId != null && WATERMARK_SUPPORTED_TYPES.includes(file.type);
|
// nop
|
||||||
|
}
|
||||||
|
|
||||||
|
item.preprocessing = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function preprocessForImage(item: UploaderItem): Promise<void> {
|
||||||
|
const imageBitmap = await window.createImageBitmap(item.file);
|
||||||
|
|
||||||
|
let preprocessedFile: Blob | File = item.file;
|
||||||
|
|
||||||
|
const needsWatermark = item.watermarkPresetId != null && WATERMARK_SUPPORTED_TYPES.includes(preprocessedFile.type);
|
||||||
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
|
const preset = prefer.s.watermarkPresets.find(p => p.id === item.watermarkPresetId);
|
||||||
if (needsWatermark && preset != null) {
|
if (needsWatermark && preset != null) {
|
||||||
const canvas = window.document.createElement('canvas');
|
const canvas = window.document.createElement('canvas');
|
||||||
|
@ -479,7 +508,7 @@ export function useUploader(options: {
|
||||||
|
|
||||||
renderer.render();
|
renderer.render();
|
||||||
|
|
||||||
file = await new Promise<Blob>((resolve) => {
|
preprocessedFile = await new Promise<Blob>((resolve) => {
|
||||||
canvas.toBlob((blob) => {
|
canvas.toBlob((blob) => {
|
||||||
if (blob == null) {
|
if (blob == null) {
|
||||||
throw new Error('Failed to convert canvas to blob');
|
throw new Error('Failed to convert canvas to blob');
|
||||||
|
@ -491,7 +520,7 @@ export function useUploader(options: {
|
||||||
}
|
}
|
||||||
|
|
||||||
const compressionSettings = getCompressionSettings(item.compressionLevel);
|
const compressionSettings = getCompressionSettings(item.compressionLevel);
|
||||||
const needsCompress = item.compressionLevel !== 0 && compressionSettings && COMPRESSION_SUPPORTED_TYPES.includes(file.type) && !(await isAnimated(file));
|
const needsCompress = item.compressionLevel !== 0 && compressionSettings && IMAGE_COMPRESSION_SUPPORTED_TYPES.includes(preprocessedFile.type) && !(await isAnimated(preprocessedFile));
|
||||||
|
|
||||||
if (needsCompress) {
|
if (needsCompress) {
|
||||||
const config = {
|
const config = {
|
||||||
|
@ -502,13 +531,13 @@ export function useUploader(options: {
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await readAndCompressImage(file, config);
|
const result = await readAndCompressImage(preprocessedFile, config);
|
||||||
if (result.size < file.size || file.type === 'image/webp') {
|
if (result.size < preprocessedFile.size || preprocessedFile.type === 'image/webp') {
|
||||||
// The compression may not always reduce the file size
|
// The compression may not always reduce the file size
|
||||||
// (and WebP is not browser safe yet)
|
// (and WebP is not browser safe yet)
|
||||||
file = result;
|
preprocessedFile = result;
|
||||||
item.compressedSize = result.size;
|
item.compressedSize = result.size;
|
||||||
item.uploadName = file.type !== config.mimeType ? `${item.name}.${mimeTypeMap[config.mimeType]}` : item.name;
|
item.uploadName = preprocessedFile.type !== config.mimeType ? `${item.name}.${mimeTypeMap[config.mimeType]}` : item.name;
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Failed to resize image', err);
|
console.error('Failed to resize image', err);
|
||||||
|
@ -518,17 +547,16 @@ export function useUploader(options: {
|
||||||
item.uploadName = item.name;
|
item.uploadName = item.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
URL.revokeObjectURL(item.thumbnail);
|
|
||||||
item.thumbnail = window.URL.createObjectURL(file);
|
|
||||||
item.preprocessedFile = markRaw(file);
|
|
||||||
item.preprocessing = false;
|
|
||||||
|
|
||||||
imageBitmap.close();
|
imageBitmap.close();
|
||||||
|
|
||||||
|
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||||
|
item.thumbnail = THUMBNAIL_SUPPORTED_TYPES.includes(preprocessedFile.type) ? window.URL.createObjectURL(preprocessedFile) : null;
|
||||||
|
item.preprocessedFile = markRaw(preprocessedFile);
|
||||||
}
|
}
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
for (const item of items.value) {
|
for (const item of items.value) {
|
||||||
URL.revokeObjectURL(item.thumbnail);
|
if (item.thumbnail != null) URL.revokeObjectURL(item.thumbnail);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue