205 lines
8.3 KiB
HTML
205 lines
8.3 KiB
HTML
<!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-S3 stepper controller." />
|
||
|
||
<!-- 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" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 9l9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/>
|
||
<polyline points="9 22 9 12 15 12 15 22"/>
|
||
</svg>
|
||
Home
|
||
</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" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round">
|
||
<circle cx="12" cy="12" r="3"/>
|
||
<path d="M12 1v4M12 19v4M4.22 4.22l2.83 2.83M16.95 16.95l2.83 2.83M1 12h4M19 12h4M4.22 19.78l2.83-2.83M16.95 7.05l2.83-2.83"/>
|
||
</svg>
|
||
Stepper Test
|
||
</a>
|
||
|
||
<!-- Spacer + coming soon -->
|
||
<div style="flex:1"></div>
|
||
<div class="divider"></div>
|
||
<span class="sidebar-section-label">Coming Soon</span>
|
||
|
||
<div class="sidebar-nav-item" style="opacity:0.35;cursor:default" aria-disabled="true">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M12 20h9M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>
|
||
</svg>
|
||
Board Control
|
||
</div>
|
||
|
||
<div class="sidebar-nav-item" style="opacity:0.35;cursor:default" aria-disabled="true">
|
||
<svg class="nav-icon" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2"
|
||
stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M3 3h7v7H3zM14 3h7v7h-7zM14 14h7v7h-7zM3 14h7v7H3z"/>
|
||
</svg>
|
||
Sequence Editor
|
||
</div>
|
||
</nav>
|
||
|
||
<!-- ── Main area ──────────────────────────────────────────────── -->
|
||
<div style="display:flex;flex-direction:column;flex:1;overflow:hidden">
|
||
|
||
<!-- Top bar -->
|
||
<header class="topbar" 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" viewBox="0 0 24 24" fill="none">
|
||
<path d="M12 2L3 7v10l9 5 9-5V7L12 2Z"
|
||
stroke="#448aff" stroke-width="1.5" stroke-linejoin="round"/>
|
||
<circle cx="12" cy="12" r="2.5" fill="#448aff" opacity="0.7"/>
|
||
</svg>
|
||
</div>
|
||
WijiBoard
|
||
</div>
|
||
|
||
<!-- BLE actions -->
|
||
<div class="topbar-actions">
|
||
<!-- 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" viewBox="0 0 24 24" fill="none"
|
||
stroke="currentColor" stroke-width="2.5"
|
||
stroke-linecap="round" stroke-linejoin="round">
|
||
<path d="M6.5 6.5l11 11M17.5 6.5l-5 5 5 5"/>
|
||
<path d="M6 17.5l5-5-5-5"/>
|
||
</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">
|
||
const V = Date.now(); // cache-buster – ensures fresh module load on every page refresh
|
||
const { default: Router } = await import(`./web/js/router.js?v=${V}`);
|
||
const { default: BLE } = await import(`./web/js/ble.js?v=${V}`);
|
||
const { default: UI } = await import(`./web/js/ui.js?v=${V}`);
|
||
const { default: HomeSection } = await import(`./web/js/sections/home.js?v=${V}`);
|
||
const { default: StepperSection } = await import(`./web/js/sections/stepper-test.js?v=${V}`);
|
||
|
||
// ── Register routes ────────────────────────────────────────────
|
||
Router.register('home', HomeSection);
|
||
Router.register('stepper-test', StepperSection);
|
||
|
||
// ── 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 => {
|
||
if (!e.message?.includes('cancelled') && !e.name?.includes('NotFound')) {
|
||
UI.log(`Connect error: ${e.message}`, 'error');
|
||
}
|
||
});
|
||
}
|
||
});
|
||
|
||
// ── Init router (last step) ────────────────────────────────────
|
||
Router.init('#view', '#home');
|
||
|
||
// ── 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>
|