wip
This commit is contained in:
parent
59b3c64c55
commit
b111f121ba
9 changed files with 200 additions and 694 deletions
26
src/index.ts
26
src/index.ts
|
|
@ -11,10 +11,10 @@ import { calendarConfig } from './core/CalendarConfig.js';
|
|||
import { CalendarTypeFactory } from './factories/CalendarTypeFactory.js';
|
||||
|
||||
/**
|
||||
* Initialize the calendar application with new state-driven approach
|
||||
* Initialize the calendar application with simple direct calls
|
||||
*/
|
||||
async function initializeCalendar(): Promise<void> {
|
||||
console.log('🗓️ Initializing Calendar Plantempus with state management...');
|
||||
console.log('🗓️ Initializing Calendar Plantempus with simple coordination...');
|
||||
|
||||
// Declare managers outside try block for global access
|
||||
let calendarManager: CalendarManager;
|
||||
|
|
@ -30,30 +30,28 @@ async function initializeCalendar(): Promise<void> {
|
|||
const config = calendarConfig;
|
||||
|
||||
// Initialize the CalendarTypeFactory before creating managers
|
||||
console.log('🏭 Phase 0: Initializing CalendarTypeFactory...');
|
||||
console.log('🏭 Initializing CalendarTypeFactory...');
|
||||
CalendarTypeFactory.initialize();
|
||||
|
||||
// Initialize managers in proper order
|
||||
console.log('📋 Phase 1: Creating core managers...');
|
||||
// Create all managers
|
||||
console.log('📋 Creating managers...');
|
||||
calendarManager = new CalendarManager(eventBus, config);
|
||||
navigationManager = new NavigationManager(eventBus);
|
||||
viewManager = new ViewManager(eventBus);
|
||||
|
||||
console.log('🎯 Phase 2: Creating data and rendering managers...');
|
||||
// These managers will now respond to state-driven events
|
||||
eventManager = new EventManager(eventBus);
|
||||
eventRenderer = new EventRenderer(eventBus);
|
||||
gridManager = new GridManager();
|
||||
scrollManager = new ScrollManager();
|
||||
|
||||
console.log('🏗️ Phase 3: Creating layout managers...');
|
||||
scrollManager = new ScrollManager(); // Will respond to GRID_RENDERED
|
||||
gridManager = new GridManager(); // Will respond to RENDERING_STARTED
|
||||
// Set manager references in CalendarManager
|
||||
calendarManager.setManagers(eventManager, gridManager, eventRenderer, scrollManager);
|
||||
|
||||
// Enable debug mode for development
|
||||
eventBus.setDebug(true);
|
||||
|
||||
// Initialize all managers using state-driven coordination
|
||||
console.log('🚀 Phase 4: Starting state-driven initialization...');
|
||||
await calendarManager.initialize(); // Now async and fully coordinated
|
||||
// Initialize using simple direct calls
|
||||
console.log('🚀 Starting simple initialization...');
|
||||
await calendarManager.initialize();
|
||||
|
||||
console.log('🎊 Calendar Plantempus initialized successfully!');
|
||||
console.log('📊 Initialization Report:', calendarManager.getInitializationReport());
|
||||
|
|
|
|||
|
|
@ -2,17 +2,22 @@ import { EventBus } from '../core/EventBus.js';
|
|||
import { EventTypes } from '../constants/EventTypes.js';
|
||||
import { CalendarConfig } from '../core/CalendarConfig.js';
|
||||
import { CalendarEvent, CalendarView, IEventBus } from '../types/CalendarTypes.js';
|
||||
import { CalendarStateManager } from './CalendarStateManager.js';
|
||||
import { StateEvents } from '../types/CalendarState.js';
|
||||
import { EventManager } from './EventManager.js';
|
||||
import { GridManager } from './GridManager.js';
|
||||
import { EventRenderer } from './EventRenderer.js';
|
||||
import { ScrollManager } from './ScrollManager.js';
|
||||
|
||||
/**
|
||||
* CalendarManager - Main coordinator for all calendar managers
|
||||
* Now delegates initialization to CalendarStateManager for better coordination
|
||||
* Uses simple direct method calls instead of complex state management
|
||||
*/
|
||||
export class CalendarManager {
|
||||
private eventBus: IEventBus;
|
||||
private config: CalendarConfig;
|
||||
private stateManager: CalendarStateManager;
|
||||
private eventManager: EventManager;
|
||||
private gridManager: GridManager;
|
||||
private eventRenderer: EventRenderer;
|
||||
private scrollManager: ScrollManager;
|
||||
private currentView: CalendarView = 'week';
|
||||
private currentDate: Date = new Date();
|
||||
private isInitialized: boolean = false;
|
||||
|
|
@ -20,13 +25,22 @@ export class CalendarManager {
|
|||
constructor(eventBus: IEventBus, config: CalendarConfig) {
|
||||
this.eventBus = eventBus;
|
||||
this.config = config;
|
||||
this.stateManager = new CalendarStateManager();
|
||||
this.setupEventListeners();
|
||||
console.log('📋 CalendarManager: Created with state management');
|
||||
console.log('📋 CalendarManager: Created with direct coordination');
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize calendar system using state-driven approach
|
||||
* Set manager references (called from index.ts)
|
||||
*/
|
||||
public setManagers(eventManager: EventManager, gridManager: GridManager, eventRenderer: EventRenderer, scrollManager: ScrollManager): void {
|
||||
this.eventManager = eventManager;
|
||||
this.gridManager = gridManager;
|
||||
this.eventRenderer = eventRenderer;
|
||||
this.scrollManager = scrollManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize calendar system using simple direct calls
|
||||
*/
|
||||
public async initialize(): Promise<void> {
|
||||
if (this.isInitialized) {
|
||||
|
|
@ -34,22 +48,37 @@ export class CalendarManager {
|
|||
return;
|
||||
}
|
||||
|
||||
console.log('🚀 CalendarManager: Starting state-driven initialization');
|
||||
console.log('🚀 CalendarManager: Starting simple initialization');
|
||||
|
||||
try {
|
||||
// Delegate to StateManager for coordinated initialization
|
||||
await this.stateManager.initialize();
|
||||
// Step 1: Load data
|
||||
console.log('📊 Loading event data...');
|
||||
await this.eventManager.loadData();
|
||||
|
||||
// Set initial view and date after successful initialization
|
||||
// Step 2: Render grid structure
|
||||
console.log('🏗️ Rendering grid...');
|
||||
await this.gridManager.render();
|
||||
|
||||
// Step 3: Initialize scroll synchronization
|
||||
console.log('📜 Setting up scroll synchronization...');
|
||||
this.scrollManager.initialize();
|
||||
|
||||
// Step 4: Set initial view and date BEFORE event rendering
|
||||
console.log('⚙️ Setting initial view and date...');
|
||||
this.setView(this.currentView);
|
||||
this.setCurrentDate(this.currentDate);
|
||||
|
||||
// Step 5: Render events (after view is set)
|
||||
console.log('🎨 Rendering events...');
|
||||
const events = this.eventManager.getEvents();
|
||||
await this.eventRenderer.renderEvents(events);
|
||||
|
||||
this.isInitialized = true;
|
||||
console.log('✅ CalendarManager: Initialization complete');
|
||||
console.log('✅ CalendarManager: Simple initialization complete');
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ CalendarManager initialization failed:', error);
|
||||
throw error; // Let the caller handle the error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -139,28 +168,19 @@ export class CalendarManager {
|
|||
* Check om calendar er initialiseret
|
||||
*/
|
||||
public isCalendarInitialized(): boolean {
|
||||
return this.isInitialized && this.stateManager.isReady();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current calendar state
|
||||
*/
|
||||
public getCurrentState(): string {
|
||||
return this.stateManager.getCurrentState();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get state manager for advanced operations
|
||||
*/
|
||||
public getStateManager(): CalendarStateManager {
|
||||
return this.stateManager;
|
||||
return this.isInitialized;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get initialization report for debugging
|
||||
*/
|
||||
public getInitializationReport(): any {
|
||||
return this.stateManager.getInitializationReport();
|
||||
return {
|
||||
isInitialized: this.isInitialized,
|
||||
currentView: this.currentView,
|
||||
currentDate: this.currentDate,
|
||||
initializationTime: 'N/A - simple initialization'
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,471 +0,0 @@
|
|||
// Calendar state management and coordination
|
||||
|
||||
import { eventBus } from '../core/EventBus';
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
import {
|
||||
CalendarState,
|
||||
StateEvents,
|
||||
CalendarEvent,
|
||||
StateChangeEvent,
|
||||
ErrorEvent,
|
||||
VALID_STATE_TRANSITIONS,
|
||||
InitializationPhase,
|
||||
STATE_TO_PHASE
|
||||
} from '../types/CalendarState';
|
||||
|
||||
/**
|
||||
* Central coordinator for calendar initialization and state management
|
||||
* Ensures proper sequencing and eliminates race conditions
|
||||
*/
|
||||
export class CalendarStateManager {
|
||||
private currentState: CalendarState = CalendarState.UNINITIALIZED;
|
||||
private stateHistory: Array<{ state: CalendarState; timestamp: number }> = [];
|
||||
private initializationStartTime: number = 0;
|
||||
private phaseTimings: Map<InitializationPhase, { start: number; end?: number }> = new Map();
|
||||
|
||||
constructor() {
|
||||
console.log('📋 CalendarStateManager: Created');
|
||||
this.recordStateChange(CalendarState.UNINITIALIZED);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current calendar state
|
||||
*/
|
||||
getCurrentState(): CalendarState {
|
||||
return this.currentState;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if calendar is in ready state
|
||||
*/
|
||||
isReady(): boolean {
|
||||
return this.currentState === CalendarState.READY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current initialization phase
|
||||
*/
|
||||
getCurrentPhase(): InitializationPhase {
|
||||
return STATE_TO_PHASE[this.currentState];
|
||||
}
|
||||
|
||||
/**
|
||||
* Main initialization method - coordinates all calendar setup
|
||||
*/
|
||||
async initialize(): Promise<void> {
|
||||
console.log('🚀 CalendarStateManager: Starting calendar initialization');
|
||||
this.initializationStartTime = Date.now();
|
||||
|
||||
try {
|
||||
// Phase 1: Configuration loading (blocks everything else)
|
||||
await this.executeConfigurationPhase();
|
||||
|
||||
// Phase 2: Parallel data loading and DOM structure setup
|
||||
await this.executeDataAndDOMPhase();
|
||||
|
||||
// Phase 3: Event rendering (requires both data and DOM)
|
||||
await this.executeEventRenderingPhase();
|
||||
|
||||
// Phase 4: Finalization
|
||||
await this.executeFinalizationPhase();
|
||||
|
||||
const totalTime = Date.now() - this.initializationStartTime;
|
||||
console.log(`🎊 Calendar initialization complete in ${totalTime}ms`);
|
||||
|
||||
} catch (error) {
|
||||
console.error('❌ Calendar initialization failed:', error);
|
||||
await this.handleInitializationError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 1: Configuration Loading
|
||||
* Must complete before any other operations
|
||||
*/
|
||||
private async executeConfigurationPhase(): Promise<void> {
|
||||
console.log('📖 Phase 1: Configuration Loading');
|
||||
await this.transitionTo(CalendarState.INITIALIZING);
|
||||
|
||||
this.startPhase(InitializationPhase.CONFIGURATION);
|
||||
|
||||
// Emit config loading started
|
||||
this.emitEvent(StateEvents.CONFIG_LOADING_STARTED, 'CalendarStateManager', {
|
||||
configSource: 'URL and DOM attributes'
|
||||
});
|
||||
|
||||
// Configuration is already loaded in CalendarConfig constructor
|
||||
// but we validate and emit the completion event
|
||||
const configValid = this.validateConfiguration();
|
||||
|
||||
if (!configValid) {
|
||||
throw new Error('Invalid calendar configuration');
|
||||
}
|
||||
|
||||
this.emitEvent(StateEvents.CONFIG_LOADED, 'CalendarStateManager', {
|
||||
calendarMode: calendarConfig.getCalendarMode(),
|
||||
dateViewSettings: calendarConfig.getDateViewSettings(),
|
||||
gridSettings: calendarConfig.getGridSettings()
|
||||
});
|
||||
|
||||
await this.transitionTo(CalendarState.CONFIG_LOADED);
|
||||
this.endPhase(InitializationPhase.CONFIGURATION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 2: Parallel Data Loading and DOM Setup
|
||||
* These can run concurrently to improve performance
|
||||
*/
|
||||
private async executeDataAndDOMPhase(): Promise<void> {
|
||||
console.log('📊 Phase 2: Data Loading and DOM Setup (Parallel)');
|
||||
this.startPhase(InitializationPhase.DATA_AND_DOM);
|
||||
|
||||
// Start both data loading and rendering setup in parallel
|
||||
const dataPromise = this.coordinateDataLoading();
|
||||
const domPromise = this.coordinateDOMSetup();
|
||||
|
||||
// Wait for both to complete
|
||||
await Promise.all([dataPromise, domPromise]);
|
||||
|
||||
this.endPhase(InitializationPhase.DATA_AND_DOM);
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinate data loading process
|
||||
*/
|
||||
private async coordinateDataLoading(): Promise<void> {
|
||||
await this.transitionTo(CalendarState.DATA_LOADING);
|
||||
|
||||
this.emitEvent(StateEvents.DATA_LOADING_STARTED, 'CalendarStateManager', {
|
||||
mode: calendarConfig.getCalendarMode(),
|
||||
period: this.getCurrentPeriod()
|
||||
});
|
||||
|
||||
// EventManager will respond to DATA_LOADING_STARTED and load data
|
||||
// We wait for its DATA_LOADED response
|
||||
await this.waitForEvent(StateEvents.DATA_LOADED, 10000);
|
||||
|
||||
await this.transitionTo(CalendarState.DATA_LOADED);
|
||||
console.log('✅ Data loading phase complete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Coordinate DOM structure setup
|
||||
*/
|
||||
private async coordinateDOMSetup(): Promise<void> {
|
||||
await this.transitionTo(CalendarState.RENDERING);
|
||||
|
||||
this.emitEvent(StateEvents.RENDERING_STARTED, 'CalendarStateManager', {
|
||||
phase: 'DOM structure setup'
|
||||
});
|
||||
|
||||
// GridManager will respond to RENDERING_STARTED and create DOM structure
|
||||
// We wait for its GRID_RENDERED response
|
||||
await this.waitForEvent(StateEvents.GRID_RENDERED, 5000);
|
||||
|
||||
await this.transitionTo(CalendarState.RENDERED);
|
||||
console.log('✅ DOM setup phase complete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 3: Event Rendering
|
||||
* Requires both data and DOM to be ready
|
||||
*/
|
||||
private async executeEventRenderingPhase(): Promise<void> {
|
||||
console.log('🎨 Phase 3: Event Rendering');
|
||||
this.startPhase(InitializationPhase.EVENT_RENDERING);
|
||||
|
||||
// Both data and DOM are ready, trigger event rendering
|
||||
// EventRenderer will wait for both GRID_RENDERED and DATA_LOADED
|
||||
|
||||
// Wait for events to be rendered
|
||||
await this.waitForEvent(StateEvents.EVENTS_RENDERED, 3000);
|
||||
|
||||
this.emitEvent(StateEvents.RENDERING_COMPLETE, 'CalendarStateManager', {
|
||||
phase: 'Event rendering complete'
|
||||
});
|
||||
|
||||
this.endPhase(InitializationPhase.EVENT_RENDERING);
|
||||
console.log('✅ Event rendering phase complete');
|
||||
}
|
||||
|
||||
/**
|
||||
* Phase 4: Finalization
|
||||
* System is ready for user interaction
|
||||
*/
|
||||
private async executeFinalizationPhase(): Promise<void> {
|
||||
console.log('🏁 Phase 4: Finalization');
|
||||
this.startPhase(InitializationPhase.FINALIZATION);
|
||||
|
||||
await this.transitionTo(CalendarState.READY);
|
||||
|
||||
const totalTime = Date.now() - this.initializationStartTime;
|
||||
|
||||
this.emitEvent(StateEvents.CALENDAR_READY, 'CalendarStateManager', {
|
||||
initializationTime: totalTime,
|
||||
finalState: this.currentState,
|
||||
phaseTimings: this.getPhaseTimings()
|
||||
});
|
||||
|
||||
this.endPhase(InitializationPhase.FINALIZATION);
|
||||
console.log(`🎉 Calendar is ready! Total initialization time: ${totalTime}ms`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Transition to a new state with validation
|
||||
*/
|
||||
private async transitionTo(newState: CalendarState): Promise<void> {
|
||||
if (!this.isValidTransition(this.currentState, newState)) {
|
||||
const error = new Error(`Invalid state transition: ${this.currentState} → ${newState}`);
|
||||
await this.handleInitializationError(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const oldState = this.currentState;
|
||||
this.currentState = newState;
|
||||
this.recordStateChange(newState);
|
||||
|
||||
// Emit state change event
|
||||
const stateChangeEvent: StateChangeEvent = {
|
||||
type: StateEvents.CALENDAR_STATE_CHANGED,
|
||||
component: 'CalendarStateManager',
|
||||
timestamp: Date.now(),
|
||||
data: {
|
||||
from: oldState,
|
||||
to: newState,
|
||||
transitionValid: true
|
||||
},
|
||||
metadata: {
|
||||
phase: STATE_TO_PHASE[newState]
|
||||
}
|
||||
};
|
||||
|
||||
eventBus.emit(StateEvents.CALENDAR_STATE_CHANGED, stateChangeEvent);
|
||||
console.log(`📍 State: ${oldState} → ${newState} [${STATE_TO_PHASE[newState]}]`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate state transition
|
||||
*/
|
||||
private isValidTransition(from: CalendarState, to: CalendarState): boolean {
|
||||
const allowedTransitions = VALID_STATE_TRANSITIONS[from] || [];
|
||||
return allowedTransitions.includes(to);
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle initialization errors with recovery attempts
|
||||
*/
|
||||
private async handleInitializationError(error: Error): Promise<void> {
|
||||
console.error('💥 Initialization error:', error);
|
||||
|
||||
const errorEvent: ErrorEvent = {
|
||||
type: StateEvents.CALENDAR_ERROR,
|
||||
component: 'CalendarStateManager',
|
||||
error,
|
||||
timestamp: Date.now(),
|
||||
data: {
|
||||
failedComponent: 'CalendarStateManager',
|
||||
currentState: this.currentState,
|
||||
canRecover: this.canRecoverFromError(error)
|
||||
}
|
||||
};
|
||||
|
||||
eventBus.emit(StateEvents.CALENDAR_ERROR, errorEvent);
|
||||
|
||||
// Attempt recovery if possible
|
||||
if (this.canRecoverFromError(error)) {
|
||||
await this.attemptRecovery(error);
|
||||
} else {
|
||||
await this.transitionTo(CalendarState.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempt to recover from errors
|
||||
*/
|
||||
private async attemptRecovery(error: Error): Promise<void> {
|
||||
console.log('🔧 Attempting error recovery...');
|
||||
|
||||
this.emitEvent(StateEvents.RECOVERY_ATTEMPTED, 'CalendarStateManager', {
|
||||
error: error.message,
|
||||
currentState: this.currentState
|
||||
});
|
||||
|
||||
try {
|
||||
// Simple recovery strategy: try to continue from a stable state
|
||||
if (this.currentState === CalendarState.DATA_LOADING) {
|
||||
// Retry data loading
|
||||
await this.coordinateDataLoading();
|
||||
} else if (this.currentState === CalendarState.RENDERING) {
|
||||
// Retry DOM setup
|
||||
await this.coordinateDOMSetup();
|
||||
}
|
||||
|
||||
this.emitEvent(StateEvents.RECOVERY_SUCCESS, 'CalendarStateManager', {
|
||||
recoveredFrom: error.message
|
||||
});
|
||||
|
||||
} catch (recoveryError) {
|
||||
console.error('❌ Recovery failed:', recoveryError);
|
||||
|
||||
this.emitEvent(StateEvents.RECOVERY_FAILED, 'CalendarStateManager', {
|
||||
originalError: error.message,
|
||||
recoveryError: (recoveryError as Error).message
|
||||
});
|
||||
|
||||
await this.transitionTo(CalendarState.ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if error is recoverable
|
||||
*/
|
||||
private canRecoverFromError(error: Error): boolean {
|
||||
// Simple recovery logic - can be extended
|
||||
const recoverableErrors = [
|
||||
'timeout',
|
||||
'network',
|
||||
'dom not ready',
|
||||
'data loading failed'
|
||||
];
|
||||
|
||||
return recoverableErrors.some(pattern =>
|
||||
error.message.toLowerCase().includes(pattern)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate calendar configuration
|
||||
*/
|
||||
private validateConfiguration(): boolean {
|
||||
try {
|
||||
const mode = calendarConfig.getCalendarMode();
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
|
||||
// Basic validation
|
||||
if (!mode || !['date', 'resource'].includes(mode)) {
|
||||
console.error('Invalid calendar mode:', mode);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!gridSettings.hourHeight || gridSettings.hourHeight < 20) {
|
||||
console.error('Invalid hour height:', gridSettings.hourHeight);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error('Configuration validation failed:', error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current period for data loading
|
||||
*/
|
||||
private getCurrentPeriod(): { start: string; end: string } {
|
||||
const currentDate = calendarConfig.getSelectedDate() || new Date();
|
||||
const mode = calendarConfig.getCalendarMode();
|
||||
|
||||
if (mode === 'date') {
|
||||
const dateSettings = calendarConfig.getDateViewSettings();
|
||||
|
||||
if (dateSettings.period === 'week') {
|
||||
const weekStart = new Date(currentDate);
|
||||
weekStart.setDate(currentDate.getDate() - currentDate.getDay());
|
||||
const weekEnd = new Date(weekStart);
|
||||
weekEnd.setDate(weekStart.getDate() + 6);
|
||||
|
||||
return {
|
||||
start: weekStart.toISOString().split('T')[0],
|
||||
end: weekEnd.toISOString().split('T')[0]
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Default to current day
|
||||
return {
|
||||
start: currentDate.toISOString().split('T')[0],
|
||||
end: currentDate.toISOString().split('T')[0]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility methods
|
||||
*/
|
||||
private recordStateChange(state: CalendarState): void {
|
||||
this.stateHistory.push({
|
||||
state,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
private startPhase(phase: InitializationPhase): void {
|
||||
this.phaseTimings.set(phase, { start: Date.now() });
|
||||
}
|
||||
|
||||
private endPhase(phase: InitializationPhase): void {
|
||||
const timing = this.phaseTimings.get(phase);
|
||||
if (timing) {
|
||||
timing.end = Date.now();
|
||||
console.log(`⏱️ ${phase} completed in ${timing.end - timing.start}ms`);
|
||||
}
|
||||
}
|
||||
|
||||
private getPhaseTimings(): Record<string, number> {
|
||||
const timings: Record<string, number> = {};
|
||||
|
||||
this.phaseTimings.forEach((timing, phase) => {
|
||||
if (timing.start && timing.end) {
|
||||
timings[phase] = timing.end - timing.start;
|
||||
}
|
||||
});
|
||||
|
||||
return timings;
|
||||
}
|
||||
|
||||
private emitEvent(type: string, component: string, data?: any): void {
|
||||
const event: CalendarEvent = {
|
||||
type,
|
||||
component,
|
||||
timestamp: Date.now(),
|
||||
data,
|
||||
metadata: {
|
||||
phase: this.getCurrentPhase()
|
||||
}
|
||||
};
|
||||
|
||||
eventBus.emit(type, event);
|
||||
}
|
||||
|
||||
private async waitForEvent(eventType: string, timeout: number = 5000): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timer = setTimeout(() => {
|
||||
reject(new Error(`Timeout waiting for event: ${eventType}`));
|
||||
}, timeout);
|
||||
|
||||
const handler = (event: Event) => {
|
||||
clearTimeout(timer);
|
||||
resolve((event as CustomEvent).detail);
|
||||
eventBus.off(eventType, handler);
|
||||
};
|
||||
|
||||
eventBus.on(eventType, handler);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug methods
|
||||
*/
|
||||
getStateHistory(): Array<{ state: CalendarState; timestamp: number }> {
|
||||
return [...this.stateHistory];
|
||||
}
|
||||
|
||||
getInitializationReport(): any {
|
||||
return {
|
||||
currentState: this.currentState,
|
||||
totalTime: Date.now() - this.initializationStartTime,
|
||||
phaseTimings: this.getPhaseTimings(),
|
||||
stateHistory: this.stateHistory
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -20,45 +20,26 @@ export class EventManager {
|
|||
}
|
||||
|
||||
private setupEventListeners(): void {
|
||||
// Listen for state-driven data loading request
|
||||
this.eventBus.on(StateEvents.DATA_LOADING_STARTED, (e: Event) => {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
console.log('EventManager: Received DATA_LOADING_STARTED, starting data load');
|
||||
|
||||
this.loadMockData().then(() => {
|
||||
console.log('EventManager: loadMockData() completed, emitting DATA_LOADED');
|
||||
// Emit state-driven data loaded event
|
||||
this.eventBus.emit(StateEvents.DATA_LOADED, {
|
||||
type: StateEvents.DATA_LOADED,
|
||||
component: 'EventManager',
|
||||
timestamp: Date.now(),
|
||||
data: {
|
||||
eventCount: this.events.length,
|
||||
calendarMode: calendarConfig.getCalendarMode(),
|
||||
period: detail.data?.period || { start: '', end: '' },
|
||||
events: this.events // Include actual events for EventRenderer
|
||||
},
|
||||
metadata: {
|
||||
phase: 'data-loading'
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
console.error('EventManager: loadMockData() failed:', error);
|
||||
this.eventBus.emit(StateEvents.DATA_FAILED, {
|
||||
type: StateEvents.DATA_FAILED,
|
||||
component: 'EventManager',
|
||||
timestamp: Date.now(),
|
||||
error,
|
||||
metadata: {
|
||||
phase: 'data-loading'
|
||||
}
|
||||
});
|
||||
// Keep only UI-related event listeners here if needed
|
||||
// Data loading is now handled via direct method calls
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method to load data - called directly by CalendarManager
|
||||
*/
|
||||
public async loadData(): Promise<void> {
|
||||
console.log('EventManager: Loading data via direct call');
|
||||
await this.loadMockData();
|
||||
console.log(`EventManager: Data loaded successfully - ${this.events.length} events`);
|
||||
|
||||
// Debug: Log first few events
|
||||
if (this.events.length > 0) {
|
||||
console.log('EventManager: First event:', {
|
||||
title: this.events[0].title,
|
||||
start: this.events[0].start,
|
||||
end: this.events[0].end
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// Legacy event listeners removed - data is now managed via state-driven events only
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private async loadMockData(): Promise<void> {
|
||||
|
|
|
|||
|
|
@ -11,99 +11,31 @@ import { CalendarTypeFactory } from '../factories/CalendarTypeFactory';
|
|||
*/
|
||||
export class EventRenderer {
|
||||
private eventBus: IEventBus;
|
||||
private pendingEvents: CalendarEvent[] = [];
|
||||
private dataReady: boolean = false;
|
||||
private gridReady: boolean = false;
|
||||
|
||||
constructor(eventBus: IEventBus) {
|
||||
this.eventBus = eventBus;
|
||||
this.setupEventListeners();
|
||||
}
|
||||
|
||||
private setupEventListeners(): void {
|
||||
// Listen for state-driven data loaded event
|
||||
this.eventBus.on(StateEvents.DATA_LOADED, (event: Event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
// Events are in customEvent.detail (direct from StateEvent payload)
|
||||
const eventCount = customEvent.detail.data?.eventCount || 0;
|
||||
const events = customEvent.detail.data?.events || [];
|
||||
console.log('EventRenderer: Received DATA_LOADED with', eventCount, 'events');
|
||||
this.pendingEvents = events; // Store the actual events
|
||||
this.dataReady = true;
|
||||
this.tryRenderEvents();
|
||||
});
|
||||
|
||||
// Listen for state-driven grid rendered event
|
||||
this.eventBus.on(StateEvents.GRID_RENDERED, (event: Event) => {
|
||||
const customEvent = event as CustomEvent;
|
||||
console.log('EventRenderer: Received GRID_RENDERED');
|
||||
this.gridReady = true;
|
||||
this.tryRenderEvents();
|
||||
});
|
||||
|
||||
|
||||
this.eventBus.on(EventTypes.VIEW_RENDERED, () => {
|
||||
// Clear existing events when view changes
|
||||
this.clearEvents();
|
||||
});
|
||||
|
||||
// Handle calendar type changes
|
||||
this.eventBus.on(EventTypes.CALENDAR_TYPE_CHANGED, () => {
|
||||
// Re-render events with new strategy
|
||||
this.tryRenderEvents();
|
||||
});
|
||||
}
|
||||
|
||||
private tryRenderEvents(): void {
|
||||
// Only render if we have both data and grid ready
|
||||
console.log('EventRenderer: tryRenderEvents called', {
|
||||
dataReady: this.dataReady,
|
||||
gridReady: this.gridReady,
|
||||
pendingEvents: this.pendingEvents.length
|
||||
});
|
||||
/**
|
||||
* Public method to render events - called directly by CalendarManager
|
||||
*/
|
||||
public async renderEvents(events: CalendarEvent[]): Promise<void> {
|
||||
console.log('EventRenderer: Direct renderEvents called with', events.length, 'events');
|
||||
|
||||
if (!this.dataReady || !this.gridReady) {
|
||||
console.log('EventRenderer: Waiting - data ready:', this.dataReady, 'grid ready:', this.gridReady);
|
||||
// Debug: Check if we have any events
|
||||
if (events.length === 0) {
|
||||
console.warn('EventRenderer: No events to render');
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.pendingEvents.length > 0) {
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
let columnsSelector = calendarType === 'resource' ? 'swp-resource-column' : 'swp-day-column';
|
||||
const columns = document.querySelectorAll(columnsSelector);
|
||||
|
||||
console.log(`EventRenderer: Found ${columns.length} ${columnsSelector} elements for ${calendarType} calendar`);
|
||||
|
||||
if (columns.length > 0) {
|
||||
console.log('🎨 EventRenderer: Both data and grid ready, rendering events!');
|
||||
const eventCount = this.pendingEvents.length;
|
||||
this.renderEvents(this.pendingEvents);
|
||||
this.pendingEvents = []; // Clear pending events after rendering
|
||||
|
||||
// Emit events rendered event
|
||||
this.eventBus.emit(StateEvents.EVENTS_RENDERED, {
|
||||
type: StateEvents.EVENTS_RENDERED,
|
||||
component: 'EventRenderer',
|
||||
timestamp: Date.now(),
|
||||
data: {
|
||||
eventCount,
|
||||
calendarMode: calendarType,
|
||||
renderMethod: 'state-driven'
|
||||
},
|
||||
metadata: {
|
||||
phase: 'event-rendering'
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('EventRenderer: Grid not ready yet, columns not found');
|
||||
}
|
||||
} else {
|
||||
console.log('EventRenderer: No pending events to render');
|
||||
}
|
||||
}
|
||||
|
||||
private renderEvents(events: CalendarEvent[]): void {
|
||||
console.log('EventRenderer: renderEvents called with', events.length, 'events');
|
||||
// Debug: Log first event details
|
||||
console.log('EventRenderer: First event details:', {
|
||||
title: events[0].title,
|
||||
start: events[0].start,
|
||||
end: events[0].end,
|
||||
allDay: events[0].allDay
|
||||
});
|
||||
|
||||
// Get the appropriate event renderer strategy
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
|
|
@ -111,27 +43,62 @@ export class EventRenderer {
|
|||
|
||||
console.log(`EventRenderer: Using ${calendarType} event renderer strategy`);
|
||||
|
||||
// Debug: Check if columns exist
|
||||
const columns = document.querySelectorAll('swp-day-column');
|
||||
console.log(`EventRenderer: Found ${columns.length} day columns in DOM`);
|
||||
|
||||
// Use strategy to render events
|
||||
eventRenderer.renderEvents(events, calendarConfig);
|
||||
|
||||
// Debug: Check if events are actually in DOM after rendering
|
||||
setTimeout(() => {
|
||||
const allRenderedEvents = document.querySelectorAll('swp-event');
|
||||
console.log(`EventRenderer: DOM check - ${allRenderedEvents.length} swp-event elements found in DOM`);
|
||||
|
||||
if (allRenderedEvents.length > 0) {
|
||||
const firstEvent = allRenderedEvents[0] as HTMLElement;
|
||||
console.log('EventRenderer: First event DOM details:', {
|
||||
visible: firstEvent.offsetWidth > 0 && firstEvent.offsetHeight > 0,
|
||||
offsetParent: !!firstEvent.offsetParent,
|
||||
computedDisplay: window.getComputedStyle(firstEvent).display,
|
||||
computedVisibility: window.getComputedStyle(firstEvent).visibility,
|
||||
computedOpacity: window.getComputedStyle(firstEvent).opacity,
|
||||
parentElement: firstEvent.parentElement?.tagName
|
||||
});
|
||||
}
|
||||
}, 100);
|
||||
|
||||
console.log(`EventRenderer: Successfully rendered ${events.length} events`);
|
||||
}
|
||||
|
||||
// Emit event rendered
|
||||
this.eventBus.emit(EventTypes.EVENT_RENDERED, {
|
||||
count: events.filter(e => !e.allDay).length
|
||||
private setupEventListeners(): void {
|
||||
// Keep only UI-related event listeners
|
||||
this.eventBus.on(EventTypes.VIEW_RENDERED, () => {
|
||||
// Clear existing events when view changes
|
||||
this.clearEvents();
|
||||
});
|
||||
|
||||
// Handle calendar type changes
|
||||
this.eventBus.on(EventTypes.CALENDAR_TYPE_CHANGED, () => {
|
||||
// Re-render would need to be triggered by CalendarManager now
|
||||
this.clearEvents();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
private clearEvents(): void {
|
||||
console.warn(`🗑️ EventRenderer: clearEvents() called from EventRenderer manager`);
|
||||
const calendarType = calendarConfig.getCalendarMode();
|
||||
const eventRenderer = CalendarTypeFactory.getEventRenderer(calendarType);
|
||||
eventRenderer.clearEvents();
|
||||
}
|
||||
|
||||
public refresh(): void {
|
||||
this.tryRenderEvents();
|
||||
// Refresh would need to be coordinated by CalendarManager now
|
||||
this.clearEvents();
|
||||
}
|
||||
|
||||
public destroy(): void {
|
||||
this.pendingEvents = [];
|
||||
this.clearEvents();
|
||||
}
|
||||
}
|
||||
|
|
@ -54,15 +54,11 @@ export class GridManager {
|
|||
|
||||
private findElements(): void {
|
||||
this.grid = document.querySelector('swp-calendar-container');
|
||||
console.log('GridManager: findElements called, found swp-calendar-container:', !!this.grid);
|
||||
}
|
||||
|
||||
private subscribeToEvents(): void {
|
||||
// Listen for state-driven rendering start event
|
||||
eventBus.on(StateEvents.RENDERING_STARTED, (e: Event) => {
|
||||
const detail = (e as CustomEvent).detail;
|
||||
console.log('GridManager: Received RENDERING_STARTED, starting DOM structure setup');
|
||||
this.render();
|
||||
});
|
||||
// State-driven events removed - render() is now called directly by CalendarManager
|
||||
|
||||
// Re-render grid on config changes
|
||||
eventBus.on(EventTypes.CONFIG_UPDATE, (e: Event) => {
|
||||
|
|
@ -119,39 +115,23 @@ export class GridManager {
|
|||
}
|
||||
|
||||
/**
|
||||
* Render the complete grid structure
|
||||
* Render the complete grid structure - now returns Promise for direct calls
|
||||
*/
|
||||
render(): void {
|
||||
if (!this.grid) return;
|
||||
async render(): Promise<void> {
|
||||
if (!this.grid) {
|
||||
console.warn('GridManager: render() called but this.grid is null, re-finding elements');
|
||||
this.findElements();
|
||||
if (!this.grid) {
|
||||
throw new Error('GridManager: swp-calendar-container not found, cannot render');
|
||||
}
|
||||
}
|
||||
|
||||
console.log('GridManager: Starting render with grid element:', this.grid);
|
||||
this.updateGridStyles();
|
||||
this.renderGrid();
|
||||
|
||||
// Emit state-driven grid rendered event
|
||||
const columnCount = this.getColumnCount();
|
||||
console.log('GridManager: Emitting GRID_RENDERED event');
|
||||
|
||||
eventBus.emit(StateEvents.GRID_RENDERED, {
|
||||
type: StateEvents.GRID_RENDERED,
|
||||
component: 'GridManager',
|
||||
timestamp: Date.now(),
|
||||
data: {
|
||||
columnCount,
|
||||
gridMode: calendarConfig.getCalendarMode(),
|
||||
domElementsCreated: [
|
||||
'swp-header-spacer',
|
||||
'swp-time-axis',
|
||||
'swp-grid-container',
|
||||
'swp-calendar-header',
|
||||
'swp-scrollable-content'
|
||||
]
|
||||
},
|
||||
metadata: {
|
||||
phase: 'rendering'
|
||||
}
|
||||
});
|
||||
|
||||
console.log('GridManager: GRID_RENDERED event emitted');
|
||||
console.log(`GridManager: Render complete - created ${columnCount} columns`);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -24,22 +24,16 @@ export class ScrollManager {
|
|||
this.subscribeToEvents();
|
||||
}
|
||||
|
||||
/**
|
||||
* Public method to initialize scroll after grid is rendered
|
||||
*/
|
||||
public initialize(): void {
|
||||
console.log('ScrollManager: Initialize called, setting up scrolling');
|
||||
this.setupScrolling();
|
||||
}
|
||||
|
||||
private subscribeToEvents(): void {
|
||||
// Initialize scroll when grid is rendered
|
||||
eventBus.on(StateEvents.GRID_RENDERED, () => {
|
||||
console.log('ScrollManager: Received GRID_RENDERED event');
|
||||
this.setupScrolling();
|
||||
});
|
||||
|
||||
// Add safety check - if grid is already rendered when ScrollManager initializes
|
||||
// This prevents race condition where GridManager renders before ScrollManager subscribes
|
||||
//setTimeout(() => {
|
||||
// const existingGrid = document.querySelector('swp-calendar-container');
|
||||
// if (existingGrid && existingGrid.children.length > 0) {
|
||||
// console.log('ScrollManager: Grid already exists, setting up scrolling');
|
||||
// this.setupScrolling();
|
||||
// }
|
||||
//}, 0);
|
||||
// State events removed - initialize() is now called directly after grid render
|
||||
|
||||
// Handle window resize
|
||||
window.addEventListener('resize', () => {
|
||||
|
|
|
|||
|
|
@ -27,8 +27,8 @@ export class DateColumnRenderer implements ColumnRenderer {
|
|||
const { currentWeek, config } = context;
|
||||
|
||||
const dates = this.getWeekDates(currentWeek);
|
||||
const weekDays = config.get('weekDays');
|
||||
const daysToShow = dates.slice(0, weekDays);
|
||||
const dateSettings = config.getDateViewSettings();
|
||||
const daysToShow = dates.slice(0, dateSettings.weekDays);
|
||||
|
||||
console.log('DateColumnRenderer: About to render', daysToShow.length, 'date columns');
|
||||
|
||||
|
|
|
|||
|
|
@ -36,9 +36,14 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
if (column) {
|
||||
const eventsLayer = column.querySelector('swp-events-layer');
|
||||
if (eventsLayer) {
|
||||
console.log(`BaseEventRenderer: Rendering event "${event.title}" in events layer`);
|
||||
this.renderEvent(event, eventsLayer, config);
|
||||
|
||||
// Debug: Verify event was actually added
|
||||
const renderedEvents = eventsLayer.querySelectorAll('swp-event');
|
||||
console.log(`BaseEventRenderer: Events layer now has ${renderedEvents.length} events`);
|
||||
} else {
|
||||
console.warn('BaseEventRenderer: No events layer found in column for event', event.title);
|
||||
console.warn('BaseEventRenderer: No events layer found in column for event', event.title, 'Column:', column);
|
||||
}
|
||||
} else {
|
||||
console.warn('BaseEventRenderer: No column found for event', event.title);
|
||||
|
|
@ -71,14 +76,23 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
`;
|
||||
|
||||
container.appendChild(eventElement);
|
||||
|
||||
console.log(`BaseEventRenderer: Created event element for "${event.title}":`, {
|
||||
top: eventElement.style.top,
|
||||
height: eventElement.style.height,
|
||||
backgroundColor: eventElement.style.backgroundColor,
|
||||
position: eventElement.style.position,
|
||||
innerHTML: eventElement.innerHTML
|
||||
});
|
||||
}
|
||||
|
||||
protected calculateEventPosition(event: CalendarEvent, config: CalendarConfig): { top: number; height: number } {
|
||||
const startDate = new Date(event.start);
|
||||
const endDate = new Date(event.end);
|
||||
|
||||
const dayStartHour = config.get('dayStartHour');
|
||||
const hourHeight = config.get('hourHeight');
|
||||
const gridSettings = config.getGridSettings();
|
||||
const dayStartHour = gridSettings.dayStartHour;
|
||||
const hourHeight = gridSettings.hourHeight;
|
||||
|
||||
// Calculate minutes from visible day start
|
||||
const startMinutes = startDate.getHours() * 60 + startDate.getMinutes();
|
||||
|
|
@ -92,6 +106,17 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
const durationMinutes = endMinutes - startMinutes;
|
||||
const height = (durationMinutes / 60) * hourHeight;
|
||||
|
||||
console.log('Event positioning calculation:', {
|
||||
eventTime: `${startDate.getHours()}:${startDate.getMinutes()}`,
|
||||
startMinutes,
|
||||
endMinutes,
|
||||
dayStartMinutes,
|
||||
dayStartHour,
|
||||
hourHeight,
|
||||
top,
|
||||
height
|
||||
});
|
||||
|
||||
return { top, height };
|
||||
}
|
||||
|
||||
|
|
@ -107,6 +132,9 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
|
|||
|
||||
clearEvents(): void {
|
||||
const existingEvents = document.querySelectorAll('swp-event');
|
||||
if (existingEvents.length > 0) {
|
||||
console.warn(`🗑️ BaseEventRenderer: REMOVING ${existingEvents.length} events from DOM! Stack trace:`, new Error().stack);
|
||||
}
|
||||
existingEvents.forEach(event => event.remove());
|
||||
}
|
||||
}
|
||||
|
|
@ -119,7 +147,16 @@ export class DateEventRenderer extends BaseEventRenderer {
|
|||
const eventDate = new Date(event.start);
|
||||
const dateStr = DateUtils.formatDate(eventDate);
|
||||
const dayColumn = document.querySelector(`swp-day-column[data-date="${dateStr}"]`) as HTMLElement;
|
||||
console.log('DateEventRenderer: Looking for day column with date', dateStr, 'found:', !!dayColumn);
|
||||
|
||||
// Debug: Check all available columns
|
||||
const allColumns = document.querySelectorAll('swp-day-column');
|
||||
const availableDates = Array.from(allColumns).map(col => (col as HTMLElement).dataset.date);
|
||||
|
||||
console.log('DateEventRenderer: Event', event.title, 'start:', event.start);
|
||||
console.log('DateEventRenderer: Looking for date:', dateStr);
|
||||
console.log('DateEventRenderer: Available columns with dates:', availableDates);
|
||||
console.log('DateEventRenderer: Found column:', !!dayColumn);
|
||||
|
||||
return dayColumn;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue