2025-08-07 00:15:44 +02:00
// Event rendering strategy interface and implementations
import { CalendarEvent } from '../types/CalendarTypes' ;
2025-09-03 20:04:47 +02:00
import { calendarConfig } from '../core/CalendarConfig' ;
2025-08-27 22:50:13 +02:00
import { eventBus } from '../core/EventBus' ;
2025-09-21 21:30:51 +02:00
import { OverlapDetector , OverlapResult } from '../utils/OverlapDetector' ;
import { SwpEventElement } from '../elements/SwpEventElement' ;
2025-09-12 22:21:56 +02:00
import { TimeFormatter } from '../utils/TimeFormatter' ;
2025-09-13 00:39:56 +02:00
import { PositionUtils } from '../utils/PositionUtils' ;
2025-09-23 20:44:15 +02:00
import { DragOffset , StackLinkData } from '../types/DragDropTypes' ;
2025-09-28 13:25:09 +02:00
import { ColumnBounds } from '../utils/ColumnDetectionUtils' ;
import { DragColumnChangeEventPayload , DragMoveEventPayload , DragStartEventPayload } from '../types/EventTypes' ;
2025-10-03 16:47:42 +02:00
import { DateService } from '../utils/DateService' ;
2025-10-03 20:59:52 +02:00
import { format } from 'date-fns' ;
2025-08-07 00:15:44 +02:00
/ * *
* Interface for event rendering strategies
* /
export interface EventRendererStrategy {
2025-09-03 20:04:47 +02:00
renderEvents ( events : CalendarEvent [ ] , container : HTMLElement ) : void ;
2025-08-16 00:51:12 +02:00
clearEvents ( container? : HTMLElement ) : void ;
2025-09-28 13:25:09 +02:00
handleDragStart ? ( payload : DragStartEventPayload ) : void ;
handleDragMove ? ( payload : DragMoveEventPayload ) : void ;
2025-09-20 09:40:56 +02:00
handleDragAutoScroll ? ( eventId : string , snappedY : number ) : void ;
2025-09-28 13:25:09 +02:00
handleDragEnd ? ( eventId : string , originalElement : HTMLElement , draggedClone : HTMLElement , finalColumn : ColumnBounds , finalY : number ) : void ;
2025-09-20 09:40:56 +02:00
handleEventClick ? ( eventId : string , originalElement : HTMLElement ) : void ;
2025-09-28 13:25:09 +02:00
handleColumnChange ? ( payload : DragColumnChangeEventPayload ) : void ;
2025-09-20 09:40:56 +02:00
handleNavigationCompleted ? ( ) : void ;
2025-08-07 00:15:44 +02:00
}
2025-10-02 23:11:26 +02:00
// Abstract methods that subclasses must implement
// private getColumns(container: HTMLElement): HTMLElement[];
// private getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[];
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 {
2025-09-20 09:40:56 +02:00
2025-10-03 20:50:40 +02:00
private dateService : DateService ;
2025-08-20 00:39:31 +02:00
2025-10-03 20:50:40 +02:00
constructor ( ) {
2025-10-04 00:32:26 +02:00
const timezone = calendarConfig . getTimezone ? . ( ) ;
2025-10-03 20:50:40 +02:00
this . dateService = new DateService ( timezone ) ;
2025-10-02 23:11:26 +02:00
this . setupDragEventListeners ( ) ;
2025-08-27 22:50:13 +02:00
}
2025-10-02 23:11:26 +02:00
private draggedClone : HTMLElement | null = null ;
private originalEvent : HTMLElement | null = null ;
2025-09-09 14:35:21 +02:00
// ============================================
// NEW OVERLAP DETECTION SYSTEM
// All new functions prefixed with new_
// ============================================
protected overlapDetector = new OverlapDetector ( ) ;
/ * *
* Ny hovedfunktion til at håndtere event overlaps
* @param events - Events der skal renderes i kolonnen
* @param container - Container element at rendere i
* /
2025-09-13 22:38:29 +02:00
protected handleEventOverlaps ( events : CalendarEvent [ ] , container : HTMLElement ) : void {
2025-09-09 14:35:21 +02:00
if ( events . length === 0 ) return ;
if ( events . length === 1 ) {
const element = this . renderEvent ( events [ 0 ] ) ;
container . appendChild ( element ) ;
return ;
}
2025-09-09 17:15:06 +02:00
// Track hvilke events der allerede er blevet processeret
const processedEvents = new Set < string > ( ) ;
2025-09-09 14:35:21 +02:00
// Gå gennem hvert event og find overlaps
events . forEach ( ( currentEvent , index ) = > {
2025-09-09 17:15:06 +02:00
// Skip events der allerede er processeret som del af en overlap gruppe
if ( processedEvents . has ( currentEvent . id ) ) {
return ;
}
2025-09-09 14:35:21 +02:00
const remainingEvents = events . slice ( index + 1 ) ;
const overlappingEvents = this . overlapDetector . resolveOverlap ( currentEvent , remainingEvents ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 14:35:21 +02:00
if ( overlappingEvents . length > 0 ) {
// Der er overlaps - opret stack links
const result = this . overlapDetector . decorateWithStackLinks ( currentEvent , overlappingEvents ) ;
2025-09-13 22:38:29 +02:00
this . renderOverlappingEvents ( result , container ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 17:15:06 +02:00
// Marker alle events i overlap gruppen som processeret
overlappingEvents . forEach ( event = > processedEvents . add ( event . id ) ) ;
2025-09-09 14:35:21 +02:00
} else {
// Intet overlap - render normalt
const element = this . renderEvent ( currentEvent ) ;
container . appendChild ( element ) ;
2025-09-09 17:15:06 +02:00
processedEvents . add ( currentEvent . id ) ;
2025-09-09 14:35:21 +02:00
}
} ) ;
}
2025-10-02 23:11:26 +02:00
2025-09-10 22:07:40 +02:00
private applyDragStyling ( element : HTMLElement ) : void {
2025-09-16 23:09:10 +02:00
element . classList . add ( 'dragging' ) ;
2025-10-04 00:32:26 +02:00
element . style . removeProperty ( "margin-left" ) ;
2025-09-10 22:07:40 +02:00
}
2025-08-27 22:50:13 +02:00
/ * *
* Update clone timestamp based on new position
* /
2025-10-03 16:05:22 +02:00
private updateCloneTimestamp ( payload : DragMoveEventPayload ) : void {
2025-10-03 16:47:42 +02:00
if ( payload . draggedClone . dataset . allDay === "true" || ! payload . columnBounds ) return ;
2025-09-10 22:07:40 +02:00
2025-09-03 20:04:47 +02:00
const gridSettings = calendarConfig . getGridSettings ( ) ;
2025-10-03 16:47:42 +02:00
const { hourHeight , dayStartHour , snapInterval } = gridSettings ;
if ( ! payload . draggedClone . dataset . originalDuration ) {
throw new DOMException ( "missing clone.dataset.originalDuration" ) ;
}
2025-09-20 09:40:56 +02:00
2025-10-03 16:47:42 +02:00
// Calculate snapped start minutes
2025-10-03 16:05:22 +02:00
const minutesFromGridStart = ( payload . snappedY / hourHeight ) * 60 ;
2025-10-03 16:47:42 +02:00
const snappedStartMinutes = this . calculateSnappedMinutes (
minutesFromGridStart , dayStartHour , snapInterval
) ;
// Calculate end minutes
const originalDuration = parseInt ( payload . draggedClone . dataset . originalDuration ) ;
const endTotalMinutes = snappedStartMinutes + originalDuration ;
// Update UI
this . updateTimeDisplay ( payload . draggedClone , snappedStartMinutes , endTotalMinutes ) ;
// Update data attributes
this . updateDateTimeAttributes (
payload . draggedClone ,
new Date ( payload . columnBounds . date ) ,
snappedStartMinutes ,
endTotalMinutes
) ;
}
2025-09-20 09:40:56 +02:00
2025-10-03 16:47:42 +02:00
/ * *
* Calculate snapped minutes from grid start
* /
private calculateSnappedMinutes ( minutesFromGridStart : number , dayStartHour : number , snapInterval : number ) : number {
2025-09-03 20:48:23 +02:00
const actualStartMinutes = ( dayStartHour * 60 ) + minutesFromGridStart ;
2025-10-03 16:47:42 +02:00
return Math . round ( actualStartMinutes / snapInterval ) * snapInterval ;
}
2025-09-20 09:40:56 +02:00
2025-10-03 16:47:42 +02:00
/ * *
* Update time display in the UI
* /
private updateTimeDisplay ( element : HTMLElement , startMinutes : number , endMinutes : number ) : void {
const timeElement = element . querySelector ( 'swp-event-time' ) ;
if ( ! timeElement ) return ;
2025-09-20 09:40:56 +02:00
2025-10-03 16:47:42 +02:00
const startTime = this . formatTimeFromMinutes ( startMinutes ) ;
const endTime = this . formatTimeFromMinutes ( endMinutes ) ;
timeElement . textContent = ` ${ startTime } - ${ endTime } ` ;
}
2025-10-02 23:11:26 +02:00
2025-10-03 16:47:42 +02:00
/ * *
* Update data - start and data - end attributes with ISO timestamps
* /
private updateDateTimeAttributes ( element : HTMLElement , columnDate : Date , startMinutes : number , endMinutes : number ) : void {
2025-10-03 20:59:52 +02:00
const startDate = this . dateService . createDateAtTime ( columnDate , startMinutes ) ;
let endDate = this . dateService . createDateAtTime ( columnDate , endMinutes ) ;
2025-10-03 16:47:42 +02:00
// Handle cross-midnight events
if ( endMinutes >= 1440 ) {
const extraDays = Math . floor ( endMinutes / 1440 ) ;
2025-10-03 20:59:52 +02:00
endDate = this . dateService . addDays ( endDate , extraDays ) ;
2025-10-03 16:47:42 +02:00
}
2025-10-03 20:59:52 +02:00
2025-10-04 00:32:26 +02:00
// Convert to UTC before storing as ISO string
element . dataset . start = this . dateService . toUTC ( startDate ) ;
element . dataset . end = this . dateService . toUTC ( endDate ) ;
2025-10-03 16:47:42 +02:00
}
2025-09-20 09:40:56 +02:00
2025-10-03 16:47:42 +02:00
/ * *
* Format minutes since midnight to time string
* /
private formatTimeFromMinutes ( totalMinutes : number ) : string {
2025-10-03 20:59:52 +02:00
return this . dateService . minutesToTime ( totalMinutes ) ;
2025-08-27 22:50:13 +02:00
}
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
/ * *
* Handle drag start event
* /
2025-09-28 13:25:09 +02:00
public handleDragStart ( payload : DragStartEventPayload ) : void {
this . originalEvent = payload . draggedElement ; ;
2025-09-20 09:40:56 +02:00
2025-09-26 22:53:49 +02:00
// 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
2025-09-26 22:53:49 +02:00
this . applyDragStyling ( this . draggedClone ) ;
// Add to current column's events layer (not directly to column)
2025-09-28 13:25:09 +02:00
const eventsLayer = payload . columnBounds ? . element . querySelector ( 'swp-events-layer' ) ;
if ( eventsLayer ) {
eventsLayer . appendChild ( this . draggedClone ) ;
2025-09-04 00:16:35 +02:00
}
2025-08-27 22:50:13 +02:00
}
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
// Make original semi-transparent
2025-09-28 13:25:09 +02:00
this . originalEvent . style . opacity = '0.3' ;
this . originalEvent . style . userSelect = 'none' ;
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
}
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
/ * *
* Handle drag move event
* /
2025-09-28 13:25:09 +02:00
public handleDragMove ( payload : DragMoveEventPayload ) : void {
2025-08-27 22:50:13 +02:00
if ( ! this . draggedClone ) return ;
2025-09-20 09:40:56 +02:00
2025-10-03 19:09:44 +02:00
// Update position - snappedY is already the event top position
// Add +1px to match the initial positioning offset from SwpEventElement
this . draggedClone . style . top = ( payload . snappedY + 1 ) + 'px' ;
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
// Update timestamp display
2025-10-03 16:05:22 +02:00
this . updateCloneTimestamp ( payload ) ;
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
}
2025-09-20 09:40:56 +02:00
/ * *
* 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
2025-10-03 16:33:26 +02:00
//this.updateCloneTimestamp(this.draggedClone, snappedY); //TODO: Commented as, we need to move all this scroll logic til scroll manager away from eventrenderer
2025-09-20 09:40:56 +02:00
}
2025-08-27 22:50:13 +02:00
/ * *
* Handle column change during drag
* /
2025-09-28 13:25:09 +02:00
public handleColumnChange ( dragColumnChangeEvent : DragColumnChangeEventPayload ) : void {
2025-08-27 22:50:13 +02:00
if ( ! this . draggedClone ) return ;
2025-09-20 09:40:56 +02:00
2025-09-28 13:25:09 +02:00
const eventsLayer = dragColumnChangeEvent . newColumn . element . querySelector ( 'swp-events-layer' ) ;
if ( eventsLayer && this . draggedClone . parentElement !== eventsLayer ) {
eventsLayer . appendChild ( this . draggedClone ) ;
2025-10-03 16:47:42 +02:00
// Recalculate timestamps with new column date
const currentTop = parseFloat ( this . draggedClone . style . top ) || 0 ;
const mockPayload : DragMoveEventPayload = {
draggedElement : dragColumnChangeEvent.originalElement ,
draggedClone : this.draggedClone ,
mousePosition : dragColumnChangeEvent.mousePosition ,
mouseOffset : { x : 0 , y : 0 } ,
columnBounds : dragColumnChangeEvent.newColumn ,
snappedY : currentTop
} ;
this . updateCloneTimestamp ( mockPayload ) ;
2025-08-27 22:50:13 +02:00
}
}
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
/ * *
* Handle drag end event
* /
2025-09-28 13:25:09 +02:00
public handleDragEnd ( eventId : string , originalElement : HTMLElement , draggedClone : HTMLElement , finalColumn : ColumnBounds , finalY : number ) : void {
2025-09-20 09:40:56 +02:00
if ( ! draggedClone || ! originalElement ) {
console . warn ( 'Missing draggedClone or originalElement' ) ;
2025-08-27 23:56:38 +02:00
return ;
}
2025-09-20 09:40:56 +02:00
2025-09-09 22:57:26 +02:00
// Check om original event var del af en stack
2025-09-20 09:40:56 +02:00
const originalStackLink = originalElement . dataset . stackLink ;
2025-09-09 22:57:26 +02:00
if ( originalStackLink ) {
try {
const stackData = JSON . parse ( originalStackLink ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 22:57:26 +02:00
// Saml ALLE event IDs fra hele stack chain
const allStackEventIds : Set < string > = new Set ( ) ;
// Recursive funktion til at traversere stack chain
2025-09-23 20:44:15 +02:00
const traverseStack = ( linkData : StackLinkData , visitedIds : Set < string > ) = > {
2025-09-09 22:57:26 +02:00
if ( linkData . prev && ! visitedIds . has ( linkData . prev ) ) {
visitedIds . add ( linkData . prev ) ;
const prevElement = document . querySelector ( ` swp-time-grid [data-event-id=" ${ linkData . prev } "] ` ) as HTMLElement ;
if ( prevElement ? . dataset . stackLink ) {
try {
const prevLinkData = JSON . parse ( prevElement . dataset . stackLink ) ;
traverseStack ( prevLinkData , visitedIds ) ;
2025-09-20 09:40:56 +02:00
} catch ( e ) { }
2025-09-09 22:57:26 +02:00
}
}
2025-09-20 09:40:56 +02:00
2025-09-09 22:57:26 +02:00
if ( linkData . next && ! visitedIds . has ( linkData . next ) ) {
visitedIds . add ( linkData . next ) ;
const nextElement = document . querySelector ( ` swp-time-grid [data-event-id=" ${ linkData . next } "] ` ) as HTMLElement ;
if ( nextElement ? . dataset . stackLink ) {
try {
const nextLinkData = JSON . parse ( nextElement . dataset . stackLink ) ;
traverseStack ( nextLinkData , visitedIds ) ;
2025-09-20 09:40:56 +02:00
} catch ( e ) { }
2025-09-09 22:57:26 +02:00
}
}
} ;
// Start traversering fra original event's stackLink
traverseStack ( stackData , allStackEventIds ) ;
// Fjern original eventId da det bliver flyttet
allStackEventIds . delete ( eventId ) ;
// Find alle stack events og fjern dem
const stackEvents : CalendarEvent [ ] = [ ] ;
let container : HTMLElement | null = null ;
allStackEventIds . forEach ( id = > {
const element = document . querySelector ( ` swp-time-grid [data-event-id=" ${ id } "] ` ) as HTMLElement ;
if ( element ) {
// Gem container reference fra første element
if ( ! container ) {
container = element . closest ( 'swp-events-layer' ) as HTMLElement ;
}
2025-09-20 09:40:56 +02:00
2025-09-29 18:39:40 +02:00
const event = SwpEventElement . extractCalendarEventFromElement ( element ) ;
2025-09-09 22:57:26 +02:00
if ( event ) {
stackEvents . push ( event ) ;
}
2025-09-20 09:40:56 +02:00
2025-09-09 22:57:26 +02:00
// Fjern elementet
element . remove ( ) ;
}
} ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 22:57:26 +02:00
// Re-render stack events hvis vi fandt nogle
if ( stackEvents . length > 0 && container ) {
2025-09-13 22:38:29 +02:00
this . handleEventOverlaps ( stackEvents , container ) ;
2025-09-09 22:57:26 +02:00
}
} catch ( e ) {
console . warn ( 'Failed to parse stackLink data:' , e ) ;
}
}
2025-09-20 09:40:56 +02:00
2025-09-04 19:22:26 +02:00
// Remove original event from any existing groups first
2025-09-20 09:40:56 +02:00
this . removeEventFromExistingGroups ( originalElement ) ;
2025-08-27 22:50:13 +02:00
// Fade out original
2025-09-29 20:50:52 +02:00
// TODO: this should be changed into a subscriber which only after a succesful placement is fired, not just mouseup as this can remove elements that are not placed.
2025-10-02 23:11:26 +02:00
this . fadeOutAndRemove ( originalElement ) ;
2025-09-20 09:40:56 +02:00
2025-08-27 23:56:38 +02:00
// Remove clone prefix and normalize clone to be a regular event
2025-09-20 09:40:56 +02:00
const cloneId = draggedClone . dataset . eventId ;
2025-08-27 22:50:13 +02:00
if ( cloneId && cloneId . startsWith ( 'clone-' ) ) {
2025-09-20 09:40:56 +02:00
draggedClone . dataset . eventId = cloneId . replace ( 'clone-' , '' ) ;
2025-08-27 22:50:13 +02:00
}
2025-09-20 09:40:56 +02:00
2025-08-27 23:56:38 +02:00
// Fully normalize the clone to be a regular event
2025-09-20 09:40:56 +02:00
draggedClone . classList . remove ( 'dragging' ) ;
2025-09-04 19:22:26 +02:00
// Behold z-index hvis det er et stacked event
2025-09-20 09:40:56 +02:00
2025-10-03 16:47:42 +02:00
// Data attributes are already updated during drag:move, so no need to update again
// The updateCloneTimestamp method keeps them synchronized throughout the drag operation
2025-09-20 09:40:56 +02:00
2025-09-09 14:35:21 +02:00
// Detect overlaps with other events in the target column and reposition if needed
2025-09-20 09:40:56 +02:00
this . handleDragDropOverlaps ( draggedClone , finalColumn ) ;
2025-09-09 22:57:26 +02:00
// Fjern stackLink data fra dropped element
2025-09-20 09:40:56 +02:00
if ( draggedClone . dataset . stackLink ) {
delete draggedClone . dataset . stackLink ;
2025-09-09 22:57:26 +02:00
}
2025-09-20 09:40:56 +02:00
// Clean up instance state (no longer needed since we get elements as parameters)
2025-08-27 22:50:13 +02:00
this . draggedClone = null ;
this . originalEvent = null ;
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
}
2025-09-20 09:40:56 +02:00
/ * *
* Handle navigation completed event
* /
public handleNavigationCompleted ( ) : void {
// Default implementation - can be overridden by subclasses
}
2025-09-09 17:30:44 +02:00
/ * *
* Handle overlap detection and re - rendering after drag - drop
* /
2025-09-28 13:25:09 +02:00
private handleDragDropOverlaps ( droppedElement : HTMLElement , targetColumn : ColumnBounds ) : void {
2025-09-20 09:40:56 +02:00
2025-09-28 13:25:09 +02:00
const eventsLayer = targetColumn . element . querySelector ( 'swp-events-layer' ) as HTMLElement ;
2025-09-09 17:30:44 +02:00
if ( ! eventsLayer ) return ;
2025-09-20 09:40:56 +02:00
2025-09-09 17:30:44 +02:00
// Convert dropped element to CalendarEvent with new position
2025-09-29 18:39:40 +02:00
const droppedEvent = SwpEventElement . extractCalendarEventFromElement ( droppedElement ) ;
2025-09-09 17:30:44 +02:00
if ( ! droppedEvent ) return ;
2025-09-20 09:40:56 +02:00
2025-09-09 17:30:44 +02:00
// Get existing events in the column (excluding the dropped element)
const existingEvents = this . getEventsInColumn ( eventsLayer , droppedElement . dataset . eventId ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 17:30:44 +02:00
// Find overlaps with the dropped event
const overlappingEvents = this . overlapDetector . resolveOverlap ( droppedEvent , existingEvents ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 17:30:44 +02:00
if ( overlappingEvents . length > 0 ) {
// Remove only affected events from DOM
const affectedEventIds = [ droppedEvent . id , . . . overlappingEvents . map ( e = > e . id ) ] ;
eventsLayer . querySelectorAll ( 'swp-event' ) . forEach ( el = > {
const eventId = ( el as HTMLElement ) . dataset . eventId ;
if ( eventId && affectedEventIds . includes ( eventId ) ) {
el . remove ( ) ;
}
} ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 17:30:44 +02:00
// Re-render affected events with overlap handling
const affectedEvents = [ droppedEvent , . . . overlappingEvents ] ;
2025-09-13 22:38:29 +02:00
this . handleEventOverlaps ( affectedEvents , eventsLayer ) ;
2025-09-09 18:03:37 +02:00
} else {
// Reset z-index for non-overlapping events
droppedElement . style . zIndex = '' ;
2025-09-09 17:30:44 +02:00
}
}
/ * *
* Get all events in a column as CalendarEvent objects
* /
private getEventsInColumn ( eventsLayer : HTMLElement , excludeEventId? : string ) : CalendarEvent [ ] {
const eventElements = eventsLayer . querySelectorAll ( 'swp-event' ) ;
const events : CalendarEvent [ ] = [ ] ;
2025-09-20 09:40:56 +02:00
2025-09-09 17:30:44 +02:00
eventElements . forEach ( el = > {
const element = el as HTMLElement ;
const eventId = element . dataset . eventId ;
2025-09-20 09:40:56 +02:00
2025-09-09 17:30:44 +02:00
// Skip the excluded event (e.g., the dropped event)
if ( excludeEventId && eventId === excludeEventId ) {
return ;
}
2025-09-20 09:40:56 +02:00
2025-09-29 18:39:40 +02:00
const event = SwpEventElement . extractCalendarEventFromElement ( element ) ;
2025-09-09 17:30:44 +02:00
if ( event ) {
events . push ( event ) ;
}
} ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 17:30:44 +02:00
return events ;
}
2025-09-04 19:22:26 +02:00
/ * *
2025-09-09 14:35:21 +02:00
* Remove event from any existing groups and cleanup empty containers
2025-09-09 17:30:44 +02:00
* In the new system , this is handled automatically by re - rendering overlaps
2025-09-04 19:22:26 +02:00
* /
2025-09-09 14:35:21 +02:00
private removeEventFromExistingGroups ( eventElement : HTMLElement ) : void {
2025-09-09 17:30:44 +02:00
// With the new system, overlap relationships are recalculated on drop
// No need to manually track and remove from groups
2025-09-04 19:22:26 +02:00
}
2025-09-09 14:35:21 +02:00
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
/ * *
* Handle conversion to all - day event
* /
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
/ * *
2025-09-29 20:50:52 +02:00
* Fade out and remove element
2025-08-27 22:50:13 +02:00
* /
private fadeOutAndRemove ( element : HTMLElement ) : void {
element . style . transition = 'opacity 0.3s ease-out' ;
element . style . opacity = '0' ;
2025-09-20 09:40:56 +02:00
2025-08-27 22:50:13 +02:00
setTimeout ( ( ) = > {
element . remove ( ) ;
} , 300 ) ;
2025-08-20 00:39:31 +02:00
}
2025-09-20 09:40:56 +02:00
2025-09-03 20:04:47 +02:00
renderEvents ( events : CalendarEvent [ ] , container : HTMLElement ) : void {
2025-09-28 13:25:09 +02:00
2025-09-22 21:53:18 +02:00
// Filter out all-day events - they should be handled by AllDayEventRenderer
const timedEvents = events . filter ( event = > ! event . allDay ) ;
2025-09-28 13:25:09 +02:00
2025-09-22 21:53:18 +02:00
console . log ( '🎯 EventRenderer: Filtering events' , {
totalEvents : events.length ,
timedEvents : timedEvents.length ,
filteredOutAllDay : events.length - timedEvents . length
} ) ;
2025-09-28 13:25:09 +02:00
2025-08-24 00:13:07 +02:00
// Find columns in the specific container for regular events
2025-08-16 00:51:12 +02:00
const columns = this . getColumns ( container ) ;
2025-08-13 23:05:58 +02:00
columns . forEach ( column = > {
2025-09-22 21:53:18 +02:00
const columnEvents = this . getEventsForColumn ( column , timedEvents ) ;
2025-09-20 09:40:56 +02:00
2025-08-13 23:05:58 +02:00
const eventsLayer = column . querySelector ( 'swp-events-layer' ) ;
if ( eventsLayer ) {
2025-09-28 13:25:09 +02:00
2025-09-13 22:38:29 +02:00
this . handleEventOverlaps ( columnEvents , eventsLayer as HTMLElement ) ;
2025-08-07 00:15:44 +02:00
}
} ) ;
}
2025-08-13 23:05:58 +02:00
2025-08-24 00:13:07 +02:00
2025-10-02 23:11:26 +02:00
private renderEvent ( event : CalendarEvent ) : HTMLElement {
2025-09-10 22:36:11 +02:00
const swpEvent = SwpEventElement . fromCalendarEvent ( event ) ;
const eventElement = swpEvent . getElement ( ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 14:35:21 +02:00
// Setup resize handles on first mouseover only
2025-10-02 23:11:26 +02:00
eventElement . addEventListener ( 'mouseover' , ( ) = > { // TODO: This is not the correct way... we should not add eventlistener on every event
2025-09-09 14:35:21 +02:00
if ( eventElement . dataset . hasResizeHandlers !== 'true' ) {
eventElement . dataset . hasResizeHandlers = 'true' ;
}
} , { once : true } ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 14:35:21 +02:00
return eventElement ;
2025-08-07 00:15:44 +02:00
}
2025-09-03 20:04:47 +02:00
protected calculateEventPosition ( event : CalendarEvent ) : { top : number ; height : number } {
2025-09-13 00:39:56 +02:00
// Delegate to PositionUtils for centralized position calculation
return PositionUtils . calculateEventPosition ( event . start , event . end ) ;
2025-08-07 00:15:44 +02:00
}
2025-08-16 00:51:12 +02:00
clearEvents ( container? : HTMLElement ) : void {
2025-09-04 19:22:26 +02:00
const selector = 'swp-event, swp-event-group' ;
2025-09-04 00:16:35 +02:00
const existingEvents = container
2025-08-16 00:51:12 +02:00
? container . querySelectorAll ( selector )
: document . querySelectorAll ( selector ) ;
2025-09-20 09:40:56 +02:00
2025-08-07 00:15:44 +02:00
existingEvents . forEach ( event = > event . remove ( ) ) ;
}
2025-09-09 14:35:21 +02:00
/ * *
* Renderer overlappende events baseret på OverlapResult
* @param result - OverlapResult med events og stack links
* @param container - Container at rendere i
* /
2025-09-13 22:38:29 +02:00
protected renderOverlappingEvents ( result : OverlapResult , container : HTMLElement ) : void {
2025-09-09 14:35:21 +02:00
// Iterate direkte gennem stackLinks - allerede sorteret fra decorateWithStackLinks
for ( const [ eventId , stackLink ] of result . stackLinks . entries ( ) ) {
const event = result . overlappingEvents . find ( e = > e . id === eventId ) ;
if ( ! event ) continue ;
2025-09-20 09:40:56 +02:00
2025-09-09 14:35:21 +02:00
const element = this . renderEvent ( event ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 22:57:26 +02:00
// Gem stack link information på DOM elementet
element . dataset . stackLink = JSON . stringify ( {
prev : stackLink.prev ,
next : stackLink.next ,
stackLevel : stackLink.stackLevel
} ) ;
2025-09-20 09:40:56 +02:00
2025-09-09 14:35:21 +02:00
// Check om dette event deler kolonne med foregående (samme start tid)
if ( stackLink . prev ) {
const prevEvent = result . overlappingEvents . find ( e = > e . id === stackLink . prev ) ;
if ( prevEvent && prevEvent . start . getTime ( ) === event . start . getTime ( ) ) {
// Samme start tid - del kolonne (side by side)
this . new_applyColumnSharingStyling ( [ element ] ) ;
} else {
// Forskellige start tider - stack vertikalt
this . new_applyStackStyling ( element , stackLink . stackLevel ) ;
}
} else {
// Første event i stack
this . new_applyStackStyling ( element , stackLink . stackLevel ) ;
}
2025-09-20 09:40:56 +02:00
2025-09-09 14:35:21 +02:00
container . appendChild ( element ) ;
}
}
/ * *
* Applicerer stack styling ( margin - left og z - index )
* @param element - Event element
* @param stackLevel - Stack niveau
* /
protected new_applyStackStyling ( element : HTMLElement , stackLevel : number ) : void {
element . style . marginLeft = ` ${ stackLevel * 15 } px ` ;
element . style . zIndex = ` ${ 100 + stackLevel } ` ;
}
/ * *
* Applicerer column sharing styling ( flexbox )
* @param elements - Event elements der skal dele plads
* /
protected new_applyColumnSharingStyling ( elements : HTMLElement [ ] ) : void {
elements . forEach ( element = > {
element . style . flex = '1' ;
element . style . minWidth = '50px' ;
} ) ;
}
2025-08-07 00:15:44 +02:00
2025-08-27 23:56:38 +02:00
2025-09-22 21:53:18 +02:00
/ * *
* Setup drag event listeners - placeholder method
* /
private setupDragEventListeners ( ) : void {
// Drag event listeners are handled by EventRendererManager
// This method exists for compatibility
}
2025-08-16 00:51:12 +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 ;
2025-08-20 00:39:31 +02:00
if ( ! columnDate ) {
return [ ] ;
}
2025-08-13 23:05:58 +02:00
const columnEvents = events . filter ( event = > {
2025-10-03 20:50:40 +02:00
const eventDateStr = this . dateService . formatISODate ( event . start ) ;
2025-08-20 00:39:31 +02:00
const matches = eventDateStr === columnDate ;
2025-09-20 09:40:56 +02:00
2025-08-20 00:39:31 +02:00
return matches ;
2025-08-13 23:05:58 +02:00
} ) ;
return columnEvents ;
}
2025-08-07 00:15:44 +02:00
}