| 1 | <!doctype html> |
| 2 | <!-- Throwaway: the sidebar's header, rendered outside the extension so the |
| 3 | layout can be looked at in a plain tab. Not shipped by build.sh. --> |
| 4 | <html> |
| 5 | <head> |
| 6 | <meta charset="utf-8" /> |
| 7 | <title>header mock</title> |
| 8 | <link rel="stylesheet" href="sidebar.css" /> |
| 9 | <style> |
| 10 | body { margin: 0; width: 380px; } |
| 11 | #term { height: 220px; background: var(--bg); } |
| 12 | #mode { |
| 13 | position: fixed; left: 390px; top: 8px; |
| 14 | font: 12px system-ui; color: #888; |
| 15 | } |
| 16 | </style> |
| 17 | </head> |
| 18 | <body> |
| 19 | <script src="lib/theme.js"></script> |
| 20 | <script> |
| 21 | // ?light renders the light palette; the default is dark. |
| 22 | const dark = matchMedia("(prefers-color-scheme: dark)").matches; |
| 23 | const t = Themes.resolveTheme( |
| 24 | location.search.includes("light") ? "light" : "dark", dark); |
| 25 | for (const [k, v] of Object.entries(t.ui)) document.documentElement.style.setProperty(k, v); |
| 26 | |
| 27 | // ?full fills the row; the default is one of each, so both layouts can be |
| 28 | // seen with slack left in the strip. |
| 29 | const FULL = location.search.includes("full"); |
| 30 | const SESSIONS = FULL |
| 31 | ? [ |
| 32 | { name: "browser", selected: true }, |
| 33 | { name: "anvil", state: "ready" }, |
| 34 | { name: "dotfiles", state: "working" }, |
| 35 | ] |
| 36 | : [{ name: "browser", selected: true }]; |
| 37 | const WINDOWS = FULL |
| 38 | ? [ |
| 39 | { index: 1, name: "termbridge", active: true, state: "working" }, |
| 40 | { index: 2, name: "sidebar", state: "waiting" }, |
| 41 | { index: 3, name: "notes" }, |
| 42 | ] |
| 43 | : [{ index: 1, name: "termbridge", active: true, state: "working" }]; |
| 44 | const GLYPH = { working: "✳", waiting: "●", ready: "●", idle: "●", none: "" }; |
| 45 | |
| 46 | (async () => { |
| 47 | const html = await (await fetch("sidebar.html")).text(); |
| 48 | const doc = new DOMParser().parseFromString(html, "text/html"); |
| 49 | const header = doc.querySelector("header"); |
| 50 | document.body.prepend(header); |
| 51 | const term = document.createElement("div"); |
| 52 | term.id = "term"; |
| 53 | document.body.appendChild(term); |
| 54 | |
| 55 | document.body.classList.add("has-session"); |
| 56 | const sessions = document.getElementById("sessions"); |
| 57 | sessions.hidden = false; |
| 58 | document.getElementById("session-new").hidden = false; |
| 59 | for (const s of SESSIONS) { |
| 60 | const b = document.createElement("button"); |
| 61 | b.className = `session-tab${s.attached ? " attached" : ""}`; |
| 62 | b.setAttribute("role", "tab"); |
| 63 | b.setAttribute("aria-selected", String(!!s.selected)); |
| 64 | if (!s.selected) { |
| 65 | const g = document.createElement("span"); |
| 66 | g.className = `glyph ${s.state ?? "none"}`; |
| 67 | g.textContent = GLYPH[s.state ?? "none"]; |
| 68 | b.appendChild(g); |
| 69 | } |
| 70 | const n = document.createElement("span"); |
| 71 | n.className = "name"; |
| 72 | n.textContent = s.name; |
| 73 | b.appendChild(n); |
| 74 | sessions.appendChild(b); |
| 75 | } |
| 76 | |
| 77 | const tabs = document.getElementById("tabs"); |
| 78 | tabs.hidden = false; |
| 79 | document.getElementById("tab-new").hidden = false; |
| 80 | for (const w of WINDOWS) { |
| 81 | const slot = document.createElement("div"); |
| 82 | slot.className = `tab-slot closable${w.active ? " active" : ""}`; |
| 83 | const b = document.createElement("button"); |
| 84 | b.className = "tab"; |
| 85 | b.setAttribute("role", "tab"); |
| 86 | b.setAttribute("aria-selected", String(!!w.active)); |
| 87 | const g = document.createElement("span"); |
| 88 | g.className = `glyph ${w.state ?? "none"}`; |
| 89 | g.textContent = GLYPH[w.state ?? "none"]; |
| 90 | b.appendChild(g); |
| 91 | const n = document.createElement("span"); |
| 92 | n.className = "name"; |
| 93 | n.textContent = w.name; |
| 94 | b.appendChild(n); |
| 95 | slot.appendChild(b); |
| 96 | tabs.appendChild(slot); |
| 97 | } |
| 98 | |
| 99 | // What syncJump would say with a pane waiting on you. |
| 100 | const jump = document.getElementById("jump"); |
| 101 | jump.disabled = false; |
| 102 | jump.dataset.state = "waiting"; |
| 103 | |
| 104 | // The same DOM moves applyTabMode makes, so the groups layout can be |
| 105 | // looked at too. Everything below mirrors sidebar.js — if the two |
| 106 | // disagree, sidebar.js is right and this is stale. |
| 107 | const $ = (id) => document.getElementById(id); |
| 108 | const HUES = { browser: -1, anvil: 210, dotfiles: 25 }; |
| 109 | function setMode(mode) { |
| 110 | const top = $("session-strip"); |
| 111 | const row = $("window-strip"); |
| 112 | document.body.classList.toggle("groups", mode === "groups"); |
| 113 | if (mode === "groups") { |
| 114 | row.prepend($("reconnect"), $("settings-toggle")); |
| 115 | row.insertBefore($("status-text"), $("tabs")); |
| 116 | row.append($("tab-new")); |
| 117 | $("omni-strip").prepend($("pick"), $("jump")); |
| 118 | $("omni-strip").hidden = false; |
| 119 | $("omni-here").hidden = false; |
| 120 | $("omni").value = ""; |
| 121 | $("omni-cwd").hidden = false; |
| 122 | $("omni-cwd").textContent = "~/Code/browser-terminal-extension"; |
| 123 | top.hidden = true; |
| 124 | document.body.classList.add("has-windows"); |
| 125 | drawGroups(); |
| 126 | } else { |
| 127 | top.hidden = false; |
| 128 | top.prepend($("reconnect"), $("settings-toggle")); |
| 129 | top.insertBefore($("status-text"), $("sessions")); |
| 130 | top.append($("session-new"), $("session-name")); |
| 131 | row.prepend($("pick"), $("jump")); |
| 132 | row.append($("tab-new")); |
| 133 | $("omni-strip").hidden = true; |
| 134 | document.body.classList.remove("has-windows"); |
| 135 | drawWindows(); |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | function drawGroups() { |
| 140 | const strip = $("tabs"); |
| 141 | strip.textContent = ""; |
| 142 | for (const s of SESSIONS) { |
| 143 | const hue = HUES[s.name]; |
| 144 | const chip = document.createElement("button"); |
| 145 | chip.className = |
| 146 | `group-chip${s.selected ? " current" : ""}${hue < 0 ? " grey" : ""}`; |
| 147 | chip.style.setProperty("--group-h", String(hue)); |
| 148 | chip.setAttribute("aria-expanded", "true"); |
| 149 | const caret = document.createElement("span"); |
| 150 | caret.className = "caret"; |
| 151 | chip.appendChild(caret); |
| 152 | const n = document.createElement("span"); |
| 153 | n.className = "name"; |
| 154 | n.textContent = s.name; |
| 155 | chip.appendChild(n); |
| 156 | strip.appendChild(chip); |
| 157 | const windows = s.selected ? WINDOWS : [{ index: 1, name: "shell", active: true }]; |
| 158 | for (const w of windows) strip.appendChild(windowTab(w, s.selected && w.active)); |
| 159 | } |
| 160 | } |
| 161 | |
| 162 | function drawWindows() { |
| 163 | const strip = $("tabs"); |
| 164 | strip.textContent = ""; |
| 165 | for (const w of WINDOWS) strip.appendChild(windowTab(w, w.active)); |
| 166 | } |
| 167 | |
| 168 | function windowTab(w, current) { |
| 169 | const slot = document.createElement("div"); |
| 170 | slot.className = `tab-slot closable${current ? " active" : ""}` + |
| 171 | `${w.active && !current ? " elsewhere" : ""}`; |
| 172 | const b = document.createElement("button"); |
| 173 | b.className = "tab"; |
| 174 | b.setAttribute("role", "tab"); |
| 175 | b.setAttribute("aria-selected", String(!!current)); |
| 176 | const g = document.createElement("span"); |
| 177 | g.className = `glyph ${w.state ?? "none"}`; |
| 178 | g.textContent = GLYPH[w.state ?? "none"]; |
| 179 | b.appendChild(g); |
| 180 | const n = document.createElement("span"); |
| 181 | n.className = "name"; |
| 182 | n.textContent = w.name; |
| 183 | b.appendChild(n); |
| 184 | slot.appendChild(b); |
| 185 | return slot; |
| 186 | } |
| 187 | |
| 188 | const toggle = document.createElement("button"); |
| 189 | toggle.id = "mode"; |
| 190 | toggle.textContent = "switch to tab groups"; |
| 191 | // ?groups starts in the other layout, so a screenshot can be taken of |
| 192 | // it without a click. |
| 193 | let mode = location.search.includes("groups") ? "groups" : "nested"; |
| 194 | if (mode === "groups") { |
| 195 | toggle.textContent = "switch to nested tabs"; |
| 196 | setMode(mode); |
| 197 | } |
| 198 | toggle.addEventListener("click", () => { |
| 199 | mode = mode === "nested" ? "groups" : "nested"; |
| 200 | toggle.textContent = `switch to ${mode === "nested" ? "tab groups" : "nested tabs"}`; |
| 201 | setMode(mode); |
| 202 | }); |
| 203 | document.body.appendChild(toggle); |
| 204 | })(); |
| 205 | </script> |
| 206 | </body> |
| 207 | </html> |