Skip to content
183 linesCodeBlameRaw
1// The title bar's work: draw what the page under it reports, and send its
2// buttons to the app (src/bridge.rs: title_bar_current, title_bar_act,
3// window_control, window_menu). A click here hands the keyboard back to
4// the page, so typing goes on where it was.
5(function () {
6 var core = window.__TAURI__ && window.__TAURI__.core;
7 var events = window.__TAURI__ && window.__TAURI__.event;
8 function invoke(command, args) {
9 if (core && typeof core.invoke === "function") return core.invoke(command, args || {});
10 return Promise.reject(new Error("the title bar cannot reach the app"));
11 }
12 function quietly(promise) {
13 promise.catch(function () {});
14 }
15 function act(action) {
16 quietly(invoke("title_bar_act", { action: action }));
17 }
18 function control(action) {
19 return invoke("window_control", { action: action });
20 }
21
22 var ua = navigator.userAgent;
23 var MAC = /Macintosh|Mac OS X/.test(ua) && !/Windows/.test(ua);
24 if (MAC) document.documentElement.classList.add("mac");
25
26 function svg(body) {
27 return (
28 '<svg viewBox="0 0 16 16" width="16" height="16" fill="none" stroke="currentColor" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
29 body +
30 "</svg>"
31 );
32 }
33 // Drawn here, from lines and boxes, as each system draws its own.
34 var ICONS = {
35 menu: svg('<line x1="3" y1="4.5" x2="13" y2="4.5"/><line x1="3" y1="8" x2="13" y2="8"/><line x1="3" y1="11.5" x2="13" y2="11.5"/>'),
36 back: svg('<polyline points="9.5,3.5 5,8 9.5,12.5"/><line x1="5" y1="8" x2="13" y2="8"/>'),
37 forward: svg('<polyline points="6.5,3.5 11,8 6.5,12.5"/><line x1="3" y1="8" x2="11" y2="8"/>'),
38 search: svg('<circle cx="7" cy="7" r="4"/><line x1="10" y1="10" x2="13" y2="13"/>'),
39 settings: svg('<line x1="2.5" y1="4.5" x2="13.5" y2="4.5"/><line x1="2.5" y1="11.5" x2="13.5" y2="11.5"/><circle cx="10" cy="4.5" r="1.75" fill="var(--bg)"/><circle cx="6" cy="11.5" r="1.75" fill="var(--bg)"/>'),
40 minimize: svg('<line x1="3.5" y1="8" x2="12.5" y2="8"/>'),
41 maximize: svg('<rect x="3.5" y="3.5" width="9" height="9" rx="1"/>'),
42 restore: svg('<rect x="3.5" y="5.5" width="7" height="7" rx="1"/><path d="M5.5 5.5V4.5a1 1 0 0 1 1-1h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-1"/>'),
43 close: svg('<line x1="4" y1="4" x2="12" y2="12"/><line x1="12" y1="4" x2="4" y2="12"/>'),
44 };
45
46 function button(kind, label, icon, onClick) {
47 var b = document.createElement("button");
48 b.type = "button";
49 b.tabIndex = -1;
50 b.className = kind;
51 b.setAttribute("aria-label", label);
52 b.title = label;
53 if (icon) b.innerHTML = ICONS[icon];
54 b.addEventListener("click", function (event) {
55 event.stopPropagation();
56 onClick(b);
57 });
58 return b;
59 }
60
61 var bar = document.getElementById("bar");
62 var left = document.getElementById("left");
63 var middle = document.getElementById("middle");
64 var right = document.getElementById("right");
65
66 if (!MAC) {
67 left.append(
68 button("icon", "Menu", "menu", function (b) {
69 var box = b.getBoundingClientRect();
70 quietly(invoke("window_menu", { x: box.left, y: box.bottom }));
71 }),
72 );
73 }
74 var back = button("icon", "Back", "back", function () {
75 act("back");
76 });
77 var forward = button("icon", "Forward", "forward", function () {
78 act("forward");
79 });
80 left.append(back, forward);
81
82 // The app's settings (the runner among them), at the right of the bar,
83 // before the window's buttons: where they are found without the menu.
84 right.append(
85 button("icon", "Settings", "settings", function () {
86 act("settings");
87 }),
88 );
89
90 var maximize = null;
91 function drawMaximized(maximized) {
92 if (!maximize) return;
93 maximize.innerHTML = maximized ? ICONS.restore : ICONS.maximize;
94 maximize.setAttribute("aria-label", maximized ? "Restore" : "Maximize");
95 maximize.title = maximized ? "Restore" : "Maximize";
96 }
97 // The window's buttons, at the right as Windows and Linux put them; macOS has its own.
98 if (!MAC) {
99 maximize = button("caption", "Maximize", "maximize", function () {
100 control("maximize").then(drawMaximized, function () {});
101 });
102 right.append(
103 button("caption", "Minimize", "minimize", function () {
104 quietly(control("minimize"));
105 }),
106 maximize,
107 button("caption close", "Close", "close", function () {
108 quietly(control("close"));
109 }),
110 );
111 var resizing = 0;
112 window.addEventListener("resize", function () {
113 clearTimeout(resizing);
114 resizing = setTimeout(function () {
115 control("maximized").then(drawMaximized, function () {});
116 }, 120);
117 });
118 control("maximized").then(drawMaximized, function () {});
119 }
120
121 // Dragging the bar moves the window; a double click maximizes it, as a system's title bar does.
122 bar.addEventListener("mousedown", function (event) {
123 if (event.button !== 0 || event.target.closest("button")) return;
124 if (event.detail === 2) {
125 control("maximize").then(drawMaximized, function () {});
126 return;
127 }
128 quietly(control("drag"));
129 });
130 // The keyboard belongs to the page: a click here gives it back.
131 window.addEventListener("mouseup", function () {
132 act("focus");
133 });
134
135 var title = document.createElement("span");
136 title.className = "title";
137 var pill = button("search", "Search", "search", function () {
138 act("search");
139 });
140 var pillText = document.createElement("span");
141 pill.append(pillText);
142
143 function setColors(colors) {
144 var root = document.documentElement.style;
145 if (!colors) return;
146 var shade = colors.dark ? "237, 237, 239" : "15, 15, 17";
147 root.setProperty("--bg", colors.background);
148 root.setProperty("--fg", colors.foreground);
149 root.setProperty("--muted", colors.muted);
150 root.setProperty("--accent", colors.accent);
151 root.setProperty("--hover", "rgba(" + shade + ", " + (colors.dark ? 0.1 : 0.07) + ")");
152 root.setProperty("--pill", "rgba(" + shade + ", " + (colors.dark ? 0.09 : 0.05) + ")");
153 root.setProperty("--pill-hover", "rgba(" + shade + ", " + (colors.dark ? 0.14 : 0.09) + ")");
154 root.setProperty("--pill-edge", "rgba(" + shade + ", " + (colors.dark ? 0.08 : 0.1) + ")");
155 document.documentElement.style.colorScheme = colors.dark ? "dark" : "light";
156 }
157
158 function draw(page) {
159 if (!page) return;
160 middle.textContent = "";
161 if (page.search) {
162 pillText.textContent = page.search;
163 pill.setAttribute("aria-label", page.search);
164 pill.removeAttribute("title");
165 middle.append(pill);
166 } else {
167 title.textContent = page.title || "g1t";
168 middle.append(title);
169 }
170 back.disabled = page.canGoBack === false;
171 forward.disabled = page.canGoForward === false;
172 setColors(page.colors);
173 }
174
175 if (events && typeof events.listen === "function") {
176 events
177 .listen("g1t-title-bar", function (event) {
178 draw(event.payload);
179 })
180 .catch(function () {});
181 }
182 invoke("title_bar_current").then(draw, function () {});
183})();