// "New Connection" modal — src/ui/dialogs/connection/{view,form}.rs equivalent. import { state, notify } from "../store.js"; import { t } from "../i18n.js"; import { iconMarkup } from "../icons.js"; import { buildParamsFromForm, spawnConnect, DEFAULT_PORTS } from "../connectActions.js"; import { escapeHtml as escapeAttr } from "../dom.js"; const PROTOCOLS = ["sftp", "ftp", "ftps"]; const PROTOCOL_LABELS = { sftp: "SFTP", ftp: "FTP", ftps: "FTPS" }; let form = emptyForm(); let overlayEl = null; let lastOpenSig = null; function emptyForm() { return { protocol: "sftp", host: "", port: "", username: "", password: "", keyPath: "", label: "", }; } /** Called by the toolbar "New Connection" button and the welcome screen CTA. */ export function openNew() { form = emptyForm(); state.connectError = ""; state.showConnectDialog = true; notify(); } /** Called from history "Edit" — prefills everything. */ export function openForEdit(entry) { form = { protocol: entry.protocol, host: entry.host, port: String(entry.port), username: entry.username, password: "", keyPath: entry.keyPath ?? "", label: "", }; state.connectError = ""; state.showConnectDialog = true; notify(); } function close() { if (state.connectLoading) return; state.showConnectDialog = false; notify(); } async function submit() { if (state.connectLoading) return; const result = buildParamsFromForm(form); if (!result.ok) { state.connectError = t(result.error); notify(); return; } await spawnConnect(result.params); } async function browseKeyFile() { try { const dialog = window.__TAURI__.dialog; const path = await dialog.open({ title: "Choose SSH key", multiple: false, directory: false }); if (typeof path === "string") { form.keyPath = path; const input = overlayEl?.querySelector("[data-field=keyPath]"); if (input) input.value = path; } } catch { /* dialog plugin unavailable / user cancelled */ } } 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.addEventListener("mousedown", (e) => e.stopPropagation()); dialog.innerHTML = `
${iconMarkup("serverSquare", 16)} ${t("connect.title")}
${PROTOCOLS.map((p) => ``).join("")}
${t("connect.host")}
${t("connect.username")}
${t("connect.port")}
${t("connect.password")}

${t("connect.keyHint")}

${t("connect.keyFile")}
${t("connect.label")}
`; overlay.appendChild(dialog); dialog.querySelectorAll('[data-action="close"]').forEach((el) => el.addEventListener("click", close), ); dialog.querySelector('[data-action="submit"]').addEventListener("click", submit); dialog.querySelector('[data-action="browse"]').addEventListener("click", browseKeyFile); dialog.querySelectorAll("[data-field]").forEach((input) => { input.addEventListener("input", () => { form[input.dataset.field] = input.value; }); }); const passInput = dialog.querySelector('[data-field="password"]'); dialog.querySelector('[data-action="toggle-pass"]').addEventListener("click", (e) => { const showing = passInput.type === "text"; passInput.type = showing ? "password" : "text"; e.currentTarget.innerHTML = iconMarkup(showing ? "eye" : "eyeClosed", 15); }); dialog.querySelectorAll("[data-protocol]").forEach((btn) => { btn.addEventListener("click", () => { const proto = btn.dataset.protocol; form.protocol = proto; if (!form.port) { dialog.querySelector('[data-field="port"]').placeholder = String(DEFAULT_PORTS[proto]); } dialog.querySelectorAll("[data-protocol]").forEach((b) => b.classList.toggle("active", b === btn)); dialog.querySelector('[data-role="key-section"]').style.display = proto === "sftp" ? "block" : "none"; }); }); dialog.addEventListener("keydown", (e) => { if (e.key === "Enter") submit(); }); return { overlay, dialog }; } function updateDynamic(dialog) { const errRole = dialog.querySelector('[data-role="error"]'); errRole.innerHTML = state.connectError ? `
${iconMarkup("dangerTriangle", 14)}${escapeAttr(state.connectError)}
` : ""; const submitBtn = dialog.querySelector('[data-action="submit"]'); submitBtn.disabled = state.connectLoading; submitBtn.textContent = state.connectLoading ? t("connect.connecting") : t("connect.connect"); const cancelBtn = dialog.querySelector('[data-action="close"]'); cancelBtn.disabled = state.connectLoading; dialog.querySelector('[data-role="spinner"]').innerHTML = state.connectLoading ? '' : ""; } export function renderConnectDialog() { const sig = JSON.stringify([state.showConnectDialog]); if (!state.showConnectDialog) { if (overlayEl) { overlayEl.remove(); overlayEl = null; } lastOpenSig = sig; return; } if (sig !== lastOpenSig || !overlayEl) { if (overlayEl) overlayEl.remove(); const { overlay, dialog } = build(); document.body.appendChild(overlay); overlayEl = overlay; overlayEl._dialog = dialog; setTimeout(() => dialog.querySelector('[data-field="host"]').focus(), 0); lastOpenSig = sig; } updateDynamic(overlayEl._dialog); }