Adds I-prefix to all interfaces
This commit is contained in:
parent
80aaab46f2
commit
8ec5f52872
44 changed files with 1731 additions and 1949 deletions
|
|
@ -1,21 +1,21 @@
|
|||
// All-day row height management and animations
|
||||
|
||||
import { eventBus } from '../core/EventBus';
|
||||
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
||||
import { ALL_DAY_CONSTANTS } from '../configuration/CalendarConfig';
|
||||
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
||||
import { AllDayLayoutEngine, EventLayout } from '../utils/AllDayLayoutEngine';
|
||||
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
import { AllDayLayoutEngine, IEventLayout } from '../utils/AllDayLayoutEngine';
|
||||
import { IColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
|
||||
import {
|
||||
DragMouseEnterHeaderEventPayload,
|
||||
DragStartEventPayload,
|
||||
DragMoveEventPayload,
|
||||
DragEndEventPayload,
|
||||
DragColumnChangeEventPayload,
|
||||
HeaderReadyEventPayload
|
||||
IDragMouseEnterHeaderEventPayload,
|
||||
IDragStartEventPayload,
|
||||
IDragMoveEventPayload,
|
||||
IDragEndEventPayload,
|
||||
IDragColumnChangeEventPayload,
|
||||
IHeaderReadyEventPayload
|
||||
} from '../types/EventTypes';
|
||||
import { DragOffset, MousePosition } from '../types/DragDropTypes';
|
||||
import { IDragOffset, IMousePosition } from '../types/DragDropTypes';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { EventManager } from './EventManager';
|
||||
import { differenceInCalendarDays } from 'date-fns';
|
||||
|
|
@ -33,10 +33,10 @@ export class AllDayManager {
|
|||
private layoutEngine: AllDayLayoutEngine | null = null;
|
||||
|
||||
// State tracking for differential updates
|
||||
private currentLayouts: EventLayout[] = [];
|
||||
private currentAllDayEvents: CalendarEvent[] = [];
|
||||
private currentWeekDates: ColumnBounds[] = [];
|
||||
private newLayouts: EventLayout[] = [];
|
||||
private currentLayouts: IEventLayout[] = [];
|
||||
private currentAllDayEvents: ICalendarEvent[] = [];
|
||||
private currentWeekDates: IColumnBounds[] = [];
|
||||
private newLayouts: IEventLayout[] = [];
|
||||
|
||||
// Expand/collapse state
|
||||
private isExpanded: boolean = false;
|
||||
|
|
@ -62,7 +62,7 @@ export class AllDayManager {
|
|||
*/
|
||||
private setupEventListeners(): void {
|
||||
eventBus.on('drag:mouseenter-header', (event) => {
|
||||
const payload = (event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
|
||||
const payload = (event as CustomEvent<IDragMouseEnterHeaderEventPayload>).detail;
|
||||
|
||||
if (payload.draggedClone.hasAttribute('data-allday'))
|
||||
return;
|
||||
|
|
@ -87,7 +87,7 @@ export class AllDayManager {
|
|||
|
||||
// Listen for drag operations on all-day events
|
||||
eventBus.on('drag:start', (event) => {
|
||||
let payload: DragStartEventPayload = (event as CustomEvent<DragStartEventPayload>).detail;
|
||||
let payload: IDragStartEventPayload = (event as CustomEvent<IDragStartEventPayload>).detail;
|
||||
|
||||
if (!payload.draggedClone?.hasAttribute('data-allday')) {
|
||||
return;
|
||||
|
|
@ -97,7 +97,7 @@ export class AllDayManager {
|
|||
});
|
||||
|
||||
eventBus.on('drag:column-change', (event) => {
|
||||
let payload: DragColumnChangeEventPayload = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
||||
let payload: IDragColumnChangeEventPayload = (event as CustomEvent<IDragColumnChangeEventPayload>).detail;
|
||||
|
||||
if (!payload.draggedClone?.hasAttribute('data-allday')) {
|
||||
return;
|
||||
|
|
@ -107,7 +107,7 @@ export class AllDayManager {
|
|||
});
|
||||
|
||||
eventBus.on('drag:end', (event) => {
|
||||
let draggedElement: DragEndEventPayload = (event as CustomEvent<DragEndEventPayload>).detail;
|
||||
let draggedElement: IDragEndEventPayload = (event as CustomEvent<IDragEndEventPayload>).detail;
|
||||
|
||||
if (draggedElement.target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore.
|
||||
return;
|
||||
|
|
@ -128,12 +128,12 @@ export class AllDayManager {
|
|||
|
||||
// Listen for header ready - when dates are populated with period data
|
||||
eventBus.on('header:ready', (event: Event) => {
|
||||
let headerReadyEventPayload = (event as CustomEvent<HeaderReadyEventPayload>).detail;
|
||||
let headerReadyEventPayload = (event as CustomEvent<IHeaderReadyEventPayload>).detail;
|
||||
|
||||
let startDate = new Date(headerReadyEventPayload.headerElements.at(0)!.date);
|
||||
let endDate = new Date(headerReadyEventPayload.headerElements.at(-1)!.date);
|
||||
|
||||
let events: CalendarEvent[] = this.eventManager.getEventsForPeriod(startDate, endDate);
|
||||
let events: ICalendarEvent[] = this.eventManager.getEventsForPeriod(startDate, endDate);
|
||||
// Filter for all-day events
|
||||
const allDayEvents = events.filter(event => event.allDay);
|
||||
|
||||
|
|
@ -302,7 +302,7 @@ export class AllDayManager {
|
|||
* Calculate layout for ALL all-day events using AllDayLayoutEngine
|
||||
* This is the correct method that processes all events together for proper overlap detection
|
||||
*/
|
||||
private calculateAllDayEventsLayout(events: CalendarEvent[], dayHeaders: ColumnBounds[]): EventLayout[] {
|
||||
private calculateAllDayEventsLayout(events: ICalendarEvent[], dayHeaders: IColumnBounds[]): IEventLayout[] {
|
||||
|
||||
// Store current state
|
||||
this.currentAllDayEvents = events;
|
||||
|
|
@ -316,12 +316,12 @@ export class AllDayManager {
|
|||
|
||||
}
|
||||
|
||||
private handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload): void {
|
||||
private handleConvertToAllDay(payload: IDragMouseEnterHeaderEventPayload): void {
|
||||
|
||||
let allDayContainer = this.getAllDayContainer();
|
||||
if (!allDayContainer) return;
|
||||
|
||||
// Create SwpAllDayEventElement from CalendarEvent
|
||||
// Create SwpAllDayEventElement from ICalendarEvent
|
||||
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
|
||||
|
||||
// Apply grid positioning
|
||||
|
|
@ -345,7 +345,7 @@ export class AllDayManager {
|
|||
/**
|
||||
* Handle drag move for all-day events - SPECIALIZED FOR ALL-DAY CONTAINER
|
||||
*/
|
||||
private handleColumnChange(dragColumnChangeEventPayload: DragColumnChangeEventPayload): void {
|
||||
private handleColumnChange(dragColumnChangeEventPayload: IDragColumnChangeEventPayload): void {
|
||||
|
||||
let allDayContainer = this.getAllDayContainer();
|
||||
if (!allDayContainer) return;
|
||||
|
|
@ -380,7 +380,7 @@ export class AllDayManager {
|
|||
}
|
||||
|
||||
|
||||
private handleDragEnd(dragEndEvent: DragEndEventPayload): void {
|
||||
private handleDragEnd(dragEndEvent: IDragEndEventPayload): void {
|
||||
|
||||
const getEventDurationDays = (start: string | undefined, end: string | undefined): number => {
|
||||
|
||||
|
|
@ -433,7 +433,7 @@ export class AllDayManager {
|
|||
dragEndEvent.draggedClone.dataset.start = this.dateService.toUTC(newStartDate);
|
||||
dragEndEvent.draggedClone.dataset.end = this.dateService.toUTC(newEndDate);
|
||||
|
||||
const droppedEvent: CalendarEvent = {
|
||||
const droppedEvent: ICalendarEvent = {
|
||||
id: eventId,
|
||||
title: dragEndEvent.draggedClone.dataset.title || '',
|
||||
start: newStartDate,
|
||||
|
|
@ -557,9 +557,9 @@ export class AllDayManager {
|
|||
});
|
||||
}
|
||||
/**
|
||||
* Count number of events in a specific column using ColumnBounds
|
||||
* Count number of events in a specific column using IColumnBounds
|
||||
*/
|
||||
private countEventsInColumn(columnBounds: ColumnBounds): number {
|
||||
private countEventsInColumn(columnBounds: IColumnBounds): number {
|
||||
let columnIndex = columnBounds.index;
|
||||
let count = 0;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { Configuration } from '../configuration/CalendarConfig';
|
||||
import { CalendarView, IEventBus } from '../types/CalendarTypes';
|
||||
import { EventManager } from './EventManager';
|
||||
import { GridManager } from './GridManager';
|
||||
|
|
@ -15,7 +15,7 @@ export class CalendarManager {
|
|||
private gridManager: GridManager;
|
||||
private eventRenderer: EventRenderingService;
|
||||
private scrollManager: ScrollManager;
|
||||
private config: CalendarConfig;
|
||||
private config: Configuration;
|
||||
private currentView: CalendarView = 'week';
|
||||
private currentDate: Date = new Date();
|
||||
private isInitialized: boolean = false;
|
||||
|
|
@ -26,7 +26,7 @@ export class CalendarManager {
|
|||
gridManager: GridManager,
|
||||
eventRenderingService: EventRenderingService,
|
||||
scrollManager: ScrollManager,
|
||||
config: CalendarConfig
|
||||
config: Configuration
|
||||
) {
|
||||
this.eventBus = eventBus;
|
||||
this.eventManager = eventManager;
|
||||
|
|
@ -232,4 +232,4 @@ export class CalendarManager {
|
|||
});
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
// Configuration manager - handles config updates with event emission
|
||||
// Uses static CalendarConfig internally but adds event-driven updates
|
||||
|
||||
import { IEventBus, ICalendarConfig } from '../types/CalendarTypes';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
|
||||
/**
|
||||
* Grid display settings interface (re-export from CalendarConfig)
|
||||
*/
|
||||
interface GridSettings {
|
||||
dayStartHour: number;
|
||||
dayEndHour: number;
|
||||
workStartHour: number;
|
||||
workEndHour: number;
|
||||
hourHeight: number;
|
||||
snapInterval: number;
|
||||
fitToWidth: boolean;
|
||||
scrollToHour: number | null;
|
||||
gridStartThresholdMinutes: number;
|
||||
showCurrentTime: boolean;
|
||||
showWorkHours: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* ConfigManager - Handles configuration updates with event emission
|
||||
* Wraps static CalendarConfig with event-driven functionality for DI system
|
||||
* Also manages CSS custom properties that reflect config values
|
||||
*/
|
||||
export class ConfigManager {
|
||||
constructor(private eventBus: IEventBus) {}
|
||||
|
||||
/**
|
||||
* Initialize CSS variables on startup
|
||||
* Must be called after DOM is ready but before any rendering
|
||||
*/
|
||||
public initialize(): void {
|
||||
this.updateCSSVariables();
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a config value and emit event
|
||||
*/
|
||||
set<K extends keyof ICalendarConfig>(key: K, value: ICalendarConfig[K]): void {
|
||||
const oldValue = CalendarConfig.get(key);
|
||||
CalendarConfig.set(key, value);
|
||||
|
||||
// Update CSS variables to reflect config change
|
||||
this.updateCSSVariables();
|
||||
|
||||
// Emit config update event
|
||||
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
|
||||
key,
|
||||
value,
|
||||
oldValue
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update multiple config values and emit event
|
||||
*/
|
||||
update(updates: Partial<ICalendarConfig>): void {
|
||||
Object.entries(updates).forEach(([key, value]) => {
|
||||
this.set(key as keyof ICalendarConfig, value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update grid display settings and emit event
|
||||
*/
|
||||
updateGridSettings(updates: Partial<GridSettings>): void {
|
||||
CalendarConfig.updateGridSettings(updates);
|
||||
|
||||
// Update CSS variables to reflect config change
|
||||
this.updateCSSVariables();
|
||||
|
||||
// Emit event after update
|
||||
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
|
||||
key: 'gridSettings',
|
||||
value: CalendarConfig.getGridSettings()
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set selected date and emit event
|
||||
*/
|
||||
setSelectedDate(date: Date): void {
|
||||
const oldDate = CalendarConfig.getSelectedDate();
|
||||
CalendarConfig.setSelectedDate(date);
|
||||
|
||||
// Emit date change event if it actually changed
|
||||
if (!oldDate || oldDate.getTime() !== date.getTime()) {
|
||||
this.eventBus.emit(CoreEvents.DATE_CHANGED, {
|
||||
date,
|
||||
oldDate
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set work week and emit event
|
||||
*/
|
||||
setWorkWeek(workWeekId: string): void {
|
||||
const oldWorkWeek = CalendarConfig.getCurrentWorkWeek();
|
||||
CalendarConfig.setWorkWeek(workWeekId);
|
||||
|
||||
// Update CSS variables to reflect config change
|
||||
this.updateCSSVariables();
|
||||
|
||||
// Emit event if changed
|
||||
if (oldWorkWeek !== workWeekId) {
|
||||
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
|
||||
key: 'workWeek',
|
||||
value: workWeekId,
|
||||
oldValue: oldWorkWeek
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all CSS custom properties based on current config
|
||||
* This keeps the DOM in sync with config values
|
||||
*/
|
||||
private updateCSSVariables(): void {
|
||||
const root = document.documentElement;
|
||||
const gridSettings = CalendarConfig.getGridSettings();
|
||||
const calendar = document.querySelector('swp-calendar') as HTMLElement;
|
||||
|
||||
// Set time-related CSS variables
|
||||
root.style.setProperty('--header-height', '80px'); // Fixed header height
|
||||
root.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`);
|
||||
root.style.setProperty('--minute-height', `${gridSettings.hourHeight / 60}px`);
|
||||
root.style.setProperty('--snap-interval', gridSettings.snapInterval.toString());
|
||||
root.style.setProperty('--day-start-hour', gridSettings.dayStartHour.toString());
|
||||
root.style.setProperty('--day-end-hour', gridSettings.dayEndHour.toString());
|
||||
root.style.setProperty('--work-start-hour', gridSettings.workStartHour.toString());
|
||||
root.style.setProperty('--work-end-hour', gridSettings.workEndHour.toString());
|
||||
|
||||
// Set column count based on view
|
||||
const columnCount = this.calculateColumnCount();
|
||||
root.style.setProperty('--grid-columns', columnCount.toString());
|
||||
|
||||
// Set column width based on fitToWidth setting
|
||||
if (gridSettings.fitToWidth) {
|
||||
root.style.setProperty('--day-column-min-width', '50px'); // Small min-width allows columns to fit available space
|
||||
} else {
|
||||
root.style.setProperty('--day-column-min-width', '250px'); // Default min-width for horizontal scroll mode
|
||||
}
|
||||
|
||||
// Set fitToWidth data attribute for CSS targeting
|
||||
if (calendar) {
|
||||
calendar.setAttribute('data-fit-to-width', gridSettings.fitToWidth.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate number of columns based on view
|
||||
*/
|
||||
private calculateColumnCount(): number {
|
||||
const dateSettings = CalendarConfig.getDateViewSettings();
|
||||
const workWeekSettings = CalendarConfig.getWorkWeekSettings();
|
||||
|
||||
switch (dateSettings.period) {
|
||||
case 'day':
|
||||
return 1;
|
||||
case 'week':
|
||||
return workWeekSettings.totalDays;
|
||||
case 'month':
|
||||
return workWeekSettings.totalDays; // Use work week for month view too
|
||||
default:
|
||||
return workWeekSettings.totalDays;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -134,33 +134,33 @@
|
|||
|
||||
import { IEventBus } from '../types/CalendarTypes';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||
import { IColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||
import { SwpEventElement, BaseSwpEventElement } from '../elements/SwpEventElement';
|
||||
import {
|
||||
DragStartEventPayload,
|
||||
DragMoveEventPayload,
|
||||
DragEndEventPayload,
|
||||
DragMouseEnterHeaderEventPayload,
|
||||
DragMouseLeaveHeaderEventPayload,
|
||||
DragMouseEnterColumnEventPayload,
|
||||
DragColumnChangeEventPayload
|
||||
IDragStartEventPayload,
|
||||
IDragMoveEventPayload,
|
||||
IDragEndEventPayload,
|
||||
IDragMouseEnterHeaderEventPayload,
|
||||
IDragMouseLeaveHeaderEventPayload,
|
||||
IDragMouseEnterColumnEventPayload,
|
||||
IDragColumnChangeEventPayload
|
||||
} from '../types/EventTypes';
|
||||
import { MousePosition } from '../types/DragDropTypes';
|
||||
import { IMousePosition } from '../types/DragDropTypes';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
|
||||
export class DragDropManager {
|
||||
private eventBus: IEventBus;
|
||||
|
||||
// Mouse tracking with optimized state
|
||||
private mouseDownPosition: MousePosition = { x: 0, y: 0 };
|
||||
private currentMousePosition: MousePosition = { x: 0, y: 0 };
|
||||
private mouseOffset: MousePosition = { x: 0, y: 0 };
|
||||
private mouseDownPosition: IMousePosition = { x: 0, y: 0 };
|
||||
private currentMousePosition: IMousePosition = { x: 0, y: 0 };
|
||||
private mouseOffset: IMousePosition = { x: 0, y: 0 };
|
||||
|
||||
// Drag state
|
||||
private originalElement!: HTMLElement | null;
|
||||
private draggedClone!: HTMLElement | null;
|
||||
private currentColumn: ColumnBounds | null = null;
|
||||
private previousColumn: ColumnBounds | null = null;
|
||||
private currentColumn: IColumnBounds | null = null;
|
||||
private previousColumn: IColumnBounds | null = null;
|
||||
private isDragStarted = false;
|
||||
|
||||
// Movement threshold to distinguish click from drag
|
||||
|
|
@ -176,7 +176,7 @@ export class DragDropManager {
|
|||
private dragAnimationId: number | null = null;
|
||||
private targetY = 0;
|
||||
private currentY = 0;
|
||||
private targetColumn: ColumnBounds | null = null;
|
||||
private targetColumn: IColumnBounds | null = null;
|
||||
private positionUtils: PositionUtils;
|
||||
|
||||
constructor(eventBus: IEventBus, positionUtils: PositionUtils) {
|
||||
|
|
@ -336,7 +336,7 @@ export class DragDropManager {
|
|||
* Try to initialize drag based on movement threshold
|
||||
* Returns true if drag was initialized, false if not enough movement
|
||||
*/
|
||||
private initializeDrag(currentPosition: MousePosition): boolean {
|
||||
private initializeDrag(currentPosition: IMousePosition): boolean {
|
||||
const deltaX = Math.abs(currentPosition.x - this.mouseDownPosition.x);
|
||||
const deltaY = Math.abs(currentPosition.y - this.mouseDownPosition.y);
|
||||
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
|
|
@ -362,7 +362,7 @@ export class DragDropManager {
|
|||
this.currentColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||
this.draggedClone = originalElement.createClone();
|
||||
|
||||
const dragStartPayload: DragStartEventPayload = {
|
||||
const dragStartPayload: IDragStartEventPayload = {
|
||||
originalElement: this.originalElement!,
|
||||
draggedClone: this.draggedClone,
|
||||
mousePosition: this.mouseDownPosition,
|
||||
|
|
@ -375,7 +375,7 @@ export class DragDropManager {
|
|||
}
|
||||
|
||||
|
||||
private continueDrag(currentPosition: MousePosition): void {
|
||||
private continueDrag(currentPosition: IMousePosition): void {
|
||||
|
||||
if (!this.draggedClone!.hasAttribute("data-allday")) {
|
||||
// Calculate raw position from mouse (no snapping)
|
||||
|
|
@ -405,7 +405,7 @@ export class DragDropManager {
|
|||
/**
|
||||
* Detect column change and emit event
|
||||
*/
|
||||
private detectColumnChange(currentPosition: MousePosition): void {
|
||||
private detectColumnChange(currentPosition: IMousePosition): void {
|
||||
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||
if (newColumn == null) return;
|
||||
|
||||
|
|
@ -413,7 +413,7 @@ export class DragDropManager {
|
|||
this.previousColumn = this.currentColumn;
|
||||
this.currentColumn = newColumn;
|
||||
|
||||
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
||||
const dragColumnChangePayload: IDragColumnChangeEventPayload = {
|
||||
originalElement: this.originalElement!,
|
||||
draggedClone: this.draggedClone!,
|
||||
previousColumn: this.previousColumn,
|
||||
|
|
@ -434,7 +434,7 @@ export class DragDropManager {
|
|||
|
||||
// Only emit drag:end if drag was actually started
|
||||
if (this.isDragStarted) {
|
||||
const mousePosition: MousePosition = { x: event.clientX, y: event.clientY };
|
||||
const mousePosition: IMousePosition = { x: event.clientX, y: event.clientY };
|
||||
|
||||
// Snap to grid on mouse up (like ResizeHandleManager)
|
||||
const column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
||||
|
|
@ -455,7 +455,7 @@ export class DragDropManager {
|
|||
if (!dropTarget)
|
||||
throw "dropTarget is null";
|
||||
|
||||
const dragEndPayload: DragEndEventPayload = {
|
||||
const dragEndPayload: IDragEndEventPayload = {
|
||||
originalElement: this.originalElement,
|
||||
draggedClone: this.draggedClone,
|
||||
mousePosition,
|
||||
|
|
@ -530,7 +530,7 @@ export class DragDropManager {
|
|||
/**
|
||||
* Optimized snap position calculation using PositionUtils
|
||||
*/
|
||||
private calculateSnapPosition(mouseY: number, column: ColumnBounds): number {
|
||||
private calculateSnapPosition(mouseY: number, column: IColumnBounds): number {
|
||||
// Calculate where the event top would be (accounting for mouse offset)
|
||||
const eventTopY = mouseY - this.mouseOffset.y;
|
||||
|
||||
|
|
@ -560,7 +560,7 @@ export class DragDropManager {
|
|||
this.currentY += step;
|
||||
|
||||
// Emit drag:move event with current draggedClone reference
|
||||
const dragMovePayload: DragMoveEventPayload = {
|
||||
const dragMovePayload: IDragMoveEventPayload = {
|
||||
originalElement: this.originalElement!,
|
||||
draggedClone: this.draggedClone, // Always uses current reference
|
||||
mousePosition: this.currentMousePosition, // Use current mouse position!
|
||||
|
|
@ -576,7 +576,7 @@ export class DragDropManager {
|
|||
this.currentY = this.targetY;
|
||||
|
||||
// Emit final position
|
||||
const dragMovePayload: DragMoveEventPayload = {
|
||||
const dragMovePayload: IDragMoveEventPayload = {
|
||||
originalElement: this.originalElement!,
|
||||
draggedClone: this.draggedClone,
|
||||
mousePosition: this.currentMousePosition, // Use current mouse position!
|
||||
|
|
@ -633,7 +633,7 @@ export class DragDropManager {
|
|||
/**
|
||||
* Detect drop target - whether dropped in swp-day-column or swp-day-header
|
||||
*/
|
||||
private detectDropTarget(position: MousePosition): 'swp-day-column' | 'swp-day-header' | null {
|
||||
private detectDropTarget(position: IMousePosition): 'swp-day-column' | 'swp-day-header' | null {
|
||||
|
||||
// Traverse up the DOM tree to find the target container
|
||||
let currentElement = this.draggedClone;
|
||||
|
|
@ -659,13 +659,13 @@ export class DragDropManager {
|
|||
return;
|
||||
}
|
||||
|
||||
const position: MousePosition = { x: event.clientX, y: event.clientY };
|
||||
const position: IMousePosition = { x: event.clientX, y: event.clientY };
|
||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||
|
||||
if (targetColumn) {
|
||||
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
||||
|
||||
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
||||
const dragMouseEnterPayload: IDragMouseEnterHeaderEventPayload = {
|
||||
targetColumn: targetColumn,
|
||||
mousePosition: position,
|
||||
originalElement: this.originalElement,
|
||||
|
|
@ -689,7 +689,7 @@ export class DragDropManager {
|
|||
return;
|
||||
}
|
||||
|
||||
const position: MousePosition = { x: event.clientX, y: event.clientY };
|
||||
const position: IMousePosition = { x: event.clientX, y: event.clientY };
|
||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||
|
||||
if (!targetColumn) {
|
||||
|
|
@ -699,10 +699,10 @@ export class DragDropManager {
|
|||
// Calculate snapped Y position
|
||||
const snappedY = this.calculateSnapPosition(position.y, targetColumn);
|
||||
|
||||
// Extract CalendarEvent from the dragged clone
|
||||
// Extract ICalendarEvent from the dragged clone
|
||||
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
||||
|
||||
const dragMouseEnterPayload: DragMouseEnterColumnEventPayload = {
|
||||
const dragMouseEnterPayload: IDragMouseEnterColumnEventPayload = {
|
||||
targetColumn: targetColumn,
|
||||
mousePosition: position,
|
||||
snappedY: snappedY,
|
||||
|
|
@ -727,14 +727,14 @@ export class DragDropManager {
|
|||
return;
|
||||
}
|
||||
|
||||
const position: MousePosition = { x: event.clientX, y: event.clientY };
|
||||
const position: IMousePosition = { x: event.clientX, y: event.clientY };
|
||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||
|
||||
if (!targetColumn) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
|
||||
const dragMouseLeavePayload: IDragMouseLeaveHeaderEventPayload = {
|
||||
targetDate: targetColumn.date,
|
||||
mousePosition: position,
|
||||
originalElement: this.originalElement,
|
||||
|
|
|
|||
|
|
@ -1,230 +1,230 @@
|
|||
/**
|
||||
* EdgeScrollManager - Auto-scroll when dragging near edges
|
||||
* Uses time-based scrolling with 2-zone system for variable speed
|
||||
*/
|
||||
|
||||
import { IEventBus } from '../types/CalendarTypes';
|
||||
import { DragMoveEventPayload, DragStartEventPayload } from '../types/EventTypes';
|
||||
|
||||
export class EdgeScrollManager {
|
||||
private scrollableContent: HTMLElement | null = null;
|
||||
private timeGrid: HTMLElement | null = null;
|
||||
private draggedClone: HTMLElement | null = null;
|
||||
private scrollRAF: number | null = null;
|
||||
private mouseY = 0;
|
||||
private isDragging = false;
|
||||
private isScrolling = false; // Track if edge-scroll is active
|
||||
private lastTs = 0;
|
||||
private rect: DOMRect | null = null;
|
||||
private initialScrollTop = 0;
|
||||
private scrollListener: ((e: Event) => void) | null = null;
|
||||
|
||||
// Constants - fixed values as per requirements
|
||||
private readonly OUTER_ZONE = 100; // px from edge (slow zone)
|
||||
private readonly INNER_ZONE = 50; // px from edge (fast zone)
|
||||
private readonly SLOW_SPEED_PXS = 140; // px/sec in outer zone
|
||||
private readonly FAST_SPEED_PXS = 640; // px/sec in inner zone
|
||||
|
||||
constructor(private eventBus: IEventBus) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
// Wait for DOM to be ready
|
||||
setTimeout(() => {
|
||||
this.scrollableContent = document.querySelector('swp-scrollable-content');
|
||||
this.timeGrid = document.querySelector('swp-time-grid');
|
||||
|
||||
if (this.scrollableContent) {
|
||||
// Disable smooth scroll for instant auto-scroll
|
||||
this.scrollableContent.style.scrollBehavior = 'auto';
|
||||
|
||||
// Add scroll listener to detect actual scrolling
|
||||
this.scrollListener = this.handleScroll.bind(this);
|
||||
this.scrollableContent.addEventListener('scroll', this.scrollListener, { passive: true });
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Listen to mousemove directly from document to always get mouse coords
|
||||
document.body.addEventListener('mousemove', (e: MouseEvent) => {
|
||||
if (this.isDragging) {
|
||||
this.mouseY = e.clientY;
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeToEvents();
|
||||
}
|
||||
|
||||
private subscribeToEvents(): void {
|
||||
|
||||
// Listen to drag events from DragDropManager
|
||||
this.eventBus.on('drag:start', (event: Event) => {
|
||||
const payload = (event as CustomEvent).detail;
|
||||
this.draggedClone = payload.draggedClone;
|
||||
this.startDrag();
|
||||
});
|
||||
|
||||
this.eventBus.on('drag:end', () => this.stopDrag());
|
||||
this.eventBus.on('drag:cancelled', () => this.stopDrag());
|
||||
|
||||
// Stop scrolling when event converts to/from all-day
|
||||
this.eventBus.on('drag:mouseenter-header', () => {
|
||||
console.log('🔄 EdgeScrollManager: Event converting to all-day - stopping scroll');
|
||||
this.stopDrag();
|
||||
});
|
||||
|
||||
this.eventBus.on('drag:mouseenter-column', () => {
|
||||
this.startDrag();
|
||||
});
|
||||
}
|
||||
|
||||
private startDrag(): void {
|
||||
console.log('🎬 EdgeScrollManager: Starting drag');
|
||||
this.isDragging = true;
|
||||
this.isScrolling = false; // Reset scroll state
|
||||
this.lastTs = performance.now();
|
||||
|
||||
// Save initial scroll position
|
||||
if (this.scrollableContent) {
|
||||
this.initialScrollTop = this.scrollableContent.scrollTop;
|
||||
}
|
||||
|
||||
if (this.scrollRAF === null) {
|
||||
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||
}
|
||||
}
|
||||
|
||||
private stopDrag(): void {
|
||||
this.isDragging = false;
|
||||
|
||||
// Emit stopped event if we were scrolling
|
||||
if (this.isScrolling) {
|
||||
this.isScrolling = false;
|
||||
console.log('🛑 EdgeScrollManager: Edge-scroll stopped (drag ended)');
|
||||
this.eventBus.emit('edgescroll:stopped', {});
|
||||
}
|
||||
|
||||
if (this.scrollRAF !== null) {
|
||||
cancelAnimationFrame(this.scrollRAF);
|
||||
this.scrollRAF = null;
|
||||
}
|
||||
this.rect = null;
|
||||
this.lastTs = 0;
|
||||
this.initialScrollTop = 0;
|
||||
}
|
||||
|
||||
private handleScroll(): void {
|
||||
if (!this.isDragging || !this.scrollableContent) return;
|
||||
|
||||
const currentScrollTop = this.scrollableContent.scrollTop;
|
||||
const scrollDelta = Math.abs(currentScrollTop - this.initialScrollTop);
|
||||
|
||||
// Only emit started event if we've actually scrolled more than 1px
|
||||
if (scrollDelta > 1 && !this.isScrolling) {
|
||||
this.isScrolling = true;
|
||||
console.log('💾 EdgeScrollManager: Edge-scroll started (actual scroll detected)', {
|
||||
initialScrollTop: this.initialScrollTop,
|
||||
currentScrollTop,
|
||||
scrollDelta
|
||||
});
|
||||
this.eventBus.emit('edgescroll:started', {});
|
||||
}
|
||||
}
|
||||
|
||||
private scrollTick(ts: number): void {
|
||||
const dt = this.lastTs ? (ts - this.lastTs) / 1000 : 0;
|
||||
this.lastTs = ts;
|
||||
|
||||
if (!this.scrollableContent) {
|
||||
this.stopDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache rect for performance (only measure once per frame)
|
||||
if (!this.rect) {
|
||||
this.rect = this.scrollableContent.getBoundingClientRect();
|
||||
}
|
||||
|
||||
let vy = 0;
|
||||
if (this.isDragging) {
|
||||
const distTop = this.mouseY - this.rect.top;
|
||||
const distBot = this.rect.bottom - this.mouseY;
|
||||
|
||||
// Check top edge
|
||||
if (distTop < this.INNER_ZONE) {
|
||||
vy = -this.FAST_SPEED_PXS;
|
||||
} else if (distTop < this.OUTER_ZONE) {
|
||||
vy = -this.SLOW_SPEED_PXS;
|
||||
}
|
||||
// Check bottom edge
|
||||
else if (distBot < this.INNER_ZONE) {
|
||||
vy = this.FAST_SPEED_PXS;
|
||||
} else if (distBot < this.OUTER_ZONE) {
|
||||
vy = this.SLOW_SPEED_PXS;
|
||||
}
|
||||
}
|
||||
|
||||
if (vy !== 0 && this.isDragging && this.timeGrid && this.draggedClone) {
|
||||
// Check if we can scroll in the requested direction
|
||||
const currentScrollTop = this.scrollableContent.scrollTop;
|
||||
const scrollableHeight = this.scrollableContent.clientHeight;
|
||||
const timeGridHeight = this.timeGrid.clientHeight;
|
||||
|
||||
// Get dragged element position and height
|
||||
const cloneRect = this.draggedClone.getBoundingClientRect();
|
||||
const cloneBottom = cloneRect.bottom;
|
||||
const timeGridRect = this.timeGrid.getBoundingClientRect();
|
||||
const timeGridBottom = timeGridRect.bottom;
|
||||
|
||||
// Check boundaries
|
||||
const atTop = currentScrollTop <= 0 && vy < 0;
|
||||
const atBottom = (cloneBottom >= timeGridBottom) && vy > 0;
|
||||
|
||||
console.log('📊 Scroll check:', {
|
||||
currentScrollTop,
|
||||
scrollableHeight,
|
||||
timeGridHeight,
|
||||
cloneBottom,
|
||||
timeGridBottom,
|
||||
atTop,
|
||||
atBottom,
|
||||
vy
|
||||
});
|
||||
|
||||
if (atTop || atBottom) {
|
||||
// At boundary - stop scrolling
|
||||
if (this.isScrolling) {
|
||||
this.isScrolling = false;
|
||||
this.initialScrollTop = this.scrollableContent.scrollTop;
|
||||
console.log('🛑 EdgeScrollManager: Edge-scroll stopped (reached boundary)');
|
||||
this.eventBus.emit('edgescroll:stopped', {});
|
||||
}
|
||||
|
||||
// Continue RAF loop to detect when mouse moves away from boundary
|
||||
if (this.isDragging) {
|
||||
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||
}
|
||||
} else {
|
||||
// Not at boundary - apply scroll
|
||||
this.scrollableContent.scrollTop += vy * dt;
|
||||
this.rect = null; // Invalidate cache for next frame
|
||||
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||
}
|
||||
} else {
|
||||
// Mouse moved away from edge - stop scrolling
|
||||
if (this.isScrolling) {
|
||||
this.isScrolling = false;
|
||||
this.initialScrollTop = this.scrollableContent.scrollTop; // Reset for next scroll
|
||||
console.log('🛑 EdgeScrollManager: Edge-scroll stopped (mouse left edge)');
|
||||
this.eventBus.emit('edgescroll:stopped', {});
|
||||
}
|
||||
|
||||
// Continue RAF loop even if not scrolling, to detect edge entry
|
||||
if (this.isDragging) {
|
||||
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||
} else {
|
||||
this.stopDrag();
|
||||
}
|
||||
}
|
||||
}
|
||||
/**
|
||||
* EdgeScrollManager - Auto-scroll when dragging near edges
|
||||
* Uses time-based scrolling with 2-zone system for variable speed
|
||||
*/
|
||||
|
||||
import { IEventBus } from '../types/CalendarTypes';
|
||||
import { IDragMoveEventPayload, IDragStartEventPayload } from '../types/EventTypes';
|
||||
|
||||
export class EdgeScrollManager {
|
||||
private scrollableContent: HTMLElement | null = null;
|
||||
private timeGrid: HTMLElement | null = null;
|
||||
private draggedClone: HTMLElement | null = null;
|
||||
private scrollRAF: number | null = null;
|
||||
private mouseY = 0;
|
||||
private isDragging = false;
|
||||
private isScrolling = false; // Track if edge-scroll is active
|
||||
private lastTs = 0;
|
||||
private rect: DOMRect | null = null;
|
||||
private initialScrollTop = 0;
|
||||
private scrollListener: ((e: Event) => void) | null = null;
|
||||
|
||||
// Constants - fixed values as per requirements
|
||||
private readonly OUTER_ZONE = 100; // px from edge (slow zone)
|
||||
private readonly INNER_ZONE = 50; // px from edge (fast zone)
|
||||
private readonly SLOW_SPEED_PXS = 140; // px/sec in outer zone
|
||||
private readonly FAST_SPEED_PXS = 640; // px/sec in inner zone
|
||||
|
||||
constructor(private eventBus: IEventBus) {
|
||||
this.init();
|
||||
}
|
||||
|
||||
private init(): void {
|
||||
// Wait for DOM to be ready
|
||||
setTimeout(() => {
|
||||
this.scrollableContent = document.querySelector('swp-scrollable-content');
|
||||
this.timeGrid = document.querySelector('swp-time-grid');
|
||||
|
||||
if (this.scrollableContent) {
|
||||
// Disable smooth scroll for instant auto-scroll
|
||||
this.scrollableContent.style.scrollBehavior = 'auto';
|
||||
|
||||
// Add scroll listener to detect actual scrolling
|
||||
this.scrollListener = this.handleScroll.bind(this);
|
||||
this.scrollableContent.addEventListener('scroll', this.scrollListener, { passive: true });
|
||||
}
|
||||
}, 100);
|
||||
|
||||
// Listen to mousemove directly from document to always get mouse coords
|
||||
document.body.addEventListener('mousemove', (e: MouseEvent) => {
|
||||
if (this.isDragging) {
|
||||
this.mouseY = e.clientY;
|
||||
}
|
||||
});
|
||||
|
||||
this.subscribeToEvents();
|
||||
}
|
||||
|
||||
private subscribeToEvents(): void {
|
||||
|
||||
// Listen to drag events from DragDropManager
|
||||
this.eventBus.on('drag:start', (event: Event) => {
|
||||
const payload = (event as CustomEvent).detail;
|
||||
this.draggedClone = payload.draggedClone;
|
||||
this.startDrag();
|
||||
});
|
||||
|
||||
this.eventBus.on('drag:end', () => this.stopDrag());
|
||||
this.eventBus.on('drag:cancelled', () => this.stopDrag());
|
||||
|
||||
// Stop scrolling when event converts to/from all-day
|
||||
this.eventBus.on('drag:mouseenter-header', () => {
|
||||
console.log('🔄 EdgeScrollManager: Event converting to all-day - stopping scroll');
|
||||
this.stopDrag();
|
||||
});
|
||||
|
||||
this.eventBus.on('drag:mouseenter-column', () => {
|
||||
this.startDrag();
|
||||
});
|
||||
}
|
||||
|
||||
private startDrag(): void {
|
||||
console.log('🎬 EdgeScrollManager: Starting drag');
|
||||
this.isDragging = true;
|
||||
this.isScrolling = false; // Reset scroll state
|
||||
this.lastTs = performance.now();
|
||||
|
||||
// Save initial scroll position
|
||||
if (this.scrollableContent) {
|
||||
this.initialScrollTop = this.scrollableContent.scrollTop;
|
||||
}
|
||||
|
||||
if (this.scrollRAF === null) {
|
||||
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||
}
|
||||
}
|
||||
|
||||
private stopDrag(): void {
|
||||
this.isDragging = false;
|
||||
|
||||
// Emit stopped event if we were scrolling
|
||||
if (this.isScrolling) {
|
||||
this.isScrolling = false;
|
||||
console.log('🛑 EdgeScrollManager: Edge-scroll stopped (drag ended)');
|
||||
this.eventBus.emit('edgescroll:stopped', {});
|
||||
}
|
||||
|
||||
if (this.scrollRAF !== null) {
|
||||
cancelAnimationFrame(this.scrollRAF);
|
||||
this.scrollRAF = null;
|
||||
}
|
||||
this.rect = null;
|
||||
this.lastTs = 0;
|
||||
this.initialScrollTop = 0;
|
||||
}
|
||||
|
||||
private handleScroll(): void {
|
||||
if (!this.isDragging || !this.scrollableContent) return;
|
||||
|
||||
const currentScrollTop = this.scrollableContent.scrollTop;
|
||||
const scrollDelta = Math.abs(currentScrollTop - this.initialScrollTop);
|
||||
|
||||
// Only emit started event if we've actually scrolled more than 1px
|
||||
if (scrollDelta > 1 && !this.isScrolling) {
|
||||
this.isScrolling = true;
|
||||
console.log('💾 EdgeScrollManager: Edge-scroll started (actual scroll detected)', {
|
||||
initialScrollTop: this.initialScrollTop,
|
||||
currentScrollTop,
|
||||
scrollDelta
|
||||
});
|
||||
this.eventBus.emit('edgescroll:started', {});
|
||||
}
|
||||
}
|
||||
|
||||
private scrollTick(ts: number): void {
|
||||
const dt = this.lastTs ? (ts - this.lastTs) / 1000 : 0;
|
||||
this.lastTs = ts;
|
||||
|
||||
if (!this.scrollableContent) {
|
||||
this.stopDrag();
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache rect for performance (only measure once per frame)
|
||||
if (!this.rect) {
|
||||
this.rect = this.scrollableContent.getBoundingClientRect();
|
||||
}
|
||||
|
||||
let vy = 0;
|
||||
if (this.isDragging) {
|
||||
const distTop = this.mouseY - this.rect.top;
|
||||
const distBot = this.rect.bottom - this.mouseY;
|
||||
|
||||
// Check top edge
|
||||
if (distTop < this.INNER_ZONE) {
|
||||
vy = -this.FAST_SPEED_PXS;
|
||||
} else if (distTop < this.OUTER_ZONE) {
|
||||
vy = -this.SLOW_SPEED_PXS;
|
||||
}
|
||||
// Check bottom edge
|
||||
else if (distBot < this.INNER_ZONE) {
|
||||
vy = this.FAST_SPEED_PXS;
|
||||
} else if (distBot < this.OUTER_ZONE) {
|
||||
vy = this.SLOW_SPEED_PXS;
|
||||
}
|
||||
}
|
||||
|
||||
if (vy !== 0 && this.isDragging && this.timeGrid && this.draggedClone) {
|
||||
// Check if we can scroll in the requested direction
|
||||
const currentScrollTop = this.scrollableContent.scrollTop;
|
||||
const scrollableHeight = this.scrollableContent.clientHeight;
|
||||
const timeGridHeight = this.timeGrid.clientHeight;
|
||||
|
||||
// Get dragged element position and height
|
||||
const cloneRect = this.draggedClone.getBoundingClientRect();
|
||||
const cloneBottom = cloneRect.bottom;
|
||||
const timeGridRect = this.timeGrid.getBoundingClientRect();
|
||||
const timeGridBottom = timeGridRect.bottom;
|
||||
|
||||
// Check boundaries
|
||||
const atTop = currentScrollTop <= 0 && vy < 0;
|
||||
const atBottom = (cloneBottom >= timeGridBottom) && vy > 0;
|
||||
|
||||
console.log('📊 Scroll check:', {
|
||||
currentScrollTop,
|
||||
scrollableHeight,
|
||||
timeGridHeight,
|
||||
cloneBottom,
|
||||
timeGridBottom,
|
||||
atTop,
|
||||
atBottom,
|
||||
vy
|
||||
});
|
||||
|
||||
if (atTop || atBottom) {
|
||||
// At boundary - stop scrolling
|
||||
if (this.isScrolling) {
|
||||
this.isScrolling = false;
|
||||
this.initialScrollTop = this.scrollableContent.scrollTop;
|
||||
console.log('🛑 EdgeScrollManager: Edge-scroll stopped (reached boundary)');
|
||||
this.eventBus.emit('edgescroll:stopped', {});
|
||||
}
|
||||
|
||||
// Continue RAF loop to detect when mouse moves away from boundary
|
||||
if (this.isDragging) {
|
||||
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||
}
|
||||
} else {
|
||||
// Not at boundary - apply scroll
|
||||
this.scrollableContent.scrollTop += vy * dt;
|
||||
this.rect = null; // Invalidate cache for next frame
|
||||
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||
}
|
||||
} else {
|
||||
// Mouse moved away from edge - stop scrolling
|
||||
if (this.isScrolling) {
|
||||
this.isScrolling = false;
|
||||
this.initialScrollTop = this.scrollableContent.scrollTop; // Reset for next scroll
|
||||
console.log('🛑 EdgeScrollManager: Edge-scroll stopped (mouse left edge)');
|
||||
this.eventBus.emit('edgescroll:stopped', {});
|
||||
}
|
||||
|
||||
// Continue RAF loop even if not scrolling, to detect edge entry
|
||||
if (this.isDragging) {
|
||||
this.scrollRAF = requestAnimationFrame((ts) => this.scrollTick(ts));
|
||||
} else {
|
||||
this.stopDrag();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -5,24 +5,24 @@
|
|||
|
||||
import { eventBus } from '../core/EventBus';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||
|
||||
// Import Fuse.js from npm
|
||||
import Fuse from 'fuse.js';
|
||||
|
||||
interface FuseResult {
|
||||
item: CalendarEvent;
|
||||
item: ICalendarEvent;
|
||||
refIndex: number;
|
||||
score?: number;
|
||||
}
|
||||
|
||||
export class EventFilterManager {
|
||||
private searchInput: HTMLInputElement | null = null;
|
||||
private allEvents: CalendarEvent[] = [];
|
||||
private allEvents: ICalendarEvent[] = [];
|
||||
private matchingEventIds: Set<string> = new Set();
|
||||
private isFilterActive: boolean = false;
|
||||
private frameRequest: number | null = null;
|
||||
private fuse: Fuse<CalendarEvent> | null = null;
|
||||
private fuse: Fuse<ICalendarEvent> | null = null;
|
||||
|
||||
constructor() {
|
||||
// Wait for DOM to be ready before initializing
|
||||
|
|
@ -77,7 +77,7 @@ export class EventFilterManager {
|
|||
});
|
||||
}
|
||||
|
||||
private updateEventsList(events: CalendarEvent[]): void {
|
||||
private updateEventsList(events: ICalendarEvent[]): void {
|
||||
this.allEvents = events;
|
||||
|
||||
// Initialize Fuse with the new events list
|
||||
|
|
|
|||
|
|
@ -5,35 +5,35 @@
|
|||
* Calculates stack levels, groups events, and determines rendering strategy.
|
||||
*/
|
||||
|
||||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
import { EventStackManager, EventGroup, StackLink } from './EventStackManager';
|
||||
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||
import { EventStackManager, IEventGroup, IStackLink } from './EventStackManager';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { Configuration } from '../configuration/CalendarConfig';
|
||||
|
||||
export interface GridGroupLayout {
|
||||
events: CalendarEvent[];
|
||||
export interface IGridGroupLayout {
|
||||
events: ICalendarEvent[];
|
||||
stackLevel: number;
|
||||
position: { top: number };
|
||||
columns: CalendarEvent[][]; // Events grouped by column (events in same array share a column)
|
||||
columns: ICalendarEvent[][]; // Events grouped by column (events in same array share a column)
|
||||
}
|
||||
|
||||
export interface StackedEventLayout {
|
||||
event: CalendarEvent;
|
||||
stackLink: StackLink;
|
||||
export interface IStackedEventLayout {
|
||||
event: ICalendarEvent;
|
||||
stackLink: IStackLink;
|
||||
position: { top: number; height: number };
|
||||
}
|
||||
|
||||
export interface ColumnLayout {
|
||||
gridGroups: GridGroupLayout[];
|
||||
stackedEvents: StackedEventLayout[];
|
||||
export interface IColumnLayout {
|
||||
gridGroups: IGridGroupLayout[];
|
||||
stackedEvents: IStackedEventLayout[];
|
||||
}
|
||||
|
||||
export class EventLayoutCoordinator {
|
||||
private stackManager: EventStackManager;
|
||||
private config: CalendarConfig;
|
||||
private config: Configuration;
|
||||
private positionUtils: PositionUtils;
|
||||
|
||||
constructor(stackManager: EventStackManager, config: CalendarConfig, positionUtils: PositionUtils) {
|
||||
constructor(stackManager: EventStackManager, config: Configuration, positionUtils: PositionUtils) {
|
||||
this.stackManager = stackManager;
|
||||
this.config = config;
|
||||
this.positionUtils = positionUtils;
|
||||
|
|
@ -42,14 +42,14 @@ export class EventLayoutCoordinator {
|
|||
/**
|
||||
* Calculate complete layout for a column of events (recursive approach)
|
||||
*/
|
||||
public calculateColumnLayout(columnEvents: CalendarEvent[]): ColumnLayout {
|
||||
public calculateColumnLayout(columnEvents: ICalendarEvent[]): IColumnLayout {
|
||||
if (columnEvents.length === 0) {
|
||||
return { gridGroups: [], stackedEvents: [] };
|
||||
}
|
||||
|
||||
const gridGroupLayouts: GridGroupLayout[] = [];
|
||||
const stackedEventLayouts: StackedEventLayout[] = [];
|
||||
const renderedEventsWithLevels: Array<{ event: CalendarEvent; level: number }> = [];
|
||||
const gridGroupLayouts: IGridGroupLayout[] = [];
|
||||
const stackedEventLayouts: IStackedEventLayout[] = [];
|
||||
const renderedEventsWithLevels: Array<{ event: ICalendarEvent; level: number }> = [];
|
||||
let remaining = [...columnEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
|
||||
|
||||
// Process events recursively
|
||||
|
|
@ -66,7 +66,7 @@ export class EventLayoutCoordinator {
|
|||
const gridCandidates = this.expandGridCandidates(firstEvent, remaining, thresholdMinutes);
|
||||
|
||||
// Decide: should this group be GRID or STACK?
|
||||
const group: EventGroup = {
|
||||
const group: IEventGroup = {
|
||||
events: gridCandidates,
|
||||
containerType: 'NONE',
|
||||
startTime: firstEvent.start
|
||||
|
|
@ -129,8 +129,8 @@ export class EventLayoutCoordinator {
|
|||
* Calculate stack level for a grid group based on already rendered events
|
||||
*/
|
||||
private calculateGridGroupStackLevelFromRendered(
|
||||
gridEvents: CalendarEvent[],
|
||||
renderedEventsWithLevels: Array<{ event: CalendarEvent; level: number }>
|
||||
gridEvents: ICalendarEvent[],
|
||||
renderedEventsWithLevels: Array<{ event: ICalendarEvent; level: number }>
|
||||
): number {
|
||||
// Find highest stack level of any rendered event that overlaps with this grid
|
||||
let maxOverlappingLevel = -1;
|
||||
|
|
@ -150,8 +150,8 @@ export class EventLayoutCoordinator {
|
|||
* Calculate stack level for a single stacked event based on already rendered events
|
||||
*/
|
||||
private calculateStackLevelFromRendered(
|
||||
event: CalendarEvent,
|
||||
renderedEventsWithLevels: Array<{ event: CalendarEvent; level: number }>
|
||||
event: ICalendarEvent,
|
||||
renderedEventsWithLevels: Array<{ event: ICalendarEvent; level: number }>
|
||||
): number {
|
||||
// Find highest stack level of any rendered event that overlaps with this event
|
||||
let maxOverlappingLevel = -1;
|
||||
|
|
@ -173,7 +173,7 @@ export class EventLayoutCoordinator {
|
|||
* @param thresholdMinutes - Threshold in minutes
|
||||
* @returns true if events conflict
|
||||
*/
|
||||
private detectConflict(event1: CalendarEvent, event2: CalendarEvent, thresholdMinutes: number): boolean {
|
||||
private detectConflict(event1: ICalendarEvent, event2: ICalendarEvent, thresholdMinutes: number): boolean {
|
||||
// Check 1: Start-to-start conflict (starts within threshold)
|
||||
const startToStartDiff = Math.abs(event1.start.getTime() - event2.start.getTime()) / (1000 * 60);
|
||||
if (startToStartDiff <= thresholdMinutes && this.stackManager.doEventsOverlap(event1, event2)) {
|
||||
|
|
@ -206,10 +206,10 @@ export class EventLayoutCoordinator {
|
|||
* @returns Array of all events in the conflict chain
|
||||
*/
|
||||
private expandGridCandidates(
|
||||
firstEvent: CalendarEvent,
|
||||
remaining: CalendarEvent[],
|
||||
firstEvent: ICalendarEvent,
|
||||
remaining: ICalendarEvent[],
|
||||
thresholdMinutes: number
|
||||
): CalendarEvent[] {
|
||||
): ICalendarEvent[] {
|
||||
const gridCandidates = [firstEvent];
|
||||
let candidatesChanged = true;
|
||||
|
||||
|
|
@ -246,11 +246,11 @@ export class EventLayoutCoordinator {
|
|||
* @param events - Events in the grid group (should already be sorted by start time)
|
||||
* @returns Array of columns, where each column is an array of events
|
||||
*/
|
||||
private allocateColumns(events: CalendarEvent[]): CalendarEvent[][] {
|
||||
private allocateColumns(events: ICalendarEvent[]): ICalendarEvent[][] {
|
||||
if (events.length === 0) return [];
|
||||
if (events.length === 1) return [[events[0]]];
|
||||
|
||||
const columns: CalendarEvent[][] = [];
|
||||
const columns: ICalendarEvent[][] = [];
|
||||
|
||||
// For each event, try to place it in an existing column where it doesn't overlap
|
||||
for (const event of events) {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { IEventBus, CalendarEvent } from '../types/CalendarTypes';
|
||||
import { IEventBus, ICalendarEvent } from '../types/CalendarTypes';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { Configuration } from '../configuration/CalendarConfig';
|
||||
import { DateService } from '../utils/DateService';
|
||||
import { IEventRepository } from '../repositories/IEventRepository';
|
||||
|
||||
|
|
@ -10,15 +10,15 @@ import { IEventRepository } from '../repositories/IEventRepository';
|
|||
*/
|
||||
export class EventManager {
|
||||
|
||||
private events: CalendarEvent[] = [];
|
||||
private events: ICalendarEvent[] = [];
|
||||
private dateService: DateService;
|
||||
private config: CalendarConfig;
|
||||
private config: Configuration;
|
||||
private repository: IEventRepository;
|
||||
|
||||
constructor(
|
||||
private eventBus: IEventBus,
|
||||
dateService: DateService,
|
||||
config: CalendarConfig,
|
||||
config: Configuration,
|
||||
repository: IEventRepository
|
||||
) {
|
||||
this.dateService = dateService;
|
||||
|
|
@ -42,14 +42,14 @@ export class EventManager {
|
|||
/**
|
||||
* Get events with optional copying for performance
|
||||
*/
|
||||
public getEvents(copy: boolean = false): CalendarEvent[] {
|
||||
public getEvents(copy: boolean = false): ICalendarEvent[] {
|
||||
return copy ? [...this.events] : this.events;
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized event lookup with early return
|
||||
*/
|
||||
public getEventById(id: string): CalendarEvent | undefined {
|
||||
public getEventById(id: string): ICalendarEvent | undefined {
|
||||
// Use find for better performance than filter + first
|
||||
return this.events.find(event => event.id === id);
|
||||
}
|
||||
|
|
@ -59,7 +59,7 @@ export class EventManager {
|
|||
* @param id Event ID to find
|
||||
* @returns Event with navigation info or null if not found
|
||||
*/
|
||||
public getEventForNavigation(id: string): { event: CalendarEvent; eventDate: Date } | null {
|
||||
public getEventForNavigation(id: string): { event: ICalendarEvent; eventDate: Date } | null {
|
||||
const event = this.getEventById(id);
|
||||
if (!event) {
|
||||
return null;
|
||||
|
|
@ -113,7 +113,7 @@ export class EventManager {
|
|||
/**
|
||||
* Get events that overlap with a given time period
|
||||
*/
|
||||
public getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[] {
|
||||
public getEventsForPeriod(startDate: Date, endDate: Date): ICalendarEvent[] {
|
||||
// Event overlaps period if it starts before period ends AND ends after period starts
|
||||
return this.events.filter(event => {
|
||||
return event.start <= endDate && event.end >= startDate;
|
||||
|
|
@ -123,8 +123,8 @@ export class EventManager {
|
|||
/**
|
||||
* Create a new event and add it to the calendar
|
||||
*/
|
||||
public addEvent(event: Omit<CalendarEvent, 'id'>): CalendarEvent {
|
||||
const newEvent: CalendarEvent = {
|
||||
public addEvent(event: Omit<ICalendarEvent, 'id'>): ICalendarEvent {
|
||||
const newEvent: ICalendarEvent = {
|
||||
...event,
|
||||
id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||
};
|
||||
|
|
@ -141,7 +141,7 @@ export class EventManager {
|
|||
/**
|
||||
* Update an existing event
|
||||
*/
|
||||
public updateEvent(id: string, updates: Partial<CalendarEvent>): CalendarEvent | null {
|
||||
public updateEvent(id: string, updates: Partial<ICalendarEvent>): ICalendarEvent | null {
|
||||
const eventIndex = this.events.findIndex(event => event.id === id);
|
||||
if (eventIndex === -1) return null;
|
||||
|
||||
|
|
|
|||
|
|
@ -13,26 +13,26 @@
|
|||
* @see stacking-visualization.html for visual examples
|
||||
*/
|
||||
|
||||
import { CalendarEvent } from '../types/CalendarTypes';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||
import { Configuration } from '../configuration/CalendarConfig';
|
||||
|
||||
export interface StackLink {
|
||||
export interface IStackLink {
|
||||
prev?: string; // Event ID of previous event in stack
|
||||
next?: string; // Event ID of next event in stack
|
||||
stackLevel: number; // Position in stack (0 = base, 1 = first offset, etc.)
|
||||
}
|
||||
|
||||
export interface EventGroup {
|
||||
events: CalendarEvent[];
|
||||
export interface IEventGroup {
|
||||
events: ICalendarEvent[];
|
||||
containerType: 'NONE' | 'GRID' | 'STACKING';
|
||||
startTime: Date;
|
||||
}
|
||||
|
||||
export class EventStackManager {
|
||||
private static readonly STACK_OFFSET_PX = 15;
|
||||
private config: CalendarConfig;
|
||||
private config: Configuration;
|
||||
|
||||
constructor(config: CalendarConfig) {
|
||||
constructor(config: Configuration) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ export class EventStackManager {
|
|||
* 1. They start within ±threshold minutes of each other (start-to-start)
|
||||
* 2. One event starts within threshold minutes before another ends (end-to-start conflict)
|
||||
*/
|
||||
public groupEventsByStartTime(events: CalendarEvent[]): EventGroup[] {
|
||||
public groupEventsByStartTime(events: ICalendarEvent[]): IEventGroup[] {
|
||||
if (events.length === 0) return [];
|
||||
|
||||
// Get threshold from config
|
||||
|
|
@ -57,7 +57,7 @@ export class EventStackManager {
|
|||
// Sort events by start time
|
||||
const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
|
||||
|
||||
const groups: EventGroup[] = [];
|
||||
const groups: IEventGroup[] = [];
|
||||
|
||||
for (const event of sorted) {
|
||||
// Find existing group that this event conflicts with
|
||||
|
|
@ -112,7 +112,7 @@ export class EventStackManager {
|
|||
* even if they overlap each other. This provides better visual indication that
|
||||
* events start at the same time.
|
||||
*/
|
||||
public decideContainerType(group: EventGroup): 'NONE' | 'GRID' | 'STACKING' {
|
||||
public decideContainerType(group: IEventGroup): 'NONE' | 'GRID' | 'STACKING' {
|
||||
if (group.events.length === 1) {
|
||||
return 'NONE';
|
||||
}
|
||||
|
|
@ -127,7 +127,7 @@ export class EventStackManager {
|
|||
/**
|
||||
* Check if two events overlap in time
|
||||
*/
|
||||
public doEventsOverlap(event1: CalendarEvent, event2: CalendarEvent): boolean {
|
||||
public doEventsOverlap(event1: ICalendarEvent, event2: ICalendarEvent): boolean {
|
||||
return event1.start < event2.end && event1.end > event2.start;
|
||||
}
|
||||
|
||||
|
|
@ -139,8 +139,8 @@ export class EventStackManager {
|
|||
/**
|
||||
* Create optimized stack links (events share levels when possible)
|
||||
*/
|
||||
public createOptimizedStackLinks(events: CalendarEvent[]): Map<string, StackLink> {
|
||||
const stackLinks = new Map<string, StackLink>();
|
||||
public createOptimizedStackLinks(events: ICalendarEvent[]): Map<string, IStackLink> {
|
||||
const stackLinks = new Map<string, IStackLink>();
|
||||
|
||||
if (events.length === 0) return stackLinks;
|
||||
|
||||
|
|
@ -218,14 +218,14 @@ export class EventStackManager {
|
|||
/**
|
||||
* Serialize stack link to JSON string
|
||||
*/
|
||||
public serializeStackLink(stackLink: StackLink): string {
|
||||
public serializeStackLink(stackLink: IStackLink): string {
|
||||
return JSON.stringify(stackLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deserialize JSON string to stack link
|
||||
*/
|
||||
public deserializeStackLink(json: string): StackLink | null {
|
||||
public deserializeStackLink(json: string): IStackLink | null {
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch (e) {
|
||||
|
|
@ -236,14 +236,14 @@ export class EventStackManager {
|
|||
/**
|
||||
* Apply stack link to DOM element
|
||||
*/
|
||||
public applyStackLinkToElement(element: HTMLElement, stackLink: StackLink): void {
|
||||
public applyStackLinkToElement(element: HTMLElement, stackLink: IStackLink): void {
|
||||
element.dataset.stackLink = this.serializeStackLink(stackLink);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stack link from DOM element
|
||||
*/
|
||||
public getStackLinkFromElement(element: HTMLElement): StackLink | null {
|
||||
public getStackLinkFromElement(element: HTMLElement): IStackLink | null {
|
||||
const data = element.dataset.stackLink;
|
||||
if (!data) return null;
|
||||
return this.deserializeStackLink(data);
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { eventBus } from '../core/EventBus';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { Configuration } from '../configuration/CalendarConfig';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { IHeaderRenderer, HeaderRenderContext } from '../renderers/DateHeaderRenderer';
|
||||
import { DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, HeaderReadyEventPayload } from '../types/EventTypes';
|
||||
import { IHeaderRenderer, IHeaderRenderContext } from '../renderers/DateHeaderRenderer';
|
||||
import { IDragMouseEnterHeaderEventPayload, IDragMouseLeaveHeaderEventPayload, IHeaderReadyEventPayload } from '../types/EventTypes';
|
||||
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||
|
||||
/**
|
||||
|
|
@ -12,9 +12,9 @@ import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
|||
*/
|
||||
export class HeaderManager {
|
||||
private headerRenderer: IHeaderRenderer;
|
||||
private config: CalendarConfig;
|
||||
private config: Configuration;
|
||||
|
||||
constructor(headerRenderer: IHeaderRenderer, config: CalendarConfig) {
|
||||
constructor(headerRenderer: IHeaderRenderer, config: Configuration) {
|
||||
this.headerRenderer = headerRenderer;
|
||||
this.config = config;
|
||||
|
||||
|
|
@ -44,7 +44,7 @@ export class HeaderManager {
|
|||
*/
|
||||
private handleDragMouseEnterHeader(event: Event): void {
|
||||
const { targetColumn: targetDate, mousePosition, originalElement, draggedClone: cloneElement } =
|
||||
(event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
|
||||
(event as CustomEvent<IDragMouseEnterHeaderEventPayload>).detail;
|
||||
|
||||
console.log('🎯 HeaderManager: Received drag:mouseenter-header', {
|
||||
targetDate,
|
||||
|
|
@ -58,7 +58,7 @@ export class HeaderManager {
|
|||
*/
|
||||
private handleDragMouseLeaveHeader(event: Event): void {
|
||||
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } =
|
||||
(event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
|
||||
(event as CustomEvent<IDragMouseLeaveHeaderEventPayload>).detail;
|
||||
|
||||
console.log('🚪 HeaderManager: Received drag:mouseleave-header', {
|
||||
targetDate,
|
||||
|
|
@ -109,7 +109,7 @@ export class HeaderManager {
|
|||
calendarHeader.innerHTML = '';
|
||||
|
||||
// Render new header content using injected renderer
|
||||
const context: HeaderRenderContext = {
|
||||
const context: IHeaderRenderContext = {
|
||||
currentWeek: currentDate,
|
||||
config: this.config
|
||||
};
|
||||
|
|
@ -120,9 +120,9 @@ export class HeaderManager {
|
|||
this.setupHeaderDragListeners();
|
||||
|
||||
// Notify other managers that header is ready with period data
|
||||
const payload: HeaderReadyEventPayload = {
|
||||
const payload: IHeaderReadyEventPayload = {
|
||||
headerElements: ColumnDetectionUtils.getHeaderColumns(),
|
||||
};
|
||||
eventBus.emit('header:ready', payload);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { eventBus } from '../core/EventBus';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { ResizeEndEventPayload } from '../types/EventTypes';
|
||||
import { Configuration } from '../configuration/CalendarConfig';
|
||||
import { IResizeEndEventPayload } from '../types/EventTypes';
|
||||
|
||||
type SwpEventEl = HTMLElement & { updateHeight?: (h: number) => void };
|
||||
|
||||
|
|
@ -29,9 +29,9 @@ export class ResizeHandleManager {
|
|||
private unsubscribers: Array<() => void> = [];
|
||||
private pointerCaptured = false;
|
||||
private prevZ?: string;
|
||||
private config: CalendarConfig;
|
||||
private config: Configuration;
|
||||
|
||||
constructor(config: CalendarConfig) {
|
||||
constructor(config: Configuration) {
|
||||
this.config = config;
|
||||
const grid = this.config.getGridSettings();
|
||||
this.hourHeightPx = grid.hourHeight;
|
||||
|
|
@ -237,7 +237,7 @@ export class ResizeHandleManager {
|
|||
|
||||
// Emit resize:end event for re-stacking
|
||||
const eventId = this.targetEl.dataset.eventId || '';
|
||||
const resizeEndPayload: ResizeEndEventPayload = {
|
||||
const resizeEndPayload: IResizeEndEventPayload = {
|
||||
eventId,
|
||||
element: this.targetEl,
|
||||
finalHeight
|
||||
|
|
|
|||
|
|
@ -1,15 +1,15 @@
|
|||
import { CalendarView, IEventBus } from '../types/CalendarTypes';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { Configuration } from '../configuration/CalendarConfig';
|
||||
import { CoreEvents } from '../constants/CoreEvents';
|
||||
|
||||
|
||||
export class ViewManager {
|
||||
private eventBus: IEventBus;
|
||||
private config: CalendarConfig;
|
||||
private config: Configuration;
|
||||
private currentView: CalendarView = 'week';
|
||||
private buttonListeners: Map<Element, EventListener> = new Map();
|
||||
|
||||
constructor(eventBus: IEventBus, config: CalendarConfig) {
|
||||
constructor(eventBus: IEventBus, config: Configuration) {
|
||||
this.eventBus = eventBus;
|
||||
this.config = config;
|
||||
this.setupEventListeners();
|
||||
|
|
@ -143,4 +143,4 @@ export class ViewManager {
|
|||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
// Work hours management for per-column scheduling
|
||||
|
||||
import { DateService } from '../utils/DateService';
|
||||
import { CalendarConfig } from '../core/CalendarConfig';
|
||||
import { Configuration } from '../configuration/CalendarConfig';
|
||||
import { PositionUtils } from '../utils/PositionUtils';
|
||||
|
||||
/**
|
||||
* Work hours for a specific day
|
||||
*/
|
||||
export interface DayWorkHours {
|
||||
export interface IDayWorkHours {
|
||||
start: number; // Hour (0-23)
|
||||
end: number; // Hour (0-23)
|
||||
}
|
||||
|
|
@ -15,18 +15,18 @@ export interface DayWorkHours {
|
|||
/**
|
||||
* Work schedule configuration
|
||||
*/
|
||||
export interface WorkScheduleConfig {
|
||||
export interface IWorkScheduleConfig {
|
||||
weeklyDefault: {
|
||||
monday: DayWorkHours | 'off';
|
||||
tuesday: DayWorkHours | 'off';
|
||||
wednesday: DayWorkHours | 'off';
|
||||
thursday: DayWorkHours | 'off';
|
||||
friday: DayWorkHours | 'off';
|
||||
saturday: DayWorkHours | 'off';
|
||||
sunday: DayWorkHours | 'off';
|
||||
monday: IDayWorkHours | 'off';
|
||||
tuesday: IDayWorkHours | 'off';
|
||||
wednesday: IDayWorkHours | 'off';
|
||||
thursday: IDayWorkHours | 'off';
|
||||
friday: IDayWorkHours | 'off';
|
||||
saturday: IDayWorkHours | 'off';
|
||||
sunday: IDayWorkHours | 'off';
|
||||
};
|
||||
dateOverrides: {
|
||||
[dateString: string]: DayWorkHours | 'off'; // YYYY-MM-DD format
|
||||
[dateString: string]: IDayWorkHours | 'off'; // YYYY-MM-DD format
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -35,11 +35,11 @@ export interface WorkScheduleConfig {
|
|||
*/
|
||||
export class WorkHoursManager {
|
||||
private dateService: DateService;
|
||||
private config: CalendarConfig;
|
||||
private config: Configuration;
|
||||
private positionUtils: PositionUtils;
|
||||
private workSchedule: WorkScheduleConfig;
|
||||
private workSchedule: IWorkScheduleConfig;
|
||||
|
||||
constructor(dateService: DateService, config: CalendarConfig, positionUtils: PositionUtils) {
|
||||
constructor(dateService: DateService, config: Configuration, positionUtils: PositionUtils) {
|
||||
this.dateService = dateService;
|
||||
this.config = config;
|
||||
this.positionUtils = positionUtils;
|
||||
|
|
@ -66,7 +66,7 @@ export class WorkHoursManager {
|
|||
/**
|
||||
* Get work hours for a specific date
|
||||
*/
|
||||
getWorkHoursForDate(date: Date): DayWorkHours | 'off' {
|
||||
getWorkHoursForDate(date: Date): IDayWorkHours | 'off' {
|
||||
const dateString = this.dateService.formatISODate(date);
|
||||
|
||||
// Check for date-specific override first
|
||||
|
|
@ -82,8 +82,8 @@ export class WorkHoursManager {
|
|||
/**
|
||||
* Get work hours for multiple dates (used by GridManager)
|
||||
*/
|
||||
getWorkHoursForDateRange(dates: Date[]): Map<string, DayWorkHours | 'off'> {
|
||||
const workHoursMap = new Map<string, DayWorkHours | 'off'>();
|
||||
getWorkHoursForDateRange(dates: Date[]): Map<string, IDayWorkHours | 'off'> {
|
||||
const workHoursMap = new Map<string, IDayWorkHours | 'off'>();
|
||||
|
||||
dates.forEach(date => {
|
||||
const dateString = this.dateService.formatISODate(date);
|
||||
|
|
@ -97,7 +97,7 @@ export class WorkHoursManager {
|
|||
/**
|
||||
* Calculate CSS custom properties for non-work hour overlays using PositionUtils
|
||||
*/
|
||||
calculateNonWorkHoursStyle(workHours: DayWorkHours | 'off'): { beforeWorkHeight: number; afterWorkTop: number } | null {
|
||||
calculateNonWorkHoursStyle(workHours: IDayWorkHours | 'off'): { beforeWorkHeight: number; afterWorkTop: number } | null {
|
||||
if (workHours === 'off') {
|
||||
return null; // Full day will be colored via CSS background
|
||||
}
|
||||
|
|
@ -121,7 +121,7 @@ export class WorkHoursManager {
|
|||
/**
|
||||
* Calculate CSS custom properties for work hours overlay using PositionUtils
|
||||
*/
|
||||
calculateWorkHoursStyle(workHours: DayWorkHours | 'off'): { top: number; height: number } | null {
|
||||
calculateWorkHoursStyle(workHours: IDayWorkHours | 'off'): { top: number; height: number } | null {
|
||||
if (workHours === 'off') {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -139,24 +139,24 @@ export class WorkHoursManager {
|
|||
/**
|
||||
* Load work schedule from JSON (future implementation)
|
||||
*/
|
||||
async loadWorkSchedule(jsonData: WorkScheduleConfig): Promise<void> {
|
||||
async loadWorkSchedule(jsonData: IWorkScheduleConfig): Promise<void> {
|
||||
this.workSchedule = jsonData;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current work schedule configuration
|
||||
*/
|
||||
getWorkSchedule(): WorkScheduleConfig {
|
||||
getWorkSchedule(): IWorkScheduleConfig {
|
||||
return this.workSchedule;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert Date to day name key
|
||||
*/
|
||||
private getDayName(date: Date): keyof WorkScheduleConfig['weeklyDefault'] {
|
||||
const dayNames: (keyof WorkScheduleConfig['weeklyDefault'])[] = [
|
||||
private getDayName(date: Date): keyof IWorkScheduleConfig['weeklyDefault'] {
|
||||
const dayNames: (keyof IWorkScheduleConfig['weeklyDefault'])[] = [
|
||||
'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'
|
||||
];
|
||||
return dayNames[date.getDay()];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue