adding recent feature + bug fixed

This commit is contained in:
DioCrafts
2025-04-02 05:08:30 +02:00
parent 21e2eb1ea9
commit a79c335b73
21 changed files with 1112 additions and 15 deletions
+78
View File
@@ -0,0 +1,78 @@
/* Estilos para la funcionalidad de archivos recientes */
/* Indicador de reciente */
.recent-indicator {
position: absolute;
top: 10px;
right: 10px;
width: 25px;
height: 25px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
color: #6c757d;
z-index: 5;
}
/* Estilos para los elementos de la vista de recientes */
.recent-item {
position: relative;
}
/* Ajustes para la vista de cuadrícula */
.file-card.recent-item {
border-left: 3px solid #6c757d;
}
/* Ajustes para la vista de lista */
.file-item.recent-item {
position: relative;
grid-template-columns: 30px minmax(200px, 2fr) 1fr 1fr 120px;
}
.file-item.recent-item .recent-indicator {
position: relative;
top: 0;
right: 0;
width: 30px;
height: 30px;
}
/* Estilos para el estado vacío específico de recientes */
.recents-empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 50px 20px;
text-align: center;
color: #6c757d;
}
.recents-empty-state i {
font-size: 48px;
color: #6c757d;
margin-bottom: 20px;
opacity: 0.6;
}
.recents-empty-state p {
margin-bottom: 10px;
max-width: 400px;
}
/* Tooltip para tiempo de acceso */
.recent-item .file-info {
cursor: help;
}
/* Animación para archivos recientes */
.recent-item {
animation: recent-fade-in 0.3s ease;
}
@keyframes recent-fade-in {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
+4
View File
@@ -8,7 +8,9 @@
<!-- Styles -->
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/inlineViewer.css">
<link rel="stylesheet" href="/css/fileViewer.css">
<link rel="stylesheet" href="/css/favorites.css">
<link rel="stylesheet" href="/css/recent.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<link rel="preconnect" href="https://cdnjs.cloudflare.com" crossorigin>
@@ -16,12 +18,14 @@
<script src="/js/i18n.js"></script>
<script src="/js/languageSelector.js"></script>
<script src="/js/inlineViewer.js"></script>
<script src="/js/fileViewer.js"></script>
<script src="/js/fileRenderer.js"></script>
<script src="/js/fileSharing.js"></script>
<script src="/js/contextMenus.js"></script>
<script src="/js/fileOperations.js"></script>
<script src="/js/search.js"></script>
<script src="/js/favorites.js"></script>
<script src="/js/recent.js"></script>
<script src="/js/ui.js"></script>
<script src="/js/components/sharedView.js"></script>
<script src="/js/app.js"></script>
+119 -1
View File
@@ -15,7 +15,8 @@ const app = {
isTrashView: false, // Whether we're in trash view
isSharedView: false, // Whether we're in shared view
isFavoritesView: false, // Whether we're in favorites view
currentSection: 'files', // Current section: 'files', 'trash', 'shared' or 'favorites'
isRecentView: false, // Whether we're in recent files view
currentSection: 'files', // Current section: 'files', 'trash', 'shared', 'favorites' or 'recent'
isSearchMode: false, // Whether we're in search mode
// File sharing related properties
shareDialogItem: null, // Item being shared in share dialog
@@ -82,6 +83,14 @@ function initApp() {
console.warn('Favorites module not available or not initializable');
}
// Initialize recent files module if available
if (window.recent && window.recent.init) {
console.log('Initializing recent files module');
window.recent.init();
} else {
console.warn('Recent files module not available or not initializable');
}
// Wait for translations to load before checking authentication
if (window.i18n && window.i18n.isLoaded && window.i18n.isLoaded()) {
// Translations already loaded, proceed with authentication
@@ -206,6 +215,13 @@ function setupEventListeners() {
return;
}
// Check if this is the recent files item
if (item.querySelector('span').getAttribute('data-i18n') === 'nav.recent') {
// Switch to recent files view
switchToRecentFilesView();
return;
}
// Check if this is the trash item
if (item === elements.trashBtn) {
// Hide shared view if active
@@ -760,6 +776,7 @@ function switchToFilesView() {
app.isTrashView = false;
app.isSharedView = false;
app.isFavoritesView = false;
app.isRecentView = false;
app.currentSection = 'files';
// Update UI
@@ -929,10 +946,111 @@ function switchToFavoritesView() {
}
}
/**
* Switch to the recent files view
*/
function switchToRecentFilesView() {
// Hide other views
app.isTrashView = false;
app.isSharedView = false;
app.isFavoritesView = false;
// Set recent view as active
app.isRecentView = true;
app.currentSection = 'recent';
// Remove active class from all nav items
elements.navItems.forEach(navItem => navItem.classList.remove('active'));
// Find recent nav item and make it active
const recentNavItem = document.querySelector('.nav-item:nth-child(3)');
if (recentNavItem) {
recentNavItem.classList.add('active');
}
// Update UI
elements.pageTitle.textContent = window.i18n ? window.i18n.t('nav.recent') : 'Recientes';
// Clear breadcrumb and show root
ui.updateBreadcrumb(window.i18n ? window.i18n.t('nav.recent') : 'Recientes');
// Hide shared view if it exists
if (window.sharedView) {
window.sharedView.hide();
}
// Configure actions bar for recent view
elements.actionsBar.innerHTML = `
<div class="action-buttons">
<button class="btn btn-secondary" id="clear-recent-btn">
<i class="fas fa-broom" style="margin-right: 5px;"></i> <span data-i18n="actions.clear_recent">Limpiar recientes</span>
</button>
</div>
<div class="view-toggle">
<button class="toggle-btn active" id="grid-view-btn" title="Vista de cuadrícula">
<i class="fas fa-th"></i>
</button>
<button class="toggle-btn" id="list-view-btn" title="Vista de lista">
<i class="fas fa-list"></i>
</button>
</div>
`;
elements.actionsBar.style.display = 'flex';
// Add event listener for clear button
document.getElementById('clear-recent-btn').addEventListener('click', () => {
if (window.recent) {
window.recent.clearRecentFiles();
window.recent.displayRecentFiles();
window.ui.showNotification('Limpieza completada', 'Se ha limpiado el historial de archivos recientes');
}
});
// Restore view toggle event listeners
document.getElementById('grid-view-btn').addEventListener('click', ui.switchToGridView);
document.getElementById('list-view-btn').addEventListener('click', ui.switchToListView);
// Update cached elements
elements.gridViewBtn = document.getElementById('grid-view-btn');
elements.listViewBtn = document.getElementById('list-view-btn');
// Show standard files containers
const filesGrid = document.getElementById('files-grid');
const filesListView = document.getElementById('files-list-view');
if (filesGrid) {
filesGrid.style.display = app.currentView === 'grid' ? 'grid' : 'none';
}
if (filesListView) {
filesListView.style.display = app.currentView === 'list' ? 'block' : 'none';
}
// Check if recent files module is initialized
if (window.recent) {
// Display recent files
window.recent.displayRecentFiles();
} else {
console.error('Recent files module not loaded or initialized');
// Show error in UI
const filesGrid = document.getElementById('files-grid');
if (filesGrid) {
filesGrid.innerHTML = `
<div class="empty-state">
<i class="fas fa-exclamation-circle" style="font-size: 48px; color: #f44336; margin-bottom: 16px;"></i>
<p>Error al cargar el módulo de archivos recientes</p>
</div>
`;
}
}
}
// Expose view switching functions globally
window.switchToFilesView = switchToFilesView;
window.switchToSharedView = switchToSharedView;
window.switchToFavoritesView = switchToFavoritesView;
window.switchToRecentFilesView = switchToRecentFilesView;
/**
* Check if user is authenticated and load user's home folder
+41
View File
@@ -79,6 +79,47 @@ const contextMenus = {
});
// File context menu options
document.getElementById('view-file-option').addEventListener('click', () => {
if (window.app.contextMenuTargetFile) {
// Fetch file details to get the mime type
fetch(`/api/files/${window.app.contextMenuTargetFile.id}?metadata=true`)
.then(response => response.json())
.then(fileDetails => {
// Check if viewable file type
if ((fileDetails.mime_type && fileDetails.mime_type.startsWith('image/')) ||
(fileDetails.mime_type && fileDetails.mime_type === 'application/pdf')) {
// Open with inline viewer
if (window.inlineViewer) {
window.inlineViewer.openFile(fileDetails);
} else if (window.fileViewer) {
window.fileViewer.open(fileDetails);
} else {
// If no viewer is available, download directly
window.fileOps.downloadFile(
window.app.contextMenuTargetFile.id,
window.app.contextMenuTargetFile.name
);
}
} else {
// For non-viewable files, download
window.fileOps.downloadFile(
window.app.contextMenuTargetFile.id,
window.app.contextMenuTargetFile.name
);
}
})
.catch(error => {
console.error('Error fetching file details:', error);
// On error, fallback to download
window.fileOps.downloadFile(
window.app.contextMenuTargetFile.id,
window.app.contextMenuTargetFile.name
);
});
}
window.ui.closeFileContextMenu();
});
document.getElementById('download-file-option').addEventListener('click', () => {
if (window.app.contextMenuTargetFile) {
window.fileOps.downloadFile(
+2 -2
View File
@@ -246,7 +246,7 @@ class InlineViewer {
this.showErrorMessage(container);
}
},
}
// Helper to show error message
showErrorMessage(container) {
@@ -256,7 +256,7 @@ class InlineViewer {
message.innerHTML = `
<div class="inline-viewer-icon"><i class="fas fa-exclamation-triangle"></i></div>
<div class="inline-viewer-text">
<p>Error al cargar el archivo.</p>`
<p>Error al cargar el archivo.</p>
<p>Intenta descargarlo directamente.</p>
</div>
`;
+291
View File
@@ -0,0 +1,291 @@
/**
* OxiCloud - Recent Files Module
* This file handles tracking and displaying recently accessed files
*/
// Recent Files Module
const recent = {
// Key for storing recent files in localStorage
STORAGE_KEY: 'oxicloud_recent_files',
// Maximum number of recent files to store
MAX_RECENT_FILES: 20,
/**
* Initialize recent files module
*/
init() {
console.log('Initializing recent files module');
this.ensureRecentFilesStorage();
this.setupEventListeners();
},
/**
* Make sure the recent files storage is initialized
*/
ensureRecentFilesStorage() {
if (!localStorage.getItem(this.STORAGE_KEY)) {
localStorage.setItem(this.STORAGE_KEY, JSON.stringify([]));
}
},
/**
* Set up event listeners to track file access
*/
setupEventListeners() {
// Listen for custom event when a file is accessed
document.addEventListener('file-accessed', (event) => {
if (event.detail && event.detail.file) {
this.addRecentFile(event.detail.file);
}
});
},
/**
* Add a file to recent files
* @param {Object} file - File object containing id, name, folder_id, etc.
*/
addRecentFile(file) {
// Don't add if no file or no ID
if (!file || !file.id) {
return;
}
// Get current recent files
const recentFiles = this.getRecentFiles();
// Remove if file already exists in recent files
const existingIndex = recentFiles.findIndex(item => item.id === file.id);
if (existingIndex !== -1) {
recentFiles.splice(existingIndex, 1);
}
// Add file with timestamp to the beginning of the array
const fileWithTimestamp = {
...file,
accessedAt: Date.now()
};
recentFiles.unshift(fileWithTimestamp);
// Keep only the most recent files (limit to MAX_RECENT_FILES)
const trimmedFiles = recentFiles.slice(0, this.MAX_RECENT_FILES);
// Save back to localStorage
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(trimmedFiles));
},
/**
* Get recent files from localStorage
* @returns {Array} Array of recent file objects with timestamps
*/
getRecentFiles() {
try {
const recentFilesJson = localStorage.getItem(this.STORAGE_KEY);
return recentFilesJson ? JSON.parse(recentFilesJson) : [];
} catch (error) {
console.error('Error loading recent files:', error);
return [];
}
},
/**
* Clear all recent files
*/
clearRecentFiles() {
localStorage.setItem(this.STORAGE_KEY, JSON.stringify([]));
},
/**
* Display recent files in the UI
*/
async displayRecentFiles() {
try {
const recentFiles = this.getRecentFiles();
// Clear existing content
const filesGrid = document.getElementById('files-grid');
const filesListView = document.getElementById('files-list-view');
filesGrid.innerHTML = '';
filesListView.innerHTML = `
<div class="list-header">
<div data-i18n="files.name">Nombre</div>
<div data-i18n="files.type">Tipo</div>
<div data-i18n="files.size">Tamaño</div>
<div data-i18n="files.last_accessed">Último acceso</div>
</div>
`;
// Update breadcrumb for recents
window.ui.updateBreadcrumb(window.i18n ? window.i18n.t('nav.recent') : 'Recientes');
// Show empty state if no recent files
if (recentFiles.length === 0) {
const emptyState = document.createElement('div');
emptyState.className = 'empty-state';
emptyState.innerHTML = `
<i class="fas fa-clock" style="font-size: 48px; color: #ddd; margin-bottom: 16px;"></i>
<p>${window.i18n ? window.i18n.t('recent.empty_state') : 'No hay archivos recientes'}</p>
<p>${window.i18n ? window.i18n.t('recent.empty_hint') : 'Los archivos que abras aparecerán aquí'}</p>
`;
filesGrid.appendChild(emptyState);
return;
}
// Process each recent file
for (const recentFile of recentFiles) {
this.createRecentFileElement(recentFile, filesGrid, filesListView);
}
// Update file icons
window.ui.updateFileIcons();
} catch (error) {
console.error('Error displaying recent files:', error);
window.ui.showNotification('Error', 'Error al cargar archivos recientes');
}
},
/**
* Create a file element for a recent file
* @param {Object} file - Recent file object
* @param {HTMLElement} filesGrid - Grid view container
* @param {HTMLElement} filesListView - List view container
*/
createRecentFileElement(file, filesGrid, filesListView) {
// Determine icon and type
let iconClass = 'fas fa-file';
let iconSpecialClass = '';
let typeLabel = 'Documento';
if (file.mime_type) {
if (file.mime_type.startsWith('image/')) {
iconClass = 'fas fa-file-image';
iconSpecialClass = 'image-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.image') : 'Imagen';
} else if (file.mime_type.startsWith('text/')) {
iconClass = 'fas fa-file-alt';
iconSpecialClass = 'text-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.text') : 'Texto';
} else if (file.mime_type.startsWith('video/')) {
iconClass = 'fas fa-file-video';
iconSpecialClass = 'video-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.video') : 'Video';
} else if (file.mime_type.startsWith('audio/')) {
iconClass = 'fas fa-file-audio';
iconSpecialClass = 'audio-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.audio') : 'Audio';
} else if (file.mime_type === 'application/pdf') {
iconClass = 'fas fa-file-pdf';
iconSpecialClass = 'pdf-icon';
typeLabel = window.i18n ? window.i18n.t('files.file_types.pdf') : 'PDF';
}
}
// Format size and date
const fileSize = window.formatFileSize ? window.formatFileSize(file.size || 0) : '0 B';
const accessedDate = new Date(file.accessedAt);
const formattedDate = accessedDate.toLocaleDateString() + ' ' +
accessedDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
// Grid view element
const fileGridElement = document.createElement('div');
fileGridElement.className = 'file-card recent-item';
fileGridElement.dataset.fileId = file.id;
fileGridElement.dataset.fileName = file.name;
fileGridElement.dataset.folderId = file.folder_id || "";
fileGridElement.innerHTML = `
<div class="recent-indicator">
<i class="fas fa-clock"></i>
</div>
<div class="file-icon ${iconSpecialClass}">
<i class="${iconClass}"></i>
</div>
<div class="file-name">${file.name}</div>
<div class="file-info">Accedido ${formattedDate.split(' ')[0]}</div>
`;
// Download on click
fileGridElement.addEventListener('click', () => {
window.location.href = `/api/files/${file.id}`;
// Dispatch custom event to update recent files
document.dispatchEvent(new CustomEvent('file-accessed', {
detail: { file }
}));
});
// Context menu
fileGridElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
window.app.contextMenuTargetFile = {
id: file.id,
name: file.name,
folder_id: file.folder_id || ""
};
let fileContextMenu = document.getElementById('file-context-menu');
fileContextMenu.style.left = `${e.pageX}px`;
fileContextMenu.style.top = `${e.pageY}px`;
fileContextMenu.style.display = 'block';
});
filesGrid.appendChild(fileGridElement);
// List view element
const fileListElement = document.createElement('div');
fileListElement.className = 'file-item recent-item';
fileListElement.dataset.fileId = file.id;
fileListElement.dataset.fileName = file.name;
fileListElement.dataset.folderId = file.folder_id || "";
fileListElement.innerHTML = `
<div class="recent-indicator">
<i class="fas fa-clock"></i>
</div>
<div class="name-cell">
<div class="file-icon ${iconSpecialClass}">
<i class="${iconClass}"></i>
</div>
<span>${file.name}</span>
</div>
<div class="type-cell">${typeLabel}</div>
<div class="size-cell">${fileSize}</div>
<div class="date-cell">${formattedDate}</div>
`;
// Download on click
fileListElement.addEventListener('click', () => {
window.location.href = `/api/files/${file.id}`;
// Dispatch custom event to update recent files
document.dispatchEvent(new CustomEvent('file-accessed', {
detail: { file }
}));
});
// Context menu
fileListElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
window.app.contextMenuTargetFile = {
id: file.id,
name: file.name,
folder_id: file.folder_id || ""
};
let fileContextMenu = document.getElementById('file-context-menu');
fileContextMenu.style.left = `${e.pageX}px`;
fileContextMenu.style.top = `${e.pageY}px`;
fileContextMenu.style.display = 'block';
});
filesListView.appendChild(fileListElement);
}
};
// Expose recent module globally
window.recent = recent;
+53 -4
View File
@@ -43,6 +43,9 @@ const ui = {
fileMenu.className = 'context-menu';
fileMenu.id = 'file-context-menu';
fileMenu.innerHTML = `
<div class="context-menu-item" id="view-file-option">
<i class="fas fa-eye"></i> <span data-i18n="actions.view">Ver</span>
</div>
<div class="context-menu-item" id="download-file-option">
<i class="fas fa-download"></i> <span data-i18n="actions.download">Descargar</span>
</div>
@@ -766,9 +769,32 @@ const ui = {
});
});
// Download on click
// View or download on click
fileGridElement.addEventListener('click', () => {
window.location.href = `/api/files/${file.id}`;
// Track this file access for recent files
if (window.recent) {
document.dispatchEvent(new CustomEvent('file-accessed', {
detail: { file }
}));
}
// Check if it's a viewable file type
if ((file.mime_type && file.mime_type.startsWith('image/')) ||
(file.mime_type && file.mime_type === 'application/pdf')) {
// Open in the inline viewer
if (window.inlineViewer) {
window.inlineViewer.openFile(file);
} else if (window.fileViewer) {
// Fallback to standard file viewer
window.fileViewer.open(file);
} else {
// No viewer available, download directly
window.location.href = `/api/files/${file.id}`;
}
} else {
// For other file types, download directly
window.location.href = `/api/files/${file.id}`;
}
});
// Context menu
@@ -823,9 +849,32 @@ const ui = {
});
});
// Download on click
// View or download on click
fileListElement.addEventListener('click', () => {
window.location.href = `/api/files/${file.id}`;
// Track this file access for recent files
if (window.recent) {
document.dispatchEvent(new CustomEvent('file-accessed', {
detail: { file }
}));
}
// Check if it's a viewable file type
if ((file.mime_type && file.mime_type.startsWith('image/')) ||
(file.mime_type && file.mime_type === 'application/pdf')) {
// Open in the inline viewer
if (window.inlineViewer) {
window.inlineViewer.openFile(file);
} else if (window.fileViewer) {
// Fallback to standard file viewer
window.fileViewer.open(file);
} else {
// No viewer available, download directly
window.location.href = `/api/files/${file.id}`;
}
} else {
// For other file types, download directly
window.location.href = `/api/files/${file.id}`;
}
});
// Context menu (list view)
+3 -1
View File
@@ -19,12 +19,14 @@
"move_to": "Move to",
"delete": "Delete",
"download": "Download",
"view": "View",
"cancel": "Cancel",
"confirm": "Confirm",
"share": "Share",
"copy": "Copy",
"notify": "Notify",
"send": "Send"
"send": "Send",
"clear_recent": "Clear recent"
},
"share": {
"dialogTitle": "Share Link",
+3 -1
View File
@@ -138,12 +138,14 @@
"move_to": "Mover a",
"delete": "Eliminar",
"download": "Descargar",
"view": "Ver",
"cancel": "Cancelar",
"confirm": "Confirmar",
"share": "Compartir",
"copy": "Copiar",
"notify": "Notificar",
"send": "Enviar"
"send": "Enviar",
"clear_recent": "Limpiar recientes"
},
"files": {
"name": "Nombre",