adding features
This commit is contained in:
@@ -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
File diff suppressed because it is too large
Load Diff
@@ -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;
|
||||
@@ -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.
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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.
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -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 |
Reference in New Issue
Block a user