From 78ca23c07a6cba790a47b4a916174a9a7051bc0b Mon Sep 17 00:00:00 2001 From: "Janus C. H. Knudsen" Date: Fri, 10 Oct 2025 16:41:48 +0200 Subject: [PATCH] Enables all-day event conversion on column hover Allows users to convert all-day events to timed events by dragging them over a day column. This implementation adds logic to the DragDropManager to detect when an all-day event is dragged over a column. It then emits a new event, 'drag:mouseenter-column', carrying the event data and target column information. The event rendering service handles this event. --- src/elements/SwpEventElement.ts | 40 ---------------- src/managers/DragDropManager.ts | 43 +++++++++++++++++ src/managers/HeaderManager.ts | 33 ++++++-------- src/renderers/EventRendererManager.ts | 66 +-------------------------- src/types/EventTypes.ts | 12 +++++ 5 files changed, 69 insertions(+), 125 deletions(-) diff --git a/src/elements/SwpEventElement.ts b/src/elements/SwpEventElement.ts index d375398..b8e7dc7 100644 --- a/src/elements/SwpEventElement.ts +++ b/src/elements/SwpEventElement.ts @@ -289,46 +289,6 @@ export class SwpEventElement extends BaseSwpEventElement { }; } - /** - * Factory method to convert an all-day HTML element to a timed SwpEventElement - */ - public static fromAllDayElement(allDayElement: HTMLElement): SwpEventElement { - const eventId = allDayElement.dataset.eventId || ''; - const title = allDayElement.dataset.title || allDayElement.textContent || 'Untitled'; - const type = allDayElement.dataset.type || 'work'; - const startStr = allDayElement.dataset.start; - const endStr = allDayElement.dataset.end; - const durationStr = allDayElement.dataset.duration; - - if (!startStr || !endStr) { - throw new Error('All-day element missing start/end dates'); - } - - const originalStart = new Date(startStr); - const duration = durationStr ? parseInt(durationStr) : 60; - - const now = new Date(); - const startDate = new Date(originalStart); - startDate.setHours(now.getHours() || 9, now.getMinutes() || 0, 0, 0); - - const endDate = new Date(startDate); - endDate.setMinutes(endDate.getMinutes() + duration); - - const calendarEvent: CalendarEvent = { - id: eventId, - title: title, - start: startDate, - end: endDate, - type: type, - allDay: false, - syncStatus: 'synced', - metadata: { - duration: duration.toString() - } - }; - - return SwpEventElement.fromCalendarEvent(calendarEvent); - } } /** diff --git a/src/managers/DragDropManager.ts b/src/managers/DragDropManager.ts index 14c0dfa..63d332e 100644 --- a/src/managers/DragDropManager.ts +++ b/src/managers/DragDropManager.ts @@ -14,6 +14,7 @@ import { DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, + DragMouseEnterColumnEventPayload, DragColumnChangeEventPayload } from '../types/EventTypes'; import { MousePosition } from '../types/DragDropTypes'; @@ -116,6 +117,8 @@ export class DragDropManager { const target = e.target as HTMLElement; if (target.closest('swp-calendar-header')) { this.handleHeaderMouseEnter(e as MouseEvent); + } else if (target.closest('swp-day-column')) { + this.handleColumnMouseEnter(e as MouseEvent); } else if (target.closest('swp-event')) { // Entered an event - activate hover tracking and set color const eventElement = target.closest('swp-event'); @@ -652,6 +655,46 @@ export class DragDropManager { } } + /** + * Handle mouse enter on day column - for converting all-day to timed events + */ + private handleColumnMouseEnter(event: MouseEvent): void { + // Only handle if we're dragging an all-day event + if (!this.isDragStarted || !this.draggedClone || !this.draggedClone.hasAttribute('data-allday')) { + 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; + } + + // Calculate snapped Y position + const snappedY = this.calculateSnapPosition(position.y, targetColumn); + + // Extract CalendarEvent from the dragged clone + const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone); + + const dragMouseEnterPayload: DragMouseEnterColumnEventPayload = { + targetColumn: targetColumn, + mousePosition: position, + snappedY: snappedY, + originalElement: this.draggedElement, + draggedClone: this.draggedClone, + calendarEvent: calendarEvent, + // Delegate pattern - allows EventRenderer to replace the clone + replaceClone: (newClone: HTMLElement) => { + this.draggedClone = newClone; + } + }; + this.eventBus.emit('drag:mouseenter-column', dragMouseEnterPayload); + } + /** * Handle mouse leave from calendar header - simplified using native events */ diff --git a/src/managers/HeaderManager.ts b/src/managers/HeaderManager.ts index e2c5f3f..e09f62f 100644 --- a/src/managers/HeaderManager.ts +++ b/src/managers/HeaderManager.ts @@ -12,7 +12,7 @@ import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils'; * Separates event handling from rendering concerns */ export class HeaderManager { - + // Event listeners for drag events private dragMouseEnterHeaderListener: ((event: Event) => void) | null = null; private dragMouseLeaveHeaderListener: ((event: Event) => void) | null = null; @@ -20,7 +20,7 @@ export class HeaderManager { constructor() { // Bind methods for event listeners this.setupHeaderDragListeners = this.setupHeaderDragListeners.bind(this); - + // Listen for navigation events to update header this.setupNavigationListener(); } @@ -36,7 +36,7 @@ export class HeaderManager { * Get cached calendar header element */ private getCalendarHeader(): HTMLElement | null { - return document.querySelector('swp-calendar-header'); + return document.querySelector('swp-calendar-header'); } /** @@ -48,42 +48,35 @@ export class HeaderManager { // Create and store event listeners this.dragMouseEnterHeaderListener = (event: Event) => { const { targetColumn: targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent).detail; - + console.log('🎯 HeaderManager: Received drag:mouseenter-header', { targetDate, originalElement: !!originalElement, cloneElement: !!cloneElement }); - + if (targetDate) { const calendarType = calendarConfig.getCalendarMode(); const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType); - - + + } }; this.dragMouseLeaveHeaderListener = (event: Event) => { const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent).detail; - + console.log('🚪 HeaderManager: Received drag:mouseleave-header', { targetDate, originalElement: !!originalElement, cloneElement: !!cloneElement }); - - eventBus.emit('header:mouseleave', { - element: this.getCalendarHeader(), - targetDate, - originalElement, - cloneElement - }); }; // Listen for drag events from DragDropManager eventBus.on('drag:mouseenter-header', this.dragMouseEnterHeaderListener); eventBus.on('drag:mouseleave-header', this.dragMouseLeaveHeaderListener); - + console.log('✅ HeaderManager: Drag event listeners attached'); } @@ -117,13 +110,13 @@ export class HeaderManager { const calendarHeader = this.getOrCreateCalendarHeader(); if (!calendarHeader) return; - // Clear existing content + // Clear existing content calendarHeader.innerHTML = ''; // Render new header content const calendarType = calendarConfig.getCalendarMode(); const headerRenderer = CalendarTypeFactory.getHeaderRenderer(calendarType); - + const context: HeaderRenderContext = { currentWeek: currentDate, config: calendarConfig, @@ -147,12 +140,12 @@ export class HeaderManager { */ private getOrCreateCalendarHeader(): HTMLElement | null { const calendarHeader = this.getCalendarHeader(); - + if (!calendarHeader) { console.warn('HeaderManager: Calendar header not found - should always exist now!'); return null; } - + return calendarHeader; } } \ No newline at end of file diff --git a/src/renderers/EventRendererManager.ts b/src/renderers/EventRendererManager.ts index e214b70..e051409 100644 --- a/src/renderers/EventRendererManager.ts +++ b/src/renderers/EventRendererManager.ts @@ -88,17 +88,6 @@ export class EventRenderingService { // Handle all drag events and delegate to appropriate renderer this.setupDragEventListeners(); - // Listen for conversion from all-day event to time event - this.eventBus.on('drag:convert-to-time_event', (event: Event) => { - const { draggedElement, mousePosition, column } = (event as CustomEvent).detail; - console.log('🔄 EventRendererManager: Received drag:convert-to-time_event', { - draggedElement: draggedElement?.dataset.eventId, - mousePosition, - column - }); - this.handleConvertToTimeEvent(draggedElement, mousePosition, column); - }); - } @@ -301,60 +290,7 @@ export class EventRenderingService { } }); } - - /** - * Handle conversion from all-day event to time event - */ - private handleConvertToTimeEvent(draggedElement: HTMLElement, mousePosition: { x: number; y: number }, column: string): void { - // Use the provided draggedElement directly - const allDayClone = draggedElement; - const draggedEventId = draggedElement?.dataset.eventId?.replace('clone-', '') || ''; - - - // Use SwpEventElement factory to create day event from all-day event - const dayElement = SwpEventElement.fromAllDayElement(allDayClone as HTMLElement); - - // Remove the all-day clone - it's no longer needed since we're converting to day event - allDayClone.remove(); - - // Set clone ID - dayElement.dataset.eventId = `clone-${draggedEventId}`; - - // Find target column - const columnElement = document.querySelector(`swp-day-column[data-date="${column}"]`); - if (!columnElement) { - console.warn('EventRendererManager: Target column not found', { column }); - return; - } - - // Find events layer in the column - const eventsLayer = columnElement.querySelector('swp-events-layer'); - if (!eventsLayer) { - console.warn('EventRendererManager: Events layer not found in column'); - return; - } - - // Add to events layer - eventsLayer.appendChild(dayElement); - - // Position based on mouse Y coordinate - const columnRect = columnElement.getBoundingClientRect(); - const relativeY = Math.max(0, mousePosition.y - columnRect.top); - dayElement.style.top = `${relativeY}px`; - - // Set drag styling - dayElement.style.zIndex = '1000'; - dayElement.style.cursor = 'grabbing'; - dayElement.style.opacity = ''; - dayElement.style.transform = ''; - - console.log('✅ EventRendererManager: Converted all-day event to time event', { - draggedEventId, - column, - mousePosition, - relativeY - }); - } + /** * Re-render affected columns after drag to recalculate stacking/grouping diff --git a/src/types/EventTypes.ts b/src/types/EventTypes.ts index 73f88ab..42f4c53 100644 --- a/src/types/EventTypes.ts +++ b/src/types/EventTypes.ts @@ -67,6 +67,18 @@ export interface DragMouseLeaveHeaderEventPayload { draggedClone: HTMLElement| null; } +// Drag mouse enter column event payload +export interface DragMouseEnterColumnEventPayload { + targetColumn: ColumnBounds; + mousePosition: MousePosition; + snappedY: number; + originalElement: HTMLElement | null; + draggedClone: HTMLElement; + calendarEvent: CalendarEvent; + // Delegate pattern - allows subscriber to replace the dragged clone + replaceClone: (newClone: HTMLElement) => void; +} + // Drag column change event payload export interface DragColumnChangeEventPayload { originalElement: HTMLElement;