show upload status when uploading images

This commit is contained in:
Zutatensuppe 2021-06-20 13:40:28 +02:00
parent 4b10fbc01b
commit b410f400fa
6 changed files with 106 additions and 10 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -4,7 +4,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>🧩 jigsaw.hyottoko.club</title> <title>🧩 jigsaw.hyottoko.club</title>
<script type="module" crossorigin src="/assets/index.1a1747a7.js"></script> <script type="module" crossorigin src="/assets/index.e0726e0c.js"></script>
<link rel="modulepreload" href="/assets/vendor.684f7bc8.js"> <link rel="modulepreload" href="/assets/vendor.684f7bc8.js">
<link rel="stylesheet" href="/assets/index.5c03949d.css"> <link rel="stylesheet" href="/assets/index.5c03949d.css">
</head> </head>

View file

@ -49,10 +49,21 @@ gallery", if possible!
</div> </div>
<div class="area-buttons"> <div class="area-buttons">
<button class="btn" :disabled="!canPostToGallery" @click="postToGallery">🖼 Post to gallery</button> <button class="btn"
<button class="btn" :disabled="!canSetupGameClick" @click="setupGameClick">🧩 Post to gallery <br /> + set up game</button> :disabled="!canPostToGallery"
@click="postToGallery"
>
<template v-if="uploading === 'postToGallery'">Uploading ({{uploadProgressPercent}}%)</template>
<template v-else>🖼 Post to gallery</template>
</button>
<button class="btn"
:disabled="!canSetupGameClick"
@click="setupGameClick"
>
<template v-if="uploading === 'setupGame'">Uploading ({{uploadProgressPercent}}%)</template>
<template v-else>🧩 Post to gallery <br /> + set up game</template>
</button>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
@ -75,6 +86,12 @@ export default defineComponent({
autocompleteTags: { autocompleteTags: {
type: Function, type: Function,
}, },
uploadProgress: {
type: Number,
},
uploading: {
type: String,
},
}, },
emits: { emits: {
bgclick: null, bgclick: null,
@ -91,10 +108,19 @@ export default defineComponent({
} }
}, },
computed: { computed: {
uploadProgressPercent (): number {
return this.uploadProgress ? Math.round(this.uploadProgress * 100) : 0
},
canPostToGallery (): boolean { canPostToGallery (): boolean {
if (this.uploading) {
return false
}
return !!(this.previewUrl && this.file) return !!(this.previewUrl && this.file)
}, },
canSetupGameClick (): boolean { canSetupGameClick (): boolean {
if (this.uploading) {
return false
}
return !!(this.previewUrl && this.file) return !!(this.previewUrl && this.file)
}, },
}, },

View file

@ -44,8 +44,11 @@ in jigsawpuzzles.io
v-if="dialog==='new-image'" v-if="dialog==='new-image'"
:autocompleteTags="autocompleteTags" :autocompleteTags="autocompleteTags"
@bgclick="dialog=''" @bgclick="dialog=''"
:uploadProgress="uploadProgress"
:uploading="uploading"
@postToGalleryClick="postToGalleryClick" @postToGalleryClick="postToGalleryClick"
@setupGameClick="setupGameClick" /> @setupGameClick="setupGameClick"
/>
<edit-image-dialog <edit-image-dialog
v-if="dialog==='edit-image'" v-if="dialog==='edit-image'"
:autocompleteTags="autocompleteTags" :autocompleteTags="autocompleteTags"
@ -61,7 +64,7 @@ in jigsawpuzzles.io
</template> </template>
<script lang="ts"> <script lang="ts">
import { defineComponent, PropType } from 'vue' import { defineComponent } from 'vue'
import ImageLibrary from './../components/ImageLibrary.vue' import ImageLibrary from './../components/ImageLibrary.vue'
import NewImageDialog from './../components/NewImageDialog.vue' import NewImageDialog from './../components/NewImageDialog.vue'
@ -69,6 +72,7 @@ import EditImageDialog from './../components/EditImageDialog.vue'
import NewGameDialog from './../components/NewGameDialog.vue' import NewGameDialog from './../components/NewGameDialog.vue'
import { GameSettings, Image, Tag } from '../../common/Types' import { GameSettings, Image, Tag } from '../../common/Types'
import Util from '../../common/Util' import Util from '../../common/Util'
import xhr from '../xhr'
export default defineComponent({ export default defineComponent({
components: { components: {
@ -97,6 +101,9 @@ export default defineComponent({
} as Image, } as Image,
dialog: '', dialog: '',
uploading: '',
uploadProgress: 0,
} }
}, },
async created() { async created() {
@ -143,15 +150,18 @@ export default defineComponent({
this.dialog = 'edit-image' this.dialog = 'edit-image'
}, },
async uploadImage (data: any) { async uploadImage (data: any) {
this.uploadProgress = 0
const formData = new FormData(); const formData = new FormData();
formData.append('file', data.file, data.file.name); formData.append('file', data.file, data.file.name);
formData.append('title', data.title) formData.append('title', data.title)
formData.append('tags', data.tags) formData.append('tags', data.tags)
const res = await xhr.post('/api/upload', {
const res = await fetch('/api/upload', {
method: 'post',
body: formData, body: formData,
onUploadProgress: (evt: ProgressEvent<XMLHttpRequestEventTarget>): void => {
this.uploadProgress = evt.loaded / evt.total
},
}) })
this.uploadProgress = 1
return await res.json() return await res.json()
}, },
async saveImage (data: any) { async saveImage (data: any) {
@ -175,12 +185,16 @@ export default defineComponent({
await this.loadImages() await this.loadImages()
}, },
async postToGalleryClick(data: any) { async postToGalleryClick(data: any) {
this.uploading = 'postToGallery'
await this.uploadImage(data) await this.uploadImage(data)
this.uploading = ''
this.dialog = '' this.dialog = ''
await this.loadImages() await this.loadImages()
}, },
async setupGameClick (data: any) { async setupGameClick (data: any) {
this.uploading = 'setupGame'
const image = await this.uploadImage(data) const image = await this.uploadImage(data)
this.uploading = ''
this.loadImages() // load images in background this.loadImages() // load images in background
this.image = image this.image = image
this.dialog = 'new-game' this.dialog = 'new-game'

56
src/frontend/xhr.ts Normal file
View file

@ -0,0 +1,56 @@
export interface Response {
status: number,
text: string,
json: () => Promise<any>,
}
export interface Options {
body: FormData|string,
headers?: any,
onUploadProgress?: (ev: ProgressEvent<XMLHttpRequestEventTarget>) => any,
}
const request = async (
method: string,
url: string,
options: Options
): Promise<Response> => {
return new Promise((resolve, reject) => {
const xhr = new window.XMLHttpRequest()
xhr.open(method, url, true)
xhr.withCredentials = true
for (const k in options.headers || {}) {
xhr.setRequestHeader(k, options.headers[k])
}
xhr.addEventListener('load', function (ev: ProgressEvent<XMLHttpRequestEventTarget>
) {
resolve({
status: this.status,
text: this.responseText,
json: async () => JSON.parse(this.responseText),
})
})
xhr.addEventListener('error', function (ev: ProgressEvent<XMLHttpRequestEventTarget>) {
reject(new Error('xhr error'))
})
if (xhr.upload && options.onUploadProgress) {
xhr.upload.addEventListener('progress', function (ev: ProgressEvent<XMLHttpRequestEventTarget>) {
// typescript complains without this extra check
if (options.onUploadProgress) {
options.onUploadProgress(ev)
}
})
}
xhr.send(options.body)
})
}
export default {
request,
get: (url: string, options: any): Promise<Response> => {
return request('get', url, options)
},
post: (url: string, options: any): Promise<Response> => {
return request('post', url, options)
},
}