kinda working safe point
This commit is contained in:
parent
b9a82af12f
commit
6dda02141e
31 changed files with 4302 additions and 2937 deletions
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