diff --git a/src/main/resources/static/css/dragdrop.css b/src/main/resources/static/css/dragdrop.css index 65ec786f..688cbca4 100644 --- a/src/main/resources/static/css/dragdrop.css +++ b/src/main/resources/static/css/dragdrop.css @@ -29,13 +29,9 @@ } .drag-manager_dragging { - width: 0px; - visibility: hidden; + opacity: 0.2; } -.drag-manager_draghover { - width: 375px !important; -} .drag-manager_draghover .insert-file-button-container { display: none !important; @@ -46,11 +42,11 @@ } html[dir="ltr"] .drag-manager_draghover img { - left: calc(50% + 62.5px) !important; + left: 80% !important; } html[dir="rtl"] .drag-manager_draghover img { - left: 125px; + left: 25px; } .drag-manager_dragging-container .hide-on-drag { @@ -58,14 +54,17 @@ html[dir="rtl"] .drag-manager_draghover img { } .drag-manager_endpoint { - width: 80px; - height: 100%; + width: 150px; + height: 250px; background-color: #ffffff10; transition: width 0.1s; animation: end-drop-expand 0.3s ease; display: flex; align-items: center; justify-content: center; + margin-left:16px; + border-radius: 8px; + } .drag-manager_endpoint svg { @@ -74,7 +73,8 @@ html[dir="rtl"] .drag-manager_draghover img { } .drag-manager_endpoint.drag-manager_draghover { - width: 150px !important; + width: 180px !important; + border: 2px solid darkgreen; } @keyframes end-drop-expand { @@ -85,3 +85,12 @@ html[dir="rtl"] .drag-manager_draghover img { width: 80px; } } +.moved-element img { + border: 8px solid #198754; + border-radius: 3px; + transition: border 0.5s ease-out; +} + +.moved-element.remove img{ + border: 8px solid transparent; +} diff --git a/src/main/resources/static/js/multitool/DragDropManager.js b/src/main/resources/static/js/multitool/DragDropManager.js index ce79ee14..747a7a3d 100644 --- a/src/main/resources/static/js/multitool/DragDropManager.js +++ b/src/main/resources/static/js/multitool/DragDropManager.js @@ -1,23 +1,25 @@ class DragDropManager { constructor(id, wrapperId) { this.dragContainer = document.getElementById(id); - this.pageDirection = document.documentElement.getAttribute("dir"); + this.pageDirection = document.documentElement.getAttribute('dir'); this.wrapper = document.getElementById(wrapperId); this.pageDragging = false; this.hoveredEl = undefined; this.draggedImageEl = undefined; this.draggedEl = undefined; this.selectedPageElements = []; // Store selected pages for multi-page mode + this.elementTimeouts = new Map(); // Add CSS dynamically - const styleElement = document.createElement("link"); - styleElement.rel = "stylesheet"; - styleElement.href = "css/dragdrop.css"; + const styleElement = document.createElement('link'); + styleElement.rel = 'stylesheet'; + styleElement.href = 'css/dragdrop.css'; document.head.appendChild(styleElement); // Create the endpoint element - const div = document.createElement("div"); - div.classList.add("drag-manager_endpoint"); + const div = document.createElement('div'); + div.classList.add('page-container'); + div.classList.add('drag-manager_endpoint'); div.innerHTML = ` @@ -35,59 +37,63 @@ class DragDropManager { startDraggingPage(div) { if (window.selectPage) { // Multi-page drag logic - this.selectedPageElements = window.selectedPages.map((index) => { - const pageEl = document.getElementById(`page-container-${index}`); - if (pageEl) { - pageEl.initialTransform = pageEl.style.transform || "translate(0px, 0px)"; - } - return pageEl; - }).filter(Boolean); + this.selectedPageElements = window.selectedPages + .map((index) => { + const pageEl = document.getElementById(`page-container-${index}`); + if (pageEl) { + pageEl.initialTransform = pageEl.style.transform || 'translate(0px, 0px)'; + pageEl.classList.add('drag-manager_dragging'); + } + return pageEl; + }) + .filter(Boolean); if (this.selectedPageElements.length === 0) return; this.pageDragging = true; - this.draggedImageEl = document.createElement("div"); - this.draggedImageEl.classList.add("multidrag"); + this.draggedImageEl = document.createElement('div'); + this.draggedImageEl.classList.add('multidrag'); this.draggedImageEl.textContent = `${this.selectedPageElements.length} ${window.translations.dragDropMessage}`; - this.draggedImageEl.style.visibility = "hidden"; + this.draggedImageEl.style.visibility = 'hidden'; + this.dragContainer.appendChild(this.draggedImageEl); } else { // Single-page drag logic this.pageDragging = true; this.draggedEl = div; - const img = div.querySelector("img"); - div.classList.add("drag-manager_dragging"); - - const imgEl = document.createElement("img"); - imgEl.classList.add("dragged-img"); + const img = div.querySelector('img'); + div.classList.add('drag-manager_dragging'); + div.classList.remove('moved-element', 'remove'); + const imgEl = document.createElement('img'); + imgEl.classList.add('dragged-img'); imgEl.src = img.src; - imgEl.style.visibility = "hidden"; - imgEl.style.transform = `rotate(${img.style.rotate === "" ? "0deg" : img.style.rotate}) translate(-50%, -50%)`; + imgEl.style.visibility = 'hidden'; + imgEl.style.transform = `rotate(${img.style.rotate === '' ? '0deg' : img.style.rotate}) translate(-50%, -50%)`; this.draggedImageEl = imgEl; this.dragContainer.appendChild(imgEl); } // Common setup for both modes - window.addEventListener("mouseup", this.stopDraggingPage); - window.addEventListener("mousemove", this.onDragEl); - this.wrapper.classList.add("drag-manager_dragging-container"); + window.addEventListener('mouseup', this.stopDraggingPage); + window.addEventListener('mousemove', this.onDragEl); + this.wrapper.classList.add('drag-manager_dragging-container'); this.wrapper.appendChild(this.endInsertionElement); } onDragEl(mouseEvent) { - const { clientX, clientY } = mouseEvent; + const {clientX, clientY} = mouseEvent; if (this.draggedImageEl) { - this.draggedImageEl.style.visibility = "visible"; + this.draggedImageEl.style.visibility = 'visible'; this.draggedImageEl.style.left = `${clientX}px`; this.draggedImageEl.style.top = `${clientY}px`; } } stopDraggingPage() { - window.removeEventListener("mousemove", this.onDragEl); - this.wrapper.classList.remove("drag-manager_dragging-container"); + window.removeEventListener('mousemove', this.onDragEl); + this.wrapper.classList.remove('drag-manager_dragging-container'); this.wrapper.removeChild(this.endInsertionElement); - window.removeEventListener("mouseup", this.stopDraggingPage); + window.removeEventListener('mouseup', this.stopDraggingPage); if (this.draggedImageEl) { this.dragContainer.removeChild(this.draggedImageEl); @@ -96,38 +102,79 @@ class DragDropManager { if (window.selectPage) { // Multi-page drop logic - if (!this.hoveredEl) { + if ( + !this.hoveredEl || + !this.hoveredEl.classList.contains('page-container') || + this.selectedPageElements.includes(this.hoveredEl) + ) { this.selectedPageElements.forEach((pageEl) => { - pageEl.style.transform = pageEl.initialTransform || "translate(0px, 0px)"; - pageEl.classList.remove("drag-manager_dragging"); + pageEl.style.transform = pageEl.initialTransform || 'translate(0px, 0px)'; + pageEl.classList.remove('drag-manager_dragging'); }); } else { this.selectedPageElements.forEach((pageEl) => { - pageEl.classList.remove("drag-manager_dragging"); + pageEl.classList.remove('drag-manager_dragging'); + if (this.hoveredEl === this.endInsertionElement) { this.movePageTo(pageEl); } else { this.movePageTo(pageEl, this.hoveredEl); } + + // Handle timeout for the current element + this.handleTimeoutForElement(pageEl); }); } this.selectedPageElements = []; - window.resetPages() + window.resetPages(); } else { // Single-page drop logic - if (!this.hoveredEl) return; - this.draggedEl.classList.remove("drag-manager_dragging"); + if ( + !this.hoveredEl || + !this.hoveredEl.classList.contains('page-container') || + this.hoveredEl === this.draggedEl + ) { + this.draggedEl.style.transform = this.draggedEl.initialTransform || 'translate(0px, 0px)'; + this.draggedEl.classList.remove('drag-manager_dragging'); + return; + } + + this.draggedEl.classList.remove('drag-manager_dragging'); + if (this.hoveredEl === this.endInsertionElement) { this.movePageTo(this.draggedEl); } else { this.movePageTo(this.draggedEl, this.hoveredEl); } + + // Handle timeout for the current element + this.handleTimeoutForElement(this.draggedEl); } this.pageDragging = false; } - setActions({ movePageTo }) { + // Helper function to manage independent timeouts + handleTimeoutForElement(element) { + // Clear existing timeout if present + if (this.elementTimeouts.has(element)) { + clearTimeout(this.elementTimeouts.get(element)); + } + + // Add the moved-element class and set a new timeout + element.classList.remove('remove'); + element.classList.add('moved-element'); + + const timeoutId = setTimeout(() => { + element.classList.add('remove'); + this.elementTimeouts.delete(element); // Cleanup the timeout map + }, 2000); + + // Store the timeout ID for this element + this.elementTimeouts.set(element, timeoutId); + } + + setActions({movePageTo}) { this.movePageTo = movePageTo; } @@ -140,18 +187,18 @@ class DragDropManager { const onMouseEnter = () => { if (this.pageDragging) { this.hoveredEl = div; - div.classList.add("drag-manager_draghover"); + div.classList.add('drag-manager_draghover'); } }; const onMouseLeave = () => { this.hoveredEl = undefined; - div.classList.remove("drag-manager_draghover"); + div.classList.remove('drag-manager_draghover'); }; - div.addEventListener("dragstart", onDragStart); - div.addEventListener("mouseenter", onMouseEnter); - div.addEventListener("mouseleave", onMouseLeave); + div.addEventListener('dragstart', onDragStart); + div.addEventListener('mouseenter', onMouseEnter); + div.addEventListener('mouseleave', onMouseLeave); return div; } diff --git a/src/main/resources/static/js/multitool/commands/move-page.js b/src/main/resources/static/js/multitool/commands/move-page.js index 6f7b185e..738af950 100644 --- a/src/main/resources/static/js/multitool/commands/move-page.js +++ b/src/main/resources/static/js/multitool/commands/move-page.js @@ -1,13 +1,7 @@ -import { Command } from "./command.js"; +import {Command} from './command.js'; export class AbstractMovePageCommand extends Command { - constructor( - startElement, - endElement, - pagesContainer, - pagesContainerWrapper, - scrollTo = false - ) { + constructor(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo = false) { super(); this.pagesContainer = pagesContainer; @@ -25,7 +19,7 @@ export class AbstractMovePageCommand extends Command { execute() { // Check & remove page number elements here too if they exist because Firefox doesn't fire the relevant event on page move. - const pageNumberElement = this.startElement.querySelector(".page-number"); + const pageNumberElement = this.startElement.querySelector('.page-number'); if (pageNumberElement) { this.startElement.removeChild(pageNumberElement); } @@ -38,11 +32,8 @@ export class AbstractMovePageCommand extends Command { } if (this.scrollTo) { - const { width } = this.startElement.getBoundingClientRect(); - const vector = - this.endIndex !== -1 && this.startIndex > this.endIndex - ? 0 - width - : width; + const {width} = this.startElement.getBoundingClientRect(); + const vector = this.endIndex !== -1 && this.startIndex > this.endIndex ? 0 - width : width; this.pagesContainerWrapper.scroll({ left: this.pagesContainerWrapper.scrollLeft + vector, @@ -52,7 +43,6 @@ export class AbstractMovePageCommand extends Command { undo() { // Requires overriding in child classes - } redo() { @@ -61,28 +51,19 @@ export class AbstractMovePageCommand extends Command { } export class MovePageUpCommand extends AbstractMovePageCommand { - constructor( - startElement, - endElement, - pagesContainer, - pagesContainerWrapper, - scrollTo = false - ) { + constructor(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo = false) { super(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo); } undo() { if (this.endElement) { this.pagesContainer.removeChild(this.endElement); - this.startElement.insertAdjacentElement("beforebegin", this.endElement); + this.startElement.insertAdjacentElement('beforebegin', this.endElement); } if (this.scrollTo) { - const { width } = this.startElement.getBoundingClientRect(); - const vector = - this.endIndex === -1 || this.startIndex <= this.endIndex - ? 0 - width - : width; + const {width} = this.startElement.getBoundingClientRect(); + const vector = this.endIndex === -1 || this.startIndex <= this.endIndex ? 0 - width : width; this.pagesContainerWrapper.scroll({ left: this.pagesContainerWrapper.scrollLeft - vector, @@ -96,13 +77,7 @@ export class MovePageUpCommand extends AbstractMovePageCommand { } export class MovePageDownCommand extends AbstractMovePageCommand { - constructor( - startElement, - endElement, - pagesContainer, - pagesContainerWrapper, - scrollTo = false - ) { + constructor(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo = false) { super(startElement, endElement, pagesContainer, pagesContainerWrapper, scrollTo); } @@ -111,15 +86,12 @@ export class MovePageDownCommand extends AbstractMovePageCommand { if (this.startElement) { this.pagesContainer.removeChild(this.startElement); - previousElement.insertAdjacentElement("beforebegin", this.startElement); + previousElement.insertAdjacentElement('beforebegin', this.startElement); } if (this.scrollTo) { - const { width } = this.startElement.getBoundingClientRect(); - const vector = - this.endIndex === -1 || this.startIndex <= this.endIndex - ? 0 - width - : width; + const {width} = this.startElement.getBoundingClientRect(); + const vector = this.endIndex === -1 || this.startIndex <= this.endIndex ? 0 - width : width; this.pagesContainerWrapper.scroll({ left: this.pagesContainerWrapper.scrollLeft - vector,