diff --git a/scripts/generate_site.py b/scripts/generate_site.py index 8d87140a..a07d2b6f 100644 --- a/scripts/generate_site.py +++ b/scripts/generate_site.py @@ -6864,7 +6864,12 @@ def _generate(args: argparse.Namespace) -> None: json.dumps(sitestarlight.sidebar(nav, present), indent=1) + "\n") write_if_changed(str(generated / "lastmod.json"), json.dumps(dict(sorted(lastmod.items())), indent=1) + "\n") - sitestarlight.picker_data(_REPO_ROOT / "install", public / "data" / "picker") + mfr_names = {mfr.lower().replace(" ", "-"): mfr for mfr in manufacturers} + titles = {} + for sys_id, (slug, console) in _system_page_map.items(): + mfr = mfr_names.get(slug, "") + titles[sys_id] = console if console.startswith(mfr) else f"{mfr} {console}" + sitestarlight.picker_data(_REPO_ROOT / "install", public / "data" / "picker", titles) total_pages = len(present) print(f"\nGenerated {total_pages} pages in {args.docs_dir}/") if stale: diff --git a/scripts/sitestarlight.py b/scripts/sitestarlight.py index 55654522..3623299b 100644 --- a/scripts/sitestarlight.py +++ b/scripts/sitestarlight.py @@ -494,9 +494,10 @@ def _links(items: list[dict]) -> Iterator[dict]: # The picker's data -def picker_data(install: Path, out: Path) -> int: +def picker_data(install: Path, out: Path, titles: dict[str, str]) -> int: """Each manifest, compacted to what the file picker reads: per file its destination, size and systems, the systems install.py --system accepts. + `titles` names a system id the way its system page does. """ out.mkdir(parents=True, exist_ok=True) index = [] @@ -512,7 +513,8 @@ def picker_data(install: Path, out: Path) -> int: "name": data.get("display_name", data["platform"]), "folder": (data.get("detect") or [{}])[0].get("bios_path", ""), "generated": data.get("generated", ""), - "systems": [{"id": s, "native": names.get(s, [])} for s in systems], + "systems": [{"id": s, "title": titles.get(s, ""), "native": names.get(s, [])} + for s in systems], "other": any(not f.get("systems") for f in data["files"]), "files": [[f["dest"], f.get("size", 0), [position[s] for s in f.get("systems") or []]] for f in sorted(data["files"], key=lambda f: f["dest"])], diff --git a/tests/test_site_picker.py b/tests/test_site_picker.py index a4f2fc0e..7ff89155 100644 --- a/tests/test_site_picker.py +++ b/tests/test_site_picker.py @@ -42,7 +42,8 @@ class PickerMatchesInstaller(unittest.TestCase): self.folder = tempfile.TemporaryDirectory(dir=REPO_ROOT / "tmp" if (REPO_ROOT / "tmp").is_dir() else None) self.out = Path(self.folder.name) - self.count = sitestarlight.picker_data(REPO_ROOT / "install", self.out) + self.count = sitestarlight.picker_data(REPO_ROOT / "install", self.out, + {"sony-playstation": "Sony PlayStation"}) def tearDown(self): self.folder.cleanup() @@ -86,6 +87,28 @@ class PickerMatchesInstaller(unittest.TestCase): self.assertIn("picked.includes('other')", source) self.assertRegex(source, re.escape("--system ${picked.join(',')}")) + def test_systems_carry_their_page_title(self): + """A beginner picks "Sony PlayStation". The id stays for the command.""" + data = json.loads((self.out / "batocera.json").read_text(encoding="utf-8")) + psx = next(s for s in data["systems"] if s["id"] == "sony-playstation") + self.assertEqual(psx["title"], "Sony PlayStation") + self.assertIn("s.title || s.id", PICKER.read_text(encoding="utf-8")) + + def test_filter_hides_rows_and_survives_a_platform_change(self): + """A display rule on the rows overrides the hidden attribute, and a new + list must keep the query typed: otherwise None clears rows no one sees.""" + source = PICKER.read_text(encoding="utf-8") + self.assertIn(".rb-pick-list label[hidden] { display: none; }", source) + load = source[source.index("const load = async"):source.index("const applyFilter")] + self.assertIn("applyFilter();", load) + + def test_sizes_use_the_installer_units(self): + """The total the page shows is the one install.py prints for the same files.""" + source = PICKER.read_text(encoding="utf-8") + self.assertNotRegex(source, r"\b1e[369]\b") + self.assertEqual(install.format_size(338_165_350), "322.5 MB") + self.assertIn("(bytes / 1024 ** 2).toFixed(1)} MB", source) + if __name__ == "__main__": unittest.main() diff --git a/website/src/components/FilePicker.astro b/website/src/components/FilePicker.astro index 793e8e5e..4586ac8f 100644 --- a/website/src/components/FilePicker.astro +++ b/website/src/components/FilePicker.astro @@ -44,7 +44,7 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/'); name: string; folder: string; generated: string; - systems: { id: string; native: string[] }[]; + systems: { id: string; title: string; native: string[] }[]; other: boolean; files: [string, number, number[]][]; }; @@ -53,9 +53,9 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/'); const MAX_ROWS = 500; const size = (bytes: number) => - bytes >= 1e9 ? `${(bytes / 1e9).toFixed(1)} GB` - : bytes >= 1e6 ? `${(bytes / 1e6).toFixed(1)} MB` - : bytes >= 1e3 ? `${(bytes / 1e3).toFixed(1)} KB` + bytes >= 1024 ** 3 ? `${(bytes / 1024 ** 3).toFixed(1)} GB` + : bytes >= 1024 ** 2 ? `${(bytes / 1024 ** 2).toFixed(1)} MB` + : bytes >= 1024 ? `${(bytes / 1024).toFixed(1)} KB` : `${bytes} B`; for (const root of document.querySelectorAll('.rb-picker')) { @@ -123,28 +123,36 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/'); if (!platform) { result.textContent = ''; return; } result.textContent = 'Loading the file list...'; data = await fetch(`${base}data/picker/${platform}.json`).then((r) => r.json()); - const choices = data!.systems.map((s) => ({ id: s.id, label: s.native.length ? `${s.id} (${s.native.join(', ')})` : s.id })); - if (data!.other) choices.push({ id: 'other', label: 'other (files no system owns: engines, computers)' }); + const choices = data!.systems.map((s) => ({ + id: s.id, + title: s.title || s.id, + detail: [s.id, ...s.native].filter((n) => n !== s.title).join(', '), + })); + if (data!.other) choices.push({ id: 'other', title: 'Other files', detail: 'engines and computers no system owns' }); for (const choice of choices) { const label = document.createElement('label'); const box = document.createElement('input'); box.type = 'checkbox'; box.value = choice.id; box.checked = preset ? preset.includes(choice.id) : true; - label.append(box, ` ${choice.label}`); + const detail = document.createElement('small'); + detail.textContent = choice.detail; + label.append(box, ` ${choice.title} `, detail); list.append(label); } + applyFilter(); render(); }; select.addEventListener('change', () => load(select.value, null)); list.addEventListener('change', render); - filter.addEventListener('input', () => { + const applyFilter = () => { const query = filter.value.trim().toLowerCase(); for (const label of list.querySelectorAll('label')) { label.hidden = !!query && !label.textContent!.toLowerCase().includes(query); } - }); + }; + filter.addEventListener('input', applyFilter); root.querySelector('[data-all]')!.addEventListener('click', () => { list.querySelectorAll('label:not([hidden]) input').forEach((box) => (box.checked = true)); render(); @@ -175,6 +183,8 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/'); border: 1px solid var(--rb-border); border-radius: 4px; } + .rb-picker .rb-pick-list label[hidden] { display: none; } + .rb-picker .rb-pick-list small { color: var(--sl-color-gray-3); } .rb-picker .rb-pick-list label { display: flex; gap: 0.5rem; align-items: center; min-height: 32px; margin: 0; font-size: 0.9rem; } .sl-markdown-content .rb-picker .rb-pick-list input { min-height: auto; width: 1.1rem; height: 1.1rem; margin: 0; flex: none; } .rb-picker .rb-pick-systems-head { display: flex; gap: 0.5rem; align-items: center; }