Adds I-prefix to all interfaces
This commit is contained in:
parent
80aaab46f2
commit
8ec5f52872
44 changed files with 1731 additions and 1949 deletions
|
|
@ -10,7 +10,9 @@
|
||||||
"Bash(rm:*)",
|
"Bash(rm:*)",
|
||||||
"Bash(npm install:*)",
|
"Bash(npm install:*)",
|
||||||
"Bash(npm test)",
|
"Bash(npm test)",
|
||||||
"Bash(cat:*)"
|
"Bash(cat:*)",
|
||||||
|
"Bash(npm run test:run:*)",
|
||||||
|
"Bash(npx tsc)"
|
||||||
],
|
],
|
||||||
"deny": []
|
"deny": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
179
src/configuration/CalendarConfig.ts
Normal file
179
src/configuration/CalendarConfig.ts
Normal file
|
|
@ -0,0 +1,179 @@
|
||||||
|
import { ICalendarConfig } from './ICalendarConfig';
|
||||||
|
import { IGridSettings } from './GridSettings';
|
||||||
|
import { IDateViewSettings } from './DateViewSettings';
|
||||||
|
import { ITimeFormatConfig } from './TimeFormatConfig';
|
||||||
|
import { IWorkWeekSettings } from './WorkWeekSettings';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All-day event layout constants
|
||||||
|
*/
|
||||||
|
export const ALL_DAY_CONSTANTS = {
|
||||||
|
EVENT_HEIGHT: 22,
|
||||||
|
EVENT_GAP: 2,
|
||||||
|
CONTAINER_PADDING: 4,
|
||||||
|
MAX_COLLAPSED_ROWS: 4,
|
||||||
|
get SINGLE_ROW_HEIGHT() {
|
||||||
|
return this.EVENT_HEIGHT + this.EVENT_GAP; // 28px
|
||||||
|
}
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Work week presets
|
||||||
|
*/
|
||||||
|
export const WORK_WEEK_PRESETS: { [key: string]: IWorkWeekSettings } = {
|
||||||
|
'standard': {
|
||||||
|
id: 'standard',
|
||||||
|
workDays: [1, 2, 3, 4, 5],
|
||||||
|
totalDays: 5,
|
||||||
|
firstWorkDay: 1
|
||||||
|
},
|
||||||
|
'compressed': {
|
||||||
|
id: 'compressed',
|
||||||
|
workDays: [1, 2, 3, 4],
|
||||||
|
totalDays: 4,
|
||||||
|
firstWorkDay: 1
|
||||||
|
},
|
||||||
|
'midweek': {
|
||||||
|
id: 'midweek',
|
||||||
|
workDays: [3, 4, 5],
|
||||||
|
totalDays: 3,
|
||||||
|
firstWorkDay: 3
|
||||||
|
},
|
||||||
|
'weekend': {
|
||||||
|
id: 'weekend',
|
||||||
|
workDays: [6, 7],
|
||||||
|
totalDays: 2,
|
||||||
|
firstWorkDay: 6
|
||||||
|
},
|
||||||
|
'fullweek': {
|
||||||
|
id: 'fullweek',
|
||||||
|
workDays: [1, 2, 3, 4, 5, 6, 7],
|
||||||
|
totalDays: 7,
|
||||||
|
firstWorkDay: 1
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configuration - DTO container for all configuration
|
||||||
|
* Pure data object loaded from JSON via ConfigManager
|
||||||
|
*/
|
||||||
|
export class Configuration {
|
||||||
|
private static _instance: Configuration | null = null;
|
||||||
|
|
||||||
|
public config: ICalendarConfig;
|
||||||
|
public gridSettings: IGridSettings;
|
||||||
|
public dateViewSettings: IDateViewSettings;
|
||||||
|
public timeFormatConfig: ITimeFormatConfig;
|
||||||
|
public currentWorkWeek: string;
|
||||||
|
public selectedDate: Date;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
config: ICalendarConfig,
|
||||||
|
gridSettings: IGridSettings,
|
||||||
|
dateViewSettings: IDateViewSettings,
|
||||||
|
timeFormatConfig: ITimeFormatConfig,
|
||||||
|
currentWorkWeek: string,
|
||||||
|
selectedDate: Date = new Date()
|
||||||
|
) {
|
||||||
|
this.config = config;
|
||||||
|
this.gridSettings = gridSettings;
|
||||||
|
this.dateViewSettings = dateViewSettings;
|
||||||
|
this.timeFormatConfig = timeFormatConfig;
|
||||||
|
this.currentWorkWeek = currentWorkWeek;
|
||||||
|
this.selectedDate = selectedDate;
|
||||||
|
|
||||||
|
// Store as singleton instance for web components
|
||||||
|
Configuration._instance = this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current Configuration instance
|
||||||
|
* Used by web components that can't use dependency injection
|
||||||
|
*/
|
||||||
|
public static getInstance(): Configuration {
|
||||||
|
if (!Configuration._instance) {
|
||||||
|
throw new Error('Configuration has not been initialized. Call ConfigManager.load() first.');
|
||||||
|
}
|
||||||
|
return Configuration._instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Computed properties
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward compatibility getters
|
||||||
|
getGridSettings(): IGridSettings {
|
||||||
|
return this.gridSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDateViewSettings(): IDateViewSettings {
|
||||||
|
return this.dateViewSettings;
|
||||||
|
}
|
||||||
|
|
||||||
|
getWorkWeekSettings(): IWorkWeekSettings {
|
||||||
|
return WORK_WEEK_PRESETS[this.currentWorkWeek] || WORK_WEEK_PRESETS['standard'];
|
||||||
|
}
|
||||||
|
|
||||||
|
getCurrentWorkWeek(): string {
|
||||||
|
return this.currentWorkWeek;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimezone(): string {
|
||||||
|
return this.timeFormatConfig.timezone;
|
||||||
|
}
|
||||||
|
|
||||||
|
getLocale(): string {
|
||||||
|
return this.timeFormatConfig.locale;
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimeFormatSettings(): ITimeFormatConfig {
|
||||||
|
return this.timeFormatConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
is24HourFormat(): boolean {
|
||||||
|
return this.timeFormatConfig.use24HourFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
getDateFormat(): 'locale' | 'technical' {
|
||||||
|
return this.timeFormatConfig.dateFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
setWorkWeek(workWeekId: string): void {
|
||||||
|
if (WORK_WEEK_PRESETS[workWeekId]) {
|
||||||
|
this.currentWorkWeek = workWeekId;
|
||||||
|
this.dateViewSettings.weekDays = WORK_WEEK_PRESETS[workWeekId].totalDays;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setSelectedDate(date: Date): void {
|
||||||
|
this.selectedDate = date;
|
||||||
|
}
|
||||||
|
|
||||||
|
isValidSnapInterval(interval: number): boolean {
|
||||||
|
return [5, 10, 15, 30, 60].includes(interval);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Backward compatibility alias
|
||||||
|
export { Configuration as CalendarConfig };
|
||||||
55
src/configuration/ConfigManager.ts
Normal file
55
src/configuration/ConfigManager.ts
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import { Configuration } from './CalendarConfig';
|
||||||
|
import { ICalendarConfig } from './ICalendarConfig';
|
||||||
|
import { TimeFormatter } from '../utils/TimeFormatter';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ConfigManager - Static configuration loader
|
||||||
|
* Loads JSON and creates Configuration instance
|
||||||
|
*/
|
||||||
|
export class ConfigManager {
|
||||||
|
/**
|
||||||
|
* Load configuration from JSON and create Configuration instance
|
||||||
|
*/
|
||||||
|
static async load(): Promise<Configuration> {
|
||||||
|
const response = await fetch('/wwwroot/data/calendar-config.json');
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to load config: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Build main config
|
||||||
|
const mainConfig: ICalendarConfig = {
|
||||||
|
scrollbarWidth: data.scrollbar.width,
|
||||||
|
scrollbarColor: data.scrollbar.color,
|
||||||
|
scrollbarTrackColor: data.scrollbar.trackColor,
|
||||||
|
scrollbarHoverColor: data.scrollbar.hoverColor,
|
||||||
|
scrollbarBorderRadius: data.scrollbar.borderRadius,
|
||||||
|
allowDrag: data.interaction.allowDrag,
|
||||||
|
allowResize: data.interaction.allowResize,
|
||||||
|
allowCreate: data.interaction.allowCreate,
|
||||||
|
apiEndpoint: data.api.endpoint,
|
||||||
|
dateFormat: data.api.dateFormat,
|
||||||
|
timeFormat: data.api.timeFormat,
|
||||||
|
enableSearch: data.features.enableSearch,
|
||||||
|
enableTouch: data.features.enableTouch,
|
||||||
|
defaultEventDuration: data.eventDefaults.defaultEventDuration,
|
||||||
|
minEventDuration: data.gridSettings.snapInterval,
|
||||||
|
maxEventDuration: data.eventDefaults.maxEventDuration
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create Configuration instance
|
||||||
|
const config = new Configuration(
|
||||||
|
mainConfig,
|
||||||
|
data.gridSettings,
|
||||||
|
data.dateViewSettings,
|
||||||
|
data.timeFormatConfig,
|
||||||
|
data.currentWorkWeek
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configure TimeFormatter
|
||||||
|
TimeFormatter.configure(config.timeFormatConfig);
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
}
|
||||||
11
src/configuration/DateViewSettings.ts
Normal file
11
src/configuration/DateViewSettings.ts
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { ViewPeriod } from '../types/CalendarTypes';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* View settings for date-based calendar mode
|
||||||
|
*/
|
||||||
|
export interface IDateViewSettings {
|
||||||
|
period: ViewPeriod;
|
||||||
|
weekDays: number;
|
||||||
|
firstDayOfWeek: number;
|
||||||
|
showAllDay: boolean;
|
||||||
|
}
|
||||||
16
src/configuration/GridSettings.ts
Normal file
16
src/configuration/GridSettings.ts
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
/**
|
||||||
|
* Grid display settings interface
|
||||||
|
*/
|
||||||
|
export interface IGridSettings {
|
||||||
|
dayStartHour: number;
|
||||||
|
dayEndHour: number;
|
||||||
|
workStartHour: number;
|
||||||
|
workEndHour: number;
|
||||||
|
hourHeight: number;
|
||||||
|
snapInterval: number;
|
||||||
|
fitToWidth: boolean;
|
||||||
|
scrollToHour: number | null;
|
||||||
|
gridStartThresholdMinutes: number;
|
||||||
|
showCurrentTime: boolean;
|
||||||
|
showWorkHours: boolean;
|
||||||
|
}
|
||||||
30
src/configuration/ICalendarConfig.ts
Normal file
30
src/configuration/ICalendarConfig.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
/**
|
||||||
|
* Main calendar configuration interface
|
||||||
|
*/
|
||||||
|
export interface ICalendarConfig {
|
||||||
|
// Scrollbar styling
|
||||||
|
scrollbarWidth: number;
|
||||||
|
scrollbarColor: string;
|
||||||
|
scrollbarTrackColor: string;
|
||||||
|
scrollbarHoverColor: string;
|
||||||
|
scrollbarBorderRadius: number;
|
||||||
|
|
||||||
|
// Interaction settings
|
||||||
|
allowDrag: boolean;
|
||||||
|
allowResize: boolean;
|
||||||
|
allowCreate: boolean;
|
||||||
|
|
||||||
|
// API settings
|
||||||
|
apiEndpoint: string;
|
||||||
|
dateFormat: string;
|
||||||
|
timeFormat: string;
|
||||||
|
|
||||||
|
// Feature flags
|
||||||
|
enableSearch: boolean;
|
||||||
|
enableTouch: boolean;
|
||||||
|
|
||||||
|
// Event defaults
|
||||||
|
defaultEventDuration: number;
|
||||||
|
minEventDuration: number;
|
||||||
|
maxEventDuration: number;
|
||||||
|
}
|
||||||
10
src/configuration/TimeFormatConfig.ts
Normal file
10
src/configuration/TimeFormatConfig.ts
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
/**
|
||||||
|
* Time format configuration settings
|
||||||
|
*/
|
||||||
|
export interface ITimeFormatConfig {
|
||||||
|
timezone: string;
|
||||||
|
use24HourFormat: boolean;
|
||||||
|
locale: string;
|
||||||
|
dateFormat: 'locale' | 'technical';
|
||||||
|
showSeconds: boolean;
|
||||||
|
}
|
||||||
9
src/configuration/WorkWeekSettings.ts
Normal file
9
src/configuration/WorkWeekSettings.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Work week configuration settings
|
||||||
|
*/
|
||||||
|
export interface IWorkWeekSettings {
|
||||||
|
id: string;
|
||||||
|
workDays: number[];
|
||||||
|
totalDays: number;
|
||||||
|
firstWorkDay: number;
|
||||||
|
}
|
||||||
|
|
@ -1,436 +0,0 @@
|
||||||
// Calendar configuration management
|
|
||||||
// Pure static configuration class - no dependencies, no events
|
|
||||||
|
|
||||||
import { ICalendarConfig, ViewPeriod } 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
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Time format configuration settings
|
|
||||||
*/
|
|
||||||
interface TimeFormatConfig {
|
|
||||||
timezone: string;
|
|
||||||
use24HourFormat: boolean;
|
|
||||||
locale: string;
|
|
||||||
dateFormat: 'locale' | 'technical';
|
|
||||||
showSeconds: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calendar configuration management - Pure static config
|
|
||||||
*/
|
|
||||||
export class CalendarConfig {
|
|
||||||
private static config: ICalendarConfig = {
|
|
||||||
// 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
|
|
||||||
};
|
|
||||||
|
|
||||||
private static selectedDate: Date | null = new Date();
|
|
||||||
private static currentWorkWeek: string = 'standard';
|
|
||||||
|
|
||||||
// Grid display settings
|
|
||||||
private static gridSettings: 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
|
|
||||||
private static dateViewSettings: DateViewSettings = {
|
|
||||||
period: 'week',
|
|
||||||
weekDays: 7,
|
|
||||||
firstDayOfWeek: 1,
|
|
||||||
showAllDay: true
|
|
||||||
};
|
|
||||||
|
|
||||||
// Time format settings - default to Denmark with technical format
|
|
||||||
private static timeFormatConfig: TimeFormatConfig = {
|
|
||||||
timezone: 'Europe/Copenhagen',
|
|
||||||
use24HourFormat: true,
|
|
||||||
locale: 'da-DK',
|
|
||||||
dateFormat: 'technical',
|
|
||||||
showSeconds: false
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize configuration - called once at startup
|
|
||||||
*/
|
|
||||||
static initialize(): void {
|
|
||||||
// Set computed values
|
|
||||||
CalendarConfig.config.minEventDuration = CalendarConfig.gridSettings.snapInterval;
|
|
||||||
|
|
||||||
// Initialize TimeFormatter with default settings
|
|
||||||
TimeFormatter.configure(CalendarConfig.timeFormatConfig);
|
|
||||||
|
|
||||||
// Load from data attributes
|
|
||||||
CalendarConfig.loadFromDOM();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load configuration from DOM data attributes
|
|
||||||
*/
|
|
||||||
private static 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) CalendarConfig.dateViewSettings.period = attrs.view as ViewPeriod;
|
|
||||||
if (attrs.weekDays) CalendarConfig.dateViewSettings.weekDays = parseInt(attrs.weekDays);
|
|
||||||
|
|
||||||
// Update grid settings
|
|
||||||
if (attrs.snapInterval) CalendarConfig.gridSettings.snapInterval = parseInt(attrs.snapInterval);
|
|
||||||
if (attrs.dayStartHour) CalendarConfig.gridSettings.dayStartHour = parseInt(attrs.dayStartHour);
|
|
||||||
if (attrs.dayEndHour) CalendarConfig.gridSettings.dayEndHour = parseInt(attrs.dayEndHour);
|
|
||||||
if (attrs.hourHeight) CalendarConfig.gridSettings.hourHeight = parseInt(attrs.hourHeight);
|
|
||||||
if (attrs.fitToWidth !== undefined) CalendarConfig.gridSettings.fitToWidth = attrs.fitToWidth === 'true';
|
|
||||||
|
|
||||||
// Update computed values
|
|
||||||
CalendarConfig.config.minEventDuration = CalendarConfig.gridSettings.snapInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a config value
|
|
||||||
*/
|
|
||||||
static get<K extends keyof ICalendarConfig>(key: K): ICalendarConfig[K] {
|
|
||||||
return CalendarConfig.config[key];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a config value (no events - use ConfigManager for updates with events)
|
|
||||||
*/
|
|
||||||
static set<K extends keyof ICalendarConfig>(key: K, value: ICalendarConfig[K]): void {
|
|
||||||
CalendarConfig.config[key] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update multiple config values (no events - use ConfigManager for updates with events)
|
|
||||||
*/
|
|
||||||
static update(updates: Partial<ICalendarConfig>): void {
|
|
||||||
Object.entries(updates).forEach(([key, value]) => {
|
|
||||||
CalendarConfig.set(key as keyof ICalendarConfig, value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get all config
|
|
||||||
*/
|
|
||||||
static getAll(): ICalendarConfig {
|
|
||||||
return { ...CalendarConfig.config };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate derived values
|
|
||||||
*/
|
|
||||||
|
|
||||||
static get minuteHeight(): number {
|
|
||||||
return CalendarConfig.gridSettings.hourHeight / 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get totalHours(): number {
|
|
||||||
return CalendarConfig.gridSettings.dayEndHour - CalendarConfig.gridSettings.dayStartHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get totalMinutes(): number {
|
|
||||||
return CalendarConfig.totalHours * 60;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get slotsPerHour(): number {
|
|
||||||
return 60 / CalendarConfig.gridSettings.snapInterval;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get totalSlots(): number {
|
|
||||||
return CalendarConfig.totalHours * CalendarConfig.slotsPerHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
static get slotHeight(): number {
|
|
||||||
return CalendarConfig.gridSettings.hourHeight / CalendarConfig.slotsPerHour;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Validate snap interval
|
|
||||||
*/
|
|
||||||
static isValidSnapInterval(interval: number): boolean {
|
|
||||||
return [5, 10, 15, 30, 60].includes(interval);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get grid display settings
|
|
||||||
*/
|
|
||||||
static getGridSettings(): GridSettings {
|
|
||||||
return { ...CalendarConfig.gridSettings };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update grid display settings (no events - use ConfigManager for updates with events)
|
|
||||||
*/
|
|
||||||
static updateGridSettings(updates: Partial<GridSettings>): void {
|
|
||||||
CalendarConfig.gridSettings = { ...CalendarConfig.gridSettings, ...updates };
|
|
||||||
|
|
||||||
// Update computed values
|
|
||||||
if (updates.snapInterval) {
|
|
||||||
CalendarConfig.config.minEventDuration = updates.snapInterval;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get date view settings
|
|
||||||
*/
|
|
||||||
static getDateViewSettings(): DateViewSettings {
|
|
||||||
return { ...CalendarConfig.dateViewSettings };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get selected date
|
|
||||||
*/
|
|
||||||
static getSelectedDate(): Date | null {
|
|
||||||
return CalendarConfig.selectedDate;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set selected date
|
|
||||||
* Note: Does not emit events - caller is responsible for event emission
|
|
||||||
*/
|
|
||||||
static setSelectedDate(date: Date): void {
|
|
||||||
CalendarConfig.selectedDate = date;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get work week presets
|
|
||||||
*/
|
|
||||||
private static 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
|
|
||||||
*/
|
|
||||||
static getWorkWeekSettings(): WorkWeekSettings {
|
|
||||||
const presets = CalendarConfig.getWorkWeekPresets();
|
|
||||||
return presets[CalendarConfig.currentWorkWeek] || presets['standard'];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set work week preset
|
|
||||||
* Note: Does not emit events - caller is responsible for event emission
|
|
||||||
*/
|
|
||||||
static setWorkWeek(workWeekId: string): void {
|
|
||||||
const presets = CalendarConfig.getWorkWeekPresets();
|
|
||||||
if (presets[workWeekId]) {
|
|
||||||
CalendarConfig.currentWorkWeek = workWeekId;
|
|
||||||
|
|
||||||
// Update dateViewSettings to match work week
|
|
||||||
CalendarConfig.dateViewSettings.weekDays = presets[workWeekId].totalDays;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current work week ID
|
|
||||||
*/
|
|
||||||
static getCurrentWorkWeek(): string {
|
|
||||||
return CalendarConfig.currentWorkWeek;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get time format settings
|
|
||||||
*/
|
|
||||||
static getTimeFormatSettings(): TimeFormatConfig {
|
|
||||||
return { ...CalendarConfig.timeFormatConfig };
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get configured timezone
|
|
||||||
*/
|
|
||||||
static getTimezone(): string {
|
|
||||||
return CalendarConfig.timeFormatConfig.timezone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get configured locale
|
|
||||||
*/
|
|
||||||
static getLocale(): string {
|
|
||||||
return CalendarConfig.timeFormatConfig.locale;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if using 24-hour format
|
|
||||||
*/
|
|
||||||
static is24HourFormat(): boolean {
|
|
||||||
return CalendarConfig.timeFormatConfig.use24HourFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get current date format
|
|
||||||
*/
|
|
||||||
static getDateFormat(): 'locale' | 'technical' {
|
|
||||||
return CalendarConfig.timeFormatConfig.dateFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load configuration from JSON
|
|
||||||
*/
|
|
||||||
static loadFromJSON(json: string): void {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(json);
|
|
||||||
if (data.gridSettings) CalendarConfig.updateGridSettings(data.gridSettings);
|
|
||||||
if (data.dateViewSettings) CalendarConfig.dateViewSettings = { ...CalendarConfig.dateViewSettings, ...data.dateViewSettings };
|
|
||||||
if (data.timeFormatConfig) {
|
|
||||||
CalendarConfig.timeFormatConfig = { ...CalendarConfig.timeFormatConfig, ...data.timeFormatConfig };
|
|
||||||
TimeFormatter.configure(CalendarConfig.timeFormatConfig);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Failed to load config from JSON:', error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ========================================================================
|
|
||||||
// Instance method wrappers for backward compatibility
|
|
||||||
// These allow injected CalendarConfig to work with existing code
|
|
||||||
// ========================================================================
|
|
||||||
|
|
||||||
get(key: keyof ICalendarConfig) { return CalendarConfig.get(key); }
|
|
||||||
set(key: keyof ICalendarConfig, value: any) { return CalendarConfig.set(key, value); }
|
|
||||||
update(updates: Partial<ICalendarConfig>) { return CalendarConfig.update(updates); }
|
|
||||||
getAll() { return CalendarConfig.getAll(); }
|
|
||||||
get minuteHeight() { return CalendarConfig.minuteHeight; }
|
|
||||||
get totalHours() { return CalendarConfig.totalHours; }
|
|
||||||
get totalMinutes() { return CalendarConfig.totalMinutes; }
|
|
||||||
get slotsPerHour() { return CalendarConfig.slotsPerHour; }
|
|
||||||
get totalSlots() { return CalendarConfig.totalSlots; }
|
|
||||||
get slotHeight() { return CalendarConfig.slotHeight; }
|
|
||||||
isValidSnapInterval(interval: number) { return CalendarConfig.isValidSnapInterval(interval); }
|
|
||||||
getGridSettings() { return CalendarConfig.getGridSettings(); }
|
|
||||||
updateGridSettings(updates: Partial<GridSettings>) { return CalendarConfig.updateGridSettings(updates); }
|
|
||||||
getDateViewSettings() { return CalendarConfig.getDateViewSettings(); }
|
|
||||||
getSelectedDate() { return CalendarConfig.getSelectedDate(); }
|
|
||||||
setSelectedDate(date: Date) { return CalendarConfig.setSelectedDate(date); }
|
|
||||||
getWorkWeekSettings() { return CalendarConfig.getWorkWeekSettings(); }
|
|
||||||
setWorkWeek(workWeekId: string) { return CalendarConfig.setWorkWeek(workWeekId); }
|
|
||||||
getCurrentWorkWeek() { return CalendarConfig.getCurrentWorkWeek(); }
|
|
||||||
getTimeFormatSettings() { return CalendarConfig.getTimeFormatSettings(); }
|
|
||||||
getTimezone() { return CalendarConfig.getTimezone(); }
|
|
||||||
getLocale() { return CalendarConfig.getLocale(); }
|
|
||||||
is24HourFormat() { return CalendarConfig.is24HourFormat(); }
|
|
||||||
getDateFormat() { return CalendarConfig.getDateFormat(); }
|
|
||||||
}
|
|
||||||
|
|
@ -1,14 +1,14 @@
|
||||||
// Core EventBus using pure DOM CustomEvents
|
// Core EventBus using pure DOM CustomEvents
|
||||||
import { EventLogEntry, ListenerEntry, IEventBus } from '../types/CalendarTypes';
|
import { IEventLogEntry, IListenerEntry, IEventBus } from '../types/CalendarTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Central event dispatcher for calendar using DOM CustomEvents
|
* Central event dispatcher for calendar using DOM CustomEvents
|
||||||
* Provides logging and debugging capabilities
|
* Provides logging and debugging capabilities
|
||||||
*/
|
*/
|
||||||
export class EventBus implements IEventBus {
|
export class EventBus implements IEventBus {
|
||||||
private eventLog: EventLogEntry[] = [];
|
private eventLog: IEventLogEntry[] = [];
|
||||||
private debug: boolean = false;
|
private debug: boolean = false;
|
||||||
private listeners: Set<ListenerEntry> = new Set();
|
private listeners: Set<IListenerEntry> = new Set();
|
||||||
|
|
||||||
// Log configuration for different categories
|
// Log configuration for different categories
|
||||||
private logConfig: { [key: string]: boolean } = {
|
private logConfig: { [key: string]: boolean } = {
|
||||||
|
|
@ -161,7 +161,7 @@ export class EventBus implements IEventBus {
|
||||||
/**
|
/**
|
||||||
* Get event history
|
* Get event history
|
||||||
*/
|
*/
|
||||||
getEventLog(eventType?: string): EventLogEntry[] {
|
getEventLog(eventType?: string): IEventLogEntry[] {
|
||||||
if (eventType) {
|
if (eventType) {
|
||||||
return this.eventLog.filter(e => e.type === eventType);
|
return this.eventLog.filter(e => e.type === eventType);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { TimeFormatter } from '../utils/TimeFormatter';
|
import { TimeFormatter } from '../utils/TimeFormatter';
|
||||||
import { PositionUtils } from '../utils/PositionUtils';
|
import { PositionUtils } from '../utils/PositionUtils';
|
||||||
import { DateService } from '../utils/DateService';
|
import { DateService } from '../utils/DateService';
|
||||||
|
|
@ -9,12 +9,12 @@ import { DateService } from '../utils/DateService';
|
||||||
*/
|
*/
|
||||||
export abstract class BaseSwpEventElement extends HTMLElement {
|
export abstract class BaseSwpEventElement extends HTMLElement {
|
||||||
protected dateService: DateService;
|
protected dateService: DateService;
|
||||||
protected config: CalendarConfig;
|
protected config: Configuration;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
// TODO: Find better solution for web component DI
|
// Get singleton instance for web components (can't use DI)
|
||||||
this.config = new CalendarConfig();
|
this.config = Configuration.getInstance();
|
||||||
this.dateService = new DateService(this.config);
|
this.dateService = new DateService(this.config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -256,11 +256,11 @@ export class SwpEventElement extends BaseSwpEventElement {
|
||||||
// ============================================
|
// ============================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create SwpEventElement from CalendarEvent
|
* Create SwpEventElement from ICalendarEvent
|
||||||
*/
|
*/
|
||||||
public static fromCalendarEvent(event: CalendarEvent): SwpEventElement {
|
public static fromCalendarEvent(event: ICalendarEvent): SwpEventElement {
|
||||||
const element = document.createElement('swp-event') as SwpEventElement;
|
const element = document.createElement('swp-event') as SwpEventElement;
|
||||||
const config = new CalendarConfig();
|
const config = Configuration.getInstance();
|
||||||
const dateService = new DateService(config);
|
const dateService = new DateService(config);
|
||||||
|
|
||||||
element.dataset.eventId = event.id;
|
element.dataset.eventId = event.id;
|
||||||
|
|
@ -274,9 +274,9 @@ export class SwpEventElement extends BaseSwpEventElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract CalendarEvent from DOM element
|
* Extract ICalendarEvent from DOM element
|
||||||
*/
|
*/
|
||||||
public static extractCalendarEventFromElement(element: HTMLElement): CalendarEvent {
|
public static extractCalendarEventFromElement(element: HTMLElement): ICalendarEvent {
|
||||||
return {
|
return {
|
||||||
id: element.dataset.eventId || '',
|
id: element.dataset.eventId || '',
|
||||||
title: element.dataset.title || '',
|
title: element.dataset.title || '',
|
||||||
|
|
@ -331,11 +331,11 @@ export class SwpAllDayEventElement extends BaseSwpEventElement {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create from CalendarEvent
|
* Create from ICalendarEvent
|
||||||
*/
|
*/
|
||||||
public static fromCalendarEvent(event: CalendarEvent): SwpAllDayEventElement {
|
public static fromCalendarEvent(event: ICalendarEvent): SwpAllDayEventElement {
|
||||||
const element = document.createElement('swp-allday-event') as SwpAllDayEventElement;
|
const element = document.createElement('swp-allday-event') as SwpAllDayEventElement;
|
||||||
const config = new CalendarConfig();
|
const config = Configuration.getInstance();
|
||||||
const dateService = new DateService(config);
|
const dateService = new DateService(config);
|
||||||
|
|
||||||
element.dataset.eventId = event.id;
|
element.dataset.eventId = event.id;
|
||||||
|
|
|
||||||
25
src/index.ts
25
src/index.ts
|
|
@ -1,7 +1,8 @@
|
||||||
// Main entry point for Calendar Plantempus
|
// Main entry point for Calendar Plantempus
|
||||||
import { Container } from '@novadi/core';
|
import { Container } from '@novadi/core';
|
||||||
import { eventBus } from './core/EventBus';
|
import { eventBus } from './core/EventBus';
|
||||||
import { CalendarConfig } from './core/CalendarConfig';
|
import { ConfigManager } from './configuration/ConfigManager';
|
||||||
|
import { Configuration } from './configuration/CalendarConfig';
|
||||||
import { URLManager } from './utils/URLManager';
|
import { URLManager } from './utils/URLManager';
|
||||||
import { IEventBus } from './types/CalendarTypes';
|
import { IEventBus } from './types/CalendarTypes';
|
||||||
|
|
||||||
|
|
@ -19,7 +20,6 @@ import { ResizeHandleManager } from './managers/ResizeHandleManager';
|
||||||
import { EdgeScrollManager } from './managers/EdgeScrollManager';
|
import { EdgeScrollManager } from './managers/EdgeScrollManager';
|
||||||
import { DragHoverManager } from './managers/DragHoverManager';
|
import { DragHoverManager } from './managers/DragHoverManager';
|
||||||
import { HeaderManager } from './managers/HeaderManager';
|
import { HeaderManager } from './managers/HeaderManager';
|
||||||
import { ConfigManager } from './managers/ConfigManager';
|
|
||||||
|
|
||||||
// Import repositories
|
// Import repositories
|
||||||
import { IEventRepository } from './repositories/IEventRepository';
|
import { IEventRepository } from './repositories/IEventRepository';
|
||||||
|
|
@ -27,7 +27,7 @@ import { MockEventRepository } from './repositories/MockEventRepository';
|
||||||
|
|
||||||
// Import renderers
|
// Import renderers
|
||||||
import { DateHeaderRenderer, type IHeaderRenderer } from './renderers/DateHeaderRenderer';
|
import { DateHeaderRenderer, type IHeaderRenderer } from './renderers/DateHeaderRenderer';
|
||||||
import { DateColumnRenderer, type ColumnRenderer } from './renderers/ColumnRenderer';
|
import { DateColumnRenderer, type IColumnRenderer } from './renderers/ColumnRenderer';
|
||||||
import { DateEventRenderer, type IEventRenderer } from './renderers/EventRenderer';
|
import { DateEventRenderer, type IEventRenderer } from './renderers/EventRenderer';
|
||||||
import { AllDayEventRenderer } from './renderers/AllDayEventRenderer';
|
import { AllDayEventRenderer } from './renderers/AllDayEventRenderer';
|
||||||
import { GridRenderer } from './renderers/GridRenderer';
|
import { GridRenderer } from './renderers/GridRenderer';
|
||||||
|
|
@ -70,8 +70,8 @@ async function handleDeepLinking(eventManager: EventManager, urlManager: URLMana
|
||||||
*/
|
*/
|
||||||
async function initializeCalendar(): Promise<void> {
|
async function initializeCalendar(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Initialize static calendar configuration
|
// Load configuration from JSON
|
||||||
CalendarConfig.initialize();
|
const config = await ConfigManager.load();
|
||||||
|
|
||||||
// Create NovaDI container
|
// Create NovaDI container
|
||||||
const container = new Container();
|
const container = new Container();
|
||||||
|
|
@ -80,21 +80,18 @@ async function initializeCalendar(): Promise<void> {
|
||||||
// Enable debug mode for development
|
// Enable debug mode for development
|
||||||
eventBus.setDebug(true);
|
eventBus.setDebug(true);
|
||||||
|
|
||||||
// Register CalendarConfig as singleton instance (static class, not instantiated)
|
|
||||||
builder.registerInstance(CalendarConfig).as<CalendarConfig>();
|
|
||||||
|
|
||||||
// Register ConfigManager for event-driven config updates
|
|
||||||
builder.registerType(ConfigManager).as<ConfigManager>();
|
|
||||||
|
|
||||||
// Bind core services as instances
|
// Bind core services as instances
|
||||||
builder.registerInstance(eventBus).as<IEventBus>();
|
builder.registerInstance(eventBus).as<IEventBus>();
|
||||||
|
|
||||||
|
// Register configuration instance
|
||||||
|
builder.registerInstance(config).as<Configuration>();
|
||||||
|
|
||||||
// Register repositories
|
// Register repositories
|
||||||
builder.registerType(MockEventRepository).as<IEventRepository>();
|
builder.registerType(MockEventRepository).as<IEventRepository>();
|
||||||
|
|
||||||
// Register renderers
|
// Register renderers
|
||||||
builder.registerType(DateHeaderRenderer).as<IHeaderRenderer>();
|
builder.registerType(DateHeaderRenderer).as<IHeaderRenderer>();
|
||||||
builder.registerType(DateColumnRenderer).as<ColumnRenderer>();
|
builder.registerType(DateColumnRenderer).as<IColumnRenderer>();
|
||||||
builder.registerType(DateEventRenderer).as<IEventRenderer>();
|
builder.registerType(DateEventRenderer).as<IEventRenderer>();
|
||||||
|
|
||||||
// Register core services and utilities
|
// Register core services and utilities
|
||||||
|
|
@ -130,7 +127,6 @@ async function initializeCalendar(): Promise<void> {
|
||||||
|
|
||||||
// Get managers from container
|
// Get managers from container
|
||||||
const eb = app.resolveType<IEventBus>();
|
const eb = app.resolveType<IEventBus>();
|
||||||
const configManager = app.resolveType<ConfigManager>();
|
|
||||||
const calendarManager = app.resolveType<CalendarManager>();
|
const calendarManager = app.resolveType<CalendarManager>();
|
||||||
const eventManager = app.resolveType<EventManager>();
|
const eventManager = app.resolveType<EventManager>();
|
||||||
const resizeHandleManager = app.resolveType<ResizeHandleManager>();
|
const resizeHandleManager = app.resolveType<ResizeHandleManager>();
|
||||||
|
|
@ -143,9 +139,6 @@ async function initializeCalendar(): Promise<void> {
|
||||||
const allDayManager = app.resolveType<AllDayManager>();
|
const allDayManager = app.resolveType<AllDayManager>();
|
||||||
const urlManager = app.resolveType<URLManager>();
|
const urlManager = app.resolveType<URLManager>();
|
||||||
|
|
||||||
// Initialize CSS variables before any rendering
|
|
||||||
configManager.initialize();
|
|
||||||
|
|
||||||
// Initialize managers
|
// Initialize managers
|
||||||
await calendarManager.initialize?.();
|
await calendarManager.initialize?.();
|
||||||
await resizeHandleManager.initialize?.();
|
await resizeHandleManager.initialize?.();
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,21 @@
|
||||||
// All-day row height management and animations
|
// All-day row height management and animations
|
||||||
|
|
||||||
import { eventBus } from '../core/EventBus';
|
import { eventBus } from '../core/EventBus';
|
||||||
import { ALL_DAY_CONSTANTS } from '../core/CalendarConfig';
|
import { ALL_DAY_CONSTANTS } from '../configuration/CalendarConfig';
|
||||||
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
import { AllDayEventRenderer } from '../renderers/AllDayEventRenderer';
|
||||||
import { AllDayLayoutEngine, EventLayout } from '../utils/AllDayLayoutEngine';
|
import { AllDayLayoutEngine, IEventLayout } from '../utils/AllDayLayoutEngine';
|
||||||
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
import { IColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
|
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
|
||||||
import {
|
import {
|
||||||
DragMouseEnterHeaderEventPayload,
|
IDragMouseEnterHeaderEventPayload,
|
||||||
DragStartEventPayload,
|
IDragStartEventPayload,
|
||||||
DragMoveEventPayload,
|
IDragMoveEventPayload,
|
||||||
DragEndEventPayload,
|
IDragEndEventPayload,
|
||||||
DragColumnChangeEventPayload,
|
IDragColumnChangeEventPayload,
|
||||||
HeaderReadyEventPayload
|
IHeaderReadyEventPayload
|
||||||
} from '../types/EventTypes';
|
} from '../types/EventTypes';
|
||||||
import { DragOffset, MousePosition } from '../types/DragDropTypes';
|
import { IDragOffset, IMousePosition } from '../types/DragDropTypes';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { EventManager } from './EventManager';
|
import { EventManager } from './EventManager';
|
||||||
import { differenceInCalendarDays } from 'date-fns';
|
import { differenceInCalendarDays } from 'date-fns';
|
||||||
|
|
@ -33,10 +33,10 @@ export class AllDayManager {
|
||||||
private layoutEngine: AllDayLayoutEngine | null = null;
|
private layoutEngine: AllDayLayoutEngine | null = null;
|
||||||
|
|
||||||
// State tracking for differential updates
|
// State tracking for differential updates
|
||||||
private currentLayouts: EventLayout[] = [];
|
private currentLayouts: IEventLayout[] = [];
|
||||||
private currentAllDayEvents: CalendarEvent[] = [];
|
private currentAllDayEvents: ICalendarEvent[] = [];
|
||||||
private currentWeekDates: ColumnBounds[] = [];
|
private currentWeekDates: IColumnBounds[] = [];
|
||||||
private newLayouts: EventLayout[] = [];
|
private newLayouts: IEventLayout[] = [];
|
||||||
|
|
||||||
// Expand/collapse state
|
// Expand/collapse state
|
||||||
private isExpanded: boolean = false;
|
private isExpanded: boolean = false;
|
||||||
|
|
@ -62,7 +62,7 @@ export class AllDayManager {
|
||||||
*/
|
*/
|
||||||
private setupEventListeners(): void {
|
private setupEventListeners(): void {
|
||||||
eventBus.on('drag:mouseenter-header', (event) => {
|
eventBus.on('drag:mouseenter-header', (event) => {
|
||||||
const payload = (event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
|
const payload = (event as CustomEvent<IDragMouseEnterHeaderEventPayload>).detail;
|
||||||
|
|
||||||
if (payload.draggedClone.hasAttribute('data-allday'))
|
if (payload.draggedClone.hasAttribute('data-allday'))
|
||||||
return;
|
return;
|
||||||
|
|
@ -87,7 +87,7 @@ export class AllDayManager {
|
||||||
|
|
||||||
// Listen for drag operations on all-day events
|
// Listen for drag operations on all-day events
|
||||||
eventBus.on('drag:start', (event) => {
|
eventBus.on('drag:start', (event) => {
|
||||||
let payload: DragStartEventPayload = (event as CustomEvent<DragStartEventPayload>).detail;
|
let payload: IDragStartEventPayload = (event as CustomEvent<IDragStartEventPayload>).detail;
|
||||||
|
|
||||||
if (!payload.draggedClone?.hasAttribute('data-allday')) {
|
if (!payload.draggedClone?.hasAttribute('data-allday')) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -97,7 +97,7 @@ export class AllDayManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
eventBus.on('drag:column-change', (event) => {
|
eventBus.on('drag:column-change', (event) => {
|
||||||
let payload: DragColumnChangeEventPayload = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
let payload: IDragColumnChangeEventPayload = (event as CustomEvent<IDragColumnChangeEventPayload>).detail;
|
||||||
|
|
||||||
if (!payload.draggedClone?.hasAttribute('data-allday')) {
|
if (!payload.draggedClone?.hasAttribute('data-allday')) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -107,7 +107,7 @@ export class AllDayManager {
|
||||||
});
|
});
|
||||||
|
|
||||||
eventBus.on('drag:end', (event) => {
|
eventBus.on('drag:end', (event) => {
|
||||||
let draggedElement: DragEndEventPayload = (event as CustomEvent<DragEndEventPayload>).detail;
|
let draggedElement: IDragEndEventPayload = (event as CustomEvent<IDragEndEventPayload>).detail;
|
||||||
|
|
||||||
if (draggedElement.target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore.
|
if (draggedElement.target != 'swp-day-header') // we are not inside the swp-day-header, so just ignore.
|
||||||
return;
|
return;
|
||||||
|
|
@ -128,12 +128,12 @@ export class AllDayManager {
|
||||||
|
|
||||||
// Listen for header ready - when dates are populated with period data
|
// Listen for header ready - when dates are populated with period data
|
||||||
eventBus.on('header:ready', (event: Event) => {
|
eventBus.on('header:ready', (event: Event) => {
|
||||||
let headerReadyEventPayload = (event as CustomEvent<HeaderReadyEventPayload>).detail;
|
let headerReadyEventPayload = (event as CustomEvent<IHeaderReadyEventPayload>).detail;
|
||||||
|
|
||||||
let startDate = new Date(headerReadyEventPayload.headerElements.at(0)!.date);
|
let startDate = new Date(headerReadyEventPayload.headerElements.at(0)!.date);
|
||||||
let endDate = new Date(headerReadyEventPayload.headerElements.at(-1)!.date);
|
let endDate = new Date(headerReadyEventPayload.headerElements.at(-1)!.date);
|
||||||
|
|
||||||
let events: CalendarEvent[] = this.eventManager.getEventsForPeriod(startDate, endDate);
|
let events: ICalendarEvent[] = this.eventManager.getEventsForPeriod(startDate, endDate);
|
||||||
// Filter for all-day events
|
// Filter for all-day events
|
||||||
const allDayEvents = events.filter(event => event.allDay);
|
const allDayEvents = events.filter(event => event.allDay);
|
||||||
|
|
||||||
|
|
@ -302,7 +302,7 @@ export class AllDayManager {
|
||||||
* Calculate layout for ALL all-day events using AllDayLayoutEngine
|
* Calculate layout for ALL all-day events using AllDayLayoutEngine
|
||||||
* This is the correct method that processes all events together for proper overlap detection
|
* This is the correct method that processes all events together for proper overlap detection
|
||||||
*/
|
*/
|
||||||
private calculateAllDayEventsLayout(events: CalendarEvent[], dayHeaders: ColumnBounds[]): EventLayout[] {
|
private calculateAllDayEventsLayout(events: ICalendarEvent[], dayHeaders: IColumnBounds[]): IEventLayout[] {
|
||||||
|
|
||||||
// Store current state
|
// Store current state
|
||||||
this.currentAllDayEvents = events;
|
this.currentAllDayEvents = events;
|
||||||
|
|
@ -316,12 +316,12 @@ export class AllDayManager {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private handleConvertToAllDay(payload: DragMouseEnterHeaderEventPayload): void {
|
private handleConvertToAllDay(payload: IDragMouseEnterHeaderEventPayload): void {
|
||||||
|
|
||||||
let allDayContainer = this.getAllDayContainer();
|
let allDayContainer = this.getAllDayContainer();
|
||||||
if (!allDayContainer) return;
|
if (!allDayContainer) return;
|
||||||
|
|
||||||
// Create SwpAllDayEventElement from CalendarEvent
|
// Create SwpAllDayEventElement from ICalendarEvent
|
||||||
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
|
const allDayElement = SwpAllDayEventElement.fromCalendarEvent(payload.calendarEvent);
|
||||||
|
|
||||||
// Apply grid positioning
|
// Apply grid positioning
|
||||||
|
|
@ -345,7 +345,7 @@ export class AllDayManager {
|
||||||
/**
|
/**
|
||||||
* Handle drag move for all-day events - SPECIALIZED FOR ALL-DAY CONTAINER
|
* Handle drag move for all-day events - SPECIALIZED FOR ALL-DAY CONTAINER
|
||||||
*/
|
*/
|
||||||
private handleColumnChange(dragColumnChangeEventPayload: DragColumnChangeEventPayload): void {
|
private handleColumnChange(dragColumnChangeEventPayload: IDragColumnChangeEventPayload): void {
|
||||||
|
|
||||||
let allDayContainer = this.getAllDayContainer();
|
let allDayContainer = this.getAllDayContainer();
|
||||||
if (!allDayContainer) return;
|
if (!allDayContainer) return;
|
||||||
|
|
@ -380,7 +380,7 @@ export class AllDayManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private handleDragEnd(dragEndEvent: DragEndEventPayload): void {
|
private handleDragEnd(dragEndEvent: IDragEndEventPayload): void {
|
||||||
|
|
||||||
const getEventDurationDays = (start: string | undefined, end: string | undefined): number => {
|
const getEventDurationDays = (start: string | undefined, end: string | undefined): number => {
|
||||||
|
|
||||||
|
|
@ -433,7 +433,7 @@ export class AllDayManager {
|
||||||
dragEndEvent.draggedClone.dataset.start = this.dateService.toUTC(newStartDate);
|
dragEndEvent.draggedClone.dataset.start = this.dateService.toUTC(newStartDate);
|
||||||
dragEndEvent.draggedClone.dataset.end = this.dateService.toUTC(newEndDate);
|
dragEndEvent.draggedClone.dataset.end = this.dateService.toUTC(newEndDate);
|
||||||
|
|
||||||
const droppedEvent: CalendarEvent = {
|
const droppedEvent: ICalendarEvent = {
|
||||||
id: eventId,
|
id: eventId,
|
||||||
title: dragEndEvent.draggedClone.dataset.title || '',
|
title: dragEndEvent.draggedClone.dataset.title || '',
|
||||||
start: newStartDate,
|
start: newStartDate,
|
||||||
|
|
@ -557,9 +557,9 @@ export class AllDayManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* Count number of events in a specific column using ColumnBounds
|
* Count number of events in a specific column using IColumnBounds
|
||||||
*/
|
*/
|
||||||
private countEventsInColumn(columnBounds: ColumnBounds): number {
|
private countEventsInColumn(columnBounds: IColumnBounds): number {
|
||||||
let columnIndex = columnBounds.index;
|
let columnIndex = columnBounds.index;
|
||||||
let count = 0;
|
let count = 0;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { CalendarView, IEventBus } from '../types/CalendarTypes';
|
import { CalendarView, IEventBus } from '../types/CalendarTypes';
|
||||||
import { EventManager } from './EventManager';
|
import { EventManager } from './EventManager';
|
||||||
import { GridManager } from './GridManager';
|
import { GridManager } from './GridManager';
|
||||||
|
|
@ -15,7 +15,7 @@ export class CalendarManager {
|
||||||
private gridManager: GridManager;
|
private gridManager: GridManager;
|
||||||
private eventRenderer: EventRenderingService;
|
private eventRenderer: EventRenderingService;
|
||||||
private scrollManager: ScrollManager;
|
private scrollManager: ScrollManager;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
private currentView: CalendarView = 'week';
|
private currentView: CalendarView = 'week';
|
||||||
private currentDate: Date = new Date();
|
private currentDate: Date = new Date();
|
||||||
private isInitialized: boolean = false;
|
private isInitialized: boolean = false;
|
||||||
|
|
@ -26,7 +26,7 @@ export class CalendarManager {
|
||||||
gridManager: GridManager,
|
gridManager: GridManager,
|
||||||
eventRenderingService: EventRenderingService,
|
eventRenderingService: EventRenderingService,
|
||||||
scrollManager: ScrollManager,
|
scrollManager: ScrollManager,
|
||||||
config: CalendarConfig
|
config: Configuration
|
||||||
) {
|
) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.eventManager = eventManager;
|
this.eventManager = eventManager;
|
||||||
|
|
|
||||||
|
|
@ -1,174 +0,0 @@
|
||||||
// Configuration manager - handles config updates with event emission
|
|
||||||
// Uses static CalendarConfig internally but adds event-driven updates
|
|
||||||
|
|
||||||
import { IEventBus, ICalendarConfig } from '../types/CalendarTypes';
|
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Grid display settings interface (re-export from CalendarConfig)
|
|
||||||
*/
|
|
||||||
interface GridSettings {
|
|
||||||
dayStartHour: number;
|
|
||||||
dayEndHour: number;
|
|
||||||
workStartHour: number;
|
|
||||||
workEndHour: number;
|
|
||||||
hourHeight: number;
|
|
||||||
snapInterval: number;
|
|
||||||
fitToWidth: boolean;
|
|
||||||
scrollToHour: number | null;
|
|
||||||
gridStartThresholdMinutes: number;
|
|
||||||
showCurrentTime: boolean;
|
|
||||||
showWorkHours: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ConfigManager - Handles configuration updates with event emission
|
|
||||||
* Wraps static CalendarConfig with event-driven functionality for DI system
|
|
||||||
* Also manages CSS custom properties that reflect config values
|
|
||||||
*/
|
|
||||||
export class ConfigManager {
|
|
||||||
constructor(private eventBus: IEventBus) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize CSS variables on startup
|
|
||||||
* Must be called after DOM is ready but before any rendering
|
|
||||||
*/
|
|
||||||
public initialize(): void {
|
|
||||||
this.updateCSSVariables();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set a config value and emit event
|
|
||||||
*/
|
|
||||||
set<K extends keyof ICalendarConfig>(key: K, value: ICalendarConfig[K]): void {
|
|
||||||
const oldValue = CalendarConfig.get(key);
|
|
||||||
CalendarConfig.set(key, value);
|
|
||||||
|
|
||||||
// Update CSS variables to reflect config change
|
|
||||||
this.updateCSSVariables();
|
|
||||||
|
|
||||||
// Emit config update event
|
|
||||||
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
oldValue
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update multiple config values and emit event
|
|
||||||
*/
|
|
||||||
update(updates: Partial<ICalendarConfig>): void {
|
|
||||||
Object.entries(updates).forEach(([key, value]) => {
|
|
||||||
this.set(key as keyof ICalendarConfig, value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update grid display settings and emit event
|
|
||||||
*/
|
|
||||||
updateGridSettings(updates: Partial<GridSettings>): void {
|
|
||||||
CalendarConfig.updateGridSettings(updates);
|
|
||||||
|
|
||||||
// Update CSS variables to reflect config change
|
|
||||||
this.updateCSSVariables();
|
|
||||||
|
|
||||||
// Emit event after update
|
|
||||||
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
|
|
||||||
key: 'gridSettings',
|
|
||||||
value: CalendarConfig.getGridSettings()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set selected date and emit event
|
|
||||||
*/
|
|
||||||
setSelectedDate(date: Date): void {
|
|
||||||
const oldDate = CalendarConfig.getSelectedDate();
|
|
||||||
CalendarConfig.setSelectedDate(date);
|
|
||||||
|
|
||||||
// Emit date change event if it actually changed
|
|
||||||
if (!oldDate || oldDate.getTime() !== date.getTime()) {
|
|
||||||
this.eventBus.emit(CoreEvents.DATE_CHANGED, {
|
|
||||||
date,
|
|
||||||
oldDate
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set work week and emit event
|
|
||||||
*/
|
|
||||||
setWorkWeek(workWeekId: string): void {
|
|
||||||
const oldWorkWeek = CalendarConfig.getCurrentWorkWeek();
|
|
||||||
CalendarConfig.setWorkWeek(workWeekId);
|
|
||||||
|
|
||||||
// Update CSS variables to reflect config change
|
|
||||||
this.updateCSSVariables();
|
|
||||||
|
|
||||||
// Emit event if changed
|
|
||||||
if (oldWorkWeek !== workWeekId) {
|
|
||||||
this.eventBus.emit(CoreEvents.REFRESH_REQUESTED, {
|
|
||||||
key: 'workWeek',
|
|
||||||
value: workWeekId,
|
|
||||||
oldValue: oldWorkWeek
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update all CSS custom properties based on current config
|
|
||||||
* This keeps the DOM in sync with config values
|
|
||||||
*/
|
|
||||||
private updateCSSVariables(): void {
|
|
||||||
const root = document.documentElement;
|
|
||||||
const gridSettings = CalendarConfig.getGridSettings();
|
|
||||||
const calendar = document.querySelector('swp-calendar') as HTMLElement;
|
|
||||||
|
|
||||||
// Set time-related CSS variables
|
|
||||||
root.style.setProperty('--header-height', '80px'); // Fixed header height
|
|
||||||
root.style.setProperty('--hour-height', `${gridSettings.hourHeight}px`);
|
|
||||||
root.style.setProperty('--minute-height', `${gridSettings.hourHeight / 60}px`);
|
|
||||||
root.style.setProperty('--snap-interval', gridSettings.snapInterval.toString());
|
|
||||||
root.style.setProperty('--day-start-hour', gridSettings.dayStartHour.toString());
|
|
||||||
root.style.setProperty('--day-end-hour', gridSettings.dayEndHour.toString());
|
|
||||||
root.style.setProperty('--work-start-hour', gridSettings.workStartHour.toString());
|
|
||||||
root.style.setProperty('--work-end-hour', gridSettings.workEndHour.toString());
|
|
||||||
|
|
||||||
// Set column count based on view
|
|
||||||
const columnCount = this.calculateColumnCount();
|
|
||||||
root.style.setProperty('--grid-columns', columnCount.toString());
|
|
||||||
|
|
||||||
// Set column width based on fitToWidth setting
|
|
||||||
if (gridSettings.fitToWidth) {
|
|
||||||
root.style.setProperty('--day-column-min-width', '50px'); // Small min-width allows columns to fit available space
|
|
||||||
} else {
|
|
||||||
root.style.setProperty('--day-column-min-width', '250px'); // Default min-width for horizontal scroll mode
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set fitToWidth data attribute for CSS targeting
|
|
||||||
if (calendar) {
|
|
||||||
calendar.setAttribute('data-fit-to-width', gridSettings.fitToWidth.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculate number of columns based on view
|
|
||||||
*/
|
|
||||||
private calculateColumnCount(): number {
|
|
||||||
const dateSettings = CalendarConfig.getDateViewSettings();
|
|
||||||
const workWeekSettings = CalendarConfig.getWorkWeekSettings();
|
|
||||||
|
|
||||||
switch (dateSettings.period) {
|
|
||||||
case 'day':
|
|
||||||
return 1;
|
|
||||||
case 'week':
|
|
||||||
return workWeekSettings.totalDays;
|
|
||||||
case 'month':
|
|
||||||
return workWeekSettings.totalDays; // Use work week for month view too
|
|
||||||
default:
|
|
||||||
return workWeekSettings.totalDays;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -134,33 +134,33 @@
|
||||||
|
|
||||||
import { IEventBus } from '../types/CalendarTypes';
|
import { IEventBus } from '../types/CalendarTypes';
|
||||||
import { PositionUtils } from '../utils/PositionUtils';
|
import { PositionUtils } from '../utils/PositionUtils';
|
||||||
import { ColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
import { IColumnBounds, ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
import { SwpEventElement, BaseSwpEventElement } from '../elements/SwpEventElement';
|
import { SwpEventElement, BaseSwpEventElement } from '../elements/SwpEventElement';
|
||||||
import {
|
import {
|
||||||
DragStartEventPayload,
|
IDragStartEventPayload,
|
||||||
DragMoveEventPayload,
|
IDragMoveEventPayload,
|
||||||
DragEndEventPayload,
|
IDragEndEventPayload,
|
||||||
DragMouseEnterHeaderEventPayload,
|
IDragMouseEnterHeaderEventPayload,
|
||||||
DragMouseLeaveHeaderEventPayload,
|
IDragMouseLeaveHeaderEventPayload,
|
||||||
DragMouseEnterColumnEventPayload,
|
IDragMouseEnterColumnEventPayload,
|
||||||
DragColumnChangeEventPayload
|
IDragColumnChangeEventPayload
|
||||||
} from '../types/EventTypes';
|
} from '../types/EventTypes';
|
||||||
import { MousePosition } from '../types/DragDropTypes';
|
import { IMousePosition } from '../types/DragDropTypes';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
|
|
||||||
export class DragDropManager {
|
export class DragDropManager {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
|
|
||||||
// Mouse tracking with optimized state
|
// Mouse tracking with optimized state
|
||||||
private mouseDownPosition: MousePosition = { x: 0, y: 0 };
|
private mouseDownPosition: IMousePosition = { x: 0, y: 0 };
|
||||||
private currentMousePosition: MousePosition = { x: 0, y: 0 };
|
private currentMousePosition: IMousePosition = { x: 0, y: 0 };
|
||||||
private mouseOffset: MousePosition = { x: 0, y: 0 };
|
private mouseOffset: IMousePosition = { x: 0, y: 0 };
|
||||||
|
|
||||||
// Drag state
|
// Drag state
|
||||||
private originalElement!: HTMLElement | null;
|
private originalElement!: HTMLElement | null;
|
||||||
private draggedClone!: HTMLElement | null;
|
private draggedClone!: HTMLElement | null;
|
||||||
private currentColumn: ColumnBounds | null = null;
|
private currentColumn: IColumnBounds | null = null;
|
||||||
private previousColumn: ColumnBounds | null = null;
|
private previousColumn: IColumnBounds | null = null;
|
||||||
private isDragStarted = false;
|
private isDragStarted = false;
|
||||||
|
|
||||||
// Movement threshold to distinguish click from drag
|
// Movement threshold to distinguish click from drag
|
||||||
|
|
@ -176,7 +176,7 @@ export class DragDropManager {
|
||||||
private dragAnimationId: number | null = null;
|
private dragAnimationId: number | null = null;
|
||||||
private targetY = 0;
|
private targetY = 0;
|
||||||
private currentY = 0;
|
private currentY = 0;
|
||||||
private targetColumn: ColumnBounds | null = null;
|
private targetColumn: IColumnBounds | null = null;
|
||||||
private positionUtils: PositionUtils;
|
private positionUtils: PositionUtils;
|
||||||
|
|
||||||
constructor(eventBus: IEventBus, positionUtils: PositionUtils) {
|
constructor(eventBus: IEventBus, positionUtils: PositionUtils) {
|
||||||
|
|
@ -336,7 +336,7 @@ export class DragDropManager {
|
||||||
* Try to initialize drag based on movement threshold
|
* Try to initialize drag based on movement threshold
|
||||||
* Returns true if drag was initialized, false if not enough movement
|
* Returns true if drag was initialized, false if not enough movement
|
||||||
*/
|
*/
|
||||||
private initializeDrag(currentPosition: MousePosition): boolean {
|
private initializeDrag(currentPosition: IMousePosition): boolean {
|
||||||
const deltaX = Math.abs(currentPosition.x - this.mouseDownPosition.x);
|
const deltaX = Math.abs(currentPosition.x - this.mouseDownPosition.x);
|
||||||
const deltaY = Math.abs(currentPosition.y - this.mouseDownPosition.y);
|
const deltaY = Math.abs(currentPosition.y - this.mouseDownPosition.y);
|
||||||
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
const totalMovement = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||||
|
|
@ -362,7 +362,7 @@ export class DragDropManager {
|
||||||
this.currentColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
this.currentColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
this.draggedClone = originalElement.createClone();
|
this.draggedClone = originalElement.createClone();
|
||||||
|
|
||||||
const dragStartPayload: DragStartEventPayload = {
|
const dragStartPayload: IDragStartEventPayload = {
|
||||||
originalElement: this.originalElement!,
|
originalElement: this.originalElement!,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
mousePosition: this.mouseDownPosition,
|
mousePosition: this.mouseDownPosition,
|
||||||
|
|
@ -375,7 +375,7 @@ export class DragDropManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private continueDrag(currentPosition: MousePosition): void {
|
private continueDrag(currentPosition: IMousePosition): void {
|
||||||
|
|
||||||
if (!this.draggedClone!.hasAttribute("data-allday")) {
|
if (!this.draggedClone!.hasAttribute("data-allday")) {
|
||||||
// Calculate raw position from mouse (no snapping)
|
// Calculate raw position from mouse (no snapping)
|
||||||
|
|
@ -405,7 +405,7 @@ export class DragDropManager {
|
||||||
/**
|
/**
|
||||||
* Detect column change and emit event
|
* Detect column change and emit event
|
||||||
*/
|
*/
|
||||||
private detectColumnChange(currentPosition: MousePosition): void {
|
private detectColumnChange(currentPosition: IMousePosition): void {
|
||||||
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
const newColumn = ColumnDetectionUtils.getColumnBounds(currentPosition);
|
||||||
if (newColumn == null) return;
|
if (newColumn == null) return;
|
||||||
|
|
||||||
|
|
@ -413,7 +413,7 @@ export class DragDropManager {
|
||||||
this.previousColumn = this.currentColumn;
|
this.previousColumn = this.currentColumn;
|
||||||
this.currentColumn = newColumn;
|
this.currentColumn = newColumn;
|
||||||
|
|
||||||
const dragColumnChangePayload: DragColumnChangeEventPayload = {
|
const dragColumnChangePayload: IDragColumnChangeEventPayload = {
|
||||||
originalElement: this.originalElement!,
|
originalElement: this.originalElement!,
|
||||||
draggedClone: this.draggedClone!,
|
draggedClone: this.draggedClone!,
|
||||||
previousColumn: this.previousColumn,
|
previousColumn: this.previousColumn,
|
||||||
|
|
@ -434,7 +434,7 @@ export class DragDropManager {
|
||||||
|
|
||||||
// Only emit drag:end if drag was actually started
|
// Only emit drag:end if drag was actually started
|
||||||
if (this.isDragStarted) {
|
if (this.isDragStarted) {
|
||||||
const mousePosition: MousePosition = { x: event.clientX, y: event.clientY };
|
const mousePosition: IMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
|
|
||||||
// Snap to grid on mouse up (like ResizeHandleManager)
|
// Snap to grid on mouse up (like ResizeHandleManager)
|
||||||
const column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
const column = ColumnDetectionUtils.getColumnBounds(mousePosition);
|
||||||
|
|
@ -455,7 +455,7 @@ export class DragDropManager {
|
||||||
if (!dropTarget)
|
if (!dropTarget)
|
||||||
throw "dropTarget is null";
|
throw "dropTarget is null";
|
||||||
|
|
||||||
const dragEndPayload: DragEndEventPayload = {
|
const dragEndPayload: IDragEndEventPayload = {
|
||||||
originalElement: this.originalElement,
|
originalElement: this.originalElement,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
mousePosition,
|
mousePosition,
|
||||||
|
|
@ -530,7 +530,7 @@ export class DragDropManager {
|
||||||
/**
|
/**
|
||||||
* Optimized snap position calculation using PositionUtils
|
* Optimized snap position calculation using PositionUtils
|
||||||
*/
|
*/
|
||||||
private calculateSnapPosition(mouseY: number, column: ColumnBounds): number {
|
private calculateSnapPosition(mouseY: number, column: IColumnBounds): number {
|
||||||
// Calculate where the event top would be (accounting for mouse offset)
|
// Calculate where the event top would be (accounting for mouse offset)
|
||||||
const eventTopY = mouseY - this.mouseOffset.y;
|
const eventTopY = mouseY - this.mouseOffset.y;
|
||||||
|
|
||||||
|
|
@ -560,7 +560,7 @@ export class DragDropManager {
|
||||||
this.currentY += step;
|
this.currentY += step;
|
||||||
|
|
||||||
// Emit drag:move event with current draggedClone reference
|
// Emit drag:move event with current draggedClone reference
|
||||||
const dragMovePayload: DragMoveEventPayload = {
|
const dragMovePayload: IDragMoveEventPayload = {
|
||||||
originalElement: this.originalElement!,
|
originalElement: this.originalElement!,
|
||||||
draggedClone: this.draggedClone, // Always uses current reference
|
draggedClone: this.draggedClone, // Always uses current reference
|
||||||
mousePosition: this.currentMousePosition, // Use current mouse position!
|
mousePosition: this.currentMousePosition, // Use current mouse position!
|
||||||
|
|
@ -576,7 +576,7 @@ export class DragDropManager {
|
||||||
this.currentY = this.targetY;
|
this.currentY = this.targetY;
|
||||||
|
|
||||||
// Emit final position
|
// Emit final position
|
||||||
const dragMovePayload: DragMoveEventPayload = {
|
const dragMovePayload: IDragMoveEventPayload = {
|
||||||
originalElement: this.originalElement!,
|
originalElement: this.originalElement!,
|
||||||
draggedClone: this.draggedClone,
|
draggedClone: this.draggedClone,
|
||||||
mousePosition: this.currentMousePosition, // Use current mouse position!
|
mousePosition: this.currentMousePosition, // Use current mouse position!
|
||||||
|
|
@ -633,7 +633,7 @@ export class DragDropManager {
|
||||||
/**
|
/**
|
||||||
* Detect drop target - whether dropped in swp-day-column or swp-day-header
|
* Detect drop target - whether dropped in swp-day-column or swp-day-header
|
||||||
*/
|
*/
|
||||||
private detectDropTarget(position: MousePosition): 'swp-day-column' | 'swp-day-header' | null {
|
private detectDropTarget(position: IMousePosition): 'swp-day-column' | 'swp-day-header' | null {
|
||||||
|
|
||||||
// Traverse up the DOM tree to find the target container
|
// Traverse up the DOM tree to find the target container
|
||||||
let currentElement = this.draggedClone;
|
let currentElement = this.draggedClone;
|
||||||
|
|
@ -659,13 +659,13 @@ export class DragDropManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position: MousePosition = { x: event.clientX, y: event.clientY };
|
const position: IMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||||
|
|
||||||
if (targetColumn) {
|
if (targetColumn) {
|
||||||
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
||||||
|
|
||||||
const dragMouseEnterPayload: DragMouseEnterHeaderEventPayload = {
|
const dragMouseEnterPayload: IDragMouseEnterHeaderEventPayload = {
|
||||||
targetColumn: targetColumn,
|
targetColumn: targetColumn,
|
||||||
mousePosition: position,
|
mousePosition: position,
|
||||||
originalElement: this.originalElement,
|
originalElement: this.originalElement,
|
||||||
|
|
@ -689,7 +689,7 @@ export class DragDropManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position: MousePosition = { x: event.clientX, y: event.clientY };
|
const position: IMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||||
|
|
||||||
if (!targetColumn) {
|
if (!targetColumn) {
|
||||||
|
|
@ -699,10 +699,10 @@ export class DragDropManager {
|
||||||
// Calculate snapped Y position
|
// Calculate snapped Y position
|
||||||
const snappedY = this.calculateSnapPosition(position.y, targetColumn);
|
const snappedY = this.calculateSnapPosition(position.y, targetColumn);
|
||||||
|
|
||||||
// Extract CalendarEvent from the dragged clone
|
// Extract ICalendarEvent from the dragged clone
|
||||||
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
const calendarEvent = SwpEventElement.extractCalendarEventFromElement(this.draggedClone);
|
||||||
|
|
||||||
const dragMouseEnterPayload: DragMouseEnterColumnEventPayload = {
|
const dragMouseEnterPayload: IDragMouseEnterColumnEventPayload = {
|
||||||
targetColumn: targetColumn,
|
targetColumn: targetColumn,
|
||||||
mousePosition: position,
|
mousePosition: position,
|
||||||
snappedY: snappedY,
|
snappedY: snappedY,
|
||||||
|
|
@ -727,14 +727,14 @@ export class DragDropManager {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const position: MousePosition = { x: event.clientX, y: event.clientY };
|
const position: IMousePosition = { x: event.clientX, y: event.clientY };
|
||||||
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
const targetColumn = ColumnDetectionUtils.getColumnBounds(position);
|
||||||
|
|
||||||
if (!targetColumn) {
|
if (!targetColumn) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const dragMouseLeavePayload: DragMouseLeaveHeaderEventPayload = {
|
const dragMouseLeavePayload: IDragMouseLeaveHeaderEventPayload = {
|
||||||
targetDate: targetColumn.date,
|
targetDate: targetColumn.date,
|
||||||
mousePosition: position,
|
mousePosition: position,
|
||||||
originalElement: this.originalElement,
|
originalElement: this.originalElement,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { IEventBus } from '../types/CalendarTypes';
|
import { IEventBus } from '../types/CalendarTypes';
|
||||||
import { DragMoveEventPayload, DragStartEventPayload } from '../types/EventTypes';
|
import { IDragMoveEventPayload, IDragStartEventPayload } from '../types/EventTypes';
|
||||||
|
|
||||||
export class EdgeScrollManager {
|
export class EdgeScrollManager {
|
||||||
private scrollableContent: HTMLElement | null = null;
|
private scrollableContent: HTMLElement | null = null;
|
||||||
|
|
|
||||||
|
|
@ -5,24 +5,24 @@
|
||||||
|
|
||||||
import { eventBus } from '../core/EventBus';
|
import { eventBus } from '../core/EventBus';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
|
|
||||||
// Import Fuse.js from npm
|
// Import Fuse.js from npm
|
||||||
import Fuse from 'fuse.js';
|
import Fuse from 'fuse.js';
|
||||||
|
|
||||||
interface FuseResult {
|
interface FuseResult {
|
||||||
item: CalendarEvent;
|
item: ICalendarEvent;
|
||||||
refIndex: number;
|
refIndex: number;
|
||||||
score?: number;
|
score?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventFilterManager {
|
export class EventFilterManager {
|
||||||
private searchInput: HTMLInputElement | null = null;
|
private searchInput: HTMLInputElement | null = null;
|
||||||
private allEvents: CalendarEvent[] = [];
|
private allEvents: ICalendarEvent[] = [];
|
||||||
private matchingEventIds: Set<string> = new Set();
|
private matchingEventIds: Set<string> = new Set();
|
||||||
private isFilterActive: boolean = false;
|
private isFilterActive: boolean = false;
|
||||||
private frameRequest: number | null = null;
|
private frameRequest: number | null = null;
|
||||||
private fuse: Fuse<CalendarEvent> | null = null;
|
private fuse: Fuse<ICalendarEvent> | null = null;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
// Wait for DOM to be ready before initializing
|
// Wait for DOM to be ready before initializing
|
||||||
|
|
@ -77,7 +77,7 @@ export class EventFilterManager {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private updateEventsList(events: CalendarEvent[]): void {
|
private updateEventsList(events: ICalendarEvent[]): void {
|
||||||
this.allEvents = events;
|
this.allEvents = events;
|
||||||
|
|
||||||
// Initialize Fuse with the new events list
|
// Initialize Fuse with the new events list
|
||||||
|
|
|
||||||
|
|
@ -5,35 +5,35 @@
|
||||||
* Calculates stack levels, groups events, and determines rendering strategy.
|
* Calculates stack levels, groups events, and determines rendering strategy.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
import { EventStackManager, EventGroup, StackLink } from './EventStackManager';
|
import { EventStackManager, IEventGroup, IStackLink } from './EventStackManager';
|
||||||
import { PositionUtils } from '../utils/PositionUtils';
|
import { PositionUtils } from '../utils/PositionUtils';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
|
|
||||||
export interface GridGroupLayout {
|
export interface IGridGroupLayout {
|
||||||
events: CalendarEvent[];
|
events: ICalendarEvent[];
|
||||||
stackLevel: number;
|
stackLevel: number;
|
||||||
position: { top: number };
|
position: { top: number };
|
||||||
columns: CalendarEvent[][]; // Events grouped by column (events in same array share a column)
|
columns: ICalendarEvent[][]; // Events grouped by column (events in same array share a column)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StackedEventLayout {
|
export interface IStackedEventLayout {
|
||||||
event: CalendarEvent;
|
event: ICalendarEvent;
|
||||||
stackLink: StackLink;
|
stackLink: IStackLink;
|
||||||
position: { top: number; height: number };
|
position: { top: number; height: number };
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ColumnLayout {
|
export interface IColumnLayout {
|
||||||
gridGroups: GridGroupLayout[];
|
gridGroups: IGridGroupLayout[];
|
||||||
stackedEvents: StackedEventLayout[];
|
stackedEvents: IStackedEventLayout[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventLayoutCoordinator {
|
export class EventLayoutCoordinator {
|
||||||
private stackManager: EventStackManager;
|
private stackManager: EventStackManager;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
private positionUtils: PositionUtils;
|
private positionUtils: PositionUtils;
|
||||||
|
|
||||||
constructor(stackManager: EventStackManager, config: CalendarConfig, positionUtils: PositionUtils) {
|
constructor(stackManager: EventStackManager, config: Configuration, positionUtils: PositionUtils) {
|
||||||
this.stackManager = stackManager;
|
this.stackManager = stackManager;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.positionUtils = positionUtils;
|
this.positionUtils = positionUtils;
|
||||||
|
|
@ -42,14 +42,14 @@ export class EventLayoutCoordinator {
|
||||||
/**
|
/**
|
||||||
* Calculate complete layout for a column of events (recursive approach)
|
* Calculate complete layout for a column of events (recursive approach)
|
||||||
*/
|
*/
|
||||||
public calculateColumnLayout(columnEvents: CalendarEvent[]): ColumnLayout {
|
public calculateColumnLayout(columnEvents: ICalendarEvent[]): IColumnLayout {
|
||||||
if (columnEvents.length === 0) {
|
if (columnEvents.length === 0) {
|
||||||
return { gridGroups: [], stackedEvents: [] };
|
return { gridGroups: [], stackedEvents: [] };
|
||||||
}
|
}
|
||||||
|
|
||||||
const gridGroupLayouts: GridGroupLayout[] = [];
|
const gridGroupLayouts: IGridGroupLayout[] = [];
|
||||||
const stackedEventLayouts: StackedEventLayout[] = [];
|
const stackedEventLayouts: IStackedEventLayout[] = [];
|
||||||
const renderedEventsWithLevels: Array<{ event: CalendarEvent; level: number }> = [];
|
const renderedEventsWithLevels: Array<{ event: ICalendarEvent; level: number }> = [];
|
||||||
let remaining = [...columnEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
|
let remaining = [...columnEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
|
||||||
|
|
||||||
// Process events recursively
|
// Process events recursively
|
||||||
|
|
@ -66,7 +66,7 @@ export class EventLayoutCoordinator {
|
||||||
const gridCandidates = this.expandGridCandidates(firstEvent, remaining, thresholdMinutes);
|
const gridCandidates = this.expandGridCandidates(firstEvent, remaining, thresholdMinutes);
|
||||||
|
|
||||||
// Decide: should this group be GRID or STACK?
|
// Decide: should this group be GRID or STACK?
|
||||||
const group: EventGroup = {
|
const group: IEventGroup = {
|
||||||
events: gridCandidates,
|
events: gridCandidates,
|
||||||
containerType: 'NONE',
|
containerType: 'NONE',
|
||||||
startTime: firstEvent.start
|
startTime: firstEvent.start
|
||||||
|
|
@ -129,8 +129,8 @@ export class EventLayoutCoordinator {
|
||||||
* Calculate stack level for a grid group based on already rendered events
|
* Calculate stack level for a grid group based on already rendered events
|
||||||
*/
|
*/
|
||||||
private calculateGridGroupStackLevelFromRendered(
|
private calculateGridGroupStackLevelFromRendered(
|
||||||
gridEvents: CalendarEvent[],
|
gridEvents: ICalendarEvent[],
|
||||||
renderedEventsWithLevels: Array<{ event: CalendarEvent; level: number }>
|
renderedEventsWithLevels: Array<{ event: ICalendarEvent; level: number }>
|
||||||
): number {
|
): number {
|
||||||
// Find highest stack level of any rendered event that overlaps with this grid
|
// Find highest stack level of any rendered event that overlaps with this grid
|
||||||
let maxOverlappingLevel = -1;
|
let maxOverlappingLevel = -1;
|
||||||
|
|
@ -150,8 +150,8 @@ export class EventLayoutCoordinator {
|
||||||
* Calculate stack level for a single stacked event based on already rendered events
|
* Calculate stack level for a single stacked event based on already rendered events
|
||||||
*/
|
*/
|
||||||
private calculateStackLevelFromRendered(
|
private calculateStackLevelFromRendered(
|
||||||
event: CalendarEvent,
|
event: ICalendarEvent,
|
||||||
renderedEventsWithLevels: Array<{ event: CalendarEvent; level: number }>
|
renderedEventsWithLevels: Array<{ event: ICalendarEvent; level: number }>
|
||||||
): number {
|
): number {
|
||||||
// Find highest stack level of any rendered event that overlaps with this event
|
// Find highest stack level of any rendered event that overlaps with this event
|
||||||
let maxOverlappingLevel = -1;
|
let maxOverlappingLevel = -1;
|
||||||
|
|
@ -173,7 +173,7 @@ export class EventLayoutCoordinator {
|
||||||
* @param thresholdMinutes - Threshold in minutes
|
* @param thresholdMinutes - Threshold in minutes
|
||||||
* @returns true if events conflict
|
* @returns true if events conflict
|
||||||
*/
|
*/
|
||||||
private detectConflict(event1: CalendarEvent, event2: CalendarEvent, thresholdMinutes: number): boolean {
|
private detectConflict(event1: ICalendarEvent, event2: ICalendarEvent, thresholdMinutes: number): boolean {
|
||||||
// Check 1: Start-to-start conflict (starts within threshold)
|
// Check 1: Start-to-start conflict (starts within threshold)
|
||||||
const startToStartDiff = Math.abs(event1.start.getTime() - event2.start.getTime()) / (1000 * 60);
|
const startToStartDiff = Math.abs(event1.start.getTime() - event2.start.getTime()) / (1000 * 60);
|
||||||
if (startToStartDiff <= thresholdMinutes && this.stackManager.doEventsOverlap(event1, event2)) {
|
if (startToStartDiff <= thresholdMinutes && this.stackManager.doEventsOverlap(event1, event2)) {
|
||||||
|
|
@ -206,10 +206,10 @@ export class EventLayoutCoordinator {
|
||||||
* @returns Array of all events in the conflict chain
|
* @returns Array of all events in the conflict chain
|
||||||
*/
|
*/
|
||||||
private expandGridCandidates(
|
private expandGridCandidates(
|
||||||
firstEvent: CalendarEvent,
|
firstEvent: ICalendarEvent,
|
||||||
remaining: CalendarEvent[],
|
remaining: ICalendarEvent[],
|
||||||
thresholdMinutes: number
|
thresholdMinutes: number
|
||||||
): CalendarEvent[] {
|
): ICalendarEvent[] {
|
||||||
const gridCandidates = [firstEvent];
|
const gridCandidates = [firstEvent];
|
||||||
let candidatesChanged = true;
|
let candidatesChanged = true;
|
||||||
|
|
||||||
|
|
@ -246,11 +246,11 @@ export class EventLayoutCoordinator {
|
||||||
* @param events - Events in the grid group (should already be sorted by start time)
|
* @param events - Events in the grid group (should already be sorted by start time)
|
||||||
* @returns Array of columns, where each column is an array of events
|
* @returns Array of columns, where each column is an array of events
|
||||||
*/
|
*/
|
||||||
private allocateColumns(events: CalendarEvent[]): CalendarEvent[][] {
|
private allocateColumns(events: ICalendarEvent[]): ICalendarEvent[][] {
|
||||||
if (events.length === 0) return [];
|
if (events.length === 0) return [];
|
||||||
if (events.length === 1) return [[events[0]]];
|
if (events.length === 1) return [[events[0]]];
|
||||||
|
|
||||||
const columns: CalendarEvent[][] = [];
|
const columns: ICalendarEvent[][] = [];
|
||||||
|
|
||||||
// For each event, try to place it in an existing column where it doesn't overlap
|
// For each event, try to place it in an existing column where it doesn't overlap
|
||||||
for (const event of events) {
|
for (const event of events) {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { IEventBus, CalendarEvent } from '../types/CalendarTypes';
|
import { IEventBus, ICalendarEvent } from '../types/CalendarTypes';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { DateService } from '../utils/DateService';
|
import { DateService } from '../utils/DateService';
|
||||||
import { IEventRepository } from '../repositories/IEventRepository';
|
import { IEventRepository } from '../repositories/IEventRepository';
|
||||||
|
|
||||||
|
|
@ -10,15 +10,15 @@ import { IEventRepository } from '../repositories/IEventRepository';
|
||||||
*/
|
*/
|
||||||
export class EventManager {
|
export class EventManager {
|
||||||
|
|
||||||
private events: CalendarEvent[] = [];
|
private events: ICalendarEvent[] = [];
|
||||||
private dateService: DateService;
|
private dateService: DateService;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
private repository: IEventRepository;
|
private repository: IEventRepository;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private eventBus: IEventBus,
|
private eventBus: IEventBus,
|
||||||
dateService: DateService,
|
dateService: DateService,
|
||||||
config: CalendarConfig,
|
config: Configuration,
|
||||||
repository: IEventRepository
|
repository: IEventRepository
|
||||||
) {
|
) {
|
||||||
this.dateService = dateService;
|
this.dateService = dateService;
|
||||||
|
|
@ -42,14 +42,14 @@ export class EventManager {
|
||||||
/**
|
/**
|
||||||
* Get events with optional copying for performance
|
* Get events with optional copying for performance
|
||||||
*/
|
*/
|
||||||
public getEvents(copy: boolean = false): CalendarEvent[] {
|
public getEvents(copy: boolean = false): ICalendarEvent[] {
|
||||||
return copy ? [...this.events] : this.events;
|
return copy ? [...this.events] : this.events;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optimized event lookup with early return
|
* Optimized event lookup with early return
|
||||||
*/
|
*/
|
||||||
public getEventById(id: string): CalendarEvent | undefined {
|
public getEventById(id: string): ICalendarEvent | undefined {
|
||||||
// Use find for better performance than filter + first
|
// Use find for better performance than filter + first
|
||||||
return this.events.find(event => event.id === id);
|
return this.events.find(event => event.id === id);
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +59,7 @@ export class EventManager {
|
||||||
* @param id Event ID to find
|
* @param id Event ID to find
|
||||||
* @returns Event with navigation info or null if not found
|
* @returns Event with navigation info or null if not found
|
||||||
*/
|
*/
|
||||||
public getEventForNavigation(id: string): { event: CalendarEvent; eventDate: Date } | null {
|
public getEventForNavigation(id: string): { event: ICalendarEvent; eventDate: Date } | null {
|
||||||
const event = this.getEventById(id);
|
const event = this.getEventById(id);
|
||||||
if (!event) {
|
if (!event) {
|
||||||
return null;
|
return null;
|
||||||
|
|
@ -113,7 +113,7 @@ export class EventManager {
|
||||||
/**
|
/**
|
||||||
* Get events that overlap with a given time period
|
* Get events that overlap with a given time period
|
||||||
*/
|
*/
|
||||||
public getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[] {
|
public getEventsForPeriod(startDate: Date, endDate: Date): ICalendarEvent[] {
|
||||||
// Event overlaps period if it starts before period ends AND ends after period starts
|
// Event overlaps period if it starts before period ends AND ends after period starts
|
||||||
return this.events.filter(event => {
|
return this.events.filter(event => {
|
||||||
return event.start <= endDate && event.end >= startDate;
|
return event.start <= endDate && event.end >= startDate;
|
||||||
|
|
@ -123,8 +123,8 @@ export class EventManager {
|
||||||
/**
|
/**
|
||||||
* Create a new event and add it to the calendar
|
* Create a new event and add it to the calendar
|
||||||
*/
|
*/
|
||||||
public addEvent(event: Omit<CalendarEvent, 'id'>): CalendarEvent {
|
public addEvent(event: Omit<ICalendarEvent, 'id'>): ICalendarEvent {
|
||||||
const newEvent: CalendarEvent = {
|
const newEvent: ICalendarEvent = {
|
||||||
...event,
|
...event,
|
||||||
id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
id: `event_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`
|
||||||
};
|
};
|
||||||
|
|
@ -141,7 +141,7 @@ export class EventManager {
|
||||||
/**
|
/**
|
||||||
* Update an existing event
|
* Update an existing event
|
||||||
*/
|
*/
|
||||||
public updateEvent(id: string, updates: Partial<CalendarEvent>): CalendarEvent | null {
|
public updateEvent(id: string, updates: Partial<ICalendarEvent>): ICalendarEvent | null {
|
||||||
const eventIndex = this.events.findIndex(event => event.id === id);
|
const eventIndex = this.events.findIndex(event => event.id === id);
|
||||||
if (eventIndex === -1) return null;
|
if (eventIndex === -1) return null;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,26 +13,26 @@
|
||||||
* @see stacking-visualization.html for visual examples
|
* @see stacking-visualization.html for visual examples
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
|
|
||||||
export interface StackLink {
|
export interface IStackLink {
|
||||||
prev?: string; // Event ID of previous event in stack
|
prev?: string; // Event ID of previous event in stack
|
||||||
next?: string; // Event ID of next event in stack
|
next?: string; // Event ID of next event in stack
|
||||||
stackLevel: number; // Position in stack (0 = base, 1 = first offset, etc.)
|
stackLevel: number; // Position in stack (0 = base, 1 = first offset, etc.)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventGroup {
|
export interface IEventGroup {
|
||||||
events: CalendarEvent[];
|
events: ICalendarEvent[];
|
||||||
containerType: 'NONE' | 'GRID' | 'STACKING';
|
containerType: 'NONE' | 'GRID' | 'STACKING';
|
||||||
startTime: Date;
|
startTime: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class EventStackManager {
|
export class EventStackManager {
|
||||||
private static readonly STACK_OFFSET_PX = 15;
|
private static readonly STACK_OFFSET_PX = 15;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
|
|
||||||
constructor(config: CalendarConfig) {
|
constructor(config: Configuration) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,7 +47,7 @@ export class EventStackManager {
|
||||||
* 1. They start within ±threshold minutes of each other (start-to-start)
|
* 1. They start within ±threshold minutes of each other (start-to-start)
|
||||||
* 2. One event starts within threshold minutes before another ends (end-to-start conflict)
|
* 2. One event starts within threshold minutes before another ends (end-to-start conflict)
|
||||||
*/
|
*/
|
||||||
public groupEventsByStartTime(events: CalendarEvent[]): EventGroup[] {
|
public groupEventsByStartTime(events: ICalendarEvent[]): IEventGroup[] {
|
||||||
if (events.length === 0) return [];
|
if (events.length === 0) return [];
|
||||||
|
|
||||||
// Get threshold from config
|
// Get threshold from config
|
||||||
|
|
@ -57,7 +57,7 @@ export class EventStackManager {
|
||||||
// Sort events by start time
|
// Sort events by start time
|
||||||
const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
|
const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
|
||||||
|
|
||||||
const groups: EventGroup[] = [];
|
const groups: IEventGroup[] = [];
|
||||||
|
|
||||||
for (const event of sorted) {
|
for (const event of sorted) {
|
||||||
// Find existing group that this event conflicts with
|
// Find existing group that this event conflicts with
|
||||||
|
|
@ -112,7 +112,7 @@ export class EventStackManager {
|
||||||
* even if they overlap each other. This provides better visual indication that
|
* even if they overlap each other. This provides better visual indication that
|
||||||
* events start at the same time.
|
* events start at the same time.
|
||||||
*/
|
*/
|
||||||
public decideContainerType(group: EventGroup): 'NONE' | 'GRID' | 'STACKING' {
|
public decideContainerType(group: IEventGroup): 'NONE' | 'GRID' | 'STACKING' {
|
||||||
if (group.events.length === 1) {
|
if (group.events.length === 1) {
|
||||||
return 'NONE';
|
return 'NONE';
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +127,7 @@ export class EventStackManager {
|
||||||
/**
|
/**
|
||||||
* Check if two events overlap in time
|
* Check if two events overlap in time
|
||||||
*/
|
*/
|
||||||
public doEventsOverlap(event1: CalendarEvent, event2: CalendarEvent): boolean {
|
public doEventsOverlap(event1: ICalendarEvent, event2: ICalendarEvent): boolean {
|
||||||
return event1.start < event2.end && event1.end > event2.start;
|
return event1.start < event2.end && event1.end > event2.start;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,8 +139,8 @@ export class EventStackManager {
|
||||||
/**
|
/**
|
||||||
* Create optimized stack links (events share levels when possible)
|
* Create optimized stack links (events share levels when possible)
|
||||||
*/
|
*/
|
||||||
public createOptimizedStackLinks(events: CalendarEvent[]): Map<string, StackLink> {
|
public createOptimizedStackLinks(events: ICalendarEvent[]): Map<string, IStackLink> {
|
||||||
const stackLinks = new Map<string, StackLink>();
|
const stackLinks = new Map<string, IStackLink>();
|
||||||
|
|
||||||
if (events.length === 0) return stackLinks;
|
if (events.length === 0) return stackLinks;
|
||||||
|
|
||||||
|
|
@ -218,14 +218,14 @@ export class EventStackManager {
|
||||||
/**
|
/**
|
||||||
* Serialize stack link to JSON string
|
* Serialize stack link to JSON string
|
||||||
*/
|
*/
|
||||||
public serializeStackLink(stackLink: StackLink): string {
|
public serializeStackLink(stackLink: IStackLink): string {
|
||||||
return JSON.stringify(stackLink);
|
return JSON.stringify(stackLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize JSON string to stack link
|
* Deserialize JSON string to stack link
|
||||||
*/
|
*/
|
||||||
public deserializeStackLink(json: string): StackLink | null {
|
public deserializeStackLink(json: string): IStackLink | null {
|
||||||
try {
|
try {
|
||||||
return JSON.parse(json);
|
return JSON.parse(json);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|
@ -236,14 +236,14 @@ export class EventStackManager {
|
||||||
/**
|
/**
|
||||||
* Apply stack link to DOM element
|
* Apply stack link to DOM element
|
||||||
*/
|
*/
|
||||||
public applyStackLinkToElement(element: HTMLElement, stackLink: StackLink): void {
|
public applyStackLinkToElement(element: HTMLElement, stackLink: IStackLink): void {
|
||||||
element.dataset.stackLink = this.serializeStackLink(stackLink);
|
element.dataset.stackLink = this.serializeStackLink(stackLink);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get stack link from DOM element
|
* Get stack link from DOM element
|
||||||
*/
|
*/
|
||||||
public getStackLinkFromElement(element: HTMLElement): StackLink | null {
|
public getStackLinkFromElement(element: HTMLElement): IStackLink | null {
|
||||||
const data = element.dataset.stackLink;
|
const data = element.dataset.stackLink;
|
||||||
if (!data) return null;
|
if (!data) return null;
|
||||||
return this.deserializeStackLink(data);
|
return this.deserializeStackLink(data);
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import { eventBus } from '../core/EventBus';
|
import { eventBus } from '../core/EventBus';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { IHeaderRenderer, HeaderRenderContext } from '../renderers/DateHeaderRenderer';
|
import { IHeaderRenderer, IHeaderRenderContext } from '../renderers/DateHeaderRenderer';
|
||||||
import { DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, HeaderReadyEventPayload } from '../types/EventTypes';
|
import { IDragMouseEnterHeaderEventPayload, IDragMouseLeaveHeaderEventPayload, IHeaderReadyEventPayload } from '../types/EventTypes';
|
||||||
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -12,9 +12,9 @@ import { ColumnDetectionUtils } from '../utils/ColumnDetectionUtils';
|
||||||
*/
|
*/
|
||||||
export class HeaderManager {
|
export class HeaderManager {
|
||||||
private headerRenderer: IHeaderRenderer;
|
private headerRenderer: IHeaderRenderer;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
|
|
||||||
constructor(headerRenderer: IHeaderRenderer, config: CalendarConfig) {
|
constructor(headerRenderer: IHeaderRenderer, config: Configuration) {
|
||||||
this.headerRenderer = headerRenderer;
|
this.headerRenderer = headerRenderer;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
|
|
||||||
|
|
@ -44,7 +44,7 @@ export class HeaderManager {
|
||||||
*/
|
*/
|
||||||
private handleDragMouseEnterHeader(event: Event): void {
|
private handleDragMouseEnterHeader(event: Event): void {
|
||||||
const { targetColumn: targetDate, mousePosition, originalElement, draggedClone: cloneElement } =
|
const { targetColumn: targetDate, mousePosition, originalElement, draggedClone: cloneElement } =
|
||||||
(event as CustomEvent<DragMouseEnterHeaderEventPayload>).detail;
|
(event as CustomEvent<IDragMouseEnterHeaderEventPayload>).detail;
|
||||||
|
|
||||||
console.log('🎯 HeaderManager: Received drag:mouseenter-header', {
|
console.log('🎯 HeaderManager: Received drag:mouseenter-header', {
|
||||||
targetDate,
|
targetDate,
|
||||||
|
|
@ -58,7 +58,7 @@ export class HeaderManager {
|
||||||
*/
|
*/
|
||||||
private handleDragMouseLeaveHeader(event: Event): void {
|
private handleDragMouseLeaveHeader(event: Event): void {
|
||||||
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } =
|
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } =
|
||||||
(event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
|
(event as CustomEvent<IDragMouseLeaveHeaderEventPayload>).detail;
|
||||||
|
|
||||||
console.log('🚪 HeaderManager: Received drag:mouseleave-header', {
|
console.log('🚪 HeaderManager: Received drag:mouseleave-header', {
|
||||||
targetDate,
|
targetDate,
|
||||||
|
|
@ -109,7 +109,7 @@ export class HeaderManager {
|
||||||
calendarHeader.innerHTML = '';
|
calendarHeader.innerHTML = '';
|
||||||
|
|
||||||
// Render new header content using injected renderer
|
// Render new header content using injected renderer
|
||||||
const context: HeaderRenderContext = {
|
const context: IHeaderRenderContext = {
|
||||||
currentWeek: currentDate,
|
currentWeek: currentDate,
|
||||||
config: this.config
|
config: this.config
|
||||||
};
|
};
|
||||||
|
|
@ -120,7 +120,7 @@ export class HeaderManager {
|
||||||
this.setupHeaderDragListeners();
|
this.setupHeaderDragListeners();
|
||||||
|
|
||||||
// Notify other managers that header is ready with period data
|
// Notify other managers that header is ready with period data
|
||||||
const payload: HeaderReadyEventPayload = {
|
const payload: IHeaderReadyEventPayload = {
|
||||||
headerElements: ColumnDetectionUtils.getHeaderColumns(),
|
headerElements: ColumnDetectionUtils.getHeaderColumns(),
|
||||||
};
|
};
|
||||||
eventBus.emit('header:ready', payload);
|
eventBus.emit('header:ready', payload);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { eventBus } from '../core/EventBus';
|
import { eventBus } from '../core/EventBus';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { ResizeEndEventPayload } from '../types/EventTypes';
|
import { IResizeEndEventPayload } from '../types/EventTypes';
|
||||||
|
|
||||||
type SwpEventEl = HTMLElement & { updateHeight?: (h: number) => void };
|
type SwpEventEl = HTMLElement & { updateHeight?: (h: number) => void };
|
||||||
|
|
||||||
|
|
@ -29,9 +29,9 @@ export class ResizeHandleManager {
|
||||||
private unsubscribers: Array<() => void> = [];
|
private unsubscribers: Array<() => void> = [];
|
||||||
private pointerCaptured = false;
|
private pointerCaptured = false;
|
||||||
private prevZ?: string;
|
private prevZ?: string;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
|
|
||||||
constructor(config: CalendarConfig) {
|
constructor(config: Configuration) {
|
||||||
this.config = config;
|
this.config = config;
|
||||||
const grid = this.config.getGridSettings();
|
const grid = this.config.getGridSettings();
|
||||||
this.hourHeightPx = grid.hourHeight;
|
this.hourHeightPx = grid.hourHeight;
|
||||||
|
|
@ -237,7 +237,7 @@ export class ResizeHandleManager {
|
||||||
|
|
||||||
// Emit resize:end event for re-stacking
|
// Emit resize:end event for re-stacking
|
||||||
const eventId = this.targetEl.dataset.eventId || '';
|
const eventId = this.targetEl.dataset.eventId || '';
|
||||||
const resizeEndPayload: ResizeEndEventPayload = {
|
const resizeEndPayload: IResizeEndEventPayload = {
|
||||||
eventId,
|
eventId,
|
||||||
element: this.targetEl,
|
element: this.targetEl,
|
||||||
finalHeight
|
finalHeight
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,15 @@
|
||||||
import { CalendarView, IEventBus } from '../types/CalendarTypes';
|
import { CalendarView, IEventBus } from '../types/CalendarTypes';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
|
|
||||||
|
|
||||||
export class ViewManager {
|
export class ViewManager {
|
||||||
private eventBus: IEventBus;
|
private eventBus: IEventBus;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
private currentView: CalendarView = 'week';
|
private currentView: CalendarView = 'week';
|
||||||
private buttonListeners: Map<Element, EventListener> = new Map();
|
private buttonListeners: Map<Element, EventListener> = new Map();
|
||||||
|
|
||||||
constructor(eventBus: IEventBus, config: CalendarConfig) {
|
constructor(eventBus: IEventBus, config: Configuration) {
|
||||||
this.eventBus = eventBus;
|
this.eventBus = eventBus;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.setupEventListeners();
|
this.setupEventListeners();
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
// Work hours management for per-column scheduling
|
// Work hours management for per-column scheduling
|
||||||
|
|
||||||
import { DateService } from '../utils/DateService';
|
import { DateService } from '../utils/DateService';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { PositionUtils } from '../utils/PositionUtils';
|
import { PositionUtils } from '../utils/PositionUtils';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Work hours for a specific day
|
* Work hours for a specific day
|
||||||
*/
|
*/
|
||||||
export interface DayWorkHours {
|
export interface IDayWorkHours {
|
||||||
start: number; // Hour (0-23)
|
start: number; // Hour (0-23)
|
||||||
end: number; // Hour (0-23)
|
end: number; // Hour (0-23)
|
||||||
}
|
}
|
||||||
|
|
@ -15,18 +15,18 @@ export interface DayWorkHours {
|
||||||
/**
|
/**
|
||||||
* Work schedule configuration
|
* Work schedule configuration
|
||||||
*/
|
*/
|
||||||
export interface WorkScheduleConfig {
|
export interface IWorkScheduleConfig {
|
||||||
weeklyDefault: {
|
weeklyDefault: {
|
||||||
monday: DayWorkHours | 'off';
|
monday: IDayWorkHours | 'off';
|
||||||
tuesday: DayWorkHours | 'off';
|
tuesday: IDayWorkHours | 'off';
|
||||||
wednesday: DayWorkHours | 'off';
|
wednesday: IDayWorkHours | 'off';
|
||||||
thursday: DayWorkHours | 'off';
|
thursday: IDayWorkHours | 'off';
|
||||||
friday: DayWorkHours | 'off';
|
friday: IDayWorkHours | 'off';
|
||||||
saturday: DayWorkHours | 'off';
|
saturday: IDayWorkHours | 'off';
|
||||||
sunday: DayWorkHours | 'off';
|
sunday: IDayWorkHours | 'off';
|
||||||
};
|
};
|
||||||
dateOverrides: {
|
dateOverrides: {
|
||||||
[dateString: string]: DayWorkHours | 'off'; // YYYY-MM-DD format
|
[dateString: string]: IDayWorkHours | 'off'; // YYYY-MM-DD format
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,11 +35,11 @@ export interface WorkScheduleConfig {
|
||||||
*/
|
*/
|
||||||
export class WorkHoursManager {
|
export class WorkHoursManager {
|
||||||
private dateService: DateService;
|
private dateService: DateService;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
private positionUtils: PositionUtils;
|
private positionUtils: PositionUtils;
|
||||||
private workSchedule: WorkScheduleConfig;
|
private workSchedule: IWorkScheduleConfig;
|
||||||
|
|
||||||
constructor(dateService: DateService, config: CalendarConfig, positionUtils: PositionUtils) {
|
constructor(dateService: DateService, config: Configuration, positionUtils: PositionUtils) {
|
||||||
this.dateService = dateService;
|
this.dateService = dateService;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
this.positionUtils = positionUtils;
|
this.positionUtils = positionUtils;
|
||||||
|
|
@ -66,7 +66,7 @@ export class WorkHoursManager {
|
||||||
/**
|
/**
|
||||||
* Get work hours for a specific date
|
* Get work hours for a specific date
|
||||||
*/
|
*/
|
||||||
getWorkHoursForDate(date: Date): DayWorkHours | 'off' {
|
getWorkHoursForDate(date: Date): IDayWorkHours | 'off' {
|
||||||
const dateString = this.dateService.formatISODate(date);
|
const dateString = this.dateService.formatISODate(date);
|
||||||
|
|
||||||
// Check for date-specific override first
|
// Check for date-specific override first
|
||||||
|
|
@ -82,8 +82,8 @@ export class WorkHoursManager {
|
||||||
/**
|
/**
|
||||||
* Get work hours for multiple dates (used by GridManager)
|
* Get work hours for multiple dates (used by GridManager)
|
||||||
*/
|
*/
|
||||||
getWorkHoursForDateRange(dates: Date[]): Map<string, DayWorkHours | 'off'> {
|
getWorkHoursForDateRange(dates: Date[]): Map<string, IDayWorkHours | 'off'> {
|
||||||
const workHoursMap = new Map<string, DayWorkHours | 'off'>();
|
const workHoursMap = new Map<string, IDayWorkHours | 'off'>();
|
||||||
|
|
||||||
dates.forEach(date => {
|
dates.forEach(date => {
|
||||||
const dateString = this.dateService.formatISODate(date);
|
const dateString = this.dateService.formatISODate(date);
|
||||||
|
|
@ -97,7 +97,7 @@ export class WorkHoursManager {
|
||||||
/**
|
/**
|
||||||
* Calculate CSS custom properties for non-work hour overlays using PositionUtils
|
* Calculate CSS custom properties for non-work hour overlays using PositionUtils
|
||||||
*/
|
*/
|
||||||
calculateNonWorkHoursStyle(workHours: DayWorkHours | 'off'): { beforeWorkHeight: number; afterWorkTop: number } | null {
|
calculateNonWorkHoursStyle(workHours: IDayWorkHours | 'off'): { beforeWorkHeight: number; afterWorkTop: number } | null {
|
||||||
if (workHours === 'off') {
|
if (workHours === 'off') {
|
||||||
return null; // Full day will be colored via CSS background
|
return null; // Full day will be colored via CSS background
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +121,7 @@ export class WorkHoursManager {
|
||||||
/**
|
/**
|
||||||
* Calculate CSS custom properties for work hours overlay using PositionUtils
|
* Calculate CSS custom properties for work hours overlay using PositionUtils
|
||||||
*/
|
*/
|
||||||
calculateWorkHoursStyle(workHours: DayWorkHours | 'off'): { top: number; height: number } | null {
|
calculateWorkHoursStyle(workHours: IDayWorkHours | 'off'): { top: number; height: number } | null {
|
||||||
if (workHours === 'off') {
|
if (workHours === 'off') {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
@ -139,22 +139,22 @@ export class WorkHoursManager {
|
||||||
/**
|
/**
|
||||||
* Load work schedule from JSON (future implementation)
|
* Load work schedule from JSON (future implementation)
|
||||||
*/
|
*/
|
||||||
async loadWorkSchedule(jsonData: WorkScheduleConfig): Promise<void> {
|
async loadWorkSchedule(jsonData: IWorkScheduleConfig): Promise<void> {
|
||||||
this.workSchedule = jsonData;
|
this.workSchedule = jsonData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get current work schedule configuration
|
* Get current work schedule configuration
|
||||||
*/
|
*/
|
||||||
getWorkSchedule(): WorkScheduleConfig {
|
getWorkSchedule(): IWorkScheduleConfig {
|
||||||
return this.workSchedule;
|
return this.workSchedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convert Date to day name key
|
* Convert Date to day name key
|
||||||
*/
|
*/
|
||||||
private getDayName(date: Date): keyof WorkScheduleConfig['weeklyDefault'] {
|
private getDayName(date: Date): keyof IWorkScheduleConfig['weeklyDefault'] {
|
||||||
const dayNames: (keyof WorkScheduleConfig['weeklyDefault'])[] = [
|
const dayNames: (keyof IWorkScheduleConfig['weeklyDefault'])[] = [
|
||||||
'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'
|
'sunday', 'monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday'
|
||||||
];
|
];
|
||||||
return dayNames[date.getDay()];
|
return dayNames[date.getDay()];
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
|
import { SwpAllDayEventElement } from '../elements/SwpEventElement';
|
||||||
import { EventLayout } from '../utils/AllDayLayoutEngine';
|
import { IEventLayout } from '../utils/AllDayLayoutEngine';
|
||||||
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
|
import { IColumnBounds } from '../utils/ColumnDetectionUtils';
|
||||||
import { EventManager } from '../managers/EventManager';
|
import { EventManager } from '../managers/EventManager';
|
||||||
import { DragStartEventPayload } from '../types/EventTypes';
|
import { IDragStartEventPayload } from '../types/EventTypes';
|
||||||
import { IEventRenderer } from './EventRenderer';
|
import { IEventRenderer } from './EventRenderer';
|
||||||
|
|
||||||
export class AllDayEventRenderer {
|
export class AllDayEventRenderer {
|
||||||
|
|
@ -38,7 +38,7 @@ export class AllDayEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Handle drag start for all-day events
|
* Handle drag start for all-day events
|
||||||
*/
|
*/
|
||||||
public handleDragStart(payload: DragStartEventPayload): void {
|
public handleDragStart(payload: IDragStartEventPayload): void {
|
||||||
|
|
||||||
this.originalEvent = payload.originalElement;;
|
this.originalEvent = payload.originalElement;;
|
||||||
this.draggedClone = payload.draggedClone;
|
this.draggedClone = payload.draggedClone;
|
||||||
|
|
@ -70,8 +70,8 @@ export class AllDayEventRenderer {
|
||||||
* Render an all-day event with pre-calculated layout
|
* Render an all-day event with pre-calculated layout
|
||||||
*/
|
*/
|
||||||
private renderAllDayEventWithLayout(
|
private renderAllDayEventWithLayout(
|
||||||
event: CalendarEvent,
|
event: ICalendarEvent,
|
||||||
layout: EventLayout
|
layout: IEventLayout
|
||||||
) {
|
) {
|
||||||
const container = this.getContainer();
|
const container = this.getContainer();
|
||||||
if (!container) return null;
|
if (!container) return null;
|
||||||
|
|
@ -109,7 +109,7 @@ export class AllDayEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Render all-day events for specific period using AllDayEventRenderer
|
* Render all-day events for specific period using AllDayEventRenderer
|
||||||
*/
|
*/
|
||||||
public renderAllDayEventsForPeriod(eventLayouts: EventLayout[]): void {
|
public renderAllDayEventsForPeriod(eventLayouts: IEventLayout[]): void {
|
||||||
this.clearAllDayEvents();
|
this.clearAllDayEvents();
|
||||||
|
|
||||||
eventLayouts.forEach(layout => {
|
eventLayouts.forEach(layout => {
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,28 @@
|
||||||
// Column rendering strategy interface and implementations
|
// Column rendering strategy interface and implementations
|
||||||
|
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { DateService } from '../utils/DateService';
|
import { DateService } from '../utils/DateService';
|
||||||
import { WorkHoursManager } from '../managers/WorkHoursManager';
|
import { WorkHoursManager } from '../managers/WorkHoursManager';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for column rendering strategies
|
* Interface for column rendering strategies
|
||||||
*/
|
*/
|
||||||
export interface ColumnRenderer {
|
export interface IColumnRenderer {
|
||||||
render(columnContainer: HTMLElement, context: ColumnRenderContext): void;
|
render(columnContainer: HTMLElement, context: IColumnRenderContext): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context for column rendering
|
* Context for column rendering
|
||||||
*/
|
*/
|
||||||
export interface ColumnRenderContext {
|
export interface IColumnRenderContext {
|
||||||
currentWeek: Date;
|
currentWeek: Date;
|
||||||
config: CalendarConfig;
|
config: Configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Date-based column renderer (original functionality)
|
* Date-based column renderer (original functionality)
|
||||||
*/
|
*/
|
||||||
export class DateColumnRenderer implements ColumnRenderer {
|
export class DateColumnRenderer implements IColumnRenderer {
|
||||||
private dateService: DateService;
|
private dateService: DateService;
|
||||||
private workHoursManager: WorkHoursManager;
|
private workHoursManager: WorkHoursManager;
|
||||||
|
|
||||||
|
|
@ -34,7 +34,7 @@ export class DateColumnRenderer implements ColumnRenderer {
|
||||||
this.workHoursManager = workHoursManager;
|
this.workHoursManager = workHoursManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
render(columnContainer: HTMLElement, context: ColumnRenderContext): void {
|
render(columnContainer: HTMLElement, context: IColumnRenderContext): void {
|
||||||
const { currentWeek, config } = context;
|
const { currentWeek, config } = context;
|
||||||
|
|
||||||
const workWeekSettings = config.getWorkWeekSettings();
|
const workWeekSettings = config.getWorkWeekSettings();
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,22 @@
|
||||||
// Header rendering strategy interface and implementations
|
// Header rendering strategy interface and implementations
|
||||||
|
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { DateService } from '../utils/DateService';
|
import { DateService } from '../utils/DateService';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for header rendering strategies
|
* Interface for header rendering strategies
|
||||||
*/
|
*/
|
||||||
export interface IHeaderRenderer {
|
export interface IHeaderRenderer {
|
||||||
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void;
|
render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Context for header rendering
|
* Context for header rendering
|
||||||
*/
|
*/
|
||||||
export interface HeaderRenderContext {
|
export interface IHeaderRenderContext {
|
||||||
currentWeek: Date;
|
currentWeek: Date;
|
||||||
config: CalendarConfig;
|
config: Configuration;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -25,7 +25,7 @@ export interface HeaderRenderContext {
|
||||||
export class DateHeaderRenderer implements IHeaderRenderer {
|
export class DateHeaderRenderer implements IHeaderRenderer {
|
||||||
private dateService!: DateService;
|
private dateService!: DateService;
|
||||||
|
|
||||||
render(calendarHeader: HTMLElement, context: HeaderRenderContext): void {
|
render(calendarHeader: HTMLElement, context: IHeaderRenderContext): void {
|
||||||
const { currentWeek, config } = context;
|
const { currentWeek, config } = context;
|
||||||
|
|
||||||
// FIRST: Always create all-day container as part of standard header structure
|
// FIRST: Always create all-day container as part of standard header structure
|
||||||
|
|
|
||||||
|
|
@ -1,29 +1,29 @@
|
||||||
// Event rendering strategy interface and implementations
|
// Event rendering strategy interface and implementations
|
||||||
|
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||||
import { PositionUtils } from '../utils/PositionUtils';
|
import { PositionUtils } from '../utils/PositionUtils';
|
||||||
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
|
import { IColumnBounds } from '../utils/ColumnDetectionUtils';
|
||||||
import { DragColumnChangeEventPayload, DragMoveEventPayload, DragStartEventPayload, DragMouseEnterColumnEventPayload } from '../types/EventTypes';
|
import { IDragColumnChangeEventPayload, IDragMoveEventPayload, IDragStartEventPayload, IDragMouseEnterColumnEventPayload } from '../types/EventTypes';
|
||||||
import { DateService } from '../utils/DateService';
|
import { DateService } from '../utils/DateService';
|
||||||
import { EventStackManager } from '../managers/EventStackManager';
|
import { EventStackManager } from '../managers/EventStackManager';
|
||||||
import { EventLayoutCoordinator, GridGroupLayout, StackedEventLayout } from '../managers/EventLayoutCoordinator';
|
import { EventLayoutCoordinator, IGridGroupLayout, IStackedEventLayout } from '../managers/EventLayoutCoordinator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for event rendering strategies
|
* Interface for event rendering strategies
|
||||||
*/
|
*/
|
||||||
export interface IEventRenderer {
|
export interface IEventRenderer {
|
||||||
renderEvents(events: CalendarEvent[], container: HTMLElement): void;
|
renderEvents(events: ICalendarEvent[], container: HTMLElement): void;
|
||||||
clearEvents(container?: HTMLElement): void;
|
clearEvents(container?: HTMLElement): void;
|
||||||
handleDragStart?(payload: DragStartEventPayload): void;
|
handleDragStart?(payload: IDragStartEventPayload): void;
|
||||||
handleDragMove?(payload: DragMoveEventPayload): void;
|
handleDragMove?(payload: IDragMoveEventPayload): void;
|
||||||
handleDragAutoScroll?(eventId: string, snappedY: number): void;
|
handleDragAutoScroll?(eventId: string, snappedY: number): void;
|
||||||
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void;
|
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void;
|
||||||
handleEventClick?(eventId: string, originalElement: HTMLElement): void;
|
handleEventClick?(eventId: string, originalElement: HTMLElement): void;
|
||||||
handleColumnChange?(payload: DragColumnChangeEventPayload): void;
|
handleColumnChange?(payload: IDragColumnChangeEventPayload): void;
|
||||||
handleNavigationCompleted?(): void;
|
handleNavigationCompleted?(): void;
|
||||||
handleConvertAllDayToTimed?(payload: DragMouseEnterColumnEventPayload): void;
|
handleConvertAllDayToTimed?(payload: IDragMouseEnterColumnEventPayload): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -34,7 +34,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
private dateService: DateService;
|
private dateService: DateService;
|
||||||
private stackManager: EventStackManager;
|
private stackManager: EventStackManager;
|
||||||
private layoutCoordinator: EventLayoutCoordinator;
|
private layoutCoordinator: EventLayoutCoordinator;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
private positionUtils: PositionUtils;
|
private positionUtils: PositionUtils;
|
||||||
private draggedClone: HTMLElement | null = null;
|
private draggedClone: HTMLElement | null = null;
|
||||||
private originalEvent: HTMLElement | null = null;
|
private originalEvent: HTMLElement | null = null;
|
||||||
|
|
@ -43,7 +43,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
dateService: DateService,
|
dateService: DateService,
|
||||||
stackManager: EventStackManager,
|
stackManager: EventStackManager,
|
||||||
layoutCoordinator: EventLayoutCoordinator,
|
layoutCoordinator: EventLayoutCoordinator,
|
||||||
config: CalendarConfig,
|
config: Configuration,
|
||||||
positionUtils: PositionUtils
|
positionUtils: PositionUtils
|
||||||
) {
|
) {
|
||||||
this.dateService = dateService;
|
this.dateService = dateService;
|
||||||
|
|
@ -63,7 +63,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Handle drag start event
|
* Handle drag start event
|
||||||
*/
|
*/
|
||||||
public handleDragStart(payload: DragStartEventPayload): void {
|
public handleDragStart(payload: IDragStartEventPayload): void {
|
||||||
|
|
||||||
this.originalEvent = payload.originalElement;;
|
this.originalEvent = payload.originalElement;;
|
||||||
|
|
||||||
|
|
@ -98,7 +98,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Handle drag move event
|
* Handle drag move event
|
||||||
*/
|
*/
|
||||||
public handleDragMove(payload: DragMoveEventPayload): void {
|
public handleDragMove(payload: IDragMoveEventPayload): void {
|
||||||
|
|
||||||
const swpEvent = payload.draggedClone as SwpEventElement;
|
const swpEvent = payload.draggedClone as SwpEventElement;
|
||||||
const columnDate = this.dateService.parseISO(payload.columnBounds!!.date);
|
const columnDate = this.dateService.parseISO(payload.columnBounds!!.date);
|
||||||
|
|
@ -108,7 +108,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Handle column change during drag
|
* Handle column change during drag
|
||||||
*/
|
*/
|
||||||
public handleColumnChange(payload: DragColumnChangeEventPayload): void {
|
public handleColumnChange(payload: IDragColumnChangeEventPayload): void {
|
||||||
|
|
||||||
const eventsLayer = payload.newColumn.element.querySelector('swp-events-layer');
|
const eventsLayer = payload.newColumn.element.querySelector('swp-events-layer');
|
||||||
if (eventsLayer && payload.draggedClone.parentElement !== eventsLayer) {
|
if (eventsLayer && payload.draggedClone.parentElement !== eventsLayer) {
|
||||||
|
|
@ -125,7 +125,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Handle conversion of all-day event to timed event
|
* Handle conversion of all-day event to timed event
|
||||||
*/
|
*/
|
||||||
public handleConvertAllDayToTimed(payload: DragMouseEnterColumnEventPayload): void {
|
public handleConvertAllDayToTimed(payload: IDragMouseEnterColumnEventPayload): void {
|
||||||
|
|
||||||
console.log('🎯 DateEventRenderer: Converting all-day to timed event', {
|
console.log('🎯 DateEventRenderer: Converting all-day to timed event', {
|
||||||
eventId: payload.calendarEvent.id,
|
eventId: payload.calendarEvent.id,
|
||||||
|
|
@ -165,7 +165,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Handle drag end event
|
* Handle drag end event
|
||||||
*/
|
*/
|
||||||
public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: ColumnBounds, finalY: number): void {
|
public handleDragEnd(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: IColumnBounds, finalY: number): void {
|
||||||
if (!draggedClone || !originalElement) {
|
if (!draggedClone || !originalElement) {
|
||||||
console.warn('Missing draggedClone or originalElement');
|
console.warn('Missing draggedClone or originalElement');
|
||||||
return;
|
return;
|
||||||
|
|
@ -209,7 +209,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
renderEvents(events: CalendarEvent[], container: HTMLElement): void {
|
renderEvents(events: ICalendarEvent[], container: HTMLElement): void {
|
||||||
// Filter out all-day events - they should be handled by AllDayEventRenderer
|
// Filter out all-day events - they should be handled by AllDayEventRenderer
|
||||||
const timedEvents = events.filter(event => !event.allDay);
|
const timedEvents = events.filter(event => !event.allDay);
|
||||||
|
|
||||||
|
|
@ -229,7 +229,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Render events in a column using combined stacking + grid algorithm
|
* Render events in a column using combined stacking + grid algorithm
|
||||||
*/
|
*/
|
||||||
private renderColumnEvents(columnEvents: CalendarEvent[], eventsLayer: HTMLElement): void {
|
private renderColumnEvents(columnEvents: ICalendarEvent[], eventsLayer: HTMLElement): void {
|
||||||
if (columnEvents.length === 0) return;
|
if (columnEvents.length === 0) return;
|
||||||
|
|
||||||
// Get layout from coordinator
|
// Get layout from coordinator
|
||||||
|
|
@ -251,7 +251,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Render events in a grid container (side-by-side with column sharing)
|
* Render events in a grid container (side-by-side with column sharing)
|
||||||
*/
|
*/
|
||||||
private renderGridGroup(gridGroup: GridGroupLayout, eventsLayer: HTMLElement): void {
|
private renderGridGroup(gridGroup: IGridGroupLayout, eventsLayer: HTMLElement): void {
|
||||||
const groupElement = document.createElement('swp-event-group');
|
const groupElement = document.createElement('swp-event-group');
|
||||||
|
|
||||||
// Add grid column class based on number of columns (not events)
|
// Add grid column class based on number of columns (not events)
|
||||||
|
|
@ -275,7 +275,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
|
|
||||||
// Render each column
|
// Render each column
|
||||||
const earliestEvent = gridGroup.events[0];
|
const earliestEvent = gridGroup.events[0];
|
||||||
gridGroup.columns.forEach(columnEvents => {
|
gridGroup.columns.forEach((columnEvents: ICalendarEvent[]) => {
|
||||||
const columnContainer = this.renderGridColumn(columnEvents, earliestEvent.start);
|
const columnContainer = this.renderGridColumn(columnEvents, earliestEvent.start);
|
||||||
groupElement.appendChild(columnContainer);
|
groupElement.appendChild(columnContainer);
|
||||||
});
|
});
|
||||||
|
|
@ -287,7 +287,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
* Render a single column within a grid group
|
* Render a single column within a grid group
|
||||||
* Column may contain multiple events that don't overlap
|
* Column may contain multiple events that don't overlap
|
||||||
*/
|
*/
|
||||||
private renderGridColumn(columnEvents: CalendarEvent[], containerStart: Date): HTMLElement {
|
private renderGridColumn(columnEvents: ICalendarEvent[], containerStart: Date): HTMLElement {
|
||||||
const columnContainer = document.createElement('div');
|
const columnContainer = document.createElement('div');
|
||||||
columnContainer.style.position = 'relative';
|
columnContainer.style.position = 'relative';
|
||||||
|
|
||||||
|
|
@ -302,7 +302,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
/**
|
/**
|
||||||
* Render event within a grid container (absolute positioning within column)
|
* Render event within a grid container (absolute positioning within column)
|
||||||
*/
|
*/
|
||||||
private renderEventInGrid(event: CalendarEvent, containerStart: Date): HTMLElement {
|
private renderEventInGrid(event: ICalendarEvent, containerStart: Date): HTMLElement {
|
||||||
const element = SwpEventElement.fromCalendarEvent(event);
|
const element = SwpEventElement.fromCalendarEvent(event);
|
||||||
|
|
||||||
// Calculate event height
|
// Calculate event height
|
||||||
|
|
@ -326,7 +326,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private renderEvent(event: CalendarEvent): HTMLElement {
|
private renderEvent(event: ICalendarEvent): HTMLElement {
|
||||||
const element = SwpEventElement.fromCalendarEvent(event);
|
const element = SwpEventElement.fromCalendarEvent(event);
|
||||||
|
|
||||||
// Apply positioning (moved from SwpEventElement.applyPositioning)
|
// Apply positioning (moved from SwpEventElement.applyPositioning)
|
||||||
|
|
@ -340,7 +340,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected calculateEventPosition(event: CalendarEvent): { top: number; height: number } {
|
protected calculateEventPosition(event: ICalendarEvent): { top: number; height: number } {
|
||||||
// Delegate to PositionUtils for centralized position calculation
|
// Delegate to PositionUtils for centralized position calculation
|
||||||
return this.positionUtils.calculateEventPosition(event.start, event.end);
|
return this.positionUtils.calculateEventPosition(event.start, event.end);
|
||||||
}
|
}
|
||||||
|
|
@ -366,7 +366,7 @@ export class DateEventRenderer implements IEventRenderer {
|
||||||
return Array.from(columns) as HTMLElement[];
|
return Array.from(columns) as HTMLElement[];
|
||||||
}
|
}
|
||||||
|
|
||||||
protected getEventsForColumn(column: HTMLElement, events: CalendarEvent[]): CalendarEvent[] {
|
protected getEventsForColumn(column: HTMLElement, events: ICalendarEvent[]): ICalendarEvent[] {
|
||||||
const columnDate = column.dataset.date;
|
const columnDate = column.dataset.date;
|
||||||
if (!columnDate) {
|
if (!columnDate) {
|
||||||
return [];
|
return [];
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
import { EventBus } from '../core/EventBus';
|
import { EventBus } from '../core/EventBus';
|
||||||
import { IEventBus, CalendarEvent, RenderContext } from '../types/CalendarTypes';
|
import { IEventBus, ICalendarEvent, IRenderContext } from '../types/CalendarTypes';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
import { EventManager } from '../managers/EventManager';
|
import { EventManager } from '../managers/EventManager';
|
||||||
import { IEventRenderer } from './EventRenderer';
|
import { IEventRenderer } from './EventRenderer';
|
||||||
import { SwpEventElement } from '../elements/SwpEventElement';
|
import { SwpEventElement } from '../elements/SwpEventElement';
|
||||||
import { DragStartEventPayload, DragMoveEventPayload, DragEndEventPayload, DragMouseEnterHeaderEventPayload, DragMouseLeaveHeaderEventPayload, DragMouseEnterColumnEventPayload, DragColumnChangeEventPayload, HeaderReadyEventPayload, ResizeEndEventPayload } from '../types/EventTypes';
|
import { IDragStartEventPayload, IDragMoveEventPayload, IDragEndEventPayload, IDragMouseEnterHeaderEventPayload, IDragMouseLeaveHeaderEventPayload, IDragMouseEnterColumnEventPayload, IDragColumnChangeEventPayload, IHeaderReadyEventPayload, IResizeEndEventPayload } from '../types/EventTypes';
|
||||||
import { DateService } from '../utils/DateService';
|
import { DateService } from '../utils/DateService';
|
||||||
import { ColumnBounds } from '../utils/ColumnDetectionUtils';
|
import { IColumnBounds } from '../utils/ColumnDetectionUtils';
|
||||||
/**
|
/**
|
||||||
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
|
* EventRenderingService - Render events i DOM med positionering using Strategy Pattern
|
||||||
* Håndterer event positioning og overlap detection
|
* Håndterer event positioning og overlap detection
|
||||||
|
|
@ -36,7 +36,7 @@ export class EventRenderingService {
|
||||||
/**
|
/**
|
||||||
* Render events in a specific container for a given period
|
* Render events in a specific container for a given period
|
||||||
*/
|
*/
|
||||||
public renderEvents(context: RenderContext): void {
|
public renderEvents(context: IRenderContext): void {
|
||||||
// Clear existing events in the specific container first
|
// Clear existing events in the specific container first
|
||||||
this.strategy.clearEvents(context.container);
|
this.strategy.clearEvents(context.container);
|
||||||
|
|
||||||
|
|
@ -133,7 +133,7 @@ export class EventRenderingService {
|
||||||
|
|
||||||
private setupDragStartListener(): void {
|
private setupDragStartListener(): void {
|
||||||
this.eventBus.on('drag:start', (event: Event) => {
|
this.eventBus.on('drag:start', (event: Event) => {
|
||||||
const dragStartPayload = (event as CustomEvent<DragStartEventPayload>).detail;
|
const dragStartPayload = (event as CustomEvent<IDragStartEventPayload>).detail;
|
||||||
|
|
||||||
if (dragStartPayload.originalElement.hasAttribute('data-allday')) {
|
if (dragStartPayload.originalElement.hasAttribute('data-allday')) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -147,7 +147,7 @@ export class EventRenderingService {
|
||||||
|
|
||||||
private setupDragMoveListener(): void {
|
private setupDragMoveListener(): void {
|
||||||
this.eventBus.on('drag:move', (event: Event) => {
|
this.eventBus.on('drag:move', (event: Event) => {
|
||||||
let dragEvent = (event as CustomEvent<DragMoveEventPayload>).detail;
|
let dragEvent = (event as CustomEvent<IDragMoveEventPayload>).detail;
|
||||||
|
|
||||||
if (dragEvent.draggedClone.hasAttribute('data-allday')) {
|
if (dragEvent.draggedClone.hasAttribute('data-allday')) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -161,7 +161,7 @@ export class EventRenderingService {
|
||||||
private setupDragEndListener(): void {
|
private setupDragEndListener(): void {
|
||||||
this.eventBus.on('drag:end', (event: Event) => {
|
this.eventBus.on('drag:end', (event: Event) => {
|
||||||
|
|
||||||
const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent<DragEndEventPayload>).detail;
|
const { originalElement: draggedElement, sourceColumn, finalPosition, target } = (event as CustomEvent<IDragEndEventPayload>).detail;
|
||||||
const finalColumn = finalPosition.column;
|
const finalColumn = finalPosition.column;
|
||||||
const finalY = finalPosition.snappedY;
|
const finalY = finalPosition.snappedY;
|
||||||
const eventId = draggedElement.dataset.eventId || '';
|
const eventId = draggedElement.dataset.eventId || '';
|
||||||
|
|
@ -207,7 +207,7 @@ export class EventRenderingService {
|
||||||
|
|
||||||
private setupDragColumnChangeListener(): void {
|
private setupDragColumnChangeListener(): void {
|
||||||
this.eventBus.on('drag:column-change', (event: Event) => {
|
this.eventBus.on('drag:column-change', (event: Event) => {
|
||||||
let columnChangeEvent = (event as CustomEvent<DragColumnChangeEventPayload>).detail;
|
let columnChangeEvent = (event as CustomEvent<IDragColumnChangeEventPayload>).detail;
|
||||||
|
|
||||||
// Filter: Only handle events where clone is NOT an all-day event (normal timed events)
|
// Filter: Only handle events where clone is NOT an all-day event (normal timed events)
|
||||||
if (columnChangeEvent.draggedClone && columnChangeEvent.draggedClone.hasAttribute('data-allday')) {
|
if (columnChangeEvent.draggedClone && columnChangeEvent.draggedClone.hasAttribute('data-allday')) {
|
||||||
|
|
@ -223,7 +223,7 @@ export class EventRenderingService {
|
||||||
private setupDragMouseLeaveHeaderListener(): void {
|
private setupDragMouseLeaveHeaderListener(): void {
|
||||||
|
|
||||||
this.dragMouseLeaveHeaderListener = (event: Event) => {
|
this.dragMouseLeaveHeaderListener = (event: Event) => {
|
||||||
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<DragMouseLeaveHeaderEventPayload>).detail;
|
const { targetDate, mousePosition, originalElement, draggedClone: cloneElement } = (event as CustomEvent<IDragMouseLeaveHeaderEventPayload>).detail;
|
||||||
|
|
||||||
if (cloneElement)
|
if (cloneElement)
|
||||||
cloneElement.style.display = '';
|
cloneElement.style.display = '';
|
||||||
|
|
@ -241,7 +241,7 @@ export class EventRenderingService {
|
||||||
|
|
||||||
private setupDragMouseEnterColumnListener(): void {
|
private setupDragMouseEnterColumnListener(): void {
|
||||||
this.eventBus.on('drag:mouseenter-column', (event: Event) => {
|
this.eventBus.on('drag:mouseenter-column', (event: Event) => {
|
||||||
const payload = (event as CustomEvent<DragMouseEnterColumnEventPayload>).detail;
|
const payload = (event as CustomEvent<IDragMouseEnterColumnEventPayload>).detail;
|
||||||
|
|
||||||
// Only handle if clone is an all-day event
|
// Only handle if clone is an all-day event
|
||||||
if (!payload.draggedClone.hasAttribute('data-allday')) {
|
if (!payload.draggedClone.hasAttribute('data-allday')) {
|
||||||
|
|
@ -263,7 +263,7 @@ export class EventRenderingService {
|
||||||
|
|
||||||
private setupResizeEndListener(): void {
|
private setupResizeEndListener(): void {
|
||||||
this.eventBus.on('resize:end', (event: Event) => {
|
this.eventBus.on('resize:end', (event: Event) => {
|
||||||
const { eventId, element } = (event as CustomEvent<ResizeEndEventPayload>).detail;
|
const { eventId, element } = (event as CustomEvent<IResizeEndEventPayload>).detail;
|
||||||
|
|
||||||
// Update event data in EventManager with new end time from resized element
|
// Update event data in EventManager with new end time from resized element
|
||||||
const swpEvent = element as SwpEventElement;
|
const swpEvent = element as SwpEventElement;
|
||||||
|
|
@ -306,7 +306,7 @@ export class EventRenderingService {
|
||||||
/**
|
/**
|
||||||
* Re-render affected columns after drag to recalculate stacking/grouping
|
* Re-render affected columns after drag to recalculate stacking/grouping
|
||||||
*/
|
*/
|
||||||
private reRenderAffectedColumns(sourceColumn: ColumnBounds | null, targetColumn: ColumnBounds | null): void {
|
private reRenderAffectedColumns(sourceColumn: IColumnBounds | null, targetColumn: IColumnBounds | null): void {
|
||||||
const columnsToRender = new Set<string>();
|
const columnsToRender = new Set<string>();
|
||||||
|
|
||||||
// Add source column if exists
|
// Add source column if exists
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { CalendarView } from '../types/CalendarTypes';
|
import { CalendarView } from '../types/CalendarTypes';
|
||||||
import { ColumnRenderer, ColumnRenderContext } from './ColumnRenderer';
|
import { IColumnRenderer, IColumnRenderContext } from './ColumnRenderer';
|
||||||
import { eventBus } from '../core/EventBus';
|
import { eventBus } from '../core/EventBus';
|
||||||
import { DateService } from '../utils/DateService';
|
import { DateService } from '../utils/DateService';
|
||||||
import { CoreEvents } from '../constants/CoreEvents';
|
import { CoreEvents } from '../constants/CoreEvents';
|
||||||
|
|
@ -82,13 +82,13 @@ export class GridRenderer {
|
||||||
private cachedGridContainer: HTMLElement | null = null;
|
private cachedGridContainer: HTMLElement | null = null;
|
||||||
private cachedTimeAxis: HTMLElement | null = null;
|
private cachedTimeAxis: HTMLElement | null = null;
|
||||||
private dateService: DateService;
|
private dateService: DateService;
|
||||||
private columnRenderer: ColumnRenderer;
|
private columnRenderer: IColumnRenderer;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
columnRenderer: ColumnRenderer,
|
columnRenderer: IColumnRenderer,
|
||||||
dateService: DateService,
|
dateService: DateService,
|
||||||
config: CalendarConfig
|
config: Configuration
|
||||||
) {
|
) {
|
||||||
this.dateService = dateService;
|
this.dateService = dateService;
|
||||||
this.columnRenderer = columnRenderer;
|
this.columnRenderer = columnRenderer;
|
||||||
|
|
@ -255,7 +255,7 @@ export class GridRenderer {
|
||||||
currentDate: Date,
|
currentDate: Date,
|
||||||
view: CalendarView
|
view: CalendarView
|
||||||
): void {
|
): void {
|
||||||
const context: ColumnRenderContext = {
|
const context: IColumnRenderContext = {
|
||||||
currentWeek: currentDate, // ColumnRenderer expects currentWeek property
|
currentWeek: currentDate, // ColumnRenderer expects currentWeek property
|
||||||
config: this.config
|
config: this.config
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* IEventRepository - Interface for event data loading
|
* IEventRepository - Interface for event data loading
|
||||||
|
|
@ -13,8 +13,8 @@ import { CalendarEvent } from '../types/CalendarTypes';
|
||||||
export interface IEventRepository {
|
export interface IEventRepository {
|
||||||
/**
|
/**
|
||||||
* Load all calendar events from the data source
|
* Load all calendar events from the data source
|
||||||
* @returns Promise resolving to array of CalendarEvent objects
|
* @returns Promise resolving to array of ICalendarEvent objects
|
||||||
* @throws Error if loading fails
|
* @throws Error if loading fails
|
||||||
*/
|
*/
|
||||||
loadEvents(): Promise<CalendarEvent[]>;
|
loadEvents(): Promise<ICalendarEvent[]>;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
import { IEventRepository } from './IEventRepository';
|
import { IEventRepository } from './IEventRepository';
|
||||||
|
|
||||||
interface RawEventData {
|
interface RawEventData {
|
||||||
|
|
@ -23,7 +23,7 @@ interface RawEventData {
|
||||||
export class MockEventRepository implements IEventRepository {
|
export class MockEventRepository implements IEventRepository {
|
||||||
private readonly dataUrl = 'data/mock-events.json';
|
private readonly dataUrl = 'data/mock-events.json';
|
||||||
|
|
||||||
public async loadEvents(): Promise<CalendarEvent[]> {
|
public async loadEvents(): Promise<ICalendarEvent[]> {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(this.dataUrl);
|
const response = await fetch(this.dataUrl);
|
||||||
|
|
||||||
|
|
@ -40,8 +40,8 @@ export class MockEventRepository implements IEventRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private processCalendarData(data: RawEventData[]): CalendarEvent[] {
|
private processCalendarData(data: RawEventData[]): ICalendarEvent[] {
|
||||||
return data.map((event): CalendarEvent => ({
|
return data.map((event): ICalendarEvent => ({
|
||||||
...event,
|
...event,
|
||||||
start: new Date(event.start),
|
start: new Date(event.start),
|
||||||
end: new Date(event.end),
|
end: new Date(event.end),
|
||||||
|
|
|
||||||
|
|
@ -8,13 +8,13 @@ export type CalendarView = ViewPeriod;
|
||||||
|
|
||||||
export type SyncStatus = 'synced' | 'pending' | 'error';
|
export type SyncStatus = 'synced' | 'pending' | 'error';
|
||||||
|
|
||||||
export interface RenderContext {
|
export interface IRenderContext {
|
||||||
container: HTMLElement;
|
container: HTMLElement;
|
||||||
startDate: Date;
|
startDate: Date;
|
||||||
endDate: Date;
|
endDate: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarEvent {
|
export interface ICalendarEvent {
|
||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
start: Date;
|
start: Date;
|
||||||
|
|
@ -55,13 +55,13 @@ export interface ICalendarConfig {
|
||||||
maxEventDuration: number; // Minutes
|
maxEventDuration: number; // Minutes
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventLogEntry {
|
export interface IEventLogEntry {
|
||||||
type: string;
|
type: string;
|
||||||
detail: unknown;
|
detail: unknown;
|
||||||
timestamp: number;
|
timestamp: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ListenerEntry {
|
export interface IListenerEntry {
|
||||||
eventType: string;
|
eventType: string;
|
||||||
handler: EventListener;
|
handler: EventListener;
|
||||||
options?: AddEventListenerOptions;
|
options?: AddEventListenerOptions;
|
||||||
|
|
@ -72,6 +72,6 @@ export interface IEventBus {
|
||||||
once(eventType: string, handler: EventListener): () => void;
|
once(eventType: string, handler: EventListener): () => void;
|
||||||
off(eventType: string, handler: EventListener): void;
|
off(eventType: string, handler: EventListener): void;
|
||||||
emit(eventType: string, detail?: unknown): boolean;
|
emit(eventType: string, detail?: unknown): boolean;
|
||||||
getEventLog(eventType?: string): EventLogEntry[];
|
getEventLog(eventType?: string): IEventLogEntry[];
|
||||||
setDebug(enabled: boolean): void;
|
setDebug(enabled: boolean): void;
|
||||||
}
|
}
|
||||||
|
|
@ -2,46 +2,46 @@
|
||||||
* Type definitions for drag and drop functionality
|
* Type definitions for drag and drop functionality
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface MousePosition {
|
export interface IMousePosition {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
clientX?: number;
|
clientX?: number;
|
||||||
clientY?: number;
|
clientY?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DragOffset {
|
export interface IDragOffset {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
offsetX?: number;
|
offsetX?: number;
|
||||||
offsetY?: number;
|
offsetY?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DragState {
|
export interface IDragState {
|
||||||
isDragging: boolean;
|
isDragging: boolean;
|
||||||
draggedElement: HTMLElement | null;
|
draggedElement: HTMLElement | null;
|
||||||
draggedClone: HTMLElement | null;
|
draggedClone: HTMLElement | null;
|
||||||
eventId: string | null;
|
eventId: string | null;
|
||||||
startColumn: string | null;
|
startColumn: string | null;
|
||||||
currentColumn: string | null;
|
currentColumn: string | null;
|
||||||
mouseOffset: DragOffset;
|
mouseOffset: IDragOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DragEndPosition {
|
export interface IDragEndPosition {
|
||||||
column: string;
|
column: string;
|
||||||
y: number;
|
y: number;
|
||||||
snappedY: number;
|
snappedY: number;
|
||||||
time?: Date;
|
time?: Date;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface StackLinkData {
|
export interface IStackLinkData {
|
||||||
prev?: string;
|
prev?: string;
|
||||||
next?: string;
|
next?: string;
|
||||||
isFirst?: boolean;
|
isFirst?: boolean;
|
||||||
isLast?: boolean;
|
isLast?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DragEventHandlers {
|
export interface IDragEventHandlers {
|
||||||
handleDragStart?(originalElement: HTMLElement, eventId: string, mouseOffset: DragOffset, column: string): void;
|
handleDragStart?(originalElement: HTMLElement, eventId: string, mouseOffset: IDragOffset, column: string): void;
|
||||||
handleDragMove?(eventId: string, snappedY: number, column: string, mouseOffset: DragOffset): void;
|
handleDragMove?(eventId: string, snappedY: number, column: string, mouseOffset: IDragOffset): void;
|
||||||
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: string, finalY: number): void;
|
handleDragEnd?(eventId: string, originalElement: HTMLElement, draggedClone: HTMLElement, finalColumn: string, finalY: number): void;
|
||||||
}
|
}
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
* Type definitions for calendar events and drag operations
|
* Type definitions for calendar events and drag operations
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { ColumnBounds } from "../utils/ColumnDetectionUtils";
|
import { IColumnBounds } from "../utils/ColumnDetectionUtils";
|
||||||
import { CalendarEvent } from "./CalendarTypes";
|
import { ICalendarEvent } from "./CalendarTypes";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Drag Event Payload Interfaces
|
* Drag Event Payload Interfaces
|
||||||
|
|
@ -11,89 +11,89 @@ import { CalendarEvent } from "./CalendarTypes";
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Common position interface
|
// Common position interface
|
||||||
export interface MousePosition {
|
export interface IMousePosition {
|
||||||
x: number;
|
x: number;
|
||||||
y: number;
|
y: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag start event payload
|
// Drag start event payload
|
||||||
export interface DragStartEventPayload {
|
export interface IDragStartEventPayload {
|
||||||
originalElement: HTMLElement;
|
originalElement: HTMLElement;
|
||||||
draggedClone: HTMLElement | null;
|
draggedClone: HTMLElement | null;
|
||||||
mousePosition: MousePosition;
|
mousePosition: IMousePosition;
|
||||||
mouseOffset: MousePosition;
|
mouseOffset: IMousePosition;
|
||||||
columnBounds: ColumnBounds | null;
|
columnBounds: IColumnBounds | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag move event payload
|
// Drag move event payload
|
||||||
export interface DragMoveEventPayload {
|
export interface IDragMoveEventPayload {
|
||||||
originalElement: HTMLElement;
|
originalElement: HTMLElement;
|
||||||
draggedClone: HTMLElement;
|
draggedClone: HTMLElement;
|
||||||
mousePosition: MousePosition;
|
mousePosition: IMousePosition;
|
||||||
mouseOffset: MousePosition;
|
mouseOffset: IMousePosition;
|
||||||
columnBounds: ColumnBounds | null;
|
columnBounds: IColumnBounds | null;
|
||||||
snappedY: number;
|
snappedY: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag end event payload
|
// Drag end event payload
|
||||||
export interface DragEndEventPayload {
|
export interface IDragEndEventPayload {
|
||||||
originalElement: HTMLElement;
|
originalElement: HTMLElement;
|
||||||
draggedClone: HTMLElement | null;
|
draggedClone: HTMLElement | null;
|
||||||
mousePosition: MousePosition;
|
mousePosition: IMousePosition;
|
||||||
sourceColumn: ColumnBounds;
|
sourceColumn: IColumnBounds;
|
||||||
finalPosition: {
|
finalPosition: {
|
||||||
column: ColumnBounds | null; // Where drag ended
|
column: IColumnBounds | null; // Where drag ended
|
||||||
snappedY: number;
|
snappedY: number;
|
||||||
};
|
};
|
||||||
target: 'swp-day-column' | 'swp-day-header' | null;
|
target: 'swp-day-column' | 'swp-day-header' | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag mouse enter header event payload
|
// Drag mouse enter header event payload
|
||||||
export interface DragMouseEnterHeaderEventPayload {
|
export interface IDragMouseEnterHeaderEventPayload {
|
||||||
targetColumn: ColumnBounds;
|
targetColumn: IColumnBounds;
|
||||||
mousePosition: MousePosition;
|
mousePosition: IMousePosition;
|
||||||
originalElement: HTMLElement | null;
|
originalElement: HTMLElement | null;
|
||||||
draggedClone: HTMLElement;
|
draggedClone: HTMLElement;
|
||||||
calendarEvent: CalendarEvent;
|
calendarEvent: ICalendarEvent;
|
||||||
replaceClone: (newClone: HTMLElement) => void;
|
replaceClone: (newClone: HTMLElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag mouse leave header event payload
|
// Drag mouse leave header event payload
|
||||||
export interface DragMouseLeaveHeaderEventPayload {
|
export interface IDragMouseLeaveHeaderEventPayload {
|
||||||
targetDate: string | null;
|
targetDate: string | null;
|
||||||
mousePosition: MousePosition;
|
mousePosition: IMousePosition;
|
||||||
originalElement: HTMLElement| null;
|
originalElement: HTMLElement| null;
|
||||||
draggedClone: HTMLElement| null;
|
draggedClone: HTMLElement| null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag mouse enter column event payload
|
// Drag mouse enter column event payload
|
||||||
export interface DragMouseEnterColumnEventPayload {
|
export interface IDragMouseEnterColumnEventPayload {
|
||||||
targetColumn: ColumnBounds;
|
targetColumn: IColumnBounds;
|
||||||
mousePosition: MousePosition;
|
mousePosition: IMousePosition;
|
||||||
snappedY: number;
|
snappedY: number;
|
||||||
originalElement: HTMLElement | null;
|
originalElement: HTMLElement | null;
|
||||||
draggedClone: HTMLElement;
|
draggedClone: HTMLElement;
|
||||||
calendarEvent: CalendarEvent;
|
calendarEvent: ICalendarEvent;
|
||||||
replaceClone: (newClone: HTMLElement) => void;
|
replaceClone: (newClone: HTMLElement) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drag column change event payload
|
// Drag column change event payload
|
||||||
export interface DragColumnChangeEventPayload {
|
export interface IDragColumnChangeEventPayload {
|
||||||
originalElement: HTMLElement;
|
originalElement: HTMLElement;
|
||||||
draggedClone: HTMLElement;
|
draggedClone: HTMLElement;
|
||||||
previousColumn: ColumnBounds | null;
|
previousColumn: IColumnBounds | null;
|
||||||
newColumn: ColumnBounds;
|
newColumn: IColumnBounds;
|
||||||
mousePosition: MousePosition;
|
mousePosition: IMousePosition;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Header ready event payload
|
// Header ready event payload
|
||||||
export interface HeaderReadyEventPayload {
|
export interface IHeaderReadyEventPayload {
|
||||||
headerElements: ColumnBounds[];
|
headerElements: IColumnBounds[];
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resize end event payload
|
// Resize end event payload
|
||||||
export interface ResizeEndEventPayload {
|
export interface IResizeEndEventPayload {
|
||||||
eventId: string;
|
eventId: string;
|
||||||
element: HTMLElement;
|
element: HTMLElement;
|
||||||
finalHeight: number;
|
finalHeight: number;
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
import { IEventBus, CalendarEvent, CalendarView } from './CalendarTypes';
|
import { IEventBus, ICalendarEvent, CalendarView } from './CalendarTypes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Complete type definition for all managers returned by ManagerFactory
|
* Complete type definition for all managers returned by ManagerFactory
|
||||||
*/
|
*/
|
||||||
export interface CalendarManagers {
|
export interface ICalendarManagers {
|
||||||
eventManager: EventManager;
|
eventManager: IEventManager;
|
||||||
eventRenderer: EventRenderingService;
|
eventRenderer: IEventRenderingService;
|
||||||
gridManager: GridManager;
|
gridManager: IGridManager;
|
||||||
scrollManager: ScrollManager;
|
scrollManager: IScrollManager;
|
||||||
navigationManager: unknown; // Avoid interface conflicts
|
navigationManager: unknown; // Avoid interface conflicts
|
||||||
viewManager: ViewManager;
|
viewManager: IViewManager;
|
||||||
calendarManager: CalendarManager;
|
calendarManager: ICalendarManager;
|
||||||
dragDropManager: unknown; // Avoid interface conflicts
|
dragDropManager: unknown; // Avoid interface conflicts
|
||||||
allDayManager: unknown; // Avoid interface conflicts
|
allDayManager: unknown; // Avoid interface conflicts
|
||||||
resizeHandleManager: ResizeHandleManager;
|
resizeHandleManager: IResizeHandleManager;
|
||||||
edgeScrollManager: unknown; // Avoid interface conflicts
|
edgeScrollManager: unknown; // Avoid interface conflicts
|
||||||
dragHoverManager: unknown; // Avoid interface conflicts
|
dragHoverManager: unknown; // Avoid interface conflicts
|
||||||
headerManager: unknown; // Avoid interface conflicts
|
headerManager: unknown; // Avoid interface conflicts
|
||||||
|
|
@ -27,50 +27,50 @@ interface IManager {
|
||||||
refresh?(): void;
|
refresh?(): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventManager extends IManager {
|
export interface IEventManager extends IManager {
|
||||||
loadData(): Promise<void>;
|
loadData(): Promise<void>;
|
||||||
getEvents(): CalendarEvent[];
|
getEvents(): ICalendarEvent[];
|
||||||
getEventsForPeriod(startDate: Date, endDate: Date): CalendarEvent[];
|
getEventsForPeriod(startDate: Date, endDate: Date): ICalendarEvent[];
|
||||||
navigateToEvent(eventId: string): boolean;
|
navigateToEvent(eventId: string): boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface EventRenderingService extends IManager {
|
export interface IEventRenderingService extends IManager {
|
||||||
// EventRenderingService doesn't have a render method in current implementation
|
// EventRenderingService doesn't have a render method in current implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GridManager extends IManager {
|
export interface IGridManager extends IManager {
|
||||||
render(): Promise<void>;
|
render(): Promise<void>;
|
||||||
getDisplayDates(): Date[];
|
getDisplayDates(): Date[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ScrollManager extends IManager {
|
export interface IScrollManager extends IManager {
|
||||||
scrollTo(scrollTop: number): void;
|
scrollTo(scrollTop: number): void;
|
||||||
scrollToHour(hour: number): void;
|
scrollToHour(hour: number): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use a more flexible interface that matches actual implementation
|
// Use a more flexible interface that matches actual implementation
|
||||||
export interface NavigationManager extends IManager {
|
export interface INavigationManager extends IManager {
|
||||||
[key: string]: unknown; // Allow any properties from actual implementation
|
[key: string]: unknown; // Allow any properties from actual implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewManager extends IManager {
|
export interface IViewManager extends IManager {
|
||||||
// ViewManager doesn't have setView in current implementation
|
// ViewManager doesn't have setView in current implementation
|
||||||
getCurrentView?(): CalendarView;
|
getCurrentView?(): CalendarView;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CalendarManager extends IManager {
|
export interface ICalendarManager extends IManager {
|
||||||
setView(view: CalendarView): void;
|
setView(view: CalendarView): void;
|
||||||
setCurrentDate(date: Date): void;
|
setCurrentDate(date: Date): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DragDropManager extends IManager {
|
export interface IDragDropManager extends IManager {
|
||||||
// DragDropManager has different interface in current implementation
|
// DragDropManager has different interface in current implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AllDayManager extends IManager {
|
export interface IAllDayManager extends IManager {
|
||||||
[key: string]: unknown; // Allow any properties from actual implementation
|
[key: string]: unknown; // Allow any properties from actual implementation
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ResizeHandleManager extends IManager {
|
export interface IResizeHandleManager extends IManager {
|
||||||
// ResizeHandleManager handles hover effects for resize handles
|
// ResizeHandleManager handles hover effects for resize handles
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { CalendarEvent } from '../types/CalendarTypes';
|
import { ICalendarEvent } from '../types/CalendarTypes';
|
||||||
|
|
||||||
export interface EventLayout {
|
export interface IEventLayout {
|
||||||
calenderEvent: CalendarEvent;
|
calenderEvent: ICalendarEvent;
|
||||||
gridArea: string; // "row-start / col-start / row-end / col-end"
|
gridArea: string; // "row-start / col-start / row-end / col-end"
|
||||||
startColumn: number;
|
startColumn: number;
|
||||||
endColumn: number;
|
endColumn: number;
|
||||||
|
|
@ -21,9 +21,9 @@ export class AllDayLayoutEngine {
|
||||||
/**
|
/**
|
||||||
* Calculate layout for all events using clean day-based logic
|
* Calculate layout for all events using clean day-based logic
|
||||||
*/
|
*/
|
||||||
public calculateLayout(events: CalendarEvent[]): EventLayout[] {
|
public calculateLayout(events: ICalendarEvent[]): IEventLayout[] {
|
||||||
|
|
||||||
let layouts: EventLayout[] = [];
|
let layouts: IEventLayout[] = [];
|
||||||
// Reset tracks for new calculation
|
// Reset tracks for new calculation
|
||||||
this.tracks = [new Array(this.weekDates.length).fill(false)];
|
this.tracks = [new Array(this.weekDates.length).fill(false)];
|
||||||
|
|
||||||
|
|
@ -43,7 +43,7 @@ export class AllDayLayoutEngine {
|
||||||
this.tracks[track][day] = true;
|
this.tracks[track][day] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const layout: EventLayout = {
|
const layout: IEventLayout = {
|
||||||
calenderEvent: event,
|
calenderEvent: event,
|
||||||
gridArea: `${track + 1} / ${startDay} / ${track + 2} / ${endDay + 1}`,
|
gridArea: `${track + 1} / ${startDay} / ${track + 2} / ${endDay + 1}`,
|
||||||
startColumn: startDay,
|
startColumn: startDay,
|
||||||
|
|
@ -89,7 +89,7 @@ export class AllDayLayoutEngine {
|
||||||
/**
|
/**
|
||||||
* Get start day index for event (1-based, 0 if not visible)
|
* Get start day index for event (1-based, 0 if not visible)
|
||||||
*/
|
*/
|
||||||
private getEventStartDay(event: CalendarEvent): number {
|
private getEventStartDay(event: ICalendarEvent): number {
|
||||||
const eventStartDate = this.formatDate(event.start);
|
const eventStartDate = this.formatDate(event.start);
|
||||||
const firstVisibleDate = this.weekDates[0];
|
const firstVisibleDate = this.weekDates[0];
|
||||||
|
|
||||||
|
|
@ -103,7 +103,7 @@ export class AllDayLayoutEngine {
|
||||||
/**
|
/**
|
||||||
* Get end day index for event (1-based, 0 if not visible)
|
* Get end day index for event (1-based, 0 if not visible)
|
||||||
*/
|
*/
|
||||||
private getEventEndDay(event: CalendarEvent): number {
|
private getEventEndDay(event: ICalendarEvent): number {
|
||||||
const eventEndDate = this.formatDate(event.end);
|
const eventEndDate = this.formatDate(event.end);
|
||||||
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
const lastVisibleDate = this.weekDates[this.weekDates.length - 1];
|
||||||
|
|
||||||
|
|
@ -117,7 +117,7 @@ export class AllDayLayoutEngine {
|
||||||
/**
|
/**
|
||||||
* Check if event is visible in the current date range
|
* Check if event is visible in the current date range
|
||||||
*/
|
*/
|
||||||
private isEventVisible(event: CalendarEvent): boolean {
|
private isEventVisible(event: ICalendarEvent): boolean {
|
||||||
if (this.weekDates.length === 0) return false;
|
if (this.weekDates.length === 0) return false;
|
||||||
|
|
||||||
const eventStartDate = this.formatDate(event.start);
|
const eventStartDate = this.formatDate(event.start);
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,10 @@
|
||||||
* Used by both DragDropManager and AllDayManager for consistent column detection
|
* Used by both DragDropManager and AllDayManager for consistent column detection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { MousePosition } from "../types/DragDropTypes";
|
import { IMousePosition } from "../types/DragDropTypes";
|
||||||
|
|
||||||
|
|
||||||
export interface ColumnBounds {
|
export interface IColumnBounds {
|
||||||
date: string;
|
date: string;
|
||||||
left: number;
|
left: number;
|
||||||
right: number;
|
right: number;
|
||||||
|
|
@ -16,7 +16,7 @@ export interface ColumnBounds {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ColumnDetectionUtils {
|
export class ColumnDetectionUtils {
|
||||||
private static columnBoundsCache: ColumnBounds[] = [];
|
private static columnBoundsCache: IColumnBounds[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update column bounds cache for coordinate-based column detection
|
* Update column bounds cache for coordinate-based column detection
|
||||||
|
|
@ -52,7 +52,7 @@ export class ColumnDetectionUtils {
|
||||||
/**
|
/**
|
||||||
* Get column date from X coordinate using cached bounds
|
* Get column date from X coordinate using cached bounds
|
||||||
*/
|
*/
|
||||||
public static getColumnBounds(position: MousePosition): ColumnBounds | null{
|
public static getColumnBounds(position: IMousePosition): IColumnBounds | null{
|
||||||
if (this.columnBoundsCache.length === 0) {
|
if (this.columnBoundsCache.length === 0) {
|
||||||
this.updateColumnBoundsCache();
|
this.updateColumnBoundsCache();
|
||||||
}
|
}
|
||||||
|
|
@ -70,7 +70,7 @@ export class ColumnDetectionUtils {
|
||||||
/**
|
/**
|
||||||
* Get column bounds by Date
|
* Get column bounds by Date
|
||||||
*/
|
*/
|
||||||
public static getColumnBoundsByDate(date: Date): ColumnBounds | null {
|
public static getColumnBoundsByDate(date: Date): IColumnBounds | null {
|
||||||
if (this.columnBoundsCache.length === 0) {
|
if (this.columnBoundsCache.length === 0) {
|
||||||
this.updateColumnBoundsCache();
|
this.updateColumnBoundsCache();
|
||||||
}
|
}
|
||||||
|
|
@ -84,12 +84,12 @@ export class ColumnDetectionUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static getColumns(): ColumnBounds[] {
|
public static getColumns(): IColumnBounds[] {
|
||||||
return [...this.columnBoundsCache];
|
return [...this.columnBoundsCache];
|
||||||
}
|
}
|
||||||
public static getHeaderColumns(): ColumnBounds[] {
|
public static getHeaderColumns(): IColumnBounds[] {
|
||||||
|
|
||||||
let dayHeaders: ColumnBounds[] = [];
|
let dayHeaders: IColumnBounds[] = [];
|
||||||
|
|
||||||
const dayColumns = document.querySelectorAll('swp-calendar-header swp-day-header');
|
const dayColumns = document.querySelectorAll('swp-calendar-header swp-day-header');
|
||||||
let index = 1;
|
let index = 1;
|
||||||
|
|
|
||||||
|
|
@ -29,12 +29,12 @@ import {
|
||||||
fromZonedTime,
|
fromZonedTime,
|
||||||
formatInTimeZone
|
formatInTimeZone
|
||||||
} from 'date-fns-tz';
|
} from 'date-fns-tz';
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
|
|
||||||
export class DateService {
|
export class DateService {
|
||||||
private timezone: string;
|
private timezone: string;
|
||||||
|
|
||||||
constructor(config: CalendarConfig) {
|
constructor(config: Configuration) {
|
||||||
this.timezone = config.getTimezone();
|
this.timezone = config.getTimezone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import { CalendarConfig } from '../core/CalendarConfig';
|
import { Configuration } from '../configuration/CalendarConfig';
|
||||||
import { ColumnBounds } from './ColumnDetectionUtils';
|
import { IColumnBounds } from './ColumnDetectionUtils';
|
||||||
import { DateService } from './DateService';
|
import { DateService } from './DateService';
|
||||||
import { TimeFormatter } from './TimeFormatter';
|
import { TimeFormatter } from './TimeFormatter';
|
||||||
|
|
||||||
|
|
@ -11,9 +11,9 @@ import { TimeFormatter } from './TimeFormatter';
|
||||||
*/
|
*/
|
||||||
export class PositionUtils {
|
export class PositionUtils {
|
||||||
private dateService: DateService;
|
private dateService: DateService;
|
||||||
private config: CalendarConfig;
|
private config: Configuration;
|
||||||
|
|
||||||
constructor(dateService: DateService, config: CalendarConfig) {
|
constructor(dateService: DateService, config: Configuration) {
|
||||||
this.dateService = dateService;
|
this.dateService = dateService;
|
||||||
this.config = config;
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
@ -169,7 +169,7 @@ export class PositionUtils {
|
||||||
/**
|
/**
|
||||||
* Beregn Y position fra mouse/touch koordinat
|
* Beregn Y position fra mouse/touch koordinat
|
||||||
*/
|
*/
|
||||||
public getPositionFromCoordinate(clientY: number, column: ColumnBounds): number {
|
public getPositionFromCoordinate(clientY: number, column: IColumnBounds): number {
|
||||||
|
|
||||||
const relativeY = clientY - column.boundingClientRect.top;
|
const relativeY = clientY - column.boundingClientRect.top;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
|
|
||||||
import { DateService } from './DateService';
|
import { DateService } from './DateService';
|
||||||
|
|
||||||
export interface TimeFormatSettings {
|
export interface ITimeFormatSettings {
|
||||||
timezone: string;
|
timezone: string;
|
||||||
use24HourFormat: boolean;
|
use24HourFormat: boolean;
|
||||||
locale: string;
|
locale: string;
|
||||||
|
|
@ -19,7 +19,7 @@ export interface TimeFormatSettings {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimeFormatter {
|
export class TimeFormatter {
|
||||||
private static settings: TimeFormatSettings = {
|
private static settings: ITimeFormatSettings = {
|
||||||
timezone: 'Europe/Copenhagen', // Default to Denmark
|
timezone: 'Europe/Copenhagen', // Default to Denmark
|
||||||
use24HourFormat: true, // 24-hour format standard in Denmark
|
use24HourFormat: true, // 24-hour format standard in Denmark
|
||||||
locale: 'da-DK', // Danish locale
|
locale: 'da-DK', // Danish locale
|
||||||
|
|
@ -44,7 +44,7 @@ export class TimeFormatter {
|
||||||
/**
|
/**
|
||||||
* Configure time formatting settings
|
* Configure time formatting settings
|
||||||
*/
|
*/
|
||||||
static configure(settings: Partial<TimeFormatSettings>): void {
|
static configure(settings: Partial<ITimeFormatSettings>): void {
|
||||||
TimeFormatter.settings = { ...TimeFormatter.settings, ...settings };
|
TimeFormatter.settings = { ...TimeFormatter.settings, ...settings };
|
||||||
// Reset DateService to pick up new timezone
|
// Reset DateService to pick up new timezone
|
||||||
TimeFormatter.dateService = null;
|
TimeFormatter.dateService = null;
|
||||||
|
|
|
||||||
87
wwwroot/data/calendar-config.json
Normal file
87
wwwroot/data/calendar-config.json
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
{
|
||||||
|
"gridSettings": {
|
||||||
|
"hourHeight": 80,
|
||||||
|
"dayStartHour": 6,
|
||||||
|
"dayEndHour": 22,
|
||||||
|
"workStartHour": 8,
|
||||||
|
"workEndHour": 17,
|
||||||
|
"snapInterval": 15,
|
||||||
|
"gridStartThresholdMinutes": 30,
|
||||||
|
"showCurrentTime": true,
|
||||||
|
"showWorkHours": true,
|
||||||
|
"fitToWidth": false,
|
||||||
|
"scrollToHour": 8
|
||||||
|
},
|
||||||
|
"dateViewSettings": {
|
||||||
|
"period": "week",
|
||||||
|
"weekDays": 7,
|
||||||
|
"firstDayOfWeek": 1,
|
||||||
|
"showAllDay": true
|
||||||
|
},
|
||||||
|
"timeFormatConfig": {
|
||||||
|
"timezone": "Europe/Copenhagen",
|
||||||
|
"use24HourFormat": true,
|
||||||
|
"locale": "da-DK",
|
||||||
|
"dateFormat": "technical",
|
||||||
|
"showSeconds": false
|
||||||
|
},
|
||||||
|
"workWeekPresets": {
|
||||||
|
"standard": {
|
||||||
|
"id": "standard",
|
||||||
|
"workDays": [1, 2, 3, 4, 5],
|
||||||
|
"totalDays": 5,
|
||||||
|
"firstWorkDay": 1
|
||||||
|
},
|
||||||
|
"compressed": {
|
||||||
|
"id": "compressed",
|
||||||
|
"workDays": [1, 2, 3, 4],
|
||||||
|
"totalDays": 4,
|
||||||
|
"firstWorkDay": 1
|
||||||
|
},
|
||||||
|
"midweek": {
|
||||||
|
"id": "midweek",
|
||||||
|
"workDays": [3, 4, 5],
|
||||||
|
"totalDays": 3,
|
||||||
|
"firstWorkDay": 3
|
||||||
|
},
|
||||||
|
"weekend": {
|
||||||
|
"id": "weekend",
|
||||||
|
"workDays": [6, 7],
|
||||||
|
"totalDays": 2,
|
||||||
|
"firstWorkDay": 6
|
||||||
|
},
|
||||||
|
"fullweek": {
|
||||||
|
"id": "fullweek",
|
||||||
|
"workDays": [1, 2, 3, 4, 5, 6, 7],
|
||||||
|
"totalDays": 7,
|
||||||
|
"firstWorkDay": 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"currentWorkWeek": "standard",
|
||||||
|
"scrollbar": {
|
||||||
|
"width": 16,
|
||||||
|
"color": "#666",
|
||||||
|
"trackColor": "#f0f0f0",
|
||||||
|
"hoverColor": "#b53f7aff",
|
||||||
|
"borderRadius": 6
|
||||||
|
},
|
||||||
|
"interaction": {
|
||||||
|
"allowDrag": true,
|
||||||
|
"allowResize": true,
|
||||||
|
"allowCreate": true
|
||||||
|
},
|
||||||
|
"api": {
|
||||||
|
"endpoint": "/api/events",
|
||||||
|
"dateFormat": "YYYY-MM-DD",
|
||||||
|
"timeFormat": "HH:mm"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"enableSearch": true,
|
||||||
|
"enableTouch": true
|
||||||
|
},
|
||||||
|
"eventDefaults": {
|
||||||
|
"defaultEventDuration": 60,
|
||||||
|
"minEventDuration": 15,
|
||||||
|
"maxEventDuration": 480
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -15,7 +15,7 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="calendar-wrapper">
|
<div class="calendar-wrapper">
|
||||||
<swp-calendar data-view="week" data-week-days="7" data-snap-interval="15" data-day-start-hour="6" data-day-end-hour="22" data-hour-height="80">
|
<swp-calendar>
|
||||||
<!-- Navigation Bar -->
|
<!-- Navigation Bar -->
|
||||||
<swp-calendar-nav>
|
<swp-calendar-nav>
|
||||||
<swp-nav-group>
|
<swp-nav-group>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue