import { CalendarEvent } from '../types/CalendarTypes'; import { calendarConfig } from '../core/CalendarConfig'; import { TimeFormatter } from '../utils/TimeFormatter'; import { PositionUtils } from '../utils/PositionUtils'; import { EventLayout } from '../utils/AllDayLayoutEngine'; /** * Abstract base class for event DOM elements */ export abstract class BaseEventElement { protected element: HTMLElement; protected event: CalendarEvent; protected constructor(event: CalendarEvent) { this.event = event; this.element = this.createElement(); this.setDataAttributes(); } /** * Create the underlying DOM element */ protected abstract createElement(): HTMLElement; /** * Set standard data attributes on the element */ protected setDataAttributes(): void { this.element.dataset.eventId = this.event.id; this.element.dataset.title = this.event.title; this.element.dataset.start = this.event.start.toISOString(); this.element.dataset.end = this.event.end.toISOString(); this.element.dataset.type = this.event.type; this.element.dataset.duration = this.event.metadata?.duration?.toString() || '60'; } /** * Get the DOM element */ public getElement(): HTMLElement { return this.element; } /** * Format time for display using TimeFormatter */ protected formatTime(date: Date): string { return TimeFormatter.formatTime(date); } /** * Calculate event position for timed events using PositionUtils */ protected calculateEventPosition(): { top: number; height: number } { return PositionUtils.calculateEventPosition(this.event.start, this.event.end); } } /** * Timed event element (swp-event) */ export class SwpEventElement extends BaseEventElement { private constructor(event: CalendarEvent) { super(event); this.createInnerStructure(); this.applyPositioning(); } protected createElement(): HTMLElement { return document.createElement('swp-event'); } /** * Create inner HTML structure */ private createInnerStructure(): void { const timeRange = TimeFormatter.formatTimeRange(this.event.start, this.event.end); const durationMinutes = (this.event.end.getTime() - this.event.start.getTime()) / (1000 * 60); this.element.innerHTML = ` ${timeRange} ${this.event.title} `; } /** * Apply positioning styles */ private applyPositioning(): void { const position = this.calculateEventPosition(); this.element.style.top = `${position.top + 1}px`; this.element.style.height = `${position.height - 3}px`; this.element.style.left = '2px'; this.element.style.right = '2px'; } /** * Factory method to create a SwpEventElement from a CalendarEvent */ public static fromCalendarEvent(event: CalendarEvent): SwpEventElement { return new SwpEventElement(event); } /** * Create a clone of this SwpEventElement with "clone-" prefix */ public createClone(): SwpEventElement { // Clone the underlying DOM element const clonedElement = this.element.cloneNode(true) as HTMLElement; // Create new SwpEventElement instance from the cloned DOM const clonedSwpEvent = SwpEventElement.fromExistingElement(clonedElement); // Apply "clone-" prefix to ID clonedSwpEvent.updateEventId(`clone-${this.event.id}`); // Cache original duration for drag operations const originalDuration = this.getOriginalEventDuration(); clonedSwpEvent.element.dataset.originalDuration = originalDuration.toString(); // Set height from original element clonedSwpEvent.element.style.height = this.element.style.height || `${this.element.getBoundingClientRect().height}px`; return clonedSwpEvent; } /** * Factory method to create SwpEventElement from existing DOM element */ public static fromExistingElement(element: HTMLElement): SwpEventElement { // Extract CalendarEvent data from DOM element const event = this.extractCalendarEventFromElement(element); // Create new instance but replace the created element with the existing one const swpEvent = new SwpEventElement(event); swpEvent.element = element; return swpEvent; } /** * Update the event ID in both the CalendarEvent and DOM element */ private updateEventId(newId: string): void { this.event.id = newId; this.element.dataset.eventId = newId; } /** * Extract original event duration from DOM element */ private getOriginalEventDuration(): number { const timeElement = this.element.querySelector('swp-event-time'); if (timeElement) { const duration = timeElement.getAttribute('data-duration'); if (duration) { return parseInt(duration); } } return 60; // Fallback } /** * Extract CalendarEvent from DOM element */ public static extractCalendarEventFromElement(element: HTMLElement): CalendarEvent { return { id: element.dataset.eventId || '', title: element.dataset.title || '', start: new Date(element.dataset.start || ''), end: new Date(element.dataset.end || ''), type: element.dataset.type || 'work', allDay: false, syncStatus: 'synced', metadata: { duration: element.dataset.duration } }; } /** * Factory method to convert an all-day HTML element to a timed SwpEventElement */ public static fromAllDayElement(allDayElement: HTMLElement): SwpEventElement { // Extract data from all-day element's dataset 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'); } // Parse dates and set reasonable 1-hour duration for timed event const originalStart = new Date(startStr); const duration = durationStr ? parseInt(durationStr) : 60; // Default 1 hour // For conversion, use current time or a reasonable default (9 AM) 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); // Create CalendarEvent object const calendarEvent: CalendarEvent = { id: eventId, title: title, start: startDate, end: endDate, type: type, allDay: false, syncStatus: 'synced', metadata: { duration: duration.toString() } }; return new SwpEventElement(calendarEvent); } } /** * All-day event element (now using unified swp-event tag) */ export class SwpAllDayEventElement extends BaseEventElement { constructor(event: CalendarEvent) { super(event); this.setAllDayAttributes(); this.createInnerStructure(); // this.applyGridPositioning(); } protected createElement(): HTMLElement { return document.createElement('swp-event'); } /** * Set all-day specific attributes */ private setAllDayAttributes(): void { this.element.dataset.allDay = "true"; this.element.dataset.start = this.event.start.toISOString(); this.element.dataset.end = this.event.end.toISOString(); } /** * Create inner structure (just text content for all-day events) */ private createInnerStructure(): void { this.element.textContent = this.event.title; } /** * Apply CSS grid positioning */ public applyGridPositioning(layout: EventLayout): void { const gridArea = `${layout.row} / ${layout.startColumn} / ${layout.row + 1} / ${layout.endColumn + 1}`; this.element.style.gridArea = gridArea; } }