adding features

This commit is contained in:
DioCrafts
2025-03-19 23:28:29 +01:00
parent 6e055c3043
commit 961545aef7
19 changed files with 2396 additions and 2190 deletions
+929
View File
@@ -0,0 +1,929 @@
/* 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;
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 30px;
justify-content: space-between;
}
.search-container {
flex-grow: 1;
max-width: 500px;
position: relative;
margin-right: 20px;
}
.search-container input {
width: 100%;
padding: 10px 15px 10px 40px;
border-radius: 50px;
border: none;
background-color: #f0f3f7;
font-size: 14px;
height: 40px;
}
.search-icon {
position: absolute;
left: 15px;
top: 50%;
transform: translateY(-50%);
color: #8895a7;
font-size: 16px;
}
.user-controls {
display: flex;
align-items: center;
}
.language-selector {
margin-right: 15px;
padding: 5px 12px;
background-color: #f0f3f7;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
display: flex;
align-items: center;
}
.language-selector::after {
content: "▼";
font-size: 8px;
margin-left: 5px;
color: #718096;
}
.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;
}
/* Estilos para las carpetas como en el mockup */
.file-icon.folder-icon {
width: 100px;
height: 70px;
background-color: #ffeaa7; /* Color amarillo claro */
border-radius: 8px;
position: relative;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.file-icon.folder-icon::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 20px;
background-color: #fdcb6e; /* Color amarillo más oscuro para la pestaña */
border-radius: 8px 8px 0 0;
}
.file-icon.folder-icon i {
display: none; /* Ocultar el icono Font Awesome */
}
/* Estilo para documentos */
.file-icon.doc-icon {
width: 100px;
height: 70px;
background-color: #e2e8f0; /* Fondo gris claro */
border-radius: 4px;
position: relative;
margin-bottom: 10px;
}
.file-icon.doc-icon::before {
content: "";
position: absolute;
top: 15px;
left: 20px;
right: 20px;
height: 4px;
background-color: #a0aec0;
border-radius: 2px;
}
.file-icon.doc-icon::after {
content: "";
position: absolute;
top: 25px;
left: 20px;
right: 30px;
height: 4px;
background-color: #a0aec0;
border-radius: 2px;
}
/* Estilo para imágenes */
.file-icon.image-icon {
width: 100px;
height: 70px;
background-color: #74b9ff; /* Fondo azul claro */
border-radius: 4px;
position: relative;
margin-bottom: 10px;
overflow: hidden;
}
.file-icon.image-icon::before {
content: "";
position: absolute;
top: 10px;
left: 15px;
width: 20px;
height: 20px;
background-color: #ffda79; /* Círculo amarillo como un sol */
border-radius: 50%;
}
/* Estilo para videos */
.file-icon.video-icon {
width: 100px;
height: 70px;
background-color: #111; /* Fondo negro */
border-radius: 4px;
position: relative;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.file-icon.video-icon::before {
content: "";
width: 0;
height: 0;
border-top: 15px solid transparent;
border-bottom: 15px solid transparent;
border-left: 20px solid white; /* Triángulo de reproducción */
}
/* Estilos para archivos de código */
.file-icon.code-icon {
width: 100px;
height: 70px;
background-color: #e2e8f0; /* Fondo gris claro */
border-radius: 4px;
position: relative;
margin-bottom: 10px;
border-top: 3px solid #556ee6; /* Borde superior azul */
overflow: hidden;
}
/* Líneas que simulan código */
.file-icon.code-icon::before,
.file-icon.code-icon::after {
content: "";
position: absolute;
left: 10px;
right: 10px;
height: 2px;
background-color: #556ee6; /* Color azul para el código */
}
.file-icon.code-icon::before {
top: 15px;
width: 80%;
}
.file-icon.code-icon::after {
top: 25px;
width: 60%;
}
/* Agregar líneas adicionales para simular código */
.file-icon.code-icon .code-line-1,
.file-icon.code-icon .code-line-2,
.file-icon.code-icon .code-line-3 {
position: absolute;
left: 10px;
height: 2px;
background-color: #a0aec0; /* Color gris para el código */
}
.file-icon.code-icon .code-line-1 {
top: 35px;
width: 70%;
}
.file-icon.code-icon .code-line-2 {
top: 45px;
width: 85%;
}
.file-icon.code-icon .code-line-3 {
top: 55px;
width: 50%;
}
/* Colores específicos para distintos tipos de archivo de código */
.file-icon.json-icon {
border-top-color: #ffb86c; /* Naranja para JSON */
}
.file-icon.json-icon::before,
.file-icon.json-icon::after {
background-color: #ffb86c;
}
.file-icon.js-icon {
border-top-color: #ffd43b; /* Amarillo para JavaScript */
}
.file-icon.js-icon::before,
.file-icon.js-icon::after {
background-color: #ffd43b;
}
.file-icon.html-icon {
border-top-color: #e34c26; /* Rojo para HTML */
}
.file-icon.html-icon::before,
.file-icon.html-icon::after {
background-color: #e34c26;
}
.file-icon.css-icon {
border-top-color: #2965f1; /* Azul para CSS */
}
.file-icon.css-icon::before,
.file-icon.css-icon::after {
background-color: #2965f1;
}
.file-icon.py-icon {
border-top-color: #3776ab; /* Azul oscuro para Python */
}
.file-icon.py-icon::before,
.file-icon.py-icon::after {
background-color: #3776ab;
}
.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 - Estilo mejorado */
.files-list-view {
display: flex;
flex-direction: column;
width: 100%;
border-radius: 10px;
overflow: hidden;
background-color: white;
box-shadow: 0 1px 3px rgba(0,0,0,0.05);
}
.list-header {
display: grid;
grid-template-columns: minmax(200px, 2fr) 1fr 1fr 120px;
padding: 15px;
font-weight: 600;
color: #2d3748;
background-color: #f8f9fa;
border-bottom: 1px solid #e0e6ed;
}
.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;
background-color: white;
}
.file-item:hover {
background-color: #f0f8ff;
}
.file-item .name-cell {
display: flex;
align-items: center;
gap: 12px;
}
.file-item .file-icon {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 8px;
font-size: 20px;
margin-bottom: 0;
}
.file-item .file-icon.folder-icon {
background-color: #ffeaa7;
position: relative;
width: 36px;
height: 36px;
margin-bottom: 0;
}
.file-item .file-icon.folder-icon::before {
content: "";
position: absolute;
top: 0;
left: 0;
right: 0;
height: 8px;
background-color: #fdcb6e;
border-radius: 8px 8px 0 0;
}
.file-item .file-icon.folder-icon i {
display: none;
}
/* Iconos específicos para archivos en vista de lista */
.file-item .file-icon.pdf-icon {
background-color: #fee2e2;
}
.file-item .file-icon.pdf-icon i {
color: #e53e3e;
}
.file-item .file-icon.image-icon {
background-color: #e0f2fe;
}
.file-item .file-icon.image-icon i {
color: #3b82f6;
}
.file-item .file-icon.video-icon {
background-color: #e2e8f0;
}
.file-item .file-icon.video-icon i {
color: #ef4444;
}
.file-item .file-icon.audio-icon {
background-color: #fef3c7;
}
.file-item .file-icon.audio-icon i {
color: #f59e0b;
}
.file-item .file-icon.text-icon {
background-color: #f3f4f6;
}
.file-item .file-icon.text-icon i {
color: #6b7280;
}
.file-item .date-cell {
color: #718096;
font-size: 14px;
}
.file-item .size-cell {
color: #718096;
font-size: 14px;
text-align: right;
}
.file-item .type-cell {
color: #4b5563;
font-weight: 500;
font-size: 14px;
}
.file-item.dragging {
opacity: 0.5;
background-color: #f9fafb;
}
.file-item.drop-target {
background-color: rgba(255, 193, 7, 0.1);
border: 2px dashed #ffc107;
}
/* 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;
}
/* Compartir diálogo */
.share-dialog {
position: absolute;
top: 50%;
right: 50px;
width: 250px;
background-color: white;
border-radius: 8px;
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
z-index: 1000;
padding: 15px;
}
.share-title {
font-weight: bold;
margin-bottom: 15px;
color: #2d3748;
padding-bottom: 10px;
border-bottom: 1px solid #e0e6ed;
}
.share-user {
display: flex;
align-items: center;
margin-bottom: 10px;
}
.user-avatar-small {
width: 24px;
height: 24px;
border-radius: 50%;
background-color: #dfe4ea;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
margin-right: 10px;
}
.user-name {
font-size: 13px;
color: #2d3748;
}
.add-user {
display: flex;
align-items: center;
justify-content: center;
padding: 8px;
margin-top: 10px;
background-color: #f0f3f7;
border-radius: 50px;
cursor: pointer;
font-size: 13px;
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;
}
}
/* Folder selection for move dialog */
.folder-select-item {
padding: 8px 10px;
margin-bottom: 5px;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
}
.folder-select-item:hover {
background-color: #f0f8ff;
}
.folder-select-item.selected {
background-color: #e6f7ff;
border-left: 3px solid #1890ff;
}
.folder-select-item i {
margin-right: 8px;
color: #ffc107;
}
+15 -2144
View File
File diff suppressed because it is too large Load Diff
+216
View File
@@ -0,0 +1,216 @@
/**
* OxiCloud - Main Application
* This file contains the core functionality, initialization and state management
*/
// Global state
const app = {
currentView: 'grid', // Current view mode: 'grid' or 'list'
currentPath: '', // Current folder path
currentFolder: null, // Current folder object
contextMenuTargetFolder: null, // Target folder for context menu
contextMenuTargetFile: null, // Target file for context menu
selectedTargetFolderId: "", // Selected target folder for move operations
moveDialogMode: 'file', // Move dialog mode: 'file' or 'folder'
};
// DOM elements
const elements = {
// Will be populated on initialization
};
/**
* Initialize the application
*/
function initApp() {
// Cache DOM elements
cacheElements();
// Create menus and dialogs
ui.initializeContextMenus();
// Setup event listeners
setupEventListeners();
// Load initial view
app.currentPath = '';
ui.updateBreadcrumb('');
loadFiles();
// Initialize file renderer if available
if (window.fileRenderer) {
console.log('Using optimized file renderer');
} else {
console.log('Using standard file rendering');
}
}
/**
* Cache DOM elements for faster access
*/
function cacheElements() {
elements.uploadBtn = document.getElementById('upload-btn');
elements.dropzone = document.getElementById('dropzone');
elements.fileInput = document.getElementById('file-input');
elements.filesGrid = document.getElementById('files-grid');
elements.filesListView = document.getElementById('files-list-view');
elements.newFolderBtn = document.getElementById('new-folder-btn');
elements.gridViewBtn = document.getElementById('grid-view-btn');
elements.listViewBtn = document.getElementById('list-view-btn');
elements.breadcrumb = document.querySelector('.breadcrumb');
}
/**
* Setup event listeners for main UI elements
*/
function setupEventListeners() {
// Set up drag and drop
ui.setupDragAndDrop();
// Upload button
elements.uploadBtn.addEventListener('click', () => {
elements.dropzone.style.display = elements.dropzone.style.display === 'none' ? 'block' : 'none';
if (elements.dropzone.style.display === 'block') {
elements.fileInput.click();
}
});
// File input
elements.fileInput.addEventListener('change', (e) => {
if (e.target.files.length > 0) {
fileOps.uploadFiles(e.target.files);
}
});
// New folder button
elements.newFolderBtn.addEventListener('click', () => {
const folderName = prompt(window.i18n ? window.i18n.t('dialogs.new_name') : 'Nombre de la carpeta:');
if (folderName) {
fileOps.createFolder(folderName);
}
});
// View toggle
elements.gridViewBtn.addEventListener('click', ui.switchToGridView);
elements.listViewBtn.addEventListener('click', ui.switchToListView);
// Load saved view preference
const savedView = localStorage.getItem('oxicloud-view');
if (savedView === 'list') {
ui.switchToListView();
}
// Global events to close context menus
document.addEventListener('click', (e) => {
const folderMenu = document.getElementById('folder-context-menu');
const fileMenu = document.getElementById('file-context-menu');
if (folderMenu && folderMenu.style.display === 'block' &&
!folderMenu.contains(e.target)) {
ui.closeContextMenu();
}
if (fileMenu && fileMenu.style.display === 'block' &&
!fileMenu.contains(e.target)) {
ui.closeFileContextMenu();
}
});
}
/**
* Load files and folders for the current path
*/
async function loadFiles() {
try {
let url = '/api/folders';
if (app.currentPath) {
url += `/${app.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
elements.filesGrid.innerHTML = '';
elements.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>
`;
// Translate the header if i18n is available
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 => {
ui.addFolderToView(folder);
});
// Also load files in this folder
let filesUrl = '/api/files';
if (app.currentPath) {
filesUrl += `?folder_id=${app.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 => {
ui.addFileToView(file);
});
}
} catch (error) {
console.error('Error loading files:', error);
// File API may not be implemented yet, so we silently ignore this error
}
// Update file icons based on file type
ui.updateFileIcons();
} catch (error) {
console.error('Error loading folders:', error);
ui.showNotification('Error', 'Could not load files and folders');
}
}
/**
* Format file size in human-readable format
* @param {number} bytes - Size in bytes
* @return {string} Formatted 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];
}
// Expose needed functions to global scope
window.app = app;
window.loadFiles = loadFiles;
window.formatFileSize = formatFileSize;
// Set up global selectFolder function for navigation
window.selectFolder = (id, name) => {
app.currentPath = id;
ui.updateBreadcrumb(name);
loadFiles();
};
// Initialize app when DOM is ready
document.addEventListener('DOMContentLoaded', initApp);
+256
View File
@@ -0,0 +1,256 @@
/**
* OxiCloud - Context Menus and Dialogs Module
* This file handles context menus and dialog functionality
*/
// Context Menus Module
const contextMenus = {
/**
* Assign events to menu items and dialogs
*/
assignMenuEvents() {
// Folder context menu options
document.getElementById('rename-folder-option').addEventListener('click', () => {
if (window.app.contextMenuTargetFolder) {
this.showRenameDialog(window.app.contextMenuTargetFolder);
}
window.ui.closeContextMenu();
});
document.getElementById('move-folder-option').addEventListener('click', () => {
if (window.app.contextMenuTargetFolder) {
this.showMoveDialog(window.app.contextMenuTargetFolder, 'folder');
}
window.ui.closeContextMenu();
});
document.getElementById('delete-folder-option').addEventListener('click', async () => {
if (window.app.contextMenuTargetFolder) {
await window.fileOps.deleteFolder(
window.app.contextMenuTargetFolder.id,
window.app.contextMenuTargetFolder.name
);
}
window.ui.closeContextMenu();
});
// File context menu options
document.getElementById('move-file-option').addEventListener('click', () => {
if (window.app.contextMenuTargetFile) {
this.showMoveDialog(window.app.contextMenuTargetFile, 'file');
}
window.ui.closeFileContextMenu();
});
document.getElementById('delete-file-option').addEventListener('click', async () => {
if (window.app.contextMenuTargetFile) {
await window.fileOps.deleteFile(
window.app.contextMenuTargetFile.id,
window.app.contextMenuTargetFile.name
);
}
window.ui.closeFileContextMenu();
});
// Rename dialog events
const renameCancelBtn = document.getElementById('rename-cancel-btn');
const renameConfirmBtn = document.getElementById('rename-confirm-btn');
const renameInput = document.getElementById('rename-input');
renameCancelBtn.addEventListener('click', this.closeRenameDialog);
renameConfirmBtn.addEventListener('click', this.renameFolder);
// Rename on Enter key
renameInput.addEventListener('keyup', (e) => {
if (e.key === 'Enter') {
this.renameFolder();
} else if (e.key === 'Escape') {
this.closeRenameDialog();
}
});
// Move dialog events
const moveCancelBtn = document.getElementById('move-cancel-btn');
const moveConfirmBtn = document.getElementById('move-confirm-btn');
moveCancelBtn.addEventListener('click', this.closeMoveDialog);
moveConfirmBtn.addEventListener('click', async () => {
if (window.app.moveDialogMode === 'file' && window.app.contextMenuTargetFile) {
const success = await window.fileOps.moveFile(
window.app.contextMenuTargetFile.id,
window.app.selectedTargetFolderId
);
if (success) {
this.closeMoveDialog();
}
} else if (window.app.moveDialogMode === 'folder' && window.app.contextMenuTargetFolder) {
const success = await window.fileOps.moveFolder(
window.app.contextMenuTargetFolder.id,
window.app.selectedTargetFolderId
);
if (success) {
this.closeMoveDialog();
}
}
});
},
/**
* Show rename dialog for a folder
* @param {Object} folder - Folder object
*/
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();
},
/**
* Close rename dialog
*/
closeRenameDialog() {
document.getElementById('rename-dialog').style.display = 'none';
window.app.contextMenuTargetFolder = null;
},
/**
* Show move dialog for a file or folder
* @param {Object} item - File or folder object
* @param {string} mode - 'file' or 'folder'
*/
async showMoveDialog(item, mode) {
// Set mode
window.app.moveDialogMode = mode;
// Reset selection
window.app.selectedTargetFolderId = "";
// Update dialog title
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');
// Load all available folders
await this.loadAllFolders(item.id, mode);
// Show dialog
document.getElementById('move-file-dialog').style.display = 'flex';
},
/**
* Close move dialog
*/
closeMoveDialog() {
document.getElementById('move-file-dialog').style.display = 'none';
window.app.contextMenuTargetFile = null;
window.app.contextMenuTargetFolder = null;
},
/**
* Rename the selected folder
*/
async renameFolder() {
if (!window.app.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;
}
const success = await window.fileOps.renameFolder(window.app.contextMenuTargetFolder.id, newName);
if (success) {
contextMenus.closeRenameDialog();
window.loadFiles();
}
},
/**
* Load all folders for the move dialog
* @param {string} itemId - ID of the item being moved
* @param {string} mode - 'file' or 'folder'
*/
async 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');
// Clear container except root option
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>
`;
// Select root by default
window.app.selectedTargetFolderId = "";
// Add all available folders
if (Array.isArray(folders)) {
folders.forEach(folder => {
// Skip folders that would create cycles
if (mode === 'folder' && folder.id === itemId) {
return;
}
// Skip current folder of the item
if (mode === 'file' && window.app.contextMenuTargetFile &&
folder.id === window.app.contextMenuTargetFile.folder_id) {
return;
}
if (mode === 'folder' && window.app.contextMenuTargetFolder &&
folder.id === window.app.contextMenuTargetFolder.parent_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', () => {
// Deselect all
document.querySelectorAll('.folder-select-item').forEach(item => {
item.classList.remove('selected');
});
// Select this one
folderItem.classList.add('selected');
window.app.selectedTargetFolderId = folder.id;
});
folderSelectContainer.appendChild(folderItem);
});
}
// Event for root option
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');
window.app.selectedTargetFolderId = "";
});
// Translate new elements
if (window.i18n && window.i18n.translatePage) {
window.i18n.translatePage();
}
}
} catch (error) {
console.error('Error loading folders:', error);
}
}
};
// Expose context menus module globally
window.contextMenus = contextMenus;
+295
View File
@@ -0,0 +1,295 @@
/**
* OxiCloud - File Operations Module
* This file handles file and folder operations (create, move, delete, rename, upload)
*/
// File Operations Module
const fileOps = {
/**
* Upload files to the server
* @param {FileList} files - Files to upload
*/
async 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 (window.app.currentPath) {
formData.append('folder_id', window.app.currentPath);
}
try {
const response = await fetch('/api/files/upload', {
method: 'POST',
body: formData
});
// Update progress
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
window.loadFiles();
setTimeout(() => {
document.getElementById('dropzone').style.display = 'none';
uploadProgressDiv.style.display = 'none';
}, 1000);
// Show success notification
window.ui.showNotification('Archivo subido', `${file.name} completado`);
}
} else {
const errorData = await response.text();
console.error('Upload error:', errorData);
window.ui.showNotification('Error', `Error al subir el archivo: ${file.name}`);
}
} catch (error) {
console.error('Network error during upload:', error);
window.ui.showNotification('Error', `Error de red al subir el archivo: ${file.name}`);
}
}
},
/**
* Create a new folder
* @param {string} name - Folder name
*/
async createFolder(name) {
try {
const response = await fetch('/api/folders', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: name,
parent_id: window.app.currentPath || null
})
});
if (response.ok) {
window.loadFiles();
window.ui.showNotification('Carpeta creada', `"${name}" creada correctamente`);
} else {
const errorData = await response.text();
console.error('Create folder error:', errorData);
window.ui.showNotification('Error', 'Error al crear la carpeta');
}
} catch (error) {
console.error('Error creating folder:', error);
window.ui.showNotification('Error', 'Error al crear la carpeta');
}
},
/**
* Move a file to another folder
* @param {string} fileId - File ID
* @param {string} targetFolderId - Target folder ID
* @returns {Promise<boolean>} - Success status
*/
async 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) {
// Reload files after moving
await window.loadFiles();
window.ui.showNotification('Archivo movido', 'Archivo movido correctamente');
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';
}
window.ui.showNotification('Error', `Error al mover el archivo: ${errorMessage}`);
return false;
}
} catch (error) {
console.error('Error moving file:', error);
window.ui.showNotification('Error', 'Error al mover el archivo');
return false;
}
},
/**
* Move a folder to another folder
* @param {string} folderId - Folder ID
* @param {string} targetFolderId - Target folder ID
* @returns {Promise<boolean>} - Success status
*/
async 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) {
// Reload files after moving
await window.loadFiles();
window.ui.showNotification('Carpeta movida', 'Carpeta movida correctamente');
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';
}
window.ui.showNotification('Error', `Error al mover la carpeta: ${errorMessage}`);
return false;
}
} catch (error) {
console.error('Error moving folder:', error);
window.ui.showNotification('Error', 'Error al mover la carpeta');
return false;
}
},
/**
* Rename a folder
* @param {string} folderId - Folder ID
* @param {string} newName - New folder name
* @returns {Promise<boolean>} - Success status
*/
async renameFolder(folderId, newName) {
try {
console.log(`Renaming folder ${folderId} to "${newName}"`);
const response = await fetch(`/api/folders/${folderId}/rename`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ name: newName })
});
console.log('Response status:', response.status);
if (response.ok) {
window.ui.showNotification('Carpeta renombrada', `Carpeta renombrada a "${newName}"`);
return true;
} else {
const errorText = await response.text();
console.error('Error response:', errorText);
let errorMessage = 'Error desconocido';
try {
// Try to parse as JSON
const errorData = JSON.parse(errorText);
errorMessage = errorData.error || response.statusText;
} catch (e) {
// If not JSON, use text as is
errorMessage = errorText || response.statusText;
}
window.ui.showNotification('Error', `Error al renombrar la carpeta: ${errorMessage}`);
return false;
}
} catch (error) {
console.error('Error renaming folder:', error);
window.ui.showNotification('Error', 'Error al renombrar la carpeta');
return false;
}
},
/**
* Delete a file
* @param {string} fileId - File ID
* @returns {Promise<boolean>} - Success status
*/
async deleteFile(fileId, fileName) {
if (!confirm(`¿Estás seguro de que quieres eliminar el archivo "${fileName}"?`)) {
return false;
}
try {
const response = await fetch(`/api/files/${fileId}`, {
method: 'DELETE'
});
if (response.ok) {
window.loadFiles();
window.ui.showNotification('Archivo eliminado', `"${fileName}" eliminado correctamente`);
return true;
} else {
window.ui.showNotification('Error', 'Error al eliminar el archivo');
return false;
}
} catch (error) {
console.error('Error deleting file:', error);
window.ui.showNotification('Error', 'Error al eliminar el archivo');
return false;
}
},
/**
* Delete a folder
* @param {string} folderId - Folder ID
* @param {string} folderName - Folder name
* @returns {Promise<boolean>} - Success status
*/
async deleteFolder(folderId, folderName) {
if (!confirm(`¿Estás seguro de que quieres eliminar la carpeta "${folderName}" y todo su contenido?`)) {
return false;
}
try {
const response = await fetch(`/api/folders/${folderId}`, {
method: 'DELETE'
});
if (response.ok) {
// If we're inside the folder we just deleted, go back up
if (window.app.currentPath === folderId) {
window.app.currentPath = '';
window.ui.updateBreadcrumb('');
}
window.loadFiles();
window.ui.showNotification('Carpeta eliminada', `"${folderName}" eliminada correctamente`);
return true;
} else {
window.ui.showNotification('Error', 'Error al eliminar la carpeta');
return false;
}
} catch (error) {
console.error('Error deleting folder:', error);
window.ui.showNotification('Error', 'Error al eliminar la carpeta');
return false;
}
}
};
// Expose file operations module globally
window.fileOps = fileOps;
+664
View File
@@ -0,0 +1,664 @@
/**
* OxiCloud - UI Module
* This file handles UI-related functions, view toggling, and interface interactions
*/
// UI Module
const ui = {
/**
* Initialize context menus and dialogs
*/
initializeContextMenus() {
// Folder context menu
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);
}
// File context menu
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);
}
// Rename dialog
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);
}
// Move dialog
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);
}
// Assign events to menu items
contextMenus.assignMenuEvents();
},
/**
* Set up drag and drop functionality
*/
setupDragAndDrop() {
const dropzone = document.getElementById('dropzone');
// Dropzone events
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) {
fileOps.uploadFiles(e.dataTransfer.files);
}
});
// Document-wide drag and drop
document.addEventListener('dragover', (e) => {
e.preventDefault();
if (e.dataTransfer.types.includes('Files')) {
dropzone.style.display = 'block';
dropzone.classList.add('active');
}
});
document.addEventListener('dragleave', (e) => {
if (e.clientX <= 0 || e.clientY <= 0 ||
e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) {
dropzone.classList.remove('active');
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) {
fileOps.uploadFiles(e.dataTransfer.files);
}
setTimeout(() => {
dropzone.style.display = 'none';
}, 500);
});
},
/**
* Switch to grid view
*/
switchToGridView() {
const filesGrid = document.getElementById('files-grid');
const filesListView = document.getElementById('files-list-view');
const gridViewBtn = document.getElementById('grid-view-btn');
const listViewBtn = document.getElementById('list-view-btn');
filesGrid.style.display = 'grid';
filesListView.style.display = 'none';
gridViewBtn.classList.add('active');
listViewBtn.classList.remove('active');
window.app.currentView = 'grid';
localStorage.setItem('oxicloud-view', 'grid');
},
/**
* Switch to list view
*/
switchToListView() {
const filesGrid = document.getElementById('files-grid');
const filesListView = document.getElementById('files-list-view');
const gridViewBtn = document.getElementById('grid-view-btn');
const listViewBtn = document.getElementById('list-view-btn');
filesGrid.style.display = 'none';
filesListView.style.display = 'flex';
gridViewBtn.classList.remove('active');
listViewBtn.classList.add('active');
window.app.currentView = 'list';
localStorage.setItem('oxicloud-view', 'list');
},
/**
* Update breadcrumb navigation
* @param {string} folderName - Name of the current folder
*/
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', () => {
window.app.currentPath = '';
this.updateBreadcrumb('');
window.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);
}
},
/**
* Show notification
* @param {string} title - Notification title
* @param {string} message - Notification message
*/
showNotification(title, message) {
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;
}
notification.style.display = 'block';
setTimeout(() => {
notification.style.display = 'none';
}, 5000);
},
/**
* Close folder context menu
*/
closeContextMenu() {
const menu = document.getElementById('folder-context-menu');
if (menu) {
menu.style.display = 'none';
window.app.contextMenuTargetFolder = null;
}
},
/**
* Close file context menu
*/
closeFileContextMenu() {
const menu = document.getElementById('file-context-menu');
if (menu) {
menu.style.display = 'none';
window.app.contextMenuTargetFile = null;
}
},
/**
* Update file icons based on file type
*/
updateFileIcons() {
const fileCards = document.querySelectorAll('.file-card');
fileCards.forEach(card => {
const fileName = card.querySelector('.file-name')?.textContent || '';
const iconElement = card.querySelector('.file-icon');
if (!iconElement) return;
if (iconElement.classList.contains('folder-icon')) {
iconElement.innerHTML = '';
}
else if (fileName.endsWith('.docx') || fileName.endsWith('.pdf') || fileName.endsWith('.txt') || fileName.endsWith('.xlsx')) {
iconElement.classList.add('doc-icon');
iconElement.innerHTML = '';
}
else if (fileName.endsWith('.jpg') || fileName.endsWith('.png') || fileName.endsWith('.gif') || fileName.endsWith('.jpeg')) {
iconElement.classList.add('image-icon');
iconElement.innerHTML = '';
}
else if (fileName.endsWith('.mp4') || fileName.endsWith('.avi') || fileName.endsWith('.mov') || fileName.endsWith('.mkv')) {
iconElement.classList.add('video-icon');
iconElement.innerHTML = '';
}
else {
const extension = fileName.split('.').pop().toLowerCase();
if (['json', 'js', 'jsx', 'ts', 'tsx', 'html', 'css', 'scss', 'py', 'java', 'c', 'cpp', 'cs', 'php', 'rb', 'go', 'rs', 'swift', 'kt'].includes(extension)) {
iconElement.className = 'file-icon code-icon';
iconElement.innerHTML = `
<div class="code-line-1"></div>
<div class="code-line-2"></div>
<div class="code-line-3"></div>
`;
if (extension === 'json') {
iconElement.classList.add('json-icon');
} else if (['js', 'jsx', 'ts', 'tsx'].includes(extension)) {
iconElement.classList.add('js-icon');
} else if (extension === 'html') {
iconElement.classList.add('html-icon');
} else if (['css', 'scss'].includes(extension)) {
iconElement.classList.add('css-icon');
} else if (extension === 'py') {
iconElement.classList.add('py-icon');
}
}
}
});
},
/**
* Add folder to the view
* @param {Object} folder - Folder object
*/
addFolderToView(folder) {
// Grid view element
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">Carpeta</div>
`;
// Drag and drop setup for folders
if (window.app.currentPath !== "") {
folderGridElement.setAttribute('draggable', 'true');
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');
});
});
}
// Click to navigate
folderGridElement.addEventListener('click', () => {
window.app.currentPath = folder.id;
this.updateBreadcrumb(folder.name);
window.loadFiles();
});
// Context menu
folderGridElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
window.app.contextMenuTargetFolder = {
id: folder.id,
name: folder.name,
parent_id: folder.parent_id || ""
};
let folderContextMenu = document.getElementById('folder-context-menu');
folderContextMenu.style.left = `${e.pageX}px`;
folderContextMenu.style.top = `${e.pageY}px`;
folderContextMenu.style.display = 'block';
});
// Drop target setup
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) {
if (id === folder.id) {
alert("No puedes mover una carpeta a sí misma");
return;
}
await fileOps.moveFolder(id, folder.id);
} else {
await fileOps.moveFile(id, folder.id);
}
}
});
document.getElementById('files-grid').appendChild(folderGridElement);
// List view element - Mejorado
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 || "";
// Format date
const modifiedDate = new Date(folder.modified_at * 1000);
const formattedDate = modifiedDate.toLocaleDateString() + ' ' +
modifiedDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
// Make draggable if not in root
if (window.app.currentPath !== "") {
folderListElement.setAttribute('draggable', 'true');
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');
});
});
}
// Mejorado: Estructura y clases para la vista de lista
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 class="type-cell">${window.i18n ? window.i18n.t('files.file_types.folder') : 'Carpeta'}</div>
<div class="size-cell">--</div>
<div class="date-cell">${formattedDate}</div>
`;
// Click to navigate
folderListElement.addEventListener('click', () => {
window.app.currentPath = folder.id;
this.updateBreadcrumb(folder.name);
window.loadFiles();
});
// Context menu
folderListElement.addEventListener('contextmenu', (e) => {
e.preventDefault();
window.app.contextMenuTargetFolder = {
id: folder.id,
name: folder.name,
parent_id: folder.parent_id || ""
};
let folderContextMenu = document.getElementById('folder-context-menu');
folderContextMenu.style.left = `${e.pageX}px`;
folderContextMenu.style.top = `${e.pageY}px`;
folderContextMenu.style.display = 'block';
});
// Drop target setup for list view
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) {
if (id === folder.id) {
alert("No puedes mover una carpeta a sí misma");
return;
}
await fileOps.moveFolder(id, folder.id);
} else {
await fileOps.moveFile(id, folder.id);
}
}
});
document.getElementById('files-list-view').appendChild(folderListElement);
},
/**
* Add file to the view
* @param {Object} file - File object
*/
addFileToView(file) {
// 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(file.size);
const modifiedDate = new Date(file.modified_at * 1000);
const formattedDate = modifiedDate.toLocaleDateString() + ' ' +
modifiedDate.toLocaleTimeString([], {hour: '2-digit', minute:'2-digit'});
// Grid view element
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.split(' ')[0]}</div>
`;
fileGridElement.dataset.fileId = file.id;
fileGridElement.dataset.fileName = file.name;
fileGridElement.dataset.folderId = file.folder_id || "";
// Make draggable
fileGridElement.setAttribute('draggable', 'true');
fileGridElement.addEventListener('dragstart', (e) => {
e.dataTransfer.setData('text/plain', file.id);
fileGridElement.classList.add('dragging');
});
fileGridElement.addEventListener('dragend', () => {
fileGridElement.classList.remove('dragging');
document.querySelectorAll('.drop-target').forEach(el => {
el.classList.remove('drop-target');
});
});
// Download on click
fileGridElement.addEventListener('click', () => {
window.location.href = `/api/files/${file.id}`;
});
// 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';
});
document.getElementById('files-grid').appendChild(fileGridElement);
// List view element - Mejorado con clases específicas y diseño mejorado
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 ${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>
`;
// Make draggable (list view)
fileListElement.setAttribute('draggable', 'true');
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');
});
});
// Download on click
fileListElement.addEventListener('click', () => {
window.location.href = `/api/files/${file.id}`;
});
// Context menu (list view)
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';
});
document.getElementById('files-list-view').appendChild(fileListElement);
}
};
// Expose UI module globally
window.ui = ui;
+7
View File
@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
<!-- Fondo circular más pequeño -->
<circle cx="250" cy="250" r="180" fill="#f8f9fa" />
<!-- Forma de nube principal - manteniendo la misma posición -->
<path d="M330 300c27.6 0 50-22.4 50-50s-22.4-50-50-50c-5.3 0-10.3 0.8-15.1 2.3C307.4 174.5 282.6 155 253 155c-29.6 0-54.4 19.5-62.9 46.3C185.3 200.8 180.3 200 175 200c-27.6 0-50 22.4-50 50s22.4 50 50 50h155z" fill="#ff5e3a" />
</svg>

After

Width:  |  Height:  |  Size: 463 B

@@ -0,0 +1,3 @@
[ZoneTransfer]
ZoneId=3
HostUrl=https://claude.ai/
Binary file not shown.
Binary file not shown.
-7
View File
@@ -1,7 +0,0 @@
{
"path_to_id": {
"prueba5": "58a82b8c-ef9d-43cf-b593-4668fdb0ef03",
"prueba4": "248abd28-e94e-4c75-b1b6-712d9957d850",
"prueba3": "30dd94f1-86c6-4b9d-bc9a-d51686f0dbcd"
}
}
-10
View File
@@ -1,10 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500">
<!-- Fondo circular -->
<circle cx="250" cy="250" r="200" fill="#f8f9fa" />
<!-- Forma de nube principal - inspirada en Nextcloud pero con estilo propio -->
<path d="M330 280c27.6 0 50-22.4 50-50s-22.4-50-50-50c-5.3 0-10.3 0.8-15.1 2.3C307.4 154.5 282.6 135 253 135c-29.6 0-54.4 19.5-62.9 46.3C185.3 180.8 180.3 180 175 180c-27.6 0-50 22.4-50 50s22.4 50 50 50h155z" fill="#ff5e3a" />
<!-- Texto del logotipo - solo el nombre -->
<text x="250" y="350" font-family="Arial, sans-serif" font-size="55" font-weight="bold" text-anchor="middle" fill="#2b3a4a">OxiCloud</text>
</svg>

Before

Width:  |  Height:  |  Size: 657 B

Binary file not shown.
-11
View File
@@ -1,11 +0,0 @@
{
"path_to_id": {
"prueba5/NIST.SP.500-322.pdf": "50d9675e-617f-472d-a6e7-cdcf83dfadfe",
"prueba3/NIST.SP.500-322.pdf": "a73a94bd-77e5-41ad-8723-9675fa15e66c",
"file_ids.json": "bda2a482-6947-4922-8263-a3826d13ab11",
"folder_ids.json": "dbd1c1e4-8441-40d6-8f43-e53a53abfde0",
"prueba4/folder_ids.json": "d4b559da-bf7e-4754-99bc-970f9c7a64fc",
"prueba3/NIST.SP.500-322 (3).pdf": "e71813d5-4234-4b0f-9c0a-55acb731ce7e",
"NIST.SP.500-322 (3).pdf": "95f7039e-06ff-41b7-87a0-2d7af6cbe4fb"
}
}
-7
View File
@@ -1,7 +0,0 @@
{
"path_to_id": {
"prueba4": "66992be8-56dd-4c69-b743-98cf9c484c39",
"prueba5": "f09dfe30-f47a-4901-bf52-7214d601d24b",
"prueba3": "66047168-e2d3-44d5-85e4-656ed8b2d803"
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 22 KiB