feat: download - download dwnload limieten in settings

This commit is contained in:
kodi
2026-03-14 13:38:44 +01:00
parent ea337338e3
commit 8ea2bd1498
12 changed files with 228 additions and 26 deletions
+103 -18
View File
@@ -85,6 +85,7 @@ let downloadProgressState = {
active: false,
archiveLabel: "",
totalItems: 0,
requestKey: null,
};
let folderUploadPlanState = {
targetPane: "left",
@@ -103,6 +104,7 @@ let settingsState = {
preferredStartupPathRight: null,
selectedTheme: "default",
selectedColorMode: "dark",
zipDownloadLimits: null,
};
const VALID_THEME_FAMILIES = [
"default",
@@ -368,6 +370,10 @@ function isZipDownloadSelection(items) {
return items.length > 1 || (items.length === 1 && items[0].kind === "directory");
}
function zipDownloadRequestKey(paths) {
return paths.join("\n");
}
function selectedItemCountLabel(totalItems) {
return `${totalItems} selected item${totalItems === 1 ? "" : "s"}`;
}
@@ -399,17 +405,19 @@ function updateDownloadModalDisplay(info) {
}
function openZipDownloadModal(selectedItems) {
const requestPaths = selectedItems.map((item) => item.path);
downloadProgressState.active = true;
downloadProgressState.archiveLabel = "ZIP archive";
downloadProgressState.totalItems = selectedItems.length;
downloadProgressState.requestKey = zipDownloadRequestKey(requestPaths);
setDownloadModalVisible(true);
updateDownloadModalDisplay({
active: true,
targetText: "Preparing ZIP download",
targetText: "Preparing download...",
currentFileText: `Selection: ${selectedItemCountLabel(selectedItems.length)}`,
countText: "Step 1/3",
statusText: "preparing",
percent: 33,
countText: "Preparing zip download",
statusText: "Preparing download...",
percent: 20,
});
requestAnimationFrame(() => {
if (!downloadProgressState.active) {
@@ -417,11 +425,11 @@ function openZipDownloadModal(selectedItems) {
}
updateDownloadModalDisplay({
active: true,
targetText: "Preparing ZIP download",
targetText: "Preparing download...",
currentFileText: `Packaging ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
countText: "Step 2/3",
statusText: "packaging items",
percent: 66,
countText: "Zip preflight and packaging",
statusText: "Preparing download...",
percent: 55,
});
});
}
@@ -431,24 +439,24 @@ function markZipDownloadReady(fileName) {
downloadProgressState.archiveLabel = fileName || "ZIP archive";
updateDownloadModalDisplay({
active: false,
targetText: `Ready: ${downloadProgressState.archiveLabel}`,
targetText: `Download started: ${downloadProgressState.archiveLabel}`,
currentFileText: `Prepared ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
countText: "Step 3/3",
statusText: "ready",
countText: "Browser download started",
statusText: "Download started",
percent: 100,
});
window.setTimeout(closeDownloadModal, 240);
window.setTimeout(closeDownloadModal, 480);
}
function markZipDownloadFailed(err) {
downloadProgressState.active = false;
updateDownloadModalDisplay({
active: false,
targetText: "Preparing ZIP download",
targetText: "Preparing download...",
currentFileText: `Selection: ${selectedItemCountLabel(downloadProgressState.totalItems)}`,
countText: "Step 2/3",
statusText: `failed: ${err.message}`,
percent: 66,
countText: "Zip download failed",
statusText: err.message || "Download failed",
percent: 0,
});
}
@@ -458,6 +466,7 @@ function closeDownloadModal() {
}
downloadProgressState.archiveLabel = "";
downloadProgressState.totalItems = 0;
downloadProgressState.requestKey = null;
updateDownloadModalDisplay({
active: false,
targetText: "",
@@ -622,12 +631,19 @@ async function startDownloadSelected() {
return;
}
const zipDownload = isZipDownloadSelection(selectedItems);
const selectedPaths = selectedItems.map((item) => item.path);
const requestKey = zipDownloadRequestKey(selectedPaths);
if (zipDownload && downloadProgressState.active && downloadProgressState.requestKey === requestKey) {
setStatus("Preparing download...");
return;
}
if (zipDownload) {
openZipDownloadModal(selectedItems);
setStatus("Preparing download...");
}
try {
const selected = selectedItems[0];
const { blob, fileName } = await downloadFileRequest(selectedItems.map((item) => item.path));
const { blob, fileName } = await downloadFileRequest(selectedPaths);
const url = URL.createObjectURL(blob);
const anchor = document.createElement("a");
anchor.href = url;
@@ -680,9 +696,11 @@ function settingsElements() {
closeButton: document.getElementById("settings-close-btn"),
generalTab: document.getElementById("settings-general-tab"),
interfaceTab: document.getElementById("settings-interface-tab"),
downloadsTab: document.getElementById("settings-downloads-tab"),
logsTab: document.getElementById("settings-logs-tab"),
generalPanel: document.getElementById("settings-general-panel"),
interfacePanel: document.getElementById("settings-interface-panel"),
downloadsPanel: document.getElementById("settings-downloads-panel"),
showThumbnailsInput: document.getElementById("settings-show-thumbnails"),
startupPathLeftInput: document.getElementById("settings-startup-path-left"),
startupPathRightInput: document.getElementById("settings-startup-path-right"),
@@ -691,6 +709,11 @@ function settingsElements() {
selectedThemeInput: document.getElementById("settings-selected-theme"),
interfaceError: document.getElementById("settings-interface-error"),
interfaceSaveButton: document.getElementById("settings-interface-save-btn"),
downloadMaxItems: document.getElementById("settings-download-max-items"),
downloadMaxTotalSize: document.getElementById("settings-download-max-total-size"),
downloadMaxFileSize: document.getElementById("settings-download-max-file-size"),
downloadScanTimeout: document.getElementById("settings-download-scan-timeout"),
downloadSymlinkPolicy: document.getElementById("settings-download-symlink-policy"),
logsPanel: document.getElementById("settings-logs-panel"),
logsList: document.getElementById("settings-logs-list"),
logsError: document.getElementById("settings-logs-error"),
@@ -1661,6 +1684,7 @@ async function loadSettings() {
settingsState.preferredStartupPathRight = data.preferred_startup_path_right || null;
settingsState.selectedTheme = VALID_THEME_FAMILIES.includes(data.selected_theme) ? data.selected_theme : "default";
settingsState.selectedColorMode = VALID_COLOR_MODES.includes(data.selected_color_mode) ? data.selected_color_mode : "dark";
settingsState.zipDownloadLimits = data.zip_download_limits || null;
const elements = settingsElements();
if (elements.showThumbnailsInput) {
elements.showThumbnailsInput.checked = settingsState.showThumbnails;
@@ -1674,6 +1698,7 @@ async function loadSettings() {
if (elements.selectedThemeInput) {
elements.selectedThemeInput.value = settingsState.selectedTheme;
}
renderDownloadSettings();
}
async function saveSettings(update) {
@@ -1683,6 +1708,7 @@ async function saveSettings(update) {
settingsState.preferredStartupPathRight = data.preferred_startup_path_right || null;
settingsState.selectedTheme = VALID_THEME_FAMILIES.includes(data.selected_theme) ? data.selected_theme : "default";
settingsState.selectedColorMode = VALID_COLOR_MODES.includes(data.selected_color_mode) ? data.selected_color_mode : "dark";
settingsState.zipDownloadLimits = data.zip_download_limits || null;
const elements = settingsElements();
if (elements.showThumbnailsInput) {
elements.showThumbnailsInput.checked = settingsState.showThumbnails;
@@ -1696,6 +1722,7 @@ async function saveSettings(update) {
if (elements.selectedThemeInput) {
elements.selectedThemeInput.value = settingsState.selectedTheme;
}
renderDownloadSettings();
applyTheme(settingsState.selectedTheme, settingsState.selectedColorMode);
renderPaneItems("left");
renderPaneItems("right");
@@ -1728,6 +1755,57 @@ function isEditableSelection(item) {
return [".txt", ".log", ".md", ".yml", ".yaml", ".json", ".js", ".py", ".css", ".html"].some((suffix) => lower.endsWith(suffix));
}
function formatBinarySize(bytes) {
const value = Number(bytes);
if (!Number.isFinite(value) || value < 0) {
return "-";
}
if (value < 1024) {
return `${value} B`;
}
const units = ["KiB", "MiB", "GiB", "TiB"];
let scaled = value;
let unitIndex = -1;
do {
scaled /= 1024;
unitIndex += 1;
} while (scaled >= 1024 && unitIndex < units.length - 1);
const digits = scaled >= 10 || unitIndex === 0 ? 0 : 1;
return `${scaled.toFixed(digits)} ${units[unitIndex]}`;
}
function formatSeconds(seconds) {
const value = Number(seconds);
if (!Number.isFinite(value) || value < 0) {
return "-";
}
return `${value % 1 === 0 ? value.toFixed(0) : value.toFixed(1)} seconds`;
}
function formatSymlinkPolicy(policy) {
return policy === "not_allowed" ? "Rejected / not allowed" : (policy || "-");
}
function renderDownloadSettings() {
const elements = settingsElements();
const limits = settingsState.zipDownloadLimits || {};
if (elements.downloadMaxItems) {
elements.downloadMaxItems.textContent = limits.max_items ? `${limits.max_items} items` : "-";
}
if (elements.downloadMaxTotalSize) {
elements.downloadMaxTotalSize.textContent = formatBinarySize(limits.max_total_input_bytes);
}
if (elements.downloadMaxFileSize) {
elements.downloadMaxFileSize.textContent = formatBinarySize(limits.max_individual_file_bytes);
}
if (elements.downloadScanTimeout) {
elements.downloadScanTimeout.textContent = formatSeconds(limits.scan_timeout_seconds);
}
if (elements.downloadSymlinkPolicy) {
elements.downloadSymlinkPolicy.textContent = formatSymlinkPolicy(limits.symlink_policy);
}
}
function monacoLanguageForName(name) {
const lower = (name || "").toLowerCase();
if (lower === "dockerfile" || lower === "containerfile") {
@@ -3302,18 +3380,22 @@ async function submitSearch() {
function setSettingsTab(tab) {
const elements = settingsElements();
settingsState.activeTab = tab === "logs" ? "logs" : (tab === "interface" ? "interface" : "general");
settingsState.activeTab = tab === "logs" ? "logs" : (tab === "downloads" ? "downloads" : (tab === "interface" ? "interface" : "general"));
const isGeneral = settingsState.activeTab === "general";
const isInterface = settingsState.activeTab === "interface";
const isDownloads = settingsState.activeTab === "downloads";
const isLogs = settingsState.activeTab === "logs";
elements.generalTab.classList.toggle("is-active", isGeneral);
elements.generalTab.setAttribute("aria-selected", isGeneral ? "true" : "false");
elements.interfaceTab.classList.toggle("is-active", isInterface);
elements.interfaceTab.setAttribute("aria-selected", isInterface ? "true" : "false");
elements.downloadsTab.classList.toggle("is-active", isDownloads);
elements.downloadsTab.setAttribute("aria-selected", isDownloads ? "true" : "false");
elements.logsTab.classList.toggle("is-active", isLogs);
elements.logsTab.setAttribute("aria-selected", isLogs ? "true" : "false");
elements.generalPanel.classList.toggle("hidden", !isGeneral);
elements.interfacePanel.classList.toggle("hidden", !isInterface);
elements.downloadsPanel.classList.toggle("hidden", !isDownloads);
elements.logsPanel.classList.toggle("hidden", !isLogs);
}
@@ -3430,6 +3512,8 @@ async function openSettings(tab = "general") {
}
(settingsState.activeTab === "logs"
? elements.logsTab
: settingsState.activeTab === "downloads"
? elements.downloadsTab
: settingsState.activeTab === "interface"
? elements.interfaceTab
: elements.generalTab).focus();
@@ -4107,6 +4191,7 @@ function setupEvents() {
settings.closeButton.onclick = closeSettings;
settings.generalTab.onclick = () => setSettingsTab("general");
settings.interfaceTab.onclick = () => setSettingsTab("interface");
settings.downloadsTab.onclick = () => setSettingsTab("downloads");
settings.logsTab.onclick = async () => {
setSettingsTab("logs");
await loadHistoryForSettings();