feat: media picker and image removal
This commit is contained in:
1 parent
008b0dfbb9
commit
b3793d5dbb
54 files changed
+3769
-1412
No files matched your search
@@ -1,4 +1,14 @@
|
||||
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
|
||||
@@ -6,10 +16,41 @@ export interface UploadProgress {
|
||||
}
|
||||
|
||||
export type UploadOutcome =
|
||||
| { kind: 'success'; location: string; id: number }
|
||||
| { kind: 'rejected'; body: string }
|
||||
| { 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
|
||||
@@ -19,31 +60,19 @@ export function useImageUpload() {
|
||||
activeRequest = null
|
||||
}
|
||||
|
||||
function start(articleId: number, file: File): Promise<UploadOutcome> {
|
||||
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', `/v1/article/${articleId}/image`)
|
||||
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
|
||||
if (request.status === 201) {
|
||||
const body = JSON.parse(request.responseText) as {
|
||||
id: number
|
||||
location: string
|
||||
}
|
||||
resolve({ kind: 'success', location: body.location, id: body.id })
|
||||
return
|
||||
}
|
||||
if (request.status === 400) {
|
||||
resolve({ kind: 'rejected', body: request.responseText })
|
||||
return
|
||||
}
|
||||
resolve({ kind: 'failed' })
|
||||
resolve(outcomeOf(request))
|
||||
})
|
||||
request.addEventListener('error', () => {
|
||||
activeRequest = null
|
||||
|
||||
Reference in new issue
Block a user