Calendar/src/managers/EventLayoutCoordinator.ts
Janus C. H. Knudsen c788a1695e Extracts event layout calculations
Moves complex layout determination logic (grid grouping, stack levels, positioning) from `EventRenderer` to a new `EventLayoutCoordinator` class.

Delegates layout responsibilities to the coordinator, significantly simplifying the `EventRenderer`'s `renderColumnEvents` method. Refines `EventStackManager` by removing deprecated layout methods, consolidating its role to event grouping and core stack level management.

Improves modularity and separation of concerns within the rendering pipeline.
2025-10-06 00:24:13 +02:00

122 lines
3.9 KiB
TypeScript

/**
* EventLayoutCoordinator - Coordinates event layout calculations
*
* Separates layout logic from rendering concerns.
* Calculates stack levels, groups events, and determines rendering strategy.
*/
import { CalendarEvent } from '../types/CalendarTypes';
import { EventStackManager, EventGroup, StackLink } from './EventStackManager';
import { PositionUtils } from '../utils/PositionUtils';
export interface GridGroupLayout {
events: CalendarEvent[];
stackLevel: number;
position: { top: number };
}
export interface StackedEventLayout {
event: CalendarEvent;
stackLink: StackLink;
position: { top: number; height: number };
}
export interface ColumnLayout {
gridGroups: GridGroupLayout[];
stackedEvents: StackedEventLayout[];
}
export class EventLayoutCoordinator {
private stackManager: EventStackManager;
constructor() {
this.stackManager = new EventStackManager();
}
/**
* Calculate complete layout for a column of events
*/
public calculateColumnLayout(columnEvents: CalendarEvent[]): ColumnLayout {
if (columnEvents.length === 0) {
return { gridGroups: [], stackedEvents: [] };
}
// Step 1: Calculate stack levels for ALL events first (to understand overlaps)
const allStackLinks = this.stackManager.createOptimizedStackLinks(columnEvents);
// Step 2: Find grid candidates (start together ±15 min)
const groups = this.stackManager.groupEventsByStartTime(columnEvents);
const gridGroups = groups.filter(group => {
if (group.events.length <= 1) return false;
group.containerType = this.stackManager.decideContainerType(group);
return group.containerType === 'GRID';
});
// Step 3: Build grid group layouts
const gridGroupLayouts: GridGroupLayout[] = [];
const renderedEventIds = new Set<string>();
gridGroups.forEach(group => {
const gridStackLevel = this.calculateGridGroupStackLevel(group, columnEvents, allStackLinks);
const earliestEvent = group.events[0];
const position = PositionUtils.calculateEventPosition(earliestEvent.start, earliestEvent.end);
gridGroupLayouts.push({
events: group.events,
stackLevel: gridStackLevel,
position: { top: position.top + 1 }
});
group.events.forEach(e => renderedEventIds.add(e.id));
});
// Step 4: Build stacked event layouts for remaining events
const remainingEvents = columnEvents.filter(e => !renderedEventIds.has(e.id));
const stackedEventLayouts: StackedEventLayout[] = remainingEvents.map(event => {
const stackLink = allStackLinks.get(event.id)!;
const position = PositionUtils.calculateEventPosition(event.start, event.end);
return {
event,
stackLink,
position: { top: position.top + 1, height: position.height - 3 }
};
});
return {
gridGroups: gridGroupLayouts,
stackedEvents: stackedEventLayouts
};
}
/**
* Calculate stack level for a grid group based on what it overlaps OUTSIDE the group
*/
private calculateGridGroupStackLevel(
group: EventGroup,
allEvents: CalendarEvent[],
stackLinks: Map<string, StackLink>
): number {
const groupEventIds = new Set(group.events.map(e => e.id));
// Find all events OUTSIDE this group
const outsideEvents = allEvents.filter(e => !groupEventIds.has(e.id));
// Find the highest stackLevel of any event that overlaps with ANY event in the grid group
let maxOverlappingLevel = -1;
for (const gridEvent of group.events) {
for (const outsideEvent of outsideEvents) {
if (this.stackManager.doEventsOverlap(gridEvent, outsideEvent)) {
const outsideLink = stackLinks.get(outsideEvent.id);
if (outsideLink) {
maxOverlappingLevel = Math.max(maxOverlappingLevel, outsideLink.stackLevel);
}
}
}
}
// Grid group should be one level above the highest overlapping event
return maxOverlappingLevel + 1;
}
}