Compare commits

...

2 Commits

Author SHA1 Message Date
ece99f8e48 improve image preview 2026-01-09 22:20:45 +00:00
a0ec67f22a jump into token links when clicking 2026-01-09 22:19:49 +00:00
3 changed files with 102 additions and 22 deletions

9
app.py
View File

@ -542,8 +542,13 @@ def list_directory_contents(directory, subpath):
music_exts = ('.mp3',)
image_exts = ('.jpg', '.jpeg', '.png', '.gif', '.bmp')
blocked_filenames = ['Thumbs.db', '*.mrk']
blocked_filenames = [
'Thumbs.db',
'*.mrk',
'*.arw', '*.cr2', '*.lrf'
]
try:
with os.scandir(directory) as it:
# Sorting by name if required.

29
auth.py
View File

@ -171,16 +171,23 @@ def require_secret(f):
if 'device_id' not in session:
session['device_id'] = os.urandom(32).hex()
# AUTO-JUMP FOR TOKENS - Disabled for now to debug
# try:
# if args_token and is_valid_token(args_token):
# token_item = decode_token(args_token)
# target_foldername = token_item['folders'][0]['foldername']
# # Mark session as modified to ensure it's saved before redirect
# session.modified = True
# return redirect(f"/path/{target_foldername}")
# except Exception as e:
# print(f"Error during auto-jump: {e}")
# For token links, immediately open the single folder they grant access to.
if (
args_token
and request.method == 'GET'
and request.endpoint == 'index'
and is_valid_token(args_token)
):
try:
token_item = decode_token(args_token)
folders = token_item.get('folders', [])
if len(folders) == 1:
target_foldername = folders[0].get('foldername')
if target_foldername:
session.modified = True # ensure session persists before redirect
return redirect(f"/path/{target_foldername}")
except Exception as e:
print(f"Error during token auto-open: {e}")
return f(*args, **kwargs)
else:
@ -472,4 +479,4 @@ if __name__ == '__main__':
print("Token:", token)
result = decode_token(token)
print("Decoded payload:", result)
print("Decoded payload:", result)

View File

@ -3,6 +3,44 @@ let currentMusicFiles = []; // Array of objects with at least { path, index }
let currentMusicIndex = -1; // Index of the current music file
let currentTrackPath = "";
// Check thumb availability in parallel; sequentially generate missing ones to avoid concurrent creation.
function ensureFirstFivePreviews() {
const images = Array.from(document.querySelectorAll('.images-grid img')).slice(0, 5);
if (!images.length) return;
const checks = images.map(img => {
const thumbUrl = img.dataset.thumbUrl || img.src;
const fullUrl = img.dataset.fullUrl;
return fetch(thumbUrl, { method: 'GET', cache: 'no-store' })
.then(res => ({
img,
thumbUrl,
fullUrl,
hasThumb: res.ok && res.status !== 204
}))
.catch(() => ({ img, thumbUrl, fullUrl, hasThumb: false }));
});
Promise.all(checks).then(results => {
const missing = results.filter(r => !r.hasThumb && r.fullUrl);
if (!missing.length) return;
generateThumbsSequentially(missing);
});
}
async function generateThumbsSequentially(entries) {
for (const { img, thumbUrl, fullUrl } of entries) {
try {
await fetch(fullUrl, { method: 'GET', cache: 'no-store' });
const cacheBust = `_=${Date.now()}`;
const separator = thumbUrl.includes('?') ? '&' : '?';
img.src = `${thumbUrl}${separator}${cacheBust}`;
} catch (e) {
// ignore; best effort
}
}
}
// Cache common DOM elements
const mainContainer = document.querySelector('main');
const searchContainer = document.querySelector('search');
@ -61,17 +99,20 @@ function renderContent(data) {
});
document.getElementById('breadcrumbs').innerHTML = breadcrumbHTML;
const imageFiles = data.files.filter(file => file.file_type === 'image');
const nonImageFiles = data.files.filter(file => file.file_type !== 'image');
// 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');
&& imageFiles.length > 0
&& nonImageFiles.length === 0;
let contentHTML = '';
if (isImageOnly) {
// Display thumbnails grid
contentHTML += '<div class="images-grid">';
data.files.forEach(file => {
imageFiles.forEach(file => {
const thumbUrl = `${file.path}?thumbnail=true`;
contentHTML += `
<div class="image-item">
@ -81,6 +122,8 @@ function renderContent(data) {
data-file-type="${file.file_type}">
<img
src="/media/${thumbUrl}"
data-thumb-url="/media/${thumbUrl}"
data-full-url="/media/${file.path}"
class="thumbnail"
onload="this.closest('.image-item').classList.add('loaded')"
/>
@ -138,9 +181,9 @@ function renderContent(data) {
// Render files (including music and non-image files)
currentMusicFiles = [];
if (data.files.length > 0) {
if (nonImageFiles.length > 0) {
contentHTML += '<ul>';
data.files.forEach((file, idx) => {
nonImageFiles.forEach((file, idx) => {
let symbol = '📄';
if (file.file_type === 'music') {
symbol = '🔊';
@ -158,6 +201,32 @@ function renderContent(data) {
});
contentHTML += '</ul>';
}
// Show images in preview grid after the file list
if (imageFiles.length > 0) {
contentHTML += '<div class="images-grid">';
imageFiles.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}"
data-thumb-url="/media/${thumbUrl}"
data-full-url="/media/${file.path}"
class="thumbnail"
onload="this.closest('.image-item').classList.add('loaded')"
/>
</a>
<span class="file-name">${file.name}</span>
</div>
`;
});
contentHTML += '</div>';
}
}
// Insert generated content
@ -174,7 +243,7 @@ function renderContent(data) {
});
// Attach event listeners for file items (including images)
if (isImageOnly) {
if (isImageOnly || imageFiles.length > 0) {
document.querySelectorAll('.image-item').forEach(item => {
item.addEventListener('click', function(e) {
if (!e.target.closest('a')) {
@ -195,9 +264,8 @@ function renderContent(data) {
}
// Update gallery images for lightbox or similar
currentGalleryImages = data.files
.filter(f => f.file_type === 'image')
.map(f => f.path);
currentGalleryImages = imageFiles.map(f => f.path);
ensureFirstFivePreviews();
attachEventListeners();
}