Settings: left sidebar nav with separate page views per category
Replaced horizontal tab bar with a left sidebar navigation that feels like separate pages: - Left nav with icons + labels (Connection, Audio, 4CM Routing, Network, Bluetooth, About) - Active category highlighted in accent color - Each category has its own URL via hash (#connection, #audio, etc.) - Back/forward browser navigation via popstate handler - Stacked layout on mobile (sidebar becomes scrollable row)
This commit is contained in:
+95
-24
@@ -696,43 +696,87 @@ body {
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
/* ── Settings tabs ────────────────────────────────────────────────── */
|
||||
/* ── Settings layout: sidebar + content ──────────────────────────── */
|
||||
|
||||
.settings-tabs {
|
||||
.settings-layout {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
overflow-x: auto;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
scrollbar-width: none;
|
||||
margin-bottom: 12px;
|
||||
padding-bottom: 4px;
|
||||
border-bottom: 1px solid var(--border);
|
||||
gap: 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.settings-tabs::-webkit-scrollbar {
|
||||
display: none;
|
||||
/* ── Settings sidebar ──────────────────────────────────────────────── */
|
||||
|
||||
.settings-sidebar {
|
||||
width: 180px;
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
background: var(--bg-secondary);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
padding: 6px;
|
||||
position: sticky;
|
||||
top: 12px;
|
||||
}
|
||||
|
||||
.settings-tab {
|
||||
padding: 8px 14px;
|
||||
.settings-nav-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
padding: 10px 12px;
|
||||
border-radius: 6px;
|
||||
text-decoration: none;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.85rem;
|
||||
font-weight: 500;
|
||||
transition: all 0.15s;
|
||||
cursor: pointer;
|
||||
touch-action: manipulation;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.82rem;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
border-bottom: 2px solid transparent;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
touch-action: manipulation;
|
||||
width: 100%;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.settings-tab:hover {
|
||||
.settings-nav-item:hover {
|
||||
background: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.settings-tab.active {
|
||||
color: var(--accent);
|
||||
border-bottom-color: var(--accent);
|
||||
.settings-nav-item.active {
|
||||
background: var(--accent);
|
||||
color: #000;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.settings-nav-item.active .settings-nav-icon {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.settings-nav-icon {
|
||||
font-size: 1rem;
|
||||
width: 22px;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.settings-nav-item.active .settings-nav-icon {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.settings-nav-label {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* ── Settings content panel ──────────────────────────────────────────── */
|
||||
|
||||
.settings-content {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.settings-panel {
|
||||
@@ -743,6 +787,33 @@ body {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Mobile: stack sidebar above content */
|
||||
@media (max-width: 640px) {
|
||||
.settings-layout {
|
||||
flex-direction: column;
|
||||
}
|
||||
.settings-sidebar {
|
||||
width: 100%;
|
||||
flex-direction: row;
|
||||
overflow-x: auto;
|
||||
position: static;
|
||||
scrollbar-width: none;
|
||||
}
|
||||
.settings-sidebar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.settings-nav-item {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
padding: 8px 12px;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
.settings-nav-icon {
|
||||
font-size: 0.85rem;
|
||||
width: 18px;
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Network UI ──────────────────────────────────────────────────── */
|
||||
|
||||
.network-status-row {
|
||||
|
||||
+297
-239
@@ -6,259 +6,298 @@
|
||||
<h1>Settings</h1>
|
||||
</div>
|
||||
|
||||
<!-- Tab navigation -->
|
||||
<div class="settings-tabs">
|
||||
<button class="settings-tab active" data-tab="connection" onclick="switchTab('connection')">Connection</button>
|
||||
<button class="settings-tab" data-tab="audio" onclick="switchTab('audio')">Audio</button>
|
||||
<button class="settings-tab" data-tab="routing" onclick="switchTab('routing')">4CM Routing</button>
|
||||
<button class="settings-tab" data-tab="network" onclick="switchTab('network')">Network</button>
|
||||
<button class="settings-tab" data-tab="bluetooth" onclick="switchTab('bluetooth')">Bluetooth</button>
|
||||
<button class="settings-tab" data-tab="about" onclick="switchTab('about')">About</button>
|
||||
</div>
|
||||
<div class="settings-layout">
|
||||
<!-- Left sidebar navigation -->
|
||||
<nav class="settings-sidebar">
|
||||
<a class="settings-nav-item active" href="#connection" data-tab="connection" onclick="switchTab('connection')">
|
||||
<span class="settings-nav-icon">🔌</span>
|
||||
<span class="settings-nav-label">Connection</span>
|
||||
</a>
|
||||
<a class="settings-nav-item" href="#audio" data-tab="audio" onclick="switchTab('audio')">
|
||||
<span class="settings-nav-icon">🎚️</span>
|
||||
<span class="settings-nav-label">Audio</span>
|
||||
</a>
|
||||
<a class="settings-nav-item" href="#routing" data-tab="routing" onclick="switchTab('routing')">
|
||||
<span class="settings-nav-icon">🔀</span>
|
||||
<span class="settings-nav-label">4CM Routing</span>
|
||||
</a>
|
||||
<a class="settings-nav-item" href="#network" data-tab="network" onclick="switchTab('network')">
|
||||
<span class="settings-nav-icon">📶</span>
|
||||
<span class="settings-nav-label">Network</span>
|
||||
</a>
|
||||
<a class="settings-nav-item" href="#bluetooth" data-tab="bluetooth" onclick="switchTab('bluetooth')">
|
||||
<span class="settings-nav-icon">🔵</span>
|
||||
<span class="settings-nav-label">Bluetooth</span>
|
||||
</a>
|
||||
<a class="settings-nav-item" href="#about" data-tab="about" onclick="switchTab('about')">
|
||||
<span class="settings-nav-icon">ℹ️</span>
|
||||
<span class="settings-nav-label">About</span>
|
||||
</a>
|
||||
</nav>
|
||||
|
||||
<!-- Tab: Connection -->
|
||||
<div class="settings-panel active" id="tab-connection">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Connection</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Hostname</div>
|
||||
<div class="setting-value"><code id="hostname">pedal.local</code></div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Web Server</div>
|
||||
<div class="setting-value"><code id="web-url">http://pedal.local:8080</code></div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Connected Clients</div>
|
||||
<div class="setting-value"><span id="ws-count">0</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Audio -->
|
||||
<div class="settings-panel" id="tab-audio">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Audio</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Master Volume</div>
|
||||
<div class="setting-value">
|
||||
<input type="range" class="slider" id="settings-volume"
|
||||
min="0" max="100" value="{{ (master_volume * 100)|int }}"
|
||||
oninput="setVolume(this.value)">
|
||||
<span id="settings-volume-text">{{ (master_volume * 100)|int }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Tuner</div>
|
||||
<div class="setting-value">
|
||||
<button class="btn toggle-btn {% if tuner_enabled %}active{% endif %}"
|
||||
id="settings-tuner-btn" onclick="toggleTuner()">
|
||||
{% if tuner_enabled %}ON{% else %}OFF{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: 4CM Routing -->
|
||||
<div class="settings-panel" id="tab-routing">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>4-Cable Method (4CM)</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">4CM Mode</div>
|
||||
<div class="setting-value">
|
||||
<button class="btn toggle-btn {% if routing_mode == '4cm' %}active{% endif %}"
|
||||
id="4cm-toggle-btn" onclick="toggle4cm()">
|
||||
{% if routing_mode == '4cm' %}ON{% else %}OFF{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row" id="breakpoint-row"
|
||||
{% if routing_mode != '4cm' %}style="opacity: 0.4; pointer-events: none;"{% endif %}>
|
||||
<div class="setting-label">Breakpoint</div>
|
||||
<div class="setting-value">
|
||||
<input type="range" class="slider" id="4cm-breakpoint"
|
||||
min="0" max="16" value="{{ routing_breakpoint }}"
|
||||
oninput="set4cmBreakpoint(this.value)">
|
||||
<span id="4cm-breakpoint-text">{{ routing_breakpoint }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Routing</div>
|
||||
<div class="setting-value">
|
||||
<span id="4cm-routing-desc" class="routing-desc">
|
||||
{% if routing_mode == '4cm' %}
|
||||
Input 1 (Guitar) → Pre blocks [0..{{ routing_breakpoint }}) → Send
|
||||
| Input 2 (Return) → Post blocks [{{ routing_breakpoint }}..] → Output
|
||||
{% else %}
|
||||
Mono: Guitar → Full chain → Output
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Network -->
|
||||
<div class="settings-panel" id="tab-network">
|
||||
<!-- WiFi Client Status -->
|
||||
<div class="card" id="wifi-status-card">
|
||||
<div class="card-header"><h2>WiFi Status</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="network-status-row">
|
||||
<div class="network-status-icon" id="wifi-icon">📶</div>
|
||||
<div class="network-status-info">
|
||||
<div class="network-ssid" id="wifi-ssid">—</div>
|
||||
<div class="network-ip" id="wifi-ip">—</div>
|
||||
<div class="network-signal"><span id="wifi-signal-text">—</span></div>
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="refreshWiFiStatus()">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- WiFi Scan & Connect -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>WiFi Networks</h2>
|
||||
<button class="btn btn-secondary" onclick="scanWiFi()" id="wifi-scan-btn">Scan</button>
|
||||
</div>
|
||||
<div class="card-body" id="wifi-scan-results">
|
||||
<div class="hint">Click Scan to find available WiFi networks.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Known Networks -->
|
||||
<div class="card" id="wifi-saved-card">
|
||||
<div class="card-header"><h2>Known Networks</h2></div>
|
||||
<div class="card-body" id="wifi-saved-list">
|
||||
<div class="loading">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hotspot -->
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>WiFi Hotspot</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="hotspot-controls">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Hotspot Status</div>
|
||||
<div class="setting-value">
|
||||
<span id="hotspot-status-indicator" class="hotspot-indicator hotspot-off">Off</span>
|
||||
<!-- Content area -->
|
||||
<div class="settings-content">
|
||||
<!-- Tab: Connection -->
|
||||
<div class="settings-panel active" id="tab-connection">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Connection</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Hostname</div>
|
||||
<div class="setting-value"><code id="hostname">pedal.local</code></div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Web Server</div>
|
||||
<div class="setting-value"><code id="web-url">http://pedal.local:8080</code></div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Connected Clients</div>
|
||||
<div class="setting-value"><span id="ws-count">0</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row" id="hotspot-ssid-row">
|
||||
<div class="setting-label">SSID</div>
|
||||
<div class="setting-value">
|
||||
<input type="text" class="network-input" id="hotspot-ssid" value="Pi-Pedal" maxlength="32">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Audio -->
|
||||
<div class="settings-panel" id="tab-audio">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Audio</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Master Volume</div>
|
||||
<div class="setting-value">
|
||||
<input type="range" class="slider" id="settings-volume"
|
||||
min="0" max="100" value="{{ (master_volume * 100)|int }}"
|
||||
oninput="setVolume(this.value)">
|
||||
<span id="settings-volume-text">{{ (master_volume * 100)|int }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Tuner</div>
|
||||
<div class="setting-value">
|
||||
<button class="btn toggle-btn {% if tuner_enabled %}active{% endif %}"
|
||||
id="settings-tuner-btn" onclick="toggleTuner()">
|
||||
{% if tuner_enabled %}ON{% else %}OFF{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row" style="border-bottom:none">
|
||||
<div class="setting-label">Latency Profile</div>
|
||||
<div class="setting-value">
|
||||
<select id="audio-profile-select" class="profile-select"
|
||||
onchange="switchAudioProfile(this.value)">
|
||||
<option value="">Loading...</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row" style="border-bottom:none">
|
||||
<div class="setting-label">JACK Status</div>
|
||||
<div class="setting-value">
|
||||
<span id="jack-status" class="jack-check">—</span>
|
||||
<button class="btn btn-secondary btn-sm" onclick="refreshAudioProfile()">⟳</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row" id="hotspot-psk-row">
|
||||
<div class="setting-label">Password</div>
|
||||
<div class="setting-value">
|
||||
<input type="password" class="network-input" id="hotspot-psk" value="pedal1234" maxlength="63">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: 4CM Routing -->
|
||||
<div class="settings-panel" id="tab-routing">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>4-Cable Method (4CM)</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">4CM Mode</div>
|
||||
<div class="setting-value">
|
||||
<button class="btn toggle-btn {% if routing_mode == '4cm' %}active{% endif %}"
|
||||
id="4cm-toggle-btn" onclick="toggle4cm()">
|
||||
{% if routing_mode == '4cm' %}ON{% else %}OFF{% endif %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row" id="breakpoint-row"
|
||||
{% if routing_mode != '4cm' %}style="opacity: 0.4; pointer-events: none;"{% endif %}>
|
||||
<div class="setting-label">Breakpoint</div>
|
||||
<div class="setting-value">
|
||||
<input type="range" class="slider" id="4cm-breakpoint"
|
||||
min="0" max="16" value="{{ routing_breakpoint }}"
|
||||
oninput="set4cmBreakpoint(this.value)">
|
||||
<span id="4cm-breakpoint-text">{{ routing_breakpoint }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Routing</div>
|
||||
<div class="setting-value">
|
||||
<span id="4cm-routing-desc" class="routing-desc">
|
||||
{% if routing_mode == '4cm' %}
|
||||
Input 1 (Guitar) → Pre blocks [0..{{ routing_breakpoint }}) → Send
|
||||
| Input 2 (Return) → Post blocks [{{ routing_breakpoint }}..] → Output
|
||||
{% else %}
|
||||
Mono: Guitar → Full chain → Output
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Connected Clients</div>
|
||||
<div class="setting-value"><span id="hotspot-clients">0</span></div>
|
||||
</div>
|
||||
<div class="hotspot-actions">
|
||||
<button class="btn btn-primary" id="hotspot-enable-btn" onclick="enableHotspot()">Enable Hotspot</button>
|
||||
<button class="btn btn-danger" id="hotspot-disable-btn" onclick="disableHotspot()">Disable Hotspot</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: Bluetooth -->
|
||||
<div class="settings-panel" id="tab-bluetooth">
|
||||
<!-- BT Adapter Status -->
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Bluetooth</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Bluetooth</div>
|
||||
<div class="setting-value">
|
||||
<button class="btn toggle-btn" id="bt-power-btn" onclick="toggleBTPower()">OFF</button>
|
||||
<!-- Tab: Network -->
|
||||
<div class="settings-panel" id="tab-network">
|
||||
<!-- WiFi Client Status -->
|
||||
<div class="card" id="wifi-status-card">
|
||||
<div class="card-header"><h2>WiFi Status</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="network-status-row">
|
||||
<div class="network-status-icon" id="wifi-icon">📶</div>
|
||||
<div class="network-status-info">
|
||||
<div class="network-ssid" id="wifi-ssid">—</div>
|
||||
<div class="network-ip" id="wifi-ip">—</div>
|
||||
<div class="network-signal"><span id="wifi-signal-text">—</span></div>
|
||||
</div>
|
||||
<button class="btn btn-secondary" onclick="refreshWiFiStatus()">Refresh</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Discoverable</div>
|
||||
<div class="setting-value">
|
||||
<button class="btn toggle-btn" id="bt-discoverable-btn" onclick="toggleBTDiscoverable()">OFF</button>
|
||||
|
||||
<!-- WiFi Scan & Connect -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>WiFi Networks</h2>
|
||||
<button class="btn btn-secondary" onclick="scanWiFi()" id="wifi-scan-btn">Scan</button>
|
||||
</div>
|
||||
<div class="card-body" id="wifi-scan-results">
|
||||
<div class="hint">Click Scan to find available WiFi networks.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Adapter Name</div>
|
||||
<div class="setting-value"><span id="bt-adapter-name">—</span></div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Address</div>
|
||||
<div class="setting-value"><code id="bt-address">—</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BT Scan -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Discover Devices</h2>
|
||||
<button class="btn btn-secondary" onclick="scanBT()" id="bt-scan-btn">Scan</button>
|
||||
</div>
|
||||
<div class="card-body" id="bt-scan-results">
|
||||
<div class="hint">Click Scan to find discoverable Bluetooth devices.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Paired Devices -->
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Paired Devices</h2></div>
|
||||
<div class="card-body" id="bt-paired-list">
|
||||
<div class="loading">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BT MIDI Service -->
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Bluetooth MIDI</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">BT MIDI Service</div>
|
||||
<div class="setting-value">
|
||||
<span class="bt-midi-indicator" id="bt-midi-indicator">Off</span>
|
||||
<!-- Known Networks -->
|
||||
<div class="card" id="wifi-saved-card">
|
||||
<div class="card-header"><h2>Known Networks</h2></div>
|
||||
<div class="card-body" id="wifi-saved-list">
|
||||
<div class="loading">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bt-midi-actions">
|
||||
<button class="btn btn-primary" id="bt-midi-enable-btn" onclick="enableBTMIDI()">Enable</button>
|
||||
<button class="btn btn-danger" id="bt-midi-disable-btn" onclick="disableBTMIDI()">Disable</button>
|
||||
</div>
|
||||
<div class="setting-row mt-8">
|
||||
<div class="setting-label">Connected MIDI Devices</div>
|
||||
<div class="setting-value" id="bt-midi-devices">
|
||||
<span class="hint">None connected</span>
|
||||
|
||||
<!-- Hotspot -->
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>WiFi Hotspot</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="hotspot-controls">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Hotspot Status</div>
|
||||
<div class="setting-value">
|
||||
<span id="hotspot-status-indicator" class="hotspot-indicator hotspot-off">Off</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row" id="hotspot-ssid-row">
|
||||
<div class="setting-label">SSID</div>
|
||||
<div class="setting-value">
|
||||
<input type="text" class="network-input" id="hotspot-ssid" value="Pi-Pedal" maxlength="32">
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row" id="hotspot-psk-row">
|
||||
<div class="setting-label">Password</div>
|
||||
<div class="setting-value">
|
||||
<input type="password" class="network-input" id="hotspot-psk" value="pedal1234" maxlength="63">
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Connected Clients</div>
|
||||
<div class="setting-value"><span id="hotspot-clients">0</span></div>
|
||||
</div>
|
||||
<div class="hotspot-actions">
|
||||
<button class="btn btn-primary" id="hotspot-enable-btn" onclick="enableHotspot()">Enable Hotspot</button>
|
||||
<button class="btn btn-danger" id="hotspot-disable-btn" onclick="disableHotspot()">Disable Hotspot</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: About -->
|
||||
<div class="settings-panel" id="tab-about">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>About</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="about-info">
|
||||
<p><strong>Pi Multi-FX Pedal</strong> v0.1.0</p>
|
||||
<p>Raspberry Pi 4B — Python 3.11 — JACK Audio</p>
|
||||
<p><a href="https://gitea.ourpad.casa/shawn/pi-multifx-pedal" target="_blank">Gitea Repository</a></p>
|
||||
<!-- Tab: Bluetooth -->
|
||||
<div class="settings-panel" id="tab-bluetooth">
|
||||
<!-- BT Adapter Status -->
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Bluetooth</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Bluetooth</div>
|
||||
<div class="setting-value">
|
||||
<button class="btn toggle-btn" id="bt-power-btn" onclick="toggleBTPower()">OFF</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Discoverable</div>
|
||||
<div class="setting-value">
|
||||
<button class="btn toggle-btn" id="bt-discoverable-btn" onclick="toggleBTDiscoverable()">OFF</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Adapter Name</div>
|
||||
<div class="setting-value"><span id="bt-adapter-name">—</span></div>
|
||||
</div>
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">Address</div>
|
||||
<div class="setting-value"><code id="bt-address">—</code></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BT Scan -->
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h2>Discover Devices</h2>
|
||||
<button class="btn btn-secondary" onclick="scanBT()" id="bt-scan-btn">Scan</button>
|
||||
</div>
|
||||
<div class="card-body" id="bt-scan-results">
|
||||
<div class="hint">Click Scan to find discoverable Bluetooth devices.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Paired Devices -->
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Paired Devices</h2></div>
|
||||
<div class="card-body" id="bt-paired-list">
|
||||
<div class="loading">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- BT MIDI Service -->
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>Bluetooth MIDI</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="setting-row">
|
||||
<div class="setting-label">BT MIDI Service</div>
|
||||
<div class="setting-value">
|
||||
<span class="bt-midi-indicator" id="bt-midi-indicator">Off</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bt-midi-actions">
|
||||
<button class="btn btn-primary" id="bt-midi-enable-btn" onclick="enableBTMIDI()">Enable</button>
|
||||
<button class="btn btn-danger" id="bt-midi-disable-btn" onclick="disableBTMIDI()">Disable</button>
|
||||
</div>
|
||||
<div class="setting-row mt-8">
|
||||
<div class="setting-label">Connected MIDI Devices</div>
|
||||
<div class="setting-value" id="bt-midi-devices">
|
||||
<span class="hint">None connected</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tab: About -->
|
||||
<div class="settings-panel" id="tab-about">
|
||||
<div class="card">
|
||||
<div class="card-header"><h2>About</h2></div>
|
||||
<div class="card-body">
|
||||
<div class="about-info">
|
||||
<p><strong>Pi Multi-FX Pedal</strong> v0.1.0</p>
|
||||
<p>Raspberry Pi 4B — Python 3.11 — JACK Audio</p>
|
||||
<p><a href="https://gitea.ourpad.casa/shawn/pi-multifx-pedal" target="_blank">Gitea Repository</a></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -293,13 +332,21 @@
|
||||
<script src="/static/network.js"></script>
|
||||
<script src="/static/bluetooth.js"></script>
|
||||
<script>
|
||||
/* ── Tab switching ────────────────────────────────────────────────── */
|
||||
/* ── Tab switching (left nav) ──────────────────────────────────── */
|
||||
function switchTab(tabId) {
|
||||
document.querySelectorAll('.settings-tab').forEach(t => t.classList.remove('active'));
|
||||
// Update nav items
|
||||
document.querySelectorAll('.settings-nav-item').forEach(a => a.classList.remove('active'));
|
||||
document.querySelector(`.settings-nav-item[data-tab="${tabId}"]`).classList.add('active');
|
||||
|
||||
// Update panels
|
||||
document.querySelectorAll('.settings-panel').forEach(p => p.classList.remove('active'));
|
||||
document.querySelector(`.settings-tab[data-tab="${tabId}"]`).classList.add('active');
|
||||
document.getElementById(`tab-${tabId}`).classList.add('active');
|
||||
|
||||
// Sync URL hash
|
||||
if (window.location.hash !== `#${tabId}`) {
|
||||
history.pushState(null, '', `#${tabId}`);
|
||||
}
|
||||
|
||||
// Refresh tab data when switching to it
|
||||
if (tabId === 'network') {
|
||||
refreshWiFiStatus();
|
||||
@@ -308,16 +355,27 @@ function switchTab(tabId) {
|
||||
refreshBTStatus();
|
||||
refreshBTMIDIStatus();
|
||||
loadPairedDevices();
|
||||
} else if (tabId === 'audio') {
|
||||
refreshAudioProfile();
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Initial load on settings page ───────────────────────────────── */
|
||||
/* ── Initial load & hash routing ───────────────────────────────── */
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// Check if we should auto-switch based on URL hash
|
||||
const hash = window.location.hash.replace('#', '');
|
||||
if (['network', 'bluetooth', 'audio', 'routing', 'about'].includes(hash)) {
|
||||
const valid = ['connection', 'audio', 'routing', 'network', 'bluetooth', 'about'];
|
||||
if (valid.includes(hash)) {
|
||||
switchTab(hash);
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Listen for back/forward navigation ───────────────────────── */
|
||||
window.addEventListener('popstate', () => {
|
||||
const hash = window.location.hash.replace('#', '');
|
||||
const valid = ['connection', 'audio', 'routing', 'network', 'bluetooth', 'about'];
|
||||
if (valid.includes(hash)) {
|
||||
switchTab(hash);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
Reference in New Issue
Block a user