Introduces event element classes

Creates `SwpEventElement` and `SwpAllDayEventElement` classes for handling event rendering.

Refactors event creation logic in `EventRenderer` to utilize these classes, improving code organization and reusability.

Adds factory methods for creating event elements from `CalendarEvent` objects, simplifying event instantiation and data management.
This commit is contained in:
Janus Knudsen 2025-09-10 22:36:11 +02:00
parent 3bd74d6f4e
commit e9298934c6
2 changed files with 240 additions and 51 deletions

View file

@ -0,0 +1,178 @@
import { CalendarEvent } from '../types/CalendarTypes';
import { calendarConfig } from '../core/CalendarConfig';
/**
* 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
*/
protected formatTime(date: Date): string {
const hours = date.getHours();
const minutes = date.getMinutes();
const period = hours >= 12 ? 'PM' : 'AM';
const displayHours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours);
return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`;
}
/**
* Calculate event position for timed events
*/
protected calculateEventPosition(): { top: number; height: number } {
const gridSettings = calendarConfig.getGridSettings();
const dayStartHour = gridSettings.dayStartHour;
const hourHeight = gridSettings.hourHeight;
const startMinutes = this.event.start.getHours() * 60 + this.event.start.getMinutes();
const endMinutes = this.event.end.getHours() * 60 + this.event.end.getMinutes();
const dayStartMinutes = dayStartHour * 60;
const top = ((startMinutes - dayStartMinutes) / 60) * hourHeight;
const durationMinutes = endMinutes - startMinutes;
const height = (durationMinutes / 60) * hourHeight;
return { top, height };
}
}
/**
* 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 startTime = this.formatTime(this.event.start);
const endTime = this.formatTime(this.event.end);
const durationMinutes = (this.event.end.getTime() - this.event.start.getTime()) / (1000 * 60);
this.element.innerHTML = `
<swp-event-time data-duration="${durationMinutes}">${startTime} - ${endTime}</swp-event-time>
<swp-event-title>${this.event.title}</swp-event-title>
`;
}
/**
* Apply positioning styles
*/
private applyPositioning(): void {
const position = this.calculateEventPosition();
this.element.style.position = 'absolute';
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);
}
}
/**
* All-day event element (swp-allday-event)
*/
export class SwpAllDayEventElement extends BaseEventElement {
private columnIndex: number;
private constructor(event: CalendarEvent, columnIndex: number) {
super(event);
this.columnIndex = columnIndex;
this.setAllDayAttributes();
this.createInnerStructure();
this.applyGridPositioning();
}
protected createElement(): HTMLElement {
return document.createElement('swp-allday-event');
}
/**
* Set all-day specific attributes
*/
private setAllDayAttributes(): void {
this.element.dataset.allDay = "true";
// Override start/end times to be full day
const dateStr = this.event.start.toISOString().split('T')[0];
this.element.dataset.start = `${dateStr}T00:00:00`;
this.element.dataset.end = `${dateStr}T23:59:59`;
}
/**
* Create inner structure (just text content for all-day events)
*/
private createInnerStructure(): void {
this.element.textContent = this.event.title;
}
/**
* Apply CSS grid positioning
*/
private applyGridPositioning(): void {
this.element.style.gridColumn = this.columnIndex.toString();
}
/**
* Factory method to create from CalendarEvent and target date
*/
public static fromCalendarEvent(event: CalendarEvent, targetDate: string): SwpAllDayEventElement {
// Calculate column index
const dayHeaders = document.querySelectorAll('swp-day-header');
let columnIndex = 1;
dayHeaders.forEach((header, index) => {
if ((header as HTMLElement).dataset.date === targetDate) {
columnIndex = index + 1;
}
});
return new SwpAllDayEventElement(event, columnIndex);
}
}