This commit is contained in:
Janus Knudsen 2025-08-09 01:16:04 +02:00
parent 59b3c64c55
commit b111f121ba
9 changed files with 200 additions and 694 deletions

View file

@ -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());

View file

@ -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'
};
}
/**

View file

@ -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
};
}
}

View file

@ -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');
// Keep only UI-related event listeners here if needed
// Data loading is now handled via direct method calls
}
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'
}
});
/**
* 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> {

View file

@ -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();
});
/**
* 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');
// 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
});
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);
// Emit event rendered
this.eventBus.emit(EventTypes.EVENT_RENDERED, {
count: events.filter(e => !e.allDay).length
// 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`);
}
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();
}
}

View file

@ -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`);
}
/**

View file

@ -24,22 +24,16 @@ export class ScrollManager {
this.subscribeToEvents();
}
private subscribeToEvents(): void {
// Initialize scroll when grid is rendered
eventBus.on(StateEvents.GRID_RENDERED, () => {
console.log('ScrollManager: Received GRID_RENDERED event');
this.setupScrolling();
});
/**
* Public method to initialize scroll after grid is rendered
*/
public initialize(): void {
console.log('ScrollManager: Initialize called, setting up scrolling');
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);
private subscribeToEvents(): void {
// State events removed - initialize() is now called directly after grid render
// Handle window resize
window.addEventListener('resize', () => {

View file

@ -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');

View file

@ -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;
}
}