diff options
| author | bobzel <zzzman@gmail.com> | 2024-09-11 10:37:03 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2024-09-11 10:37:03 -0400 |
| commit | 1ed378a79d545516a42121819d7049c20aecbc8c (patch) | |
| tree | b1f112b03b1dbf0c3b23ec7a019c7da06c1aed3f /src/client/views/nodes | |
| parent | 62eda6aa6af765526fd065dc271b9d53e2b28002 (diff) | |
| parent | d3817d1c03b68ab2c69ab5ccfb5e8d6943df8f25 (diff) | |
Merge branch 'master' into zach-starter
Diffstat (limited to 'src/client/views/nodes')
| -rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 7 | ||||
| -rw-r--r-- | src/client/views/nodes/FieldView.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/FontIconBox/FontIconBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 14 | ||||
| -rw-r--r-- | src/client/views/nodes/KeyValueBox.tsx | 2 | ||||
| -rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 4 | ||||
| -rw-r--r-- | src/client/views/nodes/calendarBox/CalendarBox.scss | 25 | ||||
| -rw-r--r-- | src/client/views/nodes/calendarBox/CalendarBox.tsx | 258 | ||||
| -rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 |
9 files changed, 214 insertions, 102 deletions
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 1eae163df..39a2e3a31 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -3,7 +3,7 @@ import { Tooltip } from '@mui/material'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { returnFalse, returnNone, setupMoveUpEvents } from '../../../ClientUtils'; +import { returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; @@ -303,6 +303,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() <DocumentView // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} + fitWidth={undefined} + NativeHeight={returnZero} + NativeWidth={returnZero} ignoreUsePath={layoutString ? true : undefined} renderDepth={this.props.renderDepth + 1} LayoutTemplateString={layoutString} @@ -310,8 +313,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>() containerViewPath={this.DocumentView?.().docViewPath} moveDocument={whichSlot.endsWith('1') ? this.moveDoc1 : this.moveDoc2} removeDocument={whichSlot.endsWith('1') ? this.remDoc1 : this.remDoc2} - NativeWidth={this.layoutWidth} - NativeHeight={this.layoutHeight} isContentActive={emptyFunction} isDocumentActive={returnFalse} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index dd71fd946..c269c7bcb 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -82,6 +82,7 @@ export interface FieldViewSharedProps { // eslint-disable-next-line no-use-before-define onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined; fitWidth?: (doc: Doc) => boolean | undefined; + dontCenter?: 'x' | 'y' | 'xy' | undefined; searchFilterDocs: () => Doc[]; showTitle?: () => string; whenChildContentsActiveChanged: (isActive: boolean) => void; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index f2f7f39bb..7a09ad9e2 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -345,7 +345,7 @@ export class FontIconBox extends ViewBoxBaseComponent<ButtonProps>() { @computed get editableText() { const script = ScriptCast(this.Document.script); - const checkResult = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result; + const checkResult = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result as string; const setValue = (value: string) => script?.script.run({ this: this.Document, value, _readOnly_: false }).result as boolean; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d0a7fc6ac..be3525544 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -11,7 +11,7 @@ import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { ObjectField } from '../../../fields/ObjectField'; -import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { Cast, ImageCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; @@ -188,8 +188,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @undoBatch setNativeSize = action(() => { + const oldnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); const nscale = NumCast(this._props.PanelWidth()) * NumCast(this.layoutDoc._freeform_scale, 1); - const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); + const nw = nscale / oldnativeWidth; this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw; this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw; this.dataDoc._freeform_panX = nw * NumCast(this.dataDoc._freeform_panX); @@ -198,6 +199,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this.dataDoc._freeform_panX_min = this.dataDoc._freeform_panX_min ? nw * NumCast(this.dataDoc._freeform_panX_min) : undefined; this.dataDoc._freeform_panY_max = this.dataDoc._freeform_panY_max ? nw * NumCast(this.dataDoc._freeform_panY_max) : undefined; this.dataDoc._freeform_panY_min = this.dataDoc._freeform_panY_min ? nw * NumCast(this.dataDoc._freeform_panY_min) : undefined; + const newnativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); + DocListCast(this.dataDoc[this.annotationKey]).forEach(doc => { + doc.x = (NumCast(doc.x) / oldnativeWidth) * newnativeWidth; + doc.y = (NumCast(doc.y) / oldnativeWidth) * newnativeWidth; + if (!RTFCast(doc[Doc.LayoutFieldKey(doc)])) { + doc.width = (NumCast(doc.width) / oldnativeWidth) * newnativeWidth; + doc.height = (NumCast(doc.height) / oldnativeWidth) * newnativeWidth; + } + }); }); @undoBatch rotate = action(() => { diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 95e344004..3daacc9bb 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -84,7 +84,7 @@ export class KeyValueBox extends ViewBoxBaseComponent<FieldViewProps>() { const onDelegate = rawvalue.startsWith('='); rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue; const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false; - rawvalue = type ? rawvalue.substring(2) : rawvalue; + rawvalue = type ? rawvalue.substring(2) : rawvalue.replace(/^:/, ''); rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, `$1`)'); const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(+rawvalue) ? rawvalue : '`' + rawvalue + '`'; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b17275a1e..cb0b0d71f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -56,10 +56,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { @computed get pdfUrl() { return Cast(this.dataDoc[this._props.fieldKey], PdfField); } - @computed get pdfThumb() { - return ImageCast(this.layoutDoc['thumb-frozen'], ImageCast(this.layoutDoc.thumb))?.url; - } - constructor(props: FieldViewProps) { super(props); makeObservable(this); diff --git a/src/client/views/nodes/calendarBox/CalendarBox.scss b/src/client/views/nodes/calendarBox/CalendarBox.scss new file mode 100644 index 000000000..f8ac4b2d1 --- /dev/null +++ b/src/client/views/nodes/calendarBox/CalendarBox.scss @@ -0,0 +1,25 @@ +.calendarBox { + display: flex; + width: 100%; + height: 100%; + transform-origin: top left; + .calendarBox-wrapper { + width: 100%; + height: 100%; + .fc-timegrid-body { + width: 100% !important; + table { + width: 100% !important; + } + } + .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..678b7dd0b 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -1,127 +1,207 @@ -import { Calendar, EventSourceInput } from '@fullcalendar/core'; +import { Calendar, DateInput, EventClickArg, EventSourceInput } from '@fullcalendar/core'; import dayGridPlugin from '@fullcalendar/daygrid'; import multiMonthPlugin from '@fullcalendar/multimonth'; -import { makeObservable } from 'mobx'; +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'; import { dateRangeStrToDates } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; -import { StrCast } from '../../../../fields/Types'; -import { DocumentType } from '../../../documents/DocumentTypes'; -import { Docs } from '../../../documents/Documents'; -import { ViewBoxBaseComponent } from '../../DocComponent'; -import { FieldView, FieldViewProps } from '../FieldView'; - -type CalendarView = 'month' | 'multi-month' | 'week'; +import { BoolCast, NumCast, StrCast } from '../../../../fields/Types'; +import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView'; +import './CalendarBox.scss'; +import { Id } from '../../../../fields/FieldSymbols'; +import { DocServer } from '../../../DocServer'; +import { DocumentView } from '../DocumentView'; +import { OpenWhere } from '../OpenWhere'; +import { DragManager } from '../../../util/DragManager'; +import { DocData } from '../../../../fields/DocSymbols'; + +type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay'; @observer -export class CalendarBox extends ViewBoxBaseComponent<FieldViewProps>() { - public static LayoutString(fieldKey: string = 'calendar') { - return FieldView.LayoutString(CalendarBox, fieldKey); - } - - constructor(props: FieldViewProps) { +export class CalendarBox extends CollectionSubView() { + _calendarRef: HTMLDivElement | null = null; + _calendar: Calendar | undefined; + _oldWheel: HTMLElement | null = null; + _observer: ResizeObserver | undefined; + _eventsDisposer: IReactionDisposer | undefined; + _selectDisposer: IReactionDisposer | undefined; + + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } - componentDidMount(): void {} - - componentWillUnmount(): void {} - - _calendarRef = React.createRef<HTMLElement>(); + @observable _multiMonth = 0; + isMultiMonth: boolean | undefined; - get dateRangeStr() { - return StrCast(this.Document.date_range); + componentDidMount(): void { + this._props.setContentViewBox?.(this); + this._eventsDisposer = reaction( + () => ({ 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 } + ); } - - // 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'; + componentWillUnmount(): void { + this._eventsDisposer?.(); + this._selectDisposer?.(); } - get calendarStartDate() { - return this.dateRangeStr.split('|')[0]; + @computed get calendarEvents(): EventSourceInput | undefined { + return this.childDocs.map(doc => { + const { start, end } = dateRangeStrToDates(StrCast(doc.date_range)); + return { + title: StrCast(doc.title), + start, + end, + groupId: doc[Id], + startEditable: true, + endEditable: true, + allDay: BoolCast(doc.allDay), + 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), + }, + }; + }); } - get calendarToDate() { - return this.dateRangeStr.split('|')[1]; + @computed get dateRangeStrDates() { + return dateRangeStrToDates(StrCast(this.Document.date_range)); } - - get childDocs(): Doc[] { - return this.childDocs; // get all sub docs for a calendar + get dateSelect() { + return dateRangeStrToDates(StrCast(this.Document.date)); } - docBackgroundColor(type: string): string { - // TODO: Return a different color based on the event type - console.log(type); - return 'blue'; + // 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 { 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'; } - 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) : ''; + // TODO: Return a different color based on the event type + eventToColor(event: Doc): string { + return 'red'; + } - 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, - }, - }; + 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}`; }); + return true; } - handleEventClick = (/* arg: EventClickArg */) => { - // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc. + onInternalDrop = (e: Event, de: DragManager.DropEvent): boolean => { + if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData); + return false; }; - calendarEl: HTMLElement = document.getElementById('calendar-box-v1')!; + handleEventClick = (arg: EventClickArg) => { + const doc = DocServer.GetCachedRefField(arg.event._def.groupId ?? ''); + DocumentView.DeselectAll(); + if (doc) { + DocumentView.showDocument(doc, { openLocation: OpenWhere.lightboxAlways }); + arg.jsEvent.stopPropagation(); + } + }; // 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, 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, + })); + cal?.render(); + setTimeout(() => cal?.view.calendar.select(this.dateSelect.start, this.dateSelect.end)); + }; + onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); render() { return ( - <div className="calendar-box-conatiner"> - <div id="calendar-box-v1" /> + <div + key={this.calendarViewType} + className="calendarBox" + onPointerDown={e => { + setTimeout( + action(() => { + const cname = (e.nativeEvent.target as HTMLButtonElement)?.className ?? ''; + if (cname.includes('multiMonth')) this.dataDoc[this.fieldKey + '_calendarType'] = 'multiMonth'; + if (cname.includes('dayGridMonth')) this.dataDoc[this.fieldKey + '_calendarType'] = 'dayGridMonth'; + 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})`, + }} + 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(); + } + }}> + <div className="calendarBox-wrapper" ref={r => (this._calendarRef = r)} /> </div> ); } } -Docs.Prototypes.TemplateMap.set(DocumentType.CALENDAR, { - layout: { view: CalendarBox, dataField: 'data' }, - options: { acl: '' }, -}); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index a88bd8920..343e255dc 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -2035,7 +2035,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB style={{ cursor: this._props.isContentActive() ? 'text' : undefined, height: this._props.height ? 'max-content' : undefined, - overflow: this.layout_autoHeight ? 'hidden' : undefined, pointerEvents: Doc.ActiveTool === InkTool.None && !SnappingManager.ExploreMode ? undefined : 'none', }} onContextMenu={this.specificContextMenu} @@ -2054,7 +2053,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB }} style={{ width: this.noSidebar ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`, - overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined, + overflow: this.layoutDoc._createDocOnCR || this.layoutDoc._layout_hideScroll ? 'hidden' : this.layout_autoHeight ? 'visible' : undefined, }} onScroll={this.onScroll} onDrop={this.ondrop}> |
