Enables all-day event to timed event conversion

Introduces the ability to convert all-day events to timed events by dragging them out of the header.

Leverages a factory method to create timed events from all-day elements, ensuring proper data conversion and styling.

Improves user experience by allowing more flexible event scheduling.
This commit is contained in:
Janus Knudsen 2025-09-10 23:57:48 +02:00
parent e9298934c6
commit 163314353b
3 changed files with 72 additions and 39 deletions

View file

@ -115,6 +115,51 @@ export class SwpEventElement extends BaseEventElement {
public static fromCalendarEvent(event: CalendarEvent): SwpEventElement {
return new SwpEventElement(event);
}
/**
* Factory method to convert an all-day HTML element to a timed SwpEventElement
*/
public static fromAllDayElement(allDayElement: HTMLElement): SwpEventElement {
// Extract data from all-day element's dataset
const eventId = allDayElement.dataset.eventId || '';
const title = allDayElement.dataset.title || allDayElement.textContent || 'Untitled';
const type = allDayElement.dataset.type || 'work';
const startStr = allDayElement.dataset.start;
const endStr = allDayElement.dataset.end;
const durationStr = allDayElement.dataset.duration;
if (!startStr || !endStr) {
throw new Error('All-day element missing start/end dates');
}
// Parse dates and set reasonable 1-hour duration for timed event
const originalStart = new Date(startStr);
const duration = durationStr ? parseInt(durationStr) : 60; // Default 1 hour
// For conversion, use current time or a reasonable default (9 AM)
const now = new Date();
const startDate = new Date(originalStart);
startDate.setHours(now.getHours() || 9, now.getMinutes() || 0, 0, 0);
const endDate = new Date(startDate);
endDate.setMinutes(endDate.getMinutes() + duration);
// Create CalendarEvent object
const calendarEvent: CalendarEvent = {
id: eventId,
title: title,
start: startDate,
end: endDate,
type: type,
allDay: false,
syncStatus: 'synced',
metadata: {
duration: duration.toString()
}
};
return new SwpEventElement(calendarEvent);
}
}
/**

View file

@ -123,26 +123,13 @@ export class DragDropManager {
// Listen for header mouseleave events (for all-day to timed conversion when leaving header)
this.eventBus.on('header:mouseleave', (event) => {
// Check if we're dragging an all-day event
if ((event as any).buttons === 1 && this.draggedEventId && this.isAllDayEventBeingDragged()) {
// Get current mouse position to determine target column and Y
const currentColumn = this.detectColumn(this.lastMousePosition.x, this.lastMousePosition.y);
if (currentColumn) {
// Calculate Y position relative to the column
const columnElement = this.getCachedColumnElement(currentColumn);
if (columnElement) {
const columnRect = columnElement.getBoundingClientRect();
const targetY = this.lastMousePosition.y - columnRect.top - this.mouseOffset.y;
// Emit event to convert to timed
this.eventBus.emit('drag:convert-to-timed', {
if (this.draggedEventId && this.isAllDayEventBeingDragged()) {
// Convert all-day event to timed event using SwpEventElement factory
this.eventBus.emit('drag:convert-allday-to-timed', {
eventId: this.draggedEventId,
targetColumn: currentColumn,
targetY: Math.max(0, targetY)
originalElement: this.originalElement
});
}
}
}
});
}

View file

@ -147,10 +147,10 @@ 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 all-day to timed conversion (when leaving header)
eventBus.on('drag:convert-allday-to-timed', (event) => {
const { eventId, originalElement } = (event as CustomEvent).detail;
this.handleConvertAllDayToTimed(eventId, originalElement);
});
// Handle navigation period change (when slide animation completes)
@ -780,30 +780,31 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
}
/**
* Handle simple all-day duration adjustment (when leaving header)
* Handle all-day to timed conversion using SwpEventElement factory
*/
private handleAdjustAllDayDuration(eventId: string, durationMinutes: number): void {
private handleConvertAllDayToTimed(eventId: string, originalElement: HTMLElement): void {
if (!this.draggedClone) return;
// Only adjust if it's an all-day event
// Only convert 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();
// Use SwpEventElement factory to create a proper timed event
const swpTimedEvent = SwpEventElement.fromAllDayElement(this.draggedClone);
const newTimedElement = swpTimedEvent.getElement();
// 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`;
// Apply drag styling to the new element
this.applyDragStyling(newTimedElement);
// 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";
// Get parent container
const parent = this.draggedClone.parentElement;
// Apply basic timed event positioning
this.applyDragStyling(this.draggedClone);
// Replace the all-day clone with the new timed event element
if (parent) {
parent.replaceChild(newTimedElement, this.draggedClone);
}
// Update our reference to the new element
this.draggedClone = newTimedElement;
}
/**