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.
122 lines
3.9 KiB
TypeScript
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;
|
|
}
|
|
}
|