aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorzaultavangar <zaul_tavangar@brown.edu>2023-12-17 20:13:21 -0500
committerzaultavangar <zaul_tavangar@brown.edu>2023-12-17 20:13:21 -0500
commit6e78d5d0bf88d25db48a82e498fe0193dc9baedf (patch)
treed8ae61ade77d5902109fa20d3c8251172357d2a4
parent84f7c1d590a4137dfd9de8c11640f1177b390a08 (diff)
developing CalendarBox and CollectionCalendarView
-rw-r--r--package-lock.json39
-rw-r--r--package.json3
-rw-r--r--src/Utils.ts21
-rw-r--r--src/client/util/CalendarManager.tsx3
-rw-r--r--src/client/views/collections/CollectionCalendarView.tsx103
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx110
6 files changed, 273 insertions, 6 deletions
diff --git a/package-lock.json b/package-lock.json
index 2d2740381..ed66d83a6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -22,6 +22,9 @@
"@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/daygrid": "^6.1.10",
+ "@fullcalendar/multimonth": "^6.1.10",
"@internationalized/date": "^3.5.0",
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
"@mui/icons-material": "^5.14.19",
@@ -2653,6 +2656,33 @@
"react": ">=16.3"
}
},
+ "node_modules/@fullcalendar/core": {
+ "version": "6.1.10",
+ "resolved": "https://registry.npmjs.org/@fullcalendar/core/-/core-6.1.10.tgz",
+ "integrity": "sha512-oTXGJSAGpCf1oY+CKp5qYjMHkJCPBkJ3SHitl63n8Q6xKeiwQ4EF6Au451euUovREwJpLmD1AyZrCnWmtB9AVg==",
+ "dependencies": {
+ "preact": "~10.12.1"
+ }
+ },
+ "node_modules/@fullcalendar/daygrid": {
+ "version": "6.1.10",
+ "resolved": "https://registry.npmjs.org/@fullcalendar/daygrid/-/daygrid-6.1.10.tgz",
+ "integrity": "sha512-Z4GRm1IyHKgxXFTWGcEI0nTsvYOIkpE0aMt3/o3ER2SZkF+hfwcDFhtj0c9+WhMjXFIWYeoTnA9rUOY7Zl/nxA==",
+ "peerDependencies": {
+ "@fullcalendar/core": "~6.1.10"
+ }
+ },
+ "node_modules/@fullcalendar/multimonth": {
+ "version": "6.1.10",
+ "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.10.tgz",
+ "integrity": "sha512-mLGEgD+sv8rNfKphyOCWRQapX7/nAvHgQmDTjuy4STlhHsddCYVmFlsSgF373ph1tXh41wAJUVdNO/pDmCvUfA==",
+ "dependencies": {
+ "@fullcalendar/daygrid": "~6.1.10"
+ },
+ "peerDependencies": {
+ "@fullcalendar/core": "~6.1.10"
+ }
+ },
"node_modules/@googlemaps/js-api-loader": {
"version": "1.16.2",
"resolved": "https://registry.npmjs.org/@googlemaps/js-api-loader/-/js-api-loader-1.16.2.tgz",
@@ -29048,6 +29078,15 @@
"resolved": "https://registry.npmjs.org/potpack/-/potpack-2.0.0.tgz",
"integrity": "sha512-Q+/tYsFU9r7xoOJ+y/ZTtdVQwTWfzjbiXBDMM/JKUux3+QPP02iUuIoeBQ+Ot6oEDlC+/PGjB/5A3K7KKb7hcw=="
},
+ "node_modules/preact": {
+ "version": "10.12.1",
+ "resolved": "https://registry.npmjs.org/preact/-/preact-10.12.1.tgz",
+ "integrity": "sha512-l8386ixSsBdbreOAkqtrwqHwdvR35ID8c3rKPa8lCWuO86dBi32QWHV4vfsZK1utLLFMvw+Z5Ad4XLkZzchscg==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/preact"
+ }
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
diff --git a/package.json b/package.json
index 098e30ffb..6f0911a2d 100644
--- a/package.json
+++ b/package.json
@@ -106,6 +106,9 @@
"@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/daygrid": "^6.1.10",
+ "@fullcalendar/multimonth": "^6.1.10",
"@internationalized/date": "^3.5.0",
"@mapbox/mapbox-gl-geocoder": "^5.0.2",
"@mui/icons-material": "^5.14.19",
diff --git a/src/Utils.ts b/src/Utils.ts
index 5f9475f23..a060e4a2c 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -895,3 +895,24 @@ export function setupMoveUpEvents(
document.addEventListener('pointerup', _upEvent, true);
document.addEventListener('click', _clickEvent, true);
}
+
+export function dateRangeStrToDates (dateStr: string) {
+ // dateStr in yyyy-mm-dd format
+ const dateRangeParts = dateStr.split("|"); // splits into from and to date
+ 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]);
+
+ const toYear = parseInt(toParts[0]);
+ const toMonth = parseInt(toParts[1])-1;
+ const toDay = parseInt(toParts[2]);
+
+
+ return [
+ new Date(fromYear, fromMonth, fromDay),
+ new Date(toYear, toMonth, toDay)
+ ];
+} \ No newline at end of file
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index d5d4203b1..c3e54ebd3 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -125,6 +125,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
@action
handleCalendarTitleChange = (event: React.ChangeEvent<HTMLTextAreaElement | HTMLInputElement>) => {
+ console.log("Existing calendars: ", this.existingCalendars);
this.calendarName = event.target.value;
};
@@ -175,7 +176,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
const subDocEmbedding = Doc.MakeEmbedding(targetDoc); // embedding
console.log("subdoc embedding", subDocEmbedding);
subDocEmbedding.embedContainer = calendar; // set embed container
- subDocEmbedding.date_range = `${startDateStr}-${endDateStr}`; // set subDoc date range
+ subDocEmbedding.date_range = `${startDateStr}|${endDateStr}`; // set subDoc date range
Doc.AddDocToList(calendar, 'data', subDocEmbedding); // add embedded subDoc to calendar
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx
index 99dc09732..f94d05c61 100644
--- a/src/client/views/collections/CollectionCalendarView.tsx
+++ b/src/client/views/collections/CollectionCalendarView.tsx
@@ -1,8 +1,18 @@
import * as React from 'react';
import { CollectionSubView } from "./CollectionSubView";
import { observer } from 'mobx-react';
-import { makeObservable, observable } from 'mobx';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { computed, makeObservable, observable } from 'mobx';
+import { Doc, DocListCast, Opt } from '../../../fields/Doc';
+import { CollectionStackingView } from './CollectionStackingView';
+import { CollectionViewType } from '../../documents/DocumentTypes';
+import { dateRangeStrToDates, emptyFunction, returnAll, returnEmptyDoclist, returnNone, returnOne, returnTrue } from '../../../Utils';
+import { DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { TbRuler } from 'react-icons/tb';
+import { Transform } from '../../util/Transform';
+import { DocData } from '../../../fields/DocSymbols';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
+import { StyleProp } from '../StyleProvider';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
@observer
export class CollectionCalendarView extends CollectionSubView(){
@@ -20,13 +30,98 @@ export class CollectionCalendarView extends CollectionSubView(){
}
- @observable private existingCalendars: Doc[] = DocListCast(Doc.MyCalendars?.data);
+ @computed get allCalendars(){
+ return this.childDocs; // returns a list of docs (i.e. calendars)
+ }
+
+ removeCalendar = () => {
+
+ }
+
+ addCalendar = (doc: Doc | Doc[], annotationKey?: string | undefined): boolean => {
+ // bring up calendar modal with option to create a calendar
+ return true;
+ }
+
+ _stackRef = React.createRef<CollectionStackingView>();
+
+ panelHeight = () => {
+ return 0; // a standard height for all calendars; TODO: change
+ }
+
+ panelWidth = () => {
+ return 0; //a standard width for all calendars; TODO: change
+ }
+
+ // most recent calendar should come first
+ sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => {
+ const aDateRangeStr = StrCast(calendarA.date_range);
+ const bDateRangeStr = StrCast(calendarB.date_range);
+
+ const [aFromDate, aToDate] = dateRangeStrToDates(aDateRangeStr);
+ const [bFromDate, bToDate] = dateRangeStrToDates(bDateRangeStr);
+
+ if (aFromDate > bFromDate) {
+ return -1; // a comes first
+ } else if (aFromDate < bFromDate) {
+ return 1; // b comes first
+ } else { // start dates are the same
+ if (aToDate > bToDate) {
+ return -1; // a comes first
+ } else if (aToDate < bToDate) {
+ return 1; // b comes first
+ } else {
+ return 0; // same start and end dates
+ }
+ }
+
+
+ }
+
+
+
+ screenToLocalTransform = () =>
+ this._props
+ .ScreenToLocalTransform()
+ .translate(Doc.NativeWidth(this._props.Document), 0)
+ .scale(this._props.NativeDimScaling?.() || 1);
+
+ get calendarsKey() {
+ return this._props.fieldKey + '_calendars';
+ }
render(){
return (
<div>
- Hello
+ <CollectionStackingView
+ {...this._props}
+ setContentView={emptyFunction}
+ // NativeWidth={}
+ // NativeHeight={returnZero}
+ ref={this._stackRef}
+ PanelHeight={this.panelHeight}
+ PanelWidth={this.panelWidth}
+ // childFilters={this.childFilters} DO I NEED THIS?
+ sortFunc={this.sortByMostRecentDate}
+ setHeight={this.setHeightCallback}
+ isAnnotationOverlay={false}
+ // select={emptyFunction} What does this mean?
+ NativeDimScaling={returnOne}
+ isAnyChildContentActive={returnTrue} // ??
+ // childDocumentsActive={}
+ // whenChildContentsActiveChanged={}
+ childHideDecorationTitle={false}
+ removeDocument={this.removeDocument} // will calendar automatically be removed from myCalendars
+ moveDocument={this.moveDocument}
+ addDocument={this.addCalendar}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ renderDepth={this._props.renderDepth + 1}
+ type_collection={CollectionViewType.Stacking}
+ fieldKey={this.calendarsKey}
+ pointerEvents={returnAll}
+ />
</div>
+
)
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 0aa3b4ccc..989feb774 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -5,6 +5,13 @@ import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps, ViewBoxBaseCompon
import { FieldView, FieldViewProps } from '../FieldView';
import { StrCast } from '../../../../fields/Types';
import { makeObservable } from 'mobx';
+import { dateRangeStrToDates } from '../../../../Utils';
+import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core'
+import dayGridPlugin from '@fullcalendar/daygrid'
+import multiMonthPlugin from '@fullcalendar/multimonth'
+import { faListNumeric } from '@fortawesome/free-solid-svg-icons';
+
+type CalendarView = 'month' | 'multi-month' | 'week';
@observer
export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
@@ -12,6 +19,103 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
return FieldView.LayoutString(CalendarBox, fieldKey);
}
+ componentDidMount(): void {
+
+ }
+
+ componentWillUnmount(): void {
+
+ }
+
+ _calendarRef = React.createRef<HTMLElement>()
+
+ get dateRangeStr (){
+ return StrCast(this.Document.date_range);
+ }
+
+ // Choose a calendar view based on the date range
+ get calendarViewType (): CalendarView {
+ const [fromDate, toDate] = dateRangeStrToDates(this.dateRangeStr);
+
+ if (fromDate.getFullYear() !== toDate.getFullYear() || fromDate.getMonth() !== toDate.getMonth()) return 'multi-month';
+
+ if (Math.abs(fromDate.getDay() - toDate.getDay()) > 7) return 'month';
+ return 'week';
+ }
+
+ get calendarStartDate () {
+ return this.dateRangeStr.split("|")[0];
+ }
+
+ get calendarToDate () {
+ return this.dateRangeStr.split("|")[1];
+ }
+
+ get childDocs (): Doc[] {
+ return this.childDocs; // get all sub docs for a calendar
+ }
+
+ docBackgroundColor (type: string): string {
+ // TODO: Return a different color based on the event type
+ return 'blue';
+ }
+
+ get calendarEvents (): EventSourceInput | undefined {
+ if (this.childDocs.length === 0) return undefined;
+ return this.childDocs.map((doc, idx) => {
+ 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
+ }
+ )
+
+ }
+
+
constructor(props: any){
super(props);
makeObservable(this);
@@ -19,7 +123,11 @@ export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>(){
render(){
return (
- <div></div>
+ <div className='calendar-box-conatiner'>
+ <div id='calendar-box-v1'>
+
+ </div>
+ </div>
);
}