From 8e2cc917ab7cb913755aabe9aecce6d4bb157a0a Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 4 Sep 2024 09:03:13 -0400 Subject: updated filter panel to allow filtering by tags --- src/client/documents/Documents.ts | 1 + src/client/views/FilterPanel.tsx | 26 +++++++++++--------------- 2 files changed, 12 insertions(+), 15 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index af181b031..1acc6bb46 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -450,6 +450,7 @@ export class DocumentOptions { onDragStart?: ScriptField; // script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop target?: Doc; // available for use in scripts. used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script) tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true); + tags_chat?: LISTt = new ListInfo('hashtags added to document by chatGPT', true); treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view'); treeView_HideUnrendered?: BOOLt = new BoolInfo("tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeView_FieldKey tag (presBox)"); treeView_HideHeaderIfTemplate?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)'); diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index b11fa3bd5..2f6d1fbaa 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -192,21 +192,17 @@ export class FilterPanel extends ObservableReactComponent { const allCollectionDocs = new Set(); SearchUtil.foreachRecursiveDoc(this.targetDocChildren, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); const set = new Set([...StrListCast(this.Document.childFilters).map(filter => filter.split(Doc.FilterSep)[1]), Doc.FilterNone, Doc.FilterAny]); - if (facetHeader === 'tags') - allCollectionDocs.forEach(child => - StrListCast(child[facetHeader]) - .filter(h => h) - .forEach(key => set.add(key)) - ); - else - allCollectionDocs.forEach(child => { - const fieldVal = child[facetHeader] as FieldType; - if (!(fieldVal instanceof List)) { - // currently we have no good way of filtering based on a field that is a list - set.add(Field.toString(fieldVal)); - (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString()); - } - }); + + allCollectionDocs.forEach(child => { + const fieldVal = child[facetHeader] as FieldType; + const fieldStrList = StrListCast(child[facetHeader]).filter(h => h); + if (fieldStrList.length) fieldStrList.forEach(key => set.add(key)); + else if (!(fieldVal instanceof List)) { + // currently we have no good way of filtering based on a field that is a list + set.add(Field.toString(fieldVal)); + (fieldVal === true || fieldVal === false) && set.add((!fieldVal).toString()); + } + }); const facetValues = Array.from(set).filter(v => v); let nonNumbers = 0; -- cgit v1.2.3-70-g09d2 From 158d501642b0183b286913eb396c396922166435 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 4 Sep 2024 09:47:38 -0400 Subject: prevent tags from getting really big --- src/client/views/TagsView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 0ac015b36..7723da900 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -266,7 +266,7 @@ export class TagsView extends ObservableReactComponent { } @computed get currentScale() { - return NumCast(DocCast(this._props.View.Document.embedContainer)?._freeform_scale, 1); + return Math.max(1, 1 / this._props.View.screenToLocalScale()); } @computed get isEditing() { return this._isEditing && DocumentView.SelectedDocs().includes(this._props.View.Document); -- 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') 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') 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') 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') 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') 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') 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') 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 85447fcdb901d1d03ac969e71e97520e1bb98cbb Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 6 Sep 2024 13:57:03 -0400 Subject: removed calendar button. added metadata value input via tagsview --- src/client/views/DocumentButtonBar.tsx | 18 ------------------ src/client/views/TagsView.tsx | 15 +++++++++------ 2 files changed, 9 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 58b7f207c..437ef045f 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -263,23 +263,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( ); } - @computed - get calendarButton() { - const targetDoc = this.view0?.Document; - return !targetDoc ? null : ( - Open calendar menu
}> -
{ - CalendarManager.Instance.open(this.view0, targetDoc); - }}> - -
- - ); - } - @computed get keywordButton() { return !DocumentView.Selected().length ? null : ( @@ -460,7 +443,6 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( {!DocumentView.Selected().some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
{this.recordButton}
-
{this.calendarButton}
{this.keywordButton}
{!Doc.UserDoc().documentLinksButton_fullMenu ? null :
{this.shareButton}
}
{this.menuButton}
diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 7723da900..2d583d392 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -283,15 +283,18 @@ export class TagsView extends ObservableReactComponent { }; /** - * Adds the specified tag to the Doc. If the tag is not prefixed with '#', then a '#' prefix is added. - * Whne the tag (after the '#') begins with '@', then a metadata key/value pair is displayed instead of - * just the tag. - * @param tag tag string to add + * Adds the specified tag or metadata to the Doc. If the tag is not prefixed with '#', then a '#' prefix is added. + * When the tag (after the '#') begins with '@', then a metadata key/value pair is displayed instead of + * just the tag. In addition, a suffix of : can be added to set a metadata value + * @param tag tag string to add (format: # | #@field(:(=)?value)? ) */ submitTag = undoable( action((tag: string) => { - const submittedLabel = tag.trim(); - submittedLabel && TagItem.addTagToDoc(this._props.View.Document, '#' + submittedLabel.replace(/^#/, '')); + const submittedLabel = tag.trim().replace(/^#/, '').split(':'); + if (submittedLabel[0]) { + TagItem.addTagToDoc(this._props.View.Document, '#' + submittedLabel[0]); + if (submittedLabel.length > 1) Doc.SetField(this._props.View.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]); + } this._currentInput = ''; // Clear the input box }), 'added doc label' -- cgit v1.2.3-70-g09d2 From 8b4de19e9944bbac2f41a621c5369bedca43db52 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 6 Sep 2024 15:20:51 -0400 Subject: fixed inclusion on typescript type_decls for scripting.ts by fixing .gitignore --- .gitignore | 2 +- src/client/util/node_modules/type_decls.d | 224 ++++++++++++++++++++++++++++++ src/client/views/TagsView.tsx | 2 +- src/typings/type_decls.d | 224 ------------------------------ 4 files changed, 226 insertions(+), 226 deletions(-) create mode 100644 src/client/util/node_modules/type_decls.d delete mode 100644 src/typings/type_decls.d (limited to 'src') diff --git a/.gitignore b/.gitignore index 6a1963b4e..26c504059 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -node_modules +/node_modules dist/ .DS_Store .env diff --git a/src/client/util/node_modules/type_decls.d b/src/client/util/node_modules/type_decls.d new file mode 100644 index 000000000..1a93bbe59 --- /dev/null +++ b/src/client/util/node_modules/type_decls.d @@ -0,0 +1,224 @@ +//@ts-ignore +declare type PropertyKey = string | number | symbol; +interface Array { + length: number; + toString(): string; + toLocaleString(): string; + pop(): T | undefined; + push(...items: T[]): number; + concat(...items: ConcatArray[]): T[]; + concat(...items: (T | ConcatArray)[]): T[]; + join(separator?: string): string; + reverse(): T[]; + shift(): T | undefined; + slice(start?: number, end?: number): T[]; + sort(compareFn?: (a: T, b: T) => number): this; + splice(start: number, deleteCount?: number): T[]; + splice(start: number, deleteCount: number, ...items: T[]): T[]; + unshift(...items: T[]): number; + indexOf(searchElement: T, fromIndex?: number): number; + lastIndexOf(searchElement: T, fromIndex?: number): number; + every(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean; + some(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean; + forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void; + map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; + filter(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[]; + filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[]; + reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; + reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; + reduce(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; + reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; + reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; + reduceRight(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; + + [n: number]: T; +} + +interface Function { + apply(this: Function, thisArg: any, argArray?: any): any; + call(this: Function, thisArg: any, ...argArray: any[]): any; + bind(this: Function, thisArg: any, ...argArray: any[]): any; + toString(): string; + + prototype: any; + readonly length: number; + + // Non-standard extensions + arguments: any; + caller: Function; +} +interface Boolean { + valueOf(): boolean; +} +interface Number { + toString(radix?: number): string; + toFixed(fractionDigits?: number): string; + toExponential(fractionDigits?: number): string; + toPrecision(precision?: number): string; + valueOf(): number; +} +interface IArguments { + [index: number]: any; + length: number; + callee: Function; +} +interface RegExp { + readonly flags: string; + readonly sticky: boolean; + readonly unicode: boolean; +} +interface Date { + now() : string; +} +interface String { + codePointAt(pos: number): number | undefined; + includes(searchString: string, position?: number): boolean; + endsWith(searchString: string, endPosition?: number): boolean; + normalize(form: "NFC" | "NFD" | "NFKC" | "NFKD"): string; + normalize(form?: string): string; + repeat(count: number): string; + replace(a:any, b:any):string; // bcz: fix this + startsWith(searchString: string, position?: number): boolean; + anchor(name: string): string; + big(): string; + blink(): string; + bold(): string; + fixed(): string; + fontcolor(color: string): string; + fontsize(size: number): string; + fontsize(size: string): string; + italics(): string; + link(url: string): string; + small(): string; + strike(): string; + sub(): string; + sup(): string; +} +interface Object { + constructor: Function; + toString(): string; + toLocaleString(): string; + valueOf(): Object; + hasOwnProperty(v: PropertyKey): boolean; + isPrototypeOf(v: Object): boolean; + propertyIsEnumerable(v: PropertyKey): boolean; +} +interface ConcatArray { + readonly length: number; + readonly [n: number]: T; + join(separator?: string): string; + slice(start?: number, end?: number): T[]; +} +interface URL { + hash: string; + host: string; + hostname: string; + href: string; + readonly origin: string; + password: string; + pathname: string; + port: string; + protocol: string; + search: string; + username: string; + toJSON(): string; +} +interface PromiseLike { + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): PromiseLike; +} +interface Promise { + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise; + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise; +} + +declare const Update: unique symbol; +declare const Self: unique symbol; +declare const SelfProxy: unique symbol; +declare const DataSym: unique symbol; +declare const HandleUpdate: unique symbol; +declare const Id: unique symbol; +declare const OnUpdate: unique symbol; +declare const Parent: unique symbol; +declare const Copy: unique symbol; +declare const ToScriptString: unique symbol; + +declare abstract class RefField { + readonly [Id]: FieldId; + + constructor(); +} + +declare type FieldId = string; + +declare abstract class ObjectField { + abstract [Copy](): ObjectField; +} + +declare abstract class URLField extends ObjectField { + readonly url: URL; + + constructor(url: string); + constructor(url: URL); +} + +declare class RichTextField extends URLField { + [Copy](): ObjectField; + constructor(data:string, text: string); +} +declare class AudioField extends URLField { [Copy](): ObjectField; } +declare class VideoField extends URLField { [Copy](): ObjectField; } +declare class ImageField extends URLField { [Copy](): ObjectField; } +declare class WebField extends URLField { [Copy](): ObjectField; } +declare class PdfField extends URLField { [Copy](): ObjectField; } + +declare const ComputedField: any; +declare const CompileScript: any; + +// @ts-ignore +declare type Extract = T extends U ? T : never; +declare type Field = number | string | boolean | ObjectField | RefField; +declare type FieldWaiting = T extends undefined ? never : Promise; +declare type FieldResult = Opt | FieldWaiting>; + +declare type Opt = T | undefined; +declare class Doc extends RefField { + constructor(); + + [key: string]: FieldResult; + // [ToScriptString](): string; +} + +declare class List extends ObjectField { + constructor(fields?: T[]); + [index: number]: T | (T extends RefField ? Promise : never); + [Copy](): ObjectField; +} + +declare class InkField extends ObjectField { + constructor(data:Array<{X:number, Y:number}>); + [Copy](): ObjectField; +} + +// @ts-ignore +declare const console: any; + +interface DocumentOptions { } + +declare const Docs: { + ImageDocument(url: string, options?: DocumentOptions): Doc; + VideoDocument(url: string, options?: DocumentOptions): Doc; + TextDocument(options?: DocumentOptions): Doc; + PdfDocument(url: string, options?: DocumentOptions): Doc; + WebDocument(url: string, options?: DocumentOptions): Doc; + HtmlDocument(html: string, options?: DocumentOptions): Doc; + MapDocument(url: string, options?: DocumentOptions): Doc; + KVPDocument(document: Doc, options?: DocumentOptions): Doc; + FreeformDocument(documents: Doc[], options?: DocumentOptions): Doc; + SchemaDocument(columns: string[], documents: Doc[], options?: DocumentOptions): Doc; + TreeDocument(documents: Doc[], options?: DocumentOptions): Doc; + StackingDocument(documents: Doc[], options?: DocumentOptions): Doc; +}; + +declare function idToDoc(id:string):any; +declare function assignDoc(doc:Doc, field:any, id:any):string; +declare function d(...args:any[]):any; diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 2d583d392..3041de902 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -9,7 +9,7 @@ import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; -import { DocCast, NumCast, StrCast } from '../../fields/Types'; +import { DocCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; import { DragManager } from '../util/DragManager'; import { SnappingManager } from '../util/SnappingManager'; diff --git a/src/typings/type_decls.d b/src/typings/type_decls.d deleted file mode 100644 index 1a93bbe59..000000000 --- a/src/typings/type_decls.d +++ /dev/null @@ -1,224 +0,0 @@ -//@ts-ignore -declare type PropertyKey = string | number | symbol; -interface Array { - length: number; - toString(): string; - toLocaleString(): string; - pop(): T | undefined; - push(...items: T[]): number; - concat(...items: ConcatArray[]): T[]; - concat(...items: (T | ConcatArray)[]): T[]; - join(separator?: string): string; - reverse(): T[]; - shift(): T | undefined; - slice(start?: number, end?: number): T[]; - sort(compareFn?: (a: T, b: T) => number): this; - splice(start: number, deleteCount?: number): T[]; - splice(start: number, deleteCount: number, ...items: T[]): T[]; - unshift(...items: T[]): number; - indexOf(searchElement: T, fromIndex?: number): number; - lastIndexOf(searchElement: T, fromIndex?: number): number; - every(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean; - some(callbackfn: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean; - forEach(callbackfn: (value: T, index: number, array: T[]) => void, thisArg?: any): void; - map(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[]; - filter(callbackfn: (value: T, index: number, array: T[]) => value is S, thisArg?: any): S[]; - filter(callbackfn: (value: T, index: number, array: T[]) => any, thisArg?: any): T[]; - reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; - reduce(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; - reduce(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; - reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T): T; - reduceRight(callbackfn: (previousValue: T, currentValue: T, currentIndex: number, array: T[]) => T, initialValue: T): T; - reduceRight(callbackfn: (previousValue: U, currentValue: T, currentIndex: number, array: T[]) => U, initialValue: U): U; - - [n: number]: T; -} - -interface Function { - apply(this: Function, thisArg: any, argArray?: any): any; - call(this: Function, thisArg: any, ...argArray: any[]): any; - bind(this: Function, thisArg: any, ...argArray: any[]): any; - toString(): string; - - prototype: any; - readonly length: number; - - // Non-standard extensions - arguments: any; - caller: Function; -} -interface Boolean { - valueOf(): boolean; -} -interface Number { - toString(radix?: number): string; - toFixed(fractionDigits?: number): string; - toExponential(fractionDigits?: number): string; - toPrecision(precision?: number): string; - valueOf(): number; -} -interface IArguments { - [index: number]: any; - length: number; - callee: Function; -} -interface RegExp { - readonly flags: string; - readonly sticky: boolean; - readonly unicode: boolean; -} -interface Date { - now() : string; -} -interface String { - codePointAt(pos: number): number | undefined; - includes(searchString: string, position?: number): boolean; - endsWith(searchString: string, endPosition?: number): boolean; - normalize(form: "NFC" | "NFD" | "NFKC" | "NFKD"): string; - normalize(form?: string): string; - repeat(count: number): string; - replace(a:any, b:any):string; // bcz: fix this - startsWith(searchString: string, position?: number): boolean; - anchor(name: string): string; - big(): string; - blink(): string; - bold(): string; - fixed(): string; - fontcolor(color: string): string; - fontsize(size: number): string; - fontsize(size: string): string; - italics(): string; - link(url: string): string; - small(): string; - strike(): string; - sub(): string; - sup(): string; -} -interface Object { - constructor: Function; - toString(): string; - toLocaleString(): string; - valueOf(): Object; - hasOwnProperty(v: PropertyKey): boolean; - isPrototypeOf(v: Object): boolean; - propertyIsEnumerable(v: PropertyKey): boolean; -} -interface ConcatArray { - readonly length: number; - readonly [n: number]: T; - join(separator?: string): string; - slice(start?: number, end?: number): T[]; -} -interface URL { - hash: string; - host: string; - hostname: string; - href: string; - readonly origin: string; - password: string; - pathname: string; - port: string; - protocol: string; - search: string; - username: string; - toJSON(): string; -} -interface PromiseLike { - then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): PromiseLike; -} -interface Promise { - then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): Promise; - catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): Promise; -} - -declare const Update: unique symbol; -declare const Self: unique symbol; -declare const SelfProxy: unique symbol; -declare const DataSym: unique symbol; -declare const HandleUpdate: unique symbol; -declare const Id: unique symbol; -declare const OnUpdate: unique symbol; -declare const Parent: unique symbol; -declare const Copy: unique symbol; -declare const ToScriptString: unique symbol; - -declare abstract class RefField { - readonly [Id]: FieldId; - - constructor(); -} - -declare type FieldId = string; - -declare abstract class ObjectField { - abstract [Copy](): ObjectField; -} - -declare abstract class URLField extends ObjectField { - readonly url: URL; - - constructor(url: string); - constructor(url: URL); -} - -declare class RichTextField extends URLField { - [Copy](): ObjectField; - constructor(data:string, text: string); -} -declare class AudioField extends URLField { [Copy](): ObjectField; } -declare class VideoField extends URLField { [Copy](): ObjectField; } -declare class ImageField extends URLField { [Copy](): ObjectField; } -declare class WebField extends URLField { [Copy](): ObjectField; } -declare class PdfField extends URLField { [Copy](): ObjectField; } - -declare const ComputedField: any; -declare const CompileScript: any; - -// @ts-ignore -declare type Extract = T extends U ? T : never; -declare type Field = number | string | boolean | ObjectField | RefField; -declare type FieldWaiting = T extends undefined ? never : Promise; -declare type FieldResult = Opt | FieldWaiting>; - -declare type Opt = T | undefined; -declare class Doc extends RefField { - constructor(); - - [key: string]: FieldResult; - // [ToScriptString](): string; -} - -declare class List extends ObjectField { - constructor(fields?: T[]); - [index: number]: T | (T extends RefField ? Promise : never); - [Copy](): ObjectField; -} - -declare class InkField extends ObjectField { - constructor(data:Array<{X:number, Y:number}>); - [Copy](): ObjectField; -} - -// @ts-ignore -declare const console: any; - -interface DocumentOptions { } - -declare const Docs: { - ImageDocument(url: string, options?: DocumentOptions): Doc; - VideoDocument(url: string, options?: DocumentOptions): Doc; - TextDocument(options?: DocumentOptions): Doc; - PdfDocument(url: string, options?: DocumentOptions): Doc; - WebDocument(url: string, options?: DocumentOptions): Doc; - HtmlDocument(html: string, options?: DocumentOptions): Doc; - MapDocument(url: string, options?: DocumentOptions): Doc; - KVPDocument(document: Doc, options?: DocumentOptions): Doc; - FreeformDocument(documents: Doc[], options?: DocumentOptions): Doc; - SchemaDocument(columns: string[], documents: Doc[], options?: DocumentOptions): Doc; - TreeDocument(documents: Doc[], options?: DocumentOptions): Doc; - StackingDocument(documents: Doc[], options?: DocumentOptions): Doc; -}; - -declare function idToDoc(id:string):any; -declare function assignDoc(doc:Doc, field:any, id:any):string; -declare function d(...args:any[]):any; -- 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') 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 950651b904f9bb6843f15eda1322025e84015f3c Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 6 Sep 2024 15:49:08 -0400 Subject: added metadata toggle for booleans in tagsView --- src/client/views/TagsView.tsx | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 3041de902..9574bd30e 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -216,7 +216,11 @@ export class TagItem extends ObservableReactComponent { {metadata ? ( {tag}  - {Field.toString(this._props.doc[metadata])} + {typeof this._props.doc[metadata] === 'boolean' ? ( + e.stopPropagation()} onPointerDown={e => e.stopPropagation()} onChange={e => (this._props.doc[metadata] = !this._props.doc[metadata])} checked={this._props.doc[metadata] as boolean} /> + ) : ( + Field.toString(this._props.doc[metadata]) + )} ) : ( tag -- cgit v1.2.3-70-g09d2 From 3fe153d3063c197403502590baaabbab1cec5c74 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 6 Sep 2024 16:53:57 -0400 Subject: added margins for carousel view. allow fordoc decorations to be turned off for carousel items. --- src/client/documents/Documents.ts | 1 + src/client/views/PropertiesView.tsx | 4 +++- src/client/views/collections/CollectionCarouselView.tsx | 13 +++++++++---- 3 files changed, 13 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d5a7b0465..4a377a034 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -248,6 +248,7 @@ export class DocumentOptions { layout_hideResizeHandles?: BOOLt = new BoolInfo('whether to hide the resize handles when selected'); layout_hideLinkButton?: BOOLt = new BoolInfo('whether the blue link counter button should be hidden'); layout_hideDecorationTitle?: BOOLt = new BoolInfo('whether to suppress the document decortations title when selected'); + layout_hideDecorations?: BOOLt = new BoolInfo('whether to suppress all document decortations when selected'); _layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown'); layout_diagramEditor?: STRt = new StrInfo('specify the JSX string for a diagram editor view'); layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown'); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index daa8e1720..f42838086 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -138,7 +138,9 @@ export class PropertiesView extends ObservableReactComponent (!this.selectedLayoutDoc ? 0 : Math.min(NumCast(this.selectedLayoutDoc?._width), this._props.width - 20)); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 9f59322e8..a1c59d238 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import { StopEvent, returnFalse, returnOne, returnTrue, returnZero } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; @@ -167,7 +167,7 @@ export class CollectionCarouselView extends CollectionSubView() { const childValue = doc?.['caption_' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined; return childValue ?? this._props.styleProvider?.(this.layoutDoc, captionProps, property); }; - panelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0); + contentPanelHeight = () => this._props.PanelHeight() - (StrCast(this.layoutDoc._layout_showCaption) ? 50 : 0) - 2 * NumCast(this.layoutDoc.yMargin); onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); onContentClick = () => ScriptCast(this.layoutDoc.onChildClick); captionWidth = () => this._props.PanelWidth() - 2 * this.marginX; @@ -197,7 +197,9 @@ export class CollectionCarouselView extends CollectionSubView() { // Doc.setDocFilter(this.Document, 'data_star', true, 'remove'); } }; + contentScreentToLocalXf = () => this._props.ScreenToLocalTransform().translate(-NumCast(this.layoutDoc.xMargin), -NumCast(this.layoutDoc.yMargin)); + contentPanelWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc.xMargin); @computed get content() { trace(); if (this.layoutDoc._carousel_index === this.carouselItems.length && this.layoutDoc._carousel_index !== 0) { @@ -210,7 +212,7 @@ export class CollectionCarouselView extends CollectionSubView() { return !(curDoc?.layout instanceof Doc) ? null : ( <> -
+
{!carouselShowsCaptions ? null : ( -- cgit v1.2.3-70-g09d2 From adc87fe2ffc8d8d8c6a71a368b060f0ad14de2ca Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 6 Sep 2024 16:54:25 -0400 Subject: added undo for toggling boolean tags --- src/client/views/TagsView.tsx | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 9574bd30e..65e70e8fc 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -217,7 +217,13 @@ export class TagItem extends ObservableReactComponent { {tag}  {typeof this._props.doc[metadata] === 'boolean' ? ( - e.stopPropagation()} onPointerDown={e => e.stopPropagation()} onChange={e => (this._props.doc[metadata] = !this._props.doc[metadata])} checked={this._props.doc[metadata] as boolean} /> + e.stopPropagation()} + onPointerDown={e => e.stopPropagation()} + onChange={undoable(e => (this._props.doc[metadata] = !this._props.doc[metadata]), 'metadata toggle')} + checked={this._props.doc[metadata] as boolean} + /> ) : ( Field.toString(this._props.doc[metadata]) )} -- 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') 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') 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 498201bb8d77192e4d4f6dcd04d6a6c2d9a3de73 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 11 Sep 2024 09:38:45 -0400 Subject: fixed multirowview resizer width --- .../views/collections/collectionMulticolumn/CollectionMultirowView.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss index 0d49fabaa..91779065d 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss @@ -1,5 +1,6 @@ .collectionMultirowView_drop { height: 100%; + width: 100%; top: 0; left: 0; position: absolute; -- 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') 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 From 623da0b4eec34fbd238cc26a5e0c105426a6711e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 12 Sep 2024 09:50:31 -0400 Subject: added tags for multi-selections. --- src/client/views/DocumentButtonBar.tsx | 10 +++- src/client/views/DocumentDecorations.scss | 7 +++ src/client/views/DocumentDecorations.tsx | 11 +++- src/client/views/StyleProvider.tsx | 2 +- src/client/views/TagsView.tsx | 65 ++++++++++++++-------- .../collectionFreeForm/FaceCollectionBox.tsx | 2 +- 6 files changed, 69 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 437ef045f..785e69f84 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -267,7 +267,15 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( get keywordButton() { return !DocumentView.Selected().length ? null : ( Open keyword menu
}> -
DocumentView.Selected().map(dv => (dv.dataDoc.showTags = !dv.dataDoc.showTags))}> +
{ + const showing = DocumentView.Selected().some(dv => dv.dataDoc.showTags); + DocumentView.Selected().forEach(dv => { + dv.dataDoc.showTags = !showing; + }); + }}>
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 67e1054c3..346df10d5 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -38,6 +38,13 @@ $resizeHandler: 8px; background: green; border-radius: 50%; } + .documentDecorations-tagsView { + position: absolute; + height: 100%; + pointer-events: all; + border-radius: 50%; + color: black; + } } .documentDecorations-container { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index da35459bb..118a5bd3d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -35,6 +35,7 @@ import { DocumentView } from './nodes/DocumentView'; import { ImageBox } from './nodes/ImageBox'; import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { TagsView } from './TagsView'; interface DocumentDecorationsProps { PanelWidth: number; @@ -835,12 +836,20 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? 0 : `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> DocumentView.Selected()} />
)} +
+ {DocumentView.Selected().length > 1 ? : null} +
{useRotation && ( diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 513953d17..262f888fb 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -365,7 +365,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt ); }; - const tags = () => 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 8047363d9..ae9d42749 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -33,7 +33,7 @@ import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; */ interface TagItemProps { - doc: Doc; + docs: Doc[]; tag: string; tagDoc: Opt; showRemoveUI: boolean; @@ -207,8 +207,12 @@ export class TagItem extends ObservableReactComponent { e.preventDefault(); }; + @computed get doc() { + return this._props.docs.lastElement(); + } + render() { - this._props.tagDoc && setTimeout(() => TagItem.addTagToDoc(this._props.doc, this._props.tag)); // bcz: hack to make sure that Docs are added to their tag Doc collection since metadata can get set anywhere without a guard triggering an add to the collection + this._props.tagDoc && setTimeout(() => this._props.docs.forEach(doc => TagItem.addTagToDoc(doc, this._props.tag))); // bcz: hack to make sure that Docs are added to their tag Doc collection since metadata can get set anywhere without a guard triggering an add to the collection const tag = this._props.tag.replace(/^#/, ''); const metadata = tag.startsWith('@') ? tag.replace(/^@/, '') : ''; return ( @@ -216,16 +220,16 @@ export class TagItem extends ObservableReactComponent { {metadata ? ( {tag}  - {typeof this._props.doc[metadata] === 'boolean' ? ( + {typeof this.doc[metadata] === 'boolean' ? ( e.stopPropagation()} onPointerDown={e => e.stopPropagation()} - onChange={undoable(e => (this._props.doc[metadata] = !this._props.doc[metadata]), 'metadata toggle')} - checked={this._props.doc[metadata] as boolean} + onChange={undoable(e => (this.doc[metadata] = !this.doc[metadata]), 'metadata toggle')} + checked={this.doc[metadata] as boolean} /> ) : ( - Field.toString(this._props.doc[metadata]) + Field.toString(this.doc[metadata]) )} ) : ( @@ -234,7 +238,7 @@ export class TagItem extends ObservableReactComponent { {this.props.showRemoveUI && this._props.tagDoc && ( TagItem.removeTagFromDoc(this._props.doc, this._props.tag, this._props.tagDoc), `remove tag ${this._props.tag}`)} + onPointerDown={undoable(() => this._props.docs.forEach(doc => TagItem.removeTagFromDoc(doc, this._props.tag, this._props.tagDoc)), `remove tag ${this._props.tag}`)} icon={} style={{ width: '8px', height: '8px', marginLeft: '10px' }} /> @@ -245,7 +249,7 @@ export class TagItem extends ObservableReactComponent { } interface TagViewProps { - View: DocumentView; + Views: DocumentView[]; } /** @@ -261,12 +265,12 @@ export class TagsView extends ObservableReactComponent { @observable _panelHeightDirty = 0; @observable _currentInput = ''; - @observable _isEditing = !StrListCast(this._props.View.dataDoc.tags).length; + @observable _isEditing = !StrListCast(this.View.dataDoc.tags).length; _heightDisposer: IReactionDisposer | undefined; componentDidMount() { this._heightDisposer = reaction( - () => this._props.View.screenToContentsTransform(), + () => this.View.screenToContentsTransform(), xf => { this._panelHeightDirty = this._panelHeightDirty + 1; } @@ -276,11 +280,15 @@ export class TagsView extends ObservableReactComponent { this._heightDisposer?.(); } + @computed get View() { + return this._props.Views.lastElement(); + } + @computed get currentScale() { - return Math.max(1, 1 / this._props.View.screenToLocalScale()); + return this._props.Views.length > 1 ? 1 : Math.max(1, 1 / this.View.screenToLocalScale()); } @computed get isEditing() { - return this._isEditing && DocumentView.SelectedDocs().includes(this._props.View.Document); + return this._isEditing && (this._props.Views.length > 1 || DocumentView.SelectedDocs().includes(this.View.Document)); } /** @@ -290,7 +298,7 @@ export class TagsView extends ObservableReactComponent { @action setToEditing = (editing = true) => { this._isEditing = editing; - editing && this._props.View.select(false); + editing && this._props.Views.length === 1 && this.View.select(false); }; /** @@ -303,8 +311,10 @@ export class TagsView extends ObservableReactComponent { action((tag: string) => { const submittedLabel = tag.trim().replace(/^#/, '').split(':'); if (submittedLabel[0]) { - TagItem.addTagToDoc(this._props.View.Document, '#' + submittedLabel[0]); - if (submittedLabel.length > 1) Doc.SetField(this._props.View.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]); + this._props.Views.forEach(view => { + TagItem.addTagToDoc(view.Document, '#' + submittedLabel[0]); + if (submittedLabel.length > 1) Doc.SetField(view.Document, submittedLabel[0].replace(/^@/, ''), ':' + submittedLabel[1]); + }); } this._currentInput = ''; // Clear the input box }), @@ -316,20 +326,20 @@ export class TagsView extends ObservableReactComponent { * When the dropdown is clicked, this will toggle an extended UI that allows additional tags to be added/removed. */ render() { - const tagsList = new Set(StrListCast(this._props.View.dataDoc.tags)); - const chatTagsList = new Set(StrListCast(this._props.View.dataDoc.tags_chat)); + const tagsList = new Set(StrListCast(this.View.dataDoc.tags)); + const chatTagsList = new Set(StrListCast(this.View.dataDoc.tags_chat)); const facesList = new Set( - DocListCast(this._props.View.dataDoc[Doc.LayoutFieldKey(this._props.View.Document) + '_annotations']) - .concat(this._props.View.Document) + DocListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_annotations']) + .concat(this.View.Document) .filter(d => d.face) .map(doc => StrCast(DocCast(doc.face)?.title)) ); this._panelHeightDirty; - return !this._props.View.Document.showTags ? null : ( + return !this.View.Document.showTags && this._props.Views.length === 1 ? null : (
r && new ResizeObserver(action(() => (this._props.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} + ref={r => r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} style={{ transformOrigin: 'top left', maxWidth: `${100 * this.currentScale}%`, @@ -338,7 +348,7 @@ export class TagsView extends ObservableReactComponent { 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)`, + top: this._props.Views.length > 1 ? 25 : `calc(-${this.InsetDist} * ${1 / this.currentScale}px)`, }}>
@@ -346,10 +356,17 @@ export class TagsView extends ObservableReactComponent { this.setToEditing(!this._isEditing)} icon={} /> )} {Array.from(tagsList).map((tag, i) => ( - + view.Document)} + tag={tag} + tagDoc={TagItem.findTagCollectionDoc(tag) ?? TagItem.createTagCollectionDoc(tag)} + setToEditing={this.setToEditing} + showRemoveUI={this.isEditing} + /> ))} {Array.from(facesList).map((tag, i) => ( - + view.Document)} tag={tag} tagDoc={undefined} setToEditing={this.setToEditing} showRemoveUI={this.isEditing} /> ))}
{this.isEditing ? ( diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index f9f6c81ab..534f67927 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -179,7 +179,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent() { ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); })}> {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => { - const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.'); + const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url.href.split('.') ?? ['-missing-', '.png']; return (
Date: Thu, 12 Sep 2024 13:40:10 -0400 Subject: fixed removing a face image from a face doc --- src/client/views/search/FaceRecognitionHandler.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index 4f6f5d314..636a44984 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -119,8 +119,7 @@ export class FaceRecognitionHandler { * @param faceDoc - unique face Doc */ public static UniqueFaceRemoveFaceImage = (faceAnno: Doc, faceDoc: Doc) => { - Doc.RemoveDocFromList(faceDoc[DocData], 'face_annos', faceAnno); - faceAnno.face = undefined; + FaceRecognitionHandler.ImageDocFaceAnnos(faceAnno).forEach(face => Doc.RemoveDocFromList(faceDoc[DocData], 'face_annos', face) && (face.face = undefined)); }; constructor() { -- cgit v1.2.3-70-g09d2 From bb1f4c62071987cc59461a8ce53522fa7a9036cc Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 12 Sep 2024 14:42:10 -0400 Subject: changed closing of face rectangles to hide them. added Shift+click of tags button to show face rectangles. --- src/client/documents/Documents.ts | 1 + src/client/views/DocumentButtonBar.tsx | 14 +++++++++----- src/client/views/DocumentDecorations.tsx | 7 ++++--- src/client/views/TagsView.tsx | 6 +++--- .../views/collections/collectionFreeForm/ImageLabelBox.tsx | 4 ++-- src/client/views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/search/FaceRecognitionHandler.tsx | 3 +++ 7 files changed, 23 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fbdf361dd..89356072a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -273,6 +273,7 @@ export class DocumentOptions { _layout_showTitle?: string; // field name to display in header (:hover is an optional suffix) _layout_showSidebar?: BOOLt = new BoolInfo('whether an annotationsidebar should be displayed for text docuemnts'); _layout_showCaption?: string; // which field to display in the caption area. leave empty to have no caption + _layout_showTags?: BOOLt = new BoolInfo('whether to show the list of document tags at the bottom of a DocView'); _chromeHidden?: BOOLt = new BoolInfo('whether the editing chrome for a document is hidden'); hideClickBehaviors?: BOOLt = new BoolInfo('whether to hide click behaviors in context menu'); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 785e69f84..f14fd033b 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -9,7 +9,7 @@ import * as React from 'react'; import { FaEdit } from 'react-icons/fa'; import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; -import { Doc } from '../../fields/Doc'; +import { Doc, DocListCast } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; @@ -270,12 +270,16 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
{ - const showing = DocumentView.Selected().some(dv => dv.dataDoc.showTags); + onClick={undoable(e => { + const showing = DocumentView.Selected().some(dv => dv.layoutDoc._layout_showTags); DocumentView.Selected().forEach(dv => { - dv.dataDoc.showTags = !showing; + dv.layoutDoc._layout_showTags = !showing; + if (e.shiftKey) + DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => { + if (doc.face) doc.hidden = showing; + }); }); - }}> + }, 'show Doc tags')}>
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 118a5bd3d..907bb2614 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -232,7 +232,8 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? 0 : `${doc[DocData].showTags ? 4 + seldocview.TagPanelHeight : 4}px`, + top: DocumentView.Selected().length > 1 ? 0 : `${doc._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> DocumentView.Selected()} /> @@ -845,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent {DocumentView.Selected().length > 1 ? : null} diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index ae9d42749..0b303f6f5 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -172,12 +172,12 @@ export class TagItem extends ObservableReactComponent { docData.data = new List(newEmbeddings); docData.title = this._props.tag; docData.tags = new List([this._props.tag]); - docData.showTags = true; docData.freeform_fitContentsToBox = true; doc._freeform_panX = doc._freeform_panY = 0; doc._width = 900; doc._height = 900; doc.layout_fitWidth = true; + doc._layout_showTags = true; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); newEmbeddings.forEach(embed => Doc.SetContainer(embed, newCollection)); @@ -322,7 +322,7 @@ export class TagsView extends ObservableReactComponent { ); /** - * When 'showTags' is set on a Doc, this displays a wrapping panel of tagItemViews corresponding to all the tags set on the Doc). + * When 'layout_showTags' is set on a Doc, this displays a wrapping panel of tagItemViews corresponding to all the tags set on the Doc). * When the dropdown is clicked, this will toggle an extended UI that allows additional tags to be added/removed. */ render() { @@ -336,7 +336,7 @@ export class TagsView extends ObservableReactComponent { ); this._panelHeightDirty; - return !this.View.Document.showTags && this._props.Views.length === 1 ? null : ( + return !this.View.Document._layout_showTags && this._props.Views.length === 1 ? null : (
r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx index e419e522c..033d1590d 100644 --- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx +++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx @@ -139,9 +139,9 @@ export class ImageLabelBox extends ViewBoxBaseComponent() { toggleDisplayInformation = () => { this._displayImageInformation = !this._displayImageInformation; if (this._displayImageInformation) { - this._selectedImages.forEach(doc => (doc[DocData].showTags = true)); + this._selectedImages.forEach(doc => (doc._layout_showTags = true)); } else { - this._selectedImages.forEach(doc => (doc[DocData].showTags = false)); + this._selectedImages.forEach(doc => (doc._layout_showTags = false)); } }; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index e0d6c7c05..0ef67b4be 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -404,7 +404,7 @@ export class RichTextRules { if (!tags.includes(tag)) { tags.push(tag); this.Document[DocData].tags = new List(tags); - this.Document[DocData].showTags = true; + this.Document._layout_showTags = true; } const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); return state.tr diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index 636a44984..c507e54b6 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -220,6 +220,7 @@ export class FaceRecognitionHandler { .then(imgDocFaceDescriptions => { // For each face detected, find a match. const annos = [] as Doc[]; const scale = NumCast(imgDoc.data_nativeWidth) / img.width; + const showTags= imgDocFaceDescriptions.length > 1; imgDocFaceDescriptions.forEach((fd, i) => { const faceDescriptor = new List(Array.from(fd.descriptor)); const matchedUniqueFace = this.findMatchingFaceDoc(fd.descriptor) ?? this.createUniqueFaceDoc(activeDashboard); @@ -233,12 +234,14 @@ export class FaceRecognitionHandler { y: fd.alignedRect.box.top * scale, _width: fd.alignedRect.box.width * scale, _height: fd.alignedRect.box.height * scale, + _layout_showTags: showTags }) FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, matchedUniqueFace); // add image/faceDescriptor to matched unique face annos.push(faceAnno); }); imgDoc[DocData].data_annotations = new List(annos); + imgDoc._layout_showTags = annos.length > 0; return imgDocFaceDescriptions; }) ); // prettier-ignore -- cgit v1.2.3-70-g09d2 From cded2f6473018f15711142517ba30382682cbec0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 16 Sep 2024 15:48:08 -0400 Subject: fixed title dragging pick correlation and conversion to tab dragging. --- src/client/util/DragManager.ts | 2 +- src/client/views/DocumentDecorations.tsx | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 7db13689d..d55d193cc 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -569,7 +569,7 @@ export namespace DragManager { AbortDrag(); await finishDrag?.(new DragCompleteEvent(true, docDragData)); DragManager.StartWindowDrag?.(e, docDragData.droppedDocuments, aborted => { - if (!aborted && (docDragData?.dropAction === 'move' || docDragData?.dropAction === 'same')) { + if (!aborted && (docDragData?.dropAction === dropActionType.move || docDragData?.dropAction === dropActionType.same)) { docDragData.removeDocument?.(docDragData?.draggedDocuments[0]); } }); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 907bb2614..4b0198021 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -173,7 +173,8 @@ export class DocumentDecorations extends ObservableReactComponent 1) return false; const { left, top } = dragDocView.getBounds || { left: 0, top: 0 }; const dragData = new DragManager.DocumentDragData(DocumentView.SelectedDocs(), dragDocView._props.dropAction); - dragData.offset = dragDocView.screenToContentsTransform().transformDirection(e.x - left, e.y - top); + dragData.offset = dragDocView.screenToViewTransform().transformDirection(e.x - left, e.y - top); dragData.moveDocument = dragDocView._props.moveDocument; dragData.removeDocument = dragDocView._props.removeDocument; dragData.isDocDecorationMove = true; -- cgit v1.2.3-70-g09d2 From 62eb66ca7d3404f9977acdf73f815f4920fb964d Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 16 Sep 2024 18:14:56 -0400 Subject: fixed doc decorations from crashing when dockingView is selected. cleaned up tab doc view. --- src/client/views/DocumentDecorations.tsx | 5 +- src/client/views/TagsView.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 3 +- src/client/views/collections/TabDocView.tsx | 175 +++++++++------------ 4 files changed, 77 insertions(+), 108 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4b0198021..5e7908725 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -646,7 +646,6 @@ export class DocumentDecorations extends ObservableReactComponent { @@ -838,7 +837,7 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? 0 : `${doc._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`, + top: DocumentView.Selected().length > 1 ? 0 : `${seldocview.Document._layout_showTags ? 4 + seldocview.TagPanelHeight : 4}px`, transform: `translate(${-this._resizeBorderWidth / 2 + 10}px, ${this._resizeBorderWidth + bounds.b - bounds.y + this._titleHeight}px) `, }}> DocumentView.Selected()} /> @@ -847,7 +846,7 @@ export class DocumentDecorations extends ObservableReactComponent {DocumentView.Selected().length > 1 ? : null} diff --git a/src/client/views/TagsView.tsx b/src/client/views/TagsView.tsx index 0b303f6f5..be2c28185 100644 --- a/src/client/views/TagsView.tsx +++ b/src/client/views/TagsView.tsx @@ -336,7 +336,7 @@ export class TagsView extends ObservableReactComponent { ); this._panelHeightDirty; - return !this.View.Document._layout_showTags && this._props.Views.length === 1 ? null : ( + return this.View.ComponentView?.isUnstyledView?.() || (!this.View.Document._layout_showTags && this._props.Views.length === 1) ? null : (
r && new ResizeObserver(action(() => this._props.Views.length === 1 && (this.View.TagPanelHeight = Math.max(0, (r?.getBoundingClientRect().height ?? 0) - this.InsetDist)))).observe(r)} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e0aa79c7b..028133a6e 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -31,6 +31,7 @@ import { ScriptingRepl } from '../ScriptingRepl'; import { UndoStack } from '../UndoStack'; import './CollectionDockingView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { TabHTMLElement } from './TabDocView'; @observer export class CollectionDockingView extends CollectionSubView() { @@ -544,7 +545,7 @@ export class CollectionDockingView extends CollectionSubView() { tabCreated = (tab: { contentItem: { element: HTMLElement[] } }) => { this.tabMap.add(tab); // InitTab is added to the tab's HTMLElement in TabDocView - const tabdocviewContent = tab.contentItem.element[0]?.firstChild?.firstChild as unknown as { InitTab?: (tab: object) => void }; + const tabdocviewContent = tab.contentItem.element[0]?.firstChild?.firstChild as unknown as TabHTMLElement; tabdocviewContent?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) }; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 31b6be927..def1ea731 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -57,6 +57,7 @@ interface TabMiniThumbProps { miniLeft: () => number; } +export type TabHTMLElement = HTMLDivElement & { InitTab?: (tab: object) => void }; @observer class TabMiniThumb extends React.Component { render() { @@ -193,8 +194,9 @@ export class TabDocView extends ObservableReactComponent { .filter(tv => tv._document) .map(tv => tv._document!); } - _mainCont: HTMLDivElement | null = null; + _mainCont: TabHTMLElement | null = null; _tabReaction: IReactionDisposer | undefined; + _lastSelection = 0; // time when view was last selected - used to re-select views that get invalidated when selected /** * Adds a document to the presentation view @@ -273,19 +275,24 @@ export class TabDocView extends ObservableReactComponent { setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs } + // Flag indicating that when a tab is activated, it should not select it's document. + // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected) + public static DontSelectOnActivate = 'dontSelectOnActivate'; + + public static IsSelected = (doc?: Doc) => { + return DocumentView.getViews(doc).some(dv => dv?.IsSelected); + }; + static Activate = (tabDoc: Doc) => { const tab = Array.from(CollectionDockingView.Instance?.tabMap ?? []).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue); tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) return tab !== undefined; }; - // static ActivateTabView(doc: Doc) { - // const tabView = Array.from(TabDocView._allTabs).find(view => view._document === doc); - // if (!tabView?._activated && tabView?._document) { - // TabDocView.Activate(tabView?._document); - // return tabView; - // } - // return undefined; - // } + + get stack() { return this._props.glContainer.parent.parent; } // prettier-ignore + get tab() { return this._props.glContainer.tab; } // prettier-ignore + get view() { return this._view; } // prettier-ignore + constructor(props: TabDocViewProps) { super(props); makeObservable(this); @@ -299,37 +306,13 @@ export class TabDocView extends ObservableReactComponent { @observable _hovering = false; @observable _isActive: boolean = false; @observable _isAnyChildContentActive = false; - public static IsSelected = (doc?: Doc) => { - if (DocumentView.getViews(doc).some(dv => dv?.IsSelected)) { - return true; - } - return false; - }; - @computed get _isUserActivated() { - return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; - } - get _isContentActive() { - return this._isUserActivated || this._hovering; - } @observable _document: Doc | undefined = undefined; @observable _view: DocumentView | undefined = undefined; + @observable _forceInvalidateScreenToLocal = 0; // screentolocal is computed outside of react using a dom resize ovbserver. this hack allows the resize observer to trigger a react update - @computed get layoutDoc() { - return this._document && Doc.Layout(this._document); - } - - get stack() { - return this._props.glContainer.parent.parent; - } - get tab() { - return this._props.glContainer.tab; - } - get view() { - return this._view; - } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - _lastTab: any; - _lastView: DocumentView | undefined; + @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } // prettier-ignore + @computed get isUserActivated() { return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; } // prettier-ignore + @computed get isContentActive() { return this.isUserActivated || this._hovering; } // prettier-ignore @action // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -413,7 +396,7 @@ export class TabDocView extends ObservableReactComponent { color === variant ? DashColor(color) .fade( - this._isUserActivated + this.isUserActivated ? 0 : this._hovering ? 0.25 @@ -522,19 +505,14 @@ export class TabDocView extends ObservableReactComponent { this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged); } - // Flag indicating that when a tab is activated, it should not select it's document. - // this is used by the link properties menu when it wants to display the link target without selecting the target (which would make the link property window go away since it would no longer be selected) - public static DontSelectOnActivate = 'dontSelectOnActivate'; - - @action.bound // eslint-disable-next-line @typescript-eslint/no-explicit-any - private onActiveContentItemChanged(contentItem: any) { + onActiveContentItemChanged = (contentItem: any) => { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false)); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } - } + }; // adds a tab to the layout based on the locaiton parameter which can be: // close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab, @@ -551,7 +529,6 @@ export class TabDocView extends ObservableReactComponent { const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; const panelName = whereFields.length > 1 ? whereFields.lastElement() : ''; if (docs[0]?.dockingConfig && !keyValue) return DashboardView.openDashboard(docs[0]); - // prettier-ignore switch (whereFields[0]) { case undefined: case OpenWhere.lightbox: return LightboxView.Instance.AddDocTab(docs[0], location); @@ -559,7 +536,7 @@ export class TabDocView extends ObservableReactComponent { case OpenWhere.replace: return CollectionDockingView.ReplaceTab(docs[0], whereMods, this.stack, panelName, undefined, keyValue); case OpenWhere.toggle: return CollectionDockingView.ToggleSplit(docs[0], whereMods, this.stack, TabDocView.DontSelectOnActivate, keyValue); case OpenWhere.add:default:return CollectionDockingView.AddSplit(docs[0], whereMods, this.stack, undefined, keyValue); - } + } // prettier-ignore }; remDocTab = (doc: Doc | Doc[]) => { if (doc === this._document) { @@ -571,8 +548,6 @@ export class TabDocView extends ObservableReactComponent { }; getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame); - - @action focusFunc = () => { if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) @@ -580,7 +555,6 @@ export class TabDocView extends ObservableReactComponent { return undefined; }; active = () => this._isActive; - @observable _forceInvalidateScreenToLocal = 0; ScreenToLocalTransform = () => { this._forceInvalidateScreenToLocal; const { translateX, translateY } = ClientUtils.GetScreenTransform(this._mainCont?.children?.[0] as HTMLElement); @@ -594,47 +568,44 @@ export class TabDocView extends ObservableReactComponent { whenChildContentActiveChanges = (isActive: boolean) => { this._isAnyChildContentActive = isActive; }; - isContentActive = () => this._isContentActive; + isContentActiveFunc = () => this.isContentActive; waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined); - @computed get docView() { - return !this._activated || !this._document ? null : ( - <> - { - this._lastView && DocumentView.removeView(this._lastView); - this._view = r; - this._lastView = this._view; - })} - renderDepth={0} - LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined} - hideTitle={this._props.keyValue} - Document={this._document} - TemplateDataDocument={!Doc.AreProtosEqual(this._document[DocData], this._document) ? this._document[DocData] : undefined} - waitForDoubleClickToClick={this.waitForDoubleClick} - isContentActive={this.isContentActive} - isDocumentActive={returnFalse} - PanelWidth={this.PanelWidth} - PanelHeight={this.PanelHeight} - styleProvider={DefaultStyleProvider} - childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter} - childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter} - searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} - addDocument={undefined} - removeDocument={this.remDocTab} - addDocTab={this.addDocTab} - suppressSetHeight={!!this._document._layout_fitWidth} - ScreenToLocalTransform={this.ScreenToLocalTransform} - dontCenter="y" - whenChildContentsActiveChanged={this.whenChildContentActiveChanges} - focus={this.focusFunc} - containerViewPath={returnEmptyDocViewList} - pinToPres={TabDocView.PinDoc} - /> - {this.disableMinimap() ? null : } - - ); - } + renderDocView = (doc: Doc) => ( + { + const now = Date.now(); + this._lastSelection = this._view?.IsSelected ? now : this._lastSelection; + if (this._view) DocumentView.removeView(this._view); + this._view = r; + if (this._view && now - this._lastSelection < 1000) this._view.select(false); + })} + renderDepth={0} + LayoutTemplateString={this._props.keyValue ? KeyValueBox.LayoutString() : undefined} + hideTitle={this._props.keyValue} + Document={doc} + TemplateDataDocument={!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined} + waitForDoubleClickToClick={this.waitForDoubleClick} + isContentActive={this.isContentActiveFunc} + isDocumentActive={returnFalse} + PanelWidth={this.PanelWidth} + PanelHeight={this.PanelHeight} + styleProvider={DefaultStyleProvider} + childFilters={CollectionDockingView.Instance?.childDocFilters ?? returnEmptyFilter} + childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter} + searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} + addDocument={undefined} + removeDocument={this.remDocTab} + addDocTab={this.addDocTab} + suppressSetHeight={!!doc._layout_fitWidth} + ScreenToLocalTransform={this.ScreenToLocalTransform} + dontCenter="y" + whenChildContentsActiveChanged={this.whenChildContentActiveChanges} + focus={this.focusFunc} + containerViewPath={returnEmptyDocViewList} + pinToPres={TabDocView.PinDoc} + /> + ); render() { return ( @@ -647,23 +618,21 @@ export class TabDocView extends ObservableReactComponent { onPointerLeave={action(() => { this._hovering = false; })} // prettier-ignore onDragOver={action(() => { this._hovering = true; })} // prettier-ignore onDragLeave={action(() => { this._hovering = false; })} // prettier-ignore - ref={ref => { + ref={(ref: TabHTMLElement) => { + // "add" an InitTab function to this div to call from tabCreated in CollectionDockingView when div is reused this._mainCont = ref; if (this._mainCont) { - if (this._lastTab) { - this._view && DocumentView.removeView(this._view); - } - this._lastTab = this.tab; - (this._mainCont as { InitTab?: (tab: object) => void }).InitTab = (tab: object) => this.init(tab, this._document); - DocServer.GetRefField(this._props.documentId).then( - action(doc => { - doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document); - }) - ); - ref && new ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(ref); + this._mainCont.InitTab = (tab: object) => this.init(tab, this._document); + DocServer.GetRefField(this._props.documentId).then(action(doc => { + doc instanceof Doc && (this._document = doc) && this.tab && this.init(this.tab, this._document); + })); // prettier-ignore + new ResizeObserver(action(() => this._forceInvalidateScreenToLocal++)).observe(this._mainCont); } }}> - {this.docView} + {!this._activated || !this._document ? null : this.renderDocView(this._document)} + {this.disableMinimap() || !this._document ? null : ( + + )}
); } -- cgit v1.2.3-70-g09d2 From 4f2ee4a8642a93fb399b979750078374b317af32 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 17 Sep 2024 12:29:48 -0400 Subject: fixed carouselfView to fade. cleaned up code a bit --- .../views/collections/CollectionCarousel3DView.tsx | 1 + .../views/collections/CollectionCarouselView.scss | 15 ++ .../views/collections/CollectionCarouselView.tsx | 223 ++++++++++++--------- src/client/views/collections/TabDocView.tsx | 4 +- 4 files changed, 151 insertions(+), 92 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index c799eb3c8..54cc02825 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -78,6 +78,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { NativeWidth={returnZero} NativeHeight={returnZero} fitWidth={undefined} + containerViewPath={this.childContainerViewPath} onDoubleClickScript={this.onChildDoubleClick} renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index f115bb40a..01b20d6d3 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -12,6 +12,21 @@ user-select: none; } } +.collectionCarouselView-addFlashcards { + justify-content: center; + align-items: center; + height: 100%; + z-index: -1; + pointer-events: none; +} +.collectionCarouselView-recentlyMissed { + color: red; + z-index: 999; + position: relative; + left: 10px; + top: 10px; + pointer-events: none; +} .carouselView-back, .carouselView-fwd, .carouselView-star, diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 4bec2d963..5d71177c3 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -1,12 +1,11 @@ /* eslint-disable react/jsx-props-no-spreading */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { computed, makeObservable } from 'mobx'; +import { IReactionDisposer, action, computed, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { StopEvent, returnFalse, returnOne, returnTrue, returnZero } from '../../../ClientUtils'; -import { emptyFunction } from '../../../Utils'; -import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { StopEvent, returnOne, returnZero } from '../../../ClientUtils'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { ContextMenu } from '../ContextMenu'; @@ -32,13 +31,34 @@ export class CollectionCarouselView extends CollectionSubView() { get practiceField() { return this.fieldKey + "_practice"; } // prettier-ignore get starField() { return this.fieldKey + "_star"; } // prettier-ignore + _fadeTimer: NodeJS.Timeout | undefined; + _resetter: IReactionDisposer | undefined; + constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); } + @observable _last_index = this.carouselIndex; + @observable _last_opacity = 1; + + componentDidMount() { + this._resetter = reaction( + // automatically reset practice fields when all cards have been marked as correct + () => this.carouselItems.length, + itemsCount => { + if (this.layoutDoc.filterOp === cardMode.PRACTICE && !itemsCount) { + this.layoutDoc.filterOp = undefined; // if all of the cards are correct, show all cards and exit practice mode + this.carouselItems.forEach(item => { // reset all the practice values + item[this.practiceField] = undefined; + }); + } + } // prettier-ignore + ); + } componentWillUnmount() { this._dropDisposer?.(); + this._resetter?.(); } protected createDashEventsTarget = (ele: HTMLDivElement | null) => { @@ -48,43 +68,24 @@ export class CollectionCarouselView extends CollectionSubView() { } }; + @computed get marginX() { return NumCast(this.layoutDoc.caption_xMargin, 50); } // prettier-ignore + @computed get carouselIndex() { return NumCast(this.layoutDoc._carousel_index) % this.carouselItems.length; } // prettier-ignore @computed get carouselItems() { - return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK); - } - @computed get marginX() { - return NumCast(this.layoutDoc.caption_xMargin, 50); + return DocListCast(this.childDocList) + .filter(doc => doc.type !== DocumentType.LINK) + .filter(doc => { + switch (StrCast(this.layoutDoc.filterOp)) { + case cardMode.STAR: return !!doc[this.starField]; // show only cards that are starred + case cardMode.PRACTICE: return doc[this.practiceField] !== practiceVal.CORRECT;// show only cards that aren't marked as correct + default: return true; + } // prettier-ignore + }); } - move = (dir: number) => { - const moveToCardWithField = (match: (doc: Doc) => boolean): boolean => { - let startInd = (NumCast(this.layoutDoc._carousel_index) + dir) % this.carouselItems.length; - while (!match(this.carouselItems?.[startInd].layout) && (startInd + dir + this.carouselItems.length) % this.carouselItems.length !== this.layoutDoc._carousel_index) { - startInd = (startInd + dir + this.carouselItems.length) % this.carouselItems.length; - } - if (match(this.carouselItems?.[startInd].layout)) { - this.layoutDoc._carousel_index = startInd; - return true; - } - return match(this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)].layout); - }; - switch (StrCast(this.layoutDoc.filterOp)) { - case cardMode.STAR: // go to a flashcard that is starred, skip the ones that aren't - if (!moveToCardWithField((doc: Doc) => !!doc[this.starField])) { - this.layoutDoc.filterOp = undefined; // if there aren't any starred, show all cards - } - break; - case cardMode.PRACTICE: // go to a new index that is missed, skip the ones that are correct - if (!moveToCardWithField((doc: Doc) => doc[this.practiceField] !== practiceVal.CORRECT)) { - this.layoutDoc.filterOp = undefined; // if all of the cards are correct, show all cards and exit practice mode - - this.carouselItems.forEach(item => { // reset all the practice values - item.layout[this.practiceField] = undefined; - }); - } - break; - default: moveToCardWithField(returnTrue); - } // prettier-ignore - }; + move = action((dir: number) => { + this._last_index = this.carouselIndex; + this.layoutDoc._carousel_index = (this.carouselIndex + dir + this.carouselItems.length) % this.carouselItems.length; + }); /** * Goes to the next Doc in the stack subject to the currently selected filter option. @@ -107,8 +108,8 @@ export class CollectionCarouselView extends CollectionSubView() { */ star = (e: React.MouseEvent) => { e.stopPropagation(); - const curDoc = this.carouselItems[NumCast(this.layoutDoc._carousel_index)]; - curDoc.layout[this.starField] = curDoc.layout[this.starField] ? undefined : true; + const curDoc = this.carouselItems[this.carouselIndex]; + curDoc && (curDoc[this.starField] = curDoc[this.starField] ? undefined : true); }; /* @@ -116,8 +117,8 @@ export class CollectionCarouselView extends CollectionSubView() { */ setPracticeVal = (e: React.MouseEvent, val: string) => { e.stopPropagation(); - const curDoc = this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]; - curDoc.layout[this.practiceField] = val; + const curDoc = this.carouselItems[this.carouselIndex]; + curDoc && (curDoc[this.practiceField] = val); this.advance(e); }; @@ -132,7 +133,6 @@ export class CollectionCarouselView extends CollectionSubView() { captionWidth = () => this._props.PanelWidth() - 2 * this.marginX; specificMenu = (): void => { const cm = ContextMenu.Instance; - const revealOptions = cm.findByDescription('Filter Flashcards'); const revealItems = revealOptions?.subitems ?? []; revealItems.push({description: 'All', event: () => {this.layoutDoc.filterOp = undefined;}, icon: 'layer-group',}); // prettier-ignore @@ -141,32 +141,78 @@ export class CollectionCarouselView extends CollectionSubView() { revealItems.push({description: 'Quiz Cards', event: () => {this.layoutDoc.filterOp = cardMode.QUIZ;}, icon: 'pencil',}); // prettier-ignore !revealOptions && cm.addItem({ description: 'Filter Flashcards', addDivider: false, noexpand: true, subitems: revealItems, icon: 'layer-group' }); }; + + isChildContentActive = () => + this._props.isContentActive?.() === false + ? false + : this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) + ? true + : this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false + ? false + : undefined; + + renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => { + return ( + + ); + }; + /** + * Display an overlay of the previous card that crossfades to the next card + */ + @computed get overlay() { + const fadeTime = 500; + const lastDoc = this.carouselItems?.[this._last_index]; + return !lastDoc || this.carouselIndex === this._last_index ? null : ( +
+ {this.renderDoc( + lastDoc, + false, // hide captions if the carousel is configured to show the captions + action((r: DocumentView | null) => { + if (r) { + this._fadeTimer && clearTimeout(this._fadeTimer); + this._last_opacity = 0; + this._fadeTimer = setTimeout( + action(() => { + this._last_index = -1; + this._last_opacity = 1; + }), + fadeTime + ); + } + }) + )} +
+ ); + } @computed get content() { - const index = NumCast(this.layoutDoc._carousel_index); + const index = this.carouselIndex; const curDoc = this.carouselItems?.[index]; const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined }; const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption); - return !(curDoc?.layout instanceof Doc) ? null : ( + return !curDoc ? null : ( <>
- + {this.renderDoc(curDoc, !!carouselShowsCaptions)} + {this.overlay}
{!carouselShowsCaptions ? null : (
- +
)} ); } @computed get buttons() { - if (!this.carouselItems?.[NumCast(this.layoutDoc._carousel_index)]) return null; + if (!this.carouselItems?.[this.carouselIndex]) return null; return ( <>
@@ -196,7 +242,7 @@ export class CollectionCarouselView extends CollectionSubView() {
- +
this.setPracticeVal(e, practiceVal.MISSED)} style={{ visibility: this.layoutDoc.filterOp === cardMode.PRACTICE ? 'visible' : 'hidden' }}> @@ -208,6 +254,24 @@ export class CollectionCarouselView extends CollectionSubView() { ); } + /** + * Prompts user to add more flashcaards if they are in practice mode but there are no flashcards + */ + renderAddFlashcards = () =>

+ Add flashcards! +

// prettier-ignore + + /** + * Displays message that a flashcard was recently missed if it had previously been marked as wrong. + * */ + renderRecentlyMissed = () =>

+ Recently missed! +

// prettier-ignore + render() { return (
{this.content} - {/* Displays a message to the user to add more flashcards if they are in practice mode and no flashcards are there. */} -

- Add flashcards! -

- {/* Displays a message to the user that a flashcard was recently missed if they had previously gotten it wrong. */} -

- Recently missed! -

+ {this.renderAddFlashcards()} + {this.renderRecentlyMissed()} {this.Document._chromeHidden ? null : this.buttons}
); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index def1ea731..f56ea9d76 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -506,13 +506,13 @@ export class TabDocView extends ObservableReactComponent { } // eslint-disable-next-line @typescript-eslint/no-explicit-any - onActiveContentItemChanged = (contentItem: any) => { + onActiveContentItemChanged = action((contentItem: any) => { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false)); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } - }; + }); // adds a tab to the layout based on the locaiton parameter which can be: // close[:{left,right,top,bottom}] - e.g., "close" will close the tab, "close:left" will close the left tab, -- cgit v1.2.3-70-g09d2 From 0268a524540ff36bf007744eff6709dfcdedec96 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 17 Sep 2024 18:43:26 -0400 Subject: from last --- .../views/collections/CollectionCarouselView.tsx | 42 ++++++++++++---------- 1 file changed, 23 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index d9a99f47f..91a7c6514 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -89,6 +89,10 @@ export class CollectionCarouselView extends CollectionSubView() { .filter(doc => !this.practiceMode || (BoolCast(doc?._layout_isFlashcard) && doc[this.practiceField] !== practiceVal.CORRECT))// show only cards that aren't marked as correct } // prettier-ignore + /** + * Move forward or backward the specified number of Docs + * @param dir signed number indicating Docs to move forward or backward + */ move = action((dir: number) => { this._last_index = this.carouselIndex; this.layoutDoc._carousel_index = this.carouselItems.length ? (this.carouselIndex + dir + this.carouselItems.length) % this.carouselItems.length : 0; @@ -111,9 +115,9 @@ export class CollectionCarouselView extends CollectionSubView() { }; /* - * Stars the document when the star button is pressed. + * Toggles whether the 'star' metadata field is set on the current Doc */ - star = (e: React.MouseEvent) => { + toggleStar = (e: React.MouseEvent) => { e.stopPropagation(); const curDoc = this.carouselItems[this.carouselIndex]; curDoc && (curDoc[this.starField] = curDoc[this.starField] ? undefined : true); @@ -129,6 +133,16 @@ export class CollectionCarouselView extends CollectionSubView() { this.advance(e); }; + /** + * Sets the practice mode answer style for flashcards + * @param mode practiceMode or undefined for no practice + */ + setPracticeMode = (mode: practiceMode | undefined) => { + this.layoutDoc.practiceMode = mode; + this.carouselItems?.map(doc => (doc[this.practiceField] = undefined)); + if (mode === practiceMode.QUIZ) this.carouselItems?.map(doc => (doc[this.sideField] = undefined)); + }; + captionStyleProvider = (doc: Doc | undefined, captionProps: Opt, property: string) => { // first look for properties on the document in the carousel, then fallback to properties on the container const childValue = doc?.['caption_' + property] ? this._props.styleProvider?.(doc, captionProps, property) : undefined; @@ -138,13 +152,6 @@ export class CollectionCarouselView extends CollectionSubView() { onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); onContentClick = () => ScriptCast(this.layoutDoc.onChildClick); captionWidth = () => this._props.PanelWidth() - 2 * this.marginX; - - setPracticeMode = (mode: practiceMode | undefined) => { - this.layoutDoc.practiceMode = mode; - this.carouselItems?.map(doc => (doc[this.practiceField] = undefined)); - if (mode === practiceMode.QUIZ) this.carouselItems?.map(doc => (doc[this.sideField] = undefined)); - }; - contentScreentToLocalXf = () => this._props.ScreenToLocalTransform().translate(-NumCast(this.layoutDoc.xMargin), -NumCast(this.layoutDoc.yMargin)); contentPanelWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc.xMargin); @@ -264,7 +271,7 @@ export class CollectionCarouselView extends CollectionSubView() { <>
-
+
@@ -298,7 +305,7 @@ export class CollectionCarouselView extends CollectionSubView() { ); } - togglePracticeMode = (mode: practiceMode) => this.setPracticeMode(mode === this.layoutDoc.practiceMode ? undefined : mode); + togglePracticeMode = (mode: practiceMode) => this.setPracticeMode(mode === this.practiceMode ? undefined : mode); toggleFilterMode = () => Doc.setDocFilter(this.Document, 'star', true, 'match', true); setColor = (mode: practiceMode | cardMode, which: string) => { return which === mode ? 'white' : 'light gray'}; //prettier-ignore @@ -314,12 +321,12 @@ export class CollectionCarouselView extends CollectionSubView() {
this.togglePracticeMode(practiceMode.QUIZ)}> - +
- +
this.togglePracticeMode(practiceMode.PRACTICE)}> - +
@@ -342,13 +349,10 @@ export class CollectionCarouselView extends CollectionSubView() {

{ - if (this.filterMessage) { - this.layoutDoc.practiceMode = undefined; + if (this.filterMessage || this.practiceMessage) { + this.setPracticeMode(undefined); Doc.setDocFilter(this.layoutDoc, 'star', undefined, 'remove'); } - this.childDocs.forEach(item => { - item[this.practiceField] = undefined; - }); }}> {this.filterMessage || this.practiceMessage}

-- cgit v1.2.3-70-g09d2