// node_modules/fflate/esm/browser.js var u8 = Uint8Array; var u16 = Uint16Array; var i32 = Int32Array; var fleb = new u8([ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, /* unused */ 0, 0, /* impossible */ 0 ]); var fdeb = new u8([ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, /* unused */ 0, 0 ]); var clim = new u8([16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15]); var freb = function(eb, start) { var b = new u16(31); for (var i = 0; i < 31; ++i) { b[i] = start += 1 << eb[i - 1]; } var r = new i32(b[30]); for (var i = 1; i < 30; ++i) { for (var j = b[i]; j < b[i + 1]; ++j) { r[j] = j - b[i] << 5 | i; } } return { b, r }; }; var _a = freb(fleb, 2); var fl = _a.b; var revfl = _a.r; fl[28] = 258, revfl[258] = 28; var _b = freb(fdeb, 0); var fd = _b.b; var revfd = _b.r; var rev = new u16(32768); for (i = 0; i < 32768; ++i) { x = (i & 43690) >> 1 | (i & 21845) << 1; x = (x & 52428) >> 2 | (x & 13107) << 2; x = (x & 61680) >> 4 | (x & 3855) << 4; rev[i] = ((x & 65280) >> 8 | (x & 255) << 8) >> 1; } var x; var i; var hMap = (function(cd, mb, r) { var s = cd.length; var i = 0; var l = new u16(mb); for (; i < s; ++i) { if (cd[i]) ++l[cd[i] - 1]; } var le = new u16(mb); for (i = 1; i < mb; ++i) { le[i] = le[i - 1] + l[i - 1] << 1; } var co; if (r) { co = new u16(1 << mb); var rvb = 15 - mb; for (i = 0; i < s; ++i) { if (cd[i]) { var sv = i << 4 | cd[i]; var r_1 = mb - cd[i]; var v = le[cd[i] - 1]++ << r_1; for (var m = v | (1 << r_1) - 1; v <= m; ++v) { co[rev[v] >> rvb] = sv; } } } } else { co = new u16(s); for (i = 0; i < s; ++i) { if (cd[i]) { co[i] = rev[le[cd[i] - 1]++] >> 15 - cd[i]; } } } return co; }); var flt = new u8(288); for (i = 0; i < 144; ++i) flt[i] = 8; var i; for (i = 144; i < 256; ++i) flt[i] = 9; var i; for (i = 256; i < 280; ++i) flt[i] = 7; var i; for (i = 280; i < 288; ++i) flt[i] = 8; var i; var fdt = new u8(32); for (i = 0; i < 32; ++i) fdt[i] = 5; var i; var flrm = /* @__PURE__ */ hMap(flt, 9, 1); var fdrm = /* @__PURE__ */ hMap(fdt, 5, 1); var max = function(a) { var m = a[0]; for (var i = 1; i < a.length; ++i) { if (a[i] > m) m = a[i]; } return m; }; var bits = function(d, p, m) { var o = p / 8 | 0; return (d[o] | d[o + 1] << 8) >> (p & 7) & m; }; var bits16 = function(d, p) { var o = p / 8 | 0; return (d[o] | d[o + 1] << 8 | d[o + 2] << 16) >> (p & 7); }; var shft = function(p) { return (p + 7) / 8 | 0; }; var slc = function(v, s, e) { if (s == null || s < 0) s = 0; if (e == null || e > v.length) e = v.length; return new u8(v.subarray(s, e)); }; var ec = [ "unexpected EOF", "invalid block type", "invalid length/literal", "invalid distance", "stream finished", "no stream handler", , // determined by compression function "no callback", "invalid UTF-8 data", "extra field too long", "date not in range 1980-2099", "filename too long", "stream finishing", "invalid zip data" // determined by unknown compression method ]; var err = function(ind, msg, nt) { var e = new Error(msg || ec[ind]); e.code = ind; if (Error.captureStackTrace) Error.captureStackTrace(e, err); if (!nt) throw e; return e; }; var inflt = function(dat, st, buf, dict) { var sl = dat.length, dl = dict ? dict.length : 0; if (!sl || st.f && !st.l) return buf || new u8(0); var noBuf = !buf; var resize = noBuf || st.i != 2; var noSt = st.i; if (noBuf) buf = new u8(sl * 3); var cbuf = function(l2) { var bl = buf.length; if (l2 > bl) { var nbuf = new u8(Math.max(bl * 2, l2)); nbuf.set(buf); buf = nbuf; } }; var final = st.f || 0, pos = st.p || 0, bt = st.b || 0, lm = st.l, dm = st.d, lbt = st.m, dbt = st.n; var tbts = sl * 8; do { if (!lm) { final = bits(dat, pos, 1); var type = bits(dat, pos + 1, 3); pos += 3; if (!type) { var s = shft(pos) + 4, l = dat[s - 4] | dat[s - 3] << 8, t = s + l; if (t > sl) { if (noSt) err(0); break; } if (resize) cbuf(bt + l); buf.set(dat.subarray(s, t), bt); st.b = bt += l, st.p = pos = t * 8, st.f = final; continue; } else if (type == 1) lm = flrm, dm = fdrm, lbt = 9, dbt = 5; else if (type == 2) { var hLit = bits(dat, pos, 31) + 257, hcLen = bits(dat, pos + 10, 15) + 4; var tl = hLit + bits(dat, pos + 5, 31) + 1; pos += 14; var ldt = new u8(tl); var clt = new u8(19); for (var i = 0; i < hcLen; ++i) { clt[clim[i]] = bits(dat, pos + i * 3, 7); } pos += hcLen * 3; var clb = max(clt), clbmsk = (1 << clb) - 1; var clm = hMap(clt, clb, 1); for (var i = 0; i < tl; ) { var r = clm[bits(dat, pos, clbmsk)]; pos += r & 15; var s = r >> 4; if (s < 16) { ldt[i++] = s; } else { var c = 0, n = 0; if (s == 16) n = 3 + bits(dat, pos, 3), pos += 2, c = ldt[i - 1]; else if (s == 17) n = 3 + bits(dat, pos, 7), pos += 3; else if (s == 18) n = 11 + bits(dat, pos, 127), pos += 7; while (n--) ldt[i++] = c; } } var lt = ldt.subarray(0, hLit), dt = ldt.subarray(hLit); lbt = max(lt); dbt = max(dt); lm = hMap(lt, lbt, 1); dm = hMap(dt, dbt, 1); } else err(1); if (pos > tbts) { if (noSt) err(0); break; } } if (resize) cbuf(bt + 131072); var lms = (1 << lbt) - 1, dms = (1 << dbt) - 1; var lpos = pos; for (; ; lpos = pos) { var c = lm[bits16(dat, pos) & lms], sym = c >> 4; pos += c & 15; if (pos > tbts) { if (noSt) err(0); break; } if (!c) err(2); if (sym < 256) buf[bt++] = sym; else if (sym == 256) { lpos = pos, lm = null; break; } else { var add = sym - 254; if (sym > 264) { var i = sym - 257, b = fleb[i]; add = bits(dat, pos, (1 << b) - 1) + fl[i]; pos += b; } var d = dm[bits16(dat, pos) & dms], dsym = d >> 4; if (!d) err(3); pos += d & 15; var dt = fd[dsym]; if (dsym > 3) { var b = fdeb[dsym]; dt += bits16(dat, pos) & (1 << b) - 1, pos += b; } if (pos > tbts) { if (noSt) err(0); break; } if (resize) cbuf(bt + 131072); var end = bt + add; if (bt < dt) { var shift = dl - dt, dend = Math.min(dt, end); if (shift + bt < 0) err(3); for (; bt < dend; ++bt) buf[bt] = dict[shift + bt]; } for (; bt < end; ++bt) buf[bt] = buf[bt - dt]; } } st.l = lm, st.p = lpos, st.b = bt, st.f = final; if (lm) final = 1, st.m = lbt, st.d = dm, st.n = dbt; } while (!final); return bt != buf.length && noBuf ? slc(buf, 0, bt) : buf.subarray(0, bt); }; var et = /* @__PURE__ */ new u8(0); var b2 = function(d, b) { return d[b] | d[b + 1] << 8; }; var b4 = function(d, b) { return (d[b] | d[b + 1] << 8 | d[b + 2] << 16 | d[b + 3] << 24) >>> 0; }; var b8 = function(d, b) { return b4(d, b) + b4(d, b + 4) * 4294967296; }; function inflateSync(data, opts) { return inflt(data, { i: 2 }, opts && opts.out, opts && opts.dictionary); } var td = typeof TextDecoder != "undefined" && /* @__PURE__ */ new TextDecoder(); var tds = 0; try { td.decode(et, { stream: true }); tds = 1; } catch (e) { } var dutf8 = function(d) { for (var r = "", i = 0; ; ) { var c = d[i++]; var eb = (c > 127) + (c > 223) + (c > 239); if (i + eb > d.length) return { s: r, r: slc(d, i - 1) }; if (!eb) r += String.fromCharCode(c); else if (eb == 3) { c = ((c & 15) << 18 | (d[i++] & 63) << 12 | (d[i++] & 63) << 6 | d[i++] & 63) - 65536, r += String.fromCharCode(55296 | c >> 10, 56320 | c & 1023); } else if (eb & 1) r += String.fromCharCode((c & 31) << 6 | d[i++] & 63); else r += String.fromCharCode((c & 15) << 12 | (d[i++] & 63) << 6 | d[i++] & 63); } }; function strFromU8(dat, latin1) { if (latin1) { var r = ""; for (var i = 0; i < dat.length; i += 16384) r += String.fromCharCode.apply(null, dat.subarray(i, i + 16384)); return r; } else if (td) { return td.decode(dat); } else { var _a2 = dutf8(dat), s = _a2.s, r = _a2.r; if (r.length) err(8); return s; } } var slzh = function(d, b) { return b + 30 + b2(d, b + 26) + b2(d, b + 28); }; var zh = function(d, b, z) { var fnl = b2(d, b + 28), efl = b2(d, b + 30), fn = strFromU8(d.subarray(b + 46, b + 46 + fnl), !(b2(d, b + 8) & 2048)), es = b + 46 + fnl; var _a2 = z64hs(d, es, efl, z, b4(d, b + 20), b4(d, b + 24), b4(d, b + 42)), sc = _a2[0], su = _a2[1], off = _a2[2]; return [b2(d, b + 10), sc, su, fn, es + efl + b2(d, b + 32), off]; }; var z64hs = function(d, b, l, z, sc, su, off) { var nsc = sc == 4294967295, nsu = su == 4294967295, noff = off == 4294967295, e = b + l; var nf = nsc + nsu + noff; if (z && nf) { for (; b + 4 < e; b += 4 + b2(d, b + 2)) { if (b2(d, b) == 1) { return [ nsc ? b8(d, b + 4 + 8 * nsu) : sc, nsu ? b8(d, b + 4) : su, noff ? b8(d, b + 4 + 8 * (nsu + nsc)) : off, 1 ]; } } if (z < 2) err(13); } return [sc, su, off, 0]; }; function unzipSync(data, opts) { var files = {}; var e = data.length - 22; for (; b4(data, e) != 101010256; --e) { if (!e || data.length - e > 65558) err(13); } ; var c = b2(data, e + 8); if (!c) return {}; var o = b4(data, e + 16); var z = b4(data, e - 20) == 117853008; if (z) { var ze = b4(data, e - 12); z = b4(data, ze) == 101075792; if (z) { c = b4(data, ze + 32); o = b4(data, ze + 48); } } var fltr = opts && opts.filter; for (var i = 0; i < c; ++i) { var _a2 = zh(data, o, z), c_2 = _a2[0], sc = _a2[1], su = _a2[2], fn = _a2[3], no = _a2[4], off = _a2[5], b = slzh(data, off); o = no; if (!fltr || fltr({ name: fn, size: sc, originalSize: su, compression: c_2 })) { if (!c_2) files[fn] = slc(data, b, b + sc); else if (c_2 == 8) files[fn] = inflateSync(data.subarray(b, b + sc), { out: new u8(su) }); else err(14, "unknown compression type " + c_2); } } return files; } // upload/frontend/table/rebase_tree.js function rebaseTree(root, prefix) { root.path = `${prefix}/${root.path}`; if (root.file) root.file = new File([root.file], root.file.name, { lastModified: root.file.lastModified }); root.children.forEach((child) => rebaseTree(child, prefix)); return root; } // upload/frontend/zip/list_zip_files.js var DEFAULT_LIMITS = { maxEntries: 1e3, maxTotalBytes: 100 * 1024 * 1024, maxEntryBytes: 50 * 1024 * 1024, maxDepth: 20 }; function extensionAllowed(name, allowedExt) { const lowerName = name.toLowerCase(); return allowedExt.some((extension) => lowerName.endsWith(extension.toLowerCase())); } function makeFile(data, name) { return new File([data], name.split("/").at(-1)); } function safeEntryParts(entryName) { if (!entryName || entryName.startsWith("/") || entryName.includes("\\")) return null; const parts = entryName.split("/"); if (parts.some((part) => !part || part === "." || part === "..")) return null; return parts; } function node(kind, name, path, children = [], file = null) { return { id: crypto.randomUUID(), kind, name, path, children, file, expanded: true }; } function addPath(root, parts, fileNode) { let current = root; parts.forEach((part, index) => { const last = index === parts.length - 1; let child = current.children.find((item) => item.name === part); if (!child) { child = last ? fileNode : node("folder", part, `${current.path}/${part}`); current.children.push(child); } current = child; }); } async function listEntries(data, zipName, allowedExt, depth, limits, budget) { if (depth > limits.maxDepth) throw new Error("\u0421\u043B\u0438\u0448\u043A\u043E\u043C \u0433\u043B\u0443\u0431\u043E\u043A\u0430\u044F \u0432\u043B\u043E\u0436\u0435\u043D\u043D\u043E\u0441\u0442\u044C ZIP"); const entries = unzipSync(data, { filter: (entry) => { budget.entries += 1; if (budget.entries > limits.maxEntries) throw new Error("\u0421\u043B\u0438\u0448\u043A\u043E\u043C \u043C\u043D\u043E\u0433\u043E ZIP entries"); const lowerName = entry.name.toLowerCase(); if (!extensionAllowed(lowerName, allowedExt) && !lowerName.endsWith(".zip")) return false; if (entry.originalSize > limits.maxEntryBytes) return false; if (budget.totalBytes + entry.originalSize > limits.maxTotalBytes) { throw new Error("\u041F\u0440\u0435\u0432\u044B\u0448\u0435\u043D \u0441\u0443\u043C\u043C\u0430\u0440\u043D\u044B\u0439 \u0440\u0430\u0437\u043C\u0435\u0440 \u0440\u0430\u0441\u043F\u0430\u043A\u043E\u0432\u0430\u043D\u043D\u044B\u0445 ZIP entries"); } budget.totalBytes += entry.originalSize; return true; } }); const root = node("zip", zipName, zipName); for (const [entryName, entryData] of Object.entries(entries)) { if (entryName.endsWith("/")) continue; const parts = safeEntryParts(entryName); if (!parts) continue; const normalizedName = parts.join("/"); if (normalizedName.toLowerCase().endsWith(".zip")) { const nested = await listEntries(entryData, entryName, allowedExt, depth + 1, limits, budget); if (nested) { rebaseTree(nested, zipName); nested.name = parts.at(-1); addPath(root, parts, nested); } } else if (extensionAllowed(normalizedName, allowedExt)) { const path = `${zipName}/${normalizedName}`; const file = makeFile(entryData, path); addPath(root, parts, node("file", parts.at(-1), path, [], file)); } } return root.children.length ? root : null; } async function listZipFiles(file, allowedExt, customLimits = {}) { const data = new Uint8Array(await file.arrayBuffer()); const limits = { ...DEFAULT_LIMITS, ...customLimits }; return listEntries(data, file.name, allowedExt, 0, limits, { entries: 0, totalBytes: 0 }); } // upload/frontend/table/add_file_with_dedup.js function addFileWithDedup(state, fileNode) { const accept = (node2) => { if (node2.kind === "file") { const key = `${node2.path}\0${node2.file.size}`; if (state.fileKeys.has(key)) return null; state.fileKeys.add(key); return node2; } node2.children = node2.children.map(accept).filter(Boolean); return node2.children.length ? node2 : null; }; const accepted = accept(fileNode); if (!accepted || accepted.kind !== "file" && !accepted.children.length) return false; if (accepted.kind !== "file") { const existing = state.nodes.find((node2) => node2.kind !== "file" && node2.path === accepted.path); if (existing) { mergeChildren(existing, accepted); return true; } } state.nodes.push(accepted); return true; } function mergeChildren(target, incoming) { incoming.children.forEach((child) => { if (child.kind === "file") { const duplicate = target.children.some((existing2) => existing2.kind === "file" && existing2.path === child.path && existing2.file.size === child.file.size); if (!duplicate) target.children.push(child); return; } const existing = target.children.find((candidate) => candidate.kind !== "file" && candidate.path === child.path); if (existing) mergeChildren(existing, child); else target.children.push(child); }); } // upload/frontend/table/esc.js function esc(value) { return String(value).replace(/[&<>"']/g, (character) => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" })[character]); } // upload/frontend/table/fs.js function fs(bytes) { return bytes < 1024 ? `${bytes} B` : bytes < 1048576 ? `${(bytes / 1024).toFixed(1)} KB` : `${(bytes / 1048576).toFixed(1)} MB`; } // upload/frontend/table/render.js var DEFAULT_RENDER_CONFIG = { labels: { statusReady: "\u0433\u043E\u0442\u043E\u0432", remove: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", empty: "\u041D\u0435\u0442 \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432", columns: { path: "\u041F\u0443\u0442\u044C", size: "\u0420\u0430\u0437\u043C\u0435\u0440", status: "\u0421\u0442\u0430\u0442\u0443\u0441" }, count: (count, bytes) => `${count} \u0444\u0430\u0439\u043B\u043E\u0432 \xB7 ${fs(bytes)}` }, layout: { columns: ["path", "size", "status"] } }; function renderConfig(cfg = {}) { return { labels: { ...DEFAULT_RENDER_CONFIG.labels, ...cfg.labels || {}, columns: { ...DEFAULT_RENDER_CONFIG.labels.columns, ...cfg.labels?.columns || {} } }, layout: { ...DEFAULT_RENDER_CONFIG.layout, ...cfg.layout || {} } }; } function renderNode(node2, depth, cfg) { const isGroup = node2.kind !== "file"; const padding = depth * 4; const name = esc(node2.name); const action = isGroup ? `` : `${name}`; const remove = ``; const pathAttribute = node2.kind === "file" ? ` data-path="${esc(node2.path)}"` : ""; const cells = { path: action, size: node2.kind === "file" ? fs(node2.file.size) : "", status: node2.error ? esc(node2.error) : node2.kind === "file" ? esc(cfg.labels.statusReady) : "" }; const columns = cfg.layout.columns?.length ? cfg.layout.columns : Object.keys(DEFAULT_RENDER_CONFIG.labels.columns); const row = `` + columns.map((column) => `${cells[column] || ""}`).join("") + `${remove}`; if (!isGroup || !node2.expanded) return row; return row + node2.children.map((child) => renderNode(child, depth + 1, cfg)).join(""); } function render(state, elements, cfg) { const normalized = renderConfig(cfg); const columns = normalized.layout.columns?.length ? normalized.layout.columns : Object.keys(DEFAULT_RENDER_CONFIG.labels.columns); elements.tableBodyEl.innerHTML = state.nodes.length ? state.nodes.map((node2) => renderNode(node2, 0, normalized)).join("") : `${esc(normalized.labels.empty)}`; const files = []; const visit = (node2) => { if (node2.kind === "file") files.push(node2); else node2.children.forEach(visit); }; state.nodes.forEach(visit); const totalBytes = files.reduce((sum, node2) => sum + node2.file.size, 0); elements.countEl.textContent = normalized.labels.count(files.length, totalBytes); } function findNode(nodes, id) { for (const node2 of nodes) { if (node2.id === id) return { node: node2, nodes }; const found = findNode(node2.children || [], id); if (found) return found; } return null; } function flattenFiles(nodes, result = []) { nodes.forEach((node2) => { if (node2.kind === "file") result.push({ path: node2.path, name: node2.name, size: node2.file.size, file: node2.file }); else flattenFiles(node2.children, result); }); return result; } // upload/frontend/table/on_files_change.js async function addFiles(state, cfg, files, elements) { for (const file of Array.from(files)) { if (!file.name.toLowerCase().endsWith(".zip")) { if (!cfg.allowedExt.some((extension) => file.name.toLowerCase().endsWith(extension.toLowerCase()))) { continue; } addFileWithDedup(state, { id: crypto.randomUUID(), kind: "file", name: file.name, path: file.name, file, children: [], expanded: true }); continue; } try { const zipTree = await listZipFiles(file, cfg.allowedExt, cfg.limits); if (zipTree) addFileWithDedup(state, zipTree); } catch (error) { if (typeof cfg.onError === "function") { cfg.onError(error, file); } else if (elements.statusEl) { elements.statusEl.textContent = `\u041E\u0448\u0438\u0431\u043A\u0430 \u0447\u0442\u0435\u043D\u0438\u044F \u0430\u0440\u0445\u0438\u0432\u0430 ${file.name}: ${error.message}`; } continue; } } render(state, elements, cfg); } function onFilesChange(state, cfg, elements) { return async () => { if (state.busy) return; state.busy = true; try { await addFiles(state, cfg, elements.fileInputEl.files, elements); } finally { elements.fileInputEl.value = ""; state.busy = false; } }; } // upload/frontend/table/on_folder_change.js function onFolderChange(state, cfg, elements) { return async () => { if (state.busy) return; state.busy = true; try { const roots = /* @__PURE__ */ new Map(); for (const file of Array.from(elements.folderInputEl.files)) { const parts = (file.webkitRelativePath || file.name).split("/"); const relativePath = parts.slice(1).join("/") || file.name; const lowerPath = relativePath.toLowerCase(); const rootName = parts[0] || file.name; if (!roots.has(rootName)) { roots.set(rootName, { id: crypto.randomUUID(), kind: "folder", name: rootName, path: rootName, children: [], expanded: true }); } const root = roots.get(rootName); const addToFolder = (node2) => { let current = root; const nodeParts = node2.path.split("/").slice(1); nodeParts.forEach((part, index) => { const last = index === nodeParts.length - 1; let child = current.children.find((item) => item.name === part); if (!child) { child = last ? node2 : { id: crypto.randomUUID(), kind: "folder", name: part, path: `${rootName}/${nodeParts.slice(0, index + 1).join("/")}`, children: [], expanded: true }; current.children.push(child); } current = child; }); }; if (lowerPath.endsWith(".zip")) { try { const zip = await listZipFiles(file, cfg.allowedExt, cfg.limits); if (zip) { rebaseTree(zip, rootName); addToFolder(zip); } } catch (error) { if (typeof cfg.onError === "function") { cfg.onError(error, file); } else if (elements.statusEl) { elements.statusEl.textContent = `\u041E\u0448\u0438\u0431\u043A\u0430 \u0447\u0442\u0435\u043D\u0438\u044F \u0430\u0440\u0445\u0438\u0432\u0430 ${file.name}: ${error.message}`; } continue; } } else if (cfg.allowedExt.some((extension) => lowerPath.endsWith(extension))) { addToFolder({ id: crypto.randomUUID(), kind: "file", name: parts.at(-1), path: `${rootName}/${relativePath}`, file, children: [], expanded: true }); } } roots.forEach((root) => addFileWithDedup(state, root)); elements.folderInputEl.value = ""; render(state, elements, cfg); } finally { state.busy = false; } }; } // upload/frontend/upload/put_to_vm.js function putToVm(file, url, options = {}) { return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); if (options.onXHR) { options.onXHR(xhr); } const abortHandler = () => { xhr.abort(); reject(new DOMException("Upload aborted", "AbortError")); }; if (options.signal) { if (options.signal.aborted) { reject(new DOMException("Upload aborted", "AbortError")); return; } options.signal.addEventListener("abort", abortHandler, { once: true }); } xhr.open("PUT", url); xhr.timeout = options.timeoutMs || 3e5; xhr.upload.onprogress = (e) => { if (e.lengthComputable && options.onProgress) { const pct = Math.round(e.loaded / e.total * 100); options.onProgress(pct, e.loaded, e.total); } }; xhr.onload = () => { if (options.signal) { options.signal.removeEventListener("abort", abortHandler); } if (xhr.status >= 200 && xhr.status < 300) { resolve(); } else { reject(new Error(`\u0411\u0443\u0444\u0435\u0440: HTTP ${xhr.status} ${xhr.statusText}`)); } }; xhr.onerror = () => { if (options.signal) { options.signal.removeEventListener("abort", abortHandler); } reject(new Error("\u0421\u0435\u0442\u0435\u0432\u0430\u044F \u043E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0435 \u0432 \u0431\u0443\u0444\u0435\u0440")); }; xhr.ontimeout = () => { if (options.signal) { options.signal.removeEventListener("abort", abortHandler); } reject(new Error(`\u0422\u0430\u0439\u043C\u0430\u0443\u0442 \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u043E\u0442\u0432\u0435\u0442\u0430 \u0431\u0443\u0444\u0435\u0440\u0430 (${Math.round(xhr.timeout / 1e3)}\u0441)`)); }; xhr.onabort = () => { if (options.signal) { options.signal.removeEventListener("abort", abortHandler); } reject(new DOMException("Upload aborted", "AbortError")); }; xhr.send(file); }); } // upload/frontend/upload/upload_via_vm.js function generateUuid() { if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") { return crypto.randomUUID(); } return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (c) => { const r = Math.random() * 16 | 0; const v = c === "x" ? r : r & 3 | 8; return v.toString(16); }); } function normalizeFileEntry(item) { if (item instanceof File || item instanceof Blob) { return { name: item.name || "unnamed", size: item.size, file: item, path: item.name || "" }; } if (item && item.file) { return { name: item.name || item.file.name || "unnamed", size: item.size ?? item.file.size ?? 0, file: item.file, path: item.path || item.name || "" }; } throw new Error("\u041D\u0435\u043A\u043E\u0440\u0440\u0435\u043A\u0442\u043D\u044B\u0439 \u043E\u0431\u044A\u0435\u043A\u0442 \u0444\u0430\u0439\u043B\u0430"); } async function uploadViaVM(files, options = {}) { if (!options.vmUploadUrl) { return { ok: false, error: "\u041F\u0430\u0440\u0430\u043C\u0435\u0442\u0440 options.vmUploadUrl \u043E\u0431\u044F\u0437\u0430\u0442\u0435\u043B\u0435\u043D" }; } const normalizedFiles = Array.from(files).map(normalizeFileEntry); const total = normalizedFiles.length; if (total === 0) { return { ok: true, session: options.session || "", count: 0 }; } const backendUrl = options.backendUploadUrl || (options.apiBase || "") + "/api/upload_refs"; const vmBase = options.vmUploadUrl.endsWith("/") ? options.vmUploadUrl : `${options.vmUploadUrl}/`; let currentSession = options.session || ""; let totalAdded = 0; for (let k = 0; k < total; k++) { if (options.signal?.aborted) { return { ok: false, error: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u0430", aborted: true, session: currentSession }; } const { name, size, file } = normalizedFiles[k]; const fileKey = `${generateUuid()}_${k}`; const fileUrl = `${vmBase}${fileKey}`; if (options.onStatus) { options.onStatus(`\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u0432 \u0431\u0443\u0444\u0435\u0440 (${k + 1}/${total}): ${name}`); } if (options.onFileStatus) { options.onFileStatus(k, "\u23F3 \u0412 \u0431\u0443\u0444\u0435\u0440..."); } const t0 = performance.now(); try { await putToVm(file, fileUrl, { signal: options.signal, onXHR: options.onXHR, onProgress(pct, loaded, fileTotal) { const elapsed = (performance.now() - t0) / 1e3; const speed = elapsed > 0 ? loaded / elapsed : 0; if (options.onFileStatus) { options.onFileStatus(k, `\u23F3 ${pct}% (${fs(speed)}/s)`); } if (options.onProgress) { options.onProgress({ index: k, total, pct, speed, loaded, fileTotal, name }); } } }); } catch (err2) { if (options.signal?.aborted) { return { ok: false, error: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u0430", aborted: true, session: currentSession }; } if (options.onFileStatus) { options.onFileStatus(k, "\u2717 \u041E\u0448\u0438\u0431\u043A\u0430 \u0431\u0443\u0444\u0435\u0440\u0430"); } return { ok: false, error: `\u041E\u0448\u0438\u0431\u043A\u0430 \u043E\u0442\u043F\u0440\u0430\u0432\u043A\u0438 \u0444\u0430\u0439\u043B\u0430 ${name} \u0432 \u0431\u0443\u0444\u0435\u0440: ${err2.message}`, failedIndex: k, session: currentSession }; } if (options.onStatus) { options.onStatus(`\u041F\u0440\u0438\u0451\u043C \u0441\u0435\u0440\u0432\u0438\u0441\u043E\u043C (${k + 1}/${total}): ${name}`); } if (options.onFileStatus) { options.onFileStatus(k, "\u26A1 \u0412 \u0441\u0435\u0441\u0441\u0438\u044E..."); } try { const resp = await fetch(backendUrl, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ session: currentSession, files: [{ name, size, url: fileUrl }] }), signal: options.signal }); if (!resp.ok) { throw new Error(`HTTP ${resp.status} ${resp.statusText}`); } const data = await resp.json(); if (!data.ok) { throw new Error(data.error || "\u0411\u044D\u043A\u0435\u043D\u0434 \u0432\u0435\u0440\u043D\u0443\u043B \u043E\u0448\u0438\u0431\u043A\u0443"); } currentSession = data.session || currentSession; totalAdded = data.count ?? totalAdded + 1; if (options.onFileStatus) { options.onFileStatus(k, "\u2713 \u0414\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D"); } if (options.onFileComplete) { options.onFileComplete({ fileIndex: k, name, size, session: currentSession, totalAdded }); } } catch (err2) { if (options.signal?.aborted) { return { ok: false, error: "\u0417\u0430\u0433\u0440\u0443\u0437\u043A\u0430 \u043E\u0442\u043C\u0435\u043D\u0435\u043D\u0430", aborted: true, session: currentSession }; } if (options.onFileStatus) { options.onFileStatus(k, "\u2717 \u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438\u0451\u043C\u0430"); } return { ok: false, error: `\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438\u0451\u043C\u0430 \u0444\u0430\u0439\u043B\u0430 ${name} \u0431\u044D\u043A\u0435\u043D\u0434\u043E\u043C: ${err2.message}`, failedIndex: k, session: currentSession }; } } if (options.onStatus) { options.onStatus(`\u0417\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u043E. \u0423\u0441\u043F\u0435\u0448\u043D\u043E \u043F\u0435\u0440\u0435\u0434\u0430\u043D\u043E \u0444\u0430\u0439\u043B\u043E\u0432: ${total}`); } return { ok: true, session: currentSession, count: totalAdded }; } // upload/frontend/index.js var DEFAULTS = { allowedExt: [], labels: { pickFiles: "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u0444\u0430\u0439\u043B\u044B", pickFolder: "\u0412\u044B\u0431\u0440\u0430\u0442\u044C \u043F\u0430\u043F\u043A\u0443", clear: "\u041E\u0447\u0438\u0441\u0442\u0438\u0442\u044C", empty: "\u041D\u0435\u0442 \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0445 \u0444\u0430\u0439\u043B\u043E\u0432", statusReady: "\u0433\u043E\u0442\u043E\u0432", remove: "\u0423\u0434\u0430\u043B\u0438\u0442\u044C", columns: { path: "\u041F\u0443\u0442\u044C", size: "\u0420\u0430\u0437\u043C\u0435\u0440", status: "\u0421\u0442\u0430\u0442\u0443\u0441" }, count: (count, bytes) => `${count} \u0444\u0430\u0439\u043B\u043E\u0432 \xB7 ${bytes} B` }, layout: { columns: ["path", "size", "status"], controls: ["files", "folder", "clear"] }, limits: { maxEntries: 1e3, maxTotalBytes: 100 * 1024 * 1024, maxEntryBytes: 50 * 1024 * 1024, maxDepth: 20 } }; function resolveMount(mount) { const element = typeof mount === "string" ? document.querySelector(mount) : mount; if (!element) throw new Error("File picker mount element \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D"); return element; } function normalizeConfig(config) { return { ...config, allowedExt: config.allowedExt || DEFAULTS.allowedExt, labels: { ...DEFAULTS.labels, ...config.labels || {}, columns: { ...DEFAULTS.labels.columns, ...config.labels?.columns || {} } }, layout: { ...DEFAULTS.layout, ...config.layout || {} }, limits: { ...DEFAULTS.limits, ...config.limits || {} } }; } function injectStyles() { if (document.querySelector("style[data-file-picker]")) return; const style = document.createElement("style"); style.dataset.filePicker = ""; style.textContent = ` .file-picker { color: #17211b; font-family: Georgia, 'Times New Roman', serif; } .file-picker .fp-toolbar { display: flex; flex-wrap: wrap; gap: 10px; margin: 16px 0; } .file-picker .fp-toolbar button { border: 0; border-radius: 4px; padding: 12px 17px; color: #f7faf4; background: #1f5a3b; font: 700 13px sans-serif; cursor: pointer; } .file-picker .fp-toolbar button.secondary { color: #1f5a3b; background: #cbdcc9; } .file-picker .fp-toolbar button.quiet { color: #5d6a61; background: transparent; } .file-picker .fp-table-wrap { overflow-x: auto; border-top: 1px solid #b9c7ba; } .file-picker .fp-table { width: 100%; border-collapse: collapse; font: 14px/1.4 sans-serif; } .file-picker .fp-table th, .file-picker .fp-table td { padding: 14px 10px; border-bottom: 1px solid #cbd5cc; text-align: left; } .file-picker .fp-table th { color: #56745f; font-size: 11px; text-transform: uppercase; letter-spacing: 1px; } .file-picker .fp-status, .file-picker .fp-count { color: #5d6a61; font: 13px sans-serif; } .file-picker .fp-count { min-height: 22px; } .file-picker .tree-name { display: inline-flex; align-items: center; gap: 8px; min-width: 240px; padding-top: 0; padding-bottom: 0; border: 0; background: transparent; color: #17211b; font: inherit; text-align: left; } .file-picker .tree-group { padding-left: 0; cursor: pointer; } .file-picker .tree-chevron { display: inline-block; width: 12px; color: #78917d; font-size: 10px; } .file-picker .remove-btn { padding: 2px 7px; color: #8c5148; background: transparent; font: 20px/1 sans-serif; } .file-picker .tree-row td:first-child { white-space: nowrap; } @media (max-width: 600px) { .file-picker .fp-toolbar button { flex: 1 1 42%; } } `; document.head.append(style); } function buildMarkup(root, cfg) { const controls = cfg.layout.controls || []; const control = (name, id, label, className = "") => controls.includes(name) ? `` : ""; const columns = cfg.layout.columns?.length ? cfg.layout.columns : Object.keys(cfg.labels.columns); const headings = columns.map((column) => `${esc(cfg.labels.columns[column] || column)}`).join(""); root.innerHTML = `
${control("files", "fp-files-btn", cfg.labels.pickFiles)} ${control("folder", "fp-folder-btn", cfg.labels.pickFolder, "secondary")} ${control("clear", "fp-clear-btn", cfg.labels.clear, "quiet")}

${headings}

`; } function notify(cfg, state) { if (typeof cfg.onChange === "function") cfg.onChange([...flattenFiles(state.nodes)]); } function removeMetadata(state, node2) { if (node2.kind === "file") { state.fileKeys.delete(`${node2.path}\0${node2.file.size}`); return; } node2.children.forEach((child) => removeMetadata(state, child)); } function removeNode(state, id) { const found = findNode(state.nodes, id); if (!found) return false; removeMetadata(state, found.node); found.nodes.splice(found.nodes.indexOf(found.node), 1); return true; } function initFilePicker(config) { const cfg = normalizeConfig(config); const mount = resolveMount(cfg.mount); injectStyles(); buildMarkup(mount, cfg); const root = mount.firstElementChild; const elements = { fileInputEl: root.querySelector(".fp-file-input"), folderInputEl: root.querySelector(".fp-folder-input"), statusEl: root.querySelector(".fp-status"), tableBodyEl: root.querySelector(".fp-table-body"), countEl: root.querySelector(".fp-count") }; const state = { nodes: [], fileKeys: /* @__PURE__ */ new Set(), busy: false }; const listeners = []; const listen = (target, event, handler) => { target.addEventListener(event, handler); listeners.push(() => target.removeEventListener(event, handler)); }; const redraw = () => render(state, elements, cfg); const filesChange = onFilesChange(state, cfg, elements); const folderChange = onFolderChange(state, cfg, elements); listen(elements.fileInputEl, "change", async () => { await filesChange(); notify(cfg, state); }); listen(elements.folderInputEl, "change", async () => { await folderChange(); notify(cfg, state); }); listen(elements.tableBodyEl, "click", (event) => { const toggle = event.target.closest("[data-toggle]"); if (toggle) { const found = findNode(state.nodes, toggle.dataset.toggle); if (found) found.node.expanded = !found.node.expanded; redraw(); return; } const remove = event.target.closest("[data-remove]"); if (remove) { if (removeNode(state, remove.dataset.remove)) { redraw(); notify(cfg, state); } } }); if (root.querySelector("#fp-files-btn")) listen(root.querySelector("#fp-files-btn"), "click", () => elements.fileInputEl.click()); if (root.querySelector("#fp-folder-btn")) listen(root.querySelector("#fp-folder-btn"), "click", () => elements.folderInputEl.click()); if (root.querySelector("#fp-clear-btn")) listen(root.querySelector("#fp-clear-btn"), "click", () => { state.nodes = []; state.fileKeys.clear(); redraw(); notify(cfg, state); }); redraw(); return { pickFiles: () => elements.fileInputEl.click(), pickFolder: () => elements.folderInputEl.click(), addFiles: async (files) => { await addFiles(state, cfg, files, elements); notify(cfg, state); }, getFiles: () => [...flattenFiles(state.nodes)], remove: (id) => { if (removeNode(state, id)) { redraw(); notify(cfg, state); } }, clear: () => { state.nodes = []; state.fileKeys.clear(); redraw(); notify(cfg, state); }, render: redraw, destroy: () => { listeners.forEach((removeListener) => removeListener()); state.nodes = []; state.fileKeys.clear(); mount.replaceChildren(); } }; } export { DEFAULTS, initFilePicker, putToVm, uploadViaVM };