add thumbnails in frontend
This commit is contained in:
parent
8260cf5ae3
commit
ba0bf2d731
14
app.py
14
app.py
@ -100,6 +100,8 @@ def list_directory_contents(directory, subpath):
|
||||
music_exts = ('.mp3',)
|
||||
image_exts = ('.jpg', '.jpeg', '.png', '.gif', '.bmp')
|
||||
|
||||
blocked_filenames = ['Thumbs.db']
|
||||
|
||||
try:
|
||||
with os.scandir(directory) as it:
|
||||
# Sorting by name if required.
|
||||
@ -108,6 +110,10 @@ def list_directory_contents(directory, subpath):
|
||||
if entry.name.startswith('.'):
|
||||
continue
|
||||
|
||||
# Skip blocked_filenames
|
||||
if entry.name in blocked_filenames:
|
||||
continue
|
||||
|
||||
if entry.is_dir(follow_symlinks=False):
|
||||
if entry.name in ["Transkription", "@eaDir"]:
|
||||
continue
|
||||
@ -287,7 +293,7 @@ def serve_file(subpath):
|
||||
img = ImageOps.exif_transpose(orig)
|
||||
variants = {
|
||||
orig_key: (1920, 1920),
|
||||
small_key: ( 192, 192),
|
||||
small_key: ( 480, 480),
|
||||
}
|
||||
for key, size in variants.items():
|
||||
thumb = img.copy()
|
||||
@ -295,10 +301,8 @@ def serve_file(subpath):
|
||||
if thumb.mode in ("RGBA", "P"):
|
||||
thumb = thumb.convert("RGB")
|
||||
bio = io.BytesIO()
|
||||
save_kwargs = {'format': 'JPEG', 'quality': 85}
|
||||
if exif_bytes:
|
||||
save_kwargs['exif'] = exif_bytes
|
||||
thumb.save(bio, **save_kwargs)
|
||||
# don’t pass exif_bytes here
|
||||
thumb.save(bio, format='JPEG', quality=85)
|
||||
bio.seek(0)
|
||||
cache.set(key, bio, read=True)
|
||||
# Read back the variant we need
|
||||
|
||||
@ -237,3 +237,26 @@ footer {
|
||||
opacity: 0;
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.images-grid {
|
||||
display: grid;
|
||||
/* make each column exactly 150px wide */
|
||||
grid-template-columns: repeat(auto-fill, 150px);
|
||||
/* force every row exactly 150px tall */
|
||||
grid-auto-rows: 150px;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.image-item {
|
||||
/* ensure the thumbnail can’t overflow its cell */
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
/* fill the full cell */
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
/* crop/scale to cover without stretching */
|
||||
object-fit: cover;
|
||||
border-radius: 4px;
|
||||
}
|
||||
@ -34,8 +34,7 @@ function paintFile() {
|
||||
}
|
||||
|
||||
function renderContent(data) {
|
||||
|
||||
// Render breadcrumbs, directories (grid view when appropriate), and files.
|
||||
// Render breadcrumbs
|
||||
let breadcrumbHTML = '';
|
||||
data.breadcrumbs.forEach((crumb, index) => {
|
||||
breadcrumbHTML += `<a href="#" class="breadcrumb-link" data-path="${crumb.path}">${crumb.name}</a>`;
|
||||
@ -45,14 +44,32 @@ function renderContent(data) {
|
||||
});
|
||||
document.getElementById('breadcrumbs').innerHTML = breadcrumbHTML;
|
||||
|
||||
// Check for image-only directory (no subdirectories, at least one file, all images)
|
||||
const isImageOnly = data.directories.length === 0
|
||||
&& data.files.length > 0
|
||||
&& data.files.every(file => file.file_type === 'image');
|
||||
|
||||
// Render directories.
|
||||
let contentHTML = '';
|
||||
|
||||
if (isImageOnly) {
|
||||
// Display thumbnails grid
|
||||
contentHTML += '<div class="images-grid">';
|
||||
data.files.forEach(file => {
|
||||
const thumbUrl = `${file.path}?thumbnail=true`;
|
||||
contentHTML += `
|
||||
<div class="image-item">
|
||||
<a href="#" class="play-file image-link" data-url="${file.path}" data-file-type="${file.file_type}">
|
||||
<img src="/media/${thumbUrl}" alt="${file.name}" class="thumbnail" />
|
||||
</a>
|
||||
</div>
|
||||
`;
|
||||
});
|
||||
contentHTML += '</div>';
|
||||
} else {
|
||||
// Render directories normally
|
||||
if (data.directories.length > 0) {
|
||||
contentHTML += '<ul>';
|
||||
// Check if every directory name is short (≤15 characters) and no files are present
|
||||
const areAllShort = data.directories.every(dir => dir.name.length <= 15) && data.files.length === 0;
|
||||
if (areAllShort & data.breadcrumbs.length != 1) {
|
||||
if (areAllShort && data.breadcrumbs.length !== 1) {
|
||||
contentHTML += '<div class="directories-grid">';
|
||||
data.directories.forEach(dir => {
|
||||
contentHTML += `<div class="directory-item">📁 <a href="#" class="directory-link" data-path="${dir.path}">${dir.name}</a></div>`;
|
||||
@ -63,15 +80,15 @@ function renderContent(data) {
|
||||
data.directories.forEach(dir => {
|
||||
contentHTML += `<li class="directory-item">📁 <a href="#" class="directory-link" data-path="${dir.path}">${dir.name}</a></li>`;
|
||||
});
|
||||
if (data.breadcrumbs.length == 1) {
|
||||
if (data.breadcrumbs.length === 1) {
|
||||
contentHTML += `<li class="link-item" onclick="window.location.href='/search'">🔎 <a href="/search" class="link-link">Suche</a></li>`;
|
||||
}
|
||||
contentHTML += '</ul>';
|
||||
}
|
||||
}
|
||||
|
||||
// Render files.
|
||||
currentMusicFiles = []; // Reset the music files array.
|
||||
// Render files (including music and non-image files)
|
||||
currentMusicFiles = [];
|
||||
if (data.files.length > 0) {
|
||||
contentHTML += '<ul>';
|
||||
data.files.forEach((file, idx) => {
|
||||
@ -92,55 +109,48 @@ function renderContent(data) {
|
||||
});
|
||||
contentHTML += '</ul>';
|
||||
}
|
||||
}
|
||||
|
||||
// Insert the generated HTML into a container in the DOM.
|
||||
// Insert generated content
|
||||
document.getElementById('content').innerHTML = contentHTML;
|
||||
|
||||
// Now attach the event listeners for directories.
|
||||
document.querySelectorAll('div.directory-item').forEach(li => {
|
||||
li.addEventListener('click', function(e) {
|
||||
// Only trigger if the click did not start on an <a>.
|
||||
// Attach event listeners for directories
|
||||
document.querySelectorAll('.directory-item').forEach(item => {
|
||||
item.addEventListener('click', function(e) {
|
||||
if (!e.target.closest('a')) {
|
||||
const anchor = li.querySelector('a.directory-link');
|
||||
if (anchor) {
|
||||
anchor.click();
|
||||
}
|
||||
const anchor = item.querySelector('a.directory-link');
|
||||
if (anchor) anchor.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Now attach the event listeners for directories.
|
||||
document.querySelectorAll('li.directory-item').forEach(li => {
|
||||
li.addEventListener('click', function(e) {
|
||||
// Only trigger if the click did not start on an <a>.
|
||||
// Attach event listeners for file items (including images)
|
||||
if (isImageOnly) {
|
||||
document.querySelectorAll('.image-item').forEach(item => {
|
||||
item.addEventListener('click', function(e) {
|
||||
if (!e.target.closest('a')) {
|
||||
const anchor = li.querySelector('a.directory-link');
|
||||
if (anchor) {
|
||||
anchor.click();
|
||||
}
|
||||
const anchor = item.querySelector('a.image-link');
|
||||
if (anchor) anchor.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Now attach the event listeners for files.
|
||||
document.querySelectorAll('li.file-item').forEach(li => {
|
||||
li.addEventListener('click', function(e) {
|
||||
// Only trigger if the click did not start on an <a>.
|
||||
} else {
|
||||
document.querySelectorAll('li.file-item').forEach(item => {
|
||||
item.addEventListener('click', function(e) {
|
||||
if (!e.target.closest('a')) {
|
||||
const anchor = li.querySelector('a.play-file');
|
||||
if (anchor) {
|
||||
anchor.click();
|
||||
}
|
||||
const anchor = item.querySelector('a.play-file');
|
||||
if (anchor) anchor.click();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Update global variable for gallery images (only image files).
|
||||
// Update gallery images for lightbox or similar
|
||||
currentGalleryImages = data.files
|
||||
.filter(f => f.file_type === 'image')
|
||||
.map(f => f.path);
|
||||
|
||||
attachEventListeners(); // Reattach event listeners for newly rendered elements.
|
||||
attachEventListeners();
|
||||
}
|
||||
|
||||
// Fetch directory data from the API.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user