Improves drag and drop with snap and autoscroll
Enhances the drag and drop functionality by snapping the dragged element to a grid and ensuring continuous updates during auto-scroll. The changes include tracking mouse position for consistent clone positioning, snapping to 15-minute intervals, and updating clone position during auto-scroll. Also, removes unnecessary logging.
This commit is contained in:
parent
fc354ad618
commit
6ede297bb5
1 changed files with 151 additions and 19 deletions
|
|
@ -19,6 +19,7 @@ export class ColumnDetector {
|
|||
private autoScrollAnimationId: number | null = null;
|
||||
private scrollSpeed = 10; // pixels per frame
|
||||
private scrollThreshold = 30; // pixels from edge
|
||||
private currentMouseY = 0; // Track current mouse Y for scroll updates
|
||||
|
||||
// Konfiguration for snap interval
|
||||
private snapIntervalMinutes = 15; // 15 minutter
|
||||
|
|
@ -81,14 +82,23 @@ export class ColumnDetector {
|
|||
eventBus.on('header:mouseover', (event) => {
|
||||
const { dayHeader, headerRenderer } = (event as CustomEvent).detail;
|
||||
if (this.isMouseDown && this.draggedClone) {
|
||||
console.log('Dragging clone over header - calling addToAllDay');
|
||||
console.log('Dragging clone over header - expanding and converting to all-day');
|
||||
headerRenderer.addToAllDay(dayHeader);
|
||||
|
||||
// Convert clone to all-day format immediately
|
||||
const targetDate = dayHeader.dataset.date;
|
||||
if (targetDate) {
|
||||
this.convertToAllDayPreview(targetDate);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private handleMouseMove(event: MouseEvent): void {
|
||||
// Track current mouse position for auto-scroll updates
|
||||
this.currentMouseY = event.clientY;
|
||||
|
||||
// Hvis musen er holdt nede, tjek for snap interval vertikal bevægelse
|
||||
if (this.isMouseDown) {
|
||||
const deltaY = Math.abs(event.clientY - this.lastLoggedPosition.y);
|
||||
|
|
@ -102,17 +112,26 @@ export class ColumnDetector {
|
|||
});
|
||||
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
||||
|
||||
// Opdater klonens Y-position ved snap interval (relativt til kolonne)
|
||||
// Snap klonens position til nærmeste 15-min interval
|
||||
if (this.draggedClone && this.draggedClone.parentElement) {
|
||||
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
||||
const relativeY = event.clientY - columnRect.top - this.mouseOffset.y;
|
||||
this.draggedClone.style.top = relativeY + 'px';
|
||||
const rawRelativeY = event.clientY - columnRect.top - this.mouseOffset.y;
|
||||
|
||||
// Snap til nærmeste 15-min grid
|
||||
const snappedY = Math.round(rawRelativeY / this.snapDistancePx) * this.snapDistancePx;
|
||||
this.draggedClone.style.top = snappedY + 'px';
|
||||
}
|
||||
}
|
||||
|
||||
// Kontinuerlig opdatering under auto-scroll for at sikre klonen følger musen
|
||||
if (this.draggedClone && this.draggedClone.parentElement && this.autoScrollAnimationId !== null) {
|
||||
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
||||
const relativeY = event.clientY - columnRect.top - this.mouseOffset.y;
|
||||
this.draggedClone.style.top = relativeY + 'px';
|
||||
}
|
||||
|
||||
// Auto-scroll detection når der er en aktiv clone
|
||||
if (this.draggedClone) {
|
||||
console.log('ColumnDetector: Checking auto-scroll, mouse at:', event.clientY);
|
||||
this.checkAutoScroll(event);
|
||||
}
|
||||
}
|
||||
|
|
@ -287,18 +306,24 @@ export class ColumnDetector {
|
|||
|
||||
// Drop operationen: fade out original og remove clone suffix
|
||||
if (this.originalEvent && this.draggedClone) {
|
||||
// Fade out og fjern originalen
|
||||
this.fadeOutAndRemove(this.originalEvent);
|
||||
// Check if clone was converted to all-day (is now in header)
|
||||
const cloneInHeader = this.draggedClone.closest('swp-calendar-header');
|
||||
|
||||
// Fjern clone suffix fra klonen
|
||||
this.removeClonePrefix(this.draggedClone);
|
||||
if (cloneInHeader) {
|
||||
console.log('Drop completed: all-day event created');
|
||||
// Clone is now an all-day event, just fade out original
|
||||
this.fadeOutAndRemove(this.originalEvent);
|
||||
} else {
|
||||
console.log('Drop in regular area - keeping as timed event');
|
||||
// Normal drop: fade out original and keep clone as timed
|
||||
this.fadeOutAndRemove(this.originalEvent);
|
||||
this.removeClonePrefix(this.draggedClone);
|
||||
}
|
||||
|
||||
// Ryd op
|
||||
this.originalEvent = null;
|
||||
this.draggedClone = null;
|
||||
this.scrollContainer = null;
|
||||
|
||||
console.log('Drop completed: original faded out and removed, clone became permanent');
|
||||
}
|
||||
|
||||
// Cleanup hvis ingen drop (ingen clone var aktiv)
|
||||
|
|
@ -343,14 +368,6 @@ export class ColumnDetector {
|
|||
const distanceFromTop = mouseY - containerRect.top;
|
||||
const distanceFromBottom = containerRect.bottom - mouseY;
|
||||
|
||||
console.log('ColumnDetector: Auto-scroll check:', {
|
||||
mouseY,
|
||||
containerTop: containerRect.top,
|
||||
containerBottom: containerRect.bottom,
|
||||
distanceFromTop: Math.round(distanceFromTop),
|
||||
distanceFromBottom: Math.round(distanceFromBottom),
|
||||
threshold: this.scrollThreshold
|
||||
});
|
||||
|
||||
// Check if we need to scroll up
|
||||
if (distanceFromTop <= this.scrollThreshold && distanceFromTop > 0) {
|
||||
|
|
@ -386,6 +403,13 @@ export class ColumnDetector {
|
|||
const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed;
|
||||
this.scrollContainer.scrollTop += scrollAmount;
|
||||
|
||||
// Update clone position based on current mouse position after scroll
|
||||
if (this.draggedClone && this.draggedClone.parentElement) {
|
||||
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
||||
const relativeY = this.currentMouseY - columnRect.top - this.mouseOffset.y;
|
||||
this.draggedClone.style.top = relativeY + 'px';
|
||||
}
|
||||
|
||||
// Continue animation
|
||||
this.autoScrollAnimationId = requestAnimationFrame(scroll);
|
||||
};
|
||||
|
|
@ -403,6 +427,114 @@ export class ColumnDetector {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert dragged clone to all-day event preview
|
||||
*/
|
||||
private convertToAllDayPreview(targetDate: string): void {
|
||||
if (!this.draggedClone) return;
|
||||
|
||||
// Only convert once
|
||||
if (this.draggedClone.tagName === 'SWP-ALLDAY-EVENT') {
|
||||
return;
|
||||
}
|
||||
|
||||
// Transform clone to all-day format
|
||||
this.transformCloneToAllDay(this.draggedClone, targetDate);
|
||||
|
||||
// No need to recalculate height - addToAllDay already handles this
|
||||
console.log(`Converted clone to all-day preview for date: ${targetDate}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform clone from timed event to all-day event format
|
||||
*/
|
||||
private transformCloneToAllDay(clone: HTMLElement, targetDate: string): void {
|
||||
console.log('transformCloneToAllDay called with:', { clone, targetDate });
|
||||
|
||||
const calendarHeader = document.querySelector('swp-calendar-header');
|
||||
if (!calendarHeader) {
|
||||
console.error('No calendar header found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find or create all-day container for target date
|
||||
const container = this.findOrCreateAllDayContainer(calendarHeader as HTMLElement, targetDate);
|
||||
if (!container) {
|
||||
console.error('No container found/created');
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract title from original clone (remove time info)
|
||||
const titleElement = clone.querySelector('swp-event-title');
|
||||
const eventTitle = titleElement ? titleElement.textContent || 'Untitled Event' : 'Untitled Event';
|
||||
|
||||
console.log('Creating all-day event with title:', eventTitle);
|
||||
|
||||
// Create new all-day event element
|
||||
const allDayEvent = document.createElement('swp-allday-event');
|
||||
allDayEvent.setAttribute('data-event-id', clone.dataset.eventId || '');
|
||||
allDayEvent.setAttribute('data-type', clone.dataset.type || 'work');
|
||||
allDayEvent.textContent = eventTitle;
|
||||
|
||||
console.log('All-day event created:', allDayEvent);
|
||||
|
||||
// Remove the original clone from its current parent
|
||||
if (clone.parentElement) {
|
||||
console.log('Removing original clone from parent:', clone.parentElement);
|
||||
clone.parentElement.removeChild(clone);
|
||||
}
|
||||
|
||||
// Add new all-day event to container
|
||||
container.appendChild(allDayEvent);
|
||||
console.log('All-day event added to container:', container);
|
||||
|
||||
// Update reference to point to new element
|
||||
this.draggedClone = allDayEvent;
|
||||
|
||||
console.log(`Transformed clone to all-day event in container for date: ${targetDate}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Find existing or create new all-day container for specific date
|
||||
*/
|
||||
private findOrCreateAllDayContainer(calendarHeader: HTMLElement, targetDate: string): HTMLElement | null {
|
||||
// Find day headers to determine column index
|
||||
const dayHeaders = calendarHeader.querySelectorAll('swp-day-header');
|
||||
let columnIndex = -1;
|
||||
|
||||
for (let i = 0; i < dayHeaders.length; i++) {
|
||||
const dayHeader = dayHeaders[i] as HTMLElement;
|
||||
if (dayHeader.dataset.date === targetDate) {
|
||||
columnIndex = i + 1; // 1-based grid index
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (columnIndex === -1) {
|
||||
console.error(`Could not find column for date: ${targetDate}`);
|
||||
return null;
|
||||
}
|
||||
|
||||
// Look for existing container for this single column
|
||||
const containerKey = `${columnIndex}-1`; // single column span
|
||||
let container = calendarHeader.querySelector(`swp-allday-container[data-container-key="${containerKey}"]`);
|
||||
|
||||
if (!container) {
|
||||
// Create new container
|
||||
container = document.createElement('swp-allday-container');
|
||||
container.setAttribute('data-container-key', containerKey);
|
||||
container.setAttribute('data-date', targetDate);
|
||||
(container as HTMLElement).style.gridColumn = `${columnIndex}`;
|
||||
(container as HTMLElement).style.gridRow = '2'; // All-day row
|
||||
calendarHeader.appendChild(container);
|
||||
|
||||
console.log(`Created new all-day container for column ${columnIndex}, date: ${targetDate}`);
|
||||
}
|
||||
|
||||
return container as HTMLElement;
|
||||
}
|
||||
|
||||
|
||||
public destroy(): void {
|
||||
this.stopAutoScroll();
|
||||
document.body.removeEventListener('mousemove', this.handleMouseMove.bind(this));
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue