Initial commit: Calendar Plantempus project setup with TypeScript, ASP.NET Core, and event-driven architecture
This commit is contained in:
commit
f06c02121c
38 changed files with 8233 additions and 0 deletions
191
src/core/CalendarConfig.ts
Normal file
191
src/core/CalendarConfig.ts
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
// Calendar configuration management
|
||||
|
||||
import { eventBus } from './EventBus';
|
||||
import { EventTypes } from '../constants/EventTypes';
|
||||
import { CalendarConfig as ICalendarConfig, ViewType } from '../types/CalendarTypes';
|
||||
|
||||
/**
|
||||
* View-specific settings interface
|
||||
*/
|
||||
interface ViewSettings {
|
||||
columns: number;
|
||||
showAllDay: boolean;
|
||||
scrollToHour: number | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calendar configuration management
|
||||
*/
|
||||
export class CalendarConfig {
|
||||
private config: ICalendarConfig;
|
||||
|
||||
constructor() {
|
||||
this.config = {
|
||||
// View settings
|
||||
view: 'week', // 'day' | 'week' | 'month'
|
||||
weekDays: 7, // 4-7 days for week view
|
||||
firstDayOfWeek: 1, // 0 = Sunday, 1 = Monday
|
||||
|
||||
// Time settings
|
||||
dayStartHour: 7, // Calendar starts at 7 AM
|
||||
dayEndHour: 19, // Calendar ends at 7 PM
|
||||
workStartHour: 8, // Work hours start
|
||||
workEndHour: 17, // Work hours end
|
||||
snapInterval: 15, // Minutes: 5, 10, 15, 30, 60
|
||||
|
||||
// Display settings
|
||||
hourHeight: 60, // Pixels per hour
|
||||
showCurrentTime: true,
|
||||
showWorkHours: true,
|
||||
|
||||
// Interaction settings
|
||||
allowDrag: true,
|
||||
allowResize: true,
|
||||
allowCreate: true,
|
||||
|
||||
// API settings
|
||||
apiEndpoint: '/api/events',
|
||||
dateFormat: 'YYYY-MM-DD',
|
||||
timeFormat: 'HH:mm',
|
||||
|
||||
// Feature flags
|
||||
enableSearch: true,
|
||||
enableTouch: true,
|
||||
|
||||
// Event defaults
|
||||
defaultEventDuration: 60, // Minutes
|
||||
minEventDuration: 15, // Will be same as snapInterval
|
||||
maxEventDuration: 480 // 8 hours
|
||||
};
|
||||
|
||||
// Set computed values
|
||||
this.config.minEventDuration = this.config.snapInterval;
|
||||
|
||||
// Load from data attributes
|
||||
this.loadFromDOM();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load configuration from DOM data attributes
|
||||
*/
|
||||
private loadFromDOM(): void {
|
||||
const calendar = document.querySelector('swp-calendar') as HTMLElement;
|
||||
if (!calendar) return;
|
||||
|
||||
// Read data attributes
|
||||
const attrs = calendar.dataset;
|
||||
|
||||
if (attrs.view) this.config.view = attrs.view as ViewType;
|
||||
if (attrs.weekDays) this.config.weekDays = parseInt(attrs.weekDays);
|
||||
if (attrs.snapInterval) this.config.snapInterval = parseInt(attrs.snapInterval);
|
||||
if (attrs.dayStartHour) this.config.dayStartHour = parseInt(attrs.dayStartHour);
|
||||
if (attrs.dayEndHour) this.config.dayEndHour = parseInt(attrs.dayEndHour);
|
||||
if (attrs.hourHeight) this.config.hourHeight = parseInt(attrs.hourHeight);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a config value
|
||||
*/
|
||||
get<K extends keyof ICalendarConfig>(key: K): ICalendarConfig[K] {
|
||||
return this.config[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a config value
|
||||
*/
|
||||
set<K extends keyof ICalendarConfig>(key: K, value: ICalendarConfig[K]): void {
|
||||
const oldValue = this.config[key];
|
||||
this.config[key] = value;
|
||||
|
||||
// Update computed values
|
||||
if (key === 'snapInterval') {
|
||||
this.config.minEventDuration = value as number;
|
||||
}
|
||||
|
||||
// Emit config update event
|
||||
eventBus.emit(EventTypes.CONFIG_UPDATE, {
|
||||
key,
|
||||
value,
|
||||
oldValue
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Update multiple config values
|
||||
*/
|
||||
update(updates: Partial<ICalendarConfig>): void {
|
||||
Object.entries(updates).forEach(([key, value]) => {
|
||||
this.set(key as keyof ICalendarConfig, value);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all config
|
||||
*/
|
||||
getAll(): ICalendarConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate derived values
|
||||
*/
|
||||
|
||||
get minuteHeight(): number {
|
||||
return this.config.hourHeight / 60;
|
||||
}
|
||||
|
||||
get totalHours(): number {
|
||||
return this.config.dayEndHour - this.config.dayStartHour;
|
||||
}
|
||||
|
||||
get totalMinutes(): number {
|
||||
return this.totalHours * 60;
|
||||
}
|
||||
|
||||
get slotsPerHour(): number {
|
||||
return 60 / this.config.snapInterval;
|
||||
}
|
||||
|
||||
get totalSlots(): number {
|
||||
return this.totalHours * this.slotsPerHour;
|
||||
}
|
||||
|
||||
get slotHeight(): number {
|
||||
return this.config.hourHeight / this.slotsPerHour;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate snap interval
|
||||
*/
|
||||
isValidSnapInterval(interval: number): boolean {
|
||||
return [5, 10, 15, 30, 60].includes(interval);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get view-specific settings
|
||||
*/
|
||||
getViewSettings(view: ViewType = this.config.view): ViewSettings {
|
||||
const settings: Record<ViewType, ViewSettings> = {
|
||||
day: {
|
||||
columns: 1,
|
||||
showAllDay: true,
|
||||
scrollToHour: 8
|
||||
},
|
||||
week: {
|
||||
columns: this.config.weekDays,
|
||||
showAllDay: true,
|
||||
scrollToHour: 8
|
||||
},
|
||||
month: {
|
||||
columns: 7,
|
||||
showAllDay: false,
|
||||
scrollToHour: null
|
||||
}
|
||||
};
|
||||
|
||||
return settings[view] || settings.week;
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const calendarConfig = new CalendarConfig();
|
||||
103
src/core/EventBus.ts
Normal file
103
src/core/EventBus.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
// Core EventBus using pure DOM CustomEvents
|
||||
import { EventLogEntry, ListenerEntry, IEventBus } from '../types/CalendarTypes';
|
||||
|
||||
/**
|
||||
* Central event dispatcher for calendar using DOM CustomEvents
|
||||
* Provides logging and debugging capabilities
|
||||
*/
|
||||
export class EventBus implements IEventBus {
|
||||
private eventLog: EventLogEntry[] = [];
|
||||
private debug: boolean = false;
|
||||
private listeners: Set<ListenerEntry> = new Set();
|
||||
|
||||
/**
|
||||
* Subscribe to an event via DOM addEventListener
|
||||
*/
|
||||
on(eventType: string, handler: EventListener, options?: AddEventListenerOptions): () => void {
|
||||
document.addEventListener(eventType, handler, options);
|
||||
|
||||
// Track for cleanup
|
||||
this.listeners.add({ eventType, handler, options });
|
||||
|
||||
// Return unsubscribe function
|
||||
return () => this.off(eventType, handler);
|
||||
}
|
||||
|
||||
/**
|
||||
* Subscribe to an event once
|
||||
*/
|
||||
once(eventType: string, handler: EventListener): () => void {
|
||||
return this.on(eventType, handler, { once: true });
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsubscribe from an event
|
||||
*/
|
||||
off(eventType: string, handler: EventListener): void {
|
||||
document.removeEventListener(eventType, handler);
|
||||
|
||||
// Remove from tracking
|
||||
for (const listener of this.listeners) {
|
||||
if (listener.eventType === eventType && listener.handler === handler) {
|
||||
this.listeners.delete(listener);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Emit an event via DOM CustomEvent
|
||||
*/
|
||||
emit(eventType: string, detail: any = {}): boolean {
|
||||
const event = new CustomEvent(eventType, {
|
||||
detail,
|
||||
bubbles: true,
|
||||
cancelable: true
|
||||
});
|
||||
|
||||
// Log event
|
||||
if (this.debug) {
|
||||
console.log(`📢 Event: ${eventType}`, detail);
|
||||
}
|
||||
|
||||
this.eventLog.push({
|
||||
type: eventType,
|
||||
detail,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
|
||||
// Emit on document (only DOM events now)
|
||||
return !document.dispatchEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get event history
|
||||
*/
|
||||
getEventLog(eventType?: string): EventLogEntry[] {
|
||||
if (eventType) {
|
||||
return this.eventLog.filter(e => e.type === eventType);
|
||||
}
|
||||
return this.eventLog;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable/disable debug mode
|
||||
*/
|
||||
setDebug(enabled: boolean): void {
|
||||
this.debug = enabled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all tracked listeners
|
||||
*/
|
||||
destroy(): void {
|
||||
for (const listener of this.listeners) {
|
||||
document.removeEventListener(listener.eventType, listener.handler);
|
||||
}
|
||||
this.listeners.clear();
|
||||
this.eventLog = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Create singleton instance
|
||||
export const eventBus = new EventBus();
|
||||
Loading…
Add table
Add a link
Reference in a new issue