mirror of
https://github.com/Abdess/retroarch_system.git
synced 2026-10-23 20:29:06 -05:00
feat: tidy title, footer and file picker
This commit is contained in:
1 parent
60de1284d1
commit
9e274f0469
4 files changed
+172
-25
No files matched your search
@@ -4,10 +4,19 @@
|
||||
// which convert.py derives from install/<platform>.json. The selection
|
||||
// follows install.py narrow(): a file is kept when one of its systems is
|
||||
// chosen, a file with no system when "other" is chosen.
|
||||
// The list follows the current data; release.json says what the published
|
||||
// ZIP of each setup holds, so the page can say when the two differ.
|
||||
import release from '../../../release.json';
|
||||
|
||||
const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
const packs = Object.fromEntries(
|
||||
Object.values(release.packs).flatMap((pack) =>
|
||||
pack.platforms.map((platform) => [platform, { files: pack.files, size: pack.extracted_size }]),
|
||||
),
|
||||
);
|
||||
---
|
||||
|
||||
<div class="rb-picker" data-base={base}>
|
||||
<div class="rb-picker" data-base={base} data-release={JSON.stringify({ tag: release.tag, packs })}>
|
||||
<fieldset>
|
||||
<label for="rb-pick-platform">Your setup</label>
|
||||
<select id="rb-pick-platform" name="platform">
|
||||
@@ -17,16 +26,20 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
<div class="rb-pick-systems" hidden>
|
||||
<p class="rb-pick-systems-head">
|
||||
<strong>Systems</strong>
|
||||
<button type="button" data-all>All</button>
|
||||
<button type="button" data-none>None</button>
|
||||
<button type="button" class="rb-text-button" data-all>All</button>
|
||||
<button type="button" class="rb-text-button" data-none>None</button>
|
||||
</p>
|
||||
<input type="search" class="rb-pick-filter" placeholder="Filter systems" aria-label="Filter systems" />
|
||||
<input type="search" class="rb-pick-filter" placeholder="Type a console" aria-label="Type a console" />
|
||||
<div class="rb-pick-list" role="group" aria-label="Systems"></div>
|
||||
</div>
|
||||
<div class="rb-pick-result" role="status" aria-live="polite"></div>
|
||||
<p class="rb-pick-release" hidden></p>
|
||||
<div class="rb-pick-output" hidden>
|
||||
<p><strong>Install command</strong> (Linux, macOS, Steam Deck, handhelds)</p>
|
||||
<pre class="rb-pick-cmd"><code></code></pre>
|
||||
<p><strong>Install command</strong> for Linux, macOS, Steam Deck and handhelds</p>
|
||||
<figure class="rb-pick-frame">
|
||||
<figcaption>Terminal</figcaption>
|
||||
<pre class="rb-pick-cmd"><code></code></pre>
|
||||
</figure>
|
||||
<button type="button" data-copy>Copy the command</button>
|
||||
<details>
|
||||
<summary>Files and where they go</summary>
|
||||
@@ -49,6 +62,8 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
files: [string, number, number[]][];
|
||||
};
|
||||
|
||||
type Release = { tag: string; packs: Record<string, { files: number; size: number }> };
|
||||
|
||||
const INSTALL = 'curl -fsSL https://raw.githubusercontent.com/Abdess/retrobios/main/install.sh | sh -s --';
|
||||
const MAX_ROWS = 500;
|
||||
|
||||
@@ -68,6 +83,9 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
const output = root.querySelector<HTMLElement>('.rb-pick-output')!;
|
||||
const cmd = root.querySelector<HTMLElement>('.rb-pick-cmd code')!;
|
||||
const tbody = root.querySelector('tbody')!;
|
||||
const releaseLine = root.querySelector<HTMLElement>('.rb-pick-release')!;
|
||||
const release: Release = JSON.parse(root.dataset.release ?? '{"tag":"","packs":{}}');
|
||||
const count = (n: number) => n.toLocaleString('en');
|
||||
let data: Data | null = null;
|
||||
|
||||
const params = new URLSearchParams(location.search);
|
||||
@@ -86,7 +104,7 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
const total = files.reduce((sum, [, bytes]) => sum + bytes, 0);
|
||||
const all = picked.length === data.systems.length + (data.other ? 1 : 0);
|
||||
result.textContent = picked.length
|
||||
? `${files.length} files, ${size(total)}, for ${picked.length} of ${data.systems.length + (data.other ? 1 : 0)} choices. Folder: ${data.folder || 'the setup BIOS folder'}.`
|
||||
? `${count(files.length)} files, ${size(total)}, for ${count(picked.length)} of ${count(data.systems.length + (data.other ? 1 : 0))} choices. Folder: ${data.folder || 'the setup BIOS folder'}.`
|
||||
: 'Choose at least one system.';
|
||||
output.hidden = !picked.length;
|
||||
cmd.textContent = `${INSTALL} --platform ${data.platform}${all ? '' : ` --system ${picked.join(',')}`}`;
|
||||
@@ -105,7 +123,7 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
const row = document.createElement('tr');
|
||||
const cell = document.createElement('td');
|
||||
cell.colSpan = 2;
|
||||
cell.textContent = `and ${files.length - MAX_ROWS} more.`;
|
||||
cell.textContent = `and ${count(files.length - MAX_ROWS)} more.`;
|
||||
row.append(cell);
|
||||
tbody.append(row);
|
||||
}
|
||||
@@ -119,32 +137,45 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
const load = async (platform: string, preset: string[] | null) => {
|
||||
list.replaceChildren();
|
||||
output.hidden = true;
|
||||
releaseLine.hidden = true;
|
||||
systemsBox.hidden = !platform;
|
||||
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,
|
||||
title: s.title || s.id,
|
||||
detail: [s.id, ...s.native].filter((n) => n !== s.title).join(', '),
|
||||
}));
|
||||
// Sorted by the name a reader looks for, not by id.
|
||||
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(', '),
|
||||
}))
|
||||
.sort((a, b) => a.title.localeCompare(b.title, 'en'));
|
||||
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;
|
||||
box.checked = preset ? preset.includes(choice.id) : false;
|
||||
const detail = document.createElement('small');
|
||||
detail.textContent = choice.detail;
|
||||
label.append(box, ` ${choice.title} `, detail);
|
||||
list.append(label);
|
||||
}
|
||||
const pack = release.packs[data!.platform];
|
||||
const listed = data!.files.reduce((sum, [, bytes]) => sum + bytes, 0);
|
||||
if (pack && (pack.files !== data!.files.length || pack.size !== listed)) {
|
||||
releaseLine.textContent = `The ZIP of release ${release.tag} holds ${count(pack.files)} files, ${size(pack.size)}. It was built from the list of that day. The command installs the current list.`;
|
||||
releaseLine.hidden = false;
|
||||
}
|
||||
applyFilter();
|
||||
render();
|
||||
};
|
||||
|
||||
select.addEventListener('change', () => load(select.value, null));
|
||||
select.addEventListener('change', async () => {
|
||||
await load(select.value, null);
|
||||
if (select.value) filter.focus();
|
||||
});
|
||||
list.addEventListener('change', render);
|
||||
const applyFilter = () => {
|
||||
const query = filter.value.trim().toLowerCase();
|
||||
@@ -192,5 +223,42 @@ const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
.rb-picker .rb-pick-filter { width: 100%; margin: 0.5rem 0; }
|
||||
.rb-picker .rb-pick-cmd code { white-space: pre-wrap; overflow-wrap: anywhere; }
|
||||
.rb-picker .rb-pick-result { margin-top: 1rem; font-weight: 600; }
|
||||
.rb-picker fieldset { border: 0; padding: 0; }
|
||||
.rb-picker fieldset {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.35rem;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
}
|
||||
.rb-picker fieldset label { font-weight: 600; }
|
||||
.rb-picker .rb-pick-release { margin-top: 0.5rem; color: var(--sl-color-gray-2); }
|
||||
/* All and None act on the list: plain text, not two primary buttons. */
|
||||
.sl-markdown-content .rb-picker .rb-pick-systems-head .rb-text-button {
|
||||
min-height: 32px;
|
||||
padding: 0 0.25rem;
|
||||
background: none;
|
||||
color: var(--rb-link);
|
||||
font-weight: 400;
|
||||
text-decoration: underline;
|
||||
}
|
||||
/* The command in the same titled frame as the page's other commands. */
|
||||
.rb-picker .rb-pick-frame {
|
||||
margin: 0.5rem 0;
|
||||
border: 1px solid var(--sl-color-gray-5);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.rb-picker .rb-pick-frame figcaption {
|
||||
padding: 0.35rem 1rem;
|
||||
border-bottom: 1px solid var(--sl-color-gray-5);
|
||||
background: var(--sl-color-gray-6);
|
||||
color: var(--sl-color-gray-2);
|
||||
font-size: var(--sl-text-xs);
|
||||
}
|
||||
.rb-picker .rb-pick-frame pre {
|
||||
margin: 0;
|
||||
padding: 0.75rem 1rem;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -1,10 +1,49 @@
|
||||
---
|
||||
// Starlight's footer (edit link, previous and next) and the licence line.
|
||||
// Starlight's footer (edit link, previous and next), who keeps the project
|
||||
// and where to report a problem, the release the packs come from, and the
|
||||
// licence line. The release is read from release.json, written when the
|
||||
// packs were published: the site may be newer than the packs.
|
||||
import Default from '@astrojs/starlight/components/Footer.astro';
|
||||
import release from '../../../release.json';
|
||||
|
||||
const REPO = 'https://github.com/Abdess/retrobios';
|
||||
const MONTHS = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
|
||||
|
||||
// A release tag is its date, vYYYY.MM.DD.
|
||||
const releaseDate = (tag: string): string | undefined => {
|
||||
const match = /^v(\d{4})\.(\d{2})\.(\d{2})$/.exec(tag);
|
||||
return match ? `${Number(match[3])} ${MONTHS[Number(match[2]) - 1]} ${match[1]}` : undefined;
|
||||
};
|
||||
|
||||
const tag: string = release.tag;
|
||||
const date = releaseDate(tag);
|
||||
---
|
||||
|
||||
<Default />
|
||||
<p class="rb-copyright">
|
||||
MIT for the tooling. BIOS and firmware files are third-party system software, preserved for personal backup,
|
||||
archival and interoperability.
|
||||
</p>
|
||||
<div class="rb-footer">
|
||||
<p>
|
||||
Maintained by <a href="https://github.com/Abdess">Abdess on GitHub</a>.{' '}
|
||||
<a href={`${REPO}/issues/new`}>Report a problem</a>.
|
||||
</p>
|
||||
<p>
|
||||
Packs from release <a href={`${REPO}/releases/tag/${tag}`}>{tag}</a>{date && <>, <time datetime={tag.slice(1).replaceAll('.', '-')}>{date}</time></>}.
|
||||
</p>
|
||||
<p class="rb-copyright">
|
||||
MIT for the tooling. BIOS and firmware files are third-party system software, preserved for personal backup,
|
||||
archival and interoperability.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.rb-footer {
|
||||
margin-top: 1.5rem;
|
||||
padding-top: 1rem;
|
||||
border-top: 1px solid var(--sl-color-hairline-light);
|
||||
font-size: var(--sl-text-xs);
|
||||
color: var(--sl-color-gray-3);
|
||||
}
|
||||
.rb-footer p { margin: 0.25rem 0 0; }
|
||||
.rb-footer a { color: inherit; }
|
||||
.rb-footer a:hover { color: var(--sl-color-white); }
|
||||
.rb-footer .rb-copyright { margin-top: 0.75rem; }
|
||||
</style>
|
||||
@@ -1,6 +1,8 @@
|
||||
---
|
||||
// Breadcrumbs, the level label and the title. The trail says which tab a
|
||||
// deep page belongs to; the label says who the page is written for.
|
||||
// deep page belongs to; the label says who the page is written for, and is
|
||||
// left out where the first crumb already names that tab. A page with a
|
||||
// `logo` (a path under the site root) shows it beside its title.
|
||||
const PAGE_TITLE_ID = '_top';
|
||||
|
||||
const LABELS = {
|
||||
@@ -11,8 +13,20 @@ const LABELS = {
|
||||
contribute: 'For contributors',
|
||||
} as const;
|
||||
|
||||
const { title, level } = Astro.locals.starlightRoute.entry.data;
|
||||
// The tab each level is read in, as the header names it.
|
||||
const TABS = {
|
||||
start: 'Start here',
|
||||
setup: 'Your setup',
|
||||
explore: 'Explore',
|
||||
reference: 'Reference',
|
||||
contribute: 'Contribute',
|
||||
} as const;
|
||||
|
||||
const { title, level, logo } = Astro.locals.starlightRoute.entry.data;
|
||||
const crumbs = Astro.locals.rbBreadcrumbs ?? [];
|
||||
const badge = level && crumbs[0]?.label !== TABS[level] && crumbs[0]?.label !== LABELS[level];
|
||||
const base = import.meta.env.BASE_URL.replace(/\/?$/, '/');
|
||||
const logoSrc = logo && (/^(https?:)?\//.test(logo) ? logo : base + logo);
|
||||
---
|
||||
|
||||
{
|
||||
@@ -26,8 +40,17 @@ const crumbs = Astro.locals.rbBreadcrumbs ?? [];
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
{level && <span class={`rb-level rb-level-${level}`}>{LABELS[level]}</span>}
|
||||
<h1 id={PAGE_TITLE_ID}>{title}</h1>
|
||||
{badge && <span class={`rb-level rb-level-${level}`}>{LABELS[level]}</span>}
|
||||
{
|
||||
logoSrc ? (
|
||||
<div class="rb-title-logo">
|
||||
<img src={logoSrc} alt="" height="40" />
|
||||
<h1 id={PAGE_TITLE_ID}>{title}</h1>
|
||||
</div>
|
||||
) : (
|
||||
<h1 id={PAGE_TITLE_ID}>{title}</h1>
|
||||
)
|
||||
}
|
||||
|
||||
<style>
|
||||
h1 {
|
||||
@@ -37,4 +60,18 @@ const crumbs = Astro.locals.rbBreadcrumbs ?? [];
|
||||
font-weight: 700;
|
||||
color: var(--sl-color-white);
|
||||
}
|
||||
.rb-title-logo {
|
||||
display: flex;
|
||||
gap: 0.75rem;
|
||||
align-items: center;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
.rb-title-logo h1 { margin-top: 0; }
|
||||
.rb-title-logo img {
|
||||
flex: none;
|
||||
width: auto;
|
||||
height: 40px;
|
||||
max-width: 8rem;
|
||||
object-fit: contain;
|
||||
}
|
||||
</style>
|
||||
@@ -16,6 +16,9 @@ export const collections = {
|
||||
searchWeight: z.number().min(0.1).max(10).optional(),
|
||||
// The date the page's content carries, for the sitemap.
|
||||
lastmod: z.string().optional(),
|
||||
// A mark drawn beside the title (a platform's logo), a path under
|
||||
// the site root such as assets/logos/batocera.svg.
|
||||
logo: z.string().optional(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
|
||||
Reference in new issue
Block a user