mirror of
https://github.com/ChuckBuilds/LEDMatrix.git
synced 2026-04-11 21:33:00 +00:00
Compare commits
3 Commits
main
...
88c4faa9ac
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
88c4faa9ac | ||
|
|
4c2c7c0d17 | ||
|
|
34a55b5a55 |
@@ -83,6 +83,9 @@
|
|||||||
[data-theme="dark"] .hover\:bg-gray-200:hover { background-color: #4b5563; }
|
[data-theme="dark"] .hover\:bg-gray-200:hover { background-color: #4b5563; }
|
||||||
[data-theme="dark"] .hover\:text-gray-700:hover { color: #e5e7eb; }
|
[data-theme="dark"] .hover\:text-gray-700:hover { color: #e5e7eb; }
|
||||||
[data-theme="dark"] .hover\:border-gray-300:hover { border-color: #6b7280; }
|
[data-theme="dark"] .hover\:border-gray-300:hover { border-color: #6b7280; }
|
||||||
|
[data-theme="dark"] .bg-red-100 { background-color: #450a0a; }
|
||||||
|
[data-theme="dark"] .text-red-700 { color: #fca5a5; }
|
||||||
|
[data-theme="dark"] .hover\:bg-red-200:hover { background-color: #7f1d1d; }
|
||||||
|
|
||||||
/* Base styles */
|
/* Base styles */
|
||||||
* {
|
* {
|
||||||
@@ -663,6 +666,39 @@ button.bg-white {
|
|||||||
color: var(--color-purple-text);
|
color: var(--color-purple-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Filter Pill Toggle States */
|
||||||
|
.filter-pill {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-pill[data-active="true"] {
|
||||||
|
background-color: var(--color-info-bg);
|
||||||
|
border-color: var(--color-info);
|
||||||
|
color: var(--color-info);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.filter-pill[data-active="true"]:hover {
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Category Filter Pills */
|
||||||
|
.category-filter-pill {
|
||||||
|
cursor: pointer;
|
||||||
|
user-select: none;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-filter-pill[data-active="true"] {
|
||||||
|
background-color: var(--color-info-bg);
|
||||||
|
border-color: var(--color-info);
|
||||||
|
color: var(--color-info);
|
||||||
|
font-weight: 600;
|
||||||
|
box-shadow: 0 0 0 1px var(--color-info);
|
||||||
|
}
|
||||||
|
|
||||||
/* Section Headers with Subtle Gradients */
|
/* Section Headers with Subtle Gradients */
|
||||||
.section-header {
|
.section-header {
|
||||||
background: linear-gradient(135deg, rgb(255 255 255 / 90%) 0%, rgb(249 250 251 / 90%) 100%);
|
background: linear-gradient(135deg, rgb(255 255 255 / 90%) 0%, rgb(249 250 251 / 90%) 100%);
|
||||||
|
|||||||
@@ -863,6 +863,40 @@ window.currentPluginConfig = null;
|
|||||||
let currentOnDemandPluginId = null;
|
let currentOnDemandPluginId = null;
|
||||||
let hasLoadedOnDemandStatus = false;
|
let hasLoadedOnDemandStatus = false;
|
||||||
|
|
||||||
|
// Store filter/sort state
|
||||||
|
const storeFilterState = {
|
||||||
|
sort: localStorage.getItem('storeSort') || 'a-z',
|
||||||
|
filterVerified: false,
|
||||||
|
filterNew: false,
|
||||||
|
filterInstalled: null, // null = all, true = installed only, false = not installed only
|
||||||
|
filterAuthors: [],
|
||||||
|
filterCategories: [],
|
||||||
|
|
||||||
|
persist() {
|
||||||
|
localStorage.setItem('storeSort', this.sort);
|
||||||
|
},
|
||||||
|
|
||||||
|
reset() {
|
||||||
|
this.sort = 'a-z';
|
||||||
|
this.filterVerified = false;
|
||||||
|
this.filterNew = false;
|
||||||
|
this.filterInstalled = null;
|
||||||
|
this.filterAuthors = [];
|
||||||
|
this.filterCategories = [];
|
||||||
|
this.persist();
|
||||||
|
},
|
||||||
|
|
||||||
|
activeCount() {
|
||||||
|
let count = 0;
|
||||||
|
if (this.filterVerified) count++;
|
||||||
|
if (this.filterNew) count++;
|
||||||
|
if (this.filterInstalled !== null) count++;
|
||||||
|
count += this.filterAuthors.length;
|
||||||
|
count += this.filterCategories.length;
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Shared on-demand status store (mirrors Alpine store when available)
|
// Shared on-demand status store (mirrors Alpine store when available)
|
||||||
window.__onDemandStore = window.__onDemandStore || {
|
window.__onDemandStore = window.__onDemandStore || {
|
||||||
loading: true,
|
loading: true,
|
||||||
@@ -1105,13 +1139,15 @@ function initializePluginPageWhenReady() {
|
|||||||
document.body.addEventListener('htmx:afterSwap', function(event) {
|
document.body.addEventListener('htmx:afterSwap', function(event) {
|
||||||
const target = event.detail.target;
|
const target = event.detail.target;
|
||||||
// Check if plugins content was swapped in
|
// Check if plugins content was swapped in
|
||||||
if (target.id === 'plugins-content' ||
|
if (target.id === 'plugins-content' ||
|
||||||
target.querySelector('#installed-plugins-grid') ||
|
target.querySelector('[data-plugins-loaded]')) {
|
||||||
document.getElementById('installed-plugins-grid')) {
|
|
||||||
console.log('HTMX swap detected for plugins, initializing...');
|
console.log('HTMX swap detected for plugins, initializing...');
|
||||||
// Reset initialization flag to allow re-initialization after HTMX swap
|
// Reset initialization flags to allow re-initialization after HTMX swap
|
||||||
window.pluginManager.initialized = false;
|
window.pluginManager.initialized = false;
|
||||||
window.pluginManager.initializing = false;
|
window.pluginManager.initializing = false;
|
||||||
|
pluginsInitialized = false;
|
||||||
|
pluginLoadCache.data = null;
|
||||||
|
pluginLoadCache.promise = null;
|
||||||
initTimer = setTimeout(attemptInit, 100);
|
initTimer = setTimeout(attemptInit, 100);
|
||||||
}
|
}
|
||||||
}, { once: false }); // Allow multiple swaps
|
}, { once: false }); // Allow multiple swaps
|
||||||
@@ -1172,7 +1208,10 @@ function initializePlugins() {
|
|||||||
categorySelect._listenerSetup = true;
|
categorySelect._listenerSetup = true;
|
||||||
categorySelect.addEventListener('change', searchPluginStore);
|
categorySelect.addEventListener('change', searchPluginStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Setup store sort/filter controls
|
||||||
|
setupStoreFilterListeners();
|
||||||
|
|
||||||
// Setup GitHub installation handlers
|
// Setup GitHub installation handlers
|
||||||
console.log('[initializePlugins] About to call setupGitHubInstallHandlers...');
|
console.log('[initializePlugins] About to call setupGitHubInstallHandlers...');
|
||||||
if (typeof setupGitHubInstallHandlers === 'function') {
|
if (typeof setupGitHubInstallHandlers === 'function') {
|
||||||
@@ -1682,6 +1721,8 @@ function startOnDemandStatusPolling() {
|
|||||||
|
|
||||||
window.loadOnDemandStatus = loadOnDemandStatus;
|
window.loadOnDemandStatus = loadOnDemandStatus;
|
||||||
|
|
||||||
|
let updateAllRunning = false;
|
||||||
|
|
||||||
async function runUpdateAllPlugins() {
|
async function runUpdateAllPlugins() {
|
||||||
console.log('[runUpdateAllPlugins] Button clicked, checking for updates...');
|
console.log('[runUpdateAllPlugins] Button clicked, checking for updates...');
|
||||||
const button = document.getElementById('update-all-plugins-btn');
|
const button = document.getElementById('update-all-plugins-btn');
|
||||||
@@ -1691,7 +1732,7 @@ async function runUpdateAllPlugins() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (button.dataset.running === 'true') {
|
if (updateAllRunning) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1702,7 +1743,7 @@ async function runUpdateAllPlugins() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const originalContent = button.innerHTML;
|
const originalContent = button.innerHTML;
|
||||||
button.dataset.running = 'true';
|
updateAllRunning = true;
|
||||||
button.disabled = true;
|
button.disabled = true;
|
||||||
button.classList.add('opacity-60', 'cursor-wait');
|
button.classList.add('opacity-60', 'cursor-wait');
|
||||||
|
|
||||||
@@ -1712,7 +1753,11 @@ async function runUpdateAllPlugins() {
|
|||||||
for (let i = 0; i < plugins.length; i++) {
|
for (let i = 0; i < plugins.length; i++) {
|
||||||
const plugin = plugins[i];
|
const plugin = plugins[i];
|
||||||
const pluginId = plugin.id;
|
const pluginId = plugin.id;
|
||||||
button.innerHTML = `<i class="fas fa-sync fa-spin mr-2"></i>Updating ${i + 1}/${plugins.length}...`;
|
// Re-fetch button in case DOM was replaced by HTMX swap
|
||||||
|
const btn = document.getElementById('update-all-plugins-btn');
|
||||||
|
if (btn) {
|
||||||
|
btn.innerHTML = `<i class="fas fa-sync fa-spin mr-2"></i>Updating ${i + 1}/${plugins.length}...`;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/v3/plugins/update', {
|
const response = await fetch('/api/v3/plugins/update', {
|
||||||
@@ -1752,10 +1797,13 @@ async function runUpdateAllPlugins() {
|
|||||||
console.error('Bulk plugin update failed:', error);
|
console.error('Bulk plugin update failed:', error);
|
||||||
showNotification('Failed to update all plugins: ' + error.message, 'error');
|
showNotification('Failed to update all plugins: ' + error.message, 'error');
|
||||||
} finally {
|
} finally {
|
||||||
button.innerHTML = originalContent;
|
updateAllRunning = false;
|
||||||
button.disabled = false;
|
const btn = document.getElementById('update-all-plugins-btn');
|
||||||
button.classList.remove('opacity-60', 'cursor-wait');
|
if (btn) {
|
||||||
button.dataset.running = 'false';
|
btn.innerHTML = originalContent;
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.classList.remove('opacity-60', 'cursor-wait');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -5076,6 +5124,250 @@ function restartDisplay() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Store Filter/Sort Functions ---
|
||||||
|
|
||||||
|
function setupStoreFilterListeners() {
|
||||||
|
console.log('[FILTER SETUP] setupStoreFilterListeners() called');
|
||||||
|
// Sort dropdown
|
||||||
|
const sortSelect = document.getElementById('store-sort');
|
||||||
|
console.log('[FILTER SETUP] store-sort element:', !!sortSelect, 'listenerSetup:', sortSelect?._listenerSetup);
|
||||||
|
if (sortSelect && !sortSelect._listenerSetup) {
|
||||||
|
sortSelect._listenerSetup = true;
|
||||||
|
sortSelect.value = storeFilterState.sort;
|
||||||
|
sortSelect.addEventListener('change', () => {
|
||||||
|
console.log('[FILTER] Sort changed to:', sortSelect.value, 'cache:', !!pluginStoreCache);
|
||||||
|
storeFilterState.sort = sortSelect.value;
|
||||||
|
storeFilterState.persist();
|
||||||
|
applyStoreFiltersAndSort();
|
||||||
|
});
|
||||||
|
console.log('[FILTER SETUP] Sort listener attached');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verified filter toggle
|
||||||
|
const verifiedBtn = document.getElementById('filter-verified');
|
||||||
|
if (verifiedBtn && !verifiedBtn._listenerSetup) {
|
||||||
|
verifiedBtn._listenerSetup = true;
|
||||||
|
verifiedBtn.addEventListener('click', () => {
|
||||||
|
storeFilterState.filterVerified = !storeFilterState.filterVerified;
|
||||||
|
verifiedBtn.dataset.active = storeFilterState.filterVerified;
|
||||||
|
applyStoreFiltersAndSort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// New filter toggle
|
||||||
|
const newBtn = document.getElementById('filter-new');
|
||||||
|
if (newBtn && !newBtn._listenerSetup) {
|
||||||
|
newBtn._listenerSetup = true;
|
||||||
|
newBtn.addEventListener('click', () => {
|
||||||
|
storeFilterState.filterNew = !storeFilterState.filterNew;
|
||||||
|
newBtn.dataset.active = storeFilterState.filterNew;
|
||||||
|
applyStoreFiltersAndSort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Installed filter (cycles: All -> Installed -> Not Installed -> All)
|
||||||
|
const installedBtn = document.getElementById('filter-installed');
|
||||||
|
if (installedBtn && !installedBtn._listenerSetup) {
|
||||||
|
installedBtn._listenerSetup = true;
|
||||||
|
installedBtn.addEventListener('click', () => {
|
||||||
|
const states = [null, true, false];
|
||||||
|
const labels = ['All', 'Installed', 'Not Installed'];
|
||||||
|
const icons = ['fa-download', 'fa-check', 'fa-times'];
|
||||||
|
const current = states.indexOf(storeFilterState.filterInstalled);
|
||||||
|
const next = (current + 1) % states.length;
|
||||||
|
storeFilterState.filterInstalled = states[next];
|
||||||
|
installedBtn.querySelector('span').textContent = labels[next];
|
||||||
|
installedBtn.querySelector('i').className = `fas ${icons[next]} mr-1`;
|
||||||
|
installedBtn.dataset.active = String(states[next] !== null);
|
||||||
|
applyStoreFiltersAndSort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Author dropdown
|
||||||
|
const authorSelect = document.getElementById('filter-author');
|
||||||
|
if (authorSelect && !authorSelect._listenerSetup) {
|
||||||
|
authorSelect._listenerSetup = true;
|
||||||
|
authorSelect.addEventListener('change', () => {
|
||||||
|
storeFilterState.filterAuthors = authorSelect.value
|
||||||
|
? [authorSelect.value] : [];
|
||||||
|
applyStoreFiltersAndSort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tag pills (event delegation on container)
|
||||||
|
// Category pills (event delegation on container)
|
||||||
|
const catsPills = document.getElementById('filter-categories-pills');
|
||||||
|
console.log('[FILTER SETUP] filter-categories-pills element:', catsPills, 'listenerSetup:', catsPills?._listenerSetup);
|
||||||
|
if (catsPills && !catsPills._listenerSetup) {
|
||||||
|
catsPills._listenerSetup = true;
|
||||||
|
catsPills.addEventListener('click', (e) => {
|
||||||
|
console.log('[FILTER] Category pill click, target:', e.target, 'closest:', e.target.closest('.category-filter-pill'));
|
||||||
|
const pill = e.target.closest('.category-filter-pill');
|
||||||
|
if (!pill) return;
|
||||||
|
const cat = pill.dataset.category;
|
||||||
|
const idx = storeFilterState.filterCategories.indexOf(cat);
|
||||||
|
if (idx >= 0) {
|
||||||
|
storeFilterState.filterCategories.splice(idx, 1);
|
||||||
|
pill.dataset.active = 'false';
|
||||||
|
} else {
|
||||||
|
storeFilterState.filterCategories.push(cat);
|
||||||
|
pill.dataset.active = 'true';
|
||||||
|
}
|
||||||
|
console.log('[FILTER] Categories now:', storeFilterState.filterCategories, 'cache size:', pluginStoreCache?.length);
|
||||||
|
applyStoreFiltersAndSort();
|
||||||
|
});
|
||||||
|
console.log('[FILTER SETUP] Category pill listener attached');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear filters button
|
||||||
|
const clearBtn = document.getElementById('clear-filters-btn');
|
||||||
|
if (clearBtn && !clearBtn._listenerSetup) {
|
||||||
|
clearBtn._listenerSetup = true;
|
||||||
|
clearBtn.addEventListener('click', () => {
|
||||||
|
storeFilterState.reset();
|
||||||
|
// Reset all UI elements
|
||||||
|
const sort = document.getElementById('store-sort');
|
||||||
|
if (sort) sort.value = 'a-z';
|
||||||
|
const vBtn = document.getElementById('filter-verified');
|
||||||
|
if (vBtn) vBtn.dataset.active = 'false';
|
||||||
|
const nBtn = document.getElementById('filter-new');
|
||||||
|
if (nBtn) nBtn.dataset.active = 'false';
|
||||||
|
const iBtn = document.getElementById('filter-installed');
|
||||||
|
if (iBtn) {
|
||||||
|
iBtn.dataset.active = 'false';
|
||||||
|
const span = iBtn.querySelector('span');
|
||||||
|
if (span) span.textContent = 'All';
|
||||||
|
const icon = iBtn.querySelector('i');
|
||||||
|
if (icon) icon.className = 'fas fa-download mr-1';
|
||||||
|
}
|
||||||
|
const auth = document.getElementById('filter-author');
|
||||||
|
if (auth) auth.value = '';
|
||||||
|
document.querySelectorAll('.category-filter-pill').forEach(p => {
|
||||||
|
p.dataset.active = 'false';
|
||||||
|
});
|
||||||
|
applyStoreFiltersAndSort();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyStoreFiltersAndSort() {
|
||||||
|
console.log('[FILTER] applyStoreFiltersAndSort called, cache:', pluginStoreCache?.length, 'state:', JSON.stringify(storeFilterState));
|
||||||
|
if (!pluginStoreCache) { console.log('[FILTER] No cache, returning'); return; }
|
||||||
|
|
||||||
|
let plugins = [...pluginStoreCache];
|
||||||
|
const installedIds = new Set(
|
||||||
|
(window.installedPlugins || []).map(p => p.id)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Apply filters
|
||||||
|
if (storeFilterState.filterVerified) {
|
||||||
|
plugins = plugins.filter(p => p.verified);
|
||||||
|
}
|
||||||
|
if (storeFilterState.filterNew) {
|
||||||
|
plugins = plugins.filter(p => isNewPlugin(p.last_updated));
|
||||||
|
}
|
||||||
|
if (storeFilterState.filterInstalled === true) {
|
||||||
|
plugins = plugins.filter(p => installedIds.has(p.id));
|
||||||
|
} else if (storeFilterState.filterInstalled === false) {
|
||||||
|
plugins = plugins.filter(p => !installedIds.has(p.id));
|
||||||
|
}
|
||||||
|
if (storeFilterState.filterAuthors.length > 0) {
|
||||||
|
const authorSet = new Set(storeFilterState.filterAuthors);
|
||||||
|
plugins = plugins.filter(p => authorSet.has(p.author));
|
||||||
|
}
|
||||||
|
if (storeFilterState.filterCategories.length > 0) {
|
||||||
|
const catSet = new Set(storeFilterState.filterCategories);
|
||||||
|
plugins = plugins.filter(p => catSet.has(p.category));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply sort
|
||||||
|
switch (storeFilterState.sort) {
|
||||||
|
case 'a-z':
|
||||||
|
plugins.sort((a, b) =>
|
||||||
|
(a.name || a.id).localeCompare(b.name || b.id));
|
||||||
|
break;
|
||||||
|
case 'z-a':
|
||||||
|
plugins.sort((a, b) =>
|
||||||
|
(b.name || b.id).localeCompare(a.name || a.id));
|
||||||
|
break;
|
||||||
|
case 'verified':
|
||||||
|
plugins.sort((a, b) =>
|
||||||
|
(b.verified ? 1 : 0) - (a.verified ? 1 : 0) ||
|
||||||
|
(a.name || a.id).localeCompare(b.name || b.id));
|
||||||
|
break;
|
||||||
|
case 'newest':
|
||||||
|
plugins.sort((a, b) => {
|
||||||
|
// Prefer static per-plugin last_updated over GitHub pushed_at (which is repo-wide)
|
||||||
|
const dateA = a.last_updated || a.last_updated_iso || '';
|
||||||
|
const dateB = b.last_updated || b.last_updated_iso || '';
|
||||||
|
return dateB.localeCompare(dateA);
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'category':
|
||||||
|
plugins.sort((a, b) =>
|
||||||
|
(a.category || '').localeCompare(b.category || '') ||
|
||||||
|
(a.name || a.id).localeCompare(b.name || b.id));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderPluginStore(plugins);
|
||||||
|
|
||||||
|
// Update result count
|
||||||
|
const countEl = document.getElementById('store-count');
|
||||||
|
if (countEl) {
|
||||||
|
const total = pluginStoreCache.length;
|
||||||
|
const shown = plugins.length;
|
||||||
|
countEl.innerHTML = shown < total
|
||||||
|
? `${shown} of ${total} shown`
|
||||||
|
: `${total} available`;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateFilterCountBadge();
|
||||||
|
}
|
||||||
|
|
||||||
|
function populateFilterControls() {
|
||||||
|
if (!pluginStoreCache) return;
|
||||||
|
|
||||||
|
// Collect unique authors
|
||||||
|
const authors = [...new Set(
|
||||||
|
pluginStoreCache.map(p => p.author).filter(Boolean)
|
||||||
|
)].sort();
|
||||||
|
|
||||||
|
const authorSelect = document.getElementById('filter-author');
|
||||||
|
if (authorSelect) {
|
||||||
|
const currentVal = authorSelect.value;
|
||||||
|
authorSelect.innerHTML = '<option value="">All Authors</option>' +
|
||||||
|
authors.map(a => `<option value="${escapeHtml(a)}">${escapeHtml(a)}</option>`).join('');
|
||||||
|
authorSelect.value = currentVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect unique categories sorted alphabetically
|
||||||
|
const categories = [...new Set(
|
||||||
|
pluginStoreCache.map(p => p.category).filter(Boolean)
|
||||||
|
)].sort();
|
||||||
|
|
||||||
|
const catsContainer = document.getElementById('filter-categories-container');
|
||||||
|
const catsPills = document.getElementById('filter-categories-pills');
|
||||||
|
if (catsContainer && catsPills && categories.length > 0) {
|
||||||
|
catsContainer.classList.remove('hidden');
|
||||||
|
catsPills.innerHTML = categories.map(cat =>
|
||||||
|
`<button class="category-filter-pill badge badge-info cursor-pointer" data-category="${escapeHtml(cat)}" data-active="${storeFilterState.filterCategories.includes(cat)}">${escapeHtml(cat)}</button>`
|
||||||
|
).join('');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateFilterCountBadge() {
|
||||||
|
const count = storeFilterState.activeCount();
|
||||||
|
const clearBtn = document.getElementById('clear-filters-btn');
|
||||||
|
const badge = document.getElementById('filter-count-badge');
|
||||||
|
if (clearBtn) {
|
||||||
|
clearBtn.classList.toggle('hidden', count === 0);
|
||||||
|
}
|
||||||
|
if (badge) {
|
||||||
|
badge.textContent = count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function searchPluginStore(fetchCommitInfo = true) {
|
function searchPluginStore(fetchCommitInfo = true) {
|
||||||
pluginLog('[STORE] Searching plugin store...', { fetchCommitInfo });
|
pluginLog('[STORE] Searching plugin store...', { fetchCommitInfo });
|
||||||
|
|
||||||
@@ -5100,15 +5392,7 @@ function searchPluginStore(fetchCommitInfo = true) {
|
|||||||
console.error('plugin-store-grid element not found, cannot render cached plugins');
|
console.error('plugin-store-grid element not found, cannot render cached plugins');
|
||||||
// Don't return, let it fetch fresh data
|
// Don't return, let it fetch fresh data
|
||||||
} else {
|
} else {
|
||||||
renderPluginStore(pluginStoreCache);
|
applyStoreFiltersAndSort();
|
||||||
try {
|
|
||||||
const countEl = document.getElementById('store-count');
|
|
||||||
if (countEl) {
|
|
||||||
countEl.innerHTML = `${pluginStoreCache.length} available`;
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn('Could not update store count:', e);
|
|
||||||
}
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5158,8 +5442,9 @@ function searchPluginStore(fetchCommitInfo = true) {
|
|||||||
pluginStoreCache = plugins;
|
pluginStoreCache = plugins;
|
||||||
cacheTimestamp = Date.now();
|
cacheTimestamp = Date.now();
|
||||||
console.log('Cached plugin store data');
|
console.log('Cached plugin store data');
|
||||||
|
populateFilterControls();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure plugin store grid exists before rendering
|
// Ensure plugin store grid exists before rendering
|
||||||
const storeGrid = document.getElementById('plugin-store-grid');
|
const storeGrid = document.getElementById('plugin-store-grid');
|
||||||
if (!storeGrid) {
|
if (!storeGrid) {
|
||||||
@@ -5168,17 +5453,20 @@ function searchPluginStore(fetchCommitInfo = true) {
|
|||||||
window.__pendingStorePlugins = plugins;
|
window.__pendingStorePlugins = plugins;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderPluginStore(plugins);
|
|
||||||
|
|
||||||
// Update count - safely check element exists
|
// Route through filter/sort pipeline if cache is available, otherwise render directly
|
||||||
try {
|
if (pluginStoreCache) {
|
||||||
const countEl = document.getElementById('store-count');
|
applyStoreFiltersAndSort();
|
||||||
if (countEl) {
|
} else {
|
||||||
countEl.innerHTML = `${plugins.length} available`;
|
renderPluginStore(plugins);
|
||||||
|
try {
|
||||||
|
const countEl = document.getElementById('store-count');
|
||||||
|
if (countEl) {
|
||||||
|
countEl.innerHTML = `${plugins.length} available`;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not update store count:', e);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
|
||||||
console.warn('Could not update store count:', e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure GitHub token collapse handler is attached after store is rendered
|
// Ensure GitHub token collapse handler is attached after store is rendered
|
||||||
@@ -5279,7 +5567,17 @@ function renderPluginStore(plugins) {
|
|||||||
return JSON.stringify(text || '');
|
return JSON.stringify(text || '');
|
||||||
};
|
};
|
||||||
|
|
||||||
container.innerHTML = plugins.map(plugin => `
|
// Build installed lookup for badges
|
||||||
|
const installedMap = new Map();
|
||||||
|
(window.installedPlugins || []).forEach(p => {
|
||||||
|
installedMap.set(p.id, p.version || '');
|
||||||
|
});
|
||||||
|
|
||||||
|
container.innerHTML = plugins.map(plugin => {
|
||||||
|
const isInstalled = installedMap.has(plugin.id);
|
||||||
|
const installedVersion = installedMap.get(plugin.id);
|
||||||
|
const hasUpdate = isInstalled && plugin.version && installedVersion && plugin.version !== installedVersion;
|
||||||
|
return `
|
||||||
<div class="plugin-card">
|
<div class="plugin-card">
|
||||||
<div class="flex items-start justify-between mb-4">
|
<div class="flex items-start justify-between mb-4">
|
||||||
<div class="flex-1 min-w-0">
|
<div class="flex-1 min-w-0">
|
||||||
@@ -5287,6 +5585,8 @@ function renderPluginStore(plugins) {
|
|||||||
<h4 class="font-semibold text-gray-900 text-base">${escapeHtml(plugin.name || plugin.id)}</h4>
|
<h4 class="font-semibold text-gray-900 text-base">${escapeHtml(plugin.name || plugin.id)}</h4>
|
||||||
${plugin.verified ? '<span class="badge badge-success"><i class="fas fa-check-circle mr-1"></i>Verified</span>' : ''}
|
${plugin.verified ? '<span class="badge badge-success"><i class="fas fa-check-circle mr-1"></i>Verified</span>' : ''}
|
||||||
${isNewPlugin(plugin.last_updated) ? '<span class="badge badge-info"><i class="fas fa-sparkles mr-1"></i>New</span>' : ''}
|
${isNewPlugin(plugin.last_updated) ? '<span class="badge badge-info"><i class="fas fa-sparkles mr-1"></i>New</span>' : ''}
|
||||||
|
${isInstalled ? '<span class="badge badge-success"><i class="fas fa-check mr-1"></i>Installed</span>' : ''}
|
||||||
|
${hasUpdate ? '<span class="badge badge-warning"><i class="fas fa-arrow-up mr-1"></i>Update</span>' : ''}
|
||||||
${plugin._source === 'custom_repository' ? `<span class="badge badge-accent" title="From: ${escapeHtml(plugin._repository_name || plugin._repository_url || 'Custom Repository')}"><i class="fas fa-bookmark mr-1"></i>Custom</span>` : ''}
|
${plugin._source === 'custom_repository' ? `<span class="badge badge-accent" title="From: ${escapeHtml(plugin._repository_name || plugin._repository_url || 'Custom Repository')}"><i class="fas fa-bookmark mr-1"></i>Custom</span>` : ''}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm text-gray-600 space-y-1.5 mb-3">
|
<div class="text-sm text-gray-600 space-y-1.5 mb-3">
|
||||||
@@ -5325,7 +5625,8 @@ function renderPluginStore(plugins) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`).join('');
|
`;
|
||||||
|
}).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expose functions to window for onclick handlers
|
// Expose functions to window for onclick handlers
|
||||||
|
|||||||
@@ -165,6 +165,68 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Sort & Filter Controls -->
|
||||||
|
<div id="store-filter-bar" class="mb-4 space-y-3">
|
||||||
|
<!-- Row 1: Sort + Quick Filters -->
|
||||||
|
<div class="flex flex-wrap items-center gap-3">
|
||||||
|
<!-- Sort Dropdown -->
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<label for="store-sort" class="text-sm font-medium text-gray-700 whitespace-nowrap">
|
||||||
|
<i class="fas fa-sort mr-1"></i>Sort:
|
||||||
|
</label>
|
||||||
|
<select id="store-sort" class="text-sm px-3 py-1.5 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option value="a-z">A → Z</option>
|
||||||
|
<option value="z-a">Z → A</option>
|
||||||
|
<option value="verified">Verified First</option>
|
||||||
|
<option value="newest">Recently Updated</option>
|
||||||
|
<option value="category">Category</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<div class="h-6 w-px bg-gray-300"></div>
|
||||||
|
|
||||||
|
<!-- Quick Filter Toggles -->
|
||||||
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
|
<span class="text-sm font-medium text-gray-700">Filter:</span>
|
||||||
|
<button id="filter-verified" type="button" class="filter-pill text-xs px-3 py-1.5 rounded-full border border-gray-300 bg-white hover:bg-gray-50 transition-colors" data-active="false">
|
||||||
|
<i class="fas fa-check-circle mr-1"></i>Verified
|
||||||
|
</button>
|
||||||
|
<button id="filter-new" type="button" class="filter-pill text-xs px-3 py-1.5 rounded-full border border-gray-300 bg-white hover:bg-gray-50 transition-colors" data-active="false">
|
||||||
|
<i class="fas fa-sparkles mr-1"></i>New
|
||||||
|
</button>
|
||||||
|
<button id="filter-installed" type="button" class="filter-pill text-xs px-3 py-1.5 rounded-full border border-gray-300 bg-white hover:bg-gray-50 transition-colors" data-active="false">
|
||||||
|
<i class="fas fa-download mr-1"></i><span>All</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Divider -->
|
||||||
|
<div class="h-6 w-px bg-gray-300"></div>
|
||||||
|
|
||||||
|
<!-- Author Dropdown -->
|
||||||
|
<select id="filter-author" class="text-sm px-3 py-1.5 border border-gray-300 rounded-lg shadow-sm focus:ring-blue-500 focus:border-blue-500">
|
||||||
|
<option value="">All Authors</option>
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<!-- Clear Filters + Badge -->
|
||||||
|
<button id="clear-filters-btn" type="button" class="hidden text-xs px-3 py-1.5 rounded-full bg-red-100 text-red-700 hover:bg-red-200 transition-colors font-medium">
|
||||||
|
<i class="fas fa-times mr-1"></i>Clear Filters
|
||||||
|
<span id="filter-count-badge" class="ml-1 inline-flex items-center justify-center bg-red-600 text-white rounded-full w-5 h-5 text-xs font-bold">0</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Row 2: Category Pills (populated dynamically) -->
|
||||||
|
<div id="filter-categories-container" class="hidden">
|
||||||
|
<div class="flex items-center gap-2 flex-wrap">
|
||||||
|
<span class="text-xs font-medium text-gray-600 whitespace-nowrap">Categories:</span>
|
||||||
|
<div id="filter-categories-pills" class="flex flex-wrap gap-1.5">
|
||||||
|
<!-- Dynamically populated category pills -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="plugin-store-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6">
|
<div id="plugin-store-grid" class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-6">
|
||||||
<!-- Loading skeleton -->
|
<!-- Loading skeleton -->
|
||||||
<div class="store-loading col-span-full">
|
<div class="store-loading col-span-full">
|
||||||
|
|||||||
Reference in New Issue
Block a user