From 4574b7f03ccc85c4bebdbfd9475788456086704f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 8 Aug 2024 12:27:40 -0400
Subject: many changes to add typing in place of 'any's etc

---
 src/client/views/collections/CollectionCalendarView.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

(limited to 'src/client/views/collections/CollectionCalendarView.tsx')

diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index a08a7c7c1..9eb16917b 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -6,11 +6,11 @@ import { dateRangeStrToDates, returnTrue } from '../../../ClientUtils';
 import { Doc, DocListCast } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
 import { CollectionStackingView } from './CollectionStackingView';
-import { CollectionSubView } from './CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView';
 
 @observer
 export class CollectionCalendarView extends CollectionSubView() {
-    constructor(props: any) {
+    constructor(props: SubCollectionViewProps) {
         super(props);
         makeObservable(this);
     }
-- 
cgit v1.2.3-70-g09d2


From 0b77229000231869695a6211e216d5b1755f53f7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 4 Sep 2024 13:05:17 -0400
Subject: made calendarBox work

---
 src/ClientUtils.ts                                 |   3 +-
 .../views/collections/CollectionCalendarView.tsx   |   4 +-
 .../views/collections/CollectionCardDeckView.scss  |  18 +--
 .../views/nodes/calendarBox/CalendarBox.scss       |  18 +++
 src/client/views/nodes/calendarBox/CalendarBox.tsx | 172 ++++++++++++---------
 5 files changed, 131 insertions(+), 84 deletions(-)
 create mode 100644 src/client/views/nodes/calendarBox/CalendarBox.scss

(limited to 'src/client/views/collections/CollectionCalendarView.tsx')

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index 55801df81..51ad55c07 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -650,6 +650,7 @@ export function DivWidth(ele: HTMLElement | null): number {
 export function dateRangeStrToDates(dateStr: string) {
     // dateStr in yyyy-mm-dd format
     const dateRangeParts = dateStr.split('|'); // splits into from and to date
+    if (dateRangeParts.length < 2) return { startDate: new Date(), endDate: new Date() };
     const fromParts = dateRangeParts[0].split('-');
     const toParts = dateRangeParts[1].split('-');
 
@@ -661,7 +662,7 @@ export function dateRangeStrToDates(dateStr: string) {
     const toMonth = parseInt(toParts[1]) - 1;
     const toDay = parseInt(toParts[2]);
 
-    return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)];
+    return { startDate: new Date(fromYear, fromMonth, fromDay), endDate: new Date(toYear, toMonth, toDay) };
 }
 
 function replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) {
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index 9eb16917b..c1f0b314b 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -38,8 +38,8 @@ export class CollectionCalendarView extends CollectionSubView() {
         const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range);
         const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range);
 
-        const [aFromDate, aToDate] = dateRangeStrToDates(aDateRangeStr);
-        const [bFromDate, bToDate] = dateRangeStrToDates(bDateRangeStr);
+        const { startDate: aFromDate, endDate: aToDate } = dateRangeStrToDates(aDateRangeStr);
+        const { startDate: bFromDate, endDate: bToDate } = dateRangeStrToDates(bDateRangeStr);
 
         if (aFromDate > bFromDate) {
             return -1; // a comes first
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss
index a089b248d..cc797d0bd 100644
--- a/src/client/views/collections/CollectionCardDeckView.scss
+++ b/src/client/views/collections/CollectionCardDeckView.scss
@@ -6,6 +6,15 @@
     position: relative;
     background-color: white;
     overflow: hidden;
+
+    button {
+        width: 35px;
+        height: 35px;
+        border-radius: 50%;
+        background-color: $dark-gray;
+        // border-color: $medium-blue;
+        margin: 5px; // transform: translateY(-50px);
+    }
 }
 
 .card-wrapper {
@@ -34,15 +43,6 @@
     justify-content: start; /* Centers buttons horizontally */
 }
 
-button {
-    width: 35px;
-    height: 35px;
-    border-radius: 50%;
-    background-color: $dark-gray;
-    // border-color: $medium-blue;
-    margin: 5px; // transform: translateY(-50px);
-}
-
 // button:hover {
 //     transform: translateY(-50px);
 // }
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.scss b/src/client/views/nodes/calendarBox/CalendarBox.scss
new file mode 100644
index 000000000..8eee47e42
--- /dev/null
+++ b/src/client/views/nodes/calendarBox/CalendarBox.scss
@@ -0,0 +1,18 @@
+.calendarBox {
+    display: flex;
+    width: 100%;
+    height: 100%;
+    .calendarBox-wrapper {
+        width: 100%;
+        height: 100%;
+        .fc-col-header {
+            width: 100% !important;
+        }
+        .fc-daygrid-body {
+            width: 100% !important;
+            .fc-scrollgrid-sync-table {
+                width: 100% !important;
+            }
+        }
+    }
+}
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index bd66941c3..ca35d85b3 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -1,16 +1,17 @@
 import { Calendar, EventSourceInput } from '@fullcalendar/core';
 import dayGridPlugin from '@fullcalendar/daygrid';
 import multiMonthPlugin from '@fullcalendar/multimonth';
-import { makeObservable } from 'mobx';
+import { action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { dateRangeStrToDates } from '../../../../ClientUtils';
-import { Doc } from '../../../../fields/Doc';
-import { StrCast } from '../../../../fields/Types';
+import { Doc, DocListCast } from '../../../../fields/Doc';
+import { NumCast, StrCast } from '../../../../fields/Types';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { FieldView, FieldViewProps } from '../FieldView';
+import './CalendarBox.scss';
 
 type CalendarView = 'month' | 'multi-month' | 'week';
 
@@ -20,108 +21,135 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return FieldView.LayoutString(CalendarBox, fieldKey);
     }
 
+    _calendarRef: HTMLDivElement | null = null;
+    _calendar: Calendar | undefined;
+    _oldWheel: HTMLElement | null = null;
+    _observer: ResizeObserver | undefined;
+
     constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
     }
 
-    componentDidMount(): void {}
+    @observable _multiMonth = 0;
 
-    componentWillUnmount(): void {}
+    componentDidMount(): void {
+        reaction(
+            () => this.calendarEvents,
+            events => {
+                this._calendar?.setOption('events', events);
+            },
+            { fireImmediately: true }
+        );
+    }
 
-    _calendarRef = React.createRef<HTMLElement>();
+    @computed get calendarEvents(): EventSourceInput | undefined {
+        return this.childDocs.map(doc => {
+            const { startDate: start, endDate: end } = dateRangeStrToDates(StrCast(doc.date_range));
+
+            return {
+                title: StrCast(doc.title),
+                start,
+                end,
+                startEditable: true,
+                endEditable: true,
+                allDay: false,
+                classNames: ['mother'], // will determine the style
+                editable: true, // subject to change in the future
+                backgroundColor: this.eventToColor(doc),
+                color: 'white',
+                extendedProps: {
+                    description: StrCast(doc.description),
+                },
+            };
+        });
+    }
 
-    get dateRangeStr() {
-        return StrCast(this.Document.date_range);
+    @computed get dateRangeStrDates() {
+        return dateRangeStrToDates(StrCast(this.Document.date_range));
     }
 
     // Choose a calendar view based on the date range
-    get calendarViewType(): CalendarView {
-        const [fromDate, toDate] = dateRangeStrToDates(this.dateRangeStr);
+    @computed get calendarViewType(): CalendarView {
+        const { startDate, endDate } = this.dateRangeStrDates;
 
-        if (fromDate.getFullYear() !== toDate.getFullYear() || fromDate.getMonth() !== toDate.getMonth()) return 'multi-month';
+        if (startDate.getFullYear() !== endDate.getFullYear() || startDate.getMonth() !== endDate.getMonth()) return 'multi-month';
 
-        if (Math.abs(fromDate.getDay() - toDate.getDay()) > 7) return 'month';
+        if (Math.abs(startDate.getDay() - endDate.getDay()) > 7) return 'month';
         return 'week';
     }
-
-    get calendarStartDate() {
-        return this.dateRangeStr.split('|')[0];
-    }
-
-    get calendarToDate() {
-        return this.dateRangeStr.split('|')[1];
+    @computed get childDocs() {
+        return DocListCast(this.dataDoc[this.fieldKey]);
     }
 
-    get childDocs(): Doc[] {
-        return this.childDocs; // get all sub docs for a calendar
-    }
-
-    docBackgroundColor(type: string): string {
+    eventToColor(event: Doc): string {
         // TODO: Return a different color based on the event type
-        console.log(type);
+        console.log(event.title);
         return 'blue';
     }
 
-    get calendarEvents(): EventSourceInput | undefined {
-        if (this.childDocs.length === 0) return undefined;
-        return this.childDocs.map(doc => {
-            const docTitle = StrCast(doc.title);
-            const docDateRange = StrCast(doc.date_range);
-            const [startDate, endDate] = dateRangeStrToDates(docDateRange);
-            const docType = doc.type;
-            const docDescription = doc.description ? StrCast(doc.description) : '';
-
-            return {
-                title: docTitle,
-                start: startDate,
-                end: endDate,
-                allDay: false,
-                classNames: [StrCast(docType)], // will determine the style
-                editable: false, // subject to change in the future
-                backgroundColor: this.docBackgroundColor(StrCast(doc.type)),
-                color: 'white',
-                extendedProps: {
-                    description: docDescription,
-                },
-            };
-        });
-    }
-
     handleEventClick = (/* arg: EventClickArg */) => {
         // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc.
     };
 
-    calendarEl: HTMLElement = document.getElementById('calendar-box-v1')!;
-
     // https://fullcalendar.io
-    get calendar() {
-        return new Calendar(this.calendarEl, {
-            plugins: [this.calendarViewType === 'multi-month' ? multiMonthPlugin : dayGridPlugin],
-            headerToolbar: {
-                left: 'prev,next today',
-                center: 'title',
-                right: 'dayGridMonth,timeGridWeek,timeGridDay,listWeek',
-            },
-            initialDate: this.calendarStartDate,
-            navLinks: true,
-            editable: false,
-            displayEventTime: false,
-            displayEventEnd: false,
-            events: this.calendarEvents,
-            eventClick: this.handleEventClick,
-        });
-    }
+    renderCalendar = () => {
+        const cal = !this._calendarRef
+            ? null
+            : (this._calendar = new Calendar(this._calendarRef, {
+                  plugins: [multiMonthPlugin, dayGridPlugin],
+                  headerToolbar: {
+                      left: 'prev,next today',
+                      center: 'title',
+                      right: 'dayGridMonth multiMonth', // timeGridWeek timeGridDay listWeek',
+                  },
+                  initialDate: this.dateRangeStrDates.startDate,
+                  navLinks: true,
+                  editable: false,
+                  displayEventTime: false,
+                  displayEventEnd: false,
+                  aspectRatio: NumCast(this.Document.width) / NumCast(this.Document.height),
+                  events: this.calendarEvents,
+                  eventClick: this.handleEventClick,
+              }));
+        cal?.render();
+    };
 
+    onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
     render() {
+        this._multiMonth;
         return (
-            <div className="calendar-box-conatiner">
-                <div id="calendar-box-v1" />
+            <div
+                key={this._multiMonth}
+                className="calendarBox"
+                onPointerDown={action(e =>
+                    setTimeout(() => {
+                        if ((e.nativeEvent.target as HTMLButtonElement)?.className?.includes('multiMonth-button')) {
+                            this._multiMonth = this._multiMonth + 1;
+                        }
+                    })
+                )}
+                ref={r => {
+                    this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+                    this._oldWheel = r;
+                    // prevent wheel events from passively propagating up through containers and prevents containers from preventDefault which would block scrolling
+                    r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+
+                    if (r) {
+                        this._observer?.disconnect();
+                        (this._observer = new ResizeObserver(() => {
+                            this._calendar?.setOption('aspectRatio', NumCast(this.Document.width) / NumCast(this.Document.height));
+                            this._calendar?.updateSize();
+                        })).observe(r);
+                        this.renderCalendar();
+                    }
+                }}>
+                <div className="calendarBox-wrapper" ref={r => (this._calendarRef = r)} />
             </div>
         );
     }
 }
 Docs.Prototypes.TemplateMap.set(DocumentType.CALENDAR, {
     layout: { view: CalendarBox, dataField: 'data' },
-    options: { acl: '' },
+    options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true },
 });
-- 
cgit v1.2.3-70-g09d2


From 9014cc474a039f0daef6cc0ee2011329da7703ac Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 4 Sep 2024 22:58:40 -0400
Subject: more fixes for calendarBox to preserve selection when view changes.

---
 package-lock.json                                  | 488 +++++++++++----------
 package.json                                       |   3 +-
 src/ClientUtils.ts                                 |  22 +-
 .../views/collections/CollectionCalendarView.tsx   |   4 +-
 .../views/nodes/calendarBox/CalendarBox.scss       |   1 +
 src/client/views/nodes/calendarBox/CalendarBox.tsx |  56 ++-
 6 files changed, 311 insertions(+), 263 deletions(-)

(limited to 'src/client/views/collections/CollectionCalendarView.tsx')

diff --git a/package-lock.json b/package-lock.json
index 735db29ae..fabc6d90a 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,7 +22,7 @@
                 "@fortawesome/free-regular-svg-icons": "^6.5.1",
                 "@fortawesome/free-solid-svg-icons": "^6.5.1",
                 "@fortawesome/react-fontawesome": "^0.2.0",
-                "@fullcalendar/core": "^6.1.10",
+                "@fullcalendar/core": "^6.1.15",
                 "@fullcalendar/daygrid": "^6.1.10",
                 "@fullcalendar/multimonth": "^6.1.10",
                 "@fullcalendar/timegrid": "^6.1.15",
@@ -115,6 +115,7 @@
                 "fork-ts-checker-webpack-plugin": "^9.0.2",
                 "form-data": "^4.0.0",
                 "formidable": "3.5.1",
+                "fullcalendar": "^6.1.15",
                 "function-plot": "^1.23.3",
                 "golden-layout": "^2.6.0",
                 "google-auth-library": "^9.4.1",
@@ -2911,6 +2912,22 @@
                 "@fullcalendar/core": "~6.1.15"
             }
         },
+        "node_modules/@fullcalendar/interaction": {
+            "version": "6.1.15",
+            "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz",
+            "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==",
+            "peerDependencies": {
+                "@fullcalendar/core": "~6.1.15"
+            }
+        },
+        "node_modules/@fullcalendar/list": {
+            "version": "6.1.15",
+            "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz",
+            "integrity": "sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==",
+            "peerDependencies": {
+                "@fullcalendar/core": "~6.1.15"
+            }
+        },
         "node_modules/@fullcalendar/multimonth": {
             "version": "6.1.15",
             "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.15.tgz",
@@ -3167,13 +3184,13 @@
             }
         },
         "node_modules/@jimp/core": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.2.0.tgz",
-            "integrity": "sha512-omuAP/bmi45UUs76KE9hCfLOHT7+abvkcbOEUG267/sCVA19d1W2Ta78p3KNry23LSfFlvokGYFlO9tuvVdYiA==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.3.0.tgz",
+            "integrity": "sha512-3+ndSrQYQxyyKyUSdXyk29vHt9vc3zg+3aFrVX2RHzAMeLdjkQHqzQc/7v6VxRk6BtOh1v/VPtmsXHXVg1vVhA==",
             "dependencies": {
-                "@jimp/file-ops": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+                "@jimp/file-ops": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "await-to-js": "^3.0.0",
                 "exif-parser": "^0.1.12",
                 "file-type": "^16.0.0",
@@ -3184,13 +3201,13 @@
             }
         },
         "node_modules/@jimp/diff": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.2.0.tgz",
-            "integrity": "sha512-Ln7smOEt/v68X+kduzGfIw+9D4tzXtMeWVP+y6unVma9TConssvuUDKknV/lPw7tsktPoMLtL3zJA+TRJ7+JHQ==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.3.0.tgz",
+            "integrity": "sha512-eGFfZi8UjSZ6gGu9kpQmGPRRDt5fMV3V1qKRn0cTqtBsECAnKPG5PPT1dvnjDDBbtAOH81jhIr//ko8H5WV8jg==",
             "dependencies": {
-                "@jimp/plugin-resize": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+                "@jimp/plugin-resize": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "pixelmatch": "^5.3.0"
             },
             "engines": {
@@ -3198,21 +3215,21 @@
             }
         },
         "node_modules/@jimp/file-ops": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.2.0.tgz",
-            "integrity": "sha512-LUipYM3d9OiccVfBGQ7TGzb/X++1ME2Rgbt/UhGPK32/iA1w+sW/edczXGJAG4Q+3PzTMXKir5znPNXL+dlOpw==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.3.0.tgz",
+            "integrity": "sha512-DzbSLgUdOGT9T9qf+Ik/hBA8e4zA9tWyHnJJA/N9sJHwHNDCMtoaX3KY5ssHuRGmcngGKLwbeGjddnZXF4oIVA==",
             "engines": {
                 "node": ">=18"
             }
         },
         "node_modules/@jimp/js-bmp": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.2.0.tgz",
-            "integrity": "sha512-3JF++37nVK+V4mf7MMT5dUOf88GrcEoMt9/0fN1+gEF6EkjS7tAcx3cSInMa8geurZipsfY71XL7WTi/y8hziA==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.3.0.tgz",
+            "integrity": "sha512-sc3jvJJOMHoUtP9mlnuBCkwhHy9T2KiSfdV3XKg81v+bg9O0LudB33v3Y0dtGxSo/WOL2V6PVNIWTRf7gu+hJw==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+                "@jimp/core": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "bmp-ts": "^1.0.9"
             },
             "engines": {
@@ -3220,12 +3237,12 @@
             }
         },
         "node_modules/@jimp/js-gif": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.2.0.tgz",
-            "integrity": "sha512-7OV8ph3rGzOJ6nSQv7+m8fyIeeshg5b1h6hlD18dCO7qamPnJdc2M5+of1Fte2D7qDL8Az2vT6vgKPP05O/HHg==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.3.0.tgz",
+            "integrity": "sha512-hhAd/TpZSp6AIuRjYKFGGEsu9HzQG16Q9lZHPcTZz1TlxtTUqW60AJvFrGvnUZbTecDj3JnI3TipX8aeqZpBDg==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/types": "1.2.0",
+                "@jimp/core": "1.3.0",
+                "@jimp/types": "1.3.0",
                 "gifwrap": "^0.10.1",
                 "omggif": "^1.0.10"
             },
@@ -3234,12 +3251,12 @@
             }
         },
         "node_modules/@jimp/js-jpeg": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.2.0.tgz",
-            "integrity": "sha512-7YYl8jEHCTX2XVjUZJOHAROFDHmx8diM+fEMgGhM4teOAKNM5xVDXSnC16h/qyspHqgDDQgI16gszmo6MF3W4Q==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.3.0.tgz",
+            "integrity": "sha512-SrZL35FvKsgySS5kpqYWbVsi0rswcgxw3oRTVOy55q8F045fE4U0YqlnRdkg77dxdahUOIWVhvn5+0V+Re5F5A==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/types": "1.2.0",
+                "@jimp/core": "1.3.0",
+                "@jimp/types": "1.3.0",
                 "jpeg-js": "^0.4.4"
             },
             "engines": {
@@ -3247,12 +3264,12 @@
             }
         },
         "node_modules/@jimp/js-png": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.2.0.tgz",
-            "integrity": "sha512-toW5aqFH/alybkP2IZSLHa18O5lpwJpB5JqNQEYT1KU30V/czyUdpznlCV50PVXHZnQbb48MKJzfpLqj0O7KBA==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.3.0.tgz",
+            "integrity": "sha512-9x8uFueVupNocQQ5WEFat61M31MdRDV7O3QBDR2iEsbVeQ1+LE9Tvvm9r1PG9W91KZYzG4IYPPsogQ0TFEixqA==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/types": "1.2.0",
+                "@jimp/core": "1.3.0",
+                "@jimp/types": "1.3.0",
                 "pngjs": "^7.0.0"
             },
             "engines": {
@@ -3260,12 +3277,12 @@
             }
         },
         "node_modules/@jimp/js-tiff": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.2.0.tgz",
-            "integrity": "sha512-RhXqdkB7lhpCW9uKVXmLhFI+Inpj7CcP8GNMMWpPcYaWw8aGUqZjgdy/81nudIdXmvk4eKI5NdtWptCVzRFvsQ==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.3.0.tgz",
+            "integrity": "sha512-uin5WkVm0M8ZgotIANXU0sfVkNApsKws1ZSqsc9NZf0MYJsZkz/w8D4ld6hXkFCEQcJ/TMu7aeMZSP+I8cbmOg==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/types": "1.2.0",
+                "@jimp/core": "1.3.0",
+                "@jimp/types": "1.3.0",
                 "utif2": "^4.1.0"
             },
             "engines": {
@@ -3273,12 +3290,12 @@
             }
         },
         "node_modules/@jimp/plugin-blit": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.2.0.tgz",
-            "integrity": "sha512-y18/qt/FdINIQefmYSI6ownb1VtOsR3Y43H8JwzcsuSmpZLpF+lJwXoZLFzgCSSCpiQyb+z924k6L11C4uKH3g==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.3.0.tgz",
+            "integrity": "sha512-5OjEUM0jtzQ6KJBTougs8ozbUAxYyJiKZqceFb8mqKVMjuiM94N9425mIDDgOl5MDFwAESeTMIchKCAPMOv2FQ==",
             "dependencies": {
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3286,23 +3303,23 @@
             }
         },
         "node_modules/@jimp/plugin-blur": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.2.0.tgz",
-            "integrity": "sha512-h0/4W2zjkrDC9nZGl1r+pk5ftG6d1Zdvza/BE7xie++P2ihGp4ikgK26Hn/xlnAH691yh/uCjP0eeJw+Ggfy5Q==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.3.0.tgz",
+            "integrity": "sha512-108RHeCvHFJqpQvuaydhxwJLBwdjEWKLW6ZXWWCnCadrpKbH2yqu9P6oUhHS7atLjQ0ZBzXcM+Wj2VYR7XU8ng==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/utils": "1.2.0"
+                "@jimp/core": "1.3.0",
+                "@jimp/utils": "1.3.0"
             },
             "engines": {
                 "node": ">=18"
             }
         },
         "node_modules/@jimp/plugin-circle": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.2.0.tgz",
-            "integrity": "sha512-rfPOsLAnQ+YpmGttJRbmTHP2V5Q1Ca99HcpaF8NqOVC9cotGu+Ux3xJIo9QKxyDTO/uwx15KTwzbQAbE43Mr0A==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.3.0.tgz",
+            "integrity": "sha512-Lkz1uwD2wgysuu4TDzAVQ26+urr+siYlO/qXnMYHui9k0P735S6B6EsWrzssLDGOtqevQyYcx5u6h0Kv4lzehg==",
             "dependencies": {
-                "@jimp/types": "1.2.0",
+                "@jimp/types": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3310,13 +3327,13 @@
             }
         },
         "node_modules/@jimp/plugin-color": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.2.0.tgz",
-            "integrity": "sha512-RdDj1ZyUN478hwCbR004xQK2qJ+FQvL0F+fGVUOozpw4pWs6G0PKtGWooKGB0522sEw3tvvb5mHDnz06WWIU0w==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.3.0.tgz",
+            "integrity": "sha512-HLFtZB86W2nVnfZT+LAsyooF9efapWPmxuOKECeevunb1zHieO1ni19QXJfcqtt+cVj8UxIBGC4v9IFDJ9PGYw==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+                "@jimp/core": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "tinycolor2": "^1.6.0",
                 "zod": "^3.23.8"
             },
@@ -3325,15 +3342,15 @@
             }
         },
         "node_modules/@jimp/plugin-contain": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.2.0.tgz",
-            "integrity": "sha512-YZmQnP0eJbBUhDr4YuvfsQl04CFEwTy+EKgMX7fCc5Ne/l0Y4wiabUtB2Cr47tvIoQiOzmx8UV5jnoYJFO6CQw==",
-            "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/plugin-blit": "1.2.0",
-                "@jimp/plugin-resize": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.3.0.tgz",
+            "integrity": "sha512-1xPJ/CC4hh8IDrZFCtwQezw0RFzdrFvatzXkmfZD0cRyUXtYQ8VzExeK9MXLWi2+/nfufh+2SIhThTQ8xIzLBw==",
+            "dependencies": {
+                "@jimp/core": "1.3.0",
+                "@jimp/plugin-blit": "1.3.0",
+                "@jimp/plugin-resize": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3341,14 +3358,14 @@
             }
         },
         "node_modules/@jimp/plugin-cover": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.2.0.tgz",
-            "integrity": "sha512-EnCibB1mMSFRoIW8h53lvEAWXFb+H6iIjCoKAVuMIkPq6nhc7WCDuvvgn+oLnYaNPSsD5+ge+pafdIaAlw+jpA==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.3.0.tgz",
+            "integrity": "sha512-Q8vlXepruKU+A55PS2A+d7TPwIoYthnX61ae+TQa+/4DjYk6XZA2YlmUFhq7P3RH5p288N/84ILNSIwH5YCqAw==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/plugin-crop": "1.2.0",
-                "@jimp/plugin-resize": "1.2.0",
-                "@jimp/types": "1.2.0",
+                "@jimp/core": "1.3.0",
+                "@jimp/plugin-crop": "1.3.0",
+                "@jimp/plugin-resize": "1.3.0",
+                "@jimp/types": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3356,13 +3373,13 @@
             }
         },
         "node_modules/@jimp/plugin-crop": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.2.0.tgz",
-            "integrity": "sha512-jVq3zlrcYwnVKIS8NTCeOoJ8sWrn9Th4qDQWu7hbo6tgEAYXZCEBPC3zQzeu7RY9oETorJ3Q7zKzyKO8xzBedw==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.3.0.tgz",
+            "integrity": "sha512-AoCTYFgcDEH+sqc2IQ5CI0CgYrQZSFfZ6q4zSXkWA+irs1nDbjJeA+0vClkYxJNSkk2wqB0fys69OBoqfDabrw==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+                "@jimp/core": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3370,12 +3387,12 @@
             }
         },
         "node_modules/@jimp/plugin-displace": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.2.0.tgz",
-            "integrity": "sha512-XAm+y2xX95cioc4Ya9RlxiDRbfQ6+DF6B6nHRkzwUYN3TaB698PS8fBkBZ4VlMQ8IqD+Y+OKcEDhpWz28GqUVg==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.3.0.tgz",
+            "integrity": "sha512-8t/R0SjE7YWujeMLbUT2js9WIeyFbeQXxAiCPt4AJy1BUD56sbcWIx1zJzrL52eF+bG4AS8oLOp5arL+P7ocmQ==",
             "dependencies": {
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3383,23 +3400,23 @@
             }
         },
         "node_modules/@jimp/plugin-dither": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.2.0.tgz",
-            "integrity": "sha512-NoOeeOhqFoIBqHnJtXkkknWCykOQzH/duYTb4JTxRaHKAIelQAvDbxanLu+S7wp3brIvurJM9RDxYK0Gqd7k5A==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.3.0.tgz",
+            "integrity": "sha512-oE6kHne88OOcJBu+fk9KvMMB71UZUO4B2YYoaHVjGbtciPG9FbBBGqgD9oULVWhHuICZdDnfgFF0hhemQuQloQ==",
             "dependencies": {
-                "@jimp/types": "1.2.0"
+                "@jimp/types": "1.3.0"
             },
             "engines": {
                 "node": ">=18"
             }
         },
         "node_modules/@jimp/plugin-fisheye": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.2.0.tgz",
-            "integrity": "sha512-N5UG6vbpQkYbhYAWJcreR2vC0fWWxf9VUupDaIOM1Yp9h5Lel/8Q0o2ie5yUxsHt2wa0HOm0O9KorfHzlV6s5A==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.3.0.tgz",
+            "integrity": "sha512-VLaqY/IxrqHyjKeWpUwJZpAqug4DE26hM/8ejfPm5FmofAS1dI0deecDfbthRbw17hnPVcAiTkZ6QTiQL71Z4w==",
             "dependencies": {
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3407,11 +3424,11 @@
             }
         },
         "node_modules/@jimp/plugin-flip": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.2.0.tgz",
-            "integrity": "sha512-zI5/6dkoUdslf7BMccXURIQzXKKXu/ocTTnzf3C6VHV4M6U/QIg1BLi8A5+KkgHnvpuSM/IW3T0PDWKLpmK/CA==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.3.0.tgz",
+            "integrity": "sha512-cHeefBsjBYLbjqq8TFJHQD/6sgJLvb6XPdB8wVvKe682Y8jIilxlEhZJeUFXwsMrKKhbXNZxmtSc/pEIYCo6cA==",
             "dependencies": {
-                "@jimp/types": "1.2.0",
+                "@jimp/types": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3419,19 +3436,19 @@
             }
         },
         "node_modules/@jimp/plugin-hash": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.2.0.tgz",
-            "integrity": "sha512-opj35tK+Ku+17mq9Ip7GEwDpNr+Mk/xQcOGLBispbKLWqAG1IDyHTLSsAmDcM8pL4AXXv/96xPUWWxu4mZLwTw==",
-            "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/js-bmp": "1.2.0",
-                "@jimp/js-jpeg": "1.2.0",
-                "@jimp/js-png": "1.2.0",
-                "@jimp/js-tiff": "1.2.0",
-                "@jimp/plugin-color": "1.2.0",
-                "@jimp/plugin-resize": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.3.0.tgz",
+            "integrity": "sha512-LAUi9FFT3Kp2bI2hWXQ75t1pl6q6ZyKH/jJQZg8DPL+fFK0//TIsw/g0VxW5lZoV3mHUUorQlsZzWtNi/DGtWA==",
+            "dependencies": {
+                "@jimp/core": "1.3.0",
+                "@jimp/js-bmp": "1.3.0",
+                "@jimp/js-jpeg": "1.3.0",
+                "@jimp/js-png": "1.3.0",
+                "@jimp/js-tiff": "1.3.0",
+                "@jimp/plugin-color": "1.3.0",
+                "@jimp/plugin-resize": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "any-base": "^1.1.0"
             },
             "engines": {
@@ -3439,11 +3456,11 @@
             }
         },
         "node_modules/@jimp/plugin-mask": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.2.0.tgz",
-            "integrity": "sha512-VLGifoXRyddUDbUj0LIG3HrEeCZOHSzfOcv/bXyQq7/KHmR+HnhaAuDOlkDLBa4buZMbh4Ub+nhmDhU3kwf8yA==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.3.0.tgz",
+            "integrity": "sha512-fpU6rZ75c1gD6/8zsiPQW57+doX3KfexZ3lVYToyd720HPO/qfG9lzwfY30tJVXArI4DMbG8qN7lXKgGeWwGqw==",
             "dependencies": {
-                "@jimp/types": "1.2.0",
+                "@jimp/types": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3451,15 +3468,15 @@
             }
         },
         "node_modules/@jimp/plugin-print": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.2.0.tgz",
-            "integrity": "sha512-TLfne5npxCkf8CY/UuCT589GpKQsjQrcj6SjdVpcddGdm25t3jEt4+WE6wPFsOuocWZO6YSaJNOQJKma/BRQ3w==",
-            "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/js-jpeg": "1.2.0",
-                "@jimp/js-png": "1.2.0",
-                "@jimp/plugin-blit": "1.2.0",
-                "@jimp/types": "1.2.0",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.3.0.tgz",
+            "integrity": "sha512-WeN35Fo9Bushm6VGUdQXqXrVIFDYECeKLKN+LlAqQ/YnIlUiTirPlcyGHzEBKD8uXDCmjBYqxwadcPvRDVwFEw==",
+            "dependencies": {
+                "@jimp/core": "1.3.0",
+                "@jimp/js-jpeg": "1.3.0",
+                "@jimp/js-png": "1.3.0",
+                "@jimp/plugin-blit": "1.3.0",
+                "@jimp/types": "1.3.0",
                 "parse-bmfont-ascii": "^1.0.6",
                 "parse-bmfont-binary": "^1.0.6",
                 "parse-bmfont-xml": "^1.1.6",
@@ -3471,9 +3488,9 @@
             }
         },
         "node_modules/@jimp/plugin-quantize": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.2.0.tgz",
-            "integrity": "sha512-0sYazEK2sT1NXFInwcef0ag0X3qicqI07vUFnv4bKtxILU00tWhUyig3h6kLzZhFdiTGXt2MP3OGCu4mWQ8OfQ==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.3.0.tgz",
+            "integrity": "sha512-4Hgp5UNN8DGeX1ULNANPwlHCyuaZYZPJ/mpe/lnCN4jLI/SeBzR4g8tU+srNF6arPwRXrLNQV6T/ehAa7zhbkg==",
             "dependencies": {
                 "image-q": "^4.0.0",
                 "zod": "^3.23.8"
@@ -3483,12 +3500,12 @@
             }
         },
         "node_modules/@jimp/plugin-resize": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.2.0.tgz",
-            "integrity": "sha512-QZd72r988QdAYQs9jggHsCdatbybkU3WmVxqE0l3YbpltW13tdiMktsNTSyQT1nci7l3LvrBRuNl6pOQx7Ug5w==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.3.0.tgz",
+            "integrity": "sha512-9fMw6ff/3kzDwQ2rGNYCJ2jc9IHsxQh9eaoPb4SkVHxzq+O3yka3M2Vjf41gaYhQ5Pt5QLgQ9uYUA+2kp1RF1g==",
             "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/types": "1.2.0",
+                "@jimp/core": "1.3.0",
+                "@jimp/types": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3496,15 +3513,15 @@
             }
         },
         "node_modules/@jimp/plugin-rotate": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.2.0.tgz",
-            "integrity": "sha512-P0dvX6XlerHK6Om1rUs9kAxqbmk94VCPxuzJk5asUliOtq+wklLTfRbcmn3fQkn9xH3EtQojdE0RAVCExMkOdg==",
-            "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/plugin-crop": "1.2.0",
-                "@jimp/plugin-resize": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.3.0.tgz",
+            "integrity": "sha512-lpzk37tzk7b5RG5U6P/E3vk+bwU86TnNZOc1LHQeTOEpfFAPMTJl6w+OlLLxVNJ7HUQege/8P47N2onQX00wXw==",
+            "dependencies": {
+                "@jimp/core": "1.3.0",
+                "@jimp/plugin-crop": "1.3.0",
+                "@jimp/plugin-resize": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3512,15 +3529,15 @@
             }
         },
         "node_modules/@jimp/plugin-threshold": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.2.0.tgz",
-            "integrity": "sha512-yMibcCFQ1NLx94DFdGqPG0z3EiOohgbYhDYpqMmadBu1IUOcHl8EnCJjD673RZxQh/4s6MR6vpcT5epjtFVnGg==",
-            "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/plugin-color": "1.2.0",
-                "@jimp/plugin-hash": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.3.0.tgz",
+            "integrity": "sha512-lKBzZEgjI/zM51/muGyL2juGEkK361/yFpRcmjafIijq0sHNww1rhqSa0AhO80iCmN77A4Ym/oelY0qh4mtlLQ==",
+            "dependencies": {
+                "@jimp/core": "1.3.0",
+                "@jimp/plugin-color": "1.3.0",
+                "@jimp/plugin-hash": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0",
                 "zod": "^3.23.8"
             },
             "engines": {
@@ -3528,9 +3545,9 @@
             }
         },
         "node_modules/@jimp/types": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.2.0.tgz",
-            "integrity": "sha512-Zjt+cV0f9Txnx9xdT+SJeNVgbgsQdtMTNWF8W6OAs3pLI9KaAelsXLi9F9jWikoctAI58GU/LOG52LLQkE/5qQ==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.3.0.tgz",
+            "integrity": "sha512-K4RaTmDTqZqbjjwOtxzVH9QyCgQBukXjKOmdgNuCmu7ugrpeTeWV7SvrwZZPhTt31lmja8A3a3Dw1ScVZdtmyA==",
             "dependencies": {
                 "zod": "^3.23.8"
             },
@@ -3539,11 +3556,11 @@
             }
         },
         "node_modules/@jimp/utils": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.2.0.tgz",
-            "integrity": "sha512-7/CIawD7fKfzG1QWU66nS5JxLNbhCiYuv6ce2ytJUzzPPLrJ0X250cS0kb30FvPaNu42DfvJUMUjxbg+9Ia79g==",
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.3.0.tgz",
+            "integrity": "sha512-QIye6IWJaQ3Q9+6rxgQiFI1I7MwrQZYxlhYhPolJv+BfCXBT8XWJymV8J75vlnjvz3kN2AMXAEU7c7w0h2Tz3Q==",
             "dependencies": {
-                "@jimp/types": "1.2.0",
+                "@jimp/types": "1.3.0",
                 "tinycolor2": "^1.6.0"
             },
             "engines": {
@@ -9560,9 +9577,9 @@
             "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g=="
         },
         "node_modules/@types/node": {
-            "version": "22.5.3",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.3.tgz",
-            "integrity": "sha512-njripolh85IA9SQGTAqbmnNZTdxv7X/4OYGPz8tgy5JDr8MP+uDBa921GpYEoDDnwm0Hmn5ZPeJgiiSTPoOzkQ==",
+            "version": "22.5.4",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz",
+            "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==",
             "dependencies": {
                 "undici-types": "~6.19.2"
             }
@@ -10273,36 +10290,36 @@
             "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
         },
         "node_modules/@vue/compiler-core": {
-            "version": "3.5.0",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.0.tgz",
-            "integrity": "sha512-ja7cpqAOfw4tyFAxgBz70Z42miNDeaqTxExTsnXDLomRpqfyCgyvZvFp482fmsElpfvsoMJUsvzULhvxUTW6Iw==",
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.1.tgz",
+            "integrity": "sha512-WdjF+NSgFYdWttHevHw5uaJFtKPalhmxhlu2uREj8cLP0uyKKIR60/JvSZNTp0x+NSd63iTiORQTx3+tt55NWQ==",
             "dependencies": {
                 "@babel/parser": "^7.25.3",
-                "@vue/shared": "3.5.0",
+                "@vue/shared": "3.5.1",
                 "entities": "^4.5.0",
                 "estree-walker": "^2.0.2",
                 "source-map-js": "^1.2.0"
             }
         },
         "node_modules/@vue/compiler-dom": {
-            "version": "3.5.0",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.0.tgz",
-            "integrity": "sha512-xYjUybWZXl+1R/toDy815i4PbeehL2hThiSGkcpmIOCy2HoYyeeC/gAWK/Y/xsoK+GSw198/T5O31bYuQx5uvQ==",
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.1.tgz",
+            "integrity": "sha512-Ao23fB1lINo18HLCbJVApvzd9OQe8MgmQSgyY5+umbWj2w92w9KykVmJ4Iv2US5nak3ixc2B+7Km7JTNhQ8kSQ==",
             "dependencies": {
-                "@vue/compiler-core": "3.5.0",
-                "@vue/shared": "3.5.0"
+                "@vue/compiler-core": "3.5.1",
+                "@vue/shared": "3.5.1"
             }
         },
         "node_modules/@vue/compiler-sfc": {
-            "version": "3.5.0",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.0.tgz",
-            "integrity": "sha512-B9DgLtrqok2GLuaFjLlSL15ZG3ZDBiitUH1ecex9guh/ZcA5MCdwuVE6nsfQxktuZY/QY0awJ35/ripIviCQTQ==",
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.1.tgz",
+            "integrity": "sha512-DFizMNH8eDglLhlfwJ0+ciBsztaYe3fY/zcZjrqL1ljXvUw/UpC84M1d7HpBTCW68SNqZyIxrs1XWmf+73Y65w==",
             "dependencies": {
                 "@babel/parser": "^7.25.3",
-                "@vue/compiler-core": "3.5.0",
-                "@vue/compiler-dom": "3.5.0",
-                "@vue/compiler-ssr": "3.5.0",
-                "@vue/shared": "3.5.0",
+                "@vue/compiler-core": "3.5.1",
+                "@vue/compiler-dom": "3.5.1",
+                "@vue/compiler-ssr": "3.5.1",
+                "@vue/shared": "3.5.1",
                 "estree-walker": "^2.0.2",
                 "magic-string": "^0.30.11",
                 "postcss": "^8.4.44",
@@ -10310,18 +10327,18 @@
             }
         },
         "node_modules/@vue/compiler-ssr": {
-            "version": "3.5.0",
-            "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.0.tgz",
-            "integrity": "sha512-E263QZmA1dqRd7c3u/sWTLRMpQOT0aZ8av/L9SoD/v/BVMZaWFHPUUBswS+bzrfvG2suJF8vSLKx6k6ba5SUdA==",
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.1.tgz",
+            "integrity": "sha512-C1hpSHQgRM8bg+5XWWD7CkFaVpSn9wZHCLRd10AmxqrH17d4EMP6+XcZpwBOM7H1jeStU5naEapZZWX0kso1tQ==",
             "dependencies": {
-                "@vue/compiler-dom": "3.5.0",
-                "@vue/shared": "3.5.0"
+                "@vue/compiler-dom": "3.5.1",
+                "@vue/shared": "3.5.1"
             }
         },
         "node_modules/@vue/shared": {
-            "version": "3.5.0",
-            "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.0.tgz",
-            "integrity": "sha512-m9IgiteBpCkFaMNwCOBkFksA7z8QiKc30ooRuoXWUFRDu0mGyNPlFHmbncF0/Kra1RlX8QrmBbRaIxVvikaR0Q=="
+            "version": "3.5.1",
+            "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.1.tgz",
+            "integrity": "sha512-NdcTRoO4KuW2RSFgpE2c+E/R/ZHaRzWPxAGxhmxZaaqLh6nYCXx7lc9a88ioqOCxCaV2SFJmujkxbUScW7dNsQ=="
         },
         "node_modules/@webassemblyjs/ast": {
             "version": "1.12.1",
@@ -19038,6 +19055,19 @@
                 "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
             }
         },
+        "node_modules/fullcalendar": {
+            "version": "6.1.15",
+            "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.15.tgz",
+            "integrity": "sha512-CFnh1yswjRh9puJVDk8VGwTlyZ6eXxr4qLI7QCA0+bozyAm+BluP1US5mOtgk0gEq23nQxGSNDoBvAraz++saQ==",
+            "dependencies": {
+                "@fullcalendar/core": "~6.1.15",
+                "@fullcalendar/daygrid": "~6.1.15",
+                "@fullcalendar/interaction": "~6.1.15",
+                "@fullcalendar/list": "~6.1.15",
+                "@fullcalendar/multimonth": "~6.1.15",
+                "@fullcalendar/timegrid": "~6.1.15"
+            }
+        },
         "node_modules/function-bind": {
             "version": "1.1.2",
             "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -21724,37 +21754,37 @@
             }
         },
         "node_modules/jimp": {
-            "version": "1.2.0",
-            "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.2.0.tgz",
-            "integrity": "sha512-oNqOurG39OYE50TqFoYoHhMbZ8eUiTOtQ2MFPzfdXLUzjNsB7qJUR1ZJRGep4JfFhjf2G0HJiYoqDR2XkXHR/A==",
-            "dependencies": {
-                "@jimp/core": "1.2.0",
-                "@jimp/diff": "1.2.0",
-                "@jimp/js-bmp": "1.2.0",
-                "@jimp/js-gif": "1.2.0",
-                "@jimp/js-jpeg": "1.2.0",
-                "@jimp/js-png": "1.2.0",
-                "@jimp/js-tiff": "1.2.0",
-                "@jimp/plugin-blit": "1.2.0",
-                "@jimp/plugin-blur": "1.2.0",
-                "@jimp/plugin-circle": "1.2.0",
-                "@jimp/plugin-color": "1.2.0",
-                "@jimp/plugin-contain": "1.2.0",
-                "@jimp/plugin-cover": "1.2.0",
-                "@jimp/plugin-crop": "1.2.0",
-                "@jimp/plugin-displace": "1.2.0",
-                "@jimp/plugin-dither": "1.2.0",
-                "@jimp/plugin-fisheye": "1.2.0",
-                "@jimp/plugin-flip": "1.2.0",
-                "@jimp/plugin-hash": "1.2.0",
-                "@jimp/plugin-mask": "1.2.0",
-                "@jimp/plugin-print": "1.2.0",
-                "@jimp/plugin-quantize": "1.2.0",
-                "@jimp/plugin-resize": "1.2.0",
-                "@jimp/plugin-rotate": "1.2.0",
-                "@jimp/plugin-threshold": "1.2.0",
-                "@jimp/types": "1.2.0",
-                "@jimp/utils": "1.2.0"
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.3.0.tgz",
+            "integrity": "sha512-eJnVMuqDQ545taNLp13gVZynnoOvE0xZ2Oti9alkld47dNhmFHBmFTBMTYaZr7zceGTf54RGdr7C4d2WUNwc0g==",
+            "dependencies": {
+                "@jimp/core": "1.3.0",
+                "@jimp/diff": "1.3.0",
+                "@jimp/js-bmp": "1.3.0",
+                "@jimp/js-gif": "1.3.0",
+                "@jimp/js-jpeg": "1.3.0",
+                "@jimp/js-png": "1.3.0",
+                "@jimp/js-tiff": "1.3.0",
+                "@jimp/plugin-blit": "1.3.0",
+                "@jimp/plugin-blur": "1.3.0",
+                "@jimp/plugin-circle": "1.3.0",
+                "@jimp/plugin-color": "1.3.0",
+                "@jimp/plugin-contain": "1.3.0",
+                "@jimp/plugin-cover": "1.3.0",
+                "@jimp/plugin-crop": "1.3.0",
+                "@jimp/plugin-displace": "1.3.0",
+                "@jimp/plugin-dither": "1.3.0",
+                "@jimp/plugin-fisheye": "1.3.0",
+                "@jimp/plugin-flip": "1.3.0",
+                "@jimp/plugin-hash": "1.3.0",
+                "@jimp/plugin-mask": "1.3.0",
+                "@jimp/plugin-print": "1.3.0",
+                "@jimp/plugin-quantize": "1.3.0",
+                "@jimp/plugin-resize": "1.3.0",
+                "@jimp/plugin-rotate": "1.3.0",
+                "@jimp/plugin-threshold": "1.3.0",
+                "@jimp/types": "1.3.0",
+                "@jimp/utils": "1.3.0"
             },
             "engines": {
                 "node": ">=18"
@@ -27163,13 +27193,13 @@
             }
         },
         "node_modules/openai": {
-            "version": "4.57.1",
-            "resolved": "https://registry.npmjs.org/openai/-/openai-4.57.1.tgz",
-            "integrity": "sha512-7q+4U9A/klaAT40bqL6sPFhIKb4jsUJ8udddCzaf8mdwICYeBG7grps/zDcrOUfkwCxCzR6fxfDDah3WqHoVUA==",
+            "version": "4.57.3",
+            "resolved": "https://registry.npmjs.org/openai/-/openai-4.57.3.tgz",
+            "integrity": "sha512-mTz5/SmulkkeSpqbSr6WNLRU6krkyhnbfRUC8XfaXbj1T6xUorKEELjZvbRSzI714JLOk1MeFkqYS9H4WHhqDQ==",
             "dependencies": {
                 "@types/node": "^18.11.18",
                 "@types/node-fetch": "^2.6.4",
-                "@types/qs": "^6.9.7",
+                "@types/qs": "^6.9.15",
                 "abort-controller": "^3.0.0",
                 "agentkeepalive": "^4.2.1",
                 "form-data-encoder": "1.7.2",
@@ -27190,9 +27220,9 @@
             }
         },
         "node_modules/openai/node_modules/@types/node": {
-            "version": "18.19.49",
-            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.49.tgz",
-            "integrity": "sha512-ALCeIR6n0nQ7j0FUF1ycOhrp6+XutJWqEu/vtdEqXFUQwkBfgUA5cEg3ZNmjWGF/ZYA/FcF9QMkL55Ar0O6UrA==",
+            "version": "18.19.50",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz",
+            "integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==",
             "dependencies": {
                 "undici-types": "~5.26.4"
             }
@@ -28870,9 +28900,9 @@
             }
         },
         "node_modules/react-intersection-observer": {
-            "version": "9.13.0",
-            "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.0.tgz",
-            "integrity": "sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==",
+            "version": "9.13.1",
+            "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.1.tgz",
+            "integrity": "sha512-tSzDaTy0qwNPLJHg8XZhlyHTgGW6drFKTtvjdL+p6um12rcnp8Z5XstE+QNBJ7c64n5o0Lj4ilUleA41bmDoMw==",
             "peerDependencies": {
                 "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0",
                 "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
diff --git a/package.json b/package.json
index 6254b443a..05992ebc3 100644
--- a/package.json
+++ b/package.json
@@ -101,7 +101,7 @@
         "@fortawesome/free-regular-svg-icons": "^6.5.1",
         "@fortawesome/free-solid-svg-icons": "^6.5.1",
         "@fortawesome/react-fontawesome": "^0.2.0",
-        "@fullcalendar/core": "^6.1.10",
+        "@fullcalendar/core": "^6.1.15",
         "@fullcalendar/daygrid": "^6.1.10",
         "@fullcalendar/multimonth": "^6.1.10",
         "@fullcalendar/timegrid": "^6.1.15",
@@ -194,6 +194,7 @@
         "fork-ts-checker-webpack-plugin": "^9.0.2",
         "form-data": "^4.0.0",
         "formidable": "3.5.1",
+        "fullcalendar": "^6.1.15",
         "function-plot": "^1.23.3",
         "golden-layout": "^2.6.0",
         "google-auth-library": "^9.4.1",
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index d149c2eae..01eda7e98 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -648,24 +648,14 @@ export function DivWidth(ele: HTMLElement | null): number {
 }
 
 export function dateRangeStrToDates(dateStr: string) {
+    const toDate = (str: string) => {
+        return !str.includes('T') && str.includes('-') ? new Date(Number(str.split('-')[0]), Number(str.split('-')[1]) - 1, Number(str.split('-')[2])) : new Date(str);
+    };
     // dateStr in yyyy-mm-dd format
     const dateRangeParts = dateStr.split('|'); // splits into from and to date
-    if (dateRangeParts.length < 2) return { startDate: new Date(), endDate: new Date() };
-    return { startDate: new Date(dateRangeParts[0]), endDate: new Date(dateRangeParts[1]) };
-    const fromParts = dateRangeParts[0].split('-');
-    const toParts = dateRangeParts[1].split('-');
-
-    const fromYear = parseInt(fromParts[0]);
-    const fromMonth = parseInt(fromParts[1]) - 1;
-    const fromDay = parseInt(fromParts[2]?.split('T')[0]);
-    const fromHour = parseInt(fromParts[2]?.split('T')[1]?.split(':')[0] || '12');
-
-    const toYear = parseInt(toParts[0]);
-    const toMonth = parseInt(toParts[1]) - 1;
-    const toDay = parseInt(toParts[2]?.split('T')[0]);
-    const toHour = parseInt(fromParts[2]?.split('T')[1]?.split(':')[0] || '12');
-
-    return { startDate: new Date(fromYear, fromMonth, fromDay, fromHour), endDate: new Date(toYear, toMonth, toDay, toHour) };
+    if (dateRangeParts.length < 2 && !dateRangeParts[0]) return { start: new Date(), end: new Date() };
+    if (dateRangeParts.length < 2) return { start: toDate(dateRangeParts[0]), end: toDate(dateRangeParts[0]) };
+    return { start: new Date(dateRangeParts[0]), end: new Date(dateRangeParts[1]) };
 }
 
 function replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) {
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index c1f0b314b..30fd9fc2b 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -38,8 +38,8 @@ export class CollectionCalendarView extends CollectionSubView() {
         const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range);
         const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range);
 
-        const { startDate: aFromDate, endDate: aToDate } = dateRangeStrToDates(aDateRangeStr);
-        const { startDate: bFromDate, endDate: bToDate } = dateRangeStrToDates(bDateRangeStr);
+        const { start: aFromDate, end: aToDate } = dateRangeStrToDates(aDateRangeStr);
+        const { start: bFromDate, end: bToDate } = dateRangeStrToDates(bDateRangeStr);
 
         if (aFromDate > bFromDate) {
             return -1; // a comes first
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.scss b/src/client/views/nodes/calendarBox/CalendarBox.scss
index df6ce3e64..f8ac4b2d1 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.scss
+++ b/src/client/views/nodes/calendarBox/CalendarBox.scss
@@ -2,6 +2,7 @@
     display: flex;
     width: 100%;
     height: 100%;
+    transform-origin: top left;
     .calendarBox-wrapper {
         width: 100%;
         height: 100%;
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 9452cc10a..678b7dd0b 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -1,7 +1,8 @@
-import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core';
+import { Calendar, DateInput, EventClickArg, EventSourceInput } from '@fullcalendar/core';
 import dayGridPlugin from '@fullcalendar/daygrid';
 import multiMonthPlugin from '@fullcalendar/multimonth';
 import timeGrid from '@fullcalendar/timegrid';
+import interactionPlugin from '@fullcalendar/interaction';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -26,6 +27,7 @@ export class CalendarBox extends CollectionSubView() {
     _oldWheel: HTMLElement | null = null;
     _observer: ResizeObserver | undefined;
     _eventsDisposer: IReactionDisposer | undefined;
+    _selectDisposer: IReactionDisposer | undefined;
 
     constructor(props: SubCollectionViewProps) {
         super(props);
@@ -38,18 +40,32 @@ export class CalendarBox extends CollectionSubView() {
     componentDidMount(): void {
         this._props.setContentViewBox?.(this);
         this._eventsDisposer = reaction(
-            () => this.calendarEvents,
-            events => this._calendar?.setOption('events', events),
+            () => ({ events: this.calendarEvents }),
+            ({ events }) => this._calendar?.setOption('events', events),
+            { fireImmediately: true }
+        );
+        this._selectDisposer = reaction(
+            () => ({ initialDate: this.dateSelect }),
+            ({ initialDate }) => {
+                const state = this._calendar?.getCurrentData();
+                state &&
+                    this._calendar?.dispatch({
+                        type: 'CHANGE_DATE',
+                        dateMarker: state.dateEnv.createMarker(initialDate.start),
+                    });
+                setTimeout(() => (initialDate.start.toISOString() !== initialDate.end.toISOString() ? this._calendar?.select(initialDate.start, initialDate.end) : this._calendar?.select(initialDate.start)));
+            },
             { fireImmediately: true }
         );
     }
     componentWillUnmount(): void {
         this._eventsDisposer?.();
+        this._selectDisposer?.();
     }
 
     @computed get calendarEvents(): EventSourceInput | undefined {
         return this.childDocs.map(doc => {
-            const { startDate: start, endDate: end } = dateRangeStrToDates(StrCast(doc.date_range));
+            const { start, end } = dateRangeStrToDates(StrCast(doc.date_range));
             return {
                 title: StrCast(doc.title),
                 start,
@@ -61,6 +77,7 @@ export class CalendarBox extends CollectionSubView() {
                 classNames: ['mother'], // will determine the style
                 editable: true, // subject to change in the future
                 backgroundColor: this.eventToColor(doc),
+                borderColor: this.eventToColor(doc),
                 color: 'white',
                 extendedProps: {
                     description: StrCast(doc.description),
@@ -72,20 +89,23 @@ export class CalendarBox extends CollectionSubView() {
     @computed get dateRangeStrDates() {
         return dateRangeStrToDates(StrCast(this.Document.date_range));
     }
+    get dateSelect() {
+        return dateRangeStrToDates(StrCast(this.Document.date));
+    }
 
     // Choose a calendar view based on the date range
     @computed get calendarViewType(): CalendarView {
         if (this.dataDoc[this.fieldKey + '_calendarType']) return StrCast(this.dataDoc[this.fieldKey + '_calendarType']) as CalendarView;
         if (this.isMultiMonth) return 'multiMonth';
-        const { startDate, endDate } = this.dateRangeStrDates;
-        if (startDate.getFullYear() !== endDate.getFullYear() || startDate.getMonth() !== endDate.getMonth()) return 'multiMonth';
-        if (Math.abs(startDate.getDay() - endDate.getDay()) > 7) return 'dayGridMonth';
+        const { start, end } = this.dateRangeStrDates;
+        if (start.getFullYear() !== end.getFullYear() || start.getMonth() !== end.getMonth()) return 'multiMonth';
+        if (Math.abs(start.getDay() - end.getDay()) > 7) return 'dayGridMonth';
         return 'timeGridWeek';
     }
 
     // TODO: Return a different color based on the event type
     eventToColor(event: Doc): string {
-        return 'blue';
+        return 'red';
     }
 
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) {
@@ -116,32 +136,39 @@ export class CalendarBox extends CollectionSubView() {
         const cal = !this._calendarRef
             ? null
             : (this._calendar = new Calendar(this._calendarRef, {
-                  plugins: [multiMonthPlugin, dayGridPlugin, timeGrid],
+                  plugins: [multiMonthPlugin, dayGridPlugin, timeGrid, interactionPlugin],
                   headerToolbar: {
                       left: 'prev,next today',
                       center: 'title',
                       right: 'multiMonth dayGridMonth timeGridWeek timeGridDay',
                   },
+                  selectable: true,
                   initialView: this.calendarViewType === 'multiMonth' ? undefined : this.calendarViewType,
-                  initialDate: this.dateRangeStrDates.startDate,
+                  initialDate: this.dateSelect.start,
                   navLinks: true,
                   editable: false,
                   displayEventTime: false,
                   displayEventEnd: false,
+                  select: info => {
+                      const start = dateRangeStrToDates(info.startStr).start.toISOString();
+                      const end = dateRangeStrToDates(info.endStr).start.toISOString();
+                      this.dataDoc.date = start + '|' + end;
+                  },
                   aspectRatio: NumCast(this.Document.width) / NumCast(this.Document.height),
                   events: this.calendarEvents,
                   eventClick: this.handleEventClick,
               }));
         cal?.render();
+        setTimeout(() => cal?.view.calendar.select(this.dateSelect.start, this.dateSelect.end));
     };
 
     onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
     render() {
         return (
             <div
-                key={StrCast(this.dataDoc[this.fieldKey + '_calendarType'])}
+                key={this.calendarViewType}
                 className="calendarBox"
-                onPointerDown={e =>
+                onPointerDown={e => {
                     setTimeout(
                         action(() => {
                             const cname = (e.nativeEvent.target as HTMLButtonElement)?.className ?? '';
@@ -150,13 +177,12 @@ export class CalendarBox extends CollectionSubView() {
                             if (cname.includes('timeGridWeek')) this.dataDoc[this.fieldKey + '_calendarType'] = 'timeGridWeek';
                             if (cname.includes('timeGridDay')) this.dataDoc[this.fieldKey + '_calendarType'] = 'timeGridDay';
                         })
-                    )
-                }
+                    );
+                }}
                 style={{
                     width: this._props.PanelWidth() / this._props.ScreenToLocalTransform().Scale,
                     height: this._props.PanelHeight() / this._props.ScreenToLocalTransform().Scale,
                     transform: `scale(${this._props.ScreenToLocalTransform().Scale})`,
-                    transformOrigin: 'top left',
                 }}
                 ref={r => {
                     this.createDashEventsTarget(r);
-- 
cgit v1.2.3-70-g09d2

