adding features
This commit is contained in:
@@ -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);
|
||||
@@ -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;
|
||||
@@ -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
@@ -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;
|
||||
Reference in New Issue
Block a user