Files
chimera-Public/renderer/modules/hotkeys.js
T

104 lines
5.5 KiB
JavaScript

// ── System View hotkeys ────────────────────────────────────────────────────────
document.addEventListener('keydown', e => {
// Escape always exits in-app fullscreen, regardless of active page
if (e.key === 'Escape' && $('sv-viewer-wrap')?.classList.contains('sv-viewer-wrap--fullscreen')) {
e.preventDefault();
svToggleFullscreen();
return;
}
if (!$('page-system-view')?.classList.contains('active')) return;
const tag = document.activeElement?.tagName;
if (tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT') return;
const k = e.key.toLowerCase();
if (k === _svHotkeys.mute) { e.preventDefault(); $('btn-sv-mute').click(); }
else if (k === _svHotkeys.gif) { e.preventDefault(); $('btn-sv-record').click(); }
else if (k === _svHotkeys.video) { e.preventDefault(); $('btn-sv-vid-rec').click(); }
else if (k === _svHotkeys.fullscreen) { e.preventDefault(); svToggleFullscreen(); }
else if (k === _svHotkeys.popout) { e.preventDefault(); $('btn-sv-popout').click(); }
});
// Auto-scan toast on launch
window.pork.on('local:scan:complete', async ({ total, added }) => {
if (added > 0) {
showToast(`Auto-scan: found ${added} new game${added !== 1 ? 's' : ''} (${total} total) — fetching metadata…`, 'ok');
} else if (total > 0) {
showToast(`Auto-scan: ${total} game${total !== 1 ? 's' : ''} already tracked`, 'info');
}
// Always refresh the active page so pruned/stale game rows clear from the UI
// even when 0 games were found this scan.
const activePage = document.querySelector('.page.active')?.id;
if (activePage === 'page-dashboard') loadDashboard();
if (activePage === 'page-games') loadGames();
if (activePage === 'page-backups') loadBackups();
});
// Toast when Prospero background queue finishes
window.pork.on('prospero:queue:done', ({ fetched, failed }) => {
if (fetched > 0 && failed === 0) {
showToast(`Metadata fetched for ${fetched} game${fetched !== 1 ? 's' : ''}`, 'ok');
} else if (fetched > 0) {
showToast(`Metadata fetched for ${fetched} game${fetched !== 1 ? 's' : ''} (${failed} failed)`, 'info');
} else if (failed > 0) {
showToast(`Metadata fetch failed for ${failed} game${failed !== 1 ? 's' : ''}`, 'error', 8000);
}
});
// Live-refresh modal when background fetch completes
window.pork.on('prospero:updated', async ({ game_id, data }) => {
// Reload games list in background so future opens have fresh data
state.games = await window.pork.listGames({});
renderGames(); // Redraw table so icon/name/metadata show without requiring a page nav
// Update cheat icon map and any visible cheat card for this game
if (data.iconUrl) {
_cheatIconMap.set(game_id, data.iconUrl);
document.querySelectorAll(`.cheat-card[data-cusa="${game_id}"]`).forEach(card => {
const empty = card.querySelector('.cheat-card-icon-empty');
if (!empty) return;
const img = document.createElement('img');
img.className = 'cheat-card-icon';
img.alt = '';
img.src = data.iconUrl;
img.onerror = () => { img.style.display = 'none'; };
empty.replaceWith(img);
});
}
// If the modal is open for this game, refresh it
if (state.currentGame?.game_id === game_id) {
const updated = state.games.find(g => g.game_id === game_id);
if (updated) {
state.currentGame = updated;
$('modal-title').textContent = updated.prospero_name || updated.title || game_id;
$('modal-subtitle').textContent = `${updated.game_id} · ${updated.prospero_region || (updated.version ? 'v' + updated.version : '')}`;
// Refresh info table with new metadata
const patches = tryParseJson(updated.prospero_patches, []);
const fwVersions = [...new Set(patches.map(p => p.requiredFirmware).filter(Boolean))];
const dlc = tryParseJson(updated.prospero_dlc, []);
const infoRows = [
['Game ID', updated.game_id],
['Content ID', updated.content_id || updated.prospero_content_id || '—'],
['Version', updated.version || updated.prospero_version || '—'],
['Size', updated.backup_size ? fmt(updated.backup_size) : (updated.size ? fmt(updated.size) : (updated.prospero_size || '—'))],
['Installed', updated.installed ? 'Yes' : 'No'],
['Backed Up', updated.backed_up ? 'Yes' : 'No'],
['Prepared for firmware', updated.porked_firmware ? `${updated.porked_firmware}${updated.porked_at ? ' (' + new Date(updated.porked_at).toLocaleDateString() + ')' : ''}` : '—'],
['Publisher', updated.prospero_publisher || '—'],
['Publisher ID', updated.prospero_publisher_id || '—'],
['Region', updated.prospero_region || '—'],
['Last Updated', updated.prospero_last_updated || '—'],
['Required Firmware', fwVersions.length ? fwVersions.join(', ') : '—'],
['DLC Available', dlc.length ? `${dlc.length} item(s)` : 'None'],
['FTP Path', updated.ftp_path || '—'],
['Last Scanned', updated.last_scanned ? new Date(updated.last_scanned).toLocaleString() : '—'],
['Metadata Age', updated.prospero_fetched_at ? fmtAge(Date.now() - updated.prospero_fetched_at) : 'Never fetched'],
];
$('modal-info').innerHTML = infoRows.map(([k, v]) => `<tr><th>${escHtml(k)}</th><td>${escHtml(v)}</td></tr>`).join('');
renderProsperoData(updated);
}
}
});