aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/calendarBox/CalendarBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/calendarBox/CalendarBox.tsx')
-rw-r--r--src/client/views/nodes/calendarBox/CalendarBox.tsx185
1 files changed, 102 insertions, 83 deletions
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx
index 009eb82cd..504dc2559 100644
--- a/src/client/views/nodes/calendarBox/CalendarBox.tsx
+++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx
@@ -1,43 +1,43 @@
import { Calendar, EventClickArg, EventDropArg, EventSourceInput } from '@fullcalendar/core';
import dayGridPlugin from '@fullcalendar/daygrid';
+import interactionPlugin from '@fullcalendar/interaction';
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 FullCalendar from '@fullcalendar/react';
+import { IReactionDisposer, action, computed, makeObservable, observable, reaction, untracked } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { dateRangeStrToDates } from '../../../../ClientUtils';
import { Doc } from '../../../../fields/Doc';
-import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
-import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView';
-import './CalendarBox.scss';
import { Id } from '../../../../fields/FieldSymbols';
+import { BoolCast, StrCast } from '../../../../fields/Types';
import { DocServer } from '../../../DocServer';
-import { DocumentView } from '../DocumentView';
-import { OpenWhere } from '../OpenWhere';
import { DragManager } from '../../../util/DragManager';
-import { DocData } from '../../../../fields/DocSymbols';
+import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView';
import { ContextMenu } from '../../ContextMenu';
+import { DocumentView } from '../DocumentView';
+import { OpenWhere } from '../OpenWhere';
+import './CalendarBox.scss';
+import { undoable } from '../../../util/UndoManager';
type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay';
@observer
export class CalendarBox extends CollectionSubView() {
- _calendarRef: HTMLDivElement | null = null;
+ _calendarRef: FullCalendar | null = null;
_calendar: Calendar | undefined;
- _oldWheel: HTMLElement | null = null;
_observer: ResizeObserver | undefined;
_eventsDisposer: IReactionDisposer | undefined;
_selectDisposer: IReactionDisposer | undefined;
+ _isMultiMonth: boolean | undefined;
+
+ @observable _multiMonth = 0;
constructor(props: SubCollectionViewProps) {
super(props);
makeObservable(this);
}
- @observable _multiMonth = 0;
- isMultiMonth: boolean | undefined;
-
componentDidMount(): void {
this._props.setContentViewBox?.(this);
this._eventsDisposer = reaction(
@@ -54,7 +54,7 @@ export class CalendarBox extends CollectionSubView() {
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)));
+ setTimeout(() => initialDate.start.toISOString() !== initialDate.end.toISOString() && this._calendar?.select(initialDate.start, initialDate.end));
},
{ fireImmediately: true }
);
@@ -97,7 +97,7 @@ export class CalendarBox extends CollectionSubView() {
// 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';
+ if (this._isMultiMonth) return 'multiMonth';
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';
@@ -106,14 +106,15 @@ export class CalendarBox extends CollectionSubView() {
// TODO: Return a different color based on the event type
eventToColor = (event: Doc): string => {
- return 'red';
+ return 'red' + event;
};
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
internalDocDrop = (e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) => {
if (!super.onInternalDrop(e, de)) return false;
de.complete.docDragData?.droppedDocuments.forEach(doc => {
const today = new Date().toISOString();
- if (!doc.date_range) doc[DocData].date_range = `${today}|${today}`;
+ if (!doc.date_range) doc.$date_range = `${today}|${today}`;
});
return true;
};
@@ -123,10 +124,13 @@ export class CalendarBox extends CollectionSubView() {
return false;
};
- handleEventDrop = (arg: EventDropArg) => {
+ handleEventDrop = undoable((arg: EventDropArg) => {
const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? '');
- doc && arg.event.start && (doc.date_range = arg.event.start?.toString() + '|' + (arg.event.end ?? arg.event.start).toString());
- };
+ if (doc && arg.event.start) {
+ doc.$allDay = false;
+ doc.$date_range = arg.event.start?.toString() + '|' + (arg.event.end ?? arg.event.start).toString();
+ }
+ }, 'change event date');
handleEventClick = (arg: EventClickArg) => {
const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? '');
@@ -145,61 +149,88 @@ export class CalendarBox extends CollectionSubView() {
};
// https://fullcalendar.io
- renderCalendar = () => {
- const cal = !this._calendarRef
- ? null
- : (this._calendar = new Calendar(this._calendarRef, {
- 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.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,
- eventDrop: this.handleEventDrop,
- eventDidMount: arg => {
- arg.el.addEventListener('pointerdown', ev => {
- ev.button && ev.stopPropagation();
- });
- if (navigator.userAgent.includes('Macintosh')) {
- arg.el.addEventListener('pointerup', ev => {
- ev.button && ev.stopPropagation();
- ev.button && this.handleEventContextMenu(ev.pageX, ev.pageY, arg.event._def.groupId);
- });
- }
- arg.el.addEventListener('contextmenu', ev => {
- if (!navigator.userAgent.includes('Macintosh')) {
- this.handleEventContextMenu(ev.pageX, ev.pageY, arg.event._def.groupId);
+ @computed get renderCalendar() {
+ const availableWidth = this._props.PanelWidth() / (this._props.DocumentView?.().UIBtnScaling ?? 1);
+ const btn = (text: string, view: string | (() => void), hint: string) => ({ text, hint, click: typeof view === 'string' ? () => this._calendarRef?.getApi().changeView(view) : view });
+ return (
+ <FullCalendar
+ ref={r => (this._calendarRef = r)}
+ customButtons={{
+ nowBtn: btn('Now', () => this._calendarRef?.getApi().gotoDate(new Date()), 'Go to Today'),
+ multiBtn: btn('M+', 'multiMonth', 'Multiple Month View'),
+ monthBtn: btn('M', 'dayGridMonth', 'Month View'),
+ weekBtn: btn('W', 'timeGridWeek', 'Week View'),
+ dayBtn: btn('D', 'timeGridDay', 'Day View'),
+ }}
+ headerToolbar={
+ availableWidth > 450
+ ? {
+ left: 'prev,next nowBtn',
+ center: 'title',
+ right: 'multiBtn monthBtn weekBtn dayBtn',
}
- ev.stopPropagation();
- ev.preventDefault();
- });
- },
- }));
- cal?.render();
- setTimeout(() => cal?.view.calendar.select(this.dateSelect.start, this.dateSelect.end));
- };
+ : availableWidth > 300
+ ? {
+ left: 'prev,next',
+ center: 'title',
+ right: '',
+ }
+ : {
+ left: '',
+ center: 'title',
+ right: '',
+ }
+ }
+ selectable={true}
+ initialView={this.calendarViewType === 'multiMonth' ? undefined : this.calendarViewType}
+ initialDate={untracked(() => this.dateSelect.start)}
+ navLinks={true}
+ editable={false}
+ // expandRows={true}
+ // handleWindowResize={true}
+ displayEventTime={false}
+ displayEventEnd={false}
+ plugins={[multiMonthPlugin, dayGridPlugin, timeGrid, interactionPlugin]}
+ aspectRatio={this._props.PanelWidth() / this._props.PanelHeight()}
+ weekends={false}
+ events={this.calendarEvents}
+ eventClick={this.handleEventClick}
+ eventDrop={this.handleEventDrop}
+ unselectAuto={false}
+ // unselect={() => {}}
+ select={info => {
+ const start = dateRangeStrToDates(info.startStr).start.toISOString();
+ const end = info.allDay ? start : dateRangeStrToDates(info.endStr).start.toISOString();
+ this.dataDoc.date = start + '|' + end;
+ }}
+ // eventContent={() => {
+ // return null;
+ // }}
+ eventDidMount={arg => {
+ arg.el.addEventListener('pointerdown', ev => ev.button && ev.stopPropagation());
+ if (navigator.userAgent.includes('Macintosh')) {
+ arg.el.addEventListener('pointerup', ev => {
+ ev.button && ev.stopPropagation();
+ ev.button && this.handleEventContextMenu(ev.pageX, ev.pageY, arg.event._def.groupId);
+ });
+ }
+ arg.el.addEventListener('contextmenu', ev => {
+ if (!navigator.userAgent.includes('Macintosh')) {
+ this.handleEventContextMenu(ev.pageX, ev.pageY, arg.event._def.groupId);
+ }
+ ev.stopPropagation();
+ ev.preventDefault();
+ });
+ }}
+ />
+ );
+ }
- onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
render() {
return (
<div
key={this.calendarViewType}
- className="calendarBox"
+ className={`calendarBox${this._props.isContentActive() ? '-interactive' : ''}`}
onPointerDown={e => {
setTimeout(
action(() => {
@@ -218,21 +249,9 @@ export class CalendarBox extends CollectionSubView() {
}}
ref={r => {
this.createDashEventsTarget(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();
- }
+ this.fixWheelEvents(r, this._props.isContentActive);
}}>
- <div className="calendarBox-wrapper" ref={r => (this._calendarRef = r)} />
+ {this.renderCalendar}
</div>
);
}