feat: preview image files inline in web file browser (#2429)
This commit is contained in:
@@ -169,6 +169,27 @@
|
|||||||
.modal.picker-mode {
|
.modal.picker-mode {
|
||||||
max-width: 920px;
|
max-width: 920px;
|
||||||
}
|
}
|
||||||
|
.modal.image-preview-mode {
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
.image-preview-stage {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
overflow: auto;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
.image-preview-stage img {
|
||||||
|
max-width: 100%;
|
||||||
|
max-height: 65vh;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
#imagePreviewDownload {
|
||||||
|
display: inline-block;
|
||||||
|
width: auto;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
.picker-columns.picker-active {
|
.picker-columns.picker-active {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
@@ -1779,6 +1800,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Image Preview Modal -->
|
||||||
|
<div class="modal-overlay" id="imagePreviewModal">
|
||||||
|
<div class="modal image-preview-mode">
|
||||||
|
<button class="modal-close" onclick="closeImagePreview()">×</button>
|
||||||
|
<h3>🖼️ Preview</h3>
|
||||||
|
<div class="folder-form">
|
||||||
|
<p class="file-info"><strong id="imagePreviewName"></strong></p>
|
||||||
|
<div class="image-preview-stage">
|
||||||
|
<img id="imagePreviewImg" alt="">
|
||||||
|
</div>
|
||||||
|
<a id="imagePreviewDownload" class="move-btn-confirm" rel="noopener noreferrer" target="_blank" href="#">Download</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// get current path from query parameter
|
// get current path from query parameter
|
||||||
const currentPath = decodeURIComponent(new URLSearchParams(window.location.search).get('path') || '/');
|
const currentPath = decodeURIComponent(new URLSearchParams(window.location.search).get('path') || '/');
|
||||||
@@ -1886,6 +1922,7 @@
|
|||||||
if (overlay.id === 'deleteModal') return closeDeleteModal();
|
if (overlay.id === 'deleteModal') return closeDeleteModal();
|
||||||
if (overlay.id === 'renameModal') return closeRenameModal();
|
if (overlay.id === 'renameModal') return closeRenameModal();
|
||||||
if (overlay.id === 'moveModal') return closeMoveModal();
|
if (overlay.id === 'moveModal') return closeMoveModal();
|
||||||
|
if (overlay.id === 'imagePreviewModal') return closeImagePreview();
|
||||||
overlay.classList.remove('open');
|
overlay.classList.remove('open');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1998,8 +2035,13 @@
|
|||||||
// Checkbox cell + file row
|
// Checkbox cell + file row
|
||||||
fileTableContent += `<tr class="${file.isEpub ? 'epub-file' : ''}">`;
|
fileTableContent += `<tr class="${file.isEpub ? 'epub-file' : ''}">`;
|
||||||
fileTableContent += `<td><input type="checkbox" class="select-item" data-path="${encodeURIComponent(filePath)}" data-name="${escapeHtml(file.name)}" data-type="file"></td>`;
|
fileTableContent += `<td><input type="checkbox" class="select-item" data-path="${encodeURIComponent(filePath)}" data-name="${escapeHtml(file.name)}" data-type="file"></td>`;
|
||||||
fileTableContent += `<td><span class="file-icon">${file.isEpub ? '📗' : '📄'}</span>`;
|
const fileIsImage = isImageFile(file.name);
|
||||||
fileTableContent += `<a rel="noopener noreferrer" target="_blank" href="/download?path=${encodeURIComponent(filePath)}" class="file-link">${escapeHtml(file.name)}</a>`;
|
fileTableContent += `<td><span class="file-icon">${file.isEpub ? '📗' : (fileIsImage ? '🖼️' : '📄')}</span>`;
|
||||||
|
if (fileIsImage) {
|
||||||
|
fileTableContent += `<a href="${downloadUrl(filePath)}" class="file-link image-preview-link">${escapeHtml(file.name)}</a>`;
|
||||||
|
} else {
|
||||||
|
fileTableContent += `<a rel="noopener noreferrer" target="_blank" href="${downloadUrl(filePath)}" class="file-link">${escapeHtml(file.name)}</a>`;
|
||||||
|
}
|
||||||
fileTableContent += '</td>';
|
fileTableContent += '</td>';
|
||||||
fileTableContent += file.isEpub ? '<td><span class="epub-badge">EPUB</span></td>' : `<td>${escapeHtml(file.name.split('.').pop().toUpperCase())}</td>`;
|
fileTableContent += file.isEpub ? '<td><span class="epub-badge">EPUB</span></td>' : `<td>${escapeHtml(file.name.split('.').pop().toUpperCase())}</td>`;
|
||||||
fileTableContent += `<td>${formatFileSize(file.size)}</td>`;
|
fileTableContent += `<td>${formatFileSize(file.size)}</td>`;
|
||||||
@@ -2017,6 +2059,30 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Image preview
|
||||||
|
function isImageFile(name) {
|
||||||
|
return /\.(png|jpe?g|bmp|gif|webp)$/i.test(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
function downloadUrl(filePath) {
|
||||||
|
return `/download?path=${encodeURIComponent(filePath)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function openImagePreview(url, name) {
|
||||||
|
const img = document.getElementById('imagePreviewImg');
|
||||||
|
document.getElementById('imagePreviewName').textContent = name;
|
||||||
|
img.src = url;
|
||||||
|
img.alt = name;
|
||||||
|
document.getElementById('imagePreviewDownload').href = url;
|
||||||
|
document.getElementById('imagePreviewModal').classList.add('open');
|
||||||
|
}
|
||||||
|
|
||||||
|
function closeImagePreview() {
|
||||||
|
document.getElementById('imagePreviewModal').classList.remove('open');
|
||||||
|
// Clear src to free the decoded image and avoid showing the stale one on reopen.
|
||||||
|
document.getElementById('imagePreviewImg').src = '';
|
||||||
|
}
|
||||||
|
|
||||||
// Modal functions
|
// Modal functions
|
||||||
function openUploadModal() {
|
function openUploadModal() {
|
||||||
// Reset converter variables to defaults
|
// Reset converter variables to defaults
|
||||||
@@ -2892,6 +2958,14 @@
|
|||||||
|
|
||||||
// Initialize quality settings handlers
|
// Initialize quality settings handlers
|
||||||
document.addEventListener('DOMContentLoaded', function() {
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
// Delegated so it survives file-table re-renders (avoids inline onclick with untrusted names).
|
||||||
|
document.getElementById('file-table').addEventListener('click', function(e) {
|
||||||
|
const link = e.target.closest('.image-preview-link');
|
||||||
|
if (!link) return;
|
||||||
|
e.preventDefault();
|
||||||
|
openImagePreview(link.getAttribute('href'), link.textContent);
|
||||||
|
});
|
||||||
|
|
||||||
const qualitySlider = document.getElementById('qualitySlider');
|
const qualitySlider = document.getElementById('qualitySlider');
|
||||||
const qualityInput = document.getElementById('qualityInput');
|
const qualityInput = document.getElementById('qualityInput');
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user