Moving away from Azure Devops #1

Merged
Janus007 merged 113 commits from refac into master 2026-02-03 00:04:27 +01:00
7 changed files with 132 additions and 62 deletions
Showing only changes of commit f670598e7a - Show all commits

View file

@ -59,7 +59,7 @@ export class EventRenderer {
private handleDragEnd(payload: IDragEndPayload): void { private handleDragEnd(payload: IDragEndPayload): void {
if (payload.target === 'header') { if (payload.target === 'header') {
// Event was dropped in header drawer - remove from grid // 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(); element?.remove();
} }
} }
@ -284,8 +284,11 @@ export class EventRenderer {
private createEventElement(event: ICalendarEvent): HTMLElement { private createEventElement(event: ICalendarEvent): HTMLElement {
const element = document.createElement('swp-event'); const element = document.createElement('swp-event');
// Only essential data attribute (eventId for DragDropManager compatibility) // Data attributes for SwpEvent compatibility
element.dataset.eventId = event.id; element.dataset.eventId = event.id;
if (event.resourceId) {
element.dataset.resourceId = event.resourceId;
}
// Calculate position // Calculate position
const position = calculateEventPosition(event.start, event.end, this.gridConfig); const position = calculateEventPosition(event.start, event.end, this.gridConfig);

View file

@ -13,6 +13,7 @@ import {
IDragMoveHeaderPayload, IDragMoveHeaderPayload,
IDragLeaveHeaderPayload IDragLeaveHeaderPayload
} from '../types/DragTypes'; } from '../types/DragTypes';
import { SwpEvent } from '../types/SwpEvent';
interface DragState { interface DragState {
eventId: string; eventId: string;
@ -146,18 +147,19 @@ export class DragDropManager {
// Remove ghost // Remove ghost
this.dragState.ghostElement.remove(); 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 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 // Emit drag:end
const payload: IDragEndPayload = { const payload: IDragEndPayload = {
eventId: this.dragState.eventId, swpEvent,
element: this.dragState.element,
snappedY,
columnElement: this.dragState.columnElement,
dateKey,
resourceId,
sourceDateKey: this.dragState.sourceDateKey, sourceDateKey: this.dragState.sourceDateKey,
sourceResourceId: this.dragState.sourceResourceId, sourceResourceId: this.dragState.sourceResourceId,
target: this.inHeader ? 'header' : 'grid' target: this.inHeader ? 'header' : 'grid'

View file

@ -7,18 +7,15 @@
import { ICalendarEvent, IEventBus, IEventUpdatedPayload } from '../types/CalendarTypes'; import { ICalendarEvent, IEventBus, IEventUpdatedPayload } from '../types/CalendarTypes';
import { EventService } from '../storage/events/EventService'; import { EventService } from '../storage/events/EventService';
import { IGridConfig } from '../core/IGridConfig';
import { DateService } from '../core/DateService'; import { DateService } from '../core/DateService';
import { CoreEvents } from '../constants/CoreEvents'; import { CoreEvents } from '../constants/CoreEvents';
import { IDragEndPayload } from '../types/DragTypes'; import { IDragEndPayload } from '../types/DragTypes';
import { IResizeEndPayload } from '../types/ResizeTypes'; import { IResizeEndPayload } from '../types/ResizeTypes';
import { pixelsToMinutes } from '../utils/PositionUtils';
export class EventPersistenceManager { export class EventPersistenceManager {
constructor( constructor(
private eventService: EventService, private eventService: EventService,
private eventBus: IEventBus, private eventBus: IEventBus,
private gridConfig: IGridConfig,
private dateService: DateService private dateService: DateService
) { ) {
this.setupListeners(); this.setupListeners();
@ -34,44 +31,34 @@ export class EventPersistenceManager {
*/ */
private handleDragEnd = async (e: Event): Promise<void> => { private handleDragEnd = async (e: Event): Promise<void> => {
const payload = (e as CustomEvent<IDragEndPayload>).detail; const payload = (e as CustomEvent<IDragEndPayload>).detail;
const { swpEvent } = payload;
// Get existing event // Get existing event to merge with
const event = await this.eventService.get(payload.eventId); const event = await this.eventService.get(swpEvent.eventId);
if (!event) { if (!event) {
console.warn(`EventPersistenceManager: Event ${payload.eventId} not found`); console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`);
return; return;
} }
// Calculate new start time from snappedY // Update and save - start/end already calculated in SwpEvent
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
const updatedEvent: ICalendarEvent = { const updatedEvent: ICalendarEvent = {
...event, ...event,
start: newStart, start: swpEvent.start,
end: newEnd, end: swpEvent.end,
resourceId: payload.resourceId ?? event.resourceId, resourceId: swpEvent.resourceId ?? event.resourceId,
syncStatus: 'pending' syncStatus: 'pending'
}; };
await this.eventService.save(updatedEvent); await this.eventService.save(updatedEvent);
// Emit EVENT_UPDATED for EventRenderer to re-render affected columns // Emit EVENT_UPDATED for EventRenderer to re-render affected columns
const targetDateKey = this.dateService.getDateKey(swpEvent.start);
const updatePayload: IEventUpdatedPayload = { const updatePayload: IEventUpdatedPayload = {
eventId: updatedEvent.id, eventId: updatedEvent.id,
sourceDateKey: payload.sourceDateKey, sourceDateKey: payload.sourceDateKey,
sourceResourceId: payload.sourceResourceId, sourceResourceId: payload.sourceResourceId,
targetDateKey: payload.dateKey, targetDateKey,
targetResourceId: payload.resourceId targetResourceId: swpEvent.resourceId
}; };
this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload);
}; };
@ -81,21 +68,19 @@ export class EventPersistenceManager {
*/ */
private handleResizeEnd = async (e: Event): Promise<void> => { private handleResizeEnd = async (e: Event): Promise<void> => {
const payload = (e as CustomEvent<IResizeEndPayload>).detail; const payload = (e as CustomEvent<IResizeEndPayload>).detail;
const { swpEvent } = payload;
// Get existing event // Get existing event to merge with
const event = await this.eventService.get(payload.eventId); const event = await this.eventService.get(swpEvent.eventId);
if (!event) { if (!event) {
console.warn(`EventPersistenceManager: Event ${payload.eventId} not found`); console.warn(`EventPersistenceManager: Event ${swpEvent.eventId} not found`);
return; return;
} }
// Calculate new end time // Update and save - end already calculated in SwpEvent
const newEnd = new Date(event.start.getTime() + payload.newDurationMinutes * 60 * 1000);
// Update and save
const updatedEvent: ICalendarEvent = { const updatedEvent: ICalendarEvent = {
...event, ...event,
end: newEnd, end: swpEvent.end,
syncStatus: 'pending' syncStatus: 'pending'
}; };
@ -103,13 +88,13 @@ export class EventPersistenceManager {
// Emit EVENT_UPDATED for EventRenderer to re-render the column // Emit EVENT_UPDATED for EventRenderer to re-render the column
// Resize stays in same column, so source and target are the same // 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 = { const updatePayload: IEventUpdatedPayload = {
eventId: updatedEvent.id, eventId: updatedEvent.id,
sourceDateKey: dateKey, sourceDateKey: dateKey,
sourceResourceId: event.resourceId, sourceResourceId: swpEvent.resourceId,
targetDateKey: dateKey, targetDateKey: dateKey,
targetResourceId: event.resourceId targetResourceId: swpEvent.resourceId
}; };
this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload); this.eventBus.emit(CoreEvents.EVENT_UPDATED, updatePayload);
}; };

View file

@ -4,6 +4,7 @@ import { pixelsToMinutes, minutesToPixels, snapToGrid } from '../utils/PositionU
import { DateService } from '../core/DateService'; import { DateService } from '../core/DateService';
import { CoreEvents } from '../constants/CoreEvents'; import { CoreEvents } from '../constants/CoreEvents';
import { IResizeStartPayload, IResizeEndPayload } from '../types/ResizeTypes'; import { IResizeStartPayload, IResizeEndPayload } from '../types/ResizeTypes';
import { SwpEvent } from '../types/SwpEvent';
/** /**
* ResizeManager - Handles resize of calendar events * ResizeManager - Handles resize of calendar events
@ -250,15 +251,20 @@ export class ResizeManager {
// Remove global resizing class // Remove global resizing class
document.documentElement.classList.remove('swp--resizing'); document.documentElement.classList.remove('swp--resizing');
// Emit resize end event // Get dateKey from parent column
const newHeight = this.resizeState.currentHeight; const column = this.resizeState.element.closest('swp-day-column') as HTMLElement;
const newDurationMinutes = pixelsToMinutes(newHeight, this.gridConfig); 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, { this.eventBus.emit(CoreEvents.EVENT_RESIZE_END, {
eventId: this.resizeState.eventId, swpEvent
element: this.resizeState.element,
newHeight,
newDurationMinutes
} as IResizeEndPayload); } as IResizeEndPayload);
// Reset state // Reset state

View file

@ -2,6 +2,8 @@
* DragTypes - Event payloads for drag-drop operations * DragTypes - Event payloads for drag-drop operations
*/ */
import { SwpEvent } from './SwpEvent';
export interface IMousePosition { export interface IMousePosition {
x: number; x: number;
y: number; y: number;
@ -24,12 +26,7 @@ export interface IDragMovePayload {
} }
export interface IDragEndPayload { export interface IDragEndPayload {
eventId: string; swpEvent: SwpEvent; // Wrapper with element, start, end, eventId, resourceId
element: HTMLElement;
snappedY: number; // Final snapped position
columnElement: HTMLElement;
dateKey: string; // Target column date (from dataset)
resourceId?: string; // Target column resource (resource mode)
sourceDateKey: string; // Source column date (where drag started) sourceDateKey: string; // Source column date (where drag started)
sourceResourceId?: string; // Source column resource (where drag started) sourceResourceId?: string; // Source column resource (where drag started)
target: 'grid' | 'header'; // Where the event was dropped target: 'grid' | 'header'; // Where the event was dropped

View file

@ -2,6 +2,8 @@
* ResizeTypes - Event payloads for resize operations * ResizeTypes - Event payloads for resize operations
*/ */
import { SwpEvent } from './SwpEvent';
export interface IResizeStartPayload { export interface IResizeStartPayload {
eventId: string; eventId: string;
element: HTMLElement; element: HTMLElement;
@ -9,8 +11,5 @@ export interface IResizeStartPayload {
} }
export interface IResizeEndPayload { export interface IResizeEndPayload {
eventId: string; swpEvent: SwpEvent; // Wrapper with element, start, end, eventId, resourceId
element: HTMLElement;
newHeight: number;
newDurationMinutes: number;
} }

78
src/v2/types/SwpEvent.ts Normal file
View 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);
}
}