diff --git a/dashboard/js/onboard.js b/dashboard/js/onboard.js
index ccccf18..3afe48f 100644
--- a/dashboard/js/onboard.js
+++ b/dashboard/js/onboard.js
@@ -482,24 +482,12 @@
(flashRetryCount > 0 ? 'Try Again' : 'Start Flashing') + '';
btnArea.appendChild(installBtn);
- installBtn.addEventListener('state-changed', function (e) {
- var s = (e.detail || {}).state;
- if (s) { appendLog('log', ['state: ' + s]); }
- });
-
- installBtn.addEventListener('flash-start', function () {
- appendLog('log', ['flash-start']);
- btnArea.style.display = 'none';
- document.getElementById('flash-progress-bar').style.display = 'block';
- document.getElementById('flash-progress-fill').style.width = '0%';
- document.getElementById('flash-recovery').style.display = 'none';
- document.getElementById('flash-log-details').open = true;
- setStatus('Connecting...');
- });
-
var flashCompleteTimer = null;
+ var dlgObserver = null;
+
function onFlashComplete() {
if (flashCompleteTimer) { clearTimeout(flashCompleteTimer); flashCompleteTimer = null; }
+ if (dlgObserver) { dlgObserver.disconnect(); dlgObserver = null; }
restoreConsole();
document.getElementById('flash-progress-bar').style.display = 'none';
setStatus('✓ Firmware flashed successfully!');
@@ -508,50 +496,104 @@
setTimeout(function () { goToStep(state.currentStepIndex + 1); }, 1500);
}
- installBtn.addEventListener('flash-progress', function (e) {
- var detail = e.detail || {};
- var pct = detail.bytesTotal > 0
- ? Math.round((detail.bytesWritten / detail.bytesTotal) * 100) : 0;
- document.getElementById('flash-progress-fill').style.width = pct + '%';
- setStatus('Flashing... ' + pct + '%');
- // If we hit 100% but flash-success doesn't fire (e.g. JS error in
- // esp-web-tools' progress display), show a manual Continue button.
- if (pct >= 100 && !flashCompleteTimer) {
- flashCompleteTimer = setTimeout(function () {
- flashCompleteTimer = null;
- appendLog('log', ['flash-success not received after 100% — showing manual continue']);
- document.getElementById('flash-progress-bar').style.display = 'none';
- setStatus('✓ Flash complete.');
- document.getElementById('flash-status-text').style.color = '#a5d6a7';
- btnArea.style.display = 'block';
- btnArea.innerHTML = '';
- document.getElementById('flash-continue-btn').addEventListener('click', function () {
- saveState();
- goToStep(state.currentStepIndex + 1);
- });
- }, 4000);
- }
- });
+ // Inject CSS into the shadow root to hide the esp-web-tools dialog once
+ // flashing starts — we drive the UI inline from that point on.
+ function hideDlgOverlay() {
+ var sr = installBtn.shadowRoot;
+ if (!sr || sr.querySelector('#ewt-suppress')) { return; }
+ var s = document.createElement('style');
+ s.id = 'ewt-suppress';
+ // Hide the dialog and its backdrop; keep the activate slot visible
+ s.textContent = ':host > *:not(slot) { display:none !important; }';
+ sr.appendChild(s);
+ }
- installBtn.addEventListener('flash-success', function () {
- appendLog('log', ['flash-success']);
- onFlashComplete();
- });
-
- installBtn.addEventListener('flash-error', function (e) {
- flashRetryCount++;
- var errDetail = '';
- try { errDetail = JSON.stringify(e.detail || {}); } catch (_) { errDetail = String(e.detail); }
- appendLog('error', ['flash-error detail=' + errDetail]);
- document.getElementById('flash-progress-bar').style.display = 'none';
+ function showProgressUI() {
+ btnArea.style.display = 'none';
+ document.getElementById('flash-progress-bar').style.display = 'block';
+ document.getElementById('flash-recovery').style.display = 'none';
document.getElementById('flash-log-details').open = true;
- setStatus('');
- var recovery = document.getElementById('flash-recovery');
- recovery.style.display = 'block';
- recovery.innerHTML = renderBootloaderHelp(flashRetryCount);
- btnArea.style.display = 'block';
- mountInstallButton();
- });
+ }
+
+ // esp-web-tools v10 only fires state-changed, and it fires from the dialog
+ // element inside the shadow root (not composed), so the event never reaches
+ // listeners on the host. We use a MutationObserver to find the dialog element
+ // and attach directly to it.
+ function attachToDialog(dlg) {
+ dlg.addEventListener('state-changed', function (e) {
+ var detail = e.detail || {};
+ var s = detail.state;
+ var det = detail.details || {};
+ appendLog('log', ['state: ' + s]);
+
+ if (s === 'erasing') {
+ hideDlgOverlay();
+ showProgressUI();
+ document.getElementById('flash-progress-fill').style.width = '5%';
+ setStatus('Erasing flash...');
+ } else if (s === 'writing') {
+ hideDlgOverlay();
+ showProgressUI();
+ // percentage is 0–1
+ var pct = Math.round((det.percentage || 0) * 100);
+ document.getElementById('flash-progress-fill').style.width = pct + '%';
+ setStatus('Flashing... ' + pct + '%');
+ // Fallback: if finished state never fires, show Continue after 4s
+ if (pct >= 100 && !flashCompleteTimer) {
+ flashCompleteTimer = setTimeout(function () {
+ flashCompleteTimer = null;
+ appendLog('log', ['finished state not received — manual continue']);
+ document.getElementById('flash-progress-bar').style.display = 'none';
+ setStatus('✓ Flash complete.');
+ document.getElementById('flash-status-text').style.color = '#a5d6a7';
+ btnArea.style.display = 'block';
+ btnArea.innerHTML = '';
+ document.getElementById('flash-continue-btn').addEventListener('click', function () {
+ saveState();
+ goToStep(state.currentStepIndex + 1);
+ });
+ }, 4000);
+ }
+ } else if (s === 'finished') {
+ appendLog('log', ['flash-success']);
+ onFlashComplete();
+ } else if (s === 'error') {
+ if (dlgObserver) { dlgObserver.disconnect(); dlgObserver = null; }
+ if (flashCompleteTimer) { clearTimeout(flashCompleteTimer); flashCompleteTimer = null; }
+ hideDlgOverlay();
+ flashRetryCount++;
+ appendLog('error', ['flash-error: ' + (detail.message || JSON.stringify(detail))]);
+ document.getElementById('flash-progress-bar').style.display = 'none';
+ document.getElementById('flash-log-details').open = true;
+ setStatus('');
+ var recovery = document.getElementById('flash-recovery');
+ recovery.style.display = 'block';
+ recovery.innerHTML = renderBootloaderHelp(flashRetryCount);
+ btnArea.style.display = 'block';
+ mountInstallButton();
+ }
+ });
+ }
+
+ // Watch for the dialog element to appear in the shadow root
+ var sr = installBtn.shadowRoot;
+ if (sr) {
+ dlgObserver = new MutationObserver(function (mutations) {
+ for (var i = 0; i < mutations.length; i++) {
+ var added = mutations[i].addedNodes;
+ for (var j = 0; j < added.length; j++) {
+ var node = added[j];
+ if (node.nodeType === 1 && node.tagName.toLowerCase() !== 'style') {
+ // This is the dialog element — attach and stop observing
+ if (dlgObserver) { dlgObserver.disconnect(); dlgObserver = null; }
+ attachToDialog(node);
+ return;
+ }
+ }
+ }
+ });
+ dlgObserver.observe(sr, { childList: true });
+ }
}
mountInstallButton();