mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
Relates to #571. ## What Adds a **Name / IP** sort toggle to the Player device list. The choice is persisted in `localStorage` (`aftertouch_device_sort`), following the same preference pattern as the service-URL field in `PlayURL.js`. ## Why The device list was previously ordered only by IP: the service datastore keys devices by IP address and Go marshals map keys lexicographically, so the frontend received an already-IP-ordered object and rendered it as-is. BirdyBA (#571) asked to be able to sort by name instead. ## Changes - `DeviceList.js`: a `sortEntries()` helper plus a `useState`-backed toggle seeded from `localStorage`. Name mode sorts by `device.info.name` (falling back to the IP key when a device has no name yet); IP mode sorts the IP key **numerically** (`.2` before `.10`), which also tidies the old lexicographic ordering. - `css/app.css`: additive `.device-sort` / `.sort-btn` styling, reusing the existing accent / `.active` look. No existing rules touched. No backend change: the device name and IP are already in the payload. ## Testing - `node --check` on `DeviceList.js` passes. - `make build-player` succeeds (the static tree is `//go:embed`ed into the binary). - Manual: open the Player, toggle Name / IP, confirm the order changes and the choice survives a page reload. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
ca4adb3e11
commit
1270eed554
@@ -324,6 +324,30 @@ img { display: block; max-width: 100%; }
|
||||
}
|
||||
.back-btn:hover { background: var(--bg); }
|
||||
|
||||
/* ── Device sort toggle ──────────────────────────────────────────────────── */
|
||||
.device-sort {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: .4rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.device-sort-label {
|
||||
font-size: .85rem;
|
||||
opacity: .65;
|
||||
margin-right: .1rem;
|
||||
}
|
||||
.sort-btn {
|
||||
padding: .25rem .7rem;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 999px;
|
||||
background: var(--surface);
|
||||
color: inherit;
|
||||
font-size: .85rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.sort-btn:hover { background: var(--bg); }
|
||||
.sort-btn.active { background: var(--accent); color: var(--accent-fg); border-color: var(--accent); }
|
||||
|
||||
/* ── Device grid ─────────────────────────────────────────────────────────── */
|
||||
.device-grid {
|
||||
display: grid;
|
||||
|
||||
@@ -1,8 +1,26 @@
|
||||
import { h } from 'preact';
|
||||
import { useState } from 'preact/hooks';
|
||||
import htm from 'htm';
|
||||
|
||||
const html = htm.bind(h);
|
||||
|
||||
const SORT_LS_KEY = 'aftertouch_device_sort';
|
||||
|
||||
function sortEntries(entries, mode) {
|
||||
const copy = [...entries];
|
||||
if (mode === 'name') {
|
||||
// Sort by the speaker's display name, falling back to the map key (its IP)
|
||||
// when a device has no name yet.
|
||||
copy.sort(([idA, a], [idB, b]) =>
|
||||
(a?.info?.name || idA).localeCompare(b?.info?.name || idB, undefined, { sensitivity: 'base' }));
|
||||
} else {
|
||||
// Default: by IP (the map key), ordered numerically so .2 precedes .10.
|
||||
copy.sort(([idA], [idB]) =>
|
||||
idA.localeCompare(idB, undefined, { numeric: true, sensitivity: 'base' }));
|
||||
}
|
||||
return copy;
|
||||
}
|
||||
|
||||
function DeviceCard({ id, device, onSelect, onRemove }) {
|
||||
const { info, status } = device;
|
||||
const np = status?.nowPlaying;
|
||||
@@ -37,7 +55,14 @@ function DeviceCard({ id, device, onSelect, onRemove }) {
|
||||
}
|
||||
|
||||
export function DeviceList({ devices, isDiscovering, onSelect, onDiscover, onRemove }) {
|
||||
const entries = Object.entries(devices);
|
||||
const [sortMode, setSortMode] = useState(() => localStorage.getItem(SORT_LS_KEY) || 'ip');
|
||||
|
||||
function changeSort(mode) {
|
||||
setSortMode(mode);
|
||||
localStorage.setItem(SORT_LS_KEY, mode);
|
||||
}
|
||||
|
||||
const entries = sortEntries(Object.entries(devices), sortMode);
|
||||
|
||||
return html`
|
||||
<div class="device-list-container">
|
||||
@@ -51,6 +76,13 @@ export function DeviceList({ devices, isDiscovering, onSelect, onDiscover, onRem
|
||||
</button>
|
||||
</div>`
|
||||
: html`
|
||||
<div class="device-sort" key="sort">
|
||||
<span class="device-sort-label">Sort by</span>
|
||||
<button class="sort-btn ${sortMode === 'name' ? 'active' : ''}"
|
||||
onClick=${() => changeSort('name')}>Name</button>
|
||||
<button class="sort-btn ${sortMode === 'ip' ? 'active' : ''}"
|
||||
onClick=${() => changeSort('ip')}>IP</button>
|
||||
</div>
|
||||
<div class="device-grid" key="grid">
|
||||
${entries.map(([id, device]) => html`
|
||||
<${DeviceCard} key=${id} id=${id} device=${device} onSelect=${onSelect} onRemove=${onRemove} />
|
||||
|
||||
Reference in New Issue
Block a user