diff --git a/pkg/service/soundtouchweb/static/css/app.css b/pkg/service/soundtouchweb/static/css/app.css index f4fa05e..9f2daf4 100644 --- a/pkg/service/soundtouchweb/static/css/app.css +++ b/pkg/service/soundtouchweb/static/css/app.css @@ -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; diff --git a/pkg/service/soundtouchweb/static/js/components/DeviceList.js b/pkg/service/soundtouchweb/static/js/components/DeviceList.js index 2a69517..4785300 100644 --- a/pkg/service/soundtouchweb/static/js/components/DeviceList.js +++ b/pkg/service/soundtouchweb/static/js/components/DeviceList.js @@ -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`