diff options
Diffstat (limited to 'src/client/views/nodes/calendarBox/CalendarBox.tsx')
-rw-r--r-- | src/client/views/nodes/calendarBox/CalendarBox.tsx | 208 |
1 files changed, 113 insertions, 95 deletions
diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index df505eb16..40064ad4d 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -4,13 +4,14 @@ import dayGridPlugin from '@fullcalendar/daygrid'; import interactionPlugin from '@fullcalendar/interaction'; import multiMonthPlugin from '@fullcalendar/multimonth'; import timeGrid from '@fullcalendar/timegrid'; -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 { Id } from '../../../../fields/FieldSymbols'; -import { BoolCast, NumCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, StrCast } from '../../../../fields/Types'; import { DocServer } from '../../../DocServer'; import { DragManager } from '../../../util/DragManager'; import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView'; @@ -19,25 +20,26 @@ import { DocumentView } from '../DocumentView'; import { OpenWhere } from '../OpenWhere'; import './CalendarBox.scss'; import { DateField } from '../../../../fields/DateField'; +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; _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 +56,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 } ); @@ -98,7 +100,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'; @@ -129,7 +131,7 @@ export class CalendarBox extends CollectionSubView() { return false; }; - handleEventDrop = (arg: EventDropArg | EventResizeDoneArg) => { + handleEventDrop = undoable((arg: EventDropArg | EventResizeDoneArg ) => { 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) return; @@ -153,7 +155,7 @@ export class CalendarBox extends CollectionSubView() { doc.$startTime = new DateField(startDate); doc.$endTime = new DateField(endDate); } - }; + }, 'change event date'); handleEventClick = (arg: EventClickArg) => { const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? ''); @@ -172,89 +174,114 @@ 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, - eventResize: this.handleEventDrop, - eventDidMount: arg => { - const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? ''); - if (!doc) return; - - if (doc.type === 'task') { - const checkButton = document.createElement('button'); - checkButton.innerText = doc.$completed ? '✅' : '⬜'; - checkButton.style.position = 'absolute'; - checkButton.style.right = '5px'; - checkButton.style.top = '50%'; - checkButton.style.transform = 'translateY(-50%)'; - checkButton.style.background = 'transparent'; - checkButton.style.border = 'none'; - checkButton.style.cursor = 'pointer'; - checkButton.style.fontSize = '18px'; - checkButton.style.zIndex = '1000'; - checkButton.style.padding = '0'; - checkButton.style.margin = '0'; + @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:any) => (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', + } + : 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:any) => { + 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:any) => { const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? ''); + if (!doc) return; - checkButton.onclick = ev => { - ev.stopPropagation(); - doc.$completed = !doc.$completed; - this._calendar?.refetchEvents(); - }; + if (doc.type === 'task') { + const checkButton = document.createElement('button'); + checkButton.innerText = doc.$completed ? '✅' : '⬜'; + checkButton.style.position = 'absolute'; + checkButton.style.right = '5px'; + checkButton.style.top = '50%'; + checkButton.style.transform = 'translateY(-50%)'; + checkButton.style.background = 'transparent'; + checkButton.style.border = 'none'; + checkButton.style.cursor = 'pointer'; + checkButton.style.fontSize = '18px'; + checkButton.style.zIndex = '1000'; + checkButton.style.padding = '0'; + checkButton.style.margin = '0'; - arg.el.style.position = 'relative'; - arg.el.appendChild(checkButton); - } + checkButton.onclick = ev => { + ev.stopPropagation(); + doc.$completed = !doc.$completed; + this._calendar?.refetchEvents(); + }; - 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(); - }); - }, - })); - cal?.render(); - setTimeout(() => cal?.view.calendar.select(this.dateSelect.start, this.dateSelect.end)); - }; + arg.el.style.position = 'relative'; + arg.el.appendChild(checkButton); + } + arg.el.addEventListener('pointerdown', (ev:any) => ev.button && ev.stopPropagation()); + if (navigator.userAgent.includes('Macintosh')) { + arg.el.addEventListener('pointerup', (ev:any) => { + ev.button && ev.stopPropagation(); + ev.button && this.handleEventContextMenu(ev.pageX, ev.pageY, arg.event._def.groupId); + }); + } + arg.el.addEventListener('contextmenu', (ev:any) => { + if (!navigator.userAgent.includes('Macintosh')) { + this.handleEventContextMenu(ev.pageX, ev.pageY, arg.event._def.groupId); + } + ev.stopPropagation(); + ev.preventDefault(); + }); + }} + /> + ); + } render() { return ( <div key={this.calendarViewType} - className="calendarBox" + className={`calendarBox${this._props.isContentActive() ? '-interactive' : ''}`} onPointerDown={e => { setTimeout( action(() => { @@ -274,17 +301,8 @@ export class CalendarBox extends CollectionSubView() { ref={r => { this.createDashEventsTarget(r); this.fixWheelEvents(r, this._props.isContentActive); - - 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)} /> + {this.renderCalendar} </div> ); } |