diff --git a/frontend/src/components/upload/UploadQueueList.vue b/frontend/src/components/upload/UploadQueueList.vue
index 62e7e8b..f089d8a 100644
--- a/frontend/src/components/upload/UploadQueueList.vue
+++ b/frontend/src/components/upload/UploadQueueList.vue
@@ -14,11 +14,7 @@
loading="lazy"
decoding="async"
/>
-
+
{{ row.file.name }}
@@ -28,7 +24,7 @@
- {{ row.metadata.message }}
+ {{ formatMetadataPreviewMessage(row.metadata) }}
{{ row.upload.message }}
@@ -41,13 +37,19 @@
diff --git a/frontend/src/stores/exiftool.ts b/frontend/src/stores/exiftool.ts
index 935aed5..44c6605 100644
--- a/frontend/src/stores/exiftool.ts
+++ b/frontend/src/stores/exiftool.ts
@@ -6,6 +6,7 @@ import type { ExiftoolWorkerOutMessage } from '@/types/workers/exiftoolWorkerOut
export const useExiftoolStore = defineStore('exiftool', () => {
const initStatus = ref('idle');
const initErrorMessage = ref(null);
+ const wasmDownloadPercent = ref(null);
const worker = ref(null);
@@ -35,6 +36,7 @@ export const useExiftoolStore = defineStore('exiftool', () => {
if (initStatus.value === 'idle') {
initStatus.value = 'loading';
initErrorMessage.value = null;
+ wasmDownloadPercent.value = 0;
initPromise = new Promise((resolve, reject) => {
initPromiseResolve = resolve;
initPromiseReject = reject;
@@ -69,8 +71,15 @@ export const useExiftoolStore = defineStore('exiftool', () => {
const handleWorkerMessage = (event: MessageEvent): void => {
const message = event.data;
+ if (message.type === 'wasm-download-progress') {
+ wasmDownloadPercent.value = message.percent;
+
+ return;
+ }
+
if (message.type === 'init-done') {
initStatus.value = 'ready';
+ wasmDownloadPercent.value = null;
initPromiseResolve?.();
initPromiseResolve = null;
initPromiseReject = null;
@@ -81,6 +90,7 @@ export const useExiftoolStore = defineStore('exiftool', () => {
if (message.type === 'init-error') {
console.error('[exiftool] init-error', message.message);
initStatus.value = 'error';
+ wasmDownloadPercent.value = null;
initErrorMessage.value = message.message;
initPromiseReject?.(new Error(message.message));
initPromiseResolve = null;
@@ -181,6 +191,7 @@ export const useExiftoolStore = defineStore('exiftool', () => {
return {
initStatus,
initErrorMessage,
+ wasmDownloadPercent,
initializeExiftool,
readMetadata,
stripAllMetadata
diff --git a/frontend/src/stores/metadata.ts b/frontend/src/stores/metadata.ts
index 3859473..a7bba8b 100644
--- a/frontend/src/stores/metadata.ts
+++ b/frontend/src/stores/metadata.ts
@@ -5,7 +5,7 @@ import type { MetadataPreviewEntry } from '@/types/metadata/MetadataPreview';
import { makeKeyFromFile } from '@/utils/file/makeKeyFromFile';
const METADATA_IDLE_MESSAGE = 'Waiting to load metadata...';
-const EXIFTOOL_INITIALIZING_MESSAGE = 'Initializing ExifTool. It may take a minute on slow networks...';
+const EXIFTOOL_INITIALIZING_MESSAGE = 'Downloading ExifTool…';
const METADATA_PROCESSING_MESSAGE = 'Stripping metadata...';
const METADATA_READY_MESSAGE = 'Metadata stripped';
const METADATA_LOAD_ERROR_MESSAGE = 'Could not load image metadata';
diff --git a/frontend/src/types/workers/exiftoolWorkerOutMessage.ts b/frontend/src/types/workers/exiftoolWorkerOutMessage.ts
index 330ea61..2ccd1e3 100644
--- a/frontend/src/types/workers/exiftoolWorkerOutMessage.ts
+++ b/frontend/src/types/workers/exiftoolWorkerOutMessage.ts
@@ -1,4 +1,5 @@
export type ExiftoolWorkerOutMessage =
+ | { type: 'wasm-download-progress'; percent: number }
| { type: 'init-done' }
| { type: 'init-error'; message: string }
| { type: 'read-metadata-done'; id: string; output: string }
diff --git a/frontend/src/workers/exiftoolWorker.ts b/frontend/src/workers/exiftoolWorker.ts
index 0f80021..3ce8d18 100644
--- a/frontend/src/workers/exiftoolWorker.ts
+++ b/frontend/src/workers/exiftoolWorker.ts
@@ -1,5 +1,6 @@
import zeroperlWasmUrl from '@6over3/zeroperl-ts/zeroperl.wasm?url';
import { parseMetadata, writeMetadata } from '@uswriting/exiftool';
+import axios from 'axios';
import { EXIFTOOL_WORKER_INIT_JPEG } from '@/consts/exiftoolWorkerInitJpeg';
import type { ExiftoolWorkerInMessage } from '@/types/workers/exiftoolWorkerInMessage';
import type { ExiftoolWorkerJob } from '@/types/workers/exiftoolWorkerJob';
@@ -14,13 +15,22 @@ let cachedZeroperlWasm: ArrayBuffer | null = null;
*/
const fetchZeroperlWasm = async (): Promise => {
if (!cachedZeroperlWasm) {
- const response = await fetch(zeroperlWasmUrl);
+ const { data } = await axios.get(zeroperlWasmUrl, {
+ responseType: 'arraybuffer',
+ onDownloadProgress: progressEvent => {
+ if (!progressEvent.total) {
+ return;
+ }
- if (!response.ok) {
- throw new Error(`Failed to load zeroperl.wasm: HTTP ${response.status}`);
- }
+ const percent = Math.min(100, (progressEvent.loaded / progressEvent.total) * 100);
- cachedZeroperlWasm = await response.arrayBuffer();
+ const rounded = parseFloat(percent.toFixed(2));
+
+ self.postMessage({ type: 'wasm-download-progress', percent: rounded });
+ }
+ });
+
+ cachedZeroperlWasm = data;
}
return new Response(cachedZeroperlWasm, {