anvilsign in

collin/browser-terminal-extension

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