import {
  observeResize
} from "./chunk-6VGP7A7A.js";
import {
  EventHandler
} from "./chunk-SGIVSRN5.js";
import "./chunk-JSBRDJBE.js";

// src/js-tools/js-utils/algorithms/heap.ts
var EmptyHeapError = class extends Error {
  constructor() {
    super("Heap is empty.");
  }
};
var HeapItemRef = class {
  constructor(value, index, heap) {
    this._value = value;
    this._index = index;
    this._heap = heap;
  }
  get value() {
    return this._value;
  }
};
var Heap = class _Heap {
  constructor(compare, items) {
    this._items = [];
    this._compare = compare;
    if (items) {
      for (const item of items) {
        this.insert(item);
      }
    }
  }
  get size() {
    return this._items.length;
  }
  /**
   * Insert a value. Returns a ref handle for later update/delete. O(log n).
   */
  insert(value) {
    const ref = new HeapItemRef(value, this._items.length, this);
    this._items.push(ref);
    this._bubbleUp(ref._index);
    return ref;
  }
  /**
   * Return the top value without removing it. O(1).
   * Throws `EmptyHeapError` if the heap is empty.
   */
  peek() {
    if (this._items.length === 0) throw new EmptyHeapError();
    return this._items[0]._value;
  }
  /**
   * Remove and return the top value. O(log n).
   * Throws `EmptyHeapError` if the heap is empty.
   */
  pop() {
    if (this._items.length === 0) throw new EmptyHeapError();
    const top = this._items[0]._value;
    this._removeAt(0);
    return top;
  }
  /**
   * Update a ref's value and re-heapify. O(log n).
   */
  update(ref, newValue) {
    ref._value = newValue;
    this._bubbleUp(ref._index);
    this._siftDown(ref._index);
  }
  /**
   * Remove an arbitrary element by its ref. O(log n).
   */
  delete(ref) {
    this._removeAt(ref._index);
  }
  /**
   * Return all values in sorted order (smallest first for a min-heap).
   * The heap is left unchanged. O(n log n).
   */
  toSortedArray() {
    const copy = new _Heap(this._compare);
    for (const ref of this._items) {
      copy.insert(ref._value);
    }
    const result = [];
    while (copy.size > 0) {
      result.push(copy.pop());
    }
    return result;
  }
  // -------------------------------------------------------------------------
  // Internal helpers
  // -------------------------------------------------------------------------
  _swap(i, j) {
    const a = this._items[i];
    const b = this._items[j];
    this._items[i] = b;
    this._items[j] = a;
    b._index = i;
    a._index = j;
  }
  _bubbleUp(i) {
    while (i > 0) {
      const parent = i - 1 >> 1;
      if (this._compare(this._items[i]._value, this._items[parent]._value) < 0) {
        this._swap(i, parent);
        i = parent;
      } else {
        break;
      }
    }
  }
  _siftDown(i) {
    const n = this._items.length;
    let prev;
    do {
      prev = i;
      let smallest = i;
      const left = 2 * i + 1;
      const right = 2 * i + 2;
      if (left < n && this._compare(this._items[left]._value, this._items[smallest]._value) < 0) {
        smallest = left;
      }
      if (right < n && this._compare(this._items[right]._value, this._items[smallest]._value) < 0) {
        smallest = right;
      }
      if (smallest !== i) {
        this._swap(i, smallest);
        i = smallest;
      }
    } while (i !== prev);
  }
  _removeAt(i) {
    const last = this._items.length - 1;
    if (i === last) {
      this._items.pop();
      return;
    }
    this._swap(i, last);
    this._items.pop();
    this._bubbleUp(i);
    this._siftDown(i);
  }
};

// src/js-tools/js-utils/algorithms/lru.ts
var LRU = class {
  constructor(capacity = 100, entries = []) {
    this._map = /* @__PURE__ */ new Map();
    // head = most recently used, tail = least recently used
    this._head = null;
    this._tail = null;
    this._capacity = capacity;
    for (const [k, v] of entries) {
      this.set(k, v);
    }
  }
  // -------------------------------------------------------------------------
  // Core operations
  // -------------------------------------------------------------------------
  /**
   * Return the value for `key` and promote it to MRU.
   * Returns `undefined` if the key is not in the cache.
   */
  get(key) {
    const node = this._map.get(key);
    if (!node) return void 0;
    this._moveToHead(node);
    return node.value;
  }
  /**
   * Return the value for `key` WITHOUT updating LRU order.
   * Returns `undefined` if the key is not in the cache.
   */
  peek(key) {
    return this._map.get(key)?.value;
  }
  /**
   * Insert or update `key` with `value`.
   * - If the key already exists its value is updated and it is promoted to MRU.
   * - If the cache is at capacity the least-recently-used entry is evicted.
   *
   * Returns the evicted `[key, value]` pair, or `null` if nothing was evicted.
   */
  set(key, value) {
    const existing = this._map.get(key);
    if (existing) {
      existing.value = value;
      this._moveToHead(existing);
      return null;
    }
    let evicted = null;
    if (this._map.size >= this._capacity) {
      const tail = this._tail;
      evicted = [tail.key, tail.value];
      this._map.delete(tail.key);
      this._removeNode(tail);
    }
    const node = { key, value, prev: null, next: null };
    this._map.set(key, node);
    this._addToHead(node);
    return evicted;
  }
  /**
   * Return `true` if `key` is in the cache (does NOT update LRU order).
   */
  has(key) {
    return this._map.has(key);
  }
  /**
   * Remove `key` from the cache.
   * Returns `true` if the key existed, `false` otherwise.
   */
  delete(key) {
    const node = this._map.get(key);
    if (!node) return false;
    this._map.delete(key);
    this._removeNode(node);
    return true;
  }
  /** Remove all entries. */
  clear() {
    this._map.clear();
    this._head = null;
    this._tail = null;
  }
  // -------------------------------------------------------------------------
  // Iteration (MRU → LRU order)
  // -------------------------------------------------------------------------
  get size() {
    return this._map.size;
  }
  keys() {
    const result = [];
    let node = this._head;
    while (node) {
      result.push(node.key);
      node = node.next;
    }
    return result;
  }
  values() {
    const result = [];
    let node = this._head;
    while (node) {
      result.push(node.value);
      node = node.next;
    }
    return result;
  }
  entries() {
    const result = [];
    let node = this._head;
    while (node) {
      result.push([node.key, node.value]);
      node = node.next;
    }
    return result;
  }
  forEach(callback) {
    let node = this._head;
    while (node) {
      callback(node.value, node.key, this);
      node = node.next;
    }
  }
  // -------------------------------------------------------------------------
  // Doubly-linked list helpers
  // -------------------------------------------------------------------------
  _addToHead(node) {
    node.prev = null;
    node.next = this._head;
    if (this._head) this._head.prev = node;
    this._head = node;
    if (!this._tail) this._tail = node;
  }
  _removeNode(node) {
    if (node.prev) node.prev.next = node.next;
    else this._head = node.next;
    if (node.next) node.next.prev = node.prev;
    else this._tail = node.prev;
    node.prev = null;
    node.next = null;
  }
  _moveToHead(node) {
    if (node === this._head) return;
    this._removeNode(node);
    this._addToHead(node);
  }
};

// src/js-tools/js-utils/geometry/geom.ts
function centroid(points, dim) {
  if (points.length === 0) {
    throw new RangeError("Cannot compute centroid of empty array");
  }
  const centroid2 = Array(dim).fill(0);
  for (const point of points) {
    for (let i = 0; i < dim; ++i) {
      centroid2[i] += point[i];
    }
  }
  return centroid2.map((v) => v / points.length);
}
function dot(vec1, vec2, dim) {
  let s = 0;
  for (let i = 0; i < dim; ++i) {
    s += vec1[i] * vec2[i];
  }
  return s;
}
function magnitude(vec) {
  let s = 0;
  for (let i = 0; i < vec.length; ++i) {
    s += vec[i] * vec[i];
  }
  return Math.sqrt(s);
}
function normalize(vec) {
  const mag = magnitude(vec);
  if (mag === 0) {
    throw new RangeError("Cannot normalise a zero vector");
  }
  return vec.map((v) => v / mag);
}
function projectPointOnSegment(point, seg, dim) {
  const [start, end] = seg;
  const d = Array(dim);
  let mag = 0;
  let dist = 0;
  for (let i = 0; i < dim; ++i) {
    d[i] = end[i] - start[i];
    mag += d[i] * d[i];
  }
  mag = Math.sqrt(mag);
  const uVec = d.map((v) => v / mag);
  for (let i = 0; i < dim; ++i) {
    dist += uVec[i] * (point[i] - start[i]);
  }
  return Array.from({ length: dim }, (_, i) => start[i] + dist * uVec[i]);
}
function pointSegmentDistance(point, seg, dim) {
  const pointOnSegment = projectPointOnSegment(point, seg, dim);
  let inBounds = true;
  for (let i = 0; i < dim; ++i) {
    if (pointOnSegment[i] < Math.min(seg[0][i], seg[1][i]) || pointOnSegment[i] > Math.max(seg[0][i], seg[1][i])) {
      inBounds = false;
      break;
    }
  }
  if (inBounds) {
    let dist = 0;
    let d;
    for (let i = 0; i < dim; ++i) {
      d = point[i] - pointOnSegment[i];
      dist += d * d;
    }
    return Math.sqrt(dist);
  } else {
    let distFromStart = 0;
    let distFromEnd = 0;
    let d;
    for (let i = 0; i < dim; ++i) {
      d = point[i] - seg[0][i];
      distFromStart += d * d;
      d = point[i] - seg[1][i];
      distFromEnd += d * d;
    }
    return Math.sqrt(Math.min(distFromStart, distFromEnd));
  }
}

// src/js-tools/js-utils/algorithms/kdtree.ts
function defaultQualifier(point, splitDim, splitVal) {
  return [point[splitDim] < splitVal, point[splitDim] >= splitVal];
}
var KDTree = class {
  constructor(options) {
    this._k = options.k;
    this._data = options.data;
    this._getPoint = options.getPoint;
    this._distanceTo = options.distanceTo;
    this._qualifier = options.qualifier ?? ((item, dim, val) => defaultQualifier(this._getPoint(item), dim, val));
    const indices = Array.from({ length: options.data.length }, (_, i) => i);
    const points = options.data.map(options.getPoint);
    this._root = this._buildNode(indices, points, 0);
  }
  // -------------------------------------------------------------------------
  // Public API
  // -------------------------------------------------------------------------
  /**
   * Find the k nearest neighbours to `query`.
   * Returns `[dataIndex, distance]` pairs sorted by distance ascending.
   */
  kNN(k, query) {
    if (!this._root) return [];
    const heap = new Heap((a, b) => b[0] - a[0]);
    this._knnSearch(this._root, k, query, heap);
    return heap.toSortedArray().map(([dist, idx]) => [idx, dist]).reverse();
  }
  /**
   * Find all data indices whose point lies within `bounds`.
   * `bounds` is `[minCoords, maxCoords]`, each an array of length `k`.
   */
  rangeQuery(bounds) {
    const results = [];
    if (this._root) this._rangeSearch(this._root, bounds, results);
    return results;
  }
  // -------------------------------------------------------------------------
  // Private helpers
  // -------------------------------------------------------------------------
  _buildNode(indices, points, splitDim) {
    if (indices.length === 0) return null;
    if (indices.length === 1) {
      const idx = indices[0];
      return {
        dataIndex: idx,
        point: points[idx],
        splitDim,
        splitVal: points[idx][splitDim],
        left: null,
        right: null
      };
    }
    const splitIndex = Math.floor(indices.length / 2);
    const splitVal = this._nthElement(points, indices, 0, indices.length, splitIndex, splitDim);
    const nodeIdx = indices[splitIndex];
    const leftIndices = [];
    const rightIndices = [];
    for (const i of indices) {
      if (i === nodeIdx) continue;
      const [goLeft, goRight] = this._qualifier(this._data[i], splitDim, splitVal);
      if (goLeft) leftIndices.push(i);
      if (goRight) rightIndices.push(i);
    }
    const nextDim = (splitDim + 1) % this._k;
    return {
      dataIndex: nodeIdx,
      point: points[nodeIdx],
      splitDim,
      splitVal,
      left: this._buildNode(leftIndices, points, nextDim),
      right: this._buildNode(rightIndices, points, nextDim)
    };
  }
  _knnSearch(node, k, query, heap) {
    const dist = this._distanceTo(this._data[node.dataIndex], query);
    heap.insert([dist, node.dataIndex]);
    if (heap.size > k) heap.pop();
    const distFromSplit = query[node.splitDim] - node.splitVal;
    const first = distFromSplit < 0 ? node.left : node.right;
    const second = distFromSplit < 0 ? node.right : node.left;
    if (first) this._knnSearch(first, k, query, heap);
    if (second) this._knnSearch(second, k, query, heap);
  }
  _rangeSearch(node, bounds, results) {
    let inBounds = true;
    for (let i = 0; i < this._k; i++) {
      if (node.point[i] < bounds[0][i] || node.point[i] > bounds[1][i]) {
        inBounds = false;
        break;
      }
    }
    if (inBounds) results.push(node.dataIndex);
    if (node.left && bounds[0][node.splitDim] < node.splitVal) {
      this._rangeSearch(node.left, bounds, results);
    }
    if (node.right && bounds[1][node.splitDim] >= node.splitVal) {
      this._rangeSearch(node.right, bounds, results);
    }
  }
  // Quickselect: partition indices so the element at `index` is in sorted
  // position by `splitDim`. Returns the split value.
  _nthElement(points, indices, start, end, index, splitDim) {
    if (end - start === 1) return points[indices[start]][splitDim];
    const n = end - start;
    const pivotVal = points[indices[start + Math.floor(Math.random() * n)]][splitDim];
    const pivotIdx = this._partition(points, indices, start, end, pivotVal, splitDim);
    const lessCount = pivotIdx - start;
    let equalCount = 0;
    for (let i = pivotIdx; i < end; i++) {
      if (points[indices[i]][splitDim] === pivotVal) equalCount++;
      else break;
    }
    if (lessCount > index) {
      return this._nthElement(points, indices, start, pivotIdx, index, splitDim);
    } else if (lessCount + equalCount > index) {
      return pivotVal;
    } else {
      const offset = lessCount + equalCount;
      return this._nthElement(points, indices, start + offset, end, index - offset, splitDim);
    }
  }
  _partition(points, indices, start, end, pivotVal, splitDim) {
    let j = start;
    let k = end;
    let i = start;
    while (i < k) {
      const val = points[indices[i]][splitDim];
      if (val < pivotVal) {
        [indices[i], indices[j]] = [indices[j], indices[i]];
        j++;
        i++;
      } else if (val > pivotVal) {
        k--;
        [indices[i], indices[k]] = [indices[k], indices[i]];
      } else {
        i++;
      }
    }
    return j;
  }
};
function kdPointTree(points) {
  const dim = points[0]?.length ?? 2;
  return new KDTree({
    k: dim,
    data: points,
    getPoint: (p) => p,
    distanceTo: (p, query) => {
      let s = 0;
      for (let i = 0; i < p.length; i++) {
        const d = p[i] - query[i];
        s += d * d;
      }
      return Math.sqrt(s);
    }
  });
}
function kdSegmentTree(segments) {
  const dim = segments[0]?.[0]?.length ?? 2;
  return new KDTree({
    k: dim,
    data: segments,
    getPoint: (seg) => seg[0],
    distanceTo: (seg, query) => pointSegmentDistance(query, seg, dim),
    qualifier: (seg, splitDim, splitVal) => [
      seg[0][splitDim] < splitVal || seg[1][splitDim] < splitVal,
      seg[0][splitDim] >= splitVal || seg[1][splitDim] >= splitVal
    ]
  });
}

// src/js-tools/js-utils/geometry/geom-2d.ts
var Collinear = /* @__PURE__ */ Symbol("collinear");
var Clockwise = /* @__PURE__ */ Symbol("clockwise");
var CounterClockWise = /* @__PURE__ */ Symbol("counter-clockwise");
function getPointsOrientation(p, q, r) {
  const c = (q[1] - p[1]) * (r[0] - q[0]) - (q[0] - p[0]) * (r[1] - q[1]);
  if (c > 0) {
    return Clockwise;
  } else if (c < 0) {
    return CounterClockWise;
  } else {
    return Collinear;
  }
}
function computeConvexHull(points) {
  let minPoint = points[0];
  for (let i = 1; i < points.length; ++i) {
    if (points[i][1] < minPoint[1]) {
      minPoint = points[i];
    }
  }
  const sortedPoints = points.filter((p) => p[0] !== minPoint[0] || p[1] !== minPoint[1]).map((p) => {
    const v = [p[0] - minPoint[0], p[1] - minPoint[1]];
    const m = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
    return [p, v[0] / m];
  }).sort((a, b) => a[1] - b[1]).map((a) => a[0]);
  const ch = [minPoint];
  for (let i = 0; i < sortedPoints.length + 1; ++i) {
    const r = i < sortedPoints.length ? sortedPoints[i] : minPoint;
    while (ch.length >= 2) {
      const p = ch[ch.length - 2];
      const q = ch[ch.length - 1];
      if (getPointsOrientation(p, q, r) !== Clockwise) {
        ch.pop();
      } else {
        break;
      }
    }
    ch.push(r);
  }
  return ch;
}
function pointInPolygon(point, poly) {
  const dys = poly.map((p) => p[1] - point[1]);
  let isInside = false;
  for (let i = 0; i < poly.length; ++i) {
    const nextDy = dys[(i + 1) % poly.length];
    const nextPt = poly[(i + 1) % poly.length];
    if (dys[i] < 0 && nextDy > 0 || dys[i] > 0 && nextDy < 0) {
      const df = dys[i] / (dys[i] - nextDy);
      const cx = df * nextPt[0] + (1 - df) * poly[i][0];
      if (cx > point[0]) {
        isInside = !isInside;
      }
    }
  }
  return isInside;
}
function polygonInPolygon(sourcePolygon, targetPolygon) {
  return sourcePolygon.every((point) => pointInPolygon(point, targetPolygon));
}
function hasSeparatingAxis(poly1, poly2) {
  return !Array(poly1.length - 1).fill(0).map((_, i) => i).every((i) => {
    const line = [poly1[i], poly1[i + 1]];
    const o = line[0];
    const v = [line[1][0] - o[0], line[1][1] - o[1]];
    const m = Math.sqrt(v[0] * v[0] + v[1] * v[1]);
    const n = [-v[1] / m, v[0] / m];
    let sum = 0;
    let sumOfAbs = 0;
    for (let j = 0; j < poly2.length - 1; ++j) {
      const d = (poly2[j][0] - o[0]) * n[0] + (poly2[j][1] - o[1]) * n[1];
      sum += d;
      sumOfAbs += Math.abs(d);
    }
    return Math.abs(sum) !== sumOfAbs || sum <= 0;
  });
}
function getPolygonOrientation(poly) {
  let sum = 0;
  for (let i = 1; i < poly.length; ++i) {
    sum += (poly[i][0] - poly[i - 1][0]) * (poly[i][1] + poly[i - 1][1]);
  }
  return sum > 0 ? Clockwise : CounterClockWise;
}
function normalizePolygon(poly, orientation) {
  const o = getPolygonOrientation(poly);
  if (o !== orientation) {
    return [...poly].reverse();
  } else {
    return poly;
  }
}
function intersectingPolygons(poly1, poly2) {
  poly1 = normalizePolygon(poly1, Clockwise);
  poly2 = normalizePolygon(poly2, Clockwise);
  return !hasSeparatingAxis(poly1, poly2) || !hasSeparatingAxis(poly2, poly1);
}

// src/js-tools/js-utils/browser/animation.ts
function animate(callback) {
  let ref = 0;
  let running = true;
  function animateSubroutine(time) {
    ref = 0;
    callback(time);
    if (running) {
      ref = requestAnimationFrame(animateSubroutine);
    }
  }
  ref = requestAnimationFrame(animateSubroutine);
  return () => {
    running = false;
    if (ref !== 0) {
      cancelAnimationFrame(ref);
      ref = 0;
    }
  };
}
function mixObjects(obj1, obj2, f) {
  return Object.fromEntries(
    Object.entries(obj1).map(([key, val]) => [
      key,
      val * (1 - f) + obj2[key] * f
    ])
  );
}
function transition(from, to, setState, duration) {
  let ref = 0;
  let initialized = false;
  let startTime = 0;
  function update(time) {
    ref = 0;
    if (!initialized) {
      initialized = true;
      startTime = time;
      ref = requestAnimationFrame(update);
    } else {
      setState(
        mixObjects(from, to, Math.min(1, (time - startTime) / duration))
      );
      if (time - startTime < duration) {
        ref = requestAnimationFrame(update);
      }
    }
  }
  ref = requestAnimationFrame(update);
  return () => {
    if (ref !== 0) {
      cancelAnimationFrame(ref);
      ref = 0;
    }
  };
}
function lazyAnimate(callback) {
  let needsUpdate = true;
  let updateProcess = Promise.resolve();
  async function tryUpdate() {
    if (needsUpdate) {
      needsUpdate = false;
      await callback();
      await tryUpdate();
    }
  }
  async function triggerUpdate(callback2 = () => null) {
    needsUpdate = true;
    updateProcess = updateProcess.then(tryUpdate).then(() => callback2 && callback2());
    await updateProcess;
  }
  triggerUpdate();
  return triggerUpdate;
}

// src/js-tools/js-utils/browser/cv.ts
var Failed2DContextError = class extends Error {
  constructor() {
    super("Failed to get 2d context.");
  }
};
async function imageFromDataUrl(dataUrl) {
  const img = document.createElement("img");
  await new Promise((resolve, reject) => {
    img.src = dataUrl;
    img.onload = resolve;
    img.onerror = reject;
  });
  img.onload = null;
  img.onerror = null;
  return img;
}
var imagefromDataUrl = imageFromDataUrl;
async function imageFromCanvas(canvas) {
  return imageFromDataUrl(canvas.toDataURL());
}
function resizeImage(img, width, height) {
  const aspect = img.naturalWidth / img.naturalHeight;
  if (width === void 0) {
    width = height * aspect;
  }
  if (height === void 0) {
    height = width / aspect;
  }
  if (img.naturalHeight === height && img.naturalWidth === width) {
    return img;
  }
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext("2d");
  if (!ctx) {
    throw new Failed2DContextError();
  }
  ctx.drawImage(
    img,
    0,
    0,
    img.naturalWidth,
    img.naturalHeight,
    0,
    0,
    canvas.width,
    canvas.height
  );
  return canvas;
}
function cropImage(img, sx, sy, width, height) {
  const canvas = document.createElement("canvas");
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext("2d");
  if (!ctx) {
    throw new Failed2DContextError();
  }
  ctx.drawImage(img, sx, sy, width, height, 0, 0, width, height);
  return canvas;
}

// src/js-tools/js-utils/browser/file-utils.ts
function downloadFile(name, content) {
  const element = document.createElement("a");
  element.setAttribute(
    "href",
    "data:text/plain;charset=utf-8," + encodeURIComponent(content)
  );
  element.setAttribute("download", name);
  element.style.display = "none";
  document.body.appendChild(element);
  element.click();
  document.body.removeChild(element);
}
function downloadCSVRows(name, rows) {
  const content = rows.map((r) => r.join(",")).join("\r\n");
  return downloadFile(name, content);
}
function downloadCSVColumns(name, columns) {
  if (columns.length === 0) return downloadFile(name, "");
  const n = Math.max(...columns.map((c) => c.length));
  const m = columns.length;
  const rows = Array(n).fill(null).map(
    (_, i) => Array(m).fill(null).map((_2, j) => i < columns[j].length ? columns[j][i] : "")
  );
  return downloadCSVRows(name, rows);
}

// src/js-tools/js-utils/browser/request.ts
import isString from "lodash-es/isString";
var globalOptions = {
  baseUrl: ""
};
function requestGlobalOptions({ baseUrl }) {
  globalOptions.baseUrl = baseUrl;
}
function createApiUrl(url) {
  return globalOptions.baseUrl + url;
}
async function request(url, options = {}) {
  const onProgress = options.onProgress;
  let res;
  if (isString(url)) {
    res = await fetch(createApiUrl(url), options);
  } else if (url instanceof Response) {
    res = url;
  } else {
    res = new Response(url);
  }
  if (res.status >= 400) {
    return res;
  } else {
    return new Response(
      new ReadableStream({
        async start(controller) {
          if (res.body) {
            const reader = res.body.getReader();
            let results = await reader.read();
            let progress = 0;
            while (!results.done) {
              const { value } = results;
              progress += value.byteLength;
              controller.enqueue(value);
              if (onProgress) {
                onProgress(progress);
              }
              results = await reader.read();
            }
          }
          controller.close();
        }
      }),
      res
    );
  }
}

// src/js-tools/js-utils/util/format.ts
function formatBytes(bytes, decimals = 2) {
  if (!+bytes) return "0 Bytes";
  if (bytes < 0 || !isFinite(bytes)) {
    throw new RangeError(`Invalid byte value: ${bytes}`);
  }
  const k = 1024;
  const dm = decimals < 0 ? 0 : decimals;
  const sizes = [
    "Bytes",
    "KiB",
    "MiB",
    "GiB",
    "TiB",
    "PiB",
    "EiB",
    "ZiB",
    "YiB"
  ];
  const i = Math.floor(Math.log(bytes) / Math.log(k));
  return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
}

// src/js-tools/js-utils/util/functional-tools.ts
import isFunction from "lodash-es/isFunction";
function mapEffect(collection, effect) {
  const unsetters = collection.map(effect);
  return () => {
    unsetters.forEach((unset) => isFunction(unset) && unset());
  };
}

// src/js-tools/js-utils/util/path-utils.ts
function join(...path) {
  const parts = Array();
  for (const p of path) {
    parts.push(...p.split("/").filter((s) => s !== ""));
  }
  if (parts.length === 0) {
    return "";
  } else {
    return `/${parts.join("/")}/`;
  }
}
export {
  Clockwise,
  CounterClockWise,
  EmptyHeapError,
  EventHandler,
  Failed2DContextError,
  Heap,
  HeapItemRef,
  KDTree,
  LRU,
  animate,
  centroid,
  computeConvexHull,
  createApiUrl,
  cropImage,
  dot,
  downloadCSVColumns,
  downloadCSVRows,
  downloadFile,
  formatBytes,
  getPointsOrientation,
  imageFromCanvas,
  imageFromDataUrl,
  imagefromDataUrl,
  intersectingPolygons,
  join,
  kdPointTree,
  kdSegmentTree,
  lazyAnimate,
  magnitude,
  mapEffect,
  normalize,
  normalizePolygon,
  observeResize,
  pointInPolygon,
  pointSegmentDistance,
  polygonInPolygon,
  projectPointOnSegment,
  request,
  requestGlobalOptions,
  resizeImage,
  transition
};
