// Settings dialog — nav list left, section content right. Mirrors the old // src/ui/app/settings/{general,connections,transfers,about}.rs sections // (Security folded in here too). Unlike connectDialog/opDialogs there are no // free-text inputs in here, so the whole content pane can just be rebuilt // from scratch on every render -- no focus/caret state to preserve. import { state, notify } from "../store.js"; import { t } from "../i18n.js"; import { iconMarkup } from "../icons.js"; import { escapeHtml } from "../dom.js"; import { setConfirmBeforeDelete, useCurrentFolderAsDefault, resetDefaultFolder, setMaxConcurrent, setAutoClearCompletedSecs, togglePanel, clearHistory, deleteSite, forgetPassword, forgetAllPasswords, revealDatabase, setLanguage, setTheme, } from "../settingsActions.js"; import { openSiteManager } from "./siteManager.js"; const LANG_OPTIONS = [ ["en", "English"], ["ru", "Русский"], ["es", "Español"], ["fr", "Français"], ["de", "Deutsch"], ["it", "Italiano"], ["pt", "Português"], ["pl", "Polski"], ["zh", "简体中文"], ["ja", "日本語"], ["ko", "한국어"], ["tr", "Türkçe"], ]; const SECTIONS = [ { id: "general", icon: "settings", labelKey: "settings.navGeneral" }, { id: "appearance", icon: "monitor", labelKey: "settings.navAppearance" }, { id: "connections", icon: "serverSquare", labelKey: "settings.navConnections" }, { id: "history", icon: "history", labelKey: "settings.navHistory" }, { id: "security", icon: "shieldKeyhole", labelKey: "settings.navSecurity" }, { id: "transfers", icon: "clock", labelKey: "settings.navTransfers" }, { id: "about", icon: "documentText", labelKey: "settings.navAbout" }, ]; let overlayEl = null; let activeSection = "general"; let historyClearConfirm = false; export function openSettings() { activeSection = "general"; historyClearConfirm = false; state.showSettingsDialog = true; notify(); } function close() { state.showSettingsDialog = false; notify(); } function switchSection(id) { activeSection = id; historyClearConfirm = false; renderContent(); renderNav(); } // A plain , not a `; } function sectionAppearance() { const currentTheme = state.settings.theme; const themes = [ ["ember", "settings.themeEmber"], ["frost", "settings.themeFrost"], ["midnight", "settings.themeMidnight"], ["forest", "settings.themeForest"], ["dawn", "settings.themeDawn"], ["ash", "settings.themeAsh"], ]; const toggles = [ ["showToolbar", "settings.toolbar"], ["showSidebar", "settings.sidebar"], ["showStatusBar", "settings.statusBar"], ["showQueuePanel", "settings.transferQueue"], ]; return `
${t("settings.navAppearance")}
${t("settings.theme")}
${t("settings.themeHint")}
${themes .map( ([id, labelKey]) => ` `, ) .join("")}
${toggles .map( ([key, labelKey]) => `
${switchToggle(state.settings[key])}
${t(labelKey)}
`, ) .join("")}
`; } function sectionConnections() { const sites = state.sites .map( (s) => `
${escapeHtml(s.name)}
${escapeHtml(s.username)}@${escapeHtml(s.host)}:${s.port}
`, ) .join(""); const bookmarks = state.sidebar.bookmarks .map( (b) => `
${escapeHtml(b.name)}
${escapeHtml(b.path)}
`, ) .join(""); return `
${t("settings.sites")}
${state.sites.length ? sites : `
${t("settings.noSites")}
`}
${t("settings.bookmarksTitle")}
${state.sidebar.bookmarks.length ? bookmarks : `
${t("settings.noBookmarks")}
`}
`; } function sectionHistory() { const rows = state.history .map( (h) => `
${escapeHtml(h.username)}@${escapeHtml(h.host)}:${h.port}
${escapeHtml(h.connectedAt)}
`, ) .join(""); const footer = state.history.length ? historyClearConfirm ? `
${t("settings.confirmClear")}
` : `` : ""; return `
${t("settings.historyTitle")}
${state.history.length ? rows : `
${t("settings.historyEmpty")}
`} ${footer}
`; } function sectionSecurity() { const rows = state.sites .map( (s) => `
${escapeHtml(s.name)}
`, ) .join(""); return `
${t("settings.navSecurity")}
${t("settings.securityHint")}
${state.sites.length ? rows : `
${t("settings.noConnections")}
`}
`; } function segmented(options, current, action) { return `
${options .map( ([label, value]) => ``, ) .join("")}
`; } function sectionTransfers() { return `
${t("settings.navTransfers")}
${t("settings.concurrentTitle")}
${t("settings.concurrentHint")}
${segmented( [1, 2, 3, 4, 5, 6].map((n) => [String(n), n]), state.settings.maxConcurrent, "set-max-concurrent", )}
${t("settings.autoRemoveTitle")}
${t("settings.autoRemoveHint")}
${segmented( [ [t("settings.never"), 0], [t("settings.tenSec"), 10], [t("settings.thirtySec"), 30], [t("settings.oneMin"), 60], ], state.settings.autoClearCompletedSecs, "set-auto-clear", )}
`; } function sectionAbout() { return `
${t("settings.navAbout")}
${t("settings.version")}0.1.0
${t("settings.license")}GPL-3.0-only
${t("settings.database")}…
`; } const RENDERERS = { general: sectionGeneral, appearance: sectionAppearance, connections: sectionConnections, history: sectionHistory, security: sectionSecurity, transfers: sectionTransfers, about: sectionAbout, }; // --- shell + wiring ------------------------------------------------------------ function build() { const overlay = document.createElement("div"); overlay.className = "overlay"; overlay.addEventListener("mousedown", (e) => { if (e.target === overlay) close(); }); const dialog = document.createElement("div"); dialog.className = "dialog dialog-settings"; dialog.addEventListener("mousedown", (e) => e.stopPropagation()); dialog.innerHTML = `
${iconMarkup("settings", 16)} ${t("settings.title")}
`; dialog.querySelector('[data-action="close"]').addEventListener("click", close); overlay.appendChild(dialog); document.body.appendChild(overlay); return overlay; } function renderNav() { const nav = overlayEl.querySelector('[data-role="nav"]'); nav.innerHTML = SECTIONS.map( (s) => ` `, ).join(""); nav.querySelectorAll("[data-section]").forEach((btn) => { btn.addEventListener("click", () => switchSection(btn.dataset.section)); }); } function renderContent() { const content = overlayEl.querySelector('[data-role="content"]'); content.innerHTML = RENDERERS[activeSection](); wireContentActions(content); } function wireContentActions(root) { root.querySelector("[data-action='toggle-confirm-delete']")?.addEventListener("click", () => { setConfirmBeforeDelete(!state.settings.confirmBeforeDelete); renderContent(); }); root.querySelector("[data-action='use-current-folder']")?.addEventListener("click", async () => { await useCurrentFolderAsDefault(); renderContent(); }); root.querySelector("[data-action='reset-folder']")?.addEventListener("click", async () => { await resetDefaultFolder(); renderContent(); }); root.querySelectorAll("[data-action='set-theme'] .theme-card").forEach((el) => { el.addEventListener("click", () => { setTheme(el.dataset.themeId); renderContent(); }); }); root.querySelectorAll("[data-panel-toggle]").forEach((el) => { el.addEventListener("click", () => { togglePanel(el.dataset.panelToggle); renderContent(); }); }); root.querySelectorAll("[data-action='edit-site']").forEach((btn) => { btn.addEventListener("click", () => { openSiteManager(btn.dataset.id); }); }); root.querySelectorAll("[data-action='delete-site']").forEach((btn) => { btn.addEventListener("click", async () => { await deleteSite(btn.dataset.id); renderContent(); }); }); root.querySelectorAll("[data-action='delete-bookmark']").forEach((btn) => { btn.addEventListener("click", async () => { const { removeBookmark } = await import("../fsActions.js"); await removeBookmark(Number(btn.dataset.id)); renderContent(); }); }); root.querySelector("[data-action='clear-history']")?.addEventListener("click", () => { historyClearConfirm = true; renderContent(); }); root.querySelector("[data-action='cancel-clear-history']")?.addEventListener("click", () => { historyClearConfirm = false; renderContent(); }); root.querySelector("[data-action='confirm-clear-history']")?.addEventListener("click", async () => { await clearHistory(); historyClearConfirm = false; renderContent(); }); root.querySelectorAll("[data-action='forget-password']").forEach((btn) => { btn.addEventListener("click", () => forgetPassword(btn.dataset.id)); }); root.querySelector("[data-action='forget-all']")?.addEventListener("click", forgetAllPasswords); root.querySelectorAll("[data-action='set-max-concurrent'] .segmented-item").forEach((btn) => { btn.addEventListener("click", () => { setMaxConcurrent(Number(btn.dataset.value)); renderContent(); }); }); root.querySelectorAll("[data-action='set-auto-clear'] .segmented-item").forEach((btn) => { btn.addEventListener("click", () => { setAutoClearCompletedSecs(Number(btn.dataset.value)); renderContent(); }); }); root.querySelector("[data-action='reveal-db']")?.addEventListener("click", revealDatabase); root.querySelector("[data-action='set-language']")?.addEventListener("change", (e) => { setLanguage(e.target.value); // Re-render everything so all strings update immediately renderNav(); renderContent(); // After re-render, re-select the correct option const sel = overlayEl.querySelector("[data-action='set-language']"); if (sel) sel.value = state.settings.language; }); const dbPathEl = root.querySelector('[data-role="db-path"]'); if (dbPathEl) { import("../ipc.js").then((ipc) => ipc.appDataDir().then((dir) => { dbPathEl.textContent = `${dir}/wherry.db`; }), ); } } let wasOpen = false; export function renderSettingsDialog() { if (!state.showSettingsDialog) { if (overlayEl) { overlayEl.remove(); overlayEl = null; } wasOpen = false; return; } if (!wasOpen) { overlayEl = build(); renderNav(); renderContent(); wasOpen = true; } }