feat(gateway-ui): replace legacy CSS with Tailwind base and responsive router
Reduce style.css from 1740 lines to ~120 lines covering only what Tailwind cannot express (scrollbars, keyframes, stateful toggles). Rewrite app.js router for responsive nav with desktop sidebar active states, mobile pill indicators, and dual connection status updates. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+49
-13
@@ -2,6 +2,7 @@
|
|||||||
* Flynn SPA Router
|
* Flynn SPA Router
|
||||||
*
|
*
|
||||||
* Hash-based routing with page lifecycle management.
|
* Hash-based routing with page lifecycle management.
|
||||||
|
* Drives both desktop sidebar and mobile pill navigation.
|
||||||
*/
|
*/
|
||||||
import { getClient } from './lib/ws-client.js';
|
import { getClient } from './lib/ws-client.js';
|
||||||
import { registerPwaServiceWorker } from './lib/pwa.js';
|
import { registerPwaServiceWorker } from './lib/pwa.js';
|
||||||
@@ -19,8 +20,7 @@ export function navigate(path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getPath() {
|
function getPath() {
|
||||||
const hash = window.location.hash.slice(1) || '/';
|
return window.location.hash.slice(1) || '/';
|
||||||
return hash;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function render() {
|
async function render() {
|
||||||
@@ -28,7 +28,7 @@ async function render() {
|
|||||||
const page = routes.get(path);
|
const page = routes.get(path);
|
||||||
|
|
||||||
if (!page) {
|
if (!page) {
|
||||||
contentEl.innerHTML = '<div class="page-error"><h2>Page not found</h2></div>';
|
contentEl.innerHTML = '<div class="p-8 text-center text-zinc-500">Page not found</div>';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,17 +37,22 @@ async function render() {
|
|||||||
currentPage.teardown();
|
currentPage.teardown();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update nav
|
// Update desktop sidebar active state
|
||||||
document.querySelectorAll('.nav-link').forEach(link => {
|
document.querySelectorAll('.nav-link').forEach(link => {
|
||||||
link.classList.toggle('active', link.getAttribute('href') === `#${path}`);
|
link.classList.toggle('active', link.getAttribute('href') === `#${path}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Update mobile pill active state
|
||||||
|
document.querySelectorAll('.page-pill').forEach(pill => {
|
||||||
|
pill.classList.toggle('active', pill.dataset.hash === `#${path}`);
|
||||||
|
});
|
||||||
|
|
||||||
// Render new page
|
// Render new page
|
||||||
currentPage = page;
|
currentPage = page;
|
||||||
contentEl.innerHTML = '';
|
contentEl.innerHTML = '';
|
||||||
|
|
||||||
const pageEl = document.createElement('div');
|
const pageEl = document.createElement('div');
|
||||||
pageEl.className = 'page';
|
pageEl.className = 'page max-w-7xl mx-auto px-4 md:px-0';
|
||||||
contentEl.appendChild(pageEl);
|
contentEl.appendChild(pageEl);
|
||||||
|
|
||||||
await page.render(pageEl, getClient());
|
await page.render(pageEl, getClient());
|
||||||
@@ -56,23 +61,54 @@ async function render() {
|
|||||||
export function initRouter() {
|
export function initRouter() {
|
||||||
contentEl = document.getElementById('content');
|
contentEl = document.getElementById('content');
|
||||||
window.addEventListener('hashchange', render);
|
window.addEventListener('hashchange', render);
|
||||||
|
|
||||||
|
// Wire mobile pill click handlers
|
||||||
|
document.querySelectorAll('.page-pill').forEach(pill => {
|
||||||
|
pill.addEventListener('click', () => {
|
||||||
|
window.location.hash = pill.dataset.hash;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
void registerPwaServiceWorker().catch(() => undefined);
|
void registerPwaServiceWorker().catch(() => undefined);
|
||||||
render();
|
render();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Connection status indicator
|
// Connection status indicator — updates both desktop and mobile dots
|
||||||
export function initStatusIndicator() {
|
export function initStatusIndicator() {
|
||||||
const statusEl = document.getElementById('conn-status');
|
const connDot = document.getElementById('conn-dot');
|
||||||
|
const connText = document.getElementById('conn-text');
|
||||||
|
const connDotMobile = document.getElementById('conn-dot-mobile');
|
||||||
const client = getClient();
|
const client = getClient();
|
||||||
|
|
||||||
|
const textMap = {
|
||||||
|
connected: 'Connected',
|
||||||
|
connecting: 'Connecting...',
|
||||||
|
disconnected: 'Disconnected',
|
||||||
|
locked: 'Locked',
|
||||||
|
};
|
||||||
|
|
||||||
|
const colorMap = {
|
||||||
|
connected: 'bg-green-500',
|
||||||
|
connecting: 'bg-amber-500',
|
||||||
|
disconnected: 'bg-red-500',
|
||||||
|
locked: 'bg-red-500',
|
||||||
|
};
|
||||||
|
|
||||||
|
const baseDotClasses = 'w-2 h-2 rounded-full shrink-0';
|
||||||
|
|
||||||
|
function applyStatus(status) {
|
||||||
|
const text = textMap[status] || 'Disconnected';
|
||||||
|
const color = colorMap[status] || 'bg-zinc-600';
|
||||||
|
|
||||||
|
if (connText) connText.textContent = text;
|
||||||
|
if (connDot) connDot.className = `${baseDotClasses} ${color}`;
|
||||||
|
if (connDotMobile) connDotMobile.className = `${baseDotClasses} ${color}`;
|
||||||
|
}
|
||||||
|
|
||||||
client.onStatusChange((status) => {
|
client.onStatusChange((status) => {
|
||||||
statusEl.textContent = status === 'connected' ? 'Connected' :
|
applyStatus(status);
|
||||||
status === 'connecting' ? 'Connecting...' : 'Disconnected';
|
|
||||||
statusEl.className = `conn-status ${status}`;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Set initial status
|
// Set initial status
|
||||||
statusEl.textContent = client.status === 'connected' ? 'Connected' :
|
applyStatus(client.status);
|
||||||
client.status === 'connecting' ? 'Connecting...' : 'Disconnected';
|
|
||||||
statusEl.className = `conn-status ${client.status}`;
|
|
||||||
}
|
}
|
||||||
|
|||||||
+68
-1689
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user