Polish upload UI for dual queue status and batch confirm flow.

Metadata error confirm, success styling, upload-confirmed hiding of configure, and remove stale PR plan doc.
This commit is contained in:
2026-10-23 18:34:17 +02:00
parent 271c2b7b2d
commit b4cc26a632
6 changed files with 156 additions and 61 deletions
+47 -17
View File
@@ -11,9 +11,14 @@
<div class="flex flex-col gap-2">
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
<UploadDropzone ref="dropzoneRef" :disabled="loadingMetadata" @pick="onFilesPicked" />
<UploadDropzone
ref="dropzoneRef"
:disabled="loadingMetadata || batchUploading"
@pick="onFilesPicked"
/>
<button
v-if="!uploadConfirmed"
type="button"
class="inline-flex shrink-0 items-center justify-center rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground transition-colors hover:opacity-90 disabled:cursor-not-allowed disabled:opacity-60 sm:self-center"
:disabled="!canOpenConfigure"
@@ -37,6 +42,7 @@
<UploadConfigureModal
v-model:open="configureModalOpen"
v-model:settings="batchSettings"
:metadata-error-count="metadataErrorCount"
@upload="onBatchUpload"
/>
</main>
@@ -48,7 +54,9 @@ import type UploadDropzone from '@/components/upload/UploadDropzone.vue';
import { config } from '@/config';
import { useExiftoolStore } from '@/stores/exiftool';
import { useMetadataStore } from '@/stores/metadata';
import { useUploadStore } from '@/stores/upload';
import type { UploadBatchSettingsForm } from '@/types/upload/UploadBatchSettingsForm';
import { makeKeyFromFile } from '@/utils/file/makeKeyFromFile';
import { analyzePickedUploadFiles } from '@/utils/upload/analyzePickedUploadFiles';
import { buildPickedUploadWarning } from '@/utils/upload/buildPickedUploadWarning';
import { formatUploadBatchSettingsSummary } from '@/utils/upload/formatUploadBatchSettingsSummary';
@@ -58,14 +66,17 @@ const { maxQueueSize, maxImageBytes } = config.validation.upload;
const exiftoolStore = useExiftoolStore();
const metadataStore = useMetadataStore();
const uploadStore = useUploadStore();
const { initializeExiftool } = exiftoolStore;
const { makeKeyFromFile, getPreview, loadPreviews, clearAllPreviews } = metadataStore;
const { loadPreviews, clearAllPreviews } = metadataStore;
const { clearAllUploads, uploadBatch } = uploadStore;
const queuedFiles = ref<File[]>([]);
const selectedFile = ref<File | null>(null);
const selectionError = ref<string | null>(null);
const loadingMetadata = ref(false);
const batchUploading = ref(false);
const dropzoneRef = ref<InstanceType<typeof UploadDropzone> | null>(null);
const batchSettings = ref<UploadBatchSettingsForm>({
mode: 'zk',
@@ -75,6 +86,7 @@ const batchSettings = ref<UploadBatchSettingsForm>({
password: ''
});
const configureModalOpen = ref(false);
const uploadConfirmed = ref(false);
const confirmedBatchSummary = ref<string | null>(null);
onBeforeMount(async () => {
@@ -88,25 +100,28 @@ onBeforeMount(async () => {
const maxImageSizeFormatted = computed(() => prettyBytes(maxImageBytes, { binary: true, maximumFractionDigits: 1 }));
const queueRows = computed(() =>
queuedFiles.value.map(file => ({
file,
key: makeKeyFromFile(file),
preview: getPreview(file)
}))
queuedFiles.value.map(file => {
const key = makeKeyFromFile(file);
return {
file,
key,
metadata: metadataStore.getPreview(file),
upload: uploadStore.getUpload(file)
};
})
);
const selectedPreview = computed(() => getPreview(selectedFile.value));
const selectedPreview = computed(() => metadataStore.getPreview(selectedFile.value));
const canOpenConfigure = computed(() => {
if (queuedFiles.value.length === 0 || loadingMetadata.value) {
return false;
}
const metadataErrorCount = computed(
() => queuedFiles.value.filter(file => metadataStore.getPreview(file).status === 'error').length
);
return !queuedFiles.value.some(file => getPreview(file).status === 'error');
});
const canOpenConfigure = computed(() => queuedFiles.value.length > 0 && !loadingMetadata.value);
const onFilesPicked = async (picked: File[]): Promise<void> => {
if (loadingMetadata.value) {
if (loadingMetadata.value || batchUploading.value) {
return;
}
@@ -158,13 +173,28 @@ const resetUploadSelection = (): void => {
clearAllPreviews();
queuedFiles.value = [];
selectedFile.value = null;
clearAllUploads();
dropzoneRef.value?.clearInput();
configureModalOpen.value = false;
uploadConfirmed.value = false;
confirmedBatchSummary.value = null;
};
const onBatchUpload = (): void => {
confirmedBatchSummary.value = formatUploadBatchSettingsSummary(batchSettings.value);
const onBatchUpload = async (uploadDespiteMetadataFailure?: boolean): Promise<void> => {
const settings = batchSettings.value;
confirmedBatchSummary.value = formatUploadBatchSettingsSummary(settings);
uploadConfirmed.value = true;
configureModalOpen.value = false;
batchUploading.value = true;
try {
await uploadBatch(queuedFiles.value, settings, uploadDespiteMetadataFailure);
} catch (error) {
console.error(error);
} finally {
batchUploading.value = false;
}
};
</script>