@luckydye/calendar 1.3.0 → 1.3.1
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 +1548 -1373
- package/package.json +2 -2
- package/src/CalDAVConfig.ts +50 -1
- package/src/CalendarInternal.ts +9 -4
- package/src/CalendarLayer.ts +28 -0
- package/src/CalendarView.ts +277 -1110
- package/src/GoogleCalendarSource.ts +25 -0
- package/src/IndexedDBStorage.ts +3 -0
- package/src/Keybinds.ts +3 -18
- package/src/StatusBar.ts +11 -0
- package/src/Theme.ts +4 -4
- package/src/TimeseriesJson.ts +114 -0
- package/src/app.ts +44 -4
- package/src/layers/EventsLayer.ts +958 -0
- package/src/layers/GridLayer.ts +296 -0
- package/src/layers/TimeseriesHeatmapLayer.ts +132 -0
- package/src/lib.ts +1 -0
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@luckydye/calendar",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.1",
|
|
4
4
|
"author": "Tim Havlicek",
|
|
5
5
|
"contributors": [],
|
|
6
6
|
"description": "",
|
|
7
7
|
"type": "module",
|
|
8
8
|
"main": "dist/calendar.js",
|
|
9
|
-
"types": "src/
|
|
9
|
+
"types": "src/lib.ts",
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build:app": "APP=true bunx --bun vite build --outDir=dist/calendar --base=./",
|
|
12
12
|
"build": "bunx --bun vite build",
|
package/src/CalDAVConfig.ts
CHANGED
|
@@ -44,7 +44,20 @@ interface InhouseSource extends CalendarSource {
|
|
|
44
44
|
locked?: boolean;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
interface TimeseriesJsonSource extends CalendarSource {
|
|
48
|
+
type: "timeseries-json";
|
|
49
|
+
credentials: {
|
|
50
|
+
url: string;
|
|
51
|
+
};
|
|
52
|
+
locked?: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
type ConfigurableSource =
|
|
56
|
+
| CalDAVSourceConfig
|
|
57
|
+
| ICalSource
|
|
58
|
+
| GoogleSource
|
|
59
|
+
| InhouseSource
|
|
60
|
+
| TimeseriesJsonSource;
|
|
48
61
|
|
|
49
62
|
interface SidebarCalendar {
|
|
50
63
|
id: string;
|
|
@@ -621,6 +634,22 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
621
634
|
enabled: this.formData.enabled ?? true,
|
|
622
635
|
locked: this.formData.locked ?? false,
|
|
623
636
|
} as ICalSource;
|
|
637
|
+
} else if (this.formData.type === "timeseries-json") {
|
|
638
|
+
if (!this.formData.credentials?.url) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
source = {
|
|
643
|
+
id: this.editingId || crypto.randomUUID(),
|
|
644
|
+
name: this.formData.name,
|
|
645
|
+
type: "timeseries-json",
|
|
646
|
+
credentials: {
|
|
647
|
+
url: this.formData.credentials.url,
|
|
648
|
+
},
|
|
649
|
+
color: this.formData.color || "#06B6D4",
|
|
650
|
+
enabled: this.formData.enabled ?? true,
|
|
651
|
+
locked: this.formData.locked ?? false,
|
|
652
|
+
} as TimeseriesJsonSource;
|
|
624
653
|
} else if (this.formData.type === "google") {
|
|
625
654
|
if (!this.formData.credentials?.accessToken) {
|
|
626
655
|
alert('Please authenticate with Google before adding the calendar');
|
|
@@ -899,12 +928,15 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
899
928
|
this.updateForm("color", "#4285F4");
|
|
900
929
|
} else if (type === "caldav" && !this.formData.color) {
|
|
901
930
|
this.updateForm("color", "#FF6E68");
|
|
931
|
+
} else if (type === "timeseries-json" && !this.formData.color) {
|
|
932
|
+
this.updateForm("color", "#06B6D4");
|
|
902
933
|
}
|
|
903
934
|
}}
|
|
904
935
|
?disabled=${isEditing}
|
|
905
936
|
>
|
|
906
937
|
<option value="caldav">CalDAV (with credentials)</option>
|
|
907
938
|
<option value="ical">iCal URL</option>
|
|
939
|
+
<option value="timeseries-json">Timeseries JSON (URL)</option>
|
|
908
940
|
<option value="google">Google Calendar</option>
|
|
909
941
|
<option value="inhouse">Inhouse Booking System</option>
|
|
910
942
|
</select>
|
|
@@ -972,6 +1004,23 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
972
1004
|
/>
|
|
973
1005
|
</div>
|
|
974
1006
|
`
|
|
1007
|
+
: sourceType === "timeseries-json"
|
|
1008
|
+
? html`
|
|
1009
|
+
<div class="form-group">
|
|
1010
|
+
<label class="form-label">Timeseries JSON URL</label>
|
|
1011
|
+
<input
|
|
1012
|
+
class="form-input"
|
|
1013
|
+
type="text"
|
|
1014
|
+
placeholder="https://example.com/data.json"
|
|
1015
|
+
.value=${this.formData.credentials?.url || ""}
|
|
1016
|
+
@input=${(e: Event) =>
|
|
1017
|
+
this.updateForm("url", (e.target as HTMLInputElement).value)}
|
|
1018
|
+
/>
|
|
1019
|
+
<small style="color: var(--text-muted, rgba(255, 255, 255, 0.5)); font-size: 11px;">
|
|
1020
|
+
Expected format: JSON array of objects with a <code>timestamp</code> field. Rendered as a heatmap backdrop.
|
|
1021
|
+
</small>
|
|
1022
|
+
</div>
|
|
1023
|
+
`
|
|
975
1024
|
: sourceType === "google"
|
|
976
1025
|
? html`
|
|
977
1026
|
<div class="google-auth-section">
|
package/src/CalendarInternal.ts
CHANGED
|
@@ -49,6 +49,7 @@ export interface CalendarEvent {
|
|
|
49
49
|
status?: 'TENTATIVE' | 'CONFIRMED' | 'CANCELLED';
|
|
50
50
|
reminders?: NotificationConfig[];
|
|
51
51
|
isAllDay?: boolean;
|
|
52
|
+
visualStyle?: 'heatmap';
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
export interface WeekInfo {
|
|
@@ -72,8 +73,9 @@ export interface EventSegment {
|
|
|
72
73
|
}
|
|
73
74
|
|
|
74
75
|
// Configuration for the sliding window buffer
|
|
75
|
-
const BUFFER_WEEKS =
|
|
76
|
-
const EXTEND_WEEKS =
|
|
76
|
+
const BUFFER_WEEKS = 4; // Trigger extension when within this many weeks of the edge
|
|
77
|
+
const EXTEND_WEEKS = 26; // Weeks to add per extension (~6 months); must be >> BUFFER_WEEKS so
|
|
78
|
+
// the scroll thumb lands well past the buffer entry after each extension, preventing cascade
|
|
77
79
|
const MAX_WEEKS = 96; // Maximum weeks to keep in memory (trim beyond this)
|
|
78
80
|
|
|
79
81
|
export class CalendarInternal {
|
|
@@ -258,18 +260,21 @@ export class CalendarInternal {
|
|
|
258
260
|
|
|
259
261
|
getFilteredEvents(filter?: string) {
|
|
260
262
|
const baseEvents = Array.from(this.calendarEvents.values());
|
|
263
|
+
return this.filterEvents(baseEvents, filter);
|
|
264
|
+
}
|
|
261
265
|
|
|
266
|
+
filterEvents(events: CalendarEvent[], filter?: string): CalendarEvent[] {
|
|
262
267
|
// Filter by enabled calendars
|
|
263
268
|
// Note: enabledCalendars contains calendar IDs, not sourceIds
|
|
264
269
|
const enabledEvents = this.enabledCalendars.size > 0
|
|
265
|
-
?
|
|
270
|
+
? events.filter(e => {
|
|
266
271
|
// For backwards compatibility: check both sourceId and calendarId
|
|
267
272
|
// CalDAV events: match via calendarId
|
|
268
273
|
// Other sources: match via sourceId
|
|
269
274
|
return (e.calendarId && this.enabledCalendars.has(e.calendarId)) ||
|
|
270
275
|
(e.sourceId && this.enabledCalendars.has(e.sourceId));
|
|
271
276
|
})
|
|
272
|
-
:
|
|
277
|
+
: events;
|
|
273
278
|
|
|
274
279
|
// Mark events from locked calendars as read-only
|
|
275
280
|
const lockedEvents = this.lockedCalendars.size > 0
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { WeekInfo } from "./CalendarInternal.js";
|
|
2
|
+
|
|
3
|
+
// Minimum dayHeight at which timed events are rendered at their actual time-of-day position.
|
|
4
|
+
export const TIME_SCALE_DAY_HEIGHT = 300;
|
|
5
|
+
|
|
6
|
+
export interface LayerContext {
|
|
7
|
+
ctx: CanvasRenderingContext2D;
|
|
8
|
+
width: number;
|
|
9
|
+
height: number;
|
|
10
|
+
scrollTop: number;
|
|
11
|
+
dayWidth: number;
|
|
12
|
+
dayHeight: number;
|
|
13
|
+
leftGutterWidth: number;
|
|
14
|
+
columnsPerRow: number;
|
|
15
|
+
rowsPerWeek: number;
|
|
16
|
+
visibleWeeks: WeekInfo[];
|
|
17
|
+
allWeeks: WeekInfo[];
|
|
18
|
+
fontFamily: string;
|
|
19
|
+
styles: Record<string, string>;
|
|
20
|
+
getDayVisualPosition: (dayIndex: number) => { row: number; col: number };
|
|
21
|
+
filter: string | null;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface CalendarLayer {
|
|
25
|
+
name: string;
|
|
26
|
+
enabled: boolean;
|
|
27
|
+
render(lc: LayerContext): void;
|
|
28
|
+
}
|