Refactors all-day event layout calculation

Improves all-day event drag and drop by recalculating layouts and applying differential updates to minimize DOM manipulations.

This change optimizes the update process by comparing current and new layouts, only updating elements with changed grid areas, leading to smoother transitions.
Removes obsolete code.
This commit is contained in:
Janus C. H. Knudsen 2025-09-30 22:35:07 +02:00
parent 6223bcd176
commit cf463cc691

View file

@ -24,15 +24,17 @@ export class AllDayManager {
private layoutEngine: AllDayLayoutEngine | null = null;
// State tracking for differential updates
private currentLayouts: Map<string, string> = new Map();
private currentLayouts: EventLayout[] = [];
private currentAllDayEvents: CalendarEvent[] = [];
private currentWeekDates: string[] = [];
private newLayouts: EventLayout[] = [];
// Expand/collapse state
private isExpanded: boolean = false;
private actualRowCount: number = 0;
private readonly MAX_COLLAPSED_ROWS = 4; // Show 4 rows when collapsed (3 events + 1 indicator row)
constructor() {
this.allDayEventRenderer = new AllDayEventRenderer();
this.setupEventListeners();
@ -168,13 +170,11 @@ export class AllDayManager {
*/
public checkAndAnimateAllDayHeight(): void {
const container = this.getAllDayContainer();
if (!container) {
this.animateToRows(0);
this.updateChevronButton(false);
return;
}
const allDayEvents = container.querySelectorAll('swp-event');
const allDayEvents = container?.querySelectorAll('swp-event');
//use currentLayouts here instead of the queryselector
// Calculate required rows - 0 if no events (will collapse)
let maxRows = 0;
@ -191,11 +191,6 @@ export class AllDayManager {
// Max rows = highest row number (e.g. if row 3 is used, height = 3 rows)
maxRows = highestRow;
console.log('🔍 AllDayManager: Height calculation FIXED', {
totalEvents: allDayEvents.length,
highestRowFound: highestRow,
maxRows
});
}
// Store actual row count
@ -203,11 +198,11 @@ export class AllDayManager {
// Determine what to display
let displayRows = maxRows;
if (maxRows > this.MAX_COLLAPSED_ROWS) {
// Show chevron button
this.updateChevronButton(true);
// Show 4 rows when collapsed (3 events + indicators)
if (!this.isExpanded) {
displayRows = this.MAX_COLLAPSED_ROWS;
@ -285,28 +280,6 @@ export class AllDayManager {
});
}
/**
* Store current layouts from DOM for comparison
*/
private storeCurrentLayouts(): void {
this.currentLayouts.clear();
const container = this.getAllDayContainer();
if (!container) return;
container.querySelectorAll('swp-event').forEach(element => {
const htmlElement = element as HTMLElement;
const eventId = htmlElement.dataset.eventId;
const gridArea = htmlElement.style.gridArea;
if (eventId && gridArea) {
this.currentLayouts.set(eventId, gridArea);
}
});
console.log('📋 AllDayManager: Stored current layouts', {
count: this.currentLayouts.size,
layouts: Array.from(this.currentLayouts.entries())
});
}
/**
* Set current events and week dates (called by EventRendererManager)
@ -332,10 +305,10 @@ export class AllDayManager {
this.currentWeekDates = weekDates;
// Initialize layout engine with provided week dates
this.layoutEngine = new AllDayLayoutEngine(weekDates);
var layoutEngine = new AllDayLayoutEngine(weekDates);
// Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly
return this.layoutEngine.calculateLayout(events);
return layoutEngine.calculateLayout(events);
}
@ -346,7 +319,7 @@ export class AllDayManager {
*/
private handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload): void {
if(payload.draggedClone?.dataset == null)
if (payload.draggedClone?.dataset == null)
console.error("payload.cloneElement.dataset.eventId is null");
@ -366,14 +339,9 @@ export class AllDayManager {
// Add to container
allDayContainer?.appendChild(payload.draggedClone);
ColumnDetectionUtils.updateColumnBoundsCache();
console.log('✅ AllDayManager: Converted to all-day style (simple row 1)', {
eventId: payload.draggedClone.dataset.eventId,
gridColumn: payload.targetColumn,
gridRow: 1
});
}
@ -432,28 +400,16 @@ export class AllDayManager {
dragClone.style.gridRow = '1'; // Force row 1 during drag
dragClone.style.gridArea = `1 / ${targetColumn.index} / 2 / ${targetColumn.index + 1}`;
console.log('🔄 AllDayManager: Updated all-day drag clone position', {
eventId: dragClone.dataset.eventId,
targetColumn,
gridRow: 1,
gridArea: dragClone.style.gridArea,
mouseX: mousePosition.x
});
}
/**
* Handle drag end for all-day events - WITH DIFFERENTIAL UPDATES
*/
private handleDragEnd(dragEndEvent: DragEndEventPayload): void {
console.log('🎯 AllDayManager: Starting drag end with differential updates', {
dragEndEvent
});
if (dragEndEvent.draggedClone == null)
return;
// 1. Store current layouts BEFORE any changes
this.storeCurrentLayouts();
// 2. Normalize clone ID
const cloneId = dragEndEvent.draggedClone?.dataset.eventId;
@ -484,19 +440,20 @@ export class AllDayManager {
const tempEvents = [...this.currentAllDayEvents, droppedEvent];
// 4. Calculate new layouts for ALL events
const newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates);
this.newLayouts = this.calculateAllDayEventsLayout(tempEvents, this.currentWeekDates);
// 5. Apply differential updates - only update events that changed
let changedCount = 0;
newLayouts.forEach((layout) => {
const oldGridArea = this.currentLayouts.get(layout.calenderEvent.id);
const newGridArea = layout.gridArea;
this.newLayouts.forEach((layout) => {
// Find current layout for this event
var currentLayout = this.currentLayouts.find(old => old.calenderEvent.id === layout.calenderEvent.id);
var currentGridArea = currentLayout?.gridArea;
var newGridArea = layout.gridArea;
if (oldGridArea !== newGridArea) {
if (currentGridArea !== newGridArea) {
changedCount++;
const element = dragEndEvent.draggedClone; //:end document.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement;
const element = dragEndEvent.draggedClone;
if (element) {
// Add transition class for smooth animation
element.classList.add('transitioning');
element.style.gridArea = newGridArea;
@ -509,6 +466,9 @@ export class AllDayManager {
}
});
if (changedCount > 0)
this.currentLayouts = this.newLayouts;
// 6. Clean up drag styles from the dropped clone
dragEndEvent.draggedClone.classList.remove('dragging');
dragEndEvent.draggedClone.style.zIndex = '';
@ -516,8 +476,7 @@ export class AllDayManager {
dragEndEvent.draggedClone.style.opacity = '';
// 7. Restore original element opacity
dragEndEvent.originalElement.remove();
//originalElement.style.opacity = '';
dragEndEvent.originalElement.remove(); //TODO: this should be an event that only fade and remove if confirmed dragdrop
// 8. Check if height adjustment is needed
this.checkAndAnimateAllDayHeight();
@ -530,9 +489,9 @@ export class AllDayManager {
private updateChevronButton(show: boolean): void {
const headerSpacer = this.getHeaderSpacer();
if (!headerSpacer) return;
let chevron = headerSpacer.querySelector('.allday-chevron') as HTMLElement;
if (show && !chevron) {
// Create chevron button
chevron = document.createElement('button');
@ -568,17 +527,17 @@ export class AllDayManager {
private updateOverflowIndicators(): void {
const container = this.getAllDayContainer();
if (!container) return;
container.querySelectorAll('swp-event').forEach((element) => {
const event = element as HTMLElement;
const gridRow = parseInt(event.style.gridRow) || 1;
if (gridRow === 4) {
// Store original content before converting to indicator
if (!event.dataset.originalTitle) {
event.dataset.originalTitle = event.dataset.title || event.innerHTML;
}
// Convert row 4 events to indicators
const overflowCount = this.actualRowCount - 3; // Total overflow rows
event.classList.add('max-event-overflow');
@ -600,12 +559,12 @@ export class AllDayManager {
private clearOverflowIndicators(): void {
const container = this.getAllDayContainer();
if (!container) return;
container.querySelectorAll('.max-event-overflow').forEach((event) => {
const htmlEvent = event as HTMLElement;
htmlEvent.classList.remove('max-event-overflow');
htmlEvent.onclick = null;
// Restore original title from data-title
if (htmlEvent.dataset.title) {
htmlEvent.innerHTML = htmlEvent.dataset.title;
@ -613,7 +572,7 @@ export class AllDayManager {
htmlEvent.innerHTML = htmlEvent.dataset.originalTitle;
}
});
// Show all hidden events
container.querySelectorAll('swp-event[style*="display: none"]').forEach((event) => {
(event as HTMLElement).style.display = '';