/** * pc-live | Software Store App */ const StoreApp = (() => { const WIN_ID = 'app-store'; let _apps = []; let _appsMap = {}; // app_id -> app (for dependency lookups) let _filter = { category: 'all', search: '', installedOnly: false }; // Category definitions โ€“ icon + label. // Order here determines sidebar order. const CATEGORY_META = { all: { label: 'All Apps', icon: '๐Ÿ—‚' }, system: { label: 'System', icon: '๐Ÿ–ฅ' }, utility: { label: 'Utility', icon: '๐Ÿ”ง' }, communication: { label: 'Communication', icon: '๐Ÿ“ก' }, security: { label: 'Security', icon: '๐Ÿ”' }, business: { label: 'Business', icon: '๐Ÿ’ผ' }, government: { label: 'Government', icon: '๐Ÿ›' }, entertainment: { label: 'Entertainment', icon: '๐ŸŽฎ' }, darknet: { label: 'Darknet', icon: '๐ŸŒ‘' }, }; /* โ”€โ”€ Helpers โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ // Only show categories that actually have apps in the current list function getActiveCategories() { const present = new Set(['all']); for (const app of _apps) { if (app.category) present.add(app.category); } return Object.keys(CATEGORY_META).filter(c => present.has(c)); } function filtered() { let list = _apps; if (_filter.installedOnly) list = list.filter(a => a.installed); if (_filter.category !== 'all') list = list.filter(a => a.category === _filter.category); if (_filter.search) { const q = _filter.search.toLowerCase(); list = list.filter(a => (a.name || '').toLowerCase().includes(q) || (a.description || '').toLowerCase().includes(q) ); } return list; } /* โ”€โ”€ Render โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ function renderSidebar() { const cats = getActiveCategories(); return `
${cats.map(c => { const m = CATEGORY_META[c] || { icon: '๐Ÿ“‚', label: c }; return ` `; }).join('')}
`; } function renderCard(app) { const icon = app.icon || '๐Ÿ“ฆ'; const priceLabel = app.price > 0 ? `$${app.price.toLocaleString()}` : 'Free'; const priceClass = app.price > 0 ? '' : 'free'; const catMeta = CATEGORY_META[app.category] || { icon: '๐Ÿ“‚' }; // Resolve missing dependencies const missingDeps = (app.dependencies || []) .filter(depId => { const d = _appsMap[depId]; return !d || !d.installed; }) .map(depId => { const d = _appsMap[depId]; return d ? d.name : depId; }); // Action buttons let action = ''; if (app.installed) { if (app.update_available) { action = ``; } else { action = ``; } if (!app.default) { action += ``; } } else { const locked = missingDeps.length > 0; action = ``; } // Info badges let badges = `${catMeta.icon} ${app.category || 'other'}`; badges += `v${app.version || '?'}`; if (app.installed) badges += `โœ“`; if (app.update_available) badges += `โ†‘ Update`; if (app.permissions?.job) badges += `๐Ÿ”‘ ${app.permissions.job}`; if (missingDeps.length) badges += `โš  Needs: ${missingDeps.join(', ')}`; return `
${icon}
${app.name || app.app_id}
${app.description || ''}
`; } function _refresh() { const body = WindowManager.getBody(WIN_ID); if (!body) return; const list = filtered(); const instCount = _apps.filter(a => a.installed).length; const totalCount = _apps.length; body.innerHTML = `
${renderSidebar()}
${list.length === 0 ? `
๐Ÿ“ฆ

No apps found

` : `
${list.map(renderCard).join('')}
` }
๐Ÿ“ฆ ${instCount} installed  ยท  ${totalCount} available
`; } /* โ”€โ”€ Filter actions โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ function _setCategory(cat) { _filter.category = cat; _refresh(); } function _setSearch(q) { _filter.search = q; _refresh(); } function _toggleInstalled() { _filter.installedOnly = !_filter.installedOnly; _refresh(); } /* โ”€โ”€ Install / Uninstall โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ function _install(appId) { fetchNui('installApp', { appId }); } function _uninstall(appId) { fetchNui('uninstallApp', { appId }); } /* โ”€โ”€ NUI event โ€“ store data received โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ function onStoreData(apps) { _apps = apps || []; _appsMap = {}; for (const a of _apps) _appsMap[a.app_id] = a; _refresh(); } /* โ”€โ”€ Open โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ */ function open() { const created = WindowManager.create({ id: WIN_ID, title: 'Software Store', icon: '๐Ÿ›’', width: 860, height: 560, content: '', }); if (created) { _apps = []; _appsMap = {}; _filter = { category: 'all', search: '', installedOnly: false }; _refresh(); fetchNui('getStore', {}); } } return { open, _setCategory, _setSearch, _toggleInstalled, _install, _uninstall, onStoreData }; })();