android: tap-to-sync, the per-track auto-sync switch, the vocal-reduced copy
The editor the desktop drives with a mouse is better under a thumb: the song plays, the line to time sits large in the middle of the screen with the one behind and the one ahead faint around it, and a tap anywhere stamps it. One layer the back gesture peels, and a set left mid-tap is offered back by the row that opened it; Save writes the track's own document, lines never tapped left out of it and the button saying how many are kept. The lyrics sheet also gains the per-track auto-sync switch and the desktop's reduced-vocals copy — the same title, artist and duration, so the words keep following it, and one row puts the original back.
This commit is contained in:
1 parent
e693dfb868
commit
24c6857847
9 files changed
+434
-11
No files matched your search
+17
-1
@@ -124,12 +124,14 @@ function plTouch(p) {
|
||||
setTimeout(() => mockEmit("soulseek:playlists-changed", {}), 30);
|
||||
}
|
||||
const plOf = (args) => PL.find((p) => p.id === Number(args?.id ?? args?.playlistId));
|
||||
/* the per-track auto-sync switch the lyrics sheet flips */
|
||||
let autoSyncOff = false;
|
||||
/* every track has three timed lines; the sheet's tools have something to act on */
|
||||
function mockLyrics(title) {
|
||||
return {
|
||||
status: "synced", source: "lrclib",
|
||||
synced: [{ time_ms: 0, text: "Here is " + (title || "a song") }, { time_ms: 4000, text: "Every word in time" }, { time_ms: 9000, text: "And the last one, late" }],
|
||||
plain: null, offset_ms: 0, auto_offset_ms: 0, auto_sync_off: false,
|
||||
plain: null, offset_ms: 0, auto_offset_ms: 0, auto_sync_off: autoSyncOff,
|
||||
};
|
||||
}
|
||||
function mockPlaylists(cmd, args) {
|
||||
@@ -714,6 +716,20 @@ function mockNet(cmd, args) {
|
||||
const synced = timed.every(Boolean);
|
||||
return Promise.resolve({ status: synced ? "synced" : "plain", source: "pasted", synced: synced ? timed : [], plain: synced ? null : lines.join("\n"), offset_ms: 0, auto_offset_ms: 0, auto_sync_off: false });
|
||||
}
|
||||
if (cmd === "lyrics_set_auto_sync") { autoSyncOff = !args?.enabled; return Promise.resolve(); }
|
||||
if (cmd === "lyrics_save_lines") {
|
||||
/* the Rust drops the empty lines and sorts by time — a half-tapped
|
||||
set is stored as the lines that HAVE one */
|
||||
const lines = (args?.lines || []).filter((l) => String(l?.text || "").trim())
|
||||
.map((l) => ({ time_ms: l.time_ms, text: l.text })).sort((a, b) => a.time_ms - b.time_ms);
|
||||
if (!lines.length) return Promise.reject(new Error("Nothing to save"));
|
||||
/* the Rust's payload_from does NOT carry the entry's auto-sync flag
|
||||
back from a write — false here, faithfully, so the walk notices if
|
||||
the store ever applies a save over the switch */
|
||||
return Promise.resolve({ status: "synced", source: "user", synced: lines, plain: null, offset_ms: 0, auto_offset_ms: 0, auto_sync_off: false });
|
||||
}
|
||||
/* the vocal-reduced copy: a render takes a beat, and it is a new file */
|
||||
if (cmd === "lyrics_prepare_instrumental") return new Promise((r) => setTimeout(() => r({ path: String(args?.path || "") + ".instrumental.wav", cached: false, durationSecs: 214 }), 400));
|
||||
if (cmd === "lyrics_clear") return Promise.resolve();
|
||||
if (cmd === "clear_play_history") return Promise.resolve();
|
||||
if (cmd === "refresh_album_art") return new Promise((r) => setTimeout(() => r("C:\\mock\\cover.jpg"), 200));
|
||||
|
||||
Reference in new issue
Block a user