/* Atlus — Package Manager app */
(function () {
'use strict';
let container = null;
let searchInput = null;
let listEl = null;
let statusEl = null;
let searchTimeout = null;
let expandedPkg = null; // currently expanded package name
// ---- Search ----
async function searchPackages(query) {
if (!query || query.length < 2) {
listEl.innerHTML = '
Type at least 2 characters to search packages
';
return;
}
listEl.innerHTML = 'Searching…
';
try {
const res = await Atlus.apiFetch(`/api/packages/search?q=${encodeURIComponent(query)}`);
if (res.status === 503) {
listEl.innerHTML = 'Package manager not available on this system
';
return;
}
if (!res.ok) {
const err = await res.json().catch(() => ({ detail: `HTTP ${res.status}` }));
throw new Error(err.detail || `Search failed (${res.status})`);
}
const results = await res.json();
if (results.length === 0) {
listEl.innerHTML = 'No packages found
';
return;
}
renderResults(results);
updateStatus(`${results.length} result${results.length !== 1 ? 's' : ''}`);
} catch (e) {
listEl.innerHTML = `Error: ${e.message}
`;
}
}
// ---- Render results ----
function renderResults(results) {
listEl.innerHTML = '';
results.forEach(pkg => {
const row = document.createElement('div');
row.className = 'pkg-row';
row.dataset.name = pkg.name;
row.innerHTML = `
`;
const header = row.querySelector('.pkg-row-header');
const detail = row.querySelector('.pkg-detail');
header.addEventListener('click', () => {
if (expandedPkg === pkg.name) {
// Collapse
detail.classList.add('hidden');
row.classList.remove('expanded');
row.querySelector('.pkg-expand-icon').textContent = '▸';
expandedPkg = null;
} else {
// Collapse previous
if (expandedPkg) {
const prev = listEl.querySelector(`.pkg-row[data-name="${expandedPkg}"]`);
if (prev) {
prev.querySelector('.pkg-detail').classList.add('hidden');
prev.classList.remove('expanded');
prev.querySelector('.pkg-expand-icon').textContent = '▸';
}
}
// Expand this
expandedPkg = pkg.name;
row.classList.add('expanded');
row.querySelector('.pkg-expand-icon').textContent = '▾';
loadPackageDetail(pkg.name, detail);
}
});
listEl.appendChild(row);
});
}
// ---- Package detail ----
async function loadPackageDetail(name, detailEl) {
detailEl.classList.remove('hidden');
detailEl.innerHTML = 'Loading…
';
try {
const res = await Atlus.apiFetch(`/api/packages/info/${encodeURIComponent(name)}`);
if (!res.ok) throw new Error('Failed to load package info');
const info = await res.json();
renderDetail(info, detailEl);
} catch (e) {
detailEl.innerHTML = `Error: ${e.message}
`;
}
}
function renderDetail(info, detailEl) {
const installedBadge = info.installed
? `Installed ${info.installed_version || ''}`
: `Not installed`;
const sizeText = info.installed_size
? `${info.installed_size} KB installed`
: (info.size ? `${Atlus.formatBytes(parseInt(info.size))} download` : '');
detailEl.innerHTML = `
${info.description || 'No description available'}
${info.section ? `Section: ${info.section}` : ''}
${sizeText ? `Size: ${sizeText}` : ''}
${info.architecture ? `Arch: ${info.architecture}` : ''}
${info.depends ? `Depends: ${info.depends}
` : ''}
${info.homepage ? `Homepage: ${info.homepage}
` : ''}
`;
// Action button handler
const actionBtn = detailEl.querySelector('.pkg-action-btn');
if (actionBtn) {
actionBtn.addEventListener('click', (e) => {
e.stopPropagation();
const pkgName = actionBtn.dataset.name;
if (actionBtn.classList.contains('install')) {
installPackage(pkgName, actionBtn, detailEl);
} else {
removePackage(pkgName, actionBtn, detailEl);
}
});
}
}
// ---- Install / Remove ----
async function installPackage(name, btn, detailEl) {
btn.disabled = true;
btn.textContent = 'Installing…';
updateStatus(`Installing ${name}…`);
try {
const res = await Atlus.apiFetch('/api/packages/install', {
method: 'POST',
body: { name },
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.detail || 'Install failed');
}
updateStatus(`${name} installed successfully`);
// Reload detail to reflect new status
await loadPackageDetail(name, detailEl);
} catch (e) {
btn.disabled = false;
btn.textContent = 'Install';
updateStatus(`Error: ${e.message}`);
}
}
async function removePackage(name, btn, detailEl) {
if (!confirm(`Remove package "${name}"?`)) return;
btn.disabled = true;
btn.textContent = 'Removing…';
updateStatus(`Removing ${name}…`);
try {
const res = await Atlus.apiFetch('/api/packages/remove', {
method: 'POST',
body: { name },
});
if (!res.ok) {
const err = await res.json();
throw new Error(err.detail || 'Remove failed');
}
updateStatus(`${name} removed successfully`);
// Reload detail to reflect new status
await loadPackageDetail(name, detailEl);
} catch (e) {
btn.disabled = false;
btn.textContent = 'Remove';
updateStatus(`Error: ${e.message}`);
}
}
// ---- Refresh cache ----
async function refreshCache(btn) {
btn.disabled = true;
btn.textContent = 'Updating…';
updateStatus('Updating package cache…');
try {
const res = await Atlus.apiFetch('/api/packages/update-cache', {
method: 'POST',
});
if (!res.ok) throw new Error('Cache update failed');
updateStatus('Package cache updated');
} catch (e) {
updateStatus(`Error: ${e.message}`);
}
btn.disabled = false;
btn.textContent = 'Refresh Cache';
}
// ---- Status ----
function updateStatus(text) {
if (statusEl) statusEl.textContent = text;
}
// ---- App registration ----
Atlus.registerApp('packages', {
title: 'Packages',
init(el) {
container = el;
container.classList.add('app-packages');
// Toolbar
const toolbar = document.createElement('div');
toolbar.className = 'pkg-toolbar';
searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.className = 'pkg-search';
searchInput.placeholder = 'Search packages…';
searchInput.addEventListener('input', () => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(() => {
searchPackages(searchInput.value.trim());
}, 300);
});
toolbar.appendChild(searchInput);
const cacheBtn = document.createElement('button');
cacheBtn.className = 'pkg-cache-btn';
cacheBtn.textContent = 'Refresh Cache';
cacheBtn.addEventListener('click', () => refreshCache(cacheBtn));
toolbar.appendChild(cacheBtn);
container.appendChild(toolbar);
// Results list
listEl = document.createElement('div');
listEl.className = 'pkg-list';
listEl.innerHTML = 'Type at least 2 characters to search packages
';
container.appendChild(listEl);
// Status bar
statusEl = document.createElement('div');
statusEl.className = 'pkg-status';
statusEl.textContent = 'Ready';
container.appendChild(statusEl);
// Focus search
setTimeout(() => searchInput.focus(), 100);
},
destroy() {
clearTimeout(searchTimeout);
container = null;
searchInput = null;
listEl = null;
statusEl = null;
expandedPkg = null;
},
});
})();