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

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