Calendar/src/renderers/EventRenderer.ts

223 lines
7.2 KiB
TypeScript
Raw Normal View History

2025-08-07 00:15:44 +02:00
// Event rendering strategy interface and implementations
import { CalendarEvent } from '../types/CalendarTypes';
import { calendarConfig } from '../core/CalendarConfig';
import { SwpEventElement } from '../elements/SwpEventElement';
import { PositionUtils } from '../utils/PositionUtils';
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
import { DragColumnChangeEventPayload, DragMoveEventPayload, DragStartEventPayload } from '../types/EventTypes';
import { DateService } from '../utils/DateService';
2025-08-07 00:15:44 +02:00
/**
* Interface for event rendering strategies
*/
export interface EventRendererStrategy {
renderEvents(events: CalendarEvent[], container: HTMLElement): void;
clearEvents(container?: HTMLElement): void;
handleDragStart?(payload: DragStartEventPayload): void;
handleDragMove?(payload: DragMoveEventPayload): void;
handleDragAutoScroll?(eventId: string, snappedY: number): void;
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void;
handleEventClick?(eventId: string, originalElement: HTMLElement): void;
handleColumnChange?(payload: DragColumnChangeEventPayload): void;
handleNavigationCompleted?(): void;
2025-08-07 00:15:44 +02:00
}
/**
2025-10-02 23:11:26 +02:00
* Date-based event renderer
2025-08-07 00:15:44 +02:00
*/
2025-10-02 23:11:26 +02:00
export class DateEventRenderer implements EventRendererStrategy {
private dateService: DateService;
2025-10-04 14:50:25 +02:00
private draggedClone: HTMLElement | null = null;
private originalEvent: HTMLElement | null = null;
constructor() {
const timezone = calendarConfig.getTimezone?.();
this.dateService = new DateService(timezone);
}
2025-10-02 23:11:26 +02:00
private applyDragStyling(element: HTMLElement): void {
element.classList.add('dragging');
element.style.removeProperty("margin-left");
}
/**
* Handle drag start event
*/
public handleDragStart(payload: DragStartEventPayload): void {
this.originalEvent = payload.draggedElement;;
// Use the clone from the payload instead of creating a new one
this.draggedClone = payload.draggedClone;
if (this.draggedClone) {
2025-10-02 23:11:26 +02:00
// Apply drag styling
this.applyDragStyling(this.draggedClone);
// Add to current column's events layer (not directly to column)
const eventsLayer = payload.columnBounds?.element.querySelector('swp-events-layer');
if (eventsLayer) {
eventsLayer.appendChild(this.draggedClone);
}
}
// Make original semi-transparent
this.originalEvent.style.opacity = '0.3';
this.originalEvent.style.userSelect = 'none';
}
/**
* Handle drag move event
*/
public handleDragMove(payload: DragMoveEventPayload): void {
if (!this.draggedClone || !payload.columnBounds) return;
// Delegate to SwpEventElement to update position and timestamps
const swpEvent = this.draggedClone as SwpEventElement;
const columnDate = new Date(payload.columnBounds.date);
swpEvent.updatePosition(columnDate, payload.snappedY);
}
/**
* Handle drag auto-scroll event
*/
public handleDragAutoScroll(eventId: string, snappedY: number): void {
if (!this.draggedClone) return;
// Update position directly using the calculated snapped position
this.draggedClone.style.top = snappedY + 'px';
// Update timestamp display
//this.updateCloneTimestamp(this.draggedClone, snappedY); //TODO: Commented as, we need to move all this scroll logic til scroll manager away from eventrenderer
}
/**
* Handle column change during drag
*/
public handleColumnChange(dragColumnChangeEvent: DragColumnChangeEventPayload): void {
if (!this.draggedClone) return;
const eventsLayer = dragColumnChangeEvent.newColumn.element.querySelector('swp-events-layer');
if (eventsLayer && this.draggedClone.parentElement !== eventsLayer) {
eventsLayer.appendChild(this.draggedClone);
// Recalculate timestamps with new column date
const currentTop = parseFloat(this.draggedClone.style.top) || 0;
const swpEvent = this.draggedClone as SwpEventElement;
const columnDate = new Date(dragColumnChangeEvent.newColumn.date);
swpEvent.updatePosition(columnDate, currentTop);
}
}
/**
* Handle drag end event
*/
public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void {
if (!draggedClone || !originalElement) {
console.warn('Missing draggedClone or originalElement');
return;
}
// Fade out original
2025-10-02 23:11:26 +02:00
this.fadeOutAndRemove(originalElement);
// Remove clone prefix and normalize clone to be a regular event
const cloneId = draggedClone.dataset.eventId;
if (cloneId && cloneId.startsWith('clone-')) {
draggedClone.dataset.eventId = cloneId.replace('clone-', '');
}
// Fully normalize the clone to be a regular event
draggedClone.classList.remove('dragging');
2025-10-04 14:50:25 +02:00
// Clean up instance state
this.draggedClone = null;
this.originalEvent = null;
}
/**
* Handle navigation completed event
*/
public handleNavigationCompleted(): void {
// Default implementation - can be overridden by subclasses
}
/**
* Fade out and remove element
*/
private fadeOutAndRemove(element: HTMLElement): void {
element.style.transition = 'opacity 0.3s ease-out';
element.style.opacity = '0';
setTimeout(() => {
element.remove();
}, 300);
}
renderEvents(events: CalendarEvent[], container: HTMLElement): void {
// Filter out all-day events - they should be handled by AllDayEventRenderer
const timedEvents = events.filter(event => !event.allDay);
// Find columns in the specific container for regular events
const columns = this.getColumns(container);
2025-08-13 23:05:58 +02:00
columns.forEach(column => {
const columnEvents = this.getEventsForColumn(column, timedEvents);
2025-08-13 23:05:58 +02:00
const eventsLayer = column.querySelector('swp-events-layer');
2025-10-04 14:50:25 +02:00
2025-08-13 23:05:58 +02:00
if (eventsLayer) {
2025-10-04 14:50:25 +02:00
// Simply render each event - no overlap handling
columnEvents.forEach(event => {
const element = this.renderEvent(event);
eventsLayer.appendChild(element);
});
2025-08-07 00:15:44 +02:00
}
});
}
2025-10-02 23:11:26 +02:00
private renderEvent(event: CalendarEvent): HTMLElement {
return SwpEventElement.fromCalendarEvent(event);
2025-08-07 00:15:44 +02:00
}
protected calculateEventPosition(event: CalendarEvent): { top: number; height: number } {
// Delegate to PositionUtils for centralized position calculation
return PositionUtils.calculateEventPosition(event.start, event.end);
2025-08-07 00:15:44 +02:00
}
clearEvents(container?: HTMLElement): void {
2025-10-04 14:50:25 +02:00
const selector = 'swp-event';
const existingEvents = container
? container.querySelectorAll(selector)
: document.querySelectorAll(selector);
2025-08-07 00:15:44 +02:00
existingEvents.forEach(event => event.remove());
}
2025-09-09 14:35:21 +02:00
protected getColumns(container: HTMLElement): HTMLElement[] {
const columns = container.querySelectorAll('swp-day-column');
2025-08-13 23:05:58 +02:00
return Array.from(columns) as HTMLElement[];
}
protected getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[] {
const columnDate = column.dataset.date;
if (!columnDate) {
return [];
}
2025-08-13 23:05:58 +02:00
const columnEvents = events.filter(event => {
const eventDateStr = this.dateService.formatISODate(event.start);
const matches = eventDateStr === columnDate;
return matches;
2025-08-13 23:05:58 +02:00
});
return columnEvents;
}
2025-08-07 00:15:44 +02:00
}