kinda working safe point

This commit is contained in:
pika 2025-03-23 03:29:05 +01:00
parent b9a82af12f
commit 6dda02141e
31 changed files with 4302 additions and 2937 deletions

View file

@ -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);

View 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
View 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
View 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
View 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();
});
}
});

View 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
View 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.');
});
}
});
}
}

View 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
View 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';
}
}
}
});

View file

@ -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');