diff options
author | bobzel <zzzman@gmail.com> | 2024-09-30 18:26:59 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2024-09-30 18:26:59 -0400 |
commit | 431a03690ed131e4bb925cec465d91adfb0d9421 (patch) | |
tree | baeb2fc8d7518646f988406b6a3d67a677f42865 | |
parent | cf45abf8ada938caddb226c825166d4acdee3086 (diff) |
fixed so that you can't set the author field from the schema view. fixed schema view to not trigger cell updates after cells have been unmounted (e.g, so that dragging a tab over a schema vew doesn't crash)
-rw-r--r-- | src/client/views/collections/collectionSchema/SchemaCellField.tsx | 190 | ||||
-rw-r--r-- | src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx | 2115 |
2 files changed, 1226 insertions, 1079 deletions
diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx index 065544ac9..e26dd9646 100644 --- a/src/client/views/collections/collectionSchema/SchemaCellField.tsx +++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx @@ -1,22 +1,22 @@ -import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from "mobx"; -import { ObservableReactComponent } from "../../ObservableReactComponent"; -import { observer } from "mobx-react"; -import { OverlayView } from "../../OverlayView"; -import { DocumentIconContainer } from "../../nodes/DocumentIcon"; -import React, { FormEvent } from "react"; -import { FieldView, FieldViewProps } from "../../nodes/FieldView"; -import { ObjectField } from "../../../../fields/ObjectField"; -import { Doc } from "../../../../fields/Doc"; -import { DocumentView } from "../../nodes/DocumentView"; +import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { observer } from 'mobx-react'; +import { OverlayView } from '../../OverlayView'; +import { DocumentIconContainer } from '../../nodes/DocumentIcon'; +import React, { FormEvent } from 'react'; +import { FieldView, FieldViewProps } from '../../nodes/FieldView'; +import { ObjectField } from '../../../../fields/ObjectField'; +import { Doc } from '../../../../fields/Doc'; +import { DocumentView } from '../../nodes/DocumentView'; /** * The SchemaCellField renders text in schema cells while the user is editing, and updates the * contents of the field based on user input. It handles some cell-side logic for equations, such * as how equations are broken up within the text. - * - * The current implementation parses innerHTML to create spans based on the text in the cell. + * + * The current implementation parses innerHTML to create spans based on the text in the cell. * A more robust/safer approach would directly add elements in the react structure, but this - * has been challenging to implement. + * has been challenging to implement. */ export interface SchemaCellFieldProps { @@ -26,7 +26,7 @@ export interface SchemaCellFieldProps { oneLine?: boolean; Document: Doc; fieldKey: string; - refSelectModeInfo: {enabled: boolean, currEditing: SchemaCellField | undefined}; + refSelectModeInfo: { enabled: boolean; currEditing: SchemaCellField | undefined }; highlightCells?: (text: string) => void; GetValue(): string | undefined; SetValue(value: string, shiftDown?: boolean, enterKey?: boolean): boolean; @@ -35,7 +35,6 @@ export interface SchemaCellFieldProps { @observer export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldProps> { - private _disposers: { [name: string]: IReactionDisposer } = {}; private _inputref: HTMLDivElement | null = null; private _unrenderedContent: string = ''; @@ -48,7 +47,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro constructor(props: SchemaCellFieldProps) { super(props); makeObservable(this); - setTimeout(() => { + setTimeout(() => { this._unrenderedContent = this._props.GetValue() ?? ''; this.setContent(this._unrenderedContent); }); //must be moved to end of batch or else other docs aren't loaded, so render as d-1 in function @@ -56,9 +55,11 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro get docIndex(){return DocumentView.getDocViewIndex(this._props.Document);} // prettier-ignore - get selfRefPattern() {return `d${this.docIndex}.${this._props.fieldKey}`}; + get selfRefPattern() { + return `d${this.docIndex}.${this._props.fieldKey}`; + } - @computed get lastCharBeforeCursor(){ + @computed get lastCharBeforeCursor() { const pos = this.cursorPosition; const content = this._unrenderedContent; const text = this._unrenderedContent.substring(0, pos ?? content.length); @@ -90,7 +91,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro this._props.highlightCells?.(this._unrenderedContent); this.setContent(this._unrenderedContent); setTimeout(() => this.setCursorPosition(this._unrenderedContent.length)); - } + } }); } else { this._overlayDisposer?.(); @@ -104,10 +105,11 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro this._disposers.fieldUpdate = reaction( () => this._props.GetValue(), fieldVal => { + console.log('Update: ' + this._props.Document.title, this._props.fieldKey, fieldVal); this._unrenderedContent = fieldVal ?? ''; this.finalizeEdit(false, false, false); } - ) + ); } componentDidUpdate(prevProps: Readonly<SchemaCellFieldProps>) { @@ -120,7 +122,10 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro }); } + _unmounted = false; componentWillUnmount(): void { + this._unmounted = true; + console.log('Unmount: ' + this._props.Document.title, this._props.fieldKey); this._overlayDisposer?.(); Object.values(this._disposers).forEach(disposer => disposer?.()); this.finalizeEdit(false, true, false); @@ -129,7 +134,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro generateSpan = (text: string, cell: HTMLDivElement | undefined) => { const selfRef = text === this.selfRefPattern; return `<span style="text-decoration: ${selfRef ? 'underline' : 'none'}; text-decoration-color: red; color: ${selfRef ? 'gray' : cell?.style.borderTop.replace('2px solid', '')}">${text}</span>`; - } + }; makeSpans = (content: string) => { let chunkedText = content; @@ -144,28 +149,28 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro const cell = this._props.getCells(match[0]); if (cell.length) { matches.push(match[0]); - cells.set(match[0], cell[0]) + cells.set(match[0], cell[0]); } } matches.forEach((match: string) => { chunkedText = chunkedText.replace(match, this.generateSpan(match, cells.get(match))); - }) + }); return chunkedText; - } + }; /** - * Sets the rendered content of the cell to save user inputs. + * Sets the rendered content of the cell to save user inputs. * @param content the content to set * @param restoreCursorPos whether the cursor should be set back to where it was rather than the 0th index; should usually be true */ - @action + @action setContent = (content: string, restoreCursorPos?: boolean) => { const pos = this.cursorPosition; this._displayedContent = this.makeSpans(content); restoreCursorPos && setTimeout(() => this.setCursorPosition(pos)); - } + }; //Called from schemaview when a cell is selected to add a reference to the equation /** @@ -181,7 +186,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro const newText = atPos ? content.slice(0, robustPos) + text + content.slice(cursorPos ?? content.length) : this._unrenderedContent.concat(text); this.onChange(undefined, newText); setTimeout(() => this.setCursorPosition(robustPos + text.length)); - } + }; @action setIsFocused = (value: boolean) => { @@ -195,32 +200,31 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro */ get cursorPosition() { const selection = window.getSelection(); - if (!selection || selection.rangeCount === 0 || !this._inputref) return null; - + if (!selection || selection.rangeCount === 0 || !this._inputref) return null; + const range = selection.getRangeAt(0); const adjRange = range.cloneRange(); - + adjRange.selectNodeContents(this._inputref); adjRange.setEnd(range.startContainer, range.startOffset); - return adjRange.toString().length; + return adjRange.toString().length; } - setCursorPosition = (position: number | null) => { const selection = window.getSelection(); - if (!selection || position === null || !this._inputref) return; - + if (!selection || position === null || !this._inputref) return; + const range = document.createRange(); range.setStart(this._inputref, 0); range.collapse(true); - + let currentPos = 0; const setRange = (nodes: NodeList) => { for (let i = 0; i < nodes.length; ++i) { const node = nodes[i]; - if (node.nodeType === Node.TEXT_NODE) { + if (node.nodeType === Node.TEXT_NODE) { if (!node.textContent) return; const nextPos = currentPos + node.textContent.length; if (position <= nextPos) { @@ -231,11 +235,10 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro return true; } currentPos = nextPos; - - } else if ((node.nodeType === Node.ELEMENT_NODE) && (setRange(node.childNodes))) return true; + } else if (node.nodeType === Node.ELEMENT_NODE && setRange(node.childNodes)) return true; } return false; - } + }; setRange(this._inputref.childNodes); }; @@ -265,7 +268,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro const properties = this._props.refSelectModeInfo; properties.enabled = enabled; properties.currEditing = enabled ? this : undefined; - } + }; @action onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { @@ -289,9 +292,12 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro e.stopPropagation(); this._editing = false; break; - case 'ArrowUp': case 'ArrowDown': case 'ArrowLeft': case 'ArrowRight': // prettier-ignore + case 'ArrowUp': + case 'ArrowDown': + case 'ArrowLeft': + case 'ArrowRight': // prettier-ignore e.stopPropagation(); - setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0) + setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0); break; case ' ': e.stopPropagation(); @@ -300,13 +306,16 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro setTimeout(() => { this.setContent(this._unrenderedContent); setTimeout(() => this.setCursorPosition(cursorPos)); - } - , 0); + }, 0); break; case 'u': // for some reason 'u' otherwise exits the editor e.stopPropagation(); break; - case 'Shift': case 'Alt': case 'Meta': case 'Control': case ':': // prettier-ignore + case 'Shift': + case 'Alt': + case 'Meta': + case 'Control': + case ':': // prettier-ignore break; // eslint-disable-next-line no-fallthrough default: @@ -323,66 +332,63 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro }; @action - finalizeEdit(shiftDown: boolean, lostFocus: boolean, enterKey: boolean) { + finalizeEdit = (shiftDown: boolean, lostFocus: boolean, enterKey: boolean) => { + if (this._unmounted) { + return; + } if (this._unrenderedContent.replace(this.selfRefPattern, '') !== this._unrenderedContent) { - this._dependencyMessageShown ? this._dependencyMessageShown = false : - alert(`Circular dependency detected. Please update the field at ${this.selfRefPattern}.`) + if (this._dependencyMessageShown) { + this._dependencyMessageShown = false; + } else alert(`Circular dependency detected. Please update the field at ${this.selfRefPattern}.`); this._dependencyMessageShown = true; return; } this.setContent(this._unrenderedContent); - - if (this._props.SetValue(this._unrenderedContent, shiftDown, enterKey)) { - this._editing = false; - } else { - this._editing = false; - !lostFocus && - setTimeout( - action(() => { - this._editing = true; - }), - 0 - ); + + if (!this._props.SetValue(this._unrenderedContent, shiftDown, enterKey) && !lostFocus) { + setTimeout(action(() => (this._editing = true))); } - } + this._editing = false; + }; staticDisplay = () => { - return <span className='editableView-static'> - { - // eslint-disable-next-line react/jsx-props-no-spreading - this._props.fieldContents ? <FieldView {...this._props.fieldContents}/> : '' - } - </span> - } + return <span className="editableView-static">{this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : ''}</span>; + }; renderEditor = () => { return ( - <div - contentEditable - className='schemaField-editing' - ref={r => { this._inputref = r; }} - style={{ cursor: 'text', outline: 'none', overflow: 'auto', minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, }} - onBlur={() => {this._props.refSelectModeInfo.enabled ? setTimeout(() => {this.setIsFocused(true)}, 1000) : this.finalizeEdit(false, true, false)}} - autoFocus - onInput={this.onChange} - onKeyDown={this.onKeyDown} - onPointerDown={e => {e.stopPropagation(); setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0)}} //timeout callback ensures that refSelectMode is properly set - onClick={e => e.stopPropagation} - onPointerUp={e => e.stopPropagation} - onPointerMove={e => {e.stopPropagation(); e.preventDefault()}} - dangerouslySetInnerHTML={{ __html: this._displayedContent }} - > - </div> + <div + contentEditable + className="schemaField-editing" + ref={r => { + this._inputref = r; + }} + style={{ cursor: 'text', outline: 'none', overflow: 'auto', minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20 }} + onBlur={() => (this._props.refSelectModeInfo.enabled ? setTimeout(() => this.setIsFocused(true), 1000) : this.finalizeEdit(false, true, false))} + autoFocus + onInput={this.onChange} + onKeyDown={this.onKeyDown} + onPointerDown={e => { + e.stopPropagation(); + setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0); + }} //timeout callback ensures that refSelectMode is properly set + onClick={e => e.stopPropagation} + onPointerUp={e => e.stopPropagation} + onPointerMove={e => { + e.stopPropagation(); + e.preventDefault(); + }} + dangerouslySetInnerHTML={{ __html: this._displayedContent }}></div> ); - } + }; render() { const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n'); - if ((this._editing && gval !== undefined)) { + if (this._editing && gval !== undefined) { return <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}>{this.renderEditor()}</div>; - } else return ( - this._props.contents instanceof ObjectField ? null : ( + } else + return this._props.contents instanceof ObjectField ? null : ( <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`} style={{ @@ -393,8 +399,6 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro onClick={this.onClick}> {this.staticDisplay()} </div> - ) - ); + ); } - -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx index 5d5c35dce..32aded9de 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx @@ -1,50 +1,42 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { Colors } from 'browndash-components'; +import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; +import { IDisposer } from 'mobx-utils'; import * as React from 'react'; -import { ClientUtils, returnAll, returnFalse, returnNone, returnOne, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; +import ReactLoading from 'react-loading'; +import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../../ClientUtils'; +import { emptyFunction } from '../../../../Utils'; import { Doc, NumListCast, StrListCast } from '../../../../fields/Doc'; -import { DocCast, ImageCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { Id } from '../../../../fields/FieldSymbols'; +import { DocCast, ImageCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { emptyFunction } from '../../../../Utils'; +import { Networking } from '../../../Network'; +import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; +import { Docs } from '../../../documents/Documents'; +import { DragManager } from '../../../util/DragManager'; +import { MakeTemplate } from '../../../util/DropConverter'; import { SnappingManager } from '../../../util/SnappingManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; +import { LightboxView } from '../../LightboxView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; import { DocumentView, DocumentViewInternal } from '../DocumentView'; +import { FieldViewProps } from '../FieldView'; +import { OpenWhere } from '../OpenWhere'; import { DataVizBox } from './DataVizBox'; import './DocCreatorMenu.scss'; -import { Id } from '../../../../fields/FieldSymbols'; -import { Colors, IconButton, Size } from 'browndash-components'; -import { MakeTemplate } from '../../../util/DropConverter'; -import { DragManager } from '../../../util/DragManager'; -import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT'; -import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; -import { Docs } from '../../../documents/Documents'; -import { OpenWhere } from '../OpenWhere'; -import { IDisposer } from 'mobx-utils'; -import { LightboxView } from '../../LightboxView'; -import ReactLoading from 'react-loading'; -import { CollectionStackingView } from '../../collections/CollectionStackingView'; -import { FieldViewProps } from '../FieldView'; -import { CollectionViewType } from '../../../documents/DocumentTypes'; -import { dropActionType } from '../../../util/DropActionTypes'; -import { ImageBox } from '../ImageBox'; -import { a } from '@react-spring/web'; -import { RichTextMenu } from '../formattedText/RichTextMenu'; -import e from 'cors'; -import { Networking } from '../../../Network'; export enum LayoutType { - Stacked = 'stacked', - Grid = 'grid', - Row = 'row', - Column = 'column', - Custom = 'custom' + Stacked = 'stacked', + Grid = 'grid', + Row = 'row', + Column = 'column', + Custom = 'custom', } @observer export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { - static Instance: DocCreatorMenu; private _disposers: { [name: string]: IDisposer } = {}; @@ -54,13 +46,13 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { @observable _templateDocs: Doc[] = []; @observable _selectedTemplate: Doc | undefined = undefined; @observable _columns: Col[] = []; - @observable _selectedCols: {title: string, type: string, desc: string}[] | undefined = []; + @observable _selectedCols: { title: string; type: string; desc: string }[] | undefined = []; - @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns?: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0}; + @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; + @observable _expandedPreview: { icon: ImageField; doc: Doc } | undefined = undefined; @observable _suggestedTemplates: Doc[] = []; @observable _GPTOpt: boolean = false; @@ -76,7 +68,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { @observable _hoveredLayoutPreview: number | undefined = undefined; @observable _mouseX: number = -1; @observable _mouseY: number = -1; - @observable _startPos?: {x: number, y: number}; + @observable _startPos?: { x: number; y: number }; @observable _shouldDisplay: boolean = false; @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' = 'templates'; @@ -87,10 +79,10 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { @observable _snapPt: any; @observable _resizeHdlId: string = ''; @observable _resizing: boolean = false; - @observable _offset: {x: number, y: number} = {x: 0, y: 0}; + @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 _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; constructor(props: any) { @@ -100,32 +92,40 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { //setTimeout(() => this.generateTemplates('')); } - @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}; + @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; + }; @computed get docsToRender() { return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : []; } - @computed get rowsCount(){ + @computed get rowsCount() { switch (this._layout.type) { - case LayoutType.Row: case LayoutType.Stacked: + 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: + default: return 0; } } - @computed get columnsCount(){ + @computed get columnsCount() { switch (this._layout.type) { case LayoutType.Row: return this.docsToRender.length; - case LayoutType.Column: case LayoutType.Stacked: + case LayoutType.Column: + case LayoutType.Stacked: return 1; case LayoutType.Grid: return this._layout.columns ?? 0; @@ -140,31 +140,33 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { @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 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); + return col; + }) + .concat(this._columns); } - @computed get canMakeDocs(){ + @computed get canMakeDocs() { return this._selectedTemplate !== undefined && this._layout !== undefined; } - get bounds(): {t: number, b: number, l: number, r: number} { + 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}; + const bounds = { t: rect?.top ?? 0, b: rect?.bottom ?? 0, l: rect?.left ?? 0, r: rect?.right ?? 0 }; return bounds; } @@ -179,8 +181,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { clickEv.preventDefault(); func(); }, 'create docs') - ) - } + ); + }; @action onPointerDown = (e: PointerEvent) => { @@ -214,10 +216,21 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { componentDidMount() { document.addEventListener('pointerdown', this.onPointerDown, true); document.addEventListener('pointerup', this.onPointerUp); - this._disposers.templates = reaction(() => this._templateDocs.slice(), (docs) => docs.map(this.getIcon)); - this._disposers.gpt = reaction(() => this._suggestedTemplates.slice(), (docs) => docs.map(this.getIcon)); + this._disposers.templates = reaction( + () => this._templateDocs.slice(), + docs => docs.map(this.getIcon) + ); + this._disposers.gpt = reaction( + () => this._suggestedTemplates.slice(), + docs => docs.map(this.getIcon) + ); //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.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: ''}})) } @@ -227,12 +240,14 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { document.removeEventListener('pointerup', this.onPointerUp); } - updateIcons = (docs: Doc[]) => { docs.map(this.getIcon) } + updateIcons = (docs: Doc[]) => { + docs.map(this.getIcon); + }; @action updateSelectedCols = (cols: string[]) => { - this._selectedCols - } + this._selectedCols; + }; @action toggleDisplay = (x: number, y: number) => { @@ -246,10 +261,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { }; @action - closeMenu = () => { this._shouldDisplay = false }; + closeMenu = () => { + this._shouldDisplay = false; + }; @action - openMenu = () => { + openMenu = () => { const allTemplates = this._templateDocs.concat(this._suggestedTemplates); this._shouldDisplay = true; this.updateIcons(allTemplates); @@ -285,7 +302,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { await new Promise<any>(res => { setTimeout(() => { res(this._interactionLock = undefined)})}); }); // prettier-ignore return true; - } + }; @action onDrag = (e: any): boolean => { @@ -294,12 +311,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { this._initDimensions.x = this._pageX; this._initDimensions.y = this._pageY; return true; - } + }; 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}}; + 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; @@ -314,11 +331,11 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { return vals; }; - 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) + 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; + 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); @@ -330,10 +347,10 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { const docView = DocumentView.getDocumentView(doc); if (docView) { docView.ComponentView?.updateIcon?.(); - return new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 500));; + return new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 500)); } return undefined; - }; + } @action updateSelectedTemplate = (template: Doc) => { if (this._selectedTemplate === template) { @@ -353,10 +370,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { }; 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 this._layout.xMargin === layout.layout.xMargin && this._layout.yMargin === layout.layout.yMargin && this._layout.type === layout.layout.type && this._layout.columns === layout.columns; }; @action @@ -365,7 +379,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { const origCount = this._callCount; let prompt: string = `(#${origCount}) Please generate for the fields:`; - this.selectedFields?.forEach(field => prompt += ` ${field},`) + this.selectedFields?.forEach(field => (prompt += ` ${field},`)); prompt += ` (-----NOT A FIELD-----) Additional prompt: ${inputText}`; this._GPTLoading = true; @@ -375,37 +389,43 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { 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); + 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); - } + } }; @action - createGeneratedTemplates = (layouts: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[], tempWidth: number, tempHeight: number) => { + 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[] = []; 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 }); + 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 template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400000, y: 400000 }); mainCollection.addDocument(template); - + GPTTemplates.push(template); }); - setTimeout(() => {this.setGSuggestedTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/}, 100); + setTimeout(() => { + this.setGSuggestedTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/ + }, 100); this.forceUpdate(); }; @@ -421,9 +441,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { this._templateDocs.splice(this._templateDocs.indexOf(doc), 1); }; - - - testTemplate = async() => { + testTemplate = async () => { // const temp = TemplateLayouts.FourField001; // const title: Doc = FieldFuncs.TextField({tl: temp.fields[0].tl, br: temp.fields[0].br}, temp.height, temp.width, 'title', 'Title', {backgroundColor: 'transparent'}); // const image: Doc = FieldFuncs.ImageField({tl: temp.fields[1].tl, br: temp.fields[1].br}, temp.height, temp.width, 'title', '', {borderColor: '#159fe4', borderWidth: '10', cornerRounding: 10, rotation: 40}); @@ -453,7 +471,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { try { const res = await gptImageCall('Image of panda eating a cookie'); - if (res){ + if (res) { const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res }); console.log(result); @@ -461,15 +479,14 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { } catch (e) { console.log(e); } - }; @action addField = () => { - const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.UNSET, desc: '', sizes: []}]) + const newFields: Col[] = this._columns.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [] }]); this._columns = newFields; }; - @action removeField = (field: {title: string, type: string, desc: string}) => { + @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 { @@ -483,11 +500,11 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { } if (this._columns.length === 1) { - this._columns = [] + this._columns = []; } else { this._columns.splice(this._columns.indexOf(toRemove[0]), 1); } - } + } }; @action setColTitle = (column: Col, title: string) => { @@ -530,13 +547,13 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { this.forceUpdate(); }; - generateGPTImage = async(prompt: string): Promise<string | undefined> => { - console.log(prompt) + generateGPTImage = async (prompt: string): Promise<string | undefined> => { + console.log(prompt); try { const res = await gptImageCall(prompt); - if (res){ + if (res) { const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res }); const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client); return source; @@ -544,12 +561,16 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { } catch (e) { console.log(e); } - } + }; 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 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(() => []); + const matches: number[][] = Array(template.fields.length) + .fill([]) + .map(() => []); template.fields.forEach((field, i) => { cols.forEach((col, v) => { @@ -576,8 +597,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { } } return false; - } - + }; + for (let v = 0; v < fieldsCt; ++v) { used.fill(false); augmentingPath(v); @@ -592,7 +613,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { return count; }; - findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => { let validTemplates: any[] = []; templates.forEach(template => { @@ -602,7 +622,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { if (this.maxMatches(numFields, matches) === numFields) { validTemplates.push(template.title); } - }) + }); validTemplates = validTemplates.map(title => TemplateLayouts.getTemplateByTitle(title)); @@ -613,12 +633,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { // if (field.subfields) { // const doc = FieldFuncs.FreeformField({ - // tl: field.tl, - // br: field.br }, - // template.height, - // template.width, - // column.title, - // '', + // tl: field.tl, + // br: field.br }, + // template.height, + // template.width, + // column.title, + // '', // field.opts // ); @@ -634,10 +654,10 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { /** * 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 + * @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<Doc> => { + fillPresetTemplate = async (template: TemplateDocInfos, assignments: { [field: string]: Col }): Promise<Doc> => { const wordLimit = (size: TemplateFieldSize) => { switch (size) { case TemplateFieldSize.TINY: @@ -650,44 +670,43 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { return 50; case TemplateFieldSize.HUGE: return 100; - default: + default: return 10; } - } + }; - const renderTextCalls = async(): Promise<Doc[]> => { + const renderTextCalls = async (): Promise<Doc[]> => { const rendered: Doc[] = []; if (GPTTextCalls.length) { - try { const prompt = fieldContent + GPTTextAssignment; - + const res = await gptAPICall(prompt, GPTCallType.FILL); - - if (res){ - - const assignments: {[title: string]: {number: string, content: string}} = JSON.parse(res); + + 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); - - const doc = FieldUtils.TextField({ - tl: field.tl, - br: field.br }, - template.height, - template.width, - col.title, - info.content ?? '', + + 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) { + } catch (err) { console.log(err); } } @@ -695,23 +714,25 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { return rendered; }; - const createGeneratedImage = async(fieldNum: string, col: Col, prompt: string) => { + 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 ?? '', + const doc = FieldUtils.ImageField( + { + tl: field.tl, + br: field.br, + }, + template.height, + template.width, + col.title, + url ?? '', field.opts ); return doc; - } + }; - const renderImageCalls = async(): Promise<Doc[]> => { + const renderImageCalls = async (): Promise<Doc[]> => { const rendered: Doc[] = []; const calls = GPTIMGCalls; @@ -719,24 +740,28 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { 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 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){ + } catch (e) { console.log(e); } } return rendered; - } + }; const fields: Doc[] = []; @@ -748,9 +773,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { 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} ---` + string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`; }); - return string += ' ***'; + return (string += ' ***'); }; const GPTTextAssignment = stringifyGPTInfo(GPTTextCalls); @@ -761,45 +786,49 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { const field: Field = template.fields[Number(f)]; const col = strCol[1]; - const doc = (col.type === TemplateFieldType.VISUAL ? FieldUtils.ImageField : FieldUtils.TextField)({ - tl: field.tl, - br: field.br }, - template.height, - template.width, - col.title, - col.defaultContent ?? '', + 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 ?? ''} ---` + 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, + 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, + 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', + _layout_borderRounding: `${template.opts.cornerRounding}px` ?? '0px', borderWidth: template.opts.borderWidth, borderColor: template.opts.borderColor, - x: 40000, + x: 40000, y: 40000, }); @@ -807,40 +836,46 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { 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)}); + 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: ` + descriptions += `---------- NEW TEMPLATE TO INCLUDE: Description of template ${template.title}'s fields: `; template.fields.forEach((field, index) => { - descriptions += `{Field #${index}: ${field.description}} ` + descriptions += `{Field #${index}: ${field.description}} `; }); }); return descriptions; }; - compileColDescriptions = (cols: Col[]): string => { + 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}} `); + 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]; }; + getColByTitle = (title: string) => { + return this.fieldsInfos.filter(col => col.title === title)[0]; + }; @action - assignColsToFields = async(templates: TemplateDocInfos[], cols: Col[]): Promise<[TemplateDocInfos, {[field: number]: Col}][]> => { + assignColsToFields = async (templates: TemplateDocInfos[], cols: Col[]): Promise<[TemplateDocInfos, { [field: number]: Col }][]> => { const fieldDescriptions: string = this.compileFieldDescriptions(templates); const colDescriptions: string = this.compileColDescriptions(cols); @@ -857,24 +892,26 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { 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}][] = []; + 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]) - }) + 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 []; }; @@ -884,178 +921,200 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { 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<Doc>[] = assignments.map(([template, assignments]) => - this.fillPresetTemplate(template, assignments) - ); - + + const renderedTemplatePromises: Promise<Doc>[] = assignments.map(([template, assignments]) => this.fillPresetTemplate(template, assignments)); + const renderedTemplates: Doc[] = await Promise.all(renderedTemplatePromises); - - setTimeout(() => { this.setGSuggestedTemplates(renderedTemplates); this._GPTLoading = false }); + + setTimeout(() => { + this.setGSuggestedTemplates(renderedTemplates); + this._GPTLoading = false; + }); }; - @action setExpandedView = (info: {icon: ImageField, doc: Doc} | undefined) => { + @action setExpandedView = (info: { icon: ImageField; doc: Doc } | undefined) => { this._expandedPreview = info; - } + }; - get templatesPreviewContents(){ + get templatesPreviewContents() { const renderedTemplates: Doc[] = []; - const GPTOptions = - <div></div> + const GPTOptions = <div></div>; - //<img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} /> + //<img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} /> return ( <div className={`docCreatorMenu-templates-view`}> - {this._expandedPreview ? - <div className='docCreatorMenu-expanded-template-preview'> - <img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} /> - <div className='right-buttons-panel'> - <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.setExpandedView(undefined))}> - <FontAwesomeIcon icon='minimize'/> - </button> - <button className='docCreatorMenu-menu-button section-reveal-options top-right-lower' onPointerDown={e => this.setUpButtonClick(e, () => this._expandedPreview && this._templateDocs.push(this._expandedPreview.doc))}> - <FontAwesomeIcon icon='plus' color='white'/> - </button> - </div> - </div> - : - <div> - <div className='docCreatorMenu-section' style={{height: this._GPTOpt ? 200 : 200}}> - <div className='docCreatorMenu-section-topbar'> - <div className='docCreatorMenu-section-title'>Suggested Templates</div> - <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._menuContent = 'dashboard'))}> - <FontAwesomeIcon icon='gear'/> + {this._expandedPreview ? ( + <div className="docCreatorMenu-expanded-template-preview"> + <img className="docCreatorMenu-preview-image expanded" src={this._expandedPreview.icon!.url.href.replace('.png', '_o.png')} /> + <div className="right-buttons-panel"> + <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this.setUpButtonClick(e, () => this.setExpandedView(undefined))}> + <FontAwesomeIcon icon="minimize" /> + </button> + <button className="docCreatorMenu-menu-button section-reveal-options top-right-lower" onPointerDown={e => this.setUpButtonClick(e, () => this._expandedPreview && this._templateDocs.push(this._expandedPreview.doc))}> + <FontAwesomeIcon icon="plus" color="white" /> </button> </div> - <div className='docCreatorMenu-templates-preview-window' style={{justifyContent: this._GPTLoading || this._menuDimensions.width > 400 ? 'center' : ''}}> - {this._GPTLoading ? ( - <div className="loading-spinner"> - <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} /> - </div> - ) : ( - this._suggestedTemplates?.map(doc => - ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info => - <div - className='docCreatorMenu-preview-window' - style={{ - border: this._selectedTemplate === info.doc ? `solid 3px ${Colors.MEDIUM_BLUE}` : '', - boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : '' - }} - onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}> - <button className='option-button left' onPointerDown={e => this.setUpButtonClick(e, () => {this.setExpandedView(info)})}> - <FontAwesomeIcon icon='magnifying-glass' color='white'/> - </button> - <button className='option-button right' onPointerDown={e => this.setUpButtonClick(e, () => this._templateDocs.push(info.doc))}> - <FontAwesomeIcon icon='plus' color='white'/> - </button> - <img className='docCreatorMenu-preview-image' src={info.icon!.url.href.replace(".png", "_o.png")} /> - </div> - ))} - </div> - <div className='docCreatorMenu-GPT-options'> - <div className='docCreatorMenu-GPT-options-container'> - <button className='docCreatorMenu-menu-button' onPointerDown={e => this.setUpButtonClick(e, () => this.generatePresetTemplates())}> - <FontAwesomeIcon icon='arrows-rotate'/> + </div> + ) : ( + <div> + <div className="docCreatorMenu-section" style={{ height: this._GPTOpt ? 200 : 200 }}> + <div className="docCreatorMenu-section-topbar"> + <div className="docCreatorMenu-section-title">Suggested Templates</div> + <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => (this._menuContent = 'dashboard')))}> + <FontAwesomeIcon icon="gear" /> </button> </div> - {this._GPTOpt ? GPTOptions : null } - </div> - </div> - <hr className='docCreatorMenu-option-divider full no-margin'/> - <div className='docCreatorMenu-section'> - <div className='docCreatorMenu-section-topbar'> - <div className='docCreatorMenu-section-title'>Your Templates</div> - <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, () => this._GPTOpt = !this._GPTOpt)}> - <FontAwesomeIcon icon='gear'/> - </button> - </div> - <div className='docCreatorMenu-templates-preview-window' style={{justifyContent: this._menuDimensions.width > 400 ? 'center' : ''}}> - <div className='docCreatorMenu-preview-window empty' onPointerDown={e => this.testTemplate()}> - <FontAwesomeIcon icon='plus' color='rgb(160, 160, 160)'/> + <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this._GPTLoading || this._menuDimensions.width > 400 ? 'center' : '' }}> + {this._GPTLoading ? ( + <div className="loading-spinner"> + <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} /> + </div> + ) : ( + this._suggestedTemplates + ?.map(doc => ({ icon: ImageCast(doc.icon), doc })) + .filter(info => info.icon && info.doc) + .map(info => ( + <div + className="docCreatorMenu-preview-window" + style={{ + border: this._selectedTemplate === info.doc ? `solid 3px ${Colors.MEDIUM_BLUE}` : '', + boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : '', + }} + onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}> + <button + className="option-button left" + onPointerDown={e => + this.setUpButtonClick(e, () => { + this.setExpandedView(info); + }) + }> + <FontAwesomeIcon icon="magnifying-glass" color="white" /> + </button> + <button className="option-button right" onPointerDown={e => this.setUpButtonClick(e, () => this._templateDocs.push(info.doc))}> + <FontAwesomeIcon icon="plus" color="white" /> + </button> + <img className="docCreatorMenu-preview-image" src={info.icon!.url.href.replace('.png', '_o.png')} /> + </div> + )) + )} + </div> + <div className="docCreatorMenu-GPT-options"> + <div className="docCreatorMenu-GPT-options-container"> + <button className="docCreatorMenu-menu-button" onPointerDown={e => this.setUpButtonClick(e, () => this.generatePresetTemplates())}> + <FontAwesomeIcon icon="arrows-rotate" /> + </button> + </div> + {this._GPTOpt ? GPTOptions : null} + </div> </div> - {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 (<div - className='docCreatorMenu-preview-window' - style={{ - border: this._selectedTemplate === info.doc ? `solid 3px ${Colors.MEDIUM_BLUE}` : '', - boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : '' - }} - onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}> - <button className='option-button left' onPointerDown={e => this.setUpButtonClick(e, () => {this.editTemplate(info.doc)})}> - <FontAwesomeIcon icon='pencil' color='black'/> - </button> - <button className='option-button right' onPointerDown={e => this.setUpButtonClick(e, () => {this.removeTemplate(info.doc)})}> - <FontAwesomeIcon icon='trash' color='black'/> + <hr className="docCreatorMenu-option-divider full no-margin" /> + <div className="docCreatorMenu-section"> + <div className="docCreatorMenu-section-topbar"> + <div className="docCreatorMenu-section-title">Your Templates</div> + <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, () => (this._GPTOpt = !this._GPTOpt))}> + <FontAwesomeIcon icon="gear" /> </button> - <img className='docCreatorMenu-preview-image' src={info.icon!.url.href.replace(".png", "_o.png")} /> </div> - )})} + <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this._menuDimensions.width > 400 ? 'center' : '' }}> + <div className="docCreatorMenu-preview-window empty" onPointerDown={e => this.testTemplate()}> + <FontAwesomeIcon icon="plus" color="rgb(160, 160, 160)" /> + </div> + {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 ( + <div + className="docCreatorMenu-preview-window" + style={{ + border: this._selectedTemplate === info.doc ? `solid 3px ${Colors.MEDIUM_BLUE}` : '', + boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : '', + }} + onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}> + <button + className="option-button left" + onPointerDown={e => + this.setUpButtonClick(e, () => { + this.editTemplate(info.doc); + }) + }> + <FontAwesomeIcon icon="pencil" color="black" /> + </button> + <button + className="option-button right" + onPointerDown={e => + this.setUpButtonClick(e, () => { + this.removeTemplate(info.doc); + }) + }> + <FontAwesomeIcon icon="trash" color="black" /> + </button> + <img className="docCreatorMenu-preview-image" src={info.icon!.url.href.replace('.png', '_o.png')} /> + </div> + ); + })} + </div> </div> </div> - </div> - } + )} </div> ); } - get savedLayoutsPreviewContents(){ + get savedLayoutsPreviewContents() { return ( - <div className='docCreatorMenu-preview-container'> - {this._savedLayouts.map((layout, index) => - <div - className='docCreatorMenu-preview-window' - style={{ - border: this.isSelectedLayout(layout) ? `solid 3px ${Colors.MEDIUM_BLUE}` : '', - boxShadow: this.isSelectedLayout(layout) ? `0 0 15px rgba(68, 118, 247, .8)` : '' - }} - onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedSavedLayout(layout)))} - > - {this.layoutPreviewContents(87, layout, true, index)} - </div> - )} + <div className="docCreatorMenu-preview-container"> + {this._savedLayouts.map((layout, index) => ( + <div + className="docCreatorMenu-preview-window" + style={{ + border: this.isSelectedLayout(layout) ? `solid 3px ${Colors.MEDIUM_BLUE}` : '', + boxShadow: this.isSelectedLayout(layout) ? `0 0 15px rgba(68, 118, 247, .8)` : '', + }} + onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedSavedLayout(layout)))}> + {this.layoutPreviewContents(87, layout, true, index)} + </div> + ))} </div> ); } - @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) }; + @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); + }; get layoutConfigOptions() { const optionInput = (icon: string, func: Function, def?: number, key?: string, noMargin?: boolean) => { return ( - <div className='docCreatorMenu-option-container small no-margin' key={key} style={{marginTop: noMargin ? '0px' : ''}} - > - <div className='docCreatorMenu-option-title config layout-config'> - <FontAwesomeIcon icon={icon as any}/> + <div className="docCreatorMenu-option-container small no-margin" key={key} style={{ marginTop: noMargin ? '0px' : '' }}> + <div className="docCreatorMenu-option-title config layout-config"> + <FontAwesomeIcon icon={icon as any} /> </div> - <input defaultValue={def} onInput={(e) => func(e.currentTarget.value)} className='docCreatorMenu-input config layout-config'/> + <input defaultValue={def} onInput={e => func(e.currentTarget.value)} className="docCreatorMenu-input config layout-config" /> </div> ); - } + }; switch (this._layout.type) { case LayoutType.Row: - return ( - <div className='docCreatorMenu-configuration-bar'> - {optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '0')} - </div> - ); + return <div className="docCreatorMenu-configuration-bar">{optionInput('arrows-left-right', this.updateXMargin, this._layout.xMargin, '0')}</div>; case LayoutType.Column: - return ( - <div className='docCreatorMenu-configuration-bar'> - {optionInput('arrows-up-down', this.updateYMargin, this._layout.yMargin, '1')} - </div> - ); + return <div className="docCreatorMenu-configuration-bar">{optionInput('arrows-up-down', this.updateYMargin, this._layout.yMargin, '1')}</div>; case LayoutType.Grid: return ( - <div className='docCreatorMenu-configuration-bar'> + <div className="docCreatorMenu-configuration-bar"> {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)} @@ -1072,9 +1131,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { // return Docs.Create.FreeformDocument([], { _height: 200, _width: 200, title: 'title'}); // } - screenToLocalTransform = () => - this._props - .ScreenToLocalTransform(); + screenToLocalTransform = () => this._props.ScreenToLocalTransform(); layoutPreviewContents = (outerSpan: number, altLayout?: DataVizTemplateLayout, small: boolean = false, id?: number) => { const doc: Doc | undefined = altLayout ? altLayout.template : this._selectedTemplate; @@ -1084,10 +1141,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { 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 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)} + const scaledDown = (input: number) => { + return input / ((largerSpan / outerSpan) * this._layoutPreviewScale); + }; const fontSize = Math.min(scaledDown(docWidth / 3), scaledDown(docHeight / 3)); return ( @@ -1114,220 +1173,275 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { // {null} // </CollectionFreeFormView> // </div> - <div className='docCreatorMenu-layout-preview-window-wrapper' id={String(id) ?? undefined}> - <div className='docCreatorMenu-zoom-button-container'> - <button - className='docCreatorMenu-zoom-button' - onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._layoutPreviewScale *= 1.25))}> - <FontAwesomeIcon icon={'minus'}/> + <div className="docCreatorMenu-layout-preview-window-wrapper" id={String(id) ?? undefined}> + <div className="docCreatorMenu-zoom-button-container"> + <button className="docCreatorMenu-zoom-button" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => (this._layoutPreviewScale *= 1.25)))}> + <FontAwesomeIcon icon={'minus'} /> </button> - <button - className='docCreatorMenu-zoom-button zoom-in' - onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._layoutPreviewScale *= .75))}> - <FontAwesomeIcon icon={'plus'}/> + <button className="docCreatorMenu-zoom-button zoom-in" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => (this._layoutPreviewScale *= 0.75)))}> + <FontAwesomeIcon icon={'plus'} /> </button> - {altLayout ? <button - className='docCreatorMenu-zoom-button trash' - onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._savedLayouts.splice(this._savedLayouts.indexOf(altLayout), 1)))}> - <FontAwesomeIcon icon={'trash'}/> - </button> : null} + {altLayout ? ( + <button className="docCreatorMenu-zoom-button trash" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._savedLayouts.splice(this._savedLayouts.indexOf(altLayout), 1)))}> + <FontAwesomeIcon icon={'trash'} /> + </button> + ) : null} </div> - {<div - id={String(id) ?? undefined} - className={`docCreatorMenu-layout-preview-window ${small ? 'small' : ''}`} - style={{ - gridTemplateColumns: `repeat(${altLayout ? altLayout.columns : this.columnsCount}, ${scaledDown(docWidth)}px`, - gridTemplateRows: `${scaledDown(docHeight)}px`, - gridAutoRows: `${scaledDown(docHeight)}px`, - rowGap: `${scaledDown(layout.yMargin)}px`, - columnGap: `${scaledDown(layout.xMargin)}px` - }}> - {this._layout.type === LayoutType.Stacked ? - <div - className='docCreatorMenu-layout-preview-item' - style={{ - width: scaledDown(docWidth), - height: scaledDown(docHeight), - fontSize: fontSize, - }} - > - All - </div> : - this.docsToRender.map(num => - <div - onMouseEnter={() => this._dataViz?.setSpecialHighlightedRow(num)} - onMouseLeave={() => this._dataViz?.setSpecialHighlightedRow(undefined)} - className='docCreatorMenu-layout-preview-item' + { + <div + id={String(id) ?? undefined} + className={`docCreatorMenu-layout-preview-window ${small ? 'small' : ''}`} style={{ - width: scaledDown(docWidth), - height: scaledDown(docHeight), - fontSize: fontSize, - }} - > - {num} - </div> - )} - - </div>} + gridTemplateColumns: `repeat(${altLayout ? altLayout.columns : this.columnsCount}, ${scaledDown(docWidth)}px`, + gridTemplateRows: `${scaledDown(docHeight)}px`, + gridAutoRows: `${scaledDown(docHeight)}px`, + rowGap: `${scaledDown(layout.yMargin)}px`, + columnGap: `${scaledDown(layout.xMargin)}px`, + }}> + {this._layout.type === LayoutType.Stacked ? ( + <div + className="docCreatorMenu-layout-preview-item" + style={{ + width: scaledDown(docWidth), + height: scaledDown(docHeight), + fontSize: fontSize, + }}> + All + </div> + ) : ( + this.docsToRender.map(num => ( + <div + onMouseEnter={() => this._dataViz?.setSpecialHighlightedRow(num)} + onMouseLeave={() => this._dataViz?.setSpecialHighlightedRow(undefined)} + className="docCreatorMenu-layout-preview-item" + style={{ + width: scaledDown(docWidth), + height: scaledDown(docHeight), + fontSize: fontSize, + }}> + {num} + </div> + )) + )} + </div> + } </div> ); - } - - get optionsMenuContents(){ - const layoutEquals = (layout: DataVizTemplateLayout) => { + }; - } //TODO: ADD LATER + get optionsMenuContents() { + const layoutEquals = (layout: DataVizTemplateLayout) => {}; //TODO: ADD LATER const layoutOption = (option: LayoutType, optStyle?: {}, specialFunc?: Function) => { return ( - <div - className="docCreatorMenu-dropdown-option" + <div + className="docCreatorMenu-dropdown-option" style={optStyle} - onPointerDown={e => this.setUpButtonClick(e, () => {specialFunc?.(); runInAction(() => this._layout.type = option)})}> + onPointerDown={e => + this.setUpButtonClick(e, () => { + specialFunc?.(); + runInAction(() => (this._layout.type = option)); + }) + }> {option} </div> ); - } + }; const selectionBox = (width: number, height: number, icon: string, specClass?: string, options?: JSX.Element[], manual?: boolean): JSX.Element => { - return (<div className='docCreatorMenu-option-container'> - <div className={`docCreatorMenu-option-title config ${specClass}`} style={{width: width * .4, height: height}}> - <FontAwesomeIcon icon={icon as any}/> + return ( + <div className="docCreatorMenu-option-container"> + <div className={`docCreatorMenu-option-title config ${specClass}`} style={{ width: width * 0.4, height: height }}> + <FontAwesomeIcon icon={icon as any} /> + </div> + {manual ? ( + <input className={`docCreatorMenu-input config ${specClass}`} style={{ width: width * 0.6, height: height }} /> + ) : ( + <select className={`docCreatorMenu-input config ${specClass}`} style={{ width: width * 0.6, height: height }}> + {options} + </select> + )} </div> - {manual ? <input className={`docCreatorMenu-input config ${specClass}`} style={{width: width * .6, height: height}}/> : - <select className={`docCreatorMenu-input config ${specClass}`} style={{width: width * .6, height: height}}> - {options} - </select> - } - </div>); - } + ); + }; const repeatOptions = [0, 1, 2, 3, 4, 5]; return ( - <div className='docCreatorMenu-menu-container'> - <div className='docCreatorMenu-option-container layout'> - <div className='docCreatorMenu-dropdown-hoverable'> + <div className="docCreatorMenu-menu-container"> + <div className="docCreatorMenu-option-container layout"> + <div className="docCreatorMenu-dropdown-hoverable"> <div className="docCreatorMenu-option-title">{this._layout.type ? this._layout.type.toUpperCase() : 'Choose Layout'}</div> <div className="docCreatorMenu-dropdown-content"> {layoutOption(LayoutType.Stacked)} - {layoutOption(LayoutType.Grid, undefined, () => {if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length))})} + {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`})} + {layoutOption(LayoutType.Custom, { borderBottom: `0px` })} </div> </div> - <button - className='docCreatorMenu-menu-button preview-toggle' - onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._layoutPreview = !this._layoutPreview))}> - <FontAwesomeIcon icon={this._layoutPreview ? 'minus' : 'magnifying-glass'}/> + <button className="docCreatorMenu-menu-button preview-toggle" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => (this._layoutPreview = !this._layoutPreview)))}> + <FontAwesomeIcon icon={this._layoutPreview ? 'minus' : 'magnifying-glass'} /> </button> </div> - {this._layout.type ? this.layoutConfigOptions: null} - {this._layoutPreview ? this.layoutPreviewContents(this._menuDimensions.width * .75) : null} - {selectionBox(60, 20, 'repeat', undefined, repeatOptions.map(num => <option onPointerDown={e => this._layout.repeat = num}>{`${num}x`}</option>))} - <hr className='docCreatorMenu-option-divider'/> - <div className='docCreatorMenu-general-options-container'> + {this._layout.type ? this.layoutConfigOptions : null} + {this._layoutPreview ? this.layoutPreviewContents(this._menuDimensions.width * 0.75) : null} + {selectionBox( + 60, + 20, + 'repeat', + undefined, + repeatOptions.map(num => <option onPointerDown={e => (this._layout.repeat = num)}>{`${num}x`}</option>) + )} + <hr className="docCreatorMenu-option-divider" /> + <div className="docCreatorMenu-general-options-container"> <button - className='docCreatorMenu-save-layout-button' - onPointerDown={e => setupMoveUpEvents( this, e, returnFalse, emptyFunction, - undoable(clickEv => { - clickEv.stopPropagation(); - if (!this._selectedTemplate) return; - const layout: DataVizTemplateLayout = {template: this._selectedTemplate, layout: {type: this._layout.type, xMargin: this._layout.xMargin, yMargin:this._layout.yMargin, repeat: 0}, columns: this.columnsCount, rows: this.rowsCount, docsNumList: this.docsToRender}; - if (!this._savedLayouts.includes(layout)) { this._savedLayouts.push(layout) }; - }, 'make docs') - ) - }> - <FontAwesomeIcon icon='floppy-disk'/> + className="docCreatorMenu-save-layout-button" + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + undoable(clickEv => { + clickEv.stopPropagation(); + if (!this._selectedTemplate) return; + const layout: DataVizTemplateLayout = { + template: this._selectedTemplate, + layout: { type: this._layout.type, xMargin: this._layout.xMargin, yMargin: this._layout.yMargin, repeat: 0 }, + columns: this.columnsCount, + rows: this.rowsCount, + docsNumList: this.docsToRender, + }; + if (!this._savedLayouts.includes(layout)) { + this._savedLayouts.push(layout); + } + }, 'make docs') + ) + }> + <FontAwesomeIcon icon="floppy-disk" /> </button> - <button - className='docCreatorMenu-create-docs-button' - style={{backgroundColor: this.canMakeDocs ? '' : 'rgb(155, 155, 155)', border: this.canMakeDocs ? '' : 'solid 2px rgb(180, 180, 180)'}} - onPointerDown={e => setupMoveUpEvents( this, e, returnFalse, emptyFunction, + <button + className="docCreatorMenu-create-docs-button" + style={{ backgroundColor: this.canMakeDocs ? '' : 'rgb(155, 155, 155)', border: this.canMakeDocs ? '' : 'solid 2px rgb(180, 180, 180)' }} + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, undoable(clickEv => { clickEv.stopPropagation(); if (!this._selectedTemplate) return; - const templateInfo: DataVizTemplateInfo = {doc: this._selectedTemplate, layout: this._layout, referencePos: {x: this._pageX + 450, y: this._pageY}, columns: this.columnsCount}; + const templateInfo: DataVizTemplateInfo = { doc: this._selectedTemplate, layout: this._layout, referencePos: { x: this._pageX + 450, y: this._pageY }, columns: this.columnsCount }; this._dataViz?.createDocsFromTemplate(templateInfo); - }, 'make docs') + }, 'make docs') ) }> - <FontAwesomeIcon icon='plus'/> + <FontAwesomeIcon icon="plus" /> </button> </div> </div> ); } - get dashboardContents(){ + get dashboardContents() { const sizes: string[] = ['tiny', 'small', 'medium', 'large', 'huge']; const fieldPanel = (field: Col) => { return ( - <div className='field-panel'> - <div className='top-bar'> - <span className='field-title'>{`${field.title} Field`}</span> - <button className='docCreatorMenu-menu-button section-reveal-options no-margin' onPointerDown={e => this.setUpButtonClick(e, this.addField)} style={{position: 'absolute', right: '0px'}}> - <FontAwesomeIcon icon='minus'/> + <div className="field-panel"> + <div className="top-bar"> + <span className="field-title">{`${field.title} Field`}</span> + <button className="docCreatorMenu-menu-button section-reveal-options no-margin" onPointerDown={e => this.setUpButtonClick(e, this.addField)} style={{ position: 'absolute', right: '0px' }}> + <FontAwesomeIcon icon="minus" /> </button> </div> - <div className='opts-bar'> - <div className='opt-box'> - <div className='top-bar'> Title </div> - <textarea className='content' style={{width: '100%', height: 'calc(100% - 20px)'}} defaultValue={field.title} placeholder={'Enter title'} onChange={(e) => this.setColTitle(field, e.target.value)}/> + <div className="opts-bar"> + <div className="opt-box"> + <div className="top-bar"> Title </div> + <textarea className="content" style={{ width: '100%', height: 'calc(100% - 20px)' }} defaultValue={field.title} placeholder={'Enter title'} onChange={e => this.setColTitle(field, e.target.value)} /> </div> - <div className='opt-box'> - <div className='top-bar'> Type </div> - <div className='content'> - <span className='type-display'>{field.type === TemplateFieldType.TEXT ? 'Text Field' : field.type === TemplateFieldType.VISUAL ? 'File Field' : ''}</span> - <div className='bubbles'> - <input className='bubble' type="radio" name="type" onClick={() => {this.setColType(field, TemplateFieldType.TEXT)}}/> - <div className='text'>Text</div> - <input className='bubble' type="radio" name="type" onClick={() => {this.setColType(field, TemplateFieldType.VISUAL)}}/> - <div className='text'>File</div> + <div className="opt-box"> + <div className="top-bar"> Type </div> + <div className="content"> + <span className="type-display">{field.type === TemplateFieldType.TEXT ? 'Text Field' : field.type === TemplateFieldType.VISUAL ? 'File Field' : ''}</span> + <div className="bubbles"> + <input + className="bubble" + type="radio" + name="type" + onClick={() => { + this.setColType(field, TemplateFieldType.TEXT); + }} + /> + <div className="text">Text</div> + <input + className="bubble" + type="radio" + name="type" + onClick={() => { + this.setColType(field, TemplateFieldType.VISUAL); + }} + /> + <div className="text">File</div> </div> </div> </div> </div> - <div className='sizes-box'> - <div className='top-bar'> Valid Sizes </div> - <div className='content'> - <div className='bubbles'> - {sizes.map(size => <> - <input className='bubble' type="checkbox" name="type" checked={field.sizes.includes(size as TemplateFieldSize)} onChange={(e) => {this.modifyColSizes(field, size as TemplateFieldSize, e.target.checked)}}/> - <div className='text'>{size}</div> - </>)} + <div className="sizes-box"> + <div className="top-bar"> Valid Sizes </div> + <div className="content"> + <div className="bubbles"> + {sizes.map(size => ( + <> + <input + className="bubble" + type="checkbox" + name="type" + checked={field.sizes.includes(size as TemplateFieldSize)} + onChange={e => { + this.modifyColSizes(field, size as TemplateFieldSize, e.target.checked); + }} + /> + <div className="text">{size}</div> + </> + ))} </div> </div> </div> - <div className='desc-box'> - <div className='top-bar'> Description </div> - <textarea className='content' onChange={(e) => this.setColDesc(field, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description to help with template generation.'} /> + <div className="desc-box"> + <div className="top-bar"> Description </div> + <textarea + className="content" + onChange={e => this.setColDesc(field, e.target.value)} + defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc} + placeholder={this._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description to help with template generation.'} + /> </div> </div> - ) - } + ); + }; return ( - <div className='docCreatorMenu-dashboard-view'> - <div className='topbar'> - <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, this.addField)}> - <FontAwesomeIcon icon='plus'/> - </button> - <button className='docCreatorMenu-menu-button section-reveal-options float-right' onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._menuContent = 'templates'))}> - <FontAwesomeIcon icon='arrow-left'/> - </button> - </div> - <div className='panels-container'> - {this.fieldsInfos.map(field => fieldPanel(field))} + <div className="docCreatorMenu-dashboard-view"> + <div className="topbar"> + <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.setUpButtonClick(e, this.addField)}> + <FontAwesomeIcon icon="plus" /> + </button> + <button className="docCreatorMenu-menu-button section-reveal-options float-right" onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => (this._menuContent = 'templates')))}> + <FontAwesomeIcon icon="arrow-left" /> + </button> + </div> + <div className="panels-container">{this.fieldsInfos.map(field => fieldPanel(field))}</div> </div> - </div> ); } - get renderSelectedViewType(){ - switch (this._menuContent){ + get renderSelectedViewType() { + switch (this._menuContent) { case 'templates': return this.templatesPreviewContents; case 'options': @@ -1341,7 +1455,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { } } - get resizePanes(){ + get resizePanes() { const ref = this._ref?.getBoundingClientRect(); const height: number = ref?.height ?? 0; const width: number = ref?.width ?? 0; @@ -1362,90 +1476,96 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> { const topButton = (icon: string, opt: string, func: Function, tag: string) => { return ( <div className={`top-button-container ${tag} ${opt === this._menuContent ? 'selected' : ''}`}> - <div + <div className="top-button-content" - onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => {func()}))}> - <FontAwesomeIcon icon={icon as any}/> + onPointerDown={e => + this.setUpButtonClick(e, () => + runInAction(() => { + func(); + }) + ) + }> + <FontAwesomeIcon icon={icon as any} /> </div> </div> ); - } + }; - const onPreviewSelected = () => {this._menuContent = 'templates'} - const onSavedSelected = () => {this._menuContent = 'dashboard'} + const onPreviewSelected = () => { + this._menuContent = 'templates'; + }; + const onSavedSelected = () => { + this._menuContent = 'dashboard'; + }; const onOptionsSelected = () => { this._menuContent = 'options'; if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length)); - } - + }; return ( - <div className='docCreatorMenu'> - {!this._shouldDisplay ? undefined : - <div - className="docCreatorMenu-cont" - ref={r => this._ref = r} - style={{ - display: '', - left: this._pageX, - top: this._pageY, - width: this._menuDimensions.width, - height: this._menuDimensions.height, - background: SnappingManager.userBackgroundColor, - color: SnappingManager.userColor, - }}> + <div className="docCreatorMenu"> + {!this._shouldDisplay ? undefined : ( + <div + className="docCreatorMenu-cont" + ref={r => (this._ref = r)} + style={{ + display: '', + left: this._pageX, + top: this._pageY, + width: this._menuDimensions.width, + height: this._menuDimensions.height, + background: SnappingManager.userBackgroundColor, + color: SnappingManager.userColor, + }}> {this.resizePanes} - <div - className='docCreatorMenu-menu' - onPointerDown={e => - setupMoveUpEvents( - this, - e, - (e) => { - this._dragging = true; - this._startPos = {x: 0, y: 0}; - this._startPos.x = e.pageX - (this._ref?.getBoundingClientRect().left ?? 0); - this._startPos.y = e.pageY - (this._ref?.getBoundingClientRect().top ?? 0); - document.addEventListener('pointermove', this.onDrag); - return true; - }, - emptyFunction, - undoable(clickEv => { - clickEv.stopPropagation(); - }, 'drag menu') - ) - } - > - <div className='docCreatorMenu-top-buttons-container'> + <div + className="docCreatorMenu-menu" + onPointerDown={e => + setupMoveUpEvents( + this, + e, + e => { + this._dragging = true; + this._startPos = { x: 0, y: 0 }; + this._startPos.x = e.pageX - (this._ref?.getBoundingClientRect().left ?? 0); + this._startPos.y = e.pageY - (this._ref?.getBoundingClientRect().top ?? 0); + document.addEventListener('pointermove', this.onDrag); + return true; + }, + emptyFunction, + undoable(clickEv => { + clickEv.stopPropagation(); + }, 'drag menu') + ) + }> + <div className="docCreatorMenu-top-buttons-container"> {topButton('table-cells', 'templates', onPreviewSelected, 'left')} {topButton('bars', 'options', onOptionsSelected, 'middle')} {topButton('floppy-disk', 'saved', onSavedSelected, 'right')} </div> - <button - className='docCreatorMenu-menu-button close-menu' - onPointerDown={e => this.setUpButtonClick(e, this.closeMenu)}> - <FontAwesomeIcon icon={'minus'}/> + <button className="docCreatorMenu-menu-button close-menu" onPointerDown={e => this.setUpButtonClick(e, this.closeMenu)}> + <FontAwesomeIcon icon={'minus'} /> </button> </div> {this.renderSelectedViewType} - </div> - } + </div> + )} </div> - ) - } + ); + } } export interface DataVizTemplateInfo { doc: Doc; - layout: {type: LayoutType, xMargin: number, yMargin: number, repeat: number}; + layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number }; columns: number; - referencePos: {x: number, y: number}; + referencePos: { x: number; y: number }; } export interface DataVizTemplateLayout { template: Doc; docsNumList: number[]; - layout: {type: LayoutType, xMargin: number, yMargin: number, repeat: number}; + layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number }; columns: number; rows: number; } @@ -1453,7 +1573,7 @@ export interface DataVizTemplateLayout { export enum TemplateFieldType { TEXT = 'text', VISUAL = 'visual', - UNSET = 'unset' + UNSET = 'unset', } export enum TemplateFieldSize { @@ -1461,17 +1581,16 @@ export enum TemplateFieldSize { SMALL = 'small', MEDIUM = 'medium', LARGE = 'large', - HUGE = 'huge' + HUGE = 'huge', } - export type Col = { sizes: TemplateFieldSize[]; desc: string; title: string; type: TemplateFieldType; defaultContent?: string; -} +}; type Field = { tl: [number, number]; @@ -1482,7 +1601,7 @@ type Field = { sizes?: TemplateFieldSize[]; isDecoration?: boolean; description?: string; -} +}; // class ContentField implements Field { // tl: [number, number]; @@ -1493,10 +1612,10 @@ type Field = { // sizes?: TemplateFieldSize[]; // description?: string; -// constructor( tl: [number, number], br: [number, number], -// opts: FieldOpts, subfields?: Field[], -// types?: TemplateFieldType[], -// sizes?: TemplateFieldSize[], +// constructor( tl: [number, number], br: [number, number], +// opts: FieldOpts, subfields?: Field[], +// types?: TemplateFieldType[], +// sizes?: TemplateFieldSize[], // description?: string) { // this.tl = tl; // this.br = br; @@ -1514,9 +1633,7 @@ type Field = { type DecorationField = Field; -type InkDecoration = { - -} +type InkDecoration = {}; type TemplateDecorations = Field | InkDecoration; @@ -1545,85 +1662,86 @@ export interface FieldOpts { fieldViewType?: 'freeform' | 'stacked'; } -interface TemplateOpts extends FieldOpts { - -} +interface TemplateOpts extends FieldOpts {} export class FieldUtils { - public static contentFields = (fields: Field[]) => { let toRet: Field[] = []; fields.forEach(field => { - if (!field.isDecoration) { toRet.push(field) }; + if (!field.isDecoration) { + toRet.push(field); + } toRet = toRet.concat(FieldUtils.contentFields(field.subfields ?? [])); }); return toRet; - } + }; public static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => { const words: string[] = text.split(/\s+/).filter(Boolean); - let currFontSize = 1; + let currFontSize = 1; let rowsCount = 1; let currTextHeight = currFontSize * rowsCount * 2; - + while (currTextHeight <= contHeight) { let wordIndex = 0; let currentRowWidth = 0; let wordsInCurrRow = 0; rowsCount = 1; - + while (wordIndex < words.length) { const word = words[wordIndex]; - const wordWidth = word.length * currFontSize * .5; + const wordWidth = word.length * currFontSize * 0.5; //console.log(wordWidth) - + if (currentRowWidth + wordWidth <= contWidth) { currentRowWidth += wordWidth; ++wordsInCurrRow; } else { - if (words.length !== 1 && words.length > wordsInCurrRow){ + if (words.length !== 1 && words.length > wordsInCurrRow) { rowsCount++; - currentRowWidth = wordWidth; + currentRowWidth = wordWidth; wordsInCurrRow = 1; } else { break; } } - - wordIndex++; + + wordIndex++; } - + currTextHeight = rowsCount * currFontSize * 2; //console.log(rowsCount, currFontSize, currTextHeight) - + currFontSize += 1; } - + return currFontSize - 1; }; - 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 + 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}; + const coord = { x: l, y: t }; //console.log(coords, parentWidth, parentHeight, height); - return {width, height, coord}; - } + return { width, height, coord }; + }; - 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); + 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 docWithBasicOpts = (Docs.Create.FreeformDocument)([], { - isDefaultTemplateDoc: true, - _height: height, - _width: width, - title: title, - x: coord.x, - y: coord.y, + 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}px` ?? '0px', borderColor: opts.borderColor, @@ -1634,21 +1752,21 @@ export class FieldUtils { }); return docWithBasicOpts; - } + }; - 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); + 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 bool = true; - const docWithBasicOpts = (Docs.Create.TextDocument)(content, { + 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)}` , + _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, @@ -1665,17 +1783,17 @@ export class FieldUtils { docWithBasicOpts._layout_hideScroll = true; return docWithBasicOpts; - } + }; - 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); + 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); - const doc = Docs.Create.ImageDocument(content, { - isDefaultTemplateDoc: true, - _height: height, - _width: width, - title: title, - x: coord.x, + 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 ?? '', @@ -1689,28 +1807,24 @@ export class FieldUtils { //setTimeout(() => {doc._height = height; doc._width = width}, 10); return doc; - } + }; - 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); + 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); - const doc = Docs.Create.Carousel3DDocument(fields, { _height: height, _width: width, title: title, x: coord.x, y: coord.y, _text_fontSize: `${height/2}` }) + const doc = Docs.Create.Carousel3DDocument(fields, { _height: height, _width: width, title: title, x: coord.x, y: coord.y, _text_fontSize: `${height / 2}` }); return doc; - } - + }; } export class TemplateLayouts { - public static get allTemplates(): TemplateDocInfos[] { - return Object.values(TemplateLayouts).filter( - value => typeof value === 'object' && value !== null && 'title' in value - ) as TemplateDocInfos[]; + return Object.values(TemplateLayouts).filter(value => typeof value === 'object' && value !== null && 'title' in value) as TemplateDocInfos[]; } public static getTemplateByTitle = (title: string): TemplateDocInfos | undefined => { - switch (title){ + switch (title) { case 'fourfield1': return TemplateLayouts.FourField001; case 'fourfield2': @@ -1728,173 +1842,187 @@ export class TemplateLayouts { } return undefined; - } + }; public static FourField001: TemplateDocInfos = { title: 'fourfield1', - width: 416, - height: 700, + width: 416, + height: 700, opts: { backgroundColor: '#C0B887', cornerRounding: 20, borderColor: '#6B461F', borderWidth: '12', }, - fields: [{ - tl: [-.95, -1], - br: [.95, -.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: [-.87, -.83], - br: [.87, .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: [-.8, .2], - br: [.8, .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: [-.87, .37], - br: [.87, .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', - } - }], + 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: [], }; public static FourField002: TemplateDocInfos = { title: 'fourfield2', - width: 425, - height: 778, + width: 425, + height: 778, opts: { - backgroundColor: '#242425' + backgroundColor: '#242425', }, - fields: [{ - tl: [-.83, -.95], - br: [.83, -.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: [-.65, -.2], - br: [.65, -.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: [-.65, 0], - br: [.65, .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: [-.83, .2], - br: [.83, .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: [-.8, -.075], - br: [-.525, .075], - opts: { - backgroundColor: '#F8E71C', - rotation: 45 - } - }, { - tl: [-.3075, -.0245], - br: [-.2175, .0245], - opts: { - backgroundColor: '#F8E71C', - rotation: 45 - } - }, { - tl: [-.045, -.0245], - br: [.045, .0245], - opts: { - backgroundColor: '#F8E71C', - rotation: 45 - } - }, { - tl: [.2175, -.0245], - br: [.3075, .0245], - opts: { - backgroundColor: '#F8E71C', - rotation: 45 - } - }, { - tl: [.525, -.075], - br: [.8, .075], - opts: { - backgroundColor: '#F8E71C', - rotation: 45 - } - } - - ] + 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, + }, + }, + ], }; // public static FourField003: TemplateDocInfos = { // title: 'fourfield3', - // width: 477, - // height: 662, + // width: 477, + // height: 662, // opts: { // backgroundColor: '#9E9C95' // }, // fields: [{ - // tl: [-.875, -.9], - // br: [.875, .7], + // tl: [-.875, -.9], + // br: [.875, .7], // types: [TemplateFieldType.VISUAL], // sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], // description: '', @@ -1903,8 +2031,8 @@ export class TemplateLayouts { // borderColor: '#E0E0DA', // } // }, { - // tl: [-.95, .8], - // br: [-.1, .95], + // tl: [-.95, .8], + // br: [-.1, .95], // types: [TemplateFieldType.TEXT], // sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], // description: '', @@ -1914,8 +2042,8 @@ export class TemplateLayouts { // contentXCentering: 'h-right', // } // }, { - // tl: [.1, .8], - // br: [.95, .95], + // tl: [.1, .8], + // br: [.95, .95], // types: [TemplateFieldType.TEXT], // sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL], // description: '', @@ -1926,8 +2054,8 @@ export class TemplateLayouts { // contentXCentering: 'h-left' // } // }, { - // tl: [0, -.9], - // br: [.85, -.66], + // tl: [0, -.9], + // br: [.85, -.66], // types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL], // sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE], // description: '', @@ -1937,8 +2065,8 @@ export class TemplateLayouts { // } // }], // decorations: [{ - // tl: [-.025, .8], - // br: [.025, .95], + // tl: [-.025, .8], + // br: [.025, .95], // opts: { // backgroundColor: '#E0E0DA', // } @@ -1947,242 +2075,257 @@ export class TemplateLayouts { public static FourField004: TemplateDocInfos = { title: 'fourfield4', - width: 414, - height: 583, + width: 414, + height: 583, opts: { backgroundColor: '#6CCAF0', borderColor: '#1088C3', - borderWidth: '10' + borderWidth: '10', }, - fields: [{ - tl: [-.86, -.92], - br: [-.075, -.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: [.075, -.92], - br: [.86, -.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: [-.81, -.64], - br: [.81, .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: [-.86, .6], - br: [.86, .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: [-.852, -.67], - br: [.852, .51], - opts: { - backgroundColor: 'transparent', - borderColor: '#007C0C', - 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', + }, + }, + ], }; public static ThreeField001: TemplateDocInfos = { title: 'threefield1', - width: 575, - height: 770, + width: 575, + height: 770, opts: { - backgroundColor: '#DDD3A9' + backgroundColor: '#DDD3A9', }, - fields: [{ - tl: [-.66, -.747], - br: [.66, .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, + 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: [-.7, .2], - br: [.7, .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: [-.95, .5], - br: [.95, .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: [.2, -1.32], - br: [1.8, -.66], - opts: { - backgroundColor: '#CEB155', - rotation: 45, - } - }, { - tl: [-1.8, -1.32], - br: [-.2, -.66], - opts: { - backgroundColor: '#CEB155', - rotation: 135, - } - }, { - tl: [.33, .75], - br: [1.66, 1.25], - opts: { - backgroundColor: '#CEB155', - rotation: 135, - } - }, { - tl: [-1.66, .75], - br: [-.33, 1.25], - opts: { - backgroundColor: '#CEB155', - 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, + }, + }, + ], }; public static ThreeField002: TemplateDocInfos = { title: 'threefield2', - width: 477, - height: 662, + width: 477, + height: 662, opts: { - backgroundColor: '#9E9C95' + backgroundColor: '#9E9C95', }, - fields: [{ - tl: [-.875, -.9], - br: [.875, .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: [.1, .775], - br: [.95, .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: [-.95, .775], - br: [-.1, .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: [-.025, .8], - br: [.025, .95], - opts: { - backgroundColor: '#E0E0DA', - } - }] + 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', + }, + }, + ], }; - - -// 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] -// }] -// }; - + // 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] + // }] + // }; } - -export class ContentField extends Field { - -}
\ No newline at end of file |