@sunsama/event-calendar 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +20 -0
- package/README.md +105 -0
- package/lib/commonjs/components/all-day-events.js +117 -0
- package/lib/commonjs/components/all-day-events.js.map +1 -0
- package/lib/commonjs/components/background-hours-content.js +43 -0
- package/lib/commonjs/components/background-hours-content.js.map +1 -0
- package/lib/commonjs/components/background-hours-layout.js +57 -0
- package/lib/commonjs/components/background-hours-layout.js.map +1 -0
- package/lib/commonjs/components/drag-bar.js +84 -0
- package/lib/commonjs/components/drag-bar.js.map +1 -0
- package/lib/commonjs/components/edit-event-container.js +114 -0
- package/lib/commonjs/components/edit-event-container.js.map +1 -0
- package/lib/commonjs/components/event-container.js +37 -0
- package/lib/commonjs/components/event-container.js.map +1 -0
- package/lib/commonjs/components/new-event-container.js +73 -0
- package/lib/commonjs/components/new-event-container.js.map +1 -0
- package/lib/commonjs/components/time-indicator.js +64 -0
- package/lib/commonjs/components/time-indicator.js.map +1 -0
- package/lib/commonjs/components/timed-event-container.js +91 -0
- package/lib/commonjs/components/timed-event-container.js.map +1 -0
- package/lib/commonjs/components/timed-events.js +68 -0
- package/lib/commonjs/components/timed-events.js.map +1 -0
- package/lib/commonjs/components/zoom-provider.js +109 -0
- package/lib/commonjs/components/zoom-provider.js.map +1 -0
- package/lib/commonjs/enums.js +2 -0
- package/lib/commonjs/enums.js.map +1 -0
- package/lib/commonjs/hooks/use-cloned-events.js +25 -0
- package/lib/commonjs/hooks/use-cloned-events.js.map +1 -0
- package/lib/commonjs/hooks/use-events-layout.js +34 -0
- package/lib/commonjs/hooks/use-events-layout.js.map +1 -0
- package/lib/commonjs/hooks/use-is-editing.js +83 -0
- package/lib/commonjs/hooks/use-is-editing.js.map +1 -0
- package/lib/commonjs/index.js +129 -0
- package/lib/commonjs/index.js.map +1 -0
- package/lib/commonjs/types.js +24 -0
- package/lib/commonjs/types.js.map +1 -0
- package/lib/commonjs/utils/calendar-layout.js +113 -0
- package/lib/commonjs/utils/calendar-layout.js.map +1 -0
- package/lib/commonjs/utils/compute-positioning.js +33 -0
- package/lib/commonjs/utils/compute-positioning.js.map +1 -0
- package/lib/commonjs/utils/date-utils.js +152 -0
- package/lib/commonjs/utils/date-utils.js.map +1 -0
- package/lib/commonjs/utils/double-tap-reset-zoom-gesture.js +19 -0
- package/lib/commonjs/utils/double-tap-reset-zoom-gesture.js.map +1 -0
- package/lib/commonjs/utils/generate-event-layouts.js +198 -0
- package/lib/commonjs/utils/generate-event-layouts.js.map +1 -0
- package/lib/commonjs/utils/globals.js +11 -0
- package/lib/commonjs/utils/globals.js.map +1 -0
- package/lib/commonjs/utils/pan-edit-event-gesture.js +41 -0
- package/lib/commonjs/utils/pan-edit-event-gesture.js.map +1 -0
- package/lib/module/components/all-day-events.js +110 -0
- package/lib/module/components/all-day-events.js.map +1 -0
- package/lib/module/components/background-hours-content.js +37 -0
- package/lib/module/components/background-hours-content.js.map +1 -0
- package/lib/module/components/background-hours-layout.js +51 -0
- package/lib/module/components/background-hours-layout.js.map +1 -0
- package/lib/module/components/drag-bar.js +78 -0
- package/lib/module/components/drag-bar.js.map +1 -0
- package/lib/module/components/edit-event-container.js +107 -0
- package/lib/module/components/edit-event-container.js.map +1 -0
- package/lib/module/components/event-container.js +33 -0
- package/lib/module/components/event-container.js.map +1 -0
- package/lib/module/components/new-event-container.js +67 -0
- package/lib/module/components/new-event-container.js.map +1 -0
- package/lib/module/components/time-indicator.js +57 -0
- package/lib/module/components/time-indicator.js.map +1 -0
- package/lib/module/components/timed-event-container.js +84 -0
- package/lib/module/components/timed-event-container.js.map +1 -0
- package/lib/module/components/timed-events.js +63 -0
- package/lib/module/components/timed-events.js.map +1 -0
- package/lib/module/components/zoom-provider.js +102 -0
- package/lib/module/components/zoom-provider.js.map +1 -0
- package/lib/module/enums.js +2 -0
- package/lib/module/enums.js.map +1 -0
- package/lib/module/hooks/use-cloned-events.js +21 -0
- package/lib/module/hooks/use-cloned-events.js.map +1 -0
- package/lib/module/hooks/use-events-layout.js +29 -0
- package/lib/module/hooks/use-events-layout.js.map +1 -0
- package/lib/module/hooks/use-is-editing.js +75 -0
- package/lib/module/hooks/use-is-editing.js.map +1 -0
- package/lib/module/index.js +124 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/types.js +20 -0
- package/lib/module/types.js.map +1 -0
- package/lib/module/utils/calendar-layout.js +108 -0
- package/lib/module/utils/calendar-layout.js.map +1 -0
- package/lib/module/utils/compute-positioning.js +28 -0
- package/lib/module/utils/compute-positioning.js.map +1 -0
- package/lib/module/utils/date-utils.js +139 -0
- package/lib/module/utils/date-utils.js.map +1 -0
- package/lib/module/utils/double-tap-reset-zoom-gesture.js +15 -0
- package/lib/module/utils/double-tap-reset-zoom-gesture.js.map +1 -0
- package/lib/module/utils/generate-event-layouts.js +192 -0
- package/lib/module/utils/generate-event-layouts.js.map +1 -0
- package/lib/module/utils/globals.js +7 -0
- package/lib/module/utils/globals.js.map +1 -0
- package/lib/module/utils/pan-edit-event-gesture.js +37 -0
- package/lib/module/utils/pan-edit-event-gesture.js.map +1 -0
- package/lib/typescript/commonjs/components/all-day-events.d.ts +3 -0
- package/lib/typescript/commonjs/components/all-day-events.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/background-hours-content.d.ts +7 -0
- package/lib/typescript/commonjs/components/background-hours-content.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/background-hours-layout.d.ts +7 -0
- package/lib/typescript/commonjs/components/background-hours-layout.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/drag-bar.d.ts +14 -0
- package/lib/typescript/commonjs/components/drag-bar.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/edit-event-container.d.ts +7 -0
- package/lib/typescript/commonjs/components/edit-event-container.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/event-container.d.ts +7 -0
- package/lib/typescript/commonjs/components/event-container.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/new-event-container.d.ts +3 -0
- package/lib/typescript/commonjs/components/new-event-container.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/time-indicator.d.ts +3 -0
- package/lib/typescript/commonjs/components/time-indicator.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/timed-event-container.d.ts +9 -0
- package/lib/typescript/commonjs/components/timed-event-container.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/timed-events.d.ts +7 -0
- package/lib/typescript/commonjs/components/timed-events.d.ts.map +1 -0
- package/lib/typescript/commonjs/components/zoom-provider.d.ts +7 -0
- package/lib/typescript/commonjs/components/zoom-provider.d.ts.map +1 -0
- package/lib/typescript/commonjs/enums.d.ts +2 -0
- package/lib/typescript/commonjs/enums.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/use-cloned-events.d.ts +11 -0
- package/lib/typescript/commonjs/hooks/use-cloned-events.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/use-events-layout.d.ts +13 -0
- package/lib/typescript/commonjs/hooks/use-events-layout.d.ts.map +1 -0
- package/lib/typescript/commonjs/hooks/use-is-editing.d.ts +17 -0
- package/lib/typescript/commonjs/hooks/use-is-editing.d.ts.map +1 -0
- package/lib/typescript/commonjs/index.d.ts +27 -0
- package/lib/typescript/commonjs/index.d.ts.map +1 -0
- package/lib/typescript/commonjs/package.json +1 -0
- package/lib/typescript/commonjs/types.d.ts +128 -0
- package/lib/typescript/commonjs/types.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/__tests___/compute-positioning.test.d.ts +2 -0
- package/lib/typescript/commonjs/utils/__tests___/compute-positioning.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/__tests___/date-utils.test.d.ts +2 -0
- package/lib/typescript/commonjs/utils/__tests___/date-utils.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/__tests___/generate-event-layout.test.d.ts +2 -0
- package/lib/typescript/commonjs/utils/__tests___/generate-event-layout.test.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/calendar-layout.d.ts +36 -0
- package/lib/typescript/commonjs/utils/calendar-layout.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/compute-positioning.d.ts +10 -0
- package/lib/typescript/commonjs/utils/compute-positioning.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/date-utils.d.ts +30 -0
- package/lib/typescript/commonjs/utils/date-utils.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/double-tap-reset-zoom-gesture.d.ts +5 -0
- package/lib/typescript/commonjs/utils/double-tap-reset-zoom-gesture.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/generate-event-layouts.d.ts +15 -0
- package/lib/typescript/commonjs/utils/generate-event-layouts.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/globals.d.ts +5 -0
- package/lib/typescript/commonjs/utils/globals.d.ts.map +1 -0
- package/lib/typescript/commonjs/utils/pan-edit-event-gesture.d.ts +6 -0
- package/lib/typescript/commonjs/utils/pan-edit-event-gesture.d.ts.map +1 -0
- package/lib/typescript/module/components/all-day-events.d.ts +3 -0
- package/lib/typescript/module/components/all-day-events.d.ts.map +1 -0
- package/lib/typescript/module/components/background-hours-content.d.ts +7 -0
- package/lib/typescript/module/components/background-hours-content.d.ts.map +1 -0
- package/lib/typescript/module/components/background-hours-layout.d.ts +7 -0
- package/lib/typescript/module/components/background-hours-layout.d.ts.map +1 -0
- package/lib/typescript/module/components/drag-bar.d.ts +14 -0
- package/lib/typescript/module/components/drag-bar.d.ts.map +1 -0
- package/lib/typescript/module/components/edit-event-container.d.ts +7 -0
- package/lib/typescript/module/components/edit-event-container.d.ts.map +1 -0
- package/lib/typescript/module/components/event-container.d.ts +7 -0
- package/lib/typescript/module/components/event-container.d.ts.map +1 -0
- package/lib/typescript/module/components/new-event-container.d.ts +3 -0
- package/lib/typescript/module/components/new-event-container.d.ts.map +1 -0
- package/lib/typescript/module/components/time-indicator.d.ts +3 -0
- package/lib/typescript/module/components/time-indicator.d.ts.map +1 -0
- package/lib/typescript/module/components/timed-event-container.d.ts +9 -0
- package/lib/typescript/module/components/timed-event-container.d.ts.map +1 -0
- package/lib/typescript/module/components/timed-events.d.ts +7 -0
- package/lib/typescript/module/components/timed-events.d.ts.map +1 -0
- package/lib/typescript/module/components/zoom-provider.d.ts +7 -0
- package/lib/typescript/module/components/zoom-provider.d.ts.map +1 -0
- package/lib/typescript/module/enums.d.ts +2 -0
- package/lib/typescript/module/enums.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-cloned-events.d.ts +11 -0
- package/lib/typescript/module/hooks/use-cloned-events.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-events-layout.d.ts +13 -0
- package/lib/typescript/module/hooks/use-events-layout.d.ts.map +1 -0
- package/lib/typescript/module/hooks/use-is-editing.d.ts +17 -0
- package/lib/typescript/module/hooks/use-is-editing.d.ts.map +1 -0
- package/lib/typescript/module/index.d.ts +27 -0
- package/lib/typescript/module/index.d.ts.map +1 -0
- package/lib/typescript/module/package.json +1 -0
- package/lib/typescript/module/types.d.ts +128 -0
- package/lib/typescript/module/types.d.ts.map +1 -0
- package/lib/typescript/module/utils/__tests___/compute-positioning.test.d.ts +2 -0
- package/lib/typescript/module/utils/__tests___/compute-positioning.test.d.ts.map +1 -0
- package/lib/typescript/module/utils/__tests___/date-utils.test.d.ts +2 -0
- package/lib/typescript/module/utils/__tests___/date-utils.test.d.ts.map +1 -0
- package/lib/typescript/module/utils/__tests___/generate-event-layout.test.d.ts +2 -0
- package/lib/typescript/module/utils/__tests___/generate-event-layout.test.d.ts.map +1 -0
- package/lib/typescript/module/utils/calendar-layout.d.ts +36 -0
- package/lib/typescript/module/utils/calendar-layout.d.ts.map +1 -0
- package/lib/typescript/module/utils/compute-positioning.d.ts +10 -0
- package/lib/typescript/module/utils/compute-positioning.d.ts.map +1 -0
- package/lib/typescript/module/utils/date-utils.d.ts +30 -0
- package/lib/typescript/module/utils/date-utils.d.ts.map +1 -0
- package/lib/typescript/module/utils/double-tap-reset-zoom-gesture.d.ts +5 -0
- package/lib/typescript/module/utils/double-tap-reset-zoom-gesture.d.ts.map +1 -0
- package/lib/typescript/module/utils/generate-event-layouts.d.ts +15 -0
- package/lib/typescript/module/utils/generate-event-layouts.d.ts.map +1 -0
- package/lib/typescript/module/utils/globals.d.ts +5 -0
- package/lib/typescript/module/utils/globals.d.ts.map +1 -0
- package/lib/typescript/module/utils/pan-edit-event-gesture.d.ts +6 -0
- package/lib/typescript/module/utils/pan-edit-event-gesture.d.ts.map +1 -0
- package/package.json +195 -0
- package/src/components/all-day-events.tsx +134 -0
- package/src/components/background-hours-content.tsx +51 -0
- package/src/components/background-hours-layout.tsx +61 -0
- package/src/components/drag-bar.tsx +120 -0
- package/src/components/edit-event-container.tsx +158 -0
- package/src/components/event-container.tsx +44 -0
- package/src/components/new-event-container.tsx +90 -0
- package/src/components/time-indicator.tsx +72 -0
- package/src/components/timed-event-container.tsx +124 -0
- package/src/components/timed-events.tsx +72 -0
- package/src/components/zoom-provider.tsx +146 -0
- package/src/enums.ts +0 -0
- package/src/hooks/use-cloned-events.ts +26 -0
- package/src/hooks/use-events-layout.ts +55 -0
- package/src/hooks/use-is-editing.tsx +109 -0
- package/src/index.tsx +165 -0
- package/src/types.ts +163 -0
- package/src/utils/__tests___/compute-positioning.test.ts +255 -0
- package/src/utils/__tests___/date-utils.test.ts +41 -0
- package/src/utils/__tests___/generate-event-layout.test.ts +277 -0
- package/src/utils/calendar-layout.ts +139 -0
- package/src/utils/compute-positioning.ts +44 -0
- package/src/utils/date-utils.ts +238 -0
- package/src/utils/double-tap-reset-zoom-gesture.ts +23 -0
- package/src/utils/generate-event-layouts.ts +314 -0
- package/src/utils/globals.ts +8 -0
- package/src/utils/pan-edit-event-gesture.ts +64 -0
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import generateEventLayouts from "../generate-event-layouts";
|
|
2
|
+
import { CalendarEvent, EventExtend } from "src/types";
|
|
3
|
+
|
|
4
|
+
describe("generateEventLayouts", () => {
|
|
5
|
+
it("should separate all-day events and timed events", () => {
|
|
6
|
+
const events: CalendarEvent[] = [
|
|
7
|
+
{
|
|
8
|
+
id: "1",
|
|
9
|
+
calendarId: "primary-calendar",
|
|
10
|
+
title: "All-Day Event",
|
|
11
|
+
start: "2023-10-10T00:00:00Z",
|
|
12
|
+
end: "2023-10-11T02:00:00Z",
|
|
13
|
+
isAllDay: true,
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "2",
|
|
17
|
+
calendarId: "primary-calendar",
|
|
18
|
+
title: "Timed Event",
|
|
19
|
+
start: "2023-10-10T08:30:00Z",
|
|
20
|
+
end: "2023-10-10T09:30:00Z",
|
|
21
|
+
},
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
const layouts = generateEventLayouts({
|
|
25
|
+
startCalendarDate: "2023-10-10",
|
|
26
|
+
endCalendarDate: "2023-10-10",
|
|
27
|
+
events,
|
|
28
|
+
timezone: "UTC",
|
|
29
|
+
userCalendarId: "primary-calendar",
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const dayLayout = layouts["2023-10-10"];
|
|
33
|
+
|
|
34
|
+
expect(dayLayout).toBeDefined();
|
|
35
|
+
expect(dayLayout.allDayEventsLayout.length).toBe(1);
|
|
36
|
+
expect(dayLayout.partDayEventsLayout.length).toBe(1);
|
|
37
|
+
expect(dayLayout.allDayEventsLayout[0].event.id).toBe("1");
|
|
38
|
+
expect(dayLayout.partDayEventsLayout[0].event.id).toBe("2");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("should handle events with overlapping times", () => {
|
|
42
|
+
const events: CalendarEvent[] = [
|
|
43
|
+
{
|
|
44
|
+
id: "1",
|
|
45
|
+
calendarId: "primary-calendar",
|
|
46
|
+
title: "Event 1",
|
|
47
|
+
start: "2023-10-10T08:00:00Z",
|
|
48
|
+
end: "2023-10-10T09:00:00Z",
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
id: "2",
|
|
52
|
+
calendarId: "primary-calendar",
|
|
53
|
+
title: "Event 2",
|
|
54
|
+
start: "2023-10-10T08:30:00Z",
|
|
55
|
+
end: "2023-10-10T09:30:00Z",
|
|
56
|
+
},
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
const layouts = generateEventLayouts({
|
|
60
|
+
startCalendarDate: "2023-10-10",
|
|
61
|
+
endCalendarDate: "2023-10-10",
|
|
62
|
+
events,
|
|
63
|
+
timezone: "UTC",
|
|
64
|
+
userCalendarId: "primary-calendar",
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
const dayLayout = layouts["2023-10-10"];
|
|
68
|
+
expect(dayLayout).toBeDefined();
|
|
69
|
+
const { partDayEventsLayout } = dayLayout;
|
|
70
|
+
expect(partDayEventsLayout.length).toBe(2);
|
|
71
|
+
// Overlapping events should have collisions.total = 2
|
|
72
|
+
expect(partDayEventsLayout[0].collisions?.total).toBe(2);
|
|
73
|
+
expect(partDayEventsLayout[1].collisions?.total).toBe(2);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should handle events with no overlap", () => {
|
|
77
|
+
const events: CalendarEvent[] = [
|
|
78
|
+
{
|
|
79
|
+
id: "1",
|
|
80
|
+
calendarId: "primary-calendar",
|
|
81
|
+
title: "Event 1",
|
|
82
|
+
start: "2023-10-10T08:00:00Z",
|
|
83
|
+
end: "2023-10-10T09:00:00Z",
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
id: "2",
|
|
87
|
+
calendarId: "primary-calendar",
|
|
88
|
+
title: "Event 2",
|
|
89
|
+
start: "2023-10-10T09:30:00Z",
|
|
90
|
+
end: "2023-10-10T10:30:00Z",
|
|
91
|
+
},
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
const layouts = generateEventLayouts({
|
|
95
|
+
startCalendarDate: "2023-10-10",
|
|
96
|
+
endCalendarDate: "2023-10-10",
|
|
97
|
+
events,
|
|
98
|
+
timezone: "UTC",
|
|
99
|
+
userCalendarId: "primary-calendar",
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const dayLayout = layouts["2023-10-10"];
|
|
103
|
+
expect(dayLayout).toBeDefined();
|
|
104
|
+
const { partDayEventsLayout } = dayLayout;
|
|
105
|
+
expect(partDayEventsLayout.length).toBe(2);
|
|
106
|
+
expect(partDayEventsLayout[0].collisions).toBe(undefined);
|
|
107
|
+
expect(partDayEventsLayout[1].collisions).toBe(undefined);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("should handle an empty event list", () => {
|
|
111
|
+
const events: CalendarEvent[] = [];
|
|
112
|
+
|
|
113
|
+
const layouts = generateEventLayouts({
|
|
114
|
+
startCalendarDate: "2023-10-10",
|
|
115
|
+
endCalendarDate: "2023-10-10",
|
|
116
|
+
events,
|
|
117
|
+
timezone: "UTC",
|
|
118
|
+
userCalendarId: "primary-calendar",
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const dayLayout = layouts["2023-10-10"];
|
|
122
|
+
// Depending on implementation, if no events exist, the day entry might be missing.
|
|
123
|
+
if (!dayLayout) {
|
|
124
|
+
expect(Object.keys(layouts).length).toBe(0);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
expect(dayLayout.allDayEventsLayout.length).toBe(0);
|
|
128
|
+
expect(dayLayout.partDayEventsLayout.length).toBe(0);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("should handle a single event", () => {
|
|
132
|
+
const events: CalendarEvent[] = [
|
|
133
|
+
{
|
|
134
|
+
id: "1",
|
|
135
|
+
calendarId: "primary-calendar",
|
|
136
|
+
title: "Event 1",
|
|
137
|
+
start: "2023-10-10T08:00:00Z",
|
|
138
|
+
end: "2023-10-10T09:00:00Z",
|
|
139
|
+
},
|
|
140
|
+
];
|
|
141
|
+
|
|
142
|
+
const layouts = generateEventLayouts({
|
|
143
|
+
startCalendarDate: "2023-10-10",
|
|
144
|
+
endCalendarDate: "2023-10-10",
|
|
145
|
+
events,
|
|
146
|
+
timezone: "UTC",
|
|
147
|
+
userCalendarId: "primary-calendar",
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
const dayLayout = layouts["2023-10-10"];
|
|
151
|
+
expect(dayLayout.partDayEventsLayout.length).toBe(1);
|
|
152
|
+
expect(dayLayout.partDayEventsLayout[0].collisions).toBe(undefined);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it("should handle events spanning midnight", () => {
|
|
156
|
+
const events: CalendarEvent[] = [
|
|
157
|
+
{
|
|
158
|
+
id: "1",
|
|
159
|
+
calendarId: "primary-calendar",
|
|
160
|
+
title: "Event 1",
|
|
161
|
+
start: "2023-10-10T05:00:00Z",
|
|
162
|
+
end: "2023-10-12T05:00:00Z",
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
id: "2",
|
|
166
|
+
calendarId: "primary-calendar",
|
|
167
|
+
title: "Event 2",
|
|
168
|
+
start: "2023-10-11T05:00:00Z",
|
|
169
|
+
end: "2023-10-11T09:00:00Z",
|
|
170
|
+
},
|
|
171
|
+
];
|
|
172
|
+
|
|
173
|
+
const layouts = generateEventLayouts({
|
|
174
|
+
startCalendarDate: "2023-10-10",
|
|
175
|
+
endCalendarDate: "2023-10-12",
|
|
176
|
+
events,
|
|
177
|
+
timezone: "UTC",
|
|
178
|
+
userCalendarId: "primary-calendar",
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const layoutDay1 = layouts["2023-10-10"];
|
|
182
|
+
const layoutDay2 = layouts["2023-10-11"];
|
|
183
|
+
const layoutDay3 = layouts["2023-10-12"];
|
|
184
|
+
|
|
185
|
+
// Day 2 should see both events overlapping from 00:00–01:00
|
|
186
|
+
expect(layoutDay2?.allDayEventsLayout.length).toBe(1);
|
|
187
|
+
expect(layoutDay2?.partDayEventsLayout.length).toBe(1);
|
|
188
|
+
|
|
189
|
+
const evt1 = layoutDay1.allDayEventsLayout[0];
|
|
190
|
+
const evt2 = layoutDay2.allDayEventsLayout[0];
|
|
191
|
+
const evt3 = layoutDay3.allDayEventsLayout[0];
|
|
192
|
+
|
|
193
|
+
expect(evt1.extend).toBe(EventExtend.Future);
|
|
194
|
+
expect(evt2.extend).toBe(EventExtend.Both);
|
|
195
|
+
expect(evt3.extend).toBe(EventExtend.Past);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
it("should handle events in different time zones", () => {
|
|
199
|
+
const events: CalendarEvent[] = [
|
|
200
|
+
{
|
|
201
|
+
id: "1",
|
|
202
|
+
calendarId: "primary-calendar",
|
|
203
|
+
title: "Event 1",
|
|
204
|
+
start: "2023-10-10T08:00:00+00:00",
|
|
205
|
+
end: "2023-10-10T09:00:00+00:00",
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
id: "2",
|
|
209
|
+
calendarId: "secondary-calendar",
|
|
210
|
+
title: "Event 2",
|
|
211
|
+
start: "2023-10-10T08:00:00-04:00",
|
|
212
|
+
end: "2023-10-10T09:00:00-04:00",
|
|
213
|
+
},
|
|
214
|
+
];
|
|
215
|
+
|
|
216
|
+
const layouts = generateEventLayouts({
|
|
217
|
+
startCalendarDate: "2023-10-10",
|
|
218
|
+
endCalendarDate: "2023-10-10",
|
|
219
|
+
events,
|
|
220
|
+
timezone: "UTC",
|
|
221
|
+
userCalendarId: "primary-calendar",
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const dayLayout = layouts["2023-10-10"];
|
|
225
|
+
expect(dayLayout).toBeDefined();
|
|
226
|
+
|
|
227
|
+
const { partDayEventsLayout } = dayLayout;
|
|
228
|
+
expect(partDayEventsLayout.length).toBe(2);
|
|
229
|
+
|
|
230
|
+
// These events do not overlap in UTC after conversion, so expect collisions.total = 1 for each.
|
|
231
|
+
expect(partDayEventsLayout[0].collisions).toBe(undefined);
|
|
232
|
+
expect(partDayEventsLayout[1].collisions).toBe(undefined);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
it("should prioritize primary calendar events in collision sorting", () => {
|
|
236
|
+
// Here we mix a primary calendar event with one from a non-primary calendar.
|
|
237
|
+
const events: CalendarEvent[] = [
|
|
238
|
+
{
|
|
239
|
+
id: "1",
|
|
240
|
+
calendarId: "primary-calendar",
|
|
241
|
+
title: "Primary Event",
|
|
242
|
+
start: "2023-10-10T08:00:00Z",
|
|
243
|
+
end: "2023-10-10T09:00:00Z",
|
|
244
|
+
},
|
|
245
|
+
{
|
|
246
|
+
id: "2",
|
|
247
|
+
calendarId: "secondary-calendar",
|
|
248
|
+
title: "Secondary Event",
|
|
249
|
+
start: "2023-10-10T08:15:00Z",
|
|
250
|
+
end: "2023-10-10T09:15:00Z",
|
|
251
|
+
},
|
|
252
|
+
];
|
|
253
|
+
|
|
254
|
+
const layouts = generateEventLayouts({
|
|
255
|
+
startCalendarDate: "2023-10-10",
|
|
256
|
+
endCalendarDate: "2023-10-10",
|
|
257
|
+
events,
|
|
258
|
+
timezone: "UTC",
|
|
259
|
+
userCalendarId: "primary-calendar",
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const dayLayout = layouts["2023-10-10"];
|
|
263
|
+
expect(dayLayout).toBeDefined();
|
|
264
|
+
const { partDayEventsLayout } = dayLayout;
|
|
265
|
+
expect(partDayEventsLayout.length).toBe(2);
|
|
266
|
+
|
|
267
|
+
// Expect both events to have collision data with total = 2.
|
|
268
|
+
const primaryEvent = partDayEventsLayout.find((e) => e.event.id === "1")!;
|
|
269
|
+
const secondaryEvent = partDayEventsLayout.find((e) => e.event.id === "2")!;
|
|
270
|
+
expect(primaryEvent.collisions?.total).toBe(2);
|
|
271
|
+
expect(secondaryEvent.collisions?.total).toBe(2);
|
|
272
|
+
// The primary event (from "primary-calendar") should be ordered before the secondary event.
|
|
273
|
+
expect(primaryEvent.collisions?.order).toBeLessThan(
|
|
274
|
+
secondaryEvent.collisions?.order || 0
|
|
275
|
+
);
|
|
276
|
+
});
|
|
277
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { max } from "lodash";
|
|
2
|
+
import { EventExtend } from "src/types";
|
|
3
|
+
|
|
4
|
+
export class CalendarLayout {
|
|
5
|
+
// visibleX is an array of numbers representing the x indexes currently 'in view'
|
|
6
|
+
// enableWeekBreaks is a bool indicating if events are split at week boundary (monthview)
|
|
7
|
+
// startOfWeekXOffset is the offset to the start of the week from index 0
|
|
8
|
+
array2d: any[];
|
|
9
|
+
visibleX: Set<number>;
|
|
10
|
+
enableWeekBreaks: boolean;
|
|
11
|
+
startOfWeekXOffset: number;
|
|
12
|
+
|
|
13
|
+
constructor({
|
|
14
|
+
visibleX,
|
|
15
|
+
enableWeekBreaks,
|
|
16
|
+
startOfWeekXOffset,
|
|
17
|
+
}: {
|
|
18
|
+
visibleX: number[];
|
|
19
|
+
enableWeekBreaks: boolean;
|
|
20
|
+
startOfWeekXOffset: number;
|
|
21
|
+
}) {
|
|
22
|
+
this.array2d = [];
|
|
23
|
+
this.visibleX = new Set(visibleX);
|
|
24
|
+
this.enableWeekBreaks = enableWeekBreaks;
|
|
25
|
+
this.startOfWeekXOffset = startOfWeekXOffset;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
getAt(x: number, y: number) {
|
|
29
|
+
return this.array2d[x] && this.array2d[x][y];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
setAt(x: number, y: number, value: { value: any; meta: { x: any } }) {
|
|
33
|
+
let column = this.array2d[x];
|
|
34
|
+
if (!column) {
|
|
35
|
+
column = [];
|
|
36
|
+
this.array2d[x] = column;
|
|
37
|
+
}
|
|
38
|
+
column[y] = value;
|
|
39
|
+
}
|
|
40
|
+
// assign value to the line of cells from (x, y) to (x + w, y)
|
|
41
|
+
setRange(x: number, y: number, duration: number, value: any) {
|
|
42
|
+
for (let increment = 0; increment < duration; ++increment) {
|
|
43
|
+
this.setAt(x + increment, y, { value, meta: { x } });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// does the event starting time at x with duration w fit at position y?
|
|
47
|
+
fit(x: number, y: number, duration: number) {
|
|
48
|
+
for (let increment = 0; increment < duration; ++increment) {
|
|
49
|
+
if (this.getAt(x + increment, y)) {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
// find the row where the event starting at time x with duration w fits
|
|
56
|
+
findFit(x: number, duration: number) {
|
|
57
|
+
let rowIndex = 0;
|
|
58
|
+
while (!this.fit(x, rowIndex, duration)) {
|
|
59
|
+
++rowIndex;
|
|
60
|
+
}
|
|
61
|
+
return rowIndex;
|
|
62
|
+
}
|
|
63
|
+
// inserts an event record into the matrix
|
|
64
|
+
findFitAndInsert(
|
|
65
|
+
eventStartIndex: number,
|
|
66
|
+
eventDurationDays: number,
|
|
67
|
+
event: any
|
|
68
|
+
) {
|
|
69
|
+
const rowIndex = this.findFit(eventStartIndex, eventDurationDays);
|
|
70
|
+
this.setRange(eventStartIndex, rowIndex, eventDurationDays, event);
|
|
71
|
+
}
|
|
72
|
+
// find the height of the 2d array
|
|
73
|
+
height() {
|
|
74
|
+
return this.array2d.length > 0
|
|
75
|
+
? max(this.array2d.map((col) => (col && col.length) || 0))
|
|
76
|
+
: 0;
|
|
77
|
+
}
|
|
78
|
+
// get the event and associated view data for the given cell
|
|
79
|
+
getViewAt(x: number, y: number) {
|
|
80
|
+
const record = this.getAt(x, y);
|
|
81
|
+
if (!record) {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
const {
|
|
85
|
+
value: event,
|
|
86
|
+
meta: { x: rootX },
|
|
87
|
+
} = record;
|
|
88
|
+
const previousRecord = this.visibleX.has(x - 1)
|
|
89
|
+
? this.getAt(x - 1, y)
|
|
90
|
+
: null;
|
|
91
|
+
if (event) {
|
|
92
|
+
const isPrimaryRendered =
|
|
93
|
+
x === rootX ||
|
|
94
|
+
!previousRecord ||
|
|
95
|
+
previousRecord.value !== event ||
|
|
96
|
+
(this.enableWeekBreaks &&
|
|
97
|
+
Math.floor((x + this.startOfWeekXOffset) / 7) !==
|
|
98
|
+
Math.floor((x + this.startOfWeekXOffset - 1) / 7));
|
|
99
|
+
// count the contiguous visible days for this event
|
|
100
|
+
let visibleWidthDays = 1;
|
|
101
|
+
while (
|
|
102
|
+
this.visibleX.has(x + visibleWidthDays) &&
|
|
103
|
+
(!this.enableWeekBreaks ||
|
|
104
|
+
Math.floor((x + this.startOfWeekXOffset + visibleWidthDays) / 7) ===
|
|
105
|
+
Math.floor(
|
|
106
|
+
(x + this.startOfWeekXOffset + visibleWidthDays - 1) / 7
|
|
107
|
+
)) &&
|
|
108
|
+
this.getAt(x + visibleWidthDays, y) &&
|
|
109
|
+
this.getAt(x + visibleWidthDays, y).value === event
|
|
110
|
+
) {
|
|
111
|
+
visibleWidthDays++;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const wrapStart = x !== rootX;
|
|
115
|
+
const wrapEnd =
|
|
116
|
+
this.getAt(x + visibleWidthDays, y) &&
|
|
117
|
+
this.getAt(x + visibleWidthDays, y).value === event;
|
|
118
|
+
|
|
119
|
+
let extend = EventExtend.None;
|
|
120
|
+
|
|
121
|
+
if (wrapStart && wrapEnd) {
|
|
122
|
+
extend = EventExtend.Both;
|
|
123
|
+
} else if (wrapStart) {
|
|
124
|
+
extend = EventExtend.Past;
|
|
125
|
+
} else if (wrapEnd) {
|
|
126
|
+
extend = EventExtend.Future;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return {
|
|
130
|
+
event,
|
|
131
|
+
visibleWidthDays,
|
|
132
|
+
isPrimaryRendered,
|
|
133
|
+
extend,
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import moment, { Moment } from "moment-timezone";
|
|
2
|
+
import { CollisionObject, EventPosition } from "src/types";
|
|
3
|
+
|
|
4
|
+
type ComputePositioning = {
|
|
5
|
+
// We only need the collisions part of this type
|
|
6
|
+
collisionObject: CollisionObject;
|
|
7
|
+
startOfDayMoment: Moment;
|
|
8
|
+
timezone: string;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const computePositioning = ({
|
|
12
|
+
collisionObject,
|
|
13
|
+
startOfDayMoment,
|
|
14
|
+
timezone,
|
|
15
|
+
}: ComputePositioning): EventPosition => {
|
|
16
|
+
const startDateMoment = moment.tz(collisionObject.event.start, timezone);
|
|
17
|
+
const durationMinutes = moment
|
|
18
|
+
.tz(collisionObject.event.end, timezone)
|
|
19
|
+
.diff(startDateMoment, "minutes");
|
|
20
|
+
|
|
21
|
+
let width = 100;
|
|
22
|
+
let margin = 0;
|
|
23
|
+
|
|
24
|
+
const top = startDateMoment.diff(startOfDayMoment, "minutes");
|
|
25
|
+
const height = Math.max(30, durationMinutes);
|
|
26
|
+
const collisions = collisionObject.collisions;
|
|
27
|
+
|
|
28
|
+
if (collisions) {
|
|
29
|
+
margin = (100 / collisions.total) * collisions.order;
|
|
30
|
+
width =
|
|
31
|
+
collisions.order + 1 < collisions.total
|
|
32
|
+
? Math.max(100 - 12 * collisions.total, 20)
|
|
33
|
+
: 100 / collisions.total;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return {
|
|
37
|
+
top,
|
|
38
|
+
height,
|
|
39
|
+
width: `${width}%`,
|
|
40
|
+
marginLeft: `${margin}%`,
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export default computePositioning;
|
|
@@ -0,0 +1,238 @@
|
|
|
1
|
+
import moment, { type Moment } from "moment-timezone";
|
|
2
|
+
import { isDate, range, size } from "lodash";
|
|
3
|
+
import { CalendarEvent, PrefabHour } from "src/types";
|
|
4
|
+
|
|
5
|
+
export const generatePrefabHours = (
|
|
6
|
+
timeFormat: string = "HH:mm"
|
|
7
|
+
): PrefabHour[] => {
|
|
8
|
+
const startOfDayMoment = moment().startOf("day");
|
|
9
|
+
|
|
10
|
+
return [...Array(24).keys()].reduce(
|
|
11
|
+
(
|
|
12
|
+
accum: {
|
|
13
|
+
increment: number;
|
|
14
|
+
hourFormatted: string;
|
|
15
|
+
hourMoment: Moment;
|
|
16
|
+
}[],
|
|
17
|
+
increment
|
|
18
|
+
) => {
|
|
19
|
+
const hourMoment = startOfDayMoment.clone().hour(increment);
|
|
20
|
+
|
|
21
|
+
accum.push({
|
|
22
|
+
increment,
|
|
23
|
+
hourFormatted: hourMoment.format(timeFormat),
|
|
24
|
+
hourMoment,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
return accum;
|
|
28
|
+
},
|
|
29
|
+
[]
|
|
30
|
+
);
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Returns a new moment instance at the start of the week in the user's
|
|
34
|
+
// timezone, with the user's start of week preference applied.
|
|
35
|
+
export const startOfUserWeek = (
|
|
36
|
+
startDayOfWeekOffset: number,
|
|
37
|
+
dateOrMoment: Date | Moment | string,
|
|
38
|
+
timezone: string
|
|
39
|
+
) => {
|
|
40
|
+
// If the day is Sunday, and the user's start of week preference is Sunday, return the day
|
|
41
|
+
// otherwise, the start of the 'isoWeek' will be for the previous week
|
|
42
|
+
if (
|
|
43
|
+
startDayOfWeekOffset === 0 &&
|
|
44
|
+
moment.tz(dateOrMoment, timezone).isoWeekday() === 7
|
|
45
|
+
) {
|
|
46
|
+
return moment.tz(dateOrMoment, timezone).startOf("day");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return moment
|
|
50
|
+
.tz(dateOrMoment, timezone)
|
|
51
|
+
.startOf("isoWeek")
|
|
52
|
+
.isoWeekday(startDayOfWeekOffset);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const isAllDayOrSpansMidnight = (
|
|
56
|
+
calendarEvent: CalendarEvent,
|
|
57
|
+
timezone: string
|
|
58
|
+
) => {
|
|
59
|
+
const { start, end, isAllDay } = calendarEvent;
|
|
60
|
+
|
|
61
|
+
if (isAllDay) {
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Does the range start/end span midnight in the given timezone?
|
|
66
|
+
const startMoment = moment.tz(start, timezone);
|
|
67
|
+
const endMoment = moment.tz(end, timezone);
|
|
68
|
+
|
|
69
|
+
// Handle special case where range ends at midnight exactly, in which case spansMidnight should return false
|
|
70
|
+
return !startMoment.isSame(
|
|
71
|
+
endMoment.hour() === 0 ? endMoment.subtract(1, "minute") : endMoment,
|
|
72
|
+
"day"
|
|
73
|
+
);
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
// Returns the count of unique dates in the provided timezone
|
|
77
|
+
export const getDurationInDays = (
|
|
78
|
+
calendarEvent: CalendarEvent,
|
|
79
|
+
timezone: string
|
|
80
|
+
) => {
|
|
81
|
+
// the event duration in days calculation depends on if the event is all day
|
|
82
|
+
return calendarEvent.isAllDay
|
|
83
|
+
? moment
|
|
84
|
+
.tz(calendarEvent.end, timezone)
|
|
85
|
+
.diff(moment.tz(calendarEvent.start, timezone), "days") + 1
|
|
86
|
+
: size(
|
|
87
|
+
daysInRange({
|
|
88
|
+
startDate: calendarEvent.start,
|
|
89
|
+
endDate: calendarEvent.end,
|
|
90
|
+
timezone,
|
|
91
|
+
})
|
|
92
|
+
);
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Returns an array of days (e.g. ['2022-01-02']) in a given date range.
|
|
96
|
+
export const daysInRange = ({
|
|
97
|
+
startDate,
|
|
98
|
+
endDate,
|
|
99
|
+
timezone,
|
|
100
|
+
}: {
|
|
101
|
+
startDate: Date | string;
|
|
102
|
+
endDate: Date | string;
|
|
103
|
+
timezone: string;
|
|
104
|
+
}) => {
|
|
105
|
+
const countOfDaysInRange = moment
|
|
106
|
+
.tz(endDate, timezone)
|
|
107
|
+
.diff(moment.tz(startDate, timezone), "days");
|
|
108
|
+
const startDay = moment.tz(startDate, timezone).format("YYYY-MM-DD");
|
|
109
|
+
const days = [];
|
|
110
|
+
// Make sure we loop at a max of 30 times here as we had events that were scheduled for all day long for
|
|
111
|
+
// 1000 years in the future and this was causing the app to crash
|
|
112
|
+
for (
|
|
113
|
+
let countOfDaysAfterStart = 0;
|
|
114
|
+
countOfDaysAfterStart <= Math.min(30, Math.abs(countOfDaysInRange));
|
|
115
|
+
countOfDaysAfterStart++
|
|
116
|
+
) {
|
|
117
|
+
days.push(
|
|
118
|
+
moment
|
|
119
|
+
.tz(startDay, timezone)
|
|
120
|
+
.add(countOfDaysAfterStart, "day")
|
|
121
|
+
.format("YYYY-MM-DD")
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
return days;
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
export const getDuration = (
|
|
128
|
+
calendarEvent: CalendarEvent,
|
|
129
|
+
trueDuration?: boolean
|
|
130
|
+
) => {
|
|
131
|
+
const minDiff =
|
|
132
|
+
(new Date(calendarEvent.end).valueOf() -
|
|
133
|
+
new Date(calendarEvent.start).valueOf()) /
|
|
134
|
+
(1000 * 60);
|
|
135
|
+
|
|
136
|
+
// If all-day, we want to throw in an extra 24 hours since we represent them a bit oddly.
|
|
137
|
+
return calendarEvent.isAllDay && !trueDuration
|
|
138
|
+
? minDiff + 24 * 60 * 60
|
|
139
|
+
: minDiff;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
export const computeCalendarDateRange = (
|
|
143
|
+
date: string | Date | Moment,
|
|
144
|
+
tz: string,
|
|
145
|
+
viewType: "month" | "workweek" | "3day" | "1day" | "week",
|
|
146
|
+
startDayOfWeekOffset: number
|
|
147
|
+
) => {
|
|
148
|
+
const momentDate = moment.tz(date, tz).startOf("day").toDate();
|
|
149
|
+
|
|
150
|
+
let basis: Moment;
|
|
151
|
+
let dayIndexes: number[];
|
|
152
|
+
|
|
153
|
+
if (viewType === "month") {
|
|
154
|
+
const startOfMonth = moment.tz(momentDate, tz).startOf("month");
|
|
155
|
+
const numberOfDaysInViewBeforeStartOfMonth =
|
|
156
|
+
startOfMonth.isoWeekday() - startDayOfWeekOffset;
|
|
157
|
+
basis = startOfMonth.subtract(numberOfDaysInViewBeforeStartOfMonth, "days");
|
|
158
|
+
const startOfRange = 0;
|
|
159
|
+
const numberOfDaysInViewAfterEndOfMonth =
|
|
160
|
+
(numberOfDaysInViewBeforeStartOfMonth +
|
|
161
|
+
moment.tz(date, tz).daysInMonth()) %
|
|
162
|
+
7
|
|
163
|
+
? 7 -
|
|
164
|
+
((numberOfDaysInViewBeforeStartOfMonth +
|
|
165
|
+
moment.tz(date, tz).daysInMonth()) %
|
|
166
|
+
7)
|
|
167
|
+
: 0;
|
|
168
|
+
const endOfRange =
|
|
169
|
+
numberOfDaysInViewBeforeStartOfMonth +
|
|
170
|
+
moment.tz(date, tz).daysInMonth() +
|
|
171
|
+
numberOfDaysInViewAfterEndOfMonth;
|
|
172
|
+
dayIndexes = range(startOfRange, endOfRange);
|
|
173
|
+
} else if (viewType === "workweek") {
|
|
174
|
+
basis = startOfUserWeek(startDayOfWeekOffset, momentDate, tz);
|
|
175
|
+
dayIndexes = range(0, 7).filter(
|
|
176
|
+
(dayIndex) =>
|
|
177
|
+
[0, 6].indexOf(basis.clone().add(dayIndex, "day").day()) === -1
|
|
178
|
+
);
|
|
179
|
+
} else if (viewType === "3day") {
|
|
180
|
+
// On PYD, we need to see more than just today, as potentially we'll see yesterday and tomorrow
|
|
181
|
+
basis = moment.tz(momentDate, tz);
|
|
182
|
+
dayIndexes = [0, 1, 2];
|
|
183
|
+
} else if (viewType === "1day") {
|
|
184
|
+
basis = moment.tz(momentDate, tz);
|
|
185
|
+
dayIndexes = [0];
|
|
186
|
+
} else {
|
|
187
|
+
basis = startOfUserWeek(startDayOfWeekOffset, momentDate, tz);
|
|
188
|
+
dayIndexes = range(0, 7);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const days = dayIndexes.map((dayIndex) =>
|
|
192
|
+
moment.tz(basis, tz).add(dayIndex, "day").toDate()
|
|
193
|
+
);
|
|
194
|
+
const calendarDates = dayIndexes.map((dayIndex) =>
|
|
195
|
+
moment.tz(basis, tz).add(dayIndex, "day").format("YYYY-MM-DD")
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
return {
|
|
199
|
+
basisDate: moment(basis, tz).toDate(),
|
|
200
|
+
dayIndexes,
|
|
201
|
+
days,
|
|
202
|
+
startDate: days[0],
|
|
203
|
+
endDate: moment
|
|
204
|
+
.tz(days[days.length - 1], tz)
|
|
205
|
+
.add(1, "day")
|
|
206
|
+
.toDate(),
|
|
207
|
+
calendarDates,
|
|
208
|
+
startCalendarDate: calendarDates[0],
|
|
209
|
+
endCalendarDate: calendarDates[calendarDates.length - 1],
|
|
210
|
+
};
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// tests if the date ranges intersect
|
|
214
|
+
export const dateRangeIntersect = (
|
|
215
|
+
{ startDate: start0, endDate: end0 }: { startDate: Date; endDate: Date },
|
|
216
|
+
{ startDate: start1, endDate: end1 }: { startDate: Date; endDate: Date }
|
|
217
|
+
) => {
|
|
218
|
+
if (!isDate(start0) || !isDate(end0) || !isDate(start1) || !isDate(end1)) {
|
|
219
|
+
throw `invalid parameter ${start0} ${end0}; must pass dates`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const s0 = start0.getTime();
|
|
223
|
+
const e0 = end0.getTime();
|
|
224
|
+
const s1 = start1.getTime();
|
|
225
|
+
const e1 = end1.getTime();
|
|
226
|
+
|
|
227
|
+
if (s0 > e0 || s1 > e1) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return isBetween(s0, s1, e1) || isBetween(s1, s0, e0);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const isBetween = (
|
|
235
|
+
value: number,
|
|
236
|
+
startInclusive: number,
|
|
237
|
+
endExclusive: number
|
|
238
|
+
) => value >= startInclusive && value < endExclusive;
|