Refactors all-day event layout calculation
Simplifies all-day event rendering by streamlining the layout calculation and event placement process, using the AllDayLayoutEngine to determine the grid positions. This removes deprecated methods and improves overall code clarity.
This commit is contained in:
parent
9dfd4574d8
commit
4141bffca4
7 changed files with 76 additions and 321 deletions
|
|
@ -2,6 +2,7 @@ import { CalendarEvent } from '../types/CalendarTypes';
|
||||||
import { calendarConfig } from '../core/CalendarConfig';
|
import { calendarConfig } from '../core/CalendarConfig';
|
||||||
import { TimeFormatter } from '../utils/TimeFormatter';
|
import { TimeFormatter } from '../utils/TimeFormatter';
|
||||||
import { PositionUtils } from '../utils/PositionUtils';
|
import { PositionUtils } from '../utils/PositionUtils';
|
||||||
|
import { EventLayout } from '../utils/AllDayLayoutEngine';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract base class for event DOM elements
|
* Abstract base class for event DOM elements
|
||||||
|
|
@ -75,7 +76,7 @@ export class SwpEventElement extends BaseEventElement {
|
||||||
private createInnerStructure(): void {
|
private createInnerStructure(): void {
|
||||||
const timeRange = TimeFormatter.formatTimeRange(this.event.start, this.event.end);
|
const timeRange = TimeFormatter.formatTimeRange(this.event.start, this.event.end);
|
||||||
const durationMinutes = (this.event.end.getTime() - this.event.start.getTime()) / (1000 * 60);
|
const durationMinutes = (this.event.end.getTime() - this.event.start.getTime()) / (1000 * 60);
|
||||||
|
|
||||||
this.element.innerHTML = `
|
this.element.innerHTML = `
|
||||||
<swp-event-time data-duration="${durationMinutes}">${timeRange}</swp-event-time>
|
<swp-event-time data-duration="${durationMinutes}">${timeRange}</swp-event-time>
|
||||||
<swp-event-title>${this.event.title}</swp-event-title>
|
<swp-event-title>${this.event.title}</swp-event-title>
|
||||||
|
|
@ -107,20 +108,20 @@ export class SwpEventElement extends BaseEventElement {
|
||||||
public createClone(): SwpEventElement {
|
public createClone(): SwpEventElement {
|
||||||
// Clone the underlying DOM element
|
// Clone the underlying DOM element
|
||||||
const clonedElement = this.element.cloneNode(true) as HTMLElement;
|
const clonedElement = this.element.cloneNode(true) as HTMLElement;
|
||||||
|
|
||||||
// Create new SwpEventElement instance from the cloned DOM
|
// Create new SwpEventElement instance from the cloned DOM
|
||||||
const clonedSwpEvent = SwpEventElement.fromExistingElement(clonedElement);
|
const clonedSwpEvent = SwpEventElement.fromExistingElement(clonedElement);
|
||||||
|
|
||||||
// Apply "clone-" prefix to ID
|
// Apply "clone-" prefix to ID
|
||||||
clonedSwpEvent.updateEventId(`clone-${this.event.id}`);
|
clonedSwpEvent.updateEventId(`clone-${this.event.id}`);
|
||||||
|
|
||||||
// Cache original duration for drag operations
|
// Cache original duration for drag operations
|
||||||
const originalDuration = this.getOriginalEventDuration();
|
const originalDuration = this.getOriginalEventDuration();
|
||||||
clonedSwpEvent.element.dataset.originalDuration = originalDuration.toString();
|
clonedSwpEvent.element.dataset.originalDuration = originalDuration.toString();
|
||||||
|
|
||||||
// Set height from original element
|
// Set height from original element
|
||||||
clonedSwpEvent.element.style.height = this.element.style.height || `${this.element.getBoundingClientRect().height}px`;
|
clonedSwpEvent.element.style.height = this.element.style.height || `${this.element.getBoundingClientRect().height}px`;
|
||||||
|
|
||||||
return clonedSwpEvent;
|
return clonedSwpEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -130,11 +131,11 @@ export class SwpEventElement extends BaseEventElement {
|
||||||
public static fromExistingElement(element: HTMLElement): SwpEventElement {
|
public static fromExistingElement(element: HTMLElement): SwpEventElement {
|
||||||
// Extract CalendarEvent data from DOM element
|
// Extract CalendarEvent data from DOM element
|
||||||
const event = this.extractCalendarEventFromElement(element);
|
const event = this.extractCalendarEventFromElement(element);
|
||||||
|
|
||||||
// Create new instance but replace the created element with the existing one
|
// Create new instance but replace the created element with the existing one
|
||||||
const swpEvent = new SwpEventElement(event);
|
const swpEvent = new SwpEventElement(event);
|
||||||
swpEvent.element = element;
|
swpEvent.element = element;
|
||||||
|
|
||||||
return swpEvent;
|
return swpEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +203,7 @@ export class SwpEventElement extends BaseEventElement {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const startDate = new Date(originalStart);
|
const startDate = new Date(originalStart);
|
||||||
startDate.setHours(now.getHours() || 9, now.getMinutes() || 0, 0, 0);
|
startDate.setHours(now.getHours() || 9, now.getMinutes() || 0, 0, 0);
|
||||||
|
|
||||||
const endDate = new Date(startDate);
|
const endDate = new Date(startDate);
|
||||||
endDate.setMinutes(endDate.getMinutes() + duration);
|
endDate.setMinutes(endDate.getMinutes() + duration);
|
||||||
|
|
||||||
|
|
@ -228,14 +229,12 @@ export class SwpEventElement extends BaseEventElement {
|
||||||
* All-day event element (now using unified swp-event tag)
|
* All-day event element (now using unified swp-event tag)
|
||||||
*/
|
*/
|
||||||
export class SwpAllDayEventElement extends BaseEventElement {
|
export class SwpAllDayEventElement extends BaseEventElement {
|
||||||
private columnIndex: number;
|
|
||||||
|
|
||||||
private constructor(event: CalendarEvent, columnIndex: number) {
|
constructor(event: CalendarEvent) {
|
||||||
super(event);
|
super(event);
|
||||||
this.columnIndex = columnIndex;
|
|
||||||
this.setAllDayAttributes();
|
this.setAllDayAttributes();
|
||||||
this.createInnerStructure();
|
this.createInnerStructure();
|
||||||
this.applyGridPositioning();
|
// this.applyGridPositioning();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createElement(): HTMLElement {
|
protected createElement(): HTMLElement {
|
||||||
|
|
@ -264,128 +263,9 @@ export class SwpAllDayEventElement extends BaseEventElement {
|
||||||
/**
|
/**
|
||||||
* Apply CSS grid positioning
|
* Apply CSS grid positioning
|
||||||
*/
|
*/
|
||||||
private applyGridPositioning(): void {
|
public applyGridPositioning(layout: EventLayout): void {
|
||||||
this.element.style.gridColumn = this.columnIndex.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set grid row for this all-day event
|
|
||||||
*/
|
|
||||||
public setGridRow(row: number): void {
|
|
||||||
this.element.style.gridRow = row.toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set grid column span for this all-day event
|
|
||||||
*/
|
|
||||||
public setColumnSpan(startColumn: number, endColumn: number): void {
|
|
||||||
this.element.style.gridColumn = `${startColumn} / ${endColumn + 1}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory method to create from CalendarEvent and layout (provided by AllDayManager)
|
|
||||||
*/
|
|
||||||
public static fromCalendarEventWithLayout(
|
|
||||||
event: CalendarEvent,
|
|
||||||
layout: { startColumn: number; endColumn: number; row: number; columnSpan: number }
|
|
||||||
): SwpAllDayEventElement {
|
|
||||||
// Create element with provided layout
|
|
||||||
const element = new SwpAllDayEventElement(event, layout.startColumn);
|
|
||||||
|
|
||||||
// Set complete grid-area instead of individual properties
|
|
||||||
const gridArea = `${layout.row} / ${layout.startColumn} / ${layout.row + 1} / ${layout.endColumn + 1}`;
|
const gridArea = `${layout.row} / ${layout.startColumn} / ${layout.row + 1} / ${layout.endColumn + 1}`;
|
||||||
element.element.style.gridArea = gridArea;
|
this.element.style.gridArea = gridArea;
|
||||||
|
|
||||||
console.log('✅ SwpAllDayEventElement: Created all-day event with AllDayLayoutEngine', {
|
|
||||||
eventId: event.id,
|
|
||||||
title: event.title,
|
|
||||||
gridArea: gridArea,
|
|
||||||
layout: layout
|
|
||||||
});
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Factory method to create from CalendarEvent and target date (DEPRECATED - use AllDayManager.calculateAllDayEventLayout)
|
|
||||||
* @deprecated Use AllDayManager.calculateAllDayEventLayout() and fromCalendarEventWithLayout() instead
|
|
||||||
*/
|
|
||||||
public static fromCalendarEvent(event: CalendarEvent, targetDate?: string): SwpAllDayEventElement {
|
|
||||||
console.warn('⚠️ SwpAllDayEventElement.fromCalendarEvent is deprecated. Use AllDayManager.calculateAllDayEventLayout() instead.');
|
|
||||||
|
|
||||||
// Fallback to simple column calculation without overlap detection
|
|
||||||
const { startColumn, endColumn } = this.calculateColumnSpan(event);
|
|
||||||
const finalStartColumn = targetDate ? this.getColumnIndexForDate(targetDate) : startColumn;
|
|
||||||
const finalEndColumn = targetDate ? finalStartColumn : endColumn;
|
|
||||||
|
|
||||||
// Create element with row 1 (no overlap detection)
|
|
||||||
const element = new SwpAllDayEventElement(event, finalStartColumn);
|
|
||||||
element.setGridRow(1);
|
|
||||||
element.setColumnSpan(finalStartColumn, finalEndColumn);
|
|
||||||
|
|
||||||
return element;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate column span based on event start and end dates
|
|
||||||
*/
|
|
||||||
private static calculateColumnSpan(event: CalendarEvent): { startColumn: number; endColumn: number; columnSpan: number } {
|
|
||||||
const dayHeaders = document.querySelectorAll('swp-day-header');
|
|
||||||
|
|
||||||
// Extract dates from headers
|
|
||||||
const headerDates: string[] = [];
|
|
||||||
dayHeaders.forEach(header => {
|
|
||||||
const date = (header as HTMLElement).dataset.date;
|
|
||||||
if (date) {
|
|
||||||
headerDates.push(date);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Format event dates for comparison (YYYY-MM-DD format)
|
|
||||||
const eventStartDate = event.start.toISOString().split('T')[0];
|
|
||||||
const eventEndDate = event.end.toISOString().split('T')[0];
|
|
||||||
|
|
||||||
// Find start and end column indices
|
|
||||||
let startColumn = 1;
|
|
||||||
let endColumn = headerDates.length;
|
|
||||||
|
|
||||||
headerDates.forEach((dateStr, index) => {
|
|
||||||
if (dateStr === eventStartDate) {
|
|
||||||
startColumn = index + 1;
|
|
||||||
}
|
|
||||||
if (dateStr === eventEndDate) {
|
|
||||||
endColumn = index + 1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ensure end column is at least start column
|
|
||||||
if (endColumn < startColumn) {
|
|
||||||
endColumn = startColumn;
|
|
||||||
}
|
|
||||||
|
|
||||||
const columnSpan = endColumn - startColumn + 1;
|
|
||||||
|
|
||||||
return { startColumn, endColumn, columnSpan };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get column index for a specific date
|
|
||||||
*/
|
|
||||||
private static getColumnIndexForDate(targetDate: string): number {
|
|
||||||
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 columnIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if two column ranges overlap
|
|
||||||
*/
|
|
||||||
private static columnsOverlap(startA: number, endA: number, startB: number, endB: number): boolean {
|
|
||||||
return !(endA < startB || endB < startA);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
import { eventBus } from '../core/EventBus';
|
import { eventBus } from '../core/EventBus';
|
||||||
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
||||||
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
||||||
import { AllDayLayoutEngine } from '../utils/AllDayLayoutEngine';
|
import { AllDayLayoutEngine, EventLayout } from '../utils/AllDayLayoutEngine';
|
||||||
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { CalendarEvent } from '../types/CalendarTypes';
|
||||||
import {
|
import {
|
||||||
|
|
@ -96,7 +96,7 @@ export class AllDayManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
eventBus.on('drag:end', (event) => {
|
eventBus.on('drag:end', (event) => {
|
||||||
const { draggedElement, mousePosition, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
|
const { draggedElement, mousePosition, finalPosition, target, draggedClone } = (event as CustomEvent<DragEndEventPayload>).detail;
|
||||||
|
|
||||||
if (target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore.
|
if (target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore.
|
||||||
return;
|
return;
|
||||||
|
|
@ -106,10 +106,9 @@ export class AllDayManager {
|
||||||
eventId: eventId,
|
eventId: eventId,
|
||||||
finalPosition
|
finalPosition
|
||||||
});
|
});
|
||||||
const dragClone = document.querySelector(`swp-allday-container swp-event[data-event-id="clone-${eventId}"]`);
|
|
||||||
|
|
||||||
console.log('🎯 AllDayManager: Ending drag for all-day event', { eventId });
|
console.log('🎯 AllDayManager: Ending drag for all-day event', { eventId });
|
||||||
this.handleDragEnd(draggedElement, dragClone as HTMLElement, { column: finalPosition.column || '', y: 0 });
|
this.handleDragEnd(draggedElement, draggedClone as HTMLElement, { column: finalPosition.column || '', y: 0 });
|
||||||
});
|
});
|
||||||
|
|
||||||
// Listen for drag cancellation to recalculate height
|
// Listen for drag cancellation to recalculate height
|
||||||
|
|
@ -307,18 +306,7 @@ export class AllDayManager {
|
||||||
* Calculate layout for ALL all-day events using AllDayLayoutEngine
|
* Calculate layout for ALL all-day events using AllDayLayoutEngine
|
||||||
* This is the correct method that processes all events together for proper overlap detection
|
* This is the correct method that processes all events together for proper overlap detection
|
||||||
*/
|
*/
|
||||||
public calculateAllDayEventsLayout(events: CalendarEvent[], weekDates: string[]): Map<string, {
|
public calculateAllDayEventsLayout(events: CalendarEvent[], weekDates: string[]): EventLayout[] {
|
||||||
startColumn: number;
|
|
||||||
endColumn: number;
|
|
||||||
row: number;
|
|
||||||
columnSpan: number;
|
|
||||||
gridArea: string;
|
|
||||||
}> {
|
|
||||||
console.log('🔍 AllDayManager: calculateAllDayEventsLayout - Processing all events together', {
|
|
||||||
eventCount: events.length,
|
|
||||||
events: events.map(e => ({ id: e.id, title: e.title, start: e.start.toISOString().split('T')[0], end: e.end.toISOString().split('T')[0] })),
|
|
||||||
weekDates
|
|
||||||
});
|
|
||||||
|
|
||||||
// Store current state
|
// Store current state
|
||||||
this.currentAllDayEvents = events;
|
this.currentAllDayEvents = events;
|
||||||
|
|
@ -328,35 +316,8 @@ export class AllDayManager {
|
||||||
this.layoutEngine = new AllDayLayoutEngine(weekDates);
|
this.layoutEngine = new AllDayLayoutEngine(weekDates);
|
||||||
|
|
||||||
// Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly
|
// Calculate layout for all events together - AllDayLayoutEngine handles CalendarEvents directly
|
||||||
const layouts = this.layoutEngine.calculateLayout(events);
|
return this.layoutEngine.calculateLayout(events);
|
||||||
|
|
||||||
// Convert to expected return format
|
|
||||||
const result = new Map<string, {
|
|
||||||
startColumn: number;
|
|
||||||
endColumn: number;
|
|
||||||
row: number;
|
|
||||||
columnSpan: number;
|
|
||||||
gridArea: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
layouts.forEach((layout, eventId) => {
|
|
||||||
result.set(eventId, {
|
|
||||||
startColumn: layout.startColumn,
|
|
||||||
endColumn: layout.endColumn,
|
|
||||||
row: layout.row,
|
|
||||||
columnSpan: layout.columnSpan,
|
|
||||||
gridArea: layout.gridArea
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log('✅ AllDayManager: Calculated layout for event', {
|
|
||||||
eventId,
|
|
||||||
title: events.find(e => e.id === eventId)?.title,
|
|
||||||
gridArea: layout.gridArea,
|
|
||||||
layout: layout
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -494,19 +455,14 @@ export class AllDayManager {
|
||||||
|
|
||||||
// 5. Apply differential updates - only update events that changed
|
// 5. Apply differential updates - only update events that changed
|
||||||
let changedCount = 0;
|
let changedCount = 0;
|
||||||
newLayouts.forEach((layout, eventId) => {
|
newLayouts.forEach((layout) => {
|
||||||
const oldGridArea = this.currentLayouts.get(eventId);
|
const oldGridArea = this.currentLayouts.get(layout.calenderEvent.id);
|
||||||
const newGridArea = layout.gridArea;
|
const newGridArea = layout.gridArea;
|
||||||
|
|
||||||
if (oldGridArea !== newGridArea) {
|
if (oldGridArea !== newGridArea) {
|
||||||
changedCount++;
|
changedCount++;
|
||||||
const element = document.querySelector(`[data-event-id="${eventId}"]`) as HTMLElement;
|
const element = document.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`) as HTMLElement;
|
||||||
if (element) {
|
if (element) {
|
||||||
console.log('🔄 AllDayManager: Updating event position', {
|
|
||||||
eventId,
|
|
||||||
oldGridArea,
|
|
||||||
newGridArea
|
|
||||||
});
|
|
||||||
|
|
||||||
// Add transition class for smooth animation
|
// Add transition class for smooth animation
|
||||||
element.classList.add('transitioning');
|
element.classList.add('transitioning');
|
||||||
|
|
@ -532,61 +488,6 @@ export class AllDayManager {
|
||||||
// 8. Check if height adjustment is needed
|
// 8. Check if height adjustment is needed
|
||||||
this.checkAndAnimateAllDayHeight();
|
this.checkAndAnimateAllDayHeight();
|
||||||
|
|
||||||
console.log('✅ AllDayManager: Completed differential drag end', {
|
|
||||||
eventId: droppedEventId,
|
|
||||||
totalEvents: newLayouts.size,
|
|
||||||
changedEvents: changedCount,
|
|
||||||
finalGridArea: newLayouts.get(droppedEventId)?.gridArea
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get existing all-day events from DOM
|
|
||||||
* Since we don't have direct access to EventManager, we'll get events from the current DOM
|
|
||||||
*/
|
|
||||||
private getExistingAllDayEvents(): CalendarEvent[] {
|
|
||||||
const allDayContainer = this.getAllDayContainer();
|
|
||||||
if (!allDayContainer) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const existingElements = allDayContainer.querySelectorAll('swp-event');
|
|
||||||
const events: CalendarEvent[] = [];
|
|
||||||
|
|
||||||
existingElements.forEach(element => {
|
|
||||||
const htmlElement = element as HTMLElement;
|
|
||||||
const eventId = htmlElement.dataset.eventId;
|
|
||||||
const title = htmlElement.dataset.title || htmlElement.textContent || '';
|
|
||||||
const allDayDate = htmlElement.dataset.allDayDate;
|
|
||||||
|
|
||||||
if (eventId && allDayDate) {
|
|
||||||
events.push({
|
|
||||||
id: eventId,
|
|
||||||
title: title,
|
|
||||||
start: new Date(allDayDate),
|
|
||||||
end: new Date(allDayDate),
|
|
||||||
type: 'work',
|
|
||||||
allDay: true,
|
|
||||||
syncStatus: 'synced'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return events;
|
|
||||||
}
|
|
||||||
|
|
||||||
private getVisibleDatesFromDOM(): string[] {
|
|
||||||
const dayHeaders = document.querySelectorAll('swp-calendar-header swp-day-header');
|
|
||||||
const weekDates: string[] = [];
|
|
||||||
|
|
||||||
dayHeaders.forEach(header => {
|
|
||||||
const dateAttr = header.getAttribute('data-date');
|
|
||||||
if (dateAttr) {
|
|
||||||
weekDates.push(dateAttr);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return weekDates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -132,7 +132,10 @@ export class DragDropManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleMouseDown(event: MouseEvent): void {
|
private handleMouseDown(event: MouseEvent): void {
|
||||||
this.isDragStarted = false;
|
|
||||||
|
// Clean up drag state first
|
||||||
|
this.cleanupDragState();
|
||||||
|
|
||||||
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
||||||
this.initialMousePosition = { x: event.clientX, y: event.clientY };
|
this.initialMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
|
|
@ -274,11 +277,10 @@ export class DragDropManager {
|
||||||
|
|
||||||
if (this.draggedElement) {
|
if (this.draggedElement) {
|
||||||
// Store variables locally before cleanup
|
// Store variables locally before cleanup
|
||||||
const draggedElement = this.draggedElement;
|
//const draggedElement = this.draggedElement;
|
||||||
const isDragStarted = this.isDragStarted;
|
const isDragStarted = this.isDragStarted;
|
||||||
|
|
||||||
// Clean up drag state first
|
|
||||||
this.cleanupDragState();
|
|
||||||
|
|
||||||
|
|
||||||
// Only emit drag:end if drag was actually started
|
// Only emit drag:end if drag was actually started
|
||||||
|
|
@ -292,7 +294,7 @@ export class DragDropManager {
|
||||||
const dropTarget = this.detectDropTarget(mousePosition);
|
const dropTarget = this.detectDropTarget(mousePosition);
|
||||||
|
|
||||||
console.log('🎯 DragDropManager: Emitting drag:end', {
|
console.log('🎯 DragDropManager: Emitting drag:end', {
|
||||||
draggedElement: draggedElement.dataset.eventId,
|
draggedElement: this.draggedElement.dataset.eventId,
|
||||||
finalColumn: positionData.column,
|
finalColumn: positionData.column,
|
||||||
finalY: positionData.snappedY,
|
finalY: positionData.snappedY,
|
||||||
dropTarget: dropTarget,
|
dropTarget: dropTarget,
|
||||||
|
|
@ -300,19 +302,20 @@ export class DragDropManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
const dragEndPayload: DragEndEventPayload = {
|
const dragEndPayload: DragEndEventPayload = {
|
||||||
draggedElement: draggedElement,
|
draggedElement: this.draggedElement,
|
||||||
|
draggedClone : this.draggedClone,
|
||||||
mousePosition,
|
mousePosition,
|
||||||
finalPosition: positionData,
|
finalPosition: positionData,
|
||||||
target: dropTarget
|
target: dropTarget
|
||||||
};
|
};
|
||||||
this.eventBus.emit('drag:end', dragEndPayload);
|
this.eventBus.emit('drag:end', dragEndPayload);
|
||||||
|
|
||||||
draggedElement.remove();
|
this.draggedElement.remove(); // 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.
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// This was just a click - emit click event instead
|
// This was just a click - emit click event instead
|
||||||
this.eventBus.emit('event:click', {
|
this.eventBus.emit('event:click', {
|
||||||
draggedElement: draggedElement,
|
draggedElement: this.draggedElement,
|
||||||
mousePosition: { x: event.clientX, y: event.clientY }
|
mousePosition: { x: event.clientX, y: event.clientY }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -540,13 +543,11 @@ export class DragDropManager {
|
||||||
* Detect drop target - whether dropped in swp-day-column or swp-day-header
|
* Detect drop target - whether dropped in swp-day-column or swp-day-header
|
||||||
*/
|
*/
|
||||||
private detectDropTarget(position: Position): 'swp-day-column' | 'swp-day-header' | null {
|
private detectDropTarget(position: Position): 'swp-day-column' | 'swp-day-header' | null {
|
||||||
const elementAtPosition = document.elementFromPoint(position.x, position.y);
|
|
||||||
if (!elementAtPosition) return null;
|
|
||||||
|
|
||||||
// Traverse up the DOM tree to find the target container
|
// Traverse up the DOM tree to find the target container
|
||||||
let currentElement = elementAtPosition as HTMLElement;
|
let currentElement = this.draggedClone;
|
||||||
while (currentElement && currentElement !== document.body) {
|
while (currentElement && currentElement !== document.body) {
|
||||||
if (currentElement.tagName === 'SWP-DAY-HEADER') {
|
if (currentElement.tagName === 'SWP-ALLDAY-CONTAINER') {
|
||||||
return 'swp-day-header';
|
return 'swp-day-header';
|
||||||
}
|
}
|
||||||
if (currentElement.tagName === 'SWP-DAY-COLUMN') {
|
if (currentElement.tagName === 'SWP-DAY-COLUMN') {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { CalendarEvent } from '../types/CalendarTypes';
|
||||||
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
|
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
|
||||||
|
import { EventLayout } from '../utils/AllDayLayoutEngine';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AllDayEventRenderer - Simple rendering of all-day events
|
* AllDayEventRenderer - Simple rendering of all-day events
|
||||||
|
|
@ -17,19 +18,19 @@ export class AllDayEventRenderer {
|
||||||
* Get or cache all-day container, create if it doesn't exist - SIMPLIFIED (no ghost columns)
|
* Get or cache all-day container, create if it doesn't exist - SIMPLIFIED (no ghost columns)
|
||||||
*/
|
*/
|
||||||
private getContainer(): HTMLElement | null {
|
private getContainer(): HTMLElement | null {
|
||||||
|
|
||||||
const header = document.querySelector('swp-calendar-header');
|
const header = document.querySelector('swp-calendar-header');
|
||||||
if (header) {
|
if (header) {
|
||||||
this.container = header.querySelector('swp-allday-container');
|
this.container = header.querySelector('swp-allday-container');
|
||||||
|
|
||||||
if (!this.container) {
|
if (!this.container) {
|
||||||
this.container = document.createElement('swp-allday-container');
|
this.container = document.createElement('swp-allday-container');
|
||||||
header.appendChild(this.container);
|
header.appendChild(this.container);
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return this.container;
|
}
|
||||||
|
return this.container;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// REMOVED: createGhostColumns() method - no longer needed!
|
// REMOVED: createGhostColumns() method - no longer needed!
|
||||||
|
|
@ -39,16 +40,15 @@ export class AllDayEventRenderer {
|
||||||
*/
|
*/
|
||||||
public renderAllDayEventWithLayout(
|
public renderAllDayEventWithLayout(
|
||||||
event: CalendarEvent,
|
event: CalendarEvent,
|
||||||
layout: { startColumn: number; endColumn: number; row: number; columnSpan: number }
|
layout: EventLayout
|
||||||
): HTMLElement | null {
|
) {
|
||||||
const container = this.getContainer();
|
const container = this.getContainer();
|
||||||
if (!container) return null;
|
if (!container) return null;
|
||||||
|
|
||||||
const allDayElement = SwpAllDayEventElement.fromCalendarEventWithLayout(event, layout);
|
let dayEvent = new SwpAllDayEventElement(event);
|
||||||
const element = allDayElement.getElement();
|
dayEvent.applyGridPositioning(layout);
|
||||||
|
|
||||||
container.appendChild(element);
|
container.appendChild(dayEvent.getElement());
|
||||||
return element;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -372,35 +372,11 @@ export class EventRenderingService {
|
||||||
// Pass current events to AllDayManager for state tracking
|
// Pass current events to AllDayManager for state tracking
|
||||||
this.allDayManager.setCurrentEvents(allDayEvents, weekDates);
|
this.allDayManager.setCurrentEvents(allDayEvents, weekDates);
|
||||||
|
|
||||||
// Calculate layout for ALL all-day events together using AllDayLayoutEngine
|
|
||||||
const layouts = this.allDayManager.calculateAllDayEventsLayout(allDayEvents, weekDates);
|
const layouts = this.allDayManager.calculateAllDayEventsLayout(allDayEvents, weekDates);
|
||||||
|
|
||||||
// Render each all-day event with pre-calculated layout
|
// Render each all-day event with pre-calculated layout
|
||||||
allDayEvents.forEach(event => {
|
layouts.forEach(layout => {
|
||||||
const layout = layouts.get(event.id);
|
this.allDayEventRenderer.renderAllDayEventWithLayout(layout.calenderEvent, layout);
|
||||||
if (!layout) {
|
|
||||||
console.warn('❌ EventRenderingService: No layout found for all-day event', {
|
|
||||||
id: event.id,
|
|
||||||
title: event.title
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Render with pre-calculated layout
|
|
||||||
const renderedElement = this.allDayEventRenderer.renderAllDayEventWithLayout(event, layout);
|
|
||||||
if (renderedElement) {
|
|
||||||
console.log('✅ EventRenderingService: Rendered all-day event with AllDayLayoutEngine', {
|
|
||||||
id: event.id,
|
|
||||||
title: event.title,
|
|
||||||
gridArea: layout.gridArea,
|
|
||||||
element: renderedElement.tagName
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.warn('❌ EventRenderingService: Failed to render all-day event', {
|
|
||||||
id: event.id,
|
|
||||||
title: event.title
|
|
||||||
});
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Check and adjust all-day container height after rendering
|
// Check and adjust all-day container height after rendering
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ export interface DragMoveEventPayload {
|
||||||
// Drag end event payload
|
// Drag end event payload
|
||||||
export interface DragEndEventPayload {
|
export interface DragEndEventPayload {
|
||||||
draggedElement: HTMLElement;
|
draggedElement: HTMLElement;
|
||||||
|
draggedClone: HTMLElement | null;
|
||||||
mousePosition: MousePosition;
|
mousePosition: MousePosition;
|
||||||
finalPosition: {
|
finalPosition: {
|
||||||
column: string | null;
|
column: string | null;
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { CalendarEvent } from '../types/CalendarTypes';
|
||||||
|
|
||||||
export interface EventLayout {
|
export interface EventLayout {
|
||||||
id: string;
|
calenderEvent: CalendarEvent;
|
||||||
gridArea: string; // "row-start / col-start / row-end / col-end"
|
gridArea: string; // "row-start / col-start / row-end / col-end"
|
||||||
startColumn: number;
|
startColumn: number;
|
||||||
endColumn: number;
|
endColumn: number;
|
||||||
|
|
@ -21,42 +21,38 @@ export class AllDayLayoutEngine {
|
||||||
/**
|
/**
|
||||||
* Calculate layout for all events using clean day-based logic
|
* Calculate layout for all events using clean day-based logic
|
||||||
*/
|
*/
|
||||||
public calculateLayout(events: CalendarEvent[]): Map<string, EventLayout> {
|
public calculateLayout(events: CalendarEvent[]): EventLayout[] {
|
||||||
const layouts = new Map<string, EventLayout>();
|
|
||||||
|
|
||||||
if (this.weekDates.length === 0) {
|
|
||||||
return layouts;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
let layouts: EventLayout[] = [];
|
||||||
// Reset tracks for new calculation
|
// Reset tracks for new calculation
|
||||||
this.tracks = [new Array(this.weekDates.length).fill(false)];
|
this.tracks = [new Array(this.weekDates.length).fill(false)];
|
||||||
|
|
||||||
// Filter to only visible events
|
// Filter to only visible events
|
||||||
const visibleEvents = events.filter(event => this.isEventVisible(event));
|
const visibleEvents = events.filter(event => this.isEventVisible(event));
|
||||||
|
|
||||||
// Process events in input order (no sorting)
|
// Process events in input order (no sorting)
|
||||||
for (const event of visibleEvents) {
|
for (const event of visibleEvents) {
|
||||||
const startDay = this.getEventStartDay(event);
|
const startDay = this.getEventStartDay(event);
|
||||||
const endDay = this.getEventEndDay(event);
|
const endDay = this.getEventEndDay(event);
|
||||||
|
|
||||||
if (startDay > 0 && endDay > 0) {
|
if (startDay > 0 && endDay > 0) {
|
||||||
const track = this.findAvailableTrack(startDay - 1, endDay - 1); // Convert to 0-based for tracks
|
const track = this.findAvailableTrack(startDay - 1, endDay - 1); // Convert to 0-based for tracks
|
||||||
|
|
||||||
// Mark days as occupied
|
// Mark days as occupied
|
||||||
for (let day = startDay - 1; day <= endDay - 1; day++) {
|
for (let day = startDay - 1; day <= endDay - 1; day++) {
|
||||||
this.tracks[track][day] = true;
|
this.tracks[track][day] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const layout: EventLayout = {
|
const layout: EventLayout = {
|
||||||
id: event.id,
|
calenderEvent: event,
|
||||||
gridArea: `${track + 1} / ${startDay} / ${track + 2} / ${endDay + 1}`,
|
gridArea: `${track + 1} / ${startDay} / ${track + 2} / ${endDay + 1}`,
|
||||||
startColumn: startDay,
|
startColumn: startDay,
|
||||||
endColumn: endDay,
|
endColumn: endDay,
|
||||||
row: track + 1,
|
row: track + 1,
|
||||||
columnSpan: endDay - startDay + 1
|
columnSpan: endDay - startDay + 1
|
||||||
};
|
};
|
||||||
|
layouts.push(layout);
|
||||||
layouts.set(event.id, layout);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -72,7 +68,7 @@ export class AllDayLayoutEngine {
|
||||||
return trackIndex;
|
return trackIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new track if none available
|
// Create new track if none available
|
||||||
this.tracks.push(new Array(this.weekDates.length).fill(false));
|
this.tracks.push(new Array(this.weekDates.length).fill(false));
|
||||||
return this.tracks.length - 1;
|
return this.tracks.length - 1;
|
||||||
|
|
@ -96,10 +92,10 @@ export class AllDayLayoutEngine {
|
||||||
private getEventStartDay(event: CalendarEvent): number {
|
private getEventStartDay(event: CalendarEvent): number {
|
||||||
const eventStartDate = this.formatDate(event.start);
|
const eventStartDate = this.formatDate(event.start);
|
||||||
const firstVisibleDate = this.weekDates[0];
|
const firstVisibleDate = this.weekDates[0];
|
||||||
|
|
||||||
// If event starts before visible range, clip to first visible day
|
// If event starts before visible range, clip to first visible day
|
||||||
const clippedStartDate = eventStartDate < firstVisibleDate ? firstVisibleDate : eventStartDate;
|
const clippedStartDate = eventStartDate < firstVisibleDate ? firstVisibleDate : eventStartDate;
|
||||||
|
|
||||||
const dayIndex = this.weekDates.indexOf(clippedStartDate);
|
const dayIndex = this.weekDates.indexOf(clippedStartDate);
|
||||||
return dayIndex >= 0 ? dayIndex + 1 : 0;
|
return dayIndex >= 0 ? dayIndex + 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
@ -110,10 +106,10 @@ export class AllDayLayoutEngine {
|
||||||
private getEventEndDay(event: CalendarEvent): number {
|
private getEventEndDay(event: CalendarEvent): number {
|
||||||
const eventEndDate = this.formatDate(event.end);
|
const eventEndDate = this.formatDate(event.end);
|
||||||
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
||||||
|
|
||||||
// If event ends after visible range, clip to last visible day
|
// If event ends after visible range, clip to last visible day
|
||||||
const clippedEndDate = eventEndDate > lastVisibleDate ? lastVisibleDate : eventEndDate;
|
const clippedEndDate = eventEndDate > lastVisibleDate ? lastVisibleDate : eventEndDate;
|
||||||
|
|
||||||
const dayIndex = this.weekDates.indexOf(clippedEndDate);
|
const dayIndex = this.weekDates.indexOf(clippedEndDate);
|
||||||
return dayIndex >= 0 ? dayIndex + 1 : 0;
|
return dayIndex >= 0 ? dayIndex + 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
@ -123,12 +119,12 @@ export class AllDayLayoutEngine {
|
||||||
*/
|
*/
|
||||||
private isEventVisible(event: CalendarEvent): boolean {
|
private isEventVisible(event: CalendarEvent): boolean {
|
||||||
if (this.weekDates.length === 0) return false;
|
if (this.weekDates.length === 0) return false;
|
||||||
|
|
||||||
const eventStartDate = this.formatDate(event.start);
|
const eventStartDate = this.formatDate(event.start);
|
||||||
const eventEndDate = this.formatDate(event.end);
|
const eventEndDate = this.formatDate(event.end);
|
||||||
const firstVisibleDate = this.weekDates[0];
|
const firstVisibleDate = this.weekDates[0];
|
||||||
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
||||||
|
|
||||||
// Event overlaps if it doesn't end before visible range starts
|
// Event overlaps if it doesn't end before visible range starts
|
||||||
// AND doesn't start after visible range ends
|
// AND doesn't start after visible range ends
|
||||||
return !(eventEndDate < firstVisibleDate || eventStartDate > lastVisibleDate);
|
return !(eventEndDate < firstVisibleDate || eventStartDate > lastVisibleDate);
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue