Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:38:08 +02:00
commit c737a37f71
531 files changed
+119270

No files matched your search

+268
View File
@@ -0,0 +1,268 @@
// ── Connection state ──────────────────────────────────────────────────────────
function setConnected(on) {
state.connected = on;
const dot = $('conn-dot');
const label = $('conn-label');
const badge = $('dash-conn-badge');
const shellHealth = $('chimera-shell-health');
const shellHealthLabel = $('chimera-shell-health-label');
const tr = (key, fallback) => typeof window.t === 'function' ? window.t(key, fallback) : fallback;
dot.className = `dot ${on ? 'green' : 'red'}`;
label.textContent = on ? tr('connection.connected', 'Connected') : tr('connection.disconnected', 'Disconnected');
badge.textContent = on ? tr('connection.online', 'Online') : tr('connection.offline', 'Offline');
badge.className = `badge ${on ? 'badge-green' : 'badge-red'}`;
if (shellHealth) {
shellHealth.classList.toggle('is-online', on);
shellHealth.classList.toggle('is-offline', !on);
}
if (shellHealthLabel) {
shellHealthLabel.textContent = on
? tr('connection.ready', 'Ready')
: tr('connection.disconnected', 'Disconnected');
shellHealthLabel.dataset.i18n = on ? 'connection.ready' : 'connection.disconnected';
}
$('btn-connect').disabled = on;
$('btn-disconnect').disabled = !on;
$('btn-scan').disabled = !on;
// Re-evaluate PS5 action buttons — only enabled when connected AND a location is stored
const deleteBtn = $('btn-manage-delete-ps5');
const backupBtn = $('btn-manage-backup-ps5');
if (deleteBtn) deleteBtn.disabled = !on || !state.currentGame?.ftp_path;
if (backupBtn) backupBtn.disabled = !on || !state.currentGame?.ftp_path;
}
// ── Dashboard ─────────────────────────────────────────────────────────────────
let lastHomeReadiness = null;
async function loadDashboard() {
try {
const [settings, stats, catalog, ftpStatus] = await Promise.all([
window.pork.getSettings(),
window.pork.getStats(),
window.pork.gameCatalogHealth().catch(() => ({})),
window.pork.ftpStatus().catch(() => ({ connected: state.connected })),
]);
$('s-total').textContent = stats.totalGames;
$('s-installed').textContent = stats.installed;
$('s-backedup').textContent = stats.backedUp;
$('s-backups').textContent = stats.totalBackups;
if ($('d-host') && document.activeElement !== $('d-host') && !$('d-host').value) {
$('d-host').value = settings.ftpHost || '';
}
if ($('d-ports') && document.activeElement !== $('d-ports') && !$('d-ports').value) {
$('d-ports').value = (settings.ftpPorts && settings.ftpPorts.length) ? settings.ftpPorts.join(', ') : '1337, 2121, 21';
}
renderHomeReadiness({ settings, stats, catalog, connected: Boolean(ftpStatus.connected) });
} catch (_) {}
renderPinnedTiles();
}
function renderHomeReadiness({ settings = {}, stats, catalog, connected }) {
lastHomeReadiness = { settings, stats, catalog, connected };
const tr = (key, fallback) => typeof window.t === 'function' ? window.t(key, fallback) : fallback;
const setIndicator = (id, stateName) => {
const indicator = $(id);
if (indicator) indicator.className = `chimera-readiness-dot ${stateName}`;
};
const complete = Number(catalog.complete || 0);
const missing = Number(catalog.missing || catalog.incomplete || 0);
const total = Number(stats.totalGames || 0);
const backedUp = Number(stats.backedUp || 0);
const host = String(settings.ftpHost || '').trim();
const hostConfigured = Boolean(host);
const consoleStatus = $('home-console-status');
const libraryStatus = $('home-library-status');
const backupStatus = $('home-backup-status');
const inspectorState = $('home-inspector-state');
const inspectorReason = $('home-inspector-reason');
const inspectorTarget = $('home-inspector-target');
const libraryTruth = $('home-truth-library');
const protectionTruth = $('home-truth-protection');
if (consoleStatus) consoleStatus.textContent = connected
? tr('home.consoleReady', 'Connected · manual operations')
: hostConfigured
? tr('home.targetConfiguredOffline', 'Configured · offline')
: tr('navigation.unavailable', 'Not configured');
if (libraryStatus) libraryStatus.textContent = total
? `${complete}/${total} ${tr('home.titlesEnriched', 'titles fully enriched')}${missing ? ` · ${missing} ${tr('home.toComplete', 'to complete')}` : ''}`
: tr('home.noGames', 'No games detected yet');
if (backupStatus) backupStatus.textContent = total
? `${backedUp}/${total} ${tr('home.gamesProtected', 'games protected')}`
: `${Number(stats.totalBackups || 0)} ${tr('home.backupFiles', 'backup files')}`;
if (inspectorState) {
inspectorState.textContent = connected
? tr('connection.ready', 'Ready')
: hostConfigured
? tr('home.targetOffline', 'Offline')
: tr('navigation.unavailable', 'Not configured');
inspectorState.className = `chimera-node-state ${connected ? 'ok' : hostConfigured ? 'attention' : 'unknown'}`;
}
if (inspectorReason) inspectorReason.textContent = connected
? tr('home.targetReasonConnected', 'A bounded FTP session with the PS5 is active.')
: hostConfigured
? tr('home.targetReasonOffline', 'The PS5 target is saved, but there is no active FTP session.')
: tr('home.targetReasonMissing', 'Chimera needs an explicit PS5 target before console features can connect.');
if (inspectorTarget) inspectorTarget.textContent = hostConfigured ? host : tr('navigation.unavailable', 'Not configured');
if (libraryTruth) libraryTruth.textContent = total
? `${complete}/${total} ${tr('home.titlesEnriched', 'titles fully enriched')}`
: tr('home.noGames', 'No games detected yet');
if (protectionTruth) protectionTruth.textContent = total
? `${backedUp}/${total} ${tr('home.gamesProtected', 'games protected')}`
: `${Number(stats.totalBackups || 0)} ${tr('home.backupFiles', 'backup files')}`;
setIndicator('home-console-indicator', connected ? 'ok' : hostConfigured ? 'attention' : 'unknown');
setIndicator('home-library-indicator', total && missing === 0 ? 'ok' : total ? 'attention' : 'unknown');
setIndicator('home-backup-indicator', total && backedUp >= total ? 'ok' : backedUp ? 'attention' : 'unknown');
const title = $('home-next-title');
const detail = $('home-next-detail');
const action = $('home-next-action');
if (!title || !detail || !action) return;
if (!hostConfigured) {
title.textContent = tr('home.next.configureTitle', 'Configure the PS5 target');
detail.textContent = tr('home.next.configureDetail', 'Choose the console target yourself. Chimera will not guess an address or start a connection automatically.');
action.textContent = tr('home.next.configureAction', 'Configure PS5 target');
action.dataset.route = 'settings/general';
action.dataset.focusTarget = 's-host';
} else if (!connected) {
title.textContent = tr('home.next.connectTitle', 'Connect safely to the PS5 first');
detail.textContent = tr('home.next.connectDetail', 'The target is configured. Review it and start the bounded FTP connection manually.');
action.textContent = tr('home.next.connectAction', 'Review connection');
action.dataset.route = 'home';
action.dataset.focusTarget = 'd-host';
} else if (!total) {
title.textContent = tr('home.next.scanTitle', 'Detect your game library');
detail.textContent = tr('home.next.scanDetail', 'The PS5 is reachable, but the Chimera catalog does not contain any games yet.');
action.textContent = tr('home.next.scanAction', 'Scan games');
action.dataset.route = 'library/games';
delete action.dataset.focusTarget;
} else if (missing > 0) {
title.textContent = `${tr('home.next.completeMetadata', 'Complete metadata for')} ${missing} ${missing === 1 ? tr('home.game', 'game') : tr('home.games', 'games')}`;
detail.textContent = tr('home.next.metadataDetail', 'Titles, cover art and version information are enriched automatically; review only persistent exceptions.');
action.textContent = tr('home.next.openLibrary', 'Open library');
action.dataset.route = 'library/games';
delete action.dataset.focusTarget;
} else if (backedUp < total) {
const remaining = total - backedUp;
title.textContent = `${remaining} ${remaining === 1 ? tr('home.game', 'game') : tr('home.games', 'games')} ${tr('home.next.notProtected', 'not protected yet')}`;
detail.textContent = tr('home.next.backupDetail', 'Review backups and create a new copy only from a confirmed source.');
action.textContent = tr('home.next.checkBackups', 'Check backups');
action.dataset.route = 'protection/backups';
delete action.dataset.focusTarget;
} else {
title.textContent = tr('home.next.readyTitle', 'Chimera is ready');
detail.textContent = tr('home.next.readyDetail', 'Console, metadata and library protection are in order. Updates remain explicit and reviewable.');
action.textContent = tr('home.next.reviewPayloads', 'Review payload updates');
action.dataset.route = 'payloads/updates';
delete action.dataset.focusTarget;
}
}
window.addEventListener('chimera:language-changed', () => {
if (lastHomeReadiness) renderHomeReadiness(lastHomeReadiness);
});
async function renderPinnedTiles() {
const grid = $('dash-pinned-grid');
const empty = $('dash-pinned-empty');
if (!grid) return;
try {
const s = await window.pork.getSettings();
const pinned = new Set(s.pinnedCards || []);
const cards = PINNABLE_CARDS.filter(c => pinned.has(c.id));
grid.innerHTML = '';
if (!cards.length) {
empty.hidden = false;
} else {
empty.hidden = true;
for (const c of cards) {
const tile = document.createElement('div');
tile.className = 'dash-pinned-tile';
tile.dataset.navPage = c.page;
if (c.type === 'widget') {
tile.classList.add('dash-pinned-tile--widget');
tile.removeAttribute('data-nav-page');
tile.innerHTML = _renderWidgetTile(c);
grid.appendChild(tile);
_initWidgetTile(c, tile);
continue; // already appended
} else {
tile.innerHTML = `
<div class="dash-pinned-tile-title">${escHtml(c.label)}</div>
<div class="dash-pinned-tile-page">${escHtml(_PIN_PAGE_LABELS[c.page] || c.page)}</div>
<div class="dash-pinned-tile-hint">${escHtml(c.hint)}</div>
<button class="btn btn-sm dash-pinned-tile-open">Open &rarr;</button>`;
}
grid.appendChild(tile);
}
}
// Sync all pin button visual states across the whole page
document.querySelectorAll('.btn-pin').forEach(btn => {
btn.classList.toggle('pinned', pinned.has(btn.dataset.pinId));
});
} catch (_) {}
}
// ── FTP Connect ───────────────────────────────────────────────────────────────
$('btn-connect').addEventListener('click', async () => {
const host = $('d-host').value.trim();
const ports = $('d-ports').value.split(',').map(p => p.trim()).filter(Boolean);
if (!ports.length) ports.push('21');
if (!host) { setStatus('Enter a host address', 'error'); return; }
// Credential fields start blank. Fall back to stored settings when blank
// so the user can click Connect without re-typing every session.
const saved = await window.pork.getSettings();
const user = $('d-user').value.trim() || saved.ftpUser || '';
const password = $('d-pass').value || saved.ftpPass || '';
$('btn-connect').disabled = true;
setStatus('Connecting…');
try {
const result = await window.pork.ftpConnect({ host, ports, user, password });
setConnected(true);
setStatus('Connected', 'ok');
$('sb-sync').textContent = `Connected on :${result?.port || ports[0]} at ${new Date().toLocaleTimeString()}`;
} catch (e) {
setConnected(false);
setStatus(`Connection failed: ${e.message}`, 'error');
$('btn-connect').disabled = false;
}
});
$('btn-disconnect').addEventListener('click', async () => {
await window.pork.ftpDisconnect();
setConnected(false);
setStatus('Disconnected');
});
// ── FTP Scan ──────────────────────────────────────────────────────────────────
$('btn-scan').addEventListener('click', async () => {
const prog = $('scan-progress');
prog.hidden = false;
prog.textContent = 'Scanning PS5 game folders…';
$('btn-scan').disabled = true;
window.pork.on('ftp:progress', ({ current, game }) => {
prog.textContent = `[${current}] Found: ${game.title || game.game_id}`;
});
try {
const { count } = await window.pork.ftpScan();
prog.textContent = `Scan complete — ${count} game(s) found.`;
$('sb-sync').textContent = `Last sync: ${new Date().toLocaleTimeString()}`;
setStatus(`Scanned ${count} games`, 'ok');
await loadDashboard();
} catch (e) {
prog.textContent = `Error: ${e.message}`;
setStatus(e.message, 'error');
} finally {
window.pork.off('ftp:progress');
$('btn-scan').disabled = false;
}
});