Refactors event positioning and drag-and-drop
Centralizes event position calculations into `PositionUtils` for consistency and reusability across managers and renderers. Improves drag-and-drop functionality by emitting events for all-day event conversion and streamlining position calculations during drag operations. Introduces `AllDayManager` and `AllDayEventRenderer` to manage and render all-day events in the calendar header. This allows dragging events to the header to convert them to all-day events.
This commit is contained in:
parent
8b96376d1f
commit
7054c0d40a
9 changed files with 404 additions and 72 deletions
|
|
@ -2,6 +2,8 @@
|
|||
|
||||
import { eventBus } from '../core/EventBus';
|
||||
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
||||
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
||||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
|
||||
/**
|
||||
* AllDayManager - Handles all-day row height animations and management
|
||||
|
|
@ -11,10 +13,25 @@ export class AllDayManager {
|
|||
private cachedAllDayContainer: HTMLElement | null = null;
|
||||
private cachedCalendarHeader: HTMLElement | null = null;
|
||||
private cachedHeaderSpacer: HTMLElement | null = null;
|
||||
private allDayEventRenderer: AllDayEventRenderer;
|
||||
|
||||
constructor() {
|
||||
// Bind methods for event listeners
|
||||
this.checkAndAnimateAllDayHeight = this.checkAndAnimateAllDayHeight.bind(this);
|
||||
this.allDayEventRenderer = new AllDayEventRenderer();
|
||||
|
||||
// Listen for drag-to-allday conversions
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
/**
|
||||
* Setup event listeners for drag conversions
|
||||
*/
|
||||
private setupEventListeners(): void {
|
||||
eventBus.on('drag:convert-to-allday', (event) => {
|
||||
const { targetDate, originalElement } = (event as CustomEvent).detail;
|
||||
this.handleConvertToAllDay(targetDate, originalElement);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -204,6 +221,58 @@ export class AllDayManager {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle conversion of timed event to all-day event
|
||||
*/
|
||||
private handleConvertToAllDay(targetDate: string, originalElement: HTMLElement): void {
|
||||
// Extract event data from original element
|
||||
const eventId = originalElement.dataset.eventId;
|
||||
const title = originalElement.dataset.title || originalElement.textContent || 'Untitled';
|
||||
const type = originalElement.dataset.type || 'work';
|
||||
const startStr = originalElement.dataset.start;
|
||||
const endStr = originalElement.dataset.end;
|
||||
|
||||
if (!eventId || !startStr || !endStr) {
|
||||
console.error('Original element missing required data (eventId, start, end)');
|
||||
return;
|
||||
}
|
||||
|
||||
// Create CalendarEvent for all-day conversion - preserve original times
|
||||
const originalStart = new Date(startStr);
|
||||
const originalEnd = new Date(endStr);
|
||||
|
||||
// Set date to target date but keep original time
|
||||
const targetStart = new Date(targetDate);
|
||||
targetStart.setHours(originalStart.getHours(), originalStart.getMinutes(), originalStart.getSeconds(), originalStart.getMilliseconds());
|
||||
|
||||
const targetEnd = new Date(targetDate);
|
||||
targetEnd.setHours(originalEnd.getHours(), originalEnd.getMinutes(), originalEnd.getSeconds(), originalEnd.getMilliseconds());
|
||||
|
||||
const calendarEvent: CalendarEvent = {
|
||||
id: eventId,
|
||||
title: title,
|
||||
start: targetStart,
|
||||
end: targetEnd,
|
||||
type: type,
|
||||
allDay: true,
|
||||
syncStatus: 'synced',
|
||||
metadata: {
|
||||
duration: originalElement.dataset.duration || '60'
|
||||
}
|
||||
};
|
||||
|
||||
// Use renderer to create and add all-day event
|
||||
const allDayElement = this.allDayEventRenderer.renderAllDayEvent(calendarEvent, targetDate);
|
||||
|
||||
if (allDayElement) {
|
||||
// Remove original timed event
|
||||
originalElement.remove();
|
||||
|
||||
// Animate height change
|
||||
this.checkAndAnimateAllDayHeight();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update row height when all-day events change
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { IEventBus } from '../types/CalendarTypes';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { DateCalculator } from '../utils/DateCalculator';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
|
||||
interface CachedElements {
|
||||
scrollContainer: HTMLElement | null;
|
||||
|
|
@ -93,14 +94,13 @@ export class DragDropManager {
|
|||
|
||||
// Listen for header mouseover events
|
||||
this.eventBus.on('header:mouseover', (event) => {
|
||||
const { element, targetDate, headerRenderer } = (event as CustomEvent).detail;
|
||||
const { targetDate, headerRenderer } = (event as CustomEvent).detail;
|
||||
|
||||
if (this.draggedEventId && targetDate) {
|
||||
// Emit event to convert to all-day
|
||||
this.eventBus.emit('drag:convert-to-allday', {
|
||||
eventId: this.draggedEventId,
|
||||
targetDate,
|
||||
element,
|
||||
originalElement: this.originalElement,
|
||||
headerRenderer
|
||||
});
|
||||
}
|
||||
|
|
@ -110,7 +110,7 @@ export class DragDropManager {
|
|||
this.eventBus.on('column:mouseover', (event) => {
|
||||
const { targetColumn, targetY } = (event as CustomEvent).detail;
|
||||
|
||||
if ((event as any).buttons === 1 && this.draggedEventId && this.isAllDayEventBeingDragged()) {
|
||||
if (this.draggedEventId && this.isAllDayEventBeingDragged()) {
|
||||
// Emit event to convert to timed
|
||||
this.eventBus.emit('drag:convert-to-timed', {
|
||||
eventId: this.draggedEventId,
|
||||
|
|
@ -291,7 +291,7 @@ export class DragDropManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Consolidated position calculation method
|
||||
* Consolidated position calculation method using PositionUtils
|
||||
*/
|
||||
private calculateDragPosition(mousePosition: Position): { column: string | null; snappedY: number } {
|
||||
const column = this.detectColumn(mousePosition.x, mousePosition.y);
|
||||
|
|
@ -310,15 +310,14 @@ export class DragDropManager {
|
|||
const columnElement = this.getCachedColumnElement(targetColumn);
|
||||
if (!columnElement) return mouseY;
|
||||
|
||||
const columnRect = columnElement.getBoundingClientRect();
|
||||
const relativeY = mouseY - columnRect.top - this.mouseOffset.y;
|
||||
const relativeY = PositionUtils.getPositionFromCoordinate(mouseY, columnElement);
|
||||
|
||||
// Return free position (no snapping)
|
||||
return Math.max(0, relativeY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized snap position calculation with caching (used only on drop)
|
||||
* Optimized snap position calculation using PositionUtils
|
||||
*/
|
||||
private calculateSnapPosition(mouseY: number, column: string | null = null): number {
|
||||
const targetColumn = column || this.currentColumn;
|
||||
|
|
@ -327,11 +326,8 @@ export class DragDropManager {
|
|||
const columnElement = this.getCachedColumnElement(targetColumn);
|
||||
if (!columnElement) return mouseY;
|
||||
|
||||
const columnRect = columnElement.getBoundingClientRect();
|
||||
const relativeY = mouseY - columnRect.top - this.mouseOffset.y;
|
||||
|
||||
// Snap to nearest interval using DateCalculator precision
|
||||
const snappedY = Math.round(relativeY / this.snapDistancePx) * this.snapDistancePx;
|
||||
// Use PositionUtils for consistent snapping behavior
|
||||
const snappedY = PositionUtils.getPositionFromCoordinate(mouseY, columnElement);
|
||||
|
||||
return Math.max(0, snappedY);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
import { eventBus } from '../core/EventBus';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
|
||||
/**
|
||||
* Manages scrolling functionality for the calendar using native scrollbars
|
||||
|
|
@ -96,13 +97,12 @@ export class ScrollManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Scroll to specific hour
|
||||
* Scroll to specific hour using PositionUtils
|
||||
*/
|
||||
scrollToHour(hour: number): void {
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const hourHeight = gridSettings.hourHeight;
|
||||
const dayStartHour = gridSettings.dayStartHour;
|
||||
const scrollTop = (hour - dayStartHour) * hourHeight;
|
||||
// Create time string for the hour
|
||||
const timeString = `${hour.toString().padStart(2, '0')}:00`;
|
||||
const scrollTop = PositionUtils.timeToPixels(timeString);
|
||||
|
||||
this.scrollTo(scrollTop);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
import { DateCalculator } from '../utils/DateCalculator';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
|
||||
/**
|
||||
* Work hours for a specific day
|
||||
|
|
@ -91,7 +92,7 @@ export class WorkHoursManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Calculate CSS custom properties for non-work hour overlays (before and after work)
|
||||
* Calculate CSS custom properties for non-work hour overlays using PositionUtils
|
||||
*/
|
||||
calculateNonWorkHoursStyle(workHours: DayWorkHours | 'off'): { beforeWorkHeight: number; afterWorkTop: number } | null {
|
||||
if (workHours === 'off') {
|
||||
|
|
@ -100,7 +101,6 @@ export class WorkHoursManager {
|
|||
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const dayStartHour = gridSettings.dayStartHour;
|
||||
const dayEndHour = gridSettings.dayEndHour;
|
||||
const hourHeight = gridSettings.hourHeight;
|
||||
|
||||
// Before work: from day start to work start
|
||||
|
|
@ -109,28 +109,28 @@ export class WorkHoursManager {
|
|||
// After work: from work end to day end
|
||||
const afterWorkTop = (workHours.end - dayStartHour) * hourHeight;
|
||||
|
||||
return {
|
||||
beforeWorkHeight: Math.max(0, beforeWorkHeight),
|
||||
afterWorkTop: Math.max(0, afterWorkTop)
|
||||
return {
|
||||
beforeWorkHeight: Math.max(0, beforeWorkHeight),
|
||||
afterWorkTop: Math.max(0, afterWorkTop)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate CSS custom properties for work hours overlay (legacy - for backward compatibility)
|
||||
* Calculate CSS custom properties for work hours overlay using PositionUtils
|
||||
*/
|
||||
calculateWorkHoursStyle(workHours: DayWorkHours | 'off'): { top: number; height: number } | null {
|
||||
if (workHours === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const dayStartHour = gridSettings.dayStartHour;
|
||||
const hourHeight = gridSettings.hourHeight;
|
||||
// Create dummy time strings for start and end of work hours
|
||||
const startTime = `${workHours.start.toString().padStart(2, '0')}:00`;
|
||||
const endTime = `${workHours.end.toString().padStart(2, '0')}:00`;
|
||||
|
||||
const top = (workHours.start - dayStartHour) * hourHeight;
|
||||
const height = (workHours.end - workHours.start) * hourHeight;
|
||||
// Use PositionUtils for consistent position calculation
|
||||
const position = PositionUtils.calculateEventPosition(startTime, endTime);
|
||||
|
||||
return { top, height };
|
||||
return { top: position.top, height: position.height };
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue