Skip to content

Commit 9ad0942

Browse files
Merge pull request #82 from linked-planet/dev
Dev
2 parents fc2e7a1 + 993c4d0 commit 9ad0942

File tree

11 files changed

+164
-132
lines changed

11 files changed

+164
-132
lines changed

library/src/components/timetable/TimeTable.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ export interface LPTimeTableProps<
194194

195195
/**
196196
* renderBatch tells how many groups are calculated in one step and rendered. This is useful for large time tables, where the rendering takes a long time.
197-
* @default 10
197+
* @default 1
198198
*/
199199
renderBatch?: number
200200
}
@@ -217,7 +217,7 @@ export default function LPTimeTable<
217217
)
218218
}
219219

220-
export let timeTableGroupRenderBatchSize = 10
220+
export let timeTableGroupRenderBatchSize = 1
221221

222222
/**
223223
* The LPTimeTable depends on the localization messages. It needs to be wrapped in an

library/src/components/timetable/TimeTableRows.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import {
4141
} from "./TimeTableSelectionStore"
4242
import type { ItemRowEntry } from "./useGoupRows"
4343
import { getLeftAndWidth } from "./timeTableUtils"
44-
import { useDebounceHelper, useRateLimitHelper } from "../../utils"
44+
import { useDebounceHelper, useIdleRateLimitHelper } from "../../utils"
4545

4646
interface TimeTableRowsProps<
4747
G extends TimeTableGroup,
@@ -67,7 +67,7 @@ interface TimeTableRowsProps<
6767
}
6868

6969
const intersectionStackDelay = 1
70-
const rateLimiting = 1
70+
export const renderIdleTimeout = 84 // ~12 fps
7171
const rowsMargin = 1
7272

7373
/**
@@ -137,8 +137,8 @@ export default function TimeTableRows<
137137
allPlaceholderRendered.current = false
138138
}
139139

140-
const rateLimiterIntersection = useRateLimitHelper(rateLimiting)
141-
const rateLimiterRendering = useRateLimitHelper(rateLimiting)
140+
const rateLimiterIntersection = useIdleRateLimitHelper(renderIdleTimeout)
141+
const rateLimiterRendering = useIdleRateLimitHelper(renderIdleTimeout)
142142
const debounceIntersection = useDebounceHelper(intersectionStackDelay)
143143

144144
// handle intersection is called after intersectionStackDelay ms to avoid too many calls
@@ -597,6 +597,28 @@ function TableCell<G extends TimeTableGroup, I extends TimeSlotBooking>({
597597
timeSlotMinutes,
598598
)
599599

600+
if (left < 0) {
601+
console.error(
602+
"TimeTable - left is negative, this should not happen",
603+
i,
604+
it,
605+
left,
606+
currentLeft,
607+
slotsArray,
608+
)
609+
}
610+
611+
if (width < 0) {
612+
console.error(
613+
"TimeTable - width is negative, this should not happen",
614+
i,
615+
it,
616+
left,
617+
currentLeft,
618+
slotsArray,
619+
)
620+
}
621+
600622
const leftUsed = left - currentLeft
601623
if (leftUsed < 0) {
602624
console.error(
@@ -606,8 +628,11 @@ function TableCell<G extends TimeTableGroup, I extends TimeSlotBooking>({
606628
leftUsed,
607629
left,
608630
currentLeft,
631+
slotsArray,
632+
bookingItemsBeginningInCell,
609633
)
610634
}
635+
611636
currentLeft = left + width
612637

613638
const gridTemplateColumnWidth =

library/src/components/timetable/index.ts

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,10 @@ export type { TimeTableItemProps } from "./ItemWrapper"
1818
export type { TimeTablePlaceholderItemProps } from "./PlaceholderItem"
1919
import type { TimeFrameDay as _TimeFrameDay } from "./TimeTableConfigStore"
2020

21-
import {
22-
getLeftAndWidth,
23-
getStartAndEndSlot,
24-
itemsOutsideOfDayRangeORSameStartAndEnd,
25-
} from "./timeTableUtils"
21+
import { getLeftAndWidth, getStartAndEndSlot } from "./timeTableUtils"
2622
export const timeTableUtils = {
2723
getLeftAndWidth,
2824
getStartAndEndSlot,
29-
itemsOutsideOfDayRangeORSameStartAndEnd,
3025
}
3126

3227
//const memoized = React.memo(TimeTable) as typeof TimeTable

library/src/components/timetable/timeTableUtils.ts

Lines changed: 15 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -546,7 +546,13 @@ export function getStartAndEndSlot(
546546
}
547547
}
548548

549-
let startSlot = slotsArray.findIndex((slot) => slot.isAfter(item.startDate))
549+
let startSlot = -1
550+
for (let i = 0; i < slotsArray.length; i++) {
551+
if (slotsArray[i].isAfter(item.startDate)) {
552+
startSlot = i
553+
break
554+
}
555+
}
550556
if (startSlot > 0) {
551557
// if the item starts in the middle of a slot, we need to go back one slot to get the start slot
552558
// but only if the time slot before is on the same day, else it means that the booking starts before the time frame range of the day
@@ -561,10 +567,17 @@ export function getStartAndEndSlot(
561567
}
562568
}
563569
if (startSlot === -1) {
570+
//startSlot = slotsArray.length - 1
564571
startSlot = slotsArray.length - 1
565572
}
566573

567-
let endSlot = slotsArray.findIndex((slot) => slot.isAfter(item.endDate))
574+
let endSlot = -1
575+
for (let i = startSlot; i < slotsArray.length; i++) {
576+
if (slotsArray[i].isAfter(item.endDate)) {
577+
endSlot = i
578+
break
579+
}
580+
}
568581
if (endSlot === -1) {
569582
endSlot = slotsArray.length - 1
570583
} else {
@@ -606,39 +619,3 @@ export function getStartAndEndSlot(
606619

607620
return { startSlot, endSlot, status: "in" }
608621
}
609-
610-
export function itemsOutsideOfDayRangeORSameStartAndEnd<
611-
I extends TimeSlotBooking,
612-
>(
613-
items: I[],
614-
slotsArray: readonly Dayjs[],
615-
timeFrameDay: TimeFrameDay,
616-
timeSlotMinutes: number,
617-
viewType: TimeTableViewType,
618-
) {
619-
const itemsWithSameStartAndEnd: I[] = []
620-
const itemsOutsideRange = items.filter((it) => {
621-
if (it.startDate.isSame(it.endDate)) {
622-
itemsWithSameStartAndEnd.push(it)
623-
return false
624-
}
625-
if (slotsArray.length === 0) {
626-
console.info(
627-
"timeTableUtils - itemsOutsideOfDayRange - no slotsArray",
628-
)
629-
return false
630-
}
631-
const startAndEndSlot = getStartAndEndSlot(
632-
it,
633-
slotsArray,
634-
timeFrameDay,
635-
timeSlotMinutes,
636-
viewType,
637-
)
638-
return (
639-
startAndEndSlot.status === "after" ||
640-
startAndEndSlot.status === "before"
641-
)
642-
})
643-
return { itemsOutsideRange, itemsWithSameStartAndEnd }
644-
}

library/src/components/timetable/useGoupRows.ts

Lines changed: 25 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,10 @@ import {
1111
useTTCViewType,
1212
} from "./TimeTableConfigStore"
1313
import { useTimeTableIdent } from "./TimeTableIdentContext"
14-
import {
15-
getStartAndEndSlot,
16-
isOverlapping,
17-
itemsOutsideOfDayRangeORSameStartAndEnd,
18-
} from "./timeTableUtils"
14+
import { getStartAndEndSlot, isOverlapping } from "./timeTableUtils"
1915
import { useCallback, useRef, useState } from "react"
20-
import { useRateLimitHelper } from "../../utils"
16+
import { useIdleRateLimitHelper } from "../../utils"
17+
import { renderIdleTimeout } from "./TimeTableRows"
2118

2219
/**
2320
* Contains the items of one group row (one row within one group)
@@ -29,8 +26,6 @@ export type ItemRowEntry<I extends TimeSlotBooking = TimeSlotBooking> = {
2926
status: "before" | "after" | "in" // before: starts and ends before the time slot, after: starts and ends after the time slot, in: overlaps the time slot
3027
}
3128

32-
const rateLimiting = 1
33-
3429
export function useGroupRows<
3530
G extends TimeTableGroup,
3631
I extends TimeSlotBooking,
@@ -138,38 +133,29 @@ export function useGroupRows<
138133

139134
// calculate the new group rows
140135
const {
136+
itemRows,
141137
itemsOutsideRange,
142138
itemsWithSameStartAndEnd: _itemsWithSameStartAndEnd,
143-
} = itemsOutsideOfDayRangeORSameStartAndEnd(
139+
} = getGroupItemStack(
144140
entry.items,
145141
slotsArray,
146142
timeFrameDay,
147143
timeSlotMinutes,
148144
viewType,
149145
)
146+
150147
if (itemsOutsideRange.length) {
151148
itemsOutsideOfDayRange.current = updatedItemsOutsideOfDayRange
152149
itemsOutsideOfDayRange.current[entry.group.id] =
153150
itemsOutsideRange
154151
}
152+
155153
if (_itemsWithSameStartAndEnd.length) {
156154
itemsWithSameStartAndEnd.current =
157155
updatedItemsWithSameStartAndEnd
158156
updatedItemsWithSameStartAndEnd[entry.group.id] =
159157
_itemsWithSameStartAndEnd
160158
}
161-
const groupItems = entry.items.filter(
162-
(it) => !_itemsWithSameStartAndEnd.includes(it),
163-
)
164-
//.filter((it) => !itemsOutsideRange.includes(it))
165-
166-
const itemRows = getGroupItemStack(
167-
groupItems,
168-
slotsArray,
169-
timeFrameDay,
170-
timeSlotMinutes,
171-
viewType,
172-
)
173159

174160
const oldRowCount =
175161
groupRowsState.current[entry.group.id]?.length || 0
@@ -198,7 +184,7 @@ export function useGroupRows<
198184
setCalcBatch((prev) => (prev > 10 ? prev - 1 : prev + 1))
199185
}, [slotsArray, timeFrameDay, timeSlotMinutes, viewType])
200186

201-
const rateLimiterCalc = useRateLimitHelper(rateLimiting)
187+
const rateLimiterCalc = useIdleRateLimitHelper(renderIdleTimeout)
202188

203189
if (requireNewGroupRows) {
204190
currentEntries.current = entries
@@ -288,15 +274,20 @@ function getGroupItemStack<I extends TimeSlotBooking>(
288274
timeFrameDay: TimeFrameDay,
289275
timeSlotMinutes: number,
290276
viewType: TimeTableViewType,
291-
): ItemRowEntry<I>[][] {
277+
) {
292278
const itemRows: ItemRowEntry<I>[][] = []
279+
const itemsOutsideRange: I[] = []
280+
const itemsWithSameStartAndEnd: I[] = []
293281

294282
if (!slotsArray || slotsArray.length === 0) {
295283
console.info("TimeTable - no slots array, returning empty item rows")
296-
return itemRows
284+
return { itemRows, itemsOutsideRange, itemsWithSameStartAndEnd }
297285
}
298286
for (const item of groupItems) {
299-
let added = false
287+
if (item.startDate.isSame(item.endDate)) {
288+
itemsWithSameStartAndEnd.push(item)
289+
continue
290+
}
300291

301292
const startEndSlots = getStartAndEndSlot(
302293
item,
@@ -306,36 +297,19 @@ function getGroupItemStack<I extends TimeSlotBooking>(
306297
viewType,
307298
)
308299

309-
const ret = {
310-
...startEndSlots,
311-
item,
312-
}
313-
314300
if (
315-
item.startDate.startOf("day") === item.endDate.startOf("day") &&
316-
(item.endDate.hour() < timeFrameDay.startHour ||
317-
(item.endDate.hour() === timeFrameDay.startHour &&
318-
item.endDate.minute() < timeFrameDay.startMinute))
301+
startEndSlots.status === "before" ||
302+
startEndSlots.status === "after"
319303
) {
320-
if (itemRows.length === 0) {
321-
itemRows.push([ret])
322-
} else {
323-
itemRows[0].push(ret)
324-
}
304+
itemsOutsideRange.push(item)
325305
continue
326306
}
327307

328-
if (
329-
item.startDate.hour() > timeFrameDay.endHour ||
330-
(item.startDate.hour() === timeFrameDay.endHour &&
331-
item.startDate.minute() > timeFrameDay.endMinute)
332-
) {
333-
if (itemRows.length === 0) {
334-
itemRows.push([ret])
335-
} else {
336-
itemRows[0].push(ret)
337-
}
338-
continue
308+
let added = false
309+
310+
const ret = {
311+
...startEndSlots,
312+
item,
339313
}
340314

341315
for (let r = 0; r < itemRows.length; r++) {
@@ -360,5 +334,5 @@ function getGroupItemStack<I extends TimeSlotBooking>(
360334
row.sort((a, b) => a.item.startDate.diff(b.item.startDate))
361335
}
362336

363-
return itemRows
337+
return { itemRows, itemsOutsideRange, itemsWithSameStartAndEnd }
364338
}

library/src/utils/idleRateLimit.ts

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { useRef } from "react"
2+
3+
/**
4+
* The idleRateLimitHelper returns a function, which you can call to execute a callback function.
5+
* However, the callback function will only be executed if the time elapsed since the last call is greater than the specified minimum distance and by using the requestIdleCallback function of the browser.
6+
* @param minDistanceMS the minimal elapsed time between two calls.
7+
* @param executeAfter if true, the callback will be executed using a timeout after the minimum distance has passed, to make sure it is executed.
8+
* @returns a function that takes a callback function as a parameter.
9+
*/
10+
export function idleRateLimitHelper(
11+
timeoutMS: number | undefined,
12+
executeAfter = true,
13+
) {
14+
let lastTime = 0
15+
let timeoutRunning = 0
16+
if (timeoutMS !== undefined && timeoutMS <= 0) {
17+
throw new Error("minDistanceMS must be positive and above 0")
18+
}
19+
20+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
21+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
22+
return (cb: (...args: any[]) => any, ...args: any[]) => {
23+
const now = Date.now()
24+
25+
if (timeoutMS !== undefined && now - lastTime > timeoutMS) {
26+
cb(...args)
27+
lastTime = now
28+
} else {
29+
if (timeoutRunning) {
30+
cancelIdleCallback(timeoutRunning)
31+
}
32+
if (executeAfter) {
33+
timeoutRunning = window.requestIdleCallback(
34+
() => {
35+
cb(...args)
36+
lastTime = Date.now()
37+
},
38+
{
39+
timeout: timeoutMS,
40+
},
41+
)
42+
}
43+
}
44+
}
45+
}
46+
47+
/**
48+
* The idleRateLimitHelper just in a hook:
49+
* The idleRateLimitHelper returns a function, which you can call to execute a callback function.
50+
* However, the callback function will only be executed if the time elapsed since the last call is greater than the specified minimum distance.
51+
* @param minDistanceMS the minimal elapsed time between two calls.
52+
* @returns a function that takes a callback function as a parameter.
53+
*/
54+
export function useIdleRateLimitHelper(
55+
minDistanceMS: number,
56+
executeAfter = true,
57+
) {
58+
const rateLimitHelperRef = useRef(
59+
idleRateLimitHelper(minDistanceMS, executeAfter),
60+
)
61+
return rateLimitHelperRef.current
62+
}

0 commit comments

Comments
 (0)