assets: unified tile render, empty-filter placeholder
* Orphaned items now use mediaTile() instead of a hand-written triple-
ternary, so audio and PDF files render with the same icon/player tile
as linked items. Eliminates the code duplication that caused audio
files to fall through to the <img> path in the orphaned section.
* applyFilter() tracks visibleCount after toggling item display. When
the active filter hides every item (e.g. "Audio" tab with no audio
files) but total assets exist, a type-specific placeholder is shown:
matching icon (ph-image / ph-music-note / ph-file-pdf) + "no {type}
assets" text. Hidden again on the next filter switch that has results.
* Section header hide logic narrowed to [data-section]:not(.gallery-item)
so it only targets the header divs, not the items themselves.
chore: bump version to 0.9.19
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
bf81d046e6
commit
c04eac06c4
2 files changed
+30
-11
No files matched your search
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
define('APP_NAME', 'hyprcrd');
|
define('APP_NAME', 'hyprcrd');
|
||||||
define('APP_VERSION', '0.9.18');
|
define('APP_VERSION', '0.9.19');
|
||||||
|
|
||||||
// Database path — writable directory, outside webroot
|
// Database path — writable directory, outside webroot
|
||||||
define('DB_PATH', __DIR__ . '/data/hyprcrd.db');
|
define('DB_PATH', __DIR__ . '/data/hyprcrd.db');
|
||||||
|
|||||||
@@ -118,12 +118,35 @@ export async function openGallery() {
|
|||||||
document.querySelectorAll('#gallery-tabs .gallery-tab').forEach(t => {
|
document.querySelectorAll('#gallery-tabs .gallery-tab').forEach(t => {
|
||||||
t.classList.toggle('active', t.dataset.filter === filter);
|
t.classList.toggle('active', t.dataset.filter === filter);
|
||||||
});
|
});
|
||||||
|
let visibleCount = 0;
|
||||||
grid.querySelectorAll('.gallery-item').forEach(item => {
|
grid.querySelectorAll('.gallery-item').forEach(item => {
|
||||||
const url = item.dataset.url || '';
|
const url = item.dataset.url || '';
|
||||||
const type = fileType(url);
|
const type = fileType(url);
|
||||||
const show = filter === 'all' || type === filter;
|
const show = filter === 'all' || type === filter;
|
||||||
item.style.display = show ? '' : 'none';
|
item.style.display = show ? '' : 'none';
|
||||||
|
if (show) visibleCount++;
|
||||||
});
|
});
|
||||||
|
// Hide section headers when no items in that section are visible
|
||||||
|
grid.querySelectorAll('[data-section]:not(.gallery-item)').forEach(hdr => {
|
||||||
|
const section = hdr.dataset.section;
|
||||||
|
const hasVisible = !!grid.querySelector(`.gallery-item[data-section="${section}"]:not([style*="display: none"])`);
|
||||||
|
hdr.style.display = hasVisible ? '' : 'none';
|
||||||
|
});
|
||||||
|
// Show/hide the empty-filter placeholder
|
||||||
|
let placeholder = grid.querySelector('.gallery-filter-empty');
|
||||||
|
if (visibleCount === 0 && total > 0) {
|
||||||
|
if (!placeholder) {
|
||||||
|
placeholder = document.createElement('div');
|
||||||
|
placeholder.className = 'gallery-filter-empty col-span-full text-center py-16';
|
||||||
|
const icons = { image: 'ph-image', audio: 'ph-music-note', pdf: 'ph-file-pdf' };
|
||||||
|
const icon = icons[filter] || 'ph-folder-open';
|
||||||
|
placeholder.innerHTML = `<i class="ph ${icon} text-4xl text-t-dim mb-2 block"></i><p class="font-pixel text-[10px] text-t-dim">no ${filter} assets</p>`;
|
||||||
|
grid.appendChild(placeholder);
|
||||||
|
}
|
||||||
|
placeholder.style.display = '';
|
||||||
|
} else if (placeholder) {
|
||||||
|
placeholder.style.display = 'none';
|
||||||
|
}
|
||||||
};
|
};
|
||||||
document.querySelectorAll('#gallery-tabs .gallery-tab').forEach(t => {
|
document.querySelectorAll('#gallery-tabs .gallery-tab').forEach(t => {
|
||||||
t.onclick = () => applyFilter(t.dataset.filter);
|
t.onclick = () => applyFilter(t.dataset.filter);
|
||||||
@@ -137,15 +160,11 @@ export async function openGallery() {
|
|||||||
let html = '';
|
let html = '';
|
||||||
|
|
||||||
if (orphaned.length) {
|
if (orphaned.length) {
|
||||||
html += `<div class="col-span-full font-pixel text-[10px] text-t-danger flex items-center gap-1.5 pt-2"><i class="ph ph-warning text-xs"></i>orphaned (not used by any card)</div>`;
|
html += `<div class="col-span-full font-pixel text-[10px] text-t-danger flex items-center gap-1.5 pt-2" data-section="orphaned"><i class="ph ph-warning text-xs"></i>orphaned (not used by any card)</div>`;
|
||||||
html += orphaned.map(f => `
|
html += orphaned.map(f => `
|
||||||
<div class="gallery-item group cursor-pointer" data-orphan="${f.name}" data-url="${esc(f.url)}">
|
<div class="gallery-item group cursor-pointer" data-orphan="${f.name}" data-url="${esc(f.url)}" data-section="orphaned">
|
||||||
<div class="relative aspect-square bg-t-bg border border-t-danger/30 rounded overflow-visible">
|
<div class="relative">
|
||||||
${fileType(f.url) === 'pdf'
|
${mediaTile(f.url, 'border-t-danger/30 opacity-60')}
|
||||||
? `<div class="w-full h-full flex flex-col items-center justify-center gap-1 opacity-60"><i class="ph ph-file-pdf text-3xl text-t-danger"></i><span class="font-pixel text-[8px] text-t-dim">${esc(f.name)}</span></div>`
|
|
||||||
: fileType(f.url) === 'audio'
|
|
||||||
? `<div class="w-full h-full flex items-center justify-center opacity-60"><i class="ph ph-music-note text-3xl text-t-dim"></i></div>`
|
|
||||||
: `<img src="${f.url}" class="w-full h-full object-cover opacity-60 rounded" loading="lazy">`}
|
|
||||||
<div style="position:absolute;top:4px;right:4px;display:flex;gap:4px;" class="opacity-0 group-hover:opacity-100 transition-opacity">
|
<div style="position:absolute;top:4px;right:4px;display:flex;gap:4px;" class="opacity-0 group-hover:opacity-100 transition-opacity">
|
||||||
<button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-accent rounded border-0 cursor-pointer hover:bg-t-accent hover:text-t-bg transition-colors" data-new-orphan="${f.name}" title="new card"><i class="ph ph-plus text-sm"></i></button>
|
<button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-accent rounded border-0 cursor-pointer hover:bg-t-accent hover:text-t-bg transition-colors" data-new-orphan="${f.name}" title="new card"><i class="ph ph-plus text-sm"></i></button>
|
||||||
<button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-accent rounded border-0 cursor-pointer hover:bg-t-accent hover:text-t-bg transition-colors" data-new-slide="${f.name}" title="new slide (background)"><i class="ph ph-image-square text-sm"></i></button>
|
<button class="w-6 h-6 flex items-center justify-center bg-black/80 text-t-accent rounded border-0 cursor-pointer hover:bg-t-accent hover:text-t-bg transition-colors" data-new-slide="${f.name}" title="new slide (background)"><i class="ph ph-image-square text-sm"></i></button>
|
||||||
@@ -157,9 +176,9 @@ export async function openGallery() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (linked.length) {
|
if (linked.length) {
|
||||||
if (orphaned.length) html += `<div class="col-span-full font-pixel text-[10px] text-t-muted flex items-center gap-1.5 pt-3"><i class="ph ph-link text-xs"></i>linked to cards</div>`;
|
if (orphaned.length) html += `<div class="col-span-full font-pixel text-[10px] text-t-muted flex items-center gap-1.5 pt-3" data-section="linked"><i class="ph ph-link text-xs"></i>linked to cards</div>`;
|
||||||
html += linked.map(img => `
|
html += linked.map(img => `
|
||||||
<div class="gallery-item group relative cursor-pointer" data-card-id="${img.cardId}" data-gallery-origin="${encodeURIComponent(img.url)}" data-url="${esc(img.url)}">
|
<div class="gallery-item group relative cursor-pointer" data-card-id="${img.cardId}" data-gallery-origin="${encodeURIComponent(img.url)}" data-url="${esc(img.url)}" data-section="linked">
|
||||||
${mediaTile(img.url, 'border-t-border')}
|
${mediaTile(img.url, 'border-t-border')}
|
||||||
<div class="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity rounded flex flex-col justify-end p-2">
|
<div class="absolute inset-0 bg-black/60 opacity-0 group-hover:opacity-100 transition-opacity rounded flex flex-col justify-end p-2">
|
||||||
<div class="flex items-center gap-1.5">
|
<div class="flex items-center gap-1.5">
|
||||||
|
|||||||
Reference in new issue
Block a user