Compress answers and show upload progress

This commit is contained in:
2025-09-14 17:45:34 +02:00
parent b6b43618a7
commit 5c66430cb4
6 changed files with 93 additions and 16 deletions
+3 -1
View File
@@ -114,7 +114,9 @@
"required_answer": "Erforderliche Antwort",
"finalize": "Antwort finalisieren",
"already_submitted": "Deine Antwort wurde bereits übermittelt!",
"finalize_explain": "Dies gibt den Admins die Möglichkeit deine Antwort zu bewerten und dir Punkte zu geben. Du kannst deine Antwort anschließend nicht mehr ändern!"
"finalize_explain": "Dies gibt den Admins die Möglichkeit deine Antwort zu bewerten und dir Punkte zu geben. Du kannst deine Antwort anschließend nicht mehr ändern!",
"upload_title": "Antwort wird hochgeladen...",
"upload_compress": "Bild wird komprimiert..."
},
"hunt": {
"total_points": "Punktestand: {score}",
+3 -1
View File
@@ -114,7 +114,9 @@
"required_answer": "Required Answer",
"finalize": "Finalize answer",
"already_submitted": "Your final answer has already been submitted!",
"finalize_explain": "This allows the admins to review your answer and give you points. You cannot change your answer afterwards!"
"finalize_explain": "This allows the admins to review your answer and give you points. You cannot change your answer afterwards!",
"upload_title": "Uploading answer...",
"upload_compress": "Compressing image..."
},
"hunt": {
"total_points": "Total points: {score}",
+58 -14
View File
@@ -1,5 +1,5 @@
<script setup lang="ts">
import { createImageURL } from '#shared/utils/image';
import { compressImage, createImageURL } from '#shared/utils/image';
import ClickableImage from '~/components/ClickableImage.vue';
import MultilineString from '~/components/MultilineString.vue';
import { useI18nKey } from '~/composables/useI18nKey';
@@ -55,11 +55,24 @@ const textType = {
const newImage = ref<File>();
const newImageURL = computed(() =>
newImage.value ? createImageURL(newImage.value) : undefined
);
const uploading = ref(false);
const uploadingImage = ref(false);
const compressing = ref(false);
const uploadProgress = ref(1);
const submitError = ref('');
useEventListener(window, 'beforeunload', (ev) => {
if (uploading.value) {
ev.preventDefault();
}
});
async function submit() {
uploading.value = true;
submitError.value = '';
uploadProgress.value = 0;
const body = new FormData();
body.set('lang', locale.value);
@@ -74,27 +87,38 @@ async function submit() {
body.set('final', 'true');
}
if (newImage.value) {
body.set('image', newImage.value);
uploadingImage.value = true;
compressing.value = true;
const compressedImage = await compressImage(newImage.value);
compressing.value = false;
body.set('image', compressedImage);
}
const xhr = new XMLHttpRequest();
try {
await $fetch(`/api/quest/${questSlug?.id}/submit`, {
method: 'POST',
body
await new Promise((resolve) => {
xhr.upload.addEventListener('progress', (event) => {
if (event.lengthComputable) {
uploadProgress.value = event.loaded / event.total;
}
});
xhr.addEventListener('loadend', () => {
resolve(xhr.readyState === 4 && xhr.status === 200);
uploadProgress.value = 1;
});
xhr.open('POST', `/api/quest/${questSlug?.id}/submit`, true);
xhr.withCredentials = true;
xhr.send(body);
});
await refresh();
answer.value = data.value?.answer?.text;
newImage.value = undefined;
} catch (e) {
if (e instanceof Error && 'statusCode' in e && e.statusCode === 418) {
if (xhr.status === 418) {
submitError.value = t('page.quest.already_finalized');
await refresh();
} else if (
e instanceof Error &&
'statusCode' in e &&
e.statusCode === 409
) {
} else if (xhr.status === 409) {
submitError.value = t('page.quest.conflict');
} else {
submitError.value = parseError(e);
@@ -102,11 +126,31 @@ async function submit() {
}
uploading.value = false;
uploadingImage.value = false;
}
</script>
<template>
<div>
<VaModal
:model-value="uploadingImage"
no-outside-dismiss
hide-default-actions
no-dismiss
>
<h2 class="text-2xl font-bold">{{ t('page.quest.upload_title') }}</h2>
<p v-if="compressing" class="text-lg">
{{ t('page.quest.upload_compress') }}
</p>
<VaProgressCircle
v-else
class="mx-auto"
:model-value="uploadProgress * 100"
size="50dvw"
>
{{ (uploadProgress * 100).toFixed(0) + '%' }}
</VaProgressCircle>
</VaModal>
<VaButtonGroup>
<VaButton
icon="arrow_back"
@@ -230,8 +274,8 @@ async function submit() {
</VaInput>
<VaImage
v-if="newImage"
:src="createImageURL(newImage)"
v-if="newImageURL"
:src="newImageURL"
class="max-h-60 w-full"
fit="contain"
lazy
@@ -279,7 +323,7 @@ async function submit() {
newImage ||
data.answer?.text !== answer ||
data.answer?.final !== final
)
) || uploading
"
:loading="uploading"
@click="submit"