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 autoScrollAnimationId: number | null = null;
|
||||||
private scrollSpeed = 10; // pixels per frame
|
private scrollSpeed = 10; // pixels per frame
|
||||||
private scrollThreshold = 30; // pixels from edge
|
private scrollThreshold = 30; // pixels from edge
|
||||||
|
private currentMouseY = 0; // Track current mouse Y for scroll updates
|
||||||
|
|
||||||
// Konfiguration for snap interval
|
// Konfiguration for snap interval
|
||||||
private snapIntervalMinutes = 15; // 15 minutter
|
private snapIntervalMinutes = 15; // 15 minutter
|
||||||
|
|
@ -81,14 +82,23 @@ export class ColumnDetector {
|
||||||
eventBus.on('header:mouseover', (event) => {
|
eventBus.on('header:mouseover', (event) => {
|
||||||
const { dayHeader, headerRenderer } = (event as CustomEvent).detail;
|
const { dayHeader, headerRenderer } = (event as CustomEvent).detail;
|
||||||
if (this.isMouseDown && this.draggedClone) {
|
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);
|
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 {
|
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
|
// Hvis musen er holdt nede, tjek for snap interval vertikal bevægelse
|
||||||
if (this.isMouseDown) {
|
if (this.isMouseDown) {
|
||||||
const deltaY = Math.abs(event.clientY - this.lastLoggedPosition.y);
|
const deltaY = Math.abs(event.clientY - this.lastLoggedPosition.y);
|
||||||
|
|
@ -102,17 +112,26 @@ export class ColumnDetector {
|
||||||
});
|
});
|
||||||
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
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) {
|
if (this.draggedClone && this.draggedClone.parentElement) {
|
||||||
|
const columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
||||||
|
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 columnRect = this.draggedClone.parentElement.getBoundingClientRect();
|
||||||
const relativeY = event.clientY - columnRect.top - this.mouseOffset.y;
|
const relativeY = event.clientY - columnRect.top - this.mouseOffset.y;
|
||||||
this.draggedClone.style.top = relativeY + 'px';
|
this.draggedClone.style.top = relativeY + 'px';
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Auto-scroll detection når der er en aktiv clone
|
// Auto-scroll detection når der er en aktiv clone
|
||||||
if (this.draggedClone) {
|
if (this.draggedClone) {
|
||||||
console.log('ColumnDetector: Checking auto-scroll, mouse at:', event.clientY);
|
|
||||||
this.checkAutoScroll(event);
|
this.checkAutoScroll(event);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -287,18 +306,24 @@ export class ColumnDetector {
|
||||||
|
|
||||||
// Drop operationen: fade out original og remove clone suffix
|
// Drop operationen: fade out original og remove clone suffix
|
||||||
if (this.originalEvent && this.draggedClone) {
|
if (this.originalEvent && this.draggedClone) {
|
||||||
// Fade out og fjern originalen
|
// Check if clone was converted to all-day (is now in header)
|
||||||
this.fadeOutAndRemove(this.originalEvent);
|
const cloneInHeader = this.draggedClone.closest('swp-calendar-header');
|
||||||
|
|
||||||
// Fjern clone suffix fra klonen
|
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);
|
this.removeClonePrefix(this.draggedClone);
|
||||||
|
}
|
||||||
|
|
||||||
// Ryd op
|
// Ryd op
|
||||||
this.originalEvent = null;
|
this.originalEvent = null;
|
||||||
this.draggedClone = null;
|
this.draggedClone = null;
|
||||||
this.scrollContainer = null;
|
this.scrollContainer = null;
|
||||||
|
|
||||||
console.log('Drop completed: original faded out and removed, clone became permanent');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cleanup hvis ingen drop (ingen clone var aktiv)
|
// Cleanup hvis ingen drop (ingen clone var aktiv)
|
||||||
|
|
@ -343,14 +368,6 @@ export class ColumnDetector {
|
||||||
const distanceFromTop = mouseY - containerRect.top;
|
const distanceFromTop = mouseY - containerRect.top;
|
||||||
const distanceFromBottom = containerRect.bottom - mouseY;
|
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
|
// Check if we need to scroll up
|
||||||
if (distanceFromTop <= this.scrollThreshold && distanceFromTop > 0) {
|
if (distanceFromTop <= this.scrollThreshold && distanceFromTop > 0) {
|
||||||
|
|
@ -386,6 +403,13 @@ export class ColumnDetector {
|
||||||
const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed;
|
const scrollAmount = direction === 'up' ? -this.scrollSpeed : this.scrollSpeed;
|
||||||
this.scrollContainer.scrollTop += scrollAmount;
|
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
|
// Continue animation
|
||||||
this.autoScrollAnimationId = requestAnimationFrame(scroll);
|
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 {
|
public destroy(): void {
|
||||||
this.stopAutoScroll();
|
this.stopAutoScroll();
|
||||||
document.body.removeEventListener('mousemove', this.handleMouseMove.bind(this));
|
document.body.removeEventListener('mousemove', this.handleMouseMove.bind(this));
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue