www-lordnet-sh/assets/js/wm.js
lordtet 955a330ad9 i seriously vibe coded the hell out of this LOL. I'm not a web guy. I'll
look this over to make sure its not TOO slop-heavy later on.
2026-03-23 22:49:10 -04:00

234 lines
7.4 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* ═══════════════════════════════════════════
Phosphor Terminal — Window Manager (wm.js)
═══════════════════════════════════════════ */
const WM = (() => {
"use strict";
let topZ = 100;
const state = new Map(); // wid → { el, hidden, maximized, savedStyle }
let drag = null;
let resize = null;
/* ── Init ──────────────────────────────── */
function init() {
document.querySelectorAll(".window").forEach(register);
setupIcons();
document.addEventListener("mousemove", onMove);
document.addEventListener("mouseup", onUp);
// Focus topmost visible window on load
const visible = [...state.values()].filter(s => !s.hidden);
if (visible.length) focusEl(visible[visible.length - 1].el);
}
/* ── Build and inject window chrome ────── */
function buildChrome(win) {
const title = win.dataset.title || win.dataset.wid || "";
const bar = document.createElement("div");
bar.className = "window-titlebar";
const maxBtn = document.createElement("button");
maxBtn.className = "win-btn maximize";
maxBtn.title = "Full screen";
maxBtn.textContent = "⤢";
const titleSpan = document.createElement("span");
titleSpan.className = "window-title";
titleSpan.textContent = title;
bar.appendChild(maxBtn);
bar.appendChild(titleSpan);
win.insertBefore(bar, win.firstChild);
const footer = document.createElement("div");
footer.className = "window-footer";
const closeBtn = document.createElement("button");
closeBtn.className = "win-btn close";
closeBtn.title = "Close";
closeBtn.textContent = "×";
const spacer = document.createElement("div");
spacer.className = "win-footer-spacer";
const resizeHandle = document.createElement("div");
resizeHandle.className = "win-resize";
resizeHandle.title = "Drag to resize";
resizeHandle.textContent = "◢";
footer.appendChild(closeBtn);
footer.appendChild(spacer);
footer.appendChild(resizeHandle);
win.appendChild(footer);
}
/* ── Register a window element ─────────── */
function register(win) {
const id = win.dataset.wid;
if (!id || state.has(id)) return;
buildChrome(win);
const hidden = win.classList.contains("hidden");
state.set(id, { el: win, hidden, maximized: false, savedStyle: null });
// Draggable title bar
const bar = win.querySelector(".window-titlebar");
bar.addEventListener("mousedown", e => {
if (e.button !== 0 || e.target.closest(".win-btn")) return;
startDrag(e, win);
});
// Focus on any click inside window
win.addEventListener("mousedown", () => {
if (!state.get(id)?.hidden) focusEl(win);
});
// Window buttons
const q = s => win.querySelector(s);
const closeAction = win.dataset.close;
q(".win-btn.close") ?.addEventListener("click", () => closeAction === "back" ? history.back() : hide(id));
q(".win-btn.maximize")?.addEventListener("click", () => toggleMax(id));
// Resize handle
q(".win-resize")?.addEventListener("mousedown", e => {
if (e.button !== 0) return;
e.preventDefault(); e.stopPropagation();
const r = win.getBoundingClientRect();
resize = { el: win, x0: e.clientX, y0: e.clientY, w0: r.width, h0: r.height };
});
}
/* ── Focus ─────────────────────────────── */
function focusEl(win) {
document.querySelectorAll(".window.focused")
.forEach(w => w.classList.remove("focused"));
win.classList.add("focused");
win.style.zIndex = ++topZ;
}
function focus(id) {
const s = state.get(id);
if (s && !s.hidden) focusEl(s.el);
}
/* ── Show / Hide ───────────────────────── */
function show(id) {
const s = state.get(id);
if (!s) return;
s.el.classList.remove("hidden");
s.hidden = false;
focusEl(s.el);
}
function hide(id) {
const s = state.get(id);
if (!s) return;
s.el.classList.add("hidden");
s.hidden = true;
}
function toggle(id) {
const s = state.get(id);
if (!s) return;
if (s.hidden) {
show(id);
} else if (s.el.classList.contains("focused")) {
hide(id);
} else {
focusEl(s.el);
}
}
/* ── Maximize / Restore ────────────────── */
function toggleMax(id) {
const s = state.get(id);
if (!s) return;
const el = s.el;
const area = document.querySelector(".desktop-area");
if (!s.maximized) {
s.savedStyle = {
top: el.style.top, left: el.style.left,
width: el.style.width, height: el.style.height,
transform: el.style.transform,
};
el.style.transform = "none";
el.style.top = "0";
el.style.left = "0";
el.style.width = area.clientWidth + "px";
el.style.height = area.clientHeight + "px";
s.maximized = true;
} else {
const sv = s.savedStyle;
el.style.top = sv.top;
el.style.left = sv.left;
el.style.width = sv.width;
el.style.height = sv.height;
el.style.transform = sv.transform;
s.maximized = false;
}
focusEl(el);
}
/* ── Drag ──────────────────────────────── */
function startDrag(e, win) {
e.preventDefault();
focusEl(win);
// Freeze any CSS transform into explicit top/left so dragging is consistent
const wr = win.getBoundingClientRect();
const pr = win.offsetParent?.getBoundingClientRect() ?? { left: 0, top: 0 };
const left0 = wr.left - pr.left;
const top0 = wr.top - pr.top;
win.style.transform = "none";
win.style.left = left0 + "px";
win.style.top = top0 + "px";
drag = { el: win, x0: e.clientX, y0: e.clientY, left0, top0 };
document.body.style.cursor = "move";
}
/* ── Mouse events ──────────────────────── */
function onMove(e) {
if (drag) {
const dx = e.clientX - drag.x0;
const dy = e.clientY - drag.y0;
drag.el.style.left = (drag.left0 + dx) + "px";
drag.el.style.top = (drag.top0 + dy) + "px";
}
if (resize) {
const dx = e.clientX - resize.x0;
const dy = e.clientY - resize.y0;
resize.el.style.width = Math.max(280, resize.w0 + dx) + "px";
resize.el.style.height = Math.max(140, resize.h0 + dy) + "px";
}
}
function onUp() {
drag = null;
resize = null;
document.body.style.cursor = "";
}
/* ── Desktop icons ─────────────────────── */
function setupIcons() {
document.querySelectorAll(".desktop-icon[data-opens]").forEach(icon => {
icon.addEventListener("click", () => {
document.querySelectorAll(".desktop-icon.active")
.forEach(i => i.classList.remove("active"));
icon.classList.add("active");
show(icon.dataset.opens);
});
});
}
/* ── Boot: hide icons for hidden windows ─ */
document.addEventListener("DOMContentLoaded", init);
return { show, hide, toggle, focus };
})();