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

30 lines
3.2 KiB
JavaScript

'use strict';
(function () {
let catalog = { sets: [], stats: {} };
const esc = value => String(value ?? '').replace(/[&<>"']/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[char]);
const tr = (key, fallback) => window.t?.(key, fallback) || fallback;
if (!document.querySelector('link[data-package-library-style]')) {
const link = document.createElement('link');
Object.assign(link, { rel: 'stylesheet', href: 'styles/package-library.css' });
link.dataset.packageLibraryStyle = 'true';
document.head.appendChild(link);
}
const componentHtml = component => `<li><div><strong>${esc(component.type)}</strong><span>${esc(component.componentId)} ${tr(`packageLibrary.${component.required ? 'required' : 'optional'}`, component.required ? 'required' : 'optional')}</span></div><div><span>v${esc(component.version || '?')} ${esc(component.region || '')}</span><code title="SHA-256 ${esc(component.sha256)}">${esc(component.sha256)}</code></div></li>`;
const recipeHtml = item => `<li><strong>${esc(item.label || item.recipeKey)}</strong><span>v${esc(item.version)} ${esc(item.state)} ${esc(item.recipeSha256.slice(0, 12))}...</span></li>`;
function setHtml(set) {
const recipes = set.recipes.length ? set.recipes.map(recipeHtml).join('') : `<li class="package-library-empty">${esc(tr('packageLibrary.noRecipes', 'No recipes yet'))}</li>`;
return `<article class="package-library-set"><header><div><span>${esc(set.title.externalTitleId)}</span><h3>${esc(set.title.name)}</h3></div><div><strong>${esc(set.label || set.setKey)}</strong><small>${esc(set.platform)} ${esc(set.region || '')} ${esc(set.confidence)} ${esc(set.source)}</small></div></header><div class="package-library-columns"><section><h4>${esc(tr('packageLibrary.components', 'Components'))} (${set.components.length})</h4><ul class="package-component-list">${set.components.map(componentHtml).join('')}</ul></section><section><h4>${esc(tr('packageLibrary.recipes', 'Recipes'))} (${set.recipes.length})</h4><ul class="package-recipe-list">${recipes}</ul></section></div></article>`;
}
function render() {
const host = document.getElementById('package-library-content');
if (!host) return;
const query = document.getElementById('package-library-search')?.value.trim().toLowerCase();
const sets = catalog.sets.filter(set => !query || JSON.stringify(set).toLowerCase().includes(query));
host.innerHTML = sets.length ? `<div class="package-library-list">${sets.map(setHtml).join('')}</div>` : `<div class="operations-empty"><strong>${esc(tr('packageLibrary.empty', 'No package sets match'))}</strong><span>${esc(tr('packageLibrary.emptyDetail', 'Import or adjust the search. Package inventory never implies installation.'))}</span></div>`;
const stats = document.getElementById('package-library-stats');
if (stats) stats.textContent = [['titles', 'titles'], ['sets', 'sets'], ['components', 'componentsLower'], ['recipes', 'recipesLower']].map(([field, key]) => `${catalog.stats[field] || 0} ${tr(`packageLibrary.${key}`, field)}`).join(' / ');
}
window.loadPackages = async () => { catalog = await window.pork.packageCatalog(); render(); };
document.getElementById('package-library-search')?.addEventListener('input', render);
})();