Refactors and optimizes core calendar managers

Streamlines several core managers by removing unnecessary complexity, caching, and redundant methods

Key improvements:
- Simplified event and view management logic
- Removed unnecessary caching mechanisms
- Reduced method complexity in managers
- Improved code readability and performance
This commit is contained in:
Janus C. H. Knudsen 2025-11-01 21:07:07 +01:00
parent 1ae4f00f2b
commit b6ab1ff50e
6 changed files with 193 additions and 327 deletions

View file

@ -1,6 +1,135 @@
/**
* DragDropManager - Optimized drag and drop with consolidated position calculations
* Reduces redundant DOM queries and improves performance through caching
* DragDropManager - Advanced drag-and-drop system with smooth animations and event type conversion
*
* ARCHITECTURE OVERVIEW:
* =====================
* DragDropManager provides a sophisticated drag-and-drop system for calendar events that supports:
* - Smooth animated dragging with requestAnimationFrame
* - Automatic event type conversion (timed events all-day events)
* - Scroll compensation during edge scrolling
* - Grid snapping for precise event placement
* - Column detection and change tracking
*
* KEY FEATURES:
* =============
* 1. DRAG DETECTION
* - Movement threshold (5px) to distinguish clicks from drags
* - Immediate visual feedback with cloned element
* - Mouse offset tracking for natural drag feel
*
* 2. SMOOTH ANIMATION
* - Uses requestAnimationFrame for 60fps animations
* - Interpolated movement (30% per frame) for smooth transitions
* - Continuous drag:move events for real-time updates
*
* 3. EVENT TYPE CONVERSION
* - Timed All-day: When dragging into calendar header
* - All-day Timed: When dragging into day columns
* - Automatic clone replacement with appropriate element type
*
* 4. SCROLL COMPENSATION
* - Tracks scroll delta during edge-scrolling
* - Compensates dragged element position during scroll
* - Prevents visual "jumping" when scrolling while dragging
*
* 5. GRID SNAPPING
* - Snaps to time grid on mouse up
* - Uses PositionUtils for consistent positioning
* - Accounts for mouse offset within event
*
* STATE MANAGEMENT:
* =================
* Mouse Tracking:
* - mouseDownPosition: Initial click position
* - currentMousePosition: Latest mouse position
* - mouseOffset: Click offset within event (for natural dragging)
*
* Drag State:
* - originalElement: Source event being dragged
* - draggedClone: Animated clone following mouse
* - currentColumn: Column mouse is currently over
* - previousColumn: Last column (for detecting changes)
* - isDragStarted: Whether drag threshold exceeded
*
* Scroll State:
* - scrollDeltaY: Accumulated scroll offset during drag
* - lastScrollTop: Previous scroll position
* - isScrollCompensating: Whether edge-scroll is active
*
* Animation State:
* - dragAnimationId: requestAnimationFrame ID
* - targetY: Desired position for smooth interpolation
* - currentY: Current interpolated position
*
* EVENT FLOW:
* ===========
* 1. Mouse Down (handleMouseDown)
* Store originalElement and mouse offset
* Wait for movement
*
* 2. Mouse Move (handleMouseMove)
* Check movement threshold
* Initialize drag if threshold exceeded (initializeDrag)
* Create clone
* Emit drag:start
* Start animation loop
* Continue drag (continueDrag)
* Calculate target position with scroll compensation
* Update animation target
* Detect column changes (detectColumnChange)
* Emit drag:column-change
*
* 3. Animation Loop (animateDrag)
* Interpolate currentY toward targetY
* Emit drag:move on each frame
* Schedule next frame until target reached
*
* 4. Event Type Conversion
* Entering header (handleHeaderMouseEnter)
* Emit drag:mouseenter-header
* AllDayManager creates all-day clone
* Entering column (handleColumnMouseEnter)
* Emit drag:mouseenter-column
* EventRenderingService creates timed clone
*
* 5. Mouse Up (handleMouseUp)
* Stop animation
* Snap to grid
* Detect drop target (header or column)
* Emit drag:end with final position
* Cleanup drag state
*
* SCROLL COMPENSATION SYSTEM:
* ===========================
* Problem: When EdgeScrollManager scrolls the grid during drag, the dragged element
* can appear to "jump" because the mouse position stays the same but the
* coordinate system (scrollable content) has moved.
*
* Solution: Track cumulative scroll delta and add it to mouse position calculations
*
* Flow:
* 1. EdgeScrollManager starts scrolling emit edgescroll:started
* 2. DragDropManager sets isScrollCompensating = true
* 3. On each scroll event:
* Calculate scrollDelta = currentScrollTop - lastScrollTop
* Accumulate into scrollDeltaY
* Call continueDrag with adjusted position
* 4. continueDrag adds scrollDeltaY to mouse Y coordinate
* 5. On event conversion, reset scrollDeltaY (new clone, new coordinate system)
*
* PERFORMANCE OPTIMIZATIONS:
* ==========================
* - Uses ColumnDetectionUtils cache for fast column lookups
* - Single requestAnimationFrame loop (not per-mousemove)
* - Interpolated animation reduces update frequency
* - Passive scroll listeners
* - Event delegation for header/column detection
*
* USAGE:
* ======
* const dragDropManager = new DragDropManager(eventBus, positionUtils);
* // Automatically attaches event listeners and manages drag lifecycle
* // Other managers listen to drag:start, drag:move, drag:end, etc.
*/
import { IEventBus } from '../types/CalendarTypes';
@ -122,24 +251,19 @@ export class DragDropManager {
if (this.scrollableContent) {
this.lastScrollTop = this.scrollableContent.scrollTop;
}
console.log('🎬 DragDropManager: Edge-scroll started');
});
this.eventBus.on('edgescroll:stopped', () => {
this.isScrollCompensating = false;
console.log('🛑 DragDropManager: Edge-scroll stopped');
});
// Reset scrollDeltaY when event converts (new clone created)
this.eventBus.on('drag:mouseenter-header', () => {
console.log('🔄 DragDropManager: Event converting to all-day - resetting scrollDeltaY');
this.scrollDeltaY = 0;
this.lastScrollTop = 0;
});
this.eventBus.on('drag:mouseenter-column', () => {
console.log('🔄 DragDropManager: Event converting to timed - resetting scrollDeltaY');
this.scrollDeltaY = 0;
this.lastScrollTop = 0;
});
@ -340,8 +464,6 @@ export class DragDropManager {
target: dropTarget
};
console.log('DragEndEventPayload', dragEndPayload);
this.eventBus.emit('drag:end', dragEndPayload);
this.cleanupDragState();
@ -361,7 +483,6 @@ export class DragDropManager {
const allClones = document.querySelectorAll('[data-event-id^="clone"]');
if (allClones.length > 0) {
console.log(`🧹 DragDropManager: Removing ${allClones.length} clone(s)`);
allClones.forEach(clone => clone.remove());
}
}
@ -373,8 +494,6 @@ export class DragDropManager {
private cancelDrag(): void {
if (!this.originalElement || !this.draggedClone) return;
console.log('🚫 DragDropManager: Cancelling drag - mouse left grid container');
// Get current clone position
const cloneRect = this.draggedClone.getBoundingClientRect();
@ -486,13 +605,6 @@ export class DragDropManager {
// Kald continueDrag med nuværende mus position
this.continueDrag(this.currentMousePosition);
console.log('📜 DragDropManager: Scroll compensation', {
currentScrollTop,
lastScrollTop: this.lastScrollTop - scrollDelta,
scrollDelta,
scrollDeltaY: this.scrollDeltaY
});
}
/**
@ -564,7 +676,6 @@ export class DragDropManager {
this.dragAnimationId === null;
}
};
console.log('DragMouseEnterHeaderEventPayload', dragMouseEnterPayload);
this.eventBus.emit('drag:mouseenter-header', dragMouseEnterPayload);
}
}
@ -578,13 +689,10 @@ export class DragDropManager {
return;
}
console.log('🎯 DragDropManager: Mouse entered day column');
const position: MousePosition = { x: event.clientX, y: event.clientY };
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
if (!targetColumn) {
console.warn("No column detected when entering day column");
return;
}
@ -619,13 +727,10 @@ export class DragDropManager {
return;
}
console.log('🚪 DragDropManager: Mouse left header');
const position: MousePosition = { x: event.clientX, y: event.clientY };
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
if (!targetColumn) {
console.warn("No column detected when leaving header");
return;
}