@luckydye/calendar 1.2.3 → 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 +1635 -1446
- package/package.json +2 -2
- package/src/CalDAVConfig.ts +241 -117
- package/src/CalendarInternal.ts +21 -4
- package/src/CalendarLayer.ts +28 -0
- package/src/CalendarStorage.ts +5 -0
- package/src/CalendarView.ts +360 -1196
- package/src/GoogleCalendarSource.ts +25 -0
- package/src/IndexedDBStorage.ts +16 -0
- package/src/InhouseBookingSource.ts +30 -1
- 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.css +17 -1
- package/src/app.ts +211 -186
- 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/src/app.ts
CHANGED
|
@@ -12,6 +12,7 @@ import { initializeTheme } from "./Theme.js";
|
|
|
12
12
|
import { InMemorySource } from "./InMemorySource.js";
|
|
13
13
|
import { GoogleCalendarSource } from "./GoogleCalendarSource.js";
|
|
14
14
|
import { InhouseBookingSource } from "./InhouseBookingSource.js";
|
|
15
|
+
import { fetchTimeseriesJsonEvents } from "./TimeseriesJson.js";
|
|
15
16
|
import { queueStatus } from "./StatusMessage.js";
|
|
16
17
|
import { activeCalendarStore } from "./ActiveCalendarStore.js";
|
|
17
18
|
import type { CalendarEvent } from "./CalendarInternal.js";
|
|
@@ -44,7 +45,6 @@ registerKeybinds(
|
|
|
44
45
|
{ key: "Escape", action: () => calendarElement.escape() },
|
|
45
46
|
{ key: "t", action: () => calendarElement.scrollToToday() },
|
|
46
47
|
],
|
|
47
|
-
calendarElement,
|
|
48
48
|
);
|
|
49
49
|
|
|
50
50
|
let workerPromise: Promise<ServiceWorkerRegistration> | undefined;
|
|
@@ -80,6 +80,7 @@ function updateStatusBar() {
|
|
|
80
80
|
|
|
81
81
|
// Update statusbar on relevant events
|
|
82
82
|
calendarElement.addEventListener("selection-change", updateStatusBar);
|
|
83
|
+
calendarElement.addEventListener("meta-key-change", updateStatusBar);
|
|
83
84
|
|
|
84
85
|
// Update statusbar periodically for current time
|
|
85
86
|
setInterval(updateStatusBar, 10000); // Every 10 seconds
|
|
@@ -116,6 +117,42 @@ async function sync(cal: Calendar, options?: { force?: boolean }): Promise<void>
|
|
|
116
117
|
await scheduleNotificationsForEvents(events);
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
// Mutation coordination: batches post-mutation syncs so concurrent
|
|
121
|
+
// mutations don't each trigger their own reconciliation round-trip.
|
|
122
|
+
let pendingMutations = 0;
|
|
123
|
+
let mutationsSettledResolve: (() => void) | null = null;
|
|
124
|
+
let mutationsSettledPromise: Promise<void> | null = null;
|
|
125
|
+
const dirtyCalendars = new Set<Calendar>();
|
|
126
|
+
|
|
127
|
+
async function withMutation(cal: Calendar, fn: () => Promise<void>): Promise<void> {
|
|
128
|
+
if (pendingMutations === 0) {
|
|
129
|
+
mutationsSettledPromise = new Promise(r => { mutationsSettledResolve = r; });
|
|
130
|
+
}
|
|
131
|
+
pendingMutations++;
|
|
132
|
+
try {
|
|
133
|
+
await fn();
|
|
134
|
+
} finally {
|
|
135
|
+
dirtyCalendars.add(cal);
|
|
136
|
+
pendingMutations--;
|
|
137
|
+
if (pendingMutations === 0) {
|
|
138
|
+
const resolve = mutationsSettledResolve;
|
|
139
|
+
mutationsSettledResolve = null;
|
|
140
|
+
mutationsSettledPromise = null;
|
|
141
|
+
resolve?.();
|
|
142
|
+
const toSync = [...dirtyCalendars];
|
|
143
|
+
dirtyCalendars.clear();
|
|
144
|
+
for (const c of toSync) {
|
|
145
|
+
await sync(c, { force: true });
|
|
146
|
+
}
|
|
147
|
+
updateStatusBar();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function waitForMutations(): Promise<void> {
|
|
153
|
+
if (mutationsSettledPromise) await mutationsSettledPromise;
|
|
154
|
+
}
|
|
155
|
+
|
|
119
156
|
// Initialize calendar with storage first, then sync sources
|
|
120
157
|
(async () => {
|
|
121
158
|
await calendar.initPromise;
|
|
@@ -126,18 +163,33 @@ async function sync(cal: Calendar, options?: { force?: boolean }): Promise<void>
|
|
|
126
163
|
loadedCalendars.set(sampleCalendar.id, sampleCalendar);
|
|
127
164
|
activeCalendarStore.registerCalendar(sampleCalendar);
|
|
128
165
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
166
|
+
// Create sidebar with CalDAV config
|
|
167
|
+
const sidebar = document.createElement("div");
|
|
168
|
+
sidebar.slot = "sidebar";
|
|
169
|
+
sidebar.className = "caldav-sidebar";
|
|
170
|
+
|
|
171
|
+
config = document.createElement("caldav-config") as CalDAVConfigElement;
|
|
172
|
+
config.addEventListener("sources-changed", async () => {
|
|
173
|
+
await syncCalDAV();
|
|
135
174
|
});
|
|
136
|
-
|
|
137
|
-
|
|
175
|
+
config.addEventListener("collapsed-changed", (e: Event) => {
|
|
176
|
+
const { collapsed } = (e as CustomEvent).detail;
|
|
177
|
+
sidebar.classList.toggle("collapsed", collapsed);
|
|
178
|
+
});
|
|
179
|
+
config.addEventListener("active-calendar-changed", (e: Event) => {
|
|
180
|
+
const { calendarId } = (e as CustomEvent).detail;
|
|
181
|
+
activeCalendarStore.setActive(calendarId);
|
|
182
|
+
updateActiveCalendarColor();
|
|
183
|
+
});
|
|
184
|
+
sidebar.classList.toggle("collapsed", config.collapsed);
|
|
185
|
+
sidebar.appendChild(config);
|
|
186
|
+
calendarElement.appendChild(sidebar);
|
|
138
187
|
|
|
139
|
-
//
|
|
140
|
-
|
|
188
|
+
// Subscribe to active calendar store changes → update sidebar
|
|
189
|
+
activeCalendarStore.subscribe(() => {
|
|
190
|
+
config.activeCalendarId = activeCalendarStore.getActiveId();
|
|
191
|
+
updateActiveCalendarColor();
|
|
192
|
+
});
|
|
141
193
|
|
|
142
194
|
// Sync CalDAV sources (this will call updateEnabledCalendars after calendars are registered)
|
|
143
195
|
await syncCalDAV();
|
|
@@ -146,55 +198,10 @@ async function sync(cal: Calendar, options?: { force?: boolean }): Promise<void>
|
|
|
146
198
|
updateActiveCalendarColor();
|
|
147
199
|
})();
|
|
148
200
|
|
|
149
|
-
// CalDAV configuration UI
|
|
150
|
-
calendarElement.addEventListener("caldav-config", () => {
|
|
151
|
-
showCalDAVConfig();
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
function showCalDAVConfig() {
|
|
155
|
-
// Remove existing modal if present
|
|
156
|
-
const existing = document.querySelector("caldav-config-modal");
|
|
157
|
-
if (existing) {
|
|
158
|
-
existing.remove();
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// Create modal overlay
|
|
163
|
-
const modal = document.createElement("div");
|
|
164
|
-
modal.className = "caldav-config-modal";
|
|
165
|
-
modal.style.cssText = `
|
|
166
|
-
position: fixed;
|
|
167
|
-
top: 6.5rem;
|
|
168
|
-
left: 0.5rem;
|
|
169
|
-
bottom: 7rem;
|
|
170
|
-
width: 400px;
|
|
171
|
-
z-index: 1000;
|
|
172
|
-
`;
|
|
173
|
-
|
|
174
|
-
// Create CalDAV config component
|
|
175
|
-
const config = document.createElement("caldav-config") as CalDAVConfigElement;
|
|
176
|
-
|
|
177
|
-
config.addEventListener("close", () => {
|
|
178
|
-
modal.remove();
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
config.addEventListener("sources-changed", async () => {
|
|
182
|
-
await syncCalDAV();
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
modal.appendChild(config);
|
|
186
|
-
document.body.appendChild(modal);
|
|
187
|
-
|
|
188
|
-
// Close on backdrop click
|
|
189
|
-
modal.addEventListener("click", (e) => {
|
|
190
|
-
if (e.target === modal) {
|
|
191
|
-
modal.remove();
|
|
192
|
-
}
|
|
193
|
-
});
|
|
194
|
-
}
|
|
195
201
|
|
|
196
202
|
// Store for loaded calendars
|
|
197
203
|
const loadedCalendars: Map<string, Calendar> = new Map();
|
|
204
|
+
let config: CalDAVConfigElement;
|
|
198
205
|
|
|
199
206
|
function findCalendarForEvent(event: CalendarEvent): Calendar | undefined {
|
|
200
207
|
if (event.calendarId) {
|
|
@@ -305,6 +312,29 @@ function createICalCalendar(source: CalendarSource): Calendar {
|
|
|
305
312
|
};
|
|
306
313
|
}
|
|
307
314
|
|
|
315
|
+
// Create a Timeseries JSON calendar wrapper
|
|
316
|
+
function createTimeseriesJsonCalendar(source: CalendarSource): Calendar {
|
|
317
|
+
return {
|
|
318
|
+
id: source.id,
|
|
319
|
+
name: source.name,
|
|
320
|
+
color: source.color,
|
|
321
|
+
enabled: source.enabled,
|
|
322
|
+
locked: (source as { locked?: boolean }).locked,
|
|
323
|
+
sourceId: source.id,
|
|
324
|
+
sourceType: "timeseries-json",
|
|
325
|
+
|
|
326
|
+
async fetchEvents(): Promise<CalendarEvent[]> {
|
|
327
|
+
return fetchTimeseriesJsonEvents(
|
|
328
|
+
source.credentials as { url: string; timestampField?: string; titleField?: string },
|
|
329
|
+
source.color,
|
|
330
|
+
source.name,
|
|
331
|
+
source.id,
|
|
332
|
+
);
|
|
333
|
+
},
|
|
334
|
+
// Timeseries JSON calendars are read-only by default
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
|
|
308
338
|
// Create a Google Calendar wrapper
|
|
309
339
|
function createGoogleCalendar(source: CalendarSource): Calendar {
|
|
310
340
|
const calendarId = source.credentials.calendarId || "primary";
|
|
@@ -449,6 +479,7 @@ let configuredSourceIds: Set<string> = new Set();
|
|
|
449
479
|
|
|
450
480
|
// Initial CalDAV sync - sync all configured sources
|
|
451
481
|
async function syncCalDAV(force = false) {
|
|
482
|
+
await waitForMutations();
|
|
452
483
|
const saved = localStorage.getItem("caldav-sources");
|
|
453
484
|
if (!saved) return;
|
|
454
485
|
|
|
@@ -478,7 +509,15 @@ async function syncCalDAV(force = false) {
|
|
|
478
509
|
configuredSourceIds = newSourceIds;
|
|
479
510
|
|
|
480
511
|
for (const source of sources) {
|
|
481
|
-
if (!source.enabled)
|
|
512
|
+
if (!source.enabled) {
|
|
513
|
+
// Mark existing calendars from this source as disabled
|
|
514
|
+
for (const cal of loadedCalendars.values()) {
|
|
515
|
+
if (cal.sourceId === source.id) {
|
|
516
|
+
cal.enabled = false;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
continue;
|
|
520
|
+
}
|
|
482
521
|
|
|
483
522
|
if (source.type === "caldav") {
|
|
484
523
|
if (
|
|
@@ -523,6 +562,21 @@ async function syncCalDAV(force = false) {
|
|
|
523
562
|
} catch (error) {
|
|
524
563
|
console.error(`Sync error for iCal source ${source.name}:`, error);
|
|
525
564
|
}
|
|
565
|
+
} else if (source.type === "timeseries-json") {
|
|
566
|
+
if (!source.credentials?.url) {
|
|
567
|
+
console.warn(`Skipping Timeseries JSON source ${source.name}: missing URL`);
|
|
568
|
+
continue;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
const calendar = createTimeseriesJsonCalendar(source);
|
|
572
|
+
loadedCalendars.set(calendar.id, calendar);
|
|
573
|
+
activeCalendarStore.registerCalendar(calendar);
|
|
574
|
+
|
|
575
|
+
try {
|
|
576
|
+
await sync(calendar, { force });
|
|
577
|
+
} catch (error) {
|
|
578
|
+
console.error(`Sync error for Timeseries JSON source ${source.name}:`, error);
|
|
579
|
+
}
|
|
526
580
|
} else if (source.type === "google") {
|
|
527
581
|
if (!source.credentials?.accessToken) {
|
|
528
582
|
console.warn(`Skipping Google Calendar source ${source.name}: missing access token`);
|
|
@@ -563,8 +617,14 @@ async function syncCalDAV(force = false) {
|
|
|
563
617
|
updateEnabledCalendars();
|
|
564
618
|
updateLockedCalendars();
|
|
565
619
|
|
|
566
|
-
//
|
|
567
|
-
|
|
620
|
+
// Push calendar list to sidebar
|
|
621
|
+
config.calendars = Array.from(loadedCalendars.values()).map(cal => ({
|
|
622
|
+
id: cal.id,
|
|
623
|
+
name: cal.name,
|
|
624
|
+
color: cal.color,
|
|
625
|
+
sourceId: cal.sourceId,
|
|
626
|
+
}));
|
|
627
|
+
config.activeCalendarId = activeCalendarStore.getActiveId();
|
|
568
628
|
updateActiveCalendarColor();
|
|
569
629
|
}
|
|
570
630
|
|
|
@@ -582,6 +642,55 @@ calendarElement.addEventListener("selection-change", (e) => {
|
|
|
582
642
|
console.log("Selection changed:", e.detail.selectedEvents);
|
|
583
643
|
});
|
|
584
644
|
|
|
645
|
+
function showDeleteConfirmDialog(count: number): Promise<boolean> {
|
|
646
|
+
return new Promise((resolve) => {
|
|
647
|
+
const dialog = document.createElement("dialog");
|
|
648
|
+
dialog.style.cssText =
|
|
649
|
+
"margin:auto;color:var(--text-primary);border:1px solid var(--grid-color-strong);border-radius:var(--border-radius);padding:16px;min-width:280px;";
|
|
650
|
+
|
|
651
|
+
const message = document.createElement("p");
|
|
652
|
+
message.style.cssText = "margin:0 0 16px 0;";
|
|
653
|
+
message.textContent = `Delete ${count} event${count === 1 ? "" : "s"}?`;
|
|
654
|
+
|
|
655
|
+
const buttons = document.createElement("div");
|
|
656
|
+
buttons.style.cssText = "display:flex;gap:8px;justify-content:flex-end;";
|
|
657
|
+
|
|
658
|
+
const cancel = document.createElement("button");
|
|
659
|
+
cancel.textContent = "Cancel";
|
|
660
|
+
cancel.style.cssText =
|
|
661
|
+
"padding:6px 12px;background:var(--bg-item);color:var(--text-primary);border:1px solid var(--grid-color-strong);border-radius:var(--border-radius-sm);cursor:pointer;";
|
|
662
|
+
cancel.addEventListener("click", () => {
|
|
663
|
+
dialog.close();
|
|
664
|
+
resolve(false);
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
const confirm = document.createElement("button");
|
|
668
|
+
confirm.textContent = "Delete";
|
|
669
|
+
confirm.style.cssText =
|
|
670
|
+
"padding:6px 12px;background:var(--color-danger, #e53e3e);color:#fff;border:none;border-radius:var(--border-radius-sm);cursor:pointer;";
|
|
671
|
+
confirm.addEventListener("click", () => {
|
|
672
|
+
dialog.close();
|
|
673
|
+
resolve(true);
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
dialog.addEventListener("close", () => {
|
|
677
|
+
dialog.remove();
|
|
678
|
+
});
|
|
679
|
+
dialog.addEventListener("click", (ev) => {
|
|
680
|
+
if (ev.target === dialog) {
|
|
681
|
+
dialog.close();
|
|
682
|
+
resolve(false);
|
|
683
|
+
}
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
buttons.append(cancel, confirm);
|
|
687
|
+
dialog.append(message, buttons);
|
|
688
|
+
document.body.appendChild(dialog);
|
|
689
|
+
dialog.showModal();
|
|
690
|
+
confirm.focus();
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
|
|
585
694
|
function showProjectPickerDialog(
|
|
586
695
|
projects: Array<{ id: number; name: string }>,
|
|
587
696
|
): Promise<number | null> {
|
|
@@ -595,7 +704,7 @@ function showProjectPickerDialog(
|
|
|
595
704
|
|
|
596
705
|
const dialog = document.createElement("dialog");
|
|
597
706
|
dialog.style.cssText =
|
|
598
|
-
"
|
|
707
|
+
"margin:auto;color:var(--text-primary);border:1px solid var(--grid-color-strong);border-radius:var(--border-radius);padding:16px;min-width:300px;max-width:480px;";
|
|
599
708
|
|
|
600
709
|
const input = document.createElement("input");
|
|
601
710
|
input.type = "text";
|
|
@@ -652,7 +761,7 @@ function showProjectPickerDialog(
|
|
|
652
761
|
}
|
|
653
762
|
|
|
654
763
|
calendarElement.addEventListener("create-event", async (e) => {
|
|
655
|
-
const { start, end } = e.detail;
|
|
764
|
+
const { start, end, isAllDay } = e.detail;
|
|
656
765
|
const calendar = getActiveCalendar();
|
|
657
766
|
if (!calendar) {
|
|
658
767
|
queueStatus("No calendar selected. Please configure a calendar first.");
|
|
@@ -676,9 +785,25 @@ calendarElement.addEventListener("create-event", async (e) => {
|
|
|
676
785
|
id = `0:0:${projectId}`;
|
|
677
786
|
}
|
|
678
787
|
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
788
|
+
calendarElement.internal.applyEventOptimistically({
|
|
789
|
+
id,
|
|
790
|
+
title: "New Event",
|
|
791
|
+
start,
|
|
792
|
+
end,
|
|
793
|
+
isAllDay,
|
|
794
|
+
calendar: calendar.name,
|
|
795
|
+
calendarId: calendar.calendarUrl ?? calendar.id,
|
|
796
|
+
sourceId: calendar.sourceId,
|
|
797
|
+
color: calendar.color,
|
|
798
|
+
lastSynced: new Date(),
|
|
799
|
+
});
|
|
800
|
+
try {
|
|
801
|
+
await withMutation(calendar, () => calendar.createEvent({ id, title: "New Event", start, end, isAllDay }));
|
|
802
|
+
} catch (error) {
|
|
803
|
+
calendarElement.internal.removeEventOptimistically(id);
|
|
804
|
+
console.error("Failed to create event:", error);
|
|
805
|
+
queueStatus(`Failed to create event: ${error instanceof Error ? error.message : String(error)}`);
|
|
806
|
+
}
|
|
682
807
|
});
|
|
683
808
|
|
|
684
809
|
calendarElement.addEventListener("move-event", async (e) => {
|
|
@@ -707,11 +832,10 @@ calendarElement.addEventListener("move-event", async (e) => {
|
|
|
707
832
|
calendar.name,
|
|
708
833
|
);
|
|
709
834
|
try {
|
|
710
|
-
|
|
711
|
-
await
|
|
712
|
-
console.log("Move completed on calendar:", calendar.name);
|
|
713
|
-
updateStatusBar();
|
|
835
|
+
calendarElement.internal.applyEventOptimistically({ ...event, start, end });
|
|
836
|
+
await withMutation(calendar, () => calendar.updateEvent(event.id, { start, end }));
|
|
714
837
|
} catch (error) {
|
|
838
|
+
calendarElement.internal.applyEventOptimistically(event);
|
|
715
839
|
console.error("Failed to move event:", error);
|
|
716
840
|
queueStatus(`Failed to move event: ${error instanceof Error ? error.message : String(error)}`);
|
|
717
841
|
}
|
|
@@ -739,8 +863,8 @@ calendarElement.addEventListener("update-event", async (e) => {
|
|
|
739
863
|
event
|
|
740
864
|
);
|
|
741
865
|
try {
|
|
742
|
-
|
|
743
|
-
await
|
|
866
|
+
calendarElement.internal.applyEventOptimistically({ ...event, ...updates });
|
|
867
|
+
await withMutation(calendar, () => calendar.updateEvent(event.id, updates));
|
|
744
868
|
|
|
745
869
|
if ("reminders" in updates) {
|
|
746
870
|
const eventWithReminders = { ...event, ...updates };
|
|
@@ -750,10 +874,8 @@ calendarElement.addEventListener("update-event", async (e) => {
|
|
|
750
874
|
calendarElement.selectedEventForDetail = eventWithReminders;
|
|
751
875
|
}
|
|
752
876
|
}
|
|
753
|
-
|
|
754
|
-
console.log("Update completed on calendar:", calendar.name);
|
|
755
|
-
updateStatusBar();
|
|
756
877
|
} catch (error) {
|
|
878
|
+
calendarElement.internal.applyEventOptimistically(event);
|
|
757
879
|
console.error("Failed to update event:", error);
|
|
758
880
|
queueStatus(`Failed to update event: ${error instanceof Error ? error.message : String(error)}`);
|
|
759
881
|
}
|
|
@@ -762,6 +884,13 @@ calendarElement.addEventListener("update-event", async (e) => {
|
|
|
762
884
|
calendarElement.addEventListener("delete-events", async (e) => {
|
|
763
885
|
const { events } = e.detail;
|
|
764
886
|
|
|
887
|
+
const confirmed = await showDeleteConfirmDialog(events.length);
|
|
888
|
+
if (!confirmed) return;
|
|
889
|
+
|
|
890
|
+
for (const event of events) {
|
|
891
|
+
calendarElement.internal.removeEventOptimistically(event.id);
|
|
892
|
+
}
|
|
893
|
+
|
|
765
894
|
// Group events by their calendarId
|
|
766
895
|
const eventsByCalendar = new Map<string, typeof events>();
|
|
767
896
|
for (const event of events) {
|
|
@@ -800,17 +929,19 @@ calendarElement.addEventListener("delete-events", async (e) => {
|
|
|
800
929
|
calendarEvents.map((ev) => ev.id),
|
|
801
930
|
);
|
|
802
931
|
try {
|
|
932
|
+
await withMutation(calendar, async () => {
|
|
933
|
+
for (const event of calendarEvents) {
|
|
934
|
+
await calendar.deleteEvent(event.id);
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
} catch (error) {
|
|
803
938
|
for (const event of calendarEvents) {
|
|
804
|
-
|
|
939
|
+
calendarElement.internal.applyEventOptimistically(event);
|
|
805
940
|
}
|
|
806
|
-
await sync(calendar, { force: true });
|
|
807
|
-
} catch (error) {
|
|
808
941
|
console.error("Failed to delete event:", error);
|
|
809
942
|
queueStatus(`Failed to delete event: ${error instanceof Error ? error.message : String(error)}`);
|
|
810
943
|
}
|
|
811
944
|
}
|
|
812
|
-
console.log("Delete completed");
|
|
813
|
-
updateStatusBar();
|
|
814
945
|
});
|
|
815
946
|
|
|
816
947
|
calendarElement.addEventListener("force-sync", async () => {
|
|
@@ -897,75 +1028,11 @@ calendarElement.addEventListener("import-ical", async (e) => {
|
|
|
897
1028
|
}
|
|
898
1029
|
});
|
|
899
1030
|
|
|
900
|
-
// --- Active Calendar Selector UI ---
|
|
901
|
-
function createActiveCalendarSelector() {
|
|
902
|
-
// Check if selector already exists in the slot
|
|
903
|
-
if (document.getElementById("active-calendar-selector")) {
|
|
904
|
-
return;
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
const selector = document.createElement("div");
|
|
908
|
-
selector.id = "active-calendar-selector";
|
|
909
|
-
selector.setAttribute("slot", "toolbar-center");
|
|
910
|
-
selector.style.cssText = `
|
|
911
|
-
display: flex;
|
|
912
|
-
align-items: center;
|
|
913
|
-
gap: 8px;
|
|
914
|
-
font-size: 13px;
|
|
915
|
-
margin-right: auto;
|
|
916
|
-
`;
|
|
917
|
-
|
|
918
|
-
const label = document.createElement("span");
|
|
919
|
-
label.style.cssText = `
|
|
920
|
-
color: var(--text-muted, rgba(255, 255, 255, 0.5));
|
|
921
|
-
white-space: nowrap;
|
|
922
|
-
`;
|
|
923
|
-
|
|
924
|
-
const select = document.createElement("select");
|
|
925
|
-
select.id = "active-calendar-select";
|
|
926
|
-
select.style.cssText = `
|
|
927
|
-
background: var(--bg-input, rgba(0, 0, 0, 0.3));
|
|
928
|
-
border: 1px solid var(--grid-color, rgba(255, 255, 255, 0.1));
|
|
929
|
-
border-radius: var(--border-radius-sm, 4px);
|
|
930
|
-
color: var(--text-primary, rgba(255, 255, 255, 0.9));
|
|
931
|
-
padding: 4px 8px;
|
|
932
|
-
font-size: 13px;
|
|
933
|
-
cursor: pointer;
|
|
934
|
-
min-width: 150px;
|
|
935
|
-
`;
|
|
936
|
-
|
|
937
|
-
select.addEventListener("change", (e) => {
|
|
938
|
-
const target = e.target as HTMLSelectElement;
|
|
939
|
-
try {
|
|
940
|
-
activeCalendarStore.setActive(target.value || null);
|
|
941
|
-
updateActiveCalendarColor();
|
|
942
|
-
const calendar = activeCalendarStore.getActiveCalendar();
|
|
943
|
-
if (calendar) {
|
|
944
|
-
queueStatus(`Active calendar: ${calendar.name}`);
|
|
945
|
-
}
|
|
946
|
-
} catch (err) {
|
|
947
|
-
console.error("Failed to set active calendar:", err);
|
|
948
|
-
queueStatus(`Error: ${err.message}`);
|
|
949
|
-
}
|
|
950
|
-
});
|
|
951
|
-
|
|
952
|
-
selector.appendChild(label);
|
|
953
|
-
selector.appendChild(select);
|
|
954
|
-
calendarElement.appendChild(selector);
|
|
955
|
-
|
|
956
|
-
// Subscribe to changes and update the selector and color
|
|
957
|
-
activeCalendarStore.subscribe(() => {
|
|
958
|
-
updateActiveCalendarSelector();
|
|
959
|
-
updateActiveCalendarColor();
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
// Initial update
|
|
963
|
-
updateActiveCalendarSelector();
|
|
964
|
-
}
|
|
965
1031
|
|
|
966
1032
|
function updateActiveCalendarColor() {
|
|
967
1033
|
const calendar = activeCalendarStore.getActiveCalendar();
|
|
968
1034
|
calendarElement.activeCalendarColor = calendar?.color ?? null;
|
|
1035
|
+
calendarElement.activeCalendarId = activeCalendarStore.getActiveId();
|
|
969
1036
|
}
|
|
970
1037
|
|
|
971
1038
|
function updateEnabledCalendars() {
|
|
@@ -1001,45 +1068,3 @@ function updateLockedCalendars() {
|
|
|
1001
1068
|
});
|
|
1002
1069
|
calendar.setLockedCalendars(lockedCalendarIdentifiers);
|
|
1003
1070
|
}
|
|
1004
|
-
|
|
1005
|
-
function getActiveCalendarSelect(): HTMLSelectElement | null {
|
|
1006
|
-
// Look in the light DOM first (where slotted content lives)
|
|
1007
|
-
const selector = document.getElementById("active-calendar-selector");
|
|
1008
|
-
if (selector) {
|
|
1009
|
-
return selector.querySelector(
|
|
1010
|
-
"#active-calendar-select",
|
|
1011
|
-
) as HTMLSelectElement | null;
|
|
1012
|
-
}
|
|
1013
|
-
return null;
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
function updateActiveCalendarSelector() {
|
|
1017
|
-
const select = getActiveCalendarSelect();
|
|
1018
|
-
if (!select) return;
|
|
1019
|
-
|
|
1020
|
-
const calendars = activeCalendarStore.getAvailableCalendars();
|
|
1021
|
-
const activeId = activeCalendarStore.getActiveId();
|
|
1022
|
-
|
|
1023
|
-
// Clear current options
|
|
1024
|
-
select.innerHTML = "";
|
|
1025
|
-
|
|
1026
|
-
if (calendars.length === 0) {
|
|
1027
|
-
const option = document.createElement("option");
|
|
1028
|
-
option.value = "";
|
|
1029
|
-
option.textContent = "No calendars";
|
|
1030
|
-
option.disabled = true;
|
|
1031
|
-
option.selected = true;
|
|
1032
|
-
select.appendChild(option);
|
|
1033
|
-
return;
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
|
-
for (const calendar of calendars) {
|
|
1037
|
-
const option = document.createElement("option");
|
|
1038
|
-
option.value = calendar.id;
|
|
1039
|
-
option.textContent = calendar.name;
|
|
1040
|
-
if (calendar.id === activeId) {
|
|
1041
|
-
option.selected = true;
|
|
1042
|
-
}
|
|
1043
|
-
select.appendChild(option);
|
|
1044
|
-
}
|
|
1045
|
-
}
|