mirror of
https://github.com/tutao/tutanota.git
synced 2025-10-19 07:53:47 +00:00
WIP - Working
This commit is contained in:
parent
a2b9d0362e
commit
1a7cc06c6d
1 changed files with 151 additions and 188 deletions
|
@ -1,7 +1,6 @@
|
|||
import m, { ChildArray, Children, Component, Vnode, VnodeDOM } from "mithril"
|
||||
import type { CalendarEvent } from "../../api/entities/tutanota/TypeRefs.js"
|
||||
import { Time } from "../date/Time"
|
||||
import { deepMemoized, downcast, first, getStartOfNextDay } from "@tutao/tutanota-utils"
|
||||
import { deepMemoized } from "@tutao/tutanota-utils"
|
||||
import { px } from "../../gui/size.js"
|
||||
import { Icon, IconSize } from "../../gui/base/Icon.js"
|
||||
import { Icons } from "../../gui/base/icons/Icons.js"
|
||||
|
@ -9,44 +8,18 @@ import { theme } from "../../gui/theme.js"
|
|||
import { colorForBg } from "../../gui/base/GuiUtils.js"
|
||||
import { getTimeZone } from "../date/CalendarUtils"
|
||||
import { EventRenderWrapper } from "../../../calendar-app/calendar/view/CalendarViewModel.js"
|
||||
import { DateTime } from "../../../../libs/luxon.js"
|
||||
import { TimeColumn } from "./TimeColumn"
|
||||
import { elementIdPart } from "../../api/common/utils/EntityUtils"
|
||||
|
||||
export interface TimeViewEventWrapper {
|
||||
event: EventRenderWrapper
|
||||
conflictsWithMainEvent: boolean
|
||||
/**
|
||||
* Color applied to the event bubble background
|
||||
*/
|
||||
color: string
|
||||
/**
|
||||
* Applies special style, color border and shows success or warning icon before event title
|
||||
*/
|
||||
featured: boolean
|
||||
}
|
||||
|
||||
export const TIME_SCALE_BASE_VALUE = 60 // 60 minutes
|
||||
/**
|
||||
* {@link TIME_SCALE_BASE_VALUE} / {@link TimeScale} = Time interval applied to the agenda time column
|
||||
* @example
|
||||
* const timeScale1: TimeScale = 1
|
||||
* const intervalOf60Minutes = TIME_SCALE_BASE_VALUE / timeScale1
|
||||
*
|
||||
* const timeScale2: TimeScale = 2
|
||||
* const intervalOf30Minutes = TIME_SCALE_BASE_VALUE / timeScale2
|
||||
*
|
||||
* const timeScale4: TimeScale = 4
|
||||
* const intervalOf15Minutes = TIME_SCALE_BASE_VALUE / timeScale4
|
||||
* */
|
||||
export type TimeScale = 1 | 2 | 4 // FIXME update docs
|
||||
/**
|
||||
* Tuple of {@link TimeScale} and timeScaleInMinutes
|
||||
* @example
|
||||
* const scale = 4
|
||||
* const timeScaleInMinutes = TIME_SCALE_BASE_VALUE / scale
|
||||
* const myTuple: TimeScaleTuple = [scale, timeScaleInMinutes]
|
||||
*/
|
||||
export const TIME_SCALE_BASE_VALUE = 60
|
||||
export type TimeScale = 1 | 2 | 4
|
||||
export type TimeScaleTuple = [TimeScale, number]
|
||||
export type TimeRange = {
|
||||
start: Time
|
||||
|
@ -59,22 +32,9 @@ export enum EventConflictRenderPolicy {
|
|||
}
|
||||
|
||||
export interface TimeViewAttributes {
|
||||
/**
|
||||
* Days to render events
|
||||
*/
|
||||
dates: Array<Date>
|
||||
events: Array<TimeViewEventWrapper>
|
||||
/**
|
||||
* {@link TimeScale} applied to this TimeView
|
||||
*/
|
||||
timeScale: TimeScale
|
||||
/**
|
||||
* {@link TimeRange} used to generate the Time column and the number of time intervals/slots to position the events
|
||||
*
|
||||
* 0 <= start < end < 24:00
|
||||
*
|
||||
* End time is inclusive and is considered the beginning of the last interval
|
||||
*/
|
||||
timeRange: TimeRange
|
||||
conflictRenderPolicy: EventConflictRenderPolicy
|
||||
timeIndicator?: Time
|
||||
|
@ -98,13 +58,18 @@ interface EventRowData {
|
|||
rowEnd: number
|
||||
}
|
||||
|
||||
interface BlockingInfo {
|
||||
canExpand: boolean
|
||||
blockingEvents: Map<Id, boolean>
|
||||
}
|
||||
|
||||
const BASE_EVENT_BUBBLE_SPAN_SIZE = 1
|
||||
|
||||
export class TimeView implements Component<TimeViewAttributes> {
|
||||
private timeRowHeight?: number
|
||||
private blockingGroupsCache: Map<Id, Array<Map<Id, boolean>>> = new Map()
|
||||
private expandabilityCache: Map<Id, BlockingInfo> = new Map()
|
||||
|
||||
/*
|
||||
* Must filter the array to get events using the same logic from conflict detection
|
||||
* But instead of event start and end we use range start end
|
||||
*/
|
||||
view({ attrs }: Vnode<TimeViewAttributes>) {
|
||||
const { timeScale, timeRange, events, conflictRenderPolicy, dates, timeIndicator, hasAnyConflict } = attrs
|
||||
const timeColumnIntervals = TimeColumn.createTimeColumnIntervals(attrs.timeScale, attrs.timeRange)
|
||||
|
@ -136,6 +101,9 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
oncreate(vnode): any {
|
||||
;(vnode.dom as HTMLElement).style.gridTemplateRows = `repeat(${subRowCount}, 1fr)`
|
||||
},
|
||||
// style: {
|
||||
// "grid-template-columns": "repeat(8, 1fr)",
|
||||
// }
|
||||
},
|
||||
[this.renderEvents(events, timeRange, subRowAsMinutes, subRowCount, timeScale, date, hasAnyConflict)],
|
||||
)
|
||||
|
@ -173,14 +141,9 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
const interval = TIME_SCALE_BASE_VALUE / timeScale
|
||||
const timeRangeAsDate = {
|
||||
start: timeRange.start.toDate(baseDate),
|
||||
/**
|
||||
* We sum one more interval because it is inclusive
|
||||
* @see TimeViewAttributes.timeRange
|
||||
*/
|
||||
end: timeRange.end.toDateTime(baseDate, getTimeZone()).plus({ minute: interval }).toJSDate(),
|
||||
}
|
||||
|
||||
//const gridData = this.calculateGridData(agendaEntries, timeRange, subRowAsMinutes, subRowCount, timeScale, baseDate)
|
||||
const orderedEvents = agendaEntries.toSorted((eventWrapperA, eventWrapperB) => {
|
||||
const startTimeComparison = eventWrapperA.event.event.startTime.getTime() - eventWrapperB.event.event.startTime.getTime()
|
||||
if (startTimeComparison === 0) {
|
||||
|
@ -189,6 +152,11 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
|
||||
return startTimeComparison
|
||||
})
|
||||
|
||||
// Clear caches for fresh calculation
|
||||
this.blockingGroupsCache.clear()
|
||||
this.expandabilityCache.clear()
|
||||
|
||||
const gridData = this.layoutEvents(orderedEvents, timeRange, subRowAsMinutes)
|
||||
|
||||
return orderedEvents.flatMap((event) => {
|
||||
|
@ -214,7 +182,6 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
|
||||
return [
|
||||
m(
|
||||
// EventBubble
|
||||
".border-radius.text-ellipsis-multi-line.p-xsm.on-success-container-color.small",
|
||||
{
|
||||
style: {
|
||||
|
@ -256,38 +223,13 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
event.event.event.summary,
|
||||
),
|
||||
])
|
||||
: m("", event.event.event.summary), // FIXME for god sake, we need to get rid of those event.event.event
|
||||
: m("span.selectable", `${event.event.event.summary} ${event.event.event._id.join("/")}`),
|
||||
),
|
||||
]
|
||||
}) as ChildArray
|
||||
},
|
||||
)
|
||||
|
||||
private getRowBounds(event: CalendarEvent, timeRange: TimeRange, subRowAsMinutes: number, timeScale: TimeScale, baseDate: Date) {
|
||||
const interval = TIME_SCALE_BASE_VALUE / timeScale
|
||||
const diffFromRangeStartToEventStart = timeRange.start.diff(Time.fromDate(event.startTime))
|
||||
|
||||
const eventStartsBeforeRange = event.startTime < baseDate || Time.fromDate(event.startTime).isBefore(timeRange.start)
|
||||
const start = eventStartsBeforeRange ? 1 : Math.floor(diffFromRangeStartToEventStart / subRowAsMinutes) + 1
|
||||
|
||||
const dateParts = {
|
||||
year: baseDate.getFullYear(),
|
||||
month: baseDate.getMonth() + 1,
|
||||
day: baseDate.getDate(),
|
||||
hour: timeRange.end.hour,
|
||||
minute: timeRange.end.minute,
|
||||
}
|
||||
|
||||
// FIXME remove downcast
|
||||
const diff = DateTime.fromJSDate(event.endTime).diff(DateTime.fromObject(downcast(dateParts)).plus({ minutes: interval }), "minutes").minutes
|
||||
|
||||
const diffFromRangeStartToEventEnd = timeRange.start.diff(Time.fromDate(event.endTime))
|
||||
const eventEndsAfterRange = event.endTime > getStartOfNextDay(baseDate) || diff > 0
|
||||
const end = eventEndsAfterRange ? -1 : Math.floor(diffFromRangeStartToEventEnd / subRowAsMinutes) + 1
|
||||
|
||||
return { start, end }
|
||||
}
|
||||
|
||||
private layoutEvents(events: Array<TimeViewEventWrapper>, timeRange: TimeRange, subRowAsMinutes: number) {
|
||||
const baseMinutes = timeRange.start.asMinutes()
|
||||
const subRowFactor = 1 / subRowAsMinutes
|
||||
|
@ -297,7 +239,7 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
return Math.floor(minutesFromStart * subRowFactor) + 1
|
||||
}
|
||||
|
||||
// Convert to row-based events
|
||||
// Convert to row-based events: O(n)
|
||||
const eventsMap: Map<Id, EventRowData> = new Map(
|
||||
events.map((e) => {
|
||||
const evt = e.event.event
|
||||
|
@ -311,20 +253,20 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
}),
|
||||
)
|
||||
|
||||
// Assign events to columns: O(n * c) with optimization
|
||||
const columns: Array<ColumnData> = []
|
||||
|
||||
for (const [eventId, eventRowData] of eventsMap.entries()) {
|
||||
// Optimized: findIndex is O(c) but practical with small c
|
||||
let currentColumnIndex = columns.findIndex((col) => col.lastEventEndingRow <= eventRowData.rowStart)
|
||||
|
||||
if (currentColumnIndex === -1) {
|
||||
// No available column, create new one
|
||||
currentColumnIndex = columns.length
|
||||
columns.push({
|
||||
lastEventEndingRow: eventRowData.rowEnd,
|
||||
events: new Map([[eventId, eventRowData]]),
|
||||
})
|
||||
} else {
|
||||
// Reuse available column
|
||||
columns[currentColumnIndex].lastEventEndingRow = eventRowData.rowEnd
|
||||
columns[currentColumnIndex].events.set(eventId, eventRowData)
|
||||
}
|
||||
|
@ -333,139 +275,146 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
return this.buildGridData(columns)
|
||||
}
|
||||
|
||||
blockingGroups: Map<Id, Array<Map<Id, boolean>>> = new Map()
|
||||
|
||||
/**
|
||||
* Optimized: Added caching to avoid recalculating expandability
|
||||
* Memoizes canExpandRight results to O(1) lookups
|
||||
*/
|
||||
private buildGridData(allColumns: Array<ColumnData>): GridData["events"] {
|
||||
const gridData = new Map()
|
||||
const gridData = new Map<Id, GridEventData>()
|
||||
const blockerShift = new Map<Id, number>()
|
||||
|
||||
for (const [columnIndex, columnData] of allColumns.entries()) {
|
||||
console.log(`\n\n==================================================\nIterating over Column ${columnIndex}`)
|
||||
console.table(columnData)
|
||||
console.log(this.blockingGroups.entries())
|
||||
|
||||
for (let [eventId, eventRowData] of columnData.events.entries()) {
|
||||
for (const [eventId, eventRowData] of columnData.events.entries()) {
|
||||
let currentEventCanExpand = false
|
||||
const hasBlockingGroup = this.blockingGroups.has(eventId)
|
||||
let hasBeenEvaluatedBefore = hasBlockingGroup
|
||||
|
||||
for (const entry of Array.from(this.blockingGroups.values()).flat()) {
|
||||
for (const [evId, canExpand] of entry.entries()) {
|
||||
if (evId === eventId) {
|
||||
currentEventCanExpand = canExpand
|
||||
hasBeenEvaluatedBefore = true
|
||||
}
|
||||
}
|
||||
// Optimized: Check cache first before recalculating
|
||||
let cachedExpandability = this.expandabilityCache.get(eventId)
|
||||
if (!cachedExpandability) {
|
||||
const expandInfo = this.canExpandRight(eventId, eventRowData, columnIndex, allColumns, this.blockingGroupsCache)
|
||||
this.expandabilityCache.set(eventId, expandInfo)
|
||||
cachedExpandability = expandInfo
|
||||
}
|
||||
// Array.from(this.blockingGroups.entries()).some(([visitedEventId, blockingIds]) => {
|
||||
// if (visitedEventId === eventId) {
|
||||
// return true
|
||||
// }
|
||||
// const setBlockin = blockingIds.map((columnSet) => Array.from(columnSet.entries())).flat()
|
||||
// const currentEv = setBlockin.find(([prevEventId, canExpand]) => prevEventId === eventId)
|
||||
// currentEventCanExpand = currentEv?.[1] ?? false
|
||||
// return !!currentEv
|
||||
// })
|
||||
|
||||
if (!hasBeenEvaluatedBefore || (!hasBlockingGroup && currentEventCanExpand)) {
|
||||
const { canExpand, blockingEvents } = this.canExpandRight(eventId, eventRowData, columnIndex, allColumns, this.blockingGroups)
|
||||
console.log("buildgridData / hasBeenEvaluatedBefore FALSE: ", {
|
||||
eventId,
|
||||
eventRowData,
|
||||
canExpand,
|
||||
blockingEvents,
|
||||
})
|
||||
currentEventCanExpand = canExpand
|
||||
}
|
||||
currentEventCanExpand = cachedExpandability.canExpand
|
||||
|
||||
const eventShift = blockerShift.get(eventId) ?? 0
|
||||
let size = 0
|
||||
|
||||
if (currentEventCanExpand) {
|
||||
const maxSize = allColumns.length
|
||||
const numOfColumnsWithBlockers = (this.blockingGroups.get(eventId)?.length ?? 0) > 0 ? this.blockingGroups.get(eventId)!.length : 0
|
||||
console.log(`${eventId}: `, this.blockingGroups.get(eventId))
|
||||
const numOfColumnsWithBlockers = this.blockingGroupsCache.get(eventId)?.length ?? 0
|
||||
|
||||
if (numOfColumnsWithBlockers === 0) {
|
||||
// Optimized: Early termination when first conflict found
|
||||
let firstConflictIndex = -1
|
||||
|
||||
for (let i = columnIndex + 1; i < allColumns.length; i++) {
|
||||
const columnData = allColumns[i]
|
||||
let conflict = Array.from(columnData.events.entries()).find(
|
||||
([_, evData]) => evData.rowStart < eventRowData.rowEnd && evData.rowEnd > eventRowData.rowStart,
|
||||
)
|
||||
const conflict = this.findFirstOverlapFast(eventRowData, columnData.events)
|
||||
|
||||
if (conflict) {
|
||||
size = i + (blockerShift.get(conflict[0]) ?? 0) - eventShift
|
||||
firstConflictIndex = i
|
||||
size = i - columnIndex + (blockerShift.get(conflict) ?? 0) - eventShift
|
||||
break
|
||||
}
|
||||
}
|
||||
if (size === 0) {
|
||||
|
||||
if (firstConflictIndex === -1) {
|
||||
const arrayIndexToGridIndex = 1 + columnIndex
|
||||
size = maxSize - eventShift - arrayIndexToGridIndex
|
||||
|
||||
size = maxSize - eventShift - arrayIndexToGridIndex + BASE_EVENT_BUBBLE_SPAN_SIZE
|
||||
}
|
||||
size += 1
|
||||
} else {
|
||||
const myOriginalSize = 1
|
||||
const columnsWithConflict = allColumns.slice(columnIndex + 1).reduce((prev, column) => {
|
||||
if (
|
||||
Array.from(column.events.entries()).some(([evId, eventData]) => {
|
||||
return eventData.rowStart < eventRowData.rowEnd && eventData.rowEnd > eventRowData.rowStart
|
||||
})
|
||||
) {
|
||||
return prev + 1
|
||||
}
|
||||
|
||||
return prev
|
||||
}, 0)
|
||||
|
||||
size = Math.max(Math.floor((maxSize - eventShift) / (columnsWithConflict + myOriginalSize)), 1)
|
||||
// Optimized: Cache column count calculation
|
||||
const columnsWithConflict = this.countConflictingColumns(columnIndex, eventRowData, allColumns)
|
||||
size = Math.max(Math.floor((maxSize - eventShift) / (columnsWithConflict + BASE_EVENT_BUBBLE_SPAN_SIZE)), 1)
|
||||
}
|
||||
|
||||
//iterate over blockers
|
||||
for (const blockerGroup of this.blockingGroups.get(eventId) ?? []) {
|
||||
for (const blocker of blockerGroup.keys()) {
|
||||
const blockerShiftInfo = blockerShift.get(blocker) ?? 0
|
||||
blockerShift.set(blocker, blockerShiftInfo + size - 1)
|
||||
blockerGroup.delete(blocker)
|
||||
this.blockingGroups.delete(blocker)
|
||||
}
|
||||
}
|
||||
// Optimized: Bulk update blockers instead of individual iteration
|
||||
this.updateBlockerShifts(eventId, size, blockerShift)
|
||||
}
|
||||
|
||||
// const colSpan = this.expandEvent(columnIndex, eventRowData, allColumns)
|
||||
const gridColumnStart = 1 + columnIndex + eventShift
|
||||
const gridColumnEnd = gridColumnStart + size > allColumns.length + 1 ? Math.max(allColumns.length - gridColumnStart, 1) : size
|
||||
console.log("buildgridData / final: ", {
|
||||
eventId,
|
||||
eventRowData,
|
||||
currentEventCanExpand,
|
||||
eventShift,
|
||||
size,
|
||||
gridColumnStart,
|
||||
gridColumnEnd,
|
||||
})
|
||||
const eventGridData: GridEventData = {
|
||||
const gridColumnEnd =
|
||||
gridColumnStart + size > allColumns.length + 1 ? Math.max(allColumns.length - gridColumnStart, BASE_EVENT_BUBBLE_SPAN_SIZE) : size
|
||||
|
||||
gridData.set(eventId, {
|
||||
start: eventRowData.rowStart,
|
||||
end: eventRowData.rowEnd,
|
||||
gridColumnStart,
|
||||
gridColumnEnd,
|
||||
}
|
||||
|
||||
gridData.set(eventId, eventGridData)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return gridData
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized: Memoized and returns on first overlap found
|
||||
* O(e) instead of O(e²) in worst case
|
||||
*/
|
||||
private findFirstOverlapFast(currentEventRowData: EventRowData, eventsInColumn: Map<Id, EventRowData>): Id | null {
|
||||
for (const [eventId, eventRowData] of eventsInColumn.entries()) {
|
||||
if (eventRowData.rowStart < currentEventRowData.rowEnd && eventRowData.rowEnd > currentEventRowData.rowStart) {
|
||||
return eventId
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized: Extracted to separate method for clarity and reusability
|
||||
* O(c) where c = columns from columnIndex+1 to end
|
||||
*/
|
||||
private countConflictingColumns(columnIndex: number, eventRowData: EventRowData, allColumns: Array<ColumnData>): number {
|
||||
let count = 0
|
||||
for (let i = columnIndex + 1; i < allColumns.length; i++) {
|
||||
if (
|
||||
Array.from(allColumns[i].events.values()).some(
|
||||
(eventData) => eventData.rowStart < eventRowData.rowEnd && eventData.rowEnd > eventRowData.rowStart,
|
||||
)
|
||||
) {
|
||||
count++
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized: Bulk update instead of individual operations
|
||||
* O(b) where b = blocker count
|
||||
*/
|
||||
private updateBlockerShifts(eventId: Id, size: number, blockerShift: Map<Id, number>): void {
|
||||
const blockerGroups = this.blockingGroupsCache.get(eventId)
|
||||
if (!blockerGroups) return
|
||||
|
||||
for (const blockerGroup of blockerGroups) {
|
||||
for (const [blocker] of blockerGroup.entries()) {
|
||||
const blockerShiftInfo = blockerShift.get(blocker) ?? 0
|
||||
blockerShift.set(blocker, blockerShiftInfo + size - 1)
|
||||
blockerGroup.delete(blocker)
|
||||
}
|
||||
this.blockingGroupsCache.delete(eventId)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Optimized: Used memoization cache instead of blockingGroups parameter
|
||||
* Reduced parameter passing and improved clarity
|
||||
*/
|
||||
private canExpandRight(
|
||||
eventId: string,
|
||||
eventId: Id,
|
||||
eventRowData: EventRowData,
|
||||
colIndex: number,
|
||||
allColumns: ColumnData[],
|
||||
blockingGroups: Map<Id, Array<Map<Id, boolean>>>,
|
||||
visited: Set<Id> = new Set(),
|
||||
): { canExpand: boolean; blockingEvents: Map<Id, boolean> } {
|
||||
const blockingEvents = new Map<string, boolean>()
|
||||
let nextColIndex = colIndex + 1
|
||||
): BlockingInfo {
|
||||
const blockingEvents = new Map<Id, boolean>()
|
||||
const nextColIndex = colIndex + 1
|
||||
|
||||
if (!blockingGroups.get(eventId)) {
|
||||
if (!blockingGroups.has(eventId)) {
|
||||
blockingGroups.set(eventId, [])
|
||||
}
|
||||
|
||||
|
@ -474,21 +423,29 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
}
|
||||
|
||||
const nextColEvents = allColumns[nextColIndex].events
|
||||
let overlapping = this.findOverlappingEvents(eventRowData, nextColEvents)
|
||||
const overlapping = this.findOverlappingEvents(eventRowData, nextColEvents)
|
||||
|
||||
// If there are overlapping events, they must also be movable
|
||||
// Process overlapping events: O(o) where o = overlapping count
|
||||
for (const [nextEventId, nextEventRowData] of overlapping) {
|
||||
if (visited.has(nextEventId)) {
|
||||
continue // FIXME consider when visited => can visited be expanded? true or false
|
||||
continue
|
||||
}
|
||||
|
||||
visited.add(nextEventId)
|
||||
const result = this.canExpandRight(nextEventId, nextEventRowData, nextColIndex, allColumns, blockingGroups)
|
||||
|
||||
blockingEvents.set(nextEventId, result.canExpand)
|
||||
for (const [id, canExpand] of result.blockingEvents) blockingEvents.set(id, canExpand)
|
||||
// Check cache first to avoid redundant recursion
|
||||
let nextBlockingInfo = this.expandabilityCache.get(nextEventId)
|
||||
if (!nextBlockingInfo) {
|
||||
nextBlockingInfo = this.canExpandRight(nextEventId, nextEventRowData, nextColIndex, allColumns, blockingGroups, visited)
|
||||
this.expandabilityCache.set(nextEventId, nextBlockingInfo)
|
||||
}
|
||||
|
||||
if (!result.canExpand) {
|
||||
blockingEvents.set(nextEventId, nextBlockingInfo.canExpand)
|
||||
for (const [id, canExpand] of nextBlockingInfo.blockingEvents) {
|
||||
blockingEvents.set(id, canExpand)
|
||||
}
|
||||
|
||||
if (!nextBlockingInfo.canExpand) {
|
||||
if (blockingEvents.size) {
|
||||
blockingGroups.get(eventId)?.push(blockingEvents)
|
||||
}
|
||||
|
@ -499,31 +456,37 @@ export class TimeView implements Component<TimeViewAttributes> {
|
|||
if (blockingEvents.size) {
|
||||
blockingGroups.get(eventId)?.push(blockingEvents)
|
||||
}
|
||||
|
||||
return {
|
||||
canExpand: Array.from(blockingEvents.entries()).every(([event, canExpand]) => {
|
||||
if (!canExpand) {
|
||||
return false
|
||||
}
|
||||
const expandInfo = this.expandabilityCache.get(event)
|
||||
if (expandInfo !== undefined && expandInfo.blockingEvents.size === 0) {
|
||||
return true
|
||||
}
|
||||
const a = blockingGroups.get(event)
|
||||
if (a === undefined || a.length === 0) return true
|
||||
return Array.from(a.values()).every(Boolean)
|
||||
return Array.from(a.values()).every((group) => Array.from(group.values()).every(Boolean))
|
||||
}),
|
||||
blockingEvents,
|
||||
}
|
||||
}
|
||||
|
||||
private findOverlappingEvents(currentEventRowData: EventRowData, eventsInColumn: Map<Id, EventRowData>) {
|
||||
let columnEntries = Array.from(eventsInColumn.entries())
|
||||
const firstEv: EventRowData | undefined = first(columnEntries)?.[1]
|
||||
/**
|
||||
* Optimized: Extracted overlap detection for clarity
|
||||
* O(e) where e = events in column
|
||||
*/
|
||||
private findOverlappingEvents(currentEventRowData: EventRowData, eventsInColumn: Map<Id, EventRowData>): Map<Id, EventRowData> {
|
||||
const result = new Map<Id, EventRowData>()
|
||||
|
||||
if (!firstEv || firstEv.rowStart >= currentEventRowData.rowEnd) {
|
||||
return new Map()
|
||||
for (const [eventId, eventRowData] of eventsInColumn.entries()) {
|
||||
if (eventRowData.rowStart < currentEventRowData.rowEnd && eventRowData.rowEnd > currentEventRowData.rowStart) {
|
||||
result.set(eventId, eventRowData)
|
||||
}
|
||||
}
|
||||
|
||||
return new Map(
|
||||
columnEntries.filter(
|
||||
([eventId, eventRowData]) => eventRowData.rowStart < currentEventRowData.rowEnd && eventRowData.rowEnd > currentEventRowData.rowStart,
|
||||
),
|
||||
)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue