From 735fed89c2a65e3fe37bdf2a0defcce874cdf796 Mon Sep 17 00:00:00 2001 From: ZDDC Date: Tue, 12 May 2026 12:19:28 -0500 Subject: [PATCH] feat(browse): navigate into a .zip via the server, no whole-zip download MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When browse runs against zddc-server, expanding a top-level .zip now fetches "<…>.zip/" as a normal directory listing (the server extracts members on demand) instead of downloading the entire archive and parsing it with JSZip in the browser. Members open/preview via their real server URLs like any file. Nested zips (a .zip inside a .zip) and FS-API (offline) mode keep the existing JSZip path; offline gets migrated to a shared adapter in the next change. Co-Authored-By: Claude Opus 4.7 (1M context) --- browse/js/tree.js | 37 +++++++++++++++++++++++++++++++------ 1 file changed, 31 insertions(+), 6 deletions(-) diff --git a/browse/js/tree.js b/browse/js/tree.js index 31183fd..918fc07 100644 --- a/browse/js/tree.js +++ b/browse/js/tree.js @@ -280,18 +280,43 @@ // table); the tree.setSort() method still works but only via // programmatic callers — there's no UI for changing sort yet. + // True when this zip node lives inside another zip (so its bytes + // can't be fetched as a standalone server resource — we go through + // the parent JSZip / inner-zip download path instead). In server + // mode the URL of a zip-member-inside-a-zip contains ".zip/"; in + // FS-API mode zipParentId is set. + function zipNestedInsideZip(node) { + if (node.zipParentId != null) return true; + if (state.source === 'server') { + return pathFor(node).toLowerCase().indexOf('.zip/') !== -1; + } + return false; + } + // Load a folder's children (lazy; idempotent re-loads). Dispatches // by node kind: - // - regular folder → server JSON listing OR FS-API enumeration - // - zip file → fetch+JSZip; entries become virtual children - // - zip child dir → already-listed entries from the parent zip - // (zips are enumerated whole, so child dirs - // are pre-populated when the zip expands) + // - regular folder → server JSON listing OR FS-API enumeration + // - top-level zip, server mode → server's virtual-directory listing + // of the zip's members (no whole-zip + // download — zddc-server extracts on demand) + // - zip otherwise → fetch+JSZip; entries become virtual children + // - zip child dir → already-listed entries from the parent zip + // (the whole zip was enumerated when it + // expanded, so child dirs are pre-seeded) async function loadChildren(node) { if (node.loaded) return; try { if (node.isZip) { - await loadZipChildren(node); + if (state.source === 'server' && !zipNestedInsideZip(node)) { + // zddc-server serves "<…>.zip/" as a virtual + // directory: GET it as a listing, GET a member URL + // to extract just that file. Same shape as any + // server directory, so no JSZip on the client. + setChildren(node.id, + await loader.fetchServerChildren(pathFor(node) + '/')); + } else { + await loadZipChildren(node); + } } else if (node._zipSyntheticDir) { // Synthetic dir node materialized when a zip's entry // list referenced "a/b/file" but had no "a/" entry.