Skip to content
quarry Artifact [f2544a4219]
Login

Artifact [f2544a4219]

Artifact f2544a421913419c60a480e62751d347f11b9a29ed49d98d93d7dc65fc5bc733:


// ophour.js: the two things the operations page's hour cannot do as HTML.
//
// The four tiles and the shared hour are both in the page, and its
// data-view attribute says which is shown. The control is a form button
// that reloads with ?hour=, so with no script it still works; here a
// press flips the attribute in place and corrects the address to match,
// so a reload or a shared link shows the same view. The second thing is
// reading a minute: pointing at, or dragging across, the shared hour
// draws one line through all four rows and puts that minute's four
// values in the readout under the title.
//
// Nothing here holds an element. The page streams: every few seconds the
// body is patched, so the listeners sit on the document and find their
// panel when the event happens, and the numbers a minute is read from
// travel in the panel's data-hover, worded by the server.
(function () {
  "use strict";

  // View. The button that was pressed leaves the screen with its view, so
  // focus goes to the button that took its place.
  document.addEventListener("click", function (e) {
    var btn = e.target.closest && e.target.closest(".ops-hs-f button[name=hour]");
    if (!btn || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey || e.button) return;
    var live = btn.closest("[data-view]");
    if (!live) return;
    e.preventDefault();
    var v = btn.value, open = v !== "0";
    live.setAttribute("data-view", open ? "hour" : "tiles");
    if (open && v !== "1") live.setAttribute("data-focus", v);
    else live.removeAttribute("data-focus");
    try {
      var u = new URL(location.href);
      if (open) u.searchParams.set("hour", v);
      else u.searchParams.delete("hour");
      history.replaceState(null, "", u.pathname + u.search + u.hash);
    } catch (err) { /* a cosmetic address must not stop the toggle */ }
    var next = live.querySelector(open ? "[data-hook=ops-compare-back]" : "[data-hook=ops-compare]");
    if (next) next.focus({ preventScroll: true });
    hide(live.querySelector(".ops-hr"));
  });

  // Reading a minute.
  function dataOf(panel) {
    try { return JSON.parse(panel.querySelector(".ops-hr-grid").getAttribute("data-hover")); } catch (e) { return null; }
  }
  function hide(panel) {
    if (!panel) return;
    var c = panel.querySelector(".ops-hr-cross");
    if (c) { c.hidden = true; delete c.dataset.at; }
  }
  function show(panel, i) {
    var d = dataOf(panel), c = panel.querySelector(".ops-hr-cross"), r = panel.querySelector(".ops-hr-read");
    if (!d || !c || !r || !d.at.length) return;
    var n = d.at.length;
    i = Math.max(0, Math.min(n - 1, i));
    c.style.left = ((i + 0.5) / n * 100) + "%";
    c.hidden = false;
    // The position lives on the crosshair, which patches leave alone; the
    // panel itself is rewritten from the server every few seconds.
    c.dataset.at = String(i);
    r.textContent = d.at[i] + " · " + d.rows.map(function (row) { return row.name + " " + row.v[i]; }).join(" · ");
  }
  function slot(panel, x) {
    var over = panel.querySelector(".ops-hr-over");
    var d = dataOf(panel);
    if (!over || !d) return -1;
    var box = over.getBoundingClientRect();
    if (x < box.left || x > box.right || box.width <= 0) return -1;
    return Math.floor((x - box.left) / box.width * d.at.length);
  }
  var pointed = null;
  function move(e) {
    var grid = e.target.closest && e.target.closest(".ops-hr-grid");
    if (!grid) return;
    var panel = grid.closest(".ops-hr");
    var i = slot(panel, e.clientX);
    if (i < 0) { hide(panel); return; }
    pointed = panel;
    show(panel, i);
  }
  document.addEventListener("pointermove", move);
  document.addEventListener("pointerdown", move);
  // A mouse that leaves lets go; a finger that lifts keeps its minute on
  // screen, which is the one it was reading.
  document.addEventListener("pointerout", function (e) {
    if (e.pointerType === "touch" || !pointed) return;
    var grid = e.target.closest && e.target.closest(".ops-hr-grid");
    if (grid && !grid.contains(e.relatedTarget)) { hide(pointed); pointed = null; }
  });
  document.addEventListener("keydown", function (e) {
    var grid = e.target.closest && e.target.closest(".ops-hr-grid");
    if (!grid || e.target !== grid) return;
    var panel = grid.closest(".ops-hr"), d = dataOf(panel);
    if (!d) return;
    var cross = panel.querySelector(".ops-hr-cross");
    var at = !cross || cross.dataset.at === undefined ? d.at.length : +cross.dataset.at;
    var to = { ArrowLeft: at - 1, ArrowRight: at + 1, Home: 0, End: d.at.length - 1 }[e.key];
    if (e.key === "Escape") { hide(panel); return; }
    if (to === undefined) return;
    e.preventDefault();
    show(panel, to);
  });
})();