From 58692ef043125ebd3113d81f3ca7161a7e6521cb Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Mon, 13 May 2024 18:44:23 -0400
Subject: equation parser works but occasional call stack error

---
 src/client/views/nodes/DocumentView.tsx | 1 +
 1 file changed, 1 insertion(+)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8df28a770..32480447d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1025,6 +1025,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     public static getViews = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []) as DocumentView[];
     public static getFirstDocumentView: (toFind: Doc) => DocumentView | undefined;
     public static getDocumentView: (target: Doc | undefined, preferredCollection?: DocumentView) => Opt<DocumentView>;
+    public static getDocViewIndex: (target: Doc) => number;
     public static getContextPath: (doc: Opt<Doc>, includeExistingViews?: boolean) => Doc[];
     public static getLightboxDocumentView: (toFind: Doc) => Opt<DocumentView>;
     public static showDocumentView: (targetDocView: DocumentView, options: FocusViewOptions) => Promise<void>;
-- 
cgit v1.2.3-70-g09d2


From f5c2f5bc8710a965a0b52e2e414f62a2a459a94f Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 25 Aug 2024 04:26:12 -0400
Subject: border work

---
 src/client/documents/Documents.ts       |   2 +
 src/client/views/ContextMenu.scss       |  35 ++++++++++
 src/client/views/ContextMenu.tsx        | 118 ++++++++++++++++++++++----------
 src/client/views/ContextMenuItem.tsx    |  12 +++-
 src/client/views/MainView.tsx           |   6 +-
 src/client/views/PropertiesView.tsx     |  10 +++
 src/client/views/StyleProvider.tsx      |  37 ++++++----
 src/client/views/nodes/DocumentView.tsx |  37 ++++++----
 8 files changed, 191 insertions(+), 66 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 751fe6d91..aecc79189 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -256,6 +256,8 @@ export class DocumentOptions {
     layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown');
     layout_borderRounding?: string;
     _layout_borderRounding?: STRt = new StrInfo('amount of rounding to document view corners');
+    _layout_borderWidth?: NUMt = new NumInfo('Hey now', false);
+    _layout_borderColor?: STRt = new StrInfo('No no', false);
     _layout_modificationDate?: DATEt = new DateInfo('last modification date of doc layout', false);
     _layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false);
     _layout_reflowVertical?: BOOLt = new BoolInfo('permit vertical resizing with content "reflow"');
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index 232362c5c..ff6ad526e 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -165,3 +165,38 @@
     border: solid black 1px;
     border-radius: 5px;
 }
+
+.contextMenu-borderMenu {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    width: 222px;
+    height: 300px;
+    background-color: white;
+    border: solid 1px black;
+
+    .top-bar {
+        height: 20px;
+        width: 100%;
+        display: flex; 
+
+        .close-menu {
+            margin-top: 0;
+            margin-bottom: 0;
+            margin-right: 0;
+            padding: 0;
+            margin-left: auto;
+            z-index: 999999999;
+            width: 20px;
+            height: 20px;
+            color: black;
+            background-color: transparent;
+        }
+    }
+
+    .bottom-box{
+        .width-selector{
+            
+        }
+    }
+}
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index de985263d..9472fb95c 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -5,11 +5,17 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { DivHeight, DivWidth } from '../../ClientUtils';
+import { DivHeight, DivWidth, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
 import { SnappingManager } from '../util/SnappingManager';
 import './ContextMenu.scss';
 import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
 import { ObservableReactComponent } from './ObservableReactComponent';
+import { ColorResult, SketchPicker } from 'react-color';
+import { DocumentView } from './nodes/DocumentView';
+import { Doc } from '../../fields/Doc';
+import { undoable } from '../util/UndoManager';
+import { NumCast, StrCast } from '../../fields/Types';
+import { emptyFunction } from '../../Utils';
 
 @observer
 export class ContextMenu extends ObservableReactComponent<{}> {
@@ -38,6 +44,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
     @observable _mouseX: number = -1;
     @observable _mouseY: number = -1;
     @observable _shouldDisplay: boolean = false;
+    @observable _displayColorPicker: boolean = false;
+    @observable _selectorWidth: number = 0;
 
     constructor(props: any) {
         super(props);
@@ -116,6 +124,10 @@ export class ContextMenu extends ObservableReactComponent<{}> {
         this._defaultItem = item;
     }
 
+    @action setColorPickerDisplay = (display: boolean) => {
+        this._displayColorPicker = display;
+    }
+
     static readonly buffer = 20;
     get pageX() {
         return this._pageX + this._width > window.innerWidth - ContextMenu.buffer ? window.innerWidth - ContextMenu.buffer - this._width : Math.max(0, this._pageX);
@@ -206,45 +218,79 @@ export class ContextMenu extends ObservableReactComponent<{}> {
 
     _searchRef = React.createRef<HTMLInputElement>(); // bcz: we shouldn't need this, since we set autoFocus on the <input> tag, but for some reason we do...
 
+    get colorPicker() {
+        const doc = DocumentView.Selected().lastElement().Document;
+        const layoutDoc = doc ? Doc.Layout(doc) : doc;
+
+        return (
+            <div className='contextMenu-borderMenu' style={{position: 'absolute', right: `${this._pageX}`, top: `${this._pageY}`}}>
+                <div className='top-bar'>
+                    <button className='close-menu' onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction,
+                        undoable(clickEv => {
+                            clickEv.stopPropagation();
+                            clickEv.preventDefault();
+                            this.setColorPickerDisplay(false);
+                        }, 'close border menu'))}>
+                        <FontAwesomeIcon icon='minus'/>
+                    </button>
+                </div>
+                <SketchPicker
+                onChange={undoable(
+                    action((col: ColorResult) => layoutDoc._layout_borderColor = col.hex),
+                    'set stroke color property'
+                )}
+                presetColors={[]}
+                color={StrCast(layoutDoc._layout_borderColor)}
+                />
+                <div className='bottom-box'>
+                    <input className='width-selector' type="range" min="1" max="100" value={this._selectorWidth} id="myRange" onChange={e => {layoutDoc._layout_borderWidth = e.target.value; this._selectorWidth = Number(e.target.value); console.log(layoutDoc._layout_borderWidth)}}/>
+                </div>
+            </div>
+        );
+    }
+
     render() {
         this.itemsNeedSearch && setTimeout(() => this._searchRef.current?.focus());
         return (
-            <div
-                className="contextMenu-cont"
-                ref={action((r: any) => {
-                    if (r) {
-                        this._width = DivWidth(r);
-                        this._height = DivHeight(r);
-                    }
-                    this._searchRef.current?.focus();
-                })}
-                style={{
-                    display: this._display ? '' : 'none',
-                    left: this.pageX,
-                    ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
-                    background: SnappingManager.userBackgroundColor,
-                    color: SnappingManager.userColor,
-                }}>
-                {!this.itemsNeedSearch ? null : (
-                    <span className="search-icon">
-                        <span className="icon-background">
-                            <FontAwesomeIcon icon="search" size="lg" />
+            <div>
+                {this._displayColorPicker ? this.colorPicker : null}
+                <div
+                    className="contextMenu-cont"
+                    ref={action((r: any) => {
+                        if (r) {
+                            this._width = DivWidth(r);
+                            this._height = DivHeight(r);
+                        }
+                        this._searchRef.current?.focus();
+                    })}
+                    style={{
+                        display: this._display ? '' : 'none',
+                        left: this.pageX,
+                        ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
+                        background: SnappingManager.userBackgroundColor,
+                        color: SnappingManager.userColor,
+                    }}>
+                    {!this.itemsNeedSearch ? null : (
+                        <span className="search-icon">
+                            <span className="icon-background">
+                                <FontAwesomeIcon icon="search" size="lg" />
+                            </span>
+                            <input
+                                ref={this._searchRef}
+                                style={{ color: 'black' }}
+                                className="contextMenu-item contextMenu-description search"
+                                type="text"
+                                placeholder="Filter Menu..."
+                                value={this._searchString}
+                                onKeyDown={this.onKeyDown}
+                                onChange={this.onChange}
+                                // eslint-disable-next-line jsx-a11y/no-autofocus
+                                autoFocus
+                            />
                         </span>
-                        <input
-                            ref={this._searchRef}
-                            style={{ color: 'black' }}
-                            className="contextMenu-item contextMenu-description search"
-                            type="text"
-                            placeholder="Filter Menu..."
-                            value={this._searchString}
-                            onKeyDown={this.onKeyDown}
-                            onChange={this.onChange}
-                            // eslint-disable-next-line jsx-a11y/no-autofocus
-                            autoFocus
-                        />
-                    </span>
-                )}
-                {this.menuItems}
+                    )}
+                    {this.menuItems}
+                </div>
             </div>
         );
     }
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index eb1030eec..782077fd6 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -5,8 +5,14 @@ import { action, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { SnappingManager } from '../util/SnappingManager';
-import { UndoManager } from '../util/UndoManager';
+import { UndoManager, undoable } from '../util/UndoManager';
 import { ObservableReactComponent } from './ObservableReactComponent';
+import { ColorPicker, Type } from 'browndash-components';
+import { DocumentView } from './nodes/DocumentView';
+import { Doc } from '../../fields/Doc';
+import { StrCast } from '../../fields/Types';
+import { ColorResult, SketchPicker } from 'react-color';
+import color from 'color';
 
 export interface OriginalMenuProps {
     description: string;
@@ -14,6 +20,7 @@ export interface OriginalMenuProps {
     undoable?: boolean;
     icon: IconProp | JSX.Element; // maybe should be optional (icon?)
     closeMenu?: () => void;
+    colorPicker?: boolean;
 }
 
 export interface SubmenuProps {
@@ -24,6 +31,7 @@ export interface SubmenuProps {
     addDivider?: boolean;
     icon: IconProp; // maybe should be optional (icon?)
     closeMenu?: () => void;
+    colorPicker?: boolean;
 }
 
 export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
@@ -101,7 +109,7 @@ export class ContextMenuItem extends ObservableReactComponent<ContextMenuProps &
     render() {
         if ('event' in this._props) {
             return (
-                <div className={'contextMenu-item' + (this._props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent}>
+                <div className={'contextMenu-item' + (this._props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent} >
                     {this._props.icon ? <span className="contextMenu-item-icon-background">{this.isJSXElement(this._props.icon) ? this._props.icon : <FontAwesomeIcon icon={this._props.icon} size="sm" />}</span> : null}
                     <div className="contextMenu-description">{this._props.description.replace(':', '')}</div>
                     <div
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index d4467e536..9c350afc0 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -88,6 +88,7 @@ export class MainView extends ObservableReactComponent<{}> {
     public static Live: boolean = false;
     private _docBtnRef = React.createRef<HTMLDivElement>();
 
+    @observable private _keepContextMenuOpen: boolean = false;
     @observable private _windowWidth: number = 0;
     @observable private _windowHeight: number = 0;
     @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row)
@@ -590,7 +591,10 @@ export class MainView extends ObservableReactComponent<{}> {
                 if (typeof targets[i].className === 'object') targClass = targets[i + 1].className.toString();
                 else break;
             }
-            !targClass.includes('contextMenu') && ContextMenu.Instance.closeMenu();
+            if (!targClass.includes('contextMenu')) {
+                console.log(targClass)
+                ContextMenu.Instance.closeMenu();
+            }
             !['timeline-menu-desc', 'timeline-menu-item', 'timeline-menu-input'].includes(targClass) && TimelineMenu.Instance.closeMenu();
         }
     });
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 024db82a4..775e15772 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -847,6 +847,12 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     set colorFil(value) { this.selectedDoc && (this.selectedDoc[DocData].fillColor = value || undefined); } // prettier-ignore
     @computed get colorStk() { return StrCast(this.selectedDoc?.[DocData].color); } // prettier-ignore
     set colorStk(value) { this.selectedDoc && (this.selectedDoc[DocData].color = value || undefined); } // prettier-ignore
+    @computed get borderColor() { 
+        const doc = this.selectedDoc;
+        const layoutDoc = doc ? Doc.Layout(doc) : doc;
+        return StrCast(layoutDoc);
+    }
+    set borderColor(value) { this.selectedDoc && (this.selectedDoc[DocData].color = value || undefined); } // prettier-ignore
 
     colorButton(value: string, type: string, setter: () => void) {
         return (
@@ -898,6 +904,10 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
         return this.colorPicker(this.colorStk, (color: string) => { this.colorStk = color; }); // prettier-ignore
     }
 
+    @computed get borderColorPicker() {
+        return this.colorPicker(this.colorStk, (color: string) => { this.colorStk = color; }); // prettier-ignore
+    }
+ 
     @computed get strokeAndFill() {
         return (
             <div>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 8c100f238..3a48ec957 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -55,14 +55,25 @@ export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: nu
     return style;
 }
 
-export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
-    return `M ${pw * 0.5} ${ph * inset}   C ${pw * 0.6} ${ph * inset} ${pw * (1 - 2 * inset)} 0 ${pw * (1 - inset)} ${ph * inset}   C ${pw} ${ph * (2 * inset)} ${pw * (1 - inset)} ${ph * 0.25} ${pw * (1 - inset)} ${ph * 0.3}   C ${
-        pw * (1 - inset)
-    } ${ph * 0.4} ${pw} ${ph * (1 - 2 * inset)} ${pw * (1 - inset)} ${ph * (1 - inset)}  C ${pw * (1 - 2 * inset)} ${ph} ${pw * 0.6} ${ph * (1 - inset)} ${pw * 0.5} ${ph * (1 - inset)}   C ${pw * 0.3} ${ph * (1 - inset)} ${pw * (2 * inset)} ${ph} ${
-        pw * inset
-    } ${ph * (1 - inset)}  C 0 ${ph * (1 - 2 * inset)} ${pw * inset} ${ph * 0.8} ${pw * inset} ${ph * 0.75}   C ${pw * inset} ${ph * 0.7} 0 ${ph * (2 * inset)} ${pw * inset} ${ph * inset}   C ${pw * (2 * inset)} 0 ${pw * 0.25} ${ph * inset} ${
-        pw * 0.5
-    } ${ph * inset}`;
+export function wavyBorderPath(doc: Doc, pw: number, ph: number, inset: number = 0) {
+    const layoutDoc = doc ? Doc.Layout(doc) : doc;
+    const width = pw * inset;
+    const height = ph * inset;
+
+    const radius = Math.min(Number(StrCast(layoutDoc._layout_borderRounding).replace('px', '')), (pw - 2 * width) / 2, (ph - 2 * height) / 2);
+
+    return `
+        M ${width + radius} ${height}
+        L ${pw - width - radius} ${height}
+        A ${radius} ${radius} 0 0 1 ${pw - width} ${height + radius}
+        L ${pw - width} ${ph - height - radius}
+        A ${radius} ${radius} 0 0 1 ${pw - width - radius} ${ph - height}
+        L ${width + radius} ${ph - height}
+        A ${radius} ${radius} 0 0 1 ${width} ${ph - height - radius}
+        L ${width} ${height + radius}
+        A ${radius} ${radius} 0 0 1 ${width + radius} ${height}
+        Z
+    `;
 }
 
 let _filterOpener: () => void;
@@ -189,10 +200,12 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             const rounding = StrCast(doc?.[fieldKey + 'borderRounding'], StrCast(doc?.layout_borderRounding, doc?._type_collection === CollectionViewType.Pile ? '50%' : ''));
             return (doc?.[StrCast(doc?.layout_fieldKey)] instanceof Doc || doc?.isTemplateDoc) ? StrCast(doc._layout_borderRounding,rounding) : rounding;
         }
+        // Doc.IsComicStyle(doc) &&
+        //         renderDepth &&
+        //         !doc?.layout_isSvg && 
+        //case StyleProp.
         case StyleProp.BorderPath: {
-            const borderPath = Doc.IsComicStyle(doc) &&
-                renderDepth &&
-                !doc?.layout_isSvg && { path: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0), fill: wavyBorderPath(PanelWidth?.() || 0, PanelHeight?.() || 0, 0.08), width: 3 };
+            const borderPath = doc?.type === 'image' && { path: wavyBorderPath(doc, NumCast(doc._width), NumCast(doc._height), NumCast(layoutDoc?._layout_borderWidth)/2), fill: wavyBorderPath(doc, NumCast(doc._width), NumCast(doc._height), 0.08), width: 3 };
                 return !borderPath
                     ? null
                     : {
@@ -200,7 +213,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         jsx: (
                             <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
                                 <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${PanelWidth?.()} ${PanelHeight?.()}`}>
-                                    <path d={borderPath.path} style={{ stroke: 'black', fill: 'transparent', strokeWidth: borderPath.width }} />
+                                    <path d={borderPath.path} style={{ stroke: StrCast(layoutDoc?._layout_borderColor), fill: 'transparent', strokeWidth: `${StrCast(layoutDoc?._layout_borderWidth)}px` }} />
                                 </svg>
                             </div>
                         ),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8edb37acc..fe6d529ba 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -634,6 +634,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
                     moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => { this.Document._chromeHidden = !this.Document._chromeHidden; }, icon: 'project-diagram' }); // prettier-ignore
                     moreItems.push({ description: 'Copy ID', event: () => ClientUtils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
+                    moreItems.push({ description: 'Add border', event: () => ContextMenu.Instance.setColorPickerDisplay(true), icon: 'square', colorPicker: true });
                 }
             }
 
@@ -944,22 +945,28 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
         return (
             // eslint-disable-next-line jsx-a11y/click-events-have-key-events
+            // <div style={{position: 'relative'}}>
+            //     <div className='docView-border-top' style={{width: `${this.layoutDoc._width}px`, height: '100px', backgroundColor: 'black'}}/>
+            //     <div className='docView-border-right' style={{position: 'absolute', right: '-10px', width: '10px', height: `${this.layoutDoc._height}px`, backgroundColor: 'black'}}/>
+            //     <div className='docView-border-left' style={{position: 'absolute', left: '-10px', width: '10px', height: `${this.layoutDoc._height}px`, backgroundColor: 'black'}}/>
+            //     <div className='docView-border-bottom' style={{position: 'absolute', bottom: '-10px', height: '100px', width: `${this.layoutDoc._width}px`, backgroundColor: 'black'}}/>
             <div
-                className={`${DocumentView.ROOT_DIV} docView-hack`}
-                ref={this._mainCont}
-                onContextMenu={this.onContextMenu}
-                onPointerDown={this.onPointerDown}
-                onClick={SnappingManager.ExploreMode ? this.onBrowseClick : this.onClick}
-                onPointerEnter={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
-                onPointerOver={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
-                onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
-                style={{
-                    borderRadius: this.borderRounding,
-                    pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
-                }}>
-                {this._componentView?.isUnstyledView?.() || this.Document.type === DocumentType.CONFIG ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
-                {borderPath?.jsx}
-            </div>
+                    className={`${DocumentView.ROOT_DIV} docView-hack`}
+                    ref={this._mainCont}
+                    onContextMenu={this.onContextMenu}
+                    onPointerDown={this.onPointerDown}
+                    onClick={SnappingManager.ExploreMode ? this.onBrowseClick : this.onClick}
+                    onPointerEnter={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
+                    onPointerOver={() => (!SnappingManager.IsDragging || SnappingManager.CanEmbed) && Doc.BrushDoc(this.Document)}
+                    onPointerLeave={e => !isParentOf(this._contentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.Document)}
+                    style={{
+                        borderRadius: this.borderRounding,
+                        pointerEvents: this._pointerEvents === 'visiblePainted' ? 'none' : this._pointerEvents, // visible painted means that the underlying doc contents are irregular and will process their own pointer events (otherwise, the contents are expected to fill the entire doc view box so we can handle pointer events here)
+                    }}>
+                    {this._componentView?.isUnstyledView?.() || this.Document.type === DocumentType.CONFIG ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
+                    {borderPath?.jsx}
+                </div>
+            // </div>
         );
     }
 
-- 
cgit v1.2.3-70-g09d2


From 143921c3ecee8dc0b7a91dfe725a3734d63a575e Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 25 Aug 2024 16:30:24 -0400
Subject: border

---
 src/client/views/ContextMenu.tsx        |  3 ++-
 src/client/views/StyleProvider.tsx      | 12 +++++-------
 src/client/views/nodes/DocumentView.tsx |  5 ++++-
 3 files changed, 11 insertions(+), 9 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 9472fb95c..dca00c2bf 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -223,7 +223,8 @@ export class ContextMenu extends ObservableReactComponent<{}> {
         const layoutDoc = doc ? Doc.Layout(doc) : doc;
 
         return (
-            <div className='contextMenu-borderMenu' style={{position: 'absolute', right: `${this._pageX}`, top: `${this._pageY}`}}>
+            <div className='contextMenu-borderMenu' style={{position: 'absolute', left: this.pageX,
+            ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),}}>
                 <div className='top-bar'>
                     <button className='close-menu' onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction,
                         undoable(clickEv => {
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 6e3eed42d..b7dbcf7a6 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -55,8 +55,7 @@ export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: nu
     return style;
 }
 
-export function wavyBorderPath(doc: Doc, pw: number, ph: number, rad: number = 0, inset: number = 0) {
-    const layoutDoc = doc ? Doc.Layout(doc) : doc;
+export function border(doc: Doc, pw: number, ph: number, rad: number = 0, inset: number = 0) {
     const width = pw * inset;
     const height = ph * inset;
 
@@ -210,18 +209,17 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
             const ratio = borderWidth / docWidth;
             const borderRadius = Number(StrCast(layoutDoc?._layout_borderRounding).replace('px', ''));
             const radiusRatio = borderRadius / docWidth;
-            const radius = radiusRatio * borderWidth;
-            console.log(borderWidth, docWidth, ratio)
+            const radius = radiusRatio * ((2 * borderWidth) + docWidth);
 
-            const borderPath = doc?.type === 'image' && { path: wavyBorderPath(doc, NumCast(doc._width), NumCast(doc._height), radius, -ratio/2 ?? 0), fill: wavyBorderPath(doc, NumCast(doc._width), NumCast(doc._height), 0.08), width: 3 };
+            const borderPath = doc && border(doc, NumCast(doc._width), NumCast(doc._height), radius, -ratio/2 ?? 0);
                 return !borderPath
                     ? null
                     : {
-                        clipPath: `path('${borderPath.path}')`,
+                        clipPath: `path('${borderPath}')`,
                         jsx: (
                             <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
                                 <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${PanelWidth?.()} ${PanelHeight?.()}`}>
-                                    <path d={borderPath.path} style={{ stroke: StrCast(layoutDoc?._layout_borderColor), fill: 'transparent', strokeWidth: `${StrCast(layoutDoc?._layout_borderWidth)}px` }} />
+                                    <path d={borderPath} style={{ stroke: StrCast(layoutDoc?._layout_borderColor), fill: 'transparent', strokeWidth: `${StrCast(layoutDoc?._layout_borderWidth)}px` }} />
                                 </svg>
                             </div>
                         ),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fe6d529ba..3c898dea2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -547,6 +547,10 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
             return;
         }
 
+        if (!Doc.IsSystem(this.Document)) {
+            cm.addItem({ description: 'Add border', event: () => ContextMenu.Instance.setColorPickerDisplay(true), icon: 'square', colorPicker: true });
+        }
+
         const customScripts = Cast(this.Document.contextMenuScripts, listSpec(ScriptField), []);
         StrListCast(this.Document.contextMenuLabels).forEach((label, i) =>
             cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' })
@@ -634,7 +638,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     moreItems.push({ description: 'Make View of Metadata Field', event: () => Doc.MakeMetadataFieldTemplate(this.Document, this._props.TemplateDataDocument), icon: 'concierge-bell' });
                     moreItems.push({ description: `${this.Document._chromeHidden ? 'Show' : 'Hide'} Chrome`, event: () => { this.Document._chromeHidden = !this.Document._chromeHidden; }, icon: 'project-diagram' }); // prettier-ignore
                     moreItems.push({ description: 'Copy ID', event: () => ClientUtils.CopyText(Doc.globalServerPath(this.Document)), icon: 'fingerprint' });
-                    moreItems.push({ description: 'Add border', event: () => ContextMenu.Instance.setColorPickerDisplay(true), icon: 'square', colorPicker: true });
                 }
             }
 
-- 
cgit v1.2.3-70-g09d2


From f00a5326fa331859db131bb1d8988db93602830b Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Tue, 27 Aug 2024 19:16:28 -0400
Subject: maxmatching algorithm

---
 src/client/documents/Documents.ts                  |   5 +-
 src/client/views/ContextMenu.tsx                   |   9 +-
 src/client/views/StyleProvider.tsx                 |   4 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 190 ++++++++++++++-------
 src/client/views/nodes/DocumentView.tsx            |  14 +-
 5 files changed, 139 insertions(+), 83 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index aecc79189..a4ba2978c 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -237,6 +237,9 @@ export class DocumentOptions {
     dataViz?: string;
     dataViz_savedTemplates?: LISTt;
 
+    borderWidth?: NUMt = new NumInfo('Width of user-added border', false);
+    borderColor?: STRt = new StrInfo('Color of user-added border', false);
+
     layout?: string | Doc; // default layout string or template document
     layout_isSvg?: BOOLt = new BoolInfo('whether document decorations and other selections should handle pointerEvents for svg content or use doc bounding box');
     layout_keyValue?: STRt = new StrInfo('layout definition for showing keyValue view of document', false);
@@ -256,8 +259,6 @@ export class DocumentOptions {
     layout_hideContextMenu?: BOOLt = new BoolInfo('whether the context menu can be shown');
     layout_borderRounding?: string;
     _layout_borderRounding?: STRt = new StrInfo('amount of rounding to document view corners');
-    _layout_borderWidth?: NUMt = new NumInfo('Hey now', false);
-    _layout_borderColor?: STRt = new StrInfo('No no', false);
     _layout_modificationDate?: DATEt = new DateInfo('last modification date of doc layout', false);
     _layout_nativeDimEditable?: BOOLt = new BoolInfo('native dimensions can be modified using document decoration reizers', false);
     _layout_reflowVertical?: BOOLt = new BoolInfo('permit vertical resizing with content "reflow"');
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index b80d97e81..f5654446d 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -2,7 +2,7 @@
 /* eslint-disable react/jsx-props-no-spreading */
 /* eslint-disable default-param-last */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { DivHeight, DivWidth, returnFalse, setupMoveUpEvents } from '../../ClientUtils';
@@ -222,7 +222,6 @@ export class ContextMenu extends ObservableReactComponent<{}> {
 
     get colorPicker() {
         const doc = DocumentView.Selected().lastElement().Document;
-        const layoutDoc = doc ? Doc.Layout(doc) : doc;
 
         return (
             <div className='contextMenu-borderMenu' style={{position: 'absolute', left: this.pageX,
@@ -239,15 +238,15 @@ export class ContextMenu extends ObservableReactComponent<{}> {
                 </div>
                 <SketchPicker
                 onChange={undoable(
-                    action((col: ColorResult) => layoutDoc._layout_borderColor = col.hex),
+                    action((col: ColorResult) => doc.borderColor = col.hex),
                     'set stroke color property'
                 )}
                 presetColors={[]}
-                color={StrCast(layoutDoc._layout_borderColor)}
+                color={StrCast(doc.borderColor)}
                 />
                 <div className='bottom-box'>
                     <input className='max-min-selector' defaultValue={this._widthMinMax.min} onChange={e => this._widthMinMax.max = e.target.value}/>
-                    <input className='width-selector' type="range" min={this._widthMinMax.min} max={this._widthMinMax.max} value={this._selectorWidth} id="myRange" onChange={e => {layoutDoc._layout_borderWidth = e.target.value; this._selectorWidth = Number(e.target.value); console.log(layoutDoc._layout_borderWidth)}}/>
+                    <input className='width-selector' type="range" min={this._widthMinMax.min} max={this._widthMinMax.max} value={this._selectorWidth} id="myRange" onChange={e => runInAction(() => {doc.borderWidth = e.target.value; this._selectorWidth = Number(e.target.value)})}/>
                     <input className='max-min-selector' defaultValue={this._widthMinMax.max} onChange={e => this._widthMinMax.max = e.target.value}/>
                 </div>
             </div>
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index b7dbcf7a6..3ecb101f8 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -205,7 +205,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         //case StyleProp.
         case StyleProp.BorderPath: {
             const docWidth = Number(doc?._width);
-            const borderWidth = Number(StrCast(layoutDoc?._layout_borderWidth));
+            const borderWidth = Number(StrCast(doc?.borderWidth));
             const ratio = borderWidth / docWidth;
             const borderRadius = Number(StrCast(layoutDoc?._layout_borderRounding).replace('px', ''));
             const radiusRatio = borderRadius / docWidth;
@@ -219,7 +219,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                         jsx: (
                             <div key="border2" className="documentView-customBorder" style={{ pointerEvents: 'none' }}>
                                 <svg style={{ overflow: 'visible', height: '100%' }} viewBox={`0 0 ${PanelWidth?.()} ${PanelHeight?.()}`}>
-                                    <path d={borderPath} style={{ stroke: StrCast(layoutDoc?._layout_borderColor), fill: 'transparent', strokeWidth: `${StrCast(layoutDoc?._layout_borderWidth)}px` }} />
+                                    <path d={borderPath} style={{ stroke: StrCast(doc?.borderColor), fill: 'transparent', strokeWidth: `${StrCast(doc?.borderWidth)}px` }} />
                                 </svg>
                             </div>
                         ),
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 7df445b88..ffc4dd0b1 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -419,6 +419,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
         // mainCollection.addDocument(doc);
         //this._dataViz?.getRandomSample();
+
+        console.log(this.maxMatches(3, Array.from([[2], [1], [2]])));
     }
 
     get templatesPreviewContents(){
@@ -810,6 +812,41 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         this.forceUpdate();
     }
 
+    findMatches = () => {
+
+    }
+
+    maxMatches = (fieldsCt: number, matches: number[][]) => {
+        const used: boolean[] = Array(fieldsCt).fill(false);
+        const mt: number[] = Array(fieldsCt).fill(-1);
+
+        const augmentingPath = (v: number): boolean => {
+            if (used[v]) return false;
+            used[v] = true;
+            for (const to of matches[v]) {
+                if (mt[to] === -1 || augmentingPath(mt[to])) {
+                    mt[to] = v;
+                    return true;
+                }
+            }
+            return false;
+        }
+    
+        for (let v = 0; v < fieldsCt; ++v) {
+            console.log(v)
+            used.fill(false);
+            augmentingPath(v);
+        }
+
+        let numMatches: number = 0;
+
+        for (let i = 0; i < fieldsCt; ++i) {
+            if (mt[i] !== -1) ++numMatches;
+        }
+
+        return numMatches;
+    }
+
     get dashboardContents(){
         return (
         <div className='docCreatorMenu-dashboard-view'>
@@ -985,26 +1022,31 @@ enum FieldSize {
     HUGE = 'huge'
 }
 
+type Field = {
+    tl: [number, number], 
+    br: [number, number], 
+    types: FieldType[], 
+    sizes?: FieldSize[], 
+    opts: FieldOpts;
+};
+
 export interface TemplateDocInfos {
     height: number;
     width: number;
-    fields: {tl: [number, number], br: [number, number], types: FieldType[], sizes?: FieldSize[]}[];
-}
-
-export interface TemplateDocField {
-    coordinates: {tl: [number, number], br: [number, number]};
-    getDoc: (parentWidth: number, parentHeight: number, title: string, content: string) => Doc;
+    fields: Field[];
 }
 
 export interface FieldOpts {
     backgroundColor?: string;
-    roundedCorners?: boolean;
-    vertCenteredText?: boolean;
-    horizCenteredText?: boolean;
-    transparency?: number;
+    color?: string;
+    cornerRounding?: number;
+    borderWidth?: number;
+    borderColor?: string;
+    contentXCentering?: 'left' | 'center' | 'right';
+    contentYCentering?: 'top' | 'center' | 'bottom';
+    opacity?: number;
     rotation?: number;
     //animation?: boolean;
-    fontColor?: string;
     fontBold?: boolean;
     fontTransform?: 'toUpper' | 'toLower';
 }
@@ -1023,7 +1065,9 @@ export class FieldFuncs {
     public static TextField = (coords: {tl: [number, number], br: [number, number]}, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => {
         const {width, height, coord} = FieldFuncs.getDimensions(coords, parentWidth, parentHeight);
 
-        const doc = Docs.Create.TextDocument(content, {   
+        const bool = true;
+
+        const docWithBasicOpts = (Docs.Create.TextDocument)(content, {   
             _height: height, 
             _width: width, 
             title: title, 
@@ -1031,11 +1075,15 @@ export class FieldFuncs {
             y: coord.y, 
             _text_fontSize: `${height/2}` ,
             backgroundColor: opts.backgroundColor ?? '',
-            
-        
-        })
+            color: opts.color,
+            _layout_borderRounding: `${opts.cornerRounding}`,
+            borderWidth: opts.borderWidth,
+            borderColor: opts.borderColor,
+            opacity: opts.opacity,
+            _layout_centered: opts.contentXCentering === 'center' ? true : false,
+        });
 
-        return doc;
+        return docWithBasicOpts;
     }
 
     public static ImageField = (coords: {tl: [number, number], br: [number, number]}, parentWidth: number, parentHeight: number, title: string, content: string) => {
@@ -1071,70 +1119,82 @@ export class TemplateLayouts {
             tl: [-.6, -.9], 
             br: [.6, -.8], 
             types: [FieldType.TEXT],
-            sizes: [FieldSize.TINY]
+            sizes: [FieldSize.TINY],
+            opts: {
+
+            }
         }, {
             tl: [-.9, -.7], 
             br: [.9, .2], 
             types: [FieldType.TEXT, FieldType.VISUAL],
-            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE]
+            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+            opts: {
+
+            }
         }, {
             tl: [-.6, .3], 
             br: [.6, .4], 
             types: [FieldType.TEXT],
-            sizes: [FieldSize.TINY]
+            sizes: [FieldSize.TINY],
+            opts: {
+
+            }
         }, {
             tl: [-.9, .5], 
             br: [.9, .9], 
             types: [FieldType.TEXT, FieldType.VISUAL],
-            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE]
-        }]
-    };
+            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+            opts: {
 
-    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]
+//         }]
+//     };
 
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3c898dea2..5af6e65de 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -948,12 +948,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
 
         return (
             // eslint-disable-next-line jsx-a11y/click-events-have-key-events
-            // <div style={{position: 'relative'}}>
-            //     <div className='docView-border-top' style={{width: `${this.layoutDoc._width}px`, height: '100px', backgroundColor: 'black'}}/>
-            //     <div className='docView-border-right' style={{position: 'absolute', right: '-10px', width: '10px', height: `${this.layoutDoc._height}px`, backgroundColor: 'black'}}/>
-            //     <div className='docView-border-left' style={{position: 'absolute', left: '-10px', width: '10px', height: `${this.layoutDoc._height}px`, backgroundColor: 'black'}}/>
-            //     <div className='docView-border-bottom' style={{position: 'absolute', bottom: '-10px', height: '100px', width: `${this.layoutDoc._width}px`, backgroundColor: 'black'}}/>
-            <div
+                <div
                     className={`${DocumentView.ROOT_DIV} docView-hack`}
                     ref={this._mainCont}
                     onContextMenu={this.onContextMenu}
@@ -969,7 +964,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
                     {this._componentView?.isUnstyledView?.() || this.Document.type === DocumentType.CONFIG ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation], this.Document)}
                     {borderPath?.jsx}
                 </div>
-            // </div>
         );
     }
 
@@ -1138,7 +1132,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
     }
     @computed private get panelWidth() {
-        return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
+        return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling: this._props.PanelWidth();
     }
     @computed private get panelHeight() {
         if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) {
@@ -1449,6 +1443,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
 
     render() {
         TraceMobx();
+        const borderWidth = 50/*Number(StrCast(this.layoutDoc.layout_borderWidth).replace('px', ''))*/;
+        console.log(this._props.PanelWidth(), borderWidth)
         const xshift = Math.abs(this.Xshift) <= 0.001 ? this._props.PanelWidth() : undefined;
         const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
 
@@ -1468,7 +1464,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
                         style={{
                             transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
                             width: xshift ?? `${this._props.PanelWidth() - this.Xshift * 2}px`,
-                            height: this._props.forceAutoHeight ? undefined : yshift ?? (this.layout_fitWidth ? `${this.panelHeight}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this._props.PanelWidth()}px`),
+                            height: this._props.forceAutoHeight ? '1000px' : yshift ?? (this.layout_fitWidth ? `${this.panelHeight + 500}px` : `${(this.effectiveNativeHeight / this.effectiveNativeWidth) * this._props.PanelWidth() + 500}px`),
                         }}>
                         <DocumentViewInternal
                             {...this._props}
-- 
cgit v1.2.3-70-g09d2


From 85c0b1105dbdd4037872ecfc6b921551e1939eb8 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Thu, 29 Aug 2024 04:06:15 -0400
Subject: Dataviz crash hopefully fixed; prototype func for generating
 templates; radius NaN bug handled

---
 src/client/documents/Documents.ts                  |   3 +-
 src/client/views/StyleProvider.tsx                 |   5 +
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  18 ++-
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 149 +++++++++++++++------
 .../views/nodes/DataVizBox/components/TableBox.tsx |  13 +-
 src/client/views/nodes/DocumentView.tsx            |   1 -
 6 files changed, 137 insertions(+), 52 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a4ba2978c..723f851c6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -178,6 +178,7 @@ export class DocumentOptions {
     map_bearing?: NUMt = new NumInfo('bearing of a map view', false);
     map_style?: STRt = new StrInfo('mapbox style for a map view', false);
     identifier?: STRt = new StrInfo('documentIcon displayed for each doc as "d[x]"', false);
+    _rotation?: NUMt = new NumInfo('Amount of rotation on a document in degrees', false);
 
     date_range?: STRt = new StrInfo('date range for calendar', false);
 
@@ -237,7 +238,7 @@ export class DocumentOptions {
     dataViz?: string;
     dataViz_savedTemplates?: LISTt;
 
-    borderWidth?: NUMt = new NumInfo('Width of user-added border', false);
+    borderWidth?: STRt = new StrInfo('Width of user-added border', false);
     borderColor?: STRt = new StrInfo('Color of user-added border', false);
 
     layout?: string | Doc; // default layout string or template document
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 3ecb101f8..bb0883cc0 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -56,11 +56,14 @@ export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: nu
 }
 
 export function border(doc: Doc, pw: number, ph: number, rad: number = 0, inset: number = 0) {
+    if (!rad) rad = 0;
     const width = pw * inset;
     const height = ph * inset;
 
     const radius = Math.min(rad, (pw - 2 * width) / 2, (ph - 2 * height) / 2);
 
+    console.log(pw, ph, rad, inset)
+
     return `
         M ${width + radius} ${height}
         L ${pw - width - radius} ${height}
@@ -206,8 +209,10 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
         case StyleProp.BorderPath: {
             const docWidth = Number(doc?._width);
             const borderWidth = Number(StrCast(doc?.borderWidth));
+            //console.log(borderWidth);
             const ratio = borderWidth / docWidth;
             const borderRadius = Number(StrCast(layoutDoc?._layout_borderRounding).replace('px', ''));
+            console.log(StrCast(layoutDoc?._layout_borderRounding), borderRadius, typeof borderRadius)
             const radiusRatio = borderRadius / docWidth;
             const radius = radiusRatio * ((2 * borderWidth) + docWidth);
 
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 8e9680e38..82df141fa 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -53,6 +53,7 @@ export enum DataVizView {
 
 @observer
 export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
+    private _urlError: boolean = false;
     private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
     private _marqueeref = React.createRef<MarqueeAnnotator>();
     private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
@@ -116,8 +117,17 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     // all CSV records in the dataset (that aren't an empty row)
     @computed.struct get records() {
-        const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
-        return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
+        try {
+            const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+            this._urlError = false;
+            return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
+        } catch (e){
+            this._urlError = true;
+            const data: { [key: string]: string; }[] = [
+                { error: "Data not found"},
+              ];          
+            return data;
+        }
     }
 
     // currently chosen visualization type: line, pie, histogram, table
@@ -133,8 +143,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
     selectAxes = (axes: string[]) => {
         this.layoutDoc._dataViz_axes = new List<string>(axes);
-        //axes.forEach(axis => console.log(axis)) 
-        //DocCreatorMenu.Instance.generateTemplates('');
     };
     @computed.struct get titleCol() {
         return StrCast(this.layoutDoc._dataViz_titleCol);
@@ -315,7 +323,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
+        if (!this._urlError) { if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData() };
         this._disposers.datavis = reaction(
             () => {
                 if (this.layoutDoc.dataViz_schemaLive === undefined) this.layoutDoc.dataViz_schemaLive = true;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 43cf57167..27ffc63ef 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -404,21 +404,21 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
 
     testTemplate = () => { 
-        // const temp = TemplateLayouts.TwoFieldPlusCarousel;
-        // const title = new TemplateDocTextField({tl: temp.fields[0].tl, br: temp.fields[0].br});
-        // const title1 = new TemplateDocTextField({tl: temp.fields[0].tl, br: temp.fields[0].br});
-        // const title2 = new TemplateDocTextField({tl: temp.fields[0].tl, br: temp.fields[0].br});
-        // const title3 = new TemplateDocTextField({tl: temp.fields[0].tl, br: temp.fields[0].br});
-        // const focus = new TemplateDocCarouselField({tl: temp.fields[1].tl, br: temp.fields[1].br}, temp.width, temp.height, '', [title1, title2, title3].map(field => field.getDoc(temp.width, temp.height, 'hey now', '')));
-        // const caption = new TemplateDocTextField({tl: temp.fields[2].tl, br: temp.fields[2].br});
-        // let fields = [title, caption].map(field => field.getDoc(temp.width, temp.height, 'hey now', ''));
-        // fields = fields.concat(focus.getDoc());
-        // console.log(temp.height, temp.width)
-        // const doc = Docs.Create.StackingDocument(fields, { _height: temp.height, _width: temp.width, title: 'hey', x: 400, y: 400 });
-
-        // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-        // mainCollection.addDocument(doc);
-        //this._dataViz?.getRandomSample();
+        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});
+        const caption: Doc = FieldFuncs.TextField({tl: temp.fields[2].tl, br: temp.fields[2].br}, temp.height, temp.width, 'title', 'Caption', {backgroundColor: 'transparent'});
+        const desc: Doc = FieldFuncs.TextField({tl: temp.fields[3].tl, br: temp.fields[3].br}, temp.height, temp.width, 'title', '', {backgroundColor: 'lightblue', borderColor: '#159fe4', borderWidth: '10', cornerRounding: 10});
+
+        const doc = Docs.Create.FreeformDocument([title, image, caption, desc], { _height: temp.height, _width: temp.width, title: 'hey', x: 400, y: 400 });
+
+        const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+        mainCollection.addDocument(doc);
+
+        // setTimeout(() => image.borderWidth = '10', 1000);
+        // setTimeout(() => desc.borderWidth = '10', 1000);
+
+        // this._dataViz?.getRandomSample();
 
 
         // public static FourField001: TemplateDocInfos = {
@@ -450,12 +450,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         //         opts: {
 
 
-        const field1: Col = {title: 'hey', type: TemplateFieldType.TEXT, size: FieldSize.HUGE, desc: ''}
-        const field2: Col = {title: 'heyo', type: TemplateFieldType.TEXT, size: FieldSize.TINY, desc: ''}
-        const field3: Col = {title: 'heya', type: TemplateFieldType.VISUAL, size: FieldSize.LARGE, desc: ''}
-        const field4: Col = {title: 'heyy', type: TemplateFieldType.TEXT, size: FieldSize.MEDIUM, desc: ''}
+        // const field1: Col = {title: 'hey', type: TemplateFieldType.TEXT, size: FieldSize.HUGE, desc: ''}
+        // const field2: Col = {title: 'heyo', type: TemplateFieldType.TEXT, size: FieldSize.TINY, desc: ''}
+        // const field3: Col = {title: 'heya', type: TemplateFieldType.VISUAL, size: FieldSize.LARGE, desc: ''}
+        // const field4: Col = {title: 'heyy', type: TemplateFieldType.TEXT, size: FieldSize.MEDIUM, desc: ''}
 
-        console.log(this.findValidTemplates([field1, field2, field3, field4], [TemplateLayouts.FourField001]));
+        // console.log(this.findValidTemplates([field1, field2, field3, field4], [TemplateLayouts.FourField001]));
     }
 
     get templatesPreviewContents(){
@@ -609,9 +609,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         }
     }
 
-    doc = () => {
-        return Docs.Create.FreeformDocument([], { _height: 200, _width: 200, title: 'title'});
-    }
+    // doc = () => {
+    //     return Docs.Create.FreeformDocument([], { _height: 200, _width: 200, title: 'title'});
+    // }
 
     screenToLocalTransform = () =>
     this._props
@@ -802,7 +802,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     }
 
     @action addField = () => {
-        const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.TEXT, desc: '', size: FieldSize.MEDIUM}])
+        const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.UNSET, desc: '', size: FieldSize.MEDIUM}])
         this._columns = newFields;
     }
 
@@ -822,9 +822,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             if (this._columns.length === 1) {
                 this._columns = []  
             } else {
-                console.log(this._columns)
                 this._columns.splice(this._columns.indexOf(toRemove[0]), 1);
-                console.log(this._columns)
             }
         }   
     }
@@ -899,7 +897,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         templates.forEach(template => {
             const numFields =  template.fields.length;
             const matches = this.matchesForTemplate(template, cols);
-            console.log(matches)
             if (this.maxMatches(numFields, matches) === numFields) {
                 validTemplates.push(template.title);
             }
@@ -908,7 +905,32 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         return validTemplates;
     }
 
+    createEmptyTemplate = (template: TemplateDocInfos, assignments: {[field: number]: Col}) => {
+        const fields: Doc[] = [];
+
+        Object.entries(assignments).forEach(([f, col]) => {
+            const field: Field = template.fields[Number(f)];
+            const doc = (col.type === TemplateFieldType.VISUAL ? FieldFuncs.ImageField : FieldFuncs.TextField)({
+                tl: field.tl, 
+                br: field.br }, 
+                template.height, 
+                template.width, 
+                col.title, 
+                '', 
+                field.opts
+            );
+
+            fields.push(doc);
+        });
+
+        const renderedTemplate = Docs.Create.FreeformDocument(fields, { _height: template.height, _width: template.width, title: template.title, x: 400, y: 400 });
+
+        const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+        mainCollection.addDocument(renderedTemplate);
+    }
+
     get dashboardContents(){
+
         return (
         <div className='docCreatorMenu-dashboard-view'>
             <div className='topbar'>
@@ -919,10 +941,10 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             {this.fieldsInfos.map((field, index) => 
                 <div className='field-panel' id={`${index}`}>
                     <div className='properties-wrapper'>
-                        <input className='field-property-container' id={`${Math.random() * 100}`} defaultValue={field.title} placeholder={'Enter title'} style={{width: field.title === '' ? '30%' : ''}}/>
+                        <input className='field-property-container' id={`${Math.random() * 100}`} defaultValue={field.title} placeholder={'Enter title'}/>
                         <div className='field-type-selection-container'>
-                            {field.type ? null : <span className='placeholder'>Select media type</span>}
-                            <span className='type-display'>{field.type ? field.type === 'text' ? 'Text Field' : 'File Field' : ''}</span>
+                            {field.type === TemplateFieldType.UNSET ? <span className='placeholder'>Select media type</span> : null}
+                            <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.setFieldType(field, TemplateFieldType.TEXT)}}/> 
                                 <div className='text'>Text</div>
@@ -930,15 +952,13 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                                 <div className='text'>File</div>
                             </div>
                         </div>
+                        <input className='field-property-container' id={`${Math.random() * 100}`} placeholder={'Choose size'} style={{width: field.title === '' ? '30%' : ''}}/>
                     </div>
                     <textarea className='field-description-container' onChange={(e) => this._dataViz?.setFieldDesc(field.title, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title) ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title) ?? 'Add a description to help with template generation.'} /> 
                     <div>
                         <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
                             <FontAwesomeIcon icon='trash'/>
                         </button>
-                        {/* <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
-                            <FontAwesomeIcon icon='lightbulb'/>
-                        </button> */}
                     </div>
                 </div>
             )}
@@ -1111,7 +1131,7 @@ export interface FieldOpts {
     backgroundColor?: string;
     color?: string;
     cornerRounding?: number;
-    borderWidth?: number;
+    borderWidth?: string;
     borderColor?: string;
     contentXCentering?: 'left' | 'center' | 'right';
     contentYCentering?: 'top' | 'center' | 'bottom';
@@ -1125,8 +1145,8 @@ export interface FieldOpts {
 export class FieldFuncs {
 
     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] * parentWidth / 2; const t = coords.tl[1] * parentHeight / 2; //prettier-ignore
-        const r = coords.br[0] * parentWidth / 2; const b = coords.br[1] * parentHeight / 2; //prettier-ignore
+        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};
@@ -1147,17 +1167,18 @@ export class FieldFuncs {
             _text_fontSize: `${height/2}` ,
             backgroundColor: opts.backgroundColor ?? '',
             color: opts.color,
-            _layout_borderRounding: `${opts.cornerRounding}`,
-            borderWidth: opts.borderWidth,
+            _layout_borderRounding: `${opts.cornerRounding}px` ?? '0px',
             borderColor: opts.borderColor,
+            borderWidth: opts.borderWidth,
             opacity: opts.opacity,
             _layout_centered: opts.contentXCentering === 'center' ? true : false,
+            _rotation: opts.rotation,
         });
 
         return docWithBasicOpts;
     }
 
-    public static ImageField = (coords: {tl: [number, number], br: [number, number]}, parentWidth: number, parentHeight: number, title: string, content: string) => {
+    public static ImageField = (coords: {tl: [number, number], br: [number, number]}, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => {
         const {width, height, coord} = FieldFuncs.getDimensions(coords, parentWidth, parentHeight);
 
         const doc = Docs.Create.ImageDocument(content, {   
@@ -1166,7 +1187,16 @@ export class FieldFuncs {
             title: title, 
             x: coord.x, 
             y: coord.y, 
-            _text_fontSize: `${height/2}` })
+            _text_fontSize: `${height/2}` ,
+            backgroundColor: opts.backgroundColor ?? '',
+            color: opts.color,
+            _layout_borderRounding: `${opts.cornerRounding}px` ?? '0px',
+            borderColor: opts.borderColor,
+            borderWidth: opts.borderWidth,
+            opacity: opts.opacity,
+            _layout_centered: opts.contentXCentering === 'center' ? true : false,
+            _rotation: opts.rotation,
+        });
 
         return doc;
     }
@@ -1222,6 +1252,45 @@ export class TemplateLayouts {
         }]
     };
 
+    public static FourField002: TemplateDocInfos = {
+        title: 'fourfield2',
+        width: 475, 
+        height: 870, 
+        fields: [{
+            tl: [-.95, -.83], 
+            br: [-.15, .83], 
+            types: [TemplateFieldType.VISUAL],
+            sizes: [FieldSize.MEDIUM, FieldSize.LARGE],
+            opts: {
+
+            }
+        }, {
+            tl: [-.9, -.7], 
+            br: [.9, .2], 
+            types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
+            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+            opts: {
+
+            }
+        }, {
+            tl: [-.6, .3], 
+            br: [.6, .4], 
+            types: [TemplateFieldType.TEXT],
+            sizes: [FieldSize.TINY],
+            opts: {
+
+            }
+        }, {
+            tl: [-.9, .5], 
+            br: [.9, .9], 
+            types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
+            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+            opts: {
+
+            }
+        }]
+    };
+
 //     public static FourField002: TemplateDocInfos = {
 //         width: 450, 
 //         height: 600, 
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index 559b8507c..6d800bc19 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -178,7 +178,6 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                 } else {
                     const newAxes = this._props.axes;
                     if (newAxes.includes(col)) newAxes.splice(newAxes.indexOf(col), 1);
-                    else if (newAxes.length > 2) newAxes[newAxes.length - 1] = col;
                     else newAxes.push(col);
                     this._props.selectAxes(newAxes);
                 }
@@ -410,11 +409,13 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                                                 color:
                                                     this._props.axes.slice().reverse().lastElement() === col
                                                         ? 'darkgreen'
-                                                        : this._props.axes.length > 2 && this._props.axes.lastElement() === col
+                                                        : this._props.axes.length > 3 && this._props.axes.lastElement() === col
                                                           ? 'darkred'
-                                                          : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] === col)
+                                                          : this._props.axes.length > 3 && this._props.axes[1] === col
                                                             ? 'darkblue'
-                                                            : undefined,
+                                                            : this._props.axes.lastElement() === col || (this._props.axes.length > 3 && this._props.axes[2] === col) 
+                                                              ? 'darkcyan'
+                                                              : undefined,
                                                 background: this.settingTitle
                                                     ? 'lightgrey'
                                                     : this._props.axes.slice().reverse().lastElement() === col
@@ -423,7 +424,9 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                                                         ? '#Fbdbdb'
                                                         : this._props.axes.lastElement() === col || (this._props.axes.length > 2 && this._props.axes[1] === col)
                                                           ? '#c6ebf7'
-                                                          : undefined,
+                                                          : this._props.axes.lastElement() === col || (this._props.axes.length > 3 && this._props.axes[2] === col) 
+                                                            ? '#c2f0f4'
+                                                            : undefined,
                                                 fontWeight: 'bolder',
                                                 border: '3px solid black',
                                             }}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5af6e65de..79a2def87 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1444,7 +1444,6 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
     render() {
         TraceMobx();
         const borderWidth = 50/*Number(StrCast(this.layoutDoc.layout_borderWidth).replace('px', ''))*/;
-        console.log(this._props.PanelWidth(), borderWidth)
         const xshift = Math.abs(this.Xshift) <= 0.001 ? this._props.PanelWidth() : undefined;
         const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
 
-- 
cgit v1.2.3-70-g09d2


From 2e883e481fa2064983bcd16425118c1abb55cedb Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 4 Oct 2024 20:56:32 -0400
Subject: cleaning up lint problems.

---
 src/client/documents/Documents.ts                  |    3 +-
 src/client/views/MainView.tsx                      |    6 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 4203 ++++++++++----------
 src/client/views/nodes/DocumentView.tsx            |    3 +-
 4 files changed, 2106 insertions(+), 2109 deletions(-)

(limited to 'src/client/views/nodes/DocumentView.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 38a4258b9..0a7047128 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -18,7 +18,6 @@ import { PointData } from '../../pen-gestures/GestureTypes';
 import { DocServer } from '../DocServer';
 import { dropActionType } from '../util/DropActionTypes';
 import { CollectionViewType, DocumentType } from './DocumentTypes';
-import { MarkType } from 'prosemirror-model';
 
 class EmptyBox {
     public static LayoutString() {
@@ -229,7 +228,7 @@ export class DocumentOptions {
     _lockedPosition?: BOOLt = new BoolInfo("lock the x,y coordinates of the document so that it can't be dragged");
     _lockedTransform?: BOOLt = new BoolInfo('lock the freeform_panx,freeform_pany and scale parameters of the document so that it be panned/zoomed');
     _childrenSharedWithSchema?: BOOLt = new BoolInfo("whether this document's children are displayed in its parent schema view", false);
-    _lockedSchemaEditing?: BOOLt = new BoolInfo("", false);
+    _lockedSchemaEditing?: BOOLt = new BoolInfo('', false);
 
     dataViz_title?: string;
     dataViz_line?: string;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 6feb6bd16..fc159d96b 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -41,6 +41,7 @@ import { DashboardView } from './DashboardView';
 import { DictationOverlay } from './DictationOverlay';
 import { DocumentDecorations } from './DocumentDecorations';
 import { GestureOverlay } from './GestureOverlay';
+import { InkTranscription } from './InkTranscription';
 import { LightboxView } from './LightboxView';
 import './MainView.scss';
 import { ObservableReactComponent } from './ObservableReactComponent';
@@ -57,6 +58,7 @@ import { ImageLabelHandler } from './collections/collectionFreeForm/ImageLabelHa
 import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu';
 import { CollectionLinearView } from './collections/collectionLinear';
 import { LinkMenu } from './linking/LinkMenu';
+import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu';
 import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp';
 import { DocButtonState } from './nodes/DocumentLinksButton';
 import { DocumentView, DocumentViewInternal } from './nodes/DocumentView';
@@ -73,10 +75,8 @@ import GenerativeFill from './nodes/generativeFill/GenerativeFill';
 import { PresBox } from './nodes/trails';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
-import { TopBar } from './topbar/TopBar';
-import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu';
 import { SmartDrawHandler } from './smartdraw/SmartDrawHandler';
-import { InkTranscription } from './InkTranscription';
+import { TopBar } from './topbar/TopBar';
 
 // eslint-disable-next-line @typescript-eslint/no-require-imports
 const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 44f743a97..05bedfcdc 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -28,6 +28,7 @@ import { DataVizBox } from './DataVizBox';
 import './DocCreatorMenu.scss';
 import { DefaultStyleProvider, returnEmptyDocViewList } from '../../StyleProvider';
 import { Transform } from '../../../util/Transform';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 
 export enum LayoutType {
     Stacked = 'stacked',
@@ -36,2330 +37,2328 @@ export enum LayoutType {
     Column = 'column',
     Custom = 'custom',
 }
+export interface DataVizTemplateInfo {
+    doc: Doc;
+    layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number };
+    columns: number;
+    referencePos: { x: number; y: number };
+}
 
-@observer
-export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
-    static Instance: DocCreatorMenu;
+export interface DataVizTemplateLayout {
+    template: Doc;
+    docsNumList: number[];
+    layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number };
+    columns: number;
+    rows: number;
+}
 
-    private _disposers: { [name: string]: IDisposer } = {};
+export enum TemplateFieldType {
+    TEXT = 'text',
+    VISUAL = 'visual',
+    UNSET = 'unset',
+}
 
-    private _ref: HTMLDivElement | null = null;
+export enum TemplateFieldSize {
+    TINY = 'tiny',
+    SMALL = 'small',
+    MEDIUM = 'medium',
+    LARGE = 'large',
+    HUGE = 'huge',
+}
 
-    @observable _templateDocs: Doc[] = [];
-    @observable _selectedTemplate: Doc | undefined = undefined;
-    @observable _columns: Col[] = [];
-    @observable _selectedCols: { title: string; type: string; desc: string }[] | undefined = [];
+export type Col = {
+    sizes: TemplateFieldSize[];
+    desc: string;
+    title: string;
+    type: TemplateFieldType;
+    defaultContent?: string;
+};
+export interface FieldOpts {
+    backgroundColor?: string;
+    color?: string;
+    cornerRounding?: number;
+    borderWidth?: string;
+    borderColor?: string;
+    contentXCentering?: 'h-left' | 'h-center' | 'h-right';
+    contentYCentering?: 'top' | 'center' | 'bottom';
+    opacity?: number;
+    rotation?: number;
+    //animation?: boolean;
+    fontBold?: boolean;
+    fontTransform?: 'uppercase' | 'lowercase';
+    fieldViewType?: 'freeform' | 'stacked';
+}
 
-    @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0 };
-    @observable _layoutPreview: boolean = true;
-    @observable _layoutPreviewScale: number = 1;
-    @observable _savedLayouts: DataVizTemplateLayout[] = [];
-    @observable _expandedPreview: { icon: ImageField; doc: Doc } | undefined = undefined;
+type Field = {
+    tl: [number, number];
+    br: [number, number];
+    opts: FieldOpts;
+    subfields?: Field[];
+    types?: TemplateFieldType[];
+    sizes?: TemplateFieldSize[];
+    isDecoration?: boolean;
+    description?: string;
+};
 
-    @observable _suggestedTemplates: Doc[] = [];
-    @observable _GPTOpt: boolean = false;
-    @observable _userPrompt: string = '';
-    @observable _callCount: number = 0;
-    @observable _GPTLoading: boolean = false;
+// class ContentField implements Field {
+//     tl: [number, number];
+//     br: [number, number];
+//     opts: FieldOpts;
+//     subfields?: Field[];
+//     types?: TemplateFieldType[];
+//     sizes?: TemplateFieldSize[];
+//     description?: string;
 
-    @observable _pageX: number = 0;
-    @observable _pageY: number = 0;
-    @observable _indicatorX: number | undefined = undefined;
-    @observable _indicatorY: number | undefined = undefined;
+//     constructor( tl: [number, number], br: [number, number],
+//                         opts: FieldOpts, subfields?: Field[],
+//                         types?: TemplateFieldType[],
+//                         sizes?: TemplateFieldSize[],
+//                         description?: string) {
+//         this.tl = tl;
+//         this.br = br;
+//         this.opts = opts;
+//         this.subfields = subfields;
+//         this.types = types;
+//         this.sizes = sizes;
+//         this.description = description;
+//     }
 
-    @observable _hoveredLayoutPreview: number | undefined = undefined;
-    @observable _mouseX: number = -1;
-    @observable _mouseY: number = -1;
-    @observable _startPos?: { x: number; y: number };
-    @observable _shouldDisplay: boolean = false;
+//     render = (content: any): Doc => {
+//         return new Doc;
+//     }
+// }
 
-    @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' = 'templates';
-    @observable _dragging: boolean = false;
-    @observable _draggingIndicator: boolean = false;
-    @observable _dataViz?: DataVizBox;
-    @observable _interactionLock: any;
-    @observable _snapPt: any;
-    @observable _resizeHdlId: string = '';
-    @observable _resizing: boolean = false;
-    @observable _offset: { x: number; y: number } = { x: 0, y: 0 };
-    @observable _resizeUndo: UndoManager.Batch | undefined = undefined;
-    @observable _initDimensions: { width: number; height: number; x?: number; y?: number } = { width: 300, height: 400, x: undefined, y: undefined };
-    @observable _menuDimensions: { width: number; height: number } = { width: 400, height: 400 };
-    @observable _editing: boolean = false;
+type DecorationField = Field;
 
-    constructor(props: any) {
-        super(props);
-        makeObservable(this);
-        DocCreatorMenu.Instance = this;
-        //setTimeout(() => this.generateTemplates(''));
-    }
+type InkDecoration = {};
 
-    @action setDataViz = (dataViz: DataVizBox) => {
-        this._dataViz = dataViz;
-    };
-    @action setTemplateDocs = (docs: Doc[]) => {
-        this._templateDocs = docs.map(doc => (doc.annotationOn ? DocCast(doc.annotationOn) : doc));
-    };
-    @action setGSuggestedTemplates = (docs: Doc[]) => {
-        this._suggestedTemplates = docs;
-    };
+type TemplateDecorations = Field | InkDecoration;
 
-    @computed get docsToRender() {
-        return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : [];
-    }
+interface TemplateOpts extends FieldOpts {}
 
-    @computed get rowsCount() {
-        switch (this._layout.type) {
-            case LayoutType.Row:
-            case LayoutType.Stacked:
-                return 1;
-            case LayoutType.Column:
-                return this.docsToRender.length;
-            case LayoutType.Grid:
-                return Math.ceil(this.docsToRender.length / (this._layout.columns ?? 1)) ?? 0;
-            default:
-                return 0;
-        }
+export interface TemplateDocInfos {
+    title: string;
+    height: number;
+    width: number;
+    opts: TemplateOpts;
+    fields: Field[];
+    decorations: Field[];
+}
+
+export class TemplateLayouts {
+    public static get allTemplates(): TemplateDocInfos[] {
+        return Object.values(TemplateLayouts).filter(value => typeof value === 'object' && value !== null && 'title' in value) as TemplateDocInfos[];
     }
 
-    @computed get columnsCount() {
-        switch (this._layout.type) {
-            case LayoutType.Row:
-                return this.docsToRender.length;
-            case LayoutType.Column:
-            case LayoutType.Stacked:
-                return 1;
-            case LayoutType.Grid:
-                return this._layout.columns ?? 0;
+    public static getTemplateByTitle = (title: string): TemplateDocInfos | undefined => {
+        switch (title) {
+            case 'fourfield1':
+                return TemplateLayouts.FourField001;
+            case 'fourfield2':
+                return TemplateLayouts.FourField002;
+            // case 'fourfield3':
+            //     return TemplateLayouts.FourField003;
+            case 'fourfield4':
+                return TemplateLayouts.FourField004;
+            case 'threefield1':
+                return TemplateLayouts.ThreeField001;
+            case 'threefield2':
+                return TemplateLayouts.ThreeField002;
             default:
-                return 0;
+                break;
         }
-    }
 
-    @computed get selectedFields() {
-        return StrListCast(this._dataViz?.layoutDoc._dataViz_axes);
-    }
-
-    @computed get fieldsInfos(): Col[] {
-        const colInfo = this._dataViz?.colsInfo;
-        return this.selectedFields
-            .map(field => {
-                const fieldInfo = colInfo?.get(field);
-
-                const col: Col = {
-                    title: field,
-                    type: fieldInfo?.type ?? TemplateFieldType.UNSET,
-                    desc: fieldInfo?.desc ?? '',
-                    sizes: fieldInfo?.sizes ?? [TemplateFieldSize.MEDIUM],
-                };
-
-                if (fieldInfo?.defaultContent !== undefined) {
-                    col.defaultContent = fieldInfo.defaultContent;
-                }
-
-                return col;
-            })
-            .concat(this._columns);
-    }
-
-    @computed get canMakeDocs() {
-        return this._selectedTemplate !== undefined && this._layout !== undefined;
-    }
-
-    get bounds(): { t: number; b: number; l: number; r: number } {
-        const rect = this._ref?.getBoundingClientRect();
-        const bounds = { t: rect?.top ?? 0, b: rect?.bottom ?? 0, l: rect?.left ?? 0, r: rect?.right ?? 0 };
-        return bounds;
-    }
-
-    setUpButtonClick = (e: any, func: Function) => {
-        setupMoveUpEvents(
-            this,
-            e,
-            returnFalse,
-            emptyFunction,
-            undoable(clickEv => {
-                clickEv.stopPropagation();
-                clickEv.preventDefault();
-                func();
-            }, 'create docs')
-        );
-    };
-
-    @action
-    onPointerDown = (e: PointerEvent) => {
-        this._mouseX = e.clientX;
-        this._mouseY = e.clientY;
+        return undefined;
     };
 
-    @action
-    onPointerUp = (e: PointerEvent) => {
-        if (this._resizing) {
-            this._initDimensions.width = this._menuDimensions.width;
-            this._initDimensions.height = this._menuDimensions.height;
-            this._initDimensions.x = this._pageX;
-            this._initDimensions.y = this._pageY;
-            document.removeEventListener('pointermove', this.onResize);
-            SnappingManager.SetIsResizing(undefined);
-            this._resizing = false;
-        }
-        if (this._dragging) {
-            document.removeEventListener('pointermove', this.onDrag);
-            this._dragging = false;
-        }
-        if (e.button !== 2 && !e.ctrlKey) return;
-        const curX = e.clientX;
-        const curY = e.clientY;
-        if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) {
-            this._shouldDisplay = false;
-        }
-    };
-
-    componentDidMount() {
-        document.addEventListener('pointerdown', this.onPointerDown, true);
-        document.addEventListener('pointerup', this.onPointerUp);
-        this._disposers.templates = reaction(
-            () => this._templateDocs.slice(),
-            docs => this.updateIcons(docs)
-        );
-        this._disposers.gpt = reaction(
-            () => this._suggestedTemplates.slice(),
-            docs => this.updateIcons(docs)
-        );
-        //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')})
-        this._disposers.lightbox = reaction(
-            () => LightboxView.LightboxDoc(),
-            doc => {
-                doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu();
-            }
-        );
-        //this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', desc: ''}}))
-    }
-
-    componentWillUnmount() {
-        Object.values(this._disposers).forEach(disposer => disposer?.());
-        document.removeEventListener('pointerdown', this.onPointerDown, true);
-        document.removeEventListener('pointerup', this.onPointerUp);
-    }
-
-    updateIcons = (docs: Doc[]) => {
-        console.log('called')
-        docs.map(this.getIcon);
-    };
-
-    @action
-    updateSelectedCols = (cols: string[]) => {
-        this._selectedCols;
-    };
-
-    @action
-    toggleDisplay = (x: number, y: number) => {
-        if (this._shouldDisplay) {
-            this._shouldDisplay = false;
-        } else {
-            this._pageX = x;
-            this._pageY = y;
-            this._shouldDisplay = true;
-        }
-    };
-
-    @action
-    closeMenu = () => {
-        this._shouldDisplay = false;
-    };
-
-    @action
-    openMenu = () => {
-        const allTemplates = this._templateDocs.concat(this._suggestedTemplates);
-        this._shouldDisplay = true;
-        this.updateIcons(allTemplates);
+    public static FourField001: TemplateDocInfos = {
+        title: 'fourfield1',
+        width: 416,
+        height: 700,
+        opts: {
+            backgroundColor: '#C0B887',
+            cornerRounding: 20,
+            borderColor: '#6B461F',
+            borderWidth: '12',
+        },
+        fields: [
+            {
+                tl: [-0.95, -1],
+                br: [0.95, -0.85],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.TINY],
+                description: 'A title field for very short text that contextualizes the content.',
+                opts: {
+                    backgroundColor: 'transparent',
+                    color: '#F1F0E9',
+                    contentXCentering: 'h-center',
+                    fontBold: true,
+                },
+            },
+            {
+                tl: [-0.87, -0.83],
+                br: [0.87, 0.2],
+                types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
+                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+                description: 'The main focus of the template; could be an image, long text, etc.',
+                opts: {
+                    cornerRounding: 20,
+                    borderColor: '#8F5B25',
+                    borderWidth: '6',
+                    backgroundColor: '#CECAB9',
+                },
+            },
+            {
+                tl: [-0.8, 0.2],
+                br: [0.8, 0.3],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
+                description: 'A caption for field #2, very short to short text that contextualizes the content of field #2',
+                opts: {
+                    backgroundColor: 'transparent',
+                    contentXCentering: 'h-center',
+                    color: '#F1F0E9',
+                },
+            },
+            {
+                tl: [-0.87, 0.37],
+                br: [0.87, 0.96],
+                types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
+                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+                description: 'A medium-sized field for medium/long text.',
+                opts: {
+                    cornerRounding: 15,
+                    borderColor: '#8F5B25',
+                    borderWidth: '6',
+                    backgroundColor: '#CECAB9',
+                },
+            },
+        ],
+        decorations: [],
     };
 
-    @action
-    onResizePointerDown = (e: React.PointerEvent): void => {
-        this._resizing = true;
-        document.addEventListener('pointermove', this.onResize);
-        SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
-        e.stopPropagation();
-        const id = (this._resizeHdlId = e.currentTarget.className);
-        const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as any).width.replace('px', '')) / 2 : 0;
-        const bounds = e.currentTarget.getBoundingClientRect();
-        this._offset = {
-            x: id.toLowerCase().includes('left') ? bounds.right - e.clientX - pad : bounds.left - e.clientX + pad, //
-            y: id.toLowerCase().includes('top') ? bounds.bottom - e.clientY - pad : bounds.top - e.clientY + pad,
-        };
-        this._resizeUndo = UndoManager.StartBatch('drag resizing');
-        this._snapPt = { x: e.pageX, y: e.pageY };
+    public static FourField002: TemplateDocInfos = {
+        title: 'fourfield2',
+        width: 425,
+        height: 778,
+        opts: {
+            backgroundColor: '#242425',
+        },
+        fields: [
+            {
+                tl: [-0.83, -0.95],
+                br: [0.83, -0.2],
+                types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
+                description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus.',
+                opts: {
+                    borderWidth: '8',
+                    borderColor: '#F8E71C',
+                },
+            },
+            {
+                tl: [-0.65, -0.2],
+                br: [0.65, -0.02],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.TINY],
+                description: 'A tiny field for just a word or two of plain text.',
+                opts: {
+                    backgroundColor: 'transparent',
+                    color: 'white',
+                    contentXCentering: 'h-center',
+                    fontTransform: 'uppercase',
+                },
+            },
+            {
+                tl: [-0.65, 0],
+                br: [0.65, 0.18],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.TINY],
+                description: 'A tiny field for just a word or two of plain text.',
+                opts: {
+                    backgroundColor: 'transparent',
+                    color: 'white',
+                    contentXCentering: 'h-center',
+                    fontTransform: 'uppercase',
+                },
+            },
+            {
+                tl: [-0.83, 0.2],
+                br: [0.83, 0.95],
+                types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
+                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+                description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus, or share focus with field 1.',
+                opts: {
+                    borderWidth: '8',
+                    borderColor: '#F8E71C',
+                    color: 'white',
+                    backgroundColor: '#242425',
+                },
+            },
+        ],
+        decorations: [
+            {
+                tl: [-0.8, -0.075],
+                br: [-0.525, 0.075],
+                opts: {
+                    backgroundColor: '#F8E71C',
+                    rotation: 45,
+                },
+            },
+            {
+                tl: [-0.3075, -0.0245],
+                br: [-0.2175, 0.0245],
+                opts: {
+                    backgroundColor: '#F8E71C',
+                    rotation: 45,
+                },
+            },
+            {
+                tl: [-0.045, -0.0245],
+                br: [0.045, 0.0245],
+                opts: {
+                    backgroundColor: '#F8E71C',
+                    rotation: 45,
+                },
+            },
+            {
+                tl: [0.2175, -0.0245],
+                br: [0.3075, 0.0245],
+                opts: {
+                    backgroundColor: '#F8E71C',
+                    rotation: 45,
+                },
+            },
+            {
+                tl: [0.525, -0.075],
+                br: [0.8, 0.075],
+                opts: {
+                    backgroundColor: '#F8E71C',
+                    rotation: 45,
+                },
+            },
+        ],
     };
 
-    @action
-    onResize = (e: any): boolean => {
-        const dragHdl = this._resizeHdlId.split(' ')[1];
-        const thisPt = DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y);
-
-        const { scale, refPt, transl } = this.getResizeVals(thisPt, dragHdl);
-        !this._interactionLock && runInAction(async () => {   // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
-        this._interactionLock = true;
-        const scaleAspect = {x: scale.x, y: scale.y};
-        this.resizeView(refPt, scaleAspect, transl); // prettier-ignore
-        await new Promise<any>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
-        }); // prettier-ignore
-        return true;
-    };
-
-    @action
-    onDrag = (e: any): boolean => {
-        this._pageX = e.pageX - (this._startPos?.x ?? 0);
-        this._pageY = e.pageY - (this._startPos?.y ?? 0);
-        this._initDimensions.x = this._pageX;
-        this._initDimensions.y = this._pageY;
-        return true;
-    };
+    // public static FourField003: TemplateDocInfos = {
+    //     title: 'fourfield3',
+    //     width: 477,
+    //     height: 662,
+    //     opts: {
+    //         backgroundColor: '#9E9C95'
+    //     },
+    //     fields: [{
+    //         tl: [-.875, -.9],
+    //         br: [.875, .7],
+    //         types: [TemplateFieldType.VISUAL],
+    //         sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+    //         description: '',
+    //         opts: {
+    //             borderWidth: '15',
+    //             borderColor: '#E0E0DA',
+    //         }
+    //     }, {
+    //         tl: [-.95, .8],
+    //         br: [-.1, .95],
+    //         types: [TemplateFieldType.TEXT],
+    //         sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
+    //         description: '',
+    //         opts: {
+    //             backgroundColor: 'transparent',
+    //             color: 'white',
+    //             contentXCentering: 'h-right',
+    //         }
+    //     }, {
+    //         tl: [.1, .8],
+    //         br: [.95, .95],
+    //         types: [TemplateFieldType.TEXT],
+    //         sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
+    //         description: '',
+    //         opts: {
+    //             backgroundColor: 'transparent',
+    //             color: 'red',
+    //             fontTransform: 'uppercase',
+    //             contentXCentering: 'h-left'
+    //         }
+    //     }, {
+    //         tl: [0, -.9],
+    //         br: [.85, -.66],
+    //         types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
+    //         sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+    //         description: '',
+    //         opts: {
+    //             backgroundColor: 'transparent',
+    //             contentXCentering: 'h-right'
+    //         }
+    //     }],
+    //     decorations: [{
+    //         tl: [-.025, .8],
+    //         br: [.025, .95],
+    //         opts: {
+    //             backgroundColor: '#E0E0DA',
+    //         }
+    //     }]
+    // };
 
-    getResizeVals = (thisPt: { x: number; y: number }, dragHdl: string) => {
-        const [w, h] = [this._initDimensions.width, this._initDimensions.height];
-        const [moveX, moveY] = [thisPt.x - this._snapPt.x, thisPt.y - this._snapPt.y];
-        let vals: { scale: { x: number; y: number }; refPt: [number, number]; transl: { x: number; y: number } };
-        switch (dragHdl) {
-            case 'topLeft':    vals = { scale: { x: 1 - moveX / w, y: 1 -moveY / h },  refPt: [this.bounds.r, this.bounds.b], transl: {x: moveX, y: moveY } }; break;
-            case 'topRight':   vals = { scale: { x: 1 + moveX / w, y: 1 -moveY / h },  refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break;
-            case 'top':        vals = { scale: { x: 1,             y: 1 -moveY / h },  refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break;
-            case 'left':       vals = { scale: { x: 1 - moveX / w, y: 1 },             refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break;
-            case 'bottomLeft': vals = { scale: { x: 1 - moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break;
-            case 'right':      vals = { scale: { x: 1 + moveX / w, y: 1 },             refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
-            case 'bottomRight':vals = { scale: { x: 1 + moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
-            case 'bottom':     vals = { scale: { x: 1,             y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
-            default:           vals = { scale: { x: 1,             y: 1 },             refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
-        } // prettier-ignore
-        return vals;
+    public static FourField004: TemplateDocInfos = {
+        title: 'fourfield4',
+        width: 414,
+        height: 583,
+        opts: {
+            backgroundColor: '#6CCAF0',
+            borderColor: '#1088C3',
+            borderWidth: '10',
+        },
+        fields: [
+            {
+                tl: [-0.86, -0.92],
+                br: [-0.075, -0.77],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.TINY],
+                description: 'A tiny field for just a word or two of plain text.',
+                opts: {
+                    backgroundColor: '#E2B4F5',
+                    borderWidth: '9',
+                    borderColor: '#9222F1',
+                    contentXCentering: 'h-center',
+                },
+            },
+            {
+                tl: [0.075, -0.92],
+                br: [0.86, -0.77],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.TINY],
+                description: 'A tiny field for just a word or two of plain text.',
+                opts: {
+                    backgroundColor: '#F5B4DD',
+                    borderWidth: '9',
+                    borderColor: '#E260F3',
+                    contentXCentering: 'h-center',
+                },
+            },
+            {
+                tl: [-0.81, -0.64],
+                br: [0.81, 0.48],
+                types: [TemplateFieldType.VISUAL],
+                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+                description: 'A large to huge field for visual content that is the main content of the template.',
+                opts: {
+                    borderWidth: '16',
+                    borderColor: '#A2BD77',
+                },
+            },
+            {
+                tl: [-0.86, 0.6],
+                br: [0.86, 0.92],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
+                description: 'A medium to large field for text that describes the visual content above',
+                opts: {
+                    borderWidth: '9',
+                    borderColor: '#F0D601',
+                    backgroundColor: '#F3F57D',
+                },
+            },
+        ],
+        decorations: [
+            {
+                tl: [-0.852, -0.67],
+                br: [0.852, 0.51],
+                opts: {
+                    backgroundColor: 'transparent',
+                    borderColor: '#007C0C',
+                    borderWidth: '10',
+                },
+            },
+        ],
     };
 
-    resizeView = (refPt: number[], scale: { x: number; y: number }, translation: { x: number; y: number }) => {
-        const refCent = [refPt[0], refPt[1]]; // fixed reference point for resize (ie, a point that doesn't move)
-        if (this._initDimensions.x === undefined) this._initDimensions.x = this._pageX;
-        if (this._initDimensions.y === undefined) this._initDimensions.y = this._pageY;
-        const { height, width, x, y } = this._initDimensions;
-
-        this._menuDimensions.width = Math.max(300, scale.x * width);
-        this._menuDimensions.height = Math.max(200, scale.y * height);
-        this._pageX = x + translation.x;
-        this._pageY = y + translation.y;
+    public static ThreeField001: TemplateDocInfos = {
+        title: 'threefield1',
+        width: 575,
+        height: 770,
+        opts: {
+            backgroundColor: '#DDD3A9',
+        },
+        fields: [
+            {
+                tl: [-0.66, -0.747],
+                br: [0.66, 0.247],
+                types: [TemplateFieldType.VISUAL],
+                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+                description: 'A medium to large field for visual content that is the central focus.',
+                opts: {
+                    borderColor: 'yellow',
+                    borderWidth: '8',
+                    rotation: 45,
+                },
+            },
+            {
+                tl: [-0.7, 0.2],
+                br: [0.7, 0.46],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
+                description: 'A very small text field for one to a few words. A good caption for the image.',
+                opts: {
+                    backgroundColor: 'transparent',
+                    contentXCentering: 'h-center',
+                },
+            },
+            {
+                tl: [-0.95, 0.5],
+                br: [0.95, 0.95],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
+                description: 'A medium to large text field for a thorough description of the image. ',
+                opts: {
+                    backgroundColor: 'transparent',
+                    color: 'white',
+                },
+            },
+        ],
+        decorations: [
+            {
+                tl: [0.2, -1.32],
+                br: [1.8, -0.66],
+                opts: {
+                    backgroundColor: '#CEB155',
+                    rotation: 45,
+                },
+            },
+            {
+                tl: [-1.8, -1.32],
+                br: [-0.2, -0.66],
+                opts: {
+                    backgroundColor: '#CEB155',
+                    rotation: 135,
+                },
+            },
+            {
+                tl: [0.33, 0.75],
+                br: [1.66, 1.25],
+                opts: {
+                    backgroundColor: '#CEB155',
+                    rotation: 135,
+                },
+            },
+            {
+                tl: [-1.66, 0.75],
+                br: [-0.33, 1.25],
+                opts: {
+                    backgroundColor: '#CEB155',
+                    rotation: 45,
+                },
+            },
+        ],
     };
 
-    async getIcon(doc: Doc) {
-        const docView = DocumentView.getDocumentView(doc);
-        if (docView) {
-            docView.ComponentView?.updateIcon?.();
-            return new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 500));
-        }
-        return undefined;
-    }
-
-    @action updateSelectedTemplate = (template: Doc) => {
-        if (this._selectedTemplate === template) {
-            this._selectedTemplate = undefined;
-            return;
-        } else {
-            this._selectedTemplate = template;
-            MakeTemplate(template);
-        }
+    public static ThreeField002: TemplateDocInfos = {
+        title: 'threefield2',
+        width: 477,
+        height: 662,
+        opts: {
+            backgroundColor: '#9E9C95',
+        },
+        fields: [
+            {
+                tl: [-0.875, -0.9],
+                br: [0.875, 0.7],
+                types: [TemplateFieldType.VISUAL],
+                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+                description: 'A medium to large visual field for the main content of the template',
+                opts: {
+                    borderWidth: '15',
+                    borderColor: '#E0E0DA',
+                },
+            },
+            {
+                tl: [0.1, 0.775],
+                br: [0.95, 0.975],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
+                description: 'A very small text field for one to a few words. The content should represent a general categorization of the image.',
+                opts: {
+                    backgroundColor: 'transparent',
+                    color: '#AF0D0D',
+                    fontTransform: 'uppercase',
+                    fontBold: true,
+                    contentXCentering: 'h-left',
+                },
+            },
+            {
+                tl: [-0.95, 0.775],
+                br: [-0.1, 0.975],
+                types: [TemplateFieldType.TEXT],
+                sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
+                description: 'A very small text field for one to a few words. The content should contextualize field 2.',
+                opts: {
+                    backgroundColor: 'transparent',
+                    color: 'black',
+                    contentXCentering: 'h-right',
+                },
+            },
+        ],
+        decorations: [
+            {
+                tl: [-0.025, 0.8],
+                br: [0.025, 0.95],
+                opts: {
+                    backgroundColor: '#E0E0DA',
+                },
+            },
+        ],
     };
+}
 
-    @action updateSelectedSavedLayout = (layout: DataVizTemplateLayout) => {
-        this._layout.xMargin = layout.layout.xMargin;
-        this._layout.yMargin = layout.layout.yMargin;
-        this._layout.type = layout.layout.type;
-        this._layout.columns = layout.columns;
-    };
+export class FieldUtils {
+    public static contentFields = (fields: Field[]) => {
+        let toRet: Field[] = [];
+        fields.forEach(field => {
+            if (!field.isDecoration) {
+                toRet.push(field);
+            }
+            toRet = toRet.concat(FieldUtils.contentFields(field.subfields ?? []));
+        });
 
-    isSelectedLayout = (layout: DataVizTemplateLayout) => {
-        return this._layout.xMargin === layout.layout.xMargin && this._layout.yMargin === layout.layout.yMargin && this._layout.type === layout.layout.type && this._layout.columns === layout.columns;
+        return toRet;
     };
 
-    @action
-    generateTemplates = async (inputText: string) => {
-        ++this._callCount;
-        const origCount = this._callCount;
+    public static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => {
+        const words: string[] = text.split(/\s+/).filter(Boolean);
 
-        let prompt: string = `(#${origCount}) Please generate for the fields:`;
-        this.selectedFields?.forEach(field => (prompt += ` ${field},`));
-        prompt += ` (-----NOT A FIELD-----) Additional prompt: ${inputText}`;
+        let currFontSize = 1;
+        let rowsCount = 1;
+        let currTextHeight = currFontSize * rowsCount * 2;
 
-        this._GPTLoading = true;
+        while (currTextHeight <= contHeight) {
+            let wordIndex = 0;
+            let currentRowWidth = 0;
+            let wordsInCurrRow = 0;
+            rowsCount = 1;
 
-        try {
-            const res = await gptAPICall(prompt, GPTCallType.TEMPLATE);
+            while (wordIndex < words.length) {
+                const word = words[wordIndex];
+                const wordWidth = word.length * currFontSize * 0.5;
+                //console.log(wordWidth)
 
-            if (res && this._callCount === origCount) {
-                this._suggestedTemplates = [];
-                const templates: { template_type: string; fieldVals: { title: string; tlx: string; tly: string; brx: string; bry: string }[] }[] = JSON.parse(res);
-                this.createGeneratedTemplates(templates, 500, 500);
+                if (currentRowWidth + wordWidth <= contWidth) {
+                    currentRowWidth += wordWidth;
+                    ++wordsInCurrRow;
+                } else {
+                    if (words.length !== 1 && words.length > wordsInCurrRow) {
+                        rowsCount++;
+                        currentRowWidth = wordWidth;
+                        wordsInCurrRow = 1;
+                    } else {
+                        break;
+                    }
+                }
+
+                wordIndex++;
             }
-        } catch (err) {
-            console.error(err);
+
+            currTextHeight = rowsCount * currFontSize * 2;
+            //console.log(rowsCount, currFontSize, currTextHeight)
+
+            currFontSize += 1;
         }
+
+        return currFontSize - 1;
     };
 
-    @action
-    createGeneratedTemplates = (layouts: { template_type: string; fieldVals: { title: string; tlx: string; tly: string; brx: string; bry: string }[] }[], tempWidth: number, tempHeight: number) => {
-        const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-        const GPTTemplates: Doc[] = [];
+    private static getDimensions = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number): { width: number; height: number; coord: { x: number; y: number } } => {
+        const l = (coords.tl[0] * parentHeight) / 2;
+        const t = coords.tl[1] * parentWidth / 2; //prettier-ignore
+        const r = (coords.br[0] * parentHeight) / 2;
+        const b = coords.br[1] * parentWidth / 2; //prettier-ignore
+        const width = r - l;
+        const height = b - t;
+        const coord = { x: l, y: t };
+        //console.log(coords, parentWidth, parentHeight, height);
+        return { width, height, coord };
+    };
 
-        layouts.forEach(layout => {
-            const fields: Doc[] = layout.fieldVals.map(field => {
-                const left: number = (Number(field.tlx) * tempWidth) / 2;
-                const top: number = Number(field.tly) * tempHeight / 2; //prettier-ignore
-                const right: number = (Number(field.brx) * tempWidth) / 2;
-                const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore
-                const height = bottom - top;
-                const width = right - left;
-                const doc = !field.title.includes('$$')
-                    ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height / 2}` })
-                    : Docs.Create.ImageDocument('', { _height: height, _width: width, title: field.title.replace(/\$\$/g, ''), x: left, y: top });
-                return doc;
-            });
+    public static FreeformField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => {
+        const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight);
 
-            const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400000, y: 400000 });
+        const docWithBasicOpts = Docs.Create.FreeformDocument([], {
+            isDefaultTemplateDoc: true,
+            _height: height,
+            _width: width,
+            title: title,
+            x: coord.x,
+            y: coord.y,
+            backgroundColor: opts.backgroundColor ?? '',
+            _layout_borderRounding: `${opts.cornerRounding ?? 0}px`,
+            borderColor: opts.borderColor,
+            borderWidth: opts.borderWidth,
+            opacity: opts.opacity,
+            hCentering: opts.contentXCentering,
+            _rotation: opts.rotation,
+        });
 
-            mainCollection.addDocument(template);
+        return docWithBasicOpts;
+    };
 
-            GPTTemplates.push(template);
+    public static TextField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => {
+        const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight);
+
+        const docWithBasicOpts = Docs.Create.TextDocument(content, {
+            isDefaultTemplateDoc: true,
+            _height: height,
+            _width: width,
+            title: title,
+            x: coord.x,
+            y: coord.y,
+            _text_fontSize: `${FieldUtils.calculateFontSize(width, height, content, true)}`,
+            backgroundColor: opts.backgroundColor ?? '',
+            text_fontColor: opts.color,
+            contentBold: opts.fontBold,
+            textTransform: opts.fontTransform,
+            color: opts.color,
+            _layout_borderRounding: `${opts.cornerRounding ?? 0}px`,
+            borderColor: opts.borderColor,
+            borderWidth: opts.borderWidth,
+            opacity: opts.opacity,
+            hCentering: opts.contentXCentering,
+            _rotation: opts.rotation,
         });
 
-        setTimeout(() => {
-            this.setGSuggestedTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/
-        }, 100);
+        docWithBasicOpts._layout_hideScroll = true;
 
-        this.forceUpdate();
+        return docWithBasicOpts;
     };
 
-    editTemplate = (doc: Doc) => {
-        //this.closeMenu();
-        DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight);
-        DocumentView.DeselectAll();
-        Doc.UnBrushDoc(doc);
-    };
+    public static ImageField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, content: string, opts: FieldOpts) => {
+        const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight);
 
-    removeTemplate = (doc: Doc) => {
-        this._templateDocs.splice(this._templateDocs.indexOf(doc), 1);
-    };
+        const doc = Docs.Create.ImageDocument(content, {
+            isDefaultTemplateDoc: true,
+            _height: height,
+            _width: width,
+            title: title,
+            x: coord.x,
+            y: coord.y,
+            _layout_fitWidth: false,
+            backgroundColor: opts.backgroundColor ?? '',
+            _layout_borderRounding: `${opts.cornerRounding ?? 0}px`,
+            borderColor: opts.borderColor,
+            borderWidth: opts.borderWidth,
+            opacity: opts.opacity,
+            _rotation: opts.rotation,
+        });
 
-    testTemplate = async () => {
-        this.updateIcons(this._suggestedTemplates.slice());
-        this.forceUpdate();
+        //setTimeout(() => {doc._height = height; doc._width = width}, 10);
 
-        // try {
-        //     const res = await gptImageCall('Image of panda eating a cookie');
+        return doc;
+    };
 
-        //     if (res) {
-        //         const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res });
+    public static CarouselField = (coords: { tl: [number, number]; br: [number, number] }, parentWidth: number, parentHeight: number, title: string, fields: Doc[]) => {
+        const { width, height, coord } = FieldUtils.getDimensions(coords, parentWidth, parentHeight);
 
-        //         console.log(result);
-        //     }
-        // } catch (e) {
-        //     console.log(e);
-        // }
-    };
+        const doc = Docs.Create.Carousel3DDocument(fields, { _height: height, _width: width, title: title, x: coord.x, y: coord.y, _text_fontSize: `${height / 2}` });
 
-    @action addField = () => {
-        const newFields: Col[] = this._columns.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [] }]);
-        this._columns = newFields;
+        return doc;
     };
+}
 
-    @action removeField = (field: { title: string; type: string; desc: string }) => {
-        if (this._dataViz?.axes.includes(field.title)) {
-            this._dataViz.selectAxes(this._dataViz.axes.filter(col => col !== field.title));
-        } else {
-            const toRemove = this._columns.filter(f => f === field);
-            if (!toRemove) return;
-
-            if (toRemove.length > 1) {
-                while (toRemove.length > 1) {
-                    toRemove.pop();
-                }
-            }
-
-            if (this._columns.length === 1) {
-                this._columns = [];
-            } else {
-                this._columns.splice(this._columns.indexOf(toRemove[0]), 1);
-            }
-        }
-    };
+//     public static FourField002: TemplateDocInfos = {
+//         width: 450,
+//         height: 600,
+//         fields: [{
+//             tl: [-.6, -.9],
+//             br: [.6, -.8],
+//             types: [FieldType.TEXT],
+//             sizes: [FieldSize.TINY]
+//         }, {
+//             tl: [-.9, -.7],
+//             br: [.9, .2],
+//             types: [FieldType.TEXT, FieldType.VISUAL],
+//             sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE]
+//         }, {
+//             tl: [-.9, .3],
+//             br: [-.05, .9],
+//             types: [FieldType.TEXT],
+//             sizes: [FieldSize.TINY]
+//         }, {
+//             tl: [.05, .3],
+//             br: [.9, .9],
+//             types: [FieldType.TEXT, FieldType.VISUAL],
+//             sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE]
+//         }]
+//     };
+
+//     public static TwoFieldPlusCarousel: TemplateDocInfos = {
+//         width: 500,
+//         height: 600,
+//         fields: [{
+//             tl: [-.9, -.99],
+//             br: [.9, -.7],
+//             types: [FieldType.TEXT],
+//             sizes: [FieldSize.TINY]
+//         }, {
+//             tl: [-.9, -.65],
+//             br: [.9, .35],
+//             types: [],
+//             sizes: []
+//         }, {
+//             tl: [-.9, .4],
+//             br: [.9, .95],
+//             types: [FieldType.TEXT],
+//             sizes: [FieldSize.TINY]
+//         }]
+//     };
+//  }
 
-    @action setColTitle = (column: Col, title: string) => {
-        if (this.selectedFields.includes(column.title)) {
-            this._dataViz?.setColumnTitle(column.title, title);
-        } else {
-            column.title = title;
-        }
-        this.forceUpdate();
-    };
+@observer
+export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
+    // eslint-disable-next-line no-use-before-define
+    static Instance: DocCreatorMenu;
 
-    @action setColType = (column: Col, type: TemplateFieldType) => {
-        if (this.selectedFields.includes(column.title)) {
-            this._dataViz?.setColumnType(column.title, type);
-        } else {
-            column.type = type;
-        }
-        this.forceUpdate();
-    };
+    private _disposers: { [name: string]: IDisposer } = {};
 
-    modifyColSizes = (column: Col, size: TemplateFieldSize, valid: boolean) => {
-        if (this.selectedFields.includes(column.title)) {
-            this._dataViz?.modifyColumnSizes(column.title, size, valid);
-        } else {
-            if (!valid && column.sizes.includes(size)) {
-                column.sizes.splice(column.sizes.indexOf(size), 1);
-            } else if (valid && !column.sizes.includes(size)) {
-                column.sizes.push(size);
-            }
-        }
-        this.forceUpdate();
-    };
+    private _ref: HTMLDivElement | null = null;
 
-    setColDesc = (column: Col, desc: string) => {
-        if (this.selectedFields.includes(column.title)) {
-            this._dataViz?.setColumnDesc(column.title, desc);
-        } else {
-            column.desc = desc;
-        }
-        this.forceUpdate();
-    };
+    @observable _templateDocs: Doc[] = [];
+    @observable _selectedTemplate: Doc | undefined = undefined;
+    @observable _columns: Col[] = [];
+    @observable _selectedCols: { title: string; type: string; desc: string }[] | undefined = [];
 
-    generateGPTImage = async (prompt: string): Promise<string | undefined> => {
-        console.log(prompt);
+    @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0 };
+    @observable _layoutPreview: boolean = true;
+    @observable _layoutPreviewScale: number = 1;
+    @observable _savedLayouts: DataVizTemplateLayout[] = [];
+    @observable _expandedPreview: { icon: ImageField; doc: Doc } | undefined = undefined;
 
-        try {
-            const res = await gptImageCall(prompt);
+    @observable _suggestedTemplates: Doc[] = [];
+    @observable _GPTOpt: boolean = false;
+    @observable _userPrompt: string = '';
+    @observable _callCount: number = 0;
+    @observable _GPTLoading: boolean = false;
 
-            if (res) {
-                const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res });
-                const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client);
-                return source;
-            }
-        } catch (e) {
-            console.log(e);
-        }
-    };
+    @observable _pageX: number = 0;
+    @observable _pageY: number = 0;
+    @observable _indicatorX: number | undefined = undefined;
+    @observable _indicatorY: number | undefined = undefined;
 
-    matchesForTemplate = (template: TemplateDocInfos, cols: Col[]): number[][] => {
-        const colMatchesField = (col: Col, field: Field) => {
-            return field.sizes?.some(size => col.sizes?.includes(size)) && field.types?.includes(col.type);
-        };
+    @observable _hoveredLayoutPreview: number | undefined = undefined;
+    @observable _mouseX: number = -1;
+    @observable _mouseY: number = -1;
+    @observable _startPos?: { x: number; y: number };
+    @observable _shouldDisplay: boolean = false;
 
-        const matches: number[][] = Array(template.fields.length)
-            .fill([])
-            .map(() => []);
+    @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' = 'templates';
+    @observable _dragging: boolean = false;
+    @observable _draggingIndicator: boolean = false;
+    @observable _dataViz?: DataVizBox;
+    @observable _interactionLock?: boolean;
+    @observable _snapPt: { x: number; y: number } = { x: 0, y: 0 };
+    @observable _resizeHdlId: string = '';
+    @observable _resizing: boolean = false;
+    @observable _offset: { x: number; y: number } = { x: 0, y: 0 };
+    @observable _resizeUndo: UndoManager.Batch | undefined = undefined;
+    @observable _initDimensions: { width: number; height: number; x?: number; y?: number } = { width: 300, height: 400, x: undefined, y: undefined };
+    @observable _menuDimensions: { width: number; height: number } = { width: 400, height: 400 };
+    @observable _editing: boolean = false;
 
-        template.fields.forEach((field, i) => {
-            cols.forEach((col, v) => {
-                if (colMatchesField(col, field)) {
-                    matches[i].push(v);
-                }
-            });
-        });
+    constructor(props: FieldViewProps) {
+        super(props);
+        makeObservable(this);
+        DocCreatorMenu.Instance = this;
+        //setTimeout(() => this.generateTemplates(''));
+    }
 
-        return matches;
+    @action setDataViz = (dataViz: DataVizBox) => {
+        this._dataViz = dataViz;
+    };
+    @action setTemplateDocs = (docs: Doc[]) => {
+        this._templateDocs = docs.map(doc => (doc.annotationOn ? DocCast(doc.annotationOn) : doc));
+    };
+    @action setGSuggestedTemplates = (docs: Doc[]) => {
+        this._suggestedTemplates = docs;
     };
 
-    maxMatches = (fieldsCt: number, matches: number[][]) => {
-        const used: boolean[] = Array(fieldsCt).fill(false);
-        const mt: number[] = Array(fieldsCt).fill(-1);
-
-        const augmentingPath = (v: number): boolean => {
-            if (used[v]) return false;
-            used[v] = true;
-            for (const to of matches[v]) {
-                if (mt[to] === -1 || augmentingPath(mt[to])) {
-                    mt[to] = v;
-                    return true;
-                }
-            }
-            return false;
-        };
+    @computed get docsToRender() {
+        return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : [];
+    }
 
-        for (let v = 0; v < fieldsCt; ++v) {
-            used.fill(false);
-            augmentingPath(v);
+    @computed get rowsCount() {
+        switch (this._layout.type) {
+            case LayoutType.Row:
+            case LayoutType.Stacked:
+                return 1;
+            case LayoutType.Column:
+                return this.docsToRender.length;
+            case LayoutType.Grid:
+                return Math.ceil(this.docsToRender.length / (this._layout.columns ?? 1)) ?? 0;
+            default:
+                return 0;
         }
+    }
 
-        let count: number = 0;
-
-        for (let i = 0; i < fieldsCt; ++i) {
-            if (mt[i] !== -1) ++count;
+    @computed get columnsCount() {
+        switch (this._layout.type) {
+            case LayoutType.Row:
+                return this.docsToRender.length;
+            case LayoutType.Column:
+            case LayoutType.Stacked:
+                return 1;
+            case LayoutType.Grid:
+                return this._layout.columns ?? 0;
+            default:
+                return 0;
         }
+    }
 
-        return count;
-    };
-
-    findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => {
-        let validTemplates: any[] = [];
-        templates.forEach(template => {
-            const numFields = template.fields.length;
-            if (!(numFields === cols.length)) return;
-            const matches = this.matchesForTemplate(template, cols);
-            if (this.maxMatches(numFields, matches) === numFields) {
-                validTemplates.push(template.title);
-            }
-        });
+    @computed get selectedFields() {
+        return StrListCast(this._dataViz?.layoutDoc._dataViz_axes);
+    }
 
-        validTemplates = validTemplates.map(title => TemplateLayouts.getTemplateByTitle(title));
+    @computed get fieldsInfos(): Col[] {
+        const colInfo = this._dataViz?.colsInfo;
+        return this.selectedFields
+            .map(field => {
+                const fieldInfo = colInfo?.get(field);
 
-        return validTemplates;
-    };
+                const col: Col = {
+                    title: field,
+                    type: fieldInfo?.type ?? TemplateFieldType.UNSET,
+                    desc: fieldInfo?.desc ?? '',
+                    sizes: fieldInfo?.sizes ?? [TemplateFieldSize.MEDIUM],
+                };
 
-    // createColumnField = (template: TemplateDocInfos, field: Field, column: Col): Doc => {
+                if (fieldInfo?.defaultContent !== undefined) {
+                    col.defaultContent = fieldInfo.defaultContent;
+                }
 
-    //     if (field.subfields) {
-    //         const doc = FieldFuncs.FreeformField({
-    //             tl: field.tl,
-    //             br: field.br },
-    //             template.height,
-    //             template.width,
-    //             column.title,
-    //             '',
-    //             field.opts
-    //         );
+                return col;
+            })
+            .concat(this._columns);
+    }
 
-    //         field.subfields[1].forEach(f => {
-    //             const fDoc = ()
-    //         })
+    @computed get canMakeDocs() {
+        return this._selectedTemplate !== undefined && this._layout !== undefined;
+    }
 
-    //     }
+    get bounds(): { t: number; b: number; l: number; r: number } {
+        const rect = this._ref?.getBoundingClientRect();
+        const bounds = { t: rect?.top ?? 0, b: rect?.bottom ?? 0, l: rect?.left ?? 0, r: rect?.right ?? 0 };
+        return bounds;
+    }
 
-    //     return new Doc;
-    // }
+    setUpButtonClick = (e: React.PointerEvent, func: () => void) => {
+        setupMoveUpEvents(
+            this,
+            e,
+            returnFalse,
+            emptyFunction,
+            undoable(clickEv => {
+                clickEv.stopPropagation();
+                clickEv.preventDefault();
+                func();
+            }, 'create docs')
+        );
+    };
 
-    /**
-     * Populates a preset template framework with content from a datavizbox or any AI-generated content.
-     * @param template the preloaded template framework being filled in
-     * @param assignments a list of template field numbers (from top to bottom) and their assigned columns from the linked dataviz
-     * @returns a doc containing the fully rendered template
-     */
-    fillPresetTemplate = async (template: TemplateDocInfos, assignments: { [field: string]: Col }): Promise<Doc> => {
-        const wordLimit = (size: TemplateFieldSize) => {
-            switch (size) {
-                case TemplateFieldSize.TINY:
-                    return 2;
-                case TemplateFieldSize.SMALL:
-                    return 5;
-                case TemplateFieldSize.MEDIUM:
-                    return 20;
-                case TemplateFieldSize.LARGE:
-                    return 50;
-                case TemplateFieldSize.HUGE:
-                    return 100;
-                default:
-                    return 10;
-            }
-        };
+    @action
+    onPointerDown = (e: PointerEvent) => {
+        this._mouseX = e.clientX;
+        this._mouseY = e.clientY;
+    };
 
-        const renderTextCalls = async (): Promise<Doc[]> => {
-            const rendered: Doc[] = [];
+    @action
+    onPointerUp = (e: PointerEvent) => {
+        if (this._resizing) {
+            this._initDimensions.width = this._menuDimensions.width;
+            this._initDimensions.height = this._menuDimensions.height;
+            this._initDimensions.x = this._pageX;
+            this._initDimensions.y = this._pageY;
+            document.removeEventListener('pointermove', this.onResize);
+            SnappingManager.SetIsResizing(undefined);
+            this._resizing = false;
+        }
+        if (this._dragging) {
+            document.removeEventListener('pointermove', this.onDrag);
+            this._dragging = false;
+        }
+        if (e.button !== 2 && !e.ctrlKey) return;
+        const curX = e.clientX;
+        const curY = e.clientY;
+        if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) {
+            this._shouldDisplay = false;
+        }
+    };
 
-            if (GPTTextCalls.length) {
-                try {
-                    const prompt = fieldContent + GPTTextAssignment;
+    componentDidMount() {
+        document.addEventListener('pointerdown', this.onPointerDown, true);
+        document.addEventListener('pointerup', this.onPointerUp);
+        this._disposers.templates = reaction(
+            () => this._templateDocs.slice(),
+            docs => this.updateIcons(docs)
+        );
+        this._disposers.gpt = reaction(
+            () => this._suggestedTemplates.slice(),
+            docs => this.updateIcons(docs)
+        );
+        //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')})
+        this._disposers.lightbox = reaction(
+            () => LightboxView.LightboxDoc(),
+            doc => {
+                doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu();
+            }
+        );
+        //this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', desc: ''}}))
+    }
 
-                    const res = await gptAPICall(prompt, GPTCallType.FILL);
+    componentWillUnmount() {
+        Object.values(this._disposers).forEach(disposer => disposer?.());
+        document.removeEventListener('pointerdown', this.onPointerDown, true);
+        document.removeEventListener('pointerup', this.onPointerUp);
+    }
 
-                    if (res) {
-                        const assignments: { [title: string]: { number: string; content: string } } = JSON.parse(res);
-                        //console.log('assignments', GPTAssignments, 'assignment string', GPTAssignmentString, 'field content', fieldContent, 'response', res, 'assignments', assignments);
-                        Object.entries(assignments).forEach(([title, info]) => {
-                            const field: Field = template.fields[Number(info.number)];
-                            const col = this.getColByTitle(title);
+    updateIcons = (docs: Doc[]) => {
+        console.log('called');
+        docs.map(this.getIcon);
+    };
 
-                            const doc = FieldUtils.TextField(
-                                {
-                                    tl: field.tl,
-                                    br: field.br,
-                                },
-                                template.height,
-                                template.width,
-                                col.title,
-                                info.content ?? '',
-                                field.opts
-                            );
+    @action
+    updateSelectedCols = (cols: string[]) => {
+        this._selectedCols;
+    };
 
-                            rendered.push(doc);
-                        });
-                    }
-                } catch (err) {
-                    console.log(err);
-                }
-            }
+    @action
+    toggleDisplay = (x: number, y: number) => {
+        if (this._shouldDisplay) {
+            this._shouldDisplay = false;
+        } else {
+            this._pageX = x;
+            this._pageY = y;
+            this._shouldDisplay = true;
+        }
+    };
 
-            return rendered;
-        };
+    @action
+    closeMenu = () => {
+        this._shouldDisplay = false;
+    };
 
-        const createGeneratedImage = async (fieldNum: string, col: Col, prompt: string) => {
-            const url = await this.generateGPTImage(prompt);
-            const field: Field = template.fields[Number(fieldNum)];
-            const doc = FieldUtils.ImageField(
-                {
-                    tl: field.tl,
-                    br: field.br,
-                },
-                template.height,
-                template.width,
-                col.title,
-                url ?? '',
-                field.opts
-            );
+    @action
+    openMenu = () => {
+        const allTemplates = this._templateDocs.concat(this._suggestedTemplates);
+        this._shouldDisplay = true;
+        this.updateIcons(allTemplates);
+    };
 
-            return doc;
+    @action
+    onResizePointerDown = (e: React.PointerEvent<HTMLDivElement>): void => {
+        this._resizing = true;
+        document.addEventListener('pointermove', this.onResize);
+        SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them
+        e.stopPropagation();
+        const id = (this._resizeHdlId = e.currentTarget.className);
+        const pad = id.includes('Left') || id.includes('Right') ? Number(getComputedStyle(e.target as Element).width.replace('px', '')) / 2 : 0;
+        const bounds = e.currentTarget.getBoundingClientRect();
+        this._offset = {
+            x: id.toLowerCase().includes('left') ? bounds.right - e.clientX - pad : bounds.left - e.clientX + pad, //
+            y: id.toLowerCase().includes('top') ? bounds.bottom - e.clientY - pad : bounds.top - e.clientY + pad,
         };
+        this._resizeUndo = UndoManager.StartBatch('drag resizing');
+        this._snapPt = { x: e.pageX, y: e.pageY };
+    };
 
-        const renderImageCalls = async (): Promise<Doc[]> => {
-            const rendered: Doc[] = [];
-            const calls = GPTIMGCalls;
+    @action
+    onResize = (e: PointerEvent): boolean => {
+        const dragHdl = this._resizeHdlId.split(' ')[1];
+        const thisPt = DragManager.snapDrag(e, -this._offset.x, -this._offset.y, this._offset.x, this._offset.y);
 
-            if (calls.length) {
-                try {
-                    const renderedImages: Doc[] = await Promise.all(
-                        calls.map(async ([fieldNum, col]) => {
-                            const sysPrompt =
-                                'Your job is to create a prompt for an AI image generator to help it generate an image based on existing content in a template and a user prompt. Your prompt should focus heavily on visual elements to help the image generator; avoid unecessary info that might distract it. ONLY INCLUDE THE PROMPT, NO OTHER TEXT OR EXPLANATION. The existing content is as follows: ' +
-                                fieldContent +
-                                ' **** The user prompt is: ' +
-                                col.desc;
+        const { scale, refPt, transl } = this.getResizeVals(thisPt, dragHdl);
+        !this._interactionLock && runInAction(async () => {   // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate)
+        this._interactionLock = true;
+        const scaleAspect = {x: scale.x, y: scale.y};
+        this.resizeView(refPt, scaleAspect, transl); // prettier-ignore
+        await new Promise<boolean|undefined>(res => { setTimeout(() => { res(this._interactionLock = undefined)})});
+        }); // prettier-ignore
+        return true;
+    };
 
-                            const prompt = await gptAPICall(sysPrompt, GPTCallType.COMPLETEPROMPT);
-                            console.log(sysPrompt, prompt);
+    @action
+    onDrag = (e: PointerEvent): boolean => {
+        this._pageX = e.pageX - (this._startPos?.x ?? 0);
+        this._pageY = e.pageY - (this._startPos?.y ?? 0);
+        this._initDimensions.x = this._pageX;
+        this._initDimensions.y = this._pageY;
+        return true;
+    };
 
-                            return createGeneratedImage(fieldNum, col, prompt);
-                        })
-                    );
+    getResizeVals = (thisPt: { x: number; y: number }, dragHdl: string) => {
+        const [w, h] = [this._initDimensions.width, this._initDimensions.height];
+        const [moveX, moveY] = [thisPt.x - this._snapPt.x, thisPt.y - this._snapPt.y];
+        let vals: { scale: { x: number; y: number }; refPt: [number, number]; transl: { x: number; y: number } };
+        switch (dragHdl) {
+            case 'topLeft':    vals = { scale: { x: 1 - moveX / w, y: 1 -moveY / h },  refPt: [this.bounds.r, this.bounds.b], transl: {x: moveX, y: moveY } }; break;
+            case 'topRight':   vals = { scale: { x: 1 + moveX / w, y: 1 -moveY / h },  refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break;
+            case 'top':        vals = { scale: { x: 1,             y: 1 -moveY / h },  refPt: [this.bounds.l, this.bounds.b], transl: {x: 0, y: moveY } }; break;
+            case 'left':       vals = { scale: { x: 1 - moveX / w, y: 1 },             refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break;
+            case 'bottomLeft': vals = { scale: { x: 1 - moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.r, this.bounds.t], transl: {x: moveX, y: 0 } }; break;
+            case 'right':      vals = { scale: { x: 1 + moveX / w, y: 1 },             refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
+            case 'bottomRight':vals = { scale: { x: 1 + moveX / w, y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
+            case 'bottom':     vals = { scale: { x: 1,             y: 1 + moveY / h }, refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
+            default:           vals = { scale: { x: 1,             y: 1 },             refPt: [this.bounds.l, this.bounds.t], transl: {x: 0, y: 0 } }; break;
+        } // prettier-ignore
+        return vals;
+    };
 
-                    const renderedTemplates: Doc[] = await Promise.all(renderedImages);
-                    renderedTemplates.forEach(doc => rendered.push(doc));
-                } catch (e) {
-                    console.log(e);
-                }
-            }
+    resizeView = (refPt: number[], scale: { x: number; y: number }, translation: { x: number; y: number }) => {
+        if (this._initDimensions.x === undefined) this._initDimensions.x = this._pageX;
+        if (this._initDimensions.y === undefined) this._initDimensions.y = this._pageY;
+        const { height, width, x, y } = this._initDimensions;
 
-            return rendered;
-        };
+        this._menuDimensions.width = Math.max(300, scale.x * width);
+        this._menuDimensions.height = Math.max(200, scale.y * height);
+        this._pageX = x + translation.x;
+        this._pageY = y + translation.y;
+    };
 
-        const fields: Doc[] = [];
+    async getIcon(doc: Doc) {
+        const docView = DocumentView.getDocumentView(doc);
+        if (docView) {
+            docView.ComponentView?.updateIcon?.();
+            return new Promise<ImageField | undefined>(res => setTimeout(() => res(ImageCast(docView.Document.icon)), 500));
+        }
+        return undefined;
+    }
 
-        const GPTAssignments = Object.entries(assignments).filter(([f, col]) => this._columns.includes(col));
-        const nonGPTAssignments: [string, Col][] = Object.entries(assignments).filter(a => !GPTAssignments.includes(a));
-        const GPTTextCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.TEXT);
-        const GPTIMGCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.VISUAL);
+    @action updateSelectedTemplate = (template: Doc) => {
+        if (this._selectedTemplate === template) {
+            this._selectedTemplate = undefined;
+            return;
+        } else {
+            this._selectedTemplate = template;
+            MakeTemplate(template);
+        }
+    };
 
-        const stringifyGPTInfo = (calls: [string, Col][]): string => {
-            let string: string = '*** COLUMN INFO:';
-            calls.forEach(([fieldNum, col]) => {
-                string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`;
-            });
-            return (string += ' ***');
-        };
+    @action updateSelectedSavedLayout = (layout: DataVizTemplateLayout) => {
+        this._layout.xMargin = layout.layout.xMargin;
+        this._layout.yMargin = layout.layout.yMargin;
+        this._layout.type = layout.layout.type;
+        this._layout.columns = layout.columns;
+    };
 
-        const GPTTextAssignment = stringifyGPTInfo(GPTTextCalls);
+    isSelectedLayout = (layout: DataVizTemplateLayout) => {
+        return this._layout.xMargin === layout.layout.xMargin && this._layout.yMargin === layout.layout.yMargin && this._layout.type === layout.layout.type && this._layout.columns === layout.columns;
+    };
 
-        let fieldContent: string = '';
-
-        Object.entries(nonGPTAssignments).forEach(([f, strCol]) => {
-            const field: Field = template.fields[Number(f)];
-            const col = strCol[1];
+    @action
+    generateTemplates = async (inputText: string) => {
+        ++this._callCount;
+        const origCount = this._callCount;
 
-            const doc = (col.type === TemplateFieldType.VISUAL ? FieldUtils.ImageField : FieldUtils.TextField)(
-                {
-                    tl: field.tl,
-                    br: field.br,
-                },
-                template.height,
-                template.width,
-                col.title,
-                col.defaultContent ?? '',
-                field.opts
-            );
+        let prompt: string = `(#${origCount}) Please generate for the fields:`;
+        this.selectedFields?.forEach(field => (prompt += ` ${field},`));
+        prompt += ` (-----NOT A FIELD-----) Additional prompt: ${inputText}`;
 
-            fieldContent += `--- Field #${f} (title: ${col.title}): ${col.defaultContent ?? ''} ---`;
+        this._GPTLoading = true;
 
-            fields.push(doc);
-        });
+        try {
+            const res = await gptAPICall(prompt, GPTCallType.TEMPLATE);
 
-        template.decorations.forEach(dec => {
-            const doc = FieldUtils.FreeformField(
-                {
-                    tl: dec.tl,
-                    br: dec.br,
-                },
-                template.height,
-                template.width,
-                '',
-                '',
-                dec.opts
-            );
+            if (res && this._callCount === origCount) {
+                this._suggestedTemplates = [];
+                const templates: { template_type: string; fieldVals: { title: string; tlx: string; tly: string; brx: string; bry: string }[] }[] = JSON.parse(res);
+                this.createGeneratedTemplates(templates, 500, 500);
+            }
+        } catch (err) {
+            console.error(err);
+        }
+    };
 
-            fields.push(doc);
-        });
+    @action
+    createGeneratedTemplates = (layouts: { template_type: string; fieldVals: { title: string; tlx: string; tly: string; brx: string; bry: string }[] }[], tempWidth: number, tempHeight: number) => {
+        const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+        const GPTTemplates: Doc[] = [];
 
-        const createMainDoc = (): Doc => {
-            const main = Docs.Create.FreeformDocument(fields, {
-                _height: template.height,
-                _width: template.width,
-                title: template.title,
-                backgroundColor: template.opts.backgroundColor,
-                _layout_borderRounding: `${template.opts.cornerRounding}px` ?? '0px',
-                borderWidth: template.opts.borderWidth,
-                borderColor: template.opts.borderColor,
-                x: 40000,
-                y: 40000,
+        layouts.forEach(layout => {
+            const fields: Doc[] = layout.fieldVals.map(field => {
+                const left: number = (Number(field.tlx) * tempWidth) / 2;
+                const top: number = Number(field.tly) * tempHeight / 2; //prettier-ignore
+                const right: number = (Number(field.brx) * tempWidth) / 2;
+                const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore
+                const height = bottom - top;
+                const width = right - left;
+                const doc = !field.title.includes('$$')
+                    ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height / 2}` })
+                    : Docs.Create.ImageDocument('', { _height: height, _width: width, title: field.title.replace(/\$\$/g, ''), x: left, y: top });
+                return doc;
             });
 
-            const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-            mainCollection.addDocument(main);
-
-            return main;
-        };
+            const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400000, y: 400000 });
 
-        const textCalls = await renderTextCalls();
-        const imageCalls = await renderImageCalls();
+            mainCollection.addDocument(template);
 
-        textCalls.forEach(doc => {
-            fields.push(doc);
-        });
-        imageCalls.forEach(doc => {
-            fields.push(doc);
+            GPTTemplates.push(template);
         });
 
-        return createMainDoc();
-    };
-
-    compileFieldDescriptions = (templates: TemplateDocInfos[]): string => {
-        let descriptions: string = '';
-        templates.forEach(template => {
-            descriptions += `----------  NEW TEMPLATE TO INCLUDE: Description of template ${template.title}'s fields: `;
-            template.fields.forEach((field, index) => {
-                descriptions += `{Field #${index}: ${field.description}} `;
-            });
-        });
+        setTimeout(() => {
+            this.setGSuggestedTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/
+        }, 100);
 
-        return descriptions;
+        this.forceUpdate();
     };
 
-    compileColDescriptions = (cols: Col[]): string => {
-        let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:';
-        cols.forEach(col => (descriptions += `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `));
-
-        return descriptions;
+    editTemplate = (doc: Doc) => {
+        //this.closeMenu();
+        DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight);
+        DocumentView.DeselectAll();
+        Doc.UnBrushDoc(doc);
     };
 
-    getColByTitle = (title: string) => {
-        return this.fieldsInfos.filter(col => col.title === title)[0];
+    removeTemplate = (doc: Doc) => {
+        this._templateDocs.splice(this._templateDocs.indexOf(doc), 1);
     };
 
-    @action
-    assignColsToFields = async (templates: TemplateDocInfos[], cols: Col[]): Promise<[TemplateDocInfos, { [field: number]: Col }][]> => {
-        const fieldDescriptions: string = this.compileFieldDescriptions(templates);
-        const colDescriptions: string = this.compileColDescriptions(cols);
+    testTemplate = async () => {
+        this.updateIcons(this._suggestedTemplates.slice());
+        this.forceUpdate();
 
-        const inputText = fieldDescriptions.concat(colDescriptions);
+        // try {
+        //     const res = await gptImageCall('Image of panda eating a cookie');
 
-        ++this._callCount;
-        const origCount = this._callCount;
+        //     if (res) {
+        //         const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res });
 
-        let prompt: string = `(${origCount}) ${inputText}`;
+        //         console.log(result);
+        //     }
+        // } catch (e) {
+        //     console.log(e);
+        // }
+    };
 
-        this._GPTLoading = true;
+    @action addField = () => {
+        const newFields: Col[] = this._columns.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [] }]);
+        this._columns = newFields;
+    };
 
-        try {
-            const res = await gptAPICall(prompt, GPTCallType.TEMPLATE);
+    @action removeField = (field: { title: string; type: string; desc: string }) => {
+        if (this._dataViz?.axes.includes(field.title)) {
+            this._dataViz.selectAxes(this._dataViz.axes.filter(col => col !== field.title));
+        } else {
+            const toRemove = this._columns.filter(f => f === field);
+            if (!toRemove) return;
 
-            if (res && this._callCount === origCount) {
-                const assignments: { [templateTitle: string]: { [field: string]: string } } = JSON.parse(res);
-                const brokenDownAssignments: [TemplateDocInfos, { [field: number]: Col }][] = [];
+            if (toRemove.length > 1) {
+                while (toRemove.length > 1) {
+                    toRemove.pop();
+                }
+            }
 
-                Object.entries(assignments).forEach(([tempTitle, assignment]) => {
-                    const template = TemplateLayouts.getTemplateByTitle(tempTitle);
-                    if (!template) return;
-                    const toObj = Object.entries(assignment).reduce(
-                        (a, [fieldNum, colTitle]) => {
-                            a[Number(fieldNum)] = this.getColByTitle(colTitle);
-                            return a;
-                        },
-                        {} as { [field: number]: Col }
-                    );
-                    brokenDownAssignments.push([template, toObj]);
-                });
-                return brokenDownAssignments;
+            if (this._columns.length === 1) {
+                this._columns = [];
+            } else {
+                this._columns.splice(this._columns.indexOf(toRemove[0]), 1);
             }
-        } catch (err) {
-            console.error(err);
         }
-
-        return [];
     };
 
-    generatePresetTemplates = async () => {
-        this._dataViz?.updateColDefaults();
+    @action setColTitle = (column: Col, title: string) => {
+        if (this.selectedFields.includes(column.title)) {
+            this._dataViz?.setColumnTitle(column.title, title);
+        } else {
+            column.title = title;
+        }
+        this.forceUpdate();
+    };
 
-        const cols = this.fieldsInfos;
-        const templates = this.findValidTemplates(cols, TemplateLayouts.allTemplates);
+    @action setColType = (column: Col, type: TemplateFieldType) => {
+        if (this.selectedFields.includes(column.title)) {
+            this._dataViz?.setColumnType(column.title, type);
+        } else {
+            column.type = type;
+        }
+        this.forceUpdate();
+    };
 
-        const assignments: [TemplateDocInfos, { [field: number]: Col }][] = await this.assignColsToFields(templates, cols);
+    modifyColSizes = (column: Col, size: TemplateFieldSize, valid: boolean) => {
+        if (this.selectedFields.includes(column.title)) {
+            this._dataViz?.modifyColumnSizes(column.title, size, valid);
+        } else {
+            if (!valid && column.sizes.includes(size)) {
+                column.sizes.splice(column.sizes.indexOf(size), 1);
+            } else if (valid && !column.sizes.includes(size)) {
+                column.sizes.push(size);
+            }
+        }
+        this.forceUpdate();
+    };
 
-        const renderedTemplatePromises: Promise<Doc>[] = assignments.map(([template, assignments]) => this.fillPresetTemplate(template, assignments));
+    setColDesc = (column: Col, desc: string) => {
+        if (this.selectedFields.includes(column.title)) {
+            this._dataViz?.setColumnDesc(column.title, desc);
+        } else {
+            column.desc = desc;
+        }
+        this.forceUpdate();
+    };
 
-        const renderedTemplates: Doc[] = await Promise.all(renderedTemplatePromises);
+    generateGPTImage = async (prompt: string): Promise<string | undefined> => {
+        console.log(prompt);
 
-        setTimeout(() => {
-            this.setGSuggestedTemplates(renderedTemplates);
-            this._GPTLoading = false;
-        });
-    };
+        try {
+            const res = await gptImageCall(prompt);
 
-    @action setExpandedView = (info: { icon: ImageField; doc: Doc } | undefined) => {
-        if (info) {
-            const doc = info.doc;
-            const wrapper: Doc = Docs.Create.FreeformDocument([info.doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''});
-            const newInfo = {icon: new ImageField(''), doc: wrapper}
-            this._expandedPreview = newInfo;
-        } else {
-            this._expandedPreview = info;
+            if (res) {
+                const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res });
+                const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client);
+                return source;
+            }
+        } catch (e) {
+            console.log(e);
         }
     };
 
-    get editingWindow(){
-        const doc = this._expandedPreview?.doc ?? new Doc();
-        const rendered = 
-            <div className="docCreatorMenu-expanded-template-preview">
-                <CollectionFreeFormView
-                    Document={this._expandedPreview!.doc}
-                    docViewPath={returnEmptyDocViewList}
-                    childLayoutTemplate={() => Cast(doc.childLayoutTemplate, Doc, null)}
-                    isContentActive={emptyFunction}
-                    isAnyChildContentActive={() => true}
-                    select={emptyFunction}
-                    isSelected={returnFalse}
-                    fieldKey={Doc.LayoutFieldKey(doc)}
-                    addDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    removeDocument={returnFalse}
-                    PanelWidth={() => this._menuDimensions.width - 10}
-                    PanelHeight={() => this._menuDimensions.height - 60}
-                    ScreenToLocalTransform={Transform.Identity}
-                    renderDepth={5}
-                    whenChildContentsActiveChanged={emptyFunction}
-                    focus={emptyFunction}
-                    styleProvider={DefaultStyleProvider}
-                    addDocTab={this._props.addDocTab}
-                    // eslint-disable-next-line no-use-before-define
-                    pinToPres={() => undefined}
-                    childFilters={returnEmptyFilter}
-                    childFiltersByRanges={returnEmptyFilter}
-                    searchFilterDocs={returnEmptyDoclist}
-                    fitContentsToBox={returnTrue}
-                    xPadding={0}
-                    yPadding={0}
-                />
-            </div>
+    matchesForTemplate = (template: TemplateDocInfos, cols: Col[]): number[][] => {
+        const colMatchesField = (col: Col, field: Field) => {
+            return field.sizes?.some(size => col.sizes?.includes(size)) && field.types?.includes(col.type);
+        };
 
+        const matches: number[][] = Array(template.fields.length)
+            .fill([])
+            .map(() => []);
 
-        return (
-            <div className="docCreatorMenu-expanded-template-preview">
-                <div className="top-panel"/>
-                {rendered}
-                <div className="right-buttons-panel">
-                    <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this.setUpButtonClick(e, () => {this._expandedPreview && this.updateIcons(this._suggestedTemplates.slice()); 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>
+        template.fields.forEach((field, i) => {
+            cols.forEach((col, v) => {
+                if (colMatchesField(col, field)) {
+                    matches[i].push(v);
+                }
+            });
+        });
 
-        );
-    }
+        return matches;
+    };
 
-    get templatesPreviewContents() {
-        const renderedTemplates: Doc[] = [];
+    maxMatches = (fieldsCt: number, matches: number[][]) => {
+        const used: boolean[] = Array(fieldsCt).fill(false);
+        const mt: number[] = Array(fieldsCt).fill(-1);
 
-        const GPTOptions = <div></div>;
+        const augmentingPath = (v: number): boolean => {
+            if (used[v]) return false;
+            used[v] = true;
+            for (const to of matches[v]) {
+                if (mt[to] === -1 || augmentingPath(mt[to])) {
+                    mt[to] = v;
+                    return true;
+                }
+            }
+            return false;
+        };
 
-        //<img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} />
+        for (let v = 0; v < fieldsCt; ++v) {
+            used.fill(false);
+            augmentingPath(v);
+        }
 
-        return (
-            <div className={`docCreatorMenu-templates-view`}>
-                {this._expandedPreview ? (
-                    this.editingWindow
-                ) : (
-                    <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>
-                            <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>
-                        <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>
-                                {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>
-        );
-    }
+        let count: number = 0;
 
-    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>
-        );
-    }
+        for (let i = 0; i < fieldsCt; ++i) {
+            if (mt[i] !== -1) ++count;
+        }
 
-    @action updateXMargin = (input: string) => {
-        this._layout.xMargin = Number(input);
-    };
-    @action updateYMargin = (input: string) => {
-        this._layout.yMargin = Number(input);
-    };
-    @action updateColumns = (input: string) => {
-        this._layout.columns = Number(input);
+        return count;
     };
 
-    get layoutConfigOptions() {
-        const optionInput = (icon: string, func: Function, def?: number, key?: string, noMargin?: boolean) => {
-            return (
-                <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" />
-                </div>
-            );
-        };
+    findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => {
+        const validTemplates: string[] = [];
+        templates.forEach(template => {
+            const numFields = template.fields.length;
+            if (!(numFields === cols.length)) return;
+            const matches = this.matchesForTemplate(template, cols);
+            if (this.maxMatches(numFields, matches) === numFields) {
+                validTemplates.push(template.title);
+            }
+        });
 
-        switch (this._layout.type) {
-            case LayoutType.Row:
-                return <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>;
-            case LayoutType.Grid:
-                return (
-                    <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)}
-                    </div>
-                );
-            case LayoutType.Stacked:
-                return null;
-            default:
-                break;
-        }
-    }
+        return validTemplates
+            .map(title => TemplateLayouts.getTemplateByTitle(title))
+            .filter(t => t)
+            .map(t => t!);
+    };
 
-    // doc = () => {
-    //     return Docs.Create.FreeformDocument([], { _height: 200, _width: 200, title: 'title'});
-    // }
+    // createColumnField = (template: TemplateDocInfos, field: Field, column: Col): Doc => {
 
-    screenToLocalTransform = () => this._props.ScreenToLocalTransform();
+    //     if (field.subfields) {
+    //         const doc = FieldFuncs.FreeformField({
+    //             tl: field.tl,
+    //             br: field.br },
+    //             template.height,
+    //             template.width,
+    //             column.title,
+    //             '',
+    //             field.opts
+    //         );
 
-    layoutPreviewContents = (outerSpan: number, altLayout?: DataVizTemplateLayout, small: boolean = false, id?: number) => {
-        const doc: Doc | undefined = altLayout ? altLayout.template : this._selectedTemplate;
-        if (!doc) return;
+    //         field.subfields[1].forEach(f => {
+    //             const fDoc = ()
+    //         })
 
-        const layout = altLayout ? altLayout.layout : this._layout;
+    //     }
 
-        const docWidth: number = Number(doc._width);
-        const docHeight: number = Number(doc._height);
-        const horizontalSpan: number = (docWidth + layout.xMargin) * (altLayout ? altLayout.columns : this.columnsCount) - layout.xMargin;
-        const verticalSpan: number = (docHeight + layout.yMargin) * (altLayout ? altLayout.rows : this.rowsCount) - layout.yMargin;
-        const largerSpan: number = horizontalSpan > verticalSpan ? horizontalSpan : verticalSpan;
-        const scaledDown = (input: number) => {
-            return input / ((largerSpan / outerSpan) * this._layoutPreviewScale);
+    //     return new Doc;
+    // }
+
+    /**
+     * Populates a preset template framework with content from a datavizbox or any AI-generated content.
+     * @param template the preloaded template framework being filled in
+     * @param assignments a list of template field numbers (from top to bottom) and their assigned columns from the linked dataviz
+     * @returns a doc containing the fully rendered template
+     */
+    fillPresetTemplate = async (template: TemplateDocInfos, assignments: { [field: string]: Col }): Promise<Doc> => {
+        const fields: Doc[] = [];
+        const wordLimit = (size: TemplateFieldSize) => {
+            switch (size) {
+                case TemplateFieldSize.TINY:   return 2;
+                case TemplateFieldSize.SMALL:  return 5;
+                case TemplateFieldSize.MEDIUM: return 20;
+                case TemplateFieldSize.LARGE:  return 50;
+                case TemplateFieldSize.HUGE:   return 100;
+                default:                       return 10;
+            } // prettier-ignore
+        };
+        const stringifyGPTInfo = (calls: [string, Col][]): string => {
+            let string: string = '*** COLUMN INFO:';
+            calls.forEach(([fieldNum, col]) => {
+                string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`;
+            });
+            return (string += ' ***');
         };
-        const fontSize = Math.min(scaledDown(docWidth / 3), scaledDown(docHeight / 3));
 
-        return (
-            // <div className='divvv' style={{width: 100, height: 100, border: `1px solid white`}}>
-            //     <CollectionFreeFormView
-            //         // eslint-disable-next-line react/jsx-props-no-spreading
-            //         {...this._props}
-            //         Document={new Doc()}
-            //         isContentActive={returnFalse}
-            //         setContentViewBox={emptyFunction}
-            //         NativeWidth={() => 100}
-            //         NativeHeight={() => 100}
-            //         pointerEvents={SnappingManager.IsDragging ? returnAll : returnNone}
-            //         isAnnotationOverlay
-            //         isAnnotationOverlayScrollable
-            //         childDocumentsActive={returnFalse}
-            //         fieldKey={this._props.fieldKey + '_annotations'}
-            //         dropAction={dropActionType.move}
-            //         select={emptyFunction}
-            //         addDocument={returnFalse}
-            //         removeDocument={returnFalse}
-            //         moveDocument={returnFalse}
-            //         renderDepth={this._props.renderDepth + 1}>
-            //         {null}
-            //     </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'} />
-                    </button>
-                    <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}
-                </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"
-                                    style={{
-                                        width: scaledDown(docWidth),
-                                        height: scaledDown(docHeight),
-                                        fontSize: fontSize,
-                                    }}>
-                                    {num}
-                                </div>
-                            ))
-                        )}
-                    </div>
+        const GPTAssignments = Object.entries(assignments).filter(([f, col]) => this._columns.includes(col));
+        const nonGPTAssignments: [string, Col][] = Object.entries(assignments).filter(a => !GPTAssignments.includes(a));
+        const GPTTextCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.TEXT);
+        const GPTIMGCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.VISUAL);
+        const GPTTextAssignment = stringifyGPTInfo(GPTTextCalls);
+
+        let fieldContent: string = '';
+
+        const renderTextCalls = async (): Promise<Doc[]> => {
+            const rendered: Doc[] = [];
+
+            if (GPTTextCalls.length) {
+                try {
+                    const prompt = fieldContent + GPTTextAssignment;
+
+                    const res = await gptAPICall(prompt, GPTCallType.FILL);
+
+                    if (res) {
+                        const asngs: { [title: string]: { number: string; content: string } } = JSON.parse(res);
+                        //console.log('assignments', GPTAssignments, 'assignment string', GPTAssignmentString, 'field content', fieldContent, 'response', res, 'assignments', assignments);
+                        Object.entries(asngs).forEach(([title, info]) => {
+                            const field: Field = template.fields[Number(info.number)];
+                            const col = this.getColByTitle(title);
+
+                            const doc = FieldUtils.TextField(
+                                {
+                                    tl: field.tl,
+                                    br: field.br,
+                                },
+                                template.height,
+                                template.width,
+                                col.title,
+                                info.content ?? '',
+                                field.opts
+                            );
+
+                            rendered.push(doc);
+                        });
+                    }
+                } catch (err) {
+                    console.log(err);
                 }
-            </div>
-        );
-    };
+            }
 
-    get optionsMenuContents() {
-        const layoutEquals = (layout: DataVizTemplateLayout) => {}; //TODO: ADD LATER
+            return rendered;
+        };
 
-        const layoutOption = (option: LayoutType, optStyle?: {}, specialFunc?: Function) => {
-            return (
-                <div
-                    className="docCreatorMenu-dropdown-option"
-                    style={optStyle}
-                    onPointerDown={e =>
-                        this.setUpButtonClick(e, () => {
-                            specialFunc?.();
-                            runInAction(() => (this._layout.type = option));
-                        })
-                    }>
-                    {option}
-                </div>
+        const createGeneratedImage = async (fieldNum: string, col: Col, prompt: string) => {
+            const url = await this.generateGPTImage(prompt);
+            const field: Field = template.fields[Number(fieldNum)];
+            const doc = FieldUtils.ImageField(
+                {
+                    tl: field.tl,
+                    br: field.br,
+                },
+                template.height,
+                template.width,
+                col.title,
+                url ?? '',
+                field.opts
             );
+
+            return doc;
         };
 
-        const selectionBox = (width: number, height: number, icon: string, specClass?: string, options?: JSX.Element[], manual?: boolean): JSX.Element => {
-            return (
-                <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>
-            );
+        const renderImageCalls = async (): Promise<Doc[]> => {
+            const rendered: Doc[] = [];
+            const calls = GPTIMGCalls;
+
+            if (calls.length) {
+                try {
+                    const renderedImages: Doc[] = await Promise.all(
+                        calls.map(async ([fieldNum, col]) => {
+                            const sysPrompt =
+                                'Your job is to create a prompt for an AI image generator to help it generate an image based on existing content in a template and a user prompt. Your prompt should focus heavily on visual elements to help the image generator; avoid unecessary info that might distract it. ONLY INCLUDE THE PROMPT, NO OTHER TEXT OR EXPLANATION. The existing content is as follows: ' +
+                                fieldContent +
+                                ' **** The user prompt is: ' +
+                                col.desc;
+
+                            const prompt = await gptAPICall(sysPrompt, GPTCallType.COMPLETEPROMPT);
+                            console.log(sysPrompt, prompt);
+
+                            return createGeneratedImage(fieldNum, col, prompt);
+                        })
+                    );
+
+                    const renderedTemplates: Doc[] = await Promise.all(renderedImages);
+                    renderedTemplates.forEach(doc => rendered.push(doc));
+                } catch (e) {
+                    console.log(e);
+                }
+            }
+
+            return rendered;
         };
 
-        const repeatOptions = [0, 1, 2, 3, 4, 5];
+        Object.entries(nonGPTAssignments).forEach(([f, strCol]) => {
+            const field: Field = template.fields[Number(f)];
+            const col = strCol[1];
 
-        return (
-            <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.Row)}
-                            {layoutOption(LayoutType.Column)}
-                            {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>
-                </div>
-                {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">
+            const doc = (col.type === TemplateFieldType.VISUAL ? FieldUtils.ImageField : FieldUtils.TextField)(
+                {
+                    tl: field.tl,
+                    br: field.br,
+                },
+                template.height,
+                template.width,
+                col.title,
+                col.defaultContent ?? '',
+                field.opts
+            );
+
+            fieldContent += `--- Field #${f} (title: ${col.title}): ${col.defaultContent ?? ''} ---`;
+
+            fields.push(doc);
+        });
+
+        template.decorations.forEach(dec => {
+            const doc = FieldUtils.FreeformField(
+                {
+                    tl: dec.tl,
+                    br: dec.br,
+                },
+                template.height,
+                template.width,
+                '',
+                '',
+                dec.opts
+            );
+
+            fields.push(doc);
+        });
+
+        const createMainDoc = (): Doc => {
+            const main = Docs.Create.FreeformDocument(fields, {
+                _height: template.height,
+                _width: template.width,
+                title: template.title,
+                backgroundColor: template.opts.backgroundColor,
+                _layout_borderRounding: `${template.opts.cornerRounding ?? 0}px`,
+                borderWidth: template.opts.borderWidth,
+                borderColor: template.opts.borderColor,
+                x: 40000,
+                y: 40000,
+            });
+
+            const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+            mainCollection.addDocument(main);
+
+            return main;
+        };
+
+        const textCalls = await renderTextCalls();
+        const imageCalls = await renderImageCalls();
+
+        textCalls.forEach(doc => {
+            fields.push(doc);
+        });
+        imageCalls.forEach(doc => {
+            fields.push(doc);
+        });
+
+        return createMainDoc();
+    };
+
+    compileFieldDescriptions = (templates: TemplateDocInfos[]): string => {
+        let descriptions: string = '';
+        templates.forEach(template => {
+            descriptions += `----------  NEW TEMPLATE TO INCLUDE: Description of template ${template.title}'s fields: `;
+            template.fields.forEach((field, index) => {
+                descriptions += `{Field #${index}: ${field.description}} `;
+            });
+        });
+
+        return descriptions;
+    };
+
+    compileColDescriptions = (cols: Col[]): string => {
+        let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:';
+        cols.forEach(col => (descriptions += `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `));
+
+        return descriptions;
+    };
+
+    getColByTitle = (title: string) => {
+        return this.fieldsInfos.filter(col => col.title === title)[0];
+    };
+
+    @action
+    assignColsToFields = async (templates: TemplateDocInfos[], cols: Col[]): Promise<[TemplateDocInfos, { [field: number]: Col }][]> => {
+        const fieldDescriptions: string = this.compileFieldDescriptions(templates);
+        const colDescriptions: string = this.compileColDescriptions(cols);
+
+        const inputText = fieldDescriptions.concat(colDescriptions);
+
+        ++this._callCount;
+        const origCount = this._callCount;
+
+        const prompt: string = `(${origCount}) ${inputText}`;
+
+        this._GPTLoading = true;
+
+        try {
+            const res = await gptAPICall(prompt, GPTCallType.TEMPLATE);
+
+            if (res && this._callCount === origCount) {
+                const assignments: { [templateTitle: string]: { [field: string]: string } } = JSON.parse(res);
+                const brokenDownAssignments: [TemplateDocInfos, { [field: number]: Col }][] = [];
+
+                Object.entries(assignments).forEach(([tempTitle, assignment]) => {
+                    const template = TemplateLayouts.getTemplateByTitle(tempTitle);
+                    if (!template) return;
+                    const toObj = Object.entries(assignment).reduce(
+                        (a, [fieldNum, colTitle]) => {
+                            a[Number(fieldNum)] = this.getColByTitle(colTitle);
+                            return a;
+                        },
+                        {} as { [field: number]: Col }
+                    );
+                    brokenDownAssignments.push([template, toObj]);
+                });
+                return brokenDownAssignments;
+            }
+        } catch (err) {
+            console.error(err);
+        }
+
+        return [];
+    };
+
+    generatePresetTemplates = async () => {
+        this._dataViz?.updateColDefaults();
+
+        const cols = this.fieldsInfos;
+        const templates = this.findValidTemplates(cols, TemplateLayouts.allTemplates);
+
+        const assignments: [TemplateDocInfos, { [field: number]: Col }][] = await this.assignColsToFields(templates, cols);
+
+        const renderedTemplatePromises: Promise<Doc>[] = assignments.map(([template, asgns]) => this.fillPresetTemplate(template, asgns));
+
+        const renderedTemplates: Doc[] = await Promise.all(renderedTemplatePromises);
+
+        setTimeout(() => {
+            this.setGSuggestedTemplates(renderedTemplates);
+            this._GPTLoading = false;
+        });
+    };
+
+    @action setExpandedView = (info: { icon: ImageField; doc: Doc } | undefined) => {
+        if (info) {
+            const doc = info.doc;
+            const wrapper: Doc = Docs.Create.FreeformDocument([info.doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: '' });
+            const newInfo = { icon: new ImageField(''), doc: wrapper };
+            this._expandedPreview = newInfo;
+        } else {
+            this._expandedPreview = info;
+        }
+    };
+
+    get editingWindow() {
+        const doc = this._expandedPreview?.doc ?? new Doc();
+        const rendered = (
+            <div className="docCreatorMenu-expanded-template-preview">
+                <CollectionFreeFormView
+                    Document={this._expandedPreview!.doc}
+                    docViewPath={returnEmptyDocViewList}
+                    childLayoutTemplate={() => Cast(doc.childLayoutTemplate, Doc, null)}
+                    isContentActive={emptyFunction}
+                    isAnyChildContentActive={() => true}
+                    select={emptyFunction}
+                    isSelected={returnFalse}
+                    fieldKey={Doc.LayoutFieldKey(doc)}
+                    addDocument={returnFalse}
+                    moveDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    PanelWidth={() => this._menuDimensions.width - 10}
+                    PanelHeight={() => this._menuDimensions.height - 60}
+                    ScreenToLocalTransform={Transform.Identity}
+                    renderDepth={5}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    focus={emptyFunction}
+                    styleProvider={DefaultStyleProvider}
+                    addDocTab={this._props.addDocTab}
+                    pinToPres={emptyFunction}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    fitContentsToBox={returnTrue}
+                    xPadding={0}
+                    yPadding={0}
+                />
+            </div>
+        );
+
+        return (
+            <div className="docCreatorMenu-expanded-template-preview">
+                <div className="top-panel" />
+                {rendered}
+                <div className="right-buttons-panel">
                     <button
-                        className="docCreatorMenu-save-layout-button"
+                        className="docCreatorMenu-menu-button section-reveal-options top-right"
                         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')
-                            )
+                            this.setUpButtonClick(e, () => {
+                                this._expandedPreview && this.updateIcons(this._suggestedTemplates.slice());
+                                this.setExpandedView(undefined);
+                            })
                         }>
-                        <FontAwesomeIcon icon="floppy-disk" />
+                        <FontAwesomeIcon icon="minimize" />
                     </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,
-                                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 };
-                                    this._dataViz?.createDocsFromTemplate(templateInfo);
-                                }, 'make docs')
-                            )
-                        }>
-                        <FontAwesomeIcon icon="plus" />
+                    <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>
         );
     }
 
-    get dashboardContents() {
-        const sizes: string[] = ['tiny', 'small', 'medium', 'large', 'huge'];
+    get templatesPreviewContents() {
+        const renderedTemplates: Doc[] = [];
 
-        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.removeField(field))} 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>
-                        <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>
+        const GPTOptions = <div></div>;
+
+        //<img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} />
+
+        return (
+            <div className={`docCreatorMenu-templates-view`}>
+                {this._expandedPreview ? (
+                    this.editingWindow
+                ) : (
+                    <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>
+                            <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
+                                                key={info.doc[Id]}
+                                                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>
-                    </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>
-                                    </>
-                                ))}
+                        <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={() => 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
+                                                key={info.doc[Id]}
+                                                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 className="desc-box">
-                        <div className="top-bar"> Prompt </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/prompt 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>
+                )}
             </div>
         );
     }
 
-    get renderSelectedViewType() {
-        switch (this._menuContent) {
-            case 'templates':
-                return this.templatesPreviewContents;
-            case 'options':
-                return this.optionsMenuContents;
-            case 'saved':
-                return this.savedLayoutsPreviewContents;
-            case 'dashboard':
-                return this.dashboardContents;
-            default:
-                return undefined;
-        }
+    get savedLayoutsPreviewContents() {
+        return (
+            <div className="docCreatorMenu-preview-container">
+                {this._savedLayouts.map((layout, index) => (
+                    <div
+                        key={layout.template[Id]}
+                        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>
+        );
     }
 
-    get resizePanes() {
-        const ref = this._ref?.getBoundingClientRect();
-        const height: number = ref?.height ?? 0;
-        const width: number = ref?.width ?? 0;
-
-        return [
-            <div className='docCreatorMenu-resizer top' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: -7}}/>, 
-            <div className='docCreatorMenu-resizer right' onPointerDown={this.onResizePointerDown} style={{height: height, left: width - 3, top: 0}}/>, 
-            <div className='docCreatorMenu-resizer bottom' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: height - 3}}/>, 
-            <div className='docCreatorMenu-resizer left' onPointerDown={this.onResizePointerDown} style={{height: height, left: -7, top: 0}}/>,
-            <div className='docCreatorMenu-resizer topRight' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: -10, cursor: 'nesw-resize'}}/>,
-            <div className='docCreatorMenu-resizer topLeft' onPointerDown={this.onResizePointerDown} style={{left: -10, top: -10, cursor: 'nwse-resize'}}/>,
-            <div className='docCreatorMenu-resizer bottomRight' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: height - 5, cursor: 'nwse-resize'}}/>,
-            <div className='docCreatorMenu-resizer bottomLeft' onPointerDown={this.onResizePointerDown} style={{left: -10, top: height - 5, cursor: 'nesw-resize'}}/>
-        ]; //prettier-ignore
-    }
+    @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);
+    };
 
-    render() {
-        const topButton = (icon: string, opt: string, func: Function, tag: string) => {
+    get layoutConfigOptions() {
+        const optionInput = (icon: string, func: (arg: string) => void, def?: number, key?: string, noMargin?: boolean) => {
             return (
-                <div className={`top-button-container ${tag} ${opt === this._menuContent ? 'selected' : ''}`}>
-                    <div
-                        className="top-button-content"
-                        onPointerDown={e =>
-                            this.setUpButtonClick(e, () =>
-                                runInAction(() => {
-                                    func();
-                                })
-                            )
-                        }>
-                        <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 IconProp} />
                     </div>
+                    <input defaultValue={def} onInput={e => func(e.currentTarget.value)} className="docCreatorMenu-input config layout-config" />
                 </div>
             );
         };
 
-        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,
-                        }}>
-                        {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">
-                                {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>
-                        </div>
-                        {this.renderSelectedViewType}
+        switch (this._layout.type) {
+            case LayoutType.Row:
+                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>;
+            case LayoutType.Grid:
+                return (
+                    <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)}
                     </div>
-                )}
-            </div>
-        );
+                );
+            case LayoutType.Stacked:
+                return null;
+            default:
+                break;
+        }
     }
-}
-
-export interface DataVizTemplateInfo {
-    doc: Doc;
-    layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number };
-    columns: number;
-    referencePos: { x: number; y: number };
-}
 
-export interface DataVizTemplateLayout {
-    template: Doc;
-    docsNumList: number[];
-    layout: { type: LayoutType; xMargin: number; yMargin: number; repeat: number };
-    columns: number;
-    rows: number;
-}
+    // doc = () => {
+    //     return Docs.Create.FreeformDocument([], { _height: 200, _width: 200, title: 'title'});
+    // }
 
-export enum TemplateFieldType {
-    TEXT = 'text',
-    VISUAL = 'visual',
-    UNSET = 'unset',
-}
+    screenToLocalTransform = () => this._props.ScreenToLocalTransform();
 
-export enum TemplateFieldSize {
-    TINY = 'tiny',
-    SMALL = 'small',
-    MEDIUM = 'medium',
-    LARGE = 'large',
-    HUGE = 'huge',
-}
+    layoutPreviewContents = (outerSpan: number, altLayout?: DataVizTemplateLayout, small: boolean = false, id?: number) => {
+        const doc: Doc | undefined = altLayout ? altLayout.template : this._selectedTemplate;
+        if (!doc) return;
 
-export type Col = {
-    sizes: TemplateFieldSize[];
-    desc: string;
-    title: string;
-    type: TemplateFieldType;
-    defaultContent?: string;
-};
+        const layout = altLayout ? altLayout.layout : this._layout;
 
-type Field = {
-    tl: [number, number];
-    br: [number, number];
-    opts: FieldOpts;
-    subfields?: Field[];
-    types?: TemplateFieldType[];
-    sizes?: TemplateFieldSize[];
-    isDecoration?: boolean;
-    description?: string;
-};
+        const docWidth: number = Number(doc._width);
+        const docHeight: number = Number(doc._height);
+        const horizontalSpan: number = (docWidth + layout.xMargin) * (altLayout ? altLayout.columns : this.columnsCount) - layout.xMargin;
+        const verticalSpan: number = (docHeight + layout.yMargin) * (altLayout ? altLayout.rows : this.rowsCount) - layout.yMargin;
+        const largerSpan: number = horizontalSpan > verticalSpan ? horizontalSpan : verticalSpan;
+        const scaledDown = (input: number) => {
+            return input / ((largerSpan / outerSpan) * this._layoutPreviewScale);
+        };
+        const fontSize = Math.min(scaledDown(docWidth / 3), scaledDown(docHeight / 3));
 
-// class ContentField implements Field {
-//     tl: [number, number];
-//     br: [number, number];
-//     opts: FieldOpts;
-//     subfields?: Field[];
-//     types?: TemplateFieldType[];
-//     sizes?: TemplateFieldSize[];
-//     description?: string;
+        return (
+            // <div className='divvv' style={{width: 100, height: 100, border: `1px solid white`}}>
+            //     <CollectionFreeFormView
+            //         // eslint-disable-next-line react/jsx-props-no-spreading
+            //         {...this._props}
+            //         Document={new Doc()}
+            //         isContentActive={returnFalse}
+            //         setContentViewBox={emptyFunction}
+            //         NativeWidth={() => 100}
+            //         NativeHeight={() => 100}
+            //         pointerEvents={SnappingManager.IsDragging ? returnAll : returnNone}
+            //         isAnnotationOverlay
+            //         isAnnotationOverlayScrollable
+            //         childDocumentsActive={returnFalse}
+            //         fieldKey={this._props.fieldKey + '_annotations'}
+            //         dropAction={dropActionType.move}
+            //         select={emptyFunction}
+            //         addDocument={returnFalse}
+            //         removeDocument={returnFalse}
+            //         moveDocument={returnFalse}
+            //         renderDepth={this._props.renderDepth + 1}>
+            //         {null}
+            //     </CollectionFreeFormView>
+            // </div>
+            <div className="docCreatorMenu-layout-preview-window-wrapper" id={String(id)}>
+                <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 *= 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}
+                </div>
+                {
+                    <div
+                        id={String(id)}
+                        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
+                                    key={num}
+                                    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>
+        );
+    };
 
-//     constructor( tl: [number, number], br: [number, number],
-//                         opts: FieldOpts, subfields?: Field[],
-//                         types?: TemplateFieldType[],
-//                         sizes?: TemplateFieldSize[],
-//                         description?: string) {
-//         this.tl = tl;
-//         this.br = br;
-//         this.opts = opts;
-//         this.subfields = subfields;
-//         this.types = types;
-//         this.sizes = sizes;
-//         this.description = description;
-//     }
+    get optionsMenuContents() {
+        const layoutEquals = (layout: DataVizTemplateLayout) => {}; //TODO: ADD LATER
 
-//     render = (content: any): Doc => {
-//         return new Doc;
-//     }
-// }
+        const layoutOption = (option: LayoutType, optStyle?: object, specialFunc?: () => void) => {
+            return (
+                <div
+                    className="docCreatorMenu-dropdown-option"
+                    style={optStyle}
+                    onPointerDown={e =>
+                        this.setUpButtonClick(e, () => {
+                            specialFunc?.();
+                            runInAction(() => (this._layout.type = option));
+                        })
+                    }>
+                    {option}
+                </div>
+            );
+        };
 
-type DecorationField = Field;
+        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 * 0.4, height: height }}>
+                        <FontAwesomeIcon icon={icon as IconProp} />
+                    </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>
+            );
+        };
 
-type InkDecoration = {};
+        const repeatOptions = [0, 1, 2, 3, 4, 5];
 
-type TemplateDecorations = Field | InkDecoration;
-
-export interface TemplateDocInfos {
-    title: string;
-    height: number;
-    width: number;
-    opts: TemplateOpts;
-    fields: Field[];
-    decorations: Field[];
-}
-
-export interface FieldOpts {
-    backgroundColor?: string;
-    color?: string;
-    cornerRounding?: number;
-    borderWidth?: string;
-    borderColor?: string;
-    contentXCentering?: 'h-left' | 'h-center' | 'h-right';
-    contentYCentering?: 'top' | 'center' | 'bottom';
-    opacity?: number;
-    rotation?: number;
-    //animation?: boolean;
-    fontBold?: boolean;
-    fontTransform?: 'uppercase' | 'lowercase';
-    fieldViewType?: 'freeform' | 'stacked';
-}
-
-interface TemplateOpts extends FieldOpts {}
-
-export class FieldUtils {
-    public static contentFields = (fields: Field[]) => {
-        let toRet: Field[] = [];
-        fields.forEach(field => {
-            if (!field.isDecoration) {
-                toRet.push(field);
-            }
-            toRet = toRet.concat(FieldUtils.contentFields(field.subfields ?? []));
-        });
-
-        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 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 * 0.5;
-                //console.log(wordWidth)
-
-                if (currentRowWidth + wordWidth <= contWidth) {
-                    currentRowWidth += wordWidth;
-                    ++wordsInCurrRow;
-                } else {
-                    if (words.length !== 1 && words.length > wordsInCurrRow) {
-                        rowsCount++;
-                        currentRowWidth = wordWidth;
-                        wordsInCurrRow = 1;
-                    } else {
-                        break;
-                    }
-                }
-
-                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
-        const width = r - l;
-        const height = b - t;
-        const coord = { x: l, y: t };
-        //console.log(coords, parentWidth, parentHeight, height);
-        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);
-
-        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,
-            borderWidth: opts.borderWidth,
-            opacity: opts.opacity,
-            hCentering: opts.contentXCentering,
-            _rotation: opts.rotation,
-        });
-
-        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);
-
-        const bool = true;
-
-        const docWithBasicOpts = Docs.Create.TextDocument(content, {
-            isDefaultTemplateDoc: true,
-            _height: height,
-            _width: width,
-            title: title,
-            x: coord.x,
-            y: coord.y,
-            _text_fontSize: `${FieldUtils.calculateFontSize(width, height, content, true)}`,
-            backgroundColor: opts.backgroundColor ?? '',
-            text_fontColor: opts.color,
-            contentBold: opts.fontBold,
-            textTransform: opts.fontTransform,
-            color: opts.color,
-            _layout_borderRounding: `${opts.cornerRounding}px` ?? '0px',
-            borderColor: opts.borderColor,
-            borderWidth: opts.borderWidth,
-            opacity: opts.opacity,
-            hCentering: opts.contentXCentering,
-            _rotation: opts.rotation,
-        });
-
-        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);
-
-        const doc = Docs.Create.ImageDocument(content, {
-            isDefaultTemplateDoc: true,
-            _height: height,
-            _width: width,
-            title: title,
-            x: coord.x,
-            y: coord.y,
-            _layout_fitWidth: false,
-            backgroundColor: opts.backgroundColor ?? '',
-            _layout_borderRounding: `${opts.cornerRounding}px` ?? '0px',
-            borderColor: opts.borderColor,
-            borderWidth: opts.borderWidth,
-            opacity: opts.opacity,
-            _rotation: opts.rotation,
-        });
-
-        //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);
-
-        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[];
-    }
-
-    public static getTemplateByTitle = (title: string): TemplateDocInfos | undefined => {
-        switch (title) {
-            case 'fourfield1':
-                return TemplateLayouts.FourField001;
-            case 'fourfield2':
-                return TemplateLayouts.FourField002;
-            // case 'fourfield3':
-            //     return TemplateLayouts.FourField003;
-            case 'fourfield4':
-                return TemplateLayouts.FourField004;
-            case 'threefield1':
-                return TemplateLayouts.ThreeField001;
-            case 'threefield2':
-                return TemplateLayouts.ThreeField002;
-            default:
-                break;
-        }
-
-        return undefined;
-    };
-
-    public static FourField001: TemplateDocInfos = {
-        title: 'fourfield1',
-        width: 416,
-        height: 700,
-        opts: {
-            backgroundColor: '#C0B887',
-            cornerRounding: 20,
-            borderColor: '#6B461F',
-            borderWidth: '12',
-        },
-        fields: [
-            {
-                tl: [-0.95, -1],
-                br: [0.95, -0.85],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.TINY],
-                description: 'A title field for very short text that contextualizes the content.',
-                opts: {
-                    backgroundColor: 'transparent',
-                    color: '#F1F0E9',
-                    contentXCentering: 'h-center',
-                    fontBold: true,
-                },
-            },
-            {
-                tl: [-0.87, -0.83],
-                br: [0.87, 0.2],
-                types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
-                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-                description: 'The main focus of the template; could be an image, long text, etc.',
-                opts: {
-                    cornerRounding: 20,
-                    borderColor: '#8F5B25',
-                    borderWidth: '6',
-                    backgroundColor: '#CECAB9',
-                },
-            },
-            {
-                tl: [-0.8, 0.2],
-                br: [0.8, 0.3],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
-                description: 'A caption for field #2, very short to short text that contextualizes the content of field #2',
-                opts: {
-                    backgroundColor: 'transparent',
-                    contentXCentering: 'h-center',
-                    color: '#F1F0E9',
-                },
-            },
-            {
-                tl: [-0.87, 0.37],
-                br: [0.87, 0.96],
-                types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
-                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-                description: 'A medium-sized field for medium/long text.',
-                opts: {
-                    cornerRounding: 15,
-                    borderColor: '#8F5B25',
-                    borderWidth: '6',
-                    backgroundColor: '#CECAB9',
-                },
-            },
-        ],
-        decorations: [],
-    };
-
-    public static FourField002: TemplateDocInfos = {
-        title: 'fourfield2',
-        width: 425,
-        height: 778,
-        opts: {
-            backgroundColor: '#242425',
-        },
-        fields: [
-            {
-                tl: [-0.83, -0.95],
-                br: [0.83, -0.2],
-                types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
-                description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus.',
-                opts: {
-                    borderWidth: '8',
-                    borderColor: '#F8E71C',
-                },
-            },
-            {
-                tl: [-0.65, -0.2],
-                br: [0.65, -0.02],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.TINY],
-                description: 'A tiny field for just a word or two of plain text.',
-                opts: {
-                    backgroundColor: 'transparent',
-                    color: 'white',
-                    contentXCentering: 'h-center',
-                    fontTransform: 'uppercase',
-                },
-            },
-            {
-                tl: [-0.65, 0],
-                br: [0.65, 0.18],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.TINY],
-                description: 'A tiny field for just a word or two of plain text.',
-                opts: {
-                    backgroundColor: 'transparent',
-                    color: 'white',
-                    contentXCentering: 'h-center',
-                    fontTransform: 'uppercase',
-                },
-            },
-            {
-                tl: [-0.83, 0.2],
-                br: [0.83, 0.95],
-                types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
-                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-                description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus, or share focus with field 1.',
-                opts: {
-                    borderWidth: '8',
-                    borderColor: '#F8E71C',
-                    color: 'white',
-                    backgroundColor: '#242425',
-                },
-            },
-        ],
-        decorations: [
-            {
-                tl: [-0.8, -0.075],
-                br: [-0.525, 0.075],
-                opts: {
-                    backgroundColor: '#F8E71C',
-                    rotation: 45,
-                },
-            },
-            {
-                tl: [-0.3075, -0.0245],
-                br: [-0.2175, 0.0245],
-                opts: {
-                    backgroundColor: '#F8E71C',
-                    rotation: 45,
-                },
-            },
-            {
-                tl: [-0.045, -0.0245],
-                br: [0.045, 0.0245],
-                opts: {
-                    backgroundColor: '#F8E71C',
-                    rotation: 45,
-                },
-            },
-            {
-                tl: [0.2175, -0.0245],
-                br: [0.3075, 0.0245],
-                opts: {
-                    backgroundColor: '#F8E71C',
-                    rotation: 45,
-                },
-            },
-            {
-                tl: [0.525, -0.075],
-                br: [0.8, 0.075],
-                opts: {
-                    backgroundColor: '#F8E71C',
-                    rotation: 45,
-                },
-            },
-        ],
-    };
+        return (
+            <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.Row)}
+                            {layoutOption(LayoutType.Column)}
+                            {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>
+                </div>
+                {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 key={num} onPointerDown={() => (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" />
+                    </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,
+                                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 };
+                                    this._dataViz?.createDocsFromTemplate(templateInfo);
+                                }, 'make docs')
+                            )
+                        }>
+                        <FontAwesomeIcon icon="plus" />
+                    </button>
+                </div>
+            </div>
+        );
+    }
 
-    // public static FourField003: TemplateDocInfos = {
-    //     title: 'fourfield3',
-    //     width: 477,
-    //     height: 662,
-    //     opts: {
-    //         backgroundColor: '#9E9C95'
-    //     },
-    //     fields: [{
-    //         tl: [-.875, -.9],
-    //         br: [.875, .7],
-    //         types: [TemplateFieldType.VISUAL],
-    //         sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-    //         description: '',
-    //         opts: {
-    //             borderWidth: '15',
-    //             borderColor: '#E0E0DA',
-    //         }
-    //     }, {
-    //         tl: [-.95, .8],
-    //         br: [-.1, .95],
-    //         types: [TemplateFieldType.TEXT],
-    //         sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
-    //         description: '',
-    //         opts: {
-    //             backgroundColor: 'transparent',
-    //             color: 'white',
-    //             contentXCentering: 'h-right',
-    //         }
-    //     }, {
-    //         tl: [.1, .8],
-    //         br: [.95, .95],
-    //         types: [TemplateFieldType.TEXT],
-    //         sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
-    //         description: '',
-    //         opts: {
-    //             backgroundColor: 'transparent',
-    //             color: 'red',
-    //             fontTransform: 'uppercase',
-    //             contentXCentering: 'h-left'
-    //         }
-    //     }, {
-    //         tl: [0, -.9],
-    //         br: [.85, -.66],
-    //         types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
-    //         sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-    //         description: '',
-    //         opts: {
-    //             backgroundColor: 'transparent',
-    //             contentXCentering: 'h-right'
-    //         }
-    //     }],
-    //     decorations: [{
-    //         tl: [-.025, .8],
-    //         br: [.025, .95],
-    //         opts: {
-    //             backgroundColor: '#E0E0DA',
-    //         }
-    //     }]
-    // };
+    get dashboardContents() {
+        const sizes: string[] = ['tiny', 'small', 'medium', 'large', 'huge'];
 
-    public static FourField004: TemplateDocInfos = {
-        title: 'fourfield4',
-        width: 414,
-        height: 583,
-        opts: {
-            backgroundColor: '#6CCAF0',
-            borderColor: '#1088C3',
-            borderWidth: '10',
-        },
-        fields: [
-            {
-                tl: [-0.86, -0.92],
-                br: [-0.075, -0.77],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.TINY],
-                description: 'A tiny field for just a word or two of plain text.',
-                opts: {
-                    backgroundColor: '#E2B4F5',
-                    borderWidth: '9',
-                    borderColor: '#9222F1',
-                    contentXCentering: 'h-center',
-                },
-            },
-            {
-                tl: [0.075, -0.92],
-                br: [0.86, -0.77],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.TINY],
-                description: 'A tiny field for just a word or two of plain text.',
-                opts: {
-                    backgroundColor: '#F5B4DD',
-                    borderWidth: '9',
-                    borderColor: '#E260F3',
-                    contentXCentering: 'h-center',
-                },
-            },
-            {
-                tl: [-0.81, -0.64],
-                br: [0.81, 0.48],
-                types: [TemplateFieldType.VISUAL],
-                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-                description: 'A large to huge field for visual content that is the main content of the template.',
-                opts: {
-                    borderWidth: '16',
-                    borderColor: '#A2BD77',
-                },
-            },
-            {
-                tl: [-0.86, 0.6],
-                br: [0.86, 0.92],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
-                description: 'A medium to large field for text that describes the visual content above',
-                opts: {
-                    borderWidth: '9',
-                    borderColor: '#F0D601',
-                    backgroundColor: '#F3F57D',
-                },
-            },
-        ],
-        decorations: [
-            {
-                tl: [-0.852, -0.67],
-                br: [0.852, 0.51],
-                opts: {
-                    backgroundColor: 'transparent',
-                    borderColor: '#007C0C',
-                    borderWidth: '10',
-                },
-            },
-        ],
-    };
+        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.removeField(field))} 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>
+                        <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>
+                        </div>
+                    </div>
+                    <div className="desc-box">
+                        <div className="top-bar"> Prompt </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/prompt to help with template generation.'}
+                        />
+                    </div>
+                </div>
+            );
+        };
 
-    public static ThreeField001: TemplateDocInfos = {
-        title: 'threefield1',
-        width: 575,
-        height: 770,
-        opts: {
-            backgroundColor: '#DDD3A9',
-        },
-        fields: [
-            {
-                tl: [-0.66, -0.747],
-                br: [0.66, 0.247],
-                types: [TemplateFieldType.VISUAL],
-                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-                description: 'A medium to large field for visual content that is the central focus.',
-                opts: {
-                    borderColor: 'yellow',
-                    borderWidth: '8',
-                    rotation: 45,
-                },
-            },
-            {
-                tl: [-0.7, 0.2],
-                br: [0.7, 0.46],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
-                description: 'A very small text field for one to a few words. A good caption for the image.',
-                opts: {
-                    backgroundColor: 'transparent',
-                    contentXCentering: 'h-center',
-                },
-            },
-            {
-                tl: [-0.95, 0.5],
-                br: [0.95, 0.95],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
-                description: 'A medium to large text field for a thorough description of the image. ',
-                opts: {
-                    backgroundColor: 'transparent',
-                    color: 'white',
-                },
-            },
-        ],
-        decorations: [
-            {
-                tl: [0.2, -1.32],
-                br: [1.8, -0.66],
-                opts: {
-                    backgroundColor: '#CEB155',
-                    rotation: 45,
-                },
-            },
-            {
-                tl: [-1.8, -1.32],
-                br: [-0.2, -0.66],
-                opts: {
-                    backgroundColor: '#CEB155',
-                    rotation: 135,
-                },
-            },
-            {
-                tl: [0.33, 0.75],
-                br: [1.66, 1.25],
-                opts: {
-                    backgroundColor: '#CEB155',
-                    rotation: 135,
-                },
-            },
-            {
-                tl: [-1.66, 0.75],
-                br: [-0.33, 1.25],
-                opts: {
-                    backgroundColor: '#CEB155',
-                    rotation: 45,
-                },
-            },
-        ],
-    };
+        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>
+            </div>
+        );
+    }
 
-    public static ThreeField002: TemplateDocInfos = {
-        title: 'threefield2',
-        width: 477,
-        height: 662,
-        opts: {
-            backgroundColor: '#9E9C95',
-        },
-        fields: [
-            {
-                tl: [-0.875, -0.9],
-                br: [0.875, 0.7],
-                types: [TemplateFieldType.VISUAL],
-                sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-                description: 'A medium to large visual field for the main content of the template',
-                opts: {
-                    borderWidth: '15',
-                    borderColor: '#E0E0DA',
-                },
-            },
-            {
-                tl: [0.1, 0.775],
-                br: [0.95, 0.975],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
-                description: 'A very small text field for one to a few words. The content should represent a general categorization of the image.',
-                opts: {
-                    backgroundColor: 'transparent',
-                    color: '#AF0D0D',
-                    fontTransform: 'uppercase',
-                    fontBold: true,
-                    contentXCentering: 'h-left',
-                },
-            },
-            {
-                tl: [-0.95, 0.775],
-                br: [-0.1, 0.975],
-                types: [TemplateFieldType.TEXT],
-                sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
-                description: 'A very small text field for one to a few words. The content should contextualize field 2.',
-                opts: {
-                    backgroundColor: 'transparent',
-                    color: 'black',
-                    contentXCentering: 'h-right',
-                },
-            },
-        ],
-        decorations: [
-            {
-                tl: [-0.025, 0.8],
-                br: [0.025, 0.95],
-                opts: {
-                    backgroundColor: '#E0E0DA',
-                },
-            },
-        ],
-    };
+    get renderSelectedViewType() {
+        switch (this._menuContent) {
+            case 'templates':
+                return this.templatesPreviewContents;
+            case 'options':
+                return this.optionsMenuContents;
+            case 'saved':
+                return this.savedLayoutsPreviewContents;
+            case 'dashboard':
+                return this.dashboardContents;
+            default:
+                return undefined;
+        }
+    }
+
+    get resizePanes() {
+        const ref = this._ref?.getBoundingClientRect();
+        const height: number = ref?.height ?? 0;
+        const width: number = ref?.width ?? 0;
+
+        return [
+            <div key={1} className='docCreatorMenu-resizer top' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: -7}}/>, 
+            <div key={2} className='docCreatorMenu-resizer right' onPointerDown={this.onResizePointerDown} style={{height: height, left: width - 3, top: 0}}/>, 
+            <div key={3} className='docCreatorMenu-resizer bottom' onPointerDown={this.onResizePointerDown} style={{width: width, left: 0, top: height - 3}}/>, 
+            <div key={4} className='docCreatorMenu-resizer left' onPointerDown={this.onResizePointerDown} style={{height: height, left: -7, top: 0}}/>,
+            <div key={5} className='docCreatorMenu-resizer topRight' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: -10, cursor: 'nesw-resize'}}/>,
+            <div key={6} className='docCreatorMenu-resizer topLeft' onPointerDown={this.onResizePointerDown} style={{left: -10, top: -10, cursor: 'nwse-resize'}}/>,
+            <div key={7} className='docCreatorMenu-resizer bottomRight' onPointerDown={this.onResizePointerDown} style={{left: width - 5, top: height - 5, cursor: 'nwse-resize'}}/>,
+            <div key={8} className='docCreatorMenu-resizer bottomLeft' onPointerDown={this.onResizePointerDown} style={{left: -10, top: height - 5, cursor: 'nesw-resize'}}/>
+        ]; //prettier-ignore
+    }
+
+    render() {
+        const topButton = (icon: string, opt: string, func: () => void, tag: string) => {
+            return (
+                <div className={`top-button-container ${tag} ${opt === this._menuContent ? 'selected' : ''}`}>
+                    <div
+                        className="top-button-content"
+                        onPointerDown={e =>
+                            this.setUpButtonClick(e, () =>
+                                runInAction(() => {
+                                    func();
+                                })
+                            )
+                        }>
+                        <FontAwesomeIcon icon={icon as IconProp} />
+                    </div>
+                </div>
+            );
+        };
+
+        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));
+        };
 
-    //     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]
-    //         }]
-    //     };
+        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,
+                        }}>
+                        {this.resizePanes}
+                        <div
+                            className="docCreatorMenu-menu"
+                            onPointerDown={e =>
+                                setupMoveUpEvents(
+                                    this,
+                                    e,
+                                    moveEv => {
+                                        this._dragging = true;
+                                        this._startPos = { x: 0, y: 0 };
+                                        this._startPos.x = moveEv.pageX - (this._ref?.getBoundingClientRect().left ?? 0);
+                                        this._startPos.y = moveEv.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>
+                        </div>
+                        {this.renderSelectedViewType}
+                    </div>
+                )}
+            </div>
+        );
+    }
 }
 
 // export class ContentField extends Field {
-    
+
 // }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f84bf6db0..428fe5acb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1161,7 +1161,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
     }
     @computed private get panelWidth() {
-        return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling: this._props.PanelWidth();
+        return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
     }
     @computed private get panelHeight() {
         if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) {
@@ -1473,7 +1473,6 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
 
     render() {
         TraceMobx();
-        const borderWidth = 50/*Number(StrCast(this.layoutDoc.layout_borderWidth).replace('px', ''))*/;
         const xshift = Math.abs(this.Xshift) <= 0.001 ? this._props.PanelWidth() : undefined;
         const yshift = Math.abs(this.Yshift) <= 0.001 ? this._props.PanelHeight() : undefined;
 
-- 
cgit v1.2.3-70-g09d2

