Enhances the drag and drop experience for all-day events by expanding the header to display the all-day row when dragging an event over it. Introduces constants for all-day event layout.
315 lines
No EOL
11 KiB
TypeScript
315 lines
No EOL
11 KiB
TypeScript
/**
|
|
* ColumnDetector - Bare detect hvilken kolonne musen er over
|
|
*/
|
|
|
|
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
|
import { eventBus } from '../core/EventBus';
|
|
|
|
export class ColumnDetector {
|
|
private currentColumn: string | null = null;
|
|
private isMouseDown = false;
|
|
private lastMousePosition = { x: 0, y: 0 };
|
|
private lastLoggedPosition = { x: 0, y: 0 };
|
|
private draggedClone: HTMLElement | null = null;
|
|
private originalEvent: HTMLElement | null = null;
|
|
private mouseOffset = { x: 0, y: 0 };
|
|
|
|
// Konfiguration for snap interval
|
|
private snapIntervalMinutes = 15; // 15 minutter
|
|
private hourHeightPx = 60; // Fra CSS --hour-height
|
|
private get snapDistancePx(): number {
|
|
return (this.snapIntervalMinutes / 60) * this.hourHeightPx; // 15/60 * 60 = 15px
|
|
}
|
|
|
|
constructor() {
|
|
this.init();
|
|
}
|
|
|
|
/**
|
|
* Konfigurer snap interval
|
|
*/
|
|
public setSnapInterval(minutes: number): void {
|
|
this.snapIntervalMinutes = minutes;
|
|
console.log(`Snap interval set to ${minutes} minutes (${this.snapDistancePx}px)`);
|
|
}
|
|
|
|
/**
|
|
* Fade out og fjern element fra DOM
|
|
*/
|
|
private fadeOutAndRemove(element: HTMLElement): void {
|
|
element.style.transition = 'opacity 0.3s ease-out';
|
|
element.style.opacity = '0';
|
|
|
|
setTimeout(() => {
|
|
element.remove();
|
|
}, 300);
|
|
}
|
|
|
|
/**
|
|
* Fjern "clone-" prefix fra event ID og gendan pointer events
|
|
*/
|
|
private removeClonePrefix(clone: HTMLElement): void {
|
|
const cloneId = clone.dataset.eventId;
|
|
if (cloneId && cloneId.startsWith('clone-')) {
|
|
const originalId = cloneId.replace('clone-', '');
|
|
clone.dataset.eventId = originalId;
|
|
console.log(`Removed clone prefix: ${cloneId} -> ${originalId}`);
|
|
}
|
|
|
|
// Gendan pointer events så klonen kan dragges igen
|
|
clone.style.pointerEvents = '';
|
|
}
|
|
|
|
private init(): void {
|
|
// Lyt til mouse move på hele body
|
|
document.body.addEventListener('mousemove', this.handleMouseMove.bind(this));
|
|
|
|
// Lyt til click på hele body
|
|
document.body.addEventListener('click', this.handleClick.bind(this));
|
|
|
|
// Lyt til mouse down og up
|
|
document.body.addEventListener('mousedown', this.handleMouseDown.bind(this));
|
|
document.body.addEventListener('mouseup', this.handleMouseUp.bind(this));
|
|
|
|
// Listen for header mouseover events
|
|
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');
|
|
headerRenderer.addToAllDay(dayHeader);
|
|
}
|
|
});
|
|
}
|
|
|
|
|
|
private handleMouseMove(event: MouseEvent): void {
|
|
// Hvis musen er holdt nede, tjek for snap interval vertikal bevægelse
|
|
if (this.isMouseDown) {
|
|
const deltaY = Math.abs(event.clientY - this.lastLoggedPosition.y);
|
|
|
|
if (deltaY >= this.snapDistancePx) {
|
|
console.log(`Mouse dragged ${this.snapIntervalMinutes} minutes (${this.snapDistancePx}px) vertically:`, {
|
|
from: this.lastLoggedPosition,
|
|
to: { x: event.clientX, y: event.clientY },
|
|
verticalDistance: Math.round(deltaY),
|
|
snapInterval: `${this.snapIntervalMinutes} minutes`
|
|
});
|
|
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
|
|
|
// Opdater klonens Y-position ved snap interval (relativt til kolonne)
|
|
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';
|
|
}
|
|
}
|
|
}
|
|
|
|
// Find hvilket element musen er over (altid)
|
|
{
|
|
const elementUnder = document.elementFromPoint(event.clientX, event.clientY);
|
|
if (!elementUnder) {
|
|
// Ingen element under musen
|
|
if (this.currentColumn !== null) {
|
|
console.log('Left all columns');
|
|
this.currentColumn = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Gå op gennem DOM træet for at finde swp-day-column
|
|
let element = elementUnder as HTMLElement;
|
|
while (element && element.tagName !== 'SWP-DAY-COLUMN') {
|
|
element = element.parentElement as HTMLElement;
|
|
if (!element) {
|
|
// Ikke i en kolonne
|
|
if (this.currentColumn !== null) {
|
|
console.log('Left all columns');
|
|
this.currentColumn = null;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Vi fandt en kolonne
|
|
const date = element.dataset.date;
|
|
if (date && date !== this.currentColumn) {
|
|
console.log('Entered column:', date);
|
|
this.currentColumn = date;
|
|
|
|
// Flyt klonen til ny kolonne ved kolonneskift
|
|
if (this.draggedClone && this.isMouseDown) {
|
|
// Flyt klonen til den nye kolonne
|
|
const newColumnElement = document.querySelector(`swp-day-column[data-date="${date}"]`);
|
|
if (newColumnElement) {
|
|
newColumnElement.appendChild(this.draggedClone);
|
|
// Opdater Y-position relativt til den nye kolonne
|
|
const columnRect = newColumnElement.getBoundingClientRect();
|
|
const relativeY = event.clientY - columnRect.top - this.mouseOffset.y;
|
|
this.draggedClone.style.top = relativeY + 'px';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private handleClick(event: MouseEvent): void {
|
|
const target = event.target as HTMLElement;
|
|
|
|
// Find event element
|
|
let eventElement = target;
|
|
while (eventElement && eventElement.tagName !== 'SWP-EVENTS-LAYER') {
|
|
if (eventElement.tagName === 'SWP-EVENT' || eventElement.tagName === 'SWP-ALLDAY-EVENT') {
|
|
break;
|
|
}
|
|
eventElement = eventElement.parentElement as HTMLElement;
|
|
if (!eventElement) return;
|
|
}
|
|
|
|
// Hvis vi nåede til SWP-EVENTS-LAYER uden at finde et event, så return
|
|
if (!eventElement || eventElement.tagName === 'SWP-EVENTS-LAYER') {
|
|
return;
|
|
}
|
|
|
|
// Log event info
|
|
const eventId = eventElement.dataset.eventId;
|
|
const eventType = eventElement.dataset.type;
|
|
console.log('Clicked event:', {
|
|
id: eventId,
|
|
type: eventType,
|
|
element: eventElement,
|
|
title: eventElement.textContent
|
|
});
|
|
}
|
|
|
|
private handleMouseDown(event: MouseEvent): void {
|
|
this.isMouseDown = true;
|
|
this.lastMousePosition = { x: event.clientX, y: event.clientY };
|
|
this.lastLoggedPosition = { x: event.clientX, y: event.clientY };
|
|
console.log('Mouse down at:', this.lastMousePosition);
|
|
|
|
// Tjek om mousedown er på et event
|
|
const target = event.target as HTMLElement;
|
|
let eventElement = target;
|
|
while (eventElement && eventElement.tagName !== 'SWP-EVENTS-LAYER') {
|
|
if (eventElement.tagName === 'SWP-EVENT' || eventElement.tagName === 'SWP-ALLDAY-EVENT') {
|
|
break;
|
|
}
|
|
eventElement = eventElement.parentElement as HTMLElement;
|
|
if (!eventElement) return;
|
|
}
|
|
|
|
// Hvis vi nåede til SWP-EVENTS-LAYER uden at finde et event, så return
|
|
if (!eventElement || eventElement.tagName === 'SWP-EVENTS-LAYER') {
|
|
return;
|
|
}
|
|
|
|
// Hvis vi fandt et event, lav en clone
|
|
if (eventElement) {
|
|
// Gem reference til original event
|
|
this.originalEvent = eventElement;
|
|
this.cloneEvent(eventElement, event);
|
|
// Sæt originalen til gennemsigtig og forhindre text selection mens der trækkes
|
|
eventElement.style.opacity = '0.6';
|
|
eventElement.style.userSelect = 'none';
|
|
}
|
|
}
|
|
|
|
private cloneEvent(originalEvent: HTMLElement, mouseEvent: MouseEvent): void {
|
|
// Lav en clone
|
|
const clone = originalEvent.cloneNode(true) as HTMLElement;
|
|
|
|
// Præfiks ID med "clone-"
|
|
const originalId = originalEvent.dataset.eventId;
|
|
if (originalId) {
|
|
clone.dataset.eventId = `clone-${originalId}`;
|
|
}
|
|
|
|
// Beregn hvor på event'et musen klikkede
|
|
const eventRect = originalEvent.getBoundingClientRect();
|
|
this.mouseOffset = {
|
|
x: mouseEvent.clientX - eventRect.left, // Stadig nødvendig for cursor placering
|
|
y: mouseEvent.clientY - eventRect.top
|
|
};
|
|
|
|
// Gør klonen ready til at blive trukket
|
|
clone.style.position = 'absolute';
|
|
clone.style.zIndex = '999999';
|
|
clone.style.pointerEvents = 'none';
|
|
|
|
// Sæt størrelse fra det originale event
|
|
clone.style.width = eventRect.width + 'px';
|
|
clone.style.height = eventRect.height + 'px';
|
|
|
|
// Find den aktuelle kolonne og placer klonen der
|
|
const currentColumnElement = document.querySelector(`swp-day-column[data-date="${this.currentColumn}"]`);
|
|
if (currentColumnElement) {
|
|
// Sæt initial position relativt til kolonnen
|
|
const columnRect = currentColumnElement.getBoundingClientRect();
|
|
const relativeY = mouseEvent.clientY - columnRect.top - this.mouseOffset.y;
|
|
clone.style.top = relativeY + 'px';
|
|
|
|
currentColumnElement.appendChild(clone);
|
|
} else {
|
|
console.error('Could not find current column element:', this.currentColumn);
|
|
// Fallback til original placering
|
|
originalEvent.parentNode?.insertBefore(clone, originalEvent.nextSibling);
|
|
}
|
|
|
|
// Gem reference til klonen
|
|
this.draggedClone = clone;
|
|
|
|
console.log('Cloned event:', {
|
|
original: originalId,
|
|
clone: clone.dataset.eventId,
|
|
offset: this.mouseOffset
|
|
});
|
|
}
|
|
|
|
private handleMouseUp(event: MouseEvent): void {
|
|
this.isMouseDown = false;
|
|
console.log('Mouse up at:', { x: event.clientX, y: event.clientY });
|
|
|
|
// Drop operationen: fade out original og remove clone suffix
|
|
if (this.originalEvent && this.draggedClone) {
|
|
// Fade out og fjern originalen
|
|
this.fadeOutAndRemove(this.originalEvent);
|
|
|
|
// Fjern clone suffix fra klonen
|
|
this.removeClonePrefix(this.draggedClone);
|
|
|
|
// Ryd op
|
|
this.originalEvent = null;
|
|
this.draggedClone = null;
|
|
|
|
console.log('Drop completed: original faded out and removed, clone became permanent');
|
|
}
|
|
|
|
// Cleanup hvis ingen drop (ingen clone var aktiv)
|
|
if (this.originalEvent && !this.draggedClone) {
|
|
this.originalEvent.style.opacity = '';
|
|
this.originalEvent.style.userSelect = '';
|
|
this.originalEvent = null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Expand header to show all-day row when clone is dragged into header
|
|
*/
|
|
private expandHeaderForAllDay(): void {
|
|
const root = document.documentElement;
|
|
const currentHeight = parseInt(getComputedStyle(root).getPropertyValue('--all-day-row-height') || '0');
|
|
|
|
if (currentHeight === 0) {
|
|
root.style.setProperty('--all-day-row-height', `${ALL_DAY_CONSTANTS.SINGLE_ROW_HEIGHT}px`);
|
|
console.log('Header expanded for all-day row');
|
|
}
|
|
}
|
|
|
|
public destroy(): void {
|
|
document.body.removeEventListener('mousemove', this.handleMouseMove.bind(this));
|
|
document.body.removeEventListener('click', this.handleClick.bind(this));
|
|
document.body.removeEventListener('mousedown', this.handleMouseDown.bind(this));
|
|
document.body.removeEventListener('mouseup', this.handleMouseUp.bind(this));
|
|
}
|
|
} |