anvilsign in

collin/browser-terminal-extension

main / extension / mock.html
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>