diff --git a/bun.lock b/bun.lock index 00773a2..590d1ad 100644 --- a/bun.lock +++ b/bun.lock @@ -15,6 +15,7 @@ "byte-data": "^19.0.1", "client-zip": "^2.5.0", "clsx": "^2.1.1", + "fflate": "^0.8.2", "lucide-svelte": "^0.544.0", "music-metadata": "^11.9.0", "overlayscrollbars": "^2.12.0", diff --git a/package.json b/package.json index fc11b0f..1d2d8d4 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "byte-data": "^19.0.1", "client-zip": "^2.5.0", "clsx": "^2.1.1", + "fflate": "^0.8.2", "lucide-svelte": "^0.544.0", "music-metadata": "^11.9.0", "overlayscrollbars": "^2.12.0", diff --git a/src/lib/store/index.svelte.ts b/src/lib/store/index.svelte.ts index aec4d94..5a14aa0 100644 --- a/src/lib/store/index.svelte.ts +++ b/src/lib/store/index.svelte.ts @@ -9,6 +9,7 @@ import PQueue from "p-queue"; import { getLocale, setLocale } from "$lib/paraglide/runtime"; import { m } from "$lib/paraglide/messages"; import sanitizeHtml from "sanitize-html"; +import { unzip } from "fflate"; class Files { public files = $state([]); @@ -140,11 +141,70 @@ class Files { } private _warningShown = false; - private _add(file: VertFile | File) { + private async _handleZipFile(file: File): Promise { + try { + log(["files"], `extracting zip file: ${file.name}`); + const arrayBuffer = await file.arrayBuffer(); + const uint8Array = new Uint8Array(arrayBuffer); + + return new Promise((resolve, reject) => { + unzip(uint8Array, (err, unzipped) => { + if (err) { + error( + ["files"], + `failed to extract zip: ${err.message}`, + ); + reject(err); + return; + } + + log( + ["files"], + `extracted ${Object.keys(unzipped).length} files from zip`, + ); + + for (const [filename, data] of Object.entries(unzipped)) { + if ( + filename.startsWith(".") || + filename.includes("/__MACOSX/") || + filename.endsWith("/") + ) + continue; + + const buffer = Array.from(data); + const extractedFile = new File( + [new Uint8Array(buffer)], + filename, + { type: "application/octet-stream" }, + ); + this._add(extractedFile); + } + + resolve(); + }); + }); + } catch (e) { + error(["files"], `error processing zip file: ${e}`); + } + } + + private async _add(file: VertFile | File) { if (file instanceof VertFile) { this.files.push(file); this._addThumbnail(file); } else { + // if zip, extract and add contents + const isZip = + file.name.toLowerCase().endsWith(".zip") || + file.type === "application/zip" || + file.type === "application/x-zip-compressed"; + + if (isZip) { + await this._handleZipFile(file); + return; + } + + // regular files const format = "." + file.name.split(".").pop()?.toLowerCase(); if (!format) { log(["files"], `no extension found for ${file.name}`);