Moving away from Azure Devops #1
7 changed files with 132 additions and 62 deletions
|
|
@ -59,7 +59,7 @@ export class EventRenderer {
|
|||
private handleDragEnd(payload: IDragEndPayload): void {
|
||||
if (payload.target === 'header') {
|
||||
// Event was dropped in header drawer - remove from grid
|
||||
const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.eventId}"]`);
|
||||
const element = this.container?.querySelector(`swp-content-viewport swp-event[data-event-id="${payload.swpEvent.eventId}"]`);
|
||||
element?.remove();
|
||||
}
|
||||
}
|
||||
|
|
@ -284,8 +284,11 @@ export class EventRenderer {
|
|||
private createEventElement(event: ICalendarEvent): HTMLElement {
|
||||
const element = document.createElement('swp-event');
|
||||
|
||||
// Only essential data attribute (eventId for DragDropManager compatibility)
|
||||
// Data attributes for SwpEvent compatibility
|
||||
element.dataset.eventId = event.id;
|
||||
if (event.resourceId) {
|
||||
element.dataset.resourceId = event.resourceId;
|
||||
}
|
||||
|
||||
// Calculate position
|
||||
const position = calculateEventPosition(event.start, event.end, this.gridConfig);
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import {
|
|||
IDragMoveHeaderPayload,
|
||||
IDragLeaveHeaderPayload
|
||||
} from '../types/DragTypes';
|
||||
import { SwpEvent } from '../types/SwpEvent';
|
||||
|
||||
interface DragState {
|
||||
eventId: string;
|
||||
|
|
@ -146,18 +147,19 @@ export class DragDropManager {
|
|||
// Remove ghost
|
||||
this.dragState.ghostElement.remove();
|
||||
|
||||
// Get column data (target = current column, source = where drag started)
|
||||
// Get dateKey from target column
|
||||
const dateKey = this.dragState.columnElement.dataset.date || '';
|
||||
const resourceId = this.dragState.columnElement.dataset.resourceId;
|
||||
|
||||
// Create SwpEvent from element (reads top/height/eventId/resourceId from element)
|
||||
const swpEvent = SwpEvent.fromElement(
|
||||
this.dragState.element,
|
||||
dateKey,
|
||||
this.gridConfig
|
||||
);
|
||||
|
||||
// Emit drag:end
|
||||
const payload: IDragEndPayload = {
|
||||
eventId: this.dragState.eventId,
|
||||
element: this.dragState.element,
|
||||
snappedY,
|
||||
columnElement: this.dragState.columnElement,
|
||||
dateKey,
|
||||
resourceId,
|
||||
swpEvent,
|
||||
sourceDateKey: this.dragState.sourceDateKey,
|
||||
sourceResourceId: this.dragState.sourceResourceId,
|
||||
target: this.inHeader ? 'header' : 'grid'
|
||||
|
|
|
|||
|
|
@ -7,18 +7,15 @@
|
|||
|
||||
import { ICalendarEvent, IEventBus, IEventUpdatedPayload } from '../types/CalendarTypes';
|
||||
import { EventService } from '../storage/events/EventService';
|
||||
import { IGridConfig } from '../core/IGridConfig';
|
||||
import { DateService } from '../core/DateService';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { IDragEndPayload } from '../types/DragTypes';
|
||||
import { IResizeEndPayload } from '../types/ResizeTypes';
|
||||
import { pixelsToMinutes } from '../utils/PositionUtils';
|
||||
|
||||
export class EventPersistenceManager {
|
||||
constructor(
|
||||
private eventService: EventService,
|
||||
private eventBus: IEventBus,
|
||||
private gridConfig: IGridConfig,
|
||||
private dateService: DateService
|
||||
) {
|
||||
this.setupListeners();
|
||||
|
|
@ -34,44 +31,34 @@ export class EventPersistenceManager {
|
|||
*/
|
||||
private handleDragEnd = async (e: Event): Promise<void> => {
|
||||
const payload = (e as CustomEvent<IDragEndPayload>).detail;
|
||||
const { swpEvent } = payload;
|
||||
|
||||
// Get existing event
|
||||
const event = await this.eventService.get(payload.eventId);
|
||||
// Get existing event to merge with
|
||||
const event = await this.eventService.get(swpEvent.eventId);
|
||||
if (!event) {
|
||||
console.warn(`EventPersistenceManager: Event ${payload.eventId} not found`);
|
||||
console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate new start time from snappedY
|
||||
const minutesFromDayStart = pixelsToMinutes(payload.snappedY, this.gridConfig);
|
||||
const totalMinutes = (this.gridConfig.dayStartHour * 60) + minutesFromDayStart;
|
||||
|
||||
// Preserve duration
|
||||
const durationMs = event.end.getTime() - event.start.getTime();
|
||||
|
||||
// Create new dates with correct day from dateKey
|
||||
const newStart = new Date(payload.dateKey);
|
||||
newStart.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0);
|
||||
const newEnd = new Date(newStart.getTime() + durationMs);
|
||||
|
||||
// Update and save
|
||||
// Update and save - start/end already calculated in SwpEvent
|
||||
const updatedEvent: ICalendarEvent = {
|
||||
...event,
|
||||
start: newStart,
|
||||
end: newEnd,
|
||||
resourceId: payload.resourceId ?? event.resourceId,
|
||||
start: swpEvent.start,
|
||||
end: swpEvent.end,
|
||||
resourceId: swpEvent.resourceId ?? event.resourceId,
|
||||
syncStatus: 'pending'
|
||||
};
|
||||
|
||||
await this.eventService.save(updatedEvent);
|
||||
|
||||
// Emit EVENT_UPDATED for EventRenderer to re-render affected columns
|
||||
const targetDateKey = this.dateService.getDateKey(swpEvent.start);
|
||||
const updatePayload: IEventUpdatedPayload = {
|
||||
eventId: updatedEvent.id,
|
||||
sourceDateKey: payload.sourceDateKey,
|
||||
sourceResourceId: payload.sourceResourceId,
|
||||
targetDateKey: payload.dateKey,
|
||||
targetResourceId: payload.resourceId
|
||||
targetDateKey,
|
||||
targetResourceId: swpEvent.resourceId
|
||||
};
|
||||
this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload);
|
||||
};
|
||||
|
|
@ -81,21 +68,19 @@ export class EventPersistenceManager {
|
|||
*/
|
||||
private handleResizeEnd = async (e: Event): Promise<void> => {
|
||||
const payload = (e as CustomEvent<IResizeEndPayload>).detail;
|
||||
const { swpEvent } = payload;
|
||||
|
||||
// Get existing event
|
||||
const event = await this.eventService.get(payload.eventId);
|
||||
// Get existing event to merge with
|
||||
const event = await this.eventService.get(swpEvent.eventId);
|
||||
if (!event) {
|
||||
console.warn(`EventPersistenceManager: Event ${payload.eventId} not found`);
|
||||
console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Calculate new end time
|
||||
const newEnd = new Date(event.start.getTime() + payload.newDurationMinutes * 60 * 1000);
|
||||
|
||||
// Update and save
|
||||
// Update and save - end already calculated in SwpEvent
|
||||
const updatedEvent: ICalendarEvent = {
|
||||
...event,
|
||||
end: newEnd,
|
||||
end: swpEvent.end,
|
||||
syncStatus: 'pending'
|
||||
};
|
||||
|
||||
|
|
@ -103,13 +88,13 @@ export class EventPersistenceManager {
|
|||
|
||||
// Emit EVENT_UPDATED for EventRenderer to re-render the column
|
||||
// Resize stays in same column, so source and target are the same
|
||||
const dateKey = this.dateService.getDateKey(event.start);
|
||||
const dateKey = this.dateService.getDateKey(swpEvent.start);
|
||||
const updatePayload: IEventUpdatedPayload = {
|
||||
eventId: updatedEvent.id,
|
||||
sourceDateKey: dateKey,
|
||||
sourceResourceId: event.resourceId,
|
||||
sourceResourceId: swpEvent.resourceId,
|
||||
targetDateKey: dateKey,
|
||||
targetResourceId: event.resourceId
|
||||
targetResourceId: swpEvent.resourceId
|
||||
};
|
||||
this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { pixelsToMinutes, minutesToPixels, snapToGrid } from '../utils/PositionU
|
|||
import { DateService } from '../core/DateService';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { IResizeStartPayload, IResizeEndPayload } from '../types/ResizeTypes';
|
||||
import { SwpEvent } from '../types/SwpEvent';
|
||||
|
||||
/**
|
||||
* ResizeManager - Handles resize of calendar events
|
||||
|
|
@ -250,15 +251,20 @@ export class ResizeManager {
|
|||
// Remove global resizing class
|
||||
document.documentElement.classList.remove('swp--resizing');
|
||||
|
||||
// Emit resize end event
|
||||
const newHeight = this.resizeState.currentHeight;
|
||||
const newDurationMinutes = pixelsToMinutes(newHeight, this.gridConfig);
|
||||
// Get dateKey from parent column
|
||||
const column = this.resizeState.element.closest('swp-day-column') as HTMLElement;
|
||||
const dateKey = column?.dataset.date || '';
|
||||
|
||||
// Create SwpEvent from element (reads top/height/eventId/resourceId from element)
|
||||
const swpEvent = SwpEvent.fromElement(
|
||||
this.resizeState.element,
|
||||
dateKey,
|
||||
this.gridConfig
|
||||
);
|
||||
|
||||
// Emit resize end event
|
||||
this.eventBus.emit(CoreEvents.EVENT_RESIZE_END, {
|
||||
eventId: this.resizeState.eventId,
|
||||
element: this.resizeState.element,
|
||||
newHeight,
|
||||
newDurationMinutes
|
||||
swpEvent
|
||||
} as IResizeEndPayload);
|
||||
|
||||
// Reset state
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
* DragTypes - Event payloads for drag-drop operations
|
||||
*/
|
||||
|
||||
import { SwpEvent } from './SwpEvent';
|
||||
|
||||
export interface IMousePosition {
|
||||
x: number;
|
||||
y: number;
|
||||
|
|
@ -24,12 +26,7 @@ export interface IDragMovePayload {
|
|||
}
|
||||
|
||||
export interface IDragEndPayload {
|
||||
eventId: string;
|
||||
element: HTMLElement;
|
||||
snappedY: number; // Final snapped position
|
||||
columnElement: HTMLElement;
|
||||
dateKey: string; // Target column date (from dataset)
|
||||
resourceId?: string; // Target column resource (resource mode)
|
||||
swpEvent: SwpEvent; // Wrapper with element, start, end, eventId, resourceId
|
||||
sourceDateKey: string; // Source column date (where drag started)
|
||||
sourceResourceId?: string; // Source column resource (where drag started)
|
||||
target: 'grid' | 'header'; // Where the event was dropped
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
* ResizeTypes - Event payloads for resize operations
|
||||
*/
|
||||
|
||||
import { SwpEvent } from './SwpEvent';
|
||||
|
||||
export interface IResizeStartPayload {
|
||||
eventId: string;
|
||||
element: HTMLElement;
|
||||
|
|
@ -9,8 +11,5 @@ export interface IResizeStartPayload {
|
|||
}
|
||||
|
||||
export interface IResizeEndPayload {
|
||||
eventId: string;
|
||||
element: HTMLElement;
|
||||
newHeight: number;
|
||||
newDurationMinutes: number;
|
||||
swpEvent: SwpEvent; // Wrapper with element, start, end, eventId, resourceId
|
||||
}
|
||||
|
|
|
|||
78
src/v2/types/SwpEvent.ts
Normal file
78
src/v2/types/SwpEvent.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
import { IGridConfig } from '../core/IGridConfig';
|
||||
|
||||
/**
|
||||
* SwpEvent - Wrapper class for calendar event elements
|
||||
*
|
||||
* Encapsulates an HTMLElement and provides computed properties
|
||||
* for start/end times based on element position and grid config.
|
||||
*
|
||||
* Usage:
|
||||
* - All data (eventId, resourceId) is read from element.dataset
|
||||
* - Position (top, height) is read from element.style
|
||||
* - Factory method `fromElement()` calculates Date objects
|
||||
*/
|
||||
export class SwpEvent {
|
||||
readonly element: HTMLElement;
|
||||
private _start: Date;
|
||||
private _end: Date;
|
||||
|
||||
constructor(element: HTMLElement, start: Date, end: Date) {
|
||||
this.element = element;
|
||||
this._start = start;
|
||||
this._end = end;
|
||||
}
|
||||
|
||||
/** Event ID from element.dataset.eventId */
|
||||
get eventId(): string {
|
||||
return this.element.dataset.eventId || '';
|
||||
}
|
||||
|
||||
/** Resource ID from element.dataset.resourceId */
|
||||
get resourceId(): string | undefined {
|
||||
return this.element.dataset.resourceId;
|
||||
}
|
||||
|
||||
get start(): Date {
|
||||
return this._start;
|
||||
}
|
||||
|
||||
get end(): Date {
|
||||
return this._end;
|
||||
}
|
||||
|
||||
/** Duration in minutes */
|
||||
get durationMinutes(): number {
|
||||
return (this._end.getTime() - this._start.getTime()) / (1000 * 60);
|
||||
}
|
||||
|
||||
/** Duration in milliseconds */
|
||||
get durationMs(): number {
|
||||
return this._end.getTime() - this._start.getTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory: Create SwpEvent from element + dateKey
|
||||
* Reads top/height from element.style to calculate start/end
|
||||
*/
|
||||
static fromElement(
|
||||
element: HTMLElement,
|
||||
dateKey: string,
|
||||
gridConfig: IGridConfig
|
||||
): SwpEvent {
|
||||
const topPixels = parseFloat(element.style.top) || 0;
|
||||
const heightPixels = parseFloat(element.style.height) || 0;
|
||||
|
||||
// Calculate start from top position
|
||||
const startMinutesFromGrid = (topPixels / gridConfig.hourHeight) * 60;
|
||||
const totalMinutes = (gridConfig.dayStartHour * 60) + startMinutesFromGrid;
|
||||
|
||||
const start = new Date(dateKey);
|
||||
start.setHours(Math.floor(totalMinutes / 60), totalMinutes % 60, 0, 0);
|
||||
|
||||
// Calculate end from height
|
||||
const durationMinutes = (heightPixels / gridConfig.hourHeight) * 60;
|
||||
const end = new Date(start.getTime() + durationMinutes * 60 * 1000);
|
||||
|
||||
return new SwpEvent(element, start, end);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue