From a958577d4c27b276aa37484e3f895e196138b17c Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 1 Sep 2024 12:55:59 -0400 Subject: consolidated image resampling. fixed importing zipped workspace. --- src/client/views/nodes/PDFBox.tsx | 4 ---- 1 file changed, 4 deletions(-) (limited to 'src/client/views/nodes') 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() { @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); -- cgit v1.2.3-70-g09d2 From 0b77229000231869695a6211e216d5b1755f53f7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 4 Sep 2024 13:05:17 -0400 Subject: made calendarBox work --- src/ClientUtils.ts | 3 +- .../views/collections/CollectionCalendarView.tsx | 4 +- .../views/collections/CollectionCardDeckView.scss | 18 +-- .../views/nodes/calendarBox/CalendarBox.scss | 18 +++ src/client/views/nodes/calendarBox/CalendarBox.tsx | 172 ++++++++++++--------- 5 files changed, 131 insertions(+), 84 deletions(-) create mode 100644 src/client/views/nodes/calendarBox/CalendarBox.scss (limited to 'src/client/views/nodes') diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index 55801df81..51ad55c07 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -650,6 +650,7 @@ export function DivWidth(ele: HTMLElement | null): number { export function dateRangeStrToDates(dateStr: string) { // dateStr in yyyy-mm-dd format const dateRangeParts = dateStr.split('|'); // splits into from and to date + if (dateRangeParts.length < 2) return { startDate: new Date(), endDate: new Date() }; const fromParts = dateRangeParts[0].split('-'); const toParts = dateRangeParts[1].split('-'); @@ -661,7 +662,7 @@ export function dateRangeStrToDates(dateStr: string) { const toMonth = parseInt(toParts[1]) - 1; const toDay = parseInt(toParts[2]); - return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)]; + return { startDate: new Date(fromYear, fromMonth, fromDay), endDate: new Date(toYear, toMonth, toDay) }; } function replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) { diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx index 9eb16917b..c1f0b314b 100644 --- a/src/client/views/collections/CollectionCalendarView.tsx +++ b/src/client/views/collections/CollectionCalendarView.tsx @@ -38,8 +38,8 @@ export class CollectionCalendarView extends CollectionSubView() { const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range); const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range); - const [aFromDate, aToDate] = dateRangeStrToDates(aDateRangeStr); - const [bFromDate, bToDate] = dateRangeStrToDates(bDateRangeStr); + const { startDate: aFromDate, endDate: aToDate } = dateRangeStrToDates(aDateRangeStr); + const { startDate: bFromDate, endDate: bToDate } = dateRangeStrToDates(bDateRangeStr); if (aFromDate > bFromDate) { return -1; // a comes first diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index a089b248d..cc797d0bd 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -6,6 +6,15 @@ position: relative; background-color: white; overflow: hidden; + + button { + width: 35px; + height: 35px; + border-radius: 50%; + background-color: $dark-gray; + // border-color: $medium-blue; + margin: 5px; // transform: translateY(-50px); + } } .card-wrapper { @@ -34,15 +43,6 @@ justify-content: start; /* Centers buttons horizontally */ } -button { - width: 35px; - height: 35px; - border-radius: 50%; - background-color: $dark-gray; - // border-color: $medium-blue; - margin: 5px; // transform: translateY(-50px); -} - // button:hover { // transform: translateY(-50px); // } diff --git a/src/client/views/nodes/calendarBox/CalendarBox.scss b/src/client/views/nodes/calendarBox/CalendarBox.scss new file mode 100644 index 000000000..8eee47e42 --- /dev/null +++ b/src/client/views/nodes/calendarBox/CalendarBox.scss @@ -0,0 +1,18 @@ +.calendarBox { + display: flex; + width: 100%; + height: 100%; + .calendarBox-wrapper { + width: 100%; + height: 100%; + .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..ca35d85b3 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -1,16 +1,17 @@ import { Calendar, EventSourceInput } from '@fullcalendar/core'; import dayGridPlugin from '@fullcalendar/daygrid'; import multiMonthPlugin from '@fullcalendar/multimonth'; -import { makeObservable } from 'mobx'; +import { 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 { Doc, DocListCast } from '../../../../fields/Doc'; +import { NumCast, StrCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; +import './CalendarBox.scss'; type CalendarView = 'month' | 'multi-month' | 'week'; @@ -20,108 +21,135 @@ export class CalendarBox extends ViewBoxBaseComponent() { return FieldView.LayoutString(CalendarBox, fieldKey); } + _calendarRef: HTMLDivElement | null = null; + _calendar: Calendar | undefined; + _oldWheel: HTMLElement | null = null; + _observer: ResizeObserver | undefined; + constructor(props: FieldViewProps) { super(props); makeObservable(this); } - componentDidMount(): void {} + @observable _multiMonth = 0; - componentWillUnmount(): void {} + componentDidMount(): void { + reaction( + () => this.calendarEvents, + events => { + this._calendar?.setOption('events', events); + }, + { fireImmediately: true } + ); + } - _calendarRef = React.createRef(); + @computed get calendarEvents(): EventSourceInput | undefined { + return this.childDocs.map(doc => { + const { startDate: start, endDate: end } = dateRangeStrToDates(StrCast(doc.date_range)); + + return { + title: StrCast(doc.title), + start, + end, + startEditable: true, + endEditable: true, + allDay: false, + classNames: ['mother'], // will determine the style + editable: true, // subject to change in the future + backgroundColor: this.eventToColor(doc), + color: 'white', + extendedProps: { + description: StrCast(doc.description), + }, + }; + }); + } - get dateRangeStr() { - return StrCast(this.Document.date_range); + @computed get dateRangeStrDates() { + return dateRangeStrToDates(StrCast(this.Document.date_range)); } // Choose a calendar view based on the date range - get calendarViewType(): CalendarView { - const [fromDate, toDate] = dateRangeStrToDates(this.dateRangeStr); + @computed get calendarViewType(): CalendarView { + const { startDate, endDate } = this.dateRangeStrDates; - if (fromDate.getFullYear() !== toDate.getFullYear() || fromDate.getMonth() !== toDate.getMonth()) return 'multi-month'; + if (startDate.getFullYear() !== endDate.getFullYear() || startDate.getMonth() !== endDate.getMonth()) return 'multi-month'; - if (Math.abs(fromDate.getDay() - toDate.getDay()) > 7) return 'month'; + if (Math.abs(startDate.getDay() - endDate.getDay()) > 7) return 'month'; return 'week'; } - - get calendarStartDate() { - return this.dateRangeStr.split('|')[0]; - } - - get calendarToDate() { - return this.dateRangeStr.split('|')[1]; + @computed get childDocs() { + return DocListCast(this.dataDoc[this.fieldKey]); } - get childDocs(): Doc[] { - return this.childDocs; // get all sub docs for a calendar - } - - docBackgroundColor(type: string): string { + eventToColor(event: Doc): string { // TODO: Return a different color based on the event type - console.log(type); + console.log(event.title); return 'blue'; } - 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) : ''; - - 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, - }); - } + renderCalendar = () => { + const cal = !this._calendarRef + ? null + : (this._calendar = new Calendar(this._calendarRef, { + plugins: [multiMonthPlugin, dayGridPlugin], + headerToolbar: { + left: 'prev,next today', + center: 'title', + right: 'dayGridMonth multiMonth', // timeGridWeek timeGridDay listWeek', + }, + initialDate: this.dateRangeStrDates.startDate, + navLinks: true, + editable: false, + displayEventTime: false, + displayEventEnd: false, + aspectRatio: NumCast(this.Document.width) / NumCast(this.Document.height), + events: this.calendarEvents, + eventClick: this.handleEventClick, + })); + cal?.render(); + }; + onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); render() { + this._multiMonth; return ( -
-
+
+ setTimeout(() => { + if ((e.nativeEvent.target as HTMLButtonElement)?.className?.includes('multiMonth-button')) { + this._multiMonth = this._multiMonth + 1; + } + }) + )} + ref={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._calendarRef = r)} />
); } } Docs.Prototypes.TemplateMap.set(DocumentType.CALENDAR, { layout: { view: CalendarBox, dataField: 'data' }, - options: { acl: '' }, + options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true }, }); -- cgit v1.2.3-70-g09d2 From a5ac17898151df59c748547acbcb0d2ac63d357d Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 4 Sep 2024 13:21:52 -0400 Subject: start of conversion of Calendar into a CollectionView --- src/client/documents/DocumentTypes.ts | 1 - src/client/documents/Documents.ts | 14 +++++++++----- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/DashboardView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 4 +++- src/client/views/nodes/calendarBox/CalendarBox.tsx | 16 +++------------- 6 files changed, 17 insertions(+), 22 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index b055546fc..56d505681 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -39,7 +39,6 @@ export enum DocumentType { COMPARISON = 'comparison', PUSHPIN = 'pushpin', MAPROUTE = 'maproute', - CALENDAR = 'calendar', SCRIPTDB = 'scriptdb', // database of scripts GROUPDB = 'groupdb', // database of groups diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1acc6bb46..d5a7b0465 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -913,7 +913,15 @@ export namespace Docs { } export function CalendarDocument(options: DocumentOptions, documents: Array) { - return InstanceFromProto(Prototypes.get(DocumentType.CALENDAR), new List(documents), { ...options }); + const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { + _layout_nativeDimEditable: true, + _layout_reflowHorizontal: true, + _layout_reflowVertical: true, + ...options, + _type_collection: CollectionViewType.Calendar, + }); + documents.forEach(d => Doc.SetContainer(d, inst)); + return inst; } // shouldn't ever need to create a KVP document-- instead set the LayoutTemplateString to be a KeyValueBox for the DocumentView (see addDocTab in TabDocView) @@ -970,10 +978,6 @@ export namespace Docs { return doc; } - export function CalendarCollectionDocument(documents: Array, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Calendar }); - } - export function StackingDocument(documents: Array, options: DocumentOptions, id?: string, protoId?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Stacking }, id, undefined, protoId); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 14fb65252..f042f33ce 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -787,7 +787,7 @@ pie title Minerals in my tap water CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Card, CollectionViewType.Linear, CollectionViewType.Map, - CollectionViewType.Grid, CollectionViewType.NoteTaking, ]), + CollectionViewType.Calendar, CollectionViewType.Grid, CollectionViewType.NoteTaking, ]), title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: '{ return setView(value, _readOnly_); }'}}, { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}}, { title: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} }, diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 33e905a54..eced64524 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -465,7 +465,7 @@ export class DashboardView extends ObservableReactComponent { isSystem: true, layout_explainer: 'All of the calendars that you have created will appear here.', }; - const myCalendars = DocUtils.AssignScripts(Docs.Create.CalendarCollectionDocument([], reqdOpts)); + const myCalendars = DocUtils.AssignScripts(Docs.Create.StackingDocument([], reqdOpts)); // { treeView_ChildDoubleClick: 'openPresentation(documentView.rootDoc)' } return new PrefetchProxy(myCalendars); } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index ab93abab6..c9e934448 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -34,6 +34,7 @@ import { CollectionLinearView } from './collectionLinear'; import { CollectionMulticolumnView } from './collectionMulticolumn/CollectionMulticolumnView'; import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultirowView'; import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; +import { CalendarBox } from '../nodes/calendarBox/CalendarBox'; @observer export class CollectionView extends ViewBoxAnnotatableComponent() { @@ -91,7 +92,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent; - case CollectionViewType.Calendar: return ; + case CollectionViewType.Calendar: return ; case CollectionViewType.Docking: return ; case CollectionViewType.Tree: return ; case CollectionViewType.Multicolumn: return ; @@ -126,6 +127,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent func(CollectionViewType.Masonry), icon: 'columns' }, { description: 'Carousel', event: () => func(CollectionViewType.Carousel), icon: 'columns' }, { description: '3D Carousel', event: () => func(CollectionViewType.Carousel3D), icon: 'columns' }, + { description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns' }, { description: 'Pivot/Time', event: () => func(CollectionViewType.Time), icon: 'columns' }, { description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' }, { description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' }, diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index ca35d85b3..20650d648 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -12,21 +12,18 @@ import { Docs } from '../../../documents/Documents'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; import './CalendarBox.scss'; +import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView'; type CalendarView = 'month' | 'multi-month' | 'week'; @observer -export class CalendarBox extends ViewBoxBaseComponent() { - public static LayoutString(fieldKey: string = 'calendar') { - return FieldView.LayoutString(CalendarBox, fieldKey); - } - +export class CalendarBox extends CollectionSubView() { _calendarRef: HTMLDivElement | null = null; _calendar: Calendar | undefined; _oldWheel: HTMLElement | null = null; _observer: ResizeObserver | undefined; - constructor(props: FieldViewProps) { + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } @@ -78,9 +75,6 @@ export class CalendarBox extends ViewBoxBaseComponent() { if (Math.abs(startDate.getDay() - endDate.getDay()) > 7) return 'month'; return 'week'; } - @computed get childDocs() { - return DocListCast(this.dataDoc[this.fieldKey]); - } eventToColor(event: Doc): string { // TODO: Return a different color based on the event type @@ -149,7 +143,3 @@ export class CalendarBox extends ViewBoxBaseComponent() { ); } } -Docs.Prototypes.TemplateMap.set(DocumentType.CALENDAR, { - layout: { view: CalendarBox, dataField: 'data' }, - options: { acl: '', _layout_nativeDimEditable: true, _layout_reflowHorizontal: true, _layout_reflowVertical: true }, -}); -- cgit v1.2.3-70-g09d2 From 48e9246839d8491e1c40632851980527b8a4dbdd Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 4 Sep 2024 13:22:42 -0400 Subject: warning fix --- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes') 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() { @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; -- cgit v1.2.3-70-g09d2 From c674b67913ecfae6f99749db411d39f8f0d5b164 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 4 Sep 2024 15:16:13 -0400 Subject: calendar fixes --- package-lock.json | 30 +++++++++++++++++++--- package.json | 1 + .../views/nodes/calendarBox/CalendarBox.scss | 6 +++++ src/client/views/nodes/calendarBox/CalendarBox.tsx | 25 +++++++++--------- 4 files changed, 45 insertions(+), 17 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index 95a90c956..735db29ae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,6 +25,7 @@ "@fullcalendar/core": "^6.1.10", "@fullcalendar/daygrid": "^6.1.10", "@fullcalendar/multimonth": "^6.1.10", + "@fullcalendar/timegrid": "^6.1.15", "@internationalized/date": "^3.5.0", "@mui/icons-material": "^6.0.1", "@mui/material": "^6.0.1", @@ -2921,6 +2922,17 @@ "@fullcalendar/core": "~6.1.15" } }, + "node_modules/@fullcalendar/timegrid": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/timegrid/-/timegrid-6.1.15.tgz", + "integrity": "sha512-61ORr3A148RtxQ2FNG7JKvacyA/TEVZ7z6I+3E9Oeu3dqTf6M928bFcpehRTIK6zIA6Yifs7BeWHgOE9dFnpbw==", + "dependencies": { + "@fullcalendar/daygrid": "~6.1.15" + }, + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" + } + }, "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", @@ -18926,6 +18938,14 @@ "node": ">= 12.20" } }, + "node_modules/formdata-node/node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "engines": { + "node": ">= 14" + } + }, "node_modules/formidable": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.1.tgz", @@ -33427,11 +33447,13 @@ } }, "node_modules/web-streams-polyfill": { - "version": "4.0.0-beta.3", - "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", - "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0.tgz", + "integrity": "sha512-0zJXHRAYEjM2tUfZ2DiSOHAa2aw1tisnnhU3ufD57R8iefL+DcdJyRBRyJpG+NUimDgbTI/lH+gAE1PAvV3Cgw==", + "optional": true, + "peer": true, "engines": { - "node": ">= 14" + "node": ">= 8" } }, "node_modules/webidl-conversions": { diff --git a/package.json b/package.json index 11c299f7d..6254b443a 100644 --- a/package.json +++ b/package.json @@ -104,6 +104,7 @@ "@fullcalendar/core": "^6.1.10", "@fullcalendar/daygrid": "^6.1.10", "@fullcalendar/multimonth": "^6.1.10", + "@fullcalendar/timegrid": "^6.1.15", "@internationalized/date": "^3.5.0", "@mui/icons-material": "^6.0.1", "@mui/material": "^6.0.1", diff --git a/src/client/views/nodes/calendarBox/CalendarBox.scss b/src/client/views/nodes/calendarBox/CalendarBox.scss index 8eee47e42..df6ce3e64 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.scss +++ b/src/client/views/nodes/calendarBox/CalendarBox.scss @@ -5,6 +5,12 @@ .calendarBox-wrapper { width: 100%; height: 100%; + .fc-timegrid-body { + width: 100% !important; + table { + width: 100% !important; + } + } .fc-col-header { width: 100% !important; } diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 20650d648..0d50f3382 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -1,18 +1,15 @@ import { Calendar, EventSourceInput } from '@fullcalendar/core'; import dayGridPlugin from '@fullcalendar/daygrid'; import multiMonthPlugin from '@fullcalendar/multimonth'; -import { action, computed, makeObservable, observable, reaction } from 'mobx'; +import timeGrid from '@fullcalendar/timegrid'; +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, DocListCast } from '../../../../fields/Doc'; +import { Doc } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; -import { DocumentType } from '../../../documents/DocumentTypes'; -import { Docs } from '../../../documents/Documents'; -import { ViewBoxBaseComponent } from '../../DocComponent'; -import { FieldView, FieldViewProps } from '../FieldView'; -import './CalendarBox.scss'; import { CollectionSubView, SubCollectionViewProps } from '../../collections/CollectionSubView'; +import './CalendarBox.scss'; type CalendarView = 'month' | 'multi-month' | 'week'; @@ -22,6 +19,7 @@ export class CalendarBox extends CollectionSubView() { _calendar: Calendar | undefined; _oldWheel: HTMLElement | null = null; _observer: ResizeObserver | undefined; + _eventsDisposer: IReactionDisposer | undefined; constructor(props: SubCollectionViewProps) { super(props); @@ -31,14 +29,15 @@ export class CalendarBox extends CollectionSubView() { @observable _multiMonth = 0; componentDidMount(): void { - reaction( + this._eventsDisposer = reaction( () => this.calendarEvents, - events => { - this._calendar?.setOption('events', events); - }, + events => this._calendar?.setOption('events', events), { fireImmediately: true } ); } + componentWillUnmount(): void { + this._eventsDisposer?.(); + } @computed get calendarEvents(): EventSourceInput | undefined { return this.childDocs.map(doc => { @@ -91,11 +90,11 @@ export class CalendarBox extends CollectionSubView() { const cal = !this._calendarRef ? null : (this._calendar = new Calendar(this._calendarRef, { - plugins: [multiMonthPlugin, dayGridPlugin], + plugins: [multiMonthPlugin, dayGridPlugin, timeGrid], headerToolbar: { left: 'prev,next today', center: 'title', - right: 'dayGridMonth multiMonth', // timeGridWeek timeGridDay listWeek', + right: 'multiMonth,dayGridMonth,timeGridWeek,timeGridDay', }, initialDate: this.dateRangeStrDates.startDate, navLinks: true, -- cgit v1.2.3-70-g09d2 From df5217eea01b6ee3ce03ceede030306d05f19c58 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 4 Sep 2024 17:48:13 -0400 Subject: updated calendars to support times of events and to render evdnts at the correct time. --- src/ClientUtils.ts | 9 ++- src/client/util/DocumentManager.ts | 4 +- src/client/views/nodes/calendarBox/CalendarBox.tsx | 85 ++++++++++++++++------ 3 files changed, 69 insertions(+), 29 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index 51ad55c07..d149c2eae 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -651,18 +651,21 @@ export function dateRangeStrToDates(dateStr: string) { // dateStr in yyyy-mm-dd format const dateRangeParts = dateStr.split('|'); // splits into from and to date if (dateRangeParts.length < 2) return { startDate: new Date(), endDate: new Date() }; + return { startDate: new Date(dateRangeParts[0]), endDate: new Date(dateRangeParts[1]) }; 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 fromDay = parseInt(fromParts[2]?.split('T')[0]); + const fromHour = parseInt(fromParts[2]?.split('T')[1]?.split(':')[0] || '12'); const toYear = parseInt(toParts[0]); const toMonth = parseInt(toParts[1]) - 1; - const toDay = parseInt(toParts[2]); + const toDay = parseInt(toParts[2]?.split('T')[0]); + const toHour = parseInt(fromParts[2]?.split('T')[1]?.split(':')[0] || '12'); - return { startDate: new Date(fromYear, fromMonth, fromDay), endDate: new Date(toYear, toMonth, toDay) }; + return { startDate: new Date(fromYear, fromMonth, fromDay, fromHour), endDate: new Date(toYear, toMonth, toDay, toHour) }; } function replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) { diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 83b83240e..5ae292760 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -269,8 +269,8 @@ export class DocumentManager { if (options.openLocation?.includes(OpenWhere.lightbox)) { // even if we found the document view, if the target is a lightbox, we try to open it in the lightbox to preserve lightbox semantics (eg, there's only one active doc in the lightbox) const target = DocCast(targetDoc.annotationOn, targetDoc); - const contextView = this.getDocumentView(DocCast(target.embedContainer)); - if (contextView?.ComponentView?.addDocTab?.(target, options.openLocation)) { + const compView = this.getDocumentView(DocCast(target.embedContainer))?.ComponentView; + if ((compView?.addDocTab ?? compView?._props.addDocTab)?.(target, options.openLocation)) { await new Promise(waitres => { setTimeout(() => waitres()); }); diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 0d50f3382..9452cc10a 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -1,4 +1,4 @@ -import { Calendar, EventSourceInput } from '@fullcalendar/core'; +import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core'; import dayGridPlugin from '@fullcalendar/daygrid'; import multiMonthPlugin from '@fullcalendar/multimonth'; import timeGrid from '@fullcalendar/timegrid'; @@ -7,11 +7,17 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { dateRangeStrToDates } from '../../../../ClientUtils'; import { Doc } from '../../../../fields/Doc'; -import { NumCast, StrCast } from '../../../../fields/Types'; +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 = 'month' | 'multi-month' | 'week'; +type CalendarView = 'multiMonth' | 'dayGridMonth' | 'timeGridWeek' | 'timeGridDay'; @observer export class CalendarBox extends CollectionSubView() { @@ -27,8 +33,10 @@ export class CalendarBox extends CollectionSubView() { } @observable _multiMonth = 0; + isMultiMonth: boolean | undefined; componentDidMount(): void { + this._props.setContentViewBox?.(this); this._eventsDisposer = reaction( () => this.calendarEvents, events => this._calendar?.setOption('events', events), @@ -42,14 +50,14 @@ export class CalendarBox extends CollectionSubView() { @computed get calendarEvents(): EventSourceInput | undefined { return this.childDocs.map(doc => { const { startDate: start, endDate: end } = dateRangeStrToDates(StrCast(doc.date_range)); - return { title: StrCast(doc.title), start, end, + groupId: doc[Id], startEditable: true, endEditable: true, - allDay: false, + allDay: BoolCast(doc.allDay), classNames: ['mother'], // will determine the style editable: true, // subject to change in the future backgroundColor: this.eventToColor(doc), @@ -67,22 +75,40 @@ 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'; const { startDate, endDate } = this.dateRangeStrDates; - - if (startDate.getFullYear() !== endDate.getFullYear() || startDate.getMonth() !== endDate.getMonth()) return 'multi-month'; - - if (Math.abs(startDate.getDay() - endDate.getDay()) > 7) return 'month'; - return 'week'; + if (startDate.getFullYear() !== endDate.getFullYear() || startDate.getMonth() !== endDate.getMonth()) return 'multiMonth'; + if (Math.abs(startDate.getDay() - endDate.getDay()) > 7) return 'dayGridMonth'; + return 'timeGridWeek'; } + // TODO: Return a different color based on the event type eventToColor(event: Doc): string { - // TODO: Return a different color based on the event type - console.log(event.title); return 'blue'; } - handleEventClick = (/* arg: EventClickArg */) => { - // TODO: open popover with event description, option to open CalendarManager and change event date, delete event, etc. + 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; + } + + onInternalDrop = (e: Event, de: DragManager.DropEvent): boolean => { + if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData); + return false; + }; + + 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 @@ -94,8 +120,9 @@ export class CalendarBox extends CollectionSubView() { headerToolbar: { left: 'prev,next today', center: 'title', - right: 'multiMonth,dayGridMonth,timeGridWeek,timeGridDay', + right: 'multiMonth dayGridMonth timeGridWeek timeGridDay', }, + initialView: this.calendarViewType === 'multiMonth' ? undefined : this.calendarViewType, initialDate: this.dateRangeStrDates.startDate, navLinks: true, editable: false, @@ -110,19 +137,29 @@ export class CalendarBox extends CollectionSubView() { onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); render() { - this._multiMonth; return (
- setTimeout(() => { - if ((e.nativeEvent.target as HTMLButtonElement)?.className?.includes('multiMonth-button')) { - this._multiMonth = this._multiMonth + 1; - } - }) - )} + 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})`, + transformOrigin: 'top left', + }} 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 -- cgit v1.2.3-70-g09d2 From 9014cc474a039f0daef6cc0ee2011329da7703ac Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 4 Sep 2024 22:58:40 -0400 Subject: more fixes for calendarBox to preserve selection when view changes. --- package-lock.json | 488 +++++++++++---------- package.json | 3 +- src/ClientUtils.ts | 22 +- .../views/collections/CollectionCalendarView.tsx | 4 +- .../views/nodes/calendarBox/CalendarBox.scss | 1 + src/client/views/nodes/calendarBox/CalendarBox.tsx | 56 ++- 6 files changed, 311 insertions(+), 263 deletions(-) (limited to 'src/client/views/nodes') diff --git a/package-lock.json b/package-lock.json index 735db29ae..fabc6d90a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "@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/core": "^6.1.15", "@fullcalendar/daygrid": "^6.1.10", "@fullcalendar/multimonth": "^6.1.10", "@fullcalendar/timegrid": "^6.1.15", @@ -115,6 +115,7 @@ "fork-ts-checker-webpack-plugin": "^9.0.2", "form-data": "^4.0.0", "formidable": "3.5.1", + "fullcalendar": "^6.1.15", "function-plot": "^1.23.3", "golden-layout": "^2.6.0", "google-auth-library": "^9.4.1", @@ -2911,6 +2912,22 @@ "@fullcalendar/core": "~6.1.15" } }, + "node_modules/@fullcalendar/interaction": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/interaction/-/interaction-6.1.15.tgz", + "integrity": "sha512-DOTSkofizM7QItjgu7W68TvKKvN9PSEEvDJceyMbQDvlXHa7pm/WAVtAc6xSDZ9xmB1QramYoWGLHkCYbTW1rQ==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" + } + }, + "node_modules/@fullcalendar/list": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/@fullcalendar/list/-/list-6.1.15.tgz", + "integrity": "sha512-U1bce04tYDwkFnuVImJSy2XalYIIQr6YusOWRPM/5ivHcJh67Gm8CIMSWpi3KdRSNKFkqBxLPkfZGBMaOcJYug==", + "peerDependencies": { + "@fullcalendar/core": "~6.1.15" + } + }, "node_modules/@fullcalendar/multimonth": { "version": "6.1.15", "resolved": "https://registry.npmjs.org/@fullcalendar/multimonth/-/multimonth-6.1.15.tgz", @@ -3167,13 +3184,13 @@ } }, "node_modules/@jimp/core": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.2.0.tgz", - "integrity": "sha512-omuAP/bmi45UUs76KE9hCfLOHT7+abvkcbOEUG267/sCVA19d1W2Ta78p3KNry23LSfFlvokGYFlO9tuvVdYiA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-1.3.0.tgz", + "integrity": "sha512-3+ndSrQYQxyyKyUSdXyk29vHt9vc3zg+3aFrVX2RHzAMeLdjkQHqzQc/7v6VxRk6BtOh1v/VPtmsXHXVg1vVhA==", "dependencies": { - "@jimp/file-ops": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "@jimp/file-ops": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "await-to-js": "^3.0.0", "exif-parser": "^0.1.12", "file-type": "^16.0.0", @@ -3184,13 +3201,13 @@ } }, "node_modules/@jimp/diff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.2.0.tgz", - "integrity": "sha512-Ln7smOEt/v68X+kduzGfIw+9D4tzXtMeWVP+y6unVma9TConssvuUDKknV/lPw7tsktPoMLtL3zJA+TRJ7+JHQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/diff/-/diff-1.3.0.tgz", + "integrity": "sha512-eGFfZi8UjSZ6gGu9kpQmGPRRDt5fMV3V1qKRn0cTqtBsECAnKPG5PPT1dvnjDDBbtAOH81jhIr//ko8H5WV8jg==", "dependencies": { - "@jimp/plugin-resize": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "@jimp/plugin-resize": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "pixelmatch": "^5.3.0" }, "engines": { @@ -3198,21 +3215,21 @@ } }, "node_modules/@jimp/file-ops": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.2.0.tgz", - "integrity": "sha512-LUipYM3d9OiccVfBGQ7TGzb/X++1ME2Rgbt/UhGPK32/iA1w+sW/edczXGJAG4Q+3PzTMXKir5znPNXL+dlOpw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/file-ops/-/file-ops-1.3.0.tgz", + "integrity": "sha512-DzbSLgUdOGT9T9qf+Ik/hBA8e4zA9tWyHnJJA/N9sJHwHNDCMtoaX3KY5ssHuRGmcngGKLwbeGjddnZXF4oIVA==", "engines": { "node": ">=18" } }, "node_modules/@jimp/js-bmp": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.2.0.tgz", - "integrity": "sha512-3JF++37nVK+V4mf7MMT5dUOf88GrcEoMt9/0fN1+gEF6EkjS7tAcx3cSInMa8geurZipsfY71XL7WTi/y8hziA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/js-bmp/-/js-bmp-1.3.0.tgz", + "integrity": "sha512-sc3jvJJOMHoUtP9mlnuBCkwhHy9T2KiSfdV3XKg81v+bg9O0LudB33v3Y0dtGxSo/WOL2V6PVNIWTRf7gu+hJw==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "@jimp/core": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "bmp-ts": "^1.0.9" }, "engines": { @@ -3220,12 +3237,12 @@ } }, "node_modules/@jimp/js-gif": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.2.0.tgz", - "integrity": "sha512-7OV8ph3rGzOJ6nSQv7+m8fyIeeshg5b1h6hlD18dCO7qamPnJdc2M5+of1Fte2D7qDL8Az2vT6vgKPP05O/HHg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/js-gif/-/js-gif-1.3.0.tgz", + "integrity": "sha512-hhAd/TpZSp6AIuRjYKFGGEsu9HzQG16Q9lZHPcTZz1TlxtTUqW60AJvFrGvnUZbTecDj3JnI3TipX8aeqZpBDg==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/types": "1.2.0", + "@jimp/core": "1.3.0", + "@jimp/types": "1.3.0", "gifwrap": "^0.10.1", "omggif": "^1.0.10" }, @@ -3234,12 +3251,12 @@ } }, "node_modules/@jimp/js-jpeg": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.2.0.tgz", - "integrity": "sha512-7YYl8jEHCTX2XVjUZJOHAROFDHmx8diM+fEMgGhM4teOAKNM5xVDXSnC16h/qyspHqgDDQgI16gszmo6MF3W4Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/js-jpeg/-/js-jpeg-1.3.0.tgz", + "integrity": "sha512-SrZL35FvKsgySS5kpqYWbVsi0rswcgxw3oRTVOy55q8F045fE4U0YqlnRdkg77dxdahUOIWVhvn5+0V+Re5F5A==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/types": "1.2.0", + "@jimp/core": "1.3.0", + "@jimp/types": "1.3.0", "jpeg-js": "^0.4.4" }, "engines": { @@ -3247,12 +3264,12 @@ } }, "node_modules/@jimp/js-png": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.2.0.tgz", - "integrity": "sha512-toW5aqFH/alybkP2IZSLHa18O5lpwJpB5JqNQEYT1KU30V/czyUdpznlCV50PVXHZnQbb48MKJzfpLqj0O7KBA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/js-png/-/js-png-1.3.0.tgz", + "integrity": "sha512-9x8uFueVupNocQQ5WEFat61M31MdRDV7O3QBDR2iEsbVeQ1+LE9Tvvm9r1PG9W91KZYzG4IYPPsogQ0TFEixqA==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/types": "1.2.0", + "@jimp/core": "1.3.0", + "@jimp/types": "1.3.0", "pngjs": "^7.0.0" }, "engines": { @@ -3260,12 +3277,12 @@ } }, "node_modules/@jimp/js-tiff": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.2.0.tgz", - "integrity": "sha512-RhXqdkB7lhpCW9uKVXmLhFI+Inpj7CcP8GNMMWpPcYaWw8aGUqZjgdy/81nudIdXmvk4eKI5NdtWptCVzRFvsQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/js-tiff/-/js-tiff-1.3.0.tgz", + "integrity": "sha512-uin5WkVm0M8ZgotIANXU0sfVkNApsKws1ZSqsc9NZf0MYJsZkz/w8D4ld6hXkFCEQcJ/TMu7aeMZSP+I8cbmOg==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/types": "1.2.0", + "@jimp/core": "1.3.0", + "@jimp/types": "1.3.0", "utif2": "^4.1.0" }, "engines": { @@ -3273,12 +3290,12 @@ } }, "node_modules/@jimp/plugin-blit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.2.0.tgz", - "integrity": "sha512-y18/qt/FdINIQefmYSI6ownb1VtOsR3Y43H8JwzcsuSmpZLpF+lJwXoZLFzgCSSCpiQyb+z924k6L11C4uKH3g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-1.3.0.tgz", + "integrity": "sha512-5OjEUM0jtzQ6KJBTougs8ozbUAxYyJiKZqceFb8mqKVMjuiM94N9425mIDDgOl5MDFwAESeTMIchKCAPMOv2FQ==", "dependencies": { - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3286,23 +3303,23 @@ } }, "node_modules/@jimp/plugin-blur": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.2.0.tgz", - "integrity": "sha512-h0/4W2zjkrDC9nZGl1r+pk5ftG6d1Zdvza/BE7xie++P2ihGp4ikgK26Hn/xlnAH691yh/uCjP0eeJw+Ggfy5Q==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-1.3.0.tgz", + "integrity": "sha512-108RHeCvHFJqpQvuaydhxwJLBwdjEWKLW6ZXWWCnCadrpKbH2yqu9P6oUhHS7atLjQ0ZBzXcM+Wj2VYR7XU8ng==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/utils": "1.2.0" + "@jimp/core": "1.3.0", + "@jimp/utils": "1.3.0" }, "engines": { "node": ">=18" } }, "node_modules/@jimp/plugin-circle": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.2.0.tgz", - "integrity": "sha512-rfPOsLAnQ+YpmGttJRbmTHP2V5Q1Ca99HcpaF8NqOVC9cotGu+Ux3xJIo9QKxyDTO/uwx15KTwzbQAbE43Mr0A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-1.3.0.tgz", + "integrity": "sha512-Lkz1uwD2wgysuu4TDzAVQ26+urr+siYlO/qXnMYHui9k0P735S6B6EsWrzssLDGOtqevQyYcx5u6h0Kv4lzehg==", "dependencies": { - "@jimp/types": "1.2.0", + "@jimp/types": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3310,13 +3327,13 @@ } }, "node_modules/@jimp/plugin-color": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.2.0.tgz", - "integrity": "sha512-RdDj1ZyUN478hwCbR004xQK2qJ+FQvL0F+fGVUOozpw4pWs6G0PKtGWooKGB0522sEw3tvvb5mHDnz06WWIU0w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-1.3.0.tgz", + "integrity": "sha512-HLFtZB86W2nVnfZT+LAsyooF9efapWPmxuOKECeevunb1zHieO1ni19QXJfcqtt+cVj8UxIBGC4v9IFDJ9PGYw==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "@jimp/core": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "tinycolor2": "^1.6.0", "zod": "^3.23.8" }, @@ -3325,15 +3342,15 @@ } }, "node_modules/@jimp/plugin-contain": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.2.0.tgz", - "integrity": "sha512-YZmQnP0eJbBUhDr4YuvfsQl04CFEwTy+EKgMX7fCc5Ne/l0Y4wiabUtB2Cr47tvIoQiOzmx8UV5jnoYJFO6CQw==", - "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/plugin-blit": "1.2.0", - "@jimp/plugin-resize": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-1.3.0.tgz", + "integrity": "sha512-1xPJ/CC4hh8IDrZFCtwQezw0RFzdrFvatzXkmfZD0cRyUXtYQ8VzExeK9MXLWi2+/nfufh+2SIhThTQ8xIzLBw==", + "dependencies": { + "@jimp/core": "1.3.0", + "@jimp/plugin-blit": "1.3.0", + "@jimp/plugin-resize": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3341,14 +3358,14 @@ } }, "node_modules/@jimp/plugin-cover": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.2.0.tgz", - "integrity": "sha512-EnCibB1mMSFRoIW8h53lvEAWXFb+H6iIjCoKAVuMIkPq6nhc7WCDuvvgn+oLnYaNPSsD5+ge+pafdIaAlw+jpA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-1.3.0.tgz", + "integrity": "sha512-Q8vlXepruKU+A55PS2A+d7TPwIoYthnX61ae+TQa+/4DjYk6XZA2YlmUFhq7P3RH5p288N/84ILNSIwH5YCqAw==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/plugin-crop": "1.2.0", - "@jimp/plugin-resize": "1.2.0", - "@jimp/types": "1.2.0", + "@jimp/core": "1.3.0", + "@jimp/plugin-crop": "1.3.0", + "@jimp/plugin-resize": "1.3.0", + "@jimp/types": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3356,13 +3373,13 @@ } }, "node_modules/@jimp/plugin-crop": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.2.0.tgz", - "integrity": "sha512-jVq3zlrcYwnVKIS8NTCeOoJ8sWrn9Th4qDQWu7hbo6tgEAYXZCEBPC3zQzeu7RY9oETorJ3Q7zKzyKO8xzBedw==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-1.3.0.tgz", + "integrity": "sha512-AoCTYFgcDEH+sqc2IQ5CI0CgYrQZSFfZ6q4zSXkWA+irs1nDbjJeA+0vClkYxJNSkk2wqB0fys69OBoqfDabrw==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "@jimp/core": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3370,12 +3387,12 @@ } }, "node_modules/@jimp/plugin-displace": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.2.0.tgz", - "integrity": "sha512-XAm+y2xX95cioc4Ya9RlxiDRbfQ6+DF6B6nHRkzwUYN3TaB698PS8fBkBZ4VlMQ8IqD+Y+OKcEDhpWz28GqUVg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-1.3.0.tgz", + "integrity": "sha512-8t/R0SjE7YWujeMLbUT2js9WIeyFbeQXxAiCPt4AJy1BUD56sbcWIx1zJzrL52eF+bG4AS8oLOp5arL+P7ocmQ==", "dependencies": { - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3383,23 +3400,23 @@ } }, "node_modules/@jimp/plugin-dither": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.2.0.tgz", - "integrity": "sha512-NoOeeOhqFoIBqHnJtXkkknWCykOQzH/duYTb4JTxRaHKAIelQAvDbxanLu+S7wp3brIvurJM9RDxYK0Gqd7k5A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-1.3.0.tgz", + "integrity": "sha512-oE6kHne88OOcJBu+fk9KvMMB71UZUO4B2YYoaHVjGbtciPG9FbBBGqgD9oULVWhHuICZdDnfgFF0hhemQuQloQ==", "dependencies": { - "@jimp/types": "1.2.0" + "@jimp/types": "1.3.0" }, "engines": { "node": ">=18" } }, "node_modules/@jimp/plugin-fisheye": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.2.0.tgz", - "integrity": "sha512-N5UG6vbpQkYbhYAWJcreR2vC0fWWxf9VUupDaIOM1Yp9h5Lel/8Q0o2ie5yUxsHt2wa0HOm0O9KorfHzlV6s5A==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-1.3.0.tgz", + "integrity": "sha512-VLaqY/IxrqHyjKeWpUwJZpAqug4DE26hM/8ejfPm5FmofAS1dI0deecDfbthRbw17hnPVcAiTkZ6QTiQL71Z4w==", "dependencies": { - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3407,11 +3424,11 @@ } }, "node_modules/@jimp/plugin-flip": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.2.0.tgz", - "integrity": "sha512-zI5/6dkoUdslf7BMccXURIQzXKKXu/ocTTnzf3C6VHV4M6U/QIg1BLi8A5+KkgHnvpuSM/IW3T0PDWKLpmK/CA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-1.3.0.tgz", + "integrity": "sha512-cHeefBsjBYLbjqq8TFJHQD/6sgJLvb6XPdB8wVvKe682Y8jIilxlEhZJeUFXwsMrKKhbXNZxmtSc/pEIYCo6cA==", "dependencies": { - "@jimp/types": "1.2.0", + "@jimp/types": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3419,19 +3436,19 @@ } }, "node_modules/@jimp/plugin-hash": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.2.0.tgz", - "integrity": "sha512-opj35tK+Ku+17mq9Ip7GEwDpNr+Mk/xQcOGLBispbKLWqAG1IDyHTLSsAmDcM8pL4AXXv/96xPUWWxu4mZLwTw==", - "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/js-bmp": "1.2.0", - "@jimp/js-jpeg": "1.2.0", - "@jimp/js-png": "1.2.0", - "@jimp/js-tiff": "1.2.0", - "@jimp/plugin-color": "1.2.0", - "@jimp/plugin-resize": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-hash/-/plugin-hash-1.3.0.tgz", + "integrity": "sha512-LAUi9FFT3Kp2bI2hWXQ75t1pl6q6ZyKH/jJQZg8DPL+fFK0//TIsw/g0VxW5lZoV3mHUUorQlsZzWtNi/DGtWA==", + "dependencies": { + "@jimp/core": "1.3.0", + "@jimp/js-bmp": "1.3.0", + "@jimp/js-jpeg": "1.3.0", + "@jimp/js-png": "1.3.0", + "@jimp/js-tiff": "1.3.0", + "@jimp/plugin-color": "1.3.0", + "@jimp/plugin-resize": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "any-base": "^1.1.0" }, "engines": { @@ -3439,11 +3456,11 @@ } }, "node_modules/@jimp/plugin-mask": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.2.0.tgz", - "integrity": "sha512-VLGifoXRyddUDbUj0LIG3HrEeCZOHSzfOcv/bXyQq7/KHmR+HnhaAuDOlkDLBa4buZMbh4Ub+nhmDhU3kwf8yA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-1.3.0.tgz", + "integrity": "sha512-fpU6rZ75c1gD6/8zsiPQW57+doX3KfexZ3lVYToyd720HPO/qfG9lzwfY30tJVXArI4DMbG8qN7lXKgGeWwGqw==", "dependencies": { - "@jimp/types": "1.2.0", + "@jimp/types": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3451,15 +3468,15 @@ } }, "node_modules/@jimp/plugin-print": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.2.0.tgz", - "integrity": "sha512-TLfne5npxCkf8CY/UuCT589GpKQsjQrcj6SjdVpcddGdm25t3jEt4+WE6wPFsOuocWZO6YSaJNOQJKma/BRQ3w==", - "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/js-jpeg": "1.2.0", - "@jimp/js-png": "1.2.0", - "@jimp/plugin-blit": "1.2.0", - "@jimp/types": "1.2.0", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-1.3.0.tgz", + "integrity": "sha512-WeN35Fo9Bushm6VGUdQXqXrVIFDYECeKLKN+LlAqQ/YnIlUiTirPlcyGHzEBKD8uXDCmjBYqxwadcPvRDVwFEw==", + "dependencies": { + "@jimp/core": "1.3.0", + "@jimp/js-jpeg": "1.3.0", + "@jimp/js-png": "1.3.0", + "@jimp/plugin-blit": "1.3.0", + "@jimp/types": "1.3.0", "parse-bmfont-ascii": "^1.0.6", "parse-bmfont-binary": "^1.0.6", "parse-bmfont-xml": "^1.1.6", @@ -3471,9 +3488,9 @@ } }, "node_modules/@jimp/plugin-quantize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.2.0.tgz", - "integrity": "sha512-0sYazEK2sT1NXFInwcef0ag0X3qicqI07vUFnv4bKtxILU00tWhUyig3h6kLzZhFdiTGXt2MP3OGCu4mWQ8OfQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-quantize/-/plugin-quantize-1.3.0.tgz", + "integrity": "sha512-4Hgp5UNN8DGeX1ULNANPwlHCyuaZYZPJ/mpe/lnCN4jLI/SeBzR4g8tU+srNF6arPwRXrLNQV6T/ehAa7zhbkg==", "dependencies": { "image-q": "^4.0.0", "zod": "^3.23.8" @@ -3483,12 +3500,12 @@ } }, "node_modules/@jimp/plugin-resize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.2.0.tgz", - "integrity": "sha512-QZd72r988QdAYQs9jggHsCdatbybkU3WmVxqE0l3YbpltW13tdiMktsNTSyQT1nci7l3LvrBRuNl6pOQx7Ug5w==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-1.3.0.tgz", + "integrity": "sha512-9fMw6ff/3kzDwQ2rGNYCJ2jc9IHsxQh9eaoPb4SkVHxzq+O3yka3M2Vjf41gaYhQ5Pt5QLgQ9uYUA+2kp1RF1g==", "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/types": "1.2.0", + "@jimp/core": "1.3.0", + "@jimp/types": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3496,15 +3513,15 @@ } }, "node_modules/@jimp/plugin-rotate": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.2.0.tgz", - "integrity": "sha512-P0dvX6XlerHK6Om1rUs9kAxqbmk94VCPxuzJk5asUliOtq+wklLTfRbcmn3fQkn9xH3EtQojdE0RAVCExMkOdg==", - "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/plugin-crop": "1.2.0", - "@jimp/plugin-resize": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-1.3.0.tgz", + "integrity": "sha512-lpzk37tzk7b5RG5U6P/E3vk+bwU86TnNZOc1LHQeTOEpfFAPMTJl6w+OlLLxVNJ7HUQege/8P47N2onQX00wXw==", + "dependencies": { + "@jimp/core": "1.3.0", + "@jimp/plugin-crop": "1.3.0", + "@jimp/plugin-resize": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3512,15 +3529,15 @@ } }, "node_modules/@jimp/plugin-threshold": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.2.0.tgz", - "integrity": "sha512-yMibcCFQ1NLx94DFdGqPG0z3EiOohgbYhDYpqMmadBu1IUOcHl8EnCJjD673RZxQh/4s6MR6vpcT5epjtFVnGg==", - "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/plugin-color": "1.2.0", - "@jimp/plugin-hash": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-1.3.0.tgz", + "integrity": "sha512-lKBzZEgjI/zM51/muGyL2juGEkK361/yFpRcmjafIijq0sHNww1rhqSa0AhO80iCmN77A4Ym/oelY0qh4mtlLQ==", + "dependencies": { + "@jimp/core": "1.3.0", + "@jimp/plugin-color": "1.3.0", + "@jimp/plugin-hash": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0", "zod": "^3.23.8" }, "engines": { @@ -3528,9 +3545,9 @@ } }, "node_modules/@jimp/types": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.2.0.tgz", - "integrity": "sha512-Zjt+cV0f9Txnx9xdT+SJeNVgbgsQdtMTNWF8W6OAs3pLI9KaAelsXLi9F9jWikoctAI58GU/LOG52LLQkE/5qQ==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-1.3.0.tgz", + "integrity": "sha512-K4RaTmDTqZqbjjwOtxzVH9QyCgQBukXjKOmdgNuCmu7ugrpeTeWV7SvrwZZPhTt31lmja8A3a3Dw1ScVZdtmyA==", "dependencies": { "zod": "^3.23.8" }, @@ -3539,11 +3556,11 @@ } }, "node_modules/@jimp/utils": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.2.0.tgz", - "integrity": "sha512-7/CIawD7fKfzG1QWU66nS5JxLNbhCiYuv6ce2ytJUzzPPLrJ0X250cS0kb30FvPaNu42DfvJUMUjxbg+9Ia79g==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-1.3.0.tgz", + "integrity": "sha512-QIye6IWJaQ3Q9+6rxgQiFI1I7MwrQZYxlhYhPolJv+BfCXBT8XWJymV8J75vlnjvz3kN2AMXAEU7c7w0h2Tz3Q==", "dependencies": { - "@jimp/types": "1.2.0", + "@jimp/types": "1.3.0", "tinycolor2": "^1.6.0" }, "engines": { @@ -9560,9 +9577,9 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==" }, "node_modules/@types/node": { - "version": "22.5.3", - "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.3.tgz", - "integrity": "sha512-njripolh85IA9SQGTAqbmnNZTdxv7X/4OYGPz8tgy5JDr8MP+uDBa921GpYEoDDnwm0Hmn5ZPeJgiiSTPoOzkQ==", + "version": "22.5.4", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.5.4.tgz", + "integrity": "sha512-FDuKUJQm/ju9fT/SeX/6+gBzoPzlVCzfzmGkwKvRHQVxi4BntVbyIwf6a4Xn62mrvndLiml6z/UBXIdEVjQLXg==", "dependencies": { "undici-types": "~6.19.2" } @@ -10273,36 +10290,36 @@ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" }, "node_modules/@vue/compiler-core": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.0.tgz", - "integrity": "sha512-ja7cpqAOfw4tyFAxgBz70Z42miNDeaqTxExTsnXDLomRpqfyCgyvZvFp482fmsElpfvsoMJUsvzULhvxUTW6Iw==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.1.tgz", + "integrity": "sha512-WdjF+NSgFYdWttHevHw5uaJFtKPalhmxhlu2uREj8cLP0uyKKIR60/JvSZNTp0x+NSd63iTiORQTx3+tt55NWQ==", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/shared": "3.5.0", + "@vue/shared": "3.5.1", "entities": "^4.5.0", "estree-walker": "^2.0.2", "source-map-js": "^1.2.0" } }, "node_modules/@vue/compiler-dom": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.0.tgz", - "integrity": "sha512-xYjUybWZXl+1R/toDy815i4PbeehL2hThiSGkcpmIOCy2HoYyeeC/gAWK/Y/xsoK+GSw198/T5O31bYuQx5uvQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.1.tgz", + "integrity": "sha512-Ao23fB1lINo18HLCbJVApvzd9OQe8MgmQSgyY5+umbWj2w92w9KykVmJ4Iv2US5nak3ixc2B+7Km7JTNhQ8kSQ==", "dependencies": { - "@vue/compiler-core": "3.5.0", - "@vue/shared": "3.5.0" + "@vue/compiler-core": "3.5.1", + "@vue/shared": "3.5.1" } }, "node_modules/@vue/compiler-sfc": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.0.tgz", - "integrity": "sha512-B9DgLtrqok2GLuaFjLlSL15ZG3ZDBiitUH1ecex9guh/ZcA5MCdwuVE6nsfQxktuZY/QY0awJ35/ripIviCQTQ==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.1.tgz", + "integrity": "sha512-DFizMNH8eDglLhlfwJ0+ciBsztaYe3fY/zcZjrqL1ljXvUw/UpC84M1d7HpBTCW68SNqZyIxrs1XWmf+73Y65w==", "dependencies": { "@babel/parser": "^7.25.3", - "@vue/compiler-core": "3.5.0", - "@vue/compiler-dom": "3.5.0", - "@vue/compiler-ssr": "3.5.0", - "@vue/shared": "3.5.0", + "@vue/compiler-core": "3.5.1", + "@vue/compiler-dom": "3.5.1", + "@vue/compiler-ssr": "3.5.1", + "@vue/shared": "3.5.1", "estree-walker": "^2.0.2", "magic-string": "^0.30.11", "postcss": "^8.4.44", @@ -10310,18 +10327,18 @@ } }, "node_modules/@vue/compiler-ssr": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.0.tgz", - "integrity": "sha512-E263QZmA1dqRd7c3u/sWTLRMpQOT0aZ8av/L9SoD/v/BVMZaWFHPUUBswS+bzrfvG2suJF8vSLKx6k6ba5SUdA==", + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.1.tgz", + "integrity": "sha512-C1hpSHQgRM8bg+5XWWD7CkFaVpSn9wZHCLRd10AmxqrH17d4EMP6+XcZpwBOM7H1jeStU5naEapZZWX0kso1tQ==", "dependencies": { - "@vue/compiler-dom": "3.5.0", - "@vue/shared": "3.5.0" + "@vue/compiler-dom": "3.5.1", + "@vue/shared": "3.5.1" } }, "node_modules/@vue/shared": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.0.tgz", - "integrity": "sha512-m9IgiteBpCkFaMNwCOBkFksA7z8QiKc30ooRuoXWUFRDu0mGyNPlFHmbncF0/Kra1RlX8QrmBbRaIxVvikaR0Q==" + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.1.tgz", + "integrity": "sha512-NdcTRoO4KuW2RSFgpE2c+E/R/ZHaRzWPxAGxhmxZaaqLh6nYCXx7lc9a88ioqOCxCaV2SFJmujkxbUScW7dNsQ==" }, "node_modules/@webassemblyjs/ast": { "version": "1.12.1", @@ -19038,6 +19055,19 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/fullcalendar": { + "version": "6.1.15", + "resolved": "https://registry.npmjs.org/fullcalendar/-/fullcalendar-6.1.15.tgz", + "integrity": "sha512-CFnh1yswjRh9puJVDk8VGwTlyZ6eXxr4qLI7QCA0+bozyAm+BluP1US5mOtgk0gEq23nQxGSNDoBvAraz++saQ==", + "dependencies": { + "@fullcalendar/core": "~6.1.15", + "@fullcalendar/daygrid": "~6.1.15", + "@fullcalendar/interaction": "~6.1.15", + "@fullcalendar/list": "~6.1.15", + "@fullcalendar/multimonth": "~6.1.15", + "@fullcalendar/timegrid": "~6.1.15" + } + }, "node_modules/function-bind": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", @@ -21724,37 +21754,37 @@ } }, "node_modules/jimp": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.2.0.tgz", - "integrity": "sha512-oNqOurG39OYE50TqFoYoHhMbZ8eUiTOtQ2MFPzfdXLUzjNsB7qJUR1ZJRGep4JfFhjf2G0HJiYoqDR2XkXHR/A==", - "dependencies": { - "@jimp/core": "1.2.0", - "@jimp/diff": "1.2.0", - "@jimp/js-bmp": "1.2.0", - "@jimp/js-gif": "1.2.0", - "@jimp/js-jpeg": "1.2.0", - "@jimp/js-png": "1.2.0", - "@jimp/js-tiff": "1.2.0", - "@jimp/plugin-blit": "1.2.0", - "@jimp/plugin-blur": "1.2.0", - "@jimp/plugin-circle": "1.2.0", - "@jimp/plugin-color": "1.2.0", - "@jimp/plugin-contain": "1.2.0", - "@jimp/plugin-cover": "1.2.0", - "@jimp/plugin-crop": "1.2.0", - "@jimp/plugin-displace": "1.2.0", - "@jimp/plugin-dither": "1.2.0", - "@jimp/plugin-fisheye": "1.2.0", - "@jimp/plugin-flip": "1.2.0", - "@jimp/plugin-hash": "1.2.0", - "@jimp/plugin-mask": "1.2.0", - "@jimp/plugin-print": "1.2.0", - "@jimp/plugin-quantize": "1.2.0", - "@jimp/plugin-resize": "1.2.0", - "@jimp/plugin-rotate": "1.2.0", - "@jimp/plugin-threshold": "1.2.0", - "@jimp/types": "1.2.0", - "@jimp/utils": "1.2.0" + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-1.3.0.tgz", + "integrity": "sha512-eJnVMuqDQ545taNLp13gVZynnoOvE0xZ2Oti9alkld47dNhmFHBmFTBMTYaZr7zceGTf54RGdr7C4d2WUNwc0g==", + "dependencies": { + "@jimp/core": "1.3.0", + "@jimp/diff": "1.3.0", + "@jimp/js-bmp": "1.3.0", + "@jimp/js-gif": "1.3.0", + "@jimp/js-jpeg": "1.3.0", + "@jimp/js-png": "1.3.0", + "@jimp/js-tiff": "1.3.0", + "@jimp/plugin-blit": "1.3.0", + "@jimp/plugin-blur": "1.3.0", + "@jimp/plugin-circle": "1.3.0", + "@jimp/plugin-color": "1.3.0", + "@jimp/plugin-contain": "1.3.0", + "@jimp/plugin-cover": "1.3.0", + "@jimp/plugin-crop": "1.3.0", + "@jimp/plugin-displace": "1.3.0", + "@jimp/plugin-dither": "1.3.0", + "@jimp/plugin-fisheye": "1.3.0", + "@jimp/plugin-flip": "1.3.0", + "@jimp/plugin-hash": "1.3.0", + "@jimp/plugin-mask": "1.3.0", + "@jimp/plugin-print": "1.3.0", + "@jimp/plugin-quantize": "1.3.0", + "@jimp/plugin-resize": "1.3.0", + "@jimp/plugin-rotate": "1.3.0", + "@jimp/plugin-threshold": "1.3.0", + "@jimp/types": "1.3.0", + "@jimp/utils": "1.3.0" }, "engines": { "node": ">=18" @@ -27163,13 +27193,13 @@ } }, "node_modules/openai": { - "version": "4.57.1", - "resolved": "https://registry.npmjs.org/openai/-/openai-4.57.1.tgz", - "integrity": "sha512-7q+4U9A/klaAT40bqL6sPFhIKb4jsUJ8udddCzaf8mdwICYeBG7grps/zDcrOUfkwCxCzR6fxfDDah3WqHoVUA==", + "version": "4.57.3", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.57.3.tgz", + "integrity": "sha512-mTz5/SmulkkeSpqbSr6WNLRU6krkyhnbfRUC8XfaXbj1T6xUorKEELjZvbRSzI714JLOk1MeFkqYS9H4WHhqDQ==", "dependencies": { "@types/node": "^18.11.18", "@types/node-fetch": "^2.6.4", - "@types/qs": "^6.9.7", + "@types/qs": "^6.9.15", "abort-controller": "^3.0.0", "agentkeepalive": "^4.2.1", "form-data-encoder": "1.7.2", @@ -27190,9 +27220,9 @@ } }, "node_modules/openai/node_modules/@types/node": { - "version": "18.19.49", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.49.tgz", - "integrity": "sha512-ALCeIR6n0nQ7j0FUF1ycOhrp6+XutJWqEu/vtdEqXFUQwkBfgUA5cEg3ZNmjWGF/ZYA/FcF9QMkL55Ar0O6UrA==", + "version": "18.19.50", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.50.tgz", + "integrity": "sha512-xonK+NRrMBRtkL1hVCc3G+uXtjh1Al4opBLjqVmipe5ZAaBYWW6cNAiBVZ1BvmkBhep698rP3UM3aRAdSALuhg==", "dependencies": { "undici-types": "~5.26.4" } @@ -28870,9 +28900,9 @@ } }, "node_modules/react-intersection-observer": { - "version": "9.13.0", - "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.0.tgz", - "integrity": "sha512-y0UvBfjDiXqC8h0EWccyaj4dVBWMxgEx0t5RGNzQsvkfvZwugnKwxpu70StY4ivzYuMajavwUDjH4LJyIki9Lw==", + "version": "9.13.1", + "resolved": "https://registry.npmjs.org/react-intersection-observer/-/react-intersection-observer-9.13.1.tgz", + "integrity": "sha512-tSzDaTy0qwNPLJHg8XZhlyHTgGW6drFKTtvjdL+p6um12rcnp8Z5XstE+QNBJ7c64n5o0Lj4ilUleA41bmDoMw==", "peerDependencies": { "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" diff --git a/package.json b/package.json index 6254b443a..05992ebc3 100644 --- a/package.json +++ b/package.json @@ -101,7 +101,7 @@ "@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/core": "^6.1.15", "@fullcalendar/daygrid": "^6.1.10", "@fullcalendar/multimonth": "^6.1.10", "@fullcalendar/timegrid": "^6.1.15", @@ -194,6 +194,7 @@ "fork-ts-checker-webpack-plugin": "^9.0.2", "form-data": "^4.0.0", "formidable": "3.5.1", + "fullcalendar": "^6.1.15", "function-plot": "^1.23.3", "golden-layout": "^2.6.0", "google-auth-library": "^9.4.1", diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index d149c2eae..01eda7e98 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -648,24 +648,14 @@ export function DivWidth(ele: HTMLElement | null): number { } export function dateRangeStrToDates(dateStr: string) { + const toDate = (str: string) => { + return !str.includes('T') && str.includes('-') ? new Date(Number(str.split('-')[0]), Number(str.split('-')[1]) - 1, Number(str.split('-')[2])) : new Date(str); + }; // dateStr in yyyy-mm-dd format const dateRangeParts = dateStr.split('|'); // splits into from and to date - if (dateRangeParts.length < 2) return { startDate: new Date(), endDate: new Date() }; - return { startDate: new Date(dateRangeParts[0]), endDate: new Date(dateRangeParts[1]) }; - 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]?.split('T')[0]); - const fromHour = parseInt(fromParts[2]?.split('T')[1]?.split(':')[0] || '12'); - - const toYear = parseInt(toParts[0]); - const toMonth = parseInt(toParts[1]) - 1; - const toDay = parseInt(toParts[2]?.split('T')[0]); - const toHour = parseInt(fromParts[2]?.split('T')[1]?.split(':')[0] || '12'); - - return { startDate: new Date(fromYear, fromMonth, fromDay, fromHour), endDate: new Date(toYear, toMonth, toDay, toHour) }; + if (dateRangeParts.length < 2 && !dateRangeParts[0]) return { start: new Date(), end: new Date() }; + if (dateRangeParts.length < 2) return { start: toDate(dateRangeParts[0]), end: toDate(dateRangeParts[0]) }; + return { start: new Date(dateRangeParts[0]), end: new Date(dateRangeParts[1]) }; } function replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) { diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx index c1f0b314b..30fd9fc2b 100644 --- a/src/client/views/collections/CollectionCalendarView.tsx +++ b/src/client/views/collections/CollectionCalendarView.tsx @@ -38,8 +38,8 @@ export class CollectionCalendarView extends CollectionSubView() { const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range); const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range); - const { startDate: aFromDate, endDate: aToDate } = dateRangeStrToDates(aDateRangeStr); - const { startDate: bFromDate, endDate: bToDate } = dateRangeStrToDates(bDateRangeStr); + const { start: aFromDate, end: aToDate } = dateRangeStrToDates(aDateRangeStr); + const { start: bFromDate, end: bToDate } = dateRangeStrToDates(bDateRangeStr); if (aFromDate > bFromDate) { return -1; // a comes first diff --git a/src/client/views/nodes/calendarBox/CalendarBox.scss b/src/client/views/nodes/calendarBox/CalendarBox.scss index df6ce3e64..f8ac4b2d1 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.scss +++ b/src/client/views/nodes/calendarBox/CalendarBox.scss @@ -2,6 +2,7 @@ display: flex; width: 100%; height: 100%; + transform-origin: top left; .calendarBox-wrapper { width: 100%; height: 100%; diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 9452cc10a..678b7dd0b 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -1,7 +1,8 @@ -import { Calendar, EventClickArg, EventSourceInput } from '@fullcalendar/core'; +import { Calendar, DateInput, EventClickArg, EventSourceInput } from '@fullcalendar/core'; import dayGridPlugin from '@fullcalendar/daygrid'; 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 { observer } from 'mobx-react'; import * as React from 'react'; @@ -26,6 +27,7 @@ export class CalendarBox extends CollectionSubView() { _oldWheel: HTMLElement | null = null; _observer: ResizeObserver | undefined; _eventsDisposer: IReactionDisposer | undefined; + _selectDisposer: IReactionDisposer | undefined; constructor(props: SubCollectionViewProps) { super(props); @@ -38,18 +40,32 @@ export class CalendarBox extends CollectionSubView() { componentDidMount(): void { this._props.setContentViewBox?.(this); this._eventsDisposer = reaction( - () => this.calendarEvents, - events => this._calendar?.setOption('events', events), + () => ({ 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 } ); } componentWillUnmount(): void { this._eventsDisposer?.(); + this._selectDisposer?.(); } @computed get calendarEvents(): EventSourceInput | undefined { return this.childDocs.map(doc => { - const { startDate: start, endDate: end } = dateRangeStrToDates(StrCast(doc.date_range)); + const { start, end } = dateRangeStrToDates(StrCast(doc.date_range)); return { title: StrCast(doc.title), start, @@ -61,6 +77,7 @@ export class CalendarBox extends CollectionSubView() { 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), @@ -72,20 +89,23 @@ export class CalendarBox extends CollectionSubView() { @computed get dateRangeStrDates() { return dateRangeStrToDates(StrCast(this.Document.date_range)); } + get dateSelect() { + return dateRangeStrToDates(StrCast(this.Document.date)); + } // 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 { startDate, endDate } = this.dateRangeStrDates; - if (startDate.getFullYear() !== endDate.getFullYear() || startDate.getMonth() !== endDate.getMonth()) return 'multiMonth'; - if (Math.abs(startDate.getDay() - endDate.getDay()) > 7) return 'dayGridMonth'; + 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'; } // TODO: Return a different color based on the event type eventToColor(event: Doc): string { - return 'blue'; + return 'red'; } internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) { @@ -116,32 +136,39 @@ export class CalendarBox extends CollectionSubView() { const cal = !this._calendarRef ? null : (this._calendar = new Calendar(this._calendarRef, { - plugins: [multiMonthPlugin, dayGridPlugin, timeGrid], + 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.dateRangeStrDates.startDate, + 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 (
+ onPointerDown={e => { setTimeout( action(() => { const cname = (e.nativeEvent.target as HTMLButtonElement)?.className ?? ''; @@ -150,13 +177,12 @@ export class CalendarBox extends CollectionSubView() { 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})`, - transformOrigin: 'top left', }} ref={r => { this.createDashEventsTarget(r); -- cgit v1.2.3-70-g09d2 From 78fde29eec8c9caa0bb258d97757656ddfc78e35 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 5 Sep 2024 14:14:39 -0400 Subject: added hideScroll option for text boxes. --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src/client/views/nodes') 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 -- cgit v1.2.3-70-g09d2 From a6dc4ec556d0653efd7f958e24bdd6812ff12879 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 6 Sep 2024 15:36:37 -0400 Subject: fixed tagsView assignment to metadadtafield --- src/client/views/nodes/KeyValueBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/nodes') 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() { 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 + '`'; -- cgit v1.2.3-70-g09d2 From b9f15c10e4cfa1288e176cbd1d312c628c5998ad Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 10 Sep 2024 14:54:44 -0400 Subject: moved TagsView up into DocumentView so that one row of tags will be visible. fixed setting pixel size of images to resize annotations so they don't appear to change. added vert/horiz centering for stacking views. fixed pres box to not have scroll bars. fixed resizing properties panel. --- src/client/documents/Documents.ts | 3 ++- src/client/util/CurrentUserUtils.ts | 12 ++++++----- src/client/views/MainView.tsx | 8 +++++--- src/client/views/SidebarAnnos.tsx | 1 + src/client/views/StyleProvider.tsx | 2 +- src/client/views/TagsView.tsx | 7 +++++-- .../views/collections/CollectionCalendarView.tsx | 1 + .../views/collections/CollectionStackingView.scss | 7 ++----- .../views/collections/CollectionStackingView.tsx | 13 +++++++++--- .../CollectionStackingViewFieldColumn.tsx | 24 ++++++++++------------ src/client/views/collections/TreeView.tsx | 2 +- .../collectionFreeForm/FaceCollectionBox.tsx | 1 + src/client/views/global/globalScripts.ts | 14 ++++++++----- src/client/views/nodes/DocumentView.scss | 2 +- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/ImageBox.tsx | 14 +++++++++++-- 16 files changed, 70 insertions(+), 42 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d5a7b0465..fbdf361dd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -260,6 +260,7 @@ export class DocumentOptions { _layout_noSidebar?: BOOLt = new BoolInfo('whether to display the sidebar toggle button'); layout_boxShadow?: string; // box-shadow css string OR "standard" to use dash standard box shadow layout_maxShown?: NUMt = new NumInfo('maximum number of children to display at one time (see multicolumnview)'); + _layout_dontCenter?: STRt = new StrInfo("whether collections will center their content - values of 'x', 'xy', or 'y'"); _layout_autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents'); _layout_autoHeightMargins?: NUMt = new NumInfo('Margin heights to be added to the computed auto height of a Doc'); _layout_curPage?: NUMt = new NumInfo('current page of a PDF or other? paginated document', false); @@ -979,7 +980,7 @@ export namespace Docs { } export function StackingDocument(documents: Array, options: DocumentOptions, id?: string, protoId?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _type_collection: CollectionViewType.Stacking }, id, undefined, protoId); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _layout_dontCenter: 'y', ...options, _type_collection: CollectionViewType.Stacking }, id, undefined, protoId); } export function NoteTakingDocument(documents: Array, options: DocumentOptions, id?: string, protoId?: string) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f042f33ce..2014c48e1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -368,7 +368,7 @@ pie title Minerals in my tap water {key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 300, _height: 300, }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}}, {key: "ViewSlide", creator: slideView, opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}}, - {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, + {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, _layout_dontCenter:'xy', dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _layout_fitWidth: true, _freeform_backgroundGrid: true, }}, {key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _type_collection: CollectionViewType.Tree, treeView_HasOverlay: true, _text_fontSize: "20px", _layout_autoHeight: true, @@ -477,6 +477,7 @@ pie title Minerals in my tap water const reqdStackOpts:DocumentOptions ={ title: "menuItemPanel", childDragAction: dropActionType.same, layout_boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, + _layout_dontCenter: 'y', _chromeHidden: true, _gridGap: 0, _yMargin: 0, _xMargin: 0, _layout_autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, isSystem: true, }; return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); @@ -510,7 +511,7 @@ pie title Minerals in my tap water // doc.myUserBtns = new PrefetchProxy(userBtns); const reqdToolOps:DocumentOptions = { title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0", - layout_explainer: "This is a palette of documents that can be created.", + layout_explainer: "This is a palette of documents that can be created.", _layout_dontCenter: "y", _layout_showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _dragOnlyWithinContainer: true, layout_hideContextMenu: true, _chromeHidden: true, }; return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, userBtns]); @@ -663,7 +664,8 @@ pie title Minerals in my tap water } static stackTools(): Button[] { return [ - { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "V. Align", icon: "pallet", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"vcenter", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"hcenter", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform ] } static cardTools(): Button[] { @@ -777,8 +779,8 @@ pie title Minerals in my tap water } static imageTools() { return [ - { title: "Pixels",toolTip: "Set Native Pixel Sizze", btnType: ButtonType.ClickButton, icon: "portrait", scripts: { onClick: 'imageSetPixelSize();' }}, - { title: "Rotate",toolTip: "Rotate 90", btnType: ButtonType.ClickButton, icon: "redo-alt", scripts: { onClick: 'imageRotate90();' }}, + { title: "Pixels",toolTip: "Set Native Pixel Size", btnType: ButtonType.ClickButton, icon: "portrait", scripts: { onClick: 'imageSetPixelSize();' }}, + { title: "Rotate",toolTip: "Rotate 90", btnType: ButtonType.ClickButton, icon: "redo-alt", scripts: { onClick: 'imageRotate90();' }}, ]; } static contextMenuTools():Button[] { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 9f1c7da3d..393abea53 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -3,7 +3,7 @@ import { faBuffer, faHireAHelper } from '@fortawesome/free-brands-svg-icons'; import * as far from '@fortawesome/free-regular-svg-icons'; import * as fa from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, configure, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { action, computed, configure, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; @@ -707,8 +707,8 @@ export class MainView extends ObservableReactComponent { setupMoveUpEvents( this, e, - action(() => { - SnappingManager.SetPropertiesWidth(Math.max(0, this._dashUIWidth - e.clientX)); + action(moveEv => { + SnappingManager.SetPropertiesWidth(Math.max(0, this._dashUIWidth - moveEv.clientX)); return !SnappingManager.PropertiesWidth; }), action(() => { @@ -807,6 +807,7 @@ export class MainView extends ObservableReactComponent { childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} + dontCenter="y" /> ); @@ -832,6 +833,7 @@ export class MainView extends ObservableReactComponent { }; @computed get mainInnerContent() { + trace(); const leftMenuFlyoutWidth = this._leftMenuFlyoutWidth + this.leftMenuWidth(); const width = this.propertiesWidth() + leftMenuFlyoutWidth; return ( diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 8f0a35df0..dd60bfa65 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -246,6 +246,7 @@ export class SidebarAnnos extends ObservableReactComponent, props: Opt ); }; - const tags = () => props?.DocumentView?.() && CollectionFreeFormDocumentView.from(props.DocumentView()) ? : null; + const tags = () => props?.DocumentView?.() ? : null; return ( <> {paint()} diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 65e70e8fc..8047363d9 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -91,7 +91,7 @@ export class TagItem extends ObservableReactComponent { const tagCollection = TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag); // If the document is of type COLLECTION, make it a smart collection, otherwise, add the tag to the document. - if (doc.type === DocumentType.COL) { + if (doc.type === DocumentType.COL && !doc.annotationOn) { Doc.AddDocToList(tagCollection[DocData], 'collections', doc); // Iterate through the tag Doc collections and add a copy of the document to each collection @@ -257,6 +257,7 @@ export class TagsView extends ObservableReactComponent { super(props); makeObservable(this); } + InsetDist = 25; // how far tag panel is moved up to overlap DocumentView. @observable _panelHeightDirty = 0; @observable _currentInput = ''; @@ -328,7 +329,7 @@ export class TagsView extends ObservableReactComponent { return !this._props.View.Document.showTags ? null : (
r && new ResizeObserver(action(() => (this._props.View.TagPanelHeight = r?.getBoundingClientRect().height ?? 0))).observe(r)} + ref={r => r && new ResizeObserver(action(() => (this._props.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} style={{ transformOrigin: 'top left', maxWidth: `${100 * this.currentScale}%`, @@ -336,6 +337,8 @@ export class TagsView extends ObservableReactComponent { transform: `scale(${1 / this.currentScale})`, backgroundColor: this.isEditing ? Colors.LIGHT_GRAY : Colors.TRANSPARENT, borderColor: this.isEditing ? Colors.BLACK : Colors.TRANSPARENT, + position: 'relative', + top: `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`, }}>
diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx index 30fd9fc2b..0ea9f8ebc 100644 --- a/src/client/views/collections/CollectionCalendarView.tsx +++ b/src/client/views/collections/CollectionCalendarView.tsx @@ -82,6 +82,7 @@ export class CollectionCalendarView extends CollectionSubView() { isAnnotationOverlay={false} // select={emptyFunction} What does this mean? isAnyChildContentActive={returnTrue} // ?? + dontCenter="y" // childDocumentsActive={} // whenChildContentsActiveChanged={} childHideDecorationTitle={false} diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 6225cc52a..6400a0a8e 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -54,11 +54,8 @@ } .collectionStackingViewFieldColumn { - height: max-content; - } - - .collectionStackingViewFieldColumnDragging { - height: 100%; + display: flex; + flex-direction: column; } .collectionSchemaView-previewDoc { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 6402ef16c..2cfe9329a 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -146,7 +146,7 @@ export class CollectionStackingView extends CollectionSubView @@ -343,7 +343,7 @@ export class CollectionStackingView extends CollectionSubView Array.from(this.Sections); // what a section looks like if we're in stacking view sectionStacking = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { @@ -565,6 +571,7 @@ export class CollectionStackingView extends CollectionSubView ); }; diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 5ae08e535..aba6b51d4 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -41,6 +41,7 @@ interface CSVFieldColumnProps { columnWidth: number; numGroupColumns: number; gridGap: number; + dontCenter: 'x' | 'xy' | 'y'; type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined; headings: () => object[]; // I think that stacking view actually has a single column and then supposedly you can add more columns? Unsure @@ -345,15 +346,6 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< - {/* {evContents === noValueHeader ? null : ( -
- - - -
- )} */}
{this._props.Document._columnsHideIfEmpty ? null : headingView} {this.collapsed ? null : ( -
+
headings.indexOf(i) === idx); return (
{ }; docTransform = () => this.refTransform(this._dref?.ContentDiv); getTransform = () => this.refTransform(this._tref.current); - embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1); + embeddedPanelWidth = () => this._props.panelWidth() / (this.treeView._props.NativeDimScaling?.() || 1) - 3 /* paddingRight for bullet */; embeddedPanelHeight = () => { const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.Document))(this.treeView._props.childLayoutTemplate?.()) || this.layoutDoc; return Math.min( diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index 717081666..f9f6c81ab 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -264,6 +264,7 @@ export class FaceCollectionBox extends ViewBoxBaseComponent() { isContentActive={returnTrue} isAnyChildContentActive={returnTrue} childHideDecorations={true} + dontCenter="y" />
); diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 2c7920bdd..934ed6c3e 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -133,10 +133,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { }); // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) { +ScriptingGlobals.add(function showFreeform(attr: 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) { const selected = DocumentView.SelectedDocs().lastElement(); // prettier-ignore - const map: Map<'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'like' | 'star' | 'idea' | 'chat' | '1' | '2' | '3' | '4', + const map: Map<'flashcards' | 'hcenter' | 'vcenter' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce' | 'time' | 'docType' | 'color' | 'links' | 'like' | 'star' | 'idea' | 'chat' | '1' | '2' | '3' | '4', { waitForRender?: boolean; checkResult: (doc: Doc) => boolean; @@ -158,9 +158,13 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' else (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce(); }, }], - ['center', { - checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false), - setDoc: (doc:Doc) => { doc._stacking_alignCenter = !doc._stacking_alignCenter; }, + ['vcenter', { + checkResult: (doc:Doc) => !StrCast(doc?._layout_dontCenter).includes('y'), + setDoc: (doc:Doc) => { doc._layout_dontCenter = StrCast(doc.layout_dontCenter).includes('y') ? StrCast(doc.layout_dontCenter).replace(/y/,"") : StrCast(doc.layout_dontCenter) + 'y'; }, + }], + ['hcenter', { + checkResult: (doc:Doc) => !StrCast(doc?._layout_dontCenter).includes('x'), + setDoc: (doc:Doc) => { doc._layout_dontCenter = StrCast(doc.layout_dontCenter).includes('x') ? StrCast(doc.layout_dontCenter).replace(/x/,"") : 'x'+ StrCast(doc.layout_dontCenter); }, }], ['clusters', { waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 23dada260..2873b4bba 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -242,7 +242,7 @@ .contentFittingDocumentView { position: relative; - display: flex; + display: block; width: 100%; height: 100%; transition: inherit; 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/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() { @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() { 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(() => { -- cgit v1.2.3-70-g09d2 From dc81c08086446df700185a0ad0a8ee8f16e06ab7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 11 Sep 2024 09:35:10 -0400 Subject: fixed stacking view to used computedFn's to avoid invalidations and resotred DocumentView to flex layout by fixing stacking view's css --- .../views/collections/CollectionStackingView.tsx | 51 ++++++++++------------ src/client/views/nodes/DocumentView.scss | 2 +- 2 files changed, 24 insertions(+), 29 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 2cfe9329a..e97ee713e 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -35,6 +35,7 @@ import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; import './CollectionStackingView.scss'; import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { computedFn } from 'mobx-utils'; export type collectionStackingViewProps = { sortFunc?: (a: Doc, b: Doc) => number; @@ -127,6 +128,9 @@ export class CollectionStackingView extends CollectionSubView this.columnWidth; + columnDocHeightFn = (doc: Doc) => () => (this.isStackingView ? this.getDocHeight(doc)() : Math.min(this.getDocHeight(doc)(), this._props.PanelHeight())); + // TODO: plj - these are the children children = (docs: Doc[]) => { // TODO: can somebody explain me to what exactly TraceMobX is? @@ -140,17 +144,14 @@ export class CollectionStackingView extends CollectionSubView { - const height = () => this.getDocHeight(d); - const width = () => this.getDocWidth(d); - const trans = () => this.getDocTransition(d); // assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns - const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); + const rowSpan = Math.ceil((this.getDocHeight(d)() + this.gridGap) / this.gridGap); // just getting the style - const style = this.isStackingView ? { margin: this.dontCenter.includes('x') ? undefined : 'auto', transition: trans(), width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; + const style = this.isStackingView ? { margin: undefined, transition: this.getDocTransition(d)(), width: this.columnWidth, marginTop: i ? this.gridGap : 0, height: this.getDocHeight(d)() } : { gridRowEnd: `span ${rowSpan}` }; // So we're choosing whether we're going to render a column or a masonry doc return (
- {this.getDisplayDoc(d, width, trans, i)} + {this.getDisplayDoc(d, this.getDocTransition(d), i)}
); }); @@ -311,26 +312,23 @@ export class CollectionStackingView extends CollectionSubView (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined); @observable docRefs = new ObservableMap(); childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null)); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list - getDisplayDoc(doc: Doc, width: () => number, trans: () => string, count: number) { + getDisplayDoc(doc: Doc, trans: () => string, count: number) { const dataDoc = doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined; - const height = () => this.getDocHeight(doc); - const panelHeight = () => (this.isStackingView ? height() : Math.min(height(), this._props.PanelHeight())); - const panelWidth = () => this.columnWidth; - const stackedDocTransform = () => this.getDocTransform(doc); - this._docXfs.push({ stackedDocTransform, width, height }); + this._docXfs.push({ stackedDocTransform: this.getDocTransform(doc), width: this.getDocWidth(doc), height: this.getDocHeight(doc) }); return count > this._renderCount ? null : ( r?.ContentDiv && this.docRefs.set(doc, r))} Document={doc} TemplateDataDocument={dataDoc} renderDepth={this._props.renderDepth + 1} - PanelWidth={panelWidth} - PanelHeight={panelHeight} + PanelWidth={this.columnWidthFn} + PanelHeight={this.columnDocHeightFn(doc)} pointerEvents={this.DocumentView?.()._props.onClickScript?.() ? returnNone : undefined} // if the stack has an onClick, then we don't want the contents to be interactive (see CollectionPileView) styleProvider={this.styleProvider} containerViewPath={this.childContainerViewPath} @@ -341,8 +339,8 @@ export class CollectionStackingView extends CollectionSubView () => { // these must be referenced for document decorations to update when the text box container is scrolled this._scroll; this._props.ScreenToLocalTransform(); @@ -381,8 +379,8 @@ export class CollectionStackingView extends CollectionSubView () => { if (!d) return 0; const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); const maxWidth = this.columnWidth / this.numGroupColumns; @@ -390,12 +388,9 @@ export class CollectionStackingView extends CollectionSubView () => StrCast(d?.dataTransition)); + getDocHeight = computedFn((d?: Doc) => () => { if (!d || d.hidden) return 0; const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument; @@ -404,13 +399,13 @@ export class CollectionStackingView extends CollectionSubView { diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 2873b4bba..23dada260 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -242,7 +242,7 @@ .contentFittingDocumentView { position: relative; - display: block; + display: flex; width: 100%; height: 100%; transition: inherit; -- cgit v1.2.3-70-g09d2 From d3817d1c03b68ab2c69ab5ccfb5e8d6943df8f25 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 11 Sep 2024 10:32:18 -0400 Subject: fixed comparisonBox to honor fitWidth of Docs. fixed dropping on empty muliticolumnview. --- src/client/views/collections/CollectionStackingViewFieldColumn.tsx | 2 +- .../collectionMulticolumn/CollectionMulticolumnView.scss | 1 + src/client/views/nodes/ComparisonBox.tsx | 7 ++++--- 3 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index aba6b51d4..ed0cabd0a 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -373,7 +373,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< style={{ padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`, margin: this._props.dontCenter.includes('x') ? undefined : 'auto', - width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, + // width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, height: 'max-content', position: 'relative', gridGap: this._props.gridGap, diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss index 06d78c39e..9ed247d50 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss @@ -1,5 +1,6 @@ .collectionMulticolumnView_drop { height: 100%; + width: 100%; top: 0; left: 0; position: absolute; 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() () 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} -- cgit v1.2.3-70-g09d2