Enhances drag and drop behavior by introducing free positioning during auto-scroll and snapping to grid intervals. - Introduces a `calculateFreePosition` method to allow events to follow the mouse exactly during auto-scroll. - Modifies the drag move event to emit the snapped position during normal drag behavior. - Updates event rendering to use grid settings for snap intervals. - Updates grid styles to configure CSS variables dynamically.
348 lines
No EOL
9.1 KiB
TypeScript
348 lines
No EOL
9.1 KiB
TypeScript
/**
|
|
* GridManager - Simplified grid manager using centralized GridRenderer
|
|
* Delegates DOM rendering to GridRenderer, focuses on coordination
|
|
*/
|
|
|
|
import { eventBus } from '../core/EventBus';
|
|
import { calendarConfig } from '../core/CalendarConfig';
|
|
import { CoreEvents } from '../constants/CoreEvents';
|
|
import { ResourceCalendarData, CalendarView } from '../types/CalendarTypes';
|
|
import { GridRenderer } from '../renderers/GridRenderer';
|
|
import { GridStyleManager } from '../renderers/GridStyleManager';
|
|
import { DateCalculator } from '../utils/DateCalculator';
|
|
|
|
/**
|
|
* Simplified GridManager focused on coordination, delegates rendering to GridRenderer
|
|
*/
|
|
export class GridManager {
|
|
private container: HTMLElement | null = null;
|
|
private currentDate: Date = new Date();
|
|
private resourceData: ResourceCalendarData | null = null;
|
|
private currentView: CalendarView = 'week';
|
|
private gridRenderer: GridRenderer;
|
|
private styleManager: GridStyleManager;
|
|
private eventCleanup: (() => void)[] = [];
|
|
|
|
constructor() {
|
|
// Initialize GridRenderer and StyleManager with config
|
|
this.gridRenderer = new GridRenderer();
|
|
this.styleManager = new GridStyleManager();
|
|
this.init();
|
|
}
|
|
|
|
private init(): void {
|
|
this.findElements();
|
|
this.subscribeToEvents();
|
|
|
|
}
|
|
|
|
private findElements(): void {
|
|
this.container = document.querySelector('swp-calendar-container');
|
|
}
|
|
|
|
private subscribeToEvents(): void {
|
|
// Listen for view changes
|
|
this.eventCleanup.push(
|
|
eventBus.on(CoreEvents.VIEW_CHANGED, (e: Event) => {
|
|
const detail = (e as CustomEvent).detail;
|
|
this.currentView = detail.currentView;
|
|
this.render();
|
|
})
|
|
);
|
|
|
|
// Listen for config changes that affect rendering
|
|
this.eventCleanup.push(
|
|
eventBus.on(CoreEvents.REFRESH_REQUESTED, (e: Event) => {
|
|
this.render();
|
|
})
|
|
);
|
|
|
|
this.eventCleanup.push(
|
|
eventBus.on(CoreEvents.WORKWEEK_CHANGED, () => {
|
|
this.render();
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Switch to a different view
|
|
*/
|
|
public switchView(view: CalendarView): void {
|
|
this.currentView = view;
|
|
this.render();
|
|
}
|
|
|
|
/**
|
|
* Set resource data for resource calendar mode
|
|
*/
|
|
public setResourceData(resourceData: ResourceCalendarData | null): void {
|
|
this.resourceData = resourceData;
|
|
this.render();
|
|
}
|
|
|
|
/**
|
|
* Main render method - delegates to GridRenderer
|
|
*/
|
|
public async render(): Promise<void> {
|
|
if (!this.container) {
|
|
return;
|
|
}
|
|
|
|
// Update CSS variables first
|
|
this.styleManager.updateGridStyles(this.resourceData);
|
|
|
|
// Delegate to GridRenderer with current view context
|
|
this.gridRenderer.renderGrid(
|
|
this.container,
|
|
this.currentDate,
|
|
this.resourceData
|
|
);
|
|
|
|
// Calculate period range using DateCalculator
|
|
const periodRange = this.getPeriodRange();
|
|
|
|
// Get layout config based on current view
|
|
const layoutConfig = this.getLayoutConfig();
|
|
|
|
// Emit grid rendered event
|
|
eventBus.emit(CoreEvents.GRID_RENDERED, {
|
|
container: this.container,
|
|
currentDate: this.currentDate,
|
|
startDate: periodRange.startDate,
|
|
endDate: periodRange.endDate,
|
|
layoutConfig: layoutConfig,
|
|
columnCount: layoutConfig.columnCount
|
|
});
|
|
}
|
|
|
|
|
|
/**
|
|
* Get current period label using DateCalculator
|
|
*/
|
|
public getCurrentPeriodLabel(): string {
|
|
switch (this.currentView) {
|
|
case 'week':
|
|
case 'day':
|
|
const weekStart = DateCalculator.getISOWeekStart(this.currentDate);
|
|
const weekEnd = DateCalculator.getWeekEnd(this.currentDate);
|
|
return DateCalculator.formatDateRange(weekStart, weekEnd);
|
|
case 'month':
|
|
return this.currentDate.toLocaleDateString('en-US', { month: 'long', year: 'numeric' });
|
|
default:
|
|
const defaultWeekStart = DateCalculator.getISOWeekStart(this.currentDate);
|
|
const defaultWeekEnd = DateCalculator.getWeekEnd(this.currentDate);
|
|
return DateCalculator.formatDateRange(defaultWeekStart, defaultWeekEnd);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Navigate to next period using DateCalculator
|
|
*/
|
|
public navigateNext(): void {
|
|
let nextDate: Date;
|
|
|
|
switch (this.currentView) {
|
|
case 'week':
|
|
nextDate = DateCalculator.addWeeks(this.currentDate, 1);
|
|
break;
|
|
case 'month':
|
|
nextDate = this.addMonths(this.currentDate, 1);
|
|
break;
|
|
case 'day':
|
|
nextDate = DateCalculator.addDays(this.currentDate, 1);
|
|
break;
|
|
default:
|
|
nextDate = DateCalculator.addWeeks(this.currentDate, 1);
|
|
}
|
|
|
|
this.currentDate = nextDate;
|
|
|
|
eventBus.emit(CoreEvents.NAVIGATION_COMPLETED, {
|
|
direction: 'next',
|
|
newDate: nextDate,
|
|
periodLabel: this.getCurrentPeriodLabel()
|
|
});
|
|
|
|
this.render();
|
|
}
|
|
|
|
/**
|
|
* Navigate to previous period using DateCalculator
|
|
*/
|
|
public navigatePrevious(): void {
|
|
let prevDate: Date;
|
|
|
|
switch (this.currentView) {
|
|
case 'week':
|
|
prevDate = DateCalculator.addWeeks(this.currentDate, -1);
|
|
break;
|
|
case 'month':
|
|
prevDate = this.addMonths(this.currentDate, -1);
|
|
break;
|
|
case 'day':
|
|
prevDate = DateCalculator.addDays(this.currentDate, -1);
|
|
break;
|
|
default:
|
|
prevDate = DateCalculator.addWeeks(this.currentDate, -1);
|
|
}
|
|
|
|
this.currentDate = prevDate;
|
|
|
|
eventBus.emit(CoreEvents.NAVIGATION_COMPLETED, {
|
|
direction: 'previous',
|
|
newDate: prevDate,
|
|
periodLabel: this.getCurrentPeriodLabel()
|
|
});
|
|
|
|
this.render();
|
|
}
|
|
|
|
/**
|
|
* Navigate to today
|
|
*/
|
|
public navigateToToday(): void {
|
|
this.currentDate = new Date();
|
|
|
|
eventBus.emit(CoreEvents.DATE_CHANGED, {
|
|
newDate: this.currentDate,
|
|
periodLabel: this.getCurrentPeriodLabel()
|
|
});
|
|
|
|
this.render();
|
|
}
|
|
|
|
/**
|
|
* Get current view's display dates using DateCalculator
|
|
*/
|
|
public getDisplayDates(): Date[] {
|
|
switch (this.currentView) {
|
|
case 'week':
|
|
const weekStart = DateCalculator.getISOWeekStart(this.currentDate);
|
|
return DateCalculator.getFullWeekDates(weekStart);
|
|
case 'month':
|
|
return this.getMonthDates(this.currentDate);
|
|
case 'day':
|
|
return [this.currentDate];
|
|
default:
|
|
const defaultWeekStart = DateCalculator.getISOWeekStart(this.currentDate);
|
|
return DateCalculator.getFullWeekDates(defaultWeekStart);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get period range for current view
|
|
*/
|
|
private getPeriodRange(): { startDate: Date; endDate: Date } {
|
|
switch (this.currentView) {
|
|
case 'week':
|
|
const weekStart = DateCalculator.getISOWeekStart(this.currentDate);
|
|
const weekEnd = DateCalculator.getWeekEnd(this.currentDate);
|
|
return {
|
|
startDate: weekStart,
|
|
endDate: weekEnd
|
|
};
|
|
case 'month':
|
|
return {
|
|
startDate: this.getMonthStart(this.currentDate),
|
|
endDate: this.getMonthEnd(this.currentDate)
|
|
};
|
|
case 'day':
|
|
return {
|
|
startDate: this.currentDate,
|
|
endDate: this.currentDate
|
|
};
|
|
default:
|
|
const defaultWeekStart = DateCalculator.getISOWeekStart(this.currentDate);
|
|
const defaultWeekEnd = DateCalculator.getWeekEnd(this.currentDate);
|
|
return {
|
|
startDate: defaultWeekStart,
|
|
endDate: defaultWeekEnd
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get layout config for current view
|
|
*/
|
|
private getLayoutConfig(): any {
|
|
switch (this.currentView) {
|
|
case 'week':
|
|
return {
|
|
columnCount: 7,
|
|
type: 'week'
|
|
};
|
|
case 'month':
|
|
return {
|
|
columnCount: 7,
|
|
type: 'month'
|
|
};
|
|
case 'day':
|
|
return {
|
|
columnCount: 1,
|
|
type: 'day'
|
|
};
|
|
default:
|
|
return {
|
|
columnCount: 7,
|
|
type: 'week'
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Clean up all resources
|
|
*/
|
|
public destroy(): void {
|
|
// Clean up event listeners
|
|
this.eventCleanup.forEach(cleanup => cleanup());
|
|
this.eventCleanup = [];
|
|
|
|
// Clear references
|
|
this.container = null;
|
|
this.resourceData = null;
|
|
}
|
|
|
|
/**
|
|
* Helper method to add months to a date
|
|
*/
|
|
private addMonths(date: Date, months: number): Date {
|
|
const result = new Date(date);
|
|
result.setMonth(result.getMonth() + months);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Helper method to get month start
|
|
*/
|
|
private getMonthStart(date: Date): Date {
|
|
const result = new Date(date);
|
|
result.setDate(1);
|
|
result.setHours(0, 0, 0, 0);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Helper method to get month end
|
|
*/
|
|
private getMonthEnd(date: Date): Date {
|
|
const result = new Date(date);
|
|
result.setMonth(result.getMonth() + 1, 0);
|
|
result.setHours(23, 59, 59, 999);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Helper method to get all dates in a month
|
|
*/
|
|
private getMonthDates(date: Date): Date[] {
|
|
const dates: Date[] = [];
|
|
const monthStart = this.getMonthStart(date);
|
|
const monthEnd = this.getMonthEnd(date);
|
|
|
|
for (let d = new Date(monthStart); d <= monthEnd; d.setDate(d.getDate() + 1)) {
|
|
dates.push(new Date(d));
|
|
}
|
|
|
|
return dates;
|
|
}
|
|
} |