mirror of
https://github.com/mihawk-99/PS5_RetroArch.git
synced 2026-10-18 23:09:29 -05:00
WebUI: drawn icons for regions, devices and media sources
Flags for USA, Europe, Japan and Korea and a globe for World, on the region choice (now flag chips, like the other choices) and in place of a game card's region tag; a console icon for PS5 and a PC icon for This PC → PS5; a badge for each source: RetroArch's own invader for libretro thumbnails, and original drawings for ScreenScraper, LaunchBox and EmuMovies (not their owners' logos, which are theirs to license). Inline SVG, no image requests; the console icon is a generic outline. Checked against the console at 1440 px (the Download Media tab, a scraped SNES card). Command: tools/verify.sh: all five PASS (149 tests).
This commit is contained in:
1 parent
9b7ae64d32
commit
c36ac76809
3 files changed
+51
-6
No files matched your search
@@ -41,3 +41,5 @@ main{max-width:100rem;margin-inline:auto}
|
||||
@media(max-width:768px){.media-row{grid-template-columns:1fr}.media-hint,#scrape-system{grid-column:1}.media-row-kinds .media-label{padding-top:0}.kind-tiles{grid-template-columns:repeat(auto-fill,minmax(6.8rem,1fr))}.job-stats{grid-template-columns:repeat(2,minmax(0,1fr))}.segmented{flex-wrap:wrap}.media-bar{flex-wrap:wrap}.media-bar .primary{width:100%}}
|
||||
.kinds-area .media-hint{grid-column:auto}
|
||||
.media-head p{margin-top:0}@media(max-width:768px){.segmented{display:flex;width:100%}.segmented label{flex:1}.segmented span{text-align:center;padding:.45rem .5rem}}
|
||||
.flag{width:1.35rem;height:.9rem;border-radius:.15rem;box-shadow:0 0 0 .0555556rem rgba(0,0,0,.18);flex:none;vertical-align:middle}.flag.globe{width:1rem;height:1rem;border-radius:50%;box-shadow:none}.device{width:1.25rem;height:1.25rem;flex:none}.device.arrow{width:.9rem;height:.9rem;opacity:.7}.source-logo{width:1.15rem;height:1.15rem;flex:none;border-radius:.3rem}img.source-logo{object-fit:contain}.segmented span[data-icon]{display:inline-flex;align-items:center;gap:.4rem}.segmented span[data-icon] b{font-weight:500}.region-pick{display:flex;align-items:center;gap:.6rem;font-size:.88rem;flex-wrap:wrap}.chip[aria-checked=true] .source-logo{box-shadow:0 0 0 .111111rem rgba(255,255,255,.7)}.version-tags .version-tag{display:inline-flex;align-items:center;gap:.2rem}.version-tags .version-tag.flags{border:0;padding:0}.version-tags .version-tag.flags .flag{width:1.15rem;height:.78rem}
|
||||
@media(max-width:768px){.region-pick{width:100%}.region-pick .segmented span{padding:.4rem .3rem;font-size:.78rem;gap:.25rem}.region-pick .flag{width:1.1rem;height:.75rem}}
|
||||
+47
-4
@@ -719,6 +719,47 @@ window.addEventListener('hashchange', navigate);
|
||||
setInterval(() => { if (!document.hidden) reconnect(); }, 10000);
|
||||
navigate(); drawTransfers(); reconnect(); checkRelease();
|
||||
|
||||
|
||||
// The WebUI's drawn icons (inline SVG, no image requests): flags for regions, the
|
||||
// console and the PC, and a badge for each media source. The sources' badges are
|
||||
// original drawings, not their owners' logos.
|
||||
const EU_STARS = Array.from({ length: 12 }, (_, i) => { const a = i * Math.PI / 6; return `<circle cx="${(15 + 6 * Math.sin(a)).toFixed(2)}" cy="${(10 - 6 * Math.cos(a)).toFixed(2)}" r="0.9" fill="#ffcc00"/>`; }).join('');
|
||||
const US_STRIPES = Array.from({ length: 7 }, (_, i) => `<rect y="${(i * 20 / 6.5).toFixed(2)}" width="30" height="${(20 / 13).toFixed(2)}" fill="#b22234"/>`).join('');
|
||||
const US_STARS = Array.from({ length: 12 }, (_, i) => `<circle cx="${(1.6 + (i % 4) * 2.9 + (Math.floor(i / 4) % 2) * 1.4).toFixed(2)}" cy="${(1.7 + Math.floor(i / 4) * 2.6).toFixed(2)}" r="0.55" fill="#fff"/>`).join('');
|
||||
const ICONS = {
|
||||
'flag-us': `<svg class="flag" viewBox="0 0 30 20" aria-hidden="true"><rect width="30" height="20" fill="#fff"/>${US_STRIPES}<rect width="13" height="10.8" fill="#3c3b6e"/>${US_STARS}</svg>`,
|
||||
'flag-eu': `<svg class="flag" viewBox="0 0 30 20" aria-hidden="true"><rect width="30" height="20" fill="#003399"/>${EU_STARS}</svg>`,
|
||||
'flag-jp': '<svg class="flag" viewBox="0 0 30 20" aria-hidden="true"><rect width="30" height="20" fill="#fff"/><circle cx="15" cy="10" r="6" fill="#bc002d"/></svg>',
|
||||
'flag-kr': '<svg class="flag" viewBox="0 0 30 20" aria-hidden="true"><rect width="30" height="20" fill="#fff"/><path d="M15 6a4 4 0 0 1 0 8 2 2 0 0 1 0-4 2 2 0 0 0 0-4Z" fill="#cd2e3a"/><path d="M15 14a4 4 0 0 1 0-8 2 2 0 0 1 0 4 2 2 0 0 0 0 4Z" fill="#0047a0"/></svg>',
|
||||
globe: '<svg class="flag globe" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="9" fill="#2f80ed"/><path d="M3 12h18M12 3c-3 3-3 15 0 18M12 3c3 3 3 15 0 18M5 7h14M5 17h14" fill="none" stroke="#fff" stroke-width="1.2"/></svg>',
|
||||
ps5: '<svg class="device" viewBox="0 0 24 24" aria-hidden="true"><path d="M7 2c-2 0-3 1-3 3l1 14c0 2 1 3 3 3h1V2Z" fill="currentColor" opacity=".35"/><path d="M17 2c2 0 3 1 3 3l-1 14c0 2-1 3-3 3h-1V2Z" fill="currentColor" opacity=".35"/><rect x="9.5" y="2" width="5" height="20" rx="1" fill="currentColor"/><circle cx="12" cy="17.5" r=".9" fill="var(--surface)"/></svg>',
|
||||
pc: '<svg class="device" viewBox="0 0 24 24" aria-hidden="true"><rect x="2" y="3" width="20" height="13" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M9 20h6M12 16v4" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>',
|
||||
arrow: '<svg class="device arrow" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 12h15m-5-5 5 5-5 5" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/></svg>',
|
||||
'source-libretro': '<img class="source-logo" src="assets/retroarch.svg" alt="">',
|
||||
'source-screenscraper': '<svg class="source-logo" viewBox="0 0 24 24" aria-hidden="true"><rect x="1" y="1" width="22" height="22" rx="6" fill="#e2463c"/><rect x="5" y="6" width="14" height="9" rx="1.5" fill="none" stroke="#fff" stroke-width="1.8"/><path d="M9 19h6M12 15v4" stroke="#fff" stroke-width="1.8" stroke-linecap="round"/><path d="M8 11.5l2.2-2.2 2 2 1.8-1.8 2 2" fill="none" stroke="#fff" stroke-width="1.4" stroke-linejoin="round"/></svg>',
|
||||
'source-launchbox': '<svg class="source-logo" viewBox="0 0 24 24" aria-hidden="true"><rect x="1" y="1" width="22" height="22" rx="6" fill="#2d6cdf"/><path d="M6 9l6-3 6 3v7l-6 3-6-3Z M6 9l6 3 6-3 M12 12v7" fill="none" stroke="#fff" stroke-width="1.6" stroke-linejoin="round"/></svg>',
|
||||
'source-emumovies': '<svg class="source-logo" viewBox="0 0 24 24" aria-hidden="true"><rect x="1" y="1" width="22" height="22" rx="6" fill="#f08a24"/><rect x="5" y="6" width="14" height="12" rx="1.5" fill="none" stroke="#fff" stroke-width="1.6"/><path d="M8 6v12M16 6v12" stroke="#fff" stroke-width="1.2"/><path d="M10.5 9.5v5l4-2.5Z" fill="#fff"/></svg>' };
|
||||
function iconNode(name) {
|
||||
const template = document.createElement('template');
|
||||
template.innerHTML = ICONS[name] || '';
|
||||
return template.content.firstElementChild;
|
||||
}
|
||||
// A region tag of a game's name, as a flag ("USA", "Europe", "USA, Europe"...).
|
||||
const REGION_FLAGS = { USA: 'flag-us', Europe: 'flag-eu', Japan: 'flag-jp', Korea: 'flag-kr', World: 'globe' };
|
||||
function tagNode(tag) {
|
||||
const node = element('span', undefined, 'version-tag');
|
||||
const flags = tag.split(/,\s*/).map(part => REGION_FLAGS[part]).filter(Boolean);
|
||||
if (flags.length && flags.length === tag.split(/,\s*/).length) { for (const flag of flags) node.append(iconNode(flag)); node.title = tag; node.setAttribute('aria-label', tag); node.classList.add('flags'); }
|
||||
else node.textContent = tag;
|
||||
return node;
|
||||
}
|
||||
// The page's own [data-icon] spans get their drawing once.
|
||||
for (const span of $$('[data-icon]')) {
|
||||
const name = span.dataset.icon, label = span.textContent;
|
||||
span.replaceChildren();
|
||||
span.append(iconNode(name === 'pc-to-ps5' ? 'pc' : name)); // the words say where it goes
|
||||
span.append(element('b', label));
|
||||
}
|
||||
// The games and their media (src/scraper.h): the shared library every frontend reads.
|
||||
const KIND_NAMES = { cover: 'Cover', screenshot: 'Screenshot', title: 'Title', logo: 'Logo', video: 'Video' };
|
||||
function gameName(game) { return game.name || game.label; }
|
||||
@@ -766,7 +807,7 @@ function drawGames() {
|
||||
const badges = element('div', undefined, 'media-badges');
|
||||
for (const kind of game.media) badges.append(element('span', KIND_NAMES[kind] || kind));
|
||||
const tags = element('div', undefined, 'version-tags');
|
||||
for (const tag of gameTags(game)) tags.append(element('span', tag));
|
||||
for (const tag of gameTags(game)) tags.append(tagNode(tag));
|
||||
card.title = game.path.split('/').pop();
|
||||
info.append(element('strong', gameName(game)), tags, element('small', system.name), badges);
|
||||
card.append(pick, cover, info); grid.append(card);
|
||||
@@ -819,7 +860,9 @@ function drawMethod() {
|
||||
function drawSources() {
|
||||
const box = $('#scrape-sources'); box.replaceChildren();
|
||||
for (const source of scraperSettings.sources) {
|
||||
const chip = element('button', source.name, 'chip'); chip.type = 'button';
|
||||
const chip = element('button', undefined, 'chip'); chip.type = 'button';
|
||||
const logo = iconNode('source-' + source.id); if (logo) chip.append(logo);
|
||||
chip.append(element('span', source.name));
|
||||
chip.setAttribute('role', 'radio'); chip.setAttribute('aria-checked', source.id === scrapeSource);
|
||||
if (!source.available) { chip.disabled = true; chip.append(element('em', 'Coming', 'soon-tag')); }
|
||||
chip.addEventListener('click', () => { scrapeSource = source.id; drawSources(); drawKinds(); });
|
||||
@@ -877,7 +920,7 @@ async function openMedia() {
|
||||
for (const radio of $$('input[name="scrape-method"]')) radio.checked = radio.value === scraperSettings.mode;
|
||||
scrapeSource = scraperSettings.sources.some(s => s.id === scraperSettings.source && s.available) ? scraperSettings.source : 'libretro';
|
||||
scrapeKinds = new Set(scraperSettings.kinds);
|
||||
$('#scrape-region').value = scraperSettings.region;
|
||||
for (const radio of $$('input[name="scrape-region"]')) radio.checked = radio.value === scraperSettings.region;
|
||||
const select = $('#scrape-system'), chosen = select.value || $('#games-system').value;
|
||||
select.replaceChildren(...library.systems.map(s => new Option(`${s.name} (${s.games.length})`, s.id)));
|
||||
if (chosen) select.value = chosen;
|
||||
@@ -897,7 +940,7 @@ $('#scrape-start').addEventListener('click', async () => {
|
||||
const lines = scope === 'selected' ? [...selectedGames].map(([path, system]) => `${system}\t${path}`)
|
||||
: library.systems.filter(s => scope === 'all' || s.id === $('#scrape-system').value).map(s => `${s.id}\t`);
|
||||
if (!kinds.length || !lines.length) { drawSummary(); return; }
|
||||
const query = new URLSearchParams({ mode: method, source: scrapeSource, kinds: kinds.join(','), region: $('#scrape-region').value, language: scraperSettings.language || 'en', overwrite: $('#scrape-overwrite').checked ? '1' : '0' });
|
||||
const query = new URLSearchParams({ mode: method, source: scrapeSource, kinds: kinds.join(','), region: $('input[name="scrape-region"]:checked')?.value || 'us', language: scraperSettings.language || 'en', overwrite: $('#scrape-overwrite').checked ? '1' : '0' });
|
||||
$('#scrape-start').disabled = true; $('#scrape-result').textContent = '';
|
||||
try {
|
||||
await api('/api/scraper/settings?' + query, { method: 'POST' });
|
||||
|
||||
+2
-2
@@ -25,11 +25,11 @@
|
||||
</section>
|
||||
<section id="media" class="page media-page" hidden aria-labelledby="media-heading">
|
||||
<section class="panel media-setup"><div class="media-head"><div><h2 id="media-heading" class="visually-hidden">Download media</h2><p>Box art, screenshots and more for your games, kept on your PS5 and shared by RetroArch, EmulationStation and this page. Nothing you already have is downloaded again.</p></div></div>
|
||||
<div class="media-row"><span class="media-label">Download from</span><div class="segmented" role="radiogroup" aria-label="Download from"><label><input type="radio" name="scrape-method" value="ps5"><span>PS5</span></label><label><input type="radio" name="scrape-method" value="pc"><span>This PC → PS5</span></label></div><p id="scrape-method-hint" class="media-hint"></p></div>
|
||||
<div class="media-row"><span class="media-label">Download from</span><div class="segmented" role="radiogroup" aria-label="Download from"><label><input type="radio" name="scrape-method" value="ps5"><span data-icon="ps5">PS5</span></label><label><input type="radio" name="scrape-method" value="pc"><span data-icon="pc-to-ps5">This PC → PS5</span></label></div><p id="scrape-method-hint" class="media-hint"></p></div>
|
||||
<div class="media-row"><span class="media-label">Source</span><div id="scrape-sources" class="chips" role="radiogroup" aria-label="Media source"></div><p id="scrape-source-hint" class="media-hint"></p></div>
|
||||
<div class="media-row media-row-kinds"><span class="media-label">Media</span><div class="kinds-area"><div class="chips quick-chips"><button type="button" id="kinds-recommended" class="chip">Recommended</button><button type="button" id="kinds-all" class="chip">All available</button><button type="button" id="kinds-none" class="chip">None</button></div><div id="scrape-kinds" class="kind-tiles"></div><p id="scrape-kind-hint" class="media-hint" aria-live="polite"></p></div></div>
|
||||
<div class="media-row"><span class="media-label">Games</span><div class="segmented" role="radiogroup" aria-label="Games"><label><input type="radio" name="scrape-scope" value="selected"><span id="scrape-scope-selected">Selected</span></label><label><input type="radio" name="scrape-scope" value="system"><span>One system</span></label><label><input type="radio" name="scrape-scope" value="all" checked><span>Everything</span></label></div><select id="scrape-system" aria-label="System" hidden></select></div>
|
||||
<div class="media-row"><span class="media-label">Options</span><div class="media-options"><label class="compact-select">Region <select id="scrape-region"><option value="us">USA</option><option value="eu">Europe</option><option value="jp">Japan</option><option value="wor">World</option></select></label><label class="switch"><input id="scrape-overwrite" type="checkbox"><span></span>Replace what’s already there</label></div></div>
|
||||
<div class="media-row"><span class="media-label">Options</span><div class="media-options"><div class="region-pick"><span>Region</span><div class="segmented" role="radiogroup" aria-label="Preferred region"><label><input type="radio" name="scrape-region" value="us"><span data-icon="flag-us">USA</span></label><label><input type="radio" name="scrape-region" value="eu"><span data-icon="flag-eu">Europe</span></label><label><input type="radio" name="scrape-region" value="jp"><span data-icon="flag-jp">Japan</span></label><label><input type="radio" name="scrape-region" value="wor"><span data-icon="globe">World</span></label></div></div><label class="switch"><input id="scrape-overwrite" type="checkbox"><span></span>Replace what’s already there</label></div></div>
|
||||
<div class="media-bar"><p id="scrape-summary" role="status"></p><span id="scrape-result" role="status" class="inline-error"></span><button id="scrape-start" class="primary">Start</button></div></section>
|
||||
<section id="scrape-job" class="panel scrape-job" hidden aria-labelledby="job-title"><div class="job-head"><div><h2 id="job-title">Downloading media</h2><p id="job-summary" role="status"></p></div><div class="job-actions"><button id="job-resume" class="secondary" hidden>Resume</button><button id="job-cancel" class="secondary">Cancel</button></div></div>
|
||||
<div class="job-stats"><div class="stat"><strong id="stat-done">0</strong><span>complete</span></div><div class="stat"><strong id="stat-partial">0</strong><span>partly</span></div><div class="stat"><strong id="stat-kept">0</strong><span>already had media</span></div><div class="stat attention"><strong id="stat-attention">0</strong><span>need you</span></div><div class="stat"><strong id="stat-files">0</strong><span id="stat-files-label">files</span></div></div>
|
||||
|
||||
Reference in new issue
Block a user