Files
Oxicloud/static/js/utils/download.js
T
Claude fd80a3de67 perf(files-ui): stream media/downloads natively, rAF rubber-band, Map selection
Four related fixes to stop buffering files in page memory and stop
hammering layout from the selection paths:

- Inline viewer media: video/audio fetched the ENTIRE file into a blob
  before the first frame (a 2 GB video = 2 GB of tab heap, no
  progressive playback, no seek). The <video>/<audio> src now points
  straight at the same-origin API URL — cookies travel automatically
  and the browser streams with native Range requests, exactly like the
  music player already did.

- Downloads (file, folder ZIP, batch ZIP, viewer button): the
  fetch → blob → objectURL pattern materialized the whole payload in
  RAM before the save dialog appeared (a 10 GB batch ZIP risked
  crashing the tab) with no download progress UI. New shared
  utils/download.js hands the URL to the browser, which streams to
  disk with its own progress UI. Batch download uses the existing GET
  endpoint (same URL contract as the drag-out DownloadURL, now shared
  via buildBatchDownloadUrl); selections whose id list cannot fit in a
  URL keep the buffered POST fallback. Trade-off: failed downloads now
  surface in the browser's download shelf instead of an in-app toast.

- Rubber-band lasso: every mousemove (>100/s) walked all cards
  interleaving getBoundingClientRect() reads with class writes — up to
  N forced reflows per event, freezing the frame rate on folders with
  thousands of loaded rows. Card rects (+ item info) are now snapshot
  once per drag (rebuilt on scroll), and a single rAF pass per frame
  compares against the cached geometry, touching only cards whose
  selection state changed.

- batchToolbar: getSelection() rebuilt the selection by scanning every
  .file-item in the document (the TODO admitted it); it now reads the
  _selected Map that every selection path already keeps in sync.
  clear() scopes its DOM sweep to #files-list (the only container the
  toolbar manages) instead of the whole document.

https://claude.ai/code/session_01Dp3oWon5GBMVn4j3QXZdgx
2026-06-10 09:02:13 +00:00

45 lines
1.6 KiB
JavaScript

// @ts-check
/**
* Browser-native download helpers.
*
* Downloads are handed to the browser as same-origin navigations: the
* response streams straight to disk with the browser's own progress UI,
* and auth cookies travel automatically. Nothing is buffered in page
* memory — unlike the old `fetch → blob → objectURL` pattern, which
* materialized the entire payload in the tab's heap before the save
* dialog could even appear.
*/
/**
* Trigger a browser-native download for a same-origin URL.
*
* @param {string} url - Same-origin URL of the resource to download
* @param {string} [filename] - Suggested file name. The server's
* `Content-Disposition` filename wins when present; an empty string
* keeps whatever the server (or URL) provides.
*/
export function triggerBrowserDownload(url, filename = '') {
const link = document.createElement('a');
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
}
/**
* Build the GET URL for the batch ZIP download endpoint
* (`GET /api/batch/download` accepts comma-separated id lists).
* Shared by the batch toolbar download and the drag-out `DownloadURL`
* builder so both stay in sync with the endpoint's query contract.
*
* @param {string[]} fileIds
* @param {string[]} folderIds
* @returns {string} Root-relative URL (prepend `window.location.origin`
* when an absolute URL is required, e.g. for `DataTransfer.setData`).
*/
export function buildBatchDownloadUrl(fileIds, folderIds) {
return `/api/batch/download?file_ids=${fileIds.join(',')}&folder_ids=${folderIds.join(',')}`;
}