let menu = null, menuAt = null;
let picking = false;
export async function pick(ctx) {
const { capability, state, toast, browseEl } = ctx;
if (!capability) { toast("Folders open from the snyvi window", "Or from a terminal: snyvi browse <folder>"); return; }
if (picking) return;
picking = true;
browseEl.classList.add("picking");
try {
const r = await fetch("/api/browse/pick", { method: "POST", headers: { "x-snyvi-capability": capability } });
if (r.status === 204) return; const j = await r.json().catch(() => ({}));
if (!r.ok) { toast("Could not open a folder", j.error || `HTTP ${r.status}`); return; }
if (!state.browse.some(x => x.id === j.root.id)) state.browse = state.browse.concat(j.root);
ctx.drawBrowse();
const d = browseEl.querySelector(`.b-root[data-root="${j.root.id}"]`);
if (d) d.open = true;
ctx.browse(j.root.id, "", true);
} catch (e) { toast("Could not open a folder", String(e)); }
finally { picking = false; browseEl.classList.remove("picking"); }
}
export async function make(ctx, f) {
const { toast, api } = ctx;
try {
const j = await api("/api/desks", f ? { root: f.root, path: f.path } : {});
try {
const p = await api(`/api/desks/${j.desk.id}/panes`, {});
await api(`/api/panes/${p.pane.id}/start`, { cmd: "" });
} catch (e) { toast("The desk is made, but its shell did not start", String(e)); }
await ctx.load();
ctx.show(j.desk.id, true);
} catch (e) { toast("Could not make a desk", String(e)); }
}
export async function drop(ctx, b) {
const { state, toast, api } = ctx;
if (!b.dataset.armed) {
b.dataset.armed = "1"; b.textContent = "Close?"; b.title = "Close the desk and its panels: click again";
const li = b.closest("li"); li.classList.add("arming");
setTimeout(() => { if (b.isConnected) { delete b.dataset.armed; b.textContent = "✕"; b.title = "Close desk"; li.classList.remove("arming"); } }, 3000);
return;
}
const id = +b.dataset.dropdesk;
try { await api(`/api/desks/${id}/delete`, {}); }
catch (err) { toast(`Could not close the desk: ${err.message}`); return; }
ctx.forget(id);
await ctx.load();
if (state.view === "desk" && state.deskId === id) ctx.show(null, true);
}
export function open(ctx, f, x, y) {
const { state, capability, esc } = ctx;
if (!menu) install(ctx);
menuAt = f;
const here = state.desks ? state.desks.desks.filter(d => d.root === f.abs) : [];
menu.innerHTML = (capability ? `<button role="menuitem" data-m="new">New desk here</button>` +
here.map(d => `<button role="menuitem" data-m="show" data-id="${d.id}">Show desk ${esc(d.name)}</button>`).join("") + `<hr>` : "") +
`<button role="menuitem" data-m="copy">Copy path</button><button role="menuitem" data-m="term">Open terminal here</button>`;
menu.hidden = false;
menu.style.left = Math.max(4, Math.min(x, innerWidth - menu.offsetWidth - 8)) + "px";
menu.style.top = Math.max(4, Math.min(y, innerHeight - menu.offsetHeight - 8)) + "px";
menu.querySelector("button").focus();
}
const close = () => { if (menu) { menu.hidden = true; menuAt = null; } };
function install(ctx) {
menu = document.createElement("div");
menu.id = "ctx"; menu.hidden = true; menu.setAttribute("role", "menu");
document.body.append(menu);
menu.addEventListener("click", e => {
const b = e.target.closest("[data-m]"), f = menuAt;
if (!b || !f) return;
close();
const m = b.dataset.m;
if (m === "new") make(ctx, f);
else if (m === "show") ctx.show(+b.dataset.id, true);
else if (m === "copy") { navigator.clipboard?.writeText(f.abs); ctx.toast("Copied", f.abs); }
else ctx.terminal({ root: f.root, path: f.path });
});
menu.addEventListener("keydown", e => {
const bs = [...menu.querySelectorAll("button")], at = bs.indexOf(document.activeElement);
if (e.key === "ArrowDown" || e.key === "ArrowUp") bs[(at + (e.key === "ArrowDown" ? 1 : -1) + bs.length) % bs.length].focus();
else if (e.key === "Escape") close();
else return;
e.preventDefault(); e.stopPropagation();
});
document.addEventListener("pointerdown", e => { if (!menu.hidden && !menu.contains(e.target)) close(); }, true);
addEventListener("blur", close);
}
export { close as shut };