92 lines
2.3 KiB
TypeScript
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 }
|
|
}
|