|
| 1 | +<script> |
| 2 | + import { createEventDispatcher } from 'svelte'; |
| 3 | +
|
| 4 | + /** @type {string} */ |
| 5 | + export let id; |
| 6 | + /** @type {string} */ |
| 7 | + export let description; |
| 8 | + /** @type {string} */ |
| 9 | + export let accept; |
| 10 | + export let required = false; |
| 11 | +
|
| 12 | + /** @type {(content: string) => any} */ |
| 13 | + export let validateFile; |
| 14 | +
|
| 15 | + /** @type {(type: string, detail?: any) => boolean} */ |
| 16 | + const dispatch = createEventDispatcher(); |
| 17 | +
|
| 18 | + /** @type {FileList|null} */ |
| 19 | + let files = null; |
| 20 | + /** @type {HTMLInputElement|undefined} */ |
| 21 | + let fileInput = undefined; |
| 22 | + let fileError = ''; |
| 23 | +
|
| 24 | + async function onFileSelected() { |
| 25 | + fileError = ''; |
| 26 | + if (!files || files.length === 0) { |
| 27 | + dispatch('change', { value: null }); |
| 28 | + return; |
| 29 | + } |
| 30 | + const file = files[0]; |
| 31 | + await processFile(file); |
| 32 | + } |
| 33 | +
|
| 34 | + export function clearSelectedFile() { |
| 35 | + fileError = ''; |
| 36 | + files = null; |
| 37 | + if (fileInput) { |
| 38 | + fileInput.value = ''; |
| 39 | + } |
| 40 | + dispatch('change', { value: null }); |
| 41 | + } |
| 42 | +
|
| 43 | + /** |
| 44 | + * @param {DragEvent} ev |
| 45 | + */ |
| 46 | + async function handleDrop(ev) { |
| 47 | + ev.preventDefault(); |
| 48 | + dragOver = false; |
| 49 | +
|
| 50 | + if (!ev.dataTransfer) { |
| 51 | + return; |
| 52 | + } |
| 53 | +
|
| 54 | + if (ev.dataTransfer.files.length > 0) { |
| 55 | + files = ev.dataTransfer.files; |
| 56 | + /** @type {HTMLInputElement} */ (fileInput).files = files; |
| 57 | + await processFile(files[0]); |
| 58 | + } |
| 59 | + } |
| 60 | +
|
| 61 | + /** |
| 62 | + * @param {File} file |
| 63 | + */ |
| 64 | + async function processFile(file) { |
| 65 | + fileError = ''; |
| 66 | + let content = await file.text(); |
| 67 | + try { |
| 68 | + const data = validateFile(content); |
| 69 | + dispatch('change', { value: data }); |
| 70 | + } catch (err) { |
| 71 | + fileError = /** @type {Error}*/ (err).message; |
| 72 | + dispatch('change', { value: null }); |
| 73 | + } |
| 74 | + } |
| 75 | +
|
| 76 | + let dragOver = false; |
| 77 | +
|
| 78 | + /** |
| 79 | + * @param {DragEvent} ev |
| 80 | + */ |
| 81 | + function handleDragOver(ev) { |
| 82 | + // Prevent default behavior (Prevent file from being opened) |
| 83 | + ev.preventDefault(); |
| 84 | + dragOver = true; |
| 85 | + } |
| 86 | +
|
| 87 | + function handleDragLeave() { |
| 88 | + dragOver = false; |
| 89 | + } |
| 90 | +</script> |
| 91 | + |
| 92 | +<div |
| 93 | + class="dropZone bg-light" |
| 94 | + on:drop={handleDrop} |
| 95 | + on:dragover={handleDragOver} |
| 96 | + on:dragleave={handleDragLeave} |
| 97 | + class:dragOver |
| 98 | +> |
| 99 | + <div class="m-1"> |
| 100 | + <div class="input-group has-validation"> |
| 101 | + <label for={id} class="input-group-text"> |
| 102 | + {description} |
| 103 | + </label> |
| 104 | + <input |
| 105 | + class="form-control" |
| 106 | + {accept} |
| 107 | + type="file" |
| 108 | + name={id} |
| 109 | + {id} |
| 110 | + bind:this={fileInput} |
| 111 | + bind:files |
| 112 | + class:is-invalid={fileError} |
| 113 | + on:change={onFileSelected} |
| 114 | + {required} |
| 115 | + /> |
| 116 | + {#if files && files.length > 0} |
| 117 | + <button class="btn btn-outline-secondary" on:click={clearSelectedFile}> Clear </button> |
| 118 | + {/if} |
| 119 | + <span class="invalid-feedback">{fileError}</span> |
| 120 | + </div> |
| 121 | + </div> |
| 122 | + <p class="text-center mt-1 mb-1"> |
| 123 | + <i class="bi bi-file-earmark-arrow-up" /> or drag file here |
| 124 | + </p> |
| 125 | +</div> |
| 126 | + |
| 127 | +<style> |
| 128 | + .dropZone { |
| 129 | + outline: 2px dashed #00b3bb; |
| 130 | + outline-offset: -8px; |
| 131 | + padding: 10px; |
| 132 | + border-radius: 3px; |
| 133 | + } |
| 134 | +
|
| 135 | + .dragOver { |
| 136 | + background-color: #c8e5ff !important; |
| 137 | + } |
| 138 | +</style> |
0 commit comments