// "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 = `
`;
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);
}