feat(ui): unified zone toggle + polished interactions for sidebar/top cards
This commit is contained in:
29
CHANGELOG.md
29
CHANGELOG.md
@@ -1,5 +1,34 @@
|
||||
# Changelog
|
||||
|
||||
## Changes 10/23/2025 (v1.6.1)
|
||||
|
||||
feat(ui): unified zone toggle + polished interactions for sidebar/top cards
|
||||
|
||||
- Add floating toggle button styling (hover lift, press, focus ring, ripple)
|
||||
for #zonesToggleFloating and #sidebarToggleFloating (CSS).
|
||||
- Ensure icons are visible and centered; enforce consistent sizing/color.
|
||||
- Introduce unified “zones collapsed” state persisted via `localStorage.zonesCollapsed`.
|
||||
- Update dragAndDrop.js to:
|
||||
- manage a single floating toggle for both Sidebar and Top Zone
|
||||
- keep toggle visible when cards are in Top Zone; hide only when both cards are in Header
|
||||
- rotate icon 90° when both cards are in Top Zone and panels are open
|
||||
- respect collapsed state during DnD flows and on load
|
||||
- preserve original DnD behaviors and saved orders (sidebar/header)
|
||||
- Minor layout/visibility fixes during drag (clear temp heights; honor collapsed).
|
||||
|
||||
Notes:
|
||||
|
||||
- No breaking API changes; existing `sidebarOrder` / `headerOrder` continue to work.
|
||||
- New key: `zonesCollapsed` (string '0'/'1') controls visibility of Sidebar + Top Zone.
|
||||
|
||||
UX:
|
||||
|
||||
- Floating toggle feels more “material”: subtle hover elevation, press feedback,
|
||||
focus ring, and click ripple to restore the prior interactive feel.
|
||||
- Icons remain legible on white (explicit color set), centered in the circular button.
|
||||
|
||||
---
|
||||
|
||||
## Changes 10/22/2025 (v1.6.0)
|
||||
|
||||
feat(acl): granular per-folder permissions + stricter gates; WebDAV & UI aligned
|
||||
|
||||
@@ -2309,3 +2309,68 @@ body.dark-mode .user-dropdown .user-menu .item:hover {
|
||||
|
||||
:root { --perm-caret: #444; } /* light */
|
||||
body.dark-mode { --perm-caret: #ccc; } /* dark */
|
||||
|
||||
#zonesToggleFloating,
|
||||
#sidebarToggleFloating {
|
||||
transition:
|
||||
transform 160ms cubic-bezier(.2,.0,.2,1),
|
||||
box-shadow 160ms cubic-bezier(.2,.0,.2,1),
|
||||
border-color 160ms cubic-bezier(.2,.0,.2,1),
|
||||
background-color 160ms cubic-bezier(.2,.0,.2,1);
|
||||
}
|
||||
|
||||
#zonesToggleFloating .material-icons,
|
||||
#zonesToggleFloating .material-icons-outlined,
|
||||
#sidebarToggleFloating .material-icons,
|
||||
#sidebarToggleFloating .material-icons-outlined {
|
||||
color: #333 !important;
|
||||
font-size: 22px;
|
||||
line-height: 1;
|
||||
display: block;
|
||||
}
|
||||
|
||||
#zonesToggleFloating:hover,
|
||||
#sidebarToggleFloating:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 6px 16px rgba(0,0,0,.14);
|
||||
border-color: #cfcfcf;
|
||||
}
|
||||
|
||||
#zonesToggleFloating:active,
|
||||
#sidebarToggleFloating:active {
|
||||
transform: translateY(0) scale(.96);
|
||||
box-shadow: 0 3px 8px rgba(0,0,0,.12);
|
||||
}
|
||||
|
||||
#zonesToggleFloating:focus-visible,
|
||||
#sidebarToggleFloating:focus-visible {
|
||||
outline: none;
|
||||
box-shadow:
|
||||
0 6px 16px rgba(0,0,0,.14),
|
||||
0 0 0 3px rgba(25,118,210,.25); /* soft brandy ring */
|
||||
}
|
||||
|
||||
#zonesToggleFloating::after,
|
||||
#sidebarToggleFloating::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
border-radius: inherit;
|
||||
background: radial-gradient(circle, rgba(0,0,0,.12) 0%, rgba(0,0,0,0) 60%);
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
transition: transform 300ms ease, opacity 450ms ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
#zonesToggleFloating:active::after,
|
||||
#sidebarToggleFloating:active::after {
|
||||
transform: scale(1.4);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
#zonesToggleFloating.is-collapsed,
|
||||
#sidebarToggleFloating.is-collapsed {
|
||||
background: #fafafa;
|
||||
border-color: #e2e2e2;
|
||||
}
|
||||
@@ -4,7 +4,7 @@ import { loadAdminConfigFunc } from './auth.js';
|
||||
import { showToast, toggleVisibility, attachEnterKeyListener } from './domUtils.js';
|
||||
import { sendRequest } from './networkUtils.js';
|
||||
|
||||
const version = "v1.6.0";
|
||||
const version = "v1.6.1";
|
||||
const adminTitle = `${t("admin_panel")} <small style="font-size:12px;color:gray;">${version}</small>`;
|
||||
|
||||
|
||||
|
||||
@@ -1,29 +1,234 @@
|
||||
// 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.
|
||||
// Enhances the dashboard with drag-and-drop functionality for cards:
|
||||
// - injects a tiny floating toggle btn
|
||||
// - remembers collapsed state in localStorage
|
||||
// - keeps the original card DnD + order logic intact
|
||||
|
||||
// ---- responsive defaults ----
|
||||
const MEDIUM_MIN = 1205; // matches your small-screen cutoff
|
||||
const MEDIUM_MAX = 1600; // tweak as you like
|
||||
|
||||
const TOGGLE_TOP_PX = 10;
|
||||
const TOGGLE_LEFT_PX = 100;
|
||||
|
||||
const TOGGLE_ICON_OPEN = 'view_sidebar';
|
||||
const TOGGLE_ICON_CLOSED = 'menu';
|
||||
|
||||
function updateSidebarToggleUI() {
|
||||
const btn = document.getElementById('sidebarToggleFloating');
|
||||
const sidebar = getSidebar();
|
||||
if (!btn || !sidebar) return;
|
||||
|
||||
if (!hasSidebarCards()) { btn.remove(); return; }
|
||||
|
||||
const collapsed = isSidebarCollapsed();
|
||||
btn.innerHTML = `<i class="material-icons" aria-hidden="true">${
|
||||
collapsed ? TOGGLE_ICON_CLOSED : TOGGLE_ICON_OPEN
|
||||
}</i>`;
|
||||
btn.title = collapsed ? 'Show sidebar' : 'Hide sidebar';
|
||||
btn.style.display = 'block';
|
||||
btn.classList.toggle('toggle-ping', collapsed);
|
||||
}
|
||||
|
||||
|
||||
function hasSidebarCards() {
|
||||
const sb = getSidebar();
|
||||
return !!sb && sb.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
|
||||
}
|
||||
|
||||
function hasTopZoneCards() {
|
||||
const tz = getTopZone();
|
||||
return !!tz && tz.querySelectorAll('#uploadCard, #folderManagementCard').length > 0;
|
||||
}
|
||||
|
||||
// Both cards are in the top zone (upload + folder)
|
||||
function allCardsInTopZone() {
|
||||
const tz = getTopZone();
|
||||
if (!tz) return false;
|
||||
const hasUpload = !!tz.querySelector('#uploadCard');
|
||||
const hasFolder = !!tz.querySelector('#folderManagementCard');
|
||||
return hasUpload && hasFolder;
|
||||
}
|
||||
|
||||
function isZonesCollapsed() {
|
||||
return localStorage.getItem('zonesCollapsed') === '1';
|
||||
}
|
||||
function setZonesCollapsed(collapsed) {
|
||||
localStorage.setItem('zonesCollapsed', collapsed ? '1' : '0');
|
||||
applyZonesCollapsed();
|
||||
updateZonesToggleUI();
|
||||
}
|
||||
function applyZonesCollapsed() {
|
||||
const collapsed = isZonesCollapsed();
|
||||
const sidebar = getSidebar();
|
||||
const topZone = getTopZone();
|
||||
|
||||
if (sidebar) sidebar.style.display = collapsed ? 'none' : (hasSidebarCards() ? 'block' : 'none');
|
||||
if (topZone) topZone.style.display = collapsed ? 'none' : (hasTopZoneCards() ? '' : '');
|
||||
}
|
||||
|
||||
function isMediumScreen() {
|
||||
const w = window.innerWidth;
|
||||
return w >= MEDIUM_MIN && w < MEDIUM_MAX;
|
||||
}
|
||||
|
||||
// ----- Sidebar collapse state helpers -----
|
||||
function getSidebar() {
|
||||
return document.getElementById('sidebarDropArea');
|
||||
}
|
||||
function getTopZone() {
|
||||
return document.getElementById('uploadFolderRow');
|
||||
}
|
||||
|
||||
function isSidebarCollapsed() {
|
||||
return localStorage.getItem('sidebarCollapsed') === '1';
|
||||
}
|
||||
|
||||
function setSidebarCollapsed(collapsed) {
|
||||
localStorage.setItem('sidebarCollapsed', collapsed ? '1' : '0');
|
||||
applySidebarCollapsed();
|
||||
updateSidebarToggleUI();
|
||||
}
|
||||
|
||||
function applySidebarCollapsed() {
|
||||
const sidebar = getSidebar();
|
||||
if (!sidebar) return;
|
||||
|
||||
// We avoid hard-coding layout assumptions: simply hide/show the sidebar area.
|
||||
// If you want a sliding effect, add CSS later; JS will just toggle display.
|
||||
const collapsed = isSidebarCollapsed();
|
||||
sidebar.style.display = collapsed ? 'none' : 'block';
|
||||
}
|
||||
|
||||
function ensureZonesToggle() {
|
||||
// show only if at least one zone *can* show a card
|
||||
const shouldShow = hasSidebarCards() || hasTopZoneCards();
|
||||
|
||||
let btn = document.getElementById('sidebarToggleFloating');
|
||||
if (!shouldShow) {
|
||||
if (btn) btn.remove();
|
||||
return;
|
||||
}
|
||||
if (!btn) {
|
||||
btn = document.createElement('button');
|
||||
btn.id = 'sidebarToggleFloating';
|
||||
btn.type = 'button';
|
||||
btn.setAttribute('aria-label', 'Toggle panels');
|
||||
Object.assign(btn.style, {
|
||||
position: 'fixed',
|
||||
left: `${TOGGLE_LEFT_PX}px`,
|
||||
top: `${TOGGLE_TOP_PX}px`,
|
||||
zIndex: '1000',
|
||||
width: '38px',
|
||||
height: '38px',
|
||||
borderRadius: '19px',
|
||||
border: '1px solid #ccc',
|
||||
background: '#fff',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 2px 6px rgba(0,0,0,.15)',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
padding: '0',
|
||||
lineHeight: '0',
|
||||
});
|
||||
btn.addEventListener('click', () => {
|
||||
setZonesCollapsed(!isZonesCollapsed());
|
||||
});
|
||||
document.body.appendChild(btn);
|
||||
}
|
||||
updateZonesToggleUI();
|
||||
}
|
||||
function updateZonesToggleUI() {
|
||||
const btn = document.getElementById('sidebarToggleFloating');
|
||||
if (!btn) return;
|
||||
|
||||
// if neither zone has cards, remove the toggle
|
||||
if (!hasSidebarCards() && !hasTopZoneCards()) {
|
||||
btn.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
const collapsed = isZonesCollapsed();
|
||||
const iconName = collapsed ? TOGGLE_ICON_CLOSED : TOGGLE_ICON_OPEN;
|
||||
btn.innerHTML = `<i class="material-icons toggle-icon" aria-hidden="true">${iconName}</i>`;
|
||||
btn.title = collapsed ? 'Show panels' : 'Hide panels';
|
||||
btn.style.display = 'block';
|
||||
|
||||
// Rotate the icon 90° when BOTH cards are in the top zone and panels are open
|
||||
const iconEl = btn.querySelector('.toggle-icon');
|
||||
if (iconEl) {
|
||||
iconEl.style.transition = 'transform 0.2s ease';
|
||||
iconEl.style.display = 'inline-flex';
|
||||
iconEl.style.alignItems = 'center';
|
||||
if (!collapsed && allCardsInTopZone()) {
|
||||
iconEl.style.transform = 'rotate(90deg)';
|
||||
} else {
|
||||
iconEl.style.transform = 'rotate(0deg)';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// create a small floating toggle button (no HTML edits needed)
|
||||
function ensureSidebarToggle() {
|
||||
const sidebar = getSidebar();
|
||||
if (!sidebar) return;
|
||||
|
||||
// Only show if there are cards
|
||||
if (!hasSidebarCards()) {
|
||||
const existing = document.getElementById('sidebarToggleFloating');
|
||||
if (existing) existing.remove();
|
||||
return;
|
||||
}
|
||||
|
||||
let btn = document.getElementById('sidebarToggleFloating');
|
||||
if (!btn) {
|
||||
btn = document.createElement('button');
|
||||
btn.id = 'sidebarToggleFloating';
|
||||
btn.type = 'button';
|
||||
btn.setAttribute('aria-label', 'Toggle sidebar');
|
||||
|
||||
Object.assign(btn.style, {
|
||||
position: 'fixed',
|
||||
left: `${TOGGLE_LEFT_PX}px`,
|
||||
top: `${TOGGLE_TOP_PX}px`,
|
||||
zIndex: '10010',
|
||||
width: '38px',
|
||||
height: '38px',
|
||||
borderRadius: '19px',
|
||||
border: '1px solid #ccc',
|
||||
background: '#fff',
|
||||
cursor: 'pointer',
|
||||
boxShadow: '0 2px 6px rgba(0,0,0,.15)',
|
||||
display: 'block',
|
||||
});
|
||||
|
||||
btn.addEventListener('click', () => {
|
||||
setSidebarCollapsed(!isSidebarCollapsed());
|
||||
// icon/title/animation update after state change
|
||||
updateSidebarToggleUI();
|
||||
});
|
||||
|
||||
document.body.appendChild(btn);
|
||||
}
|
||||
|
||||
// set correct icon/title right away
|
||||
//updateSidebarToggleUI();
|
||||
//applySidebarCollapsed();
|
||||
updateZonesToggleUI();
|
||||
applyZonesCollapsed();
|
||||
}
|
||||
|
||||
// Moves cards into the sidebar based on the saved order in localStorage.
|
||||
export function loadSidebarOrder() {
|
||||
const sidebar = document.getElementById('sidebarDropArea');
|
||||
const sidebar = getSidebar();
|
||||
if (!sidebar) return;
|
||||
|
||||
const orderStr = localStorage.getItem('sidebarOrder');
|
||||
const headerOrderStr = localStorage.getItem('headerOrder');
|
||||
const defaultAppliedKey = 'layoutDefaultApplied_v1'; // bump the suffix if you ever change logic
|
||||
const defaultAppliedKey = 'layoutDefaultApplied_v1'; // bump if logic changes
|
||||
|
||||
// If we have a saved order (sidebar or header), just honor it as before
|
||||
// If we have a saved order (sidebar), honor it as before
|
||||
if (orderStr) {
|
||||
const order = JSON.parse(orderStr || '[]');
|
||||
if (Array.isArray(order) && order.length > 0) {
|
||||
@@ -37,6 +242,11 @@ export function loadSidebarOrder() {
|
||||
}
|
||||
});
|
||||
updateSidebarVisibility();
|
||||
//applySidebarCollapsed(); // NEW: honor collapsed state
|
||||
//ensureSidebarToggle(); // NEW: inject toggle
|
||||
applyZonesCollapsed();
|
||||
ensureZonesToggle();
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -45,6 +255,10 @@ export function loadSidebarOrder() {
|
||||
const headerOrder = JSON.parse(headerOrderStr || '[]');
|
||||
if (Array.isArray(headerOrder) && headerOrder.length > 0) {
|
||||
updateSidebarVisibility();
|
||||
//applySidebarCollapsed();
|
||||
//ensureSidebarToggle();
|
||||
applyZonesCollapsed();
|
||||
ensureZonesToggle();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,9 +286,12 @@ export function loadSidebarOrder() {
|
||||
}
|
||||
|
||||
updateSidebarVisibility();
|
||||
//applySidebarCollapsed();
|
||||
//ensureSidebarToggle();
|
||||
applyZonesCollapsed();
|
||||
ensureZonesToggle();
|
||||
}
|
||||
|
||||
|
||||
export function loadHeaderOrder() {
|
||||
const headerDropArea = document.getElementById('headerDropArea');
|
||||
if (!headerDropArea) return;
|
||||
@@ -104,20 +321,28 @@ export function loadSidebarOrder() {
|
||||
}
|
||||
|
||||
// Internal helper: update sidebar visibility based on its content.
|
||||
// NOTE: do NOT auto-hide if user manually collapsed; that is separate.
|
||||
function updateSidebarVisibility() {
|
||||
const sidebar = document.getElementById('sidebarDropArea');
|
||||
if (sidebar) {
|
||||
const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
|
||||
if (cards.length > 0) {
|
||||
const sidebar = getSidebar();
|
||||
if (!sidebar) return;
|
||||
|
||||
const anyCards = hasSidebarCards();
|
||||
|
||||
// clear any leftover drag height
|
||||
sidebar.style.height = '';
|
||||
|
||||
if (anyCards) {
|
||||
sidebar.classList.add('active');
|
||||
sidebar.style.display = 'block';
|
||||
// respect the unified zones-collapsed switch
|
||||
sidebar.style.display = isZonesCollapsed() ? 'none' : 'block';
|
||||
} else {
|
||||
sidebar.classList.remove('active');
|
||||
sidebar.style.display = 'none';
|
||||
}
|
||||
// Save the current order in localStorage.
|
||||
|
||||
// Save order and update toggle visibility
|
||||
saveSidebarOrder();
|
||||
}
|
||||
ensureZonesToggle(); // will hide/remove the button if no cards
|
||||
}
|
||||
|
||||
// NEW: Save header order to localStorage.
|
||||
@@ -136,9 +361,10 @@ export function loadSidebarOrder() {
|
||||
const leftCol = document.getElementById('leftCol');
|
||||
const rightCol = document.getElementById('rightCol');
|
||||
|
||||
const leftIsEmpty = !leftCol.querySelector('#uploadCard');
|
||||
const rightIsEmpty = !rightCol.querySelector('#folderManagementCard');
|
||||
const leftIsEmpty = !leftCol?.querySelector('#uploadCard');
|
||||
const rightIsEmpty = !rightCol?.querySelector('#folderManagementCard');
|
||||
|
||||
if (leftCol && rightCol) {
|
||||
if (leftIsEmpty && !rightIsEmpty) {
|
||||
leftCol.style.display = 'none';
|
||||
rightCol.style.margin = '0 auto';
|
||||
@@ -152,6 +378,7 @@ export function loadSidebarOrder() {
|
||||
rightCol.style.margin = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When a card is being dragged, if the top drop zone is empty, set its min-height.
|
||||
function addTopZoneHighlight() {
|
||||
@@ -193,7 +420,7 @@ export function loadSidebarOrder() {
|
||||
|
||||
// Internal helper: insert card into sidebar at a proper position based on event.clientY.
|
||||
function insertCardInSidebar(card, event) {
|
||||
const sidebar = document.getElementById('sidebarDropArea');
|
||||
const sidebar = getSidebar();
|
||||
if (!sidebar) return;
|
||||
const existingCards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
|
||||
let inserted = false;
|
||||
@@ -212,11 +439,13 @@ export function loadSidebarOrder() {
|
||||
// Ensure card fills the sidebar.
|
||||
card.style.width = '100%';
|
||||
animateVerticalSlide(card);
|
||||
// if user dropped into sidebar, auto-un-collapse if currently collapsed
|
||||
if (isZonesCollapsed()) setZonesCollapsed(false);
|
||||
}
|
||||
|
||||
// Internal helper: save the current sidebar card order to localStorage.
|
||||
function saveSidebarOrder() {
|
||||
const sidebar = document.getElementById('sidebarDropArea');
|
||||
const sidebar = getSidebar();
|
||||
if (sidebar) {
|
||||
const cards = sidebar.querySelectorAll('#uploadCard, #folderManagementCard');
|
||||
const order = Array.from(cards).map(card => card.id);
|
||||
@@ -227,7 +456,7 @@ export function loadSidebarOrder() {
|
||||
// Helper: move cards from sidebar back to the top drop area when on small screens.
|
||||
function moveSidebarCardsToTop() {
|
||||
if (window.innerWidth < 1205) {
|
||||
const sidebar = document.getElementById('sidebarDropArea');
|
||||
const sidebar = getSidebar();
|
||||
if (!sidebar) return;
|
||||
const cards = Array.from(sidebar.querySelectorAll('#uploadCard, #folderManagementCard'));
|
||||
cards.forEach(card => {
|
||||
@@ -270,9 +499,8 @@ export function loadSidebarOrder() {
|
||||
}
|
||||
}
|
||||
|
||||
// --- NEW HELPER FUNCTIONS FOR HEADER DROP ZONE ---
|
||||
// --- Header drop zone helpers ---
|
||||
|
||||
// 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) {
|
||||
@@ -281,8 +509,6 @@ export function loadSidebarOrder() {
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
@@ -293,12 +519,12 @@ export function loadSidebarOrder() {
|
||||
}
|
||||
}
|
||||
|
||||
// === NEW FUNCTION: Insert card into header drop zone as a material icon ===
|
||||
// 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.
|
||||
// Preserve the original by moving it to a hidden container.
|
||||
if (card.id === 'folderManagementCard' || card.id === 'uploadCard') {
|
||||
let hiddenContainer = document.getElementById('hiddenCardsContainer');
|
||||
if (!hiddenContainer) {
|
||||
@@ -307,27 +533,20 @@ export function loadSidebarOrder() {
|
||||
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') {
|
||||
if (card.parentNode?.id !== 'hiddenCardsContainer') {
|
||||
hiddenContainer.appendChild(card);
|
||||
}
|
||||
} else {
|
||||
// For other cards, simply remove from current container.
|
||||
if (card.parentNode) {
|
||||
} else 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') {
|
||||
@@ -336,39 +555,35 @@ export function loadSidebarOrder() {
|
||||
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 = '55px';
|
||||
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';
|
||||
Object.assign(modal.style, {
|
||||
position: 'fixed',
|
||||
top: '55px',
|
||||
right: '80px',
|
||||
zIndex: '11000',
|
||||
display: 'block',
|
||||
visibility: 'hidden',
|
||||
opacity: '0',
|
||||
background: 'none',
|
||||
border: 'none',
|
||||
padding: '0',
|
||||
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)) {
|
||||
@@ -376,17 +591,14 @@ export function loadSidebarOrder() {
|
||||
}
|
||||
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);
|
||||
@@ -408,33 +620,32 @@ export function loadSidebarOrder() {
|
||||
}, 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();
|
||||
}
|
||||
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() {
|
||||
// make sure toggle exists even if user hasn't dragged yet
|
||||
// ensureSidebarToggle();
|
||||
//applySidebarCollapsed();
|
||||
applyZonesCollapsed();
|
||||
ensureZonesToggle();
|
||||
|
||||
const draggableCards = document.querySelectorAll('#uploadCard, #folderManagementCard');
|
||||
draggableCards.forEach(card => {
|
||||
if (!card.dataset.originalContainerId) {
|
||||
if (!card.dataset.originalContainerId && card.parentNode) {
|
||||
card.dataset.originalContainerId = card.parentNode.id;
|
||||
}
|
||||
const header = card.querySelector('.card-header');
|
||||
@@ -451,37 +662,32 @@ export function loadSidebarOrder() {
|
||||
header.addEventListener('mousedown', function (e) {
|
||||
e.preventDefault();
|
||||
const card = this.closest('.card');
|
||||
// Capture the card's initial bounding rectangle.
|
||||
const initialRect = card.getBoundingClientRect();
|
||||
const originX = ((e.clientX - initialRect.left) / initialRect.width) * 100;
|
||||
const originY = ((e.clientY - initialRect.top) / initialRect.height) * 100;
|
||||
card.style.transformOrigin = `${originX}% ${originY}%`;
|
||||
|
||||
// Store the initial rect so we use it later.
|
||||
dragTimer = setTimeout(() => {
|
||||
isDragging = true;
|
||||
card.classList.add('dragging');
|
||||
card.style.pointerEvents = 'none';
|
||||
addTopZoneHighlight();
|
||||
|
||||
const sidebar = document.getElementById('sidebarDropArea');
|
||||
const sidebar = getSidebar();
|
||||
if (sidebar) {
|
||||
sidebar.classList.add('active');
|
||||
sidebar.style.display = 'block';
|
||||
sidebar.style.display = isSidebarCollapsed() ? 'none' : 'block';
|
||||
sidebar.classList.add('highlight');
|
||||
sidebar.style.height = '800px';
|
||||
}
|
||||
|
||||
// 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);
|
||||
@@ -493,7 +699,6 @@ export function loadSidebarOrder() {
|
||||
saveHeaderOrder();
|
||||
}
|
||||
|
||||
// Append card to body and fix its dimensions.
|
||||
document.body.appendChild(card);
|
||||
card.style.position = 'absolute';
|
||||
card.style.left = initialLeft + 'px';
|
||||
@@ -505,6 +710,7 @@ export function loadSidebarOrder() {
|
||||
card.style.zIndex = '10000';
|
||||
}, 500);
|
||||
});
|
||||
|
||||
header.addEventListener('mouseup', function () {
|
||||
clearTimeout(dragTimer);
|
||||
});
|
||||
@@ -524,13 +730,12 @@ export function loadSidebarOrder() {
|
||||
card.classList.remove('dragging');
|
||||
removeTopZoneHighlight();
|
||||
|
||||
const sidebar = document.getElementById('sidebarDropArea');
|
||||
const sidebar = getSidebar();
|
||||
if (sidebar) {
|
||||
sidebar.classList.remove('highlight');
|
||||
sidebar.style.height = '';
|
||||
}
|
||||
|
||||
// Remove any existing header icon if present.
|
||||
if (card.headerIconButton) {
|
||||
if (card.headerIconButton.parentNode) {
|
||||
card.headerIconButton.parentNode.removeChild(card.headerIconButton);
|
||||
@@ -547,7 +752,7 @@ export function loadSidebarOrder() {
|
||||
let droppedInHeader = false;
|
||||
|
||||
// Check if dropped in sidebar drop zone.
|
||||
const sidebarElem = document.getElementById('sidebarDropArea');
|
||||
const sidebarElem = getSidebar();
|
||||
if (sidebarElem) {
|
||||
const rect = sidebarElem.getBoundingClientRect();
|
||||
const dropZoneBottom = rect.top + 800; // Virtual drop zone height.
|
||||
@@ -561,6 +766,7 @@ export function loadSidebarOrder() {
|
||||
droppedInSidebar = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check the top drop zone.
|
||||
const topRow = document.getElementById('uploadFolderRow');
|
||||
if (!droppedInSidebar && topRow) {
|
||||
@@ -582,7 +788,6 @@ export function loadSidebarOrder() {
|
||||
updateTopZoneLayout();
|
||||
container.appendChild(card);
|
||||
droppedInTop = true;
|
||||
// Set a fixed width during animation.
|
||||
card.style.width = "363px";
|
||||
animateVerticalSlide(card);
|
||||
setTimeout(() => {
|
||||
@@ -591,6 +796,7 @@ export function loadSidebarOrder() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check the header drop zone.
|
||||
const headerDropArea = document.getElementById('headerDropArea');
|
||||
if (!droppedInSidebar && !droppedInTop && headerDropArea) {
|
||||
@@ -605,6 +811,7 @@ export function loadSidebarOrder() {
|
||||
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);
|
||||
@@ -635,8 +842,6 @@ export function loadSidebarOrder() {
|
||||
|
||||
updateTopZoneLayout();
|
||||
updateSidebarVisibility();
|
||||
|
||||
// Hide header drop zone if no icon is present.
|
||||
hideHeaderDropZone();
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user