30 lines
3.2 KiB
JavaScript
30 lines
3.2 KiB
JavaScript
'use strict';
|
|
(function () {
|
|
let catalog = { sets: [], stats: {} };
|
|
const esc = value => String(value ?? '').replace(/[&<>"']/g, char => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[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);
|
|
})();
|