@luckydye/calendar 1.3.1 → 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 +2777 -2010
- package/package.json +7 -1
- package/src/ActiveCalendarStore.ts +88 -88
- package/src/CalDAVConfig.ts +611 -514
- package/src/CalDAVSource.ts +561 -433
- package/src/CalendarIntegration.ts +64 -47
- package/src/CalendarInternal.ts +645 -613
- package/src/CalendarLayer.ts +1 -0
- package/src/CalendarStorage.ts +51 -48
- package/src/CalendarView.ts +1085 -505
- package/src/Color.ts +48 -54
- package/src/DescriptionSanitizer.ts +10 -0
- package/src/GoogleCalendarSource.ts +758 -661
- package/src/ICal.ts +420 -343
- package/src/InMemorySource.ts +56 -48
- package/src/IndexedDBStorage.ts +444 -395
- package/src/InhouseBookingSource.ts +614 -522
- 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 +301 -115
- 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/CalDAVConfig.ts
CHANGED
|
@@ -1,73 +1,73 @@
|
|
|
1
|
-
import { css, html
|
|
1
|
+
import { LitElement, css, html } from "lit";
|
|
2
|
+
import { CalDAVSource as CalDAVSourceClass } from "./CalDAVSource.js";
|
|
2
3
|
import type { CalendarSource } from "./CalendarIntegration.js";
|
|
3
4
|
import { authenticateWithGoogle } from "./GoogleCalendarSource.js";
|
|
4
5
|
import { InhouseBookingSource } from "./InhouseBookingSource.js";
|
|
5
|
-
import { CalDAVSource as CalDAVSourceClass } from "./CalDAVSource.js";
|
|
6
6
|
|
|
7
7
|
interface CalDAVSourceConfig extends CalendarSource {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
8
|
+
type: "caldav";
|
|
9
|
+
credentials: {
|
|
10
|
+
serverUrl: string;
|
|
11
|
+
username: string;
|
|
12
|
+
password: string;
|
|
13
|
+
};
|
|
14
|
+
locked?: boolean;
|
|
15
15
|
}
|
|
16
16
|
|
|
17
17
|
interface ICalSource extends CalendarSource {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
18
|
+
type: "ical";
|
|
19
|
+
credentials: {
|
|
20
|
+
url: string;
|
|
21
|
+
};
|
|
22
|
+
locked?: boolean;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
interface GoogleSource extends CalendarSource {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
type: "google";
|
|
27
|
+
credentials: {
|
|
28
|
+
accessToken: string;
|
|
29
|
+
refreshToken?: string;
|
|
30
|
+
tokenExpiry?: string;
|
|
31
|
+
calendarId?: string;
|
|
32
|
+
};
|
|
33
|
+
locked?: boolean;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
interface InhouseSource extends CalendarSource {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
37
|
+
type: "inhouse";
|
|
38
|
+
credentials: {
|
|
39
|
+
sessionCookie: string;
|
|
40
|
+
employeeId: string;
|
|
41
|
+
unitId?: string;
|
|
42
|
+
startHour?: 9 | 10;
|
|
43
|
+
};
|
|
44
|
+
locked?: boolean;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
interface TimeseriesJsonSource extends CalendarSource {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
type: "timeseries-json";
|
|
49
|
+
credentials: {
|
|
50
|
+
url: string;
|
|
51
|
+
};
|
|
52
|
+
locked?: boolean;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
type ConfigurableSource =
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
| CalDAVSourceConfig
|
|
57
|
+
| ICalSource
|
|
58
|
+
| GoogleSource
|
|
59
|
+
| InhouseSource
|
|
60
|
+
| TimeseriesJsonSource;
|
|
61
61
|
|
|
62
62
|
interface SidebarCalendar {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
id: string;
|
|
64
|
+
name: string;
|
|
65
|
+
color: string;
|
|
66
|
+
sourceId: string;
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
export class CalDAVConfigElement extends LitElement {
|
|
70
|
-
|
|
70
|
+
static styles = css`
|
|
71
71
|
:host {
|
|
72
72
|
display: block;
|
|
73
73
|
font-family: -apple-system, BlinkMacSystemFont, system-ui, sans-serif;
|
|
@@ -457,405 +457,438 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
457
457
|
}
|
|
458
458
|
`;
|
|
459
459
|
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
460
|
+
sources: ConfigurableSource[] = [];
|
|
461
|
+
calendars: SidebarCalendar[] = [];
|
|
462
|
+
activeCalendarId: string | null = null;
|
|
463
|
+
isAdding = false;
|
|
464
|
+
editingId: string | null = null;
|
|
465
|
+
isGoogleAuthenticating = false;
|
|
466
|
+
googleAuthError: string | null = null;
|
|
467
|
+
collapsed = localStorage.getItem("caldav-sidebar-collapsed") === "true";
|
|
468
|
+
|
|
469
|
+
private formData: Partial<ConfigurableSource> = {};
|
|
470
|
+
|
|
471
|
+
static get properties() {
|
|
472
|
+
return {
|
|
473
|
+
sources: { type: Array },
|
|
474
|
+
calendars: { type: Array },
|
|
475
|
+
activeCalendarId: { type: String },
|
|
476
|
+
isAdding: { type: Boolean },
|
|
477
|
+
editingId: { type: String },
|
|
478
|
+
isGoogleAuthenticating: { type: Boolean },
|
|
479
|
+
googleAuthError: { type: String },
|
|
480
|
+
collapsed: { type: Boolean, reflect: true },
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
constructor() {
|
|
485
|
+
super();
|
|
486
|
+
this.loadSources();
|
|
487
|
+
|
|
488
|
+
// Check for OAuth callback (authorization code flow)
|
|
489
|
+
if (typeof window !== "undefined" && window.location.search) {
|
|
490
|
+
const params = new URLSearchParams(window.location.search);
|
|
491
|
+
const code = params.get("code");
|
|
492
|
+
const error = params.get("error");
|
|
493
|
+
const state = params.get("state");
|
|
494
|
+
|
|
495
|
+
if (code || error) {
|
|
496
|
+
// Send the result to the opener window
|
|
497
|
+
if (window.opener) {
|
|
498
|
+
window.opener.postMessage(
|
|
499
|
+
{
|
|
500
|
+
type: "google-oauth-callback",
|
|
501
|
+
code: code,
|
|
502
|
+
error: error,
|
|
503
|
+
receivedState: state,
|
|
504
|
+
},
|
|
505
|
+
window.location.origin,
|
|
506
|
+
);
|
|
507
|
+
window.close();
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
connectedCallback() {
|
|
514
|
+
super.connectedCallback();
|
|
515
|
+
this.loadSources();
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
loadSources() {
|
|
519
|
+
const saved = localStorage.getItem("caldav-sources");
|
|
520
|
+
if (saved) {
|
|
521
|
+
try {
|
|
522
|
+
this.sources = JSON.parse(saved);
|
|
523
|
+
} catch {
|
|
524
|
+
this.sources = [];
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
saveSources() {
|
|
530
|
+
localStorage.setItem("caldav-sources", JSON.stringify(this.sources));
|
|
531
|
+
this.dispatchEvent(
|
|
532
|
+
new CustomEvent("sources-changed", {
|
|
533
|
+
detail: { sources: this.sources },
|
|
534
|
+
bubbles: true,
|
|
535
|
+
}),
|
|
536
|
+
);
|
|
537
|
+
this.requestUpdate();
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
toggleCollapsed() {
|
|
541
|
+
this.collapsed = !this.collapsed;
|
|
542
|
+
localStorage.setItem("caldav-sidebar-collapsed", String(this.collapsed));
|
|
543
|
+
this.dispatchEvent(
|
|
544
|
+
new CustomEvent("collapsed-changed", {
|
|
545
|
+
detail: { collapsed: this.collapsed },
|
|
546
|
+
bubbles: true,
|
|
547
|
+
}),
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
addSource() {
|
|
552
|
+
this.isAdding = true;
|
|
553
|
+
this.editingId = null;
|
|
554
|
+
this.formData = {
|
|
555
|
+
type: "caldav",
|
|
556
|
+
color: "#FF6E68",
|
|
557
|
+
enabled: true,
|
|
558
|
+
};
|
|
559
|
+
this.requestUpdate();
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
editSource(source: ConfigurableSource) {
|
|
563
|
+
this.isAdding = false;
|
|
564
|
+
this.editingId = source.id;
|
|
565
|
+
this.formData = { ...source };
|
|
566
|
+
this.requestUpdate();
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
deleteSource(id: string) {
|
|
570
|
+
this.sources = this.sources.filter((s) => s.id !== id);
|
|
571
|
+
this.saveSources();
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
toggleEnabled(source: ConfigurableSource) {
|
|
575
|
+
source.enabled = !source.enabled;
|
|
576
|
+
this.saveSources();
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
toggleLocked(source: ConfigurableSource) {
|
|
580
|
+
source.locked = !source.locked;
|
|
581
|
+
this.saveSources();
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
selectCalendar(calendarId: string) {
|
|
585
|
+
this.activeCalendarId = calendarId;
|
|
586
|
+
this.dispatchEvent(
|
|
587
|
+
new CustomEvent("active-calendar-changed", {
|
|
588
|
+
detail: { calendarId },
|
|
589
|
+
bubbles: true,
|
|
590
|
+
}),
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
|
|
594
|
+
saveForm() {
|
|
595
|
+
if (!this.formData.name?.trim()) {
|
|
596
|
+
alert("Please enter a calendar name");
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
if (!this.formData.type) {
|
|
601
|
+
alert("Please select a calendar type");
|
|
602
|
+
return;
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
let source: ConfigurableSource;
|
|
606
|
+
|
|
607
|
+
if (this.formData.type === "caldav") {
|
|
608
|
+
if (
|
|
609
|
+
!this.formData.credentials?.serverUrl ||
|
|
610
|
+
!this.formData.credentials?.username ||
|
|
611
|
+
!this.formData.credentials?.password
|
|
612
|
+
) {
|
|
613
|
+
return;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
source = {
|
|
617
|
+
id: this.editingId || crypto.randomUUID(),
|
|
618
|
+
name: this.formData.name,
|
|
619
|
+
type: "caldav",
|
|
620
|
+
credentials: {
|
|
621
|
+
serverUrl: this.formData.credentials.serverUrl,
|
|
622
|
+
username: this.formData.credentials.username,
|
|
623
|
+
password: this.formData.credentials.password,
|
|
624
|
+
},
|
|
625
|
+
color: this.formData.color || "#FF6E68",
|
|
626
|
+
enabled: this.formData.enabled ?? true,
|
|
627
|
+
locked: this.formData.locked ?? false,
|
|
628
|
+
} as CalDAVSourceConfig;
|
|
629
|
+
} else if (this.formData.type === "ical") {
|
|
630
|
+
if (!this.formData.credentials?.url) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
source = {
|
|
635
|
+
id: this.editingId || crypto.randomUUID(),
|
|
636
|
+
name: this.formData.name,
|
|
637
|
+
type: "ical",
|
|
638
|
+
credentials: {
|
|
639
|
+
url: this.formData.credentials.url,
|
|
640
|
+
},
|
|
641
|
+
color: this.formData.color || "#FF6E68",
|
|
642
|
+
enabled: this.formData.enabled ?? true,
|
|
643
|
+
locked: this.formData.locked ?? false,
|
|
644
|
+
} as ICalSource;
|
|
645
|
+
} else if (this.formData.type === "timeseries-json") {
|
|
646
|
+
if (!this.formData.credentials?.url) {
|
|
647
|
+
return;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
source = {
|
|
651
|
+
id: this.editingId || crypto.randomUUID(),
|
|
652
|
+
name: this.formData.name,
|
|
653
|
+
type: "timeseries-json",
|
|
654
|
+
credentials: {
|
|
655
|
+
url: this.formData.credentials.url,
|
|
656
|
+
},
|
|
657
|
+
color: this.formData.color || "#06B6D4",
|
|
658
|
+
enabled: this.formData.enabled ?? true,
|
|
659
|
+
locked: this.formData.locked ?? false,
|
|
660
|
+
} as TimeseriesJsonSource;
|
|
661
|
+
} else if (this.formData.type === "google") {
|
|
662
|
+
if (!this.formData.credentials?.accessToken) {
|
|
663
|
+
alert("Please authenticate with Google before adding the calendar");
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
source = {
|
|
668
|
+
id: this.editingId || crypto.randomUUID(),
|
|
669
|
+
name: this.formData.name,
|
|
670
|
+
type: "google",
|
|
671
|
+
credentials: {
|
|
672
|
+
accessToken: this.formData.credentials.accessToken,
|
|
673
|
+
refreshToken: this.formData.credentials.refreshToken,
|
|
674
|
+
tokenExpiry: this.formData.credentials.tokenExpiry,
|
|
675
|
+
calendarId: this.formData.credentials.calendarId || "primary",
|
|
676
|
+
},
|
|
677
|
+
color: this.formData.color || "#4285F4",
|
|
678
|
+
enabled: this.formData.enabled ?? true,
|
|
679
|
+
locked: this.formData.locked ?? false,
|
|
680
|
+
} as GoogleSource;
|
|
681
|
+
} else if (this.formData.type === "inhouse") {
|
|
682
|
+
if (
|
|
683
|
+
!this.formData.credentials?.sessionCookie ||
|
|
684
|
+
!this.formData.credentials?.employeeId
|
|
685
|
+
) {
|
|
686
|
+
alert("Please enter session cookie and employee ID");
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
source = {
|
|
691
|
+
id: this.editingId || crypto.randomUUID(),
|
|
692
|
+
name: this.formData.name,
|
|
693
|
+
type: "inhouse",
|
|
694
|
+
credentials: {
|
|
695
|
+
sessionCookie: this.formData.credentials.sessionCookie,
|
|
696
|
+
employeeId: this.formData.credentials.employeeId,
|
|
697
|
+
unitId: this.formData.credentials.unitId,
|
|
698
|
+
startHour: this.formData.credentials.startHour,
|
|
699
|
+
},
|
|
700
|
+
color: this.formData.color || "#FF6E68",
|
|
701
|
+
enabled: this.formData.enabled ?? true,
|
|
702
|
+
locked: this.formData.locked ?? false,
|
|
703
|
+
} as InhouseSource;
|
|
704
|
+
} else {
|
|
705
|
+
return;
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
if (this.editingId) {
|
|
709
|
+
const index = this.sources.findIndex((s) => s.id === this.editingId);
|
|
710
|
+
if (index >= 0) {
|
|
711
|
+
this.sources[index] = source;
|
|
712
|
+
}
|
|
713
|
+
} else {
|
|
714
|
+
this.sources.push(source);
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
this.isAdding = false;
|
|
718
|
+
this.editingId = null;
|
|
719
|
+
this.formData = {};
|
|
720
|
+
this.googleAuthError = null;
|
|
721
|
+
this.saveSources();
|
|
722
|
+
this.requestUpdate();
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
cancelForm() {
|
|
726
|
+
this.isAdding = false;
|
|
727
|
+
this.editingId = null;
|
|
728
|
+
this.formData = {};
|
|
729
|
+
this.googleAuthError = null;
|
|
730
|
+
this.isGoogleAuthenticating = false;
|
|
731
|
+
this.requestUpdate();
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
updateForm(field: string, value: string | boolean | number) {
|
|
735
|
+
if (
|
|
736
|
+
field === "serverUrl" ||
|
|
737
|
+
field === "username" ||
|
|
738
|
+
field === "password" ||
|
|
739
|
+
field === "url" ||
|
|
740
|
+
field === "accessToken" ||
|
|
741
|
+
field === "refreshToken" ||
|
|
742
|
+
field === "tokenExpiry" ||
|
|
743
|
+
field === "calendarId" ||
|
|
744
|
+
field === "sessionCookie" ||
|
|
745
|
+
field === "employeeId" ||
|
|
746
|
+
field === "unitId" ||
|
|
747
|
+
field === "startHour"
|
|
748
|
+
) {
|
|
749
|
+
this.formData = {
|
|
750
|
+
...this.formData,
|
|
751
|
+
credentials: {
|
|
752
|
+
...this.formData.credentials,
|
|
753
|
+
[field]: value,
|
|
754
|
+
} as ConfigurableSource["credentials"],
|
|
755
|
+
};
|
|
756
|
+
} else {
|
|
757
|
+
this.formData = { ...this.formData, [field]: value };
|
|
758
|
+
}
|
|
759
|
+
this.requestUpdate();
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async signInWithGoogle() {
|
|
763
|
+
this.isGoogleAuthenticating = true;
|
|
764
|
+
this.googleAuthError = null;
|
|
765
|
+
this.requestUpdate();
|
|
766
|
+
|
|
767
|
+
try {
|
|
768
|
+
// Load Google OAuth credentials from public directory
|
|
769
|
+
const credentialsResponse = await fetch("/credentials_google.json");
|
|
770
|
+
if (!credentialsResponse.ok) {
|
|
771
|
+
throw new Error(
|
|
772
|
+
"Google OAuth credentials file not found. Please add credentials_google.json to the public directory.",
|
|
773
|
+
);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
const credentials = await credentialsResponse.json();
|
|
777
|
+
const clientId =
|
|
778
|
+
credentials.installed?.client_id || credentials.web?.client_id;
|
|
779
|
+
const clientSecret =
|
|
780
|
+
credentials.installed?.client_secret || credentials.web?.client_secret;
|
|
781
|
+
|
|
782
|
+
if (!clientId || !clientSecret) {
|
|
783
|
+
throw new Error(
|
|
784
|
+
"Invalid credentials file format. Expected client_id and client_secret.",
|
|
785
|
+
);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const tokens = await authenticateWithGoogle(clientId, clientSecret);
|
|
789
|
+
|
|
790
|
+
// Fetch the list of calendars from Google
|
|
791
|
+
const calendarListResponse = await fetch(
|
|
792
|
+
"https://www.googleapis.com/calendar/v3/users/me/calendarList",
|
|
793
|
+
{
|
|
794
|
+
headers: {
|
|
795
|
+
Authorization: `Bearer ${tokens.accessToken}`,
|
|
796
|
+
},
|
|
797
|
+
},
|
|
798
|
+
);
|
|
799
|
+
|
|
800
|
+
if (!calendarListResponse.ok) {
|
|
801
|
+
throw new Error("Failed to fetch calendar list from Google");
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
const calendarList = await calendarListResponse.json();
|
|
805
|
+
|
|
806
|
+
// Create a source for each calendar
|
|
807
|
+
for (const calendar of calendarList.items || []) {
|
|
808
|
+
const newSource: GoogleSource = {
|
|
809
|
+
id: crypto.randomUUID(),
|
|
810
|
+
name: calendar.summary || "Untitled Calendar",
|
|
811
|
+
type: "google",
|
|
812
|
+
credentials: {
|
|
813
|
+
accessToken: tokens.accessToken,
|
|
814
|
+
refreshToken: tokens.refreshToken,
|
|
815
|
+
tokenExpiry: tokens.expiry,
|
|
816
|
+
calendarId: calendar.id,
|
|
817
|
+
},
|
|
818
|
+
color: calendar.backgroundColor || "#4285F4",
|
|
819
|
+
enabled: true,
|
|
820
|
+
};
|
|
821
|
+
|
|
822
|
+
this.sources.push(newSource);
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
this.saveSources();
|
|
826
|
+
this.isAdding = false;
|
|
827
|
+
this.formData = {};
|
|
828
|
+
this.isGoogleAuthenticating = false;
|
|
829
|
+
this.requestUpdate();
|
|
830
|
+
} catch (error) {
|
|
831
|
+
this.isGoogleAuthenticating = false;
|
|
832
|
+
this.googleAuthError =
|
|
833
|
+
error instanceof Error ? error.message : "Authentication failed";
|
|
834
|
+
this.requestUpdate();
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
close() {
|
|
839
|
+
this.dispatchEvent(new CustomEvent("close", { bubbles: true }));
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
render() {
|
|
843
|
+
const colors = [
|
|
844
|
+
"#FF6E68",
|
|
845
|
+
"#FFB800",
|
|
846
|
+
"#83D754",
|
|
847
|
+
"#0095FD",
|
|
848
|
+
"#9FC6E7",
|
|
849
|
+
"#888082",
|
|
850
|
+
"#E91E63",
|
|
851
|
+
"#9C27B0",
|
|
852
|
+
"#673AB7",
|
|
853
|
+
"#3F51B5",
|
|
854
|
+
"#2196F3",
|
|
855
|
+
"#00BCD4",
|
|
856
|
+
"#009688",
|
|
857
|
+
"#4CAF50",
|
|
858
|
+
"#8BC34A",
|
|
859
|
+
"#CDDC39",
|
|
860
|
+
"#FFEB3B",
|
|
861
|
+
"#FFC107",
|
|
862
|
+
"#FF9800",
|
|
863
|
+
"#FF5722",
|
|
864
|
+
"#795548",
|
|
865
|
+
];
|
|
866
|
+
|
|
867
|
+
return html`
|
|
839
868
|
<div class="container">
|
|
840
869
|
<div class="header">
|
|
841
870
|
<span class="title">Sources</span>
|
|
842
|
-
<button class="collapse-btn" @click=${this.toggleCollapsed} title="${
|
|
871
|
+
<button class="collapse-btn" @click=${this.toggleCollapsed} title="${
|
|
872
|
+
this.collapsed ? "Expand" : "Collapse"
|
|
873
|
+
}">
|
|
843
874
|
${this.collapsed ? "▶" : "◀"}
|
|
844
875
|
</button>
|
|
845
876
|
</div>
|
|
846
877
|
|
|
847
|
-
${
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
878
|
+
${
|
|
879
|
+
this.collapsed
|
|
880
|
+
? this.renderSourcesList()
|
|
881
|
+
: this.isAdding || this.editingId
|
|
882
|
+
? this.renderForm(colors)
|
|
883
|
+
: this.renderSourcesList()
|
|
884
|
+
}
|
|
852
885
|
</div>
|
|
853
886
|
`;
|
|
854
|
-
|
|
887
|
+
}
|
|
855
888
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
889
|
+
renderSourcesList() {
|
|
890
|
+
if (this.sources.length === 0) {
|
|
891
|
+
return html`
|
|
859
892
|
<div class="empty-state">
|
|
860
893
|
No calendar sources configured
|
|
861
894
|
</div>
|
|
@@ -863,18 +896,24 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
863
896
|
+ Add Source
|
|
864
897
|
</button>
|
|
865
898
|
`;
|
|
866
|
-
|
|
899
|
+
}
|
|
867
900
|
|
|
868
|
-
|
|
901
|
+
return html`
|
|
869
902
|
<div class="sources-list">
|
|
870
|
-
${this.sources.map(
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
903
|
+
${this.sources.map((source) => {
|
|
904
|
+
const sourceCalendars = this.calendars.filter(
|
|
905
|
+
(c) => c.sourceId === source.id,
|
|
906
|
+
);
|
|
907
|
+
return html`
|
|
908
|
+
<div class="source-item" @click=${() =>
|
|
909
|
+
this.collapsed
|
|
910
|
+
? this.toggleEnabled(source)
|
|
911
|
+
: this.editSource(source)}>
|
|
875
912
|
<div
|
|
876
913
|
class="source-color ${source.enabled ? "" : "disabled"}"
|
|
877
|
-
style="background: ${source.color}; --source-color: ${
|
|
914
|
+
style="background: ${source.color}; --source-color: ${
|
|
915
|
+
source.color
|
|
916
|
+
}"
|
|
878
917
|
></div>
|
|
879
918
|
<span class="source-name">${source.name}</span>
|
|
880
919
|
<input
|
|
@@ -886,34 +925,43 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
886
925
|
title="${source.enabled ? "Disable" : "Enable"} sync"
|
|
887
926
|
/>
|
|
888
927
|
</div>
|
|
889
|
-
${
|
|
928
|
+
${
|
|
929
|
+
source.enabled && sourceCalendars.length > 0
|
|
930
|
+
? html`
|
|
890
931
|
<div class="calendar-list">
|
|
891
|
-
${sourceCalendars.map(
|
|
932
|
+
${sourceCalendars.map(
|
|
933
|
+
(cal) => html`
|
|
892
934
|
<div
|
|
893
|
-
class="calendar-item ${
|
|
935
|
+
class="calendar-item ${
|
|
936
|
+
this.activeCalendarId === cal.id ? "active" : ""
|
|
937
|
+
}"
|
|
894
938
|
@click=${() => this.selectCalendar(cal.id)}
|
|
895
939
|
>
|
|
896
|
-
<div class="calendar-color" style="background: ${
|
|
940
|
+
<div class="calendar-color" style="background: ${
|
|
941
|
+
cal.color
|
|
942
|
+
}"></div>
|
|
897
943
|
<span class="calendar-name">${cal.name}</span>
|
|
898
944
|
</div>
|
|
899
|
-
|
|
945
|
+
`,
|
|
946
|
+
)}
|
|
900
947
|
</div>
|
|
901
|
-
`
|
|
948
|
+
`
|
|
949
|
+
: null
|
|
950
|
+
}
|
|
902
951
|
`;
|
|
903
|
-
|
|
904
|
-
)}
|
|
952
|
+
})}
|
|
905
953
|
</div>
|
|
906
954
|
<button class="add-btn" @click=${this.addSource}>
|
|
907
955
|
+ Add Source
|
|
908
956
|
</button>
|
|
909
957
|
`;
|
|
910
|
-
|
|
958
|
+
}
|
|
911
959
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
960
|
+
renderForm(colors: string[]) {
|
|
961
|
+
const isEditing = this.editingId !== null;
|
|
962
|
+
const sourceType = this.formData.type || "caldav";
|
|
915
963
|
|
|
916
|
-
|
|
964
|
+
return html`
|
|
917
965
|
<div class="form">
|
|
918
966
|
<div class="form-group">
|
|
919
967
|
<label class="form-label">Type</label>
|
|
@@ -921,17 +969,17 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
921
969
|
class="form-input"
|
|
922
970
|
.value=${sourceType}
|
|
923
971
|
@change=${(e: Event) => {
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
972
|
+
const type = (e.target as HTMLSelectElement).value;
|
|
973
|
+
this.updateForm("type", type);
|
|
974
|
+
// Set default color based on type
|
|
975
|
+
if (type === "google" && !this.formData.color) {
|
|
976
|
+
this.updateForm("color", "#4285F4");
|
|
977
|
+
} else if (type === "caldav" && !this.formData.color) {
|
|
978
|
+
this.updateForm("color", "#FF6E68");
|
|
979
|
+
} else if (type === "timeseries-json" && !this.formData.color) {
|
|
980
|
+
this.updateForm("color", "#06B6D4");
|
|
981
|
+
}
|
|
982
|
+
}}
|
|
935
983
|
?disabled=${isEditing}
|
|
936
984
|
>
|
|
937
985
|
<option value="caldav">CalDAV (with credentials)</option>
|
|
@@ -950,12 +998,13 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
950
998
|
placeholder="My Calendar"
|
|
951
999
|
.value=${this.formData.name || ""}
|
|
952
1000
|
@input=${(e: Event) =>
|
|
953
|
-
|
|
1001
|
+
this.updateForm("name", (e.target as HTMLInputElement).value)}
|
|
954
1002
|
/>
|
|
955
1003
|
</div>
|
|
956
1004
|
|
|
957
|
-
${
|
|
958
|
-
|
|
1005
|
+
${
|
|
1006
|
+
sourceType === "caldav"
|
|
1007
|
+
? html`
|
|
959
1008
|
<div class="form-group">
|
|
960
1009
|
<label class="form-label">Server URL</label>
|
|
961
1010
|
<input
|
|
@@ -964,7 +1013,10 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
964
1013
|
placeholder="https://mail.example.com/caldav/users/username/"
|
|
965
1014
|
.value=${this.formData.credentials?.serverUrl || ""}
|
|
966
1015
|
@input=${(e: Event) =>
|
|
967
|
-
|
|
1016
|
+
this.updateForm(
|
|
1017
|
+
"serverUrl",
|
|
1018
|
+
(e.target as HTMLInputElement).value,
|
|
1019
|
+
)}
|
|
968
1020
|
/>
|
|
969
1021
|
</div>
|
|
970
1022
|
|
|
@@ -975,7 +1027,10 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
975
1027
|
type="text"
|
|
976
1028
|
.value=${this.formData.credentials?.username || ""}
|
|
977
1029
|
@input=${(e: Event) =>
|
|
978
|
-
|
|
1030
|
+
this.updateForm(
|
|
1031
|
+
"username",
|
|
1032
|
+
(e.target as HTMLInputElement).value,
|
|
1033
|
+
)}
|
|
979
1034
|
/>
|
|
980
1035
|
</div>
|
|
981
1036
|
|
|
@@ -986,12 +1041,15 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
986
1041
|
type="password"
|
|
987
1042
|
.value=${this.formData.credentials?.password || ""}
|
|
988
1043
|
@input=${(e: Event) =>
|
|
989
|
-
|
|
1044
|
+
this.updateForm(
|
|
1045
|
+
"password",
|
|
1046
|
+
(e.target as HTMLInputElement).value,
|
|
1047
|
+
)}
|
|
990
1048
|
/>
|
|
991
1049
|
</div>
|
|
992
1050
|
`
|
|
993
|
-
|
|
994
|
-
|
|
1051
|
+
: sourceType === "ical"
|
|
1052
|
+
? html`
|
|
995
1053
|
<div class="form-group">
|
|
996
1054
|
<label class="form-label">iCal URL</label>
|
|
997
1055
|
<input
|
|
@@ -1000,12 +1058,15 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
1000
1058
|
placeholder="https://example.com/calendar.ics"
|
|
1001
1059
|
.value=${this.formData.credentials?.url || ""}
|
|
1002
1060
|
@input=${(e: Event) =>
|
|
1003
|
-
|
|
1061
|
+
this.updateForm(
|
|
1062
|
+
"url",
|
|
1063
|
+
(e.target as HTMLInputElement).value,
|
|
1064
|
+
)}
|
|
1004
1065
|
/>
|
|
1005
1066
|
</div>
|
|
1006
1067
|
`
|
|
1007
|
-
|
|
1008
|
-
|
|
1068
|
+
: sourceType === "timeseries-json"
|
|
1069
|
+
? html`
|
|
1009
1070
|
<div class="form-group">
|
|
1010
1071
|
<label class="form-label">Timeseries JSON URL</label>
|
|
1011
1072
|
<input
|
|
@@ -1014,31 +1075,36 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
1014
1075
|
placeholder="https://example.com/data.json"
|
|
1015
1076
|
.value=${this.formData.credentials?.url || ""}
|
|
1016
1077
|
@input=${(e: Event) =>
|
|
1017
|
-
|
|
1078
|
+
this.updateForm(
|
|
1079
|
+
"url",
|
|
1080
|
+
(e.target as HTMLInputElement).value,
|
|
1081
|
+
)}
|
|
1018
1082
|
/>
|
|
1019
1083
|
<small style="color: var(--text-muted, rgba(255, 255, 255, 0.5)); font-size: 11px;">
|
|
1020
1084
|
Expected format: JSON array of objects with a <code>timestamp</code> field. Rendered as a heatmap backdrop.
|
|
1021
1085
|
</small>
|
|
1022
1086
|
</div>
|
|
1023
1087
|
`
|
|
1024
|
-
|
|
1025
|
-
|
|
1088
|
+
: sourceType === "google"
|
|
1089
|
+
? html`
|
|
1026
1090
|
<div class="google-auth-section">
|
|
1027
|
-
${
|
|
1028
|
-
|
|
1091
|
+
${
|
|
1092
|
+
this.formData.credentials?.accessToken
|
|
1093
|
+
? html`
|
|
1029
1094
|
<div class="auth-status authenticated">
|
|
1030
1095
|
✓ Authenticated with Google
|
|
1031
1096
|
</div>
|
|
1032
1097
|
`
|
|
1033
|
-
|
|
1098
|
+
: html`
|
|
1034
1099
|
<button
|
|
1035
1100
|
class="google-login-btn"
|
|
1036
1101
|
@click=${this.signInWithGoogle}
|
|
1037
1102
|
?disabled=${this.isGoogleAuthenticating}
|
|
1038
1103
|
>
|
|
1039
|
-
${
|
|
1040
|
-
|
|
1041
|
-
|
|
1104
|
+
${
|
|
1105
|
+
this.isGoogleAuthenticating
|
|
1106
|
+
? html`<span>Signing in...</span>`
|
|
1107
|
+
: html`
|
|
1042
1108
|
<svg class="google-logo" viewBox="0 0 24 24">
|
|
1043
1109
|
<path fill="#4285F4" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
|
|
1044
1110
|
<path fill="#34A853" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
|
|
@@ -1046,12 +1112,16 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
1046
1112
|
<path fill="#EA4335" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
|
|
1047
1113
|
</svg>
|
|
1048
1114
|
<span>Sign in with Google</span>
|
|
1049
|
-
`
|
|
1115
|
+
`
|
|
1116
|
+
}
|
|
1050
1117
|
</button>
|
|
1051
|
-
`
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1118
|
+
`
|
|
1119
|
+
}
|
|
1120
|
+
${
|
|
1121
|
+
this.googleAuthError
|
|
1122
|
+
? html`<div class="auth-status error">${this.googleAuthError}</div>`
|
|
1123
|
+
: null
|
|
1124
|
+
}
|
|
1055
1125
|
</div>
|
|
1056
1126
|
|
|
1057
1127
|
<div class="form-group">
|
|
@@ -1062,12 +1132,15 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
1062
1132
|
placeholder="primary"
|
|
1063
1133
|
.value=${this.formData.credentials?.calendarId || ""}
|
|
1064
1134
|
@input=${(e: Event) =>
|
|
1065
|
-
|
|
1135
|
+
this.updateForm(
|
|
1136
|
+
"calendarId",
|
|
1137
|
+
(e.target as HTMLInputElement).value,
|
|
1138
|
+
)}
|
|
1066
1139
|
/>
|
|
1067
1140
|
</div>
|
|
1068
1141
|
`
|
|
1069
|
-
|
|
1070
|
-
|
|
1142
|
+
: sourceType === "inhouse"
|
|
1143
|
+
? html`
|
|
1071
1144
|
<div class="form-group">
|
|
1072
1145
|
<label class="form-label">Session Cookie</label>
|
|
1073
1146
|
<input
|
|
@@ -1076,7 +1149,10 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
1076
1149
|
placeholder="sessionid=abc123; csrftoken=xyz789"
|
|
1077
1150
|
.value=${this.formData.credentials?.sessionCookie || ""}
|
|
1078
1151
|
@input=${(e: Event) =>
|
|
1079
|
-
|
|
1152
|
+
this.updateForm(
|
|
1153
|
+
"sessionCookie",
|
|
1154
|
+
(e.target as HTMLInputElement).value,
|
|
1155
|
+
)}
|
|
1080
1156
|
/>
|
|
1081
1157
|
<small style="color: var(--text-muted, rgba(255, 255, 255, 0.5)); font-size: 11px;">
|
|
1082
1158
|
Copy from browser DevTools after logging in
|
|
@@ -1091,7 +1167,10 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
1091
1167
|
placeholder="589"
|
|
1092
1168
|
.value=${this.formData.credentials?.employeeId || ""}
|
|
1093
1169
|
@input=${(e: Event) =>
|
|
1094
|
-
|
|
1170
|
+
this.updateForm(
|
|
1171
|
+
"employeeId",
|
|
1172
|
+
(e.target as HTMLInputElement).value,
|
|
1173
|
+
)}
|
|
1095
1174
|
/>
|
|
1096
1175
|
</div>
|
|
1097
1176
|
|
|
@@ -1103,7 +1182,10 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
1103
1182
|
placeholder="3"
|
|
1104
1183
|
.value=${this.formData.credentials?.unitId || ""}
|
|
1105
1184
|
@input=${(e: Event) =>
|
|
1106
|
-
|
|
1185
|
+
this.updateForm(
|
|
1186
|
+
"unitId",
|
|
1187
|
+
(e.target as HTMLInputElement).value,
|
|
1188
|
+
)}
|
|
1107
1189
|
/>
|
|
1108
1190
|
</div>
|
|
1109
1191
|
|
|
@@ -1111,40 +1193,55 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
1111
1193
|
<label class="form-label">Bookings Start At</label>
|
|
1112
1194
|
<select
|
|
1113
1195
|
class="form-input"
|
|
1114
|
-
.value=${String(
|
|
1196
|
+
.value=${String(
|
|
1197
|
+
this.formData.credentials?.startHour ?? 9,
|
|
1198
|
+
)}
|
|
1115
1199
|
@change=${(e: Event) =>
|
|
1116
|
-
|
|
1200
|
+
this.updateForm(
|
|
1201
|
+
"startHour",
|
|
1202
|
+
Number((e.target as HTMLSelectElement).value),
|
|
1203
|
+
)}
|
|
1117
1204
|
>
|
|
1118
|
-
<option value="9" ?selected=${
|
|
1119
|
-
|
|
1205
|
+
<option value="9" ?selected=${
|
|
1206
|
+
(this.formData.credentials?.startHour ?? 9) === 9
|
|
1207
|
+
}>9:00 AM</option>
|
|
1208
|
+
<option value="10" ?selected=${
|
|
1209
|
+
this.formData.credentials?.startHour === 10
|
|
1210
|
+
}>10:00 AM</option>
|
|
1120
1211
|
</select>
|
|
1121
1212
|
</div>
|
|
1122
1213
|
`
|
|
1123
|
-
|
|
1214
|
+
: null
|
|
1215
|
+
}
|
|
1124
1216
|
|
|
1125
1217
|
<div class="form-group">
|
|
1126
1218
|
<label class="form-label">Color</label>
|
|
1127
1219
|
<div class="color-picker">
|
|
1128
1220
|
${colors.map(
|
|
1129
|
-
|
|
1221
|
+
(color) => html`
|
|
1130
1222
|
<div
|
|
1131
|
-
class="color-option ${
|
|
1132
|
-
|
|
1133
|
-
|
|
1223
|
+
class="color-option ${
|
|
1224
|
+
this.formData.color === color ? "selected" : ""
|
|
1225
|
+
}"
|
|
1134
1226
|
style="background: ${color}"
|
|
1135
1227
|
@click=${() => this.updateForm("color", color)}
|
|
1136
1228
|
></div>
|
|
1137
|
-
|
|
1138
|
-
|
|
1229
|
+
`,
|
|
1230
|
+
)}
|
|
1139
1231
|
</div>
|
|
1140
1232
|
</div>
|
|
1141
1233
|
|
|
1142
1234
|
<div class="form-actions">
|
|
1143
|
-
${
|
|
1144
|
-
|
|
1235
|
+
${
|
|
1236
|
+
isEditing
|
|
1237
|
+
? html`
|
|
1238
|
+
<button class="btn btn-danger" @click=${() =>
|
|
1239
|
+
this.deleteSource(this.editingId!)}>
|
|
1145
1240
|
Delete
|
|
1146
1241
|
</button>
|
|
1147
|
-
`
|
|
1242
|
+
`
|
|
1243
|
+
: null
|
|
1244
|
+
}
|
|
1148
1245
|
<button class="btn btn-secondary" @click=${this.cancelForm}>
|
|
1149
1246
|
Cancel
|
|
1150
1247
|
</button>
|
|
@@ -1154,11 +1251,11 @@ export class CalDAVConfigElement extends LitElement {
|
|
|
1154
1251
|
</div>
|
|
1155
1252
|
</div>
|
|
1156
1253
|
`;
|
|
1157
|
-
|
|
1254
|
+
}
|
|
1158
1255
|
}
|
|
1159
1256
|
|
|
1160
1257
|
try {
|
|
1161
|
-
|
|
1258
|
+
customElements.define("caldav-config", CalDAVConfigElement);
|
|
1162
1259
|
} catch (error) {
|
|
1163
|
-
|
|
1260
|
+
console.error("Failed to register custom element:", error);
|
|
1164
1261
|
}
|