diff --git a/web/src/views/Files/FilePicker.vue b/web/src/views/Files/FilePicker.vue new file mode 100644 index 0000000..3276ca9 --- /dev/null +++ b/web/src/views/Files/FilePicker.vue @@ -0,0 +1,124 @@ + + + diff --git a/web/src/views/Files/List.vue b/web/src/views/Files/List.vue new file mode 100644 index 0000000..5075725 --- /dev/null +++ b/web/src/views/Files/List.vue @@ -0,0 +1,254 @@ + + + diff --git a/web/src/views/Files/UploadButton.vue b/web/src/views/Files/UploadButton.vue new file mode 100644 index 0000000..661c6f6 --- /dev/null +++ b/web/src/views/Files/UploadButton.vue @@ -0,0 +1,141 @@ + + + diff --git a/web/src/views/Files/sha256.worker.ts b/web/src/views/Files/sha256.worker.ts new file mode 100644 index 0000000..1a48983 --- /dev/null +++ b/web/src/views/Files/sha256.worker.ts @@ -0,0 +1,34 @@ +export {} +declare const self: { + onmessage: ((e: MessageEvent) => void) | null + postMessage: (message: unknown) => void +} + +self.onmessage = async (e: MessageEvent) => { + const file = e.data.file as File + try { + // Read entire file into ArrayBuffer (with progress reporting) + const CHUNK_READ_SIZE = 16 * 1024 * 1024 // 16MB chunks for progress reporting + const buffer = new ArrayBuffer(file.size) + const view = new Uint8Array(buffer) + let offset = 0 + + while (offset < file.size) { + const end = Math.min(offset + CHUNK_READ_SIZE, file.size) + const slice = file.slice(offset, end) + const chunkBuffer = await slice.arrayBuffer() + view.set(new Uint8Array(chunkBuffer), offset) + offset = end + self.postMessage({ progress: offset / file.size }) + } + + // Compute SHA256 on entire buffer at once + const hashBuffer = await crypto.subtle.digest('SHA-256', buffer) + const hashArray = Array.from(new Uint8Array(hashBuffer)) + const sha256 = hashArray.map(b => b.toString(16).padStart(2, '0')).join('') + + self.postMessage({ sha256, progress: 1 }) + } catch (error) { + self.postMessage({ error: error instanceof Error ? error.message : 'SHA256 computation failed' }) + } +}