Skip to content

Commit eab4932

Browse files
authored
Create index.html
1 parent a05a231 commit eab4932

File tree

1 file changed

+357
-0
lines changed

1 file changed

+357
-0
lines changed

platforms/tab5/site/index.html

Lines changed: 357 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,357 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta
6+
name="viewport"
7+
content="width=device-width, initial-scale=1, maximum-scale=1"
8+
/>
9+
<title>M5Tab5 – Web Flash & Console</title>
10+
11+
<!-- ESP Web Tools (installer button) -->
12+
<script type="module"
13+
src="https://unpkg.com/esp-web-tools@10/dist/web/install-button.js?module">
14+
</script>
15+
16+
<!-- xterm.js for the terminal -->
17+
<link rel="stylesheet"
18+
href="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.min.css">
19+
<script src="https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js"></script>
20+
<script src="https://cdn.jsdelivr.net/npm/@xterm/addon-fit@0.10.0/lib/addon-fit.min.js"></script>
21+
22+
<style>
23+
:root{
24+
--bg: #0b1020;
25+
--panel: #121a32;
26+
--ink: #e8ecf6;
27+
--muted: #a8b3cf;
28+
--accent: #6ea8ff;
29+
--good: #35c274;
30+
--warn: #ffb454;
31+
--bad: #ff6b6b;
32+
--border: #223153;
33+
34+
/* ESP Web Tools theming */
35+
--esp-tools-button-color: var(--accent);
36+
--esp-tools-button-text-color: #0b1020;
37+
--esp-tools-button-border-radius: 10px;
38+
}
39+
[data-theme="light"]{
40+
--bg:#f6f7fb; --panel:#ffffff; --ink:#0b1020; --muted:#51607d;
41+
--accent:#2f6bff; --border:#e6e9f3;
42+
}
43+
*{box-sizing:border-box}
44+
body{
45+
margin:0; background:var(--bg); color:var(--ink);
46+
font: 15px/1.45 system-ui, -apple-system, Segoe UI, Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif;
47+
}
48+
a{ color: var(--accent); text-decoration: none; }
49+
a:hover{ text-decoration: underline; }
50+
51+
header{
52+
display:flex; gap:16px; align-items:center;
53+
padding:20px 24px; border-bottom:1px solid var(--border);
54+
}
55+
.brand{ font-weight:700; letter-spacing:.3px; }
56+
.spacer{ flex:1; }
57+
.theme-toggle{ background:var(--panel); color:var(--ink); border:1px solid var(--border);
58+
padding:8px 12px; border-radius:10px; cursor:pointer; }
59+
60+
.wrap{
61+
display:grid; gap:16px; grid-template-columns: 1.15fr 0.85fr;
62+
padding:16px; min-height: calc(100vh - 72px);
63+
}
64+
.card{
65+
background:var(--panel); border:1px solid var(--border);
66+
border-radius:16px; padding:16px;
67+
}
68+
h1{ margin:0 0 4px 0; font-size:22px; }
69+
h2{ margin:0 0 8px 0; font-size:16px; font-weight:600; }
70+
.muted{ color:var(--muted); }
71+
.row{ display:flex; gap:10px; flex-wrap:wrap; align-items:center; }
72+
select, button, .btn{
73+
background:#0d1733; color:var(--ink); border:1px solid var(--border);
74+
padding:10px 12px; border-radius:10px; font-weight:600;
75+
}
76+
[data-theme="light"] select, [data-theme="light"] button, [data-theme="light"] .btn{
77+
background:#f4f6fe; color:#0b1020;
78+
}
79+
80+
.cols{ display:grid; grid-template-columns: 1fr 1fr; gap:10px; }
81+
.kpi{
82+
display:flex; flex-direction:column; gap:6px;
83+
background:rgba(255,255,255,.03); border:1px dashed var(--border);
84+
padding:10px; border-radius:12px;
85+
}
86+
.kpi b{ font-size:13px; color: var(--muted); font-weight:700; }
87+
.kpi span{ font-size:14px; }
88+
89+
.terminal{
90+
height: 520px;
91+
border-radius:12px; overflow:hidden;
92+
border:1px solid var(--border);
93+
background:#000;
94+
}
95+
.toolbar{
96+
display:flex; gap:8px; align-items:center; margin-bottom:10px;
97+
}
98+
.footnote{ font-size:12px; color:var(--muted); margin-top:10px; }
99+
.list{ margin:0; padding:0; list-style:none; }
100+
.list li{ padding:8px 0; border-bottom:1px dashed var(--border); }
101+
.pill{ padding:3px 8px; border-radius:999px; border:1px solid var(--border); }
102+
.ok{ color:var(--good) } .warn{ color:var(--warn) } .bad{ color:var(--bad) }
103+
@media (max-width: 1024px){ .wrap{ grid-template-columns: 1fr; } .terminal{ height:380px; } }
104+
</style>
105+
</head>
106+
<body>
107+
<header>
108+
<div class="brand">M5Tab5 – Web Flash & Console</div>
109+
<div class="spacer"></div>
110+
<button class="theme-toggle" id="themeBtn" title="Toggle theme">Toggle Theme</button>
111+
</header>
112+
113+
<main class="wrap">
114+
<!-- LEFT: flasher + builds -->
115+
<section class="card">
116+
<h1>Flash your device</h1>
117+
<p class="muted" id="supportMsg">
118+
1) Connect the Tab5 over USB. 2) Pick a build. 3) Click <b>Install</b>.
119+
(Chrome/Edge on desktop; site must be served over HTTPS.)</p>
120+
121+
<div class="row" style="margin:12px 0;">
122+
<label for="buildSel"><b>Build:</b></label>
123+
<select id="buildSel" aria-label="Select build" style="min-width:260px"></select>
124+
<button id="refreshBtn" title="Refresh release list">Refresh</button>
125+
<span id="chipBadge" class="pill">Chip: <span id="chipTxt"></span></span>
126+
<span id="statusBadge" class="pill">Status: <span id="statusTxt" class="warn">Idle</span></span>
127+
</div>
128+
129+
<div class="cols" style="margin:10px 0 16px">
130+
<div class="kpi"><b>Selected build</b><span id="selTag"></span></div>
131+
<div class="kpi"><b>Binary</b><span id="selBin"></span></div>
132+
</div>
133+
134+
<!-- ESP Web Tools install button -->
135+
<esp-web-install-button id="installer" manifest="">
136+
<button slot="activate">Install selected build</button>
137+
<span slot="unsupported">
138+
Your browser doesn’t support Web Serial. Use Chrome or Edge on desktop.
139+
</span>
140+
<span slot="not-allowed">
141+
This page must be served via HTTPS (or localhost).
142+
</span>
143+
</esp-web-install-button>
144+
145+
<div class="footnote">
146+
• Using a <b>merged firmware</b> single image at offset <code>0</code> as recommended for ESP-IDF 4+ projects.
147+
• We dynamically generate the manifest in-page (Blob URL) and point it to the release asset’s download URL. :contentReference[oaicite:3]{index=3}
148+
</div>
149+
150+
<hr style="border:none;border-top:1px solid var(--border); margin:16px 0">
151+
152+
<h2>Recent builds</h2>
153+
<ul class="list" id="recentList"></ul>
154+
</section>
155+
156+
<!-- RIGHT: terminal -->
157+
<aside class="card">
158+
<h1>Device console</h1>
159+
<div class="toolbar">
160+
<button id="portBtn">Connect</button>
161+
<button id="disconnectBtn" disabled>Disconnect</button>
162+
<button id="clearBtn">Clear</button>
163+
<label class="pill" style="display:flex;gap:6px;align-items:center;padding:6px 10px;">
164+
<input type="checkbox" id="autoscroll" checked/> Autoscroll
165+
</label>
166+
<span class="spacer"></span>
167+
<span class="muted" id="portInfo">No port</span>
168+
</div>
169+
<div id="terminal" class="terminal"></div>
170+
<div class="footnote">
171+
Note: you can’t flash and view the serial log at the same time on the same COM port. Disconnect the console before clicking Install. :contentReference[oaicite:4]{index=4}
172+
</div>
173+
</aside>
174+
</main>
175+
176+
<footer class="card" style="margin:16px;">
177+
<div class="row">
178+
<div><b>About this project</b> — M5Tab5 User Demo (ESP-IDF 5.4.2 / ESP32-P4). See the
179+
<a href="https://github.com/baba-dev/M5Tab5-UserDemo" target="_blank" rel="noreferrer">repository</a>.</div>
180+
</div>
181+
</footer>
182+
183+
<script>
184+
(() => {
185+
const OWNER = "baba-dev";
186+
const REPO = "M5Tab5-UserDemo";
187+
const RELEASES_API = `https://api.github.com/repos/${OWNER}/${REPO}/releases?per_page=20`;
188+
189+
const buildSel = document.getElementById('buildSel');
190+
const recentList = document.getElementById('recentList');
191+
const installer = document.getElementById('installer');
192+
const selTag = document.getElementById('selTag');
193+
const selBin = document.getElementById('selBin');
194+
const chipTxt = document.getElementById('chipTxt');
195+
const statusTxt = document.getElementById('statusTxt');
196+
197+
// theme
198+
const themeBtn = document.getElementById('themeBtn');
199+
const setTheme = t => document.documentElement.setAttribute('data-theme', t);
200+
setTheme(localStorage.getItem('theme') || 'dark');
201+
themeBtn.onclick = () => {
202+
const next = (document.documentElement.getAttribute('data-theme') === 'dark') ? 'light' : 'dark';
203+
setTheme(next); localStorage.setItem('theme', next);
204+
};
205+
206+
// fetch last 5 "build-*" releases
207+
async function loadReleases(){
208+
buildSel.innerHTML = `<option>Loading…</option>`;
209+
recentList.innerHTML = "";
210+
const res = await fetch(RELEASES_API);
211+
const all = await res.json();
212+
const builds = all
213+
.filter(r => r.tag_name && r.tag_name.startsWith('build-'))
214+
.slice(0, 10); // take more, we’ll filter assets
215+
const rows = [];
216+
let choices = [];
217+
for (const r of builds){
218+
if (!Array.isArray(r.assets)) continue;
219+
for (const a of r.assets){
220+
if (!a.name.endsWith('.bin')) continue;
221+
choices.push({
222+
tag: r.tag_name,
223+
published: r.published_at,
224+
name: a.name,
225+
url: a.browser_download_url
226+
});
227+
}
228+
}
229+
// last 5 by published date
230+
choices.sort((a,b) => new Date(b.published) - new Date(a.published));
231+
choices = choices.slice(0,5);
232+
233+
buildSel.innerHTML = "";
234+
for (const c of choices){
235+
const opt = document.createElement('option');
236+
opt.value = JSON.stringify(c);
237+
opt.textContent = `${c.tag}${c.name}`;
238+
buildSel.appendChild(opt);
239+
240+
const li = document.createElement('li');
241+
li.innerHTML = `<b>${c.tag}</b> • ${c.name} <span class="muted">(${new Date(c.published).toLocaleString()})</span>`;
242+
recentList.appendChild(li);
243+
}
244+
245+
if (choices.length){
246+
setSelection(choices[0]);
247+
} else {
248+
buildSel.innerHTML = `<option>No builds found</option>`;
249+
}
250+
}
251+
252+
function setSelection(choice){
253+
selTag.textContent = choice.tag;
254+
selBin.textContent = choice.name;
255+
256+
// Build a dynamic manifest Blob (supported by ESP Web Tools docs)
257+
const manifest = {
258+
name: "M5Tab5 User Demo",
259+
version: choice.tag,
260+
new_install_prompt_erase: true,
261+
builds: [{
262+
// P4 flashing is supported via esptool-js; chipFamily list in docs
263+
// doesn’t yet mention P4, so we advertise as ESP32 single merged bin.
264+
chipFamily: "ESP32",
265+
improv: false,
266+
parts: [{ path: choice.url, offset: 0 }]
267+
}]
268+
};
269+
const blob = new Blob([JSON.stringify(manifest)], {type: "application/json"});
270+
const url = URL.createObjectURL(blob);
271+
installer.setAttribute('manifest', url);
272+
}
273+
274+
document.getElementById('refreshBtn').onclick = loadReleases;
275+
buildSel.onchange = () => setSelection(JSON.parse(buildSel.value));
276+
277+
// update chip + status from installer events when available
278+
installer.addEventListener('esp-web-install', (e) => {
279+
// Custom events aren’t documented exhaustively; keep simple
280+
statusTxt.textContent = "Installing…";
281+
statusTxt.className = "warn";
282+
});
283+
installer.addEventListener('esp-web-install-success', () => {
284+
statusTxt.textContent = "Installed";
285+
statusTxt.className = "ok";
286+
});
287+
installer.addEventListener('esp-web-install-error', () => {
288+
statusTxt.textContent = "Error";
289+
statusTxt.className = "bad";
290+
});
291+
292+
// serial console (Web Serial + xterm)
293+
const term = new window.Terminal({ cursorBlink: true, convertEol: true, fontSize: 13 });
294+
const fitAddon = new window.FitAddon.FitAddon();
295+
term.loadAddon(fitAddon);
296+
term.open(document.getElementById('terminal'));
297+
fitAddon.fit();
298+
299+
let reader, port;
300+
const portBtn = document.getElementById('portBtn');
301+
const disconnectBtn = document.getElementById('disconnectBtn');
302+
const clearBtn = document.getElementById('clearBtn');
303+
const portInfo = document.getElementById('portInfo');
304+
const autoscroll = document.getElementById('autoscroll');
305+
306+
function setPortUI(connected, info = "No port"){
307+
portBtn.disabled = connected;
308+
disconnectBtn.disabled = !connected;
309+
portInfo.textContent = info;
310+
}
311+
312+
portBtn.onclick = async () => {
313+
try{
314+
port = await navigator.serial.requestPort();
315+
await port.open({ baudRate: 115200 });
316+
setPortUI(true, "115200 baud");
317+
const dec = new TextDecoderStream();
318+
const readable = port.readable.pipeThrough(dec);
319+
reader = readable.getReader();
320+
(async () => {
321+
while (true){
322+
const { value, done } = await reader.read();
323+
if (done) break;
324+
if (value){
325+
term.write(value.replace(/\r?\n/g, "\r\n"));
326+
if (autoscroll.checked) term.scrollToBottom();
327+
}
328+
}
329+
})();
330+
} catch(e){
331+
console.error(e);
332+
}
333+
};
334+
335+
disconnectBtn.onclick = async () => {
336+
try{
337+
if (reader){ await reader.cancel(); reader.releaseLock(); }
338+
if (port){ await port.close(); }
339+
} finally{
340+
setPortUI(false);
341+
}
342+
};
343+
344+
clearBtn.onclick = () => term.reset();
345+
346+
// show chip family (best effort once connected via installer)
347+
installer.addEventListener('click', () => {
348+
// The component detects chip on connect; we mirror a placeholder
349+
chipTxt.textContent = "Detecting…";
350+
});
351+
352+
// init
353+
loadReleases();
354+
})();
355+
</script>
356+
</body>
357+
</html>

0 commit comments

Comments
 (0)