alinea
Version:
[](https://npmjs.org/package/alinea) [](https://packagephobia.com/result?p=alinea)
826 lines (823 loc) • 28.7 kB
JavaScript
import {
rgbaToThumbHash,
thumbHashToAverageRGBA
} from "../../chunks/chunk-57Y4IQL4.js";
import {
I,
m
} from "../../chunks/chunk-57QP2MGK.js";
import {
useAtom,
useSetAtom
} from "../../chunks/chunk-WF77DMLN.js";
import {
atom
} from "../../chunks/chunk-OBOPLPUQ.js";
import {
pLimit
} from "../../chunks/chunk-QQTYTFWR.js";
import {
__commonJS,
__toESM
} from "../../chunks/chunk-U5RRZUYZ.js";
// node_modules/smartcrop/smartcrop.js
var require_smartcrop = __commonJS({
"node_modules/smartcrop/smartcrop.js"(exports, module) {
(function() {
"use strict";
var smartcrop2 = {};
function NoPromises() {
throw new Error("No native promises and smartcrop.Promise not set.");
}
smartcrop2.Promise = typeof Promise !== "undefined" ? Promise : NoPromises;
smartcrop2.DEFAULTS = {
width: 0,
height: 0,
aspect: 0,
cropWidth: 0,
cropHeight: 0,
detailWeight: 0.2,
skinColor: [0.78, 0.57, 0.44],
skinBias: 0.01,
skinBrightnessMin: 0.2,
skinBrightnessMax: 1,
skinThreshold: 0.8,
skinWeight: 1.8,
saturationBrightnessMin: 0.05,
saturationBrightnessMax: 0.9,
saturationThreshold: 0.4,
saturationBias: 0.2,
saturationWeight: 0.1,
// Step * minscale rounded down to the next power of two should be good
scoreDownSample: 8,
step: 8,
scaleStep: 0.1,
minScale: 1,
maxScale: 1,
edgeRadius: 0.4,
edgeWeight: -20,
outsideImportance: -0.5,
boostWeight: 100,
ruleOfThirds: true,
prescale: true,
imageOperations: null,
canvasFactory: defaultCanvasFactory,
// Factory: defaultFactories,
debug: false
};
smartcrop2.crop = function(inputImage, options_, callback) {
var options = extend({}, smartcrop2.DEFAULTS, options_);
if (options.aspect) {
options.width = options.aspect;
options.height = 1;
}
if (options.imageOperations === null) {
options.imageOperations = canvasImageOperations(options.canvasFactory);
}
var iop = options.imageOperations;
var scale = 1;
var prescale = 1;
return iop.open(inputImage, options.input).then(function(image) {
if (options.width && options.height) {
scale = min(
image.width / options.width,
image.height / options.height
);
options.cropWidth = ~~(options.width * scale);
options.cropHeight = ~~(options.height * scale);
options.minScale = min(
options.maxScale,
max(1 / scale, options.minScale)
);
if (options.prescale !== false) {
prescale = min(max(256 / image.width, 256 / image.height), 1);
if (prescale < 1) {
image = iop.resample(
image,
image.width * prescale,
image.height * prescale
);
options.cropWidth = ~~(options.cropWidth * prescale);
options.cropHeight = ~~(options.cropHeight * prescale);
if (options.boost) {
options.boost = options.boost.map(function(boost) {
return {
x: ~~(boost.x * prescale),
y: ~~(boost.y * prescale),
width: ~~(boost.width * prescale),
height: ~~(boost.height * prescale),
weight: boost.weight
};
});
}
} else {
prescale = 1;
}
}
}
return image;
}).then(function(image) {
return iop.getData(image).then(function(data) {
var result = analyse(options, data);
var crops = result.crops || [result.topCrop];
for (var i = 0, iLen = crops.length; i < iLen; i++) {
var crop = crops[i];
crop.x = ~~(crop.x / prescale);
crop.y = ~~(crop.y / prescale);
crop.width = ~~(crop.width / prescale);
crop.height = ~~(crop.height / prescale);
}
if (callback)
callback(result);
return result;
});
});
};
smartcrop2.isAvailable = function(options) {
if (!smartcrop2.Promise)
return false;
var canvasFactory = options ? options.canvasFactory : defaultCanvasFactory;
if (canvasFactory === defaultCanvasFactory) {
var c = document.createElement("canvas");
if (!c.getContext("2d")) {
return false;
}
}
return true;
};
function edgeDetect(i, o) {
var id = i.data;
var od = o.data;
var w = i.width;
var h = i.height;
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var p = (y * w + x) * 4;
var lightness;
if (x === 0 || x >= w - 1 || y === 0 || y >= h - 1) {
lightness = sample(id, p);
} else {
lightness = sample(id, p) * 4 - sample(id, p - w * 4) - sample(id, p - 4) - sample(id, p + 4) - sample(id, p + w * 4);
}
od[p + 1] = lightness;
}
}
}
function skinDetect(options, i, o) {
var id = i.data;
var od = o.data;
var w = i.width;
var h = i.height;
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var p = (y * w + x) * 4;
var lightness = cie(id[p], id[p + 1], id[p + 2]) / 255;
var skin = skinColor(options, id[p], id[p + 1], id[p + 2]);
var isSkinColor = skin > options.skinThreshold;
var isSkinBrightness = lightness >= options.skinBrightnessMin && lightness <= options.skinBrightnessMax;
if (isSkinColor && isSkinBrightness) {
od[p] = (skin - options.skinThreshold) * (255 / (1 - options.skinThreshold));
} else {
od[p] = 0;
}
}
}
}
function saturationDetect(options, i, o) {
var id = i.data;
var od = o.data;
var w = i.width;
var h = i.height;
for (var y = 0; y < h; y++) {
for (var x = 0; x < w; x++) {
var p = (y * w + x) * 4;
var lightness = cie(id[p], id[p + 1], id[p + 2]) / 255;
var sat = saturation(id[p], id[p + 1], id[p + 2]);
var acceptableSaturation = sat > options.saturationThreshold;
var acceptableLightness = lightness >= options.saturationBrightnessMin && lightness <= options.saturationBrightnessMax;
if (acceptableLightness && acceptableSaturation) {
od[p + 2] = (sat - options.saturationThreshold) * (255 / (1 - options.saturationThreshold));
} else {
od[p + 2] = 0;
}
}
}
}
function applyBoosts(options, output) {
if (!options.boost)
return;
var od = output.data;
for (var i = 0; i < output.width; i += 4) {
od[i + 3] = 0;
}
for (i = 0; i < options.boost.length; i++) {
applyBoost(options.boost[i], options, output);
}
}
function applyBoost(boost, options, output) {
var od = output.data;
var w = output.width;
var x0 = ~~boost.x;
var x1 = ~~(boost.x + boost.width);
var y0 = ~~boost.y;
var y1 = ~~(boost.y + boost.height);
var weight = boost.weight * 255;
for (var y = y0; y < y1; y++) {
for (var x = x0; x < x1; x++) {
var i = (y * w + x) * 4;
od[i + 3] += weight;
}
}
}
function generateCrops(options, width, height) {
var results = [];
var minDimension = min(width, height);
var cropWidth = options.cropWidth || minDimension;
var cropHeight = options.cropHeight || minDimension;
for (var scale = options.maxScale; scale >= options.minScale; scale -= options.scaleStep) {
for (var y = 0; y + cropHeight * scale <= height; y += options.step) {
for (var x = 0; x + cropWidth * scale <= width; x += options.step) {
results.push({
x,
y,
width: cropWidth * scale,
height: cropHeight * scale
});
}
}
}
return results;
}
function score(options, output, crop) {
var result = {
detail: 0,
saturation: 0,
skin: 0,
boost: 0,
total: 0
};
var od = output.data;
var downSample2 = options.scoreDownSample;
var invDownSample = 1 / downSample2;
var outputHeightDownSample = output.height * downSample2;
var outputWidthDownSample = output.width * downSample2;
var outputWidth = output.width;
for (var y = 0; y < outputHeightDownSample; y += downSample2) {
for (var x = 0; x < outputWidthDownSample; x += downSample2) {
var p = (~~(y * invDownSample) * outputWidth + ~~(x * invDownSample)) * 4;
var i = importance(options, crop, x, y);
var detail = od[p + 1] / 255;
result.skin += od[p] / 255 * (detail + options.skinBias) * i;
result.detail += detail * i;
result.saturation += od[p + 2] / 255 * (detail + options.saturationBias) * i;
result.boost += od[p + 3] / 255 * i;
}
}
result.total = (result.detail * options.detailWeight + result.skin * options.skinWeight + result.saturation * options.saturationWeight + result.boost * options.boostWeight) / (crop.width * crop.height);
return result;
}
function importance(options, crop, x, y) {
if (crop.x > x || x >= crop.x + crop.width || crop.y > y || y >= crop.y + crop.height) {
return options.outsideImportance;
}
x = (x - crop.x) / crop.width;
y = (y - crop.y) / crop.height;
var px = abs(0.5 - x) * 2;
var py = abs(0.5 - y) * 2;
var dx = Math.max(px - 1 + options.edgeRadius, 0);
var dy = Math.max(py - 1 + options.edgeRadius, 0);
var d = (dx * dx + dy * dy) * options.edgeWeight;
var s = 1.41 - sqrt(px * px + py * py);
if (options.ruleOfThirds) {
s += Math.max(0, s + d + 0.5) * 1.2 * (thirds(px) + thirds(py));
}
return s + d;
}
smartcrop2.importance = importance;
function skinColor(options, r, g, b) {
var mag = sqrt(r * r + g * g + b * b);
var rd = r / mag - options.skinColor[0];
var gd = g / mag - options.skinColor[1];
var bd = b / mag - options.skinColor[2];
var d = sqrt(rd * rd + gd * gd + bd * bd);
return 1 - d;
}
function analyse(options, input) {
var result = {};
var output = new ImgData(input.width, input.height);
edgeDetect(input, output);
skinDetect(options, input, output);
saturationDetect(options, input, output);
applyBoosts(options, output);
var scoreOutput = downSample(output, options.scoreDownSample);
var topScore = -Infinity;
var topCrop = null;
var crops = generateCrops(options, input.width, input.height);
for (var i = 0, iLen = crops.length; i < iLen; i++) {
var crop = crops[i];
crop.score = score(options, scoreOutput, crop);
if (crop.score.total > topScore) {
topCrop = crop;
topScore = crop.score.total;
}
}
result.topCrop = topCrop;
if (options.debug && topCrop) {
result.crops = crops;
result.debugOutput = output;
result.debugOptions = options;
result.debugTopCrop = extend({}, result.topCrop);
}
return result;
}
function ImgData(width, height, data) {
this.width = width;
this.height = height;
if (data) {
this.data = new Uint8ClampedArray(data);
} else {
this.data = new Uint8ClampedArray(width * height * 4);
}
}
smartcrop2.ImgData = ImgData;
function downSample(input, factor) {
var idata = input.data;
var iwidth = input.width;
var width = Math.floor(input.width / factor);
var height = Math.floor(input.height / factor);
var output = new ImgData(width, height);
var data = output.data;
var ifactor2 = 1 / (factor * factor);
for (var y = 0; y < height; y++) {
for (var x = 0; x < width; x++) {
var i = (y * width + x) * 4;
var r = 0;
var g = 0;
var b = 0;
var a = 0;
var mr = 0;
var mg = 0;
for (var v = 0; v < factor; v++) {
for (var u = 0; u < factor; u++) {
var j = ((y * factor + v) * iwidth + (x * factor + u)) * 4;
r += idata[j];
g += idata[j + 1];
b += idata[j + 2];
a += idata[j + 3];
mr = Math.max(mr, idata[j]);
mg = Math.max(mg, idata[j + 1]);
}
}
data[i] = r * ifactor2 * 0.5 + mr * 0.5;
data[i + 1] = g * ifactor2 * 0.7 + mg * 0.3;
data[i + 2] = b * ifactor2;
data[i + 3] = a * ifactor2;
}
}
return output;
}
smartcrop2._downSample = downSample;
function defaultCanvasFactory(w, h) {
var c = document.createElement("canvas");
c.width = w;
c.height = h;
return c;
}
function canvasImageOperations(canvasFactory) {
return {
// Takes imageInput as argument
// returns an object which has at least
// {width: n, height: n}
open: function(image) {
var w = image.naturalWidth || image.width;
var h = image.naturalHeight || image.height;
var c = canvasFactory(w, h);
var ctx = c.getContext("2d");
if (image.naturalWidth && (image.naturalWidth != image.width || image.naturalHeight != image.height)) {
c.width = image.naturalWidth;
c.height = image.naturalHeight;
} else {
c.width = image.width;
c.height = image.height;
}
ctx.drawImage(image, 0, 0);
return smartcrop2.Promise.resolve(c);
},
// Takes an image (as returned by open), and changes it's size by resampling
resample: function(image, width, height) {
return Promise.resolve(image).then(function(image2) {
var c = canvasFactory(~~width, ~~height);
var ctx = c.getContext("2d");
ctx.drawImage(
image2,
0,
0,
image2.width,
image2.height,
0,
0,
c.width,
c.height
);
return smartcrop2.Promise.resolve(c);
});
},
getData: function(image) {
return Promise.resolve(image).then(function(c) {
var ctx = c.getContext("2d");
var id = ctx.getImageData(0, 0, c.width, c.height);
return new ImgData(c.width, c.height, id.data);
});
}
};
}
smartcrop2._canvasImageOperations = canvasImageOperations;
var min = Math.min;
var max = Math.max;
var abs = Math.abs;
var sqrt = Math.sqrt;
function extend(o) {
for (var i = 1, iLen = arguments.length; i < iLen; i++) {
var arg = arguments[i];
if (arg) {
for (var name in arg) {
o[name] = arg[name];
}
}
}
return o;
}
function thirds(x) {
x = ((x - 1 / 3 + 1) % 2 * 0.5 - 0.5) * 16;
return Math.max(1 - x * x, 0);
}
function cie(r, g, b) {
return 0.5126 * b + 0.7152 * g + 0.0722 * r;
}
function sample(id, p) {
return cie(id[p], id[p + 1], id[p + 2]);
}
function saturation(r, g, b) {
var maximum = max(r / 255, g / 255, b / 255);
var minimum = min(r / 255, g / 255, b / 255);
if (maximum === minimum) {
return 0;
}
var l = (maximum + minimum) / 2;
var d = maximum - minimum;
return l > 0.5 ? d / (2 - maximum - minimum) : d / (maximum + minimum);
}
if (typeof define !== "undefined" && define.amd)
define(function() {
return smartcrop2;
});
if (typeof exports !== "undefined")
exports.smartcrop = smartcrop2;
else if (typeof navigator !== "undefined")
window.SmartCrop = window.smartcrop = smartcrop2;
if (typeof module !== "undefined") {
module.exports = smartcrop2;
}
})();
}
});
// src/dashboard/hook/UseUploads.ts
import { Media } from "alinea/backend/Media";
import { createFileHash } from "alinea/backend/util/ContentHash";
import {
Entry,
EntryPhase,
HttpError,
Workspace
} from "alinea/core";
import { entryFileName, entryFilepath } from "alinea/core/EntryFilenames";
import { createId } from "alinea/core/Id";
import { MutationType } from "alinea/core/Mutation";
import { base64 } from "alinea/core/util/Encoding";
import { createEntryRow } from "alinea/core/util/EntryRows";
import { generateKeyBetween } from "alinea/core/util/FractionalIndexing";
import {
basename,
dirname,
extname,
join,
normalize
} from "alinea/core/util/Paths";
var import_smartcrop = __toESM(require_smartcrop(), 1);
import { useEffect } from "react";
import { useMutate } from "../atoms/DbAtoms.js";
import { errorAtom } from "../atoms/ErrorAtoms.js";
import { withResolvers } from "../util/WithResolvers.js";
import { useConfig } from "./UseConfig.js";
import { useGraph } from "./UseGraph.js";
import { useSession } from "./UseSession.js";
var UploadStatus = /* @__PURE__ */ ((UploadStatus2) => {
UploadStatus2[UploadStatus2["Queued"] = 0] = "Queued";
UploadStatus2[UploadStatus2["CreatingPreview"] = 1] = "CreatingPreview";
UploadStatus2[UploadStatus2["Uploading"] = 2] = "Uploading";
UploadStatus2[UploadStatus2["Uploaded"] = 3] = "Uploaded";
UploadStatus2[UploadStatus2["Done"] = 4] = "Done";
return UploadStatus2;
})(UploadStatus || {});
var defaultTasker = pLimit(Infinity);
var cpuTasker = pLimit(1);
var networkTasker = pLimit(8);
var batchTasker = pLimit(1);
var tasker = {
[0 /* Queued */]: defaultTasker,
[1 /* CreatingPreview */]: cpuTasker,
[2 /* Uploading */]: networkTasker,
[3 /* Uploaded */]: defaultTasker,
[4 /* Done */]: defaultTasker
};
async function process(upload, publishUpload, client) {
switch (upload.status) {
case 0 /* Queued */:
const isImage = Media.isImage(upload.file.name);
const next = isImage ? 1 /* CreatingPreview */ : 2 /* Uploading */;
return { ...upload, status: next };
case 1 /* CreatingPreview */: {
const url = URL.createObjectURL(upload.file);
const image = await new Promise((resolve, reject) => {
const image2 = new Image();
image2.onload = () => resolve(image2);
image2.onerror = (err) => reject(err);
image2.src = url;
}).finally(() => URL.revokeObjectURL(url));
const size = Math.max(image.width, image.height);
const thumbW = Math.round(100 * image.width / size);
const thumbH = Math.round(100 * image.height / size);
const thumbCanvas = document.createElement("canvas");
const thumbContext = thumbCanvas.getContext("2d");
thumbCanvas.width = thumbW;
thumbCanvas.height = thumbH;
thumbContext.drawImage(image, 0, 0, thumbW, thumbH);
const pixels = thumbContext.getImageData(0, 0, thumbW, thumbH);
const thumbHash = rgbaToThumbHash(thumbW, thumbH, pixels.data);
const { r, g, b, a } = thumbHashToAverageRGBA(thumbHash);
const averageColor = I(m(r * 255, g * 255, b * 255, a));
const previewW = Math.min(
Math.round(160 * image.width / size),
image.width
);
const previewH = Math.min(
Math.round(160 * image.height / size),
image.height
);
const previewCanvas = document.createElement("canvas");
const previewContext = previewCanvas.getContext("2d");
previewContext.imageSmoothingEnabled = true;
previewContext.imageSmoothingQuality = "high";
previewCanvas.width = previewW;
previewCanvas.height = previewH;
previewContext.drawImage(image, 0, 0, previewW, previewH);
const preview = previewCanvas.toDataURL("image/webp");
const crop = await import_smartcrop.default.crop(image, { width: 100, height: 100 });
const focus = {
x: (crop.topCrop.x + crop.topCrop.width / 2) / image.width,
y: (crop.topCrop.y + crop.topCrop.height / 2) / image.height
};
return {
...upload,
preview,
averageColor,
focus,
thumbHash: base64.stringify(thumbHash),
width: image.naturalWidth,
height: image.naturalHeight,
status: 2 /* Uploading */
};
}
case 2 /* Uploading */: {
const fileName = upload.file.name;
const file = join(upload.to.directory, fileName);
const info = await client.prepareUpload(file);
await fetch(info.upload.url, {
method: info.upload.method ?? "POST",
body: upload.file
}).then(async (result) => {
if (!result.ok)
throw new HttpError(
result.status,
`Could not reach server for upload`
);
});
return { ...upload, info, status: 3 /* Uploaded */ };
}
case 3 /* Uploaded */: {
const { replace } = upload;
const info = upload.info;
const entry = await publishUpload(upload);
return { ...upload, result: entry, status: 4 /* Done */ };
}
case 4 /* Done */:
throw new Error("Should not end up here");
}
}
function createBatch(mutate) {
let trigger = withResolvers();
let nextRun = void 0;
const batch = [];
async function run() {
const todo = batch.splice(0, batch.length);
try {
await batchTasker(() => mutate(...todo));
trigger.resolve(void 0);
} catch (error) {
trigger.reject(error);
} finally {
trigger = withResolvers();
}
}
return (...mutations) => {
batch.push(...mutations);
clearTimeout(nextRun);
nextRun = setTimeout(run, 200);
return trigger.promise;
};
}
var uploadsAtom = atom([]);
function useUploads(onSelect) {
const config = useConfig();
const graph = useGraph();
const { cnx: client } = useSession();
const mutate = useMutate();
const setErrorAtom = useSetAtom(errorAtom);
const [uploads, setUploads] = useAtom(uploadsAtom);
const batch = createBatch(mutate);
useEffect(() => {
return () => setUploads([]);
}, []);
async function batchMutations(...mutations) {
await batch(...mutations);
}
async function createEntry(upload2) {
const entryId = upload2.info?.entryId ?? createId();
const { parentId } = upload2.to;
const buffer = await upload2.file.arrayBuffer();
const parent = await graph.preferPublished.maybeGet(
Entry({ entryId: parentId }).select({
level: Entry.level,
entryId: Entry.entryId,
url: Entry.url,
path: Entry.path,
parentPaths({ parents }) {
return parents().select(Entry.path);
}
})
);
const prev = await graph.preferPublished.maybeGet(Entry({ parent: parentId }));
const extension = extname(upload2.file.name.toLowerCase());
const path = basename(upload2.file.name.toLowerCase(), extension);
const entryLocation = {
workspace: upload2.to.workspace,
root: upload2.to.root,
locale: null,
path,
phase: EntryPhase.Published
};
const filePath = entryFilepath(
config,
entryLocation,
parent ? parent.parentPaths.concat(parent.path) : []
);
const parentDir = dirname(filePath);
const { location } = upload2.info;
const workspace = Workspace.data(config.workspaces[upload2.to.workspace]);
const prefix = workspace.mediaDir && normalize(workspace.mediaDir);
const fileLocation = prefix && location.startsWith(prefix) ? location.slice(prefix.length) : location;
const hash = await createFileHash(new Uint8Array(buffer));
const entry = await createEntryRow(config, {
...entryLocation,
parent: parent?.entryId ?? null,
entryId,
type: "MediaFile",
url: (parent ? parent.url : "") + "/" + path,
title: basename(path, extension),
seeded: false,
modifiedAt: Date.now(),
searchableText: "",
index: generateKeyBetween(null, prev?.index ?? null),
i18nId: entryId,
level: parent ? parent.level + 1 : 0,
parentDir,
filePath,
childrenDir: filePath.slice(0, -".json".length),
active: true,
main: true,
data: {
title: basename(path, extension),
location: fileLocation,
extension,
size: buffer.byteLength,
hash,
width: upload2.width,
height: upload2.height,
averageColor: upload2.averageColor,
focus: upload2.focus,
thumbHash: upload2.thumbHash,
preview: upload2.preview
}
});
const file = entryFileName(
config,
entry,
parent ? parent.parentPaths.concat(parent.path) : []
);
return { file, entry };
}
async function uploadFile(upload2) {
function update(upload3) {
setUploads((current) => {
const result = current.slice();
const index = current.findIndex((u) => u.id === upload3.id);
if (index === -1)
return result;
result[index] = upload3;
return result;
});
}
while (true) {
const next = await tasker[upload2.status](
() => process(upload2, publishUpload, client)
).catch((error) => {
return { ...upload2, error, status: 4 /* Done */ };
});
update(next);
if (next.status === 4 /* Done */) {
if (next.error) {
setErrorAtom(next.error.message, next.error);
}
const result = next.result;
if (!result)
break;
onSelect?.(result);
break;
} else {
upload2 = next;
}
}
}
async function publishUpload(upload2) {
const { replace } = upload2;
const info = upload2.info;
const { file, entry } = await createEntry(upload2);
if (!replace) {
await batchMutations(
{
type: MutationType.Create,
entryId: entry.entryId,
file,
entry
},
{
type: MutationType.Upload,
entryId: entry.entryId,
url: info.previewUrl,
file: info.location
}
);
return entry;
}
const newEntry = await createEntryRow(config, {
...replace.entry,
data: { ...entry.data, title: replace.entry.title }
});
const mediaFile = replace.entry.data;
await batchMutations(
{
type: MutationType.Edit,
entryId: replace.entry.entryId,
file: replace.entryFile,
entry: newEntry
},
{
type: MutationType.Upload,
entryId: replace.entry.entryId,
url: info.previewUrl,
file: info.location
},
{
type: MutationType.FileRemove,
entryId: replace.entry.entryId,
file: replace.entryFile,
workspace: replace.entry.workspace,
location: Media.ORIGINAL_LOCATION in mediaFile ? mediaFile[Media.ORIGINAL_LOCATION] : mediaFile.location,
replace: true
}
);
return newEntry;
}
async function upload(files, to, replace) {
const uploads2 = Array.from(files).map((file) => {
return { id: createId(), file, to, replace, status: 0 /* Queued */ };
});
setUploads((current) => [...uploads2, ...current]);
return Promise.all(uploads2.map(uploadFile));
}
return { upload, uploads };
}
export {
UploadStatus,
useUploads
};