import { api, describeError } from "./api.js"; import { element } from "./dom.js"; import { notificationsEnabled, setNotificationsEnabled } from "./notify.js"; const themeStorageKey = "ddc-theme"; const fields = { dialog: document.getElementById("settings-dialog"), form: document.getElementById("settings-form"), closeButton: document.getElementById("settings-close"), cancelButton: document.getElementById("settings-cancel"), error: document.getElementById("settings-error"), concurrent: document.getElementById("setting-concurrent"), connections: document.getElementById("setting-connections"), notifications: document.getElementById("setting-notifications"), minFree: document.getElementById("setting-min-free"), ports: document.getElementById("setting-ports"), autoExtract: document.getElementById("setting-auto-extract"), cleanup: document.getElementById("setting-cleanup"), downloadPaths: document.getElementById("setting-download-paths"), theme: document.getElementById("setting-theme"), downloadDir: document.getElementById("info-download-dir"), roots: document.getElementById("info-roots"), }; export function setupSettings(onSaved) { fields.closeButton.addEventListener("click", () => fields.dialog.close()); fields.cancelButton.addEventListener("click", () => fields.dialog.close()); fields.form.addEventListener("submit", (event) => saveSettings(event, onSaved)); fields.theme.addEventListener("change", () => applyTheme(fields.theme.value)); fields.dialog.addEventListener("close", () => applyTheme(readTheme())); } export async function loadSettings() { const data = await api.settings(); return data.settings; } export async function openSettings() { fields.error.textContent = ""; const data = await api.settings(); fillForm(data); fields.dialog.showModal(); } function fillForm(data) { const settings = data.settings; fields.concurrent.value = settings.maxConcurrentDownloads; fields.connections.value = settings.connectionsPerDownload; fields.notifications.checked = notificationsEnabled(); fields.minFree.value = settings.minFreeGB; fields.ports.value = settings.allowedPorts.length > 0 ? settings.allowedPorts.join(", ") : "*"; fields.autoExtract.checked = settings.autoExtract; fields.cleanup.checked = settings.cleanupAfterMove; fields.downloadPaths.value = (settings.downloadPaths || []).join("\n"); fields.theme.value = readTheme(); fields.downloadDir.textContent = data.downloadDir; fields.roots.replaceChildren(...data.libraryRoots.map((root) => element("div", "mono", root))); } async function saveSettings(event, onSaved) { event.preventDefault(); fields.error.textContent = ""; try { const payload = { maxConcurrentDownloads: Number(fields.concurrent.value), connectionsPerDownload: Number(fields.connections.value), minFreeGB: Number(fields.minFree.value), allowedPorts: parsePorts(fields.ports.value), autoExtract: fields.autoExtract.checked, cleanupAfterMove: fields.cleanup.checked, downloadPaths: parseDownloadPaths(fields.downloadPaths.value), }; await setNotificationsEnabled(fields.notifications.checked); const data = await api.saveSettings(payload); storeTheme(fields.theme.value); fields.dialog.close(); onSaved(data.settings); } catch (error) { fields.error.textContent = describeError(error); } } function parsePorts(text) { const trimmed = text.trim(); if (trimmed === "*") return []; const ports = trimmed.split(/[\s,]+/).filter(Boolean).map(Number); const invalid = ports.length === 0 || ports.some((port) => !Number.isInteger(port) || port < 1 || port > 65535); if (invalid) throw new Error("Allowed ports must be numbers between 1 and 65535, or * for any port."); return ports; } function parseDownloadPaths(text) { return text.split("\n").map((line) => line.trim()).filter(Boolean); } function readTheme() { try { return localStorage.getItem(themeStorageKey) || "system"; } catch { return "system"; } } function storeTheme(theme) { try { localStorage.setItem(themeStorageKey, theme); } catch { return; } finally { applyTheme(theme); } } function applyTheme(theme) { if (theme === "light" || theme === "dark") document.documentElement.dataset.theme = theme; else delete document.documentElement.dataset.theme; }