android: the room list filtered by name, the rest on demand

Soulseek lists hundreds of rooms; the phone showed the first 80 and no way
to the rest. Community → Rooms now has a filter by name and extends the
list 200 at a time.
This commit is contained in:
KeelrLeek committed 2026-09-13 21:07:11 -05:00
1 parent 60303c8a95
commit d95c3f48d1
5 files changed
+35 -2

No files matched your search

+3
View File
@@ -172,6 +172,9 @@ namesake), the catalog's released records minus the shelf's by loose title,
dimmed cards that open the catalog record page, the totals remembered in dimmed cards that open the catalog record page, the totals remembered in
`library.gaps.v1` so the line says its number before the lookup. `library.gaps.v1` so the line says its number before the lookup.
The server's room list on Community is filtered by name and shows 80 at a
time with a way to the rest (Soulseek lists hundreds).
## A peer, and their shares ## A peer, and their shares
A raw result's ⋯ (or a long press on its head) is the peer's sheet: play A raw result's ⋯ (or a long press on its head) is the peer's sheet: play
+1 -1
View File
@@ -376,7 +376,7 @@ const later = (ms, fn) => setTimeout(fn, ms);
if (enabled && typeof window !== "undefined") window.__ssMock = { emit: mockEmit }; if (enabled && typeof window !== "undefined") window.__ssMock = { emit: mockEmit };
const MOCK_USER = "mockuser"; const MOCK_USER = "mockuser";
const ROOMS = [["Lounge", 412], ["Electronic", 98], ["Vinyl Rips", 37], ["Jazz", 61], ["Lossless Only", 23]]; const ROOMS = [["Lounge", 412], ["Electronic", 98], ["Vinyl Rips", 37], ["Jazz", 61], ["Lossless Only", 23], ...Array.from({ length: 90 }, (_, i) => ["Room " + (i + 1), 90 - i])];
const PEOPLE = ["ada", "bo", "cy", "dee"]; const PEOPLE = ["ada", "bo", "cy", "dee"];
const LINES = ["anyone got the 2024 remaster?", "the vinyl rip is warmer imo", "that folder is complete btw", "brb"]; const LINES = ["anyone got the 2024 remaster?", "the vinyl rip is warmer imo", "that folder is complete btw", "brb"];
const PEERS = [ const PEERS = [
+21 -1
View File
@@ -20,7 +20,17 @@
const online = $derived(slsk.connection === "online"); const online = $derived(slsk.connection === "online");
const joinedNames = $derived(Object.keys(slsk.joined)); const joinedNames = $derived(Object.keys(slsk.joined));
const otherRooms = $derived(slsk.rooms.filter((r) => !slsk.joined[r.name]).slice(0, 80)); /* the server's rooms: filtered by name, the biggest first, 80 at a
time with a way to the rest */
let roomQ = $state("");
let roomCap = $state(80);
const roomsMatching = $derived.by(() => {
const k = roomQ.trim().toLowerCase();
const list = slsk.rooms.filter((r) => !slsk.joined[r.name]);
return k ? list.filter((r) => r.name.toLowerCase().includes(k)) : list;
});
const otherRooms = $derived(roomsMatching.slice(0, roomCap));
$effect(() => { void roomQ; roomCap = 80; });
const threads = $derived( const threads = $derived(
Object.entries(slsk.chats) Object.entries(slsk.chats)
.map(([u, c]) => ({ u, c, last: c.messages[c.messages.length - 1] })) .map(([u, c]) => ({ u, c, last: c.messages[c.messages.length - 1] }))
@@ -121,6 +131,13 @@
<div class="sec" style="padding-top:{joinedNames.length ? 18 : 8}px"><h2>Rooms <em>· {slsk.rooms.length} on the server</em></h2></div> <div class="sec" style="padding-top:{joinedNames.length ? 18 : 8}px"><h2>Rooms <em>· {slsk.rooms.length} on the server</em></h2></div>
{#if !slsk.rooms.length} {#if !slsk.rooms.length}
<div class="sec-note">Asking the server for its rooms…</div> <div class="sec-note">Asking the server for its rooms…</div>
{:else if slsk.rooms.length > 8}
<div class="sfield roomfilter">
<Icon name="search" size={18} />
<input value={roomQ} oninput={(e) => (roomQ = e.currentTarget.value)} type="search" placeholder="Find a room" autocomplete="off" autocapitalize="off" spellcheck="false" aria-label="Find a room" />
{#if roomQ}<button class="iconbtn sm" onclick={() => (roomQ = "")} aria-label="Clear"><Icon name="x" size={18} /></button>{/if}
</div>
{#if roomQ}<div class="sec-note">{roomsMatching.length} of {slsk.rooms.length} rooms</div>{/if}
{/if} {/if}
{#each otherRooms as r (r.name)} {#each otherRooms as r (r.name)}
<button class="row" onclick={() => open(r.name)}> <button class="row" onclick={() => open(r.name)}>
@@ -129,6 +146,9 @@
<span class="r"><Icon name="plus" size={18} /></span> <span class="r"><Icon name="plus" size={18} /></span>
</button> </button>
{/each} {/each}
{#if roomsMatching.length > roomCap}
<div style="padding: 4px var(--pad) 8px"><button class="btn btn-ghost wide" onclick={() => (roomCap += 200)}>+ {roomsMatching.length - roomCap} more rooms</button></div>
{/if}
{:else if tab === "chat"} {:else if tab === "chat"}
{#if !threads.length} {#if !threads.length}
<div class="stub"><b>No conversations yet.</b>A name in a room, or a buddy, opens one.</div> <div class="stub"><b>No conversations yet.</b>A name in a room, or a buddy, opens one.</div>
+3
View File
@@ -1365,3 +1365,6 @@ html[data-motion="off"] *, html[data-motion="off"] *::before, html[data-motion="
/* the lyrics face's way to its tools */ /* the lyrics face's way to its tools */
.lyr-tools { display: block; margin: 18px auto 8px; padding: 7px 14px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); color: var(--text-secondary); font-size: 12.5px; font-weight: 700; } .lyr-tools { display: block; margin: 18px auto 8px; padding: 7px 14px; border-radius: 999px; background: rgba(255, 255, 255, 0.08); color: var(--text-secondary); font-size: 12.5px; font-weight: 700; }
.lyr-tools:active { background: rgba(255, 255, 255, 0.14); } .lyr-tools:active { background: rgba(255, 255, 255, 0.14); }
/* Community: the room filter sits with the section, not the page's own field */
.sfield.roomfilter { margin-top: 2px; }
+7
View File
@@ -112,6 +112,7 @@ const PROBE = `(function () {
plfilter: !!document.querySelector('.plfilter input'), plfilter: !!document.querySelector('.plfilter input'),
plfirst: (document.querySelector('.pl-rows .trk .t') || {}).textContent || '', plfirst: (document.querySelector('.pl-rows .trk .t') || {}).textContent || '',
qfirst: (document.querySelector('.q-face .row .t') || {}).textContent || '', qfirst: (document.querySelector('.q-face .row .t') || {}).textContent || '',
rooms: document.querySelectorAll('.screen button.row .art.hash').length,
plthumbs: document.querySelectorAll('.pl-rows .thumb img').length, plthumbs: document.querySelectorAll('.pl-rows .thumb img').length,
imp: document.querySelectorAll('.imp-rows .trk').length, imp: document.querySelectorAll('.imp-rows .trk').length,
impTags: [].map.call(document.querySelectorAll('.imp-rows .tag'), function (t) { return t.textContent.trim(); }).join('|'), impTags: [].map.call(document.querySelectorAll('.imp-rows .tag'), function (t) { return t.textContent.trim(); }).join('|'),
@@ -202,6 +203,10 @@ const MOCK_STEPS = [
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Open')`, name: "user-lookup", wait: 700 }, { do: `__click('.halfsheet .prompt-acts .btn-fill', 'Open')`, name: "user-lookup", wait: 700 },
{ do: `document.querySelector('.scrim').click()`, wait: 500 }, { do: `document.querySelector('.scrim').click()`, wait: 500 },
{ do: `__click('.segtabs button', 'Rooms')`, wait: 500 }, { do: `__click('.segtabs button', 'Rooms')`, wait: 500 },
/* the room list: filtered by name, the rest on demand */
{ do: `(function () { var i = document.querySelector('.roomfilter input'); i.value = 'vin'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "rooms-filtered", wait: 500 },
{ do: `(function () { var i = document.querySelector('.roomfilter input'); i.value = ''; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, wait: 400 },
{ do: `__click('.screen .btn', 'more rooms')`, name: "rooms-more", wait: 500 },
/* the fake network: a room with a few lines in it */ /* the fake network: a room with a few lines in it */
{ do: `__click('.screen button.row', 'Lounge')`, name: "room", wait: 2400 }, { do: `__click('.screen button.row', 'Lounge')`, name: "room", wait: 2400 },
{ do: `history.back()`, wait: 700 }, { do: `history.back()`, wait: 700 },
@@ -422,6 +427,8 @@ const MOCK_EXPECT = {
complete the set, following — the desktop's page */ complete the set, following — the desktop's page */
"home-again": (i) => /^(Good |Still)/.test(i.title) && /^Coming/.test(i.feat) && i.news >= 1 && /New from your artists/.test(i.secs) && /From the back catalogue/.test(i.secs) && /Recommended for you/.test(i.secs) && /Following/.test(i.secs), "home-again": (i) => /^(Good |Still)/.test(i.title) && /^Coming/.test(i.feat) && i.news >= 1 && /New from your artists/.test(i.secs) && /From the back catalogue/.test(i.secs) && /Recommended for you/.test(i.secs) && /Following/.test(i.secs),
room: (i) => i.screen.includes("tr-push") && i.msgs >= 2, room: (i) => i.screen.includes("tr-push") && i.msgs >= 2,
"rooms-filtered": (i) => i.rooms === 1,
"rooms-more": (i) => i.rooms >= 95,
/* the artist with a picture shows it on the hero and its card; the /* the artist with a picture shows it on the hero and its card; the
namesake under Deezer's empty-hash placeholder shows the app's own */ namesake under Deezer's empty-hash placeholder shows the app's own */
"cat-results": (i) => i.songs >= 4 && i.top.startsWith("Daft Punk") && i.heroImg && i.cardImgs === 1 && i.cardPhs === 1, "cat-results": (i) => i.songs >= 4 && i.top.startsWith("Daft Punk") && i.heroImg && i.cardImgs === 1 && i.cardPhs === 1,