Header Drop Zone Extension

This commit is contained in:
Ryan
2025-04-03 15:12:48 -04:00
committed by GitHub
parent 84822e699e
commit 9020251ed5
5 changed files with 617 additions and 305 deletions

View File

@@ -147,6 +147,10 @@ FileRise is a lightweight, secure, self-hosted web application for uploading, sy
- **Top Bar Drop Zone:** - **Top Bar Drop Zone:**
- A top drop zone is available for reordering or managing cards quickly. - A top drop zone is available for reordering or managing cards quickly.
- Dragging a card to the top drop zone provides immediate visual feedback, ensuring a fluid and customizable workflow. - Dragging a card to the top drop zone provides immediate visual feedback, ensuring a fluid and customizable workflow.
- **Header Drop Zone with State Preservation:**
- Cards can be dragged into the header drop zone, where they are represented by a compact material icon.
- **State Preservation:** Instead of removing the card from the DOM, the original card is moved into a hidden container. This ensures that dynamic features (such as the folder tree in the Folder Management card or file selection in the Upload card) remain fully initialized and retain their state on page refresh.
- **Modal Display:** When the user interacts (via hover or click) with the header icon, the card is temporarily moved into a modal overlay for full interaction. When the modal is closed, the card is returned to the hidden container, keeping its state persistent.
- **Seamless Interaction:** - **Seamless Interaction:**
- Both drop zones support smooth drag-and-drop interactions with animations and pointer event adjustments, ensuring reliable card placement regardless of screen position. - Both drop zones support smooth drag-and-drop interactions with animations and pointer event adjustments, ensuring reliable card placement regardless of screen position.
@@ -170,10 +174,10 @@ FileRise is a lightweight, secure, self-hosted web application for uploading, sy
- Features an intuitive interface with Material Icons for quick recognition and access. - Features an intuitive interface with Material Icons for quick recognition and access.
- Allows administrators to manage authentication settings, user management, and login methods in real time. - Allows administrators to manage authentication settings, user management, and login methods in real time.
- Includes real-time validation that prevents the accidental disabling of all authentication methods simultaneously. - Includes real-time validation that prevents the accidental disabling of all authentication methods simultaneously.
- User Permissions options - **User Permissions Options:**
- Folder Only gives user their own root folder - *Folder Only* gives user their own root folder.
- Read Only makes it so user can only read the files - *Read Only* makes it so the user can only read the files.
- Disable upload - *Disable Upload* prevents file uploads.
--- ---

View File

@@ -1,364 +1,599 @@
// dragAndDrop.js // dragAndDrop.js
// This file handles drag-and-drop functionality for cards in the sidebar, header and top drop zones.
// It also manages the visibility of the sidebar and header drop zones based on the current state of the application.
// It includes functions to save and load the order of cards in the sidebar and header from localStorage.
// It also includes functions to handle the drag-and-drop events, including mouse movements and drop zones.
// It uses CSS classes to manage the appearance of the sidebar and header drop zones during drag-and-drop operations.
// Moves cards into the sidebar based on the saved order in localStorage. // Moves cards into the sidebar based on the saved order in localStorage.
export function loadSidebarOrder() { export function loadSidebarOrder() {
const sidebar = document.getElementById('sidebarDropArea'); const sidebar = document.getElementById('sidebarDropArea');
if (!sidebar) return; if (!sidebar) return;
const orderStr = localStorage.getItem('sidebarOrder'); const orderStr = localStorage.getItem('sidebarOrder');
if (orderStr) { if (orderStr) {
const order = JSON.parse(orderStr); const order = JSON.parse(orderStr);
if (order.length > 0) { if (order.length > 0) {
// Ensure main wrapper is visible. // Ensure main wrapper is visible.
const mainWrapper = document.querySelector('.main-wrapper'); const mainWrapper = document.querySelector('.main-wrapper');
if (mainWrapper) { if (mainWrapper) {
mainWrapper.style.display = 'flex'; mainWrapper.style.display = 'flex';
}
// For each saved ID, move the card into the sidebar.
order.forEach(id => {
const card = document.getElementById(id);
if (card && card.parentNode.id !== 'sidebarDropArea') {
sidebar.appendChild(card);
// Animate vertical slide for sidebar card
animateVerticalSlide(card);
} }
// For each saved ID, move the card into the sidebar. });
order.forEach(id => {
const card = document.getElementById(id);
if (card && card.parentNode.id !== 'sidebarDropArea') {
sidebar.appendChild(card);
// Animate vertical slide for sidebar card
animateVerticalSlide(card);
}
});
} }
}
updateSidebarVisibility();
} }
updateSidebarVisibility();
}
// Internal helper: update sidebar visibility based on its content. // NEW: Load header order from localStorage.
function updateSidebarVisibility() { export function loadHeaderOrder() {
const sidebar = document.getElementById('sidebarDropArea'); const headerDropArea = document.getElementById('headerDropArea');
if (sidebar) { if (!headerDropArea) return;
const orderStr = localStorage.getItem('headerOrder');
if (orderStr) {
const order = JSON.parse(orderStr);
if (order.length > 0) {
order.forEach(id => {
const card = document.getElementById(id);
// Only load if card is not already in header drop zone.
if (card && card.parentNode.id !== 'headerDropArea') {
insertCardInHeader(card, null);
}
});
}
}
}
// Internal helper: update sidebar visibility based on its content.
function updateSidebarVisibility() {
const sidebar = document.getElementById('sidebarDropArea');
if (sidebar) {
const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard'); const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
if (cards.length > 0) { if (cards.length > 0) {
sidebar.classList.add('active'); sidebar.classList.add('active');
sidebar.style.display = 'block'; sidebar.style.display = 'block';
} else { } else {
sidebar.classList.remove('active'); sidebar.classList.remove('active');
sidebar.style.display = 'none'; sidebar.style.display = 'none';
} }
// Save the current order in localStorage. // Save the current order in localStorage.
saveSidebarOrder(); saveSidebarOrder();
}
} }
}
// Internal helper: update top zone layout (center a card if one column is empty). // NEW: Save header order to localStorage.
function updateTopZoneLayout() { function saveHeaderOrder() {
const leftCol = document.getElementById('leftCol'); const headerDropArea = document.getElementById('headerDropArea');
const rightCol = document.getElementById('rightCol'); if (headerDropArea) {
const icons = Array.from(headerDropArea.children);
// Each header icon stores its associated card in the property cardElement.
const order = icons.map(icon => icon.cardElement.id);
localStorage.setItem('headerOrder', JSON.stringify(order));
}
}
const leftIsEmpty = !leftCol.querySelector('#uploadCard'); // Internal helper: update top zone layout (center a card if one column is empty).
const rightIsEmpty = !rightCol.querySelector('#folderManagementCard'); function updateTopZoneLayout() {
const leftCol = document.getElementById('leftCol');
const rightCol = document.getElementById('rightCol');
if (leftIsEmpty && !rightIsEmpty) { const leftIsEmpty = !leftCol.querySelector('#uploadCard');
const rightIsEmpty = !rightCol.querySelector('#folderManagementCard');
if (leftIsEmpty && !rightIsEmpty) {
leftCol.style.display = 'none'; leftCol.style.display = 'none';
rightCol.style.margin = '0 auto'; rightCol.style.margin = '0 auto';
} else if (rightIsEmpty && !leftIsEmpty) { } else if (rightIsEmpty && !leftIsEmpty) {
rightCol.style.display = 'none'; rightCol.style.display = 'none';
leftCol.style.margin = '0 auto'; leftCol.style.margin = '0 auto';
} else { } else {
leftCol.style.display = ''; leftCol.style.display = '';
rightCol.style.display = ''; rightCol.style.display = '';
leftCol.style.margin = ''; leftCol.style.margin = '';
rightCol.style.margin = ''; rightCol.style.margin = '';
}
} }
}
// When a card is being dragged, if the top drop zone is empty, set its min-height. // When a card is being dragged, if the top drop zone is empty, set its min-height.
function addTopZoneHighlight() { function addTopZoneHighlight() {
const topZone = document.getElementById('uploadFolderRow'); const topZone = document.getElementById('uploadFolderRow');
if (topZone) { if (topZone) {
topZone.classList.add('highlight'); topZone.classList.add('highlight');
if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) { if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
topZone.style.minHeight = '375px'; topZone.style.minHeight = '375px';
} }
}
} }
}
// When the drag ends, remove the extra min-height. // When the drag ends, remove the extra min-height.
function removeTopZoneHighlight() { function removeTopZoneHighlight() {
const topZone = document.getElementById('uploadFolderRow'); const topZone = document.getElementById('uploadFolderRow');
if (topZone) { if (topZone) {
topZone.classList.remove('highlight'); topZone.classList.remove('highlight');
topZone.style.minHeight = ''; topZone.style.minHeight = '';
}
} }
}
// Vertical slide/fade animation helper. // Vertical slide/fade animation helper.
function animateVerticalSlide(card) { function animateVerticalSlide(card) {
card.style.transform = 'translateY(30px)'; card.style.transform = 'translateY(30px)';
card.style.opacity = '0'; card.style.opacity = '0';
// Force reflow. // Force reflow.
card.offsetWidth; card.offsetWidth;
requestAnimationFrame(() => { requestAnimationFrame(() => {
card.style.transition = 'transform 0.3s ease, opacity 0.3s ease'; card.style.transition = 'transform 0.3s ease, opacity 0.3s ease';
card.style.transform = 'translateY(0)'; card.style.transform = 'translateY(0)';
card.style.opacity = '1'; card.style.opacity = '1';
}); });
setTimeout(() => { setTimeout(() => {
card.style.transition = ''; card.style.transition = '';
card.style.transform = ''; card.style.transform = '';
card.style.opacity = ''; card.style.opacity = '';
}, 310); }, 310);
} }
// Internal helper: insert card into sidebar at a proper position based on event.clientY. // Internal helper: insert card into sidebar at a proper position based on event.clientY.
function insertCardInSidebar(card, event) { function insertCardInSidebar(card, event) {
const sidebar = document.getElementById('sidebarDropArea'); const sidebar = document.getElementById('sidebarDropArea');
if (!sidebar) return; if (!sidebar) return;
const existingCards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard')); const existingCards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
let inserted = false; let inserted = false;
for (const currentCard of existingCards) { for (const currentCard of existingCards) {
const rect = currentCard.getBoundingClientRect(); const rect = currentCard.getBoundingClientRect();
const midY = rect.top + rect.height / 2; const midY = rect.top + rect.height / 2;
if (event.clientY < midY) { if (event.clientY < midY) {
sidebar.insertBefore(card, currentCard); sidebar.insertBefore(card, currentCard);
inserted = true; inserted = true;
break; break;
} }
} }
if (!inserted) { if (!inserted) {
sidebar.appendChild(card); sidebar.appendChild(card);
}
// Ensure card fills the sidebar.
card.style.width = '100%';
animateVerticalSlide(card);
} }
// Ensure card fills the sidebar.
card.style.width = '100%';
animateVerticalSlide(card);
}
// Internal helper: save the current sidebar card order to localStorage. // Internal helper: save the current sidebar card order to localStorage.
function saveSidebarOrder() { function saveSidebarOrder() {
const sidebar = document.getElementById('sidebarDropArea'); const sidebar = document.getElementById('sidebarDropArea');
if (sidebar) { if (sidebar) {
const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard'); const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
const order = Array.from(cards).map(card => card.id); const order = Array.from(cards).map(card => card.id);
localStorage.setItem('sidebarOrder', JSON.stringify(order)); localStorage.setItem('sidebarOrder', JSON.stringify(order));
}
} }
}
// Helper: move cards from sidebar back to the top drop area when on small screens. // Helper: move cards from sidebar back to the top drop area when on small screens.
function moveSidebarCardsToTop() { function moveSidebarCardsToTop() {
if (window.innerWidth < 1205) { if (window.innerWidth < 1205) {
const sidebar = document.getElementById('sidebarDropArea'); const sidebar = document.getElementById('sidebarDropArea');
if (!sidebar) return; if (!sidebar) return;
const cards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard')); const cards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
cards.forEach(card => { cards.forEach(card => {
const orig = document.getElementById(card.dataset.originalContainerId); const orig = document.getElementById(card.dataset.originalContainerId);
if (orig) { if (orig) {
orig.appendChild(card); orig.appendChild(card);
animateVerticalSlide(card); animateVerticalSlide(card);
} }
}); });
updateSidebarVisibility(); updateSidebarVisibility();
updateTopZoneLayout(); updateTopZoneLayout();
}
} }
}
// Listen for window resize to automatically move sidebar cards back to top on small screens. // Listen for window resize to automatically move sidebar cards back to top on small screens.
window.addEventListener('resize', function () { window.addEventListener('resize', function () {
if (window.innerWidth < 1205) { if (window.innerWidth < 1205) {
moveSidebarCardsToTop(); moveSidebarCardsToTop();
} }
}); });
// This function ensures the top drop zone (#uploadFolderRow) has a stable width when empty. // This function ensures the top drop zone (#uploadFolderRow) has a stable width when empty.
function ensureTopZonePlaceholder() { function ensureTopZonePlaceholder() {
const topZone = document.getElementById('uploadFolderRow'); const topZone = document.getElementById('uploadFolderRow');
if (!topZone) return; if (!topZone) return;
if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) { if (topZone.querySelectorAll('#uploadCard, #folderManagementCard').length === 0) {
let placeholder = topZone.querySelector('.placeholder'); let placeholder = topZone.querySelector('.placeholder');
if (!placeholder) { if (!placeholder) {
placeholder = document.createElement('div'); placeholder = document.createElement('div');
placeholder.className = 'placeholder'; placeholder.className = 'placeholder';
placeholder.style.visibility = 'hidden'; placeholder.style.visibility = 'hidden';
placeholder.style.display = 'block'; placeholder.style.display = 'block';
placeholder.style.width = '100%'; placeholder.style.width = '100%';
placeholder.style.height = '375px'; placeholder.style.height = '375px';
topZone.appendChild(placeholder); topZone.appendChild(placeholder);
} }
} else { } else {
const placeholder = topZone.querySelector('.placeholder'); const placeholder = topZone.querySelector('.placeholder');
if (placeholder) placeholder.remove(); if (placeholder) placeholder.remove();
}
} }
}
// This sets up all drag-and-drop event listeners for cards. // --- NEW HELPER FUNCTIONS FOR HEADER DROP ZONE ---
export function initDragAndDrop() {
function run() { // Show header drop zone and add a "drag-active" class so that the pseudo-element appears.
function showHeaderDropZone() {
const headerDropArea = document.getElementById('headerDropArea');
if (headerDropArea) {
headerDropArea.style.display = 'inline-flex';
headerDropArea.classList.add('drag-active');
}
}
// Hide header drop zone by removing the "drag-active" class.
// If a header icon is present (i.e. a card was dropped), the drop zone remains visible without the dashed border.
function hideHeaderDropZone() {
const headerDropArea = document.getElementById('headerDropArea');
if (headerDropArea) {
headerDropArea.classList.remove('drag-active');
if (headerDropArea.children.length === 0) {
headerDropArea.style.display = 'none';
}
}
}
// === NEW FUNCTION: Insert card into header drop zone as a material icon ===
function insertCardInHeader(card, event) {
const headerDropArea = document.getElementById('headerDropArea');
if (!headerDropArea) return;
// For folder management and upload cards, preserve the original by moving it to a hidden container.
if (card.id === 'folderManagementCard' || card.id === 'uploadCard') {
let hiddenContainer = document.getElementById('hiddenCardsContainer');
if (!hiddenContainer) {
hiddenContainer = document.createElement('div');
hiddenContainer.id = 'hiddenCardsContainer';
hiddenContainer.style.display = 'none';
document.body.appendChild(hiddenContainer);
}
// Move the original card to the hidden container if it's not already there.
if (card.parentNode.id !== 'hiddenCardsContainer') {
hiddenContainer.appendChild(card);
}
} else {
// For other cards, simply remove from current container.
if (card.parentNode) {
card.parentNode.removeChild(card);
}
}
// Create the header icon button.
const iconButton = document.createElement('button');
iconButton.className = 'header-card-icon';
// Remove default button styling.
iconButton.style.border = 'none';
iconButton.style.background = 'none';
iconButton.style.outline = 'none';
iconButton.style.cursor = 'pointer';
// Choose an icon based on the card type with 24px size.
if (card.id === 'uploadCard') {
iconButton.innerHTML = '<i class="material-icons" style="font-size:24px;">cloud_upload</i>';
} else if (card.id === 'folderManagementCard') {
iconButton.innerHTML = '<i class="material-icons" style="font-size:24px;">folder</i>';
} else {
iconButton.innerHTML = '<i class="material-icons" style="font-size:24px;">insert_drive_file</i>';
}
// Save a reference to the card in the icon button.
iconButton.cardElement = card;
// Associate this icon with the card for future removal.
card.headerIconButton = iconButton;
let modal = null;
let isLocked = false;
let hoverActive = false;
// showModal: When triggered, ensure the card is attached to the modal.
function showModal() {
if (!modal) {
modal = document.createElement('div');
modal.className = 'header-card-modal';
modal.style.position = 'fixed';
modal.style.top = '80px';
modal.style.right = '80px';
modal.style.zIndex = '11000';
// Render the modal but initially keep it hidden.
modal.style.display = 'block';
modal.style.visibility = 'hidden';
modal.style.opacity = '0';
modal.style.background = 'none';
modal.style.border = 'none';
modal.style.padding = '0';
modal.style.boxShadow = 'none';
document.body.appendChild(modal);
// Attach modal hover events.
modal.addEventListener('mouseover', handleMouseOver);
modal.addEventListener('mouseout', handleMouseOut);
iconButton.modalInstance = modal;
}
// If the card isn't already in the modal, remove it from the hidden container and attach it.
if (!modal.contains(card)) {
const hiddenContainer = document.getElementById('hiddenCardsContainer');
if (hiddenContainer && hiddenContainer.contains(card)) {
hiddenContainer.removeChild(card);
}
modal.appendChild(card);
}
// Reveal the modal.
modal.style.visibility = 'visible';
modal.style.opacity = '1';
}
// hideModal: Hide the modal and return the card to the hidden container.
function hideModal() {
if (modal && !isLocked && !hoverActive) {
modal.style.visibility = 'hidden';
modal.style.opacity = '0';
// Return the card to the hidden container.
const hiddenContainer = document.getElementById('hiddenCardsContainer');
if (hiddenContainer && modal.contains(card)) {
hiddenContainer.appendChild(card);
}
}
}
function handleMouseOver() {
hoverActive = true;
showModal();
}
function handleMouseOut() {
hoverActive = false;
setTimeout(() => {
if (!hoverActive && !isLocked) {
hideModal();
}
}, 300);
}
// Attach hover events to the icon.
iconButton.addEventListener('mouseover', handleMouseOver);
iconButton.addEventListener('mouseout', handleMouseOut);
// Toggle the locked state on click so the modal stays open.
iconButton.addEventListener('click', (e) => {
isLocked = !isLocked;
if (isLocked) {
showModal();
} else {
hideModal();
}
e.stopPropagation();
});
// Append the header icon button into the header drop zone.
headerDropArea.appendChild(iconButton);
// Save the updated header order.
saveHeaderOrder();
}
// === Main Drag and Drop Initialization ===
export function initDragAndDrop() {
function run() {
const draggableCards = document.querySelectorAll('#uploadCard, #folderManagementCard'); const draggableCards = document.querySelectorAll('#uploadCard, #folderManagementCard');
draggableCards.forEach(card => { draggableCards.forEach(card => {
if (!card.dataset.originalContainerId) { if (!card.dataset.originalContainerId) {
card.dataset.originalContainerId = card.parentNode.id; card.dataset.originalContainerId = card.parentNode.id;
} }
const header = card.querySelector('.card-header'); const header = card.querySelector('.card-header');
if (header) { if (header) {
header.classList.add('drag-header'); header.classList.add('drag-header');
} }
let isDragging = false; let isDragging = false;
let dragTimer = null; let dragTimer = null;
let offsetX = 0, offsetY = 0; let offsetX = 0, offsetY = 0;
let initialLeft, initialTop; let initialLeft, initialTop;
if (header) { if (header) {
header.addEventListener('mousedown', function (e) { header.addEventListener('mousedown', function (e) {
e.preventDefault(); e.preventDefault();
const card = this.closest('.card'); const card = this.closest('.card');
// Capture the card's initial bounding rectangle once. // Capture the card's initial bounding rectangle.
const initialRect = card.getBoundingClientRect(); const initialRect = card.getBoundingClientRect();
const originX = ((e.clientX - initialRect.left) / initialRect.width) * 100; const originX = ((e.clientX - initialRect.left) / initialRect.width) * 100;
const originY = ((e.clientY - initialRect.top) / initialRect.height) * 100; const originY = ((e.clientY - initialRect.top) / initialRect.height) * 100;
card.style.transformOrigin = `${originX}% ${originY}%`; card.style.transformOrigin = `${originX}% ${originY}%`;
// Store the initial rect so we use it later. // Store the initial rect so we use it later.
dragTimer = setTimeout(() => { dragTimer = setTimeout(() => {
isDragging = true; isDragging = true;
card.classList.add('dragging'); card.classList.add('dragging');
card.style.pointerEvents = 'none'; card.style.pointerEvents = 'none';
addTopZoneHighlight(); addTopZoneHighlight();
const sidebar = document.getElementById('sidebarDropArea'); const sidebar = document.getElementById('sidebarDropArea');
if (sidebar) { if (sidebar) {
sidebar.classList.add('active'); sidebar.classList.add('active');
sidebar.style.display = 'block'; sidebar.style.display = 'block';
sidebar.classList.add('highlight'); sidebar.classList.add('highlight');
sidebar.style.height = '800px'; sidebar.style.height = '800px';
}
// Use the stored initialRect rather than recalculating.
initialLeft = initialRect.left + window.pageXOffset;
initialTop = initialRect.top + window.pageYOffset;
offsetX = e.pageX - initialLeft;
offsetY = e.pageY - initialTop;
// Append card to body and fix its dimensions to prevent shrinking.
document.body.appendChild(card);
card.style.position = 'absolute';
card.style.left = initialLeft + 'px';
card.style.top = initialTop + 'px';
card.style.width = initialRect.width + 'px';
card.style.height = initialRect.height + 'px';
card.style.minWidth = initialRect.width + 'px';
card.style.flexShrink = '0';
card.style.zIndex = '10000';
}, 500);
});
header.addEventListener('mouseup', function () {
clearTimeout(dragTimer);
});
}
document.addEventListener('mousemove', function (e) {
if (isDragging) {
card.style.left = (e.pageX - offsetX) + 'px';
card.style.top = (e.pageY - offsetY) + 'px';
} }
// Show header drop zone while dragging.
showHeaderDropZone();
// Use the stored initialRect.
initialLeft = initialRect.left + window.pageXOffset;
initialTop = initialRect.top + window.pageYOffset;
offsetX = e.pageX - initialLeft;
offsetY = e.pageY - initialTop;
// Remove any associated header icon if present.
if (card.headerIconButton) {
if (card.headerIconButton.parentNode) {
card.headerIconButton.parentNode.removeChild(card.headerIconButton);
}
if (card.headerIconButton.modalInstance && card.headerIconButton.modalInstance.parentNode) {
card.headerIconButton.modalInstance.parentNode.removeChild(card.headerIconButton.modalInstance);
}
card.headerIconButton = null;
saveHeaderOrder();
}
// Append card to body and fix its dimensions.
document.body.appendChild(card);
card.style.position = 'absolute';
card.style.left = initialLeft + 'px';
card.style.top = initialTop + 'px';
card.style.width = initialRect.width + 'px';
card.style.height = initialRect.height + 'px';
card.style.minWidth = initialRect.width + 'px';
card.style.flexShrink = '0';
card.style.zIndex = '10000';
}, 500);
}); });
header.addEventListener('mouseup', function () {
clearTimeout(dragTimer);
});
}
document.addEventListener('mouseup', function (e) { document.addEventListener('mousemove', function (e) {
if (isDragging) { if (isDragging) {
isDragging = false; card.style.left = (e.pageX - offsetX) + 'px';
card.style.pointerEvents = ''; card.style.top = (e.pageY - offsetY) + 'px';
card.classList.remove('dragging'); }
removeTopZoneHighlight(); });
const sidebar = document.getElementById('sidebarDropArea'); document.addEventListener('mouseup', function (e) {
if (sidebar) { if (isDragging) {
sidebar.classList.remove('highlight'); isDragging = false;
sidebar.style.height = ''; card.style.pointerEvents = '';
} card.classList.remove('dragging');
removeTopZoneHighlight();
let droppedInSidebar = false; const sidebar = document.getElementById('sidebarDropArea');
let droppedInTop = false; if (sidebar) {
sidebar.classList.remove('highlight');
sidebar.style.height = '';
}
// Check if dropped in sidebar drop zone. // Remove any existing header icon if present.
const sidebarElem = document.getElementById('sidebarDropArea'); if (card.headerIconButton) {
if (sidebarElem) { if (card.headerIconButton.parentNode) {
const rect = sidebarElem.getBoundingClientRect(); card.headerIconButton.parentNode.removeChild(card.headerIconButton);
const dropZoneBottom = rect.top + 800; // Virtual drop zone height. }
if ( if (card.headerIconButton.modalInstance && card.headerIconButton.modalInstance.parentNode) {
e.clientX >= rect.left && card.headerIconButton.modalInstance.parentNode.removeChild(card.headerIconButton.modalInstance);
e.clientX <= rect.right && }
e.clientY >= rect.top && card.headerIconButton = null;
e.clientY <= dropZoneBottom saveHeaderOrder();
) { }
insertCardInSidebar(card, e);
droppedInSidebar = true;
}
}
// If not dropped in sidebar, check the top drop zone.
const topRow = document.getElementById('uploadFolderRow');
if (!droppedInSidebar && topRow) {
const rect = topRow.getBoundingClientRect();
if (
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom
) {
let container;
if (card.id === 'uploadCard') {
container = document.getElementById('leftCol');
} else if (card.id === 'folderManagementCard') {
container = document.getElementById('rightCol');
}
if (container) {
ensureTopZonePlaceholder();
updateTopZoneLayout();
container.appendChild(card);
droppedInTop = true;
// Use computed style to determine container's width.
const containerWidth = parseFloat(window.getComputedStyle(container).width);
card.style.width = "363px";
// Animate the card sliding in.
animateVerticalSlide(card);
// After animation completes, clear the inline width.
setTimeout(() => {
card.style.removeProperty('width');
}, 210);
}
}
}
// If dropped in neither area, return card to its original container. let droppedInSidebar = false;
if (!droppedInSidebar && !droppedInTop) { let droppedInTop = false;
const orig = document.getElementById(card.dataset.originalContainerId); let droppedInHeader = false;
if (orig) {
orig.appendChild(card);
card.style.removeProperty('width');
}
}
// Clear inline styles from dragging.
[
'position',
'left',
'top',
'z-index',
'height',
'min-width',
'flex-shrink',
'transition',
'transform',
'opacity'
].forEach(prop => card.style.removeProperty(prop));
// For sidebar drops, force width to 100%.
if (droppedInSidebar) {
card.style.width = '100%';
}
// Check if dropped in sidebar drop zone.
const sidebarElem = document.getElementById('sidebarDropArea');
if (sidebarElem) {
const rect = sidebarElem.getBoundingClientRect();
const dropZoneBottom = rect.top + 800; // Virtual drop zone height.
if (
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= dropZoneBottom
) {
insertCardInSidebar(card, e);
droppedInSidebar = true;
}
}
// Check the top drop zone.
const topRow = document.getElementById('uploadFolderRow');
if (!droppedInSidebar && topRow) {
const rect = topRow.getBoundingClientRect();
if (
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom
) {
let container;
if (card.id === 'uploadCard') {
container = document.getElementById('leftCol');
} else if (card.id === 'folderManagementCard') {
container = document.getElementById('rightCol');
}
if (container) {
ensureTopZonePlaceholder();
updateTopZoneLayout(); updateTopZoneLayout();
updateSidebarVisibility(); container.appendChild(card);
droppedInTop = true;
// Set a fixed width during animation.
card.style.width = "363px";
animateVerticalSlide(card);
setTimeout(() => {
card.style.removeProperty('width');
}, 210);
}
} }
}); }
}); // Check the header drop zone.
} const headerDropArea = document.getElementById('headerDropArea');
if (!droppedInSidebar && !droppedInTop && headerDropArea) {
const rect = headerDropArea.getBoundingClientRect();
if (
e.clientX >= rect.left &&
e.clientX <= rect.right &&
e.clientY >= rect.top &&
e.clientY <= rect.bottom
) {
insertCardInHeader(card, e);
droppedInHeader = true;
}
}
// If card was not dropped in any zone, return it to its original container.
if (!droppedInSidebar && !droppedInTop && !droppedInHeader) {
const orig = document.getElementById(card.dataset.originalContainerId);
if (orig) {
orig.appendChild(card);
card.style.removeProperty('width');
}
}
if (document.readyState === 'loading') { // Clear inline drag-related styles.
[
'position',
'left',
'top',
'z-index',
'height',
'min-width',
'flex-shrink',
'transition',
'transform',
'opacity'
].forEach(prop => card.style.removeProperty(prop));
// For sidebar drops, force width to 100%.
if (droppedInSidebar) {
card.style.width = '100%';
}
updateTopZoneLayout();
updateSidebarVisibility();
// Hide header drop zone if no icon is present.
hideHeaderDropZone();
}
});
});
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', run); document.addEventListener('DOMContentLoaded', run);
} else { } else {
run(); run();
}
} }
}

View File

@@ -95,6 +95,9 @@
<h1>FileRise</h1> <h1>FileRise</h1>
</div> </div>
<div class="header-right"> <div class="header-right">
<div class="header-buttons-wrapper" style="display: flex; align-items: center; gap: 10px;">
<!-- Your header drop zone -->
<div id="headerDropArea" class="header-drop-zone"></div>
<div class="header-buttons"> <div class="header-buttons">
<button id="logoutBtn" title="Logout"> <button id="logoutBtn" title="Logout">
<i class="material-icons">exit_to_app</i> <i class="material-icons">exit_to_app</i>
@@ -123,7 +126,7 @@
</div> </div>
</div> </div>
</div> </div>
<button id="addUserBtn" title="Add User" style="display: none;"> <button id="addUserBtn" title="Add User" style="display: none;">
<i class="material-icons">person_add</i> <i class="material-icons">person_add</i>
</button> </button>
<button id="removeUserBtn" title="Remove User" style="display: none;"> <button id="removeUserBtn" title="Remove User" style="display: none;">
@@ -132,10 +135,12 @@
<button id="darkModeToggle" class="dark-mode-toggle">Dark Mode</button> <button id="darkModeToggle" class="dark-mode-toggle">Dark Mode</button>
</div> </div>
</div> </div>
</div>
</header> </header>
<!-- Custom Toast Container --> <!-- Custom Toast Container -->
<div id="customToast"></div> <div id="customToast"></div>
<div id="hiddenCardsContainer" style="display:none;"></div>
<!-- Main Wrapper: Hidden by default; remove "display: none;" after login --> <!-- Main Wrapper: Hidden by default; remove "display: none;" after login -->
<div class="main-wrapper"> <div class="main-wrapper">
@@ -390,7 +395,6 @@
</div> </div>
</div> </div>
</div> </div>
<script type="module" src="main.js"></script> <script type="module" src="main.js"></script>
</body> </body>

View File

@@ -17,7 +17,7 @@ import { loadFolderTree } from './folderManager.js';
import { initUpload } from './upload.js'; import { initUpload } from './upload.js';
import { initAuth, checkAuthentication } from './auth.js'; import { initAuth, checkAuthentication } from './auth.js';
import { setupTrashRestoreDelete } from './trashRestoreDelete.js'; import { setupTrashRestoreDelete } from './trashRestoreDelete.js';
import { initDragAndDrop, loadSidebarOrder } from './dragAndDrop.js' import { initDragAndDrop, loadSidebarOrder, loadHeaderOrder } from './dragAndDrop.js'
import { initTagSearch, openTagModal, filterFilesByTag } from './fileTags.js'; import { initTagSearch, openTagModal, filterFilesByTag } from './fileTags.js';
function loadCsrfToken() { function loadCsrfToken() {
@@ -134,10 +134,12 @@ document.addEventListener("DOMContentLoaded", function () {
loadFileList(window.currentFolder); loadFileList(window.currentFolder);
initDragAndDrop(); initDragAndDrop();
loadSidebarOrder(); loadSidebarOrder();
loadHeaderOrder();
initFileActions(); initFileActions();
initUpload(); initUpload();
loadFolderTree(); loadFolderTree();
setupTrashRestoreDelete(); setupTrashRestoreDelete();
const helpBtn = document.getElementById("folderHelpBtn"); const helpBtn = document.getElementById("folderHelpBtn");
const helpTooltip = document.getElementById("folderHelpTooltip"); const helpTooltip = document.getElementById("folderHelpTooltip");
helpBtn.addEventListener("click", function () { helpBtn.addEventListener("click", function () {

View File

@@ -2023,20 +2023,17 @@ body.dark-mode .card {
z-index: 6000 !important; z-index: 6000 !important;
} }
/* Default (light mode) for admin panel content */
.admin-panel-content { .admin-panel-content {
background: #fff; background: #fff;
color: #000; color: #000;
} }
/* Dark mode overrides for admin panel content */
body.dark-mode .admin-panel-content { body.dark-mode .admin-panel-content {
background: #2c2c2c; /* dark background */ background: #2c2c2c;
color: #e0e0e0; /* light text */ color: #e0e0e0;
border: 1px solid #444; border: 1px solid #444;
} }
/* Optionally, adjust input, label, etc. for dark mode */
body.dark-mode .admin-panel-content input, body.dark-mode .admin-panel-content input,
body.dark-mode .admin-panel-content select, body.dark-mode .admin-panel-content select,
body.dark-mode .admin-panel-content textarea { body.dark-mode .admin-panel-content textarea {
@@ -2067,3 +2064,73 @@ body.dark-mode .admin-panel-content label {
.spinning { .spinning {
animation: spin 1s linear infinite; animation: spin 1s linear infinite;
} }
.rise-effect {
transform: translateY(-20px);
transition: transform 0.3s ease;
}
.toggle-modal-btn,
.collapse-btn {
background: none;
border: none;
outline: none;
cursor: pointer;
padding: 8px;
font-size: 24px;
color: #616161;
border-radius: 50%;
transition: background 0.3s ease;
}
.toggle-modal-btn:hover,
.collapse-btn:hover {
background: rgba(0, 0, 0, 0.1);
}
.toggle-modal-btn:focus,
.collapse-btn:focus {
outline: none;
}
.material-icons {
font-family: 'Material Icons';
font-weight: normal;
font-style: normal;
font-size: inherit;
line-height: 1;
letter-spacing: normal;
text-transform: none;
display: inline-block;
white-space: nowrap;
word-wrap: normal;
direction: ltr;
}
.header-drop-zone {
width: 66px;
height: 36px;
align-items: center;
justify-content: center;
gap: 5px;
display: inline-flex;
}
.header-drop-zone.drag-active {
border: 2px dashed #1565C0;
background-color: #eef;
background-color: transparent;
transition: width 0.3s ease;
box-sizing: border-box;
}
body.dark-mode .header-drop-zone.drag-active {
background-color: #333;
border: 2px dashed #555;
color: #fff;
}
.header-drop-zone.drag-active:empty::before {
content: "Drop";
font-size: 10px;
color: #aaa;
}