78 lines
4.5 KiB
JavaScript
78 lines
4.5 KiB
JavaScript
(function installDestinationSelector() {
|
|
'use strict';
|
|
|
|
let options = [];
|
|
let requiredBytes = 0;
|
|
|
|
const esc = value => String(value == null ? '' : value).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, ''');
|
|
const bytes = value => value == null ? 'unknown' : value >= 1024 ** 3 ? `${(value / 1024 ** 3).toFixed(1)} GB` : value >= 1024 ** 2 ? `${(value / 1024 ** 2).toFixed(1)} MB` : `${Math.max(0, value)} B`;
|
|
const age = value => value == null ? 'unknown age' : value < 60_000 ? 'just now' : value < 3_600_000 ? `${Math.ceil(value / 60_000)} min old` : `${Math.ceil(value / 3_600_000)} h old`;
|
|
const reason = code => ({
|
|
'offline-volume': 'offline',
|
|
'install-capability-disabled': 'install disabled by adapter',
|
|
'missing-mountpoint': 'mountpoint unavailable',
|
|
'capacity-unknown': 'capacity unknown',
|
|
'insufficient-capacity': 'not enough free space',
|
|
'stale-capacity-telemetry': 'capacity telemetry stale',
|
|
'stale-capability-telemetry': 'capability telemetry stale',
|
|
'compatibility-incompatible': 'blocked by applicable compatibility evidence',
|
|
}[code] || code);
|
|
|
|
function selected() {
|
|
const select = document.getElementById('install-destination-volume');
|
|
return options.find(item => item.volumeId === select?.value) || null;
|
|
}
|
|
|
|
function expertOpen() {
|
|
return document.getElementById('install-expert-destination')?.open === true;
|
|
}
|
|
|
|
function renderStatus() {
|
|
const status = document.getElementById('install-destination-status');
|
|
const option = selected();
|
|
if (!status) return;
|
|
if (option) {
|
|
status.className = `install-destination-status ${option.eligible ? 'is-eligible' : 'is-ineligible'}`;
|
|
status.innerHTML = `<strong>${esc(option.eligible ? 'Eligible destination' : 'Destination unavailable')}</strong><span>Required ${esc(bytes(requiredBytes))} · free ${esc(bytes(option.freeBytes))} · projected ${esc(bytes(option.projectedFreeBytes))} · evidence ${esc(age(option.capacityAgeMs))}${option.reasons.length ? ` · ${esc(option.reasons.map(reason).join(', '))}` : ''}</span>`;
|
|
const raw = document.getElementById('install-ftp-path');
|
|
if (option.compatibility) status.insertAdjacentHTML('beforeend', window.chimeraCompatibilityGuidance?.markup?.(option.compatibility) || '');
|
|
if (raw && option.suggestedRemotePath) raw.value = option.suggestedRemotePath;
|
|
} else {
|
|
status.className = 'install-destination-status is-ineligible';
|
|
status.innerHTML = '<strong>No eligible semantic destination</strong><span>Refresh console storage evidence or use the Expert raw-path override.</span>';
|
|
}
|
|
if (typeof updateInstallConfirmState === 'function') updateInstallConfirmState('pfs');
|
|
}
|
|
|
|
async function load({ titleId, required = 0, legacyPath = '' } = {}) {
|
|
requiredBytes = Math.max(0, Number(required) || 0);
|
|
const packet = await window.pork.installDestinations({ titleId, requiredBytes });
|
|
options = Array.isArray(packet?.destinations) ? packet.destinations : [];
|
|
const select = document.getElementById('install-destination-volume');
|
|
const raw = document.getElementById('install-ftp-path');
|
|
if (raw) raw.value = legacyPath || '';
|
|
if (!select) return packet;
|
|
select.innerHTML = '<option value="">Select a destination volume</option>' + options.map(option => {
|
|
const suffix = option.eligible ? `${bytes(option.freeBytes)} free` : option.reasons.map(reason).join(', ');
|
|
return `<option value="${esc(option.volumeId)}"${option.eligible ? '' : ' disabled'}>${esc(option.deviceName)} · ${esc(option.volumeKey)} · ${esc(suffix)}</option>`;
|
|
}).join('');
|
|
const preferred = options.find(option => option.eligible);
|
|
select.value = preferred?.volumeId || '';
|
|
renderStatus();
|
|
return packet;
|
|
}
|
|
|
|
function selection() {
|
|
const option = selected();
|
|
if (option?.eligible) return { destinationVolumeId: option.volumeId, remotePath: option.suggestedRemotePath, expertRawPath: false };
|
|
const raw = document.getElementById('install-ftp-path')?.value?.trim() || '';
|
|
return expertOpen() && raw ? { destinationVolumeId: null, remotePath: raw, expertRawPath: true } : null;
|
|
}
|
|
|
|
document.getElementById('install-destination-volume')?.addEventListener('change', renderStatus);
|
|
document.getElementById('install-expert-destination')?.addEventListener('toggle', renderStatus);
|
|
document.getElementById('install-ftp-path')?.addEventListener('input', renderStatus);
|
|
|
|
window.chimeraInstallDestination = Object.freeze({ load, selection, isReady: () => Boolean(selection()) });
|
|
})();
|