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.
This commit is contained in:
parent
2f58ceccd4
commit
c788a1695e
5 changed files with 166 additions and 248 deletions
|
|
@ -68,13 +68,6 @@ export class EventStackManager {
|
|||
return groups;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two events should share flexbox (within ±15 min)
|
||||
*/
|
||||
public shouldShareFlexbox(event1: CalendarEvent, event2: CalendarEvent): boolean {
|
||||
const diffMinutes = Math.abs(event1.start.getTime() - event2.start.getTime()) / (1000 * 60);
|
||||
return diffMinutes <= EventStackManager.FLEXBOX_START_THRESHOLD_MINUTES;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// PHASE 2: Container Type Decision
|
||||
|
|
@ -98,19 +91,6 @@ export class EventStackManager {
|
|||
return 'GRID';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if events within a group overlap each other
|
||||
*/
|
||||
private hasInternalOverlaps(events: CalendarEvent[]): boolean {
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
for (let j = i + 1; j < events.length; j++) {
|
||||
if (this.doEventsOverlap(events[i], events[j])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if two events overlap in time
|
||||
|
|
@ -119,117 +99,11 @@ export class EventStackManager {
|
|||
return event1.start < event2.end && event1.end > event2.start;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// PHASE 3: Late Arrivals (Nested Stacking)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Find events that start outside threshold (late arrivals)
|
||||
*/
|
||||
public findLateArrivals(groups: EventGroup[], allEvents: CalendarEvent[]): CalendarEvent[] {
|
||||
const eventsInGroups = new Set(groups.flatMap(g => g.events.map(e => e.id)));
|
||||
return allEvents.filter(event => !eventsInGroups.has(event.id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Find primary parent column for a late event (longest duration or first overlapping)
|
||||
*/
|
||||
public findPrimaryParentColumn(lateEvent: CalendarEvent, flexboxGroup: CalendarEvent[]): string | null {
|
||||
// Find all overlapping events in the flexbox group
|
||||
const overlapping = flexboxGroup.filter(event => this.doEventsOverlap(lateEvent, event));
|
||||
|
||||
if (overlapping.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Sort by duration (longest first)
|
||||
overlapping.sort((a, b) => {
|
||||
const durationA = b.end.getTime() - b.start.getTime();
|
||||
const durationB = a.end.getTime() - a.start.getTime();
|
||||
return durationA - durationB;
|
||||
});
|
||||
|
||||
return overlapping[0].id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate marginLeft for nested event (always 15px)
|
||||
*/
|
||||
public calculateNestedMarginLeft(): number {
|
||||
return EventStackManager.STACK_OFFSET_PX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate stackLevel for nested event (parent + 1)
|
||||
*/
|
||||
public calculateNestedStackLevel(parentStackLevel: number): number {
|
||||
return parentStackLevel + 1;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Flexbox Layout Calculations
|
||||
// Stack Level Calculation
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Calculate flex width for flexbox columns
|
||||
*/
|
||||
public calculateFlexWidth(columnCount: number): string {
|
||||
if (columnCount === 1) return '100%';
|
||||
if (columnCount === 2) return '50%';
|
||||
if (columnCount === 3) return '33.33%';
|
||||
if (columnCount === 4) return '25%';
|
||||
|
||||
// For 5+ columns, calculate percentage
|
||||
const percentage = (100 / columnCount).toFixed(2);
|
||||
return `${percentage}%`;
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// Existing Methods (from original TDD tests)
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Find all events that overlap with a given event
|
||||
*/
|
||||
public findOverlappingEvents(targetEvent: CalendarEvent, columnEvents: CalendarEvent[]): CalendarEvent[] {
|
||||
return columnEvents.filter(event => this.doEventsOverlap(targetEvent, event));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create stack links for overlapping events (naive sequential stacking)
|
||||
*/
|
||||
public createStackLinks(events: CalendarEvent[]): Map<string, StackLink> {
|
||||
const stackLinks = new Map<string, StackLink>();
|
||||
|
||||
if (events.length === 0) return stackLinks;
|
||||
|
||||
// Sort by start time (and by end time if start times are equal)
|
||||
const sorted = [...events].sort((a, b) => {
|
||||
const startDiff = a.start.getTime() - b.start.getTime();
|
||||
if (startDiff !== 0) return startDiff;
|
||||
return a.end.getTime() - b.end.getTime();
|
||||
});
|
||||
|
||||
// Create sequential stack
|
||||
sorted.forEach((event, index) => {
|
||||
const link: StackLink = {
|
||||
stackLevel: index
|
||||
};
|
||||
|
||||
if (index > 0) {
|
||||
link.prev = sorted[index - 1].id;
|
||||
}
|
||||
|
||||
if (index < sorted.length - 1) {
|
||||
link.next = sorted[index + 1].id;
|
||||
}
|
||||
|
||||
stackLinks.set(event.id, link);
|
||||
});
|
||||
|
||||
return stackLinks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create optimized stack links (events share levels when possible)
|
||||
*/
|
||||
|
|
@ -248,20 +122,16 @@ export class EventStackManager {
|
|||
other !== event && this.doEventsOverlap(event, other)
|
||||
);
|
||||
|
||||
console.log(`[EventStackManager] Event ${event.id} overlaps with:`, overlapping.map(e => e.id));
|
||||
|
||||
// Find the MINIMUM required level (must be above all overlapping events)
|
||||
let minRequiredLevel = 0;
|
||||
for (const other of overlapping) {
|
||||
const otherLink = stackLinks.get(other.id);
|
||||
if (otherLink) {
|
||||
console.log(` ${other.id} has stackLevel ${otherLink.stackLevel}`);
|
||||
// Must be at least one level above the overlapping event
|
||||
minRequiredLevel = Math.max(minRequiredLevel, otherLink.stackLevel + 1);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(` → Assigned stackLevel ${minRequiredLevel} (must be above all overlapping events)`);
|
||||
stackLinks.set(event.id, { stackLevel: minRequiredLevel });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue