183 lines
No EOL
8.5 KiB
JavaScript
183 lines
No EOL
8.5 KiB
JavaScript
/**
|
|
* AllDayDragService - Manages drag and drop operations for all-day events
|
|
*
|
|
* STATELESS SERVICE - Reads all data from DOM via AllDayDomReader
|
|
* - No persistent state
|
|
* - Handles timed → all-day conversion
|
|
* - Handles all-day → all-day repositioning
|
|
* - Handles column changes during drag
|
|
* - Calculates layouts on-demand from DOM
|
|
*/
|
|
import { SwpAllDayEventElement } from '../../elements/SwpEventElement';
|
|
import { AllDayLayoutEngine } from '../../utils/AllDayLayoutEngine';
|
|
import { ColumnDetectionUtils } from '../../utils/ColumnDetectionUtils';
|
|
import { ALL_DAY_CONSTANTS } from '../../configurations/CalendarConfig';
|
|
import { AllDayDomReader } from './AllDayDomReader';
|
|
export class AllDayDragService {
|
|
constructor(eventManager, allDayEventRenderer, dateService) {
|
|
this.eventManager = eventManager;
|
|
this.allDayEventRenderer = allDayEventRenderer;
|
|
this.dateService = dateService;
|
|
}
|
|
/**
|
|
* Handle conversion from timed event to all-day event
|
|
* Called when dragging a timed event into the header
|
|
*/
|
|
handleConvertToAllDay(payload) {
|
|
const allDayContainer = AllDayDomReader.getAllDayContainer();
|
|
if (!allDayContainer)
|
|
return;
|
|
// Create SwpAllDayEventElement from ICalendarEvent
|
|
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
|
|
// Apply grid positioning
|
|
allDayElement.style.gridRow = '1';
|
|
allDayElement.style.gridColumn = payload.targetColumn.index.toString();
|
|
// Remove old swp-event clone
|
|
payload.draggedClone.remove();
|
|
// Call delegate to update DragDropManager's draggedClone reference
|
|
payload.replaceClone(allDayElement);
|
|
// Append to container
|
|
allDayContainer.appendChild(allDayElement);
|
|
ColumnDetectionUtils.updateColumnBoundsCache();
|
|
}
|
|
/**
|
|
* Handle column change during drag of all-day event
|
|
* Updates grid position while maintaining event span
|
|
*/
|
|
handleColumnChange(payload) {
|
|
const allDayContainer = AllDayDomReader.getAllDayContainer();
|
|
if (!allDayContainer)
|
|
return;
|
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(payload.mousePosition);
|
|
if (!targetColumn || !payload.draggedClone)
|
|
return;
|
|
// Calculate event span from original grid positioning
|
|
const { start: gridColumnStart, end: gridColumnEnd } = AllDayDomReader.getGridColumnRange(payload.draggedClone);
|
|
const span = gridColumnEnd - gridColumnStart;
|
|
// Update clone position maintaining the span
|
|
const newStartColumn = targetColumn.index;
|
|
const newEndColumn = newStartColumn + span;
|
|
payload.draggedClone.style.gridColumn = `${newStartColumn} / ${newEndColumn}`;
|
|
}
|
|
/**
|
|
* Handle drag end for all-day → all-day drops
|
|
* Recalculates layouts and updates event positions
|
|
*/
|
|
async handleDragEnd(dragEndEvent) {
|
|
if (!dragEndEvent.draggedClone)
|
|
return;
|
|
// Normalize clone ID
|
|
dragEndEvent.draggedClone.dataset.eventId = dragEndEvent.draggedClone.dataset.eventId?.replace('clone-', '');
|
|
dragEndEvent.draggedClone.style.pointerEvents = ''; // Re-enable pointer events
|
|
dragEndEvent.originalElement.dataset.eventId += '_';
|
|
const eventId = dragEndEvent.draggedClone.dataset.eventId;
|
|
const eventDate = dragEndEvent.finalPosition.column?.date;
|
|
const eventType = dragEndEvent.draggedClone.dataset.type;
|
|
if (!eventDate || !eventId || !eventType)
|
|
return;
|
|
// Get original dates to preserve time
|
|
const originalStartDate = new Date(dragEndEvent.draggedClone.dataset.start);
|
|
const originalEndDate = new Date(dragEndEvent.draggedClone.dataset.end);
|
|
// Calculate actual duration in milliseconds (preserves hours/minutes/seconds)
|
|
const durationMs = originalEndDate.getTime() - originalStartDate.getTime();
|
|
// Create new start date with the new day but preserve original time
|
|
const newStartDate = new Date(eventDate);
|
|
newStartDate.setHours(originalStartDate.getHours(), originalStartDate.getMinutes(), originalStartDate.getSeconds(), originalStartDate.getMilliseconds());
|
|
// Create new end date by adding duration in milliseconds
|
|
const newEndDate = new Date(newStartDate.getTime() + durationMs);
|
|
// Update data attributes with new dates (convert to UTC)
|
|
dragEndEvent.draggedClone.dataset.start = this.dateService.toUTC(newStartDate);
|
|
dragEndEvent.draggedClone.dataset.end = this.dateService.toUTC(newEndDate);
|
|
const droppedEvent = {
|
|
id: eventId,
|
|
title: dragEndEvent.draggedClone.dataset.title || '',
|
|
start: newStartDate,
|
|
end: newEndDate,
|
|
type: eventType,
|
|
allDay: true,
|
|
syncStatus: 'synced'
|
|
};
|
|
// Get all events from DOM and recalculate layouts
|
|
const allEventsFromDOM = AllDayDomReader.getEventsAsData();
|
|
const weekDates = ColumnDetectionUtils.getColumns();
|
|
// Replace old event with dropped event
|
|
const updatedEvents = [
|
|
...allEventsFromDOM.filter(event => event.id !== eventId),
|
|
droppedEvent
|
|
];
|
|
// Calculate new layouts for ALL events
|
|
const newLayouts = this.calculateLayouts(updatedEvents, weekDates);
|
|
// Apply layout updates to DOM
|
|
this.applyLayoutUpdates(newLayouts);
|
|
// Clean up drag styles from the dropped clone
|
|
dragEndEvent.draggedClone.classList.remove('dragging');
|
|
dragEndEvent.draggedClone.style.zIndex = '';
|
|
dragEndEvent.draggedClone.style.cursor = '';
|
|
dragEndEvent.draggedClone.style.opacity = '';
|
|
// Apply highlight class to show the dropped event with highlight color
|
|
dragEndEvent.draggedClone.classList.add('highlight');
|
|
// Update event in repository to mark as allDay=true
|
|
await this.eventManager.updateEvent(eventId, {
|
|
start: newStartDate,
|
|
end: newEndDate,
|
|
allDay: true
|
|
});
|
|
this.fadeOutAndRemove(dragEndEvent.originalElement);
|
|
}
|
|
/**
|
|
* Calculate layouts for events using AllDayLayoutEngine
|
|
*/
|
|
calculateLayouts(events, weekDates) {
|
|
const layoutEngine = new AllDayLayoutEngine(weekDates.map(column => column.date));
|
|
return layoutEngine.calculateLayout(events);
|
|
}
|
|
/**
|
|
* Apply layout updates to DOM elements
|
|
* Only updates elements that have changed position
|
|
* Public so AllDayCoordinator can use it for full recalculation
|
|
*/
|
|
applyLayoutUpdates(newLayouts) {
|
|
const container = AllDayDomReader.getAllDayContainer();
|
|
if (!container)
|
|
return;
|
|
// Read current layouts from DOM
|
|
const currentLayoutsMap = AllDayDomReader.getCurrentLayouts();
|
|
newLayouts.forEach((layout) => {
|
|
const currentLayout = currentLayoutsMap.get(layout.calenderEvent.id);
|
|
// Only update if layout changed
|
|
if (currentLayout?.gridArea !== layout.gridArea) {
|
|
const element = container.querySelector(`[data-event-id="${layout.calenderEvent.id}"]`);
|
|
if (element) {
|
|
element.classList.add('transitioning');
|
|
element.style.gridArea = layout.gridArea;
|
|
element.style.gridRow = layout.row.toString();
|
|
element.style.gridColumn = `${layout.startColumn} / ${layout.endColumn + 1}`;
|
|
// Update overflow classes based on row
|
|
element.classList.remove('max-event-overflow-hide', 'max-event-overflow-show');
|
|
if (layout.row > ALL_DAY_CONSTANTS.MAX_COLLAPSED_ROWS) {
|
|
const isExpanded = AllDayDomReader.isExpanded();
|
|
if (isExpanded) {
|
|
element.classList.add('max-event-overflow-show');
|
|
}
|
|
else {
|
|
element.classList.add('max-event-overflow-hide');
|
|
}
|
|
}
|
|
// Remove transition class after animation
|
|
setTimeout(() => element.classList.remove('transitioning'), 200);
|
|
}
|
|
}
|
|
});
|
|
}
|
|
/**
|
|
* Fade out and remove element
|
|
*/
|
|
fadeOutAndRemove(element) {
|
|
element.style.transition = 'opacity 0.3s ease-out';
|
|
element.style.opacity = '0';
|
|
setTimeout(() => {
|
|
element.remove();
|
|
}, 300);
|
|
}
|
|
}
|
|
//# sourceMappingURL=AllDayDragService.js.map
|