Files
RenewCMS/web/src/composables/useImageUpload.ts
T

92 lines
2.3 KiB
TypeScript

import { ref } from 'vue'
import { redirectToLogin, redirectUnverified } from '@/composables/useApi'
import type { MediaItem } from '@/composables/useMedia'
const UPLOAD_URL = '/v1/media/'
const HTTP_CREATED = 201
const HTTP_BAD_REQUEST = 400
const HTTP_UNAUTHORIZED = 401
const HTTP_FORBIDDEN = 403
const HTTP_TOO_LARGE = 413
const HTTP_UNSUPPORTED = 415
export interface UploadProgress {
loaded: number
total: number
}
export type UploadOutcome =
| { kind: 'success'; item: MediaItem }
| { kind: 'rejected'; reason: 'size' | 'type' }
| { kind: 'handled' }
| { kind: 'failed' }
function readItem(responseText: string): MediaItem | null {
try {
return JSON.parse(responseText) as MediaItem
} catch {
return null
}
}
function outcomeOf(request: XMLHttpRequest): UploadOutcome {
switch (request.status) {
case HTTP_CREATED: {
const item = readItem(request.responseText)
return item ? { kind: 'success', item } : { kind: 'failed' }
}
case HTTP_TOO_LARGE:
return { kind: 'rejected', reason: 'size' }
case HTTP_UNSUPPORTED:
case HTTP_BAD_REQUEST:
return { kind: 'rejected', reason: 'type' }
case HTTP_UNAUTHORIZED:
redirectToLogin()
return { kind: 'handled' }
case HTTP_FORBIDDEN:
redirectUnverified()
return { kind: 'handled' }
default:
return { kind: 'failed' }
}
}
export function useImageUpload() {
const progress = ref<UploadProgress>({ loaded: 0, total: 0 })
let activeRequest: XMLHttpRequest | null = null
function abort() {
activeRequest?.abort()
activeRequest = null
}
function start(file: File): Promise<UploadOutcome> {
progress.value = { loaded: 0, total: file.size }
return new Promise((resolve) => {
const request = new XMLHttpRequest()
activeRequest = request
request.open('POST', UPLOAD_URL)
request.withCredentials = true
request.upload.addEventListener('progress', (event) => {
progress.value = { loaded: event.loaded, total: event.total }
})
request.addEventListener('load', () => {
activeRequest = null
resolve(outcomeOf(request))
})
request.addEventListener('error', () => {
activeRequest = null
resolve({ kind: 'failed' })
})
request.addEventListener('abort', () => {
activeRequest = null
})
const formData = new FormData()
formData.append('file', file)
request.send(formData)
})
}
return { progress, start, abort }
}