diff options
Diffstat (limited to 'src/client/views/nodes/calendarBox/CalendarBox.tsx')
| -rw-r--r-- | src/client/views/nodes/calendarBox/CalendarBox.tsx | 185 |
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> ); } |
