Everything written this session that has not been through the device: rounds 43 to 48 (each patch adversarially verified, 43's transient-notice expectation fixed), a partial round 49, the pipeline scripts, the Android Auto design and the icon comparison mock. Also the two harness changes that were never A/B'd — adaptive waits, which may only shorten a declared wait and never lengthen it, and a ship that runs the mock walk and the deploy at the same time. The A/B and its 255-step baseline are in handoff/tools; main stays at what the phone has actually walked.
21 lines
1.3 KiB
Plaintext
21 lines
1.3 KiB
Plaintext
/** The extensions the backend's parser knows; anything else is refused here
|
|
* rather than read as "pasted text" with a nonsense title. */
|
|
const PLAYLIST_EXT = /\.(m3u8?|pls|xspf|csv|json|txt)$/i;
|
|
/** The playlist-FILE door picks FIRST, like the folder door — the phone's
|
|
* own document picker (the storage plugin's `pick_file`). Every type is
|
|
* offered on purpose: Android's providers report m3u and pls under half a
|
|
* dozen types and often as plain bytes, so a filter that names one greys
|
|
* out the very file being looked for; the extension is checked here
|
|
* instead. A document with no path on this phone is copied into the app's
|
|
* cache under its own name, up to the size the parser itself refuses.
|
|
* Returns { path, name } — or null when the picker was dismissed. */
|
|
export async function pickImportFile() {
|
|
let picked;
|
|
try { picked = await api.pickFile("*/*", 8 * 1024 * 1024); }
|
|
catch (e) { if (/cancelled/.test(String(e))) return null; throw e; }
|
|
if (!picked?.path) throw new Error("That file isn't on this phone's storage — copy it here first, then import it.");
|
|
const name = picked.name || "";
|
|
if (!PLAYLIST_EXT.test(name)) throw new Error("“" + (name || "That file") + "” isn't a playlist file — M3U, M3U8, PLS, XSPF, CSV, JSON or TXT.");
|
|
return { path: picked.path, name };
|
|
}
|