@luckydye/calendar 1.3.2 → 1.4.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/dist/calendar.js +2344 -2061
- package/package.json +7 -1
- package/src/ActiveCalendarStore.ts +88 -88
- package/src/CalDAVConfig.ts +611 -514
- package/src/CalDAVSource.ts +561 -466
- package/src/CalendarIntegration.ts +64 -47
- package/src/CalendarInternal.ts +645 -614
- package/src/CalendarLayer.ts +1 -0
- package/src/CalendarStorage.ts +51 -48
- package/src/CalendarView.ts +883 -507
- package/src/Color.ts +48 -54
- package/src/GoogleCalendarSource.ts +758 -662
- package/src/ICal.ts +420 -348
- package/src/InMemorySource.ts +56 -48
- package/src/IndexedDBStorage.ts +444 -398
- package/src/InhouseBookingSource.ts +614 -523
- package/src/Keybinds.ts +6 -1
- package/src/NotificationScheduler.ts +11 -8
- package/src/StatusBar.ts +12 -8
- package/src/StatusMessage.ts +2 -2
- package/src/Theme.ts +21 -7
- package/src/TimeseriesJson.ts +98 -98
- package/src/app.ts +153 -78
- package/src/layers/EventsLayer.ts +530 -400
- package/src/layers/GridLayer.ts +45 -125
- package/src/layers/TimeseriesHeatmapLayer.ts +123 -120
- package/src/service-worker.js +3 -2
package/src/layers/GridLayer.ts
CHANGED
|
@@ -11,6 +11,7 @@ export function createGridLayer(): CalendarLayer {
|
|
|
11
11
|
width,
|
|
12
12
|
height,
|
|
13
13
|
scrollTop,
|
|
14
|
+
scrollLeft,
|
|
14
15
|
dayWidth,
|
|
15
16
|
dayHeight,
|
|
16
17
|
leftGutterWidth,
|
|
@@ -34,22 +35,18 @@ export function createGridLayer(): CalendarLayer {
|
|
|
34
35
|
);
|
|
35
36
|
if (todayIndex >= 0) {
|
|
36
37
|
const { row, col } = getDayVisualPosition(todayIndex);
|
|
37
|
-
const x = leftGutterWidth + col * dayWidth;
|
|
38
|
+
const x = leftGutterWidth + col * dayWidth - scrollLeft;
|
|
38
39
|
const dayY = week.yOffset + row * dayHeight - scrollTop;
|
|
39
|
-
ctx.fillStyle =
|
|
40
|
-
styles["--bg-today"] || "rgba(255, 255, 255, 0.05)";
|
|
40
|
+
ctx.fillStyle = styles["--bg-today"] || "rgba(255, 255, 255, 0.05)";
|
|
41
41
|
ctx.fillRect(x, dayY, dayWidth, dayHeight);
|
|
42
42
|
|
|
43
43
|
if (showTimeScale) {
|
|
44
44
|
const now = new Date();
|
|
45
|
-
const currentMinutes =
|
|
46
|
-
|
|
47
|
-
const timeY =
|
|
48
|
-
dayY + (currentMinutes / 1440) * dayHeight;
|
|
45
|
+
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
|
46
|
+
const timeY = dayY + (currentMinutes / 1440) * dayHeight;
|
|
49
47
|
if (timeY >= 0 && timeY <= height) {
|
|
50
48
|
ctx.strokeStyle =
|
|
51
|
-
styles["--accent-current-time"] ||
|
|
52
|
-
"rgba(255, 0, 0, 0.8)";
|
|
49
|
+
styles["--accent-current-time"] || "rgba(255, 0, 0, 0.8)";
|
|
53
50
|
ctx.lineWidth = 1;
|
|
54
51
|
ctx.beginPath();
|
|
55
52
|
ctx.moveTo(x, timeY);
|
|
@@ -62,19 +59,29 @@ export function createGridLayer(): CalendarLayer {
|
|
|
62
59
|
}
|
|
63
60
|
|
|
64
61
|
// Draw grid lines
|
|
65
|
-
const gridColor =
|
|
66
|
-
|
|
62
|
+
const gridColor = styles["--grid-color"] || "rgba(255, 255, 255, 0.1)";
|
|
63
|
+
const textMuted = styles["--text-muted"] || "rgba(255, 255, 255, 0.4)";
|
|
67
64
|
ctx.strokeStyle = gridColor;
|
|
68
65
|
ctx.lineWidth = 1;
|
|
69
66
|
|
|
70
67
|
// Vertical lines (day separators)
|
|
68
|
+
const hourLabelOpacity = Math.max(
|
|
69
|
+
0,
|
|
70
|
+
Math.min(1, (dayHeight - 300) / 300),
|
|
71
|
+
);
|
|
72
|
+
ctx.strokeStyle =
|
|
73
|
+
hourLabelOpacity > 0.1
|
|
74
|
+
? textMuted.replace(/[\d.]+\)$/, `${0.2 * hourLabelOpacity})`)
|
|
75
|
+
: gridColor;
|
|
71
76
|
for (let i = 1; i <= columnsPerRow; i++) {
|
|
72
|
-
const x = leftGutterWidth + i * dayWidth;
|
|
77
|
+
const x = leftGutterWidth + i * dayWidth - scrollLeft;
|
|
73
78
|
ctx.beginPath();
|
|
74
79
|
ctx.moveTo(x, 0);
|
|
75
80
|
ctx.lineTo(x, height);
|
|
76
81
|
ctx.stroke();
|
|
77
82
|
}
|
|
83
|
+
ctx.strokeStyle = gridColor;
|
|
84
|
+
ctx.lineWidth = 1;
|
|
78
85
|
|
|
79
86
|
// Horizontal lines (week separators) and left gutter content
|
|
80
87
|
for (let i = 0; i < visibleWeeks.length; i++) {
|
|
@@ -93,8 +100,7 @@ export function createGridLayer(): CalendarLayer {
|
|
|
93
100
|
if (hiddenWeeks > 0) {
|
|
94
101
|
const gapY = y;
|
|
95
102
|
const gridColorStrong =
|
|
96
|
-
styles["--grid-color-strong"] ||
|
|
97
|
-
"rgba(255, 255, 255, 0.3)";
|
|
103
|
+
styles["--grid-color-strong"] || "rgba(255, 255, 255, 0.3)";
|
|
98
104
|
ctx.strokeStyle = gridColorStrong;
|
|
99
105
|
ctx.lineWidth = 1;
|
|
100
106
|
ctx.setLineDash([4, 4]);
|
|
@@ -105,43 +111,29 @@ export function createGridLayer(): CalendarLayer {
|
|
|
105
111
|
ctx.setLineDash([]);
|
|
106
112
|
|
|
107
113
|
const textMuted =
|
|
108
|
-
styles["--text-muted"] ||
|
|
109
|
-
"rgba(255, 255, 255, 0.4)";
|
|
114
|
+
styles["--text-muted"] || "rgba(255, 255, 255, 0.4)";
|
|
110
115
|
ctx.fillStyle = textMuted;
|
|
111
116
|
ctx.textAlign = "center";
|
|
112
|
-
const ellipsisText = `⋯ ${hiddenWeeks} week${
|
|
117
|
+
const ellipsisText = `⋯ ${hiddenWeeks} week${
|
|
118
|
+
hiddenWeeks > 1 ? "s" : ""
|
|
119
|
+
}`;
|
|
113
120
|
|
|
114
|
-
const textWidth =
|
|
115
|
-
ctx.measureText(ellipsisText).width;
|
|
121
|
+
const textWidth = ctx.measureText(ellipsisText).width;
|
|
116
122
|
const pillPadding = 8;
|
|
117
123
|
const pillX =
|
|
118
|
-
(leftGutterWidth + width) / 2 -
|
|
119
|
-
textWidth / 2 -
|
|
120
|
-
pillPadding;
|
|
124
|
+
(leftGutterWidth + width) / 2 - textWidth / 2 - pillPadding;
|
|
121
125
|
const pillY = gapY - 8;
|
|
122
126
|
const pillWidth = textWidth + pillPadding * 2;
|
|
123
127
|
const pillHeight = 16;
|
|
124
128
|
|
|
125
|
-
const bgPrimary =
|
|
126
|
-
styles["--bg-primary"] ||
|
|
127
|
-
"rgba(30, 30, 30, 0.9)";
|
|
129
|
+
const bgPrimary = styles["--bg-primary"] || "rgba(30, 30, 30, 0.9)";
|
|
128
130
|
ctx.fillStyle = bgPrimary;
|
|
129
131
|
ctx.beginPath();
|
|
130
|
-
ctx.roundRect(
|
|
131
|
-
pillX,
|
|
132
|
-
pillY,
|
|
133
|
-
pillWidth,
|
|
134
|
-
pillHeight,
|
|
135
|
-
8,
|
|
136
|
-
);
|
|
132
|
+
ctx.roundRect(pillX, pillY, pillWidth, pillHeight, 8);
|
|
137
133
|
ctx.fill();
|
|
138
134
|
|
|
139
135
|
ctx.fillStyle = textMuted;
|
|
140
|
-
ctx.fillText(
|
|
141
|
-
ellipsisText,
|
|
142
|
-
(leftGutterWidth + width) / 2,
|
|
143
|
-
gapY + 4,
|
|
144
|
-
);
|
|
136
|
+
ctx.fillText(ellipsisText, (leftGutterWidth + width) / 2, gapY + 4);
|
|
145
137
|
}
|
|
146
138
|
}
|
|
147
139
|
|
|
@@ -164,25 +156,10 @@ export function createGridLayer(): CalendarLayer {
|
|
|
164
156
|
}
|
|
165
157
|
|
|
166
158
|
// Left gutter: hour lines and labels
|
|
167
|
-
const hourLabelOpacity = Math.max(
|
|
168
|
-
0,
|
|
169
|
-
Math.min(1, (dayHeight - 300) / 300),
|
|
170
|
-
);
|
|
171
|
-
|
|
172
|
-
ctx.font = `500 11px ${fontFamily}`;
|
|
173
|
-
ctx.textBaseline = "bottom";
|
|
174
|
-
ctx.textAlign = "right";
|
|
175
|
-
|
|
176
|
-
const textMuted =
|
|
177
|
-
styles["--text-muted"] || "rgba(255, 255, 255, 0.4)";
|
|
178
159
|
ctx.strokeStyle = gridColor.replace(
|
|
179
160
|
/[\d.]+\)$/,
|
|
180
161
|
`${0.05 * hourLabelOpacity})`,
|
|
181
162
|
);
|
|
182
|
-
ctx.fillStyle = textMuted.replace(
|
|
183
|
-
/[\d.]+\)$/,
|
|
184
|
-
`${0.4 * hourLabelOpacity})`,
|
|
185
|
-
);
|
|
186
163
|
|
|
187
164
|
for (let row = 0; row < rowsPerWeek; row++) {
|
|
188
165
|
const rowY = y + row * dayHeight;
|
|
@@ -193,70 +170,6 @@ export function createGridLayer(): CalendarLayer {
|
|
|
193
170
|
ctx.moveTo(leftGutterWidth, hourY);
|
|
194
171
|
ctx.lineTo(width, hourY);
|
|
195
172
|
ctx.stroke();
|
|
196
|
-
|
|
197
|
-
if (hourLabelOpacity > 0.1) {
|
|
198
|
-
const label = `${hour.toString().padStart(2, "0")}:00`;
|
|
199
|
-
ctx.fillText(label, 48, hourY + 4);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Current time indicator in left gutter
|
|
206
|
-
if (hourLabelOpacity > 0.1) {
|
|
207
|
-
const todayIndex = week.days.findIndex((d) =>
|
|
208
|
-
CalendarInternal.isSameDay(d, today),
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
if (todayIndex >= 0) {
|
|
212
|
-
const { row } = getDayVisualPosition(todayIndex);
|
|
213
|
-
const currentMinutes =
|
|
214
|
-
today.getHours() * 60 + today.getMinutes();
|
|
215
|
-
const timeY =
|
|
216
|
-
y +
|
|
217
|
-
row * dayHeight +
|
|
218
|
-
(currentMinutes / 1440) * dayHeight;
|
|
219
|
-
|
|
220
|
-
if (timeY >= 0 && timeY <= height) {
|
|
221
|
-
const hours = today
|
|
222
|
-
.getHours()
|
|
223
|
-
.toString()
|
|
224
|
-
.padStart(2, "0");
|
|
225
|
-
const minutes = today
|
|
226
|
-
.getMinutes()
|
|
227
|
-
.toString()
|
|
228
|
-
.padStart(2, "0");
|
|
229
|
-
const timeText = `${hours}:${minutes}`;
|
|
230
|
-
|
|
231
|
-
ctx.save();
|
|
232
|
-
ctx.textAlign = "right";
|
|
233
|
-
ctx.textBaseline = "middle";
|
|
234
|
-
|
|
235
|
-
const textWidth =
|
|
236
|
-
ctx.measureText(timeText).width;
|
|
237
|
-
const bgPaddingX = 6;
|
|
238
|
-
const textX = 48;
|
|
239
|
-
const textY = timeY;
|
|
240
|
-
|
|
241
|
-
const bgElevated =
|
|
242
|
-
styles["--bg-elevated"] ||
|
|
243
|
-
"rgba(0, 0, 0, 0.7)";
|
|
244
|
-
ctx.fillStyle = bgElevated;
|
|
245
|
-
ctx.beginPath();
|
|
246
|
-
ctx.roundRect(
|
|
247
|
-
textX - textWidth - bgPaddingX,
|
|
248
|
-
textY - 8,
|
|
249
|
-
textWidth + bgPaddingX * 2,
|
|
250
|
-
16,
|
|
251
|
-
4,
|
|
252
|
-
);
|
|
253
|
-
ctx.fill();
|
|
254
|
-
|
|
255
|
-
ctx.fillStyle =
|
|
256
|
-
styles["--text-primary"] ||
|
|
257
|
-
"rgba(255, 255, 255, 1)";
|
|
258
|
-
ctx.fillText(timeText, textX, textY);
|
|
259
|
-
ctx.restore();
|
|
260
173
|
}
|
|
261
174
|
}
|
|
262
175
|
}
|
|
@@ -268,28 +181,35 @@ export function createGridLayer(): CalendarLayer {
|
|
|
268
181
|
);
|
|
269
182
|
const textMutedForWeek =
|
|
270
183
|
styles["--text-muted"] || "rgba(255, 255, 255, 0.4)";
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
`${0.4 * weekNumberOpacity})`,
|
|
184
|
+
const isCurrentWeek = week.days.some((day) =>
|
|
185
|
+
CalendarInternal.isSameDay(day, today),
|
|
274
186
|
);
|
|
187
|
+
ctx.save();
|
|
188
|
+
ctx.font = `500 11px ${fontFamily}`;
|
|
189
|
+
ctx.textBaseline = "middle";
|
|
190
|
+
ctx.fillStyle = isCurrentWeek
|
|
191
|
+
? "rgba(255, 255, 255, 1)"
|
|
192
|
+
: textMutedForWeek.replace(
|
|
193
|
+
/[\d.]+\)$/,
|
|
194
|
+
`${0.4 * weekNumberOpacity})`,
|
|
195
|
+
);
|
|
275
196
|
ctx.textAlign = "center";
|
|
276
197
|
const label = `W${week.weekNumber}`;
|
|
277
198
|
const weekTop = y;
|
|
278
199
|
const weekBottom = y + week.height;
|
|
200
|
+
const topClearance = 52;
|
|
279
201
|
const labelY = Math.max(
|
|
280
|
-
|
|
281
|
-
Math.min(
|
|
282
|
-
weekTop + week.height / 2 + 4,
|
|
283
|
-
weekBottom - 4,
|
|
284
|
-
),
|
|
202
|
+
topClearance,
|
|
203
|
+
Math.min(weekTop + week.height / 2 + 4, weekBottom - 4),
|
|
285
204
|
);
|
|
286
205
|
if (
|
|
287
|
-
labelY >= Math.max(
|
|
206
|
+
labelY >= Math.max(topClearance, weekTop + 4) &&
|
|
288
207
|
labelY <= Math.min(height, weekBottom) &&
|
|
289
208
|
weekNumberOpacity > 0.1
|
|
290
209
|
) {
|
|
291
210
|
ctx.fillText(label, 30, labelY);
|
|
292
211
|
}
|
|
212
|
+
ctx.restore();
|
|
293
213
|
}
|
|
294
214
|
},
|
|
295
215
|
};
|
|
@@ -2,131 +2,134 @@ import type { CalendarEvent, WeekInfo } from "../CalendarInternal.js";
|
|
|
2
2
|
import type { CalendarLayer, LayerContext } from "../CalendarLayer.js";
|
|
3
3
|
|
|
4
4
|
export interface HeatmapState {
|
|
5
|
-
|
|
5
|
+
events: CalendarEvent[];
|
|
6
6
|
}
|
|
7
7
|
|
|
8
8
|
function getBucketMinutes(dayHeight: number): number {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
if (dayHeight >= 320) return 15;
|
|
10
|
+
if (dayHeight >= 240) return 30;
|
|
11
|
+
if (dayHeight >= 160) return 60;
|
|
12
|
+
return 120;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
15
|
export function createTimeseriesHeatmapLayer(
|
|
16
|
-
|
|
16
|
+
state: HeatmapState,
|
|
17
17
|
): CalendarLayer {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
18
|
+
return {
|
|
19
|
+
name: "timeseries-heatmap",
|
|
20
|
+
enabled: true,
|
|
21
|
+
render(lc: LayerContext): void {
|
|
22
|
+
const {
|
|
23
|
+
ctx,
|
|
24
|
+
height,
|
|
25
|
+
scrollTop,
|
|
26
|
+
scrollLeft,
|
|
27
|
+
dayWidth,
|
|
28
|
+
dayHeight,
|
|
29
|
+
leftGutterWidth,
|
|
30
|
+
visibleWeeks,
|
|
31
|
+
allWeeks,
|
|
32
|
+
getDayVisualPosition,
|
|
33
|
+
} = lc;
|
|
34
|
+
|
|
35
|
+
const events = state.events.filter(
|
|
36
|
+
(event) => event.visualStyle === "heatmap",
|
|
37
|
+
);
|
|
38
|
+
if (events.length === 0 || visibleWeeks.length === 0) return;
|
|
39
|
+
|
|
40
|
+
const dayInfoMap = new Map<
|
|
41
|
+
number,
|
|
42
|
+
{
|
|
43
|
+
week: WeekInfo;
|
|
44
|
+
weekIndex: number;
|
|
45
|
+
dayIndex: number;
|
|
46
|
+
row: number;
|
|
47
|
+
col: number;
|
|
48
|
+
dayStartMs: number;
|
|
49
|
+
}
|
|
50
|
+
>();
|
|
51
|
+
|
|
52
|
+
for (const week of visibleWeeks) {
|
|
53
|
+
const weekIndex = allWeeks.indexOf(week);
|
|
54
|
+
if (weekIndex < 0) continue;
|
|
55
|
+
for (let dayIndex = 0; dayIndex < 7; dayIndex++) {
|
|
56
|
+
const day = week.days[dayIndex];
|
|
57
|
+
if (!day) continue;
|
|
58
|
+
const dayStart = new Date(day);
|
|
59
|
+
dayStart.setHours(0, 0, 0, 0);
|
|
60
|
+
const { row, col } = getDayVisualPosition(dayIndex);
|
|
61
|
+
dayInfoMap.set(dayStart.getTime(), {
|
|
62
|
+
week,
|
|
63
|
+
weekIndex,
|
|
64
|
+
dayIndex,
|
|
65
|
+
row,
|
|
66
|
+
col,
|
|
67
|
+
dayStartMs: dayStart.getTime(),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (dayInfoMap.size === 0) return;
|
|
73
|
+
|
|
74
|
+
const bucketMinutes = getBucketMinutes(dayHeight);
|
|
75
|
+
const bucketCounts = new Map<
|
|
76
|
+
string,
|
|
77
|
+
{ count: number; color: string; dayKey: number; bucket: number }
|
|
78
|
+
>();
|
|
79
|
+
let maxCount = 0;
|
|
80
|
+
|
|
81
|
+
for (const event of events) {
|
|
82
|
+
const start = event.start;
|
|
83
|
+
const dayStart = new Date(start);
|
|
84
|
+
dayStart.setHours(0, 0, 0, 0);
|
|
85
|
+
const dayKey = dayStart.getTime();
|
|
86
|
+
const dayInfo = dayInfoMap.get(dayKey);
|
|
87
|
+
if (!dayInfo) continue;
|
|
88
|
+
|
|
89
|
+
const minutesIntoDay = (start.getTime() - dayInfo.dayStartMs) / 60000;
|
|
90
|
+
if (minutesIntoDay < 0 || minutesIntoDay >= 1440) continue;
|
|
91
|
+
|
|
92
|
+
const bucket = Math.floor(minutesIntoDay / bucketMinutes);
|
|
93
|
+
const color = event.color || "rgba(255, 255, 255, 0.6)";
|
|
94
|
+
const key = `${dayKey}-${bucket}-${color}`;
|
|
95
|
+
const entry = bucketCounts.get(key) || {
|
|
96
|
+
count: 0,
|
|
97
|
+
color,
|
|
98
|
+
dayKey,
|
|
99
|
+
bucket,
|
|
100
|
+
};
|
|
101
|
+
entry.count += 1;
|
|
102
|
+
bucketCounts.set(key, entry);
|
|
103
|
+
if (entry.count > maxCount) maxCount = entry.count;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (maxCount === 0) return;
|
|
107
|
+
|
|
108
|
+
const bucketHeight = (bucketMinutes / 1440) * dayHeight;
|
|
109
|
+
const minHeight = Math.max(2, bucketHeight);
|
|
110
|
+
|
|
111
|
+
for (const entry of bucketCounts.values()) {
|
|
112
|
+
const dayInfo = dayInfoMap.get(entry.dayKey);
|
|
113
|
+
if (!dayInfo) continue;
|
|
114
|
+
|
|
115
|
+
const intensity = Math.log(entry.count + 1) / Math.log(maxCount + 1);
|
|
116
|
+
const alpha = 0.08 + intensity * 0.22;
|
|
117
|
+
|
|
118
|
+
const x = leftGutterWidth + dayInfo.col * dayWidth - scrollLeft + 1;
|
|
119
|
+
const y =
|
|
120
|
+
dayInfo.week.yOffset +
|
|
121
|
+
dayInfo.row * dayHeight +
|
|
122
|
+
(entry.bucket * bucketMinutes * dayHeight) / 1440 -
|
|
123
|
+
scrollTop;
|
|
124
|
+
|
|
125
|
+
if (y > height || y + minHeight < 0) continue;
|
|
126
|
+
|
|
127
|
+
ctx.globalAlpha = alpha;
|
|
128
|
+
ctx.fillStyle = entry.color;
|
|
129
|
+
ctx.fillRect(x, y, dayWidth - 2, minHeight);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
ctx.globalAlpha = 1;
|
|
133
|
+
},
|
|
134
|
+
};
|
|
132
135
|
}
|
package/src/service-worker.js
CHANGED
|
@@ -109,8 +109,9 @@ async function getScheduledNotifications() {
|
|
|
109
109
|
request.onsuccess = () => {
|
|
110
110
|
const notifications = request.result || [];
|
|
111
111
|
// Sort by trigger time
|
|
112
|
-
notifications.sort(
|
|
113
|
-
|
|
112
|
+
notifications.sort(
|
|
113
|
+
(a, b) =>
|
|
114
|
+
new Date(a.triggerTime).getTime() - new Date(b.triggerTime).getTime(),
|
|
114
115
|
);
|
|
115
116
|
resolve(notifications);
|
|
116
117
|
};
|