android: your own Soulseek interests, and who the server says is like you
Community's fourth tab is Taste: the likes and hates this client publishes to the Soulseek server (slsk_interests_mine, slsk_interest_set), added and taken back as chips, and the people the server ranks as close to them (slsk_similar_users) — a row each into the peer sheet the app already has. Every declaration asks the server again and the chips stay quiet until it has answered, the way the desktop does, because the ranking only moves when its input does. The lists live in taste.svelte.js, read once per account when the tab is opened; the mock walk declares a like, watches the match grow, declares a hate and takes the like back.
This commit is contained in:
1 parent
7eae0ee945
commit
e693dfb868
6 files changed
+195
-6
No files matched your search
+13
-1
@@ -22,7 +22,7 @@ implements is `research/mobile-mockup/` (gitignored; see its README).
|
|||||||
| `src/mobile/contacts.svelte.js` | the local view of a person on the network: a petname, a group, a note and the two marks (`contacts_meta_list` / `contact_meta_set`) — this phone only, published nowhere |
|
| `src/mobile/contacts.svelte.js` | the local view of a person on the network: a petname, a group, a note and the two marks (`contacts_meta_list` / `contact_meta_set`) — this phone only, published nowhere |
|
||||||
| `crates/tauri-plugin-media-session/` | a Tauri plugin with a Kotlin foreground service (`MediaService`) that owns the `MediaSession`, the MediaStyle notification, audio focus and the becoming-noisy pause. The audio never passes through it; it is what keeps the process alive in the background and what the lock screen shows |
|
| `crates/tauri-plugin-media-session/` | a Tauri plugin with a Kotlin foreground service (`MediaService`) that owns the `MediaSession`, the MediaStyle notification, audio focus and the becoming-noisy pause. The audio never passes through it; it is what keeps the process alive in the background and what the lock screen shows |
|
||||||
| `src/mobile/Sheet.svelte`, `HalfSheet.svelte`, `Lane.svelte`, `Cover.svelte`, `Release.svelte` | the player (art / lyrics / queue faces, container transform from the mini bar, drags), the bottom sheets (track, record, sort, the page overflow, the sleep timer), the waveform lane, the two card pieces (a long press on a card is its sheet) |
|
| `src/mobile/Sheet.svelte`, `HalfSheet.svelte`, `Lane.svelte`, `Cover.svelte`, `Release.svelte` | the player (art / lyrics / queue faces, container transform from the mini bar, drags), the bottom sheets (track, record, sort, the page overflow, the sleep timer), the waveform lane, the two card pieces (a long press on a card is its sheet) |
|
||||||
| `src/mobile/screens/` | Home, Search (the library half: records and artists here, tracks through `library_track_search`), Library (albums / artists / tracks / playlists), Album, Artist (the library's shell at once, the catalog dissolving in: portrait, Popular, discography with what is here marked), CatalogAlbum (a catalog record, rows the library has play), Playlist, Community (sign in; rooms, chat, buddies — the Soulseek side; the private layer waits), Room, Thread, You (Appearance switches, real) |
|
| `src/mobile/screens/` | Home, Search (the library half: records and artists here, tracks through `library_track_search`), Library (albums / artists / tracks / playlists), Album, Artist (the library's shell at once, the catalog dissolving in: portrait, Popular, discography with what is here marked), CatalogAlbum (a catalog record, rows the library has play), Playlist, Community (sign in; rooms, chat, buddies, taste — the Soulseek side; the private layer waits), Room, Thread, You (Appearance switches, real) |
|
||||||
| `src/mobile/prefs.svelte.js` | Appearance, kept in localStorage: ambience, accent from artwork, motion |
|
| `src/mobile/prefs.svelte.js` | Appearance, kept in localStorage: ambience, accent from artwork, motion |
|
||||||
| `src-tauri/tauri.android.conf.json` | merged over `tauri.conf.json` on Android: one window at `mobile.html`, no desktop resources, `minSdkVersion` 26 |
|
| `src-tauri/tauri.android.conf.json` | merged over `tauri.conf.json` on Android: one window at `mobile.html`, no desktop resources, `minSdkVersion` 26 |
|
||||||
| `src-tauri/gen/android/` | the generated Gradle project (`npx tauri android init`); `minSdk = 26`. Gitignored |
|
| `src-tauri/gen/android/` | the generated Gradle project (`npx tauri android init`); `minSdk = 26`. Gitignored |
|
||||||
@@ -176,6 +176,18 @@ dimmed cards that open the catalog record page, the totals remembered in
|
|||||||
The server's room list on Community is filtered by name and shows 80 at a
|
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).
|
time with a way to the rest (Soulseek lists hundreds).
|
||||||
|
|
||||||
|
Community's fourth tab is **Taste**: the interests this phone has
|
||||||
|
declared to the Soulseek server (`slsk_interests_mine`, `slsk_interest_set`)
|
||||||
|
and the people the server ranks as close to them (`slsk_similar_users`), a
|
||||||
|
row each into the peer sheet. A like or a hate is a chip and tapping one
|
||||||
|
takes it back; "Add a like" and "Add a hate" declare a new one. Every
|
||||||
|
declaration asks the server again and the chips stay quiet until it has
|
||||||
|
answered, because the ranking only moves when its input does. The lists
|
||||||
|
are this client's own — the backend keeps them and re-declares every one
|
||||||
|
at login, since the server's copy belongs to the account and another
|
||||||
|
client signed in elsewhere can change it under us. Mock-walked only: a
|
||||||
|
live sign-in and the server's own ranking are Kyle's to see.
|
||||||
|
|
||||||
## 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
|
||||||
|
|||||||
@@ -570,6 +570,9 @@ const cacheSizes = new Map(); // path → the file's size, for stream_cache_le
|
|||||||
const preloading = new Set();
|
const preloading = new Set();
|
||||||
let xfer = null; // the one fake transfer: { received, total, timer }
|
let xfer = null; // the one fake transfer: { received, total, timer }
|
||||||
let posTimer = null; // the fake engine's position ticks
|
let posTimer = null; // the fake engine's position ticks
|
||||||
|
/* the interests this fake account has declared, and the server's match on
|
||||||
|
them — a declaration moves both, the way the real server does */
|
||||||
|
const MY_INTERESTS = { likes: ["Daft Punk", "french house", "Warp Records"], hates: ["loudness war"] };
|
||||||
function mockNet(cmd, args) {
|
function mockNet(cmd, args) {
|
||||||
if (cmd === "get_credentials") return Promise.resolve({ username: MOCK_USER, has_password: true });
|
if (cmd === "get_credentials") return Promise.resolve({ username: MOCK_USER, has_password: true });
|
||||||
if (cmd === "connect_stored" || cmd === "connect_and_login") {
|
if (cmd === "connect_stored" || cmd === "connect_and_login") {
|
||||||
@@ -676,6 +679,22 @@ function mockNet(cmd, args) {
|
|||||||
return Promise.resolve(t);
|
return Promise.resolve(t);
|
||||||
}
|
}
|
||||||
if (cmd === "stop_search") return Promise.resolve();
|
if (cmd === "stop_search") return Promise.resolve();
|
||||||
|
if (cmd === "slsk_interests_mine") return Promise.resolve({ likes: MY_INTERESTS.likes.slice(), hates: MY_INTERESTS.hates.slice() });
|
||||||
|
if (cmd === "slsk_interest_set") {
|
||||||
|
const thing = String(args?.thing || "").trim();
|
||||||
|
if (!thing) return Promise.reject(new Error("nothing to declare"));
|
||||||
|
const list = args?.like ? MY_INTERESTS.likes : MY_INTERESTS.hates;
|
||||||
|
const at = list.findIndex((t) => t.toLowerCase() === thing.toLowerCase());
|
||||||
|
if (args?.on && at < 0) list.push(thing);
|
||||||
|
if (!args?.on && at >= 0) list.splice(at, 1);
|
||||||
|
return new Promise((r) => setTimeout(() => r({ likes: MY_INTERESTS.likes.slice(), hates: MY_INTERESTS.hates.slice() }), 200));
|
||||||
|
}
|
||||||
|
if (cmd === "slsk_similar_users") {
|
||||||
|
/* the server ranks on the likes we declared: one more name per like */
|
||||||
|
const pool = ["hifi_hank", "dusty_crates", "mp3_marge", "ada", "cy", "bo"];
|
||||||
|
const n = MY_INTERESTS.likes.length;
|
||||||
|
return new Promise((r) => setTimeout(() => r(pool.slice(0, Math.min(pool.length, n + 1)).map((username, i) => ({ username, rating: Math.max(1, n - i) }))), 300));
|
||||||
|
}
|
||||||
if (cmd === "player_state") return Promise.resolve({ status: "idle", position: 0 });
|
if (cmd === "player_state") return Promise.resolve({ status: "idle", position: 0 });
|
||||||
if (cmd === "preload_local") return Promise.resolve();
|
if (cmd === "preload_local") return Promise.resolve();
|
||||||
if (cmd === "waveform") {
|
if (cmd === "waveform") {
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
/* Community — the Soulseek side: rooms, chat, buddies. Signed out it is
|
/* Community — the Soulseek side: rooms, chat, buddies, taste. Signed
|
||||||
the sign-in card; signed in it is the hub, the desktop's seats drawn
|
out it is the sign-in card; signed in it is the hub, the desktop's
|
||||||
with the phone's components. The private layer (Spaces, holdings, the
|
seats drawn with the phone's components. The private layer (Spaces,
|
||||||
sealed inbox) waits for the Freenet story on the phone. */
|
holdings, the sealed inbox) waits for the Freenet story on the
|
||||||
|
phone. */
|
||||||
import Icon from "../../components/Icon.svelte";
|
import Icon from "../../components/Icon.svelte";
|
||||||
import Identicon from "../../components/Identicon.svelte";
|
import Identicon from "../../components/Identicon.svelte";
|
||||||
import { slsk, connect, connectStored, disconnect, refreshRooms, joinRoom, leaveRoom, openThread, unreadTotal } from "../slsk.svelte.js";
|
import { slsk, connect, connectStored, disconnect, refreshRooms, joinRoom, leaveRoom, openThread, unreadTotal } from "../slsk.svelte.js";
|
||||||
@@ -10,6 +11,7 @@
|
|||||||
import { metaOf, display, realName } from "../contacts.svelte.js";
|
import { metaOf, display, realName } from "../contacts.svelte.js";
|
||||||
import { player } from "../player.svelte.js";
|
import { player } from "../player.svelte.js";
|
||||||
import { ago } from "../util.js";
|
import { ago } from "../util.js";
|
||||||
|
import { taste, loadTaste, setInterest, askSimilar } from "../taste.svelte.js";
|
||||||
|
|
||||||
let { entry, screenEl = null } = $props();
|
let { entry, screenEl = null } = $props();
|
||||||
|
|
||||||
@@ -48,6 +50,20 @@
|
|||||||
openHalf("prompt", { title: "Look up a user", placeholder: "Soulseek username", ok: "Open", onsubmit: (v) => { const u = v.trim(); if (u) openHalf("peer", { username: u }); } });
|
openHalf("prompt", { title: "Look up a user", placeholder: "Soulseek username", ok: "Open", onsubmit: (v) => { const u = v.trim(); if (u) openHalf("peer", { username: u }); } });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* your own declared interests and the people the server ranks close to
|
||||||
|
them — read once per account, when the tab is opened */
|
||||||
|
$effect(() => {
|
||||||
|
if (online && tab === "taste") void loadTaste();
|
||||||
|
});
|
||||||
|
function declare(like) {
|
||||||
|
openHalf("prompt", {
|
||||||
|
title: like ? "Declare a like" : "Declare a hate",
|
||||||
|
placeholder: like ? "an artist, a label, a genre" : "an artist or a genre you would rather not hear",
|
||||||
|
ok: "Declare",
|
||||||
|
onsubmit: (v) => { void setInterest(v, like, true); },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function signIn() {
|
async function signIn() {
|
||||||
if (!user.trim() || !pass) return;
|
if (!user.trim() || !pass) return;
|
||||||
await connect(user, pass);
|
await connect(user, pass);
|
||||||
@@ -111,7 +127,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<div class="segtabs" style="padding-top:12px">
|
<div class="segtabs" style="padding-top:12px">
|
||||||
{#each [["rooms", "Rooms"], ["chat", "Chat"], ["buddies", "Buddies"]] as [k, l] (k)}
|
{#each [["rooms", "Rooms"], ["chat", "Chat"], ["buddies", "Buddies"], ["taste", "Taste"]] as [k, l] (k)}
|
||||||
<button class:on={tab === k} onclick={() => patch({ tab: k })}>
|
<button class:on={tab === k} onclick={() => patch({ tab: k })}>
|
||||||
{l}{#if k === "chat" && unreadTotal()} <span class="count">{unreadTotal()}</span>{/if}
|
{l}{#if k === "chat" && unreadTotal()} <span class="count">{unreadTotal()}</span>{/if}
|
||||||
</button>
|
</button>
|
||||||
@@ -188,6 +204,41 @@
|
|||||||
<span class="r"><span class="dot" class:off={status(b.st) === "offline"} class:warn={status(b.st) === "away"}></span></span>
|
<span class="r"><span class="dot" class:off={status(b.st) === "offline"} class:warn={status(b.st) === "away"}></span></span>
|
||||||
</button>
|
</button>
|
||||||
{/each}
|
{/each}
|
||||||
|
{:else if tab === "taste"}
|
||||||
|
<div class="sec" style="padding-top:8px"><h2>What you like <em>· {taste.likes.length}</em></h2></div>
|
||||||
|
<div class="sec-note">Declared on Soulseek: this is what the server matches other people against.</div>
|
||||||
|
<div class="chips tastechips likes" style="padding-top:8px">
|
||||||
|
{#each taste.likes as thing (thing)}
|
||||||
|
<button class="chip on tchip" disabled={taste.busy} onclick={() => setInterest(thing, true, false)}>{thing}<Icon name="x" size={13} /></button>
|
||||||
|
{/each}
|
||||||
|
<button class="chip quiet tadd" disabled={taste.busy} onclick={() => declare(true)}><Icon name="plus" size={14} />Add a like</button>
|
||||||
|
</div>
|
||||||
|
<div class="sec" style="padding-top:18px"><h2>What you hate <em>· {taste.hates.length}</em></h2></div>
|
||||||
|
<div class="chips tastechips hates" style="padding-top:8px">
|
||||||
|
{#each taste.hates as thing (thing)}
|
||||||
|
<button class="chip tchip" disabled={taste.busy} onclick={() => setInterest(thing, false, false)}>{thing}<Icon name="x" size={13} /></button>
|
||||||
|
{/each}
|
||||||
|
<button class="chip quiet tadd" disabled={taste.busy} onclick={() => declare(false)}><Icon name="ban" size={14} />Add a hate</button>
|
||||||
|
</div>
|
||||||
|
<div class="sec" style="padding-top:18px"><h2>People like you{#if taste.similar?.users?.length} <em>· {taste.similar.users.length}</em>{/if}</h2></div>
|
||||||
|
{#if taste.asking && !taste.similar}
|
||||||
|
<div class="sec-note">Asking the server…</div>
|
||||||
|
{:else if taste.similar?.users?.length}
|
||||||
|
{#each taste.similar.users.slice(0, 40) as su (su.username)}
|
||||||
|
<button class="row similar" onclick={() => openHalf("peer", { username: su.username })}>
|
||||||
|
<span class="art round idc"><Identicon seed={su.username} size={46} /></span>
|
||||||
|
<span class="b"><span class="t">{su.username}</span><span class="m">shares {su.rating} of your {su.rating === 1 ? "like" : "likes"}</span></span>
|
||||||
|
<span class="r"><Icon name="chevron-right" size={17} /></span>
|
||||||
|
</button>
|
||||||
|
{/each}
|
||||||
|
{:else if taste.error}
|
||||||
|
<div class="stub"><b>The server did not answer.</b>{taste.error}</div>
|
||||||
|
{:else if !taste.likes.length}
|
||||||
|
<div class="stub"><b>Nothing to match you on yet.</b>The server puts people together on the likes you declare — declare one above and it has something to go on.</div>
|
||||||
|
{:else}
|
||||||
|
<div class="stub"><b>Nobody yet.</b>The server found no one close to what you declared.</div>
|
||||||
|
{/if}
|
||||||
|
<div style="padding: 6px var(--pad) 0"><button class="btn btn-ghost wide" disabled={taste.asking} onclick={() => askSimilar(true)}>Ask the server again</button></div>
|
||||||
{/if}
|
{/if}
|
||||||
<div style="padding: 18px var(--pad) 0"><button class="btn btn-ghost wide" onclick={disconnect}>Sign out</button></div>
|
<div style="padding: 18px var(--pad) 0"><button class="btn btn-ghost wide" onclick={disconnect}>Sign out</button></div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
@@ -1387,3 +1387,9 @@ html[data-motion="off"] *, html[data-motion="off"] *::before, html[data-motion="
|
|||||||
.pr-about { margin: 2px var(--pad) 10px; padding: 12px 14px; border-radius: var(--r-card); background: var(--surface); box-shadow: var(--ring); font-size: 13.5px; line-height: 1.55; color: var(--text-secondary); white-space: pre-wrap; overflow-wrap: anywhere; }
|
.pr-about { margin: 2px var(--pad) 10px; padding: 12px 14px; border-radius: var(--r-card); background: var(--surface); box-shadow: var(--ring); font-size: 13.5px; line-height: 1.55; color: var(--text-secondary); white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||||
.chips.pr-tags { flex-wrap: wrap; overflow: visible; row-gap: 7px; }
|
.chips.pr-tags { flex-wrap: wrap; overflow: visible; row-gap: 7px; }
|
||||||
.pr-gap .lead { color: var(--text-muted); }
|
.pr-gap .lead { color: var(--text-muted); }
|
||||||
|
|
||||||
|
/* Community → Taste: the interests you publish, wrapped rather than
|
||||||
|
scrolled — a declared list is read, not swiped */
|
||||||
|
.tastechips { flex-wrap: wrap; overflow: visible; }
|
||||||
|
.tastechips .tchip svg { opacity: 0.62; margin-right: -3px; }
|
||||||
|
.tastechips .chip:disabled { opacity: 0.5; }
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
/* Taste: what this device has told the Soulseek server it likes and what
|
||||||
|
it hates, and the people the server ranks as close to it. The desktop
|
||||||
|
keeps all three in `community.svelte.js` and follows one rule — the
|
||||||
|
ranking only moves when its input does, so every declaration asks the
|
||||||
|
server again and the chips stay quiet until it has answered. The lists
|
||||||
|
are THIS CLIENT's: the backend keeps them and re-declares every one of
|
||||||
|
them at login, because the server's own copy belongs to the account and
|
||||||
|
another client signed in elsewhere can change it under us. */
|
||||||
|
import * as api from "../lib/api.js";
|
||||||
|
import { slsk } from "./slsk.svelte.js";
|
||||||
|
import { player } from "./player.svelte.js";
|
||||||
|
|
||||||
|
export const taste = $state({
|
||||||
|
/** the things declared, as the backend last answered */
|
||||||
|
likes: [],
|
||||||
|
hates: [],
|
||||||
|
/** null until asked; { users: [{ username, rating }] } after */
|
||||||
|
similar: null,
|
||||||
|
/** a declaration in flight, so the chips go quiet */
|
||||||
|
busy: false,
|
||||||
|
asking: false,
|
||||||
|
/** the server's word when it would not answer */
|
||||||
|
error: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
/* the account these lists were read for — a plain variable, never $state:
|
||||||
|
the screen's effect is what calls loadTaste, and reading this inside it
|
||||||
|
would arm that effect on our own write */
|
||||||
|
let readFor = null;
|
||||||
|
|
||||||
|
/** What this device has declared. Once per account, unless forced. */
|
||||||
|
export async function loadTaste(force = false) {
|
||||||
|
const who = slsk.username || slsk.savedUser || "";
|
||||||
|
if (readFor === who && !force) return;
|
||||||
|
readFor = who;
|
||||||
|
try {
|
||||||
|
const v = await api.slskInterestsMine();
|
||||||
|
taste.likes = v?.likes ?? [];
|
||||||
|
taste.hates = v?.hates ?? [];
|
||||||
|
} catch (e) {
|
||||||
|
player.error = String(e);
|
||||||
|
}
|
||||||
|
await askSimilar(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Declare a thing, or take it back. The backend answers with both lists,
|
||||||
|
* and the server's match is asked again before the chips come back — it
|
||||||
|
* ranks on exactly this, so a second declaration must not overtake it. */
|
||||||
|
export async function setInterest(thing, like, on) {
|
||||||
|
const t = String(thing || "").trim();
|
||||||
|
if (!t || taste.busy) return false;
|
||||||
|
taste.busy = true;
|
||||||
|
try {
|
||||||
|
const v = await api.slskInterestSet(t, like, on);
|
||||||
|
taste.likes = v?.likes ?? [];
|
||||||
|
taste.hates = v?.hates ?? [];
|
||||||
|
await askSimilar(true);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
player.error = String(e);
|
||||||
|
return false;
|
||||||
|
} finally {
|
||||||
|
taste.busy = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The people the server puts near us, by the likes we declared. */
|
||||||
|
export async function askSimilar(force = false) {
|
||||||
|
if ((taste.similar && !force) || taste.asking) return;
|
||||||
|
taste.asking = true;
|
||||||
|
taste.error = "";
|
||||||
|
try {
|
||||||
|
taste.similar = { users: (await api.slskSimilarUsers()) || [] };
|
||||||
|
} catch (e) {
|
||||||
|
taste.similar = { users: [] };
|
||||||
|
taste.error = String(e);
|
||||||
|
} finally {
|
||||||
|
taste.asking = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -160,6 +160,9 @@ const PROBE = `(function () {
|
|||||||
hwant: (function () { try { var n = __ss.nav; return (n.sheet ? 1 : 0) + (n.half ? 1 : 0) + (n.stacks[n.tab].length - 1) + (n.tab !== 'home' ? 1 : 0); } catch (e) { return -1; } })(),
|
hwant: (function () { try { var n = __ss.nav; return (n.sheet ? 1 : 0) + (n.half ? 1 : 0) + (n.stacks[n.tab].length - 1) + (n.tab !== 'home' ? 1 : 0); } catch (e) { return -1; } })(),
|
||||||
cardImgs: document.querySelectorAll('.rail .acard img').length,
|
cardImgs: document.querySelectorAll('.rail .acard img').length,
|
||||||
cardPhs: document.querySelectorAll('.rail .acard svg').length,
|
cardPhs: document.querySelectorAll('.rail .acard svg').length,
|
||||||
|
likes: [].map.call(document.querySelectorAll('.tastechips.likes .tchip'), function (c) { return c.textContent.trim(); }).join('|'),
|
||||||
|
hates: [].map.call(document.querySelectorAll('.tastechips.hates .tchip'), function (c) { return c.textContent.trim(); }).join('|'),
|
||||||
|
similar: document.querySelectorAll('.screen .row.similar').length,
|
||||||
budNames: [].map.call(document.querySelectorAll('.screen .row.bud .t'), function (t) { return t.textContent.trim(); }).join('|'),
|
budNames: [].map.call(document.querySelectorAll('.screen .row.bud .t'), function (t) { return t.textContent.trim(); }).join('|'),
|
||||||
budSubs: [].map.call(document.querySelectorAll('.screen .row.bud .m'), function (t) { return t.textContent.trim(); }).join('|'),
|
budSubs: [].map.call(document.querySelectorAll('.screen .row.bud .m'), function (t) { return t.textContent.trim(); }).join('|'),
|
||||||
halfSub: ((document.querySelector('.halfsheet .sheet-head .m') || {}).textContent || '').trim(),
|
halfSub: ((document.querySelector('.halfsheet .sheet-head .m') || {}).textContent || '').trim(),
|
||||||
@@ -239,6 +242,19 @@ const MOCK_STEPS = [
|
|||||||
{ do: `(function () { var i = document.querySelector('.halfsheet .prompt .field'); i.value = 'dusty_crates'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, wait: 300 },
|
{ do: `(function () { var i = document.querySelector('.halfsheet .prompt .field'); i.value = 'dusty_crates'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, wait: 300 },
|
||||||
{ 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 },
|
||||||
|
/* your own interests: what is declared, a like added and the server's
|
||||||
|
match re-asked, a hate added, the like taken back, a match's sheet */
|
||||||
|
{ do: `(function () { var t = document.querySelector('.toast:not(.notice)'); if (t) t.click(); return true; })()`, wait: 200 },
|
||||||
|
{ do: `__click('.segtabs button', 'Taste')`, name: "taste", wait: 1000 },
|
||||||
|
{ do: `__click('.tastechips.likes .tadd', null)`, wait: 500 },
|
||||||
|
{ do: `(function () { var i = document.querySelector('.halfsheet .prompt .field'); i.value = 'Boards of Canada'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, wait: 300 },
|
||||||
|
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Declare')`, name: "taste-declared", wait: 1100 },
|
||||||
|
{ do: `__click('.tastechips.hates .tadd', null)`, wait: 500 },
|
||||||
|
{ do: `(function () { var i = document.querySelector('.halfsheet .prompt .field'); i.value = 'loudness war 2'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, wait: 300 },
|
||||||
|
{ do: `__click('.halfsheet .prompt-acts .btn-fill', 'Declare')`, name: "taste-hated", wait: 1100 },
|
||||||
|
{ do: `__click('.tastechips.likes .tchip', 'Boards of Canada')`, name: "taste-dropped", wait: 1100 },
|
||||||
|
{ do: `__click('.screen .row.similar', null)`, name: "taste-peer", wait: 700 },
|
||||||
|
{ 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 */
|
/* 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 = 'vin'; i.dispatchEvent(new Event('input', { bubbles: true })); })()`, name: "rooms-filtered", wait: 500 },
|
||||||
@@ -463,6 +479,11 @@ const MOCK_EXPECT = {
|
|||||||
"buddy-petname": (i) => i.budNames === "Zola from Vinyl Rips|cy" && /ada/.test(i.budSubs) && !i.toast,
|
"buddy-petname": (i) => i.budNames === "Zola from Vinyl Rips|cy" && /ada/.test(i.budSubs) && !i.toast,
|
||||||
"room-users": (i) => i.half.includes("up") && /Zola from Vinyl Rips/.test(i.halfText) && /ada/.test(i.halfText) && /bo/.test(i.halfText),
|
"room-users": (i) => i.half.includes("up") && /Zola from Vinyl Rips/.test(i.halfText) && /ada/.test(i.halfText) && /bo/.test(i.halfText),
|
||||||
"contact-cleared": (i) => !i.half.includes("up") && i.budNames === "ada|cy" && !i.toast,
|
"contact-cleared": (i) => !i.half.includes("up") && i.budNames === "ada|cy" && !i.toast,
|
||||||
|
taste: (i) => i.likes === "Daft Punk|french house|Warp Records" && i.hates === "loudness war" && i.similar === 4,
|
||||||
|
"taste-declared": (i) => /Boards of Canada/.test(i.likes) && i.similar === 5,
|
||||||
|
"taste-hated": (i) => i.hates === "loudness war|loudness war 2",
|
||||||
|
"taste-dropped": (i) => !/Boards of Canada/.test(i.likes) && i.similar === 4 && !i.toast,
|
||||||
|
"taste-peer": (i) => i.half.includes("up") && /hifi_hank/.test(i.halfText),
|
||||||
"home-more": (i) => i.half.includes("up"),
|
"home-more": (i) => i.half.includes("up"),
|
||||||
you: (i) => i.title.startsWith("You") && i.norm === "on" && i.folderRow && i.srcMax === "No limit",
|
you: (i) => i.title.startsWith("You") && i.norm === "on" && i.folderRow && i.srcMax === "No limit",
|
||||||
"you-norm-off": (i) => i.norm === "off",
|
"you-norm-off": (i) => i.norm === "off",
|
||||||
|
|||||||
Reference in new issue
Block a user