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:
parent
3bd74d6f4e
commit
e9298934c6
2 changed files with 240 additions and 51 deletions
178
src/elements/SwpEventElement.ts
Normal file
178
src/elements/SwpEventElement.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
|
|
@ -7,6 +7,7 @@ import { eventBus } from '../core/EventBus';
|
|||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { OverlapDetector, OverlapResult, EventId } from '../utils/OverlapDetector';
|
||||
import { ResizeManager } from '../managers/ResizeManager';
|
||||
import { SwpEventElement, SwpAllDayEventElement } from '../elements/SwpEventElement';
|
||||
|
||||
/**
|
||||
* Interface for event rendering strategies
|
||||
|
|
@ -146,6 +147,12 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
this.handleConvertToTimed(eventId, targetColumn, targetY);
|
||||
});
|
||||
|
||||
// Handle simple all-day duration adjustment (when leaving header)
|
||||
eventBus.on('drag:adjust-allday-duration', (event) => {
|
||||
const { eventId, durationMinutes } = (event as CustomEvent).detail;
|
||||
this.handleAdjustAllDayDuration(eventId, durationMinutes);
|
||||
});
|
||||
|
||||
// Handle navigation period change (when slide animation completes)
|
||||
eventBus.on(CoreEvents.NAVIGATION_COMPLETED, () => {
|
||||
// Animate all-day height after navigation completes
|
||||
|
|
@ -723,21 +730,25 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
}
|
||||
});
|
||||
|
||||
// Create all-day event with standardized data attributes
|
||||
const allDayEvent = document.createElement('swp-allday-event');
|
||||
allDayEvent.dataset.eventId = clone.dataset.eventId || '';
|
||||
allDayEvent.dataset.title = eventTitle;
|
||||
allDayEvent.dataset.start = `${targetDate}T00:00:00`;
|
||||
allDayEvent.dataset.end = `${targetDate}T23:59:59`;
|
||||
allDayEvent.dataset.type = clone.dataset.type || 'work';
|
||||
allDayEvent.dataset.duration = eventDuration;
|
||||
allDayEvent.dataset.allDay = "true";
|
||||
// Create CalendarEvent object for the factory
|
||||
const tempEvent: CalendarEvent = {
|
||||
id: clone.dataset.eventId || '',
|
||||
title: eventTitle,
|
||||
start: new Date(`${targetDate}T00:00:00`),
|
||||
end: new Date(`${targetDate}T23:59:59`),
|
||||
type: clone.dataset.type || 'work',
|
||||
allDay: true,
|
||||
syncStatus: 'synced',
|
||||
metadata: {
|
||||
duration: eventDuration
|
||||
}
|
||||
};
|
||||
|
||||
allDayEvent.textContent = eventTitle;
|
||||
// Create all-day event using factory
|
||||
const swpAllDayEvent = SwpAllDayEventElement.fromCalendarEvent(tempEvent, targetDate);
|
||||
const allDayEvent = swpAllDayEvent.getElement();
|
||||
|
||||
console.log("allDayEvent", allDayEvent.dataset);
|
||||
// Position in grid
|
||||
(allDayEvent as HTMLElement).style.gridColumn = columnIndex.toString();
|
||||
// grid-row will be set by checkAndAnimateAllDayHeight() based on actual position
|
||||
|
||||
// Remove original clone
|
||||
|
|
@ -768,6 +779,33 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
this.transformAllDayToTimed(this.draggedClone, targetColumn, targetY);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle simple all-day duration adjustment (when leaving header)
|
||||
*/
|
||||
private handleAdjustAllDayDuration(eventId: string, durationMinutes: number): void {
|
||||
if (!this.draggedClone) return;
|
||||
|
||||
// Only adjust if it's an all-day event
|
||||
if (this.draggedClone.tagName !== 'SWP-ALLDAY-EVENT') return;
|
||||
|
||||
// Simply adjust the duration and height - keep all other data intact
|
||||
this.draggedClone.dataset.duration = durationMinutes.toString();
|
||||
|
||||
// Calculate new height based on duration
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const hourHeight = gridSettings.hourHeight;
|
||||
const newHeight = (durationMinutes / 60) * hourHeight;
|
||||
this.draggedClone.style.height = `${newHeight}px`;
|
||||
|
||||
// Remove all-day specific styling to make it behave like a timed event
|
||||
this.draggedClone.style.gridColumn = '';
|
||||
this.draggedClone.style.gridRow = '';
|
||||
this.draggedClone.dataset.allDay = "false";
|
||||
|
||||
// Apply basic timed event positioning
|
||||
this.applyDragStyling(this.draggedClone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform clone from all-day to timed event
|
||||
*/
|
||||
|
|
@ -821,18 +859,12 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
}
|
||||
};
|
||||
|
||||
// Create timed event element
|
||||
const timedEvent = document.createElement('swp-event');
|
||||
timedEvent.dataset.eventId = eventId;
|
||||
timedEvent.dataset.title = eventTitle;
|
||||
timedEvent.dataset.type = eventType;
|
||||
timedEvent.dataset.start = startDate.toISOString();
|
||||
timedEvent.dataset.end = endDate.toISOString();
|
||||
timedEvent.dataset.duration = duration.toString();
|
||||
timedEvent.dataset.originalDuration = duration.toString();
|
||||
// Create timed event using factory
|
||||
const swpTimedEvent = SwpEventElement.fromCalendarEvent(tempEvent);
|
||||
const timedEvent = swpTimedEvent.getElement();
|
||||
|
||||
// Create inner structure using helper method
|
||||
timedEvent.innerHTML = this.createEventInnerStructure(tempEvent);
|
||||
// Set additional drag-specific attributes
|
||||
timedEvent.dataset.originalDuration = duration.toString();
|
||||
|
||||
// Apply drag styling and positioning
|
||||
this.applyDragStyling(timedEvent);
|
||||
|
|
@ -970,19 +1002,12 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
|
||||
// Place events directly in the single container
|
||||
eventPlacements.forEach(({ event, span, row }) => {
|
||||
// Create the all-day event element
|
||||
const allDayEvent = document.createElement('swp-allday-event');
|
||||
allDayEvent.textContent = event.title;
|
||||
// Create all-day event using factory
|
||||
const eventDateStr = DateCalculator.formatISODate(event.start);
|
||||
const swpAllDayEvent = SwpAllDayEventElement.fromCalendarEvent(event, eventDateStr);
|
||||
const allDayEvent = swpAllDayEvent.getElement();
|
||||
|
||||
// Set data attributes directly from CalendarEvent
|
||||
allDayEvent.dataset.eventId = event.id;
|
||||
allDayEvent.dataset.title = event.title;
|
||||
allDayEvent.dataset.start = event.start.toISOString();
|
||||
allDayEvent.dataset.end = event.end.toISOString();
|
||||
allDayEvent.dataset.type = event.type;
|
||||
allDayEvent.dataset.duration = event.metadata?.duration?.toString() || '60';
|
||||
|
||||
// Set grid position (column and row)
|
||||
// Override grid position for spanning events
|
||||
(allDayEvent as HTMLElement).style.gridColumn = span.columnSpan > 1
|
||||
? `${span.startColumn} / span ${span.columnSpan}`
|
||||
: `${span.startColumn}`;
|
||||
|
|
@ -1000,22 +1025,8 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
}
|
||||
|
||||
protected renderEvent(event: CalendarEvent): HTMLElement {
|
||||
const eventElement = document.createElement('swp-event');
|
||||
eventElement.dataset.eventId = event.id;
|
||||
eventElement.dataset.title = event.title;
|
||||
eventElement.dataset.start = event.start.toISOString();
|
||||
eventElement.dataset.end = event.end.toISOString();
|
||||
eventElement.dataset.type = event.type;
|
||||
eventElement.dataset.duration = event.metadata?.duration?.toString() || '60';
|
||||
|
||||
// Calculate and apply position based on time
|
||||
const position = this.calculateEventPosition(event);
|
||||
this.applyEventPositioning(eventElement, position.top + 1, position.height - 3);
|
||||
|
||||
// Color is now handled by CSS classes based on data-type attribute
|
||||
|
||||
// Create event content using helper method
|
||||
eventElement.innerHTML = this.createEventInnerStructure(event);
|
||||
const swpEvent = SwpEventElement.fromCalendarEvent(event);
|
||||
const eventElement = swpEvent.getElement();
|
||||
|
||||
// Setup resize handles on first mouseover only
|
||||
eventElement.addEventListener('mouseover', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue