Comprehensive WebUI improvements: security, UX, accessibility, performance
Security: - Replace inline onclick handlers with data-attribute event delegation (XSS fix) - Remove auth tokens from URL query params; use Authorization header + blob URLs - Defer artwork blob URL revocation to prevent ERR_FILE_NOT_FOUND Reliability: - Merge duplicate DOMContentLoaded listeners - WebSocket exponential backoff reconnect (3s base, 30s max, 20 attempts) - Connection banner with manual reconnect button after failures UX: - Toast notifications now stack (multiple visible simultaneously) - Custom styled confirm dialog replacing native confirm() - Drag-to-seek on progress bars (mouse + touch) - Keyboard shortcuts: Space, arrows, M for media controls - Browser search matches both filename and title - Path separator auto-detection (Unix/Windows) Accessibility: - WAI-ARIA Tabs pattern (tablist, tab, tabpanel roles) - Arrow/Home/End keyboard navigation in tab bar - ARIA slider roles on progress bars with live value updates - aria-label on volume sliders, aria-live on status dot Performance: - Thumbnail cache (Map, max 200 entries, LRU eviction) - Skip revocation of cached blob URLs during grid re-render - Blob URL cleanup on page unload Visual polish: - Vinyl mode uses CSS custom properties (works in light + dark themes) - Light theme shadow overrides for containers, dialogs, toasts - Optimized system font stack Code quality: - Scoped button reset, merged duplicate CSS selectors - WCAG AA contrast fix for --text-muted - Normalized CSS to consistent 4-space indentation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
<span id="mini-current-time">0:00</span>
|
||||
<span id="mini-total-time">0:00</span>
|
||||
</div>
|
||||
<div class="mini-progress-bar" id="mini-progress-bar">
|
||||
<div class="mini-progress-bar" id="mini-progress-bar" role="slider" aria-label="Playback position" aria-valuemin="0" aria-valuemax="0" aria-valuenow="0">
|
||||
<div class="mini-progress-fill" id="mini-progress-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -39,7 +39,7 @@
|
||||
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<input type="range" id="mini-volume-slider" class="mini-volume-slider" min="0" max="100" value="50">
|
||||
<input type="range" id="mini-volume-slider" class="mini-volume-slider" min="0" max="100" value="50" aria-label="Volume">
|
||||
<div class="mini-volume-display" id="mini-volume-display">50%</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -62,7 +62,7 @@
|
||||
<div class="container">
|
||||
<header>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
<span class="status-dot" id="status-dot"></span>
|
||||
<span class="status-dot" id="status-dot" aria-live="polite"></span>
|
||||
<span class="version-label" id="version-label"></span>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center; gap: 0.5rem;">
|
||||
@@ -88,32 +88,38 @@
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- Connection Banner -->
|
||||
<div class="connection-banner hidden" id="connectionBanner">
|
||||
<span id="connectionBannerText"></span>
|
||||
<button class="connection-banner-btn" id="connectionBannerBtn" onclick="manualReconnect()" style="display: none;" data-i18n="connection.reconnect">Reconnect</button>
|
||||
</div>
|
||||
|
||||
<!-- Tab Bar -->
|
||||
<div class="tab-bar" id="tabBar">
|
||||
<div class="tab-bar" id="tabBar" role="tablist">
|
||||
<div class="tab-indicator" id="tabIndicator"></div>
|
||||
<button class="tab-btn active" data-tab="player" onclick="switchTab('player')">
|
||||
<button class="tab-btn active" data-tab="player" onclick="switchTab('player')" role="tab" aria-selected="true" aria-controls="panel-player" tabindex="0">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M12 3v10.55c-.59-.34-1.27-.55-2-.55-2.21 0-4 1.79-4 4s1.79 4 4 4 4-1.79 4-4V7h4V3h-6z"/></svg>
|
||||
<span data-i18n="tab.player">Player</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="browser" onclick="switchTab('browser')">
|
||||
<button class="tab-btn" data-tab="browser" onclick="switchTab('browser')" role="tab" aria-selected="false" aria-controls="panel-browser" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M10 4H4c-1.1 0-2 .9-2 2v12c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V8c0-1.1-.9-2-2-2h-8l-2-2z"/></svg>
|
||||
<span data-i18n="tab.browser">Browser</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="quick-actions" onclick="switchTab('quick-actions')">
|
||||
<button class="tab-btn" data-tab="quick-actions" onclick="switchTab('quick-actions')" role="tab" aria-selected="false" aria-controls="panel-quick-actions" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M7 2v11h3v9l7-12h-4l4-8z"/></svg>
|
||||
<span data-i18n="tab.quick_actions">Actions</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="scripts" onclick="switchTab('scripts')">
|
||||
<button class="tab-btn" data-tab="scripts" onclick="switchTab('scripts')" role="tab" aria-selected="false" aria-controls="panel-scripts" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M9.4 16.6L4.8 12l4.6-4.6L8 6l-6 6 6 6 1.4-1.4zm5.2 0l4.6-4.6-4.6-4.6L16 6l6 6-6 6-1.4-1.4z"/></svg>
|
||||
<span data-i18n="tab.scripts">Scripts</span>
|
||||
</button>
|
||||
<button class="tab-btn" data-tab="callbacks" onclick="switchTab('callbacks')">
|
||||
<button class="tab-btn" data-tab="callbacks" onclick="switchTab('callbacks')" role="tab" aria-selected="false" aria-controls="panel-callbacks" tabindex="-1">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16"><path fill="currentColor" d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z"/></svg>
|
||||
<span data-i18n="tab.callbacks">Callbacks</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="player-container" data-tab-content="player">
|
||||
<div class="player-container" data-tab-content="player" role="tabpanel" id="panel-player">
|
||||
<div class="player-layout">
|
||||
<div class="album-art-container">
|
||||
<img id="album-art-glow" class="album-art-glow" src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 300 300'%3E%3Crect fill='%23282828' width='300' height='300'/%3E%3C/svg%3E" alt="" aria-hidden="true">
|
||||
@@ -138,7 +144,7 @@
|
||||
<span id="current-time">0:00</span>
|
||||
<span id="total-time">0:00</span>
|
||||
</div>
|
||||
<div class="progress-bar" id="progress-bar" data-duration="0">
|
||||
<div class="progress-bar" id="progress-bar" data-duration="0" role="slider" aria-label="Playback position" aria-valuemin="0" aria-valuemax="0" aria-valuenow="0">
|
||||
<div class="progress-fill" id="progress-fill"></div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -167,7 +173,7 @@
|
||||
<path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02z"/>
|
||||
</svg>
|
||||
</button>
|
||||
<input type="range" id="volume-slider" min="0" max="100" value="50">
|
||||
<input type="range" id="volume-slider" min="0" max="100" value="50" aria-label="Volume">
|
||||
<div class="volume-display" id="volume-display">50%</div>
|
||||
</div>
|
||||
|
||||
@@ -182,7 +188,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Media Browser Section -->
|
||||
<div class="browser-container" data-tab-content="browser" >
|
||||
<div class="browser-container" data-tab-content="browser" role="tabpanel" id="panel-browser">
|
||||
<!-- Breadcrumb Navigation -->
|
||||
<div class="breadcrumb" id="breadcrumb"></div>
|
||||
|
||||
@@ -249,7 +255,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Scripts Section (Quick Actions) -->
|
||||
<div class="scripts-container" id="scripts-container" data-tab-content="quick-actions" >
|
||||
<div class="scripts-container" data-tab-content="quick-actions" role="tabpanel" id="panel-quick-actions">
|
||||
<div class="scripts-grid" id="scripts-grid">
|
||||
<div class="scripts-empty empty-state-illustration">
|
||||
<svg viewBox="0 0 64 64"><path d="M20 8l-8 48"/><path d="M44 8l8 48"/><path d="M10 24h44"/><path d="M8 40h44"/></svg>
|
||||
@@ -262,7 +268,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Script Management Section -->
|
||||
<div class="script-management" data-tab-content="scripts" >
|
||||
<div class="script-management" data-tab-content="scripts" role="tabpanel" id="panel-scripts">
|
||||
<table class="scripts-table">
|
||||
<thead>
|
||||
<tr>
|
||||
@@ -290,7 +296,7 @@
|
||||
</div>
|
||||
|
||||
<!-- Callback Management Section -->
|
||||
<div class="script-management" id="callbacksSection" data-tab-content="callbacks" >
|
||||
<div class="script-management" data-tab-content="callbacks" role="tabpanel" id="panel-callbacks">
|
||||
<p style="color: var(--text-secondary); font-size: 0.875rem; margin-bottom: 1rem;" data-i18n="callbacks.description">
|
||||
Callbacks are scripts triggered automatically by media control events (play, pause, stop, etc.)
|
||||
</p>
|
||||
@@ -480,8 +486,17 @@
|
||||
</form>
|
||||
</dialog>
|
||||
|
||||
<!-- Toast Notification -->
|
||||
<div class="toast" id="toast"></div>
|
||||
<!-- Confirm Dialog -->
|
||||
<dialog id="confirmDialog" class="confirm-dialog">
|
||||
<p id="confirmDialogMessage"></p>
|
||||
<div class="confirm-dialog-actions">
|
||||
<button type="button" class="btn-cancel" id="confirmDialogCancel" data-i18n="dialog.cancel">Cancel</button>
|
||||
<button type="button" class="btn-danger" id="confirmDialogConfirm" data-i18n="dialog.confirm">Confirm</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<!-- Toast Notifications -->
|
||||
<div class="toast-container" id="toast-container"></div>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer>
|
||||
|
||||
Reference in New Issue
Block a user