Calendar/src/core/CalendarConfig.ts
Janus C. H. Knudsen fb48e410ea Refactors calendar configuration and DI container setup
Removes singleton export for CalendarConfig
Updates dependency injection to use type registration
Adds more flexible renderer and service configuration
Integrates CalendarConfig with DateService initialization
2025-10-30 22:05:06 +01:00

575 lines
No EOL
14 KiB
TypeScript

// Calendar configuration management
import { eventBus } from './EventBus';
import { CoreEvents } from '../constants/CoreEvents';
import { CalendarConfig as ICalendarConfig, ViewPeriod, CalendarMode } from '../types/CalendarTypes';
import { TimeFormatter, TimeFormatSettings } from '../utils/TimeFormatter';
/**
* All-day event layout constants
*/
export const ALL_DAY_CONSTANTS = {
EVENT_HEIGHT: 22, // Height of single all-day event
EVENT_GAP: 2, // Gap between stacked events
CONTAINER_PADDING: 4, // Container padding (top + bottom)
MAX_COLLAPSED_ROWS: 4, // Show 4 rows when collapsed (3 events + 1 indicator row)
get SINGLE_ROW_HEIGHT() {
return this.EVENT_HEIGHT + this.EVENT_GAP; // 28px
}
} as const;
/**
* Layout and timing settings for the calendar grid
*/
interface GridSettings {
// Time boundaries
dayStartHour: number;
dayEndHour: number;
workStartHour: number;
workEndHour: number;
// Layout settings
hourHeight: number;
snapInterval: number;
fitToWidth: boolean;
scrollToHour: number | null;
// Event grouping settings
gridStartThresholdMinutes: number; // ±N minutes for events to share grid columns
// Display options
showCurrentTime: boolean;
showWorkHours: boolean;
}
/**
* View settings for date-based calendar mode
*/
interface DateViewSettings {
period: ViewPeriod; // day/week/month
weekDays: number; // Number of days to show in week view
firstDayOfWeek: number; // 0=Sunday, 1=Monday
showAllDay: boolean; // Show all-day event row
}
/**
* Work week configuration settings
*/
interface WorkWeekSettings {
id: string;
workDays: number[]; // ISO 8601: [1,2,3,4,5] for mon-fri (1=Mon, 7=Sun)
totalDays: number; // 5
firstWorkDay: number; // ISO: 1 = Monday, 7 = Sunday
}
/**
* View settings for resource-based calendar mode
*/
interface ResourceViewSettings {
maxResources: number; // Maximum resources to display
showAvatars: boolean; // Display user avatars
avatarSize: number; // Avatar size in pixels
resourceNameFormat: 'full' | 'short'; // How to display names
showResourceDetails: boolean; // Show additional resource info
showAllDay: boolean; // Show all-day event row
}
/**
* Time format configuration settings
*/
interface TimeFormatConfig {
timezone: string;
use24HourFormat: boolean;
locale: string;
dateFormat: 'locale' | 'technical';
showSeconds: boolean;
}
/**
* Calendar configuration management
*/
export class CalendarConfig {
private config: ICalendarConfig;
private calendarMode: CalendarMode = 'date';
private selectedDate: Date | null = null;
private gridSettings: GridSettings;
private dateViewSettings: DateViewSettings;
private resourceViewSettings: ResourceViewSettings;
private currentWorkWeek: string = 'standard';
private timeFormatConfig: TimeFormatConfig;
constructor() {
this.config = {
// Scrollbar styling
scrollbarWidth: 16, // Width of scrollbar in pixels
scrollbarColor: '#666', // Scrollbar thumb color
scrollbarTrackColor: '#f0f0f0', // Scrollbar track color
scrollbarHoverColor: '#b53f7aff', // Scrollbar thumb hover color
scrollbarBorderRadius: 6, // Border radius for scrollbar thumb
// 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
};
// Grid display settings
this.gridSettings = {
hourHeight: 60,
dayStartHour: 0,
dayEndHour: 24,
workStartHour: 8,
workEndHour: 17,
snapInterval: 15,
gridStartThresholdMinutes: 30, // Events starting within ±15 min share grid columns
showCurrentTime: true,
showWorkHours: true,
fitToWidth: false,
scrollToHour: 8
};
// Date view settings
this.dateViewSettings = {
period: 'week',
weekDays: 7,
firstDayOfWeek: 1,
showAllDay: true
};
// Resource view settings
this.resourceViewSettings = {
maxResources: 10,
showAvatars: true,
avatarSize: 32,
resourceNameFormat: 'full',
showResourceDetails: true,
showAllDay: true
};
// Time format settings - default to Denmark with technical format
this.timeFormatConfig = {
timezone: 'Europe/Copenhagen',
use24HourFormat: true,
locale: 'da-DK',
dateFormat: 'technical',
showSeconds: false
};
// Set computed values
this.config.minEventDuration = this.gridSettings.snapInterval;
// Initialize TimeFormatter with default settings
TimeFormatter.configure(this.timeFormatConfig);
// Load calendar type from URL parameter
this.loadCalendarType();
// Load from data attributes
this.loadFromDOM();
}
/**
* Load calendar type and date from URL parameters
*/
private loadCalendarType(): void {
const urlParams = new URLSearchParams(window.location.search);
const typeParam = urlParams.get('type');
const dateParam = urlParams.get('date');
// Set calendar mode
if (typeParam === 'resource' || typeParam === 'date') {
this.calendarMode = typeParam;
} else {
this.calendarMode = 'date'; // Default
}
// Set selected date
if (dateParam) {
const parsedDate = new Date(dateParam);
if (!isNaN(parsedDate.getTime())) {
this.selectedDate = parsedDate;
} else {
this.selectedDate = new Date();
}
} else {
this.selectedDate = new Date(); // Default to today
}
}
/**
* 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;
// Update date view settings
if (attrs.view) this.dateViewSettings.period = attrs.view as ViewPeriod;
if (attrs.weekDays) this.dateViewSettings.weekDays = parseInt(attrs.weekDays);
// Update grid settings
if (attrs.snapInterval) this.gridSettings.snapInterval = parseInt(attrs.snapInterval);
if (attrs.dayStartHour) this.gridSettings.dayStartHour = parseInt(attrs.dayStartHour);
if (attrs.dayEndHour) this.gridSettings.dayEndHour = parseInt(attrs.dayEndHour);
if (attrs.hourHeight) this.gridSettings.hourHeight = parseInt(attrs.hourHeight);
if (attrs.fitToWidth !== undefined) this.gridSettings.fitToWidth = attrs.fitToWidth === 'true';
// Update computed values
this.config.minEventDuration = this.gridSettings.snapInterval;
}
/**
* 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 handled in specific update methods
// Emit config update event
eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
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.gridSettings.hourHeight / 60;
}
get totalHours(): number {
return this.gridSettings.dayEndHour - this.gridSettings.dayStartHour;
}
get totalMinutes(): number {
return this.totalHours * 60;
}
get slotsPerHour(): number {
return 60 / this.gridSettings.snapInterval;
}
get totalSlots(): number {
return this.totalHours * this.slotsPerHour;
}
get slotHeight(): number {
return this.gridSettings.hourHeight / this.slotsPerHour;
}
/**
* Validate snap interval
*/
isValidSnapInterval(interval: number): boolean {
return [5, 10, 15, 30, 60].includes(interval);
}
/**
* Get grid display settings
*/
getGridSettings(): GridSettings {
return { ...this.gridSettings };
}
/**
* Update grid display settings
*/
updateGridSettings(updates: Partial<GridSettings>): void {
this.gridSettings = { ...this.gridSettings, ...updates };
// Update computed values
if (updates.snapInterval) {
this.config.minEventDuration = updates.snapInterval;
}
// Grid settings changes trigger general refresh - avoid specific event
eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key: 'gridSettings',
value: this.gridSettings
});
}
/**
* Get date view settings
*/
getDateViewSettings(): DateViewSettings {
return { ...this.dateViewSettings };
}
/**
* Update date view settings
*/
updateDateViewSettings(updates: Partial<DateViewSettings>): void {
this.dateViewSettings = { ...this.dateViewSettings, ...updates };
// Date view settings changes trigger general refresh - avoid specific event
eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key: 'dateViewSettings',
value: this.dateViewSettings
});
}
/**
* Get resource view settings
*/
getResourceViewSettings(): ResourceViewSettings {
return { ...this.resourceViewSettings };
}
/**
* Update resource view settings
*/
updateResourceViewSettings(updates: Partial<ResourceViewSettings>): void {
this.resourceViewSettings = { ...this.resourceViewSettings, ...updates };
// Resource view settings changes trigger general refresh - avoid specific event
eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key: 'resourceViewSettings',
value: this.resourceViewSettings
});
}
/**
* Check if current mode is resource-based
*/
isResourceMode(): boolean {
return this.calendarMode === 'resource';
}
/**
* Check if current mode is date-based
*/
isDateMode(): boolean {
return this.calendarMode === 'date';
}
/**
* Get calendar mode
*/
getCalendarMode(): CalendarMode {
return this.calendarMode;
}
/**
* Set calendar mode
*/
setCalendarMode(mode: CalendarMode): void {
const oldMode = this.calendarMode;
this.calendarMode = mode;
// Emit calendar mode change event
eventBus.emit(CoreEvents.VIEW_CHANGED, {
oldType: oldMode,
newType: mode
});
}
/**
* Get selected date
*/
getSelectedDate(): Date | null {
return this.selectedDate;
}
/**
* Set selected date
* Note: Does not emit events - caller is responsible for event emission
*/
setSelectedDate(date: Date): void {
this.selectedDate = date;
}
/**
* Get work week presets
*/
private getWorkWeekPresets(): { [key: string]: WorkWeekSettings } {
return {
'standard': {
id: 'standard',
workDays: [1,2,3,4,5], // Monday-Friday (ISO)
totalDays: 5,
firstWorkDay: 1
},
'compressed': {
id: 'compressed',
workDays: [1,2,3,4], // Monday-Thursday (ISO)
totalDays: 4,
firstWorkDay: 1
},
'midweek': {
id: 'midweek',
workDays: [3,4,5], // Wednesday-Friday (ISO)
totalDays: 3,
firstWorkDay: 3
},
'weekend': {
id: 'weekend',
workDays: [6,7], // Saturday-Sunday (ISO)
totalDays: 2,
firstWorkDay: 6
},
'fullweek': {
id: 'fullweek',
workDays: [1,2,3,4,5,6,7], // Monday-Sunday (ISO)
totalDays: 7,
firstWorkDay: 1
}
};
}
/**
* Get current work week settings
*/
getWorkWeekSettings(): WorkWeekSettings {
const presets = this.getWorkWeekPresets();
return presets[this.currentWorkWeek] || presets['standard'];
}
/**
* Set work week preset
* Note: Does not emit events - caller is responsible for event emission
*/
setWorkWeek(workWeekId: string): void {
const presets = this.getWorkWeekPresets();
if (presets[workWeekId]) {
this.currentWorkWeek = workWeekId;
// Update dateViewSettings to match work week
this.dateViewSettings.weekDays = presets[workWeekId].totalDays;
}
}
/**
* Get current work week ID
*/
getCurrentWorkWeek(): string {
return this.currentWorkWeek;
}
/**
* Get time format settings
*/
getTimeFormatSettings(): TimeFormatConfig {
return { ...this.timeFormatConfig };
}
/**
* Update time format settings
*/
updateTimeFormatSettings(updates: Partial<TimeFormatConfig>): void {
this.timeFormatConfig = { ...this.timeFormatConfig, ...updates };
// Update TimeFormatter with new settings
TimeFormatter.configure(this.timeFormatConfig);
// Emit time format change event
eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
key: 'timeFormatSettings',
value: this.timeFormatConfig
});
}
/**
* Set timezone (convenience method)
*/
setTimezone(timezone: string): void {
this.updateTimeFormatSettings({ timezone });
}
/**
* Set 12/24 hour format (convenience method)
*/
set24HourFormat(use24Hour: boolean): void {
this.updateTimeFormatSettings({ use24HourFormat: use24Hour });
}
/**
* Get configured timezone
*/
getTimezone(): string {
return this.timeFormatConfig.timezone;
}
/**
* Get configured locale
*/
getLocale(): string {
return this.timeFormatConfig.locale;
}
/**
* Check if using 24-hour format
*/
is24HourFormat(): boolean {
return this.timeFormatConfig.use24HourFormat;
}
/**
* Set date format (convenience method)
*/
setDateFormat(format: 'locale' | 'technical'): void {
this.updateTimeFormatSettings({ dateFormat: format });
}
/**
* Set whether to show seconds (convenience method)
*/
setShowSeconds(show: boolean): void {
this.updateTimeFormatSettings({ showSeconds: show });
}
/**
* Get current date format
*/
getDateFormat(): 'locale' | 'technical' {
return this.timeFormatConfig.dateFormat;
}
}