Files

119 lines
4.3 KiB
JavaScript

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;
}