From f60a8539a4b1fd0c9b3249b94bff011fd7f742e5 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Fri, 9 May 2025 19:15:29 -0400
Subject: bunch of changes, everything still broken

---
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  | 134 +++++++++++++++++++++
 1 file changed, 134 insertions(+)
 create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
new file mode 100644
index 000000000..fe7822087
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -0,0 +1,134 @@
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, observable, runInAction } from "mobx";
+import React from "react";
+import { returnFalse, returnEmptyFilter } from "../../../../../../ClientUtils";
+import { emptyFunction } from "../../../../../../Utils";
+import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc";
+import { DefaultStyleProvider } from "../../../../StyleProvider";
+import { DocumentView, DocumentViewInternal } from "../../../DocumentView";
+import { DocCreatorMenu } from "../DocCreatorMenu";
+import { TemplateSidescrollView } from "./TemplatesSidescrollDisplay";
+import { observer } from "mobx-react";
+import { Transform } from "../../../../../util/Transform";
+import { Template } from "../Template";
+import { TemplateMenuAIUtils } from "../Backend/TemplateMenuAIUtils";
+import { ObservableReactComponent } from "../../../../ObservableReactComponent";
+
+interface TemplateEditingWindowProps {
+    menu: DocCreatorMenu;
+    template: Template;
+    setupButtonClick: (e: React.PointerEvent, func: () => void) => void;
+}
+
+@observer
+export class TemplateEditingWindow extends ObservableReactComponent<TemplateEditingWindowProps> {
+
+    private fireflyPrompt: string = 'Use this template to generate an empty baseball card template.';
+    private previewWindow: HTMLDivElement | null = null;
+
+    @observable _variationsTabOpen: boolean = false;
+    @observable _variations: Doc[] = [];
+
+    setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
+        this.previewWindow = node;
+    }
+
+    @action setVariationTab = (open: boolean) => {
+        this._variationsTabOpen = open;
+        if (this.previewWindow && open) {
+            this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * .6);
+        } else if (this.previewWindow && !open) {
+            this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * 5/3);
+        }
+    }
+
+    get fireflyVariationsTab() {
+
+        return (
+            <>
+                <TemplateSidescrollView
+                    menu={this._props.menu}
+                    title={'Generate Variations'}
+                    templates={[]}
+                    setupButtonClick={this._props.setupButtonClick}
+                />
+                <div className="docCreatorMenu-section">
+                    <div className="docCreatorMenu-variation-prompt-input">
+                        <textarea
+                            className="docCreatorMenu-variation-prompt-input-textbox"
+                            onChange={e => this.fireflyPrompt = e.target.value}
+                            defaultValue={''}
+                            placeholder={'Enter a custom prompt here (optional)'}
+                        />
+                        <button className="docCreatorMenu-menu-button" 
+                            onPointerDown={e => this._props.setupButtonClick(e, async () => {
+                                this._variations = await this._props.menu.generateVariations(this._props.template.getRenderedDoc()!, this.fireflyPrompt);
+                            })
+                        }>
+                            <FontAwesomeIcon icon="arrows-rotate" />
+                        </button>
+                    </div>
+                </div>
+            </>
+        )
+    }
+   
+    get renderedDocPreview(){
+        const doc: Doc = this._props.template.getRenderedDoc() as unknown as Doc;
+
+        return (
+            <div className="docCreatorMenu-expanded-template-preview" ref={this.setContainerRef}>
+                <DocumentView
+                    Document={doc}
+                    isContentActive={emptyFunction}
+                    addDocument={returnFalse}
+                    moveDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    PanelWidth={() => this.previewWindow?.clientWidth ?? 1000 - 10}
+                    PanelHeight={() => this.previewWindow?.clientHeight ?? 1000 - 60}
+                    ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)}
+                    renderDepth={5}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    focus={emptyFunction}
+                    styleProvider={DefaultStyleProvider}
+                    addDocTab={DocumentViewInternal.addDocTabFunc}
+                    pinToPres={() => undefined}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    fitContentsToBox={returnFalse}
+                    fitWidth={returnFalse}
+                />
+            </div>
+        )
+    }
+
+    render() {
+        return (
+            <div className="docCreatorMenu-expanded-template-preview">
+                <div className="top-panel" />
+                {this.renderedDocPreview}
+                {this.fireflyVariationsTab}
+                <div className="right-buttons-panel">
+                    <button
+                        className="docCreatorMenu-menu-button section-reveal-options top-right"
+                        onPointerDown={e =>
+                            this._props.setupButtonClick(e, () => {
+                                if (this._props.template === this._props.menu._selectedTemplate) {
+                                    this._props.menu.updateRenderedPreviewCollection(this._props.template);
+                                }
+                                this._props.menu.setExpandedView(undefined);
+                            })
+                        }>
+                        <FontAwesomeIcon icon="minimize" />
+                    </button>
+                    <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this._props.setupButtonClick(e, async () => {
+                            this.setVariationTab(!this._variationsTabOpen);
+                         })}>
+                         <FontAwesomeIcon icon="lightbulb" />
+                    </button>
+                </div>
+            </div>
+        );
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 1451849d857afaea3a3bdc5feaeba0d4b8182e68 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sat, 10 May 2025 16:52:56 -0400
Subject: c

---
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  | 46 ++++++++++----------
 .../Menu/TemplatesSidescrollDisplay.tsx            | 49 ++++++++++------------
 2 files changed, 47 insertions(+), 48 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index fe7822087..c5c5f5cfd 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -105,28 +105,30 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
 
     render() {
         return (
-            <div className="docCreatorMenu-expanded-template-preview">
-                <div className="top-panel" />
-                {this.renderedDocPreview}
-                {this.fireflyVariationsTab}
-                <div className="right-buttons-panel">
-                    <button
-                        className="docCreatorMenu-menu-button section-reveal-options top-right"
-                        onPointerDown={e =>
-                            this._props.setupButtonClick(e, () => {
-                                if (this._props.template === this._props.menu._selectedTemplate) {
-                                    this._props.menu.updateRenderedPreviewCollection(this._props.template);
-                                }
-                                this._props.menu.setExpandedView(undefined);
-                            })
-                        }>
-                        <FontAwesomeIcon icon="minimize" />
-                    </button>
-                    <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this._props.setupButtonClick(e, async () => {
-                            this.setVariationTab(!this._variationsTabOpen);
-                         })}>
-                         <FontAwesomeIcon icon="lightbulb" />
-                    </button>
+            <div className={`docCreatorMenu-templates-view`}>
+                <div className="docCreatorMenu-expanded-template-preview">
+                    <div className="top-panel" />
+                    {this.renderedDocPreview}
+                    {this.fireflyVariationsTab}
+                    <div className="right-buttons-panel">
+                        <button
+                            className="docCreatorMenu-menu-button section-reveal-options top-right"
+                            onPointerDown={e =>
+                                this._props.setupButtonClick(e, () => {
+                                    if (this._props.template === this._props.menu._selectedTemplate) {
+                                        this._props.menu.updateRenderedPreviewCollection(this._props.template);
+                                    }
+                                    this._props.menu.setExpandedView(undefined);
+                                })
+                            }>
+                            <FontAwesomeIcon icon="minimize" />
+                        </button>
+                        <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this._props.setupButtonClick(e, async () => {
+                                this.setVariationTab(!this._variationsTabOpen);
+                            })}>
+                            <FontAwesomeIcon icon="lightbulb" />
+                        </button>
+                    </div>
                 </div>
             </div>
         );
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx
index 5035227d1..a5f96ef90 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx
@@ -14,6 +14,7 @@ import { TemplatePreviewBox } from "./TemplatePreviewBox";
 export interface SuggestedTemplatesProps {
     menu: DocCreatorMenu;
     templates: Template[];
+    title: string;
     previewBoxLeftButtonOpts?: [string, () => any];
     previewBoxRightButtonOpts?: [string, () => any];
     setupButtonClick: (e: React.PointerEvent, func: () => void) => void;
@@ -26,32 +27,28 @@ export class TemplateSidescrollView extends ObservableReactComponent<SuggestedTe
 
     render() {
         return (
-            <div className='docCreatorMenu-templates-view'>
-                <div className="docCreatorMenu-templates-displays">
-                    <div className="docCreatorMenu-section">
-                        <div className="docCreatorMenu-section-topbar">
-                            <div className="docCreatorMenu-section-title">Suggested Templates</div>
-                            <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.props.setupButtonClick(e, () => runInAction(() => (this.props.menu._menuContent = 'dashboard')))}>
-                                <FontAwesomeIcon icon="gear" />
-                            </button>
-                        </div>
-                        <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this.props.menu._menuDimensions.width > 400 ? 'center' : '' }}>
-                            {this.props.templates.map(template => (
-                                <TemplatePreviewBox 
-                                template={template} 
-                                menu={this.props.menu} 
-                                leftButtonOpts={["magnifying-glass", (template: Template) => { this.props.menu.setExpandedView(template); this.forceUpdate(); }]}
-                                rightButtonOpts={["plus", (template: Template) => {}]}
-                                />
-                            ))}
-                        </div>
-                        <div className="docCreatorMenu-GPT-options">
-                            <div className="docCreatorMenu-GPT-options-container">
-                                <button className="docCreatorMenu-menu-button" onPointerDown={e => this.props.setupButtonClick(e, () => this.props.menu.generatePresetTemplates())}>
-                                    <FontAwesomeIcon icon="arrows-rotate" />
-                                </button>
-                            </div>
-                        </div>
+            <div className="docCreatorMenu-section">
+                <div className="docCreatorMenu-section-topbar">
+                    <div className="docCreatorMenu-section-title">{this.props.title}</div>
+                    <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.props.setupButtonClick(e, () => runInAction(() => (this.props.menu._menuContent = 'dashboard')))}>
+                        <FontAwesomeIcon icon="gear" />
+                    </button>
+                </div>
+                <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this.props.menu._menuDimensions.width > 400 ? 'center' : '' }}>
+                    {this.props.templates.map(template => (
+                        <TemplatePreviewBox 
+                        template={template} 
+                        menu={this.props.menu} 
+                        leftButtonOpts={["magnifying-glass", (template: Template) => { this.props.menu.setExpandedView(template); this.forceUpdate(); }]}
+                        rightButtonOpts={["plus", (template: Template) => {}]}
+                        />
+                    ))}
+                </div>
+                <div className="docCreatorMenu-GPT-options">
+                    <div className="docCreatorMenu-GPT-options-container">
+                        <button className="docCreatorMenu-menu-button" onPointerDown={e => this.props.setupButtonClick(e, () => this.props.menu.generatePresetTemplates())}>
+                            <FontAwesomeIcon icon="arrows-rotate" />
+                        </button>
                     </div>
                 </div>
             </div>
-- 
cgit v1.2.3-70-g09d2


From 529bd742842e76d1df464b13346fae4f22f8f3c9 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sat, 10 May 2025 18:49:47 -0400
Subject: docpreviews resize (mostly) appropriately

---
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.scss  |  8 +++
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 35 +++++----
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  | 38 +++++++---
 .../DocCreatorMenu/Menu/TemplatePreviewBox.tsx     | 84 ++++++++++++----------
 .../Menu/TemplatesSidescrollDisplay.tsx            |  7 --
 5 files changed, 106 insertions(+), 66 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
index 8f182d37f..277ca8238 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
@@ -230,6 +230,14 @@
     &.full {
         width: 100%;
     }
+
+    &.no-margin-bottom {
+        margin-bottom: 0px;
+    }
+
+    &.no-margin-top {
+        margin-top: 0px;
+    }
 }
 
 //------------------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index 48fea91e2..b626fc70c 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -89,7 +89,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     static Instance: DocCreatorMenu;
 
     private DEBUG_MODE: boolean = true;
-    private _disposers: { [name: string]: IDisposer } = {};
     private _ref: HTMLDivElement | null = null;
     private templateManager: TemplateManager;
 
@@ -268,7 +267,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     }
 
     componentWillUnmount() {
-        Object.values(this._disposers).forEach(disposer => disposer?.());
         document.removeEventListener('pointerdown', this.onPointerDown, true);
         document.removeEventListener('pointerup', this.onPointerUp);
     }
@@ -318,10 +316,10 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
         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)})});
+            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;
     };
@@ -1065,12 +1063,25 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     get renderSelectedViewType() {
         switch (this._menuContent) {
             case 'templates': 
-                return <TemplateSidescrollView 
-                    title={'Suggested Templates'} 
-                    menu={this} 
-                    setupButtonClick={this.setUpButtonClick} 
-                    templates={this._suggestedTemplates}
-                />
+                return (
+                    <div className='docCreatorMenu-templates-view'>
+                        <div className="docCreatorMenu-templates-displays">
+                            <TemplateSidescrollView 
+                                title={'Suggested Templates'} 
+                                menu={this} 
+                                setupButtonClick={this.setUpButtonClick} 
+                                templates={this._suggestedTemplates}
+                            />
+                            <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>
+                            </div>
+                        </div>
+                    </div>
+                )
             case 'templateEditing':
                 return <TemplateEditingWindow
                     setupButtonClick={this.setUpButtonClick} 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index c5c5f5cfd..08a48ccb9 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction } from "mobx";
+import { action, observable, reaction, runInAction } from "mobx";
 import React from "react";
-import { returnFalse, returnEmptyFilter } from "../../../../../../ClientUtils";
+import { returnFalse, returnEmptyFilter, returnTrue } from "../../../../../../ClientUtils";
 import { emptyFunction } from "../../../../../../Utils";
 import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc";
 import { DefaultStyleProvider } from "../../../../StyleProvider";
@@ -13,6 +13,7 @@ import { Transform } from "../../../../../util/Transform";
 import { Template } from "../Template";
 import { TemplateMenuAIUtils } from "../Backend/TemplateMenuAIUtils";
 import { ObservableReactComponent } from "../../../../ObservableReactComponent";
+import { IDisposer } from "mobx-utils";
 
 interface TemplateEditingWindowProps {
     menu: DocCreatorMenu;
@@ -25,12 +26,26 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
 
     private fireflyPrompt: string = 'Use this template to generate an empty baseball card template.';
     private previewWindow: HTMLDivElement | null = null;
+    private _disposers: { [name: string]: IDisposer } = {};
 
     @observable _variationsTabOpen: boolean = false;
     @observable _variations: Doc[] = [];
 
+    componentDidMount(): void {
+        this._disposers.windowDimensions = reaction(() => 
+            this._props.menu._resizing, 
+            () => { this.forceUpdate() }, 
+            { fireImmediately: true }
+            );
+    }
+
+    componentWillUnmount() {
+        Object.values(this._disposers).forEach(disposer => disposer?.());
+    }
+
     setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
         this.previewWindow = node;
+        this.forceUpdate();
     }
 
     @action setVariationTab = (open: boolean) => {
@@ -78,15 +93,15 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
 
         return (
             <div className="docCreatorMenu-expanded-template-preview" ref={this.setContainerRef}>
-                <DocumentView
+                {this.previewWindow ? <DocumentView
                     Document={doc}
                     isContentActive={emptyFunction}
                     addDocument={returnFalse}
                     moveDocument={returnFalse}
                     removeDocument={returnFalse}
-                    PanelWidth={() => this.previewWindow?.clientWidth ?? 1000 - 10}
-                    PanelHeight={() => this.previewWindow?.clientHeight ?? 1000 - 60}
-                    ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)}
+                    PanelWidth={() => this.previewWindow?.clientWidth ?? 500}
+                    PanelHeight={() => this.previewWindow?.clientHeight ?? 500}
+                    ScreenToLocalTransform={() => new Transform(/*-this._props.menu._pageX - 5, -this._props.menu._pageY - 35*/0, 0, 1)}
                     renderDepth={5}
                     whenChildContentsActiveChanged={emptyFunction}
                     focus={emptyFunction}
@@ -98,18 +113,19 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                     searchFilterDocs={returnEmptyDoclist}
                     fitContentsToBox={returnFalse}
                     fitWidth={returnFalse}
-                />
+                /> : null}
             </div>
         )
     }
 
     render() {
         return (
-            <div className={`docCreatorMenu-templates-view`}>
+            <div className='docCreatorMenu-templates-view'>
                 <div className="docCreatorMenu-expanded-template-preview">
-                    <div className="top-panel" />
+                    <div className="top-panel"/>
                     {this.renderedDocPreview}
-                    {this.fireflyVariationsTab}
+                    <div className="docCreatorMenu-option-divider full"/>
+                    {this._variationsTabOpen ? this.fireflyVariationsTab : null}
                     <div className="right-buttons-panel">
                         <button
                             className="docCreatorMenu-menu-button section-reveal-options top-right"
@@ -125,6 +141,8 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                         </button>
                         <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this._props.setupButtonClick(e, async () => {
                                 this.setVariationTab(!this._variationsTabOpen);
+                                this.forceUpdate();
+                                console.log('window: ', this.previewWindow)
                             })}>
                             <FontAwesomeIcon icon="lightbulb" />
                         </button>
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
index 4759a4158..e63be8f9c 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
@@ -1,7 +1,7 @@
 import { Colors } from "@dash/components/src";
 import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
 import { Template } from "../Template";
-import { runInAction } from "mobx";
+import { reaction, runInAction } from "mobx";
 import React from "react";
 import { ObservableReactComponent } from "../../../../ObservableReactComponent";
 import { DocCreatorMenu } from "../DocCreatorMenu";
@@ -12,6 +12,7 @@ import { returnEmptyFilter, returnFalse } from "../../../../../../ClientUtils";
 import { Transform } from "../../../../../util/Transform";
 import { DefaultStyleProvider } from "../../../../StyleProvider";
 import { returnEmptyDoclist } from "../../../../../../fields/Doc";
+import { IDisposer } from "mobx-utils";
 
 export interface TemplatePreviewBoxProps {
     template: Template;
@@ -22,6 +23,13 @@ export interface TemplatePreviewBoxProps {
 
 export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBoxProps> {
 
+    private previewWindow: HTMLDivElement | null = null;
+
+    setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
+        this.previewWindow = node;
+        this.forceUpdate();
+    }
+
     render() {
         const template = this.props.template;
 
@@ -29,46 +37,48 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview
             <div
                 key={template.title}
                 className="docCreatorMenu-preview-window"
+                ref={this.setContainerRef}
                 style={{
                     border: this.props.menu._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
                     boxShadow: this.props.menu._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
                 }}
-                onPointerDown={e => this.props.menu.setUpButtonClick(e, () => runInAction(() => {console.log('setting'); this.props.menu._selectedTemplate = template}))}>
-                { this.props.leftButtonOpts ? 
-                <button
-                    className="option-button left"
-                    onPointerDown={e =>
-                        this.props.menu.setUpButtonClick(e, () => this.props.leftButtonOpts![1](template))
-                    }>
-                    <FontAwesomeIcon icon={this.props.leftButtonOpts![0]} color="white" />
-                </button> : null
-                }
-                { this.props.rightButtonOpts ? 
-                <button className="option-button right" onPointerDown={e => this.props.menu.setUpButtonClick(e, () => this.props.rightButtonOpts![1](template))}>
-                    <FontAwesomeIcon icon={this.props.rightButtonOpts![0]} color="white" />
-                </button> : null }
-                <DocumentView
-                    Document={this.props.template.getRenderedDoc()!} //!!! bad
-                    isContentActive={emptyFunction} // !!! should be return false
-                    addDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    removeDocument={returnFalse}
-                    PanelWidth={() => this.props.menu._menuDimensions.height * .3}
-                    PanelHeight={() => this.props.menu._menuDimensions.height * .3}
-                    ScreenToLocalTransform={() => new Transform(-this.props.menu._pageX - 5, -this.props.menu._pageY - 35, 1)}
-                    renderDepth={1}
-                    whenChildContentsActiveChanged={emptyFunction}
-                    focus={emptyFunction}
-                    styleProvider={DefaultStyleProvider}
-                    addDocTab={this.props.menu._props.addDocTab}
-                    pinToPres={() => undefined}
-                    childFilters={returnEmptyFilter}
-                    childFiltersByRanges={returnEmptyFilter}
-                    searchFilterDocs={returnEmptyDoclist}
-                    fitContentsToBox={returnFalse}
-                    fitWidth={returnFalse}
-                    hideDecorations={true}
-                />
+                onPointerDown={e => this.props.menu.setUpButtonClick(e, () => runInAction(() => {console.log('setting'); this.props.menu._selectedTemplate = template}))}
+                >
+                    { this.props.leftButtonOpts ? 
+                    <button
+                        className="option-button left"
+                        onPointerDown={e =>
+                            this.props.menu.setUpButtonClick(e, () => this.props.leftButtonOpts![1](template))
+                        }>
+                        <FontAwesomeIcon icon={this.props.leftButtonOpts![0]} color="white" />
+                    </button> : null
+                    }
+                    { this.props.rightButtonOpts ? 
+                    <button className="option-button right" onPointerDown={e => this.props.menu.setUpButtonClick(e, () => this.props.rightButtonOpts![1](template))}>
+                        <FontAwesomeIcon icon={this.props.rightButtonOpts![0]} color="white" />
+                    </button> : null }
+                    <DocumentView
+                        Document={this.props.template.getRenderedDoc()!} //!!! bad
+                        isContentActive={emptyFunction} // !!! should be return false
+                        addDocument={returnFalse}
+                        moveDocument={returnFalse}
+                        removeDocument={returnFalse}
+                        PanelWidth={() => this.previewWindow?.clientWidth ?? this.props.menu._menuDimensions.height * .3}
+                        PanelHeight={() => this.previewWindow?.clientHeight ?? this.props.menu._menuDimensions.height * .3}
+                        ScreenToLocalTransform={() => new Transform(-this.props.menu._pageX - 5, -this.props.menu._pageY - 35, 1)}
+                        renderDepth={1}
+                        whenChildContentsActiveChanged={emptyFunction}
+                        focus={emptyFunction}
+                        styleProvider={DefaultStyleProvider}
+                        addDocTab={this.props.menu._props.addDocTab}
+                        pinToPres={() => undefined}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
+                        searchFilterDocs={returnEmptyDoclist}
+                        fitContentsToBox={returnFalse}
+                        fitWidth={returnFalse}
+                        hideDecorations={true}
+                    />
             </div>
         )
     }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx
index a5f96ef90..1f6eed9e5 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx
@@ -44,13 +44,6 @@ export class TemplateSidescrollView extends ObservableReactComponent<SuggestedTe
                         />
                     ))}
                 </div>
-                <div className="docCreatorMenu-GPT-options">
-                    <div className="docCreatorMenu-GPT-options-container">
-                        <button className="docCreatorMenu-menu-button" onPointerDown={e => this.props.setupButtonClick(e, () => this.props.menu.generatePresetTemplates())}>
-                            <FontAwesomeIcon icon="arrows-rotate" />
-                        </button>
-                    </div>
-                </div>
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From 6e66ab2ab9b5b6f988230aac83ac712b43ccfe3a Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 11 May 2025 01:30:31 -0400
Subject: working on firefly integration

---
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.scss  | 36 +++++---------
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 41 ++++++++--------
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  | 42 +++++++++++++----
 .../DocCreatorMenu/Menu/TemplatePreviewBox.tsx     | 30 +++++++++++-
 .../DocCreatorMenu/Menu/TemplatePreviewGrid.tsx    | 55 ++++++++++++++++++++++
 .../Menu/TemplatesSidescrollDisplay.tsx            | 50 --------------------
 .../nodes/DataVizBox/DocCreatorMenu/Template.ts    | 25 ++++++++++
 .../DataVizBox/DocCreatorMenu/TemplateBackend.ts   |  2 +-
 .../TemplateFieldTypes/DynamicField.ts             | 33 ++++++++-----
 .../TemplateFieldTypes/StaticContentField.ts       |  4 +-
 .../TemplateFieldTypes/TemplateField.ts            |  4 ++
 src/client/views/smartdraw/DrawingFillHandler.tsx  | 24 +++++-----
 12 files changed, 216 insertions(+), 130 deletions(-)
 create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
 delete mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
index 277ca8238..082ebb7dc 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
@@ -304,7 +304,7 @@
         display: flex;
         flex-direction: column;
         justify-content: flex-start;
-        height: 100%;
+        height: fit-content;
         position: absolute;
         right: 0px;
         top: 0px;
@@ -376,16 +376,11 @@
 
 .docCreatorMenu-preview-image{ 
     background-color: transparent;
-    height: 100px;
-    width: 100px;
+    height: 100%;
     display: block;
     object-fit: contain;
     border-radius: 5px;
 
-    &.expanded {
-        height: 100%;
-        width: 100%;
-    }
 }
 
 .docCreatorMenu-variation-prompt-input {
@@ -431,31 +426,23 @@
     flex-direction: row;
     justify-content: center;
     align-items: center;
-    position: relative;
-    width: auto;
+    position: absolute;
+    width: 100%;
+    bottom: 0px;
     margin: 0px;
+    margin-bottom: 10px;
     margin-top: 5px;
     padding: 0px;
 }
 
 .docCreatorMenu-templates-preview-window {
-    display: flex;
-    flex-direction: row;
-    justify-content: flex-start;
-    align-items: center;
-    overflow-y: scroll;
-    gap: 10px;
+    display: grid;
+    justify-content: space-evenly;
+    grid-template-columns: repeat(auto-fill, minmax(225px, 30%));
+    gap: 1rem;
     margin: 5px;
-    position: relative;
-    color: black;
-    height: 100%;
     width: calc(100% - 10px);
-    -ms-overflow-style: none; 
-    scrollbar-width: none;
-
-    .loading-spinner {
-        justify-self: center;
-    }
+    padding-bottom: 40px;
 }
 
 .divvv{
@@ -468,6 +455,7 @@
     position: relative;
     display: flex;
     flex-direction: row;
+    color: whitesmoke;
     width: 100%;
 }
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index b626fc70c..2ddaa214c 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -35,7 +35,7 @@ import { DrawingFillHandler } from '../../../smartdraw/DrawingFillHandler';
 import { CgPathIntersect } from 'react-icons/cg';
 import { StaticContentField } from './TemplateFieldTypes/StaticContentField';
 import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils'
-import { TemplateSidescrollView } from './Menu/TemplatesSidescrollDisplay';
+import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid';
 import { TemplateEditingWindow } from './Menu/TemplateEditingWindow';
 
 export enum LayoutType {
@@ -133,6 +133,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     @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 _variations: Template[] = [];
+
     constructor(props: DocCreateMenuProps) {
         super(props);
         makeObservable(this);
@@ -591,9 +593,9 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         );
     };
 
-    generateVariations = async (onDoc: Doc, prompt: string): Promise<Doc[]> => {
+    generateVariations = async (onDoc: Doc, prompt: string): Promise<string[]> => {
         this._loadingVariants = true;
-        this.variationDocss = [];
+        this.variations = [];
         const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
 
         const clone: Doc = (await Doc.MakeClone(onDoc)).clone;
@@ -605,17 +607,13 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
         this._loadingVariants = false;
 
-        return this.variationDocss;
+        return this.variations;
     }
 
-    @observable variationDocss: Doc[] = []
+    @observable variations: string[] = []
 
-    @action addVariationDoc = (doc: Doc) => {
-        this.variationDocss.push(doc);
-        const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-        mainCollection.addDocument(doc);
-        doc.x = 10000;
-        doc.y = 10000;
+    @action addVariation = (url: string) => {
+        this.variations.push(url);
     }
 
     addRenderedCollectionToMainview = () => {
@@ -767,8 +765,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
         this._renderedDocCollection = collection;
 
-        const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-        mainCollection.addDocument(collection);
+        // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+        // mainCollection.addDocument(collection);
 
         console.log('changed to: ', collection);
     };
@@ -1060,15 +1058,24 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         );
     }
 
+    @computed get editingView() {
+        return <TemplateEditingWindow
+                    setupButtonClick={this.setUpButtonClick} 
+                    template={this._currEditingTemplate as Template}
+                    menu={this}
+                />
+    }
+
     get renderSelectedViewType() {
         switch (this._menuContent) {
             case 'templates': 
                 return (
                     <div className='docCreatorMenu-templates-view'>
                         <div className="docCreatorMenu-templates-displays">
-                            <TemplateSidescrollView 
+                            <TemplatePreviewGrid 
                                 title={'Suggested Templates'} 
                                 menu={this} 
+                                optionsButtonOpts={['gear', () => (this._menuContent = 'dashboard')]}
                                 setupButtonClick={this.setUpButtonClick} 
                                 templates={this._suggestedTemplates}
                             />
@@ -1083,11 +1090,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                     </div>
                 )
             case 'templateEditing':
-                return <TemplateEditingWindow
-                    setupButtonClick={this.setUpButtonClick} 
-                    template={this._currEditingTemplate as Template}
-                    menu={this}
-                />
+                return this.editingView
             case 'options':   return this.optionsMenuContents;
             case 'dashboard': return this.dashboardContents;
         } // prettier-ignore
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index 08a48ccb9..f34b7efcf 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -7,13 +7,15 @@ import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc";
 import { DefaultStyleProvider } from "../../../../StyleProvider";
 import { DocumentView, DocumentViewInternal } from "../../../DocumentView";
 import { DocCreatorMenu } from "../DocCreatorMenu";
-import { TemplateSidescrollView } from "./TemplatesSidescrollDisplay";
+import { TemplatePreviewGrid } from "./TemplatePreviewGrid";
 import { observer } from "mobx-react";
 import { Transform } from "../../../../../util/Transform";
 import { Template } from "../Template";
 import { TemplateMenuAIUtils } from "../Backend/TemplateMenuAIUtils";
 import { ObservableReactComponent } from "../../../../ObservableReactComponent";
 import { IDisposer } from "mobx-utils";
+import { ImageField } from "../../../../../../fields/URLField";
+import { TemplatePreviewBoxProps } from "./TemplatePreviewBox";
 
 interface TemplateEditingWindowProps {
     menu: DocCreatorMenu;
@@ -28,8 +30,10 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
     private previewWindow: HTMLDivElement | null = null;
     private _disposers: { [name: string]: IDisposer } = {};
 
+    @observable _loading: boolean = false;
     @observable _variationsTabOpen: boolean = false;
-    @observable _variations: Doc[] = [];
+    @observable _variationURLs: string[] = [];
+    @observable _variations: Template[] = [];
 
     componentDidMount(): void {
         this._disposers.windowDimensions = reaction(() => 
@@ -57,15 +61,24 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
         }
     }
 
+    func = () => {
+        this._props.menu._variations.forEach(variation => {
+            variation.setImageAsBackground('https://www.onthegotours.com/repository/Great-Wall-New-Pic-228551391689622.jpg', true)
+        });
+    }
+
     get fireflyVariationsTab() {
 
         return (
             <>
-                <TemplateSidescrollView
+                <div className="docCreatorMenu-option-divider full no-margin-bottom"/>
+                <TemplatePreviewGrid
                     menu={this._props.menu}
                     title={'Generate Variations'}
-                    templates={[]}
+                    templates={this._props.menu._variations}
+                    optionsButtonOpts={['gear', this.func]}
                     setupButtonClick={this._props.setupButtonClick}
+                    previewBoxRightButtonOpts={['gear', (component: ObservableReactComponent<TemplatePreviewBoxProps>) => {component.forceUpdate();}]}
                 />
                 <div className="docCreatorMenu-section">
                     <div className="docCreatorMenu-variation-prompt-input">
@@ -77,7 +90,21 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                         />
                         <button className="docCreatorMenu-menu-button" 
                             onPointerDown={e => this._props.setupButtonClick(e, async () => {
-                                this._variations = await this._props.menu.generateVariations(this._props.template.getRenderedDoc()!, this.fireflyPrompt);
+                                this._props.menu._variations = [];
+                                this._loading = true;
+                                this._variationURLs = await this._props.menu.generateVariations(this._props.template.getRenderedDoc()!, this.fireflyPrompt);
+                                const templates: Template[] = [];
+                                this._variationURLs.forEach(url => {
+                                    const newTemplate: Template = this._props.template.cloneBase();
+                                    newTemplate.setImageAsBackground(url, true);
+                                    templates.push(newTemplate);
+                                });
+                                this._loading = false;
+                                setTimeout(() => {
+                                    console.log('setting')
+                                    this._props.menu._variations = templates;
+                                    this.forceUpdate();
+                                }, 1000);
                             })
                         }>
                             <FontAwesomeIcon icon="arrows-rotate" />
@@ -101,7 +128,7 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                     removeDocument={returnFalse}
                     PanelWidth={() => this.previewWindow?.clientWidth ?? 500}
                     PanelHeight={() => this.previewWindow?.clientHeight ?? 500}
-                    ScreenToLocalTransform={() => new Transform(/*-this._props.menu._pageX - 5, -this._props.menu._pageY - 35*/0, 0, 1)}
+                    ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)}
                     renderDepth={5}
                     whenChildContentsActiveChanged={emptyFunction}
                     focus={emptyFunction}
@@ -124,7 +151,6 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                 <div className="docCreatorMenu-expanded-template-preview">
                     <div className="top-panel"/>
                     {this.renderedDocPreview}
-                    <div className="docCreatorMenu-option-divider full"/>
                     {this._variationsTabOpen ? this.fireflyVariationsTab : null}
                     <div className="right-buttons-panel">
                         <button
@@ -141,8 +167,8 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                         </button>
                         <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this._props.setupButtonClick(e, async () => {
                                 this.setVariationTab(!this._variationsTabOpen);
+                                // this._props.template.setImageAsBackground('https://www.onthegotours.com/repository/Great-Wall-New-Pic-228551391689622.jpg', true);
                                 this.forceUpdate();
-                                console.log('window: ', this.previewWindow)
                             })}>
                             <FontAwesomeIcon icon="lightbulb" />
                         </button>
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
index e63be8f9c..a7270e540 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
@@ -11,8 +11,10 @@ import { emptyFunction } from "../../../../../../Utils";
 import { returnEmptyFilter, returnFalse } from "../../../../../../ClientUtils";
 import { Transform } from "../../../../../util/Transform";
 import { DefaultStyleProvider } from "../../../../StyleProvider";
-import { returnEmptyDoclist } from "../../../../../../fields/Doc";
+import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc";
 import { IDisposer } from "mobx-utils";
+import { ImageField } from "../../../../../../fields/URLField";
+import { ImageCast } from "../../../../../../fields/Types";
 
 export interface TemplatePreviewBoxProps {
     template: Template;
@@ -24,12 +26,34 @@ export interface TemplatePreviewBoxProps {
 export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBoxProps> {
 
     private previewWindow: HTMLDivElement | null = null;
+    // private icon: ImageField | undefined = undefined;
 
     setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
         this.previewWindow = node;
         this.forceUpdate();
     }
 
+    // componentDidMount(): void {
+    //     console.log('mounted')
+    //     setTimeout(() => {
+    //         const docView = DocumentView.getDocumentView(this.doc);
+    //         if (docView) {
+    //             console.log('docview found')
+    //             docView.ComponentView?.updateIcon?.();
+    //             setTimeout(() => {
+    //                 console.log('componentview found: ', docView.ComponentView)
+    //                 this.icon = ImageCast(docView.Document.icon);
+    //                 console.log('icon is:', this.icon, ' from: ', docView.Document.icon);
+    //                 this.forceUpdate();
+    //             }, 5000);
+    //         }
+    //     }, 1000);
+    // }
+
+    get doc() {
+        return this.props.template.getRenderedDoc() as Doc;
+    }
+
     render() {
         const template = this.props.template;
 
@@ -57,8 +81,10 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview
                     <button className="option-button right" onPointerDown={e => this.props.menu.setUpButtonClick(e, () => this.props.rightButtonOpts![1](template))}>
                         <FontAwesomeIcon icon={this.props.rightButtonOpts![0]} color="white" />
                     </button> : null }
+                    {/* { this.icon ? 
+                        <img className="docCreatorMenu-preview-image" src={this.icon.url.href.replace('.png', '_o.png')} /> */}
                     <DocumentView
-                        Document={this.props.template.getRenderedDoc()!} //!!! bad
+                        Document={this.doc}
                         isContentActive={emptyFunction} // !!! should be return false
                         addDocument={returnFalse}
                         moveDocument={returnFalse}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
new file mode 100644
index 000000000..fb246a0a0
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
@@ -0,0 +1,55 @@
+import { Colors } from "@dash/components/src";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, observable, runInAction } from "mobx";
+import React from "react";
+import ReactLoading from "react-loading";
+import { Doc } from "../../../../../../fields/Doc";
+import { StrCast } from "../../../../../../fields/Types";
+import { ObservableReactComponent } from "../../../../ObservableReactComponent";
+import { Template } from "../Template";
+import { observer } from "mobx-react";
+import { DocCreatorMenu } from "../DocCreatorMenu";
+import { TemplatePreviewBox } from "./TemplatePreviewBox";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
+
+export interface SuggestedTemplatesProps {
+    menu: DocCreatorMenu;
+    loading?: boolean;
+    templates: Template[];
+    title: string;
+    optionsButtonOpts?: [string, (...args: any) => any];
+    previewBoxLeftButtonOpts?: [string, (...args: any) => any];
+    previewBoxRightButtonOpts?: [string, (...args: any) => any];
+    setupButtonClick: (e: React.PointerEvent, func: () => void) => void;
+}
+
+@observer
+export class TemplatePreviewGrid extends ObservableReactComponent<SuggestedTemplatesProps> {
+
+    render() {
+        return (
+            <div className="docCreatorMenu-section">
+                <div className="docCreatorMenu-section-topbar">
+                    <div className="docCreatorMenu-section-title">{this.props.title}</div>
+                    {this._props.optionsButtonOpts ? (<button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.props.setupButtonClick(e, () => runInAction(this._props.optionsButtonOpts![1]))}>
+                        <FontAwesomeIcon icon={this._props.optionsButtonOpts[0] as IconProp} />
+                    </button>) : null}
+                </div>
+                <div className="docCreatorMenu-templates-preview-window">
+                    {this._props.loading ? 
+                    (<div className="loading-spinner">
+                        <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
+                    </div>)
+                    : this.props.templates.map(template => (
+                        <TemplatePreviewBox 
+                        template={template} 
+                        menu={this.props.menu} 
+                        leftButtonOpts={["magnifying-glass", (template: Template) => { this.props.menu.setExpandedView(template); this.forceUpdate(); }]}
+                        rightButtonOpts={["plus", (template: Template) => {}]}
+                        />
+                    ))}
+                </div>
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx
deleted file mode 100644
index 1f6eed9e5..000000000
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatesSidescrollDisplay.tsx
+++ /dev/null
@@ -1,50 +0,0 @@
-import { Colors } from "@dash/components/src";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction } from "mobx";
-import React from "react";
-import ReactLoading from "react-loading";
-import { Doc } from "../../../../../../fields/Doc";
-import { StrCast } from "../../../../../../fields/Types";
-import { ObservableReactComponent } from "../../../../ObservableReactComponent";
-import { Template } from "../Template";
-import { observer } from "mobx-react";
-import { DocCreatorMenu } from "../DocCreatorMenu";
-import { TemplatePreviewBox } from "./TemplatePreviewBox";
-
-export interface SuggestedTemplatesProps {
-    menu: DocCreatorMenu;
-    templates: Template[];
-    title: string;
-    previewBoxLeftButtonOpts?: [string, () => any];
-    previewBoxRightButtonOpts?: [string, () => any];
-    setupButtonClick: (e: React.PointerEvent, func: () => void) => void;
-}
-
-@observer
-export class TemplateSidescrollView extends ObservableReactComponent<SuggestedTemplatesProps> {
-
-    @observable _GPTLoading: boolean = false;
-
-    render() {
-        return (
-            <div className="docCreatorMenu-section">
-                <div className="docCreatorMenu-section-topbar">
-                    <div className="docCreatorMenu-section-title">{this.props.title}</div>
-                    <button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.props.setupButtonClick(e, () => runInAction(() => (this.props.menu._menuContent = 'dashboard')))}>
-                        <FontAwesomeIcon icon="gear" />
-                    </button>
-                </div>
-                <div className="docCreatorMenu-templates-preview-window" style={{ justifyContent: this.props.menu._menuDimensions.width > 400 ? 'center' : '' }}>
-                    {this.props.templates.map(template => (
-                        <TemplatePreviewBox 
-                        template={template} 
-                        menu={this.props.menu} 
-                        leftButtonOpts={["magnifying-glass", (template: Template) => { this.props.menu.setExpandedView(template); this.forceUpdate(); }]}
-                        rightButtonOpts={["plus", (template: Template) => {}]}
-                        />
-                    ))}
-                </div>
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
index 2bf9f8de5..1dc6692a2 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
@@ -4,6 +4,8 @@ import { TemplateFieldType, TemplateLayouts } from './TemplateBackend';
 import { DynamicField } from './TemplateFieldTypes/DynamicField';
 import { FieldSettings, TemplateField, ViewType } from './TemplateFieldTypes/TemplateField';
 import { Conditional } from './Backend/TemplateManager';
+import { ImageField } from '../../../../../fields/URLField';
+import { Doc } from '../../../../../fields/Doc';
 
 export class Template {
     _mainField: DynamicField;
@@ -110,6 +112,29 @@ export class Template {
         this.conditionalLogic[field] = this.conditionalLogic[field]?.filter(cond => cond !== statement);
     }
 
+    setImageAsBackground(url: string, makeTransparent: boolean = false) {
+        const fieldSettings: FieldSettings = {
+            tl: [-1, -1],
+            br: [1, 1],
+            opts: {},
+            viewType: ViewType.IMG,
+        }
+
+        const field: TemplateField = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField);
+        field.setContent(url);
+
+        if (makeTransparent) {
+            this.allFields.forEach(field => {
+                field.updateDocSetting('backgroundColor', 'transparent');
+                field.updateDocSetting('borderWidth', '0');
+            });
+        }
+
+        this._mainField.addField(field);
+        
+        this._mainField.refreshRenderedDoc();
+    }
+
     getMatches = (cols: Col[]): number[][] => {
         const numFields = this.contentFields.length;
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts
index a6d2136f4..06eab63f0 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts
@@ -351,7 +351,7 @@ export class TemplateLayouts {
         title: 'fourfield05',
         viewType: ViewType.FREEFORM,
         tl: [0, 0],
-        br: [400, 550],
+        br: [400, 514],
         opts: {
             backgroundColor: '#95A575',
         },
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
index 377f5acb2..ee8bcef51 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
@@ -10,10 +10,10 @@ import { FieldSettings, TemplateField, ViewType } from './TemplateField';
 
 export class DynamicField extends TemplateField {
     protected _disposers: { [name: string]: IDisposer } = {};
-    protected _subfields: TemplateField[] | undefined;
+    protected _subfields: TemplateField[] = [];
 
     get getSubfields() {
-        return this._subfields ?? [];
+        return this._subfields;
     }
     get getAllSubfields(): TemplateField[] {
         return this.getSubfields.flatMap(field => [field, ...((field as DynamicField).getAllSubfields ?? [])]);
@@ -24,7 +24,7 @@ export class DynamicField extends TemplateField {
         newDocsList.forEach(doc => !currRenderedDocs.has(doc) && this.addFieldFromDoc(doc));
         currRenderedDocs.forEach(doc => {
             if (!newDocsList.includes(doc)) {
-                this._subfields?.forEach(field => field.renderedDoc === doc && this.removeField(field));
+                this._subfields.forEach(field => field.renderedDoc === doc && this.removeField(field));
             }
         });
     };
@@ -38,14 +38,12 @@ export class DynamicField extends TemplateField {
             opts: {},
         };
 
-        this._subfields?.push(TemplateField.CreateField(settings, this._subfields.length, this));
+        this._subfields.push(TemplateField.CreateField(settings, this._subfields.length, this));
     };
 
-    addField = (field: TemplateField) => {
-        if (!this._subfields?.includes(field)) {
-            if (this._subfields) this._subfields?.push(field);
-            else this._subfields = [field];
-            // Doc.SetContainer(field.Document, this.Document);
+    addField = (field: TemplateField, layer: number = 0) => {
+        if (!this._subfields.includes(field)) {
+            this._subfields.splice(layer, 0, field);
         }
     };
 
@@ -53,13 +51,13 @@ export class DynamicField extends TemplateField {
 
     removeField = (field: TemplateField) => {
         // field.renderedDoc && this._renderDoc && Doc.RemoveDocFromList(this._renderDoc, undefined, field.renderedDoc);
-        this._subfields?.splice(this._subfields.indexOf(field), 1);
+        this._subfields.splice(this._subfields.indexOf(field), 1);
         (field as DynamicField).dispose?.();
     };
 
     // implement Field's abstract method for replacing a subfield with a new one
     exchangeFields(newField: TemplateField, oldField: TemplateField) {
-        this._subfields?.splice(this._subfields.indexOf(oldField), 1, newField);
+        this._subfields.splice(this._subfields.indexOf(oldField), 1, newField);
         this.initRenderDoc(this._settings);
     }
 
@@ -86,8 +84,9 @@ export class DynamicField extends TemplateField {
     }
 
     initRenderDoc = (settings: FieldSettings) => {
+        console.log('initializing')
         this._disposers.fieldList = reaction(() => DocListCast(this._renderDoc?.[Doc.LayoutFieldKey(this._renderDoc)]), this.handleFieldUpdate);
-        this._subfields = settings.subfields?.map((fieldSettings, index) => {fieldSettings.template = this.settings.template; return TemplateField.CreateField(fieldSettings, index, this)}) || [];
+        this._subfields = settings.subfields?.map((fieldSettings, index) => {return TemplateField.CreateField(fieldSettings, index, this)}) || [];
         const renderedSubfields = this._subfields.filter(field => field.renderedDoc).map(field => field.renderedDoc!);
         settings.opts.title = settings.title;
         this._renderDoc = (() => { switch (settings.viewType) {
@@ -97,4 +96,14 @@ export class DynamicField extends TemplateField {
         }})(); // prettier-ignore
         return this;
     };
+
+    refreshRenderedDoc = () => {
+        console.log('refreshing');
+        const renderedSubfields = this._subfields.filter(field => field.renderedDoc).map(field => field.renderedDoc!);
+        this._renderDoc = (() => { switch (this.settings.viewType) {
+            case ViewType.CAROUSEL3D: return Docs.Create.Carousel3DDocument(renderedSubfields, this.settings.opts);
+            case ViewType.FREEFORM:   
+            default:                  return Docs.Create.FreeformDocument(renderedSubfields, this.settings.opts);
+        }})(); 
+    }
 }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
index 7049bb56f..625a7bc4a 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
@@ -33,7 +33,7 @@ export class ImageTemplateField extends StaticContentField {
     initRenderDoc(settings: FieldSettings) {
         settings.opts.title = settings.title ?? '';
         settings.opts._layout_fitWidth = false;
-        this._renderDoc = Docs.Create.ImageDocument('', settings.opts);
+        this._renderDoc = Docs.Create.ImageDocument(this._content, settings.opts);
         return this;
     }
 }
@@ -46,7 +46,7 @@ export class TextTemplateField extends StaticContentField {
     initRenderDoc(settings: FieldSettings) {
         settings.opts.title = settings.title ?? '';
         settings.opts.text_fontSize = TemplateFieldUtils.calculateFontSize(this._dimensions?.width ?? 10, this._dimensions?.height ?? 10, '', true) + '';
-        this._renderDoc = Docs.Create.TextDocument('', settings.opts);
+        this._renderDoc = Docs.Create.TextDocument(this._content, settings.opts);
         return this;
     }
 }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
index b4ba3fe7d..e178756f9 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
@@ -71,6 +71,10 @@ export abstract class TemplateField {
     };
     getTitle = () => this._title;
 
+    updateDocSetting = (setting: string, newVal: string) => {
+        if (this._renderDoc) this._renderDoc[setting] = newVal;
+    }
+
     makeClone(parent?: TemplateField) {
         const settings: FieldSettings = structuredClone(this._settings);
         const cloned = TemplateField.CreateField(settings, this._id, parent, true); // create a value for this.Document/subfields that we want to ignore
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index 3d21029ab..ad5ef7118 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -51,18 +51,18 @@ export class DrawingFillHandler {
                                 const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { _width: 400, _height: 400 });
                                 drawing[DocData].ai_firefly_generatedDocs = genratedDocs;
                                 (res as Upload.ImageInformation[]).map(info => {
-                                    const doc = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, {
-                                        ai: 'firefly',
-                                        tags: new List<string>(['@ai']),
-                                        title: newPrompt,
-                                        _data_usePath: 'alternate:hover',
-                                        data_alternates: new List<Doc>([drawing]),
-                                        ai_firefly_prompt: newPrompt,
-                                        _width: 500,
-                                        data_nativeWidth: info.nativeWidth,
-                                        data_nativeHeight: info.nativeHeight,
-                                    })
                                     if (!fromDocCreator) {
+                                        const doc = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, {
+                                            ai: 'firefly',
+                                            tags: new List<string>(['@ai']),
+                                            title: newPrompt,
+                                            _data_usePath: 'alternate:hover',
+                                            data_alternates: new List<Doc>([drawing]),
+                                            ai_firefly_prompt: newPrompt,
+                                            _width: 500,
+                                            data_nativeWidth: info.nativeWidth,
+                                            data_nativeHeight: info.nativeHeight,
+                                        })
                                         Doc.AddDocToList(
                                             genratedDocs,
                                             undefined,
@@ -72,7 +72,7 @@ export class DrawingFillHandler {
                                             true
                                         );
                                     } else {
-                                        fromDocCreator.addVariationDoc(doc);
+                                        fromDocCreator.addVariation(info.accessPaths.agnostic.client);
                                     }
                                 });
                                 if (!DocumentView.getFirstDocumentView(genratedDocs) && !fromDocCreator) {
-- 
cgit v1.2.3-70-g09d2


From 59678e34dad0b6587f28ec642c84f6c6ee06543b Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 11 May 2025 02:08:38 -0400
Subject: firefly templates correctly loading

---
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx          | 14 +++++++-------
 .../DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx  |  4 ++--
 .../DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx |  8 ++++----
 3 files changed, 13 insertions(+), 13 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index f34b7efcf..fa84616cc 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -75,10 +75,11 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                 <TemplatePreviewGrid
                     menu={this._props.menu}
                     title={'Generate Variations'}
+                    loading={this._loading}
                     templates={this._props.menu._variations}
                     optionsButtonOpts={['gear', this.func]}
                     setupButtonClick={this._props.setupButtonClick}
-                    previewBoxRightButtonOpts={['gear', (component: ObservableReactComponent<TemplatePreviewBoxProps>) => {component.forceUpdate();}]}
+                    previewBoxRightButtonOpts={['gear', (template: Template) => {this.forceUpdate();}]}
                 />
                 <div className="docCreatorMenu-section">
                     <div className="docCreatorMenu-variation-prompt-input">
@@ -93,18 +94,17 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                                 this._props.menu._variations = [];
                                 this._loading = true;
                                 this._variationURLs = await this._props.menu.generateVariations(this._props.template.getRenderedDoc()!, this.fireflyPrompt);
-                                const templates: Template[] = [];
                                 this._variationURLs.forEach(url => {
                                     const newTemplate: Template = this._props.template.cloneBase();
-                                    newTemplate.setImageAsBackground(url, true);
-                                    templates.push(newTemplate);
+                                    this._props.menu._variations.push(newTemplate);
                                 });
                                 this._loading = false;
                                 setTimeout(() => {
-                                    console.log('setting')
-                                    this._props.menu._variations = templates;
+                                    this._variationURLs.forEach((url, i) => {
+                                        this._props.menu._variations[i].setImageAsBackground(url, true);
+                                    });
                                     this.forceUpdate();
-                                }, 1000);
+                                });
                             })
                         }>
                             <FontAwesomeIcon icon="arrows-rotate" />
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
index a7270e540..e94ddfc15 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
@@ -19,8 +19,8 @@ import { ImageCast } from "../../../../../../fields/Types";
 export interface TemplatePreviewBoxProps {
     template: Template;
     menu: DocCreatorMenu;
-    leftButtonOpts?: [icon: IconProp, func: (template: Template) => void]
-    rightButtonOpts?: [icon: IconProp, func: (template: Template) => void]
+    leftButtonOpts?: [icon: IconProp, func: (...args: any) => void]
+    rightButtonOpts?: [icon: IconProp, func: (...args: any) => void]
 }
 
 export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBoxProps> {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
index fb246a0a0..e78109b62 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
@@ -17,9 +17,9 @@ export interface SuggestedTemplatesProps {
     loading?: boolean;
     templates: Template[];
     title: string;
-    optionsButtonOpts?: [string, (...args: any) => any];
-    previewBoxLeftButtonOpts?: [string, (...args: any) => any];
-    previewBoxRightButtonOpts?: [string, (...args: any) => any];
+    optionsButtonOpts?: [IconProp, (...args: any) => any];
+    previewBoxLeftButtonOpts?: [IconProp, (...args: any) => any];
+    previewBoxRightButtonOpts?: [IconProp, (...args: any) => any];
     setupButtonClick: (e: React.PointerEvent, func: () => void) => void;
 }
 
@@ -45,7 +45,7 @@ export class TemplatePreviewGrid extends ObservableReactComponent<SuggestedTempl
                         template={template} 
                         menu={this.props.menu} 
                         leftButtonOpts={["magnifying-glass", (template: Template) => { this.props.menu.setExpandedView(template); this.forceUpdate(); }]}
-                        rightButtonOpts={["plus", (template: Template) => {}]}
+                        rightButtonOpts={this._props.previewBoxRightButtonOpts}
                         />
                     ))}
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 8195cb97556765a8025de554a44f48d4c15d4c64 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 11 May 2025 03:06:02 -0400
Subject: text and images are no longer messed up

---
 src/client/apis/gpt/GPT.ts                         |  2 +-
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.scss  | 15 +++++++++
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   |  6 ++--
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  |  9 +----
 .../DocCreatorMenu/Menu/TemplatePreviewBox.tsx     | 38 ++++++++++++----------
 .../TemplateFieldTypes/DynamicField.ts             |  4 +--
 .../TemplateFieldTypes/StaticContentField.ts       |  4 ++-
 .../TemplateFieldTypes/TemplateField.ts            |  4 ++-
 8 files changed, 46 insertions(+), 36 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 29b6ab989..57a229569 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -101,7 +101,7 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = {
         prompt: "BRIEFLY (<50 words) describe any differences between the rubric and the user's answer answer in second person. If there are no differences, say correct",
     },
     template: {
-        model: 'gpt-4-turbo',
+        model: 'gpt-4.1',
         maxTokens: 512,
         temp: 0.5,
         prompt: 'You will be given a list of field descriptions for one or more templates in the format {field #0: “description”}{field #1: “description”}{...}, and a list of column descriptions in the format {“title”: “description”}{...}. Your job is to match columns with fields based on their descriptions. Your output should be in the following JSON format: {“template_title”:{“#”: “title”, “#”: “title”, “#”: “title” …}, “template_title”:{“#”: “title”, “#”: “title”, “#”: “title” …}} where “template_title” is the templates title as specified in the description provided,  # represents the field # and “title” the title of the column assigned to it. A filled out example might look like {“fivefield2”:{“0”:”Name”, “1”:”Image”, “2”:”Caption”, “3”:”Position”, “4”:”Stats”}, “fivefield3”:{0:”Image”, 1:”Name”, 2:”Caption”, 3:”Stats”, 4:”Position”}. Include one object for each template. IT IS VERY IMPORTANT THAT YOU ONLY INCLUDE TEXT IN THE FORMAT ABOVE, WITH NO ADDITIONS WHATSOEVER. Do not include extraneous ‘#’ characters, ‘column’ for columns, or ‘template’ for templates: ONLY THE TITLES AND NUMBERS. There should never be one column assigned to more than one field (ie. if the “name” column is assigned to field 1, it can’t be assigned to any other fields) . Do this for each template whose fields are described. The descriptions are as follows:',
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
index 082ebb7dc..9fb973265 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
@@ -705,6 +705,19 @@
     }
 }
 
+.loading-spinner {
+    position: absolute;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    height: 100%;
+    width: 100%;
+    z-index: 200;
+    font-size: 20px;
+    font-weight: bold;
+    color: #17175e;
+}
+
 .docCreatorMenu-layout-preview-window-wrapper {
     flex: 0 0 auto;
     display: flex;
@@ -791,6 +804,8 @@
     }
 }
 
+
+
 //------------------------------------------------------------------------------------------------------------------------------------------
 // DocCreatorMenu dashboard CSS
 //--------------------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index 2ddaa214c..7beb93636 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -88,7 +88,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     // eslint-disable-next-line no-use-before-define
     static Instance: DocCreatorMenu;
 
-    private DEBUG_MODE: boolean = true;
+    private DEBUG_MODE: boolean = false;
     private _ref: HTMLDivElement | null = null;
     private templateManager: TemplateManager;
 
@@ -594,7 +594,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     };
 
     generateVariations = async (onDoc: Doc, prompt: string): Promise<string[]> => {
-        this._loadingVariants = true;
         this.variations = [];
         const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
 
@@ -605,8 +604,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
         await DrawingFillHandler.drawingToImage(clone, 100, prompt, undefined, this)
 
-        this._loadingVariants = false;
-
         return this.variations;
     }
 
@@ -1075,6 +1072,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                             <TemplatePreviewGrid 
                                 title={'Suggested Templates'} 
                                 menu={this} 
+                                loading={this._GPTLoading}
                                 optionsButtonOpts={['gear', () => (this._menuContent = 'dashboard')]}
                                 setupButtonClick={this.setUpButtonClick} 
                                 templates={this._suggestedTemplates}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index fa84616cc..8b58ac1cf 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -15,7 +15,6 @@ import { TemplateMenuAIUtils } from "../Backend/TemplateMenuAIUtils";
 import { ObservableReactComponent } from "../../../../ObservableReactComponent";
 import { IDisposer } from "mobx-utils";
 import { ImageField } from "../../../../../../fields/URLField";
-import { TemplatePreviewBoxProps } from "./TemplatePreviewBox";
 
 interface TemplateEditingWindowProps {
     menu: DocCreatorMenu;
@@ -61,12 +60,6 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
         }
     }
 
-    func = () => {
-        this._props.menu._variations.forEach(variation => {
-            variation.setImageAsBackground('https://www.onthegotours.com/repository/Great-Wall-New-Pic-228551391689622.jpg', true)
-        });
-    }
-
     get fireflyVariationsTab() {
 
         return (
@@ -77,7 +70,7 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                     title={'Generate Variations'}
                     loading={this._loading}
                     templates={this._props.menu._variations}
-                    optionsButtonOpts={['gear', this.func]}
+                    optionsButtonOpts={['gear', () => {}]}
                     setupButtonClick={this._props.setupButtonClick}
                     previewBoxRightButtonOpts={['gear', (template: Template) => {this.forceUpdate();}]}
                 />
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
index e94ddfc15..c9f817d2f 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
@@ -1,5 +1,5 @@
 import { Colors } from "@dash/components/src";
-import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
+import { FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 import { Template } from "../Template";
 import { reaction, runInAction } from "mobx";
 import React from "react";
@@ -15,15 +15,17 @@ import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc";
 import { IDisposer } from "mobx-utils";
 import { ImageField } from "../../../../../../fields/URLField";
 import { ImageCast } from "../../../../../../fields/Types";
+import { observer } from "mobx-react";
 
-export interface TemplatePreviewBoxProps {
+export interface TemplatePreviewBox_props {
     template: Template;
     menu: DocCreatorMenu;
     leftButtonOpts?: [icon: IconProp, func: (...args: any) => void]
     rightButtonOpts?: [icon: IconProp, func: (...args: any) => void]
 }
 
-export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBoxProps> {
+@observer
+export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBox_props> {
 
     private previewWindow: HTMLDivElement | null = null;
     // private icon: ImageField | undefined = undefined;
@@ -51,11 +53,11 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview
     // }
 
     get doc() {
-        return this.props.template.getRenderedDoc() as Doc;
+        return this._props.template.getRenderedDoc() as Doc;
     }
 
     render() {
-        const template = this.props.template;
+        const template = this._props.template;
 
         return (
             <div
@@ -63,23 +65,23 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview
                 className="docCreatorMenu-preview-window"
                 ref={this.setContainerRef}
                 style={{
-                    border: this.props.menu._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
-                    boxShadow: this.props.menu._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
+                    border: this._props.menu._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
+                    boxShadow: this._props.menu._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
                 }}
-                onPointerDown={e => this.props.menu.setUpButtonClick(e, () => runInAction(() => {console.log('setting'); this.props.menu._selectedTemplate = template}))}
+                onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.menu.updateSelectedTemplate(template))}
                 >
-                    { this.props.leftButtonOpts ? 
+                    { this._props.leftButtonOpts ? 
                     <button
                         className="option-button left"
                         onPointerDown={e =>
-                            this.props.menu.setUpButtonClick(e, () => this.props.leftButtonOpts![1](template))
+                            this._props.menu.setUpButtonClick(e, () => this._props.leftButtonOpts![1](template))
                         }>
-                        <FontAwesomeIcon icon={this.props.leftButtonOpts![0]} color="white" />
+                        <FontAwesomeIcon icon={this._props.leftButtonOpts![0]} color="white" />
                     </button> : null
                     }
-                    { this.props.rightButtonOpts ? 
-                    <button className="option-button right" onPointerDown={e => this.props.menu.setUpButtonClick(e, () => this.props.rightButtonOpts![1](template))}>
-                        <FontAwesomeIcon icon={this.props.rightButtonOpts![0]} color="white" />
+                    { this._props.rightButtonOpts ? 
+                    <button className="option-button right" onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.rightButtonOpts![1](template))}>
+                        <FontAwesomeIcon icon={this._props.rightButtonOpts![0]} color="white" />
                     </button> : null }
                     {/* { this.icon ? 
                         <img className="docCreatorMenu-preview-image" src={this.icon.url.href.replace('.png', '_o.png')} /> */}
@@ -89,14 +91,14 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview
                         addDocument={returnFalse}
                         moveDocument={returnFalse}
                         removeDocument={returnFalse}
-                        PanelWidth={() => this.previewWindow?.clientWidth ?? this.props.menu._menuDimensions.height * .3}
-                        PanelHeight={() => this.previewWindow?.clientHeight ?? this.props.menu._menuDimensions.height * .3}
-                        ScreenToLocalTransform={() => new Transform(-this.props.menu._pageX - 5, -this.props.menu._pageY - 35, 1)}
+                        PanelWidth={() => this.previewWindow?.clientWidth ?? this._props.menu._menuDimensions.height * .3}
+                        PanelHeight={() => this.previewWindow?.clientHeight ?? this._props.menu._menuDimensions.height * .3}
+                        ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)}
                         renderDepth={1}
                         whenChildContentsActiveChanged={emptyFunction}
                         focus={emptyFunction}
                         styleProvider={DefaultStyleProvider}
-                        addDocTab={this.props.menu._props.addDocTab}
+                        addDocTab={this._props.menu._props.addDocTab}
                         pinToPres={() => undefined}
                         childFilters={returnEmptyFilter}
                         childFiltersByRanges={returnEmptyFilter}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
index ee8bcef51..5fba68c14 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
@@ -58,7 +58,7 @@ export class DynamicField extends TemplateField {
     // implement Field's abstract method for replacing a subfield with a new one
     exchangeFields(newField: TemplateField, oldField: TemplateField) {
         this._subfields.splice(this._subfields.indexOf(oldField), 1, newField);
-        this.initRenderDoc(this._settings);
+        this.refreshRenderedDoc();
     }
 
     get isContentField(): boolean {
@@ -84,7 +84,6 @@ export class DynamicField extends TemplateField {
     }
 
     initRenderDoc = (settings: FieldSettings) => {
-        console.log('initializing')
         this._disposers.fieldList = reaction(() => DocListCast(this._renderDoc?.[Doc.LayoutFieldKey(this._renderDoc)]), this.handleFieldUpdate);
         this._subfields = settings.subfields?.map((fieldSettings, index) => {return TemplateField.CreateField(fieldSettings, index, this)}) || [];
         const renderedSubfields = this._subfields.filter(field => field.renderedDoc).map(field => field.renderedDoc!);
@@ -98,7 +97,6 @@ export class DynamicField extends TemplateField {
     };
 
     refreshRenderedDoc = () => {
-        console.log('refreshing');
         const renderedSubfields = this._subfields.filter(field => field.renderedDoc).map(field => field.renderedDoc!);
         this._renderDoc = (() => { switch (this.settings.viewType) {
             case ViewType.CAROUSEL3D: return Docs.Create.Carousel3DDocument(renderedSubfields, this.settings.opts);
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
index 625a7bc4a..420d03076 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
@@ -1,3 +1,4 @@
+import { FontSize } from '@dash/components';
 import { FieldResult } from '../../../../../../fields/Doc';
 import { DocData } from '../../../../../../fields/DocSymbols';
 import { RichTextField } from '../../../../../../fields/RichTextField';
@@ -40,7 +41,8 @@ export class ImageTemplateField extends StaticContentField {
 
 export class TextTemplateField extends StaticContentField {
     setContent(text: string, type?: ViewType) {
-        this.setDataContent(ViewType.TEXT, 'text', RichTextField.textToRtf(text), text, type);
+        const fontSize: number = TemplateFieldUtils.calculateFontSize(this._dimensions?.width ?? 10, this._dimensions?.height ?? 10, text, true);
+        this.setDataContent(ViewType.TEXT, 'text', RichTextField.textToRtf(text, undefined, {fontSize: fontSize}), text, type);
     }
 
     initRenderDoc(settings: FieldSettings) {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
index e178756f9..e2779968d 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
@@ -62,7 +62,7 @@ export abstract class TemplateField {
     abstract getContent(): string;
 
     setContent(content: string, type?: ViewType) {
-        this._settings && (this._settings.viewType = type ?? this._settings.viewType);
+        if (type) this._settings.viewType = type;
     }
 
     setTitle = (title: string) => {
@@ -73,6 +73,8 @@ export abstract class TemplateField {
 
     updateDocSetting = (setting: string, newVal: string) => {
         if (this._renderDoc) this._renderDoc[setting] = newVal;
+        const settings: {[s: string]: string } = {[setting]: newVal}
+        Object.assign(this.settings.opts, settings);
     }
 
     makeClone(parent?: TemplateField) {
-- 
cgit v1.2.3-70-g09d2


From 4af498433a887c70dc7043a5a34eef7fff5bbbe0 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 11 May 2025 07:51:59 -0400
Subject: a million and one things

---
 src/client/apis/gpt/GPT.ts                         |  1 +
 .../DocCreatorMenu/Backend/TemplateManager.ts      | 31 ++++++++++++++--------
 .../DocCreatorMenu/Backend/TemplateMenuAIUtils.ts  | 23 ++++++++--------
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 17 ++++++------
 .../DocCreatorMenu/Menu/TemplateEditingScreen.tsx  |  0
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  |  4 +--
 .../nodes/DataVizBox/DocCreatorMenu/Template.ts    |  6 ++---
 .../TemplateFieldTypes/DynamicField.ts             | 25 +++++++++++++++--
 .../TemplateFieldTypes/StaticContentField.ts       |  5 ++++
 .../TemplateFieldTypes/TemplateField.ts            | 16 +++++------
 src/client/views/nodes/ImageBox.scss               |  2 +-
 11 files changed, 82 insertions(+), 48 deletions(-)
 delete mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 57a229569..d7b378958 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -221,6 +221,7 @@ const gptAPICall = async (inputTextIn: string, callType: GPTCallType, prompt?: s
 const gptImageCall = async (prompt: string, n?: number) => {
     try {
         const response = await openai.images.generate({
+            model: 'dall-e-3',
             prompt: prompt,
             n: n ?? 1,
             size: '1024x1024',
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
index ef7dbc7ab..d11f05766 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
@@ -53,11 +53,11 @@ export class TemplateManager {
         }
     }
 
-    createDocsFromTemplate = action((dv: DataVizBox, template: Template, csvColumns: Col[], debug: boolean = false) => {
+    createDocsFromTemplate = action((dv: DataVizBox, template: Template, cols: Col[], debug: boolean = false) => {
         const fields = Array.from(Object.keys(dv.records[0]));
 
-        const processContent = (content: { [title: string]: string }) => {
-            const templateCopy = template.cloneBase();
+        const processContent = async (content: { [title: string]: string }) => {
+            const templateCopy = template.clone();
 
             fields
                 .filter(title => title)
@@ -67,14 +67,22 @@ export class TemplateManager {
                 });
 
             const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? TemplateMenuAIUtils.renderGPTImageCall : TemplateMenuAIUtils.renderGPTTextCall);
-            const gptPromises = csvColumns
-                .filter(field => field.type !== TemplateFieldType.UNSET && field.AIGenerated)
-                .map(field => {
-                    const templateField = templateCopy.getFieldByTitle(field.title);
-                    if (templateField !== undefined) {
-                        return gptFunc(field.type)(templateCopy, field, templateField.getID);
-                    }
-                });
+            const applyGPTContent = async () => {
+                const promises = cols
+                    .filter(field => field.AIGenerated)
+                    .map(field => {
+                        const templateField = templateCopy.getFieldByTitle(field.title);
+                        if (templateField !== undefined) {
+                            return gptFunc(field.type)(templateCopy, field, templateField.getID);
+                        }
+                        return null; 
+                    })
+                    .filter(p => p !== null);
+            
+                await Promise.all(promises);
+            };
+
+            await applyGPTContent();
 
             return templateCopy.getRenderedDoc();
         };
@@ -90,6 +98,7 @@ export class TemplateManager {
                       {} as { [title: string]: string }
                   )
               );
+
         return Promise.all(rowContents.map(processContent)).then(
             action(renderedDocs => {
                 return renderedDocs;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts
index 446fe3442..9bc2bfce2 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts
@@ -27,12 +27,13 @@ export class TemplateMenuAIUtils {
         }
     };
 
-    public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number | undefined): Promise<boolean> => {
-        const generateAndLoadImage = async (fieldNum: string, column: Col, prompt: string) => {
+    public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number): Promise<boolean> => {
+        const generateAndLoadImage = async (id: number, prompt: string) => {
             const url = await this.generateGPTImage(prompt);
-            const field: TemplateField = template.getFieldByID(Number(fieldNum));
+            var field: TemplateField = template.getFieldByID(id);
 
             field.setContent(url ?? '', ViewType.IMG);
+            field = template.getFieldByID(id);
             field.setTitle(col.title);
         };
 
@@ -40,14 +41,14 @@ export class TemplateMenuAIUtils {
 
         try {
             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: ' +
+                `#${Math.random() * 100}: 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);
 
-            await generateAndLoadImage(String(fieldNumber), col, prompt);
+            await generateAndLoadImage(fieldNumber, prompt);
         } catch (e) {
             console.log(e);
         }
@@ -104,20 +105,20 @@ export class TemplateMenuAIUtils {
      * @returns a doc containing the fully rendered template
      */
     public static applyGPTContentToTemplate = async (template: Template, assignments: { [field: string]: Col }): Promise<Template | undefined> => {
-        const GPTTextCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.TEXT && !col.AIGenerated);
-        const GPTIMGCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.VISUAL && !col.AIGenerated);
+        const GPTTextCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.TEXT && col.AIGenerated);
+        const GPTIMGCalls = Object.entries(assignments).filter(([, col]) => col.type === TemplateFieldType.VISUAL && col.AIGenerated);
 
         if (GPTTextCalls.length) {
-            const promises = GPTTextCalls.map(([str, col]) => {
-                return TemplateMenuAIUtils.renderGPTTextCall(template, col, Number(str));
+            const promises = GPTTextCalls.map(([id, col]) => {
+                return TemplateMenuAIUtils.renderGPTTextCall(template, col, Number(id));
             });
 
             await Promise.all(promises);
         }
 
         if (GPTIMGCalls.length) {
-            const promises = GPTIMGCalls.map(async ([fieldNum, col]) => {
-                return TemplateMenuAIUtils.renderGPTImageCall(template, col, Number(fieldNum));
+            const promises = GPTIMGCalls.map(async ([id, col]) => {
+                return TemplateMenuAIUtils.renderGPTImageCall(template, col, Number(id));
             });
 
             await Promise.all(promises);
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index 7beb93636..8a98399b6 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -374,6 +374,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     }
 
     @action updateRenderedPreviewCollection = async (template: Template) => {
+        this._docsRendering = true;
         this._fullyRenderedDocs = this._dataViz ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, template, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : [];
         this.updateRenderedDocCollection();
     };
@@ -384,7 +385,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
             return;
         } else {
             this._selectedTemplate = template;
-            //template.renderUpdates();
             this.updateRenderedPreviewCollection(template);
         }
     };
@@ -434,7 +434,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     };
 
     @action addField = () => {
-        const newFields: Col[] = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [] }]);
+        const newFields: Col[] = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [], AIGenerated: true }]);
         this._userCreatedFields = newFields;
     };
 
@@ -544,10 +544,10 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                     const toObj = Object.entries(assignment).reduce(
                         (a, [fieldID, colTitle]) => {
                             const col = this.getColByTitle(colTitle);
-                            if (!this._userCreatedFields.includes(col)) {
-                                // do the following for any fields not added by the user; will change in the future, for now only GPT content works with user-added field
-                                const field = template.getFieldByID(Number(fieldID));
+                            if (!col.AIGenerated) {
+                                var field = template.getFieldByID(Number(fieldID));
                                 field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT);
+                                field = template.getFieldByID(Number(fieldID));
                                 field.setTitle(col.title);
                             } else {
                                 a[Number(fieldID)] = this.getColByTitle(colTitle);
@@ -602,7 +602,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         clone.x = 10000;
         clone.y = 10000;
 
-        await DrawingFillHandler.drawingToImage(clone, 100, prompt, undefined, this)
+        await DrawingFillHandler.drawingToImage(clone, 100, prompt, clone, this)
 
         return this.variations;
     }
@@ -762,10 +762,9 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
         this._renderedDocCollection = collection;
 
-        // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-        // mainCollection.addDocument(collection);
+        this._docsRendering = false;
 
-        console.log('changed to: ', collection);
+        this.forceUpdate();
     };
 
     layoutPreviewContents = action(() => {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingScreen.tsx
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index 8b58ac1cf..df6b791c7 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -86,9 +86,9 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                             onPointerDown={e => this._props.setupButtonClick(e, async () => {
                                 this._props.menu._variations = [];
                                 this._loading = true;
-                                this._variationURLs = await this._props.menu.generateVariations(this._props.template.getRenderedDoc()!, this.fireflyPrompt);
+                                this._variationURLs = await this._props.menu.generateVariations(this._props.template.clone(false).getRenderedDoc()!, this.fireflyPrompt);
                                 this._variationURLs.forEach(url => {
-                                    const newTemplate: Template = this._props.template.cloneBase();
+                                    const newTemplate: Template = this._props.template.clone(true);
                                     this._props.menu._variations.push(newTemplate);
                                 });
                                 this._loading = false;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
index 1dc6692a2..10724c9f7 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
@@ -57,7 +57,7 @@ export class Template {
         //dispose each subfields disposers, etc.
     };
 
-    cloneBase = () => new Template(this._mainField?.makeClone(undefined) ?? TemplateLayouts.BasicSettings);
+    clone = (withContent: boolean = false) => new Template(this._mainField?.makeClone(undefined, withContent) ?? TemplateLayouts.BasicSettings);
 
     getRenderedDoc = () => this.doc;
 
@@ -130,9 +130,7 @@ export class Template {
             });
         }
 
-        this._mainField.addField(field);
-        
-        this._mainField.refreshRenderedDoc();
+        this._mainField.makeBackgroundField(field)
     }
 
     getMatches = (cols: Col[]): number[][] => {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
index 5fba68c14..fc520ba47 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
@@ -11,6 +11,7 @@ import { FieldSettings, TemplateField, ViewType } from './TemplateField';
 export class DynamicField extends TemplateField {
     protected _disposers: { [name: string]: IDisposer } = {};
     protected _subfields: TemplateField[] = [];
+    protected backgroundField: TemplateField | undefined;
 
     get getSubfields() {
         return this._subfields;
@@ -72,11 +73,31 @@ export class DynamicField extends TemplateField {
 
     addChildToDocument = (doc: Doc) => this._renderDoc && Doc.SetContainer(doc, this._renderDoc);
 
+    makeBackgroundField = (field: TemplateField) => {
+        if (this.backgroundField && this.backgroundField !== field) {
+            this.removeField(this.backgroundField);
+            this.backgroundField = undefined;
+        }
+        if (field && field !== this.backgroundField) {
+            this.addField(field);
+            this.backgroundField = field;
+        }
+        this.refreshRenderedDoc();
+    }
+
     matches = (): Array<number> => [];
 
-    makeClone(parent?: DynamicField) {
+    makeClone(parent?: DynamicField, withContent: boolean = false) {
         const dynClone = super.makeClone(parent) as DynamicField;
-        dynClone._subfields = this.getSubfields.map(cloneField => cloneField.makeClone(dynClone));
+        dynClone._subfields = this.getSubfields.map(field => {
+            if (field === this.backgroundField) {
+                const backgroundField: TemplateField = field.makeClone(dynClone, true);
+                dynClone.makeBackgroundField(backgroundField);
+                return backgroundField;
+            } else {
+                return field.makeClone(dynClone, withContent)
+            }
+        });
         if (dynClone._renderDoc) {
             dynClone._renderDoc[DocData].data = new List<Doc>(dynClone.getSubfields.filter(sub => sub.renderedDoc).map(sub => sub.renderedDoc!));
         }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
index 420d03076..112f37cc6 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
@@ -37,6 +37,11 @@ export class ImageTemplateField extends StaticContentField {
         this._renderDoc = Docs.Create.ImageDocument(this._content, settings.opts);
         return this;
     }
+
+    updateDocSetting(setting: string, newVal: string) {
+        if (setting === 'backgroundColor') return;
+        super.updateDocSetting(setting, newVal);
+    }
 }
 
 export class TextTemplateField extends StaticContentField {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
index e2779968d..bc6b13c76 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
@@ -67,22 +67,23 @@ export abstract class TemplateField {
 
     setTitle = (title: string) => {
         this._title = title;
-        this._renderDoc && (this._renderDoc.title = title);
+        this.settings.title = title;
+        if (this._renderDoc) this._renderDoc.title = title
     };
     getTitle = () => this._title;
 
-    updateDocSetting = (setting: string, newVal: string) => {
+    updateDocSetting(setting: string, newVal: string) {
         if (this._renderDoc) this._renderDoc[setting] = newVal;
         const settings: {[s: string]: string } = {[setting]: newVal}
         Object.assign(this.settings.opts, settings);
     }
 
-    makeClone(parent?: TemplateField) {
+    makeClone(parent?: TemplateField, withContent: boolean = false) {
         const settings: FieldSettings = structuredClone(this._settings);
         const cloned = TemplateField.CreateField(settings, this._id, parent, true); // create a value for this.Document/subfields that we want to ignore
-        this._renderDoc && Doc.MakeClone(this._renderDoc).then(({ clone }) => (cloned._renderDoc = clone));
-        cloned._title = this._title;
+        cloned.setTitle(this._title);
         cloned._dimensions = this._dimensions;
+        withContent && cloned.setContent(this.getContent());
         return cloned;
     }
 
@@ -91,9 +92,8 @@ export abstract class TemplateField {
     }
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
     changeFieldType = (newType: ViewType): TemplateField => {
-        const newSettings = this._settings;
-        newSettings.viewType = newType;
-        const newField = TemplateField.CreateField(newSettings, this._id, this._parent, true);
+        this._settings.viewType = newType;
+        const newField = TemplateField.CreateField(this._settings, this._id, this._parent, true);
         this._parent?.exchangeFields(newField, this);
         return newField;
     };
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 3d6942e6f..5625f2f56 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -105,7 +105,7 @@
     display: flex;
     height: 100%;
     img {
-        object-fit: contain;
+        // object-fit: contain;
         height: 100%;
     }
 
-- 
cgit v1.2.3-70-g09d2


From e62f51bacace3d91f388202135426445721097cc Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 11 May 2025 16:52:17 -0400
Subject: menubutton component

---
 src/client/apis/gpt/GPT.ts                         |  4 +-
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.scss  | 34 ++++----
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 32 ++------
 .../DocCreatorMenu/Menu/DocCreatorMenuButton.tsx   | 41 ++++++++++
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  | 90 +++++++++++-----------
 .../DocCreatorMenu/Menu/TemplatePreviewGrid.tsx    |  8 +-
 .../nodes/DataVizBox/DocCreatorMenu/Template.ts    |  2 +-
 7 files changed, 122 insertions(+), 89 deletions(-)
 create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/DocCreatorMenuButton.tsx

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index d7b378958..455551f40 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -107,13 +107,13 @@ const callTypeMap: { [type in GPTCallType]: GPTCallOpts } = {
         prompt: 'You will be given a list of field descriptions for one or more templates in the format {field #0: “description”}{field #1: “description”}{...}, and a list of column descriptions in the format {“title”: “description”}{...}. Your job is to match columns with fields based on their descriptions. Your output should be in the following JSON format: {“template_title”:{“#”: “title”, “#”: “title”, “#”: “title” …}, “template_title”:{“#”: “title”, “#”: “title”, “#”: “title” …}} where “template_title” is the templates title as specified in the description provided,  # represents the field # and “title” the title of the column assigned to it. A filled out example might look like {“fivefield2”:{“0”:”Name”, “1”:”Image”, “2”:”Caption”, “3”:”Position”, “4”:”Stats”}, “fivefield3”:{0:”Image”, 1:”Name”, 2:”Caption”, 3:”Stats”, 4:”Position”}. Include one object for each template. IT IS VERY IMPORTANT THAT YOU ONLY INCLUDE TEXT IN THE FORMAT ABOVE, WITH NO ADDITIONS WHATSOEVER. Do not include extraneous ‘#’ characters, ‘column’ for columns, or ‘template’ for templates: ONLY THE TITLES AND NUMBERS. There should never be one column assigned to more than one field (ie. if the “name” column is assigned to field 1, it can’t be assigned to any other fields) . Do this for each template whose fields are described. The descriptions are as follows:',
     },
     vizsum: {
-        model: 'gpt-4-turbo',
+        model: 'gpt-4.1',
         maxTokens: 512,
         temp: 0.5,
         prompt: 'Your job is to provide brief descriptions for columns in a dataset based on example rows. Your descriptions should be geared towards how each column’s data might fit together into a visual template. Would they make good titles, main focuses, captions, descriptions, etc. Pay special attention to connections between columns, i.e. is there one column that specifically seems to describe/be related to another more than the rest? You should provide your analysis in JSON format like so: {“col1”:”description”, “col2”:”description”, …}. DO NOT INCLUDE ANY OTHER TEXT, ONLY THE JSON.',
     },
     vizsum2: {
-        model: 'gpt-4-turbo',
+        model: 'gpt-4.1',
         maxTokens: 512,
         temp: 0.5,
         prompt: 'Your job is to provide structured information on columns in a dataset based on example rows. You will categorize each column in two ways: by type and size. The size categories are as follows: tiny (one or two words), small (a sentence/multiple words), medium (a few sentences), large (a longer paragraph), and huge (a very long or multiple paragraphs). The type categories are as follows: visual (links/file paths to images, pdfs, maps, or any other visual media type), and text (plain text that isn’t a link/file path). Visual media should be assumed to have size “medium” “large” or “huge”. You will give your responses in JSON format, like so: {“title (of column)”:{“type”:”text”, “size”:”small”}, “title (of column)”:{“type”:”visual”, “size”:”medium”}, …}. DO NOT INCLUDE ANY OTHER TEXT, ONLY THE JSON.',
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
index 9fb973265..2a1a79029 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
@@ -27,7 +27,6 @@
     background: whitesmoke; 
     background-color: rgb(50, 50, 50);
     border-radius: 5px;
-    border: 1px solid rgb(180, 180, 180);
     padding: 0px;
     font-size: 13px;
     //box-shadow: 3px 3px rgb(29, 29, 31);
@@ -35,6 +34,24 @@
     &:hover {
         box-shadow: none;
     }
+
+    &.no-margin {
+        margin: 0px;
+    }
+
+    &.border {
+        border: 1px solid rgb(180, 180, 180);
+    }
+
+    &.float-right {
+        float: right;
+        margin-left: auto;
+    }
+
+    &.absolute-right {
+        position: absolute;
+        right: 0px;
+    }
      
     &.right{
         margin-left: 0px;
@@ -403,6 +420,8 @@
     margin-left: 5%;
     background-color: rgb(50, 50, 50);
     border-radius: 5px;
+    overflow: hidden;
+    resize: none;
 }
 
 .docCreatorMenu-variations-tab {
@@ -459,19 +478,6 @@
     width: 100%;
 }
 
-.section-reveal-options {
-    margin-top: 0px;
-    margin-bottom: 0px;
-    margin-right: 0px;
-    margin-left: auto;
-    border: 0px;
-    background: none;
-
-    &.float-right {
-        float: right;
-    }
-}
-
 .docCreatorMenu-templates-displays {
     display: flex;
     flex-direction: column;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index 8a98399b6..2e4b81253 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -37,6 +37,7 @@ import { StaticContentField } from './TemplateFieldTypes/StaticContentField';
 import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils'
 import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid';
 import { TemplateEditingWindow } from './Menu/TemplateEditingWindow';
+import { DocCreatorMenuButton } from './Menu/DocCreatorMenuButton';
 
 export enum LayoutType {
     FREEFORM = 'Freeform',
@@ -983,9 +984,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                     }
                 }))}>
                     <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>
+                    <DocCreatorMenuButton icon={'minus'} styles={'no-margin absolute-right'} function={() => this.removeField(field)}/>
                 </div>
                 { this._collapsedCols.includes(field.title) ? null :
                     <>
@@ -1042,26 +1041,16 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         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>
+                    <DocCreatorMenuButton icon={'plus'} function={this.addField}/>
+                    <DocCreatorMenuButton icon={'arrow-left'} styles={'float-right'} function={() => runInAction(() => (this._menuContent = 'templates'))}/>
                 </div>
                 <div className="panels-container">{this.fieldsInfos.map((field, i) => fieldPanel(field, i))}</div>
             </div>
         );
     }
 
-    @computed get editingView() {
-        return <TemplateEditingWindow
-                    setupButtonClick={this.setUpButtonClick} 
-                    template={this._currEditingTemplate as Template}
-                    menu={this}
-                />
-    }
-
+    @computed get editingView() { return <TemplateEditingWindow template={this._currEditingTemplate as Template} menu={this} /> }
+    
     get renderSelectedViewType() {
         switch (this._menuContent) {
             case 'templates': 
@@ -1073,14 +1062,11 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                                 menu={this} 
                                 loading={this._GPTLoading}
                                 optionsButtonOpts={['gear', () => (this._menuContent = 'dashboard')]}
-                                setupButtonClick={this.setUpButtonClick} 
                                 templates={this._suggestedTemplates}
                             />
                             <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>
+                                    <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generatePresetTemplates}/>
                                 </div>
                             </div>
                         </div>
@@ -1166,9 +1152,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                                 {topButton('magnifying-glass', 'options', onOptionsSelected, 'middle')}
                                 {topButton('bars', 'saved', onSavedSelected, 'right')}
                             </div>
-                            <button className="docCreatorMenu-menu-button close-menu" onPointerDown={e => this.setUpButtonClick(e, this.closeMenu)}>
-                                <FontAwesomeIcon icon="minus" />
-                            </button>
+                            <DocCreatorMenuButton icon={'minus'} styles={'float-right'} function={this.closeMenu}/>
                         </div>
                         {this.renderSelectedViewType}
                     </div>
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/DocCreatorMenuButton.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/DocCreatorMenuButton.tsx
new file mode 100644
index 000000000..1d8139d40
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/DocCreatorMenuButton.tsx
@@ -0,0 +1,41 @@
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import { ObservableReactComponent } from "../../../../ObservableReactComponent";
+import React from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { setupMoveUpEvents, returnFalse } from "../../../../../../ClientUtils";
+import { emptyFunction } from "../../../../../../Utils";
+import { undoable } from "../../../../../util/UndoManager";
+import { observer } from "mobx-react";
+
+interface DocCreatorMenuButtonProps {
+    icon: IconProp;
+    function: () => any;
+    styles?: string;
+}
+
+@observer
+export class DocCreatorMenuButton extends ObservableReactComponent<DocCreatorMenuButtonProps> {
+
+    setupButtonClick = (e: React.PointerEvent, func: (...args: any) => void) => {
+        setupMoveUpEvents(
+            this,
+            e,
+            returnFalse,
+            emptyFunction,
+            undoable(clickEv => {
+                clickEv.stopPropagation();
+                clickEv.preventDefault();
+                func();
+            }, 'create docs')
+        );
+    };
+
+    render() {
+
+        return (
+            <button className={`docCreatorMenu-menu-button ${this._props.styles}`} onPointerDown={e => this.setupButtonClick(e, async () => this._props.function())}>
+                <FontAwesomeIcon icon={this._props.icon} />
+            </button>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index df6b791c7..0434d0ccb 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -15,11 +15,11 @@ import { TemplateMenuAIUtils } from "../Backend/TemplateMenuAIUtils";
 import { ObservableReactComponent } from "../../../../ObservableReactComponent";
 import { IDisposer } from "mobx-utils";
 import { ImageField } from "../../../../../../fields/URLField";
+import { DocCreatorMenuButton } from "./DocCreatorMenuButton";
 
 interface TemplateEditingWindowProps {
     menu: DocCreatorMenu;
     template: Template;
-    setupButtonClick: (e: React.PointerEvent, func: () => void) => void;
 }
 
 @observer
@@ -27,7 +27,8 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
 
     private fireflyPrompt: string = 'Use this template to generate an empty baseball card template.';
     private previewWindow: HTMLDivElement | null = null;
-    private _disposers: { [name: string]: IDisposer } = {};
+    private disposers: { [name: string]: IDisposer } = {};
+    private promptInput: HTMLTextAreaElement | null = null;
 
     @observable _loading: boolean = false;
     @observable _variationsTabOpen: boolean = false;
@@ -35,7 +36,7 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
     @observable _variations: Template[] = [];
 
     componentDidMount(): void {
-        this._disposers.windowDimensions = reaction(() => 
+        this.disposers.windowDimensions = reaction(() => 
             this._props.menu._resizing, 
             () => { this.forceUpdate() }, 
             { fireImmediately: true }
@@ -43,7 +44,12 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
     }
 
     componentWillUnmount() {
-        Object.values(this._disposers).forEach(disposer => disposer?.());
+        Object.values(this.disposers).forEach(disposer => disposer?.());
+    }
+
+    setPromptInputRef: React.LegacyRef<HTMLTextAreaElement> = (node) => {
+        this.promptInput = node;
+        this.forceUpdate();
     }
 
     setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
@@ -58,6 +64,24 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
         } else if (this.previewWindow && !open) {
             this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * 5/3);
         }
+        this.forceUpdate();
+    }
+
+    generateVariations = async () => {
+        this._props.menu._variations = [];
+        this._loading = true;
+        this._variationURLs = await this._props.menu.generateVariations(this._props.template.clone(false).getRenderedDoc()!, this.fireflyPrompt);
+        this._variationURLs.forEach(url => {
+            const newTemplate: Template = this._props.template.clone(true);
+            this._props.menu._variations.push(newTemplate);
+        });
+        this._loading = false;
+        setTimeout(() => {
+            this._variationURLs.forEach((url, i) => {
+                this._props.menu._variations[i].setImageAsBackground(url, true);
+            });
+            this.forceUpdate();
+        });
     }
 
     get fireflyVariationsTab() {
@@ -71,37 +95,27 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                     loading={this._loading}
                     templates={this._props.menu._variations}
                     optionsButtonOpts={['gear', () => {}]}
-                    setupButtonClick={this._props.setupButtonClick}
                     previewBoxRightButtonOpts={['gear', (template: Template) => {this.forceUpdate();}]}
                 />
                 <div className="docCreatorMenu-section">
                     <div className="docCreatorMenu-variation-prompt-input">
                         <textarea
                             className="docCreatorMenu-variation-prompt-input-textbox"
+                            ref={this.setPromptInputRef}
                             onChange={e => this.fireflyPrompt = e.target.value}
+                            onInput={() => {
+                                if (this.promptInput !== null) {
+                                    this.promptInput.style.height = 'auto';
+                                    this.promptInput.style.height = this.promptInput.scrollHeight + 'px';
+                                }
+                            }}
                             defaultValue={''}
                             placeholder={'Enter a custom prompt here (optional)'}
                         />
-                        <button className="docCreatorMenu-menu-button" 
-                            onPointerDown={e => this._props.setupButtonClick(e, async () => {
-                                this._props.menu._variations = [];
-                                this._loading = true;
-                                this._variationURLs = await this._props.menu.generateVariations(this._props.template.clone(false).getRenderedDoc()!, this.fireflyPrompt);
-                                this._variationURLs.forEach(url => {
-                                    const newTemplate: Template = this._props.template.clone(true);
-                                    this._props.menu._variations.push(newTemplate);
-                                });
-                                this._loading = false;
-                                setTimeout(() => {
-                                    this._variationURLs.forEach((url, i) => {
-                                        this._props.menu._variations[i].setImageAsBackground(url, true);
-                                    });
-                                    this.forceUpdate();
-                                });
-                            })
-                        }>
-                            <FontAwesomeIcon icon="arrows-rotate" />
-                        </button>
+                        <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generateVariations}/>
+                    </div>
+                    <div className='docCreatorMenu-variation-options-container'>
+
                     </div>
                 </div>
             </>
@@ -146,25 +160,13 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                     {this.renderedDocPreview}
                     {this._variationsTabOpen ? this.fireflyVariationsTab : null}
                     <div className="right-buttons-panel">
-                        <button
-                            className="docCreatorMenu-menu-button section-reveal-options top-right"
-                            onPointerDown={e =>
-                                this._props.setupButtonClick(e, () => {
-                                    if (this._props.template === this._props.menu._selectedTemplate) {
-                                        this._props.menu.updateRenderedPreviewCollection(this._props.template);
-                                    }
-                                    this._props.menu.setExpandedView(undefined);
-                                })
-                            }>
-                            <FontAwesomeIcon icon="minimize" />
-                        </button>
-                        <button className="docCreatorMenu-menu-button section-reveal-options top-right" onPointerDown={e => this._props.setupButtonClick(e, async () => {
-                                this.setVariationTab(!this._variationsTabOpen);
-                                // this._props.template.setImageAsBackground('https://www.onthegotours.com/repository/Great-Wall-New-Pic-228551391689622.jpg', true);
-                                this.forceUpdate();
-                            })}>
-                            <FontAwesomeIcon icon="lightbulb" />
-                        </button>
+                        <DocCreatorMenuButton icon={'minimize'} function={() => {
+                                if (this._props.template === this._props.menu._selectedTemplate) {
+                                    this._props.menu.updateRenderedPreviewCollection(this._props.template);
+                                }
+                                this._props.menu.setExpandedView(undefined);
+                            }}/>
+                        <DocCreatorMenuButton icon={'lightbulb'} function={() => this.setVariationTab(!this._variationsTabOpen)}/>
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
index e78109b62..84ca6546d 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
@@ -11,6 +11,7 @@ import { observer } from "mobx-react";
 import { DocCreatorMenu } from "../DocCreatorMenu";
 import { TemplatePreviewBox } from "./TemplatePreviewBox";
 import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import { DocCreatorMenuButton } from "./DocCreatorMenuButton";
 
 export interface SuggestedTemplatesProps {
     menu: DocCreatorMenu;
@@ -20,7 +21,6 @@ export interface SuggestedTemplatesProps {
     optionsButtonOpts?: [IconProp, (...args: any) => any];
     previewBoxLeftButtonOpts?: [IconProp, (...args: any) => any];
     previewBoxRightButtonOpts?: [IconProp, (...args: any) => any];
-    setupButtonClick: (e: React.PointerEvent, func: () => void) => void;
 }
 
 @observer
@@ -31,9 +31,9 @@ export class TemplatePreviewGrid extends ObservableReactComponent<SuggestedTempl
             <div className="docCreatorMenu-section">
                 <div className="docCreatorMenu-section-topbar">
                     <div className="docCreatorMenu-section-title">{this.props.title}</div>
-                    {this._props.optionsButtonOpts ? (<button className="docCreatorMenu-menu-button section-reveal-options" onPointerDown={e => this.props.setupButtonClick(e, () => runInAction(this._props.optionsButtonOpts![1]))}>
-                        <FontAwesomeIcon icon={this._props.optionsButtonOpts[0] as IconProp} />
-                    </button>) : null}
+                    {this._props.optionsButtonOpts ? 
+                        <DocCreatorMenuButton icon={this._props.optionsButtonOpts[0] as IconProp} styles={'float-right'} function={() => runInAction(this._props.optionsButtonOpts![1])}/> 
+                        : null}
                 </div>
                 <div className="docCreatorMenu-templates-preview-window">
                     {this._props.loading ? 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
index 10724c9f7..7ff521f18 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
@@ -81,7 +81,7 @@ export class Template {
 
     isValidTemplate = (cols: Col[]) => {
         const maxMatches = this.maxMatches(this.getMatches(cols));
-        return maxMatches === this.contentFields.length;
+        return maxMatches === this.contentFields.length && this.title !== 'template_framework';
     };
 
     applyConditionalLogicToField = (field: TemplateField) => {
-- 
cgit v1.2.3-70-g09d2


From 4219c751c0f984fac6e5995c1ab955a8d63a28cd Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 11 May 2025 21:18:55 -0400
Subject: many changes to firefly UI (options added) and starting work on
 finalizing conditionals

---
 .../DocCreatorMenu/Backend/TemplateManager.ts      |  14 +-
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.scss  | 300 ++++++++++-----------
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   |  36 ++-
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  | 160 +++++++----
 .../DocCreatorMenu/Menu/TemplatePreviewBox.tsx     |  32 +--
 .../DocCreatorMenu/Menu/TemplatePreviewGrid.tsx    |  10 +-
 .../nodes/DataVizBox/DocCreatorMenu/Template.ts    |  23 +-
 src/client/views/smartdraw/DrawingFillHandler.tsx  |   5 +-
 src/server/ApiManagers/FireflyManager.ts           |   6 +-
 9 files changed, 296 insertions(+), 290 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
index d11f05766..030c6db95 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
@@ -20,7 +20,7 @@ export class TemplateManager {
 
     templates: Template[] = [];
 
-    fieldConditions: Record<string, Conditional[]> = {};
+    conditionalFieldLogic: Record<string, Conditional[]> = {};
 
     constructor(templateSettings: FieldSettings[]) {
         makeAutoObservable(this);
@@ -40,16 +40,16 @@ export class TemplateManager {
     };
 
     addFieldCondition = (fieldTitle: string, condition: Conditional) => {
-        if (this.fieldConditions[fieldTitle] === undefined) {
-            this.fieldConditions[fieldTitle] = [condition];
+        if (this.conditionalFieldLogic[fieldTitle] === undefined) {
+            this.conditionalFieldLogic[fieldTitle] = [condition];
         } else {
-            this.fieldConditions[fieldTitle].push(condition);
+            this.conditionalFieldLogic[fieldTitle].push(condition);
         }
     }
 
     removeFieldCondition = (fieldTitle: string, condition: Conditional) => {
-        if (this.fieldConditions[fieldTitle]) {
-            this.fieldConditions[fieldTitle] = this.fieldConditions[fieldTitle].filter(cond => cond !== condition);
+        if (this.conditionalFieldLogic[fieldTitle]) {
+            this.conditionalFieldLogic[fieldTitle] = this.conditionalFieldLogic[fieldTitle].filter(cond => cond !== condition);
         }
     }
 
@@ -84,6 +84,8 @@ export class TemplateManager {
 
             await applyGPTContent();
 
+            templateCopy.applyConditionalLogic(this.conditionalFieldLogic);
+
             return templateCopy.getRenderedDoc();
         };
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
index 2a1a79029..8ac718fb7 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
@@ -33,6 +33,7 @@
 
     &:hover {
         box-shadow: none;
+        background-color: rgb(60, 60, 65);
     }
 
     &.no-margin {
@@ -57,42 +58,6 @@
         margin-left: 0px;
         font-size: 12px;
     }
-
-    &.close-menu {
-        font-size: 12px;
-        width: 18px;
-        height: 18px;
-        font-size: 12px;
-        margin-left: auto;
-        margin-right: 5px;
-        margin-bottom: 3px;
-    }
-
-    &.options {
-        margin-left: 0px;
-    }
-
-    &:hover {
-        background-color: rgb(60, 60, 65);
-    }
-
-    &.top-bar {
-        border-bottom: 25px solid #555;
-        border-left: 12px solid transparent;
-        border-right: 12px solid transparent;
-        // border-top-left-radius: 5px;
-        // border-top-right-radius: 5px;
-        border-radius: 0px;
-        height: 0;
-        width: 50px;
-    }
-
-    &.preview-toggle {
-        margin: 0px;
-        border-top-left-radius: 0px;
-        border-bottom-left-radius: 0px;
-        border-left: 0px;
-    }
 }
 
 .docCreatorMenu-top-buttons-container {
@@ -400,30 +365,6 @@
 
 }
 
-.docCreatorMenu-variation-prompt-input {
-    display: flex;
-    flex-direction: row;
-    justify-content: flex-start;
-    align-items: center;
-    margin: 5px;
-    gap: 15px;
-    height: 30px;
-    width: 100%;
-}
-
-.docCreatorMenu-variation-prompt-input-textbox {
-    height: 100%;
-    width: 80%;
-    color: white;
-    margin-top: 1%;
-    margin-bottom: 1%;
-    margin-left: 5%;
-    background-color: rgb(50, 50, 50);
-    border-radius: 5px;
-    overflow: hidden;
-    resize: none;
-}
-
 .docCreatorMenu-variations-tab {
     flex-grow: .5;
 }
@@ -435,6 +376,7 @@
     position: relative;
     flex-grow: 1;
     height: 100%;
+    width: 100%;
     margin: 0px;
     margin-top: 0px;
     margin-bottom: 0px;
@@ -446,7 +388,7 @@
     justify-content: center;
     align-items: center;
     position: absolute;
-    width: 100%;
+    width: calc(100% - 10px);
     bottom: 0px;
     margin: 0px;
     margin-bottom: 10px;
@@ -457,11 +399,18 @@
 .docCreatorMenu-templates-preview-window {
     display: grid;
     justify-content: space-evenly;
+    row-gap: 2rem;
     grid-template-columns: repeat(auto-fill, minmax(225px, 30%));
-    gap: 1rem;
     margin: 5px;
     width: calc(100% - 10px);
+    height: 100%;
     padding-bottom: 40px;
+
+    &.scrolling {
+        overflow-y: scroll;
+        max-height: 300px;
+        padding-bottom: 0px;
+    }
 }
 
 .divvv{
@@ -847,108 +796,6 @@
         background-color: rgb(50, 50, 50);
     }
 
-//     .field-panel {
-//         position: relative;
-//         display: flex;
-//         // align-items: flex-start;
-//         flex-direction: column;
-//         gap: 5px;
-//         padding: 5px;
-//         height: 100px;
-//         //width: 100%;
-//         border: 1px solid rgb(180, 180, 180);
-//         margin: 5px;
-//         margin-top: 0px;
-//         border-radius: 3px;
-//         flex: 0 0 auto;
-
-//         .properties-wrapper {
-//             display: flex;
-//             flex-direction: row;
-//             align-items: flex-start;
-//             gap: 5px;
-
-//             .field-property-container {
-//                 background-color: rgb(40, 40, 40);
-//                 border: 1px solid rgb(100, 100, 100);
-//                 border-radius: 3px;
-//                 width: 30%;
-//                 height: 25px;
-//                 padding-left: 3px;
-//                 align-items: center;
-//                 color: whitesmoke;
-//             }
-
-//             .field-type-selection-container {
-//                 display: flex;
-//                 flex-direction: row;
-//                 align-items: center;
-//                 background-color: rgb(40, 40, 40);
-//                 border: 1px solid rgb(100, 100, 100);
-//                 border-radius: 3px;
-//                 width: 31%;
-//                 height: 25px;
-//                 padding-left: 3px;
-//                 color: whitesmoke;
-
-//                 .placeholder {
-//                     color: gray;
-//                 }
-
-//                 &:hover .placeholder {
-//                     display: none;
-//                 }
-
-//                 .bubbles {
-//                     display: none;
-//                 }
-
-//                 .text {
-//                     margin-top: 5px;
-//                     margin-bottom: 5px;
-//                 }
-
-//                 &:hover .bubbles {
-//                     display: flex;
-//                     flex-direction: row;
-//                     align-items: flex-start;
-//                 }
-
-//                 &:hover .type-display {
-//                     display: none;
-//                 }
-
-//                 .bubble {
-//                     margin: 5px;
-//                 }
-
-//                 &:hover .bubble {
-//                     margin-top: 7px;
-//                 }
-//             }
-//         }
-
-//         .field-description-container {
-//             background-color: rgb(40, 40, 40);
-//             border: 1px solid rgb(100, 100, 100);
-//             border-radius: 3px;
-//             width: 100%;
-//             height: 100%;
-//             resize: none;
-
-//             ::-webkit-scrollbar-track {
-//                 background: none; 
-//             }
-//         }
-
-//         .top-right {
-//             position: absolute;
-//             top: 0px;
-//             right: 0px;
-//         }
-//     }
-// }
-
     .field-panel {
         display: flex;
         flex-direction: column;
@@ -1214,4 +1061,127 @@
         }
     }
 
-}
\ No newline at end of file
+}
+
+//------------------------------------------------------------------------------------------------------------------------------------------
+// EditingWindow CSS
+//--------------------------------------------------------------------------------------------------------------------------------------------
+
+.docCreatorMenu-editing-firefly-section {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    height: 100%;
+    width: 100%;
+    padding: 5px;
+}
+
+.docCreatorMenu-firefly-options {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: flex-end;
+    height: fit-content;
+    width: 100%;
+}
+
+.docCreatorMenu-variation-prompt-row {
+    display: flex;
+    flex-direction: row;
+    justify-content: flex-start;
+    align-items: center;
+    gap: 15px;
+    height: fit-content;
+    width: 100%;
+}
+
+.docCreatorMenu-variation-prompt-input-textbox {
+    height: 40px;
+    width: 80%;
+    color: white;
+    margin-top: 1%;
+    margin-bottom: 1%;
+    margin-left: 5%;
+    background-color: rgb(50, 50, 50);
+    border-radius: 5px;
+    overflow: hidden;
+    resize: none;
+}
+
+.options‑menu {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 2rem;                 
+    padding: 0.5rem 1rem;
+    background: rgb(50, 50, 50);
+    color: whitesmoke;
+    font-family: system-ui, sans-serif;
+    font-size: 0.9rem;
+    flex-wrap: wrap;         
+  }
+  
+  .menu‑item {
+    display: flex;
+    align-items: center;
+    gap: 0.5rem;
+    white-space: nowrap;
+  }
+  
+  .menu‑item input[type="range"] {
+    width: 7rem;
+    accent-color: whitesmoke;      
+  }
+  
+  .value {
+    min-width: 2ch;
+    text-align: right;
+  }
+  
+  .switch {
+    gap: 0.75rem;
+  }
+  
+  .switch .slider {
+    position: relative;
+    width: 2.2rem;
+    height: 1.1rem;
+    background: whitesmoke;
+    border-radius: 1rem;
+    cursor: pointer;
+    transition: background 0.2s;
+  }
+  
+  .switch .slider::before {
+    content: '';
+    position: absolute;
+    top: 0.1rem;
+    left: 0.1rem;
+    width: 0.9rem;
+    height: 0.9rem;
+    background: rgb(50, 50, 50);
+    border-radius: 50%;
+    transition: transform 0.2s;
+  }
+  
+  .switch input {
+    display: none;
+  }
+  
+  .switch input:checked + .slider {
+    background: #9fe29d;
+  }
+  
+  .switch input:checked + .slider::before {
+    transform: translateX(1.1rem);
+  }
+
+.firefly-option-label {
+    padding: .2em .6em .3em;
+    font-size: 100%;
+    color: whitesmoke;
+    text-align: center;
+}
+  
+
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index 2e4b81253..2565a9332 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -36,7 +36,7 @@ import { CgPathIntersect } from 'react-icons/cg';
 import { StaticContentField } from './TemplateFieldTypes/StaticContentField';
 import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils'
 import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid';
-import { TemplateEditingWindow } from './Menu/TemplateEditingWindow';
+import { FireflyStructureOptions, TemplateEditingWindow } from './Menu/TemplateEditingWindow';
 import { DocCreatorMenuButton } from './Menu/DocCreatorMenuButton';
 
 export enum LayoutType {
@@ -103,7 +103,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
     @observable _userCreatedFields: Col[] = [];
     @observable _collapsedCols: String[] = []; //any columns whose options panels are hidden
-    @observable _conditions: Conditional[] = [];
     @observable _currEditingConditional: Conditional = {} as Conditional;
 
     @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 3, repeat: 0 };
@@ -594,7 +593,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         );
     };
 
-    generateVariations = async (onDoc: Doc, prompt: string): Promise<string[]> => {
+    generateVariations = async (onDoc: Doc, prompt: string, options: FireflyStructureOptions): Promise<string[]> => {
+        const { numVariations, temperature, useStyleRef } = options;
         this.variations = [];
         const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
 
@@ -603,12 +603,12 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         clone.x = 10000;
         clone.y = 10000;
 
-        await DrawingFillHandler.drawingToImage(clone, 100, prompt, clone, this)
+        await DrawingFillHandler.drawingToImage(clone, 100 - temperature, prompt, useStyleRef ? clone : undefined, this, numVariations)
 
         return this.variations;
     }
 
-    @observable variations: string[] = []
+    variations: string[] = []
 
     @action addVariation = (url: string) => {
         this.variations.push(url);
@@ -918,7 +918,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     }
 
     get dashboardContents() {
-        const conditionForm = (title: string, parameters?: Conditional) => {
+        const conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => {
 
             const params: Conditional = parameters ?? this._currEditingConditional;
 
@@ -942,8 +942,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                     <div className='form-row-plain-text'>then</div>
                     <div className="operator-options-dropdown">
                         <span className="operator-dropdown-current">{params.target ?? 'self'}</span>
-                        <div className='operator-dropdown-option' onPointerDown={() => {params.target = 'self'}}>{'self'}</div>
-                        <div className='operator-dropdown-option' onPointerDown={() => {params.target = 'template'}}>{'template'}</div>
+                        <div className='operator-dropdown-option' onPointerDown={() => {params.target = 'self'}}>{'own'}</div>
+                        <div className='operator-dropdown-option' onPointerDown={() => {params.target = 'template'}}>{`template's`}</div>
                     </div>
                     <textarea
                         className="form-row-textarea"
@@ -959,17 +959,11 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                         defaultValue={params.value}
                     />
                 </div>
-                <div className='form-action-button' onPointerDown={e => this.setUpButtonClick(e, runInAction(() => () => {
-                        if (this._currEditingConditional === params) { 
-                            params.field = title;
-                            this._conditions.push(params);
-                            this._currEditingConditional = {} as Conditional;
-                        } else {
-                            this._conditions = this._conditions.filter(cond => cond !== params);
-                        }
-                    }))}>
-                        <FontAwesomeIcon icon={this._currEditingConditional === params ? 'plus' : 'minus'} color='white'/>
-                </div>
+                {empty ? 
+                <DocCreatorMenuButton icon={'plus'} styles={'float-right'} function={() => this.templateManager.addFieldCondition(title, params)}/>
+                :
+                <DocCreatorMenuButton icon={'minus'} styles={'float-right'} function={() => this.templateManager.removeFieldCondition(title, params)}/>
+                }
             </div>
             )
         }
@@ -1030,8 +1024,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                     </div>
                     <div className="conditionals-section">
                         <span className="conditionals-title">Conditional Logic</span>
-                        {conditionForm(field.title, this._currEditingConditional)}
-                        {this._conditions.map(condition => conditionForm(condition.field, condition))}
+                        {conditionForm(field.title, this._currEditingConditional, true)}
+                        {this.templateManager.conditionalFieldLogic[field.title]?.map(condition => conditionForm(condition.field, condition))}
                     </div>
                     </>
                 }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index 0434d0ccb..4ff509d73 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -1,5 +1,5 @@
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, reaction, runInAction } from "mobx";
+import { action, makeAutoObservable, makeObservable, observable, reaction, runInAction } from "mobx";
 import React from "react";
 import { returnFalse, returnEmptyFilter, returnTrue } from "../../../../../../ClientUtils";
 import { emptyFunction } from "../../../../../../Utils";
@@ -16,93 +16,77 @@ import { ObservableReactComponent } from "../../../../ObservableReactComponent";
 import { IDisposer } from "mobx-utils";
 import { ImageField } from "../../../../../../fields/URLField";
 import { DocCreatorMenuButton } from "./DocCreatorMenuButton";
+import { TbHistory } from "react-icons/tb";
 
-interface TemplateEditingWindowProps {
+export type FireflyStructureOptions = {
+    numVariations: number;
+    temperature: number;
+    useStyleRef: boolean;
+}
+
+interface FireflyVariationsTabProps {
     menu: DocCreatorMenu;
     template: Template;
 }
 
 @observer
-export class TemplateEditingWindow extends ObservableReactComponent<TemplateEditingWindowProps> {
+export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariationsTabProps> {
 
-    private fireflyPrompt: string = 'Use this template to generate an empty baseball card template.';
-    private previewWindow: HTMLDivElement | null = null;
-    private disposers: { [name: string]: IDisposer } = {};
-    private promptInput: HTMLTextAreaElement | null = null;
+    private prompt: string = 'Use this template to generate an empty baseball card template.';
+
+    @observable private promptInput: HTMLTextAreaElement | null = null;
 
     @observable _loading: boolean = false;
     @observable _variationsTabOpen: boolean = false;
     @observable _variationURLs: string[] = [];
-    @observable _variations: Template[] = [];
-
-    componentDidMount(): void {
-        this.disposers.windowDimensions = reaction(() => 
-            this._props.menu._resizing, 
-            () => { this.forceUpdate() }, 
-            { fireImmediately: true }
-            );
-    }
-
-    componentWillUnmount() {
-        Object.values(this.disposers).forEach(disposer => disposer?.());
-    }
-
-    setPromptInputRef: React.LegacyRef<HTMLTextAreaElement> = (node) => {
-        this.promptInput = node;
-        this.forceUpdate();
-    }
 
-    setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
-        this.previewWindow = node;
-        this.forceUpdate();
-    }
+    @observable private fireflyOptions: FireflyStructureOptions = {numVariations: 3, temperature: 0, useStyleRef: false};
 
-    @action setVariationTab = (open: boolean) => {
-        this._variationsTabOpen = open;
-        if (this.previewWindow && open) {
-            this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * .6);
-        } else if (this.previewWindow && !open) {
-            this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * 5/3);
-        }
-        this.forceUpdate();
+    constructor(props: FireflyVariationsTabProps) {
+        super(props);
+        makeObservable(this);
     }
 
     generateVariations = async () => {
         this._props.menu._variations = [];
         this._loading = true;
-        this._variationURLs = await this._props.menu.generateVariations(this._props.template.clone(false).getRenderedDoc()!, this.fireflyPrompt);
+        const doc: Doc = this._props.template.clone(false).getRenderedDoc()!;
+        this._variationURLs = await this._props.menu.generateVariations(doc, this.prompt, this.fireflyOptions);
         this._variationURLs.forEach(url => {
             const newTemplate: Template = this._props.template.clone(true);
             this._props.menu._variations.push(newTemplate);
         });
-        this._loading = false;
         setTimeout(() => {
             this._variationURLs.forEach((url, i) => {
                 this._props.menu._variations[i].setImageAsBackground(url, true);
             });
-            this.forceUpdate();
+            this._loading = false;
         });
     }
 
-    get fireflyVariationsTab() {
+    setPromptInputRef: React.LegacyRef<HTMLTextAreaElement> = (node) => {
+        this.promptInput = node;
+    }
 
+    render() {
         return (
-            <>
+            <div className='docCreatorMenu-editing-firefly-section'>
                 <div className="docCreatorMenu-option-divider full no-margin-bottom"/>
                 <TemplatePreviewGrid
                     menu={this._props.menu}
                     title={'Generate Variations'}
                     loading={this._loading}
+                    styles={'scrolling'}
                     templates={this._props.menu._variations}
                     optionsButtonOpts={['gear', () => {}]}
                     previewBoxRightButtonOpts={['gear', (template: Template) => {this.forceUpdate();}]}
                 />
-                <div className="docCreatorMenu-section">
-                    <div className="docCreatorMenu-variation-prompt-input">
+                <div className="docCreatorMenu-firefly-options">
+                    <div className="docCreatorMenu-variation-prompt-row">
                         <textarea
                             className="docCreatorMenu-variation-prompt-input-textbox"
                             ref={this.setPromptInputRef}
-                            onChange={e => this.fireflyPrompt = e.target.value}
+                            onChange={e => this.prompt = e.target.value}
                             onInput={() => {
                                 if (this.promptInput !== null) {
                                     this.promptInput.style.height = 'auto';
@@ -114,13 +98,84 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                         />
                         <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generateVariations}/>
                     </div>
-                    <div className='docCreatorMenu-variation-options-container'>
-
-                    </div>
+                        <nav className="options‑menu">
+                            <div className="menu‑item switch">
+                                <input type="checkbox" checked={this.fireflyOptions.useStyleRef} 
+                                    onChange={(e) => runInAction(() => this.fireflyOptions.useStyleRef = e.target.checked)}
+                                />
+                                <span className="slider round"></span>
+                                <span className="firefly-option-label">Use template as style guide</span>
+                            </div>
+                            <div className="menu‑item">
+                                <span className="firefly-option-label">Variations</span>
+                                <input type="range" id="variations" 
+                                    min="1" 
+                                    max="5" 
+                                    value={this.fireflyOptions.numVariations} 
+                                    onChange={(e) => runInAction(() => this.fireflyOptions.numVariations = Number(e.target.value))}
+                                />
+                                <span className="value" id="varVal">{this.fireflyOptions.numVariations}</span>
+                            </div>
+                            <div className="menu‑item">
+                                <span className="firefly-option-label">Temperature</span>
+                                <input type="range" id="temperature" 
+                                    min="1" 
+                                    max="100" 
+                                    value={this.fireflyOptions.temperature} 
+                                    onChange={(e) => runInAction(() => this.fireflyOptions.temperature = Number(e.target.value))}
+                                />
+                                <span className="value" id="tempVal">{this.fireflyOptions.temperature}</span>
+                            </div>
+                        </nav>
                 </div>
-            </>
+            </div>
         )
     }
+}
+
+interface TemplateEditingWindowProps {
+    menu: DocCreatorMenu;
+    template: Template;
+}
+
+@observer
+export class TemplateEditingWindow extends ObservableReactComponent<TemplateEditingWindowProps> {
+
+    private disposers: { [name: string]: IDisposer } = {};
+
+    @observable private previewWindow: HTMLDivElement | null = null;
+
+    @observable _variationsTabOpen: boolean = false;
+
+    constructor(props: TemplateEditingWindowProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    componentDidMount(): void {
+        this.disposers.windowDimensions = reaction(() => 
+            this._props.menu._resizing, 
+            () => { this.forceUpdate() }, 
+            { fireImmediately: true }
+            );
+    }
+
+    componentWillUnmount() {
+        Object.values(this.disposers).forEach(disposer => disposer?.());
+    }
+
+    setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
+        this.previewWindow = node;
+    }
+
+    @action setVariationTab = (open: boolean) => {
+        this._variationsTabOpen = open;
+        if (this.previewWindow && open) {
+            this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * .6);
+        } else if (this.previewWindow && !open) {
+            this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * 5/3);
+        }
+    }
    
     get renderedDocPreview(){
         const doc: Doc = this._props.template.getRenderedDoc() as unknown as Doc;
@@ -158,7 +213,11 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                 <div className="docCreatorMenu-expanded-template-preview">
                     <div className="top-panel"/>
                     {this.renderedDocPreview}
-                    {this._variationsTabOpen ? this.fireflyVariationsTab : null}
+                    {this._variationsTabOpen ? <FireflyVariationsTab
+                        menu={this._props.menu}
+                        template={this._props.template}
+                    />
+                     : null}
                     <div className="right-buttons-panel">
                         <DocCreatorMenuButton icon={'minimize'} function={() => {
                                 if (this._props.template === this._props.menu._selectedTemplate) {
@@ -167,6 +226,7 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                                 this._props.menu.setExpandedView(undefined);
                             }}/>
                         <DocCreatorMenuButton icon={'lightbulb'} function={() => this.setVariationTab(!this._variationsTabOpen)}/>
+                        <DocCreatorMenuButton icon={'arrow-rotate-backward'} function={() => { this._props.menu.setExpandedView(this._props.template); this.forceUpdate(); }}/>
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
index c9f817d2f..de2f9e455 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
@@ -1,7 +1,7 @@
 import { Colors } from "@dash/components/src";
 import { FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 import { Template } from "../Template";
-import { reaction, runInAction } from "mobx";
+import { makeObservable, observable, reaction, runInAction } from "mobx";
 import React from "react";
 import { ObservableReactComponent } from "../../../../ObservableReactComponent";
 import { DocCreatorMenu } from "../DocCreatorMenu";
@@ -17,7 +17,7 @@ import { ImageField } from "../../../../../../fields/URLField";
 import { ImageCast } from "../../../../../../fields/Types";
 import { observer } from "mobx-react";
 
-export interface TemplatePreviewBox_props {
+export interface TemplatePreviewBoxProps {
     template: Template;
     menu: DocCreatorMenu;
     leftButtonOpts?: [icon: IconProp, func: (...args: any) => void]
@@ -25,32 +25,18 @@ export interface TemplatePreviewBox_props {
 }
 
 @observer
-export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBox_props> {
+export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBoxProps> {
 
-    private previewWindow: HTMLDivElement | null = null;
-    // private icon: ImageField | undefined = undefined;
+    @observable private previewWindow: HTMLDivElement | null = null;
 
     setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
         this.previewWindow = node;
-        this.forceUpdate();
     }
 
-    // componentDidMount(): void {
-    //     console.log('mounted')
-    //     setTimeout(() => {
-    //         const docView = DocumentView.getDocumentView(this.doc);
-    //         if (docView) {
-    //             console.log('docview found')
-    //             docView.ComponentView?.updateIcon?.();
-    //             setTimeout(() => {
-    //                 console.log('componentview found: ', docView.ComponentView)
-    //                 this.icon = ImageCast(docView.Document.icon);
-    //                 console.log('icon is:', this.icon, ' from: ', docView.Document.icon);
-    //                 this.forceUpdate();
-    //             }, 5000);
-    //         }
-    //     }, 1000);
-    // }
+    constructor(props: TemplatePreviewBoxProps) {
+        super(props);
+        makeObservable(this);
+    }
 
     get doc() {
         return this._props.template.getRenderedDoc() as Doc;
@@ -83,8 +69,6 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview
                     <button className="option-button right" onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.rightButtonOpts![1](template))}>
                         <FontAwesomeIcon icon={this._props.rightButtonOpts![0]} color="white" />
                     </button> : null }
-                    {/* { this.icon ? 
-                        <img className="docCreatorMenu-preview-image" src={this.icon.url.href.replace('.png', '_o.png')} /> */}
                     <DocumentView
                         Document={this.doc}
                         isContentActive={emptyFunction} // !!! should be return false
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
index 84ca6546d..d53853c52 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
@@ -1,6 +1,6 @@
 import { Colors } from "@dash/components/src";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction } from "mobx";
+import { action, makeObservable, observable, runInAction } from "mobx";
 import React from "react";
 import ReactLoading from "react-loading";
 import { Doc } from "../../../../../../fields/Doc";
@@ -18,6 +18,7 @@ export interface SuggestedTemplatesProps {
     loading?: boolean;
     templates: Template[];
     title: string;
+    styles?: string;
     optionsButtonOpts?: [IconProp, (...args: any) => any];
     previewBoxLeftButtonOpts?: [IconProp, (...args: any) => any];
     previewBoxRightButtonOpts?: [IconProp, (...args: any) => any];
@@ -26,6 +27,11 @@ export interface SuggestedTemplatesProps {
 @observer
 export class TemplatePreviewGrid extends ObservableReactComponent<SuggestedTemplatesProps> {
 
+    constructor(props: SuggestedTemplatesProps) {
+        super(props);
+        makeObservable(this);
+    }
+
     render() {
         return (
             <div className="docCreatorMenu-section">
@@ -35,7 +41,7 @@ export class TemplatePreviewGrid extends ObservableReactComponent<SuggestedTempl
                         <DocCreatorMenuButton icon={this._props.optionsButtonOpts[0] as IconProp} styles={'float-right'} function={() => runInAction(this._props.optionsButtonOpts![1])}/> 
                         : null}
                 </div>
-                <div className="docCreatorMenu-templates-preview-window">
+                <div className={"docCreatorMenu-templates-preview-window " + this._props.styles}>
                     {this._props.loading ? 
                     (<div className="loading-spinner">
                         <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
index 7ff521f18..fae0d06e4 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
@@ -10,8 +10,6 @@ import { Doc } from '../../../../../fields/Doc';
 export class Template {
     _mainField: DynamicField;
 
-    private conditionalLogic: Record<string, Conditional[]> = {};
-
     /**
      * A Template can be created from a description of its fields (FieldSettings) or from a DynamicField
      * @param definition definition of template as settings or DynamicField
@@ -84,11 +82,11 @@ export class Template {
         return maxMatches === this.contentFields.length && this.title !== 'template_framework';
     };
 
-    applyConditionalLogicToField = (field: TemplateField) => {
+    applyConditionalLogicToField = (field: TemplateField, logic: Record<string, Conditional[]>) => {
         if (field instanceof DynamicField) return;
-        const logic: Conditional[] = this.conditionalLogic[field.getTitle()];
+        const fieldStatements: Conditional[] = logic[field.getTitle()];
         const content = field.getContent()
-        logic && logic.forEach(statement => {
+        fieldStatements && fieldStatements.forEach(statement => {
             if (content === statement.condition) {
                 if (statement.target === 'template') {
                     this._mainField.renderedDoc![statement.attribute] = statement.value;
@@ -99,17 +97,10 @@ export class Template {
         })
     }
 
-    applyConditionalLogic = () => {
-        const fields: TemplateField[] = [this._mainField, ...this.allFields];
-        fields.forEach(this.applyConditionalLogicToField);
-    }
-
-    addConditionalStatement = (field: string, statement: Conditional) => {
-        !this.conditionalLogic[field] ? this.conditionalLogic[field] = [statement] : this.conditionalLogic[field].push(statement);
-    }
-
-    removeConditionalStatement = (field: string, statement: Conditional) => {
-        this.conditionalLogic[field] = this.conditionalLogic[field]?.filter(cond => cond !== statement);
+    applyConditionalLogic = (logic: Record<string, Conditional[]>) => {
+        console.log('applying logic: ', logic)
+        const fields: TemplateField[] = [...this.allFields];
+        fields.forEach(field => this.applyConditionalLogicToField(field, logic));
     }
 
     setImageAsBackground(url: string, makeTransparent: boolean = false) {
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index ad5ef7118..48107ff0d 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -15,7 +15,7 @@ import { AspectRatioLimits, FireflyDimensionsMap, FireflyImageDimensions, Firefl
 
 const DashDropboxId = '2m86iveqdr9vzsa';
 export class DrawingFillHandler {
-    static drawingToImage = async (drawing: Doc, strength: number, user_prompt: string, styleDoc?: Doc, fromDocCreator?: DocCreatorMenu) => {
+    static drawingToImage = async (drawing: Doc, strength: number, user_prompt: string, styleDoc?: Doc, fromDocCreator?: DocCreatorMenu, numVariations: number = 4) => {
         const docData = drawing[DocData];
         const tags = StrListCast(docData.tags).map(tag => tag.slice(1));
         const styles = tags.filter(tag => FireflyStylePresets.has(tag));
@@ -45,9 +45,8 @@ export class DrawingFillHandler {
                 return imageUrlToBase64(structureUrl)
                     .then(gptDescribeImage)
                     .then((prompt, newPrompt = user_prompt || prompt) =>
-                        Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl })
+                        Networking.PostToServer('/queryFireflyImageFromStructure', { prompt: `${newPrompt}`, width: dims.width, height: dims.height, structureUrl, strength, presets: styles, styleUrl, variations: numVariations})
                             .then(res => {
-                                console.log('res is: ', res)
                                 const genratedDocs = DocCast(drawing.ai_firefly_generatedDocs) ?? Docs.Create.MasonryDocument([], { _width: 400, _height: 400 });
                                 drawing[DocData].ai_firefly_generatedDocs = genratedDocs;
                                 (res as Upload.ImageInformation[]).map(info => {
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index e5e030b0f..a272494b8 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -22,7 +22,7 @@ export default class FireflyManager extends ApiManager {
             return undefined;
         });
 
-    generateImageFromStructure = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, structureUrl: string, strength: number = 50, styles: string[], styleUrl: string | undefined) =>
+    generateImageFromStructure = (prompt: string = 'a realistic illustration of a cat coding', width: number = 2048, height: number = 2048, structureUrl: string, strength: number = 50, styles: string[], styleUrl: string | undefined, variations: number = 4) =>
         this.getBearerToken().then(response =>
             response?.json().then((data: { access_token: string }) =>
                 //prettier-ignore
@@ -36,7 +36,7 @@ export default class FireflyManager extends ApiManager {
                     ],
                     body: JSON.stringify({
                         prompt,
-                        numVariations: 4,
+                        numVariations: variations,
                         detailLevel: 'preview', 
                         modelVersion: 'image3_fast',
                         size: { width, height },
@@ -308,7 +308,7 @@ export default class FireflyManager extends ApiManager {
                                     return { styleUrl, structureUrl: dropboxStructureUrl };
                                 })
                                 .then(uploads =>
-                                    this.generateImageFromStructure(req.body.prompt, req.body.width, req.body.height, uploads.structureUrl, req.body.strength, req.body.presets, uploads.styleUrl)
+                                    this.generateImageFromStructure(req.body.prompt, req.body.width, req.body.height, uploads.structureUrl, req.body.strength, req.body.presets, uploads.styleUrl, req.body.variations)
                                         .then(images => {
                                             Promise.all((images ?? [new Error('no images were generated')]).map(fire => (fire instanceof Error ? fire : DashUploadUtils.UploadImage(fire.url))))
                                                 .then(dashImages => {
-- 
cgit v1.2.3-70-g09d2


From 5be235c8ca1b4ad5d499fac96133e77c773b7c6d Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Mon, 12 May 2025 01:27:50 -0400
Subject: data fields

---
 .../DocCreatorMenu/Backend/TemplateManager.ts      | 20 +++--
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.scss  |  5 +-
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 93 ++++++++++++----------
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  |  4 +-
 .../nodes/DataVizBox/DocCreatorMenu/Template.ts    | 26 ++++--
 .../DataVizBox/DocCreatorMenu/TemplateBackend.ts   |  1 +
 .../DocCreatorMenu/TemplateFieldTypes/DataField.ts | 21 +++++
 .../TemplateFieldTypes/TemplateField.ts            |  1 +
 8 files changed, 113 insertions(+), 58 deletions(-)
 create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DataField.ts

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
index 030c6db95..760940127 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
@@ -1,6 +1,6 @@
 import { action, makeAutoObservable } from 'mobx';
 import { Col } from '../DocCreatorMenu';
-import { FieldSettings } from '../TemplateFieldTypes/TemplateField';
+import { FieldSettings, TemplateField } from '../TemplateFieldTypes/TemplateField';
 import { Template } from '../Template';
 import { Doc, NumListCast } from '../../../../../../fields/Doc';
 import { DataVizBox } from '../../DataVizBox';
@@ -11,7 +11,7 @@ export type Conditional = {
     field: string;
     operator: '=' | '>' | '<';
     condition: string;
-    target: 'self' | 'template';
+    target: string;
     attribute: string;
     value: string;
 }
@@ -53,13 +53,21 @@ export class TemplateManager {
         }
     }
 
+    addDataField = (title: string) => {
+        this.templates.forEach(template => template.addDataField(title));
+    }
+
+    removeDataField = (title: string) => {
+        this.templates.forEach(template => template.removeDataField(title));
+    }
+
     createDocsFromTemplate = action((dv: DataVizBox, template: Template, cols: Col[], debug: boolean = false) => {
-        const fields = Array.from(Object.keys(dv.records[0]));
+        const csvFields = Array.from(Object.keys(dv.records[0]));
 
         const processContent = async (content: { [title: string]: string }) => {
             const templateCopy = template.clone();
 
-            fields
+            csvFields
                 .filter(title => title)
                 .forEach(title => {
                     const field = templateCopy.getFieldByTitle(title);
@@ -71,7 +79,7 @@ export class TemplateManager {
                 const promises = cols
                     .filter(field => field.AIGenerated)
                     .map(field => {
-                        const templateField = templateCopy.getFieldByTitle(field.title);
+                        const templateField: TemplateField = templateCopy.getFieldByTitle(field.title) as TemplateField;
                         if (templateField !== undefined) {
                             return gptFunc(field.type)(templateCopy, field, templateField.getID);
                         }
@@ -92,7 +100,7 @@ export class TemplateManager {
         const rowContents = debug
             ? [{}, {}, {}, {}]
             : NumListCast(dv.layoutDoc.dataViz_selectedRows).map(row =>
-                  fields.reduce(
+                  csvFields.reduce(
                       (values, col) => {
                           values[col] = dv.records[row][col];
                           return values;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
index 8ac718fb7..2f7b68c4d 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
@@ -1141,6 +1141,7 @@
   
   .switch {
     gap: 0.75rem;
+    margin-bottom: 0px;
   }
   
   .switch .slider {
@@ -1170,7 +1171,7 @@
   }
   
   .switch input:checked + .slider {
-    background: #9fe29d;
+    background: #78c2f1;
   }
   
   .switch input:checked + .slider::before {
@@ -1182,6 +1183,8 @@
     font-size: 100%;
     color: whitesmoke;
     text-align: center;
+    margin-bottom: 0px;
+    font-weight: 500;
 }
   
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index 2565a9332..873bfa734 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -30,7 +30,7 @@ import './DocCreatorMenu.scss';
 import { TemplateField, ViewType } from './TemplateFieldTypes/TemplateField';
 import { Template } from './Template';
 import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend';
-import { TemplateManager } from './Backend/TemplateManager';
+import { Conditional, TemplateManager } from './Backend/TemplateManager';
 import { DrawingFillHandler } from '../../../smartdraw/DrawingFillHandler';
 import { CgPathIntersect } from 'react-icons/cg';
 import { StaticContentField } from './TemplateFieldTypes/StaticContentField';
@@ -71,15 +71,6 @@ export type Col = {
     AIGenerated?: boolean;
 };
 
-export type Conditional = {
-    field: string;
-    operator: '=' | '>' | '<';
-    condition: string;
-    target: 'self' | 'template';
-    attribute: string;
-    value: string;
-}
-
 interface DocCreateMenuProps {
     addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean;
 }
@@ -364,15 +355,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         this._pageY = y + translation.y;
     };
 
-    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 updateRenderedPreviewCollection = async (template: Template) => {
         this._docsRendering = true;
         this._fullyRenderedDocs = this._dataViz ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, template, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : [];
@@ -469,11 +451,18 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     };
 
     @action setColType = (column: Col, type: TemplateFieldType) => {
+        if (type === TemplateFieldType.DATA) {
+            this.templateManager.addDataField(column.title);
+        } else if (column.type === TemplateFieldType.DATA) {
+            this.templateManager.removeDataField(column.title);
+        }
+
         if (this.selectedFields.includes(column.title)) {
             this._dataViz?.setColumnType(column.title, type);
         } else {
             column.type = type;
         }
+
         this.forceUpdate();
     };
 
@@ -576,9 +565,11 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         } else {
             this._dataViz?.updateColDefaults();
 
-            templates.push(...this.templateManager.getValidTemplates(this.fieldsInfos));
+            const contentFields = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA);
+
+            templates.push(...this.templateManager.getValidTemplates(contentFields));
 
-            const assignments = await this.assignColsToFields(templates, this.fieldsInfos);
+            const assignments = await this.assignColsToFields(templates, contentFields);
 
             const renderedTemplatePromises = assignments.map(([template, assgns]) => TemplateMenuAIUtils.applyGPTContentToTemplate(template, assgns));
 
@@ -918,6 +909,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     }
 
     get dashboardContents() {
+        const contentFieldTitles = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA).map(field => field.title).concat('template');
+
         const conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => {
 
             const params: Conditional = parameters ?? this._currEditingConditional;
@@ -942,8 +935,9 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                     <div className='form-row-plain-text'>then</div>
                     <div className="operator-options-dropdown">
                         <span className="operator-dropdown-current">{params.target ?? 'self'}</span>
-                        <div className='operator-dropdown-option' onPointerDown={() => {params.target = 'self'}}>{'own'}</div>
-                        <div className='operator-dropdown-option' onPointerDown={() => {params.target = 'template'}}>{`template's`}</div>
+                        {contentFieldTitles.map(fieldTitle => 
+                            <div className='operator-dropdown-option' onPointerDown={() => {params.target = fieldTitle}}>{fieldTitle === title ? 'own' : fieldTitle}</div>
+                        )}
                     </div>
                     <textarea
                         className="form-row-textarea"
@@ -990,38 +984,49 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                         <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>
+                                <span className="type-display">{
+                                    field.type === TemplateFieldType.TEXT ? 'Text Field' 
+                                    : field.type === TemplateFieldType.VISUAL ? 'File Field' 
+                                    : field.type === TemplateFieldType.DATA ? 'Data 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>
+                                    <input className="bubble" type="radio" name="type" onClick={() => this.setColType(field, TemplateFieldType.DATA)} />
+                                    <div className="text">Data</div>
                                 </div>
                             </div>
                         </div>
                     </div>
-                    <div className="sizes-box">
-                        <div className="top-bar"> Valid Sizes </div>
-                        <div className="content">
-                            <div className="bubbles">
-                                {Object.values(TemplateFieldSize).map(size => (
-                                    <div key={field + size}>
-                                        <input className="bubble" type="checkbox" name="type" checked={field.sizes.includes(size)} onChange={e => this.modifyColSizes(field, size, e.target.checked)} />
-                                        <div className="text">{size}</div>
+                    { field.type === TemplateFieldType.DATA ? null :
+                        (<> 
+                            <div className="sizes-box">
+                                <div className="top-bar"> Valid Sizes </div>
+                                <div className="content">
+                                    <div className="bubbles">
+                                        {Object.values(TemplateFieldSize).map(size => (
+                                            <div key={field + size}>
+                                                <input className="bubble" type="checkbox" name="type" checked={field.sizes.includes(size)} onChange={e => this.modifyColSizes(field, size, e.target.checked)} />
+                                                <div className="text">{size}</div>
+                                            </div>
+                                        ))}
                                     </div>
-                                ))}
+                                </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 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 className="conditionals-section">
                         <span className="conditionals-title">Conditional Logic</span>
                         {conditionForm(field.title, this._currEditingConditional, true)}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index 4ff509d73..e1d8ea8a5 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -99,13 +99,13 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
                         <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generateVariations}/>
                     </div>
                         <nav className="options‑menu">
-                            <div className="menu‑item switch">
+                            <label className="menu‑item switch">
                                 <input type="checkbox" checked={this.fireflyOptions.useStyleRef} 
                                     onChange={(e) => runInAction(() => this.fireflyOptions.useStyleRef = e.target.checked)}
                                 />
                                 <span className="slider round"></span>
                                 <span className="firefly-option-label">Use template as style guide</span>
-                            </div>
+                            </label>
                             <div className="menu‑item">
                                 <span className="firefly-option-label">Variations</span>
                                 <input type="range" id="variations" 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
index fae0d06e4..1889e4984 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
@@ -6,10 +6,13 @@ import { FieldSettings, TemplateField, ViewType } from './TemplateFieldTypes/Tem
 import { Conditional } from './Backend/TemplateManager';
 import { ImageField } from '../../../../../fields/URLField';
 import { Doc } from '../../../../../fields/Doc';
+import { TemplateDataField } from './TemplateFieldTypes/DataField';
 
 export class Template {
     _mainField: DynamicField;
 
+    private dataFields: TemplateDataField[] = [];
+
     /**
      * A Template can be created from a description of its fields (FieldSettings) or from a DynamicField
      * @param definition definition of template as settings or DynamicField
@@ -55,13 +58,17 @@ export class Template {
         //dispose each subfields disposers, etc.
     };
 
-    clone = (withContent: boolean = false) => new Template(this._mainField?.makeClone(undefined, withContent) ?? TemplateLayouts.BasicSettings);
+    clone = (withContent: boolean = false) => {
+        const clone = new Template(this._mainField?.makeClone(undefined, withContent) ?? TemplateLayouts.BasicSettings);
+        this.dataFields.forEach(field => clone.addDataField(field.title));
+        return clone;
+    };
 
     getRenderedDoc = () => this.doc;
 
     getFieldByID = (id: number): TemplateField => this.allFields.filter(field => field.getID === id)[0];
 
-    getFieldByTitle = (title: string) => this.allFields.filter(field => field.getTitle() === title)[0];
+    getFieldByTitle = (title: string) => [...this.allFields, ...this.dataFields].filter(field => field.getTitle() === title)[0];
 
     setupMainField = (templateInfo: FieldSettings) => TemplateField.CreateField(templateInfo, 1, undefined) as DynamicField;
 
@@ -77,12 +84,20 @@ export class Template {
         field.setTitle(col.title);
     }
 
+    addDataField = (title: string, content?: string) => {
+        this.dataFields.push(new TemplateDataField(title, content));
+    }
+
+    removeDataField = (title: string) => {
+        this.dataFields = this.dataFields.filter(field => !(field.title === title));
+    }
+
     isValidTemplate = (cols: Col[]) => {
         const maxMatches = this.maxMatches(this.getMatches(cols));
         return maxMatches === this.contentFields.length && this.title !== 'template_framework';
     };
 
-    applyConditionalLogicToField = (field: TemplateField, logic: Record<string, Conditional[]>) => {
+    applyConditionalLogicToField = (field: TemplateField | TemplateDataField, logic: Record<string, Conditional[]>) => {
         if (field instanceof DynamicField) return;
         const fieldStatements: Conditional[] = logic[field.getTitle()];
         const content = field.getContent()
@@ -91,7 +106,8 @@ export class Template {
                 if (statement.target === 'template') {
                     this._mainField.renderedDoc![statement.attribute] = statement.value;
                 } else {
-                    field.renderedDoc![statement.attribute] = statement.value;
+                    const targetField: TemplateField = this.getFieldByTitle(statement.target) as TemplateField;
+                    targetField && (targetField.renderedDoc![statement.attribute] = statement.value);
                 }
             }
         })
@@ -99,7 +115,7 @@ export class Template {
 
     applyConditionalLogic = (logic: Record<string, Conditional[]>) => {
         console.log('applying logic: ', logic)
-        const fields: TemplateField[] = [...this.allFields];
+        const fields: (TemplateField | TemplateDataField)[] = [...this.allFields, ...this.dataFields];
         fields.forEach(field => this.applyConditionalLogicToField(field, logic));
     }
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts
index 06eab63f0..26fd3a8fc 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateBackend.ts
@@ -4,6 +4,7 @@ export enum TemplateFieldType {
     TEXT = 'text',
     VISUAL = 'visual',
     UNSET = 'unset',
+    DATA = 'data',
 }
 
 export enum TemplateFieldSize {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DataField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DataField.ts
new file mode 100644
index 000000000..aaa475bed
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DataField.ts
@@ -0,0 +1,21 @@
+
+import { Template } from "../Template";
+import { TemplateField, ViewType } from "./TemplateField";
+
+export class TemplateDataField {
+
+    viewType: ViewType = ViewType.NONE;
+
+    title: string = '';
+    content: string | undefined;
+
+    constructor(title: string, content?: string) {
+        this.title = title;
+        this.content = content;
+    }
+
+    setContent(content: string, viewType?: ViewType) { this.content = content }
+    getContent() { return this.content }
+
+    getTitle() { return this.title }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
index bc6b13c76..2c304247d 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
@@ -157,6 +157,7 @@ export enum ViewType {
     DEC = 'decoration',
     IMG = 'image',
     TEXT = 'text',
+    NONE = 'none'
 }
 
 export type FieldDimensions = {
-- 
cgit v1.2.3-70-g09d2


From 03d1cd8d220fbb27acb49a3ff9e791db9292b83c Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Mon, 12 May 2025 07:26:51 -0400
Subject: c

---
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.scss  |  2 +-
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 37 ++++++++----
 .../DocCreatorMenu/Menu/ConditionalsTextarea.tsx   | 65 ++++++++++++++++++++++
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  | 15 +++--
 .../nodes/DataVizBox/DocCreatorMenu/Template.ts    | 29 +++++++++-
 .../TemplateFieldTypes/DynamicField.ts             |  8 +++
 .../TemplateFieldTypes/TemplateField.ts            |  6 ++
 src/client/views/smartdraw/DrawingFillHandler.tsx  |  6 +-
 8 files changed, 149 insertions(+), 19 deletions(-)
 create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ConditionalsTextarea.tsx

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
index 2f7b68c4d..5232ad41c 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
@@ -388,7 +388,7 @@
     justify-content: center;
     align-items: center;
     position: absolute;
-    width: calc(100% - 10px);
+    left: 50%;
     bottom: 0px;
     margin: 0px;
     margin-bottom: 10px;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index b024a6e9b..bd5c50aa0 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -38,6 +38,7 @@ import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils'
 import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid';
 import { FireflyStructureOptions, TemplateEditingWindow } from './Menu/TemplateEditingWindow';
 import { DocCreatorMenuButton } from './Menu/DocCreatorMenuButton';
+import { ConditionalsTextArea } from './Menu/ConditionalsTextarea';
 
 export enum LayoutType {
     FREEFORM = 'Freeform',
@@ -91,6 +92,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     @observable _userTemplates: Template[] = [];
     @observable _selectedTemplate: Template | undefined = undefined;
     @observable _currEditingTemplate: Template | undefined = undefined;
+    @observable _editedTemplateTrail: Template[] = [];
 
     @observable _userCreatedFields: Col[] = [];
     @observable _collapsedCols: String[] = []; //any columns whose options panels are hidden
@@ -589,8 +591,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         this.variations = [];
         const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
 
-        console.log('styleref: ', useStyleRef);
-
         const clone: Doc = (await Doc.MakeClone(onDoc)).clone;
         mainCollection.addDocument(clone);
         clone.x = 10000;
@@ -618,15 +618,19 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         }
     };
 
+    @action editLastTemplate = () => { if (this._editedTemplateTrail.length) this._currEditingTemplate = this._editedTemplateTrail.pop()}
+
     @action setExpandedView = (template: Template | undefined) => {
-        this._currEditingTemplate = template;
 
         if (template) {
             this._menuContent = 'templateEditing';
+            this._currEditingTemplate && this._editedTemplateTrail.push(this._currEditingTemplate);
         } else {
             this._menuContent = 'templates';
         }
 
+        this._currEditingTemplate = template;
+
         //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''});
     };
 
@@ -910,15 +914,26 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         );
     }
 
+    private _newCondCache: Record<string, Conditional> = {};
+
+    getParams = (title: string, parameters?: Conditional): Conditional => {
+        if (parameters) return parameters;
+
+        if (!this._newCondCache[title]) {
+            this._newCondCache[title] = { field: title, operator: '=', condition: '', target: '', attribute: '', value: '' };
+        }   
+        return this._newCondCache[title];
+    };
+
     get dashboardContents() {
         const contentFieldTitles = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA).map(field => field.title).concat('Template');
 
         const conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => {
 
-            const params: Conditional = parameters ?? this._currEditingConditional;
+            const params: Conditional = this.getParams(title, parameters);
 
             return (
-            <div className='form' id={String(Math.random() * 100000)}>
+            <div className='form'>
                 <div className='form-row'>
                     <div className='form-row-plain-text'>If</div>
                     <div className='form-row-plain-text'>{title}</div>
@@ -929,7 +944,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                         <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '<'}}>{'<'}</div>
                         <div className='operator-dropdown-option' onPointerDown={() => {params.operator = 'contains'}}>{'has'}</div>
                     </div>
-                    <textarea
+                    <input
                         className="form-row-textarea"
                         onChange={e => runInAction(() => params.condition = e.target.value)}
                         placeholder='value'
@@ -957,9 +972,9 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                     />
                 </div>
                 {empty ? 
-                <DocCreatorMenuButton icon={'plus'} styles={'float-right'} function={() => this.templateManager.addFieldCondition(title, params)}/>
+                <DocCreatorMenuButton icon={'plus'} styles={'float-right border'} function={() => this.templateManager.addFieldCondition(title, params)}/>
                 :
-                <DocCreatorMenuButton icon={'minus'} styles={'float-right'} function={() => this.templateManager.removeFieldCondition(title, params)}/>
+                <DocCreatorMenuButton icon={'minus'} styles={'float-right border'} function={() => this.templateManager.removeFieldCondition(title, params)}/>
                 }
             </div>
             )
@@ -1032,7 +1047,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                     }
                     <div className="conditionals-section">
                         <span className="conditionals-title">Conditional Logic</span>
-                        {conditionForm(field.title, this._currEditingConditional, true)}
+                        {conditionForm(field.title, undefined, true)}
                         {this.templateManager.conditionalFieldLogic[field.title]?.map(condition => conditionForm(condition.field, condition))}
                     </div>
                     </>
@@ -1053,6 +1068,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
     @computed get editingView() { return <TemplateEditingWindow template={this._currEditingTemplate as Template} menu={this} /> }
     
+    private optionsButtonOpts: [IconProp, () => any] = ['gear', () => (this._menuContent = 'dashboard')];
+
     get renderSelectedViewType() {
         switch (this._menuContent) {
             case 'templates': 
@@ -1063,7 +1080,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                                 title={'Suggested Templates'} 
                                 menu={this} 
                                 loading={this._GPTLoading}
-                                optionsButtonOpts={['gear', () => (this._menuContent = 'dashboard')]}
+                                optionsButtonOpts={this.optionsButtonOpts}
                                 templates={this._suggestedTemplates}
                             />
                             <div className="docCreatorMenu-GPT-options">
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ConditionalsTextarea.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ConditionalsTextarea.tsx
new file mode 100644
index 000000000..2ca0bde3f
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ConditionalsTextarea.tsx
@@ -0,0 +1,65 @@
+import { observer } from "mobx-react";
+import { ObservableReactComponent } from "../../../../ObservableReactComponent";
+import { Conditional } from "../Backend/TemplateManager";
+import { action, makeObservable, observable, runInAction } from "mobx";
+import React from "react";
+
+interface ConditionalsTextAreaProps {
+    conditional: Conditional;
+    property: keyof Conditional;
+}
+
+@observer
+export class ConditionalsTextArea extends ObservableReactComponent<ConditionalsTextAreaProps> {
+
+    private mirrorRef: HTMLSpanElement | null = null;
+
+    @observable private inputWidth: string = '60px';
+
+    constructor(props: ConditionalsTextAreaProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    setMirrorRef: React.LegacyRef<HTMLSpanElement> = (node) => { this.mirrorRef = node }
+
+    @action updateInputWidth() {
+        const mirror = this.mirrorRef;
+        if (mirror) {
+            const width = mirror.offsetWidth;
+            if ( width + 8 > 60) this.inputWidth = `${width + 8}px`;
+        }
+    }
+
+    render() {
+        return (
+            <div style={{ display: 'inline-block', position: 'relative' }}>
+                <span
+                    ref={this.setMirrorRef}
+                    style={{
+                        position: 'absolute',
+                        visibility: 'hidden',
+                        whiteSpace: 'pre',
+                        font: 'inherit',
+                        padding: 0,
+                    }}
+                >
+                    {this._props.conditional[this._props.property] || ' '}
+                </span>
+                <input
+                    className="form-row-input"
+                    value={this.props.conditional[this.props.property] ?? ''}
+                    onChange={e => {
+                        runInAction(() => {
+                        this.props.conditional[this.props.property] = e.target.value as any;
+                        });
+                        this.updateInputWidth();
+                    }}
+                    style={{ width: this.inputWidth }}
+                    placeholder={this.props.property}
+                />
+            </div>
+        );
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index e1d8ea8a5..cb87e9b47 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -17,6 +17,8 @@ import { IDisposer } from "mobx-utils";
 import { ImageField } from "../../../../../../fields/URLField";
 import { DocCreatorMenuButton } from "./DocCreatorMenuButton";
 import { TbHistory } from "react-icons/tb";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import { docStyle } from "pdfjs-dist/types/web/ui_utils";
 
 export type FireflyStructureOptions = {
     numVariations: number;
@@ -50,7 +52,9 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
     generateVariations = async () => {
         this._props.menu._variations = [];
         this._loading = true;
-        const doc: Doc = this._props.template.clone(false).getRenderedDoc()!;
+        const cloneTemplate = this._props.template.clone(false);
+        cloneTemplate.setMatteBackground();
+        const doc: Doc = cloneTemplate.getRenderedDoc()!;
         this._variationURLs = await this._props.menu.generateVariations(doc, this.prompt, this.fireflyOptions);
         this._variationURLs.forEach(url => {
             const newTemplate: Template = this._props.template.clone(true);
@@ -68,6 +72,9 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
         this.promptInput = node;
     }
 
+    private optionsButtonOpts: [IconProp, () => any] = ['gear', () => {}];
+    private previewBoxRightButtonOpts: [IconProp, () => any] = ['gear', () => this.forceUpdate()];
+
     render() {
         return (
             <div className='docCreatorMenu-editing-firefly-section'>
@@ -78,8 +85,8 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
                     loading={this._loading}
                     styles={'scrolling'}
                     templates={this._props.menu._variations}
-                    optionsButtonOpts={['gear', () => {}]}
-                    previewBoxRightButtonOpts={['gear', (template: Template) => {this.forceUpdate();}]}
+                    optionsButtonOpts={this.optionsButtonOpts}
+                    previewBoxRightButtonOpts={this.previewBoxRightButtonOpts}
                 />
                 <div className="docCreatorMenu-firefly-options">
                     <div className="docCreatorMenu-variation-prompt-row">
@@ -226,7 +233,7 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                                 this._props.menu.setExpandedView(undefined);
                             }}/>
                         <DocCreatorMenuButton icon={'lightbulb'} function={() => this.setVariationTab(!this._variationsTabOpen)}/>
-                        <DocCreatorMenuButton icon={'arrow-rotate-backward'} function={() => { this._props.menu.setExpandedView(this._props.template); this.forceUpdate(); }}/>
+                        <DocCreatorMenuButton icon={'arrow-rotate-backward'} function={() => { this._props.menu.editLastTemplate(); this.forceUpdate(); }}/>
                     </div>
                 </div>
             </div>
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
index 1889e4984..232a9cf67 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
@@ -137,7 +137,34 @@ export class Template {
             });
         }
 
-        this._mainField.makeBackgroundField(field)
+        this._mainField.makeBackgroundField(field);
+    }
+
+    /**
+     * This function is just a hack for now to get around weird document icon stuff (specifically it misses the background)
+     */
+     setMatteBackground(makeTransparent: boolean = false) {
+        if (this._mainField.hasBackground) {
+            return;
+        }
+
+        const fieldSettings: FieldSettings = {
+            tl: [-1, -1],
+            br: [1, 1],
+            opts: {backgroundColor: String(this._mainField.renderedDoc!.backgroundColor)},
+            viewType: ViewType.TEXT,
+        }
+
+        const field: TemplateField = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField);
+
+        if (makeTransparent) {
+            this.allFields.forEach(field => {
+                field.updateDocSetting('backgroundColor', 'transparent');
+                field.updateDocSetting('borderWidth', '0');
+            });
+        }
+
+        this._mainField.makeBackgroundField(field);
     }
 
     getMatches = (cols: Col[]): number[][] => {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
index fc520ba47..1576dd240 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
@@ -20,6 +20,10 @@ export class DynamicField extends TemplateField {
         return this.getSubfields.flatMap(field => [field, ...((field as DynamicField).getAllSubfields ?? [])]);
     }
 
+    get hasBackground() {
+        return this.backgroundField !== undefined;
+    }
+
     handleFieldUpdate = (newDocsList: Doc[]) => {
         const currRenderedDocs = new Set(this.getSubfields.filter(field => field.renderedDoc).map(field => field.renderedDoc!));
         newDocsList.forEach(doc => !currRenderedDocs.has(doc) && this.addFieldFromDoc(doc));
@@ -44,7 +48,10 @@ export class DynamicField extends TemplateField {
 
     addField = (field: TemplateField, layer: number = 0) => {
         if (!this._subfields.includes(field)) {
+            console.log('success')
+            console.log('subs: ', this._subfields)
             this._subfields.splice(layer, 0, field);
+            console.log('subffelds: ', this._subfields)
         }
     };
 
@@ -91,6 +98,7 @@ export class DynamicField extends TemplateField {
         const dynClone = super.makeClone(parent) as DynamicField;
         dynClone._subfields = this.getSubfields.map(field => {
             if (field === this.backgroundField) {
+                console.log('background found')
                 const backgroundField: TemplateField = field.makeClone(dynClone, true);
                 dynClone.makeBackgroundField(backgroundField);
                 return backgroundField;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
index 2c304247d..a1107caf3 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
@@ -81,6 +81,11 @@ export abstract class TemplateField {
     makeClone(parent?: TemplateField, withContent: boolean = false) {
         const settings: FieldSettings = structuredClone(this._settings);
         const cloned = TemplateField.CreateField(settings, this._id, parent, true); // create a value for this.Document/subfields that we want to ignore
+        cloned.renderedDoc!.width = this.renderedDoc!.width;
+        cloned.renderedDoc!.height = this.renderedDoc!.height;
+        cloned.renderedDoc!.x = this.renderedDoc!.x;
+        cloned.renderedDoc!.y = this.renderedDoc!.y;
+        cloned.renderedDoc!.backgroundColor = this.renderedDoc!.backgroundColor;
         cloned.setTitle(this._title);
         cloned._dimensions = this._dimensions;
         withContent && cloned.setContent(this.getContent());
@@ -134,6 +139,7 @@ export abstract class TemplateField {
         opts._nativeWidth ??= dimensions.width;
         opts._nativeHeight ??= dimensions.height;
         opts._layout_nativeDimEditable ??= true;
+        opts.layout_boxShadow = 'none';
     };
 }
 
diff --git a/src/client/views/smartdraw/DrawingFillHandler.tsx b/src/client/views/smartdraw/DrawingFillHandler.tsx
index 73a47681a..8918d1b0e 100644
--- a/src/client/views/smartdraw/DrawingFillHandler.tsx
+++ b/src/client/views/smartdraw/DrawingFillHandler.tsx
@@ -26,11 +26,11 @@ export class DrawingFillHandler {
             : Doc.Links(drawing)
                   .map(link => Doc.getOppositeAnchor(link, drawing))
                   .map(anchor => anchor && DocCast(anchor.embedContainer)); 
-        const styyleUrl = await DocumentView.GetDocImage(styleDocs.lastElement())?.then(styleImg => {
+        const styleUrl = await DocumentView.GetDocImage(styleDocs.lastElement())?.then(styleImg => {
             const hrefParts = ImageCast(styleImg).url.href.split('.');
             return `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
         });
-        return DocumentView.GetDocImage(drawing)?.then(imageField => {
+        return DocumentView.GetDocImage(drawing)?.then((imageField) => {
             if (imageField) {
                 const aspectRatio = (drawing.width as number) / (drawing.height as number);
                 const dims = (() => {
@@ -42,7 +42,7 @@ export class DrawingFillHandler {
                 const { href } = ImageCast(imageField).url;
                 const hrefParts = href.split('.');
                 const structureUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
-                const styleUrl = styleDoc ? `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}` : undefined;
+                const styleUrl = `${hrefParts.slice(0, -1).join('.')}_o.${hrefParts.lastElement()}`;
                 return imageUrlToBase64(structureUrl)
                     .then(gptDescribeImage)
                     .then((prompt, newPrompt = user_prompt || prompt) =>
-- 
cgit v1.2.3-70-g09d2


From f92a02ec5d676359cb268a35d30e5bf9886199c1 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Fri, 30 May 2025 03:27:03 -0400
Subject: couple more component refactors

---
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 601 +--------------------
 .../Menu/ExpandedTemplatePreview.tsx               |   0
 .../DocCreatorMenu/Menu/FieldOptionsScreen.tsx     |   0
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  |   6 +-
 .../Menu/TemplateMenuFieldOptions.tsx              | 193 +++++++
 .../Menu/TemplateRenderPreviewWindow.tsx           | 346 ++++++++++++
 .../TemplateFieldTypes/StaticContentField.ts       |   8 +-
 src/server/ApiManagers/FireflyManager.ts           |   4 +-
 8 files changed, 580 insertions(+), 578 deletions(-)
 delete mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ExpandedTemplatePreview.tsx
 delete mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/FieldOptionsScreen.tsx
 create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx
 create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index 1e54c0628..9a84e69a9 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -39,6 +39,8 @@ import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid';
 import { FireflyStructureOptions, TemplateEditingWindow } from './Menu/TemplateEditingWindow';
 import { DocCreatorMenuButton } from './Menu/DocCreatorMenuButton';
 import { ConditionalsTextArea } from './Menu/ConditionalsTextarea';
+import { TemplatesRenderPreviewWindow } from './Menu/TemplateRenderPreviewWindow';
+import { TemplateMenuFieldOptions } from './Menu/TemplateMenuFieldOptions';
 
 export enum LayoutType {
     FREEFORM = 'Freeform',
@@ -81,12 +83,10 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     // eslint-disable-next-line no-use-before-define
     static Instance: DocCreatorMenu;
 
-    private DEBUG_MODE: boolean = false;
+    DEBUG_MODE: boolean = false;
     private _ref: HTMLDivElement | null = null;
     private templateManager: TemplateManager;
 
-    @observable _fullyRenderedDocs: Doc[] = []; // collection of templates filled in with content
-    @observable _renderedDocCollection: Doc | undefined = undefined; // fullyRenderedDocs in a parent collection
     @observable _docsRendering: boolean = false; // dictates loading symbol
 
     @observable _userTemplates: Template[] = [];
@@ -95,12 +95,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     @observable _editedTemplateTrail: Template[] = [];
 
     @observable _userCreatedFields: Col[] = [];
-    @observable _collapsedCols: String[] = []; //any columns whose options panels are hidden
-    @observable _currEditingConditional: Conditional = {} as Conditional;
-
-    @observable _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 3, repeat: 0 };
-    @observable _savedLayouts: DataVizTemplateLayout[] = [];
-    @observable _loadingVariants: boolean = false;
 
     @observable _suggestedTemplates: Template[] = [];
     @observable _GPTLoading: boolean = false;
@@ -114,7 +108,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     @observable _startPos?: { x: number; y: number };
     @observable _shouldDisplay: boolean = false;
 
-    @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' | 'templateEditing' = 'templates';
+    @observable _menuContent: 'templates' | 'renderPreview' | 'saved' | 'dashboard' | 'templateEditing' = 'templates';
     @observable _dragging: boolean = false;
     @observable _dataViz?: DataVizBox;
     @observable _interactionLock: boolean | undefined;
@@ -138,8 +132,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     @action setDataViz = (dataViz: DataVizBox) => {
         this._dataViz = dataViz;
         this._selectedTemplate = undefined;
-        this._renderedDocCollection = undefined;
-        this._fullyRenderedDocs = [];
         this._suggestedTemplates = [];
         this._userCreatedFields = [];
     };
@@ -147,36 +139,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         this._suggestedTemplates = templates; //prettier-ignore
     };
 
-    @computed get docsToRender() {
-        if (this.DEBUG_MODE) {
-            return [1, 2, 3, 4];
-        } else {
-            return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : [];
-        }
-    }
-
-    @computed get rowsCount() {
-        switch (this._layout.type) {
-            case LayoutType.FREEFORM:
-                return Math.ceil(this.docsToRender.length / (this._layout.columns ?? 1)) ?? 0;
-            case LayoutType.CAROUSEL3D:
-                return 1.8;
-            default:
-                return 1;
-        }
-    }
-
-    @computed get columnsCount() {
-        switch (this._layout.type) {
-            case LayoutType.FREEFORM:
-                return this._layout.columns ?? 0;
-            case LayoutType.CAROUSEL3D:
-                return 3;
-            default:
-                return 1;
-        }
-    }
-
     @computed get selectedFields() {
         return StrListCast(this._dataViz?.layoutDoc._dataViz_axes);
     }
@@ -203,10 +165,6 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
             .concat(this._userCreatedFields);
     }
 
-    @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 };
@@ -356,12 +314,8 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         this._pageX = x + translation.x;
         this._pageY = y + translation.y;
     };
-
-    @action updateRenderedPreviewCollection = async (template: Template) => {
-        this._docsRendering = true;
-        this._fullyRenderedDocs = this._dataViz ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, template, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : [];
-        this.updateRenderedDocCollection();
-    };
+    
+    async createDocsForPreview(): Promise<Doc[]> { return this._dataViz && this._selectedTemplate ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, this._selectedTemplate, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : []; }
 
     @action updateSelectedTemplate = async (template: Template) => {
         if (this._selectedTemplate === template) {
@@ -369,53 +323,12 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
             return;
         } else {
             this._selectedTemplate = template;
-            this.updateRenderedPreviewCollection(template);
         }
     };
 
-    @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;
-    };
-
-    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;
-    };
-
-    editTemplate = (doc: Doc) => {
-        DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight);
-        DocumentView.DeselectAll();
-        Doc.UnBrushDoc(doc);
-    };
-
-    testTemplate = async () => {
-        this._suggestedTemplates = this.templateManager.templates; //prettier-ignore
-
-        //console.log(this.templateManager.templates)
-
-        // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-
-        // this.templateManager.templates.forEach(template => {
-        //     const doc = template.mainField.renderedDoc();
-        //     mainCollection.addDocument(doc);
-        // })
-
-        // this.forceUpdate();
-
-        // try {
-        //     const res = await gptImageCall('Image of panda eating a cookie');
-
-        //     if (res) {
-        //         const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res });
-
-        //         console.log(result);
-        //     }
-        // } catch (e) {
-        //     console.log(e);
-        // }
-    };
+    // testTemplate = async () => {
+    //     this._suggestedTemplates = this.templateManager.templates; //prettier-ignore
+    // };
 
     @action addField = () => {
         const newFields: Col[] = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [], AIGenerated: true }]);
@@ -607,8 +520,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         this.variations.push(url);
     }
 
-    addRenderedCollectionToMainview = () => {
-        const collection = this._renderedDocCollection;
+    addRenderedCollectionToMainview = (collection: Doc) => {
         if (collection) {
             const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
             collection.x = this._pageX - this._menuDimensions.width;
@@ -634,484 +546,34 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''});
     };
 
-    @action updateXMargin = (input: string) => {
-        this._layout.xMargin = Number(input);
-        setTimeout(() => {
-            if (!this._renderedDocCollection || !this._fullyRenderedDocs) return;
-            this.applyLayout(this._renderedDocCollection, this._fullyRenderedDocs);
-        });
-    };
-    @action updateYMargin = (input: string) => {
-        this._layout.yMargin = Number(input);
-        setTimeout(() => {
-            if (!this._renderedDocCollection || !this._fullyRenderedDocs) return;
-            this.applyLayout(this._renderedDocCollection, this._fullyRenderedDocs);
-        });
-    };
-    @action updateColumns = (input: string) => {
-        this._layout.columns = Number(input);
-        this.updateRenderedDocCollection();
-    };
-
-    get layoutConfigOptions() {
-        const optionInput = (icon: string, func: (input: string) => void, 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 IconProp} />
-                    </div>
-                    <input defaultValue={def} onInput={e => func(e.currentTarget.value)} className="docCreatorMenu-input config layout-config" />
-                </div>
-            );
-        };
-
-        switch (this._layout.type) {
-            case LayoutType.FREEFORM:
-                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>
-                );
-            default:
-                break;
-        }
-    }
-
-    applyLayout = (collection: Doc, docs: Doc[]) => {
-        const { horizontalSpan, verticalSpan } = this.previewInfo;
-        collection._height = verticalSpan;
-        collection._width = horizontalSpan;
-
-        const columns: number = this._layout.columns ?? this.columnsCount;
-        const xGap: number = this._layout.xMargin;
-        const yGap: number = this._layout.yMargin;
-        const startX: number = -Number(collection._width) / 2;
-        const startY: number = -Number(collection._height) / 2;
-        const docHeight: number = Number(docs[0]._height);
-        const docWidth: number = Number(docs[0]._width);
-
-        if (columns === 0 || docs.length === 0) {
-            return;
-        }
-
-        let i: number = 0;
-        let docsChanged: number = 0;
-        let curX: number = startX;
-        let curY: number = startY;
-
-        while (docsChanged < docs.length) {
-            while (i < columns && docsChanged < docs.length) {
-                docs[docsChanged].x = curX;
-                docs[docsChanged].y = curY;
-                curX += docWidth + xGap;
-                ++docsChanged;
-                ++i;
-            }
-            i = 0;
-            curX = startX;
-            curY += docHeight + yGap;
-        }
-    };
-
-    @computed
-    get previewInfo() {
-        const docHeight: number = Number(this._fullyRenderedDocs[0]._height);
-        const docWidth: number = Number(this._fullyRenderedDocs[0]._width);
-        const layout = this._layout;
-        return {
-            docHeight: docHeight,
-            docWidth: docWidth,
-            horizontalSpan: (docWidth + layout.xMargin) * this.columnsCount - layout.xMargin,
-            verticalSpan: (docHeight + layout.yMargin) * this.rowsCount - layout.yMargin,
-        };
-    }
-
-    /**
-     * Updates the preview that shows how all docs will be rendered in the chosen collection type.
-    @type the type of collection the docs should render to (ie. freeform, carousel, card)
-     */
-    updateRenderedDocCollection = () => {
-        if (!this._fullyRenderedDocs) return;
-
-        const collectionFactory = (): ((docs: Doc[], options: DocumentOptions) => Doc) => {
-            switch (this._layout.type) {
-                case LayoutType.CAROUSEL3D: return Docs.Create.Carousel3DDocument;
-                case LayoutType.FREEFORM:   return Docs.Create.FreeformDocument;
-                case LayoutType.CARD:       return Docs.Create.CardDeckDocument;
-                case LayoutType.MASONRY:    return Docs.Create.MasonryDocument;
-                case LayoutType.CAROUSEL:   return Docs.Create.CarouselDocument;
-                default:                    return Docs.Create.FreeformDocument;
-            } // prettier-ignore
-        };
-
-        const collection = collectionFactory()(this._fullyRenderedDocs, {
-            isDefaultTemplateDoc: true,
-            title: 'title',
-            backgroundColor: 'gray',
-            x: 200,
-            y: 200,
-            _width: 4000,
-            _height: 4000,
-        });
-
-        this.applyLayout(collection, this._fullyRenderedDocs);
-
-        this._renderedDocCollection = collection;
-
-        this._docsRendering = false;
-
-        this.forceUpdate();
-    };
-
-    layoutPreviewContents = action(() => {
-        return this._docsRendering ? (
-            <div className="docCreatorMenu-layout-preview-window-wrapper loading">
-                <div className="loading-spinner">
-                    <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
-                </div>
-            </div>
-        ) : !this._renderedDocCollection ? null : (
-            <div className="docCreatorMenu-layout-preview-window-wrapper">
-                <DocumentView
-                    Document={this._renderedDocCollection}
-                    isContentActive={emptyFunction}
-                    addDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    removeDocument={returnFalse}
-                    PanelWidth={() => this._menuDimensions.width - 80}
-                    PanelHeight={() => this._menuDimensions.height - 105}
-                    ScreenToLocalTransform={() => new Transform(-this._pageX - 5, -this._pageY - 35, 1)}
-                    renderDepth={5}
-                    whenChildContentsActiveChanged={emptyFunction}
-                    focus={emptyFunction}
-                    styleProvider={DefaultStyleProvider}
-                    addDocTab={this._props.addDocTab}
-                    pinToPres={() => undefined}
-                    childFilters={returnEmptyFilter}
-                    childFiltersByRanges={returnEmptyFilter}
-                    searchFilterDocs={returnEmptyDoclist}
-                    fitContentsToBox={returnFalse}
-                    fitWidth={returnFalse}
-                    hideDecorations={true}
+    @computed 
+    get templatesView() { return (
+        <div className='docCreatorMenu-templates-view'>
+            <div className="docCreatorMenu-templates-displays">
+                <TemplatePreviewGrid 
+                    title={'Suggested Templates'} 
+                    menu={this} 
+                    loading={this._GPTLoading}
+                    optionsButtonOpts={this.optionsButtonOpts}
+                    templates={this._suggestedTemplates}
                 />
-            </div>
-        );
-    });
-
-    get optionsMenuContents() {
-        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;
-                                this.updateRenderedDocCollection();
-                            });
-                        })
-                    }>
-                    {option}
-                </div>
-            );
-        };
-
-        const selectionBox = (width: number, height: number, icon: string, specClass?: string, options?: JSX.Element[], manual?: boolean): JSX.Element => {
-            return (
-                <div className="docCreatorMenu-option-container">
-                    <div className={`docCreatorMenu-option-title config ${specClass}`} style={{ width: width * 0.4, height: height }}>
-                        <FontAwesomeIcon icon={icon as IconProp} />
+                <div className="docCreatorMenu-GPT-options">
+                    <div className="docCreatorMenu-GPT-options-container">
+                        <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generatePresetTemplates}/>
                     </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 repeatOptions = [0, 1, 2, 3, 4, 5];
-
-        return (
-            <div className="docCreatorMenu-menu-container">
-                <div className="docCreatorMenu-option-container layout">
-                    <div className="docCreatorMenu-dropdown-hoverable">
-                        <div className="docCreatorMenu-option-title">{this._layout.type ? this._layout.type.toUpperCase() : 'Choose Layout'}</div>
-                        <div className="docCreatorMenu-dropdown-content">
-                            {layoutOption(LayoutType.FREEFORM, undefined, () => {
-                                if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length));
-                            })}
-                            {layoutOption(LayoutType.CAROUSEL)}
-                            {layoutOption(LayoutType.CAROUSEL3D)}
-                            {layoutOption(LayoutType.MASONRY)}
-                        </div>
-                    </div>
-                </div>
-                {this._layout.type ? this.layoutConfigOptions : null}
-                {this.layoutPreviewContents()}
-                {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 || !this._selectedTemplate.getRenderedDoc()) return;
-                                    const layout: DataVizTemplateLayout = {
-                                        template: this._selectedTemplate.getRenderedDoc()!,
-                                        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;
-                                    this.addRenderedCollectionToMainview();
-                                }, 'make docs')
-                            )
-                        }>
-                        <FontAwesomeIcon icon="plus" />
-                    </button>
                 </div>
             </div>
-        );
-    }
-
-    @observable private _newCondCache: Record<string, Conditional> = {};
-
-    getParams = (title: string, parameters?: Conditional): Conditional => {
-        if (parameters) return parameters;
-
-        if (!this._newCondCache[title]) {
-            this._newCondCache[title] = observable<Conditional>({
-                field: title,
-                operator: '=',
-                condition: '',
-                target: '',
-                attribute: '',
-                value: ''
-            });
-        }
-        return this._newCondCache[title];
-    };
-
-    get dashboardContents() {
-        const contentFieldTitles = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA).map(field => field.title).concat('Template');
-
-        const conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => {
-
-            var params: Conditional = this.getParams(title, parameters);
-
-            return (
-            <div className='form'>
-                <div className='form-row'>
-                    <div className='form-row-plain-text'>If</div>
-                    <div className='form-row-plain-text'>{title}</div>
-                    <div className="operator-options-dropdown">
-                        <span className="operator-dropdown-current">{params.operator ?? '='}</span>
-                        <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '='}}>{'='}</div>
-                        <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '>'}}>{'>'}</div>
-                        <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '<'}}>{'<'}</div>
-                        <div className='operator-dropdown-option' onPointerDown={() => {params.operator = 'contains'}}>{'has'}</div>
-                    </div>
-                    <input
-                        className="form-row-textarea"
-                        onChange={e => runInAction(() => params.condition = e.target.value)}
-                        placeholder='value'
-                        value={params.condition}
-                    />
-                    <div className='form-row-plain-text'>then</div>
-                    <div className="operator-options-dropdown">
-                        <span className="operator-dropdown-current">{params.target}</span>
-                        {contentFieldTitles.map(fieldTitle => 
-                            <div className='operator-dropdown-option' onPointerDown={() => {params.target = fieldTitle}}>{fieldTitle === title ? 'Own' : fieldTitle}</div>
-                        )}
-                    </div>
-                    <input
-                        className="form-row-textarea"
-                        onChange={e => runInAction(() => params.attribute = e.target.value)}
-                        placeholder='attribute'
-                        value={params.attribute}
-                    />
-                    <div className='form-row-plain-text'>{'becomes'}</div>
-                    <input
-                        className="form-row-textarea"
-                        onChange={e => runInAction(() => params.value = e.target.value)}
-                        placeholder='value'
-                        value={params.value}
-                    />
-                </div>
-                {empty ? 
-                <DocCreatorMenuButton icon={'plus'} styles={'float-right border'} function={() => {
-                    this._newCondCache[title] = observable<Conditional>({
-                        field: title,
-                        operator: '=',
-                        condition: '',
-                        target: '',
-                        attribute: '',
-                        value: ''
-                    });
-                    this.templateManager.addFieldCondition(title, params);
-                }}/>
-                :
-                <DocCreatorMenuButton icon={'minus'} styles={'float-right border'} function={() => this.templateManager.removeFieldCondition(title, params)}/>
-                }
-            </div>
-            )
-        }
-
-        const fieldPanel = (field: Col, id: number) => (
-            <div className="field-panel" key={id}>
-                <div className="top-bar" onPointerDown={e => this.setUpButtonClick(e, runInAction(() => () => {
-                    if (this._collapsedCols.includes(field.title)) {
-                        this._collapsedCols = this._collapsedCols.filter(col => col !== field.title);
-                    } else {
-                        this._collapsedCols.push(field.title);
-                    }
-                }))}>
-                    <span className="field-title">{`${field.title} Field`}</span>
-                    <DocCreatorMenuButton icon={'minus'} styles={'no-margin absolute-right'} function={() => this.removeField(field)}/>
-                </div>
-                { this._collapsedCols.includes(field.title) ? null :
-                    <>
-                    <div className="opts-bar">
-                        <div className="opt-box">
-                            <div className="top-bar"> Title </div>
-                            <textarea className="content" style={{ width: '100%', height: 'calc(100% - 20px)' }} value={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' 
-                                    : field.type === TemplateFieldType.DATA ? 'Data 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>
-                                    <input className="bubble" type="radio" name="type" onClick={() => this.setColType(field, TemplateFieldType.DATA)} />
-                                    <div className="text">Data</div>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                    { field.type === TemplateFieldType.DATA ? null :
-                        (<> 
-                            <div className="sizes-box">
-                                <div className="top-bar"> Valid Sizes </div>
-                                <div className="content">
-                                    <div className="bubbles">
-                                        {Object.values(TemplateFieldSize).map(size => (
-                                            <div key={field + size}>
-                                                <input className="bubble" type="checkbox" name="type" checked={field.sizes.includes(size)} onChange={e => this.modifyColSizes(field, size, e.target.checked)} />
-                                                <div className="text">{size}</div>
-                                            </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 className="conditionals-section">
-                        <span className="conditionals-title">Conditional Logic</span>
-                        {conditionForm(field.title, undefined, true)}
-                        {this.templateManager.conditionalFieldLogic[field.title]?.map(condition => conditionForm(condition.field, condition))}
-                    </div>
-                    </>
-                }
-            </div>
-        );
-
-        return (
-            <div className="docCreatorMenu-dashboard-view">
-                <div className="topbar">
-                    <DocCreatorMenuButton icon={'plus'} function={this.addField}/>
-                    <DocCreatorMenuButton icon={'arrow-left'} styles={'float-right'} function={() => runInAction(() => (this._menuContent = 'templates'))}/>
-                </div>
-                <div className="panels-container">{this.fieldsInfos.map((field, i) => fieldPanel(field, i))}</div>
-            </div>
-        );
-    }
-
-    @computed get editingView() { return <TemplateEditingWindow template={this._currEditingTemplate as Template} menu={this} /> }
+        </div>
+    )};
     
     private optionsButtonOpts: [IconProp, () => any] = ['gear', () => (this._menuContent = 'dashboard')];
 
     get renderSelectedViewType() {
         switch (this._menuContent) {
-            case 'templates': 
-                return (
-                    <div className='docCreatorMenu-templates-view'>
-                        <div className="docCreatorMenu-templates-displays">
-                            <TemplatePreviewGrid 
-                                title={'Suggested Templates'} 
-                                menu={this} 
-                                loading={this._GPTLoading}
-                                optionsButtonOpts={this.optionsButtonOpts}
-                                templates={this._suggestedTemplates}
-                            />
-                            <div className="docCreatorMenu-GPT-options">
-                                <div className="docCreatorMenu-GPT-options-container">
-                                    <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generatePresetTemplates}/>
-                                </div>
-                            </div>
-                        </div>
-                    </div>
-                )
-            case 'templateEditing':
-                return this.editingView
-            case 'options':   return this.optionsMenuContents;
-            case 'dashboard': return this.dashboardContents;
+            case 'templates':       return this.templatesView;
+            case 'templateEditing': return <TemplateEditingWindow template={this._currEditingTemplate as Template} menu={this} />;
+            case 'renderPreview':   return <TemplatesRenderPreviewWindow menu={this}/>;
+            case 'dashboard':       return <TemplateMenuFieldOptions menu={this} templateManager={this.templateManager}/>;
         } // prettier-ignore
         return undefined;
     }
@@ -1144,10 +606,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
         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));
-        };
+        const onOptionsSelected = () => (this._menuContent = 'renderPreview');
 
         return (
             <div className="docCreatorMenu">
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ExpandedTemplatePreview.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ExpandedTemplatePreview.tsx
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/FieldOptionsScreen.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/FieldOptionsScreen.tsx
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index cb87e9b47..3eaed79b6 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -227,9 +227,9 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
                      : null}
                     <div className="right-buttons-panel">
                         <DocCreatorMenuButton icon={'minimize'} function={() => {
-                                if (this._props.template === this._props.menu._selectedTemplate) {
-                                    this._props.menu.updateRenderedPreviewCollection(this._props.template);
-                                }
+                                // if (this._props.template === this._props.menu._selectedTemplate) {
+                                //     this._props.menu.updateRenderedPreviewCollection(this._props.template);
+                                // }
                                 this._props.menu.setExpandedView(undefined);
                             }}/>
                         <DocCreatorMenuButton icon={'lightbulb'} function={() => this.setVariationTab(!this._variationsTabOpen)}/>
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx
new file mode 100644
index 000000000..beda45ac3
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx
@@ -0,0 +1,193 @@
+import { makeObservable, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { ObservableReactComponent } from "../../../../ObservableReactComponent";
+import { Col, DocCreatorMenu } from "../DocCreatorMenu";
+import React from "react";
+import { Conditional, TemplateManager } from "../Backend/TemplateManager";
+import { TemplateFieldType, TemplateFieldSize } from "../TemplateBackend";
+import { DocCreatorMenuButton } from "./DocCreatorMenuButton";
+
+interface TemplateMenuFieldOptionsProps {
+    menu: DocCreatorMenu;
+    templateManager: TemplateManager;
+}
+
+@observer
+export class TemplateMenuFieldOptions extends ObservableReactComponent<TemplateMenuFieldOptionsProps> {
+
+    @observable _collapsedCols: String[] = []; //any columns whose options panels are hidden
+
+    constructor(props: TemplateMenuFieldOptionsProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    @observable private _newCondCache: Record<string, Conditional> = {};
+
+    getParams = (title: string, parameters?: Conditional): Conditional => {
+        if (parameters) return parameters;
+
+        if (!this._newCondCache[title]) {
+            this._newCondCache[title] = observable<Conditional>({
+                field: title,
+                operator: '=',
+                condition: '',
+                target: 'Own',
+                attribute: '',
+                value: ''
+            });
+        }
+        return this._newCondCache[title];
+    };
+
+    conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => {
+
+        const contentFieldTitles = this._props.menu.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA).map(field => field.title).concat('Template');
+        var params: Conditional = this.getParams(title, parameters);
+
+        return (
+        <div className='form'>
+            <div className='form-row'>
+                <div className='form-row-plain-text'>If</div>
+                <div className='form-row-plain-text'>{title}</div>
+                <div className="operator-options-dropdown">
+                    <span className="operator-dropdown-current">{params.operator ?? '='}</span>
+                    <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '='}}>{'='}</div>
+                </div>
+                <input
+                    className="form-row-textarea"
+                    onChange={e => runInAction(() => params.condition = e.target.value)}
+                    placeholder='value'
+                    value={params.condition}
+                />
+                <div className='form-row-plain-text'>then</div>
+                <div className="operator-options-dropdown">
+                    <span className="operator-dropdown-current">{params.target ?? 'Own'}</span>
+                    {contentFieldTitles.map(fieldTitle => 
+                        <div className='operator-dropdown-option' onPointerDown={() => {params.target = fieldTitle}}>{fieldTitle === title ? 'Own' : fieldTitle}</div>
+                    )}
+                </div>
+                <input
+                    className="form-row-textarea"
+                    onChange={e => runInAction(() => params.attribute = e.target.value)}
+                    placeholder='attribute'
+                    value={params.attribute}
+                />
+                <div className='form-row-plain-text'>{'becomes'}</div>
+                <input
+                    className="form-row-textarea"
+                    onChange={e => runInAction(() => params.value = e.target.value)}
+                    placeholder='value'
+                    value={params.value}
+                />
+            </div>
+            {empty ? 
+            <DocCreatorMenuButton icon={'plus'} styles={'float-right border'} function={() => {
+                this._newCondCache[title] = observable<Conditional>({
+                    field: title,
+                    operator: '=',
+                    condition: '',
+                    target: 'Own',
+                    attribute: '',
+                    value: ''
+                });
+                this._props.templateManager.addFieldCondition(title, params);
+            }}/>
+            :
+            <DocCreatorMenuButton icon={'minus'} styles={'float-right border'} function={() => this._props.templateManager.removeFieldCondition(title, params)}/>
+            }
+        </div>
+        )
+    }
+
+    fieldPanel = (field: Col, id: number) => (
+        <div className="field-panel" key={id}>
+            <div className="top-bar" onPointerDown={e => this._props.menu.setUpButtonClick(e, runInAction(() => () => {
+                if (this._collapsedCols.includes(field.title)) {
+                    this._collapsedCols = this._collapsedCols.filter(col => col !== field.title);
+                } else {
+                    this._collapsedCols.push(field.title);
+                }
+            }))}>
+                <span className="field-title">{`${field.title} Field`}</span>
+                <DocCreatorMenuButton icon={'minus'} styles={'no-margin absolute-right'} function={() => this._props.menu.removeField(field)}/>
+            </div>
+            { this._collapsedCols.includes(field.title) ? null :
+                <>
+                <div className="opts-bar">
+                    <div className="opt-box">
+                        <div className="top-bar"> Title </div>
+                        <textarea className="content" style={{ width: '100%', height: 'calc(100% - 20px)' }} value={field.title} placeholder={'Enter title'} onChange={e => this._props.menu.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' 
+                                : field.type === TemplateFieldType.DATA ? 'Data Field' 
+                                : ''
+                            }</span>
+                            <div className="bubbles">
+                                <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.TEXT)} />
+                                <div className="text">Text</div>
+                                <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.VISUAL)} />
+                                <div className="text">File</div>
+                                <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.DATA)} />
+                                <div className="text">Data</div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                { field.type === TemplateFieldType.DATA ? null :
+                    (<> 
+                        <div className="sizes-box">
+                            <div className="top-bar"> Valid Sizes </div>
+                            <div className="content">
+                                <div className="bubbles">
+                                    {Object.values(TemplateFieldSize).map(size => (
+                                        <div key={field + size}>
+                                            <input className="bubble" type="checkbox" name="type" checked={field.sizes.includes(size)} onChange={e => this._props.menu.modifyColSizes(field, size, e.target.checked)} />
+                                            <div className="text">{size}</div>
+                                        </div>
+                                    ))}
+                                </div>
+                            </div>
+                        </div>
+                        <div className="desc-box">
+                            <div className="top-bar"> Prompt </div>
+                            <textarea
+                                className="content"
+                                onChange={e => this._props.menu.setColDesc(field, e.target.value)}
+                                defaultValue={field.desc === this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc}
+                                placeholder={this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description/prompt to help with template generation.'}
+                            />
+                        </div> 
+                    </>)
+                }
+                <div className="conditionals-section">
+                    <span className="conditionals-title">Conditional Logic</span>
+                    {this.conditionForm(field.title, undefined, true)}
+                    {this._props.templateManager.conditionalFieldLogic[field.title]?.map(condition => this.conditionForm(condition.field, condition))}
+                </div>
+                </>
+            }
+        </div>
+    );
+
+
+
+    render() {
+        return (
+            <div className="docCreatorMenu-dashboard-view">
+                <div className="topbar">
+                    <DocCreatorMenuButton icon={'plus'} function={this._props.menu.addField}/>
+                    <DocCreatorMenuButton icon={'arrow-left'} styles={'float-right'} function={() => runInAction(() => (this._props.menu._menuContent = 'templates'))}/>
+                </div>
+                <div className="panels-container">{this._props.menu.fieldsInfos.map((field, i) => this.fieldPanel(field, i))}</div>
+            </div>
+        );
+    }
+
+
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx
new file mode 100644
index 000000000..219152549
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx
@@ -0,0 +1,346 @@
+import { action, computed, makeObservable, observable, runInAction } from "mobx";
+import { observer } from "mobx-react";
+import { ObservableReactComponent } from "../../../../ObservableReactComponent";
+import { DataVizTemplateLayout, DocCreatorMenu, LayoutType } from "../DocCreatorMenu";
+import React from "react";
+import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { setupMoveUpEvents, returnFalse, returnEmptyFilter } from "../../../../../../ClientUtils";
+import { emptyFunction } from "../../../../../../Utils";
+import { undoable } from "../../../../../util/UndoManager";
+import ReactLoading from "react-loading";
+import { Doc, NumListCast, returnEmptyDoclist } from "../../../../../../fields/Doc";
+import { StrCast } from "../../../../../../fields/Types";
+import { DefaultStyleProvider } from "../../../../StyleProvider";
+import { DocumentView } from "../../../DocumentView";
+import { Transform } from "../../../../../util/Transform";
+import { Docs, DocumentOptions } from "../../../../../documents/Documents";
+import { Template } from "../Template";
+
+interface TemplatesRenderPreviewWindowProps {
+    menu: DocCreatorMenu;
+}
+
+@observer
+export class TemplatesRenderPreviewWindow extends ObservableReactComponent<TemplatesRenderPreviewWindowProps> {
+
+    @observable private _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 0, repeat: 0 };
+
+    @observable private renderedDocs: Doc[] = [];
+    @observable private renderedDocCollection: Doc | undefined = undefined;
+
+    @observable private loading: boolean = false;
+
+    constructor(props: TemplatesRenderPreviewWindowProps) {
+        super(props);
+        makeObservable(this);
+        this.updateRenderedPreviewCollection();
+    }
+
+    @computed get canMakeDocs() {
+        return this._props.menu._selectedTemplate !== undefined && this._layout !== undefined;
+    }
+
+    @computed get docsToRender() {
+        if (this._props.menu.DEBUG_MODE) {
+            return [1, 2, 3, 4];
+        } else {
+            return NumListCast(this._props.menu._dataViz?.layoutDoc.dataViz_selectedRows);
+        }
+    }
+
+    @computed get rowsCount() {
+        switch (this._layout.type) {
+            case LayoutType.FREEFORM:
+                return Math.ceil(this.docsToRender.length / (this._layout.columns ?? 1)) ?? 0;
+            case LayoutType.CAROUSEL3D:
+                return 1.8;
+            default:
+                return 1;
+        }
+    }
+
+    @computed get columnsCount() {
+        switch (this._layout.type) {
+            case LayoutType.FREEFORM:
+                return this._layout.columns ?? 0;
+            case LayoutType.CAROUSEL3D:
+                return 3;
+            default:
+                return 1;
+        }
+    }
+
+    @action updateRenderedPreviewCollection = async () => {
+        this.loading = true;
+        this.renderedDocs = await this._props.menu.createDocsForPreview();
+        this.updateRenderedDocCollection();
+    };
+
+    /**
+     * Updates the preview that shows how all docs will be rendered in the chosen collection type.
+    @type the type of collection the docs should render to (ie. freeform, carousel, card)
+     */
+    updateRenderedDocCollection = () => {
+        if (!this.renderedDocs) return;
+
+        const collectionFactory = (): ((docs: Doc[], options: DocumentOptions) => Doc) => {
+            switch (this._layout.type) {
+                case LayoutType.CAROUSEL3D: return Docs.Create.Carousel3DDocument;
+                case LayoutType.FREEFORM:   return Docs.Create.FreeformDocument;
+                case LayoutType.CARD:       return Docs.Create.CardDeckDocument;
+                case LayoutType.MASONRY:    return Docs.Create.MasonryDocument;
+                case LayoutType.CAROUSEL:   return Docs.Create.CarouselDocument;
+                default:                    return Docs.Create.FreeformDocument;
+            } // prettier-ignore
+        };
+
+        const collection = collectionFactory()(this.renderedDocs, {
+            isDefaultTemplateDoc: true,
+            title: 'title',
+            backgroundColor: 'gray',
+            x: 200,
+            y: 200,
+            _width: 4000,
+            _height: 4000,
+        });
+
+        this.applyLayout(collection, this.renderedDocs);
+
+        this.renderedDocCollection = collection;
+
+        this.loading = false;
+
+        this.forceUpdate();
+    };
+
+    @action updateMargin = (input: string, xOrY: 'x' | 'y') => {
+        this._layout[`${xOrY}Margin`] = Number(input);
+        setTimeout(() => {
+            if (!this.renderedDocCollection || !this.renderedDocs) return;
+            this.applyLayout(this.renderedDocCollection, this.renderedDocs);
+        });
+    };
+
+    @action updateColumns = (input: string) => {
+        this._layout.columns = Number(input);
+        this.updateRenderedDocCollection();
+    };
+
+    applyLayout = (collection: Doc, docs: Doc[]) => {
+        const { horizontalSpan, verticalSpan } = this.previewInfo;
+        collection._height = verticalSpan;
+        collection._width = horizontalSpan;
+
+        const columns: number = this._layout.columns ?? this.columnsCount;
+        const xGap: number = this._layout.xMargin;
+        const yGap: number = this._layout.yMargin;
+        const startX: number = -Number(collection._width) / 2;
+        const startY: number = -Number(collection._height) / 2;
+        const docHeight: number = Number(docs[0]._height);
+        const docWidth: number = Number(docs[0]._width);
+
+        if (columns === 0 || docs.length === 0) {
+            return;
+        }
+
+        let i: number = 0;
+        let docsChanged: number = 0;
+        let curX: number = startX;
+        let curY: number = startY;
+
+        while (docsChanged < docs.length) {
+            while (i < columns && docsChanged < docs.length) {
+                docs[docsChanged].x = curX;
+                docs[docsChanged].y = curY;
+                curX += docWidth + xGap;
+                ++docsChanged;
+                ++i;
+            }
+            i = 0;
+            curX = startX;
+            curY += docHeight + yGap;
+        }
+    };
+
+    @computed
+    get previewInfo() {
+        const docHeight: number = Number(this.renderedDocs[0]._height);
+        const docWidth: number = Number(this.renderedDocs[0]._width);
+        const layout = this._layout;
+        return {
+            docHeight: docHeight,
+            docWidth: docWidth,
+            horizontalSpan: (docWidth + layout.xMargin) * this.columnsCount - layout.xMargin,
+            verticalSpan: (docHeight + layout.yMargin) * this.rowsCount - layout.yMargin,
+        };
+    }
+
+    get layoutConfigOptions() {
+        const optionInput = (icon: string, func: (input: string) => void, 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 IconProp} />
+                    </div>
+                    <input defaultValue={def} onInput={e => func(e.currentTarget.value)} className="docCreatorMenu-input config layout-config" />
+                </div>
+            );
+        };
+
+        switch (this._layout.type) {
+            case LayoutType.FREEFORM:
+                return (
+                    <div className="docCreatorMenu-configuration-bar">
+                        {optionInput('arrows-up-down', (input: string) => this.updateMargin(input, 'y'), this._layout.xMargin, '2')}
+                        {optionInput('arrows-left-right', (input: string) => this.updateMargin(input, 'x'), this._layout.xMargin, '3')}
+                        {optionInput('table-columns', this.updateColumns, this._layout.columns, '4', true)}
+                    </div>
+                );
+            default:
+                break;
+        }
+    }
+
+    layoutPreviewContents = action(() => {
+        return this.loading ? (
+            <div className="docCreatorMenu-layout-preview-window-wrapper loading">
+                <div className="loading-spinner">
+                    <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
+                </div>
+            </div>
+        ) : !this.renderedDocCollection ? null : (
+            <div className="docCreatorMenu-layout-preview-window-wrapper">
+                <DocumentView
+                    Document={this.renderedDocCollection}
+                    isContentActive={emptyFunction}
+                    addDocument={returnFalse}
+                    moveDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    PanelWidth={() => this._props.menu._menuDimensions.width - 80}
+                    PanelHeight={() => this._props.menu._menuDimensions.height - 105}
+                    ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)}
+                    renderDepth={5}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    focus={emptyFunction}
+                    styleProvider={DefaultStyleProvider}
+                    addDocTab={this._props.menu._props.addDocTab}
+                    pinToPres={() => undefined}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    fitContentsToBox={returnFalse}
+                    fitWidth={returnFalse}
+                    hideDecorations={true}
+                />
+            </div>
+        );
+    });
+
+    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>
+        );
+    };
+
+    layoutOption = (option: LayoutType, optStyle?: object, specialFunc?: () => void) => {
+        return (
+            <div
+                className="docCreatorMenu-dropdown-option"
+                style={optStyle}
+                onPointerDown={e =>
+                    this._props.menu.setUpButtonClick(e, () => {
+                        specialFunc?.();
+                        runInAction(() => {
+                            this._layout.type = option;
+                            this.updateRenderedDocCollection();
+                        });
+                    })
+                }>
+                {option}
+            </div>
+        );
+    };
+
+    get optionsMenuContents() {
+
+        const repeatOptions = [0, 1, 2, 3, 4, 5];
+
+        return (
+            <div className="docCreatorMenu-menu-container">
+                <div className="docCreatorMenu-option-container layout">
+                    <div className="docCreatorMenu-dropdown-hoverable">
+                        <div className="docCreatorMenu-option-title">{this._layout.type ? this._layout.type.toUpperCase() : 'Choose Layout'}</div>
+                        <div className="docCreatorMenu-dropdown-content">
+                            {this.layoutOption(LayoutType.FREEFORM, undefined, () => {
+                                if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length));
+                            })}
+                            {this.layoutOption(LayoutType.CAROUSEL)}
+                            {this.layoutOption(LayoutType.CAROUSEL3D)}
+                            {this.layoutOption(LayoutType.MASONRY)}
+                        </div>
+                    </div>
+                </div>
+                {this._layout.type ? this.layoutConfigOptions : null}
+                {this.layoutPreviewContents()}
+                {this.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();
+                                    //previous implementation deprecated; return later to add or scrap
+                                    return;
+                                }, 'save layout')
+                            )
+                        }>
+                        <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();
+                                    this.renderedDocCollection && this._props.menu.addRenderedCollectionToMainview(this.renderedDocCollection);
+                                }, 'make docs')
+                            )
+                        }>
+                        <FontAwesomeIcon icon="plus" />
+                    </button>
+                </div>
+            </div>
+        );
+    }
+
+    render() { return this.optionsMenuContents }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
index 112f37cc6..2a8e4f09b 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
@@ -29,6 +29,7 @@ export abstract class StaticContentField extends TemplateField {
 export class ImageTemplateField extends StaticContentField {
     setContent(url: string, type?: ViewType) {
         this.setDataContent(ViewType.IMG, 'data', new ImageField(url), url, type);
+        this._renderDoc!['backgroundColor'] = 'white';
     }
 
     initRenderDoc(settings: FieldSettings) {
@@ -39,8 +40,11 @@ export class ImageTemplateField extends StaticContentField {
     }
 
     updateDocSetting(setting: string, newVal: string) {
-        if (setting === 'backgroundColor') return;
-        super.updateDocSetting(setting, newVal);
+        if (this._renderDoc) this._renderDoc[setting] = newVal;
+        if (setting !== 'backgroundColor') {
+            const settings: {[s: string]: string } = {[setting]: newVal}
+            Object.assign(this.settings.opts, settings);
+        }
     }
 }
 
diff --git a/src/server/ApiManagers/FireflyManager.ts b/src/server/ApiManagers/FireflyManager.ts
index a272494b8..955504c23 100644
--- a/src/server/ApiManagers/FireflyManager.ts
+++ b/src/server/ApiManagers/FireflyManager.ts
@@ -37,8 +37,8 @@ export default class FireflyManager extends ApiManager {
                     body: JSON.stringify({
                         prompt,
                         numVariations: variations,
-                        detailLevel: 'preview', 
-                        modelVersion: 'image3_fast',
+                        detailLevel: 'high', 
+                        modelVersion: 'image3',
                         size: { width, height },
                         structure: !structureUrl
                             ? undefined
-- 
cgit v1.2.3-70-g09d2


From 312a07364ac8880e74e1b0a911d3e5b380886858 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Fri, 30 May 2025 04:07:22 -0400
Subject: linting

---
 .../DocCreatorMenu/Backend/TemplateManager.ts      |  2 +-
 .../DocCreatorMenu/Backend/TemplateMenuAIUtils.ts  |  6 +----
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 31 +++++++---------------
 .../DocCreatorMenu/Menu/ConditionalsTextarea.tsx   |  2 +-
 .../DocCreatorMenu/Menu/DocCreatorMenuButton.tsx   |  3 ++-
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  | 21 +++++++--------
 .../Menu/TemplateMenuFieldOptions.tsx              | 14 +++++-----
 .../DocCreatorMenu/Menu/TemplatePreviewBox.tsx     |  9 +++----
 .../DocCreatorMenu/Menu/TemplatePreviewGrid.tsx    | 15 +++++------
 .../Menu/TemplateRenderPreviewWindow.tsx           |  3 +--
 .../nodes/DataVizBox/DocCreatorMenu/Template.ts    |  9 -------
 .../DocCreatorMenu/TemplateFieldTypes/DataField.ts |  3 +--
 .../TemplateFieldTypes/DynamicField.ts             |  9 ++-----
 .../TemplateFieldTypes/StaticContentField.ts       |  1 -
 .../TemplateFieldTypes/TemplateField.ts            |  3 +--
 15 files changed, 45 insertions(+), 86 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
index 6fcca7e30..78235d000 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
@@ -2,7 +2,7 @@ import { action, makeAutoObservable } from 'mobx';
 import { Col } from '../DocCreatorMenu';
 import { FieldSettings, TemplateField } from '../TemplateFieldTypes/TemplateField';
 import { Template } from '../Template';
-import { Doc, NumListCast } from '../../../../../../fields/Doc';
+import { NumListCast } from '../../../../../../fields/Doc';
 import { DataVizBox } from '../../DataVizBox';
 import { TemplateFieldType } from '../TemplateBackend';
 import { TemplateMenuAIUtils } from './TemplateMenuAIUtils';
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts
index 9bc2bfce2..162b7a1b1 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts
@@ -1,5 +1,3 @@
-import { action } from "mobx";
-import { Upload } from "openai/resources";
 import { ClientUtils } from "../../../../../../ClientUtils";
 import { Networking } from "../../../../../Network";
 import { gptImageCall, gptAPICall, GPTCallType } from "../../../../../apis/gpt/GPT";
@@ -7,9 +5,6 @@ import { Col } from "../DocCreatorMenu";
 import { TemplateFieldSize, TemplateFieldType } from "../TemplateBackend";
 import { TemplateField, ViewType } from "../TemplateFieldTypes/TemplateField";
 import { Template } from "../Template";
-import { Doc } from "../../../../../../fields/Doc";
-import { DrawingFillHandler } from "../../../../smartdraw/DrawingFillHandler";
-import { CollectionFreeFormView } from "../../../../collections/collectionFreeForm";
 
 export class TemplateMenuAIUtils {
 
@@ -30,6 +25,7 @@ export class TemplateMenuAIUtils {
     public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number): Promise<boolean> => {
         const generateAndLoadImage = async (id: number, prompt: string) => {
             const url = await this.generateGPTImage(prompt);
+            // eslint-disable-next-line
             var field: TemplateField = template.getFieldByID(id);
 
             field.setContent(url ?? '', ViewType.IMG);
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index 9a84e69a9..ebfa3fc65 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -1,44 +1,31 @@
-import { Colors } from '@dash/components';
+
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
-import { IDisposer } from 'mobx-utils';
 import * as React from 'react';
-import ReactLoading from 'react-loading';
-import { ClientUtils, returnEmptyFilter, returnFalse, setupMoveUpEvents } from '../../../../../ClientUtils';
+import { returnFalse, setupMoveUpEvents } from '../../../../../ClientUtils';
 import { emptyFunction } from '../../../../../Utils';
-import { Doc, NumListCast, StrListCast, returnEmptyDoclist } from '../../../../../fields/Doc';
+import { Doc, StrListCast } from '../../../../../fields/Doc';
 import { Id } from '../../../../../fields/FieldSymbols';
-import { ImageCast, StrCast } from '../../../../../fields/Types';
-import { ImageField } from '../../../../../fields/URLField';
-import { Upload } from '../../../../../server/SharedMediaTypes';
-import { Networking } from '../../../../Network';
-import { GPTCallType, gptAPICall, gptImageCall } from '../../../../apis/gpt/GPT';
-import { Docs, DocumentOptions } from '../../../../documents/Documents';
+import { GPTCallType, gptAPICall } from '../../../../apis/gpt/GPT';
 import { DragManager } from '../../../../util/DragManager';
 import { SnappingManager } from '../../../../util/SnappingManager';
-import { Transform } from '../../../../util/Transform';
 import { UndoManager, undoable } from '../../../../util/UndoManager';
 import { ObservableReactComponent } from '../../../ObservableReactComponent';
-import { DefaultStyleProvider } from '../../../StyleProvider';
 import { CollectionFreeFormView } from '../../../collections/collectionFreeForm/CollectionFreeFormView';
-import { DocumentView, DocumentViewInternal } from '../../DocumentView';
+import { DocumentView } from '../../DocumentView';
 import { OpenWhere } from '../../OpenWhere';
 import { DataVizBox } from '../DataVizBox';
 import './DocCreatorMenu.scss';
-import { TemplateField, ViewType } from './TemplateFieldTypes/TemplateField';
+import { ViewType } from './TemplateFieldTypes/TemplateField';
 import { Template } from './Template';
 import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend';
-import { Conditional, TemplateManager } from './Backend/TemplateManager';
-import { DrawingFillHandler } from '../../../smartdraw/DrawingFillHandler';
-import { CgPathIntersect } from 'react-icons/cg';
-import { StaticContentField } from './TemplateFieldTypes/StaticContentField';
+import { TemplateManager } from './Backend/TemplateManager';
 import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils'
 import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid';
 import { FireflyStructureOptions, TemplateEditingWindow } from './Menu/TemplateEditingWindow';
 import { DocCreatorMenuButton } from './Menu/DocCreatorMenuButton';
-import { ConditionalsTextArea } from './Menu/ConditionalsTextarea';
 import { TemplatesRenderPreviewWindow } from './Menu/TemplateRenderPreviewWindow';
 import { TemplateMenuFieldOptions } from './Menu/TemplateMenuFieldOptions';
 
@@ -500,7 +487,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     };
 
     generateVariations = async (onDoc: Doc, prompt: string, options: FireflyStructureOptions): Promise<string[]> => {
-        const { numVariations, temperature, useStyleRef } = options;
+        // const { numVariations, temperature, useStyleRef } = options;
         this.variations = [];
         const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
 
@@ -509,7 +496,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         clone.x = 10000;
         clone.y = 10000;
 
-        await DrawingFillHandler.drawingToImage(clone, 100 - temperature, prompt, useStyleRef ? clone : undefined, this, numVariations)
+        // await DrawingFillHandler.drawingToImage(clone, 100 - temperature, prompt, useStyleRef ? clone : undefined, this, numVariations)
 
         return this.variations;
     }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ConditionalsTextarea.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ConditionalsTextarea.tsx
index 2ca0bde3f..89c2e44ff 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ConditionalsTextarea.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/ConditionalsTextarea.tsx
@@ -51,7 +51,7 @@ export class ConditionalsTextArea extends ObservableReactComponent<ConditionalsT
                     value={this.props.conditional[this.props.property] ?? ''}
                     onChange={e => {
                         runInAction(() => {
-                        this.props.conditional[this.props.property] = e.target.value as any;
+                        this.props.conditional[this.props.property] = e.target.value as "=" | ">" | "<" | "contains";
                         });
                         this.updateInputWidth();
                     }}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/DocCreatorMenuButton.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/DocCreatorMenuButton.tsx
index 1d8139d40..48d2de4de 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/DocCreatorMenuButton.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/DocCreatorMenuButton.tsx
@@ -9,13 +9,14 @@ import { observer } from "mobx-react";
 
 interface DocCreatorMenuButtonProps {
     icon: IconProp;
+    // eslint-disable-next-line
     function: () => any;
     styles?: string;
 }
 
 @observer
 export class DocCreatorMenuButton extends ObservableReactComponent<DocCreatorMenuButtonProps> {
-
+    // eslint-disable-next-line
     setupButtonClick = (e: React.PointerEvent, func: (...args: any) => void) => {
         setupMoveUpEvents(
             this,
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index 3eaed79b6..b50fff9e0 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -1,7 +1,6 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, makeAutoObservable, makeObservable, observable, reaction, runInAction } from "mobx";
+import { action, makeObservable, observable, reaction, runInAction } from "mobx";
 import React from "react";
-import { returnFalse, returnEmptyFilter, returnTrue } from "../../../../../../ClientUtils";
+import { returnFalse, returnEmptyFilter } from "../../../../../../ClientUtils";
 import { emptyFunction } from "../../../../../../Utils";
 import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc";
 import { DefaultStyleProvider } from "../../../../StyleProvider";
@@ -11,14 +10,10 @@ import { TemplatePreviewGrid } from "./TemplatePreviewGrid";
 import { observer } from "mobx-react";
 import { Transform } from "../../../../../util/Transform";
 import { Template } from "../Template";
-import { TemplateMenuAIUtils } from "../Backend/TemplateMenuAIUtils";
 import { ObservableReactComponent } from "../../../../ObservableReactComponent";
 import { IDisposer } from "mobx-utils";
-import { ImageField } from "../../../../../../fields/URLField";
 import { DocCreatorMenuButton } from "./DocCreatorMenuButton";
-import { TbHistory } from "react-icons/tb";
 import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { docStyle } from "pdfjs-dist/types/web/ui_utils";
 
 export type FireflyStructureOptions = {
     numVariations: number;
@@ -56,7 +51,7 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
         cloneTemplate.setMatteBackground();
         const doc: Doc = cloneTemplate.getRenderedDoc()!;
         this._variationURLs = await this._props.menu.generateVariations(doc, this.prompt, this.fireflyOptions);
-        this._variationURLs.forEach(url => {
+        this._variationURLs.forEach(() => {
             const newTemplate: Template = this._props.template.clone(true);
             this._props.menu._variations.push(newTemplate);
         });
@@ -72,7 +67,9 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
         this.promptInput = node;
     }
 
+    // eslint-disable-next-line
     private optionsButtonOpts: [IconProp, () => any] = ['gear', () => {}];
+    // eslint-disable-next-line
     private previewBoxRightButtonOpts: [IconProp, () => any] = ['gear', () => this.forceUpdate()];
 
     render() {
@@ -93,7 +90,7 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
                         <textarea
                             className="docCreatorMenu-variation-prompt-input-textbox"
                             ref={this.setPromptInputRef}
-                            onChange={e => this.prompt = e.target.value}
+                            onChange={e => { this.prompt = e.target.value }}
                             onInput={() => {
                                 if (this.promptInput !== null) {
                                     this.promptInput.style.height = 'auto';
@@ -108,7 +105,7 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
                         <nav className="options‑menu">
                             <label className="menu‑item switch">
                                 <input type="checkbox" checked={this.fireflyOptions.useStyleRef} 
-                                    onChange={(e) => runInAction(() => this.fireflyOptions.useStyleRef = e.target.checked)}
+                                    onChange={(e) => runInAction(() => { this.fireflyOptions.useStyleRef = e.target.checked })}
                                 />
                                 <span className="slider round"></span>
                                 <span className="firefly-option-label">Use template as style guide</span>
@@ -119,7 +116,7 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
                                     min="1" 
                                     max="5" 
                                     value={this.fireflyOptions.numVariations} 
-                                    onChange={(e) => runInAction(() => this.fireflyOptions.numVariations = Number(e.target.value))}
+                                    onChange={(e) => runInAction(() => { this.fireflyOptions.numVariations = Number(e.target.value) })}
                                 />
                                 <span className="value" id="varVal">{this.fireflyOptions.numVariations}</span>
                             </div>
@@ -129,7 +126,7 @@ export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariat
                                     min="1" 
                                     max="100" 
                                     value={this.fireflyOptions.temperature} 
-                                    onChange={(e) => runInAction(() => this.fireflyOptions.temperature = Number(e.target.value))}
+                                    onChange={(e) => runInAction(() => { this.fireflyOptions.temperature = Number(e.target.value) })}
                                 />
                                 <span className="value" id="tempVal">{this.fireflyOptions.temperature}</span>
                             </div>
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx
index beda45ac3..a4da54392 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx
@@ -15,7 +15,7 @@ interface TemplateMenuFieldOptionsProps {
 @observer
 export class TemplateMenuFieldOptions extends ObservableReactComponent<TemplateMenuFieldOptionsProps> {
 
-    @observable _collapsedCols: String[] = []; //any columns whose options panels are hidden
+    @observable _collapsedCols: string[] = []; //any columns whose options panels are hidden
 
     constructor(props: TemplateMenuFieldOptionsProps) {
         super(props);
@@ -43,7 +43,7 @@ export class TemplateMenuFieldOptions extends ObservableReactComponent<TemplateM
     conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => {
 
         const contentFieldTitles = this._props.menu.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA).map(field => field.title).concat('Template');
-        var params: Conditional = this.getParams(title, parameters);
+        const params: Conditional = this.getParams(title, parameters);
 
         return (
         <div className='form'>
@@ -56,27 +56,27 @@ export class TemplateMenuFieldOptions extends ObservableReactComponent<TemplateM
                 </div>
                 <input
                     className="form-row-textarea"
-                    onChange={e => runInAction(() => params.condition = e.target.value)}
+                    onChange={e => runInAction(() => { params.condition = e.target.value })}
                     placeholder='value'
                     value={params.condition}
                 />
                 <div className='form-row-plain-text'>then</div>
                 <div className="operator-options-dropdown">
                     <span className="operator-dropdown-current">{params.target ?? 'Own'}</span>
-                    {contentFieldTitles.map(fieldTitle => 
-                        <div className='operator-dropdown-option' onPointerDown={() => {params.target = fieldTitle}}>{fieldTitle === title ? 'Own' : fieldTitle}</div>
+                    {contentFieldTitles.map((fieldTitle, i) => 
+                        <div className='operator-dropdown-option' key={i} onPointerDown={() => {params.target = fieldTitle}}>{fieldTitle === title ? 'Own' : fieldTitle}</div>
                     )}
                 </div>
                 <input
                     className="form-row-textarea"
-                    onChange={e => runInAction(() => params.attribute = e.target.value)}
+                    onChange={e => runInAction(() => { params.attribute = e.target.value })}
                     placeholder='attribute'
                     value={params.attribute}
                 />
                 <div className='form-row-plain-text'>{'becomes'}</div>
                 <input
                     className="form-row-textarea"
-                    onChange={e => runInAction(() => params.value = e.target.value)}
+                    onChange={e => runInAction(() => { params.value = e.target.value })}
                     placeholder='value'
                     value={params.value}
                 />
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
index de2f9e455..dc4c35789 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
@@ -1,7 +1,7 @@
 import { Colors } from "@dash/components/src";
 import { FontAwesomeIcon} from "@fortawesome/react-fontawesome";
 import { Template } from "../Template";
-import { makeObservable, observable, reaction, runInAction } from "mobx";
+import { makeObservable, observable } from "mobx";
 import React from "react";
 import { ObservableReactComponent } from "../../../../ObservableReactComponent";
 import { DocCreatorMenu } from "../DocCreatorMenu";
@@ -12,15 +12,12 @@ import { returnEmptyFilter, returnFalse } from "../../../../../../ClientUtils";
 import { Transform } from "../../../../../util/Transform";
 import { DefaultStyleProvider } from "../../../../StyleProvider";
 import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc";
-import { IDisposer } from "mobx-utils";
-import { ImageField } from "../../../../../../fields/URLField";
-import { ImageCast } from "../../../../../../fields/Types";
 import { observer } from "mobx-react";
 
 export interface TemplatePreviewBoxProps {
     template: Template;
-    menu: DocCreatorMenu;
-    leftButtonOpts?: [icon: IconProp, func: (...args: any) => void]
+    menu: DocCreatorMenu; // eslint-disable-next-line
+    leftButtonOpts?: [icon: IconProp, func: (...args: any) => void] // eslint-disable-next-line
     rightButtonOpts?: [icon: IconProp, func: (...args: any) => void]
 }
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
index d53853c52..da4851f84 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewGrid.tsx
@@ -1,6 +1,4 @@
-import { Colors } from "@dash/components/src";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, makeObservable, observable, runInAction } from "mobx";
+import { makeObservable, runInAction } from "mobx";
 import React from "react";
 import ReactLoading from "react-loading";
 import { Doc } from "../../../../../../fields/Doc";
@@ -18,10 +16,10 @@ export interface SuggestedTemplatesProps {
     loading?: boolean;
     templates: Template[];
     title: string;
-    styles?: string;
-    optionsButtonOpts?: [IconProp, (...args: any) => any];
-    previewBoxLeftButtonOpts?: [IconProp, (...args: any) => any];
-    previewBoxRightButtonOpts?: [IconProp, (...args: any) => any];
+    styles?: string; // eslint-disable-next-line
+    optionsButtonOpts?: [IconProp, (...args: any) => any]; // eslint-disable-next-line
+    previewBoxLeftButtonOpts?: [IconProp, (...args: any) => any]; // eslint-disable-next-line
+    previewBoxRightButtonOpts?: [IconProp, (...args: any) => any]; 
 }
 
 @observer
@@ -46,8 +44,9 @@ export class TemplatePreviewGrid extends ObservableReactComponent<SuggestedTempl
                     (<div className="loading-spinner">
                         <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
                     </div>)
-                    : this.props.templates.map(template => (
+                    : this.props.templates.map((template, i) => (
                         <TemplatePreviewBox 
+                        key={i}
                         template={template} 
                         menu={this.props.menu} 
                         leftButtonOpts={["magnifying-glass", (template: Template) => { this.props.menu.setExpandedView(template); this.forceUpdate(); }]}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx
index 219152549..f281f770e 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx
@@ -1,7 +1,7 @@
 import { action, computed, makeObservable, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { ObservableReactComponent } from "../../../../ObservableReactComponent";
-import { DataVizTemplateLayout, DocCreatorMenu, LayoutType } from "../DocCreatorMenu";
+import { DocCreatorMenu, LayoutType } from "../DocCreatorMenu";
 import React from "react";
 import { IconProp } from "@fortawesome/fontawesome-svg-core";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
@@ -15,7 +15,6 @@ import { DefaultStyleProvider } from "../../../../StyleProvider";
 import { DocumentView } from "../../../DocumentView";
 import { Transform } from "../../../../../util/Transform";
 import { Docs, DocumentOptions } from "../../../../../documents/Documents";
-import { Template } from "../Template";
 
 interface TemplatesRenderPreviewWindowProps {
     menu: DocCreatorMenu;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
index fd87ae973..7591740e0 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
@@ -4,8 +4,6 @@ import { TemplateFieldType, TemplateLayouts } from './TemplateBackend';
 import { DynamicField } from './TemplateFieldTypes/DynamicField';
 import { FieldSettings, TemplateField, ViewType } from './TemplateFieldTypes/TemplateField';
 import { Conditional } from './Backend/TemplateManager';
-import { ImageField } from '../../../../../fields/URLField';
-import { Doc } from '../../../../../fields/Doc';
 import { TemplateDataField } from './TemplateFieldTypes/DataField';
 
 export class Template {
@@ -72,12 +70,6 @@ export class Template {
 
     setupMainField = (templateInfo: FieldSettings) => TemplateField.CreateField(templateInfo, 1, undefined) as DynamicField;
 
-    printFieldInfo = () => {
-        this.allFields.forEach(field => {
-            const doc = field.renderedDoc;
-        });
-    };
-
     assignColToField = (fieldID: number, col: Col) => {
         const field = this.getFieldByID(fieldID);
         field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT);
@@ -102,7 +94,6 @@ export class Template {
         const fieldStatements: Conditional[] = logic[field.getTitle()];
         const content = field.getContent()
         fieldStatements && fieldStatements.forEach(statement => {
-            console.log(statement);
             if (content === statement.condition) {
                 if (statement.target === 'Template') {
                     this._mainField.renderedDoc![statement.attribute] = statement.value;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DataField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DataField.ts
index aaa475bed..6b4086483 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DataField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DataField.ts
@@ -1,6 +1,5 @@
 
-import { Template } from "../Template";
-import { TemplateField, ViewType } from "./TemplateField";
+import { ViewType } from "./TemplateField";
 
 export class TemplateDataField {
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
index 1576dd240..b9042258b 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/DynamicField.ts
@@ -1,6 +1,5 @@
-import { reaction } from 'mobx';
 import { IDisposer } from 'mobx-utils';
-import { Doc, DocListCast } from '../../../../../../fields/Doc';
+import { Doc } from '../../../../../../fields/Doc';
 import { DocData } from '../../../../../../fields/DocSymbols';
 import { List } from '../../../../../../fields/List';
 import { NumCast } from '../../../../../../fields/Types';
@@ -48,10 +47,7 @@ export class DynamicField extends TemplateField {
 
     addField = (field: TemplateField, layer: number = 0) => {
         if (!this._subfields.includes(field)) {
-            console.log('success')
-            console.log('subs: ', this._subfields)
             this._subfields.splice(layer, 0, field);
-            console.log('subffelds: ', this._subfields)
         }
     };
 
@@ -98,7 +94,6 @@ export class DynamicField extends TemplateField {
         const dynClone = super.makeClone(parent) as DynamicField;
         dynClone._subfields = this.getSubfields.map(field => {
             if (field === this.backgroundField) {
-                console.log('background found')
                 const backgroundField: TemplateField = field.makeClone(dynClone, true);
                 dynClone.makeBackgroundField(backgroundField);
                 return backgroundField;
@@ -113,7 +108,7 @@ export class DynamicField extends TemplateField {
     }
 
     initRenderDoc = (settings: FieldSettings) => {
-        this._disposers.fieldList = reaction(() => DocListCast(this._renderDoc?.[Doc.LayoutFieldKey(this._renderDoc)]), this.handleFieldUpdate);
+        //this._disposers.fieldList = reaction(() => DocListCast(this._renderDoc?.[Doc.LayoutFieldKey(this._renderDoc)]), this.handleFieldUpdate);
         this._subfields = settings.subfields?.map((fieldSettings, index) => {return TemplateField.CreateField(fieldSettings, index, this)}) || [];
         const renderedSubfields = this._subfields.filter(field => field.renderedDoc).map(field => field.renderedDoc!);
         settings.opts.title = settings.title;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
index 2a8e4f09b..569c43af4 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/StaticContentField.ts
@@ -1,4 +1,3 @@
-import { FontSize } from '@dash/components';
 import { FieldResult } from '../../../../../../fields/Doc';
 import { DocData } from '../../../../../../fields/DocSymbols';
 import { RichTextField } from '../../../../../../fields/RichTextField';
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
index a1107caf3..c100d1cce 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
@@ -1,7 +1,6 @@
 /* eslint-disable no-use-before-define */
 import { Doc } from '../../../../../../fields/Doc';
 import { DocumentOptions } from '../../../../../documents/Documents';
-import { Conditional } from '../Backend/TemplateManager';
 import { Col } from '../DocCreatorMenu';
 import { Template } from '../Template';
 import { TemplateFieldSize, TemplateFieldType } from '../TemplateBackend';
@@ -95,7 +94,7 @@ export abstract class TemplateField {
     exchangeFields(newField: TemplateField, oldField: TemplateField) {
         throw new Error('Only DynamicField can exchange fields.' + newField._title + ' ' + oldField._title);
     }
-    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+
     changeFieldType = (newType: ViewType): TemplateField => {
         this._settings.viewType = newType;
         const newField = TemplateField.CreateField(this._settings, this._id, this._parent, true);
-- 
cgit v1.2.3-70-g09d2


From 215f874c5f7f28964a3428909fcee0f667388bae Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 1 Jun 2025 16:07:30 -0400
Subject: a number of fixes to document creator to fix preview of templates and
 templates with images.

---
 src/client/views/collections/TabDocView.tsx        |   4 +-
 .../DocCreatorMenu/Backend/TemplateManager.ts      |  79 ++----
 .../DocCreatorMenu/Backend/TemplateMenuAIUtils.ts  |  34 ++-
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.scss  | 183 +++++++-------
 .../DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx   | 206 ++++++---------
 .../DocCreatorMenu/Menu/TemplateEditingWindow.tsx  | 277 ++++++++++-----------
 .../Menu/TemplateMenuFieldOptions.tsx              | 252 +++++++++----------
 .../DocCreatorMenu/Menu/TemplatePreviewBox.tsx     | 113 ++++-----
 .../Menu/TemplateRenderPreviewWindow.tsx           |  97 ++++----
 .../nodes/DataVizBox/DocCreatorMenu/Template.ts    | 132 ++++------
 .../TemplateFieldTypes/TemplateField.ts            |   7 +-
 src/client/views/nodes/ImageBox.tsx                |   1 +
 12 files changed, 621 insertions(+), 764 deletions(-)

(limited to 'src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx')

diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index c4373aaa7..897a86e7f 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -584,9 +584,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> {
     miniMapColor = () => Colors.MEDIUM_GRAY;
     tabView = () => this._view;
     disableMinimap = () => !this._document;
-    whenChildContentActiveChanges = (isActive: boolean) => {
-        this._isAnyChildContentActive = isActive;
-    };
+    whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive);
     isContentActiveFunc = () => this.isContentActive;
     waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined);
     renderDocView = (doc: Doc) => (
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
index 78235d000..526fcf9c4 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateManager.ts
@@ -14,87 +14,64 @@ export type Conditional = {
     target: string;
     attribute: string;
     value: string;
-}
+};
 
 export class TemplateManager {
+    _templates: Template[] = [];
 
-    templates: Template[] = [];
-
-    conditionalFieldLogic: Record<string, Conditional[]> = {};
+    _conditionalFieldLogic: Record<string, Conditional[]> = {};
 
     constructor(templateSettings: FieldSettings[]) {
         makeAutoObservable(this);
-        this.templates = this.initializeTemplates(templateSettings);
+        this._templates = templateSettings.map(settings => new Template(settings));
     }
 
-    initializeTemplates = (templateSettings: FieldSettings[]) => templateSettings.map(settings => {
-        return new Template(settings)});
-
-    getValidTemplates = (cols: Col[]) => this.templates.filter(template => template.isValidTemplate(cols));
+    getValidTemplates = (cols: Col[]) => this._templates.filter(template => template.isValidTemplate(cols));
 
-    addTemplate = (newTemplate: Template) => this.templates.push(newTemplate);
+    addTemplate = (newTemplate: Template) => this._templates.push(newTemplate);
 
     removeTemplate = (template: Template) => {
-        this.templates.splice(this.templates.indexOf(template), 1);
+        if (this._templates.includes(template)) {
+            this._templates.splice(this._templates.indexOf(template), 1);
+        }
         template.cleanup();
     };
 
     addFieldCondition = (fieldTitle: string, condition: Conditional) => {
-        if (this.conditionalFieldLogic[fieldTitle] === undefined) {
-            this.conditionalFieldLogic[fieldTitle] = [condition];
+        if (this._conditionalFieldLogic[fieldTitle] === undefined) {
+            this._conditionalFieldLogic[fieldTitle] = [condition];
         } else {
-            this.conditionalFieldLogic[fieldTitle].push(condition);
+            this._conditionalFieldLogic[fieldTitle].push(condition);
         }
-    }
+    };
 
-    removeFieldCondition = (fieldTitle: string, condition: Conditional) => {
-        if (this.conditionalFieldLogic[fieldTitle]) {
-            this.conditionalFieldLogic[fieldTitle] = this.conditionalFieldLogic[fieldTitle].filter(cond => cond !== condition);
-        }
-    }
+    removeFieldCondition = (fieldTitle: string, condition: Conditional) => (this._conditionalFieldLogic[fieldTitle] = this._conditionalFieldLogic[fieldTitle]?.filter(cond => cond !== condition));
 
-    addDataField = (title: string) => {
-        this.templates.forEach(template => template.addDataField(title));
-    }
+    addDataField = (title: string) => this._templates.forEach(template => template.addDataField(title));
 
-    removeDataField = (title: string) => {
-        this.templates.forEach(template => template.removeDataField(title));
-    }
+    removeDataField = (title: string) => this._templates.forEach(template => template.removeDataField(title));
 
     createDocsFromTemplate = action((dv: DataVizBox, template: Template, cols: Col[], debug: boolean = false) => {
         const csvFields = Array.from(Object.keys(dv.records[0]));
 
-        const processContent = async (content: { [title: string]: string }) => {
+        const processContent = (content: { [title: string]: string }) => {
             const templateCopy = template.clone();
 
             csvFields
                 .filter(title => title)
                 .forEach(title => {
                     const field = templateCopy.getFieldByTitle(title);
-                    field && field.setContent(content[title], field.viewType);
+                    field?.setContent(content[title], field.viewType);
                 });
 
             const gptFunc = (type: TemplateFieldType) => (type === TemplateFieldType.VISUAL ? TemplateMenuAIUtils.renderGPTImageCall : TemplateMenuAIUtils.renderGPTTextCall);
-            const applyGPTContent = async () => {
-                const promises = cols
-                    .filter(field => field.AIGenerated)
-                    .map(field => {
-                        const templateField: TemplateField = templateCopy.getFieldByTitle(field.title) as TemplateField;
-                        if (templateField !== undefined) {
-                            return gptFunc(field.type)(templateCopy, field, templateField.getID);
-                        }
-                        return null; 
-                    })
-                    .filter(p => p !== null);
-            
-                await Promise.all(promises);
-            };
-
-            await applyGPTContent();
-
-            templateCopy.applyConditionalLogic(this.conditionalFieldLogic);
-
-            return templateCopy.getRenderedDoc();
+
+            const generateGptContent = cols
+                .map(field => ({ field, templateField: field?.AIGenerated && templateCopy.getFieldByTitle(field.title) }))
+                .filter(({ templateField }) => templateField instanceof TemplateField)
+                .map(({ field, templateField }) => gptFunc(field.type)(templateCopy, field, (templateField as TemplateField).getID));
+
+            return Promise.all(generateGptContent).then(() => templateCopy.applyConditionalLogic(this._conditionalFieldLogic));
         };
 
         const rowContents = debug
@@ -109,10 +86,6 @@ export class TemplateManager {
                   )
               );
 
-        return Promise.all(rowContents.map(processContent)).then(
-            action(renderedDocs => {
-                return renderedDocs;
-            })
-        );
+        return Promise.all(rowContents.map(processContent));
     });
 }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts
index 162b7a1b1..08818dd6c 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Backend/TemplateMenuAIUtils.ts
@@ -1,13 +1,13 @@
-import { ClientUtils } from "../../../../../../ClientUtils";
-import { Networking } from "../../../../../Network";
-import { gptImageCall, gptAPICall, GPTCallType } from "../../../../../apis/gpt/GPT";
-import { Col } from "../DocCreatorMenu";
-import { TemplateFieldSize, TemplateFieldType } from "../TemplateBackend";
-import { TemplateField, ViewType } from "../TemplateFieldTypes/TemplateField";
-import { Template } from "../Template";
+import { ClientUtils } from '../../../../../../ClientUtils';
+import { Networking } from '../../../../../Network';
+import { gptImageCall, gptAPICall, GPTCallType } from '../../../../../apis/gpt/GPT';
+import { Col } from '../DocCreatorMenu';
+import { TemplateFieldSize, TemplateFieldType } from '../TemplateBackend';
+import { ViewType } from '../TemplateFieldTypes/TemplateField';
+import { Template } from '../Template';
+import { Upload } from '../../../../../../server/SharedMediaTypes';
 
 export class TemplateMenuAIUtils {
-
     public static generateGPTImage = async (prompt: string): Promise<string | undefined> => {
         try {
             const res = await gptImageCall(prompt);
@@ -24,13 +24,10 @@ export class TemplateMenuAIUtils {
 
     public static renderGPTImageCall = async (template: Template, col: Col, fieldNumber: number): Promise<boolean> => {
         const generateAndLoadImage = async (id: number, prompt: string) => {
+            const field = template.getFieldByID(id);
             const url = await this.generateGPTImage(prompt);
-            // eslint-disable-next-line
-            var field: TemplateField = template.getFieldByID(id);
-
-            field.setContent(url ?? '', ViewType.IMG);
-            field = template.getFieldByID(id);
-            field.setTitle(col.title);
+            field?.setContent(url ?? '', ViewType.IMG);
+            field?.setTitle(col.title);
         };
 
         const fieldContent: string = template.compiledContent;
@@ -81,10 +78,10 @@ export class TemplateMenuAIUtils {
             if (res) {
                 const assignments: { [title: string]: { number: string; content: string } } = JSON.parse(res);
                 Object.entries(assignments).forEach(([, /* title */ info]) => {
-                    const field: TemplateField = template.getFieldByID(Number(info.number));
+                    const field = template.getFieldByID(Number(info.number));
 
-                    field.setContent(info.content ?? '', ViewType.TEXT);
-                    field.setTitle(col.title);
+                    field?.setContent(info.content ?? '', ViewType.TEXT);
+                    field?.setTitle(col.title);
                 });
             }
         } catch (err) {
@@ -122,5 +119,4 @@ export class TemplateMenuAIUtils {
 
         return template;
     };
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
index 463e69c67..6eb7fa96a 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.scss
@@ -9,10 +9,10 @@
     position: absolute;
     z-index: 1000;
     // box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
-    // background: whitesmoke; 
+    // background: whitesmoke;
     // color: black;
     border-radius: 3px;
-}   
+}
 
 .docCreatorMenu-menu {
     display: flex;
@@ -24,7 +24,7 @@
 .docCreatorMenu-menu-button {
     width: 25px;
     height: 25px;
-    background: whitesmoke; 
+    background: whitesmoke;
     background-color: rgb(50, 50, 50);
     border-radius: 5px;
     padding: 0px;
@@ -53,8 +53,8 @@
         position: absolute;
         right: 0px;
     }
-     
-    &.right{
+
+    &.right {
         margin-left: 0px;
         font-size: 12px;
     }
@@ -103,12 +103,12 @@
         }
     }
 
-    &:hover::before{
+    &:hover::before {
         border-bottom: 20px solid rgb(82, 82, 82);
     }
 
     &::before {
-        content: "";
+        content: '';
         position: absolute;
         top: 0;
         left: 0;
@@ -120,7 +120,7 @@
     }
 
     &::after {
-        content: "";    
+        content: '';
         position: absolute;
         top: -1px;
         left: -1px;
@@ -143,7 +143,7 @@
     color: white;
 }
 
-.docCreatorMenu-menu-hr{
+.docCreatorMenu-menu-hr {
     margin-top: 0px;
     margin-bottom: 0px;
     color: rgb(180, 180, 180);
@@ -173,14 +173,14 @@
     align-items: center;
     width: 40px;
     height: 40px;
-    background-color: rgb(99, 148, 238); 
+    background-color: rgb(99, 148, 238);
     border: 2px solid rgb(80, 107, 152);
     border-radius: 5px;
     margin-bottom: 20px;
     font-size: 25px;
 
-    &:hover{
-        background-color: rgb(59, 128, 255);  
+    &:hover {
+        background-color: rgb(59, 128, 255);
         border: 2px solid rgb(53, 80, 127);
     }
 }
@@ -188,7 +188,7 @@
 .docCreatorMenu-create-docs-button {
     width: 40px;
     height: 40px;
-    background-color: rgb(176, 229, 149); 
+    background-color: rgb(176, 229, 149);
     border: 2px solid rgb(126, 219, 80);
     border-radius: 5px;
     padding: 0px;
@@ -199,7 +199,7 @@
 
     &:hover {
         background-color: rgb(129, 223, 83);
-        border: 2px solid rgb(80, 185, 28); 
+        border: 2px solid rgb(80, 185, 28);
     }
 }
 
@@ -230,17 +230,22 @@
     position: absolute;
     background-color: none;
 
-    &.top, &.bottom {
+    &.top,
+    &.bottom {
         height: 10px;
         cursor: ns-resize;
     }
 
-    &.right, &.left {
+    &.right,
+    &.left {
         width: 10px;
         cursor: ew-resize;
     }
 
-    &.topRight, &.topLeft, &.bottomRight, &.bottomLeft {
+    &.topRight,
+    &.topLeft,
+    &.bottomRight,
+    &.bottomLeft {
         height: 15px;
         width: 15px;
         background-color: none;
@@ -263,7 +268,7 @@
     height: calc(100% - 30px);
     border: 1px solid rgb(180, 180, 180);
     border-radius: 5px;
-    -ms-overflow-style: none; 
+    -ms-overflow-style: none;
     scrollbar-width: none;
 }
 
@@ -277,7 +282,7 @@
     height: 100%;
     flex-grow: 1;
 
-    .top-panel{
+    .top-panel {
         width: 100%;
         height: 10px;
     }
@@ -302,13 +307,14 @@
     justify-content: center;
     align-items: center;
     height: 100%;
+    width: 100%;
     aspect-ratio: 1;
     color: none;
     border: 1px solid rgb(163, 163, 163);
     border-radius: 5px;
     box-shadow: 5px 5px rgb(29, 29, 31);
 
-    &:hover{
+    &:hover {
         background-color: rgb(72, 72, 73);
     }
 
@@ -353,20 +359,18 @@
     &:hover .option-button {
         display: block;
     }
-    
 }
 
-.docCreatorMenu-preview-image{ 
+.docCreatorMenu-preview-image {
     background-color: transparent;
     height: 100%;
     display: block;
     object-fit: contain;
     border-radius: 5px;
-
 }
 
 .docCreatorMenu-variations-tab {
-    flex-grow: .5;
+    flex-grow: 0.5;
 }
 
 .docCreatorMenu-section {
@@ -400,7 +404,7 @@
     display: grid;
     justify-content: space-evenly;
     row-gap: 2rem;
-    grid-template-columns: repeat(auto-fill, minmax(150px, 30%));
+    grid-template-columns: repeat(auto-fill, minmax(150px, 50%));
     margin: 5px;
     width: calc(100% - 10px);
     height: 100%;
@@ -413,7 +417,7 @@
     }
 }
 
-.divvv{
+.div {
     width: 200;
     height: 200;
     border: solid 1px white;
@@ -448,7 +452,7 @@
 .docCreatorMenu-GPT-generate {
     height: 30px;
     width: 30px;
-    background-color: rgb(176, 229, 149); 
+    background-color: rgb(176, 229, 149);
     border: 1px solid rgb(126, 219, 80);
     border-radius: 5px;
     padding: 0px;
@@ -459,7 +463,7 @@
 
     &:hover {
         background-color: rgb(129, 223, 83);
-        border: 2px solid rgb(80, 185, 28); 
+        border: 2px solid rgb(80, 185, 28);
     }
 }
 
@@ -477,7 +481,7 @@
 // DocCreatorMenu options CSS
 //--------------------------------------------------------------------------------------------------------------------------------------------
 
-.docCreatorMenu-option-container{
+.docCreatorMenu-option-container {
     display: flex;
     width: 180px;
     height: 30px;
@@ -487,16 +491,16 @@
     margin-top: 10px;
     margin-bottom: 10px;
 
-    &.layout{
+    &.layout {
         z-index: 5;
     }
 }
 
-.docCreatorMenu-option-title{
+.docCreatorMenu-option-title {
     display: flex;
     width: 140px;
     height: 30px;
-    background: whitesmoke; 
+    background: whitesmoke;
     background-color: rgb(34, 34, 37);
     border-radius: 5px;
     border: 1px solid rgb(180, 180, 180);
@@ -513,7 +517,7 @@
         border-radius: 0px;
         width: auto;
         text-transform: none;
-         
+
         &.small {
             height: 20px;
             transform: translateY(-5px);
@@ -607,38 +611,38 @@
     height: calc(100% - 30px);
     border: 1px solid rgb(180, 180, 180);
     border-radius: 5px;
-    -ms-overflow-style: none; 
+    -ms-overflow-style: none;
     scrollbar-width: none;
 
-    .docCreatorMenu-option-container{
+    .docCreatorMenu-option-container {
         width: 180px;
         height: 30px;
 
         .docCreatorMenu-dropdown-hoverable {
             width: 140px;
             height: 30px;
-    
+
             &:hover .docCreatorMenu-dropdown-content {
                 display: block;
             }
-    
+
             &:hover .docCreatorMenu-option-title {
                 border-bottom-left-radius: 0px;
                 border-bottom-right-radius: 0px;
             }
-        
+
             .docCreatorMenu-dropdown-content {
                 display: none;
                 min-width: 100px;
                 height: 75px;
                 overflow-y: scroll;
-                -ms-overflow-style: none; 
+                -ms-overflow-style: none;
                 scrollbar-width: none;
                 border-bottom: 1px solid rgb(180, 180, 180);
                 border-bottom-left-radius: 5px;
                 border-bottom-right-radius: 5px;
-        
-                .docCreatorMenu-dropdown-option{
+
+                .docCreatorMenu-dropdown-option {
                     display: flex;
                     background-color: rgb(42, 42, 46);
                     border-left: 1px solid rgb(180, 180, 180);
@@ -649,13 +653,13 @@
                     justify-content: center;
                     justify-items: center;
                     padding-top: 3px;
-    
+
                     &:hover {
                         background-color: rgb(68, 68, 74);
                         cursor: pointer;
                     }
                 }
-            }    
+            }
         }
     }
 }
@@ -710,7 +714,7 @@
         border: 1px solid rgb(180, 180, 180);
         border-radius: 5px;
         background-color: rgb(34, 34, 37);
-        -ms-overflow-style: none; 
+        -ms-overflow-style: none;
         scrollbar-width: none;
 
         &.small {
@@ -742,10 +746,10 @@
         z-index: 999;
     }
 
-    .docCreatorMenu-zoom-button{ 
+    .docCreatorMenu-zoom-button {
         width: 15px;
         height: 15px;
-        background: whitesmoke; 
+        background: whitesmoke;
         background-color: rgb(34, 34, 37);
         border-radius: 3px;
         border: 1px solid rgb(180, 180, 180);
@@ -759,8 +763,6 @@
     }
 }
 
-
-
 //------------------------------------------------------------------------------------------------------------------------------------------
 // DocCreatorMenu dashboard CSS
 //--------------------------------------------------------------------------------------------------------------------------------------------
@@ -778,7 +780,7 @@
     height: calc(100% - 30px);
     border: 1px solid rgb(180, 180, 180);
     border-radius: 5px;
-    -ms-overflow-style: none; 
+    -ms-overflow-style: none;
     scrollbar-width: none;
 
     .panels-container {
@@ -836,8 +838,8 @@
                 background-color: rgb(72, 72, 72);
                 cursor: pointer;
             }
-        }  
-        
+        }
+
         .opts-bar {
             display: flex;
             flex-direction: row;
@@ -880,11 +882,11 @@
                     flex-direction: row;
                     align-items: flex-start;
                 }
-        
+
                 &:hover .type-display {
                     display: none;
                 }
-        
+
                 .bubble {
                     margin: 3px;
                 }
@@ -919,7 +921,7 @@
                     flex-direction: row;
                     align-items: center;
                 }
-        
+
                 .bubble {
                     margin: 3px;
                     margin-right: 4px;
@@ -942,10 +944,8 @@
                 border-bottom-right-radius: 5px;
                 border-bottom-left-radius: 5px;
                 resize: none;
-
             }
         }
-
     }
 
     .conditionals-section {
@@ -972,7 +972,7 @@
         flex-direction: row;
         justify-content: flex-start;
         align-items: flex-start;
-        color: whitesmoke; 
+        color: whitesmoke;
         width: 100%;
         height: fit-content;
         margin-bottom: 15px;
@@ -1041,7 +1041,6 @@
             overflow-y: scroll;
             white-space: nowrap;
         }
-
     }
 
     .form {
@@ -1057,10 +1056,8 @@
             align-items: center;
             margin: 3px;
             cursor: pointer;
-
         }
     }
-
 }
 
 //------------------------------------------------------------------------------------------------------------------------------------------
@@ -1113,38 +1110,38 @@
     display: flex;
     align-items: center;
     justify-content: center;
-    gap: 2rem;                 
+    gap: 2rem;
     padding: 0.5rem 1rem;
     background: rgb(50, 50, 50);
     color: whitesmoke;
     font-family: system-ui, sans-serif;
     font-size: 0.9rem;
-    flex-wrap: wrap;         
-  }
-  
-  .menu‑item {
+    flex-wrap: wrap;
+}
+
+.menu‑item {
     display: flex;
     align-items: center;
     gap: 0.5rem;
     white-space: nowrap;
-  }
-  
-  .menu‑item input[type="range"] {
+}
+
+.menu‑item input[type='range'] {
     width: 7rem;
-    accent-color: whitesmoke;      
-  }
-  
-  .value {
+    accent-color: whitesmoke;
+}
+
+.value {
     min-width: 2ch;
     text-align: right;
-  }
-  
-  .switch {
+}
+
+.switch {
     gap: 0.75rem;
     margin-bottom: 0px;
-  }
-  
-  .switch .slider {
+}
+
+.switch .slider {
     position: relative;
     width: 2.2rem;
     height: 1.1rem;
@@ -1152,9 +1149,9 @@
     border-radius: 1rem;
     cursor: pointer;
     transition: background 0.2s;
-  }
-  
-  .switch .slider::before {
+}
+
+.switch .slider::before {
     content: '';
     position: absolute;
     top: 0.1rem;
@@ -1164,27 +1161,25 @@
     background: rgb(50, 50, 50);
     border-radius: 50%;
     transition: transform 0.2s;
-  }
-  
-  .switch input {
+}
+
+.switch input {
     display: none;
-  }
-  
-  .switch input:checked + .slider {
+}
+
+.switch input:checked + .slider {
     background: #78c2f1;
-  }
-  
-  .switch input:checked + .slider::before {
+}
+
+.switch input:checked + .slider::before {
     transform: translateX(1.1rem);
-  }
+}
 
 .firefly-option-label {
-    padding: .2em .6em .3em;
+    padding: 0.2em 0.6em 0.3em;
     font-size: 100%;
     color: whitesmoke;
     text-align: center;
     margin-bottom: 0px;
     font-weight: 500;
 }
-  
-
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
index ebfa3fc65..fb083ea75 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx
@@ -1,4 +1,3 @@
-
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable, runInAction } from 'mobx';
@@ -22,7 +21,7 @@ import { ViewType } from './TemplateFieldTypes/TemplateField';
 import { Template } from './Template';
 import { TemplateFieldSize, TemplateFieldType, TemplateLayouts } from './TemplateBackend';
 import { TemplateManager } from './Backend/TemplateManager';
-import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils'
+import { TemplateMenuAIUtils } from './Backend/TemplateMenuAIUtils';
 import { TemplatePreviewGrid } from './Menu/TemplatePreviewGrid';
 import { FireflyStructureOptions, TemplateEditingWindow } from './Menu/TemplateEditingWindow';
 import { DocCreatorMenuButton } from './Menu/DocCreatorMenuButton';
@@ -158,18 +157,12 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         return bounds;
     }
 
-    setUpButtonClick = (e: React.PointerEvent, func: (...args: any) => void) => {
-        setupMoveUpEvents(
-            this,
-            e,
-            returnFalse,
-            emptyFunction,
-            undoable(clickEv => {
-                clickEv.stopPropagation();
-                clickEv.preventDefault();
-                func();
-            }, 'create docs')
-        );
+    setUpButtonClick = (e: React.PointerEvent, func: () => void) => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, clickEv => {
+            clickEv.stopPropagation();
+            clickEv.preventDefault();
+            undoable(func, 'create docs')();
+        });
     };
 
     @action
@@ -301,16 +294,13 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         this._pageX = x + translation.x;
         this._pageY = y + translation.y;
     };
-    
-    async createDocsForPreview(): Promise<Doc[]> { return this._dataViz && this._selectedTemplate ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, this._selectedTemplate, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) as unknown as Doc[] : []; }
 
-    @action updateSelectedTemplate = async (template: Template) => {
-        if (this._selectedTemplate === template) {
-            this._selectedTemplate = undefined;
-            return;
-        } else {
-            this._selectedTemplate = template;
-        }
+    async createDocsForPreview() {
+        return this._dataViz && this._selectedTemplate ? ((await this.templateManager.createDocsFromTemplate(this._dataViz, this._selectedTemplate, this.fieldsInfos, this.DEBUG_MODE)).filter(doc => doc).map(doc => doc!) ?? []) : [];
+    }
+
+    @action updateSelectedTemplate = (template: Template) => {
+        this._selectedTemplate = this._selectedTemplate === template ? undefined : template; // toggle selection
     };
 
     // testTemplate = async () => {
@@ -318,27 +308,16 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
     // };
 
     @action addField = () => {
-        const newFields: Col[] = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [], AIGenerated: true }]);
-        this._userCreatedFields = newFields;
+        this._userCreatedFields = this._userCreatedFields.concat([{ title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [], AIGenerated: true }]);
     };
 
     @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._userCreatedFields.filter(f => f === field);
-            if (!toRemove) return;
-
-            if (toRemove.length > 1) {
-                while (toRemove.length > 1) {
-                    toRemove.pop();
-                }
-            }
-
-            if (this._userCreatedFields.length === 1) {
-                this._userCreatedFields = [];
-            } else {
-                this._userCreatedFields.splice(this._userCreatedFields.indexOf(toRemove[0]), 1);
+            const toRemove = this._userCreatedFields.findIndex(f => f === field);
+            if (toRemove !== -1) {
+                this._userCreatedFields.splice(toRemove, 1);
             }
         }
     };
@@ -390,35 +369,22 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         this.forceUpdate();
     };
 
-    compileFieldDescriptions = (templates: Template[]): string => {
-        let descriptions: string = '';
-        templates.forEach(template => {
-            descriptions += `----------  NEW TEMPLATE TO INCLUDE: The title is: ${template.title}. Its fields are: `;
-            descriptions += template.descriptionSummary;
-        });
-
-        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}} `));
+    compileFieldDescriptions = (templates: Template[]) => 
+        templates.map(template => `----------  NEW TEMPLATE TO INCLUDE: The title is: ${template.title}. Its fields are: ` + template.descriptionSummary).join(''); // prettier-ignore
 
-        return descriptions;
-    };
+    compileColDescriptions = (cols: Col[]) => 
+        ' ------------- COL DESCRIPTIONS START HERE:' + cols.map(col => `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `).join(''); // prettier-ignore
 
-    getColByTitle = (title: string) => {
-        return this.fieldsInfos.filter(col => col.title === title)[0];
-    };
+    getColByTitle = (title: string): Col | undefined => this.fieldsInfos.filter(col => col.title === title)[0];
 
     @action
     assignColsToFields = async (templates: Template[], cols: Col[]): Promise<[Template, { [field: number]: Col }][]> => {
-        const fieldDescriptions: string = this.compileFieldDescriptions(templates);
-        const colDescriptions: string = this.compileColDescriptions(cols);
+        const fieldDescriptions = this.compileFieldDescriptions(templates);
+        const colDescriptions = this.compileColDescriptions(cols);
 
         const inputText = fieldDescriptions.concat(colDescriptions);
 
-        const prompt: string = `(${Math.random() * 100000}) ${inputText}`;
+        const prompt = `(${Math.random() * 100000}) ${inputText}`;
 
         this._GPTLoading = true;
 
@@ -431,23 +397,25 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
 
                 Object.entries(assignments).forEach(([tempTitle, assignment]) => {
                     const template = templates.filter(temp => temp.title === tempTitle)[0];
-                    if (!template) return;
-                    const toObj = Object.entries(assignment).reduce(
-                        (a, [fieldID, colTitle]) => {
-                            const col = this.getColByTitle(colTitle);
-                            if (!col.AIGenerated) {
-                                var field = template.getFieldByID(Number(fieldID));
-                                field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT);
-                                field = template.getFieldByID(Number(fieldID));
-                                field.setTitle(col.title);
-                            } else {
-                                a[Number(fieldID)] = this.getColByTitle(colTitle);
-                            }
-                            return a;
-                        },
-                        {} as { [field: number]: Col }
-                    );
-                    brokenDownAssignments.push([template, toObj]);
+                    if (template) {
+                        const toObj = Object.entries(assignment).reduce(
+                            (a, [fieldID, colTitle]) => {
+                                const col = this.getColByTitle(colTitle);
+                                if (col) {
+                                    if (!col.AIGenerated) {
+                                        const field = template.getFieldByID(Number(fieldID));
+                                        field?.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT);
+                                        field?.setTitle(col.title);
+                                    } else {
+                                        a[Number(fieldID)] = col;
+                                    }
+                                }
+                                return a;
+                            },
+                            {} as { [field: number]: Col }
+                        );
+                        brokenDownAssignments.push([template, toObj]);
+                    }
                 });
 
                 return brokenDownAssignments;
@@ -459,39 +427,32 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         return [];
     };
 
-    generatePresetTemplates = async () => {
-        const templates: Template[] = [];
-
+    generatePresetTemplates = action(() => {
         if (this.DEBUG_MODE) {
-            templates.push(...this.templateManager.templates);
+            this.setSuggestedTemplates(this.templateManager._templates);
+            this._GPTLoading = false;
         } else {
             this._dataViz?.updateColDefaults();
-
             const contentFields = this.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA);
-
-            templates.push(...this.templateManager.getValidTemplates(contentFields));
-
-            const assignments = await this.assignColsToFields(templates, contentFields);
-
-            const renderedTemplatePromises = assignments.map(([template, assgns]) => TemplateMenuAIUtils.applyGPTContentToTemplate(template, assgns));
-
-            await Promise.all(renderedTemplatePromises);
+            const templates = this.templateManager.getValidTemplates(contentFields);
+
+            return this.assignColsToFields(templates, contentFields)
+                .then(pairs => 
+                    Promise.all(pairs.map(([templ, assgns]) => TemplateMenuAIUtils.applyGPTContentToTemplate(templ, assgns))))
+                .then(action(() => {
+                    this.setSuggestedTemplates(templates);
+                    this._GPTLoading = false;
+                })); // prettier-ignore
         }
+    });
 
-        setTimeout(
-            action(() => {
-                this.setSuggestedTemplates(templates);
-                this._GPTLoading = false;
-            })
-        );
-    };
-
-    generateVariations = async (onDoc: Doc, prompt: string, options: FireflyStructureOptions): Promise<string[]> => {
+    // eslint-disable-next-line @typescript-eslint/no-unused-vars
+    generateVariations = async (onDoc: Doc, prompt: string, options: FireflyStructureOptions) => {
         // const { numVariations, temperature, useStyleRef } = options;
         this.variations = [];
         const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
 
-        const clone: Doc = (await Doc.MakeClone(onDoc)).clone;
+        const clone = Doc.MakeClone(onDoc).clone;
         mainCollection.addDocument(clone);
         clone.x = 10000;
         clone.y = 10000;
@@ -499,13 +460,13 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         // await DrawingFillHandler.drawingToImage(clone, 100 - temperature, prompt, useStyleRef ? clone : undefined, this, numVariations)
 
         return this.variations;
-    }
+    };
 
-    variations: string[] = []
+    variations: string[] = [];
 
     @action addVariation = (url: string) => {
         this.variations.push(url);
-    }
+    };
 
     addRenderedCollectionToMainview = (collection: Doc) => {
         if (collection) {
@@ -517,10 +478,11 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         }
     };
 
-    @action editLastTemplate = () => { if (this._editedTemplateTrail.length) this._currEditingTemplate = this._editedTemplateTrail.pop()}
+    @action editLastTemplate = () => {
+        if (this._editedTemplateTrail.length) this._currEditingTemplate = this._editedTemplateTrail.pop();
+    };
 
     @action setExpandedView = (template: Template | undefined) => {
-
         if (template) {
             this._menuContent = 'templateEditing';
             this._currEditingTemplate && this._editedTemplateTrail.push(this._currEditingTemplate);
@@ -533,27 +495,23 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
         //Docs.Create.FreeformDocument([doc], { _height: NumListCast(doc._height)[0], _width: NumListCast(doc._width)[0], title: ''});
     };
 
-    @computed 
-    get templatesView() { return (
-        <div className='docCreatorMenu-templates-view'>
-            <div className="docCreatorMenu-templates-displays">
-                <TemplatePreviewGrid 
-                    title={'Suggested Templates'} 
-                    menu={this} 
-                    loading={this._GPTLoading}
-                    optionsButtonOpts={this.optionsButtonOpts}
-                    templates={this._suggestedTemplates}
-                />
-                <div className="docCreatorMenu-GPT-options">
-                    <div className="docCreatorMenu-GPT-options-container">
-                        <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generatePresetTemplates}/>
+    @computed
+    get templatesView() {
+        return (
+            <div className="docCreatorMenu-templates-view">
+                <div className="docCreatorMenu-templates-displays">
+                    <TemplatePreviewGrid title={'Suggested Templates'} menu={this} loading={this._GPTLoading} optionsButtonOpts={this.optionsButtonOpts} templates={this._suggestedTemplates} />
+                    <div className="docCreatorMenu-GPT-options">
+                        <div className="docCreatorMenu-GPT-options-container">
+                            <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generatePresetTemplates} />
+                        </div>
                     </div>
                 </div>
             </div>
-        </div>
-    )};
-    
-    private optionsButtonOpts: [IconProp, () => any] = ['gear', () => (this._menuContent = 'dashboard')];
+        );
+    }
+
+    private optionsButtonOpts: [IconProp, () => void] = ['gear', () => (this._menuContent = 'dashboard')];
 
     get renderSelectedViewType() {
         switch (this._menuContent) {
@@ -634,7 +592,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
                                 {topButton('magnifying-glass', 'options', onOptionsSelected, 'middle')}
                                 {topButton('bars', 'saved', onSavedSelected, 'right')}
                             </div>
-                            <DocCreatorMenuButton icon={'minus'} styles={'float-right'} function={this.closeMenu}/>
+                            <DocCreatorMenuButton icon={'minus'} styles={'float-right'} function={this.closeMenu} />
                         </div>
                         {this.renderSelectedViewType}
                     </div>
@@ -642,4 +600,4 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps>
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
index b50fff9e0..c35099e82 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateEditingWindow.tsx
@@ -1,25 +1,25 @@
-import { action, makeObservable, observable, reaction, runInAction } from "mobx";
-import React from "react";
-import { returnFalse, returnEmptyFilter } from "../../../../../../ClientUtils";
-import { emptyFunction } from "../../../../../../Utils";
-import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc";
-import { DefaultStyleProvider } from "../../../../StyleProvider";
-import { DocumentView, DocumentViewInternal } from "../../../DocumentView";
-import { DocCreatorMenu } from "../DocCreatorMenu";
-import { TemplatePreviewGrid } from "./TemplatePreviewGrid";
-import { observer } from "mobx-react";
-import { Transform } from "../../../../../util/Transform";
-import { Template } from "../Template";
-import { ObservableReactComponent } from "../../../../ObservableReactComponent";
-import { IDisposer } from "mobx-utils";
-import { DocCreatorMenuButton } from "./DocCreatorMenuButton";
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
+import { action, makeObservable, observable, reaction } from 'mobx';
+import React from 'react';
+import { returnFalse, returnEmptyFilter } from '../../../../../../ClientUtils';
+import { emptyFunction } from '../../../../../../Utils';
+import { returnEmptyDoclist } from '../../../../../../fields/Doc';
+import { DefaultStyleProvider } from '../../../../StyleProvider';
+import { DocumentView, DocumentViewInternal } from '../../../DocumentView';
+import { DocCreatorMenu } from '../DocCreatorMenu';
+import { TemplatePreviewGrid } from './TemplatePreviewGrid';
+import { observer } from 'mobx-react';
+import { Transform } from '../../../../../util/Transform';
+import { Template } from '../Template';
+import { ObservableReactComponent } from '../../../../ObservableReactComponent';
+import { IDisposer } from 'mobx-utils';
+import { DocCreatorMenuButton } from './DocCreatorMenuButton';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
 
 export type FireflyStructureOptions = {
     numVariations: number;
     temperature: number;
     useStyleRef: boolean;
-}
+};
 
 interface FireflyVariationsTabProps {
     menu: DocCreatorMenu;
@@ -28,112 +28,93 @@ interface FireflyVariationsTabProps {
 
 @observer
 export class FireflyVariationsTab extends ObservableReactComponent<FireflyVariationsTabProps> {
+    private _prompt: string = 'Use this template to generate an empty baseball card template.';
+    private _optionsButtonOpts: [IconProp, () => void] = ['gear', emptyFunction];
+    private _previewBoxRightButtonOpts: [IconProp, () => void] = ['gear', () => this.forceUpdate()];
 
-    private prompt: string = 'Use this template to generate an empty baseball card template.';
-
-    @observable private promptInput: HTMLTextAreaElement | null = null;
-
+    @observable _fireflyOptions: FireflyStructureOptions = { numVariations: 3, temperature: 0, useStyleRef: false };
+    @observable _promptInput: HTMLTextAreaElement | null = null;
     @observable _loading: boolean = false;
     @observable _variationsTabOpen: boolean = false;
     @observable _variationURLs: string[] = [];
 
-    @observable private fireflyOptions: FireflyStructureOptions = {numVariations: 3, temperature: 0, useStyleRef: false};
-
     constructor(props: FireflyVariationsTabProps) {
         super(props);
         makeObservable(this);
     }
 
-    generateVariations = async () => {
+    generateVariations = action(async () => {
         this._props.menu._variations = [];
         this._loading = true;
         const cloneTemplate = this._props.template.clone(false);
         cloneTemplate.setMatteBackground();
-        const doc: Doc = cloneTemplate.getRenderedDoc()!;
-        this._variationURLs = await this._props.menu.generateVariations(doc, this.prompt, this.fireflyOptions);
-        this._variationURLs.forEach(() => {
-            const newTemplate: Template = this._props.template.clone(true);
-            this._props.menu._variations.push(newTemplate);
-        });
-        setTimeout(() => {
-            this._variationURLs.forEach((url, i) => {
-                this._props.menu._variations[i].setImageAsBackground(url, true);
-            });
-            this._loading = false;
-        });
-    }
-
-    setPromptInputRef: React.LegacyRef<HTMLTextAreaElement> = (node) => {
-        this.promptInput = node;
-    }
-
-    // eslint-disable-next-line
-    private optionsButtonOpts: [IconProp, () => any] = ['gear', () => {}];
-    // eslint-disable-next-line
-    private previewBoxRightButtonOpts: [IconProp, () => any] = ['gear', () => this.forceUpdate()];
+        const doc = cloneTemplate.getRenderedDoc()!;
+        this._props.menu.generateVariations(doc, this._prompt, this._fireflyOptions).then(
+            action((urls: string[]) => {
+                (this._variationURLs = urls).forEach(url => {
+                    const template = this._props.template.clone(true);
+                    template.setImageAsBackground(url, true);
+                    this._props.menu._variations.push(template);
+                });
+                this._loading = false;
+            })
+        );
+    });
 
     render() {
         return (
-            <div className='docCreatorMenu-editing-firefly-section'>
-                <div className="docCreatorMenu-option-divider full no-margin-bottom"/>
+            <div className="docCreatorMenu-editing-firefly-section">
+                <div className="docCreatorMenu-option-divider full no-margin-bottom" />
                 <TemplatePreviewGrid
                     menu={this._props.menu}
-                    title={'Generate Variations'}
+                    title="Generate Variations"
                     loading={this._loading}
-                    styles={'scrolling'}
+                    styles="scrolling"
                     templates={this._props.menu._variations}
-                    optionsButtonOpts={this.optionsButtonOpts}
-                    previewBoxRightButtonOpts={this.previewBoxRightButtonOpts}
+                    optionsButtonOpts={this._optionsButtonOpts}
+                    previewBoxRightButtonOpts={this._previewBoxRightButtonOpts}
                 />
                 <div className="docCreatorMenu-firefly-options">
                     <div className="docCreatorMenu-variation-prompt-row">
                         <textarea
                             className="docCreatorMenu-variation-prompt-input-textbox"
-                            ref={this.setPromptInputRef}
-                            onChange={e => { this.prompt = e.target.value }}
+                            ref={action((node: HTMLTextAreaElement | null) => (this._promptInput = node))}
+                            onChange={e => (this._prompt = e.target.value)}
                             onInput={() => {
-                                if (this.promptInput !== null) {
-                                    this.promptInput.style.height = 'auto';
-                                    this.promptInput.style.height = this.promptInput.scrollHeight + 'px';
+                                if (this._promptInput !== null) {
+                                    this._promptInput.style.height = 'auto';
+                                    this._promptInput.style.height = this._promptInput.scrollHeight + 'px';
                                 }
                             }}
-                            defaultValue={''}
-                            placeholder={'Enter a custom prompt here (optional)'}
+                            defaultValue=""
+                            placeholder="Enter a custom prompt here (optional)"
                         />
-                        <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generateVariations}/>
+                        <DocCreatorMenuButton icon={'arrows-rotate'} styles={'border'} function={this.generateVariations} />
                     </div>
-                        <nav className="options‑menu">
-                            <label className="menu‑item switch">
-                                <input type="checkbox" checked={this.fireflyOptions.useStyleRef} 
-                                    onChange={(e) => runInAction(() => { this.fireflyOptions.useStyleRef = e.target.checked })}
-                                />
-                                <span className="slider round"></span>
-                                <span className="firefly-option-label">Use template as style guide</span>
-                            </label>
-                            <div className="menu‑item">
-                                <span className="firefly-option-label">Variations</span>
-                                <input type="range" id="variations" 
-                                    min="1" 
-                                    max="5" 
-                                    value={this.fireflyOptions.numVariations} 
-                                    onChange={(e) => runInAction(() => { this.fireflyOptions.numVariations = Number(e.target.value) })}
-                                />
-                                <span className="value" id="varVal">{this.fireflyOptions.numVariations}</span>
-                            </div>
-                            <div className="menu‑item">
-                                <span className="firefly-option-label">Temperature</span>
-                                <input type="range" id="temperature" 
-                                    min="1" 
-                                    max="100" 
-                                    value={this.fireflyOptions.temperature} 
-                                    onChange={(e) => runInAction(() => { this.fireflyOptions.temperature = Number(e.target.value) })}
-                                />
-                                <span className="value" id="tempVal">{this.fireflyOptions.temperature}</span>
-                            </div>
-                        </nav>
+                    <nav className="options‑menu">
+                        <label className="menu‑item switch">
+                            <input type="checkbox" checked={this._fireflyOptions.useStyleRef} onChange={action(e => (this._fireflyOptions.useStyleRef = e.target.checked))} />
+                            <span className="slider round"></span>
+                            <span className="firefly-option-label">Use template as style guide</span>
+                        </label>
+                        <div className="menu‑item">
+                            <span className="firefly-option-label">Variations</span>
+                            <input type="range" id="variations" min="1" max="5" value={this._fireflyOptions.numVariations} onChange={action(e => (this._fireflyOptions.numVariations = Number(e.target.value)))} />
+                            <span className="value" id="varVal">
+                                {this._fireflyOptions.numVariations}
+                            </span>
+                        </div>
+                        <div className="menu‑item">
+                            <span className="firefly-option-label">Temperature</span>
+                            <input type="range" id="temperature" min="1" max="100" value={this._fireflyOptions.temperature} onChange={action(e => (this._fireflyOptions.temperature = Number(e.target.value)))} />
+                            <span className="value" id="tempVal">
+                                {this._fireflyOptions.temperature}
+                            </span>
+                        </div>
+                    </nav>
                 </div>
             </div>
-        )
+        );
     }
 }
 
@@ -144,11 +125,9 @@ interface TemplateEditingWindowProps {
 
 @observer
 export class TemplateEditingWindow extends ObservableReactComponent<TemplateEditingWindowProps> {
-
     private disposers: { [name: string]: IDisposer } = {};
 
-    @observable private previewWindow: HTMLDivElement | null = null;
-
+    @observable private _previewWindow: HTMLDivElement | null = null;
     @observable _variationsTabOpen: boolean = false;
 
     constructor(props: TemplateEditingWindowProps) {
@@ -157,83 +136,85 @@ export class TemplateEditingWindow extends ObservableReactComponent<TemplateEdit
     }
 
     componentDidMount(): void {
-        this.disposers.windowDimensions = reaction(() => 
-            this._props.menu._resizing, 
-            () => { this.forceUpdate() }, 
+        this.disposers.windowDimensions = reaction(
+            () => this._props.menu._resizing,
+            () => this.forceUpdate(),
             { fireImmediately: true }
-            );
+        );
     }
 
     componentWillUnmount() {
         Object.values(this.disposers).forEach(disposer => disposer?.());
     }
 
-    setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
-        this.previewWindow = node;
-    }
-
     @action setVariationTab = (open: boolean) => {
         this._variationsTabOpen = open;
-        if (this.previewWindow && open) {
-            this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * .6);
-        } else if (this.previewWindow && !open) {
-            this.previewWindow.style.height = String(Number(this.previewWindow.clientHeight) * 5/3);
+        if (this._previewWindow && open) {
+            this._previewWindow.style.height = String(Number(this._previewWindow.clientHeight) * 0.6);
+        } else if (this._previewWindow && !open) {
+            this._previewWindow.style.height = String((Number(this._previewWindow.clientHeight) * 5) / 3);
         }
-    }
-   
-    get renderedDocPreview(){
-        const doc: Doc = this._props.template.getRenderedDoc() as unknown as Doc;
+    };
 
+    previewPanelWidth = () => this._previewWindow?.clientWidth ?? 500;
+    previewPanelHeight = () => this._previewWindow?.clientHeight ?? 500;
+    previewScreenToLocalXf = () => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1);
+    get renderedDocPreview() {
+        const doc = this._props.template.getRenderedDoc();
         return (
-            <div className="docCreatorMenu-expanded-template-preview" ref={this.setContainerRef}>
-                {this.previewWindow ? <DocumentView
-                    Document={doc}
-                    isContentActive={emptyFunction}
-                    addDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    removeDocument={returnFalse}
-                    PanelWidth={() => this.previewWindow?.clientWidth ?? 500}
-                    PanelHeight={() => this.previewWindow?.clientHeight ?? 500}
-                    ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)}
-                    renderDepth={5}
-                    whenChildContentsActiveChanged={emptyFunction}
-                    focus={emptyFunction}
-                    styleProvider={DefaultStyleProvider}
-                    addDocTab={DocumentViewInternal.addDocTabFunc}
-                    pinToPres={() => undefined}
-                    childFilters={returnEmptyFilter}
-                    childFiltersByRanges={returnEmptyFilter}
-                    searchFilterDocs={returnEmptyDoclist}
-                    fitContentsToBox={returnFalse}
-                    fitWidth={returnFalse}
-                /> : null}
+            <div className="docCreatorMenu-expanded-template-preview" ref={action((node: HTMLDivElement | null) => (this._previewWindow = node))}>
+                {this._previewWindow && doc ? (
+                    <DocumentView
+                        Document={doc}
+                        isContentActive={emptyFunction}
+                        addDocument={returnFalse}
+                        moveDocument={returnFalse}
+                        removeDocument={returnFalse}
+                        PanelWidth={this.previewPanelWidth}
+                        PanelHeight={this.previewPanelHeight}
+                        ScreenToLocalTransform={this.previewScreenToLocalXf}
+                        renderDepth={5}
+                        whenChildContentsActiveChanged={emptyFunction}
+                        focus={emptyFunction}
+                        styleProvider={DefaultStyleProvider}
+                        addDocTab={DocumentViewInternal.addDocTabFunc}
+                        pinToPres={emptyFunction}
+                        childFilters={returnEmptyFilter}
+                        childFiltersByRanges={returnEmptyFilter}
+                        searchFilterDocs={returnEmptyDoclist}
+                        // fitContentsToBox={returnFalse}
+                        // fitWidth={returnFalse}
+                    />
+                ) : null}
             </div>
-        )
+        );
     }
 
+    expandFunc = () => {
+        // if (this._props.template === this._props.menu._selectedTemplate) {
+        //     this._props.menu.updateRenderedPreviewCollection(this._props.template);
+        // }
+        this._props.menu.setExpandedView(undefined);
+    };
+    lastFunc = () => {
+        this._props.menu.editLastTemplate();
+        this.forceUpdate();
+    };
+    variationFunc = () => this.setVariationTab(!this._variationsTabOpen);
     render() {
         return (
-            <div className='docCreatorMenu-templates-view'>
+            <div className="docCreatorMenu-templates-view">
                 <div className="docCreatorMenu-expanded-template-preview">
-                    <div className="top-panel"/>
+                    <div className="top-panel" />
                     {this.renderedDocPreview}
-                    {this._variationsTabOpen ? <FireflyVariationsTab
-                        menu={this._props.menu}
-                        template={this._props.template}
-                    />
-                     : null}
+                    {this._variationsTabOpen ? <FireflyVariationsTab menu={this._props.menu} template={this._props.template} /> : null}
                     <div className="right-buttons-panel">
-                        <DocCreatorMenuButton icon={'minimize'} function={() => {
-                                // if (this._props.template === this._props.menu._selectedTemplate) {
-                                //     this._props.menu.updateRenderedPreviewCollection(this._props.template);
-                                // }
-                                this._props.menu.setExpandedView(undefined);
-                            }}/>
-                        <DocCreatorMenuButton icon={'lightbulb'} function={() => this.setVariationTab(!this._variationsTabOpen)}/>
-                        <DocCreatorMenuButton icon={'arrow-rotate-backward'} function={() => { this._props.menu.editLastTemplate(); this.forceUpdate(); }}/>
+                        <DocCreatorMenuButton icon="minimize" function={this.expandFunc} />
+                        <DocCreatorMenuButton icon="lightbulb" function={this.variationFunc} />
+                        <DocCreatorMenuButton icon="arrow-rotate-backward" function={this.lastFunc} />
                     </div>
                 </div>
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx
index a4da54392..f0e20837c 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateMenuFieldOptions.tsx
@@ -1,11 +1,11 @@
-import { makeObservable, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { ObservableReactComponent } from "../../../../ObservableReactComponent";
-import { Col, DocCreatorMenu } from "../DocCreatorMenu";
-import React from "react";
-import { Conditional, TemplateManager } from "../Backend/TemplateManager";
-import { TemplateFieldType, TemplateFieldSize } from "../TemplateBackend";
-import { DocCreatorMenuButton } from "./DocCreatorMenuButton";
+import { action, makeObservable, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import { ObservableReactComponent } from '../../../../ObservableReactComponent';
+import { Col, DocCreatorMenu } from '../DocCreatorMenu';
+import React from 'react';
+import { Conditional, TemplateManager } from '../Backend/TemplateManager';
+import { TemplateFieldType, TemplateFieldSize } from '../TemplateBackend';
+import { DocCreatorMenuButton } from './DocCreatorMenuButton';
 
 interface TemplateMenuFieldOptionsProps {
     menu: DocCreatorMenu;
@@ -14,7 +14,6 @@ interface TemplateMenuFieldOptionsProps {
 
 @observer
 export class TemplateMenuFieldOptions extends ObservableReactComponent<TemplateMenuFieldOptionsProps> {
-
     @observable _collapsedCols: string[] = []; //any columns whose options panels are hidden
 
     constructor(props: TemplateMenuFieldOptionsProps) {
@@ -34,160 +33,153 @@ export class TemplateMenuFieldOptions extends ObservableReactComponent<TemplateM
                 condition: '',
                 target: 'Own',
                 attribute: '',
-                value: ''
+                value: '',
             });
         }
         return this._newCondCache[title];
     };
 
     conditionForm = (title: string, parameters?: Conditional, empty: boolean = false) => {
-
-        const contentFieldTitles = this._props.menu.fieldsInfos.filter(field => field.type !== TemplateFieldType.DATA).map(field => field.title).concat('Template');
+        const contentFieldTitles = this._props.menu.fieldsInfos
+            .filter(field => field.type !== TemplateFieldType.DATA)
+            .map(field => field.title)
+            .concat('Template');
         const params: Conditional = this.getParams(title, parameters);
 
         return (
-        <div className='form'>
-            <div className='form-row'>
-                <div className='form-row-plain-text'>If</div>
-                <div className='form-row-plain-text'>{title}</div>
-                <div className="operator-options-dropdown">
-                    <span className="operator-dropdown-current">{params.operator ?? '='}</span>
-                    <div className='operator-dropdown-option' onPointerDown={() => {params.operator = '='}}>{'='}</div>
-                </div>
-                <input
-                    className="form-row-textarea"
-                    onChange={e => runInAction(() => { params.condition = e.target.value })}
-                    placeholder='value'
-                    value={params.condition}
-                />
-                <div className='form-row-plain-text'>then</div>
-                <div className="operator-options-dropdown">
-                    <span className="operator-dropdown-current">{params.target ?? 'Own'}</span>
-                    {contentFieldTitles.map((fieldTitle, i) => 
-                        <div className='operator-dropdown-option' key={i} onPointerDown={() => {params.target = fieldTitle}}>{fieldTitle === title ? 'Own' : fieldTitle}</div>
-                    )}
+            <div className="form">
+                <div className="form-row">
+                    <div className="form-row-plain-text">If</div>
+                    <div className="form-row-plain-text">{title}</div>
+                    <div className="operator-options-dropdown">
+                        <span className="operator-dropdown-current">{params.operator ?? '='}</span>
+                        <div className="operator-dropdown-option" onPointerDown={() => (params.operator = '=')}>
+                            {'='}
+                        </div>
+                    </div>
+                    <input className="form-row-textarea" onChange={action(e => (params.condition = e.target.value))} placeholder="value" value={params.condition} />
+                    <div className="form-row-plain-text">then</div>
+                    <div className="operator-options-dropdown">
+                        <span className="operator-dropdown-current">{params.target ?? 'Own'}</span>
+                        {contentFieldTitles.map((fieldTitle, i) => (
+                            <div className="operator-dropdown-option" key={i} onPointerDown={() => (params.target = fieldTitle)}>
+                                {fieldTitle === title ? 'Own' : fieldTitle}
+                            </div>
+                        ))}
+                    </div>
+                    <input className="form-row-textarea" onChange={action(e => (params.attribute = e.target.value))} placeholder="attribute" value={params.attribute} />
+                    <div className="form-row-plain-text">{'becomes'}</div>
+                    <input className="form-row-textarea" onChange={action(e => (params.value = e.target.value))} placeholder="value" value={params.value} />
                 </div>
-                <input
-                    className="form-row-textarea"
-                    onChange={e => runInAction(() => { params.attribute = e.target.value })}
-                    placeholder='attribute'
-                    value={params.attribute}
-                />
-                <div className='form-row-plain-text'>{'becomes'}</div>
-                <input
-                    className="form-row-textarea"
-                    onChange={e => runInAction(() => { params.value = e.target.value })}
-                    placeholder='value'
-                    value={params.value}
-                />
+                {empty ? (
+                    <DocCreatorMenuButton
+                        icon={'plus'}
+                        styles={'float-right border'}
+                        function={() => {
+                            this._newCondCache[title] = observable<Conditional>({
+                                field: title,
+                                operator: '=',
+                                condition: '',
+                                target: 'Own',
+                                attribute: '',
+                                value: '',
+                            });
+                            this._props.templateManager.addFieldCondition(title, params);
+                        }}
+                    />
+                ) : (
+                    <DocCreatorMenuButton icon={'minus'} styles={'float-right border'} function={() => this._props.templateManager.removeFieldCondition(title, params)} />
+                )}
             </div>
-            {empty ? 
-            <DocCreatorMenuButton icon={'plus'} styles={'float-right border'} function={() => {
-                this._newCondCache[title] = observable<Conditional>({
-                    field: title,
-                    operator: '=',
-                    condition: '',
-                    target: 'Own',
-                    attribute: '',
-                    value: ''
-                });
-                this._props.templateManager.addFieldCondition(title, params);
-            }}/>
-            :
-            <DocCreatorMenuButton icon={'minus'} styles={'float-right border'} function={() => this._props.templateManager.removeFieldCondition(title, params)}/>
-            }
-        </div>
-        )
-    }
+        );
+    };
 
     fieldPanel = (field: Col, id: number) => (
         <div className="field-panel" key={id}>
-            <div className="top-bar" onPointerDown={e => this._props.menu.setUpButtonClick(e, runInAction(() => () => {
-                if (this._collapsedCols.includes(field.title)) {
-                    this._collapsedCols = this._collapsedCols.filter(col => col !== field.title);
-                } else {
-                    this._collapsedCols.push(field.title);
-                }
-            }))}>
+            <div
+                className="top-bar"
+                onPointerDown={e =>
+                    this._props.menu.setUpButtonClick(
+                        e,
+                        action(() => {
+                            if (this._collapsedCols.includes(field.title)) {
+                                this._collapsedCols = this._collapsedCols.filter(col => col !== field.title);
+                            } else {
+                                this._collapsedCols.push(field.title);
+                            }
+                        })
+                    )
+                }>
                 <span className="field-title">{`${field.title} Field`}</span>
-                <DocCreatorMenuButton icon={'minus'} styles={'no-margin absolute-right'} function={() => this._props.menu.removeField(field)}/>
+                <DocCreatorMenuButton icon={'minus'} styles={'no-margin absolute-right'} function={() => this._props.menu.removeField(field)} />
             </div>
-            { this._collapsedCols.includes(field.title) ? null :
+            {this._collapsedCols.includes(field.title) ? null : (
                 <>
-                <div className="opts-bar">
-                    <div className="opt-box">
-                        <div className="top-bar"> Title </div>
-                        <textarea className="content" style={{ width: '100%', height: 'calc(100% - 20px)' }} value={field.title} placeholder={'Enter title'} onChange={e => this._props.menu.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' 
-                                : field.type === TemplateFieldType.DATA ? 'Data Field' 
-                                : ''
-                            }</span>
-                            <div className="bubbles">
-                                <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.TEXT)} />
-                                <div className="text">Text</div>
-                                <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.VISUAL)} />
-                                <div className="text">File</div>
-                                <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.DATA)} />
-                                <div className="text">Data</div>
-                            </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)' }} value={field.title} placeholder={'Enter title'} onChange={e => this._props.menu.setColTitle(field, e.target.value)} />
                         </div>
-                    </div>
-                </div>
-                { field.type === TemplateFieldType.DATA ? null :
-                    (<> 
-                        <div className="sizes-box">
-                            <div className="top-bar"> Valid Sizes </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' : field.type === TemplateFieldType.DATA ? 'Data Field' : ''}</span>
                                 <div className="bubbles">
-                                    {Object.values(TemplateFieldSize).map(size => (
-                                        <div key={field + size}>
-                                            <input className="bubble" type="checkbox" name="type" checked={field.sizes.includes(size)} onChange={e => this._props.menu.modifyColSizes(field, size, e.target.checked)} />
-                                            <div className="text">{size}</div>
-                                        </div>
-                                    ))}
+                                    <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.TEXT)} />
+                                    <div className="text">Text</div>
+                                    <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.VISUAL)} />
+                                    <div className="text">File</div>
+                                    <input className="bubble" type="radio" name="type" onClick={() => this._props.menu.setColType(field, TemplateFieldType.DATA)} />
+                                    <div className="text">Data</div>
                                 </div>
                             </div>
                         </div>
-                        <div className="desc-box">
-                            <div className="top-bar"> Prompt </div>
-                            <textarea
-                                className="content"
-                                onChange={e => this._props.menu.setColDesc(field, e.target.value)}
-                                defaultValue={field.desc === this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc}
-                                placeholder={this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description/prompt to help with template generation.'}
-                            />
-                        </div> 
-                    </>)
-                }
-                <div className="conditionals-section">
-                    <span className="conditionals-title">Conditional Logic</span>
-                    {this.conditionForm(field.title, undefined, true)}
-                    {this._props.templateManager.conditionalFieldLogic[field.title]?.map(condition => this.conditionForm(condition.field, condition))}
-                </div>
+                    </div>
+                    {field.type === TemplateFieldType.DATA ? null : (
+                        <>
+                            <div className="sizes-box">
+                                <div className="top-bar"> Valid Sizes </div>
+                                <div className="content">
+                                    <div className="bubbles">
+                                        {Object.values(TemplateFieldSize).map(size => (
+                                            <div key={field + size}>
+                                                <input className="bubble" type="checkbox" name="type" checked={field.sizes.includes(size)} onChange={e => this._props.menu.modifyColSizes(field, size, e.target.checked)} />
+                                                <div className="text">{size}</div>
+                                            </div>
+                                        ))}
+                                    </div>
+                                </div>
+                            </div>
+                            <div className="desc-box">
+                                <div className="top-bar"> Prompt </div>
+                                <textarea
+                                    className="content"
+                                    onChange={e => this._props.menu.setColDesc(field, e.target.value)}
+                                    defaultValue={field.desc === this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc}
+                                    placeholder={this._props.menu._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description/prompt to help with template generation.'}
+                                />
+                            </div>
+                        </>
+                    )}
+                    <div className="conditionals-section">
+                        <span className="conditionals-title">Conditional Logic</span>
+                        {this.conditionForm(field.title, undefined, true)}
+                        {this._props.templateManager._conditionalFieldLogic[field.title]?.map(condition => this.conditionForm(condition.field, condition))}
+                    </div>
                 </>
-            }
+            )}
         </div>
     );
 
-
-
     render() {
         return (
             <div className="docCreatorMenu-dashboard-view">
                 <div className="topbar">
-                    <DocCreatorMenuButton icon={'plus'} function={this._props.menu.addField}/>
-                    <DocCreatorMenuButton icon={'arrow-left'} styles={'float-right'} function={() => runInAction(() => (this._props.menu._menuContent = 'templates'))}/>
+                    <DocCreatorMenuButton icon="plus" function={this._props.menu.addField} />
+                    <DocCreatorMenuButton icon="arrow-left" styles="float-right" function={action(() => (this._props.menu._menuContent = 'templates'))} />
                 </div>
                 <div className="panels-container">{this._props.menu.fieldsInfos.map((field, i) => this.fieldPanel(field, i))}</div>
             </div>
         );
     }
-
-
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
index dc4c35789..7d02fff12 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplatePreviewBox.tsx
@@ -1,35 +1,30 @@
-import { Colors } from "@dash/components/src";
-import { FontAwesomeIcon} from "@fortawesome/react-fontawesome";
-import { Template } from "../Template";
-import { makeObservable, observable } from "mobx";
-import React from "react";
-import { ObservableReactComponent } from "../../../../ObservableReactComponent";
-import { DocCreatorMenu } from "../DocCreatorMenu";
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { DocumentView } from "../../../DocumentView";
-import { emptyFunction } from "../../../../../../Utils";
-import { returnEmptyFilter, returnFalse } from "../../../../../../ClientUtils";
-import { Transform } from "../../../../../util/Transform";
-import { DefaultStyleProvider } from "../../../../StyleProvider";
-import { Doc, returnEmptyDoclist } from "../../../../../../fields/Doc";
-import { observer } from "mobx-react";
+import { Colors } from '@dash/components/src';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Template } from '../Template';
+import { action, makeObservable, observable } from 'mobx';
+import React from 'react';
+import { ObservableReactComponent } from '../../../../ObservableReactComponent';
+import { DocCreatorMenu } from '../DocCreatorMenu';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { DocumentView } from '../../../DocumentView';
+import { emptyFunction } from '../../../../../../Utils';
+import { returnEmptyFilter, returnFalse } from '../../../../../../ClientUtils';
+import { Transform } from '../../../../../util/Transform';
+import { DefaultStyleProvider } from '../../../../StyleProvider';
+import { Doc, returnEmptyDoclist } from '../../../../../../fields/Doc';
+import { observer } from 'mobx-react';
 
 export interface TemplatePreviewBoxProps {
     template: Template;
     menu: DocCreatorMenu; // eslint-disable-next-line
-    leftButtonOpts?: [icon: IconProp, func: (...args: any) => void] // eslint-disable-next-line
-    rightButtonOpts?: [icon: IconProp, func: (...args: any) => void]
+    leftButtonOpts?: [icon: IconProp, func: (...args: any) => void]; // eslint-disable-next-line
+    rightButtonOpts?: [icon: IconProp, func: (...args: any) => void];
 }
 
 @observer
 export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreviewBoxProps> {
-
     @observable private previewWindow: HTMLDivElement | null = null;
 
-    setContainerRef: React.LegacyRef<HTMLDivElement> = (node) => {
-        this.previewWindow = node;
-    }
-
     constructor(props: TemplatePreviewBoxProps) {
         super(props);
         makeObservable(this);
@@ -39,6 +34,10 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview
         return this._props.template.getRenderedDoc() as Doc;
     }
 
+    docPanelWidth = () => this.previewWindow?.clientWidth ?? this._props.menu._menuDimensions.height * 0.3;
+    docPanelHeight = () => this.previewWindow?.clientHeight ?? this._props.menu._menuDimensions.height * 0.3;
+    docScreenToLocalXf = () => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1);
+
     render() {
         const template = this._props.template;
 
@@ -46,49 +45,45 @@ export class TemplatePreviewBox extends ObservableReactComponent<TemplatePreview
             <div
                 key={template.title}
                 className="docCreatorMenu-preview-window"
-                ref={this.setContainerRef}
+                ref={action((node: HTMLDivElement | null) => (this.previewWindow = node))}
                 style={{
                     border: this._props.menu._selectedTemplate === template ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
                     boxShadow: this._props.menu._selectedTemplate === template ? `0 0 15px rgba(68, 118, 247, .8)` : '',
                 }}
-                onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.menu.updateSelectedTemplate(template))}
-                >
-                    { this._props.leftButtonOpts ? 
-                    <button
-                        className="option-button left"
-                        onPointerDown={e =>
-                            this._props.menu.setUpButtonClick(e, () => this._props.leftButtonOpts![1](template))
-                        }>
+                onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.menu.updateSelectedTemplate(template))}>
+                {this._props.leftButtonOpts ? (
+                    <button className="option-button left" onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.leftButtonOpts![1](template))}>
                         <FontAwesomeIcon icon={this._props.leftButtonOpts![0]} color="white" />
-                    </button> : null
-                    }
-                    { this._props.rightButtonOpts ? 
+                    </button>
+                ) : null}
+                {this._props.rightButtonOpts ? (
                     <button className="option-button right" onPointerDown={e => this._props.menu.setUpButtonClick(e, () => this._props.rightButtonOpts![1](template))}>
                         <FontAwesomeIcon icon={this._props.rightButtonOpts![0]} color="white" />
-                    </button> : null }
-                    <DocumentView
-                        Document={this.doc}
-                        isContentActive={emptyFunction} // !!! should be return false
-                        addDocument={returnFalse}
-                        moveDocument={returnFalse}
-                        removeDocument={returnFalse}
-                        PanelWidth={() => this.previewWindow?.clientWidth ?? this._props.menu._menuDimensions.height * .3}
-                        PanelHeight={() => this.previewWindow?.clientHeight ?? this._props.menu._menuDimensions.height * .3}
-                        ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)}
-                        renderDepth={1}
-                        whenChildContentsActiveChanged={emptyFunction}
-                        focus={emptyFunction}
-                        styleProvider={DefaultStyleProvider}
-                        addDocTab={this._props.menu._props.addDocTab}
-                        pinToPres={() => undefined}
-                        childFilters={returnEmptyFilter}
-                        childFiltersByRanges={returnEmptyFilter}
-                        searchFilterDocs={returnEmptyDoclist}
-                        fitContentsToBox={returnFalse}
-                        fitWidth={returnFalse}
-                        hideDecorations={true}
-                    />
+                    </button>
+                ) : null}
+                <DocumentView
+                    Document={this.doc}
+                    isContentActive={emptyFunction} // !!! should be return false
+                    addDocument={returnFalse}
+                    moveDocument={returnFalse}
+                    removeDocument={returnFalse}
+                    PanelWidth={this.docPanelWidth}
+                    PanelHeight={this.docPanelHeight}
+                    ScreenToLocalTransform={this.docScreenToLocalXf}
+                    renderDepth={1}
+                    whenChildContentsActiveChanged={emptyFunction}
+                    focus={emptyFunction}
+                    styleProvider={DefaultStyleProvider}
+                    addDocTab={this._props.menu._props.addDocTab}
+                    pinToPres={emptyFunction}
+                    childFilters={returnEmptyFilter}
+                    childFiltersByRanges={returnEmptyFilter}
+                    searchFilterDocs={returnEmptyDoclist}
+                    // fitContentsToBox={returnFalse}
+                    // fitWidth={returnFalse}
+                    hideDecorations={true}
+                />
             </div>
-        )
+        );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx
index f281f770e..9222d7349 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Menu/TemplateRenderPreviewWindow.tsx
@@ -1,20 +1,20 @@
-import { action, computed, makeObservable, observable, runInAction } from "mobx";
-import { observer } from "mobx-react";
-import { ObservableReactComponent } from "../../../../ObservableReactComponent";
-import { DocCreatorMenu, LayoutType } from "../DocCreatorMenu";
-import React from "react";
-import { IconProp } from "@fortawesome/fontawesome-svg-core";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { setupMoveUpEvents, returnFalse, returnEmptyFilter } from "../../../../../../ClientUtils";
-import { emptyFunction } from "../../../../../../Utils";
-import { undoable } from "../../../../../util/UndoManager";
-import ReactLoading from "react-loading";
-import { Doc, NumListCast, returnEmptyDoclist } from "../../../../../../fields/Doc";
-import { StrCast } from "../../../../../../fields/Types";
-import { DefaultStyleProvider } from "../../../../StyleProvider";
-import { DocumentView } from "../../../DocumentView";
-import { Transform } from "../../../../../util/Transform";
-import { Docs, DocumentOptions } from "../../../../../documents/Documents";
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { observer } from 'mobx-react';
+import { ObservableReactComponent } from '../../../../ObservableReactComponent';
+import { DocCreatorMenu, LayoutType } from '../DocCreatorMenu';
+import React from 'react';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { setupMoveUpEvents, returnFalse, returnEmptyFilter } from '../../../../../../ClientUtils';
+import { emptyFunction } from '../../../../../../Utils';
+import { undoable } from '../../../../../util/UndoManager';
+import ReactLoading from 'react-loading';
+import { Doc, NumListCast, returnEmptyDoclist } from '../../../../../../fields/Doc';
+import { NumCast, StrCast } from '../../../../../../fields/Types';
+import { DefaultStyleProvider } from '../../../../StyleProvider';
+import { DocumentView } from '../../../DocumentView';
+import { Transform } from '../../../../../util/Transform';
+import { Docs, DocumentOptions } from '../../../../../documents/Documents';
 
 interface TemplatesRenderPreviewWindowProps {
     menu: DocCreatorMenu;
@@ -22,8 +22,7 @@ interface TemplatesRenderPreviewWindowProps {
 
 @observer
 export class TemplatesRenderPreviewWindow extends ObservableReactComponent<TemplatesRenderPreviewWindowProps> {
-
-    @observable private _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 0, repeat: 0 };
+    @observable private _layout: { type: LayoutType; yMargin: number; xMargin: number; columns?: number; repeat: number } = { type: LayoutType.FREEFORM, yMargin: 10, xMargin: 10, columns: 1, repeat: 0 };
 
     @observable private renderedDocs: Doc[] = [];
     @observable private renderedDocCollection: Doc | undefined = undefined;
@@ -62,7 +61,7 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ
     @computed get columnsCount() {
         switch (this._layout.type) {
             case LayoutType.FREEFORM:
-                return this._layout.columns ?? 0;
+                return this._layout.columns ?? 1;
             case LayoutType.CAROUSEL3D:
                 return 3;
             default:
@@ -130,28 +129,27 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ
         const { horizontalSpan, verticalSpan } = this.previewInfo;
         collection._height = verticalSpan;
         collection._width = horizontalSpan;
-
-        const columns: number = this._layout.columns ?? this.columnsCount;
-        const xGap: number = this._layout.xMargin;
-        const yGap: number = this._layout.yMargin;
-        const startX: number = -Number(collection._width) / 2;
-        const startY: number = -Number(collection._height) / 2;
-        const docHeight: number = Number(docs[0]._height);
-        const docWidth: number = Number(docs[0]._width);
-
-        if (columns === 0 || docs.length === 0) {
-            return;
-        }
-
-        let i: number = 0;
-        let docsChanged: number = 0;
-        let curX: number = startX;
-        let curY: number = startY;
+        collection.layout_fitWidth = true;
+        collection.freeform_fitContentsToBox = true;
+
+        const columns = (this._layout.columns ?? this.columnsCount) || 1;
+        const xGap = this._layout.xMargin;
+        const yGap = this._layout.yMargin;
+        const startX = -collection._width / 2;
+        const startY = -collection._height / 2;
+        const docHeight = NumCast(docs[0]?._height);
+        const docWidth = NumCast(docs[0]?._width);
+
+        let i = 0;
+        let docsChanged = 0;
+        let curX = startX;
+        let curY = startY;
 
         while (docsChanged < docs.length) {
             while (i < columns && docsChanged < docs.length) {
                 docs[docsChanged].x = curX;
                 docs[docsChanged].y = curY;
+                docs[docsChanged].layout_fitWidth = false;
                 curX += docWidth + xGap;
                 ++docsChanged;
                 ++i;
@@ -164,8 +162,8 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ
 
     @computed
     get previewInfo() {
-        const docHeight: number = Number(this.renderedDocs[0]._height);
-        const docWidth: number = Number(this.renderedDocs[0]._width);
+        const docHeight = NumCast(this.renderedDocs[0]?._height);
+        const docWidth = NumCast(this.renderedDocs[0]?._width);
         const layout = this._layout;
         return {
             docHeight: docHeight,
@@ -201,6 +199,10 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ
         }
     }
 
+    layoutPanelWidth = () => this._props.menu._menuDimensions.width - 80;
+    layoutPanelHeight = () => this._props.menu._menuDimensions.height - 105;
+    layoutScreenToLocalXf = () => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1);
+
     layoutPreviewContents = action(() => {
         return this.loading ? (
             <div className="docCreatorMenu-layout-preview-window-wrapper loading">
@@ -216,20 +218,18 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ
                     addDocument={returnFalse}
                     moveDocument={returnFalse}
                     removeDocument={returnFalse}
-                    PanelWidth={() => this._props.menu._menuDimensions.width - 80}
-                    PanelHeight={() => this._props.menu._menuDimensions.height - 105}
-                    ScreenToLocalTransform={() => new Transform(-this._props.menu._pageX - 5, -this._props.menu._pageY - 35, 1)}
+                    PanelWidth={this.layoutPanelWidth}
+                    PanelHeight={this.layoutPanelHeight}
+                    ScreenToLocalTransform={this.layoutScreenToLocalXf}
                     renderDepth={5}
                     whenChildContentsActiveChanged={emptyFunction}
                     focus={emptyFunction}
                     styleProvider={DefaultStyleProvider}
                     addDocTab={this._props.menu._props.addDocTab}
-                    pinToPres={() => undefined}
+                    pinToPres={emptyFunction}
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     searchFilterDocs={returnEmptyDoclist}
-                    fitContentsToBox={returnFalse}
-                    fitWidth={returnFalse}
                     hideDecorations={true}
                 />
             </div>
@@ -273,7 +273,6 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ
     };
 
     get optionsMenuContents() {
-
         const repeatOptions = [0, 1, 2, 3, 4, 5];
 
         return (
@@ -341,5 +340,7 @@ export class TemplatesRenderPreviewWindow extends ObservableReactComponent<Templ
         );
     }
 
-    render() { return this.optionsMenuContents }
-}
\ No newline at end of file
+    render() {
+        return this.optionsMenuContents;
+    }
+}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
index 7591740e0..e2a2a3c1c 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/Template.ts
@@ -9,7 +9,7 @@ import { TemplateDataField } from './TemplateFieldTypes/DataField';
 export class Template {
     _mainField: DynamicField;
 
-    private dataFields: TemplateDataField[] = [];
+    private _dataFields: TemplateDataField[] = [];
 
     /**
      * A Template can be created from a description of its fields (FieldSettings) or from a DynamicField
@@ -20,37 +20,13 @@ export class Template {
         this._mainField = definition instanceof DynamicField ? definition : this.setupMainField(definition);
     }
 
-    get childFields(): TemplateField[] {
-        return this._mainField?.getSubfields ?? [];
-    }
-    get allFields(): TemplateField[] {
-        return this._mainField?.getAllSubfields ?? [];
-    }
-    get contentFields(): TemplateField[] {
-        return this.allFields.filter(field => field.isContentField);
-    }
-    get doc() {
-        return this._mainField?.renderedDoc;
-    }
-    get title() {
-        return this._mainField?.getTitle();
-    }
-
-    get descriptionSummary(): string {
-        let summary: string = '';
-        this.contentFields.forEach(field => {
-            summary += `--- Field #${field.getID} (title: ${field.getTitle()}): ${field.getDescription ?? ''} ---`;
-        });
-        return summary;
-    }
-
-    get compiledContent(): string {
-        let summary: string = '';
-        this.contentFields.forEach(field => {
-            summary += `--- Field #${field.getID} (title: ${field.getTitle()}): ${field.getContent() ?? ''} ---`;
-        });
-        return summary;
-    }
+    get childFields()        { return this._mainField?.getSubfields ?? []; } // prettier-ignore
+    get allFields()          { return this._mainField?.getAllSubfields ?? []; } // prettier-ignore
+    get contentFields()      { return this.allFields.filter(field => field.isContentField); } // prettier-ignore
+    get doc()                { return this._mainField?.renderedDoc; } // prettier-ignore
+    get title()              { return this._mainField?.getTitle(); } // prettier-ignore
+    get descriptionSummary() { return this.contentFields.map(f => `--- Field #${f.getID} (title: ${f.getTitle()}): ${f.getDescription ?? ''} ---`).join(); } // prettier-ignore
+    get compiledContent()    { return this.contentFields.map(f => `--- Field #${f.getID} (title: ${f.getTitle()}): ${f.getContent() ?? ''} ---`).join(); } // prettier-ignore
 
     cleanup = () => {
         //dispose each subfields disposers, etc.
@@ -58,61 +34,56 @@ export class Template {
 
     clone = (withContent: boolean = false) => {
         const clone = new Template(this._mainField?.makeClone(undefined, withContent) ?? TemplateLayouts.BasicSettings);
-        this.dataFields.forEach(field => clone.addDataField(field.title));
+        this._dataFields.forEach(field => clone.addDataField(field.title));
         return clone;
     };
 
     getRenderedDoc = () => this.doc;
 
-    getFieldByID = (id: number): TemplateField => this.allFields.filter(field => field.getID === id)[0];
+    getFieldByID = (id: number): TemplateField | undefined => this.allFields.filter(field => field.getID === id)[0];
 
-    getFieldByTitle = (title: string) => [...this.allFields, ...this.dataFields].filter(field => field.getTitle() === title)[0];
+    getFieldByTitle = (title: string) => [...this.allFields, ...this._dataFields].filter(field => field.getTitle() === title)[0];
 
     setupMainField = (templateInfo: FieldSettings) => TemplateField.CreateField(templateInfo, 1, undefined) as DynamicField;
 
     assignColToField = (fieldID: number, col: Col) => {
         const field = this.getFieldByID(fieldID);
-        field.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT);
-        field.setTitle(col.title);
-    }
+        field?.setContent(col.defaultContent ?? '', col.type === TemplateFieldType.VISUAL ? ViewType.IMG : ViewType.TEXT);
+        field?.setTitle(col.title);
+    };
 
-    addDataField = (title: string, content?: string) => {
-        this.dataFields.push(new TemplateDataField(title, content));
-    }
+    addDataField = (title: string, content?: string) => this._dataFields.push(new TemplateDataField(title, content));
 
-    removeDataField = (title: string) => {
-        this.dataFields = this.dataFields.filter(field => !(field.title === title));
-    }
+    removeDataField = (title: string) => (this._dataFields = this._dataFields.filter(field => field.title !== title));
 
-    isValidTemplate = (cols: Col[]) => {
-        const maxMatches = this.maxMatches(this.getMatches(cols));
-        return maxMatches === this.contentFields.length && this.title !== 'template_framework';
-    };
+    isValidTemplate = (cols: Col[]) => this.title !== 'template_framework' && this.maxMatches(this.getMatches(cols)) === this.contentFields.length;
 
     applyConditionalLogicToField = (field: TemplateField | TemplateDataField, logic: Record<string, Conditional[]>) => {
         if (field instanceof DynamicField) return;
-        const fieldStatements: Conditional[] = logic[field.getTitle()];
-        const content = field.getContent()
-        fieldStatements && fieldStatements.forEach(statement => {
+        const fieldStatements = logic[field.getTitle()];
+        const content = field.getContent();
+        fieldStatements?.forEach(statement => {
             if (content === statement.condition) {
                 if (statement.target === 'Template') {
-                    this._mainField.renderedDoc![statement.attribute] = statement.value;
-                    Object.assign(this._mainField.settings.opts, {[statement.attribute]: statement.value});
+                    if (this._mainField.renderedDoc) {
+                        this._mainField.renderedDoc[statement.attribute] = statement.value;
+                        Object.assign(this._mainField.settings.opts, { [statement.attribute]: statement.value });
+                    }
                 } else {
-                    const targetField: TemplateField = this.getFieldByTitle(statement.target) as TemplateField;
-                    if (targetField) {
-                        targetField.renderedDoc![statement.attribute] = statement.value;
-                        Object.assign(targetField.settings.opts, {[statement.attribute]: statement.value});
-                    } 
+                    const targetField = this.getFieldByTitle(statement.target);
+                    if (targetField instanceof TemplateField && targetField.renderedDoc) {
+                        targetField.renderedDoc[statement.attribute] = statement.value;
+                        Object.assign(targetField.settings.opts, { [statement.attribute]: statement.value });
+                    }
                 }
             }
-        })
-    }
+        });
+    };
 
     applyConditionalLogic = (logic: Record<string, Conditional[]>) => {
-        const fields: (TemplateField | TemplateDataField)[] = [...this.allFields, ...this.dataFields];
-        fields.forEach(field => this.applyConditionalLogicToField(field, logic));
-    }
+        [...this.allFields, ...this._dataFields].forEach(field => this.applyConditionalLogicToField(field, logic));
+        return this.getRenderedDoc();
+    };
 
     setImageAsBackground(url: string, makeTransparent: boolean = false) {
         const fieldSettings: FieldSettings = {
@@ -120,15 +91,15 @@ export class Template {
             br: [1, 1],
             opts: {},
             viewType: ViewType.IMG,
-        }
+        };
 
-        const field: TemplateField = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField);
+        const field = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField);
         field.setContent(url);
 
         if (makeTransparent) {
-            this.allFields.forEach(field => {
-                field.updateDocSetting('backgroundColor', 'transparent');
-                field.updateDocSetting('borderWidth', '0');
+            this.allFields.forEach(aField => {
+                aField.updateDocSetting('backgroundColor', 'transparent');
+                aField.updateDocSetting('borderWidth', '0');
             });
         }
 
@@ -138,7 +109,7 @@ export class Template {
     /**
      * This function is just a hack for now to get around weird document icon stuff (specifically it misses the background)
      */
-     setMatteBackground(makeTransparent: boolean = false) {
+    setMatteBackground(makeTransparent: boolean = false) {
         if (this._mainField.hasBackground) {
             return;
         }
@@ -146,18 +117,17 @@ export class Template {
         const fieldSettings: FieldSettings = {
             tl: [-1, -1],
             br: [1, 1],
-            opts: {backgroundColor: String(this._mainField.renderedDoc!.backgroundColor)},
+            opts: { backgroundColor: String(this._mainField.renderedDoc!.backgroundColor) },
             viewType: ViewType.TEXT,
-        }
+        };
 
-        const field: TemplateField = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField);
+        const field = TemplateField.CreateField(fieldSettings, Math.random() * 100 + 100, this._mainField);
 
-        if (makeTransparent) {
-            this.allFields.forEach(field => {
-                field.updateDocSetting('backgroundColor', 'transparent');
-                field.updateDocSetting('borderWidth', '0');
+        makeTransparent &&
+            this.allFields.forEach(aField => {
+                aField.updateDocSetting('backgroundColor', 'transparent');
+                aField.updateDocSetting('borderWidth', '0');
             });
-        }
 
         this._mainField.makeBackgroundField(field);
     }
@@ -167,9 +137,7 @@ export class Template {
 
         if (cols.length !== numFields) return [];
 
-        const matches: number[][] = Array(numFields)
-            .fill([])
-            .map(() => []);
+        const matches = Array<number[]>(numFields);
 
         this.contentFields.forEach((field, i) => (matches[i] = field.matches(cols)));
 
@@ -180,8 +148,8 @@ export class Template {
         if (matches.length === 0) return 0;
 
         const fieldsCt = this.contentFields.length;
-        const used: boolean[] = Array(fieldsCt).fill(false);
-        const mt: number[] = Array(fieldsCt).fill(-1);
+        const used = Array<boolean>(fieldsCt).fill(false);
+        const mt = Array<number>(fieldsCt).fill(-1);
 
         const augmentingPath = (v: number): boolean => {
             if (!used[v]) {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
index c100d1cce..091ef834a 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/TemplateFieldTypes/TemplateField.ts
@@ -67,13 +67,13 @@ export abstract class TemplateField {
     setTitle = (title: string) => {
         this._title = title;
         this.settings.title = title;
-        if (this._renderDoc) this._renderDoc.title = title
+        if (this._renderDoc) this._renderDoc.title = title;
     };
     getTitle = () => this._title;
 
     updateDocSetting(setting: string, newVal: string) {
         if (this._renderDoc) this._renderDoc[setting] = newVal;
-        const settings: {[s: string]: string } = {[setting]: newVal}
+        const settings: { [s: string]: string } = { [setting]: newVal };
         Object.assign(this.settings.opts, settings);
     }
 
@@ -162,7 +162,7 @@ export enum ViewType {
     DEC = 'decoration',
     IMG = 'image',
     TEXT = 'text',
-    NONE = 'none'
+    NONE = 'none',
 }
 
 export type FieldDimensions = {
@@ -170,4 +170,3 @@ export type FieldDimensions = {
     height: number;
     coord: { x: number; y: number };
 };
-
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 30fc44f62..8ed59c6e1 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -175,6 +175,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width), height: this.layoutDoc._height }),
             ({ nativeSize, width, height }) => {
                 if (!this.layoutDoc._layout_nativeDimEditable || !height || this.layoutDoc.layout_resetNativeDim) {
+                    if (!this._props.TemplateDataDocument) this.layoutDoc._nativeWidth = this.layoutDoc._nativeHeight = undefined;
                     this.layoutDoc.layout_resetNativeDim = undefined; // reset dimensions of templates rendered with content or if image changes.  afterwards, remove this flag.
                     this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
                 }
-- 
cgit v1.2.3-70-g09d2

