Stacking and Sharecolumn WIP

This commit is contained in:
Janus C. H. Knudsen 2025-10-06 17:05:18 +02:00
parent c788a1695e
commit 6b8c5d4673
7 changed files with 763 additions and 51 deletions

View file

@ -4,16 +4,17 @@
* This class handles the creation and maintenance of "stack chains" - doubly-linked
* lists of overlapping events stored directly in DOM elements via data attributes.
*
* Implements 3-phase algorithm for flexbox + nested stacking:
* Phase 1: Group events by start time proximity (±15 min threshold)
* Phase 2: Decide container type (FLEXBOX vs STACKING)
* Phase 3: Handle late arrivals (nested stacking)
* Implements 3-phase algorithm for grid + nested stacking:
* Phase 1: Group events by start time proximity (configurable threshold)
* Phase 2: Decide container type (GRID vs STACKING)
* Phase 3: Handle late arrivals (nested stacking - NOT IMPLEMENTED)
*
* @see STACKING_CONCEPT.md for detailed documentation
* @see stacking-visualization.html for visual examples
*/
import { CalendarEvent } from '../types/CalendarTypes';
import { calendarConfig } from '../core/CalendarConfig';
export interface StackLink {
prev?: string; // Event ID of previous event in stack
@ -28,7 +29,6 @@ export interface EventGroup {
}
export class EventStackManager {
private static readonly FLEXBOX_START_THRESHOLD_MINUTES = 15;
private static readonly STACK_OFFSET_PX = 15;
// ============================================
@ -36,22 +36,49 @@ export class EventStackManager {
// ============================================
/**
* Group events by start time proximity (±15 min threshold)
* Group events by time conflicts (both start-to-start and end-to-start within threshold)
*
* Events are grouped if:
* 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)
*/
public groupEventsByStartTime(events: CalendarEvent[]): EventGroup[] {
if (events.length === 0) return [];
// Get threshold from config
const gridSettings = calendarConfig.getGridSettings();
const thresholdMinutes = gridSettings.gridStartThresholdMinutes;
// Sort events by start time
const sorted = [...events].sort((a, b) => a.start.getTime() - b.start.getTime());
const groups: EventGroup[] = [];
for (const event of sorted) {
// Find existing group within threshold
// Find existing group that this event conflicts with
const existingGroup = groups.find(group => {
const groupStart = group.startTime;
const diffMinutes = Math.abs(event.start.getTime() - groupStart.getTime()) / (1000 * 60);
return diffMinutes <= EventStackManager.FLEXBOX_START_THRESHOLD_MINUTES;
// Check if event conflicts with ANY event in the group
return group.events.some(groupEvent => {
// Start-to-start conflict: events start within threshold
const startToStartMinutes = Math.abs(event.start.getTime() - groupEvent.start.getTime()) / (1000 * 60);
if (startToStartMinutes <= thresholdMinutes) {
return true;
}
// End-to-start conflict: event starts within threshold before groupEvent ends
const endToStartMinutes = (groupEvent.end.getTime() - event.start.getTime()) / (1000 * 60);
if (endToStartMinutes > 0 && endToStartMinutes <= thresholdMinutes) {
return true;
}
// Also check reverse: groupEvent starts within threshold before event ends
const reverseEndToStart = (event.end.getTime() - groupEvent.start.getTime()) / (1000 * 60);
if (reverseEndToStart > 0 && reverseEndToStart <= thresholdMinutes) {
return true;
}
return false;
});
});
if (existingGroup) {
@ -76,7 +103,7 @@ export class EventStackManager {
/**
* Decide container type for a group of events
*
* Rule: Events starting simultaneously (within ±15 min) should ALWAYS use GRID,
* Rule: Events starting simultaneously (within threshold) should ALWAYS use GRID,
* even if they overlap each other. This provides better visual indication that
* events start at the same time.
*/
@ -85,7 +112,7 @@ export class EventStackManager {
return 'NONE';
}
// If events are grouped together (start within ±15 min), they should share columns (GRID)
// If events are grouped together (start within threshold), they should share columns (GRID)
// This is true EVEN if they overlap, because the visual priority is to show
// that they start simultaneously.
return 'GRID';