kinda working safe point
This commit is contained in:
parent
b9a82af12f
commit
6dda02141e
31 changed files with 4302 additions and 2937 deletions
|
@ -1,3 +1,408 @@
|
|||
.browser-container {
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--border-radius);
|
||||
padding: 1.5rem;
|
||||
margin-bottom: 2rem;
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.browser-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-bottom: 1.5rem;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.browser-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.browser-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-bottom: 1.5rem;
|
||||
background: var(--bg-light);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: var(--border-radius-sm);
|
||||
}
|
||||
|
||||
.breadcrumb-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
margin: 0 0.5rem;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.view-toggle {
|
||||
display: flex;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-sm);
|
||||
overflow: hidden;
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.view-btn {
|
||||
border: none;
|
||||
background: var(--card-bg);
|
||||
padding: 0.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.view-btn.active {
|
||||
background: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.search-bar {
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.search-bar input {
|
||||
width: 100%;
|
||||
padding: 0.5rem 1rem 0.5rem 2.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background: var(--bg-light);
|
||||
}
|
||||
|
||||
.search-icon {
|
||||
position: absolute;
|
||||
left: 0.75rem;
|
||||
top: 0.75rem;
|
||||
color: var(--text-muted);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.sort-dropdown {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sort-dropdown-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-sm);
|
||||
background: var(--bg-light);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sort-dropdown-menu {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-sm);
|
||||
min-width: 200px;
|
||||
z-index: 10;
|
||||
box-shadow: var(--shadow-md);
|
||||
display: none;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.sort-dropdown-menu.show {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.sort-option {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sort-option:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.sort-option.active {
|
||||
color: var(--primary-color);
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.files-container {
|
||||
min-height: 200px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.loading-indicator {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: rgba(var(--card-bg-rgb), 0.7);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 5;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.loading-indicator.show {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 4px solid rgba(var(--primary-color-rgb), 0.3);
|
||||
border-radius: 50%;
|
||||
border-top-color: var(--primary-color);
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
to {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
||||
/* Grid view */
|
||||
.files-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.folder-item,
|
||||
.file-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 1rem;
|
||||
border-radius: var(--border-radius-sm);
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all 0.2s ease;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.folder-item:hover,
|
||||
.file-item:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: var(--shadow-sm);
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.item-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 0.75rem;
|
||||
text-align: center;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.folder-item .item-icon {
|
||||
color: #ffc107;
|
||||
}
|
||||
|
||||
.item-info {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.item-name {
|
||||
font-weight: 500;
|
||||
margin-bottom: 0.25rem;
|
||||
word-break: break-word;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.item-details {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
font-size: 0.85rem;
|
||||
color: var(--text-muted);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* List view */
|
||||
.files-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.list-view .folder-item,
|
||||
.list-view .file-item {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
padding: 0.75rem 1rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.list-view .item-icon {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0;
|
||||
margin-right: 1rem;
|
||||
width: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.list-view .item-info {
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.list-view .item-name {
|
||||
margin-bottom: 0;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.list-view .item-details {
|
||||
margin-left: auto;
|
||||
min-width: 200px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.list-view .item-date {
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
/* Empty folder state */
|
||||
.empty-folder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 3rem;
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.empty-message h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-message p {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.empty-actions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Context menu */
|
||||
.context-menu {
|
||||
position: fixed;
|
||||
background: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: var(--border-radius-sm);
|
||||
box-shadow: var(--shadow-md);
|
||||
z-index: 100;
|
||||
min-width: 180px;
|
||||
padding: 0.5rem 0;
|
||||
}
|
||||
|
||||
.context-menu-item {
|
||||
padding: 0.5rem 1rem;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.context-menu-item:hover {
|
||||
background: var(--bg-hover);
|
||||
}
|
||||
|
||||
.context-menu-item.danger {
|
||||
color: var(--danger-color);
|
||||
}
|
||||
|
||||
/* Responsive */
|
||||
@media (max-width: 768px) {
|
||||
.browser-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.browser-actions {
|
||||
width: 100%;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.filter-bar {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.files-grid {
|
||||
grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
|
||||
}
|
||||
|
||||
.list-view .item-info {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.list-view .item-details {
|
||||
margin-left: 0;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
}
|
||||
|
||||
/* Modal fixes - ensure they're hidden by default */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
overflow: auto;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal.visible {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background: var(--card-bg);
|
||||
border-radius: var(--border-radius);
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
box-shadow: var(--shadow-lg);
|
||||
margin: 2rem;
|
||||
}
|
||||
|
||||
/* File Browser Styles */
|
||||
.browser-container {
|
||||
background: var(--card-bg);
|
||||
|
|
52
app/static/css/context-menu.css
Normal file
52
app/static/css/context-menu.css
Normal file
|
@ -0,0 +1,52 @@
|
|||
/* Context Menu Styles */
|
||||
.context-menu {
|
||||
position: absolute;
|
||||
display: none;
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
|
||||
padding: 8px 0;
|
||||
min-width: 180px;
|
||||
z-index: 1000;
|
||||
animation: context-menu-appear 0.2s ease;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
@keyframes context-menu-appear {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu-item {
|
||||
padding: 10px 16px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
font-size: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.context-menu-item:hover {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.context-menu-item i {
|
||||
margin-right: 10px;
|
||||
width: 16px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.context-menu-divider {
|
||||
height: 1px;
|
||||
background-color: var(--border-color);
|
||||
margin: 5px 0;
|
||||
}
|
File diff suppressed because it is too large
Load diff
130
app/static/css/modal.css
Normal file
130
app/static/css/modal.css
Normal file
|
@ -0,0 +1,130 @@
|
|||
/* Beautiful modal styles */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
backdrop-filter: blur(3px);
|
||||
z-index: 1000;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal.visible {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
|
||||
width: 90%;
|
||||
max-width: 400px;
|
||||
position: relative;
|
||||
transform: scale(0.95);
|
||||
opacity: 0;
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.modal.visible .modal-content {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
padding: 1.25rem 1.5rem;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-weight: 600;
|
||||
color: var(--text);
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--text-secondary);
|
||||
transition: color 0.2s ease;
|
||||
line-height: 1;
|
||||
padding: 0;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
color: var(--danger-color);
|
||||
background-color: rgba(var(--danger-color-rgb), 0.1);
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 1.5rem;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 1.25rem;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
font-weight: 500;
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.form-group input[type="text"],
|
||||
.form-group input[type="password"],
|
||||
.form-group input[type="email"] {
|
||||
width: 100%;
|
||||
padding: 0.75rem 1rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: var(--bg);
|
||||
color: var(--text);
|
||||
font-size: 1rem;
|
||||
transition: border-color 0.3s ease, box-shadow 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
box-shadow: 0 0 0 3px rgba(var(--primary-color-rgb), 0.2);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 0.75rem;
|
||||
margin-top: 1.5rem;
|
||||
}
|
||||
|
||||
.form-actions .btn {
|
||||
padding: 0.6rem 1.25rem;
|
||||
border-radius: 8px;
|
||||
font-weight: 500;
|
||||
transition: background 0.3s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.form-actions .btn:hover {
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.form-actions .btn:active {
|
||||
transform: translateY(0);
|
||||
}
|
609
app/static/css/styles.css
Normal file
609
app/static/css/styles.css
Normal file
|
@ -0,0 +1,609 @@
|
|||
/* Add these styles to your existing styles.css file */
|
||||
|
||||
/* Theme Toggle */
|
||||
.theme-toggle-icon {
|
||||
cursor: pointer;
|
||||
padding: 0.5rem;
|
||||
border-radius: 50%;
|
||||
transition: all 0.3s ease;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.theme-toggle-icon:hover {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
[data-theme="dark"] .theme-toggle-icon:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Make sure theme transitions work */
|
||||
body,
|
||||
.card,
|
||||
.navbar,
|
||||
.sidebar,
|
||||
input,
|
||||
select,
|
||||
textarea,
|
||||
button,
|
||||
.modal-content,
|
||||
.file-item,
|
||||
.folder-item {
|
||||
transition: background-color 0.3s ease, color 0.3s ease, border-color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Making sure the styles apply properly to light/dark themes */
|
||||
:root {
|
||||
--primary-color: #4a6bff;
|
||||
--primary-hover: #3a5bed;
|
||||
--secondary-color: #6c757d;
|
||||
--success-color: #28a745;
|
||||
--danger-color: #dc3545;
|
||||
--warning-color: #ffc107;
|
||||
--info-color: #17a2b8;
|
||||
--light-color: #f8f9fa;
|
||||
--dark-color: #343a40;
|
||||
--background-color: #1e2029;
|
||||
--card-bg: #282a36;
|
||||
--text-color: #f8f8f2;
|
||||
--text-muted: #bd93f9;
|
||||
--border-color: #44475a;
|
||||
--transition-speed: 0.3s;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
/* Ensure these are applied */
|
||||
--bg: #121418;
|
||||
--card-bg: #1e2029;
|
||||
--text: #f2f3f8;
|
||||
--primary-color-rgb: 109, 93, 252;
|
||||
}
|
||||
|
||||
/* Theme script fix */
|
||||
body {
|
||||
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
||||
background-color: var(--background-color);
|
||||
color: var(--text-color);
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
/* Global dropzone overlay for quick uploads */
|
||||
.global-dropzone {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.7);
|
||||
backdrop-filter: blur(5px);
|
||||
z-index: 9999;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.global-dropzone.active {
|
||||
display: flex;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.dropzone-content {
|
||||
text-align: center;
|
||||
color: white;
|
||||
padding: 2rem;
|
||||
border-radius: 12px;
|
||||
background-color: rgba(var(--primary-color-rgb), 0.3);
|
||||
border: 3px dashed rgba(255, 255, 255, 0.5);
|
||||
max-width: 500px;
|
||||
width: 90%;
|
||||
animation: pulse 2s infinite;
|
||||
}
|
||||
|
||||
.dropzone-icon {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.dropzone-content h3 {
|
||||
font-size: 1.8rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.dropzone-content p {
|
||||
opacity: 0.8;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
/* Upload toast notification */
|
||||
.upload-toast {
|
||||
position: fixed;
|
||||
bottom: 2rem;
|
||||
right: 2rem;
|
||||
width: 350px;
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
|
||||
z-index: 1080;
|
||||
overflow: hidden;
|
||||
display: none;
|
||||
transform: translateY(20px);
|
||||
opacity: 0;
|
||||
transition: transform 0.3s ease, opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.upload-toast.active {
|
||||
display: block;
|
||||
transform: translateY(0);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.upload-toast-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0.8rem 1rem;
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.upload-toast-header i {
|
||||
margin-right: 0.75rem;
|
||||
}
|
||||
|
||||
.upload-toast-close {
|
||||
margin-left: auto;
|
||||
background: none;
|
||||
border: none;
|
||||
color: white;
|
||||
font-size: 1.25rem;
|
||||
cursor: pointer;
|
||||
padding: 0;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.upload-toast-close:hover {
|
||||
background-color: rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.upload-toast-body {
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.upload-toast-progress-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.upload-toast-progress-bar-container {
|
||||
height: 8px;
|
||||
background-color: var(--border-color);
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.upload-toast-progress-bar {
|
||||
height: 100%;
|
||||
width: 0;
|
||||
background-color: var(--primary-color);
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
50% {
|
||||
transform: scale(1.03);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* File and folder views */
|
||||
.files-container {
|
||||
padding: 20px;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
/* Grid view */
|
||||
.files-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.grid-view .folder-item,
|
||||
.grid-view .file-item {
|
||||
padding: 15px;
|
||||
border-radius: 8px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
border: 1px solid var(--border-color);
|
||||
position: relative;
|
||||
transition: all 0.3s;
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
height: 150px;
|
||||
}
|
||||
|
||||
.grid-view .item-icon {
|
||||
font-size: 2.5rem;
|
||||
margin-bottom: 10px;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.grid-view .folder-item .item-icon {
|
||||
color: #f1c40f;
|
||||
}
|
||||
|
||||
.grid-view .item-info {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.grid-view .item-name {
|
||||
font-weight: 500;
|
||||
margin-bottom: 5px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.grid-view .item-details {
|
||||
font-size: 0.8rem;
|
||||
color: var(--text-muted);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
/* File actions */
|
||||
.file-actions {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
right: 5px;
|
||||
display: flex;
|
||||
gap: 5px;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.folder-item:hover .file-actions,
|
||||
.file-item:hover .file-actions {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.action-btn {
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
border: none;
|
||||
color: white;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.action-btn:hover {
|
||||
background: var(--primary-color);
|
||||
}
|
||||
|
||||
.action-btn.edit:hover {
|
||||
background: var(--info-color);
|
||||
}
|
||||
|
||||
.action-btn.delete:hover {
|
||||
background: var(--danger-color);
|
||||
}
|
||||
|
||||
/* Empty folder */
|
||||
.empty-folder {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 300px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 3rem;
|
||||
color: var(--border-color);
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.empty-message h3 {
|
||||
margin-bottom: 10px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.empty-message p {
|
||||
color: var(--text-muted);
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.empty-actions {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Main styles for the file management system */
|
||||
.browser-container {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.25);
|
||||
margin-bottom: 30px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.browser-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.browser-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.browser-title h2 {
|
||||
margin: 0 0 0 10px;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
.browser-title i {
|
||||
font-size: 1.5rem;
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.browser-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
/* Breadcrumbs */
|
||||
.breadcrumbs {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 10px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.breadcrumb-item {
|
||||
color: var(--text-muted);
|
||||
text-decoration: none;
|
||||
font-size: 0.9rem;
|
||||
padding: 5px 0;
|
||||
}
|
||||
|
||||
.breadcrumb-item:hover {
|
||||
color: var(--primary-color);
|
||||
}
|
||||
|
||||
.breadcrumb-separator {
|
||||
margin: 0 8px;
|
||||
color: var(--secondary-color);
|
||||
}
|
||||
|
||||
/* Search bar */
|
||||
.search-container {
|
||||
position: relative;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.search-container input {
|
||||
padding: 8px 15px 8px 35px;
|
||||
border-radius: 20px;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
color: var(--text-color);
|
||||
width: 200px;
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.search-container input:focus {
|
||||
width: 250px;
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.search-btn {
|
||||
position: absolute;
|
||||
left: 10px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* View toggle */
|
||||
.view-toggle {
|
||||
display: flex;
|
||||
border-radius: 4px;
|
||||
overflow: hidden;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.view-btn {
|
||||
background-color: var(--card-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-muted);
|
||||
padding: 8px 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.view-btn:first-child {
|
||||
border-radius: 4px 0 0 4px;
|
||||
}
|
||||
|
||||
.view-btn:last-child {
|
||||
border-radius: 0 4px 4px 0;
|
||||
}
|
||||
|
||||
.view-btn.active {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
/* Buttons */
|
||||
.btn {
|
||||
padding: 8px 15px;
|
||||
border-radius: 4px;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
font-weight: 500;
|
||||
transition: all 0.2s;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.btn.primary {
|
||||
background-color: var(--primary-color);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.btn.primary:hover {
|
||||
background-color: var(--primary-hover);
|
||||
}
|
||||
|
||||
.btn.secondary {
|
||||
background-color: transparent;
|
||||
color: var(--text-color);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.btn.secondary:hover {
|
||||
background-color: rgba(255, 255, 255, 0.1);
|
||||
}
|
||||
|
||||
/* Modal */
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
z-index: 1000;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.modal.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: var(--card-bg);
|
||||
border-radius: 8px;
|
||||
width: 100%;
|
||||
max-width: 500px;
|
||||
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
|
||||
animation: modal-appear 0.3s forwards;
|
||||
}
|
||||
|
||||
@keyframes modal-appear {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-50px);
|
||||
}
|
||||
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.modal-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 15px 20px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.modal-header h3 {
|
||||
margin: 0;
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.modal-close {
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
cursor: pointer;
|
||||
color: var(--text-color);
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.modal-close:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.modal-body {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: rgba(0, 0, 0, 0.2);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: var(--primary-color);
|
||||
}
|
||||
|
||||
.form-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
}
|
246
app/static/js/browser.js
Normal file
246
app/static/js/browser.js
Normal file
|
@ -0,0 +1,246 @@
|
|||
/**
|
||||
* File browser functionality
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// View toggle functionality
|
||||
const filesContainer = document.getElementById('files-container');
|
||||
const gridViewBtn = document.getElementById('grid-view-btn');
|
||||
const listViewBtn = document.getElementById('list-view-btn');
|
||||
|
||||
if (filesContainer && gridViewBtn && listViewBtn) {
|
||||
// Set initial view based on saved preference
|
||||
const savedView = localStorage.getItem('view_preference') || 'grid';
|
||||
filesContainer.className = `files-container ${savedView}-view`;
|
||||
|
||||
// Highlight the correct button
|
||||
if (savedView === 'grid') {
|
||||
gridViewBtn.classList.add('active');
|
||||
listViewBtn.classList.remove('active');
|
||||
} else {
|
||||
listViewBtn.classList.add('active');
|
||||
gridViewBtn.classList.remove('active');
|
||||
}
|
||||
|
||||
// Add event listeners
|
||||
gridViewBtn.addEventListener('click', function () {
|
||||
filesContainer.className = 'files-container grid-view';
|
||||
gridViewBtn.classList.add('active');
|
||||
listViewBtn.classList.remove('active');
|
||||
|
||||
// Save preference
|
||||
localStorage.setItem('view_preference', 'grid');
|
||||
});
|
||||
|
||||
listViewBtn.addEventListener('click', function () {
|
||||
filesContainer.className = 'files-container list-view';
|
||||
listViewBtn.classList.add('active');
|
||||
gridViewBtn.classList.remove('active');
|
||||
|
||||
// Save preference
|
||||
localStorage.setItem('view_preference', 'list');
|
||||
});
|
||||
}
|
||||
|
||||
// Variables for tracking selected items
|
||||
let selectedItemId = null;
|
||||
let selectedItemType = null;
|
||||
|
||||
// Setup context menu functionality
|
||||
function setupContextMenu() {
|
||||
// Context menu already implemented via context-menu.js
|
||||
// We'll just need to ensure our item actions are properly set
|
||||
|
||||
// Add item click handler to set selected item
|
||||
document.querySelectorAll('.file-item, .folder-item').forEach(item => {
|
||||
item.addEventListener('click', function (e) {
|
||||
// If clicking on an action button, don't select the item
|
||||
if (e.target.closest('.item-actions') || e.target.closest('a')) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set selected item
|
||||
selectedItemId = this.dataset.id;
|
||||
selectedItemType = this.classList.contains('folder-item') ? 'folder' : 'file';
|
||||
|
||||
// Highlight selected item
|
||||
document.querySelectorAll('.file-item, .folder-item').forEach(i => {
|
||||
i.classList.remove('selected');
|
||||
});
|
||||
this.classList.add('selected');
|
||||
});
|
||||
|
||||
// Right-click to open context menu
|
||||
item.addEventListener('contextmenu', function (e) {
|
||||
// Set selected item
|
||||
selectedItemId = this.dataset.id;
|
||||
selectedItemType = this.classList.contains('folder-item') ? 'folder' : 'file';
|
||||
|
||||
// Highlight selected item
|
||||
document.querySelectorAll('.file-item, .folder-item').forEach(i => {
|
||||
i.classList.remove('selected');
|
||||
});
|
||||
this.classList.add('selected');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle folder creation
|
||||
const newFolderForm = document.getElementById('new-folder-form');
|
||||
if (newFolderForm) {
|
||||
newFolderForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const folderName = document.getElementById('folder-name').value;
|
||||
const parentId = document.querySelector('input[name="parent_id"]').value;
|
||||
|
||||
// Create FormData
|
||||
const formData = new FormData();
|
||||
formData.append('name', folderName);
|
||||
if (parentId) formData.append('parent_id', parentId);
|
||||
|
||||
// Send request
|
||||
fetch('/files/create_folder', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Close modal
|
||||
closeModal('new-folder-modal');
|
||||
|
||||
// Show success message
|
||||
showAlert('Folder created successfully', 'success');
|
||||
|
||||
// Reload page to show new folder
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
} else {
|
||||
showAlert(data.error || 'Error creating folder', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('Error creating folder: ' + error, 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle rename
|
||||
const renameForm = document.getElementById('rename-form');
|
||||
if (renameForm) {
|
||||
renameForm.addEventListener('submit', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const newName = document.getElementById('new-name').value;
|
||||
|
||||
if (!selectedItemId) {
|
||||
showAlert('No item selected', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/files/rename', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
item_id: selectedItemId,
|
||||
new_name: newName
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Close modal
|
||||
closeModal('rename-modal');
|
||||
|
||||
// Show success message
|
||||
showAlert('Item renamed successfully', 'success');
|
||||
|
||||
// Update item name in the UI or reload
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
} else {
|
||||
showAlert(data.error || 'Failed to rename item', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('Error: ' + error, 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle delete confirmation
|
||||
const confirmDeleteBtn = document.getElementById('confirm-delete-btn');
|
||||
if (confirmDeleteBtn) {
|
||||
confirmDeleteBtn.addEventListener('click', function () {
|
||||
if (!selectedItemId) {
|
||||
showAlert('No item selected', 'error');
|
||||
closeModal('delete-modal');
|
||||
return;
|
||||
}
|
||||
|
||||
fetch(`/files/delete/${selectedItemId}`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'X-Requested-With': 'XMLHttpRequest'
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Close modal
|
||||
closeModal('delete-modal');
|
||||
|
||||
// Show success message
|
||||
showAlert(data.message || 'Item deleted successfully', 'success');
|
||||
|
||||
// Remove the item from the UI or reload
|
||||
setTimeout(() => window.location.reload(), 500);
|
||||
} else {
|
||||
showAlert(data.error || 'Failed to delete item', 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showAlert('Error: ' + error, 'error');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize
|
||||
setupContextMenu();
|
||||
|
||||
// Buttons to open modals
|
||||
const deleteBtn = document.getElementById('delete-btn');
|
||||
if (deleteBtn) {
|
||||
deleteBtn.addEventListener('click', function () {
|
||||
if (!selectedItemId) {
|
||||
showAlert('Please select an item first', 'warning');
|
||||
return;
|
||||
}
|
||||
openModal('delete-modal');
|
||||
});
|
||||
}
|
||||
|
||||
const renameBtn = document.getElementById('rename-btn');
|
||||
if (renameBtn) {
|
||||
renameBtn.addEventListener('click', function () {
|
||||
if (!selectedItemId) {
|
||||
showAlert('Please select an item first', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get current name
|
||||
const selectedItem = document.querySelector(`.file-item[data-id="${selectedItemId}"], .folder-item[data-id="${selectedItemId}"]`);
|
||||
const currentName = selectedItem ? selectedItem.querySelector('.item-name').textContent.trim() : '';
|
||||
|
||||
// Set current name in the input
|
||||
document.getElementById('new-name').value = currentName;
|
||||
|
||||
openModal('rename-modal');
|
||||
document.getElementById('new-name').focus();
|
||||
});
|
||||
}
|
||||
});
|
144
app/static/js/context-menu.js
Normal file
144
app/static/js/context-menu.js
Normal file
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* Context Menu for Files/Folders
|
||||
*/
|
||||
|
||||
class ContextMenu {
|
||||
constructor() {
|
||||
this.menu = null;
|
||||
this.currentTarget = null;
|
||||
this.init();
|
||||
}
|
||||
|
||||
init() {
|
||||
// Create context menu element
|
||||
this.menu = document.createElement('div');
|
||||
this.menu.className = 'context-menu';
|
||||
this.menu.style.display = 'none';
|
||||
document.body.appendChild(this.menu);
|
||||
|
||||
// Close menu on click outside
|
||||
document.addEventListener('click', (e) => {
|
||||
if (this.menu.style.display === 'block') {
|
||||
this.hideMenu();
|
||||
}
|
||||
});
|
||||
|
||||
// Prevent default context menu
|
||||
document.addEventListener('contextmenu', (e) => {
|
||||
if (e.target.closest('.file-item, .folder-item')) {
|
||||
e.preventDefault();
|
||||
this.showMenu(e);
|
||||
}
|
||||
});
|
||||
|
||||
// Close on escape key
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && this.menu.style.display === 'block') {
|
||||
this.hideMenu();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
showMenu(e) {
|
||||
// Get target item
|
||||
this.currentTarget = e.target.closest('.file-item, .folder-item');
|
||||
const itemId = this.currentTarget.dataset.id;
|
||||
const itemType = this.currentTarget.classList.contains('folder-item') ? 'folder' : 'file';
|
||||
const itemName = this.currentTarget.querySelector('.item-name').textContent;
|
||||
|
||||
// Create menu items based on item type
|
||||
this.menu.innerHTML = '';
|
||||
|
||||
if (itemType === 'folder') {
|
||||
// Folder actions
|
||||
this.addMenuItem('Open', 'fa-folder-open', () => {
|
||||
window.location.href = `/files/browse/${itemId}`;
|
||||
});
|
||||
|
||||
this.addMenuItem('Rename', 'fa-edit', () => {
|
||||
openModal('rename-modal');
|
||||
document.getElementById('new-name').value = itemName;
|
||||
window.selectedItemId = itemId;
|
||||
});
|
||||
|
||||
this.addMenuItem('Delete', 'fa-trash-alt', () => {
|
||||
openModal('delete-modal');
|
||||
window.selectedItemId = itemId;
|
||||
});
|
||||
} else {
|
||||
// File actions
|
||||
this.addMenuItem('Download', 'fa-download', () => {
|
||||
window.location.href = `/files/download/${itemId}`;
|
||||
});
|
||||
|
||||
this.addMenuItem('View', 'fa-eye', () => {
|
||||
window.location.href = `/files/view/${itemId}`;
|
||||
});
|
||||
|
||||
this.addMenuItem('Rename', 'fa-edit', () => {
|
||||
openModal('rename-modal');
|
||||
document.getElementById('new-name').value = itemName;
|
||||
window.selectedItemId = itemId;
|
||||
});
|
||||
|
||||
this.addMenuItem('Delete', 'fa-trash-alt', () => {
|
||||
openModal('delete-modal');
|
||||
window.selectedItemId = itemId;
|
||||
});
|
||||
}
|
||||
|
||||
// Position menu
|
||||
const x = e.clientX;
|
||||
const y = e.clientY;
|
||||
|
||||
// Set menu position
|
||||
this.menu.style.left = `${x}px`;
|
||||
this.menu.style.top = `${y}px`;
|
||||
|
||||
// Show menu with animation
|
||||
this.menu.style.display = 'block';
|
||||
|
||||
// Adjust position if menu goes off screen
|
||||
const menuRect = this.menu.getBoundingClientRect();
|
||||
const windowWidth = window.innerWidth;
|
||||
const windowHeight = window.innerHeight;
|
||||
|
||||
if (menuRect.right > windowWidth) {
|
||||
this.menu.style.left = `${windowWidth - menuRect.width - 10}px`;
|
||||
}
|
||||
|
||||
if (menuRect.bottom > windowHeight) {
|
||||
this.menu.style.top = `${windowHeight - menuRect.height - 10}px`;
|
||||
}
|
||||
}
|
||||
|
||||
hideMenu() {
|
||||
this.menu.style.display = 'none';
|
||||
this.currentTarget = null;
|
||||
}
|
||||
|
||||
addMenuItem(label, icon, action) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'context-menu-item';
|
||||
item.innerHTML = `<i class="fas ${icon}"></i> ${label}`;
|
||||
|
||||
item.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.hideMenu();
|
||||
action();
|
||||
});
|
||||
|
||||
this.menu.appendChild(item);
|
||||
}
|
||||
|
||||
addDivider() {
|
||||
const divider = document.createElement('div');
|
||||
divider.className = 'context-menu-divider';
|
||||
this.menu.appendChild(divider);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize context menu
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
window.contextMenu = new ContextMenu();
|
||||
});
|
208
app/static/js/main.js
Normal file
208
app/static/js/main.js
Normal file
|
@ -0,0 +1,208 @@
|
|||
// Main JavaScript file for Flask Files
|
||||
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Initialize components
|
||||
initializeViewToggle();
|
||||
initializeFolderNavigation();
|
||||
initializeModals();
|
||||
initializeContextMenu();
|
||||
initializeUploadFunctionality();
|
||||
|
||||
// Register service worker if supported
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/static/js/service-worker.js')
|
||||
.then(function (registration) {
|
||||
console.log('Service Worker registered with scope:', registration.scope);
|
||||
}).catch(function (error) {
|
||||
console.log('Service Worker registration failed:', error);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle between grid and list views
|
||||
function initializeViewToggle() {
|
||||
const gridViewBtn = document.getElementById('grid-view-btn');
|
||||
const listViewBtn = document.getElementById('list-view-btn');
|
||||
const filesContainer = document.getElementById('files-container');
|
||||
|
||||
if (gridViewBtn && listViewBtn && filesContainer) {
|
||||
gridViewBtn.addEventListener('click', function () {
|
||||
filesContainer.classList.add('grid-view');
|
||||
filesContainer.classList.remove('list-view');
|
||||
gridViewBtn.classList.add('active');
|
||||
listViewBtn.classList.remove('active');
|
||||
localStorage.setItem('fileViewPreference', 'grid');
|
||||
});
|
||||
|
||||
listViewBtn.addEventListener('click', function () {
|
||||
filesContainer.classList.add('list-view');
|
||||
filesContainer.classList.remove('grid-view');
|
||||
listViewBtn.classList.add('active');
|
||||
gridViewBtn.classList.remove('active');
|
||||
localStorage.setItem('fileViewPreference', 'list');
|
||||
});
|
||||
|
||||
// Load user preference from localStorage
|
||||
const viewPreference = localStorage.getItem('fileViewPreference') || 'grid';
|
||||
if (viewPreference === 'grid') {
|
||||
gridViewBtn.click();
|
||||
} else {
|
||||
listViewBtn.click();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add animations for folder navigation
|
||||
function initializeFolderNavigation() {
|
||||
// Add click event to folder items
|
||||
document.querySelectorAll('.folder-item').forEach(folder => {
|
||||
folder.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
|
||||
const href = this.getAttribute('href');
|
||||
const filesContainer = document.getElementById('files-container');
|
||||
|
||||
// Add transition class
|
||||
filesContainer.classList.add('changing');
|
||||
|
||||
// After a short delay, navigate to the folder
|
||||
setTimeout(() => {
|
||||
window.location.href = href;
|
||||
}, 200);
|
||||
});
|
||||
});
|
||||
|
||||
// Add the animation class when page loads
|
||||
const filesContainer = document.getElementById('files-container');
|
||||
if (filesContainer) {
|
||||
// Remove the class to trigger animation
|
||||
filesContainer.classList.add('folder-enter-active');
|
||||
}
|
||||
}
|
||||
|
||||
// Modal handling
|
||||
function initializeModals() {
|
||||
// New folder modal
|
||||
const newFolderBtn = document.getElementById('new-folder-btn');
|
||||
const newFolderModal = document.getElementById('new-folder-modal');
|
||||
const emptyNewFolderBtn = document.getElementById('empty-new-folder-btn');
|
||||
|
||||
if (newFolderBtn && newFolderModal) {
|
||||
newFolderBtn.addEventListener('click', function () {
|
||||
newFolderModal.style.display = 'flex';
|
||||
document.getElementById('folder-name').focus();
|
||||
});
|
||||
|
||||
if (emptyNewFolderBtn) {
|
||||
emptyNewFolderBtn.addEventListener('click', function () {
|
||||
newFolderModal.style.display = 'flex';
|
||||
document.getElementById('folder-name').focus();
|
||||
});
|
||||
}
|
||||
|
||||
// Close modal
|
||||
document.querySelectorAll('.modal-close, .modal-cancel').forEach(btn => {
|
||||
btn.addEventListener('click', function () {
|
||||
newFolderModal.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
// Close on click outside
|
||||
window.addEventListener('click', function (event) {
|
||||
if (event.target === newFolderModal) {
|
||||
newFolderModal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Context menu for right-click on files/folders
|
||||
function initializeContextMenu() {
|
||||
const contextMenu = document.getElementById('context-menu');
|
||||
|
||||
if (!contextMenu) return;
|
||||
|
||||
document.addEventListener('contextmenu', function (e) {
|
||||
const fileItem = e.target.closest('.file-item, .folder-item');
|
||||
|
||||
if (fileItem) {
|
||||
e.preventDefault();
|
||||
|
||||
const itemId = fileItem.getAttribute('data-id');
|
||||
const itemType = fileItem.classList.contains('file-item') ? 'file' : 'folder';
|
||||
|
||||
// Position menu
|
||||
contextMenu.style.left = `${e.pageX}px`;
|
||||
contextMenu.style.top = `${e.pageY}px`;
|
||||
|
||||
// Show menu
|
||||
contextMenu.style.display = 'block';
|
||||
contextMenu.setAttribute('data-item-id', itemId);
|
||||
contextMenu.setAttribute('data-item-type', itemType);
|
||||
|
||||
// Set up buttons for different item types
|
||||
setupContextMenuActions(contextMenu, itemId, itemType);
|
||||
}
|
||||
});
|
||||
|
||||
// Hide menu on click elsewhere
|
||||
document.addEventListener('click', function () {
|
||||
contextMenu.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
function setupContextMenuActions(menu, itemId, itemType) {
|
||||
// Show/hide appropriate actions based on item type
|
||||
menu.querySelectorAll('[data-action]').forEach(action => {
|
||||
const forType = action.getAttribute('data-for');
|
||||
if (forType === 'all' || forType === itemType) {
|
||||
action.style.display = 'block';
|
||||
} else {
|
||||
action.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Initialize file upload functionality
|
||||
function initializeUploadFunctionality() {
|
||||
const uploadBtn = document.querySelector('a[href*="upload"]');
|
||||
const fileInput = document.getElementById('file-upload');
|
||||
|
||||
if (uploadBtn && fileInput) {
|
||||
fileInput.addEventListener('change', function (e) {
|
||||
if (this.files.length) {
|
||||
const formData = new FormData();
|
||||
|
||||
for (let i = 0; i < this.files.length; i++) {
|
||||
formData.append('file', this.files[i]);
|
||||
}
|
||||
|
||||
// Get current folder ID from URL if available
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const folderId = urlParams.get('folder_id');
|
||||
|
||||
if (folderId) {
|
||||
formData.append('folder_id', folderId);
|
||||
}
|
||||
|
||||
fetch('/files/upload', {
|
||||
method: 'POST',
|
||||
body: formData,
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Refresh page to show new file
|
||||
window.location.reload();
|
||||
} else {
|
||||
alert('Upload failed: ' + data.error);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('Error:', error);
|
||||
alert('Upload failed. Please try again.');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
183
app/static/js/quick-upload.js
Normal file
183
app/static/js/quick-upload.js
Normal file
|
@ -0,0 +1,183 @@
|
|||
/**
|
||||
* Quick upload functionality for instant file uploads
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
// Elements
|
||||
const globalDropzone = document.getElementById('global-dropzone');
|
||||
const uploadToast = document.getElementById('upload-toast');
|
||||
const uploadProgressBar = document.getElementById('upload-toast-progress-bar');
|
||||
const uploadPercentage = document.getElementById('upload-toast-percentage');
|
||||
const uploadFileName = document.getElementById('upload-toast-file');
|
||||
const uploadToastClose = document.getElementById('upload-toast-close');
|
||||
|
||||
// Get current folder ID from URL or data attribute
|
||||
function getCurrentFolderId() {
|
||||
// Check if we're on a folder page
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const folderIdFromUrl = urlParams.get('folder_id');
|
||||
|
||||
// Check for a data attribute on the page
|
||||
const folderElement = document.querySelector('[data-current-folder-id]');
|
||||
const folderIdFromData = folderElement ? folderElement.dataset.currentFolderId : null;
|
||||
|
||||
return folderIdFromUrl || folderIdFromData || null;
|
||||
}
|
||||
|
||||
// Show global dropzone when files are dragged over the window
|
||||
window.addEventListener('dragover', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Only show dropzone if user is on dashboard or files page
|
||||
const onRelevantPage = window.location.pathname.includes('/dashboard') ||
|
||||
window.location.pathname.includes('/files');
|
||||
|
||||
if (onRelevantPage) {
|
||||
globalDropzone.classList.add('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Hide dropzone when dragging leaves window
|
||||
window.addEventListener('dragleave', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Only hide if leaving the window (not entering child elements)
|
||||
if (e.clientX <= 0 || e.clientY <= 0 ||
|
||||
e.clientX >= window.innerWidth || e.clientY >= window.innerHeight) {
|
||||
globalDropzone.classList.remove('active');
|
||||
}
|
||||
});
|
||||
|
||||
// Handle drop event for quick upload
|
||||
window.addEventListener('drop', function (e) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
// Hide the dropzone
|
||||
globalDropzone.classList.remove('active');
|
||||
|
||||
// Make sure files were dropped
|
||||
if (!e.dataTransfer.files || e.dataTransfer.files.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Show upload progress toast
|
||||
uploadToast.classList.add('active');
|
||||
|
||||
// Get the current folder ID (null for root)
|
||||
const currentFolderId = getCurrentFolderId();
|
||||
|
||||
// Upload the files
|
||||
uploadFiles(e.dataTransfer.files, currentFolderId);
|
||||
});
|
||||
|
||||
// Close upload toast
|
||||
if (uploadToastClose) {
|
||||
uploadToastClose.addEventListener('click', function () {
|
||||
uploadToast.classList.remove('active');
|
||||
});
|
||||
}
|
||||
|
||||
// Quick upload function
|
||||
function uploadFiles(files, folderId) {
|
||||
// Create FormData object
|
||||
const formData = new FormData();
|
||||
|
||||
// Add folder ID if provided
|
||||
if (folderId) {
|
||||
formData.append('parent_folder_id', folderId);
|
||||
}
|
||||
|
||||
// Add all files
|
||||
for (let i = 0; i < files.length; i++) {
|
||||
formData.append('files[]', files[i]);
|
||||
}
|
||||
|
||||
// Create XHR request
|
||||
const xhr = new XMLHttpRequest();
|
||||
|
||||
// Update progress
|
||||
xhr.upload.addEventListener('progress', function (e) {
|
||||
if (e.lengthComputable) {
|
||||
const percent = Math.round((e.loaded / e.total) * 100);
|
||||
uploadProgressBar.style.width = percent + '%';
|
||||
uploadPercentage.textContent = percent + '%';
|
||||
|
||||
if (files.length === 1) {
|
||||
uploadFileName.textContent = files[0].name;
|
||||
} else {
|
||||
uploadFileName.textContent = `Uploading ${files.length} files...`;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Handle completion
|
||||
xhr.addEventListener('load', function () {
|
||||
if (xhr.status >= 200 && xhr.status < 300) {
|
||||
// Success - upload complete
|
||||
uploadProgressBar.style.width = '100%';
|
||||
uploadPercentage.textContent = '100%';
|
||||
uploadFileName.textContent = 'Upload Complete!';
|
||||
|
||||
// Show success alert
|
||||
if (typeof showAlert === 'function') {
|
||||
showAlert('Files uploaded successfully!', 'success');
|
||||
}
|
||||
|
||||
// Reload page after brief delay to show new files
|
||||
setTimeout(function () {
|
||||
window.location.reload();
|
||||
}, 1000);
|
||||
} else {
|
||||
// Error
|
||||
let errorMessage = 'Upload failed';
|
||||
|
||||
try {
|
||||
const response = JSON.parse(xhr.responseText);
|
||||
if (response.error) {
|
||||
errorMessage = response.error;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parsing error
|
||||
}
|
||||
|
||||
uploadFileName.textContent = errorMessage;
|
||||
|
||||
if (typeof showAlert === 'function') {
|
||||
showAlert(errorMessage, 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// Hide toast after delay
|
||||
setTimeout(function () {
|
||||
uploadToast.classList.remove('active');
|
||||
|
||||
// Reset progress
|
||||
setTimeout(function () {
|
||||
uploadProgressBar.style.width = '0%';
|
||||
uploadPercentage.textContent = '0%';
|
||||
uploadFileName.textContent = 'Processing...';
|
||||
}, 300);
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
// Handle errors
|
||||
xhr.addEventListener('error', function () {
|
||||
uploadFileName.textContent = 'Network error occurred';
|
||||
|
||||
if (typeof showAlert === 'function') {
|
||||
showAlert('Network error occurred', 'error');
|
||||
}
|
||||
|
||||
// Hide toast after delay
|
||||
setTimeout(function () {
|
||||
uploadToast.classList.remove('active');
|
||||
}, 3000);
|
||||
});
|
||||
|
||||
// Set up and send the request
|
||||
xhr.open('POST', '/files/upload_xhr', true);
|
||||
xhr.send(formData);
|
||||
}
|
||||
});
|
62
app/static/js/theme.js
Normal file
62
app/static/js/theme.js
Normal file
|
@ -0,0 +1,62 @@
|
|||
/**
|
||||
* Theme handling functionality
|
||||
*/
|
||||
document.addEventListener('DOMContentLoaded', function () {
|
||||
const themeToggle = document.querySelector('.theme-toggle-icon');
|
||||
|
||||
if (themeToggle) {
|
||||
themeToggle.addEventListener('click', function () {
|
||||
const currentTheme = document.documentElement.getAttribute('data-theme') || 'system';
|
||||
let newTheme;
|
||||
|
||||
if (currentTheme === 'dark') {
|
||||
newTheme = 'light';
|
||||
} else {
|
||||
newTheme = 'dark';
|
||||
}
|
||||
|
||||
// Update theme
|
||||
document.documentElement.setAttribute('data-theme', newTheme);
|
||||
|
||||
// Store preference
|
||||
localStorage.setItem('theme_preference', newTheme);
|
||||
|
||||
// Update icon
|
||||
updateThemeIcon(newTheme);
|
||||
});
|
||||
|
||||
// Initialize theme on page load
|
||||
initTheme();
|
||||
}
|
||||
|
||||
function initTheme() {
|
||||
// Get saved preference
|
||||
let theme = localStorage.getItem('theme_preference');
|
||||
|
||||
// If no preference, check system preference
|
||||
if (!theme) {
|
||||
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
|
||||
theme = 'dark';
|
||||
} else {
|
||||
theme = 'light';
|
||||
}
|
||||
}
|
||||
|
||||
// Apply theme
|
||||
document.documentElement.setAttribute('data-theme', theme);
|
||||
|
||||
// Update icon
|
||||
updateThemeIcon(theme);
|
||||
}
|
||||
|
||||
function updateThemeIcon(theme) {
|
||||
const icon = document.querySelector('.theme-toggle-icon i');
|
||||
if (icon) {
|
||||
if (theme === 'dark') {
|
||||
icon.className = 'fas fa-sun';
|
||||
} else {
|
||||
icon.className = 'fas fa-moon';
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
|
@ -10,6 +10,9 @@ document.addEventListener('DOMContentLoaded', function () {
|
|||
const fileList = document.getElementById('file-list');
|
||||
const folderList = document.getElementById('folder-file-list');
|
||||
|
||||
// Fix to avoid darkModeToggle is null error
|
||||
const darkModeToggle = document.querySelector('.theme-toggle-icon');
|
||||
|
||||
// Progress elements
|
||||
const progressBar = document.getElementById('progress-bar');
|
||||
const progressPercentage = document.getElementById('progress-percentage');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue