149 lines
4.0 KiB
TypeScript
149 lines
4.0 KiB
TypeScript
import type {
|
|
UploadProgressItem,
|
|
UploadResponse
|
|
} from '~/types/image'
|
|
import { isUnauthorizedError } from './useAuth'
|
|
import { prepareFilesForUpload } from './useClipboardImage'
|
|
import { normalizeUploadTagNames } from './useUploadPreferences'
|
|
import { uploadFormDataWithProgress } from '~/utils/upload-progress'
|
|
|
|
const MAX_FILES = 10
|
|
|
|
export function useImageUpload() {
|
|
const toast = useToast()
|
|
const { t } = useI18n()
|
|
const { compressEnabled, clientWebpQuality, uploadTagNames } = useUploadPreferences()
|
|
const { load: loadUploadPolicy } = useUploadPolicy()
|
|
const uploading = ref(false)
|
|
const progressItems = ref<UploadProgressItem[]>([])
|
|
const lastUploadResult = ref<UploadResponse | null>(null)
|
|
|
|
async function uploadFiles(
|
|
files: File[],
|
|
options?: { notify?: boolean }
|
|
) {
|
|
if (uploading.value) return
|
|
|
|
if (!files.length) {
|
|
toast.add({ title: t('upload.selectImages'), color: 'warning' })
|
|
return
|
|
}
|
|
|
|
if (files.length > MAX_FILES) {
|
|
toast.add({
|
|
title: t('upload.maxFiles', { n: MAX_FILES }),
|
|
color: 'warning'
|
|
})
|
|
return
|
|
}
|
|
|
|
uploading.value = true
|
|
progressItems.value = files.map(file => ({
|
|
name: file.name,
|
|
status: 'pending',
|
|
progress: 0
|
|
}))
|
|
|
|
try {
|
|
const preparedFiles = await prepareFilesForUpload(files, {
|
|
enabled: compressEnabled.value,
|
|
quality: clientWebpQuality.value / 100
|
|
})
|
|
const currentPolicy = await loadUploadPolicy().catch(() => null)
|
|
const maxFileSizeMb = currentPolicy?.imageMaxFileSizeMb ?? 10
|
|
const prepared = preparedFiles.filter((file) => {
|
|
if (file.size <= maxFileSizeMb * 1024 * 1024) return true
|
|
toast.add({
|
|
title: t('files.fileTooLarge', {
|
|
name: file.name,
|
|
limit: maxFileSizeMb
|
|
}),
|
|
color: 'error'
|
|
})
|
|
return false
|
|
})
|
|
if (!prepared.length) {
|
|
progressItems.value = []
|
|
return
|
|
}
|
|
|
|
progressItems.value = prepared.map(file => ({
|
|
name: file.name,
|
|
status: 'uploading',
|
|
progress: 0
|
|
}))
|
|
|
|
const formData = new FormData()
|
|
for (const file of prepared) {
|
|
formData.append('files', file, file.name)
|
|
}
|
|
|
|
const tagNames = normalizeUploadTagNames(uploadTagNames.value).join(',')
|
|
if (tagNames) {
|
|
formData.append('tagNames', tagNames)
|
|
}
|
|
|
|
const response = await uploadFormDataWithProgress<UploadResponse>(
|
|
'/api/images/upload',
|
|
formData,
|
|
(percent) => {
|
|
progressItems.value = progressItems.value.map(item => ({
|
|
...item,
|
|
progress: Math.min(99, percent)
|
|
}))
|
|
}
|
|
)
|
|
|
|
lastUploadResult.value = response
|
|
|
|
progressItems.value = prepared.map((file) => {
|
|
const failed = response.errors.find(error => error.name === file.name)
|
|
return {
|
|
name: file.name,
|
|
status: failed ? 'error' : 'success',
|
|
progress: 100,
|
|
error: failed?.error.message
|
|
}
|
|
})
|
|
|
|
if (response.items.length && options?.notify !== false) {
|
|
toast.add({
|
|
title: t('upload.uploadSuccess', { n: response.items.length }),
|
|
color: 'success'
|
|
})
|
|
}
|
|
|
|
if (response.errors.length) {
|
|
toast.add({
|
|
title: t('upload.uploadPartialFail', { n: response.errors.length }),
|
|
color: 'warning'
|
|
})
|
|
}
|
|
|
|
return response
|
|
} catch (error: unknown) {
|
|
if (isUnauthorizedError(error)) {
|
|
throw error
|
|
}
|
|
const message = error instanceof Error ? error.message : t('upload.uploadFailed')
|
|
progressItems.value = progressItems.value.map(item => ({
|
|
...item,
|
|
status: 'error',
|
|
progress: 100,
|
|
error: message
|
|
}))
|
|
toast.add({ title: message, color: 'error' })
|
|
throw error
|
|
} finally {
|
|
uploading.value = false
|
|
}
|
|
}
|
|
|
|
return {
|
|
uploading,
|
|
progressItems,
|
|
lastUploadResult,
|
|
uploadFiles
|
|
}
|
|
}
|