fix(submissions): broadcast crop delete + fix for crop admin
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||
import { ref, onMounted, onUnmounted, computed, watch } from "vue";
|
||||
import { useRouter } from "vue-router";
|
||||
import { useI18n } from "vue-i18n";
|
||||
import { useTitle } from "@vueuse/core";
|
||||
@@ -47,7 +47,8 @@ const userSubmissions = ref<SubmissionOut[]>([]);
|
||||
const isLoadingSubmissions = ref(false);
|
||||
const selectedSubmissionFile = ref<any>(null);
|
||||
const isSubmissionFileModalOpen = ref(false);
|
||||
const copyFeedback = ref<string>("");
|
||||
// Feedback shown on the copy button that was clicked, keyed by "file" or crop id
|
||||
const copyFeedback = ref<{ key: string; message: string } | null>(null);
|
||||
const isDrawingCrop = ref(false);
|
||||
const cropStartX = ref(0);
|
||||
const cropStartY = ref(0);
|
||||
@@ -141,23 +142,23 @@ const closeSubmissionFileModal = () => {
|
||||
selectedSubmissionFile.value = null;
|
||||
};
|
||||
|
||||
const copyOCRText = async () => {
|
||||
if (selectedSubmissionFile.value?.ocr_raw_data) {
|
||||
try {
|
||||
await navigator.clipboard.writeText(selectedSubmissionFile.value.ocr_raw_data);
|
||||
copyFeedback.value = t("common.copied");
|
||||
setTimeout(() => {
|
||||
copyFeedback.value = "";
|
||||
}, 2000);
|
||||
} catch (err) {
|
||||
copyFeedback.value = t("submissions.copyFailed");
|
||||
setTimeout(() => {
|
||||
copyFeedback.value = "";
|
||||
}, 2000);
|
||||
}
|
||||
const copyOCRText = async (text: string | undefined, key: string) => {
|
||||
if (!text) return;
|
||||
|
||||
try {
|
||||
await navigator.clipboard.writeText(text);
|
||||
copyFeedback.value = { key, message: t("common.copied") };
|
||||
} catch (err) {
|
||||
copyFeedback.value = { key, message: t("submissions.copyFailed") };
|
||||
}
|
||||
setTimeout(() => {
|
||||
if (copyFeedback.value?.key === key) copyFeedback.value = null;
|
||||
}, 2000);
|
||||
};
|
||||
|
||||
const copyLabel = (key: string) =>
|
||||
copyFeedback.value?.key === key ? copyFeedback.value.message : t("common.copy");
|
||||
|
||||
const getCanvasCoordinates = (canvas: HTMLCanvasElement, clientX: number, clientY: number) => {
|
||||
const rect = canvas.getBoundingClientRect();
|
||||
const scaleX = canvas.width / rect.width;
|
||||
@@ -246,6 +247,12 @@ const redrawCropCanvas = () => {
|
||||
img.src = selectedSubmissionFile.value?.file_url;
|
||||
};
|
||||
|
||||
// Crops can also arrive over the websocket (e.g. drawn by another admin)
|
||||
watch(
|
||||
() => selectedSubmissionFile.value?.crops?.length,
|
||||
() => redrawCropCanvas(),
|
||||
);
|
||||
|
||||
const submitCrop = async () => {
|
||||
if (!currentCrop.value || !selectedSubmissionFile.value) return;
|
||||
|
||||
@@ -262,10 +269,12 @@ const submitCrop = async () => {
|
||||
},
|
||||
});
|
||||
|
||||
// Add the new crop to the list immediately
|
||||
// Add the new crop to the list immediately, unless the websocket beat us to it
|
||||
if (newCrop) {
|
||||
selectedSubmissionFile.value.crops = selectedSubmissionFile.value.crops || [];
|
||||
selectedSubmissionFile.value.crops.push(newCrop);
|
||||
if (!selectedSubmissionFile.value.crops.some((c: any) => c.id === newCrop.id)) {
|
||||
selectedSubmissionFile.value.crops.push(newCrop);
|
||||
}
|
||||
}
|
||||
|
||||
currentCrop.value = null;
|
||||
@@ -480,6 +489,7 @@ const loadUserSubmissions = async () => {
|
||||
// the next match when there's only one, so players follow along without a refresh.
|
||||
useSubmissionsWebSocket({
|
||||
userSubmissions,
|
||||
scopeId: props.scopeId,
|
||||
onScopeUpdate: (scope) => {
|
||||
if (scope.id !== props.scopeId || (scope.is_active && scope.assigned)) return;
|
||||
|
||||
@@ -909,9 +919,10 @@ onUnmounted(() => {
|
||||
<div>
|
||||
<div class="flex items-center justify-between mb-2">
|
||||
<p class="text-xs font-semibold text-base-content/70">{{ t("submissions.extractedText") }}</p>
|
||||
<button @click="copyOCRText" class="btn btn-xs btn-ghost gap-1">
|
||||
<button @click="copyOCRText(selectedSubmissionFile.ocr_raw_data, 'file')"
|
||||
class="btn btn-xs btn-ghost gap-1">
|
||||
<i class="mdi mdi-content-copy"></i>
|
||||
{{ copyFeedback || t("common.copy") }}
|
||||
{{ copyLabel("file") }}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
@@ -948,7 +959,14 @@ onUnmounted(() => {
|
||||
<div v-for="crop in selectedSubmissionFile.crops" :key="crop.id" class="space-y-2">
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-xs font-medium">{{ crop.label || t("submissions.cropLabelFallback", { x: crop.x1, y: crop.y1 }) }}</p>
|
||||
<span :class="getOCRStatusColor(crop.ocr_status)">{{ crop.ocr_status }}</span>
|
||||
<div class="flex items-center gap-1">
|
||||
<button v-if="crop.ocr_status === 'processed' && crop.ocr_raw_data"
|
||||
@click="copyOCRText(crop.ocr_raw_data, crop.id)" class="btn btn-xs btn-ghost gap-1">
|
||||
<i class="mdi mdi-content-copy"></i>
|
||||
{{ copyLabel(crop.id) }}
|
||||
</button>
|
||||
<span :class="getOCRStatusColor(crop.ocr_status)">{{ crop.ocr_status }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="crop.ocr_status === 'processed' && crop.ocr_raw_data"
|
||||
class="bg-base-100 p-2 rounded text-xs whitespace-pre-wrap break-words font-mono max-h-32 overflow-y-auto">
|
||||
|
||||
Reference in New Issue
Block a user