better preview of files

This commit is contained in:
2025-06-01 12:38:51 +02:00
parent aeefd17b10
commit ea118a37c5
8 changed files with 140 additions and 23 deletions

View File

@@ -66,7 +66,7 @@ export class FilePreview {
}
async previewFile(file) {
const contentDiv = document.getElementById(`${this.options.containerId}Content`);
const contentDiv = document.getElementById('filePreviewContent');
const extension = file.name.split('.').pop().toLowerCase();
// Show loading spinner

View File

@@ -9,6 +9,28 @@
* - File details display
*/
import { FilePreview } from './components/filePreview.js';
/**
* Formats a file size in bytes to a human-readable string.
* @param {number} bytes - The file size in bytes
* @returns {string} Formatted file size string
*/
function formatFileSize(bytes) {
if (!bytes) return '0 B';
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
let size = bytes;
let unitIndex = 0;
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024;
unitIndex++;
}
return `${size.toFixed(1)} ${units[unitIndex]}`;
}
let currentView = 'grid';
let lastSelectedIndex = -1;
let sortColumn = 'name'; // Set default sort column to name
@@ -21,6 +43,14 @@ window.isAdmin = document.body.dataset.isAdmin === 'true';
// Check if we're on the trash page
const isTrashPage = window.location.pathname.includes('/trash');
// Initialize FilePreview component
const filePreview = new FilePreview({
containerId: 'filePreviewModal',
onClose: () => {
// Clean up any resources if needed
}
});
/**
* Initializes the file view and fetches files.
* Sets up the preferred view and initial file sorting.
@@ -189,17 +219,31 @@ function renderFiles(files) {
${isTrashPage ? `<th onclick="sortFiles('auto_delete')" style="cursor:pointer;">Auto Delete ${(sortColumn==='auto_delete') ? (sortDirection===1?'▲':'▼') : ''}</th>` : ''}
<th class='file-actions'></th>
</tr></thead><tbody>`;
files.forEach((file, idx) => {
let icon = file.type === 'folder'
? `<i class='fas fa-folder' style='font-size:1.5rem;color:var(--primary-color);'></i>`
: `<i class='fas ${getFileIcon(file.name)}' style='font-size:1.5rem;color:var(--secondary-color);'></i>`;
let size = file.size !== '-' ? (file.size > 0 ? (file.size < 1024*1024 ? (file.size/1024).toFixed(1)+' KB' : (file.size/1024/1024).toFixed(2)+' MB') : '0 KB') : '-';
let icon = file.type === 'folder' ?
`<i class="fas fa-folder" style="color:var(--primary-color);"></i>` :
`<i class="fas ${getFileIcon(file.name)}" style="color:var(--secondary-color);"></i>`;
let size = file.type === 'folder' ? '-' : formatFileSize(file.size);
let dblClickAction = file.type === 'folder' ?
`ondblclick='navigateToFolder("${file.name}")'` : '';
let actionsArr = [];
let dblClickAction = '';
if (file.type === 'folder') {
dblClickAction = `ondblclick=\"window.location.href='/room/${file.room_id}?path=${encodeURIComponent(file.path ? file.path + '/' + file.name : file.name)}'\"`;
} else {
dblClickAction = `ondblclick=\"window.location.href='/api/rooms/${file.room_id}/files/${encodeURIComponent(file.name)}?path=${encodeURIComponent(file.path)}'\"`;
// Add preview button for supported file types
if (file.type !== 'folder') {
const extension = file.name.split('.').pop().toLowerCase();
const supportedTypes = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'mp4', 'webm', 'mp3', 'wav'];
if (supportedTypes.includes(extension)) {
actionsArr.push(`
<button class="btn btn-sm file-action-btn" title="Preview" onclick="event.stopPropagation(); previewFile(${idx})"
style="background-color:var(--primary-opacity-8);color:var(--primary-color);">
<i class="fas fa-eye"></i>
</button>
`);
}
}
if (isTrashPage) {
@@ -225,16 +269,29 @@ function renderFiles(files) {
grid.innerHTML = table;
} else {
files.forEach((file, idx) => {
let icon = file.type === 'folder'
? `<i class='fas fa-folder' style='font-size:2.5rem;color:var(--primary-color);'></i>`
: `<i class='fas ${getFileIcon(file.name)}' style='font-size:2.5rem;color:var(--secondary-color);'></i>`;
let size = file.size !== '-' ? (file.size > 0 ? (file.size < 1024*1024 ? (file.size/1024).toFixed(1)+' KB' : (file.size/1024/1024).toFixed(2)+' MB') : '0 KB') : '-';
let icon = file.type === 'folder' ?
`<i class="fas fa-folder" style="color:var(--primary-color);"></i>` :
`<i class="fas ${getFileIcon(file.name)}" style="color:var(--secondary-color);"></i>`;
let size = file.type === 'folder' ? '-' : formatFileSize(file.size);
let dblClickAction = file.type === 'folder' ?
`ondblclick='navigateToFolder("${file.name}")'` : '';
let actionsArr = [];
let dblClickAction = '';
if (file.type === 'folder') {
dblClickAction = `ondblclick=\"window.location.href='/room/${file.room_id}?path=${encodeURIComponent(file.path ? file.path + '/' + file.name : file.name)}'\"`;
} else {
dblClickAction = `ondblclick=\"window.location.href='/api/rooms/${file.room_id}/files/${encodeURIComponent(file.name)}?path=${encodeURIComponent(file.path)}'\"`;
// Add preview button for supported file types
if (file.type !== 'folder') {
const extension = file.name.split('.').pop().toLowerCase();
const supportedTypes = ['jpg', 'jpeg', 'png', 'gif', 'pdf', 'mp4', 'webm', 'mp3', 'wav'];
if (supportedTypes.includes(extension)) {
actionsArr.push(`
<button class="btn btn-sm file-action-btn" title="Preview" onclick="event.stopPropagation(); previewFile(${idx})"
style="background-color:var(--primary-opacity-8);color:var(--primary-color);">
<i class="fas fa-eye"></i>
</button>
`);
}
}
if (isTrashPage) {
@@ -606,6 +663,30 @@ function formatDate(dateString) {
return date.toLocaleString();
}
// Add previewFile function
async function previewFile(index) {
const file = currentFiles[index];
if (!file) return;
const fileUrl = `/api/rooms/${file.room_id}/files/${encodeURIComponent(file.name)}?path=${encodeURIComponent(file.path || '')}&preview=true`;
await filePreview.previewFile({
name: file.name,
url: fileUrl
});
}
// Make functions globally accessible
window.previewFile = previewFile;
window.restoreFile = restoreFile;
window.showPermanentDeleteModal = showPermanentDeleteModal;
window.showDetailsModal = showDetailsModal;
window.toggleStar = toggleStar;
window.sortFiles = sortFiles;
window.navigateToFile = navigateToFile;
window.showEmptyTrashModal = showEmptyTrashModal;
window.permanentDeleteFile = permanentDeleteFile;
// Initialize search functionality
document.addEventListener('DOMContentLoaded', function() {
initializeView();

View File

@@ -27,7 +27,7 @@ export class ViewManager {
this.sortColumn = 'name';
this.sortDirection = 'asc';
this.filePreview = new FilePreview({
containerId: 'roomFilePreviewModal',
containerId: 'filePreviewModal',
onClose: () => {
// Clean up any resources if needed
}

View File

@@ -0,0 +1,27 @@
<!-- File Preview Modal -->
<div class="modal fade" id="filePreviewModal" tabindex="-1" aria-labelledby="filePreviewModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="filePreviewModalLabel">File Preview</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body p-0">
<div id="filePreviewContent" class="d-flex justify-content-center align-items-center" style="min-height: 400px;">
<div class="text-center">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2 text-muted">Loading preview...</p>
</div>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<a id="downloadPreviewBtn" href="#" class="btn btn-primary" download>
<i class="fas fa-download me-1"></i>Download
</a>
</div>
</div>
</div>
</div>

View File

@@ -248,7 +248,7 @@
</div>
<!-- Move Modal -->
<div class="modal fade" id="moveModal" tabindex="-1" aria-labelledby="moveModalLabel" aria-hidden="true">
<div id="moveModal" class="modal fade" tabindex="-1" aria-labelledby="moveModalLabel" aria-hidden="true">
<div class="modal-dialog modal-dialog-centered">
<div class="modal-content">
<div class="modal-header">
@@ -272,6 +272,10 @@
</div>
</div>
</div>
<!-- Include modals -->
{% include 'components/details_modal.html' %}
{% include 'components/file_preview_modal.html' %}
{% endblock %}
{% block extra_js %}

View File

@@ -14,6 +14,7 @@
<option value="file_upload">File Upload</option>
<option value="file_delete">File Delete</option>
<option value="file_download">File Download</option>
<option value="file_preview">File Preview</option>
<option value="file_restore">File Restore</option>
<option value="file_move">File Move</option>
<option value="file_rename">File Rename</option>
@@ -90,6 +91,8 @@
<span class="badge bg-danger">File Delete</span>
{% elif event.event_type == 'file_download' %}
<span class="badge bg-info">File Download</span>
{% elif event.event_type == 'file_preview' %}
<span class="badge bg-info">File Preview</span>
{% elif event.event_type == 'file_restore' %}
<span class="badge bg-warning">File Restore</span>
{% elif event.event_type == 'file_move' %}

View File

@@ -38,9 +38,10 @@
</div>
{% include 'components/details_modal.html' %}
{% include 'components/file_preview_modal.html' %}
{% endblock %}
{% block extra_js %}
<script src="{{ url_for('static', filename='js/file-grid.js', v=config.CSS_VERSION) }}"></script>
<script type="module" src="{{ url_for('static', filename='js/file-grid.js', v=config.CSS_VERSION) }}"></script>
<script src="{{ url_for('static', filename='js/starred.js', v=config.CSS_VERSION) }}"></script>
{% endblock %}

View File

@@ -44,9 +44,10 @@
{% include 'components/details_modal.html' %}
{% include 'components/permanent_delete_modal.html' %}
{% include 'components/empty_trash_modal.html' %}
{% include 'components/file_preview_modal.html' %}
{% endblock %}
{% block extra_js %}
<script src="{{ url_for('static', filename='js/file-grid.js', v=config.CSS_VERSION) }}"></script>
<script type="module" src="{{ url_for('static', filename='js/file-grid.js', v=config.CSS_VERSION) }}"></script>
<script src="{{ url_for('static', filename='js/trash.js', v=config.CSS_VERSION) }}"></script>
{% endblock %}