diff --git a/src/lib/Batch.svelte b/src/lib/Batch.svelte new file mode 100644 index 0000000..d8ecd31 --- /dev/null +++ b/src/lib/Batch.svelte @@ -0,0 +1,281 @@ + + +
+
+

+ {#if stage === "scanning"} + Scanning {scanned} of {items.length}… + {:else if stage === "review"} + {items.length} images ready, {dirty} with hidden data + {:else if stage === "cleaning"} + Cleaning {done} of {items.length}… + {:else} + {succeeded.length} of {items.length} cleaned + {/if} +

+ +
+ + + +
+ {#if stage === "done"} + + + {:else} + + + {/if} +
+
+ + diff --git a/src/lib/Dropzone.svelte b/src/lib/Dropzone.svelte index 38690da..49321ec 100644 --- a/src/lib/Dropzone.svelte +++ b/src/lib/Dropzone.svelte @@ -11,15 +11,33 @@ let fileInput; let depth = 0; - function offer(file) { - if (!file) return; - if (!isAccepted(file)) { + const MAX_FILES = 50; + + function offer(list) { + const incoming = [...(list || [])].filter(Boolean); + if (incoming.length === 0) return; + + const accepted = incoming.filter(isAccepted); + if (accepted.length === 0) { + const type = incoming[0].type; dispatch("reject", { - message: `Untrace cannot read ${file.type || "that file type"}. Choose a JPEG, PNG, TIFF, HEIC or AVIF image.`, + message: `Untrace cannot read ${type || "that file type"}. Choose JPEG, PNG, TIFF, HEIC or AVIF images.`, }); return; } - dispatch("select", { file }); + + const skipped = incoming.length - accepted.length; + const capped = accepted.slice(0, MAX_FILES); + const over = accepted.length - capped.length; + if (skipped > 0 || over > 0) { + dispatch("reject", { + message: + over > 0 + ? `Only the first ${MAX_FILES} images were added.` + : `Skipped ${skipped} file${skipped === 1 ? "" : "s"} Untrace cannot read.`, + }); + } + dispatch("select", { files: capped }); } function onDrop(event) { @@ -27,7 +45,7 @@ depth = 0; dragging = false; if (busy) return; - offer(event.dataTransfer?.files?.[0]); + offer(event.dataTransfer?.files); } function onDragEnter(event) { @@ -44,7 +62,7 @@ } function onChange(event) { - offer(event.target.files?.[0]); + offer(event.target.files); // Reset so choosing the same file twice still fires a change event. event.target.value = ""; } @@ -54,7 +72,7 @@ const item = [...(event.clipboardData?.items || [])].find((entry) => entry.type.startsWith("image/") ); - if (item) offer(item.getAsFile()); + if (item) offer([item.getAsFile()]); } @@ -77,10 +95,10 @@ > - {dragging ? "Drop to scan it" : "Drop an image, or choose a file"} + {dragging ? "Drop to scan" : "Drop images, or choose files"} - Nothing is uploaded. The image is read and rewritten inside this tab. + Nothing is uploaded. Add one image or up to 50 at once. JPEG @@ -98,6 +116,7 @@ 1) { + batchFiles = event.detail.files; + return; + } + file = event.detail.files[0]; previewUrl = URL.createObjectURL(file); metadata = null; stage = "scanning"; @@ -75,6 +82,7 @@ function reset() { release(); + batchFiles = null; file = null; metadata = null; stage = "idle"; @@ -86,6 +94,13 @@

Clean an image

+ {#if batchFiles} + dispatch("notify", e.detail)} + on:reset={reset} + /> + {:else}
    {#each steps as label, index}
  1. @@ -179,6 +194,7 @@ {/if} {/if} + {/if}