diff --git a/modules/servers/VirtFusionDirect/hooks.php b/modules/servers/VirtFusionDirect/hooks.php index 5faf246..568ef3f 100644 --- a/modules/servers/VirtFusionDirect/hooks.php +++ b/modules/servers/VirtFusionDirect/hooks.php @@ -208,16 +208,40 @@ add_hook('ClientAreaFooterOutput', 1, function ($vars) { galleryContainer.style.marginTop = '8px'; if (osGalleryData.categories && osGalleryData.categories.length > 0) { - osGalleryData.categories.forEach(function(cat) { + osGalleryData.categories.forEach(function(cat, ci) { var section = document.createElement('div'); section.className = 'vf-os-category'; - var title = document.createElement('h5'); - title.className = 'vf-os-category-title'; - title.textContent = cat.name; - section.appendChild(title); + + var header = document.createElement('div'); + header.className = 'vf-os-category-header'; + var catColor = getBrandColor(cat.name); + + var catIcon = document.createElement('span'); + catIcon.className = 'vf-os-category-icon'; + catIcon.style.background = catColor; + catIcon.textContent = (cat.name || '?')[0].toUpperCase(); + + var catTitle = document.createElement('span'); + catTitle.textContent = cat.name + ' (' + cat.templates.length + ')'; + + var arrow = document.createElement('span'); + arrow.className = 'vf-os-category-arrow'; + arrow.textContent = ci === 0 ? '\u25BC' : '\u25B6'; + + header.appendChild(catIcon); + header.appendChild(catTitle); + header.appendChild(arrow); + section.appendChild(header); var grid = document.createElement('div'); grid.className = 'vf-os-grid'; + if (ci !== 0) grid.style.display = 'none'; + + header.addEventListener('click', function() { + var isOpen = grid.style.display !== 'none'; + grid.style.display = isOpen ? 'none' : ''; + arrow.textContent = isOpen ? '\u25B6' : '\u25BC'; + }); cat.templates.forEach(function(tpl) { var fullLabel = tpl.name + (tpl.version ? ' ' + tpl.version : '') + (tpl.variant ? ' ' + tpl.variant : ''); @@ -228,23 +252,10 @@ add_hook('ClientAreaFooterOutput', 1, function ($vars) { var iconDiv = document.createElement('div'); iconDiv.className = 'vf-os-icon'; - iconDiv.style.background = getBrandColor(cat.name || tpl.name); - if (tpl.icon && osGalleryData.baseUrl) { - var img = document.createElement('img'); - img.src = osGalleryData.baseUrl + '/storage/os/' + encodeURIComponent(tpl.icon); - img.alt = ''; - img.onerror = function() { - this.parentNode.textContent = ''; - var sp = document.createElement('span'); - sp.textContent = (tpl.name || '?')[0].toUpperCase(); - this.parentNode.appendChild(sp); - }; - iconDiv.appendChild(img); - } else { - var sp = document.createElement('span'); - sp.textContent = (tpl.name || '?')[0].toUpperCase(); - iconDiv.appendChild(sp); - } + iconDiv.style.background = catColor; + var sp = document.createElement('span'); + sp.textContent = (tpl.name || '?')[0].toUpperCase(); + iconDiv.appendChild(sp); card.appendChild(iconDiv); var labelDiv = document.createElement('div'); diff --git a/modules/servers/VirtFusionDirect/templates/css/module.css b/modules/servers/VirtFusionDirect/templates/css/module.css index e10692d..785994d 100644 --- a/modules/servers/VirtFusionDirect/templates/css/module.css +++ b/modules/servers/VirtFusionDirect/templates/css/module.css @@ -263,24 +263,49 @@ } /* OS Template Gallery */ -.vf-os-category-title { - text-transform: uppercase; +.vf-os-category-header { + display: flex; + align-items: center; + gap: 8px; + padding: 10px 12px; + margin-top: 6px; + border: 1px solid #dee2e6; + border-radius: 6px; + cursor: pointer; + font-weight: 700; + font-size: 0.9rem; + user-select: none; + transition: background 0.15s; +} +.vf-os-category:first-child .vf-os-category-header { + margin-top: 0; +} +.vf-os-category-header:hover { + background: rgba(0,0,0,0.03); +} +.vf-os-category-icon { + width: 28px; + height: 28px; + border-radius: 6px; + display: flex; + align-items: center; + justify-content: center; + color: #fff; font-weight: 700; font-size: 0.85rem; - letter-spacing: 0.5px; - border-bottom: 1px solid #dee2e6; - padding-bottom: 6px; - margin-top: 16px; - margin-bottom: 10px; + flex-shrink: 0; } -.vf-os-category:first-child .vf-os-category-title { - margin-top: 0; +.vf-os-category-arrow { + margin-left: auto; + font-size: 0.7rem; + color: #888; } .vf-os-grid { display: flex; flex-wrap: wrap; gap: 8px; - margin-bottom: 12px; + padding: 10px 0; + margin-bottom: 4px; } .vf-os-card { width: 120px; diff --git a/modules/servers/VirtFusionDirect/templates/js/module.js b/modules/servers/VirtFusionDirect/templates/js/module.js index 09f5c3b..b18dae8 100644 --- a/modules/servers/VirtFusionDirect/templates/js/module.js +++ b/modules/servers/VirtFusionDirect/templates/js/module.js @@ -317,32 +317,37 @@ function vfRenderOsGallery(container, data, hiddenInput) { return; } - var baseUrl = data.baseUrl || ""; - $.each(data.categories, function (ci, category) { var section = $('
').attr("data-category", ci); - var title = $('').text(category.name); - section.append(title); + var brandColor = vfGetBrandColor(category.name); + + // Accordion header + var header = $(''); + var iconSpan = $('').css("background", brandColor).text((category.name || "?")[0].toUpperCase()); + var titleSpan = $('').text(category.name + " (" + category.templates.length + ")"); + var arrow = $('' + (ci === 0 ? '▼' : '▶') + ''); + header.append(iconSpan).append(titleSpan).append(arrow); + section.append(header); + + // Collapsible grid — first category open by default var grid = $(''); + if (ci !== 0) grid.hide(); + + header.on("click", function () { + var isVisible = grid.is(":visible"); + grid.slideToggle(200); + arrow.html(isVisible ? '▶' : '▼'); + }); $.each(category.templates, function (ti, tpl) { var label = tpl.name + (tpl.version ? " " + tpl.version : "") + (tpl.variant ? " " + tpl.variant : ""); - var brandColor = vfGetBrandColor(category.name || tpl.name); var card = $('') .attr("data-id", tpl.id) .attr("data-search", label.toLowerCase()); if (tpl.eol) card.addClass("vf-os-card-eol"); var iconDiv = $('').css("background", brandColor); - if (tpl.icon && baseUrl) { - var img = $('