Files
Oxicloud/static/index.html
T
2025-03-17 21:28:08 +01:00

1991 lines
76 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title data-i18n="app.title">RustCloud</title>
<style>
/* Reset y estilos base */
* {
box-sizing: border-box;
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
body {
display: flex;
height: 100vh;
background-color: #f5f7fa;
overflow: hidden;
}
/* Barra lateral */
.sidebar {
width: 250px;
background-color: #2a3042;
color: #fff;
display: flex;
flex-direction: column;
height: 100%;
flex-shrink: 0;
}
.logo-container {
padding: 20px;
display: flex;
align-items: center;
justify-content: flex-start;
margin-bottom: 20px;
}
.logo {
width: 40px;
height: 40px;
background-color: #ff5e3a;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-right: 10px;
}
.logo svg {
width: 24px;
height: 24px;
fill: white;
}
.app-name {
font-size: 18px;
font-weight: bold;
color: white;
}
.nav-menu {
display: flex;
flex-direction: column;
flex-grow: 1;
padding: 0 15px;
}
.nav-item {
display: flex;
align-items: center;
padding: 12px 15px;
margin-bottom: 5px;
border-radius: 8px;
cursor: pointer;
color: white;
font-size: 16px;
}
.nav-item:hover {
background-color: #3a4157;
}
.nav-item.active {
background-color: #374e65;
}
.nav-item i {
margin-right: 15px;
width: 20px;
text-align: center;
}
/* Indicador de almacenamiento */
.storage-container {
margin: 20px 15px;
background-color: #374e65;
border-radius: 8px;
padding: 15px;
}
.storage-title {
text-align: center;
margin-bottom: 10px;
font-size: 14px;
}
.storage-bar {
height: 10px;
background-color: #6b7e8f;
border-radius: 5px;
overflow: hidden;
margin-bottom: 10px;
}
.storage-fill {
height: 100%;
background-color: #ff5e3a;
width: 56%;
}
.storage-info {
text-align: center;
font-size: 12px;
color: #f5f5f5;
}
/* Contenido principal */
.main-content {
flex-grow: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
/* Barra superior */
.top-bar {
height: 70px;
background-color: white;
border-bottom: 1px solid #e6e6e6;
display: flex;
align-items: center;
padding: 0 20px;
justify-content: space-between;
}
.search-container {
flex-grow: 1;
max-width: 500px;
position: relative;
}
.search-container input {
width: 100%;
padding: 10px 15px 10px 40px;
border-radius: 50px;
border: none;
background-color: #f0f3f7;
font-size: 14px;
}
.search-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: #8895a7;
}
.user-controls {
display: flex;
align-items: center;
}
.language-selector {
margin-right: 15px;
padding: 8px 12px;
background-color: #f0f3f7;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
}
.language-selector::after {
content: "▼";
font-size: 10px;
margin-left: 5px;
}
.user-avatar {
width: 40px;
height: 40px;
background-color: #ff5e3a;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
}
/* Área de contenido */
.content-area {
flex-grow: 1;
padding: 20px;
overflow-y: auto;
}
.page-title {
font-size: 24px;
font-weight: bold;
margin-bottom: 20px;
color: #2d3748;
}
.actions-bar {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
.action-buttons {
display: flex;
gap: 10px;
}
.btn {
padding: 10px 20px;
border-radius: 50px;
border: none;
cursor: pointer;
display: flex;
align-items: center;
font-size: 14px;
}
.btn-primary {
background-color: #ff5e3a;
color: white;
}
.btn-secondary {
background-color: #f0f3f7;
color: #333;
}
.view-toggle {
display: flex;
border-radius: 8px;
overflow: hidden;
}
.toggle-btn {
background-color: #f0f3f7;
border: none;
padding: 8px 15px;
cursor: pointer;
}
.toggle-btn.active {
background-color: #e6e6e6;
}
/* Files grid */
.files-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 20px;
}
.file-card {
background-color: white;
border-radius: 8px;
padding: 20px;
display: flex;
flex-direction: column;
align-items: center;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
cursor: pointer;
transition: transform 0.2s, box-shadow 0.2s;
}
.file-card:hover {
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
}
.file-card.dragging {
opacity: 0.5;
transform: scale(0.95);
box-shadow: none;
}
.file-card.drop-target {
background-color: rgba(255, 193, 7, 0.1);
border: 2px dashed #ffc107;
}
.file-icon {
width: 100px;
height: 70px;
margin-bottom: 15px;
display: flex;
align-items: center;
justify-content: center;
font-size: 32px;
}
.folder-icon {
color: #ffc107;
}
.file-name {
font-size: 14px;
font-weight: 500;
text-align: center;
margin-bottom: 5px;
color: #2d3748;
}
.file-info {
font-size: 12px;
color: #718096;
text-align: center;
}
/* Lista de archivos */
.files-list-view {
display: flex;
flex-direction: column;
width: 100%;
}
.list-header {
display: grid;
grid-template-columns: minmax(200px, 2fr) 1fr 1fr 120px;
padding: 10px 15px;
border-bottom: 1px solid #e0e6ed;
font-weight: bold;
color: #2d3748;
background-color: #f8f9fa;
}
.file-item {
display: grid;
grid-template-columns: minmax(200px, 2fr) 1fr 1fr 120px;
padding: 12px 15px;
border-bottom: 1px solid #f0f0f0;
align-items: center;
cursor: pointer;
transition: background-color 0.2s;
}
.file-item:hover {
background-color: #f0f8ff;
}
.file-item .name-cell {
display: flex;
align-items: center;
gap: 10px;
}
.file-item .file-icon {
font-size: 20px;
margin-bottom: 0;
width: auto;
height: auto;
}
/* Breadcrumb */
.breadcrumb {
display: flex;
align-items: center;
margin-bottom: 15px;
font-size: 14px;
color: #666;
}
.breadcrumb-item {
cursor: pointer;
}
.breadcrumb-item:hover {
text-decoration: underline;
}
.breadcrumb-separator {
margin: 0 8px;
}
/* Context menu */
.context-menu {
position: absolute;
background-color: white;
border: 1px solid #ddd;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
padding: 5px 0;
min-width: 150px;
z-index: 1000;
display: none;
}
.context-menu-item {
padding: 8px 15px;
cursor: pointer;
display: flex;
align-items: center;
color: #333;
}
.context-menu-item:hover {
background-color: #f0f8ff;
}
.context-menu-item i {
margin-right: 8px;
width: 16px;
text-align: center;
}
/* Dialog */
.rename-dialog {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0,0,0,0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 2000;
display: none;
}
.rename-dialog-content {
background-color: white;
border-radius: 8px;
padding: 20px;
width: 400px;
max-width: 90%;
}
.rename-dialog-header {
font-size: 18px;
font-weight: bold;
margin-bottom: 15px;
}
.rename-dialog input {
width: 100%;
padding: 10px;
margin-bottom: 15px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
}
.rename-dialog-buttons {
display: flex;
justify-content: flex-end;
gap: 10px;
}
/* Dropzone */
.dropzone {
border: 2px dashed #ddd;
border-radius: 8px;
padding: 20px;
text-align: center;
margin: 20px 0;
color: #666;
display: none;
}
.dropzone.active {
border-color: #ff5e3a;
background-color: rgba(255, 94, 58, 0.05);
}
.upload-progress {
margin-top: 15px;
width: 100%;
display: none;
}
.progress-bar {
background-color: #f0f0f0;
height: 6px;
border-radius: 3px;
overflow: hidden;
}
.progress-fill {
height: 100%;
background-color: #ff5e3a;
width: 0%;
transition: width 0.3s;
}
/* Notificación */
.notification {
position: absolute;
top: 70px;
right: 20px;
background-color: white;
width: 250px;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
padding: 15px;
border-left: 4px solid #ff5e3a;
z-index: 1000;
display: none;
}
.notification-title {
font-weight: bold;
font-size: 14px;
margin-bottom: 5px;
color: #2d3748;
}
.notification-message {
font-size: 12px;
color: #718096;
}
/* Responsive */
@media (max-width: 768px) {
.sidebar {
width: 60px;
padding: 15px 10px;
}
.app-name, .storage-title, .storage-info {
display: none;
}
.nav-item {
justify-content: center;
padding: 12px 0;
}
.nav-item i {
margin-right: 0;
}
.nav-item span {
display: none;
}
}
</style>
<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>
<!-- Internationalization scripts -->
<script src="/js/i18n.js"></script>
<script src="/js/languageSelector.js"></script>
<!-- File renderer -->
<script src="/js/fileRenderer.js"></script>
<!-- Service Worker Registration -->
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('/sw.js')
.then(reg => console.log('Service Worker registered successfully'))
.catch(err => console.log('Service Worker registration failed:', err));
});
}
</script>
</head>
<body>
<!-- Barra lateral -->
<div class="sidebar">
<div class="logo-container">
<div class="logo">
<svg viewBox="0 0 24 24">
<path d="M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z" fill="#fff"/>
</svg>
</div>
<div class="app-name">RustCloud</div>
</div>
<div class="nav-menu">
<div class="nav-item active">
<i class="fas fa-folder"></i>
<span data-i18n="nav.files">Files</span>
</div>
<div class="nav-item">
<i class="fas fa-share-alt"></i>
<span data-i18n="nav.shared">Shared</span>
</div>
<div class="nav-item">
<i class="fas fa-clock"></i>
<span data-i18n="nav.recent">Recent</span>
</div>
<div class="nav-item">
<i class="fas fa-star"></i>
<span data-i18n="nav.favorites">Favorites</span>
</div>
<div class="nav-item">
<i class="fas fa-trash"></i>
<span data-i18n="nav.trash">Trash</span>
</div>
</div>
<div class="storage-container">
<div class="storage-title">Almacenamiento</div>
<div class="storage-bar">
<div class="storage-fill"></div>
</div>
<div class="storage-info">56% usado (5.6GB / 10GB)</div>
</div>
</div>
<!-- Contenido principal -->
<div class="main-content">
<!-- Barra superior -->
<div class="top-bar">
<div class="search-container">
<i class="fas fa-search search-icon"></i>
<input type="text" data-i18n-placeholder="actions.search" placeholder="Buscar archivos, carpetas...">
</div>
<div class="user-controls">
<div id="language-selector" class="language-selector"></div>
<div class="user-avatar">MR</div>
</div>
</div>
<div class="content-area">
<h1 class="page-title" data-i18n="nav.files">Archivos</h1>
<div class="actions-bar">
<div class="action-buttons">
<button class="btn btn-primary" id="upload-btn">
<i class="fas fa-upload" style="margin-right: 5px;"></i> <span data-i18n="actions.upload">Subir</span>
</button>
<button class="btn btn-secondary" id="new-folder-btn">
<i class="fas fa-folder-plus" style="margin-right: 5px;"></i> <span data-i18n="actions.new_folder">Nueva carpeta</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>
</div>
<div class="dropzone" id="dropzone">
<i class="fas fa-cloud-upload-alt" style="font-size: 32px; margin-bottom: 10px;"></i>
<p data-i18n="dropzone.drag_files">Arrastra archivos aquí o haz clic para seleccionar</p>
<input type="file" id="file-input" style="display: none;" multiple>
<div class="upload-progress">
<div class="progress-bar">
<div class="progress-fill"></div>
</div>
</div>
</div>
<div class="breadcrumb">
<span class="breadcrumb-item" data-i18n="breadcrumb.home">Home</span>
</div>
<!-- Files Container -->
<div class="files-container">
<!-- Grid View -->
<div class="files-grid" id="files-grid">
<!-- Files will be populated here -->
</div>
<!-- List View (hidden by default) -->
<div class="files-list-view" id="files-list-view" style="display: none;">
<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.modified">Modificado</div>
</div>
<!-- Files will be populated here in list view -->
</div>
</div>
</div>
</div>
<!-- Context menus and dialogs will be created dynamically -->
<script>
// Add missing translations
window.addEventListener('DOMContentLoaded', function() {
// Wait for i18n to initialize
setTimeout(function() {
if (window.i18n) {
// English
if (!window.i18n.t('files.view_grid')) {
// Add view_grid and view_list translations to existing English translations
const enTranslations = {
'files.view_grid': 'Grid view',
'files.view_list': 'List view'
};
// Add them to the i18n system (if possible)
for (const key in enTranslations) {
// We're simulating adding translations to the cache
// This is a workaround and might need adaptation for your actual i18n system
const baseObj = window.i18n._getBaseObjectForKey ?
window.i18n._getBaseObjectForKey('en', key) : null;
if (baseObj) {
const lastKey = key.split('.').pop();
baseObj[lastKey] = enTranslations[key];
}
}
}
// Spanish
if (!window.i18n.t('files.view_grid')) {
// Add view_grid and view_list translations to existing Spanish translations
const esTranslations = {
'files.view_grid': 'Vista de cuadrícula',
'files.view_list': 'Vista de lista'
};
// Add them to the i18n system (if possible)
for (const key in esTranslations) {
// We're simulating adding translations to the cache
// This is a workaround and might need adaptation for your actual i18n system
const baseObj = window.i18n._getBaseObjectForKey ?
window.i18n._getBaseObjectForKey('es', key) : null;
if (baseObj) {
const lastKey = key.split('.').pop();
baseObj[lastKey] = esTranslations[key];
}
}
}
// Re-translate the page
if (window.i18n.translatePage) {
window.i18n.translatePage();
}
}
}, 500);
});
// Helper function to format file size
function formatFileSize(bytes) {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
}
document.addEventListener('DOMContentLoaded', () => {
// Variables y elementos DOM globales
const uploadBtn = document.getElementById('upload-btn');
const dropzone = document.getElementById('dropzone');
const fileInput = document.getElementById('file-input');
const filesGrid = document.getElementById('files-grid');
const filesListView = document.getElementById('files-list-view');
const newFolderBtn = document.getElementById('new-folder-btn');
const gridViewBtn = document.getElementById('grid-view-btn');
const listViewBtn = document.getElementById('list-view-btn');
// Variables para menús contextuales
let contextMenuTargetFolder = null;
let contextMenuTargetFile = null;
let selectedTargetFolderId = "";
let moveDialogMode = 'file'; // 'file' o 'folder'
// Current view state
let currentView = 'grid'; // 'grid' or 'list'
let currentPath = '';
// Crear los menús contextuales y diálogos
initializeContextMenus();
// Configurar listeners para arrastrar y soltar archivos
setupDragAndDrop();
// Configurar listeners de eventos para botones
setupEventListeners();
// Cargar la vista inicial
currentPath = '';
updateBreadcrumb('');
loadFiles();
/**
* Inicializa los menús contextuales y diálogos
*/
function initializeContextMenus() {
// Menú contextual para carpetas
if (!document.getElementById('folder-context-menu')) {
const folderMenu = document.createElement('div');
folderMenu.className = 'context-menu';
folderMenu.id = 'folder-context-menu';
folderMenu.innerHTML = `
<div class="context-menu-item" id="rename-folder-option">
<i class="fas fa-edit"></i> <span data-i18n="actions.rename">Renombrar</span>
</div>
<div class="context-menu-item" id="move-folder-option">
<i class="fas fa-exchange-alt"></i> <span data-i18n="actions.move">Mover a...</span>
</div>
<div class="context-menu-item" id="delete-folder-option">
<i class="fas fa-trash"></i> <span data-i18n="actions.delete">Eliminar</span>
</div>
`;
document.body.appendChild(folderMenu);
}
// Menú contextual para archivos
if (!document.getElementById('file-context-menu')) {
const fileMenu = document.createElement('div');
fileMenu.className = 'context-menu';
fileMenu.id = 'file-context-menu';
fileMenu.innerHTML = `
<div class="context-menu-item" id="move-file-option">
<i class="fas fa-exchange-alt"></i> <span data-i18n="actions.move">Mover a...</span>
</div>
<div class="context-menu-item" id="delete-file-option">
<i class="fas fa-trash"></i> <span data-i18n="actions.delete">Eliminar</span>
</div>
`;
document.body.appendChild(fileMenu);
}
// Diálogo de renombrar
if (!document.getElementById('rename-dialog')) {
const renameDialog = document.createElement('div');
renameDialog.className = 'rename-dialog';
renameDialog.id = 'rename-dialog';
renameDialog.innerHTML = `
<div class="rename-dialog-content">
<div class="rename-dialog-header" data-i18n="dialogs.rename_folder">Renombrar carpeta</div>
<input type="text" id="rename-input" data-i18n-placeholder="dialogs.new_name" placeholder="Nuevo nombre">
<div class="rename-dialog-buttons">
<button class="btn" id="rename-cancel-btn" data-i18n="actions.cancel">Cancelar</button>
<button class="btn btn-primary" id="rename-confirm-btn" data-i18n="actions.rename">Renombrar</button>
</div>
</div>
`;
document.body.appendChild(renameDialog);
}
// Diálogo de mover
if (!document.getElementById('move-file-dialog')) {
const moveDialog = document.createElement('div');
moveDialog.className = 'rename-dialog';
moveDialog.id = 'move-file-dialog';
moveDialog.innerHTML = `
<div class="rename-dialog-content">
<div class="rename-dialog-header" data-i18n="dialogs.move_file">Mover archivo</div>
<p data-i18n="dialogs.select_destination">Selecciona la carpeta destino:</p>
<div id="folder-select-container" style="max-height: 200px; overflow-y: auto; margin: 15px 0; border: 1px solid #ddd; border-radius: 4px; padding: 10px;">
<!-- Las carpetas se cargarán aquí dinámicamente -->
<div class="folder-select-item" data-folder-id="">
<i class="fas fa-folder"></i> <span data-i18n="dialogs.root">Raíz</span>
</div>
</div>
<div class="rename-dialog-buttons">
<button class="btn" id="move-cancel-btn" data-i18n="actions.cancel">Cancelar</button>
<button class="btn btn-primary" id="move-confirm-btn" data-i18n="actions.move_to">Mover</button>
</div>
</div>
`;
document.body.appendChild(moveDialog);
}
// Asignar eventos a los menús y diálogos después de crearlos
assignMenuEvents();
}
/**
* Asigna eventos a los menús contextuales y diálogos
*/
function assignMenuEvents() {
// Variables para elementos de los menús y diálogos
const folderContextMenu = document.getElementById('folder-context-menu');
const fileContextMenu = document.getElementById('file-context-menu');
const renameDialog = document.getElementById('rename-dialog');
const renameInput = document.getElementById('rename-input');
const renameCancelBtn = document.getElementById('rename-cancel-btn');
const renameConfirmBtn = document.getElementById('rename-confirm-btn');
const moveFileDialog = document.getElementById('move-file-dialog');
const folderSelectContainer = document.getElementById('folder-select-container');
const moveCancelBtn = document.getElementById('move-cancel-btn');
const moveConfirmBtn = document.getElementById('move-confirm-btn');
// Eventos para el menú contextual de carpetas
document.getElementById('rename-folder-option').addEventListener('click', () => {
if (contextMenuTargetFolder) {
showRenameDialog(contextMenuTargetFolder);
}
closeContextMenu();
});
document.getElementById('move-folder-option').addEventListener('click', () => {
if (contextMenuTargetFolder) {
showMoveDialog(contextMenuTargetFolder, 'folder');
}
closeContextMenu();
});
document.getElementById('delete-folder-option').addEventListener('click', async () => {
if (contextMenuTargetFolder) {
if (confirm(`¿Estás seguro de que quieres eliminar la carpeta "${contextMenuTargetFolder.name}" y todo su contenido?`)) {
try {
const response = await fetch(`/api/folders/${contextMenuTargetFolder.id}`, {
method: 'DELETE'
});
if (response.ok) {
// Si estamos dentro de la carpeta que acabamos de eliminar, volver al nivel superior
if (currentPath === contextMenuTargetFolder.id) {
currentPath = '';
updateBreadcrumb('');
}
loadFiles();
} else {
alert('Error al eliminar la carpeta');
}
} catch (error) {
console.error('Error deleting folder:', error);
alert('Error al eliminar la carpeta');
}
}
}
closeContextMenu();
});
// Eventos para el menú contextual de archivos
document.getElementById('move-file-option').addEventListener('click', () => {
if (contextMenuTargetFile) {
showMoveDialog(contextMenuTargetFile, 'file');
}
closeFileContextMenu();
});
document.getElementById('delete-file-option').addEventListener('click', async () => {
if (contextMenuTargetFile) {
if (confirm(`¿Estás seguro de que quieres eliminar el archivo "${contextMenuTargetFile.name}"?`)) {
try {
const response = await fetch(`/api/files/${contextMenuTargetFile.id}`, {
method: 'DELETE'
});
if (response.ok) {
loadFiles();
} else {
alert('Error al eliminar el archivo');
}
} catch (error) {
console.error('Error deleting file:', error);
alert('Error al eliminar el archivo');
}
}
}
closeFileContextMenu();
});
// Eventos para el diálogo de renombrar
renameCancelBtn.addEventListener('click', closeRenameDialog);
renameConfirmBtn.addEventListener('click', renameFolder);
// Eventos para el diálogo de mover
moveCancelBtn.addEventListener('click', closeMoveDialog);
moveConfirmBtn.addEventListener('click', async () => {
if (moveDialogMode === 'file' && contextMenuTargetFile) {
const success = await moveFile(contextMenuTargetFile.id, selectedTargetFolderId);
if (success) {
closeMoveDialog();
}
} else if (moveDialogMode === 'folder' && contextMenuTargetFolder) {
const success = await moveFolder(contextMenuTargetFolder.id, selectedTargetFolderId);
if (success) {
closeMoveDialog();
}
}
});
// Evento para renombrar al presionar Enter en el input
renameInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
renameFolder();
} else if (e.key === 'Escape') {
closeRenameDialog();
}
});
// Cerrar menús al hacer clic en cualquier parte
document.addEventListener('click', (e) => {
if (folderContextMenu && folderContextMenu.style.display === 'block' &&
!folderContextMenu.contains(e.target)) {
closeContextMenu();
}
if (fileContextMenu && fileContextMenu.style.display === 'block' &&
!fileContextMenu.contains(e.target)) {
closeFileContextMenu();
}
});
}
/**
* Configura los listeners para arrastrar y soltar archivos
*/
function setupDragAndDrop() {
// Arrastrar y soltar en el elemento dropzone
dropzone.addEventListener('dragover', (e) => {
e.preventDefault();
dropzone.classList.add('active');
});
dropzone.addEventListener('dragleave', () => {
dropzone.classList.remove('active');
});
dropzone.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('active');
if (e.dataTransfer.files.length > 0) {
uploadFiles(e.dataTransfer.files);
}
});
// Arrastrar y soltar en toda la página
document.addEventListener('dragover', (e) => {
e.preventDefault();
// Mostrar la zona de soltar cuando se arrastra algo sobre la página
if (e.dataTransfer.types.includes('Files')) {
dropzone.style.display = 'block';
dropzone.classList.add('active');
}
});
document.addEventListener('dragleave', (e) => {
// Solo ocultar si el ratón sale completamente de la ventana
if (e.clientX <= 0 || e.clientY <= 0 ||
e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) {
dropzone.classList.remove('active');
// Opcional: ocultar después de un breve retraso
setTimeout(() => {
if (!dropzone.classList.contains('active')) {
dropzone.style.display = 'none';
}
}, 100);
}
});
document.addEventListener('drop', (e) => {
e.preventDefault();
dropzone.classList.remove('active');
if (e.dataTransfer.files.length > 0) {
uploadFiles(e.dataTransfer.files);
}
// Ocultar la zona después de soltar
setTimeout(() => {
dropzone.style.display = 'none';
}, 500);
});
}
/**
* Configura los listeners de eventos para botones
*/
function setupEventListeners() {
// Toggle dropzone visibility
uploadBtn.addEventListener('click', () => {
dropzone.style.display = dropzone.style.display === 'none' ? 'block' : 'none';
if (dropzone.style.display === 'block') {
fileInput.click();
}
});
// File input change event
fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
uploadFiles(e.target.files);
}
});
// New folder button
newFolderBtn.addEventListener('click', () => {
const folderName = prompt('Nombre de la carpeta:');
if (folderName) {
createFolder(folderName);
}
});
// View toggle event listeners
gridViewBtn.addEventListener('click', switchToGridView);
listViewBtn.addEventListener('click', switchToListView);
// Load saved view preference
const savedView = localStorage.getItem('oxicloud-view');
if (savedView === 'list') {
switchToListView();
}
}
/**
* Cambia a vista de cuadrícula
*/
function switchToGridView() {
filesGrid.style.display = 'grid';
filesListView.style.display = 'none';
gridViewBtn.classList.add('active');
listViewBtn.classList.remove('active');
currentView = 'grid';
localStorage.setItem('oxicloud-view', 'grid');
}
/**
* Cambia a vista de lista
*/
function switchToListView() {
filesGrid.style.display = 'none';
filesListView.style.display = 'flex';
gridViewBtn.classList.remove('active');
listViewBtn.classList.add('active');
currentView = 'list';
localStorage.setItem('oxicloud-view', 'list');
}
/**
* Cierra el menú contextual de carpetas
*/
function closeContextMenu() {
const menu = document.getElementById('folder-context-menu');
if (menu) {
menu.style.display = 'none';
contextMenuTargetFolder = null;
}
}
/**
* Cierra el menú contextual de archivos
*/
function closeFileContextMenu() {
const menu = document.getElementById('file-context-menu');
if (menu) {
menu.style.display = 'none';
contextMenuTargetFile = null;
}
}
/**
* Muestra el diálogo de renombrar para una carpeta
*/
function showRenameDialog(folder) {
const renameInput = document.getElementById('rename-input');
const renameDialog = document.getElementById('rename-dialog');
renameInput.value = folder.name;
renameDialog.style.display = 'flex';
renameInput.focus();
renameInput.select();
}
/**
* Cierra el diálogo de renombrar
*/
function closeRenameDialog() {
document.getElementById('rename-dialog').style.display = 'none';
contextMenuTargetFolder = null;
}
/**
* Muestra el diálogo de mover para un archivo o carpeta
*/
async function showMoveDialog(item, mode) {
// Establecer el modo (archivo o carpeta)
moveDialogMode = mode;
// Restablecer la selección
selectedTargetFolderId = "";
// Actualizar título del diálogo
const dialogHeader = document.getElementById('move-file-dialog').querySelector('.rename-dialog-header');
dialogHeader.textContent = mode === 'file' ?
(window.i18n ? window.i18n.t('dialogs.move_file') : 'Mover archivo') :
(window.i18n ? window.i18n.t('dialogs.move_folder') : 'Mover carpeta');
// Cargar todas las carpetas disponibles
await loadAllFolders(item.id, mode);
// Mostrar el diálogo
document.getElementById('move-file-dialog').style.display = 'flex';
}
/**
* Cierra el diálogo de mover
*/
function closeMoveDialog() {
document.getElementById('move-file-dialog').style.display = 'none';
contextMenuTargetFile = null;
contextMenuTargetFolder = null;
}
/**
* Renombra la carpeta seleccionada
*/
async function renameFolder() {
if (!contextMenuTargetFolder) return;
const newName = document.getElementById('rename-input').value.trim();
if (!newName) {
alert(window.i18n ? window.i18n.t('errors.empty_name') : 'El nombre no puede estar vacío');
return;
}
try {
console.log(`Renaming folder ${contextMenuTargetFolder.id} to "${newName}"`);
const response = await fetch(`/api/folders/${contextMenuTargetFolder.id}/rename`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: newName })
});
console.log('Response status:', response.status);
if (response.ok) {
// Cerramos el diálogo y recargamos los archivos
closeRenameDialog();
loadFiles();
} else {
const errorText = await response.text();
console.error('Error response:', errorText);
let errorMessage = 'Error desconocido';
try {
// Intentar parsear como JSON
const errorData = JSON.parse(errorText);
errorMessage = errorData.error || response.statusText;
} catch (e) {
// Si no es JSON, usar el texto tal cual
errorMessage = errorText || response.statusText;
}
alert(`Error al renombrar la carpeta: ${errorMessage}`);
}
} catch (error) {
console.error('Error renaming folder:', error);
alert('Error al renombrar la carpeta. Inténtalo de nuevo.');
}
}
/**
* Carga todas las carpetas para el diálogo de mover
*/
async function loadAllFolders(itemId, mode) {
try {
const response = await fetch('/api/folders');
if (response.ok) {
const folders = await response.json();
const folderSelectContainer = document.getElementById('folder-select-container');
// Limpiar el contenedor excepto la opción de carpeta raíz
folderSelectContainer.innerHTML = `
<div class="folder-select-item selected" data-folder-id="">
<i class="fas fa-folder"></i> <span data-i18n="dialogs.root">Raíz</span>
</div>
`;
// Seleccionar la raíz por defecto
selectedTargetFolderId = "";
// Añadir todas las carpetas disponibles
if (Array.isArray(folders)) {
folders.forEach(folder => {
// Si es una carpeta, no mostrar:
// 1. La propia carpeta que se mueve
// 2. Ninguna subcarpeta de la carpeta que se mueve (para evitar ciclos)
if (mode === 'folder' && folder.id === itemId) {
return;
}
// No mostrar la carpeta actual del elemento como opción
if (mode === 'file' && contextMenuTargetFile && folder.id === contextMenuTargetFile.folder_id) {
return;
}
if (mode === 'folder' && contextMenuTargetFolder && folder.id === contextMenuTargetFolder.folder_id) {
return;
}
const folderItem = document.createElement('div');
folderItem.className = 'folder-select-item';
folderItem.dataset.folderId = folder.id;
folderItem.innerHTML = `<i class="fas fa-folder"></i> ${folder.name}`;
folderItem.addEventListener('click', () => {
// Deseleccionar todos
document.querySelectorAll('.folder-select-item').forEach(item => {
item.classList.remove('selected');
});
// Seleccionar este
folderItem.classList.add('selected');
selectedTargetFolderId = folder.id;
});
folderSelectContainer.appendChild(folderItem);
});
}
// Evento para la opción de carpeta raíz
const rootOption = folderSelectContainer.querySelector('.folder-select-item');
rootOption.addEventListener('click', () => {
document.querySelectorAll('.folder-select-item').forEach(item => {
item.classList.remove('selected');
});
rootOption.classList.add('selected');
selectedTargetFolderId = "";
});
// Re-traducir la página para aplicar las traducciones a los nuevos elementos
if (window.i18n && window.i18n.translatePage) {
window.i18n.translatePage();
}
}
} catch (error) {
console.error('Error loading folders:', error);
}
}
/**
* Mueve un archivo a otra carpeta
*/
async function moveFile(fileId, targetFolderId) {
try {
const response = await fetch(`/api/files/${fileId}/move`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
folder_id: targetFolderId === "" ? null : targetFolderId
})
});
if (response.ok) {
// Recargar archivos después de mover
await loadFiles();
return true;
} else {
let errorMessage = 'Error desconocido';
try {
const errorData = await response.json();
errorMessage = errorData.error || 'Error desconocido';
} catch (e) {
errorMessage = 'Error al procesar la respuesta del servidor';
}
alert(`Error al mover el archivo: ${errorMessage}`);
return false;
}
} catch (error) {
console.error('Error moving file:', error);
alert('Error al mover el archivo. Inténtalo de nuevo.');
return false;
}
}
/**
* Mueve una carpeta a otra carpeta
*/
async function moveFolder(folderId, targetFolderId) {
try {
const response = await fetch(`/api/folders/${folderId}/move`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
parent_id: targetFolderId === "" ? null : targetFolderId
})
});
if (response.ok) {
// Recargar archivos después de mover
await loadFiles();
return true;
} else {
let errorMessage = 'Error desconocido';
try {
const errorData = await response.json();
errorMessage = errorData.error || 'Error desconocido';
} catch (e) {
errorMessage = 'Error al procesar la respuesta del servidor';
}
alert(`Error al mover la carpeta: ${errorMessage}`);
return false;
}
} catch (error) {
console.error('Error moving folder:', error);
alert('Error al mover la carpeta. Inténtalo de nuevo.');
return false;
}
}
/**
* Crea una nueva carpeta
*/
async function createFolder(name) {
try {
const response = await fetch('/api/folders', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: name,
parent_id: currentPath || null
})
});
if (response.ok) {
loadFiles();
} else {
alert('Error al crear la carpeta');
}
} catch (error) {
console.error('Error creating folder:', error);
alert('Error al crear la carpeta');
}
}
/**
* Actualiza la información de ruta (breadcrumb)
*/
function updateBreadcrumb(folderName) {
const breadcrumb = document.querySelector('.breadcrumb');
breadcrumb.innerHTML = '';
const homeItem = document.createElement('span');
homeItem.className = 'breadcrumb-item';
homeItem.textContent = window.i18n ? window.i18n.t('breadcrumb.home') : 'Home';
homeItem.addEventListener('click', () => {
currentPath = '';
updateBreadcrumb('');
loadFiles();
});
breadcrumb.appendChild(homeItem);
if (folderName) {
const separator = document.createElement('span');
separator.className = 'breadcrumb-separator';
separator.textContent = '>';
breadcrumb.appendChild(separator);
const folderItem = document.createElement('span');
folderItem.className = 'breadcrumb-item';
folderItem.textContent = folderName;
breadcrumb.appendChild(folderItem);
}
}
/**
* Carga archivos y carpetas
*/
async function loadFiles() {
try {
let url = '/api/folders';
if (currentPath) {
url += `/${currentPath}`;
}
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Server responded with status: ${response.status}`);
}
const folders = await response.json();
// Clear existing files in both views
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.modified">Modificado</div>
</div>
`;
// Re-traducir la página para aplicar las traducciones a los nuevos elementos de la cabecera
if (window.i18n && window.i18n.translatePage) {
window.i18n.translatePage();
}
// Add folders (check if it's an array)
const folderList = Array.isArray(folders) ? folders : [];
folderList.forEach(folder => {
addFolderToView(folder);
});
// Also load files in this folder
let filesUrl = '/api/files';
if (currentPath) {
filesUrl += `?folder_id=${currentPath}`;
}
try {
const filesResponse = await fetch(filesUrl);
if (filesResponse.ok) {
const files = await filesResponse.json();
// Add files (check if it's an array)
const fileList = Array.isArray(files) ? files : [];
fileList.forEach(file => {
addFileToView(file);
});
}
} catch (error) {
console.error('Error loading files:', error);
// File API may not be implemented yet, so we silently ignore this error
}
} catch (error) {
console.error('Error loading files:', error);
}
}
/**
* Añade una carpeta a las vistas
*/
function addFolderToView(folder) {
// Create folder element for grid view
const folderGridElement = document.createElement('div');
folderGridElement.className = 'file-card';
folderGridElement.dataset.folderId = folder.id;
folderGridElement.dataset.folderName = folder.name;
folderGridElement.dataset.parentId = folder.parent_id || "";
folderGridElement.innerHTML = `
<div class="file-icon folder-icon">
<i class="fas fa-folder"></i>
</div>
<div class="file-name">${folder.name}</div>
<div class="file-info">15 archivos</div>
`;
// Hacer la carpeta arrastrable (excepto si estamos en la misma carpeta que es su padre)
if (currentPath !== "") {
folderGridElement.setAttribute('draggable', 'true');
// Eventos de arrastrar
folderGridElement.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', folder.id);
e.dataTransfer.setData('application/oxicloud-folder', 'true');
folderGridElement.classList.add('dragging');
});
folderGridElement.addEventListener('dragend', () => {
folderGridElement.classList.remove('dragging');
document.querySelectorAll('.drop-target').forEach(el => {
el.classList.remove('drop-target');
});
});
}
// Evento de clic normal para navegar
folderGridElement.addEventListener('click', () => {
currentPath = folder.id;
updateBreadcrumb(folder.name);
loadFiles();
});
// Evento de clic derecho para el menú contextual
folderGridElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
// Guardamos la carpeta seleccionada
contextMenuTargetFolder = {
id: folder.id,
name: folder.name,
parent_id: folder.parent_id || ""
};
// Posicionamos y mostramos el menú contextual
let folderContextMenu = document.getElementById('folder-context-menu');
folderContextMenu.style.left = `${e.pageX}px`;
folderContextMenu.style.top = `${e.pageY}px`;
folderContextMenu.style.display = 'block';
});
// Eventos para arrastrar y soltar archivos en carpetas
folderGridElement.addEventListener('dragover', (e) => {
e.preventDefault();
folderGridElement.classList.add('drop-target');
});
folderGridElement.addEventListener('dragleave', () => {
folderGridElement.classList.remove('drop-target');
});
folderGridElement.addEventListener('drop', async (e) => {
e.preventDefault();
folderGridElement.classList.remove('drop-target');
const id = e.dataTransfer.getData('text/plain');
const isFolder = e.dataTransfer.getData('application/oxicloud-folder') === 'true';
if (id) {
if (isFolder) {
// Evitar mover una carpeta a sí misma o a su hijo
if (id === folder.id) {
alert("No puedes mover una carpeta a sí misma");
return;
}
await moveFolder(id, folder.id);
} else {
await moveFile(id, folder.id);
}
}
});
filesGrid.appendChild(folderGridElement);
// Create folder element for list view
const folderListElement = document.createElement('div');
folderListElement.className = 'file-item';
folderListElement.dataset.folderId = folder.id;
folderListElement.dataset.folderName = folder.name;
folderListElement.dataset.parentId = folder.parent_id || "";
// Convert timestamps to human readable format
const modifiedDate = new Date(folder.modified_at * 1000);
const formattedDate = modifiedDate.toLocaleDateString() + ' ' +
modifiedDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
// Hacer la carpeta arrastrable (excepto si estamos en la misma carpeta que es su padre)
if (currentPath !== "") {
folderListElement.setAttribute('draggable', 'true');
// Eventos de arrastrar
folderListElement.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', folder.id);
e.dataTransfer.setData('application/oxicloud-folder', 'true');
folderListElement.classList.add('dragging');
});
folderListElement.addEventListener('dragend', () => {
folderListElement.classList.remove('dragging');
document.querySelectorAll('.drop-target').forEach(el => {
el.classList.remove('drop-target');
});
});
}
folderListElement.innerHTML = `
<div class="name-cell">
<div class="file-icon folder-icon">
<i class="fas fa-folder"></i>
</div>
<span>${folder.name}</span>
</div>
<div>Carpeta</div>
<div>--</div>
<div>${formattedDate}</div>
`;
// Evento de clic normal para navegar
folderListElement.addEventListener('click', () => {
currentPath = folder.id;
updateBreadcrumb(folder.name);
loadFiles();
});
// Evento de clic derecho para el menú contextual
folderListElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
// Guardamos la carpeta seleccionada
contextMenuTargetFolder = {
id: folder.id,
name: folder.name,
parent_id: folder.parent_id || ""
};
// Posicionamos y mostramos el menú contextual
let folderContextMenu = document.getElementById('folder-context-menu');
folderContextMenu.style.left = `${e.pageX}px`;
folderContextMenu.style.top = `${e.pageY}px`;
folderContextMenu.style.display = 'block';
});
// Eventos para arrastrar y soltar archivos en carpetas (vista lista)
folderListElement.addEventListener('dragover', (e) => {
e.preventDefault();
folderListElement.classList.add('drop-target');
});
folderListElement.addEventListener('dragleave', () => {
folderListElement.classList.remove('drop-target');
});
folderListElement.addEventListener('drop', async (e) => {
e.preventDefault();
folderListElement.classList.remove('drop-target');
const id = e.dataTransfer.getData('text/plain');
const isFolder = e.dataTransfer.getData('application/oxicloud-folder') === 'true';
if (id) {
if (isFolder) {
// Evitar mover una carpeta a sí misma o a su hijo
if (id === folder.id) {
alert("No puedes mover una carpeta a sí misma");
return;
}
await moveFolder(id, folder.id);
} else {
await moveFile(id, folder.id);
}
}
});
filesListView.appendChild(folderListElement);
}
/**
* Añade un archivo a las vistas
*/
function addFileToView(file) {
// Determine file icon and type label based on MIME type
let iconClass = 'fas fa-file';
let typeLabel = 'Documento';
if (file.mime_type) {
if (file.mime_type.startsWith('image/')) {
iconClass = 'fas fa-file-image';
typeLabel = 'Imagen';
} else if (file.mime_type.startsWith('text/')) {
iconClass = 'fas fa-file-alt';
typeLabel = 'Texto';
} else if (file.mime_type.startsWith('video/')) {
iconClass = 'fas fa-file-video';
typeLabel = 'Video';
} else if (file.mime_type.startsWith('audio/')) {
iconClass = 'fas fa-file-audio';
typeLabel = 'Audio';
} else if (file.mime_type === 'application/pdf') {
iconClass = 'fas fa-file-pdf';
typeLabel = 'PDF';
}
}
// Format file size
const fileSize = formatFileSize(file.size);
// Convert timestamp to human readable format
const modifiedDate = new Date(file.modified_at * 1000);
const formattedDate = modifiedDate.toLocaleDateString() + ' ' +
modifiedDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
// Create file element for grid view
const fileGridElement = document.createElement('div');
fileGridElement.className = 'file-card';
fileGridElement.innerHTML = `
<div class="file-icon">
<i class="${iconClass}"></i>
</div>
<div class="file-name">${file.name}</div>
<div class="file-info">Modificado ${formattedDate}</div>
`;
// Atributos de datos para el archivo
fileGridElement.dataset.fileId = file.id;
fileGridElement.dataset.fileName = file.name;
fileGridElement.dataset.folderId = file.folder_id || "";
// Hacer el archivo arrastrable para mover
fileGridElement.setAttribute('draggable', 'true');
// Eventos de arrastrar
fileGridElement.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', file.id);
fileGridElement.classList.add('dragging');
});
fileGridElement.addEventListener('dragend', () => {
fileGridElement.classList.remove('dragging');
// Eliminar la clase drop-target de todas las carpetas
document.querySelectorAll('.drop-target').forEach(el => {
el.classList.remove('drop-target');
});
});
// Add download functionality
fileGridElement.addEventListener('click', () => {
window.location.href = `/api/files/${file.id}`;
});
// Menú contextual para el archivo
fileGridElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
// Guardamos el archivo seleccionado
contextMenuTargetFile = {
id: file.id,
name: file.name,
folder_id: file.folder_id || ""
};
// Posicionamos y mostramos el menú contextual
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);
// Create file element for list view
const fileListElement = document.createElement('div');
fileListElement.className = 'file-item';
fileListElement.dataset.fileId = file.id;
fileListElement.dataset.fileName = file.name;
fileListElement.dataset.folderId = file.folder_id || "";
fileListElement.innerHTML = `
<div class="name-cell">
<div class="file-icon">
<i class="${iconClass}"></i>
</div>
<span>${file.name}</span>
</div>
<div>${typeLabel}</div>
<div>${fileSize}</div>
<div>${formattedDate}</div>
`;
// Hacer el archivo arrastrable para mover (vista lista)
fileListElement.setAttribute('draggable', 'true');
// Eventos de arrastrar (vista lista)
fileListElement.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', file.id);
fileListElement.classList.add('dragging');
});
fileListElement.addEventListener('dragend', () => {
fileListElement.classList.remove('dragging');
document.querySelectorAll('.drop-target').forEach(el => {
el.classList.remove('drop-target');
});
});
// Add download functionality
fileListElement.addEventListener('click', () => {
window.location.href = `/api/files/${file.id}`;
});
// Menú contextual para el archivo (vista lista)
fileListElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
// Una vez creado el menú, asignamos el archivo objetivo
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);
}
/**
* Sube archivos al servidor
*/
async function uploadFiles(files) {
const progressBar = document.querySelector('.progress-fill');
const uploadProgressDiv = document.querySelector('.upload-progress');
uploadProgressDiv.style.display = 'block';
progressBar.style.width = '0%';
let uploadedCount = 0;
const totalFiles = files.length;
for (let i = 0; i < totalFiles; i++) {
const file = files[i];
const formData = new FormData();
formData.append('file', file);
if (currentPath) {
formData.append('folder_id', currentPath);
}
try {
// Usar fetch en lugar de XMLHttpRequest para una mejor gestión de errores
const response = await fetch('/api/files/upload', {
method: 'POST',
body: formData
});
// Actualizar progreso
uploadedCount++;
const percentComplete = (uploadedCount / totalFiles) * 100;
progressBar.style.width = percentComplete + '%';
if (response.ok) {
console.log(`Successfully uploaded ${file.name}`);
if (i === totalFiles - 1) {
// Last file uploaded
loadFiles();
setTimeout(() => {
dropzone.style.display = 'none';
uploadProgressDiv.style.display = 'none';
}, 1000);
// Mostrar notificación de éxito
showNotification('Archivo subido', `${file.name} completado`);
}
} else {
const errorData = await response.text();
console.error('Upload error:', errorData);
alert(`Error al subir el archivo: ${file.name}`);
}
} catch (error) {
console.error('Network error during upload:', error);
alert(`Error de red al subir el archivo: ${file.name}`);
}
}
}
/**
* Muestra una notificación
*/
function showNotification(title, message) {
// Comprobar si ya existe la notificación, de lo contrario crearla
let notification = document.querySelector('.notification');
if (!notification) {
notification = document.createElement('div');
notification.className = 'notification';
notification.innerHTML = `
<div class="notification-title">${title}</div>
<div class="notification-message">${message}</div>
`;
document.body.appendChild(notification);
} else {
notification.querySelector('.notification-title').textContent = title;
notification.querySelector('.notification-message').textContent = message;
}
// Mostrar la notificación
notification.style.display = 'block';
// Ocultar la notificación después de 5 segundos
setTimeout(() => {
notification.style.display = 'none';
}, 5000);
}
// Exponer la función selectFolder globalmente para usarla en el breadcrumb
window.selectFolder = (id, name) => {
currentPath = id;
updateBreadcrumb(name);
loadFiles();
};
});
</script>
</body>
</html>