From fbea2a097191f22ae20a609760bd632fc98ca414 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 2 Oct 2024 17:53:50 -0400 Subject: added xMargin for carousel to prevent buttons from overlapping text, etc. Made showing tags an option for carouselViews that needs to be turned on with showChildTags. Gave text boxes extra height when show Tags is on. --- src/client/views/collections/CollectionCarouselView.tsx | 3 ++- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 15 ++++++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 74cf580c9..8b3a699ed 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -165,7 +165,7 @@ export class CollectionCarouselView extends CollectionSubView() { NativeWidth={returnZero} NativeHeight={returnZero} fitWidth={this._props.childLayoutFitWidth} - showTags={true} + showTags={BoolCast(this.layoutDoc.showChildTags)} containerViewPath={this.childContainerViewPath} setContentViewBox={undefined} ScreenToLocalTransform={this.childScreenToLocalXf} @@ -178,6 +178,7 @@ export class CollectionCarouselView extends CollectionSubView() { LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} TemplateDataDocument={DocCast(Doc.Layout(doc).resolvedDataDoc)} + xPadding={35} PanelHeight={this.panelHeight} /> ); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d3bc08bd3..1e6984e3d 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1194,6 +1194,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }), - ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight }) => { - const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); + () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layoutAutoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins, tagsHeight: this.tagsHeight }), + ({ sidebarHeight, textHeight, layoutAutoHeight, marginsHeight, tagsHeight }) => { + const newHeight = this.contentScaling * (tagsHeight + marginsHeight + Math.max(sidebarHeight, textHeight)); if ( (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this._props.isSelected()) && // layoutAutoHeight && @@ -2003,8 +2007,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide); - const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0); - const paddingY = NumCast(this.layoutDoc._yMargin, this._props.yPadding || 0); + const paddingX = Math.max(this._props.xPadding ?? 0, NumCast(this.layoutDoc._xMargin)); + const paddingY = Math.max(this._props.yPadding ?? 0, NumCast(this.layoutDoc._yMargin)); const styleFromLayout = styleFromLayoutString(this.Document, this._props, scale); // this converts any expressions in the format string to style props. e.g., return styleFromLayout?.height === '0px' ? null : (
Date: Wed, 2 Oct 2024 18:01:54 -0400 Subject: fixed bottom padding in textbox for tags to depend on yMargins. --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 1e6984e3d..5db854b69 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1195,7 +1195,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Thu, 3 Oct 2024 11:50:27 -0400 Subject: fixed tagsView height when zooming. --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 5db854b69..70e25d119 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1195,7 +1195,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Fri, 4 Oct 2024 18:39:15 -0400 Subject: cleaning up CardDeckView so that animations work smoothly --- src/client/views/Main.tsx | 2 - .../views/collections/CollectionCalendarView.tsx | 99 ---------- .../views/collections/CollectionCardDeckView.tsx | 199 +++++++++------------ src/client/views/collections/CollectionSubView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 2 - src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/FieldView.tsx | 3 +- src/client/views/nodes/calendarBox/CalendarBox.tsx | 2 +- 8 files changed, 88 insertions(+), 222 deletions(-) delete mode 100644 src/client/views/collections/CollectionCalendarView.tsx (limited to 'src') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 73d2872d1..17eea585a 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -18,7 +18,6 @@ import { TrackMovements } from '../util/TrackMovements'; import { KeyManager } from './GlobalKeyHandler'; import { InkingStroke } from './InkingStroke'; import { MainView } from './MainView'; -import { CollectionCalendarView } from './collections/CollectionCalendarView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionView } from './collections/CollectionView'; import { TabDocView } from './collections/TabDocView'; @@ -128,7 +127,6 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, - CollectionCalendarView, CollectionView, WebBox, KeyValueBox, diff --git a/src/client/views/collections/CollectionCalendarView.tsx b/src/client/views/collections/CollectionCalendarView.tsx deleted file mode 100644 index 0ea9f8ebc..000000000 --- a/src/client/views/collections/CollectionCalendarView.tsx +++ /dev/null @@ -1,99 +0,0 @@ -import { computed, makeObservable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { emptyFunction } from '../../../Utils'; -import { dateRangeStrToDates, returnTrue } from '../../../ClientUtils'; -import { Doc, DocListCast } from '../../../fields/Doc'; -import { StrCast } from '../../../fields/Types'; -import { CollectionStackingView } from './CollectionStackingView'; -import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; - -@observer -export class CollectionCalendarView extends CollectionSubView() { - constructor(props: SubCollectionViewProps) { - super(props); - makeObservable(this); - } - - componentDidMount(): void {} - - componentWillUnmount(): void {} - - @computed get allCalendars() { - return this.childDocs; // returns a list of docs (i.e. calendars) - } - - removeCalendar = () => {}; - - addCalendar = (/* doc: Doc | Doc[], annotationKey?: string | undefined */): boolean => - // bring up calendar modal with option to create a calendar - true; - - _stackRef = React.createRef(); - - panelHeight = () => this._props.PanelHeight() - 40; // this should be the height of the stacking view. For now, it's the hieight of the calendar view minus 40 to allow for a title - - // most recent calendar should come first - sortByMostRecentDate = (calendarA: Doc, calendarB: Doc) => { - const aDateRangeStr = StrCast(DocListCast(calendarA.data).lastElement()?.date_range); - const bDateRangeStr = StrCast(DocListCast(calendarB.data).lastElement()?.date_range); - - const { start: aFromDate, end: aToDate } = dateRangeStrToDates(aDateRangeStr); - const { start: bFromDate, end: bToDate } = dateRangeStrToDates(bDateRangeStr); - - if (aFromDate > bFromDate) { - return -1; // a comes first - } - if (aFromDate < bFromDate) { - return 1; // b comes first - } - // start dates are the same - if (aToDate > bToDate) { - return -1; // a comes first - } - if (aToDate < bToDate) { - return 1; // b comes first - } - return 0; // same start and end dates - }; - - screenToLocalTransform = () => - this._props - .ScreenToLocalTransform() - .translate(Doc.NativeWidth(this.Document), 0) - .scale(this._props.NativeDimScaling?.() || 1); - - get calendarsKey() { - return this._props.fieldKey; - } - - render() { - return ( -
- -
- ); - } -} diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 101cc8082..a9ab9de26 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -2,7 +2,6 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, DashColor, returnFalse, returnZero } from '../../../ClientUtils'; -import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -13,7 +12,6 @@ import { gptImageLabel } from '../../apis/gpt/GPT'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; -import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoable } from '../../util/UndoManager'; @@ -23,12 +21,12 @@ import { DocumentView } from '../nodes/DocumentView'; import { GPTPopup, GPTPopupMode } from '../pdf/GPTPopup/GPTPopup'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; +import { computedFn } from 'mobx-utils'; enum cardSortings { Time = 'time', Type = 'type', Color = 'color', - Custom = 'custom', Chat = 'chat', Tag = 'tag', None = '', @@ -46,14 +44,13 @@ export class CollectionCardView extends CollectionSubView() { private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [key: string]: IReactionDisposer } = {}; private _textToDoc = new Map(); - private _dropped = false; // indicate when a card doc has just moved; + private _dropped = false; // set when a card doc has just moved and the drop method has been called - prevents the pointerUp method from hiding doc decorations (which needs to be done when clicking on a card to animate it to front/center) @observable _forceChildXf = 0; @observable _hoveredNodeIndex = -1; @observable _docRefs = new ObservableMap(); @observable _maxRowCount = 10; @observable _docDraggedIndex: number = -1; - @observable overIndex: number = -1; static imageUrlToBase64 = async (imageUrl: string): Promise => { try { @@ -101,7 +98,6 @@ export class CollectionCardView extends CollectionSubView() { componentDidMount() { this._props.setContentViewBox?.(this); - // Reaction to cardSort changes this._disposers.sort = reaction( () => GPTPopup.Instance.visible, isVis => { @@ -135,8 +131,7 @@ export class CollectionCardView extends CollectionSubView() { } /** - * The child documents to be rendered-- either all of them except the Links or the docs in the currently active - * custom group + * The child documents to be rendered-- everything other than ink/link docs (which are marks as being svg's) */ @computed get childDocsWithoutLinks() { return this.childDocs.filter(l => !l.layout_isSvg); @@ -155,8 +150,7 @@ export class CollectionCardView extends CollectionSubView() { */ quizMode = () => { const randomIndex = Math.floor(Math.random() * this.childDocs.length); - SelectionManager.DeselectAll(); - DocumentView.SelectView(DocumentView.getDocumentView(this.childDocs[randomIndex]), false); + DocumentView.getDocumentView(this.childDocs[randomIndex])?.select(false); }; /** @@ -168,29 +162,15 @@ export class CollectionCardView extends CollectionSubView() { @action setHoveredNodeIndex = (index: number) => { - if (!DocumentView.SelectedDocs().includes(this.childDocs[index])) { - this._hoveredNodeIndex = index; - } + if (!SnappingManager.IsDragging) this._hoveredNodeIndex = index; }; - /** - * Translates the hovered node to the center of the screen - * @param index - * @returns - */ - translateHover = (index: number) => (this._hoveredNodeIndex === index && !DocumentView.SelectedDocs().includes(this.childDocs[index]) ? -50 : 0); - - isSelected = (index: number) => DocumentView.SelectedDocs().includes(this.childDocs[index]); - - /** - * Returns all the documents except the one that's currently selected - */ - inactiveDocs = () => this.childDocsWithoutLinks.filter(d => !DocumentView.SelectedDocs().includes(d)); + isSelected = (doc: Doc) => this._docRefs.get(doc)?.IsSelected; childPanelWidth = () => NumCast(this.layoutDoc.childPanelWidth, this._props.PanelWidth() / 2); childPanelHeight = () => this._props.PanelHeight() * this.fitContentScale; onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); - isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this._props.isAnyChildContentActive(); - isChildContentActive = () => !!this.isContentActive(); + isContentActive = () => this._props.isSelected() || this._props.isContentActive() || this.isAnyChildContentActive(); + isAnyChildContentActive = this._props.isAnyChildContentActive; /** * Returns the degree to rotate a card dependind on the amount of cards in their row and their index in said row @@ -202,13 +182,14 @@ export class CollectionCardView extends CollectionSubView() { if (amCards == 1) return 0; const possRotate = -30 + index * (30 / ((amCards - (amCards % 2)) / 2)); - const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2))); - - if (amCards % 2 === 0 && possRotate === 0) { - return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2))); - } - if (amCards % 2 === 0 && index > (amCards + 1) / 2) { - return possRotate + stepMag; + if (amCards % 2 === 0) { + if (possRotate === 0) { + return possRotate + Math.abs(-30 + (index - 1) * (30 / (amCards / 2))); + } + if (index > (amCards + 1) / 2) { + const stepMag = Math.abs(-30 + (amCards / 2 - 1) * (30 / ((amCards - (amCards % 2)) / 2))); + return possRotate + stepMag; + } } return possRotate; @@ -274,21 +255,12 @@ export class CollectionCardView extends CollectionSubView() { /** * Checks to see if a card is being dragged and calls the appropriate methods if so - * @param e the current pointer event */ - @action onPointerMove = (x: number, y: number) => { this._docDraggedIndex = DragManager.docsBeingDragged.length ? this.findCardDropIndex(x, y) : -1; }; - /** - * Handles external drop of images/PDFs etc from outside Dash. - */ - onExternalDrop = async (e: React.DragEvent): Promise => { - super.onExternalDrop(e, {}); - }; - /** * Resets all the doc dragging vairables once a card is dropped * @param e @@ -326,7 +298,7 @@ export class CollectionCardView extends CollectionSubView() { } /** - * Used to determine how to sort cards based on tags. The lestmost tags are given lower values while cards to the right are + * Used to determine how to sort cards based on tags. The leftmost tags are given lower values while cards to the right are * given higher values. Decimals are used to determine placement for cards with multiple tags * @param doc the doc whose value is being determined * @returns its value based on its tags @@ -352,24 +324,14 @@ export class CollectionCardView extends CollectionSubView() { docs.sort((docA, docB) => { const [typeA, typeB] = (() => { switch (sortType) { - case cardSortings.Time: - return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()]; - case cardSortings.Color: { - const d1 = DashColor(StrCast(docA.backgroundColor)); - const d2 = DashColor(StrCast(docB.backgroundColor)); - return [d1.hsv().hue(), d2.hsv().hue()]; - } - case cardSortings.Tag: - return [this.tagValue(docA) ?? 9999, this.tagValue(docB) ?? 9999]; - case cardSortings.Chat: - return [NumCast(docA.chatIndex) ?? 9999, NumCast(docB.chatIndex) ?? 9999]; - default: - return [StrCast(docA.type), StrCast(docB.type)]; + default: + case cardSortings.Type: return [StrCast(docA.type), StrCast(docB.type)]; + case cardSortings.Chat: return [NumCast(docA.chatIndex, 9999), NumCast(docB.chatIndex,9999)]; + case cardSortings.Time: return [DateCast(docA.author_date)?.date ?? Date.now(), DateCast(docB.author_date)?.date ?? Date.now()]; + case cardSortings.Color:return [DashColor(StrCast(docA.backgroundColor)).hsv().hue(), DashColor(StrCast(docB.backgroundColor)).hsv().hue()]; } - })(); - - const out = typeA < typeB ? -1 : typeA > typeB ? 1 : 0; - return isDesc ? out : -out; + })(); //prettier-ignore + return (typeA < typeB ? -1 : typeA > typeB ? 1 : 0) * (isDesc ? 1 : -1); }); if (dragIndex !== -1) { const draggedDoc = DragManager.docsBeingDragged[0]; @@ -382,6 +344,15 @@ export class CollectionCardView extends CollectionSubView() { return docs; }; + 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; + displayDoc = (doc: Doc, screenToLocalTransform: () => Transform) => ( ); @@ -416,13 +388,11 @@ export class CollectionCardView extends CollectionSubView() { if (this.sortedDocs.length < this._maxRowCount) { return this.sortedDocs.length; } - // 13 - 3 = 10 const totalCards = this.sortedDocs.length; // if 9 or less if (index < totalCards - (totalCards % this._maxRowCount)) { return this._maxRowCount; } - // (3) return totalCards % this._maxRowCount; }; /** @@ -443,17 +413,17 @@ export class CollectionCardView extends CollectionSubView() { /** * Determines how far to translate a card in the y direction depending on its index, whether or not its being hovered, or if it's selected * @param isHovered - * @param isSelected + * @param isActive * @param realIndex * @param amCards * @param calcRowIndex * @returns */ - calculateTranslateY = (isHovered: boolean, isSelected: boolean, realIndex: number, amCards: number, calcRowIndex: number) => { + calculateTranslateY = (isHovered: boolean, isActive: boolean, realIndex: number, amCards: number, calcRowIndex: number) => { const rowHeight = (this._props.PanelHeight() * this.fitContentScale) / this.numRows; const rowIndex = Math.trunc(realIndex / this._maxRowCount); const rowToCenterShift = this.numRows / 2 - rowIndex; - if (isSelected) return rowToCenterShift * rowHeight - rowHeight / 2; + if (isActive) return rowToCenterShift * rowHeight - rowHeight / 2; if (amCards == 1) return 50 * this.fitContentScale; return this.translateY(amCards, calcRowIndex, realIndex); }; @@ -576,15 +546,43 @@ export class CollectionCardView extends CollectionSubView() { await this.childPairStringListAndUpdateSortDesc(); }; + childScreenToLocal = computedFn((doc: Doc, index: number, calcRowIndex: number, isSelected: boolean, amCards: number) => () => { + // need to explicitly trigger an invalidation since we're reading everything from the Dom + this._forceChildXf; + this._props.ScreenToLocalTransform(); + + const dref = this._docRefs.get(doc); + const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv); + if (!scale) return new Transform(0, 0, 0); + + return new Transform(-translateX + (dref?.centeringX || 0) * scale, + -translateY + (dref?.centeringY || 0) * scale, 1) + .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore + }); + + cardPointerUp = action((doc: Doc) => { + // if a card doc has just moved, or a card is selected and in front, then ignore this event + if (this.isSelected(doc) || this._dropped) { + this._dropped = false; + } else { + // otherwise, turn off documentDecorations becase we're in a selection transition and want to avoid artifacts. + // Turn them back on when the animation has completed and the render and backend structures are in synch + SnappingManager.SetIsResizing(doc[Id]); + setTimeout( + action(() => { + SnappingManager.SetIsResizing(undefined); + this._forceChildXf++; + }), + 1000 + ); + } + }); + /** * Actually renders all the cards */ @computed get renderCards() { - const sortedDocs = this.sortedDocs; - const anySelected = this.childDocs.some(doc => DocumentView.SelectedDocs().includes(doc)); - const isEmpty = this.childDocsWithoutLinks.length === 0; - - if (isEmpty) { + if (!this.childDocsWithoutLinks.length) { return ( Sorry ! There are no cards in this group @@ -593,31 +591,18 @@ export class CollectionCardView extends CollectionSubView() { } // Map sorted documents to their rendered components - return sortedDocs.map((doc, index) => { - const realIndex = sortedDocs.indexOf(doc); + return this.sortedDocs.map((doc, index) => { + const realIndex = this.sortedDocs.indexOf(doc); const calcRowIndex = this.overflowIndexCalc(realIndex); const amCards = this.overflowAmCardsCalc(realIndex); const view = DocumentView.getDocumentView(doc, this.DocumentView?.()); - const isSelected = view?.ComponentView?.isAnyChildContentActive?.() || view?.IsSelected ? true : false; - const childScreenToLocal = () => { - // need to explicitly trigger an invalidation since we're reading everything from the Dom - this._forceChildXf; - this._props.ScreenToLocalTransform(); - - const dref = this._docRefs.get(doc); - const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv); - if (!scale) return new Transform(0, 0, 0); - - return new Transform(-translateX + (dref?.centeringX || 0) * scale, - -translateY + (dref?.centeringY || 0) * scale, 1) - .scale(1 / scale).rotate(!isSelected ? -this.rotate(amCards, calcRowIndex) : 0); // prettier-ignore - }; + const childScreenToLocal = this.childScreenToLocal(doc, index, calcRowIndex, !!view?.IsContentActive, amCards); const translateIfSelected = () => { const indexInRow = index % this._maxRowCount; const rowIndex = Math.trunc(index / this._maxRowCount); - const rowCenterIndex = Math.min(this._maxRowCount, sortedDocs.length - rowIndex * this._maxRowCount) / 2; + const rowCenterIndex = Math.min(this._maxRowCount, this.sortedDocs.length - rowIndex * this._maxRowCount) / 2; return (rowCenterIndex - indexInRow) * 100 - 50; }; const aspect = NumCast(doc.height) / NumCast(doc.width, 1); @@ -627,33 +612,18 @@ export class CollectionCardView extends CollectionSubView() { return (
{ - // if a card doc has just moved, or a card is selected and in front, then ignore this event - if (DocumentView.SelectedDocs().includes(doc) || this._dropped) { - this._dropped = false; - } else { - // otherwise, turn off documentDecorations becase we're in a selection transition and want to avoid artifacts. - // Turn them back on when the animation has completed and the render and backend structures are in synch - SnappingManager.SetIsResizing(doc[Id]); - setTimeout( - action(() => { - SnappingManager.SetIsResizing(undefined); - this._forceChildXf++; - }), - 1000 - ); - } - })} + className={`card-item${view?.IsContentActive ? '-active' : this.isAnyChildContentActive() ? '-inactive' : ''}`} + onPointerUp={() => this.cardPointerUp(doc)} style={{ width: this.childPanelWidth(), height: 'max-content', - transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, isSelected, realIndex, amCards, calcRowIndex)}px) - translateX(calc(${isSelected ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px)) - rotate(${!isSelected ? this.rotate(amCards, calcRowIndex) : 0}deg) - scale(${isSelected ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.05 : 1})`, + transform: `translateY(${this.calculateTranslateY(this._hoveredNodeIndex === index, !!view?.IsContentActive, realIndex, amCards, calcRowIndex)}px) + translateX(calc(${view?.IsContentActive ? translateIfSelected() : 0}% + ${this.translateOverflowX(realIndex, amCards)}px)) + rotate(${!view?.IsContentActive ? this.rotate(amCards, calcRowIndex) : 0}deg) + scale(${view?.IsContentActive ? `${Math.min(hscale, vscale) * 100}%` : this._hoveredNodeIndex === index ? 1.1 : 1})`, }} // prettier-ignore - onPointerEnter={() => !SnappingManager.IsDragging && this.setHoveredNodeIndex(index)}> + onPointerEnter={() => this.setHoveredNodeIndex(index)} + onPointerLeave={() => this.setHoveredNodeIndex(-1)}> {this.displayDoc(doc, childScreenToLocal)}
); @@ -680,8 +650,7 @@ export class CollectionCardView extends CollectionSubView() { ...(!isEmpty && { transform: `scale(${1 / this.fitContentScale})` }), ...(!isEmpty && { height: `${100 * this.fitContentScale}%` }), gridAutoRows: `${100 / this.numRows}%`, - }} - onMouseLeave={() => this.setHoveredNodeIndex(-1)}> + }}> {this.renderCards}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5d32482c3..581201a20 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -296,7 +296,7 @@ export function CollectionSubView() { return false; } - protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: (docs: Doc[]) => void) { + protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions = {}, completed?: (docs: Doc[]) => void) { if (e.ctrlKey) { e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl return; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index c9e934448..7418d4360 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/jsx-props-no-spreading */ import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -16,7 +15,6 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView } from '../nodes/FieldView'; import { OpenWhere } from '../nodes/OpenWhere'; -import { CollectionCalendarView } from './CollectionCalendarView'; import { CollectionCardView } from './CollectionCardDeckView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d730e661b..81b5f946a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1206,6 +1206,7 @@ export class DocumentView extends DocComponent() { public set IsSelected(val) { runInAction(() => { this._selected = val; }); } // prettier-ignore public get IsSelected() { return this._selected; } // prettier-ignore + public get IsContentActive(){ return this._docViewInternal?.isContentActive(); } // prettier-ignore public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore public get ComponentView() { return this._docViewInternal?._componentView; } // prettier-ignore diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 683edba16..f6eb4009c 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -6,7 +6,6 @@ import { DateField } from '../../../fields/DateField'; import { Doc, Field, FieldType, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { ScriptField } from '../../../fields/ScriptField'; -import { WebField } from '../../../fields/URLField'; import { dropActionType } from '../../util/DropActionTypes'; import { Transform } from '../../util/Transform'; import { PinProps } from '../PinFuncs'; @@ -14,9 +13,9 @@ import { ViewBoxInterface } from '../ViewBoxInterface'; import { DocumentView } from './DocumentView'; import { FocusViewOptions } from './FocusViewOptions'; import { OpenWhere } from './OpenWhere'; +import { WebField } from '../../../fields/URLField'; export type FocusFuncType = (doc: Doc, options: FocusViewOptions) => Opt; -// eslint-disable-next-line no-use-before-define export type StyleProviderFuncType = ( doc: Opt, // eslint-disable-next-line no-use-before-define diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index 678b7dd0b..d38cb5423 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -1,4 +1,4 @@ -import { Calendar, DateInput, EventClickArg, 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'; -- cgit v1.2.3-70-g09d2 From 2e883e481fa2064983bcd16425118c1abb55cedb Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 4 Oct 2024 20:56:32 -0400 Subject: cleaning up lint problems. --- src/client/documents/Documents.ts | 3 +- src/client/views/MainView.tsx | 6 +- .../views/nodes/DataVizBox/DocCreatorMenu.tsx | 4203 ++++++++++---------- src/client/views/nodes/DocumentView.tsx | 3 +- 4 files changed, 2106 insertions(+), 2109 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 38a4258b9..0a7047128 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -18,7 +18,6 @@ import { PointData } from '../../pen-gestures/GestureTypes'; import { DocServer } from '../DocServer'; import { dropActionType } from '../util/DropActionTypes'; import { CollectionViewType, DocumentType } from './DocumentTypes'; -import { MarkType } from 'prosemirror-model'; class EmptyBox { public static LayoutString() { @@ -229,7 +228,7 @@ export class DocumentOptions { _lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged"); _lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed'); _childrenSharedWithSchema?: BOOLt = new BoolInfo("whether this document's children are displayed in its parent schema view", false); - _lockedSchemaEditing?: BOOLt = new BoolInfo("", false); + _lockedSchemaEditing?: BOOLt = new BoolInfo('', false); dataViz_title?: string; dataViz_line?: string; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 6feb6bd16..fc159d96b 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -41,6 +41,7 @@ import { DashboardView } from './DashboardView'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import { GestureOverlay } from './GestureOverlay'; +import { InkTranscription } from './InkTranscription'; import { LightboxView } from './LightboxView'; import './MainView.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -57,6 +58,7 @@ import { ImageLabelHandler } from './collections/collectionFreeForm/ImageLabelHa import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { LinkMenu } from './linking/LinkMenu'; +import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu'; import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; @@ -73,10 +75,8 @@ import GenerativeFill from './nodes/generativeFill/GenerativeFill'; import { PresBox } from './nodes/trails'; import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; -import { TopBar } from './topbar/TopBar'; -import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu'; import { SmartDrawHandler } from './smartdraw/SmartDrawHandler'; -import { InkTranscription } from './InkTranscription'; +import { TopBar } from './topbar/TopBar'; // eslint-disable-next-line @typescript-eslint/no-require-imports const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx index 44f743a97..05bedfcdc 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx @@ -28,6 +28,7 @@ import { DataVizBox } from './DataVizBox'; import './DocCreatorMenu.scss'; import { DefaultStyleProvider, returnEmptyDocViewList } from '../../StyleProvider'; import { Transform } from '../../../util/Transform'; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; export enum LayoutType { Stacked = 'stacked', @@ -36,2330 +37,2328 @@ export enum LayoutType { Column = 'column', Custom = 'custom', } +export interface DataVizTemplateInfo { + doc: Doc; + layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number }; + columns: number; + referencePos: { x: number; y: number }; +} -@observer -export class DocCreatorMenu extends ObservableReactComponent { - static Instance: DocCreatorMenu; +export interface DataVizTemplateLayout { + template: Doc; + docsNumList: number[]; + layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number }; + columns: number; + rows: number; +} - private _disposers: { [name: string]: IDisposer } = {}; +export enum TemplateFieldType { + TEXT = 'text', + VISUAL = 'visual', + UNSET = 'unset', +} - private _ref: HTMLDivElement | null = null; +export enum TemplateFieldSize { + TINY = 'tiny', + SMALL = 'small', + MEDIUM = 'medium', + LARGE = 'large', + HUGE = 'huge', +} - @observable _templateDocs: Doc[] = []; - @observable _selectedTemplate: Doc | undefined = undefined; - @observable _columns: Col[] = []; - @observable _selectedCols: { title: string; type: string; desc: string }[] | undefined = []; +export type Col = { + sizes: TemplateFieldSize[]; + desc: string; + title: string; + type: TemplateFieldType; + defaultContent?: string; +}; +export interface FieldOpts { + backgroundColor?: string; + color?: string; + cornerRounding?: number; + borderWidth?: string; + borderColor?: string; + contentXCentering?: 'h-left' | 'h-center' | 'h-right'; + contentYCentering?: 'top' | 'center' | 'bottom'; + opacity?: number; + rotation?: number; + //animation?: boolean; + fontBold?: boolean; + fontTransform?: 'uppercase' | 'lowercase'; + fieldViewType?: 'freeform' | 'stacked'; +} - @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0 }; - @observable _layoutPreview: boolean = true; - @observable _layoutPreviewScale: number = 1; - @observable _savedLayouts: DataVizTemplateLayout[] = []; - @observable _expandedPreview: { icon: ImageField; doc: Doc } | undefined = undefined; +type Field = { + tl: [number, number]; + br: [number, number]; + opts: FieldOpts; + subfields?: Field[]; + types?: TemplateFieldType[]; + sizes?: TemplateFieldSize[]; + isDecoration?: boolean; + description?: string; +}; - @observable _suggestedTemplates: Doc[] = []; - @observable _GPTOpt: boolean = false; - @observable _userPrompt: string = ''; - @observable _callCount: number = 0; - @observable _GPTLoading: boolean = false; +// class ContentField implements Field { +// tl: [number, number]; +// br: [number, number]; +// opts: FieldOpts; +// subfields?: Field[]; +// types?: TemplateFieldType[]; +// sizes?: TemplateFieldSize[]; +// description?: string; - @observable _pageX: number = 0; - @observable _pageY: number = 0; - @observable _indicatorX: number | undefined = undefined; - @observable _indicatorY: number | undefined = undefined; +// constructor( tl: [number, number], br: [number, number], +// opts: FieldOpts, subfields?: Field[], +// types?: TemplateFieldType[], +// sizes?: TemplateFieldSize[], +// description?: string) { +// this.tl = tl; +// this.br = br; +// this.opts = opts; +// this.subfields = subfields; +// this.types = types; +// this.sizes = sizes; +// this.description = description; +// } - @observable _hoveredLayoutPreview: number | undefined = undefined; - @observable _mouseX: number = -1; - @observable _mouseY: number = -1; - @observable _startPos?: { x: number; y: number }; - @observable _shouldDisplay: boolean = false; +// render = (content: any): Doc => { +// return new Doc; +// } +// } - @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' = 'templates'; - @observable _dragging: boolean = false; - @observable _draggingIndicator: boolean = false; - @observable _dataViz?: DataVizBox; - @observable _interactionLock: any; - @observable _snapPt: any; - @observable _resizeHdlId: string = ''; - @observable _resizing: boolean = false; - @observable _offset: { x: number; y: number } = { x: 0, y: 0 }; - @observable _resizeUndo: UndoManager.Batch | undefined = undefined; - @observable _initDimensions: { width: number; height: number; x?: number; y?: number } = { width: 300, height: 400, x: undefined, y: undefined }; - @observable _menuDimensions: { width: number; height: number } = { width: 400, height: 400 }; - @observable _editing: boolean = false; +type DecorationField = Field; - constructor(props: any) { - super(props); - makeObservable(this); - DocCreatorMenu.Instance = this; - //setTimeout(() => this.generateTemplates('')); - } +type InkDecoration = {}; - @action setDataViz = (dataViz: DataVizBox) => { - this._dataViz = dataViz; - }; - @action setTemplateDocs = (docs: Doc[]) => { - this._templateDocs = docs.map(doc => (doc.annotationOn ? DocCast(doc.annotationOn) : doc)); - }; - @action setGSuggestedTemplates = (docs: Doc[]) => { - this._suggestedTemplates = docs; - }; +type TemplateDecorations = Field | InkDecoration; - @computed get docsToRender() { - return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : []; - } +interface TemplateOpts extends FieldOpts {} - @computed get rowsCount() { - switch (this._layout.type) { - case LayoutType.Row: - case LayoutType.Stacked: - return 1; - case LayoutType.Column: - return this.docsToRender.length; - case LayoutType.Grid: - return Math.ceil(this.docsToRender.length / (this._layout.columns ?? 1)) ?? 0; - default: - return 0; - } +export interface TemplateDocInfos { + title: string; + height: number; + width: number; + opts: TemplateOpts; + fields: Field[]; + decorations: Field[]; +} + +export class TemplateLayouts { + public static get allTemplates(): TemplateDocInfos[] { + return Object.values(TemplateLayouts).filter(value => typeof value === 'object' && value !== null && 'title' in value) as TemplateDocInfos[]; } - @computed get columnsCount() { - switch (this._layout.type) { - case LayoutType.Row: - return this.docsToRender.length; - case LayoutType.Column: - case LayoutType.Stacked: - return 1; - case LayoutType.Grid: - return this._layout.columns ?? 0; + public static getTemplateByTitle = (title: string): TemplateDocInfos | undefined => { + switch (title) { + case 'fourfield1': + return TemplateLayouts.FourField001; + case 'fourfield2': + return TemplateLayouts.FourField002; + // case 'fourfield3': + // return TemplateLayouts.FourField003; + case 'fourfield4': + return TemplateLayouts.FourField004; + case 'threefield1': + return TemplateLayouts.ThreeField001; + case 'threefield2': + return TemplateLayouts.ThreeField002; default: - return 0; + break; } - } - @computed get selectedFields() { - return StrListCast(this._dataViz?.layoutDoc._dataViz_axes); - } - - @computed get fieldsInfos(): Col[] { - const colInfo = this._dataViz?.colsInfo; - return this.selectedFields - .map(field => { - const fieldInfo = colInfo?.get(field); - - const col: Col = { - title: field, - type: fieldInfo?.type ?? TemplateFieldType.UNSET, - desc: fieldInfo?.desc ?? '', - sizes: fieldInfo?.sizes ?? [TemplateFieldSize.MEDIUM], - }; - - if (fieldInfo?.defaultContent !== undefined) { - col.defaultContent = fieldInfo.defaultContent; - } - - return col; - }) - .concat(this._columns); - } - - @computed get canMakeDocs() { - return this._selectedTemplate !== undefined && this._layout !== undefined; - } - - get bounds(): { t: number; b: number; l: number; r: number } { - const rect = this._ref?.getBoundingClientRect(); - const bounds = { t: rect?.top ?? 0, b: rect?.bottom ?? 0, l: rect?.left ?? 0, r: rect?.right ?? 0 }; - return bounds; - } - - setUpButtonClick = (e: any, func: Function) => { - setupMoveUpEvents( - this, - e, - returnFalse, - emptyFunction, - undoable(clickEv => { - clickEv.stopPropagation(); - clickEv.preventDefault(); - func(); - }, 'create docs') - ); - }; - - @action - onPointerDown = (e: PointerEvent) => { - this._mouseX = e.clientX; - this._mouseY = e.clientY; + return undefined; }; - @action - onPointerUp = (e: PointerEvent) => { - if (this._resizing) { - this._initDimensions.width = this._menuDimensions.width; - this._initDimensions.height = this._menuDimensions.height; - this._initDimensions.x = this._pageX; - this._initDimensions.y = this._pageY; - document.removeEventListener('pointermove', this.onResize); - SnappingManager.SetIsResizing(undefined); - this._resizing = false; - } - if (this._dragging) { - document.removeEventListener('pointermove', this.onDrag); - this._dragging = false; - } - if (e.button !== 2 && !e.ctrlKey) return; - const curX = e.clientX; - const curY = e.clientY; - if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) { - this._shouldDisplay = false; - } - }; - - componentDidMount() { - document.addEventListener('pointerdown', this.onPointerDown, true); - document.addEventListener('pointerup', this.onPointerUp); - this._disposers.templates = reaction( - () => this._templateDocs.slice(), - docs => this.updateIcons(docs) - ); - this._disposers.gpt = reaction( - () => this._suggestedTemplates.slice(), - docs => this.updateIcons(docs) - ); - //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')}) - this._disposers.lightbox = reaction( - () => LightboxView.LightboxDoc(), - doc => { - doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu(); - } - ); - //this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', desc: ''}})) - } - - componentWillUnmount() { - Object.values(this._disposers).forEach(disposer => disposer?.()); - document.removeEventListener('pointerdown', this.onPointerDown, true); - document.removeEventListener('pointerup', this.onPointerUp); - } - - updateIcons = (docs: Doc[]) => { - console.log('called') - docs.map(this.getIcon); - }; - - @action - updateSelectedCols = (cols: string[]) => { - this._selectedCols; - }; - - @action - toggleDisplay = (x: number, y: number) => { - if (this._shouldDisplay) { - this._shouldDisplay = false; - } else { - this._pageX = x; - this._pageY = y; - this._shouldDisplay = true; - } - }; - - @action - closeMenu = () => { - this._shouldDisplay = false; - }; - - @action - openMenu = () => { - const allTemplates = this._templateDocs.concat(this._suggestedTemplates); - this._shouldDisplay = true; - this.updateIcons(allTemplates); + public static FourField001: TemplateDocInfos = { + title: 'fourfield1', + width: 416, + height: 700, + opts: { + backgroundColor: '#C0B887', + cornerRounding: 20, + borderColor: '#6B461F', + borderWidth: '12', + }, + fields: [ + { + tl: [-0.95, -1], + br: [0.95, -0.85], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY], + description: 'A title field for very short text that contextualizes the content.', + opts: { + backgroundColor: 'transparent', + color: '#F1F0E9', + contentXCentering: 'h-center', + fontBold: true, + }, + }, + { + tl: [-0.87, -0.83], + br: [0.87, 0.2], + types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: 'The main focus of the template; could be an image, long text, etc.', + opts: { + cornerRounding: 20, + borderColor: '#8F5B25', + borderWidth: '6', + backgroundColor: '#CECAB9', + }, + }, + { + tl: [-0.8, 0.2], + br: [0.8, 0.3], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], + description: 'A caption for field #2, very short to short text that contextualizes the content of field #2', + opts: { + backgroundColor: 'transparent', + contentXCentering: 'h-center', + color: '#F1F0E9', + }, + }, + { + tl: [-0.87, 0.37], + br: [0.87, 0.96], + types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: 'A medium-sized field for medium/long text.', + opts: { + cornerRounding: 15, + borderColor: '#8F5B25', + borderWidth: '6', + backgroundColor: '#CECAB9', + }, + }, + ], + decorations: [], }; - @action - onResizePointerDown = (e: React.PointerEvent): void => { - this._resizing = true; - document.addEventListener('pointermove', this.onResize); - SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them - e.stopPropagation(); - const id = (this._resizeHdlId = e.currentTarget.className); - const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as any).width.replace('px', '')) / 2 : 0; - const bounds = e.currentTarget.getBoundingClientRect(); - this._offset = { - x: id.toLowerCase().includes('left') ? bounds.right - e.clientX - pad : bounds.left - e.clientX + pad, // - y: id.toLowerCase().includes('top') ? bounds.bottom - e.clientY - pad : bounds.top - e.clientY + pad, - }; - this._resizeUndo = UndoManager.StartBatch('drag resizing'); - this._snapPt = { x: e.pageX, y: e.pageY }; + public static FourField002: TemplateDocInfos = { + title: 'fourfield2', + width: 425, + height: 778, + opts: { + backgroundColor: '#242425', + }, + fields: [ + { + tl: [-0.83, -0.95], + br: [0.83, -0.2], + types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], + description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus.', + opts: { + borderWidth: '8', + borderColor: '#F8E71C', + }, + }, + { + tl: [-0.65, -0.2], + br: [0.65, -0.02], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY], + description: 'A tiny field for just a word or two of plain text.', + opts: { + backgroundColor: 'transparent', + color: 'white', + contentXCentering: 'h-center', + fontTransform: 'uppercase', + }, + }, + { + tl: [-0.65, 0], + br: [0.65, 0.18], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY], + description: 'A tiny field for just a word or two of plain text.', + opts: { + backgroundColor: 'transparent', + color: 'white', + contentXCentering: 'h-center', + fontTransform: 'uppercase', + }, + }, + { + tl: [-0.83, 0.2], + br: [0.83, 0.95], + types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus, or share focus with field 1.', + opts: { + borderWidth: '8', + borderColor: '#F8E71C', + color: 'white', + backgroundColor: '#242425', + }, + }, + ], + decorations: [ + { + tl: [-0.8, -0.075], + br: [-0.525, 0.075], + opts: { + backgroundColor: '#F8E71C', + rotation: 45, + }, + }, + { + tl: [-0.3075, -0.0245], + br: [-0.2175, 0.0245], + opts: { + backgroundColor: '#F8E71C', + rotation: 45, + }, + }, + { + tl: [-0.045, -0.0245], + br: [0.045, 0.0245], + opts: { + backgroundColor: '#F8E71C', + rotation: 45, + }, + }, + { + tl: [0.2175, -0.0245], + br: [0.3075, 0.0245], + opts: { + backgroundColor: '#F8E71C', + rotation: 45, + }, + }, + { + tl: [0.525, -0.075], + br: [0.8, 0.075], + opts: { + backgroundColor: '#F8E71C', + rotation: 45, + }, + }, + ], }; - @action - onResize = (e: any): boolean => { - const dragHdl = this._resizeHdlId.split(' ')[1]; - const thisPt = DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y); - - const { scale, refPt, transl } = this.getResizeVals(thisPt, dragHdl); - !this._interactionLock && runInAction(async () => { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate) - this._interactionLock = true; - const scaleAspect = {x: scale.x, y: scale.y}; - this.resizeView(refPt, scaleAspect, transl); // prettier-ignore - await new Promise(res => { setTimeout(() => { res(this._interactionLock = undefined)})}); - }); // prettier-ignore - return true; - }; - - @action - onDrag = (e: any): boolean => { - this._pageX = e.pageX - (this._startPos?.x ?? 0); - this._pageY = e.pageY - (this._startPos?.y ?? 0); - this._initDimensions.x = this._pageX; - this._initDimensions.y = this._pageY; - return true; - }; + // public static FourField003: TemplateDocInfos = { + // title: 'fourfield3', + // width: 477, + // height: 662, + // opts: { + // backgroundColor: '#9E9C95' + // }, + // fields: [{ + // tl: [-.875, -.9], + // br: [.875, .7], + // types: [TemplateFieldType.VISUAL], + // sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + // description: '', + // opts: { + // borderWidth: '15', + // borderColor: '#E0E0DA', + // } + // }, { + // tl: [-.95, .8], + // br: [-.1, .95], + // types: [TemplateFieldType.TEXT], + // sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], + // description: '', + // opts: { + // backgroundColor: 'transparent', + // color: 'white', + // contentXCentering: 'h-right', + // } + // }, { + // tl: [.1, .8], + // br: [.95, .95], + // types: [TemplateFieldType.TEXT], + // sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], + // description: '', + // opts: { + // backgroundColor: 'transparent', + // color: 'red', + // fontTransform: 'uppercase', + // contentXCentering: 'h-left' + // } + // }, { + // tl: [0, -.9], + // br: [.85, -.66], + // types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], + // sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + // description: '', + // opts: { + // backgroundColor: 'transparent', + // contentXCentering: 'h-right' + // } + // }], + // decorations: [{ + // tl: [-.025, .8], + // br: [.025, .95], + // opts: { + // backgroundColor: '#E0E0DA', + // } + // }] + // }; - getResizeVals = (thisPt: { x: number; y: number }, dragHdl: string) => { - const [w, h] = [this._initDimensions.width, this._initDimensions.height]; - const [moveX, moveY] = [thisPt.x - this._snapPt.x, thisPt.y - this._snapPt.y]; - let vals: { scale: { x: number; y: number }; refPt: [number, number]; transl: { x: number; y: number } }; - switch (dragHdl) { - case 'topLeft': vals = { scale: { x: 1 - moveX / w, y: 1 -moveY / h }, refPt: [this.bounds.r, this.bounds.b], transl: {x: moveX, y: moveY } }; break; - case 'topRight': vals = { scale: { x: 1 + moveX / w, y: 1 -moveY / h }, refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break; - case 'top': vals = { scale: { x: 1, y: 1 -moveY / h }, refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break; - case 'left': vals = { scale: { x: 1 - moveX / w, y: 1 }, refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break; - case 'bottomLeft': vals = { scale: { x: 1 - moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break; - case 'right': vals = { scale: { x: 1 + moveX / w, y: 1 }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; - case 'bottomRight':vals = { scale: { x: 1 + moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; - case 'bottom': vals = { scale: { x: 1, y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; - default: vals = { scale: { x: 1, y: 1 }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; - } // prettier-ignore - return vals; + public static FourField004: TemplateDocInfos = { + title: 'fourfield4', + width: 414, + height: 583, + opts: { + backgroundColor: '#6CCAF0', + borderColor: '#1088C3', + borderWidth: '10', + }, + fields: [ + { + tl: [-0.86, -0.92], + br: [-0.075, -0.77], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY], + description: 'A tiny field for just a word or two of plain text.', + opts: { + backgroundColor: '#E2B4F5', + borderWidth: '9', + borderColor: '#9222F1', + contentXCentering: 'h-center', + }, + }, + { + tl: [0.075, -0.92], + br: [0.86, -0.77], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY], + description: 'A tiny field for just a word or two of plain text.', + opts: { + backgroundColor: '#F5B4DD', + borderWidth: '9', + borderColor: '#E260F3', + contentXCentering: 'h-center', + }, + }, + { + tl: [-0.81, -0.64], + br: [0.81, 0.48], + types: [TemplateFieldType.VISUAL], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: 'A large to huge field for visual content that is the main content of the template.', + opts: { + borderWidth: '16', + borderColor: '#A2BD77', + }, + }, + { + tl: [-0.86, 0.6], + br: [0.86, 0.92], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], + description: 'A medium to large field for text that describes the visual content above', + opts: { + borderWidth: '9', + borderColor: '#F0D601', + backgroundColor: '#F3F57D', + }, + }, + ], + decorations: [ + { + tl: [-0.852, -0.67], + br: [0.852, 0.51], + opts: { + backgroundColor: 'transparent', + borderColor: '#007C0C', + borderWidth: '10', + }, + }, + ], }; - resizeView = (refPt: number[], scale: { x: number; y: number }, translation: { x: number; y: number }) => { - const refCent = [refPt[0], refPt[1]]; // fixed reference point for resize (ie, a point that doesn't move) - if (this._initDimensions.x === undefined) this._initDimensions.x = this._pageX; - if (this._initDimensions.y === undefined) this._initDimensions.y = this._pageY; - const { height, width, x, y } = this._initDimensions; - - this._menuDimensions.width = Math.max(300, scale.x * width); - this._menuDimensions.height = Math.max(200, scale.y * height); - this._pageX = x + translation.x; - this._pageY = y + translation.y; + public static ThreeField001: TemplateDocInfos = { + title: 'threefield1', + width: 575, + height: 770, + opts: { + backgroundColor: '#DDD3A9', + }, + fields: [ + { + tl: [-0.66, -0.747], + br: [0.66, 0.247], + types: [TemplateFieldType.VISUAL], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: 'A medium to large field for visual content that is the central focus.', + opts: { + borderColor: 'yellow', + borderWidth: '8', + rotation: 45, + }, + }, + { + tl: [-0.7, 0.2], + br: [0.7, 0.46], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], + description: 'A very small text field for one to a few words. A good caption for the image.', + opts: { + backgroundColor: 'transparent', + contentXCentering: 'h-center', + }, + }, + { + tl: [-0.95, 0.5], + br: [0.95, 0.95], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE], + description: 'A medium to large text field for a thorough description of the image. ', + opts: { + backgroundColor: 'transparent', + color: 'white', + }, + }, + ], + decorations: [ + { + tl: [0.2, -1.32], + br: [1.8, -0.66], + opts: { + backgroundColor: '#CEB155', + rotation: 45, + }, + }, + { + tl: [-1.8, -1.32], + br: [-0.2, -0.66], + opts: { + backgroundColor: '#CEB155', + rotation: 135, + }, + }, + { + tl: [0.33, 0.75], + br: [1.66, 1.25], + opts: { + backgroundColor: '#CEB155', + rotation: 135, + }, + }, + { + tl: [-1.66, 0.75], + br: [-0.33, 1.25], + opts: { + backgroundColor: '#CEB155', + rotation: 45, + }, + }, + ], }; - async getIcon(doc: Doc) { - const docView = DocumentView.getDocumentView(doc); - if (docView) { - docView.ComponentView?.updateIcon?.(); - return new Promise(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 500)); - } - return undefined; - } - - @action updateSelectedTemplate = (template: Doc) => { - if (this._selectedTemplate === template) { - this._selectedTemplate = undefined; - return; - } else { - this._selectedTemplate = template; - MakeTemplate(template); - } + public static ThreeField002: TemplateDocInfos = { + title: 'threefield2', + width: 477, + height: 662, + opts: { + backgroundColor: '#9E9C95', + }, + fields: [ + { + tl: [-0.875, -0.9], + br: [0.875, 0.7], + types: [TemplateFieldType.VISUAL], + sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], + description: 'A medium to large visual field for the main content of the template', + opts: { + borderWidth: '15', + borderColor: '#E0E0DA', + }, + }, + { + tl: [0.1, 0.775], + br: [0.95, 0.975], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], + description: 'A very small text field for one to a few words. The content should represent a general categorization of the image.', + opts: { + backgroundColor: 'transparent', + color: '#AF0D0D', + fontTransform: 'uppercase', + fontBold: true, + contentXCentering: 'h-left', + }, + }, + { + tl: [-0.95, 0.775], + br: [-0.1, 0.975], + types: [TemplateFieldType.TEXT], + sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], + description: 'A very small text field for one to a few words. The content should contextualize field 2.', + opts: { + backgroundColor: 'transparent', + color: 'black', + contentXCentering: 'h-right', + }, + }, + ], + decorations: [ + { + tl: [-0.025, 0.8], + br: [0.025, 0.95], + opts: { + backgroundColor: '#E0E0DA', + }, + }, + ], }; +} - @action updateSelectedSavedLayout = (layout: DataVizTemplateLayout) => { - this._layout.xMargin = layout.layout.xMargin; - this._layout.yMargin = layout.layout.yMargin; - this._layout.type = layout.layout.type; - this._layout.columns = layout.columns; - }; +export class FieldUtils { + public static contentFields = (fields: Field[]) => { + let toRet: Field[] = []; + fields.forEach(field => { + if (!field.isDecoration) { + toRet.push(field); + } + toRet = toRet.concat(FieldUtils.contentFields(field.subfields ?? [])); + }); - isSelectedLayout = (layout: DataVizTemplateLayout) => { - return this._layout.xMargin === layout.layout.xMargin && this._layout.yMargin === layout.layout.yMargin && this._layout.type === layout.layout.type && this._layout.columns === layout.columns; + return toRet; }; - @action - generateTemplates = async (inputText: string) => { - ++this._callCount; - const origCount = this._callCount; + public static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => { + const words: string[] = text.split(/\s+/).filter(Boolean); - let prompt: string = `(#${origCount}) Please generate for the fields:`; - this.selectedFields?.forEach(field => (prompt += ` ${field},`)); - prompt += ` (-----NOT A FIELD-----) Additional prompt: ${inputText}`; + let currFontSize = 1; + let rowsCount = 1; + let currTextHeight = currFontSize * rowsCount * 2; - this._GPTLoading = true; + while (currTextHeight <= contHeight) { + let wordIndex = 0; + let currentRowWidth = 0; + let wordsInCurrRow = 0; + rowsCount = 1; - try { - const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); + while (wordIndex < words.length) { + const word = words[wordIndex]; + const wordWidth = word.length * currFontSize * 0.5; + //console.log(wordWidth) - if (res && this._callCount === origCount) { - this._suggestedTemplates = []; - const templates: { template_type: string; fieldVals: { title: string; tlx: string; tly: string; brx: string; bry: string }[] }[] = JSON.parse(res); - this.createGeneratedTemplates(templates, 500, 500); + if (currentRowWidth + wordWidth <= contWidth) { + currentRowWidth += wordWidth; + ++wordsInCurrRow; + } else { + if (words.length !== 1 && words.length > wordsInCurrRow) { + rowsCount++; + currentRowWidth = wordWidth; + wordsInCurrRow = 1; + } else { + break; + } + } + + wordIndex++; } - } catch (err) { - console.error(err); + + currTextHeight = rowsCount * currFontSize * 2; + //console.log(rowsCount, currFontSize, currTextHeight) + + currFontSize += 1; } + + return currFontSize - 1; }; - @action - createGeneratedTemplates = (layouts: { template_type: string; fieldVals: { title: string; tlx: string; tly: string; brx: string; bry: string }[] }[], tempWidth: number, tempHeight: number) => { - const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; - const GPTTemplates: Doc[] = []; + private static getDimensions = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number): { width: number; height: number; coord: { x: number; y: number } } => { + const l = (coords.tl[0] * parentHeight) / 2; + const t = coords.tl[1] * parentWidth / 2; //prettier-ignore + const r = (coords.br[0] * parentHeight) / 2; + const b = coords.br[1] * parentWidth / 2; //prettier-ignore + const width = r - l; + const height = b - t; + const coord = { x: l, y: t }; + //console.log(coords, parentWidth, parentHeight, height); + return { width, height, coord }; + }; - layouts.forEach(layout => { - const fields: Doc[] = layout.fieldVals.map(field => { - const left: number = (Number(field.tlx) * tempWidth) / 2; - const top: number = Number(field.tly) * tempHeight / 2; //prettier-ignore - const right: number = (Number(field.brx) * tempWidth) / 2; - const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore - const height = bottom - top; - const width = right - left; - const doc = !field.title.includes('$$') - ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height / 2}` }) - : Docs.Create.ImageDocument('', { _height: height, _width: width, title: field.title.replace(/\$\$/g, ''), x: left, y: top }); - return doc; - }); + public static FreeformField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => { + const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); - const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400000, y: 400000 }); + const docWithBasicOpts = Docs.Create.FreeformDocument([], { + isDefaultTemplateDoc: true, + _height: height, + _width: width, + title: title, + x: coord.x, + y: coord.y, + backgroundColor: opts.backgroundColor ?? '', + _layout_borderRounding: `${opts.cornerRounding ?? 0}px`, + borderColor: opts.borderColor, + borderWidth: opts.borderWidth, + opacity: opts.opacity, + hCentering: opts.contentXCentering, + _rotation: opts.rotation, + }); - mainCollection.addDocument(template); + return docWithBasicOpts; + }; - GPTTemplates.push(template); + public static TextField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => { + const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); + + const docWithBasicOpts = Docs.Create.TextDocument(content, { + isDefaultTemplateDoc: true, + _height: height, + _width: width, + title: title, + x: coord.x, + y: coord.y, + _text_fontSize: `${FieldUtils.calculateFontSize(width, height, content, true)}`, + backgroundColor: opts.backgroundColor ?? '', + text_fontColor: opts.color, + contentBold: opts.fontBold, + textTransform: opts.fontTransform, + color: opts.color, + _layout_borderRounding: `${opts.cornerRounding ?? 0}px`, + borderColor: opts.borderColor, + borderWidth: opts.borderWidth, + opacity: opts.opacity, + hCentering: opts.contentXCentering, + _rotation: opts.rotation, }); - setTimeout(() => { - this.setGSuggestedTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/ - }, 100); + docWithBasicOpts._layout_hideScroll = true; - this.forceUpdate(); + return docWithBasicOpts; }; - editTemplate = (doc: Doc) => { - //this.closeMenu(); - DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight); - DocumentView.DeselectAll(); - Doc.UnBrushDoc(doc); - }; + public static ImageField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => { + const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); - removeTemplate = (doc: Doc) => { - this._templateDocs.splice(this._templateDocs.indexOf(doc), 1); - }; + const doc = Docs.Create.ImageDocument(content, { + isDefaultTemplateDoc: true, + _height: height, + _width: width, + title: title, + x: coord.x, + y: coord.y, + _layout_fitWidth: false, + backgroundColor: opts.backgroundColor ?? '', + _layout_borderRounding: `${opts.cornerRounding ?? 0}px`, + borderColor: opts.borderColor, + borderWidth: opts.borderWidth, + opacity: opts.opacity, + _rotation: opts.rotation, + }); - testTemplate = async () => { - this.updateIcons(this._suggestedTemplates.slice()); - this.forceUpdate(); + //setTimeout(() => {doc._height = height; doc._width = width}, 10); - // try { - // const res = await gptImageCall('Image of panda eating a cookie'); + return doc; + }; - // if (res) { - // const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res }); + public static CarouselField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, fields: Doc[]) => { + const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight); - // console.log(result); - // } - // } catch (e) { - // console.log(e); - // } - }; + const doc = Docs.Create.Carousel3DDocument(fields, { _height: height, _width: width, title: title, x: coord.x, y: coord.y, _text_fontSize: `${height / 2}` }); - @action addField = () => { - const newFields: Col[] = this._columns.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [] }]); - this._columns = newFields; + return doc; }; +} - @action removeField = (field: { title: string; type: string; desc: string }) => { - if (this._dataViz?.axes.includes(field.title)) { - this._dataViz.selectAxes(this._dataViz.axes.filter(col => col !== field.title)); - } else { - const toRemove = this._columns.filter(f => f === field); - if (!toRemove) return; - - if (toRemove.length > 1) { - while (toRemove.length > 1) { - toRemove.pop(); - } - } - - if (this._columns.length === 1) { - this._columns = []; - } else { - this._columns.splice(this._columns.indexOf(toRemove[0]), 1); - } - } - }; +// public static FourField002: TemplateDocInfos = { +// width: 450, +// height: 600, +// fields: [{ +// tl: [-.6, -.9], +// br: [.6, -.8], +// types: [FieldType.TEXT], +// sizes: [FieldSize.TINY] +// }, { +// tl: [-.9, -.7], +// br: [.9, .2], +// types: [FieldType.TEXT, FieldType.VISUAL], +// sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE] +// }, { +// tl: [-.9, .3], +// br: [-.05, .9], +// types: [FieldType.TEXT], +// sizes: [FieldSize.TINY] +// }, { +// tl: [.05, .3], +// br: [.9, .9], +// types: [FieldType.TEXT, FieldType.VISUAL], +// sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE] +// }] +// }; + +// public static TwoFieldPlusCarousel: TemplateDocInfos = { +// width: 500, +// height: 600, +// fields: [{ +// tl: [-.9, -.99], +// br: [.9, -.7], +// types: [FieldType.TEXT], +// sizes: [FieldSize.TINY] +// }, { +// tl: [-.9, -.65], +// br: [.9, .35], +// types: [], +// sizes: [] +// }, { +// tl: [-.9, .4], +// br: [.9, .95], +// types: [FieldType.TEXT], +// sizes: [FieldSize.TINY] +// }] +// }; +// } - @action setColTitle = (column: Col, title: string) => { - if (this.selectedFields.includes(column.title)) { - this._dataViz?.setColumnTitle(column.title, title); - } else { - column.title = title; - } - this.forceUpdate(); - }; +@observer +export class DocCreatorMenu extends ObservableReactComponent { + // eslint-disable-next-line no-use-before-define + static Instance: DocCreatorMenu; - @action setColType = (column: Col, type: TemplateFieldType) => { - if (this.selectedFields.includes(column.title)) { - this._dataViz?.setColumnType(column.title, type); - } else { - column.type = type; - } - this.forceUpdate(); - }; + private _disposers: { [name: string]: IDisposer } = {}; - modifyColSizes = (column: Col, size: TemplateFieldSize, valid: boolean) => { - if (this.selectedFields.includes(column.title)) { - this._dataViz?.modifyColumnSizes(column.title, size, valid); - } else { - if (!valid && column.sizes.includes(size)) { - column.sizes.splice(column.sizes.indexOf(size), 1); - } else if (valid && !column.sizes.includes(size)) { - column.sizes.push(size); - } - } - this.forceUpdate(); - }; + private _ref: HTMLDivElement | null = null; - setColDesc = (column: Col, desc: string) => { - if (this.selectedFields.includes(column.title)) { - this._dataViz?.setColumnDesc(column.title, desc); - } else { - column.desc = desc; - } - this.forceUpdate(); - }; + @observable _templateDocs: Doc[] = []; + @observable _selectedTemplate: Doc | undefined = undefined; + @observable _columns: Col[] = []; + @observable _selectedCols: { title: string; type: string; desc: string }[] | undefined = []; - generateGPTImage = async (prompt: string): Promise => { - console.log(prompt); + @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0 }; + @observable _layoutPreview: boolean = true; + @observable _layoutPreviewScale: number = 1; + @observable _savedLayouts: DataVizTemplateLayout[] = []; + @observable _expandedPreview: { icon: ImageField; doc: Doc } | undefined = undefined; - try { - const res = await gptImageCall(prompt); + @observable _suggestedTemplates: Doc[] = []; + @observable _GPTOpt: boolean = false; + @observable _userPrompt: string = ''; + @observable _callCount: number = 0; + @observable _GPTLoading: boolean = false; - if (res) { - const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res }); - const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client); - return source; - } - } catch (e) { - console.log(e); - } - }; + @observable _pageX: number = 0; + @observable _pageY: number = 0; + @observable _indicatorX: number | undefined = undefined; + @observable _indicatorY: number | undefined = undefined; - matchesForTemplate = (template: TemplateDocInfos, cols: Col[]): number[][] => { - const colMatchesField = (col: Col, field: Field) => { - return field.sizes?.some(size => col.sizes?.includes(size)) && field.types?.includes(col.type); - }; + @observable _hoveredLayoutPreview: number | undefined = undefined; + @observable _mouseX: number = -1; + @observable _mouseY: number = -1; + @observable _startPos?: { x: number; y: number }; + @observable _shouldDisplay: boolean = false; - const matches: number[][] = Array(template.fields.length) - .fill([]) - .map(() => []); + @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' = 'templates'; + @observable _dragging: boolean = false; + @observable _draggingIndicator: boolean = false; + @observable _dataViz?: DataVizBox; + @observable _interactionLock?: boolean; + @observable _snapPt: { x: number; y: number } = { x: 0, y: 0 }; + @observable _resizeHdlId: string = ''; + @observable _resizing: boolean = false; + @observable _offset: { x: number; y: number } = { x: 0, y: 0 }; + @observable _resizeUndo: UndoManager.Batch | undefined = undefined; + @observable _initDimensions: { width: number; height: number; x?: number; y?: number } = { width: 300, height: 400, x: undefined, y: undefined }; + @observable _menuDimensions: { width: number; height: number } = { width: 400, height: 400 }; + @observable _editing: boolean = false; - template.fields.forEach((field, i) => { - cols.forEach((col, v) => { - if (colMatchesField(col, field)) { - matches[i].push(v); - } - }); - }); + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + DocCreatorMenu.Instance = this; + //setTimeout(() => this.generateTemplates('')); + } - return matches; + @action setDataViz = (dataViz: DataVizBox) => { + this._dataViz = dataViz; + }; + @action setTemplateDocs = (docs: Doc[]) => { + this._templateDocs = docs.map(doc => (doc.annotationOn ? DocCast(doc.annotationOn) : doc)); + }; + @action setGSuggestedTemplates = (docs: Doc[]) => { + this._suggestedTemplates = docs; }; - maxMatches = (fieldsCt: number, matches: number[][]) => { - const used: boolean[] = Array(fieldsCt).fill(false); - const mt: number[] = Array(fieldsCt).fill(-1); - - const augmentingPath = (v: number): boolean => { - if (used[v]) return false; - used[v] = true; - for (const to of matches[v]) { - if (mt[to] === -1 || augmentingPath(mt[to])) { - mt[to] = v; - return true; - } - } - return false; - }; + @computed get docsToRender() { + return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : []; + } - for (let v = 0; v < fieldsCt; ++v) { - used.fill(false); - augmentingPath(v); + @computed get rowsCount() { + switch (this._layout.type) { + case LayoutType.Row: + case LayoutType.Stacked: + return 1; + case LayoutType.Column: + return this.docsToRender.length; + case LayoutType.Grid: + return Math.ceil(this.docsToRender.length / (this._layout.columns ?? 1)) ?? 0; + default: + return 0; } + } - let count: number = 0; - - for (let i = 0; i < fieldsCt; ++i) { - if (mt[i] !== -1) ++count; + @computed get columnsCount() { + switch (this._layout.type) { + case LayoutType.Row: + return this.docsToRender.length; + case LayoutType.Column: + case LayoutType.Stacked: + return 1; + case LayoutType.Grid: + return this._layout.columns ?? 0; + default: + return 0; } + } - return count; - }; - - findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => { - let validTemplates: any[] = []; - templates.forEach(template => { - const numFields = template.fields.length; - if (!(numFields === cols.length)) return; - const matches = this.matchesForTemplate(template, cols); - if (this.maxMatches(numFields, matches) === numFields) { - validTemplates.push(template.title); - } - }); + @computed get selectedFields() { + return StrListCast(this._dataViz?.layoutDoc._dataViz_axes); + } - validTemplates = validTemplates.map(title => TemplateLayouts.getTemplateByTitle(title)); + @computed get fieldsInfos(): Col[] { + const colInfo = this._dataViz?.colsInfo; + return this.selectedFields + .map(field => { + const fieldInfo = colInfo?.get(field); - return validTemplates; - }; + const col: Col = { + title: field, + type: fieldInfo?.type ?? TemplateFieldType.UNSET, + desc: fieldInfo?.desc ?? '', + sizes: fieldInfo?.sizes ?? [TemplateFieldSize.MEDIUM], + }; - // createColumnField = (template: TemplateDocInfos, field: Field, column: Col): Doc => { + if (fieldInfo?.defaultContent !== undefined) { + col.defaultContent = fieldInfo.defaultContent; + } - // if (field.subfields) { - // const doc = FieldFuncs.FreeformField({ - // tl: field.tl, - // br: field.br }, - // template.height, - // template.width, - // column.title, - // '', - // field.opts - // ); + return col; + }) + .concat(this._columns); + } - // field.subfields[1].forEach(f => { - // const fDoc = () - // }) + @computed get canMakeDocs() { + return this._selectedTemplate !== undefined && this._layout !== undefined; + } - // } + get bounds(): { t: number; b: number; l: number; r: number } { + const rect = this._ref?.getBoundingClientRect(); + const bounds = { t: rect?.top ?? 0, b: rect?.bottom ?? 0, l: rect?.left ?? 0, r: rect?.right ?? 0 }; + return bounds; + } - // return new Doc; - // } + setUpButtonClick = (e: React.PointerEvent, func: () => void) => { + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoable(clickEv => { + clickEv.stopPropagation(); + clickEv.preventDefault(); + func(); + }, 'create docs') + ); + }; - /** - * Populates a preset template framework with content from a datavizbox or any AI-generated content. - * @param template the preloaded template framework being filled in - * @param assignments a list of template field numbers (from top to bottom) and their assigned columns from the linked dataviz - * @returns a doc containing the fully rendered template - */ - fillPresetTemplate = async (template: TemplateDocInfos, assignments: { [field: string]: Col }): Promise => { - const wordLimit = (size: TemplateFieldSize) => { - switch (size) { - case TemplateFieldSize.TINY: - return 2; - case TemplateFieldSize.SMALL: - return 5; - case TemplateFieldSize.MEDIUM: - return 20; - case TemplateFieldSize.LARGE: - return 50; - case TemplateFieldSize.HUGE: - return 100; - default: - return 10; - } - }; + @action + onPointerDown = (e: PointerEvent) => { + this._mouseX = e.clientX; + this._mouseY = e.clientY; + }; - const renderTextCalls = async (): Promise => { - const rendered: Doc[] = []; + @action + onPointerUp = (e: PointerEvent) => { + if (this._resizing) { + this._initDimensions.width = this._menuDimensions.width; + this._initDimensions.height = this._menuDimensions.height; + this._initDimensions.x = this._pageX; + this._initDimensions.y = this._pageY; + document.removeEventListener('pointermove', this.onResize); + SnappingManager.SetIsResizing(undefined); + this._resizing = false; + } + if (this._dragging) { + document.removeEventListener('pointermove', this.onDrag); + this._dragging = false; + } + if (e.button !== 2 && !e.ctrlKey) return; + const curX = e.clientX; + const curY = e.clientY; + if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) { + this._shouldDisplay = false; + } + }; - if (GPTTextCalls.length) { - try { - const prompt = fieldContent + GPTTextAssignment; + componentDidMount() { + document.addEventListener('pointerdown', this.onPointerDown, true); + document.addEventListener('pointerup', this.onPointerUp); + this._disposers.templates = reaction( + () => this._templateDocs.slice(), + docs => this.updateIcons(docs) + ); + this._disposers.gpt = reaction( + () => this._suggestedTemplates.slice(), + docs => this.updateIcons(docs) + ); + //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')}) + this._disposers.lightbox = reaction( + () => LightboxView.LightboxDoc(), + doc => { + doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu(); + } + ); + //this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', desc: ''}})) + } - const res = await gptAPICall(prompt, GPTCallType.FILL); + componentWillUnmount() { + Object.values(this._disposers).forEach(disposer => disposer?.()); + document.removeEventListener('pointerdown', this.onPointerDown, true); + document.removeEventListener('pointerup', this.onPointerUp); + } - if (res) { - const assignments: { [title: string]: { number: string; content: string } } = JSON.parse(res); - //console.log('assignments', GPTAssignments, 'assignment string', GPTAssignmentString, 'field content', fieldContent, 'response', res, 'assignments', assignments); - Object.entries(assignments).forEach(([title, info]) => { - const field: Field = template.fields[Number(info.number)]; - const col = this.getColByTitle(title); + updateIcons = (docs: Doc[]) => { + console.log('called'); + docs.map(this.getIcon); + }; - const doc = FieldUtils.TextField( - { - tl: field.tl, - br: field.br, - }, - template.height, - template.width, - col.title, - info.content ?? '', - field.opts - ); + @action + updateSelectedCols = (cols: string[]) => { + this._selectedCols; + }; - rendered.push(doc); - }); - } - } catch (err) { - console.log(err); - } - } + @action + toggleDisplay = (x: number, y: number) => { + if (this._shouldDisplay) { + this._shouldDisplay = false; + } else { + this._pageX = x; + this._pageY = y; + this._shouldDisplay = true; + } + }; - return rendered; - }; + @action + closeMenu = () => { + this._shouldDisplay = false; + }; - const createGeneratedImage = async (fieldNum: string, col: Col, prompt: string) => { - const url = await this.generateGPTImage(prompt); - const field: Field = template.fields[Number(fieldNum)]; - const doc = FieldUtils.ImageField( - { - tl: field.tl, - br: field.br, - }, - template.height, - template.width, - col.title, - url ?? '', - field.opts - ); + @action + openMenu = () => { + const allTemplates = this._templateDocs.concat(this._suggestedTemplates); + this._shouldDisplay = true; + this.updateIcons(allTemplates); + }; - return doc; + @action + onResizePointerDown = (e: React.PointerEvent): void => { + this._resizing = true; + document.addEventListener('pointermove', this.onResize); + SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them + e.stopPropagation(); + const id = (this._resizeHdlId = e.currentTarget.className); + const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as Element).width.replace('px', '')) / 2 : 0; + const bounds = e.currentTarget.getBoundingClientRect(); + this._offset = { + x: id.toLowerCase().includes('left') ? bounds.right - e.clientX - pad : bounds.left - e.clientX + pad, // + y: id.toLowerCase().includes('top') ? bounds.bottom - e.clientY - pad : bounds.top - e.clientY + pad, }; + this._resizeUndo = UndoManager.StartBatch('drag resizing'); + this._snapPt = { x: e.pageX, y: e.pageY }; + }; - const renderImageCalls = async (): Promise => { - const rendered: Doc[] = []; - const calls = GPTIMGCalls; + @action + onResize = (e: PointerEvent): boolean => { + const dragHdl = this._resizeHdlId.split(' ')[1]; + const thisPt = DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y); - if (calls.length) { - try { - const renderedImages: Doc[] = await Promise.all( - calls.map(async ([fieldNum, col]) => { - const sysPrompt = - 'Your job is to create a prompt for an AI image generator to help it generate an image based on existing content in a template and a user prompt. Your prompt should focus heavily on visual elements to help the image generator; avoid unecessary info that might distract it. ONLY INCLUDE THE PROMPT, NO OTHER TEXT OR EXPLANATION. The existing content is as follows: ' + - fieldContent + - ' **** The user prompt is: ' + - col.desc; + const { scale, refPt, transl } = this.getResizeVals(thisPt, dragHdl); + !this._interactionLock && runInAction(async () => { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate) + this._interactionLock = true; + const scaleAspect = {x: scale.x, y: scale.y}; + this.resizeView(refPt, scaleAspect, transl); // prettier-ignore + await new Promise(res => { setTimeout(() => { res(this._interactionLock = undefined)})}); + }); // prettier-ignore + return true; + }; - const prompt = await gptAPICall(sysPrompt, GPTCallType.COMPLETEPROMPT); - console.log(sysPrompt, prompt); + @action + onDrag = (e: PointerEvent): boolean => { + this._pageX = e.pageX - (this._startPos?.x ?? 0); + this._pageY = e.pageY - (this._startPos?.y ?? 0); + this._initDimensions.x = this._pageX; + this._initDimensions.y = this._pageY; + return true; + }; - return createGeneratedImage(fieldNum, col, prompt); - }) - ); + getResizeVals = (thisPt: { x: number; y: number }, dragHdl: string) => { + const [w, h] = [this._initDimensions.width, this._initDimensions.height]; + const [moveX, moveY] = [thisPt.x - this._snapPt.x, thisPt.y - this._snapPt.y]; + let vals: { scale: { x: number; y: number }; refPt: [number, number]; transl: { x: number; y: number } }; + switch (dragHdl) { + case 'topLeft': vals = { scale: { x: 1 - moveX / w, y: 1 -moveY / h }, refPt: [this.bounds.r, this.bounds.b], transl: {x: moveX, y: moveY } }; break; + case 'topRight': vals = { scale: { x: 1 + moveX / w, y: 1 -moveY / h }, refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break; + case 'top': vals = { scale: { x: 1, y: 1 -moveY / h }, refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break; + case 'left': vals = { scale: { x: 1 - moveX / w, y: 1 }, refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break; + case 'bottomLeft': vals = { scale: { x: 1 - moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break; + case 'right': vals = { scale: { x: 1 + moveX / w, y: 1 }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; + case 'bottomRight':vals = { scale: { x: 1 + moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; + case 'bottom': vals = { scale: { x: 1, y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; + default: vals = { scale: { x: 1, y: 1 }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break; + } // prettier-ignore + return vals; + }; - const renderedTemplates: Doc[] = await Promise.all(renderedImages); - renderedTemplates.forEach(doc => rendered.push(doc)); - } catch (e) { - console.log(e); - } - } + resizeView = (refPt: number[], scale: { x: number; y: number }, translation: { x: number; y: number }) => { + if (this._initDimensions.x === undefined) this._initDimensions.x = this._pageX; + if (this._initDimensions.y === undefined) this._initDimensions.y = this._pageY; + const { height, width, x, y } = this._initDimensions; - return rendered; - }; + this._menuDimensions.width = Math.max(300, scale.x * width); + this._menuDimensions.height = Math.max(200, scale.y * height); + this._pageX = x + translation.x; + this._pageY = y + translation.y; + }; - const fields: Doc[] = []; + async getIcon(doc: Doc) { + const docView = DocumentView.getDocumentView(doc); + if (docView) { + docView.ComponentView?.updateIcon?.(); + return new Promise(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 500)); + } + return undefined; + } - const GPTAssignments = Object.entries(assignments).filter(([f, col]) => this._columns.includes(col)); - const nonGPTAssignments: [string, Col][] = Object.entries(assignments).filter(a => !GPTAssignments.includes(a)); - const GPTTextCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.TEXT); - const GPTIMGCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.VISUAL); + @action updateSelectedTemplate = (template: Doc) => { + if (this._selectedTemplate === template) { + this._selectedTemplate = undefined; + return; + } else { + this._selectedTemplate = template; + MakeTemplate(template); + } + }; - const stringifyGPTInfo = (calls: [string, Col][]): string => { - let string: string = '*** COLUMN INFO:'; - calls.forEach(([fieldNum, col]) => { - string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`; - }); - return (string += ' ***'); - }; + @action updateSelectedSavedLayout = (layout: DataVizTemplateLayout) => { + this._layout.xMargin = layout.layout.xMargin; + this._layout.yMargin = layout.layout.yMargin; + this._layout.type = layout.layout.type; + this._layout.columns = layout.columns; + }; - const GPTTextAssignment = stringifyGPTInfo(GPTTextCalls); + isSelectedLayout = (layout: DataVizTemplateLayout) => { + return this._layout.xMargin === layout.layout.xMargin && this._layout.yMargin === layout.layout.yMargin && this._layout.type === layout.layout.type && this._layout.columns === layout.columns; + }; - let fieldContent: string = ''; - - Object.entries(nonGPTAssignments).forEach(([f, strCol]) => { - const field: Field = template.fields[Number(f)]; - const col = strCol[1]; + @action + generateTemplates = async (inputText: string) => { + ++this._callCount; + const origCount = this._callCount; - const doc = (col.type === TemplateFieldType.VISUAL ? FieldUtils.ImageField : FieldUtils.TextField)( - { - tl: field.tl, - br: field.br, - }, - template.height, - template.width, - col.title, - col.defaultContent ?? '', - field.opts - ); + let prompt: string = `(#${origCount}) Please generate for the fields:`; + this.selectedFields?.forEach(field => (prompt += ` ${field},`)); + prompt += ` (-----NOT A FIELD-----) Additional prompt: ${inputText}`; - fieldContent += `--- Field #${f} (title: ${col.title}): ${col.defaultContent ?? ''} ---`; + this._GPTLoading = true; - fields.push(doc); - }); + try { + const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); - template.decorations.forEach(dec => { - const doc = FieldUtils.FreeformField( - { - tl: dec.tl, - br: dec.br, - }, - template.height, - template.width, - '', - '', - dec.opts - ); + if (res && this._callCount === origCount) { + this._suggestedTemplates = []; + const templates: { template_type: string; fieldVals: { title: string; tlx: string; tly: string; brx: string; bry: string }[] }[] = JSON.parse(res); + this.createGeneratedTemplates(templates, 500, 500); + } + } catch (err) { + console.error(err); + } + }; - fields.push(doc); - }); + @action + createGeneratedTemplates = (layouts: { template_type: string; fieldVals: { title: string; tlx: string; tly: string; brx: string; bry: string }[] }[], tempWidth: number, tempHeight: number) => { + const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; + const GPTTemplates: Doc[] = []; - const createMainDoc = (): Doc => { - const main = Docs.Create.FreeformDocument(fields, { - _height: template.height, - _width: template.width, - title: template.title, - backgroundColor: template.opts.backgroundColor, - _layout_borderRounding: `${template.opts.cornerRounding}px` ?? '0px', - borderWidth: template.opts.borderWidth, - borderColor: template.opts.borderColor, - x: 40000, - y: 40000, + layouts.forEach(layout => { + const fields: Doc[] = layout.fieldVals.map(field => { + const left: number = (Number(field.tlx) * tempWidth) / 2; + const top: number = Number(field.tly) * tempHeight / 2; //prettier-ignore + const right: number = (Number(field.brx) * tempWidth) / 2; + const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore + const height = bottom - top; + const width = right - left; + const doc = !field.title.includes('$$') + ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height / 2}` }) + : Docs.Create.ImageDocument('', { _height: height, _width: width, title: field.title.replace(/\$\$/g, ''), x: left, y: top }); + return doc; }); - const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; - mainCollection.addDocument(main); - - return main; - }; + const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400000, y: 400000 }); - const textCalls = await renderTextCalls(); - const imageCalls = await renderImageCalls(); + mainCollection.addDocument(template); - textCalls.forEach(doc => { - fields.push(doc); - }); - imageCalls.forEach(doc => { - fields.push(doc); + GPTTemplates.push(template); }); - return createMainDoc(); - }; - - compileFieldDescriptions = (templates: TemplateDocInfos[]): string => { - let descriptions: string = ''; - templates.forEach(template => { - descriptions += `---------- NEW TEMPLATE TO INCLUDE: Description of template ${template.title}'s fields: `; - template.fields.forEach((field, index) => { - descriptions += `{Field #${index}: ${field.description}} `; - }); - }); + setTimeout(() => { + this.setGSuggestedTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/ + }, 100); - return descriptions; + this.forceUpdate(); }; - compileColDescriptions = (cols: Col[]): string => { - let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:'; - cols.forEach(col => (descriptions += `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `)); - - return descriptions; + editTemplate = (doc: Doc) => { + //this.closeMenu(); + DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight); + DocumentView.DeselectAll(); + Doc.UnBrushDoc(doc); }; - getColByTitle = (title: string) => { - return this.fieldsInfos.filter(col => col.title === title)[0]; + removeTemplate = (doc: Doc) => { + this._templateDocs.splice(this._templateDocs.indexOf(doc), 1); }; - @action - assignColsToFields = async (templates: TemplateDocInfos[], cols: Col[]): Promise<[TemplateDocInfos, { [field: number]: Col }][]> => { - const fieldDescriptions: string = this.compileFieldDescriptions(templates); - const colDescriptions: string = this.compileColDescriptions(cols); + testTemplate = async () => { + this.updateIcons(this._suggestedTemplates.slice()); + this.forceUpdate(); - const inputText = fieldDescriptions.concat(colDescriptions); + // try { + // const res = await gptImageCall('Image of panda eating a cookie'); - ++this._callCount; - const origCount = this._callCount; + // if (res) { + // const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res }); - let prompt: string = `(${origCount}) ${inputText}`; + // console.log(result); + // } + // } catch (e) { + // console.log(e); + // } + }; - this._GPTLoading = true; + @action addField = () => { + const newFields: Col[] = this._columns.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [] }]); + this._columns = newFields; + }; - try { - const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); + @action removeField = (field: { title: string; type: string; desc: string }) => { + if (this._dataViz?.axes.includes(field.title)) { + this._dataViz.selectAxes(this._dataViz.axes.filter(col => col !== field.title)); + } else { + const toRemove = this._columns.filter(f => f === field); + if (!toRemove) return; - if (res && this._callCount === origCount) { - const assignments: { [templateTitle: string]: { [field: string]: string } } = JSON.parse(res); - const brokenDownAssignments: [TemplateDocInfos, { [field: number]: Col }][] = []; + if (toRemove.length > 1) { + while (toRemove.length > 1) { + toRemove.pop(); + } + } - Object.entries(assignments).forEach(([tempTitle, assignment]) => { - const template = TemplateLayouts.getTemplateByTitle(tempTitle); - if (!template) return; - const toObj = Object.entries(assignment).reduce( - (a, [fieldNum, colTitle]) => { - a[Number(fieldNum)] = this.getColByTitle(colTitle); - return a; - }, - {} as { [field: number]: Col } - ); - brokenDownAssignments.push([template, toObj]); - }); - return brokenDownAssignments; + if (this._columns.length === 1) { + this._columns = []; + } else { + this._columns.splice(this._columns.indexOf(toRemove[0]), 1); } - } catch (err) { - console.error(err); } - - return []; }; - generatePresetTemplates = async () => { - this._dataViz?.updateColDefaults(); + @action setColTitle = (column: Col, title: string) => { + if (this.selectedFields.includes(column.title)) { + this._dataViz?.setColumnTitle(column.title, title); + } else { + column.title = title; + } + this.forceUpdate(); + }; - const cols = this.fieldsInfos; - const templates = this.findValidTemplates(cols, TemplateLayouts.allTemplates); + @action setColType = (column: Col, type: TemplateFieldType) => { + if (this.selectedFields.includes(column.title)) { + this._dataViz?.setColumnType(column.title, type); + } else { + column.type = type; + } + this.forceUpdate(); + }; - const assignments: [TemplateDocInfos, { [field: number]: Col }][] = await this.assignColsToFields(templates, cols); + modifyColSizes = (column: Col, size: TemplateFieldSize, valid: boolean) => { + if (this.selectedFields.includes(column.title)) { + this._dataViz?.modifyColumnSizes(column.title, size, valid); + } else { + if (!valid && column.sizes.includes(size)) { + column.sizes.splice(column.sizes.indexOf(size), 1); + } else if (valid && !column.sizes.includes(size)) { + column.sizes.push(size); + } + } + this.forceUpdate(); + }; - const renderedTemplatePromises: Promise[] = assignments.map(([template, assignments]) => this.fillPresetTemplate(template, assignments)); + setColDesc = (column: Col, desc: string) => { + if (this.selectedFields.includes(column.title)) { + this._dataViz?.setColumnDesc(column.title, desc); + } else { + column.desc = desc; + } + this.forceUpdate(); + }; - const renderedTemplates: Doc[] = await Promise.all(renderedTemplatePromises); + generateGPTImage = async (prompt: string): Promise => { + console.log(prompt); - setTimeout(() => { - this.setGSuggestedTemplates(renderedTemplates); - this._GPTLoading = false; - }); - }; + try { + const res = await gptImageCall(prompt); - @action setExpandedView = (info: { icon: ImageField; doc: Doc } | undefined) => { - if (info) { - const doc = info.doc; - const wrapper: Doc = Docs.Create.FreeformDocument([info.doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''}); - const newInfo = {icon: new ImageField(''), doc: wrapper} - this._expandedPreview = newInfo; - } else { - this._expandedPreview = info; + if (res) { + const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res }); + const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client); + return source; + } + } catch (e) { + console.log(e); } }; - get editingWindow(){ - const doc = this._expandedPreview?.doc ?? new Doc(); - const rendered = -
- Cast(doc.childLayoutTemplate, Doc, null)} - isContentActive={emptyFunction} - isAnyChildContentActive={() => true} - select={emptyFunction} - isSelected={returnFalse} - fieldKey={Doc.LayoutFieldKey(doc)} - addDocument={returnFalse} - moveDocument={returnFalse} - removeDocument={returnFalse} - PanelWidth={() => this._menuDimensions.width - 10} - PanelHeight={() => this._menuDimensions.height - 60} - ScreenToLocalTransform={Transform.Identity} - renderDepth={5} - whenChildContentsActiveChanged={emptyFunction} - focus={emptyFunction} - styleProvider={DefaultStyleProvider} - addDocTab={this._props.addDocTab} - // eslint-disable-next-line no-use-before-define - pinToPres={() => undefined} - childFilters={returnEmptyFilter} - childFiltersByRanges={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - fitContentsToBox={returnTrue} - xPadding={0} - yPadding={0} - /> -
+ matchesForTemplate = (template: TemplateDocInfos, cols: Col[]): number[][] => { + const colMatchesField = (col: Col, field: Field) => { + return field.sizes?.some(size => col.sizes?.includes(size)) && field.types?.includes(col.type); + }; + const matches: number[][] = Array(template.fields.length) + .fill([]) + .map(() => []); - return ( -
-
- {rendered} -
- - -
-
+ template.fields.forEach((field, i) => { + cols.forEach((col, v) => { + if (colMatchesField(col, field)) { + matches[i].push(v); + } + }); + }); - ); - } + return matches; + }; - get templatesPreviewContents() { - const renderedTemplates: Doc[] = []; + maxMatches = (fieldsCt: number, matches: number[][]) => { + const used: boolean[] = Array(fieldsCt).fill(false); + const mt: number[] = Array(fieldsCt).fill(-1); - const GPTOptions =
; + const augmentingPath = (v: number): boolean => { + if (used[v]) return false; + used[v] = true; + for (const to of matches[v]) { + if (mt[to] === -1 || augmentingPath(mt[to])) { + mt[to] = v; + return true; + } + } + return false; + }; - // + for (let v = 0; v < fieldsCt; ++v) { + used.fill(false); + augmentingPath(v); + } - return ( -
- {this._expandedPreview ? ( - this.editingWindow - ) : ( -
-
-
-
Suggested Templates
- -
-
400 ? 'center' : '' }}> - {this._GPTLoading ? ( -
- -
- ) : ( - this._suggestedTemplates - ?.map(doc => ({ icon: ImageCast(doc.icon), doc })) - .filter(info => info.icon && info.doc) - .map(info => ( -
this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}> - - - -
- )) - )} -
-
-
- -
- {this._GPTOpt ? GPTOptions : null} -
-
-
-
-
-
Your Templates
- -
-
400 ? 'center' : '' }}> -
this.testTemplate()}> - -
- {this._templateDocs - .map(doc => ({ icon: ImageCast(doc.icon), doc })) - .filter(info => info.icon && info.doc) - .map(info => { - if (renderedTemplates.includes(info.doc)) return undefined; - renderedTemplates.push(info.doc); - return ( -
this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}> - - - -
- ); - })} -
-
-
- )} -
- ); - } + let count: number = 0; - get savedLayoutsPreviewContents() { - return ( -
- {this._savedLayouts.map((layout, index) => ( -
this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedSavedLayout(layout)))}> - {this.layoutPreviewContents(87, layout, true, index)} -
- ))} -
- ); - } + for (let i = 0; i < fieldsCt; ++i) { + if (mt[i] !== -1) ++count; + } - @action updateXMargin = (input: string) => { - this._layout.xMargin = Number(input); - }; - @action updateYMargin = (input: string) => { - this._layout.yMargin = Number(input); - }; - @action updateColumns = (input: string) => { - this._layout.columns = Number(input); + return count; }; - get layoutConfigOptions() { - const optionInput = (icon: string, func: Function, def?: number, key?: string, noMargin?: boolean) => { - return ( -
-
- -
- func(e.currentTarget.value)} className="docCreatorMenu-input config layout-config" /> -
- ); - }; + findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => { + const validTemplates: string[] = []; + templates.forEach(template => { + const numFields = template.fields.length; + if (!(numFields === cols.length)) return; + const matches = this.matchesForTemplate(template, cols); + if (this.maxMatches(numFields, matches) === numFields) { + validTemplates.push(template.title); + } + }); - switch (this._layout.type) { - case LayoutType.Row: - return
{optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '0')}
; - case LayoutType.Column: - return
{optionInput('arrows-up-down', this.updateYMargin, this._layout.yMargin, '1')}
; - case LayoutType.Grid: - return ( -
- {optionInput('arrows-up-down', this.updateYMargin, this._layout.xMargin, '2')} - {optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '3')} - {optionInput('table-columns', this.updateColumns, this._layout.columns, '4', true)} -
- ); - case LayoutType.Stacked: - return null; - default: - break; - } - } + return validTemplates + .map(title => TemplateLayouts.getTemplateByTitle(title)) + .filter(t => t) + .map(t => t!); + }; - // doc = () => { - // return Docs.Create.FreeformDocument([], { _height: 200, _width: 200, title: 'title'}); - // } + // createColumnField = (template: TemplateDocInfos, field: Field, column: Col): Doc => { - screenToLocalTransform = () => this._props.ScreenToLocalTransform(); + // if (field.subfields) { + // const doc = FieldFuncs.FreeformField({ + // tl: field.tl, + // br: field.br }, + // template.height, + // template.width, + // column.title, + // '', + // field.opts + // ); - layoutPreviewContents = (outerSpan: number, altLayout?: DataVizTemplateLayout, small: boolean = false, id?: number) => { - const doc: Doc | undefined = altLayout ? altLayout.template : this._selectedTemplate; - if (!doc) return; + // field.subfields[1].forEach(f => { + // const fDoc = () + // }) - const layout = altLayout ? altLayout.layout : this._layout; + // } - const docWidth: number = Number(doc._width); - const docHeight: number = Number(doc._height); - const horizontalSpan: number = (docWidth + layout.xMargin) * (altLayout ? altLayout.columns : this.columnsCount) - layout.xMargin; - const verticalSpan: number = (docHeight + layout.yMargin) * (altLayout ? altLayout.rows : this.rowsCount) - layout.yMargin; - const largerSpan: number = horizontalSpan > verticalSpan ? horizontalSpan : verticalSpan; - const scaledDown = (input: number) => { - return input / ((largerSpan / outerSpan) * this._layoutPreviewScale); + // return new Doc; + // } + + /** + * Populates a preset template framework with content from a datavizbox or any AI-generated content. + * @param template the preloaded template framework being filled in + * @param assignments a list of template field numbers (from top to bottom) and their assigned columns from the linked dataviz + * @returns a doc containing the fully rendered template + */ + fillPresetTemplate = async (template: TemplateDocInfos, assignments: { [field: string]: Col }): Promise => { + const fields: Doc[] = []; + const wordLimit = (size: TemplateFieldSize) => { + switch (size) { + case TemplateFieldSize.TINY: return 2; + case TemplateFieldSize.SMALL: return 5; + case TemplateFieldSize.MEDIUM: return 20; + case TemplateFieldSize.LARGE: return 50; + case TemplateFieldSize.HUGE: return 100; + default: return 10; + } // prettier-ignore + }; + const stringifyGPTInfo = (calls: [string, Col][]): string => { + let string: string = '*** COLUMN INFO:'; + calls.forEach(([fieldNum, col]) => { + string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`; + }); + return (string += ' ***'); }; - const fontSize = Math.min(scaledDown(docWidth / 3), scaledDown(docHeight / 3)); - return ( - //
- // 100} - // NativeHeight={() => 100} - // pointerEvents={SnappingManager.IsDragging ? returnAll : returnNone} - // isAnnotationOverlay - // isAnnotationOverlayScrollable - // childDocumentsActive={returnFalse} - // fieldKey={this._props.fieldKey + '_annotations'} - // dropAction={dropActionType.move} - // select={emptyFunction} - // addDocument={returnFalse} - // removeDocument={returnFalse} - // moveDocument={returnFalse} - // renderDepth={this._props.renderDepth + 1}> - // {null} - // - //
-
-
- - - {altLayout ? ( - - ) : null} -
- { -
- {this._layout.type === LayoutType.Stacked ? ( -
- All -
- ) : ( - this.docsToRender.map(num => ( -
this._dataViz?.setSpecialHighlightedRow(num)} - onMouseLeave={() => this._dataViz?.setSpecialHighlightedRow(undefined)} - className="docCreatorMenu-layout-preview-item" - style={{ - width: scaledDown(docWidth), - height: scaledDown(docHeight), - fontSize: fontSize, - }}> - {num} -
- )) - )} -
+ const GPTAssignments = Object.entries(assignments).filter(([f, col]) => this._columns.includes(col)); + const nonGPTAssignments: [string, Col][] = Object.entries(assignments).filter(a => !GPTAssignments.includes(a)); + const GPTTextCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.TEXT); + const GPTIMGCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.VISUAL); + const GPTTextAssignment = stringifyGPTInfo(GPTTextCalls); + + let fieldContent: string = ''; + + const renderTextCalls = async (): Promise => { + const rendered: Doc[] = []; + + if (GPTTextCalls.length) { + try { + const prompt = fieldContent + GPTTextAssignment; + + const res = await gptAPICall(prompt, GPTCallType.FILL); + + if (res) { + const asngs: { [title: string]: { number: string; content: string } } = JSON.parse(res); + //console.log('assignments', GPTAssignments, 'assignment string', GPTAssignmentString, 'field content', fieldContent, 'response', res, 'assignments', assignments); + Object.entries(asngs).forEach(([title, info]) => { + const field: Field = template.fields[Number(info.number)]; + const col = this.getColByTitle(title); + + const doc = FieldUtils.TextField( + { + tl: field.tl, + br: field.br, + }, + template.height, + template.width, + col.title, + info.content ?? '', + field.opts + ); + + rendered.push(doc); + }); + } + } catch (err) { + console.log(err); } -
- ); - }; + } - get optionsMenuContents() { - const layoutEquals = (layout: DataVizTemplateLayout) => {}; //TODO: ADD LATER + return rendered; + }; - const layoutOption = (option: LayoutType, optStyle?: {}, specialFunc?: Function) => { - return ( -
- this.setUpButtonClick(e, () => { - specialFunc?.(); - runInAction(() => (this._layout.type = option)); - }) - }> - {option} -
+ const createGeneratedImage = async (fieldNum: string, col: Col, prompt: string) => { + const url = await this.generateGPTImage(prompt); + const field: Field = template.fields[Number(fieldNum)]; + const doc = FieldUtils.ImageField( + { + tl: field.tl, + br: field.br, + }, + template.height, + template.width, + col.title, + url ?? '', + field.opts ); + + return doc; }; - const selectionBox = (width: number, height: number, icon: string, specClass?: string, options?: JSX.Element[], manual?: boolean): JSX.Element => { - return ( -
-
- -
- {manual ? ( - - ) : ( - - )} -
- ); + const renderImageCalls = async (): Promise => { + const rendered: Doc[] = []; + const calls = GPTIMGCalls; + + if (calls.length) { + try { + const renderedImages: Doc[] = await Promise.all( + calls.map(async ([fieldNum, col]) => { + const sysPrompt = + 'Your job is to create a prompt for an AI image generator to help it generate an image based on existing content in a template and a user prompt. Your prompt should focus heavily on visual elements to help the image generator; avoid unecessary info that might distract it. ONLY INCLUDE THE PROMPT, NO OTHER TEXT OR EXPLANATION. The existing content is as follows: ' + + fieldContent + + ' **** The user prompt is: ' + + col.desc; + + const prompt = await gptAPICall(sysPrompt, GPTCallType.COMPLETEPROMPT); + console.log(sysPrompt, prompt); + + return createGeneratedImage(fieldNum, col, prompt); + }) + ); + + const renderedTemplates: Doc[] = await Promise.all(renderedImages); + renderedTemplates.forEach(doc => rendered.push(doc)); + } catch (e) { + console.log(e); + } + } + + return rendered; }; - const repeatOptions = [0, 1, 2, 3, 4, 5]; + Object.entries(nonGPTAssignments).forEach(([f, strCol]) => { + const field: Field = template.fields[Number(f)]; + const col = strCol[1]; - return ( -
-
-
-
{this._layout.type ? this._layout.type.toUpperCase() : 'Choose Layout'}
-
- {layoutOption(LayoutType.Stacked)} - {layoutOption(LayoutType.Grid, undefined, () => { - if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length)); - })} - {layoutOption(LayoutType.Row)} - {layoutOption(LayoutType.Column)} - {layoutOption(LayoutType.Custom, { borderBottom: `0px` })} -
-
- -
- {this._layout.type ? this.layoutConfigOptions : null} - {this._layoutPreview ? this.layoutPreviewContents(this._menuDimensions.width * 0.75) : null} - {selectionBox( - 60, - 20, - 'repeat', - undefined, - repeatOptions.map(num => ) - )} -
-
+ const doc = (col.type === TemplateFieldType.VISUAL ? FieldUtils.ImageField : FieldUtils.TextField)( + { + tl: field.tl, + br: field.br, + }, + template.height, + template.width, + col.title, + col.defaultContent ?? '', + field.opts + ); + + fieldContent += `--- Field #${f} (title: ${col.title}): ${col.defaultContent ?? ''} ---`; + + fields.push(doc); + }); + + template.decorations.forEach(dec => { + const doc = FieldUtils.FreeformField( + { + tl: dec.tl, + br: dec.br, + }, + template.height, + template.width, + '', + '', + dec.opts + ); + + fields.push(doc); + }); + + const createMainDoc = (): Doc => { + const main = Docs.Create.FreeformDocument(fields, { + _height: template.height, + _width: template.width, + title: template.title, + backgroundColor: template.opts.backgroundColor, + _layout_borderRounding: `${template.opts.cornerRounding ?? 0}px`, + borderWidth: template.opts.borderWidth, + borderColor: template.opts.borderColor, + x: 40000, + y: 40000, + }); + + const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView; + mainCollection.addDocument(main); + + return main; + }; + + const textCalls = await renderTextCalls(); + const imageCalls = await renderImageCalls(); + + textCalls.forEach(doc => { + fields.push(doc); + }); + imageCalls.forEach(doc => { + fields.push(doc); + }); + + return createMainDoc(); + }; + + compileFieldDescriptions = (templates: TemplateDocInfos[]): string => { + let descriptions: string = ''; + templates.forEach(template => { + descriptions += `---------- NEW TEMPLATE TO INCLUDE: Description of template ${template.title}'s fields: `; + template.fields.forEach((field, index) => { + descriptions += `{Field #${index}: ${field.description}} `; + }); + }); + + return descriptions; + }; + + compileColDescriptions = (cols: Col[]): string => { + let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:'; + cols.forEach(col => (descriptions += `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `)); + + return descriptions; + }; + + getColByTitle = (title: string) => { + return this.fieldsInfos.filter(col => col.title === title)[0]; + }; + + @action + assignColsToFields = async (templates: TemplateDocInfos[], cols: Col[]): Promise<[TemplateDocInfos, { [field: number]: Col }][]> => { + const fieldDescriptions: string = this.compileFieldDescriptions(templates); + const colDescriptions: string = this.compileColDescriptions(cols); + + const inputText = fieldDescriptions.concat(colDescriptions); + + ++this._callCount; + const origCount = this._callCount; + + const prompt: string = `(${origCount}) ${inputText}`; + + this._GPTLoading = true; + + try { + const res = await gptAPICall(prompt, GPTCallType.TEMPLATE); + + if (res && this._callCount === origCount) { + const assignments: { [templateTitle: string]: { [field: string]: string } } = JSON.parse(res); + const brokenDownAssignments: [TemplateDocInfos, { [field: number]: Col }][] = []; + + Object.entries(assignments).forEach(([tempTitle, assignment]) => { + const template = TemplateLayouts.getTemplateByTitle(tempTitle); + if (!template) return; + const toObj = Object.entries(assignment).reduce( + (a, [fieldNum, colTitle]) => { + a[Number(fieldNum)] = this.getColByTitle(colTitle); + return a; + }, + {} as { [field: number]: Col } + ); + brokenDownAssignments.push([template, toObj]); + }); + return brokenDownAssignments; + } + } catch (err) { + console.error(err); + } + + return []; + }; + + generatePresetTemplates = async () => { + this._dataViz?.updateColDefaults(); + + const cols = this.fieldsInfos; + const templates = this.findValidTemplates(cols, TemplateLayouts.allTemplates); + + const assignments: [TemplateDocInfos, { [field: number]: Col }][] = await this.assignColsToFields(templates, cols); + + const renderedTemplatePromises: Promise[] = assignments.map(([template, asgns]) => this.fillPresetTemplate(template, asgns)); + + const renderedTemplates: Doc[] = await Promise.all(renderedTemplatePromises); + + setTimeout(() => { + this.setGSuggestedTemplates(renderedTemplates); + this._GPTLoading = false; + }); + }; + + @action setExpandedView = (info: { icon: ImageField; doc: Doc } | undefined) => { + if (info) { + const doc = info.doc; + const wrapper: Doc = Docs.Create.FreeformDocument([info.doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: '' }); + const newInfo = { icon: new ImageField(''), doc: wrapper }; + this._expandedPreview = newInfo; + } else { + this._expandedPreview = info; + } + }; + + get editingWindow() { + const doc = this._expandedPreview?.doc ?? new Doc(); + const rendered = ( +
+ Cast(doc.childLayoutTemplate, Doc, null)} + isContentActive={emptyFunction} + isAnyChildContentActive={() => true} + select={emptyFunction} + isSelected={returnFalse} + fieldKey={Doc.LayoutFieldKey(doc)} + addDocument={returnFalse} + moveDocument={returnFalse} + removeDocument={returnFalse} + PanelWidth={() => this._menuDimensions.width - 10} + PanelHeight={() => this._menuDimensions.height - 60} + ScreenToLocalTransform={Transform.Identity} + renderDepth={5} + whenChildContentsActiveChanged={emptyFunction} + focus={emptyFunction} + styleProvider={DefaultStyleProvider} + addDocTab={this._props.addDocTab} + pinToPres={emptyFunction} + childFilters={returnEmptyFilter} + childFiltersByRanges={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + fitContentsToBox={returnTrue} + xPadding={0} + yPadding={0} + /> +
+ ); + + return ( +
+
+ {rendered} +
-
); } - get dashboardContents() { - const sizes: string[] = ['tiny', 'small', 'medium', 'large', 'huge']; + get templatesPreviewContents() { + const renderedTemplates: Doc[] = []; - const fieldPanel = (field: Col) => { - return ( -
-
- {`${field.title} Field`} - -
-
-
-
Title
-