Some ignored filles was missing
This commit is contained in:
parent
7db22245e2
commit
fd5ab6bc0d
268 changed files with 31970 additions and 4 deletions
399
wwwroot/js/managers/SimpleEventOverlapManager.js
Normal file
399
wwwroot/js/managers/SimpleEventOverlapManager.js
Normal file
|
|
@ -0,0 +1,399 @@
|
|||
/**
|
||||
* SimpleEventOverlapManager - Clean, focused overlap management
|
||||
* Eliminates complex state tracking in favor of direct DOM manipulation
|
||||
*/
|
||||
import { calendarConfig } from '../core/CalendarConfig';
|
||||
export var OverlapType;
|
||||
(function (OverlapType) {
|
||||
OverlapType["NONE"] = "none";
|
||||
OverlapType["COLUMN_SHARING"] = "column_sharing";
|
||||
OverlapType["STACKING"] = "stacking";
|
||||
})(OverlapType || (OverlapType = {}));
|
||||
export class SimpleEventOverlapManager {
|
||||
/**
|
||||
* Detect overlap type between two DOM elements - pixel-based logic
|
||||
*/
|
||||
resolveOverlapType(element1, element2) {
|
||||
const top1 = parseInt(element1.style.top) || 0;
|
||||
const height1 = parseInt(element1.style.height) || 0;
|
||||
const bottom1 = top1 + height1;
|
||||
const top2 = parseInt(element2.style.top) || 0;
|
||||
const height2 = parseInt(element2.style.height) || 0;
|
||||
const bottom2 = top2 + height2;
|
||||
// Check if events overlap in pixel space
|
||||
const tolerance = 2;
|
||||
if (bottom1 <= (top2 + tolerance) || bottom2 <= (top1 + tolerance)) {
|
||||
return OverlapType.NONE;
|
||||
}
|
||||
// Events overlap - check start position difference for overlap type
|
||||
const startDifference = Math.abs(top1 - top2);
|
||||
// Over 40px start difference = stacking
|
||||
if (startDifference > 40) {
|
||||
return OverlapType.STACKING;
|
||||
}
|
||||
// Within 40px start difference = column sharing
|
||||
return OverlapType.COLUMN_SHARING;
|
||||
}
|
||||
/**
|
||||
* Group overlapping elements - pixel-based algorithm
|
||||
*/
|
||||
groupOverlappingElements(elements) {
|
||||
const groups = [];
|
||||
const processed = new Set();
|
||||
for (const element of elements) {
|
||||
if (processed.has(element))
|
||||
continue;
|
||||
// Find all elements that overlap with this one
|
||||
const overlapping = elements.filter(other => {
|
||||
if (processed.has(other))
|
||||
return false;
|
||||
return other === element || this.resolveOverlapType(element, other) !== OverlapType.NONE;
|
||||
});
|
||||
// Mark all as processed
|
||||
overlapping.forEach(e => processed.add(e));
|
||||
groups.push(overlapping);
|
||||
}
|
||||
return groups;
|
||||
}
|
||||
/**
|
||||
* Create flexbox container for column sharing - clean and simple
|
||||
*/
|
||||
createEventGroup(events, position) {
|
||||
const container = document.createElement('swp-event-group');
|
||||
return container;
|
||||
}
|
||||
/**
|
||||
* Add event to flexbox group - simple relative positioning
|
||||
*/
|
||||
addToEventGroup(container, eventElement) {
|
||||
// Set duration-based height
|
||||
const duration = eventElement.dataset.duration;
|
||||
if (duration) {
|
||||
const durationMinutes = parseInt(duration);
|
||||
const gridSettings = calendarConfig.getGridSettings();
|
||||
const height = (durationMinutes / 60) * gridSettings.hourHeight;
|
||||
eventElement.style.height = `${height - 3}px`;
|
||||
}
|
||||
// Flexbox styling
|
||||
eventElement.style.position = 'relative';
|
||||
eventElement.style.flex = '1';
|
||||
eventElement.style.minWidth = '50px';
|
||||
container.appendChild(eventElement);
|
||||
}
|
||||
/**
|
||||
* Create stacked event with data-attribute tracking
|
||||
*/
|
||||
createStackedEvent(eventElement, underlyingElement, stackLevel) {
|
||||
const marginLeft = stackLevel * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX;
|
||||
// Apply visual styling
|
||||
eventElement.style.marginLeft = `${marginLeft}px`;
|
||||
eventElement.style.left = '2px';
|
||||
eventElement.style.right = '2px';
|
||||
eventElement.style.zIndex = `${100 + stackLevel}`;
|
||||
// Set up stack linking via data attributes
|
||||
const eventId = eventElement.dataset.eventId;
|
||||
const underlyingId = underlyingElement.dataset.eventId;
|
||||
if (!eventId || !underlyingId) {
|
||||
console.warn('Missing event IDs for stack linking:', eventId, underlyingId);
|
||||
return;
|
||||
}
|
||||
// Find the last event in the stack chain
|
||||
let lastElement = underlyingElement;
|
||||
let lastLink = this.getStackLink(lastElement);
|
||||
// If underlying doesn't have stack link yet, create it
|
||||
if (!lastLink) {
|
||||
this.setStackLink(lastElement, { stackLevel: 0 });
|
||||
lastLink = { stackLevel: 0 };
|
||||
}
|
||||
// Traverse to find the end of the chain
|
||||
while (lastLink?.next) {
|
||||
const nextElement = this.findElementById(lastLink.next);
|
||||
if (!nextElement)
|
||||
break;
|
||||
lastElement = nextElement;
|
||||
lastLink = this.getStackLink(lastElement);
|
||||
}
|
||||
// Link the new event to the end of the chain
|
||||
const lastElementId = lastElement.dataset.eventId;
|
||||
this.setStackLink(lastElement, {
|
||||
...lastLink,
|
||||
next: eventId
|
||||
});
|
||||
this.setStackLink(eventElement, {
|
||||
prev: lastElementId,
|
||||
stackLevel: stackLevel
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Remove stacked styling with proper stack re-linking
|
||||
*/
|
||||
removeStackedStyling(eventElement) {
|
||||
// Clear visual styling
|
||||
eventElement.style.marginLeft = '';
|
||||
eventElement.style.zIndex = '';
|
||||
eventElement.style.left = '2px';
|
||||
eventElement.style.right = '2px';
|
||||
// Handle stack chain re-linking
|
||||
const link = this.getStackLink(eventElement);
|
||||
if (link) {
|
||||
// Re-link prev and next events
|
||||
if (link.prev && link.next) {
|
||||
// Middle element - link prev to next
|
||||
const prevElement = this.findElementById(link.prev);
|
||||
const nextElement = this.findElementById(link.next);
|
||||
if (prevElement && nextElement) {
|
||||
const prevLink = this.getStackLink(prevElement);
|
||||
const nextLink = this.getStackLink(nextElement);
|
||||
// CRITICAL: Check if prev and next actually overlap without the middle element
|
||||
const actuallyOverlap = this.resolveOverlapType(prevElement, nextElement);
|
||||
if (!actuallyOverlap) {
|
||||
// CHAIN BREAKING: prev and next don't overlap - break the chain
|
||||
console.log('Breaking stack chain - events do not overlap directly');
|
||||
// Prev element: remove next link (becomes end of its own chain)
|
||||
this.setStackLink(prevElement, {
|
||||
...prevLink,
|
||||
next: undefined
|
||||
});
|
||||
// Next element: becomes standalone (remove all stack links and styling)
|
||||
this.setStackLink(nextElement, null);
|
||||
nextElement.style.marginLeft = '';
|
||||
nextElement.style.zIndex = '';
|
||||
// If next element had subsequent events, they also become standalone
|
||||
if (nextLink?.next) {
|
||||
let subsequentId = nextLink.next;
|
||||
while (subsequentId) {
|
||||
const subsequentElement = this.findElementById(subsequentId);
|
||||
if (!subsequentElement)
|
||||
break;
|
||||
const subsequentLink = this.getStackLink(subsequentElement);
|
||||
this.setStackLink(subsequentElement, null);
|
||||
subsequentElement.style.marginLeft = '';
|
||||
subsequentElement.style.zIndex = '';
|
||||
subsequentId = subsequentLink?.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
// NORMAL STACKING: they overlap, maintain the chain
|
||||
this.setStackLink(prevElement, {
|
||||
...prevLink,
|
||||
next: link.next
|
||||
});
|
||||
const correctStackLevel = (prevLink?.stackLevel ?? 0) + 1;
|
||||
this.setStackLink(nextElement, {
|
||||
...nextLink,
|
||||
prev: link.prev,
|
||||
stackLevel: correctStackLevel
|
||||
});
|
||||
// Update visual styling to match new stackLevel
|
||||
const marginLeft = correctStackLevel * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX;
|
||||
nextElement.style.marginLeft = `${marginLeft}px`;
|
||||
nextElement.style.zIndex = `${100 + correctStackLevel}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (link.prev) {
|
||||
// Last element - remove next link from prev
|
||||
const prevElement = this.findElementById(link.prev);
|
||||
if (prevElement) {
|
||||
const prevLink = this.getStackLink(prevElement);
|
||||
this.setStackLink(prevElement, {
|
||||
...prevLink,
|
||||
next: undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
else if (link.next) {
|
||||
// First element - remove prev link from next
|
||||
const nextElement = this.findElementById(link.next);
|
||||
if (nextElement) {
|
||||
const nextLink = this.getStackLink(nextElement);
|
||||
this.setStackLink(nextElement, {
|
||||
...nextLink,
|
||||
prev: undefined,
|
||||
stackLevel: 0 // Next becomes the base event
|
||||
});
|
||||
}
|
||||
}
|
||||
// Only update subsequent stack levels if we didn't break the chain
|
||||
if (link.prev && link.next) {
|
||||
const nextElement = this.findElementById(link.next);
|
||||
const nextLink = nextElement ? this.getStackLink(nextElement) : null;
|
||||
// If next element still has a stack link, the chain wasn't broken
|
||||
if (nextLink && nextLink.next) {
|
||||
this.updateSubsequentStackLevels(nextLink.next, -1);
|
||||
}
|
||||
// If nextLink is null, chain was broken - no subsequent updates needed
|
||||
}
|
||||
else {
|
||||
// First or last removal - update all subsequent
|
||||
this.updateSubsequentStackLevels(link.next, -1);
|
||||
}
|
||||
// Clear this element's stack link
|
||||
this.setStackLink(eventElement, null);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Update stack levels for all events following a given event ID
|
||||
*/
|
||||
updateSubsequentStackLevels(startEventId, levelDelta) {
|
||||
let currentId = startEventId;
|
||||
while (currentId) {
|
||||
const currentElement = this.findElementById(currentId);
|
||||
if (!currentElement)
|
||||
break;
|
||||
const currentLink = this.getStackLink(currentElement);
|
||||
if (!currentLink)
|
||||
break;
|
||||
// Update stack level
|
||||
const newLevel = Math.max(0, currentLink.stackLevel + levelDelta);
|
||||
this.setStackLink(currentElement, {
|
||||
...currentLink,
|
||||
stackLevel: newLevel
|
||||
});
|
||||
// Update visual styling
|
||||
const marginLeft = newLevel * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX;
|
||||
currentElement.style.marginLeft = `${marginLeft}px`;
|
||||
currentElement.style.zIndex = `${100 + newLevel}`;
|
||||
currentId = currentLink.next;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* Check if element is stacked - check both style and data-stack-link
|
||||
*/
|
||||
isStackedEvent(element) {
|
||||
const marginLeft = element.style.marginLeft;
|
||||
const hasMarginLeft = marginLeft !== '' && marginLeft !== '0px';
|
||||
const hasStackLink = this.getStackLink(element) !== null;
|
||||
return hasMarginLeft || hasStackLink;
|
||||
}
|
||||
/**
|
||||
* Remove event from group with proper cleanup
|
||||
*/
|
||||
removeFromEventGroup(container, eventId) {
|
||||
const eventElement = container.querySelector(`swp-event[data-event-id="${eventId}"]`);
|
||||
if (!eventElement)
|
||||
return false;
|
||||
// Simply remove the element - no position calculation needed since it's being removed
|
||||
eventElement.remove();
|
||||
// Handle remaining events
|
||||
const remainingEvents = container.querySelectorAll('swp-event');
|
||||
const remainingCount = remainingEvents.length;
|
||||
if (remainingCount === 0) {
|
||||
container.remove();
|
||||
return true;
|
||||
}
|
||||
if (remainingCount === 1) {
|
||||
const remainingEvent = remainingEvents[0];
|
||||
// Convert last event back to absolute positioning - use current pixel position
|
||||
const currentTop = parseInt(remainingEvent.style.top) || 0;
|
||||
remainingEvent.style.position = 'absolute';
|
||||
remainingEvent.style.top = `${currentTop}px`;
|
||||
remainingEvent.style.left = '2px';
|
||||
remainingEvent.style.right = '2px';
|
||||
remainingEvent.style.flex = '';
|
||||
remainingEvent.style.minWidth = '';
|
||||
container.parentElement?.insertBefore(remainingEvent, container);
|
||||
container.remove();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
/**
|
||||
* Restack events in container - respects separate stack chains
|
||||
*/
|
||||
restackEventsInContainer(container) {
|
||||
const stackedEvents = Array.from(container.querySelectorAll('swp-event'))
|
||||
.filter(el => this.isStackedEvent(el));
|
||||
if (stackedEvents.length === 0)
|
||||
return;
|
||||
// Group events by their stack chains
|
||||
const processedEventIds = new Set();
|
||||
const stackChains = [];
|
||||
for (const element of stackedEvents) {
|
||||
const eventId = element.dataset.eventId;
|
||||
if (!eventId || processedEventIds.has(eventId))
|
||||
continue;
|
||||
// Find the root of this stack chain (stackLevel 0 or no prev link)
|
||||
let rootElement = element;
|
||||
let rootLink = this.getStackLink(rootElement);
|
||||
while (rootLink?.prev) {
|
||||
const prevElement = this.findElementById(rootLink.prev);
|
||||
if (!prevElement)
|
||||
break;
|
||||
rootElement = prevElement;
|
||||
rootLink = this.getStackLink(rootElement);
|
||||
}
|
||||
// Collect all elements in this chain
|
||||
const chain = [];
|
||||
let currentElement = rootElement;
|
||||
while (currentElement) {
|
||||
chain.push(currentElement);
|
||||
processedEventIds.add(currentElement.dataset.eventId);
|
||||
const currentLink = this.getStackLink(currentElement);
|
||||
if (!currentLink?.next)
|
||||
break;
|
||||
const nextElement = this.findElementById(currentLink.next);
|
||||
if (!nextElement)
|
||||
break;
|
||||
currentElement = nextElement;
|
||||
}
|
||||
if (chain.length > 1) { // Only add chains with multiple events
|
||||
stackChains.push(chain);
|
||||
}
|
||||
}
|
||||
// Re-stack each chain separately
|
||||
stackChains.forEach(chain => {
|
||||
chain.forEach((element, index) => {
|
||||
const marginLeft = index * SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX;
|
||||
element.style.marginLeft = `${marginLeft}px`;
|
||||
element.style.zIndex = `${100 + index}`;
|
||||
// Update the data-stack-link with correct stackLevel
|
||||
const link = this.getStackLink(element);
|
||||
if (link) {
|
||||
this.setStackLink(element, {
|
||||
...link,
|
||||
stackLevel: index
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Utility methods - simple DOM traversal
|
||||
*/
|
||||
getEventGroup(eventElement) {
|
||||
return eventElement.closest('swp-event-group');
|
||||
}
|
||||
isInEventGroup(element) {
|
||||
return this.getEventGroup(element) !== null;
|
||||
}
|
||||
/**
|
||||
* Helper methods for data-attribute based stack tracking
|
||||
*/
|
||||
getStackLink(element) {
|
||||
const linkData = element.dataset.stackLink;
|
||||
if (!linkData)
|
||||
return null;
|
||||
try {
|
||||
return JSON.parse(linkData);
|
||||
}
|
||||
catch (e) {
|
||||
console.warn('Failed to parse stack link data:', linkData, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
setStackLink(element, link) {
|
||||
if (link === null) {
|
||||
delete element.dataset.stackLink;
|
||||
}
|
||||
else {
|
||||
element.dataset.stackLink = JSON.stringify(link);
|
||||
}
|
||||
}
|
||||
findElementById(eventId) {
|
||||
return document.querySelector(`swp-event[data-event-id="${eventId}"]`);
|
||||
}
|
||||
}
|
||||
SimpleEventOverlapManager.STACKING_WIDTH_REDUCTION_PX = 15;
|
||||
//# sourceMappingURL=SimpleEventOverlapManager.js.map
|
||||
Loading…
Add table
Add a link
Reference in a new issue