Files
ESP32-WijiBoard/index.html
T
PROFERIS - Mi³osz Stocki dd8f6b2277 Idle animations. Closes #14
2026-07-09 14:01:00 +02:00

253 lines
11 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>WijiBoard Control Dashboard</title>
<meta name="description"
content="WijiBoard SCARA arm control dashboard. Web Bluetooth interface for the ESP32-C3 stepper controller." />
<link rel="icon" type="image/svg+xml" href="favicon.svg" />
<link rel="manifest" href="manifest.json" />
<!-- Shared styles -->
<link rel="stylesheet" href="web/styles/base.css" />
<link rel="stylesheet" href="web/styles/components.css" />
</head>
<body>
<div class="app-shell">
<!-- ── Sidebar overlay (click to close) ──────────────────────── -->
<div class="sidebar-overlay" id="sidebar-overlay"></div>
<!-- ── Sidebar ───────────────────────────────────────────────── -->
<nav class="sidebar" id="sidebar" aria-label="Main navigation">
<span class="sidebar-section-label">Navigation</span>
<a class="sidebar-nav-item active" id="nav-home" href="#home" data-route="home" role="button"
aria-label="Go to Home">
<svg class="nav-icon"><use href="web/assets/icons.svg#icon-home"></use></svg>
Home
</a>
<a class="sidebar-nav-item" id="nav-board-control" href="#board-control" data-route="board-control" role="button"
aria-label="Go to Board Control">
<svg class="nav-icon"><use href="web/assets/icons.svg#icon-board"></use></svg>
Board Control
</a>
<a class="sidebar-nav-item" id="nav-input-text" href="#input-text" data-route="input-text" role="button"
aria-label="Go to Input Text">
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M21 11.5a8.38 8.38 0 0 1-.9 3.8 8.5 8.5 0 0 1-7.6 4.7 8.38 8.38 0 0 1-3.8-.9L3 21l1.9-5.7a8.38 8.38 0 0 1-.9-3.8 8.5 8.5 0 0 1 4.7-7.6 8.38 8.38 0 0 1 3.8-.9h.5a8.48 8.48 0 0 1 8 8v.5z"/>
</svg>
Input Text
</a>
<a class="sidebar-nav-item" id="nav-sequence-editor" href="#sequence-editor" data-route="sequence-editor" role="button"
aria-label="Go to Sequence Editor">
<svg class="nav-icon"><use href="web/assets/icons.svg#icon-sequence"></use></svg>
Sequence Editor
</a>
<a class="sidebar-nav-item" id="nav-stepper-test" href="#stepper-test" data-route="stepper-test" role="button"
aria-label="Go to Stepper Test">
<svg class="nav-icon"><use href="web/assets/icons.svg#icon-stepper"></use></svg>
Stepper Test
</a>
<!-- Spacer -->
<div style="flex:1"></div>
</nav>
<!-- ── Main area ──────────────────────────────────────────────── -->
<div style="display:flex;flex-direction:column;flex:1;overflow:hidden">
<!-- Top bar -->
<header class="top-bar" style="display:flex; justify-content:space-between; align-items:center; padding: 12px 10px; background: var(--surface-1); border-bottom: 1px solid var(--border);" role="banner">
<!-- Hamburger -->
<button class="hamburger" id="hamburger-btn" aria-label="Toggle sidebar" aria-expanded="true"
aria-controls="sidebar">
<div class="hamburger-bar"></div>
<div class="hamburger-bar"></div>
<div class="hamburger-bar"></div>
</button>
<!-- Brand -->
<div class="topbar-brand">
<div class="brand-icon" aria-hidden="true">
<svg width="16" height="16"><use href="web/assets/icons.svg#icon-brand"></use></svg>
</div>
WijiBoard
</div>
<!-- BLE actions -->
<div class="topbar-actions">
<!-- Disable steppers button (hidden by default) -->
<button class="btn btn-sm btn-ghost" id="btn-disable-steppers" style="display:none; margin-right:8px; padding:0 8px; color: var(--danger-color);" title="Disable Motors (Requires Rehoming)" aria-label="Disable Steppers">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M18.36 6.64a9 9 0 1 1-12.73 0"></path>
<line x1="12" y1="2" x2="12" y2="12"></line>
</svg>
</button>
<!-- BLE status pill -->
<div class="ble-status" id="ble-status" aria-live="polite">
<div class="ble-dot" id="ble-dot"></div>
<span class="ble-label" id="ble-label">Disconnected</span>
</div>
<!-- Connect / Disconnect button -->
<button class="btn btn-sm btn-primary" id="btn-ble-connect" aria-label="Connect to WijiBoard via Bluetooth">
<svg width="13" height="13"><use href="web/assets/icons.svg#icon-ble"></use></svg>
<span class="btn-text">Connect</span>
</button>
</div>
</header>
<!-- Router outlet -->
<main class="main-content" id="view" role="main" aria-label="Page content">
<!-- Sections are injected here by router.js -->
</main>
</div>
</div>
<!-- ── App bootstrap (ES module entry point) ─────────────────── -->
<script type="module">
import Router from './web/js/router.js';
import BLE from './web/js/ble.js';
import UI from './web/js/ui.js';
import HomeSection from './web/js/sections/home.js';
import StepperSection from './web/js/sections/stepper-test.js';
import BoardControlSection from './web/js/sections/board-control.js';
import InputTextSection from './web/js/sections/input-text.js';
import SequenceEditorSection from './web/js/sections/sequence-editor.js';
import './web/js/idle-effects.js';
// ── Register routes ────────────────────────────────────────────
Router.register('home', HomeSection);
Router.register('stepper-test', StepperSection);
Router.register('board-control', BoardControlSection);
Router.register('input-text', InputTextSection);
Router.register('sequence-editor', SequenceEditorSection);
// ── Sidebar toggle logic ───────────────────────────────────────
const sidebar = document.getElementById('sidebar');
const overlay = document.getElementById('sidebar-overlay');
const hamburger = document.getElementById('hamburger-btn');
const mainContent = document.getElementById('view');
let sidebarOpen = true;
function setSidebarOpen(open) {
sidebarOpen = open;
sidebar.classList.toggle('collapsed', !open);
overlay.classList.toggle('visible', open && window.innerWidth < 900);
hamburger.classList.toggle('open', open);
hamburger.setAttribute('aria-expanded', open);
// Shift main content on desktop
const mc = hamburger.closest('.app-shell')?.querySelector('.main-content');
if (mc) mc.classList.toggle('sidebar-collapsed', !open);
}
hamburger.addEventListener('click', () => setSidebarOpen(!sidebarOpen));
overlay.addEventListener('click', () => setSidebarOpen(false));
// Close sidebar on small screens when nav link clicked
document.querySelectorAll('.sidebar-nav-item[data-route]').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const route = link.dataset.route;
Router.navigate('#' + route);
if (window.innerWidth < 900) setSidebarOpen(false);
});
});
// Collapse sidebar by default on small screens
if (window.innerWidth < 900) setSidebarOpen(false);
// ── Wire BLE events to shared UI ──────────────────────────────
UI.initBLEListeners();
// Connect button handler (initial state)
document.getElementById('btn-ble-connect').addEventListener('click', () => {
if (BLE.isConnected()) {
BLE.disconnect();
} else {
BLE.connect().catch(e => {
console.error('[BLE Connect Error]', e);
UI.setButtonLoading('btn-ble-connect', false);
if (!e.message?.includes('cancelled')) {
UI.log(`Connect error: ${e.message} (${e.name})`, 'error');
}
});
}
});
// ── Init router (last step) ────────────────────────────────────
Router.init('#view', '#home');
// ── Register Service Worker for PWA ────────────────────────────
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('./sw.js').catch(err => {
console.warn('[PWA] Service Worker registration failed:', err);
});
});
}
// ── PWA Install Logic ──────────────────────────────────────────
window.deferredPrompt = null;
window.addEventListener('beforeinstallprompt', (e) => {
// Prevent automatic prompt
e.preventDefault();
window.deferredPrompt = e;
// Dispatch a custom event so sections can update their UI
window.dispatchEvent(new Event('pwa-installable'));
});
// ── Disable Steppers & Idle Timeout Logic ──────────────────────
const btnDisable = document.getElementById('btn-disable-steppers');
btnDisable.addEventListener('click', async () => {
if (BLE.isConnected()) {
await BLE.write('DISABLE').catch(e => UI.log(e.message, 'error'));
localStorage.setItem('wiji_homed', 'false');
UI.log('Steppers manually disabled. Rehoming required.', 'warn');
const warningEl = document.getElementById('home-warning');
if (warningEl) warningEl.style.display = 'block';
}
});
document.addEventListener('ble:status', (e) => {
if (e.detail === 'SYS:IDLE_TIMEOUT') {
localStorage.setItem('wiji_homed', 'false');
UI.log('Steppers disabled due to 30 mins inactivity. Rehoming required.', 'warn');
const warningEl = document.getElementById('home-warning');
if (warningEl) warningEl.style.display = 'block';
}
});
BLE.on('connected', async () => {
btnDisable.style.display = 'inline-flex';
await BLE.write('POS').catch(e => console.warn('POS sync failed:', e));
});
BLE.on('disconnected', () => {
btnDisable.style.display = 'none';
});
// ── Check Web Bluetooth availability ──────────────────────────
if (!navigator.bluetooth) {
setTimeout(() => {
UI.log('⚠ Web Bluetooth not available. Use Chrome or Edge on localhost/HTTPS.', 'error');
}, 600);
}
</script>
</body>
</html>