Files
time/ventures/havre/split.js
T

85 lines
4.6 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.
// split.js: on a wide screen, the line between the list (chats / characters / harbor) and the chat can be dragged, or
// moved with the arrow keys once focused. Double-click (or Home) puts it back. The list can also be hidden, like a
// sidebar: the ‹ button on the line, dragging it most of the way to the left, or Ctrl/Cmd+B; › brings it back.
// The width and whether it's hidden are kept in this browser. Below 1040px the two take turns on screen, so there is
// nothing to drag and the divider hides (style.css).
(() => {
const KEY = "havre.split", HID = "havre.split.hidden", MIN = 320, CHAT_MIN = 380, SNAP = 200;
const get = (k) => { try { return JSON.parse(localStorage.getItem(k)); } catch { return null; } };
const put = (k, v) => { try { v == null ? localStorage.removeItem(k) : localStorage.setItem(k, JSON.stringify(v)); } catch { /* not kept */ } };
function start() {
const studio = document.getElementById("studio"), chars = document.getElementById("chars"), chat = document.getElementById("chat");
if (!studio || !chars || !chat) return;
const bar = document.createElement("div");
bar.className = "splitter";
bar.tabIndex = 0;
bar.setAttribute("role", "separator");
bar.setAttribute("aria-orientation", "vertical");
bar.setAttribute("aria-controls", "chars");
bar.setAttribute("aria-label", "Resize the list and the chat");
bar.title = "drag to resize · double-click to reset";
const btn = document.createElement("button");
btn.type = "button";
btn.className = "splitbtn";
bar.append(btn);
chat.before(bar);
const wide = () => window.matchMedia("(min-width: 1041px)").matches;
const clamp = (w) => Math.round(Math.max(MIN, Math.min(w, studio.clientWidth - CHAT_MIN - bar.offsetWidth)));
let hidden = get(HID) === true;
function show() {
const off = hidden && wide();
studio.classList.toggle("list-hidden", off);
btn.textContent = off ? "›" : "‹";
btn.setAttribute("aria-label", off ? "Show the list" : "Hide the list");
btn.title = off ? "show the list (ctrl+b)" : "hide the list (ctrl+b)";
btn.setAttribute("aria-expanded", off ? "false" : "true");
}
function hide(on) { hidden = on; put(HID, on || null); show(); }
function set(w, keep = true) {
if (w == null || !wide()) { chars.style.flex = ""; bar.removeAttribute("aria-valuenow"); if (w == null && keep) put(KEY, null); return; }
w = clamp(w);
chars.style.flex = `0 0 ${w}px`;
bar.setAttribute("aria-valuenow", String(w));
if (keep) put(KEY, w);
}
set(get(KEY), false);
show();
window.addEventListener("resize", () => { set(get(KEY), false); show(); });
btn.addEventListener("pointerdown", (e) => e.stopPropagation());
btn.addEventListener("dblclick", (e) => e.stopPropagation());
btn.addEventListener("click", () => hide(!hidden));
bar.addEventListener("pointerdown", (e) => {
if (!wide() || e.button !== 0) return;
e.preventDefault();
bar.setPointerCapture(e.pointerId);
const left = studio.getBoundingClientRect().left;
document.body.classList.add("splitting");
const move = (ev) => {
const w = ev.clientX - left;
if (w < SNAP) { if (!hidden) hide(true); return; } // most of the way left: tuck it away
if (hidden) hide(false);
set(w);
};
const up = () => { bar.removeEventListener("pointermove", move); bar.removeEventListener("pointerup", up); bar.removeEventListener("pointercancel", up); document.body.classList.remove("splitting"); };
bar.addEventListener("pointermove", move);
bar.addEventListener("pointerup", up);
bar.addEventListener("pointercancel", up);
});
bar.addEventListener("dblclick", () => { hide(false); set(null); });
bar.addEventListener("keydown", (e) => {
if (e.target !== bar) return;
const now = chars.getBoundingClientRect().width, step = e.shiftKey ? 80 : 20;
if (e.key === "ArrowLeft") { if (hidden) return e.preventDefault(); if (now - step < MIN) hide(true); else set(now - step); }
else if (e.key === "ArrowRight") { if (hidden) hide(false); else set(now + step); }
else if (e.key === "Home") { hide(false); set(null); }
else if (e.key === "Enter" || e.key === " ") hide(!hidden);
else return;
e.preventDefault();
});
document.addEventListener("keydown", (e) => {
if ((e.ctrlKey || e.metaKey) && !e.altKey && !e.shiftKey && e.key.toLowerCase() === "b" && wide()) { e.preventDefault(); hide(!hidden); }
});
}
if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", start); else start();
})();