From 77d76c1a7e88b3ad4b72c4cd6aa4d4772042b65d Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Mon, 8 Jul 2024 14:07:26 -0400
Subject: method in dataviz for identifying valid templates (WIP); option added
 to contextmenu to create docs from dataViz

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 23 +++++++++++++++++++++++
 1 file changed, 23 insertions(+)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 4d5f15a3e..1cb1ad6af 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -32,6 +32,8 @@ import { Histogram } from './components/Histogram';
 import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
+import { LinkManager } from '../../../util/LinkManager';
+import { Collection } from 'mongoose';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -431,6 +433,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const options = cm.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
         optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
+        optionItems.push({ description: `Create documents`, event: () => this.askGPT(), icon: 'table-cells' });
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
     };
 
@@ -445,6 +448,26 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         GPTPopup.Instance.generateDataAnalysis();
     });
 
+    getPossibleTemplates = (): Doc[] => {
+        const linkedDocs: Doc[] = LinkManager.Instance.getAllRelatedLinks(this.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)));
+        const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'collection');
+        const isColumnTitle = (title: string): boolean => {
+            const colTitles: string[] = Object.keys(this.records[0]);
+            for (let i = 0; i < colTitles.length; ++i){
+                if (colTitles[i] === title) return true;
+            }
+            return false;
+        }
+        const isValidTemplate = (collection: Doc) => {
+            const childDocs = DocListCast(collection[Doc.LayoutFieldKey(collection)]);
+            for (let i = 0; i < childDocs.length; ++i){
+                if (isColumnTitle(String(childDocs[i].title))) return true;
+            }
+            return false;
+        }
+        return linkedCollections.filter(col => isValidTemplate(col));
+    }
+
     /**
      * creates a new dataviz document filter from this one
      *      it appears to the right of this document, with the
-- 
cgit v1.2.3-70-g09d2


From 0c68bfcf4041558edf94f2898fc0531f24433351 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Tue, 9 Jul 2024 03:12:28 -0400
Subject: started doccreatormenu for dataviz

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  16 ++-
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     |  12 +++
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 113 +++++++++++++++++++++
 3 files changed, 138 insertions(+), 3 deletions(-)
 create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
 create mode 100644 src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 1cb1ad6af..dae535ba6 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -34,6 +34,7 @@ import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
 import { LinkManager } from '../../../util/LinkManager';
 import { Collection } from 'mongoose';
+import { DocCreatorMenu } from './DocCreatorMenu';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -433,7 +434,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const options = cm.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
         optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
-        optionItems.push({ description: `Create documents`, event: () => this.askGPT(), icon: 'table-cells' });
+        optionItems.push({ description: `Create documents`, event: () => DocCreatorMenu.Instance.displayMenu(200, 200), icon: 'table-cells' });
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
     };
 
@@ -450,16 +451,22 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     getPossibleTemplates = (): Doc[] => {
         const linkedDocs: Doc[] = LinkManager.Instance.getAllRelatedLinks(this.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)));
-        const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'collection');
+        const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'config').filter(doc => DocCast(doc.annotationOn).type === 'collection');
+        console.log('cols: ' + linkedCollections)
         const isColumnTitle = (title: string): boolean => {
             const colTitles: string[] = Object.keys(this.records[0]);
+            console.log('titles: ' + colTitles)
             for (let i = 0; i < colTitles.length; ++i){
-                if (colTitles[i] === title) return true;
+                if (colTitles[i] === title) {
+                    console.log(true);
+                    return true;
+                }
             }
             return false;
         }
         const isValidTemplate = (collection: Doc) => {
             const childDocs = DocListCast(collection[Doc.LayoutFieldKey(collection)]);
+            console.log('childDocs of col: ' + childDocs)
             for (let i = 0; i < childDocs.length; ++i){
                 if (isColumnTitle(String(childDocs[i].title))) return true;
             }
@@ -511,6 +518,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             // displays how to get data into the DataVizBox if its empty
             <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command (ctrl + p) to bring the data table to your canvas.</div>
         ) : (
+            <div>
+                <DocCreatorMenu/>
             <div
                 className="dataViz-box"
                 onPointerDown={this.marqueeDown}
@@ -587,6 +596,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     />
                 )}
             </div>
+            </div>
         );
     }
 }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
new file mode 100644
index 000000000..fea5a6f59
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -0,0 +1,12 @@
+.docCreatorMenu-cont {
+    position: absolute;
+    display: flex;
+    min-width: 300px;
+    min-height: 400px;
+    z-index: 6;
+    box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
+    flex-direction: column;
+    background: whitesmoke;
+    color: black;
+    border-radius: 3px;
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
new file mode 100644
index 000000000..d5c2bc227
--- /dev/null
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -0,0 +1,113 @@
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { SnappingManager } from '../../../util/SnappingManager';
+import './DocCreatorMenu.scss';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
+
+@observer
+export class DocCreatorMenu extends ObservableReactComponent<{}> {
+
+    static Instance: DocCreatorMenu;
+
+    private _reactionDisposer?: IReactionDisposer;
+
+    @observable _pageX: number = 0;
+    @observable _pageY: number = 0;
+    @observable _display: boolean = false;
+
+    @observable _yRelativeToTop: boolean = true;
+    @observable _selectedIndex = -1;
+
+    @observable _mouseX: number = -1;
+    @observable _mouseY: number = -1;
+    @observable _shouldDisplay: boolean = false;
+
+    constructor(props: any) {
+        super(props);
+        makeObservable(this);
+        DocCreatorMenu.Instance = this;
+    }
+
+    @action
+    onPointerDown = (e: PointerEvent) => {
+        this._mouseX = e.clientX;
+        this._mouseY = e.clientY;
+    };
+    @action
+    onPointerUp = (e: PointerEvent) => {
+        if (e.button !== 2 && !e.ctrlKey) return;
+        const curX = e.clientX;
+        const curY = e.clientY;
+        if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) {
+            this._shouldDisplay = false;
+        }
+
+        if (this._shouldDisplay) {
+            this._display = true;
+        }
+    };
+
+    componentDidMount() {
+        document.addEventListener('pointerdown', this.onPointerDown, true);
+        document.addEventListener('pointerup', this.onPointerUp);
+    }
+
+    componentWillUnmount() {
+        document.removeEventListener('pointerdown', this.onPointerDown, true);
+        document.removeEventListener('pointerup', this.onPointerUp);
+        this._reactionDisposer?.();
+    }
+
+    static readonly buffer = 20;
+    static readonly width = 300;
+    static readonly height = 400;
+    get pageX() {
+        return this._pageX + DocCreatorMenu.width > window.innerWidth - DocCreatorMenu.buffer ? window.innerWidth - DocCreatorMenu.buffer - DocCreatorMenu.width : Math.max(0, this._pageX);
+    }
+
+    get pageY() {
+        return this._pageY + DocCreatorMenu.height > window.innerHeight - DocCreatorMenu.buffer ? window.innerHeight - DocCreatorMenu.buffer - DocCreatorMenu.height : Math.max(0, this._pageY);
+    }
+
+    @action
+    displayMenu = (x: number, y: number) => {
+        // maxX and maxY will change if the UI/font size changes, but will work for any amount
+        // of items added to the menu
+
+        this._pageX = x;
+        this._pageY = y;
+        this._shouldDisplay = true;
+    };
+
+    @action
+    closeMenu = () => {
+        const wasOpen = this._display;
+        this._display = false;
+        this._shouldDisplay = false;
+        return wasOpen;
+    };
+
+    render() {
+        return (
+            <div
+                className="docCreatorMenu-cont"
+                style={{
+                    display: '',
+                    left: this.pageX,
+                    ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
+                    background: SnappingManager.userBackgroundColor,
+                    color: SnappingManager.userColor,
+                }}>
+            {!this._shouldDisplay ? undefined : 
+            <div>hi hi</div>
+            
+            
+            }
+            </div>
+            )
+        }
+    
+
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From b669b8c82efa9217cf065e670c7f6111b096f20d Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 10 Jul 2024 02:11:46 -0400
Subject: docCreatorMenu progress

---
 src/client/views/ContextMenu.tsx                   |   1 -
 src/client/views/MainView.tsx                      |   3 +
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   9 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     |  59 ++++++++--
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 119 +++++++++++++++++----
 5 files changed, 157 insertions(+), 34 deletions(-)

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

diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index d784a14b8..de985263d 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -269,7 +269,6 @@ export class ContextMenu extends ObservableReactComponent<{}> {
                 // if (this._searchString.startsWith(this._defaultPrefix)) {
                 this._defaultItem?.(this._searchString.substring(this._defaultPrefix.length));
             }
-            this.closeMenu();
             e.preventDefault();
             e.stopPropagation();
         }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 5b9ddbb88..926e5fd55 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -76,6 +76,7 @@ import { PresBox } from './nodes/trails';
 import { AnchorMenu } from './pdf/AnchorMenu';
 import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 import { TopBar } from './topbar/TopBar';
+import { DocCreatorMenu } from './nodes/DataVizBox/DocCreatorMenu';
 
 const { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } = require('./global/globalCssVariables.module.scss'); // prettier-ignore
 const _global = (window /* browser */ || global) /* node */ as any;
@@ -278,6 +279,7 @@ export class MainView extends ObservableReactComponent<{}> {
 
         library.add(
             ...[
+                fa.faWindowMaximize,
                 fa.faGift,
                 fa.faLockOpen,
                 fa.faSort,
@@ -1093,6 +1095,7 @@ export class MainView extends ObservableReactComponent<{}> {
                 <PreviewCursor />
                 <TaskCompletionBox />
                 <ContextMenu />
+                <DocCreatorMenu/>
                 <ImageLabelHandler />
                 <AnchorMenu />
                 <MapAnchorMenu />
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index dae535ba6..1e0a72a91 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -429,12 +429,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this.layoutDoc.dataViz_filterSelection = !this.layoutDoc.dataViz_filterSelection;
     };
 
-    specificContextMenu = (): void => {
+    specificContextMenu = (x: number, y: number): void => {
         const cm = ContextMenu.Instance;
         const options = cm.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
         optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
-        optionItems.push({ description: `Create documents`, event: () => DocCreatorMenu.Instance.displayMenu(200, 200), icon: 'table-cells' });
+        optionItems.push({ description: `Create documents`, event: () => DocCreatorMenu.Instance.toggleDisplay(x, y), icon: 'table-cells' });
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
     };
 
@@ -518,8 +518,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             // displays how to get data into the DataVizBox if its empty
             <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command (ctrl + p) to bring the data table to your canvas.</div>
         ) : (
-            <div>
-                <DocCreatorMenu/>
             <div
                 className="dataViz-box"
                 onPointerDown={this.marqueeDown}
@@ -530,7 +528,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     transform: `scale(${scale})`,
                     position: 'absolute',
                 }}
-                onContextMenu={this.specificContextMenu}
+                onContextMenu={(e) => this.specificContextMenu(e.pageX, e.pageY)}
                 onWheel={e => e.stopPropagation()}
                 ref={this._mainCont}>
                 <div className="datatype-button">
@@ -596,7 +594,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     />
                 )}
             </div>
-            </div>
         );
     }
 }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index fea5a6f59..43053ee34 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -1,12 +1,55 @@
 .docCreatorMenu-cont {
     position: absolute;
-    display: flex;
-    min-width: 300px;
-    min-height: 400px;
-    z-index: 6;
-    box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
-    flex-direction: column;
-    background: whitesmoke;
-    color: black;
+    z-index: 100000;
+    // box-shadow: 0px 3px 4px rgba(0, 0, 0, 30%);
+    // background: whitesmoke; 
+    // color: black;
     border-radius: 3px;
+}   
+
+.docCreatorMenu-menu {
+    display: flex;
+    flex-direction: row;
+}
+
+.docCreatorMenu-menu-button {
+    width: 30px;
+    height: 30px;
+    background: whitesmoke; 
+    background-color: rgb(34, 34, 37);
+    border-radius: 5px;
+    border: 1px solid whitesmoke;
+    padding: 0px;
+    font-size: 14px;
+     
+    &.right{
+        margin-left: auto;
+        font-size: 12px;
+    }
+
+    &.close-menu {
+        margin-left: 0px;
+        font-size: 12px;
+    }
+
+    &.options {
+        margin-left: 0px;
+    }
+
+    &:hover {
+        background-color: rgb(60, 60, 65);
+    }
+}
+
+.docCreatorMenu-menu-hr{
+    margin-top: 0px;
+    margin-bottom: 0px;
+}
+
+.docCreatorMenu-content {
+    margin: 5px;
+    width: calc(100% - 10px);
+    height: calc(100% - 51px);
+    border: 1px solid whitesmoke;
+    border-radius: 5px;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index d5c2bc227..dbd385047 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -1,16 +1,25 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { SnappingManager } from '../../../util/SnappingManager';
 import './DocCreatorMenu.scss';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
+import { IconButton, Size } from 'browndash-components';
+import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
+import { emptyFunction } from '../../../../Utils';
+import { undoable } from '../../../util/UndoManager';
+import { DragManager } from '../../../util/DragManager';
+import { DocumentView } from '../DocumentView';
+import { dropActionType } from '../../../util/DropActionTypes';
 
 @observer
 export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     static Instance: DocCreatorMenu;
 
+    private _ref: HTMLDivElement | null = null;
+
     private _reactionDisposer?: IReactionDisposer;
 
     @observable _pageX: number = 0;
@@ -22,8 +31,12 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     @observable _mouseX: number = -1;
     @observable _mouseY: number = -1;
+    @observable _startPos: {x: number, y: number} | undefined = undefined;
     @observable _shouldDisplay: boolean = false;
 
+    @observable _menuContent: 'templates' | 'options' = 'templates';
+    @observable _dragging: boolean = false;
+
     constructor(props: any) {
         super(props);
         makeObservable(this);
@@ -35,6 +48,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         this._mouseX = e.clientX;
         this._mouseY = e.clientY;
     };
+
     @action
     onPointerUp = (e: PointerEvent) => {
         if (e.button !== 2 && !e.ctrlKey) return;
@@ -43,10 +57,6 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         if (Math.abs(this._mouseX - curX) > 1 || Math.abs(this._mouseY - curY) > 1) {
             this._shouldDisplay = false;
         }
-
-        if (this._shouldDisplay) {
-            this._display = true;
-        }
     };
 
     componentDidMount() {
@@ -67,18 +77,18 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         return this._pageX + DocCreatorMenu.width > window.innerWidth - DocCreatorMenu.buffer ? window.innerWidth - DocCreatorMenu.buffer - DocCreatorMenu.width : Math.max(0, this._pageX);
     }
 
-    get pageY() {
-        return this._pageY + DocCreatorMenu.height > window.innerHeight - DocCreatorMenu.buffer ? window.innerHeight - DocCreatorMenu.buffer - DocCreatorMenu.height : Math.max(0, this._pageY);
-    }
-
     @action
-    displayMenu = (x: number, y: number) => {
+    toggleDisplay = (x: number, y: number) => {
         // maxX and maxY will change if the UI/font size changes, but will work for any amount
         // of items added to the menu
-
-        this._pageX = x;
-        this._pageY = y;
-        this._shouldDisplay = true;
+        if (this._shouldDisplay) {
+            this._shouldDisplay = false;
+        } else {
+            this._pageX = x;
+            console.log(y);
+            this._pageY = y;
+            this._shouldDisplay = true;
+        }
     };
 
     @action
@@ -89,21 +99,92 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         return wasOpen;
     };
 
+    @action
+    onPointerMove = (e: any) => {
+        if (this._dragging){
+            this._pageX = e.pageX - (this._startPos?.x ?? 0);
+            this._pageY = e.pageY - (this._startPos?.y ?? 0);
+        }
+    }
+
+    setDragStart = () => {
+
+    }
+
     render() {
         return (
             <div
                 className="docCreatorMenu-cont"
+                ref={r => this._ref = r}
                 style={{
                     display: '',
-                    left: this.pageX,
-                    ...(this._yRelativeToTop ? { top: Math.max(0, this.pageY) } : { bottom: this.pageY }),
+                    left: this._pageX,
+                    top: this._pageY,
+                    width: this._shouldDisplay ? 300 : 0,
+                    height: this._shouldDisplay ? 400 : 0,
                     background: SnappingManager.userBackgroundColor,
                     color: SnappingManager.userColor,
                 }}>
             {!this._shouldDisplay ? undefined : 
-            <div>hi hi</div>
-            
-            
+                <>
+                    <div 
+                    className='docCreatorMenu-menu'
+                    onPointerDown={e =>
+                        setupMoveUpEvents(
+                            this,
+                            e,
+                            (e) => {
+                                this._dragging = true;
+                                this._startPos = {x: 0, y: 0};
+                                this._startPos.x = e.pageX - (this._ref?.getBoundingClientRect().left ?? 0);
+                                this._startPos.y = e.pageY - (this._ref?.getBoundingClientRect().top ?? 0);
+                                return true;
+                            },
+                            emptyFunction,
+                            undoable(clickEv => {
+                                clickEv.stopPropagation();
+                            }, 'open actions menu')
+                        )
+                    
+                    }
+                    onPointerMove={e => this.onPointerMove(e)}
+                    onPointerUp={() => this._dragging = false}
+                    >  
+                        <button 
+                            className='docCreatorMenu-menu-button'
+                            onPointerDown={e =>
+                                setupMoveUpEvents(
+                                    this,
+                                    e,
+                                    returnFalse,
+                                    emptyFunction,
+                                    undoable(clickEv => {
+                                        clickEv.stopPropagation();
+                                        runInAction(() => this._menuContent = this._menuContent === 'templates' ? 'options' : 'templates');
+                                    }, 'open actions menu')
+                                )
+                            }
+                        >
+                            <FontAwesomeIcon icon={this._menuContent === 'templates' ? 'table-cells' : 'window-maximize'}/>
+                        </button>
+                        <button className='docCreatorMenu-menu-button close-menu'>
+                            <FontAwesomeIcon icon={'minus'}/>
+                        </button>
+                        <button className='docCreatorMenu-menu-button right'>
+                            <FontAwesomeIcon icon={'plus'}/>
+                        </button>
+                    </div>
+                    <hr className='docCreatorMenu-menu-hr'/>
+                    <div className='docCreatorMenu-content'>
+                        {this._menuContent === 'templates' ? 
+                            <div>
+                            </div>
+                            :
+                            <div>
+                            </div>
+                        }
+                    </div>
+                </>
             }
             </div>
             )
-- 
cgit v1.2.3-70-g09d2


From 77bf838bebc813d476788fabed6bc7bcbf8197b0 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 10 Jul 2024 14:36:39 -0400
Subject: images displaying

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 13 ++--
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     | 54 +++++++++++++--
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 80 +++++++++++++---------
 3 files changed, 107 insertions(+), 40 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 1e0a72a91..f75ba9700 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -429,12 +429,18 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this.layoutDoc.dataViz_filterSelection = !this.layoutDoc.dataViz_filterSelection;
     };
 
+    openDocCreatorMenu = (x: number, y: number) => {
+        DocCreatorMenu.Instance.toggleDisplay(x, y);
+        DocCreatorMenu.Instance.setDataViz(this);
+        DocCreatorMenu.Instance.setTemplateDocs(this.getPossibleTemplates());
+    }
+
     specificContextMenu = (x: number, y: number): void => {
         const cm = ContextMenu.Instance;
         const options = cm.findByDescription('Options...');
         const optionItems = options && 'subitems' in options ? options.subitems : [];
         optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
-        optionItems.push({ description: `Create documents`, event: () => DocCreatorMenu.Instance.toggleDisplay(x, y), icon: 'table-cells' });
+        optionItems.push({ description: `Create documents`, event: () => this.openDocCreatorMenu(x, y), icon: 'table-cells' });
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
     };
 
@@ -452,13 +458,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     getPossibleTemplates = (): Doc[] => {
         const linkedDocs: Doc[] = LinkManager.Instance.getAllRelatedLinks(this.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)));
         const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'config').filter(doc => DocCast(doc.annotationOn).type === 'collection');
-        console.log('cols: ' + linkedCollections)
         const isColumnTitle = (title: string): boolean => {
             const colTitles: string[] = Object.keys(this.records[0]);
             console.log('titles: ' + colTitles)
             for (let i = 0; i < colTitles.length; ++i){
                 if (colTitles[i] === title) {
-                    console.log(true);
+                    console.log(true); 
                     return true;
                 }
             }
@@ -472,7 +477,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             }
             return false;
         }
-        return linkedCollections.filter(col => isValidTemplate(col));
+        return linkedDocs;
     }
 
     /**
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index dc0f99cbd..43d4ed7e5 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -23,13 +23,13 @@
     font-size: 14px;
      
     &.right{
-        margin-left: auto;
+        margin-left: 0px;
         font-size: 12px;
     }
 
     &.close-menu {
-        margin-left: 0px;
         font-size: 12px;
+        margin-left: auto;
     }
 
     &.options {
@@ -46,7 +46,7 @@
     margin-bottom: 0px;
 }
 
-.docCreatorMenu-preview-content {
+.docCreatorMenu-preview-container {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
     overflow-y: scroll;
@@ -64,4 +64,50 @@
         border: 1px solid whitesmoke;
         border-radius: 5px;
     }
-}
\ No newline at end of file
+}
+
+.docCreatorMenu-options-container {
+    display: flex;
+    overflow-y: scroll;
+    margin: 5px;
+    width: calc(100% - 10px);
+    height: calc(100% - 51px);
+    border: 1px solid whitesmoke;
+    border-radius: 5px;
+
+    .docCreatorMenu-dropdown-button{
+        display: flex;
+        width: 120px;
+        height: 30px;
+        background: whitesmoke; 
+        background-color: rgb(34, 34, 37);
+        border-radius: 5px;
+        border: 1px solid whitesmoke;
+        padding: 0px;
+        font-size: 13px;
+        margin: 10px;
+        align-content: center;
+        justify-content: center;
+        text-transform: uppercase;
+
+        // &:hover .docCreatorMenu-dropdown-content{
+        //     display: block;
+        // }
+    }
+
+    .docCreatorMenu-dropdown-content {
+        display: none;
+        position: absolute;
+        min-width: 100px;
+        z-index: 1;
+
+        .docCreatorMenu-dropdown-option{
+            display: block;
+        }
+
+    }
+
+
+
+}
+
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 520dd9c2d..1976ecad2 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -1,17 +1,18 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, makeObservable, observable, runInAction } from 'mobx';
+import { IReactionDisposer, action, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { SnappingManager } from '../../../util/SnappingManager';
-import './DocCreatorMenu.scss';
-import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { IconButton, Size } from 'browndash-components';
 import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
+import { Doc } from '../../../../fields/Doc';
+import { DocCast, ImageCast } from '../../../../fields/Types';
+import { ImageField } from '../../../../fields/URLField';
 import { emptyFunction } from '../../../../Utils';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { undoable } from '../../../util/UndoManager';
-import { DragManager } from '../../../util/DragManager';
+import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { DocumentView } from '../DocumentView';
-import { dropActionType } from '../../../util/DropActionTypes';
+import { DataVizBox } from './DataVizBox';
+import './DocCreatorMenu.scss';
 
 @observer
 export class DocCreatorMenu extends ObservableReactComponent<{}> {
@@ -20,22 +21,21 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     private _ref: HTMLDivElement | null = null;
 
-    private _reactionDisposer?: IReactionDisposer;
+    @observable _templateDocs: Doc[] = [];
+    @observable _icons : (ImageField|undefined)[] = [];
 
     @observable _pageX: number = 0;
     @observable _pageY: number = 0;
     @observable _display: boolean = false;
 
-    @observable _yRelativeToTop: boolean = true;
-    @observable _selectedIndex = -1;
-
     @observable _mouseX: number = -1;
     @observable _mouseY: number = -1;
-    @observable _startPos: {x: number, y: number} | undefined = undefined;
+    @observable _startPos?: {x: number, y: number};
     @observable _shouldDisplay: boolean = false;
 
     @observable _menuContent: 'templates' | 'options' = 'templates';
     @observable _dragging: boolean = false;
+    @observable _dataViz?: DataVizBox;
 
     constructor(props: any) {
         super(props);
@@ -43,6 +43,11 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         DocCreatorMenu.Instance = this;
     }
 
+    @action setDataViz = (dataViz: DataVizBox) => { this._dataViz = dataViz };
+    @action setTemplateDocs = (docs: Doc[]) => {
+         this._templateDocs = docs.map(doc => doc.annotationOn ? DocCast(doc.annotationOn):doc);
+    };
+
     @action
     onPointerDown = (e: PointerEvent) => {
         this._mouseX = e.clientX;
@@ -59,22 +64,19 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         }
     };
 
+    _disposer: IReactionDisposer|undefined;
     componentDidMount() {
         document.addEventListener('pointerdown', this.onPointerDown, true);
         document.addEventListener('pointerup', this.onPointerUp);
+        this._disposer = reaction(() => this._templateDocs.slice(),
+            async(docs) => this._icons =  (await Promise.all(docs.map(doc => this.getIcon(doc))))
+            , {fireImmediately: true});
     }
 
     componentWillUnmount() {
+        this._disposer?.();
         document.removeEventListener('pointerdown', this.onPointerDown, true);
         document.removeEventListener('pointerup', this.onPointerUp);
-        this._reactionDisposer?.();
-    }
-
-    static readonly buffer = 20;
-    static readonly width = 300;
-    static readonly height = 400;
-    get pageX() {
-        return this._pageX + DocCreatorMenu.width > window.innerWidth - DocCreatorMenu.buffer ? window.innerWidth - DocCreatorMenu.buffer - DocCreatorMenu.width : Math.max(0, this._pageX);
     }
 
     @action
@@ -105,23 +107,37 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         }
     }
 
+    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;
+    }
+
     get templatesPreviewContents(){
-        const tempArray = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
         return (
-            <div className='docCreatorMenu-preview-content'>
-                {tempArray.map(i => 
-                    <div
-                        className='docCreatorMenu-preview-window'
-                    >
-                    
-                    </div>)}
+            <div className='docCreatorMenu-preview-container'>
+                {this._icons.filter(url => url).map(url => 
+                    <div className='docCreatorMenu-preview-window'>
+                        <img src={url!.url.href.replace(".png", "_s.png")} />
+                    </div>)
+                }
             </div>
         );
     }
 
     get optionsMenuContents(){
         return (
-            <div></div>
+            <div className='docCreatorMenu-options-container'>
+                <div className="docCreatorMenu-dropdown-button">Dropdown</div>
+                <div className="docCreatorMenu-dropdown-content">
+                    <div className="docCreatorMenu-dropdown-option">Link 1</div>
+                    <div className="docCreatorMenu-dropdown-option">Link 1</div>
+                    <div className="docCreatorMenu-dropdown-option">Link 1</div>
+                </div>
+            </div>
         );
     }
 
@@ -181,6 +197,9 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         >
                             <FontAwesomeIcon icon={this._menuContent === 'templates' ? 'table-cells' : 'window-maximize'}/>
                         </button>
+                        <button className='docCreatorMenu-menu-button right'>
+                            <FontAwesomeIcon icon={'plus'}/>
+                        </button>
                         <button 
                             className='docCreatorMenu-menu-button close-menu'
                             onPointerDown={e =>
@@ -198,9 +217,6 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         >
                             <FontAwesomeIcon icon={'minus'}/>
                         </button>
-                        <button className='docCreatorMenu-menu-button right'>
-                            <FontAwesomeIcon icon={'plus'}/>
-                        </button>
                     </div>
                     <hr className='docCreatorMenu-menu-hr'/>
                     {this._menuContent === 'templates' ? this.templatesPreviewContents : this.optionsMenuContents}
-- 
cgit v1.2.3-70-g09d2


From 54fbd0815e93da8aff7a601676a90aa0faf87de8 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Thu, 11 Jul 2024 13:19:04 -0400
Subject: collection filtering works

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx      | 5 ++---
 src/client/views/nodes/DataVizBox/DocCreatorMenu.scss | 5 +++--
 src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx  | 7 ++++---
 3 files changed, 9 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index f75ba9700..c43177c51 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -457,10 +457,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     getPossibleTemplates = (): Doc[] => {
         const linkedDocs: Doc[] = LinkManager.Instance.getAllRelatedLinks(this.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)));
-        const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'config').filter(doc => DocCast(doc.annotationOn).type === 'collection');
+        const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'config').filter(doc => DocCast(doc.annotationOn).type === 'collection').map(doc => DocCast(doc.annotationOn));
         const isColumnTitle = (title: string): boolean => {
             const colTitles: string[] = Object.keys(this.records[0]);
-            console.log('titles: ' + colTitles)
             for (let i = 0; i < colTitles.length; ++i){
                 if (colTitles[i] === title) {
                     console.log(true); 
@@ -477,7 +476,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             }
             return false;
         }
-        return linkedDocs;
+        return linkedCollections.filter(col => isValidTemplate(col));
     }
 
     /**
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index ba20c9179..4944f3071 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -66,9 +66,9 @@
         height: 125px;
         margin-top: 10px;
         margin-left: 10px;
-        border: 1px solid whitesmoke;
+        border: 1px solid rgb(163, 163, 163);
         border-radius: 5px;
-        box-shadow: 5px 5px rgb(34, 34, 37);
+        box-shadow: 5px 5px rgb(29, 29, 31);
 
         &:hover{
             background-color: rgb(72, 72, 73);
@@ -134,6 +134,7 @@
             overflow-y: scroll;
             -ms-overflow-style: none; 
             scrollbar-width: none;
+            border-bottom: solid 1px whitesmoke;
     
             .docCreatorMenu-dropdown-option{
                 display: flex;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 356172b6a..3ebf97570 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -150,10 +150,11 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     get optionsMenuContents(){
 
-        const layoutOption = (option: 'stacked' | 'grid' | 'row' | 'column' | 'custom') => {
+        const layoutOption = (option: 'stacked' | 'grid' | 'row' | 'column' | 'custom', optStyle?: {}) => {
             return (
                 <div 
                     className="docCreatorMenu-dropdown-option" 
+                    style={optStyle}
                     onPointerDown={e =>
                         setupMoveUpEvents(this, e, returnFalse, emptyFunction,
                             undoable(clickEv => {
@@ -176,7 +177,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         {layoutOption('grid')}
                         {layoutOption('row')}
                         {layoutOption('column')}
-                        {layoutOption('custom')}
+                        {layoutOption('custom', {borderBottom: `0px`})}
                     </div>
                 </div>
                 <div className='docCreatorMenu-layout-preview-window'>
@@ -239,7 +240,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                                 )
                             }
                         >
-                            <FontAwesomeIcon icon={this._menuContent === 'templates' ? 'table-cells' : 'window-maximize'}/>
+                            <FontAwesomeIcon icon={this._menuContent === 'templates' ? 'bars' : 'magnifying-glass'}/>
                         </button>
                         <button className='docCreatorMenu-menu-button right'>
                             <FontAwesomeIcon icon={'plus'}/>
-- 
cgit v1.2.3-70-g09d2


From 1447bae0e24016f0c4fde772235d3e60f2b3555c Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Fri, 12 Jul 2024 14:44:48 -0400
Subject: basic doc creation from templates working

---
 .../collections/collectionSchema/SchemaRowBox.tsx  |  3 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 51 +++++++++++++---
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 68 ++++++++++++++++------
 3 files changed, 95 insertions(+), 27 deletions(-)

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

diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index 2410b2793..72f5d8c25 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -24,6 +24,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { infoState } from '../collectionFreeForm/CollectionFreeFormInfoState';
 import { TbShieldX } from 'react-icons/tb';
+import { DocumentType } from '../../../documents/DocumentTypes';
 
 interface SchemaRowBoxProps extends FieldViewProps {
     rowIndex: number;
@@ -77,7 +78,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
             icon: 'minus',
         });
         const childDocs = DocListCast(this.Document[Doc.LayoutFieldKey(this.Document)])
-        if (this.Document.type === 'collection' && childDocs.length) {
+        if (childDocs.length) {
                 ContextMenu.Instance.addItem({
                 description: this.Document._childrenSharedWithSchema ? 'Remove children from schema' : 'Add children to schema',
                 event: () => {
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index c43177c51..86a9db79c 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -5,14 +5,14 @@ import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
 import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
+import { ClientUtils, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
 import { emptyFunction } from '../../../../Utils';
-import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
-import { TraceMobx } from '../../../../fields/util';
+import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
 import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
@@ -33,8 +33,10 @@ import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
 import { LinkManager } from '../../../util/LinkManager';
-import { Collection } from 'mongoose';
-import { DocCreatorMenu } from './DocCreatorMenu';
+import { DataVizTemplateInfo, DocCreatorMenu } from './DocCreatorMenu';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
+import { PrefetchProxy } from '../../../../fields/Proxy';
+import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -149,6 +151,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         // }
         // return func() ?? false;
     };
+
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
         const visibleAnchor = AnchorMenu.Instance.GetAnchor?.(undefined, addAsAnnotation);
         const anchor = !pinProps
@@ -457,7 +460,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     getPossibleTemplates = (): Doc[] => {
         const linkedDocs: Doc[] = LinkManager.Instance.getAllRelatedLinks(this.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)));
-        const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'config').filter(doc => DocCast(doc.annotationOn).type === 'collection').map(doc => DocCast(doc.annotationOn));
+        const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'config').map(doc => DocCast(doc.annotationOn));
         const isColumnTitle = (title: string): boolean => {
             const colTitles: string[] = Object.keys(this.records[0]);
             for (let i = 0; i < colTitles.length; ++i){
@@ -470,7 +473,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
         const isValidTemplate = (collection: Doc) => {
             const childDocs = DocListCast(collection[Doc.LayoutFieldKey(collection)]);
-            console.log('childDocs of col: ' + childDocs)
             for (let i = 0; i < childDocs.length; ++i){
                 if (isColumnTitle(String(childDocs[i].title))) return true;
             }
@@ -479,6 +481,41 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return linkedCollections.filter(col => isValidTemplate(col));
     }
 
+    ApplyTemplateTo = (templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) => {
+        if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) {
+            if (target.resolvedDataDoc) {
+                target[targetKey] = new PrefetchProxy(templateDoc);
+            } else {
+                titleTarget && (Doc.GetProto(target).title = titleTarget);
+                const setDoc = [AclAdmin, AclEdit, AclAugment].includes(GetEffectiveAcl(Doc.GetProto(target))) ? Doc.GetProto(target) : target;
+                setDoc[targetKey] = new PrefetchProxy(templateDoc);
+            }
+        }
+        return target;
+    }
+
+    @action
+    createDocsFromTemplate = (templateInfo: DataVizTemplateInfo) => {
+        const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+        const fields: string[] = Array.from(Object.keys(this.records[0])); 
+        const selectedRows = NumListCast(this.layoutDoc.dataViz_selectedRows);
+        const docs: Doc[] = selectedRows.map(row => {
+            const values: String[] = [];
+            fields.forEach(col => values.push(this.records[row][col]));
+
+            const proto = new Doc();
+            proto.author = ClientUtils.CurrentUserEmail();
+            values.forEach((val, i) => {proto[fields[i]] = val as FieldType});
+            
+            const target = Doc.MakeDelegate(proto);
+            const targetKey = StrCast(templateInfo.doc.layout_fieldKey, 'layout');
+            const applied = this.ApplyTemplateTo(templateInfo.doc, target, targetKey, templateInfo.doc.title + `${row}`);
+            target.layout_fieldKey = targetKey;
+            return applied;
+        });
+        docs.forEach(doc => mainCollection.addDocument(doc));
+    }
+
     /**
      * creates a new dataviz document filter from this one
      *      it appears to the right of this document, with the
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 3ebf97570..88308c226 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { IReactionDisposer, ObservableMap, action, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
+import { returnAll, returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc } from '../../../../fields/Doc';
 import { DocCast, ImageCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
@@ -15,6 +15,15 @@ import { DataVizBox } from './DataVizBox';
 import './DocCreatorMenu.scss';
 import { Id } from '../../../../fields/FieldSymbols';
 import { Colors } from 'browndash-components';
+import { MakeTemplate } from '../../../util/DropConverter';
+
+export enum LayoutType {
+    Stacked = 'stacked', 
+    Grid = 'grid', 
+    Row = 'row', 
+    Column = 'column', 
+    Custom = 'custom'
+}
 
 @observer
 export class DocCreatorMenu extends ObservableReactComponent<{}> {
@@ -25,9 +34,9 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     @observable _templateDocs: Doc[] = [];
     @observable _templateRefToDoc?: ObservableMap<HTMLDivElement, Doc>;
-    @observable _selectedTemplateID?: Doc;
+    @observable _selectedTemplate?: Doc;
 
-    @observable _selectedLayout?: 'stacked' | 'grid' | 'row' | 'column' | 'custom';
+    @observable _selectedLayout?: LayoutType;
 
     @observable _pageX: number = 0;
     @observable _pageY: number = 0;
@@ -49,9 +58,8 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     }
 
     @action setDataViz = (dataViz: DataVizBox) => { this._dataViz = dataViz };
-    @action setTemplateDocs = (docs: Doc[]) => {
-         this._templateDocs = docs.map(doc => doc.annotationOn ? DocCast(doc.annotationOn):doc);
-    };
+    @action setTemplateDocs = (docs: Doc[]) => {this._templateDocs = docs.map(doc => doc.annotationOn ? DocCast(doc.annotationOn):doc)};
+
 
     @action
     onPointerDown = (e: PointerEvent) => {
@@ -126,17 +134,22 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                 {this._templateDocs.map(doc => ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info => {
                     if (renderedTemplates.includes(info.doc)) return undefined;
                     renderedTemplates.push(info.doc);
+                    //let ref: any = undefined;
                     return (<div 
+                    //ref={r => ref = r}
                     className='docCreatorMenu-preview-window'
                     style={{
-                        background: this._selectedTemplateID === info.doc ? Colors.MEDIUM_BLUE : '',
-                        boxShadow: this._selectedTemplateID === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : ''
+                        background: this._selectedTemplate === info.doc ? Colors.MEDIUM_BLUE : '',
+                        boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : ''
                     }}
                     onPointerDown={e =>
                         setupMoveUpEvents(this, e, returnFalse, emptyFunction,
                             undoable(clickEv => {
                                 clickEv.stopPropagation();
-                                this._selectedTemplateID = info.doc;
+                                this._selectedTemplate = info.doc;
+                                MakeTemplate(info.doc);
+                                // ref.style.background = Colors.MEDIUM_BLUE;
+                                // ref.style.boxShadow = `0 0 15px rgba(68, 118, 247, .8)`;
                                 this.forceUpdate();
                             }, 'open actions menu')
                         )
@@ -150,7 +163,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     get optionsMenuContents(){
 
-        const layoutOption = (option: 'stacked' | 'grid' | 'row' | 'column' | 'custom', optStyle?: {}) => {
+        const layoutOption = (option: LayoutType, optStyle?: {}) => {
             return (
                 <div 
                     className="docCreatorMenu-dropdown-option" 
@@ -173,11 +186,11 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                 <div className='docCreatorMenu-dropdown-hoverable'>
                     <div className="docCreatorMenu-dropdown-title">Choose Layout</div>
                     <div className="docCreatorMenu-dropdown-content">
-                        {layoutOption('stacked')}
-                        {layoutOption('grid')}
-                        {layoutOption('row')}
-                        {layoutOption('column')}
-                        {layoutOption('custom', {borderBottom: `0px`})}
+                        {layoutOption(LayoutType.Stacked)}
+                        {layoutOption(LayoutType.Grid)}
+                        {layoutOption(LayoutType.Row)}
+                        {layoutOption(LayoutType.Column)}
+                        {layoutOption(LayoutType.Custom, {borderBottom: `0px`})}
                     </div>
                 </div>
                 <div className='docCreatorMenu-layout-preview-window'>
@@ -242,7 +255,22 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         >
                             <FontAwesomeIcon icon={this._menuContent === 'templates' ? 'bars' : 'magnifying-glass'}/>
                         </button>
-                        <button className='docCreatorMenu-menu-button right'>
+                        <button 
+                        className='docCreatorMenu-menu-button right'
+                        onPointerDown={e =>
+                            setupMoveUpEvents(
+                                this,
+                                e,
+                                returnFalse,
+                                emptyFunction,
+                                undoable(clickEv => {
+                                    clickEv.stopPropagation();
+                                    if (!this._selectedTemplate) return;
+                                    const templateInfo: DataVizTemplateInfo = {doc: this._selectedTemplate};
+                                    this._dataViz?.createDocsFromTemplate(templateInfo);
+                                }, 'make docs')
+                            )
+                        }>
                             <FontAwesomeIcon icon={'plus'}/>
                         </button>
                         <button 
@@ -258,8 +286,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                                         this.closeMenu();
                                     }, 'close menu')
                                 )
-                            }
-                        >
+                            }>
                             <FontAwesomeIcon icon={'minus'}/>
                         </button>
                     </div>
@@ -270,6 +297,9 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
             </div>
             )
         }
-    
+}
 
+export interface DataVizTemplateInfo {
+    doc: Doc;
+    layout?: LayoutType;
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From c09ccb23ee490b5c1060f734665fd85668a62659 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Fri, 12 Jul 2024 18:25:26 -0400
Subject: repeat option added to menu; css refactoring

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   5 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     | 166 ++++++++++++++-------
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      |  72 ++++++---
 3 files changed, 168 insertions(+), 75 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 86a9db79c..283288013 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -496,6 +496,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @action
     createDocsFromTemplate = (templateInfo: DataVizTemplateInfo) => {
+        if (!templateInfo.doc) return;
         const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
         const fields: string[] = Array.from(Object.keys(this.records[0])); 
         const selectedRows = NumListCast(this.layoutDoc.dataViz_selectedRows);
@@ -508,8 +509,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             values.forEach((val, i) => {proto[fields[i]] = val as FieldType});
             
             const target = Doc.MakeDelegate(proto);
-            const targetKey = StrCast(templateInfo.doc.layout_fieldKey, 'layout');
-            const applied = this.ApplyTemplateTo(templateInfo.doc, target, targetKey, templateInfo.doc.title + `${row}`);
+            const targetKey = StrCast(templateInfo.doc!.layout_fieldKey, 'layout');
+            const applied = this.ApplyTemplateTo(templateInfo.doc!, target, targetKey, templateInfo.doc!.title + `${row}`);
             target.layout_fieldKey = targetKey;
             return applied;
         });
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index 4944f3071..243954873 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -46,6 +46,11 @@
     margin-bottom: 0px;
 }
 
+//------------------------------------------------------------------------------------------------------------------------------------------
+//DocCreatorMenu templates preview CSS
+//--------------------------------------------------------------------------------------------------------------------------------------------
+
+
 .docCreatorMenu-preview-container {
     display: grid;
     grid-template-columns: repeat(2, 1fr);
@@ -83,7 +88,66 @@
     }
 }
 
-.docCreatorMenu-options-container {
+//------------------------------------------------------------------------------------------------------------------------------------------
+//DocCreatorMenu options CSS
+//--------------------------------------------------------------------------------------------------------------------------------------------
+
+.docCreatorMenu-option-container{
+    display: flex;
+    width: 180px;
+    height: 30px;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+    margin-top: 10px;
+
+    &.layout{
+        z-index: 5;
+    }
+}
+
+.docCreatorMenu-option-title{
+    display: flex;
+    width: 140px;
+    height: 30px;
+    background: whitesmoke; 
+    background-color: rgb(34, 34, 37);
+    border-radius: 5px;
+    border: 1px solid whitesmoke;
+    padding: 0px;
+    font-size: 12px;
+    align-items: center;
+    justify-content: center;
+    text-transform: uppercase;
+    cursor: pointer;
+
+    &.repeat {
+        border-top-right-radius: 0px;
+        border-bottom-right-radius: 0px;
+        width: 60px;
+        border-right: 0px;
+    }
+}
+
+.docCreatorMenu-input {
+    display: flex;
+    height: 30px;
+    background-color: rgb(34, 34, 37);
+    border-radius: 5px;
+    border: 1px solid whitesmoke;
+    align-items: center;
+    justify-content: center;
+
+    &.repeat {
+        margin: 0px;
+        border-top-left-radius: 0px;
+        border-bottom-left-radius: 0px;
+        border-left: 0px;
+        width: 40px;
+    }
+}
+
+.docCreatorMenu-menu-container {
     display: flex;
     flex-direction: column;
     justify-content: flex-start;
@@ -95,73 +159,63 @@
     border: 1px solid whitesmoke;
     border-radius: 5px;
 
-    .docCreatorMenu-dropdown-hoverable {
-        width: 140px;
-        height: 25px;
-        margin-bottom: 17px;
-        z-index: 5;
-
-        &:hover .docCreatorMenu-dropdown-content {
-            display: block;
-        }
+    .docCreatorMenu-option-container{
+        width: 180px;
+        height: 30px;
 
-        &:hover .docCreatorMenu-dropdown-title {
-            border-bottom-left-radius: 0px;
-            border-bottom-right-radius: 0px;
-        }
-
-        .docCreatorMenu-dropdown-title{
-            display: flex;
+        .docCreatorMenu-dropdown-hoverable {
             width: 140px;
-            height: 25px;
-            background: whitesmoke; 
-            background-color: rgb(34, 34, 37);
-            border-radius: 5px;
-            border: 1px solid whitesmoke;
-            padding: 0px;
-            font-size: 12px;
-            margin-top: 10px;
-            align-items: center;
-            justify-content: center;
-            text-transform: uppercase;
-            cursor: pointer;
-        }
+            height: 30px;
     
-        .docCreatorMenu-dropdown-content {
-            display: none;
-            min-width: 100px;
-            height: 75px;
-            overflow-y: scroll;
-            -ms-overflow-style: none; 
-            scrollbar-width: none;
-            border-bottom: solid 1px whitesmoke;
+            &:hover .docCreatorMenu-dropdown-content {
+                display: block;
+            }
     
-            .docCreatorMenu-dropdown-option{
-                display: flex;
-                background-color: rgb(42, 42, 46);
-                border-left: 1px solid whitesmoke;
-                border-right: 1px solid whitesmoke;
-                border-bottom: 1px solid whitesmoke;
-                width: 140px;
-                height: 25px;
-                justify-content: center;
-                justify-items: center;
-                padding-top: 3px;
-
-                &:hover {
-                    background-color: rgb(68, 68, 74);
-                    cursor: pointer;
-                }
+            &: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; 
+                scrollbar-width: none;
+                border-bottom: solid 1px whitesmoke;
+                border-bottom-left-radius: 5px;
+                border-bottom-right-radius: 5px;
+        
+                .docCreatorMenu-dropdown-option{
+                    display: flex;
+                    background-color: rgb(42, 42, 46);
+                    border-left: 1px solid whitesmoke;
+                    border-right: 1px solid whitesmoke;
+                    border-bottom: 1px solid whitesmoke;
+                    width: 140px;
+                    height: 25px;
+                    justify-content: center;
+                    justify-items: center;
+                    padding-top: 3px;
+    
+                    &:hover {
+                        background-color: rgb(68, 68, 74);
+                        cursor: pointer;
+                    }
+                }
+            }    
+        }
+
     }
 
     .docCreatorMenu-layout-preview-window {
+        aspect-ratio: 1 / 1;
         width: 85%;
-        height: 100px;
         border: 1px solid whitesmoke;
         border-radius: 5px;
         background-color: rgb(34, 34, 37);
+        margin-top: 10px;
     }
 }
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 88308c226..1b4fe0879 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -31,12 +31,14 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     static Instance: DocCreatorMenu;
 
     private _ref: HTMLDivElement | null = null;
+    private _templateInfo: DataVizTemplateInfo = {};
 
     @observable _templateDocs: Doc[] = [];
     @observable _templateRefToDoc?: ObservableMap<HTMLDivElement, Doc>;
-    @observable _selectedTemplate?: Doc;
+    @observable _selectedTemplate: Doc | undefined = undefined;
 
-    @observable _selectedLayout?: LayoutType;
+    @observable _selectedLayout: LayoutType | undefined = undefined;
+    @observable _layoutPreview: boolean = false;
 
     @observable _pageX: number = 0;
     @observable _pageY: number = 0;
@@ -150,7 +152,6 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                                 MakeTemplate(info.doc);
                                 // ref.style.background = Colors.MEDIUM_BLUE;
                                 // ref.style.boxShadow = `0 0 15px rgba(68, 118, 247, .8)`;
-                                this.forceUpdate();
                             }, 'open actions menu')
                         )
                     }>
@@ -161,8 +162,15 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         );
     }
 
-    get optionsMenuContents(){
+    get layoutPreviewContents() {
+        return (
+            <div className='docCreatorMenu-layout-preview-window'>
+
+            </div>
+        );
+    }
 
+    get optionsMenuContents(){
         const layoutOption = (option: LayoutType, optStyle?: {}) => {
             return (
                 <div 
@@ -181,20 +189,49 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
             );
         }
 
+        const repeatOption = (num: number) => {
+            return (<option 
+            value="Option 1"
+            onPointerDown={e => this._templateInfo.repeat = num}
+            >
+                {`${num}x`}
+            </option>);
+        }
+
+        const repeatOptions = [0, 1, 2, 3, 4, 5];
+
         return (
-            <div className='docCreatorMenu-options-container'>
-                <div className='docCreatorMenu-dropdown-hoverable'>
-                    <div className="docCreatorMenu-dropdown-title">Choose Layout</div>
-                    <div className="docCreatorMenu-dropdown-content">
-                        {layoutOption(LayoutType.Stacked)}
-                        {layoutOption(LayoutType.Grid)}
-                        {layoutOption(LayoutType.Row)}
-                        {layoutOption(LayoutType.Column)}
-                        {layoutOption(LayoutType.Custom, {borderBottom: `0px`})}
+            <div className='docCreatorMenu-menu-container'>
+                <div className='docCreatorMenu-option-container layout'>
+                    <div className='docCreatorMenu-dropdown-hoverable'>
+                        <div className="docCreatorMenu-option-title">{this._selectedLayout ? this._selectedLayout.toUpperCase() : 'Choose Layout'}</div>
+                        <div className="docCreatorMenu-dropdown-content">
+                            {layoutOption(LayoutType.Stacked)}
+                            {layoutOption(LayoutType.Grid)}
+                            {layoutOption(LayoutType.Row)}
+                            {layoutOption(LayoutType.Column)}
+                            {layoutOption(LayoutType.Custom, {borderBottom: `0px`})}
+                        </div>
                     </div>
+                    <button 
+                        className='docCreatorMenu-menu-button'
+                        onPointerDown={e =>
+                            setupMoveUpEvents(this, e, returnFalse, emptyFunction,
+                                undoable(clickEv => {
+                                    clickEv.stopPropagation();
+                                    runInAction(() => this._layoutPreview = !this._layoutPreview);
+                                }, 'open actions menu')
+                            )
+                        }>
+                        <FontAwesomeIcon icon={this._layoutPreview ? 'minus' : 'magnifying-glass'}/>
+                    </button>
                 </div>
-                <div className='docCreatorMenu-layout-preview-window'>
-
+                {this._layoutPreview ? this.layoutPreviewContents : null}
+                <div className='docCreatorMenu-option-container'>
+                    <div className="docCreatorMenu-option-title repeat">Repeat</div>
+                    <select className='docCreatorMenu-input repeat'>
+                        {repeatOptions.map(num => repeatOption(num))}
+                    </select>
                 </div>
             </div>
         );
@@ -253,7 +290,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                                 )
                             }
                         >
-                            <FontAwesomeIcon icon={this._menuContent === 'templates' ? 'bars' : 'magnifying-glass'}/>
+                            <FontAwesomeIcon icon={this._menuContent === 'templates' ? 'bars' : 'table-cells'}/>
                         </button>
                         <button 
                         className='docCreatorMenu-menu-button right'
@@ -300,6 +337,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 }
 
 export interface DataVizTemplateInfo {
-    doc: Doc;
+    doc?: Doc;
     layout?: LayoutType;
+    repeat?: number;
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 58d3eeb6e45555e5eeaf172f571b500ca3b564c0 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 17 Jul 2024 00:54:22 -0400
Subject: creation with layout works

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 38 ++++++++++++++++-
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     | 10 +++++
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 48 ++++++++++++++++++----
 3 files changed, 88 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 283288013..e03da8e7b 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -33,10 +33,11 @@ import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
 import { LinkManager } from '../../../util/LinkManager';
-import { DataVizTemplateInfo, DocCreatorMenu } from './DocCreatorMenu';
+import { DataVizTemplateInfo, DocCreatorMenu, LayoutType } from './DocCreatorMenu';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
+import { template } from 'lodash';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -494,6 +495,38 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return target;
     }
 
+    applyLayout = (templateInfo: DataVizTemplateInfo, docs: Doc[]) => {
+        if (templateInfo.layout.type === LayoutType.Stacked) return;
+        const columns: number = templateInfo.columns;
+        const xGap: number = templateInfo.layout.xMargin;
+        const yGap: number = templateInfo.layout.yMargin;
+        const repeat: number = templateInfo.layout.repeat;
+        const startX: number = templateInfo.referencePos.x;
+        const startY: number = templateInfo.referencePos.y;
+        const templWidth = Number(templateInfo.doc._width);
+        const templHeight = Number(templateInfo.doc._height);
+
+        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;
+                console.log('x: ' + docs[i].x + ' y: ' + docs[i].y);
+                curX += templWidth + xGap;
+                ++docsChanged;
+                ++i;
+            }
+
+            i = 0;
+            curX = startX;
+            curY += templHeight + yGap;
+        }
+    }
+
     @action
     createDocsFromTemplate = (templateInfo: DataVizTemplateInfo) => {
         if (!templateInfo.doc) return;
@@ -514,7 +547,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             target.layout_fieldKey = targetKey;
             return applied;
         });
+
         docs.forEach(doc => mainCollection.addDocument(doc));
+
+        this.applyLayout(templateInfo, docs);
     }
 
     /**
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index d2f8f13d1..8b17f1ed4 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -59,6 +59,16 @@
     color: rgb(180, 180, 180);
 }
 
+
+.docCreatorMenu-placement-indicator {
+    position: absolute;
+    z-index: 100000;
+    border-left: solid 3px #9fd7fb;
+    border-top: solid 3px #9fd7fb;
+    width: 25px;
+    height: 25px;
+}
+
 //------------------------------------------------------------------------------------------------------------------------------------------
 //DocCreatorMenu templates preview CSS
 //--------------------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 3eda2c891..b1488679f 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -33,18 +33,19 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     static Instance: DocCreatorMenu;
 
     private _ref: HTMLDivElement | null = null;
-    private _templateInfo: DataVizTemplateInfo = {};
 
     @observable _templateDocs: Doc[] = [];
     @observable _templateRefToDoc?: ObservableMap<HTMLDivElement, Doc>;
     @observable _selectedTemplate: Doc | undefined = undefined;
 
-    @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, columns: 1};
+    @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, columns: 1, repeat: 0};
     @observable _layoutPreview: boolean = true;
     @observable _layoutPreviewScale: number = 1;
 
     @observable _pageX: number = 0;
     @observable _pageY: number = 0;
+    @observable _indicatorX: number | undefined = undefined;
+    @observable _indicatorY: number | undefined = undefined;
     @observable _display: boolean = false;
 
     @observable _mouseX: number = -1;
@@ -54,6 +55,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     @observable _menuContent: 'templates' | 'options' = 'templates';
     @observable _dragging: boolean = false;
+    @observable _draggingIndicator: boolean = false;
     @observable _dataViz?: DataVizBox;
 
     constructor(props: any) {
@@ -158,6 +160,10 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     @action
     onPointerMove = (e: any) => {
+        // if (this._draggingIndicator) {
+        //     this._indicatorX = e.pageX - (this._startPos?.x ?? 0) + this._pageX;
+        //     this._indicatorY = e.pageY - (this._startPos?.y ?? 0) + this._pageY;
+        // } else 
         if (this._dragging){
             this._pageX = e.pageX - (this._startPos?.x ?? 0);
             this._pageY = e.pageY - (this._startPos?.y ?? 0);
@@ -356,7 +362,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                 </div>
                 {this._layout.type ? this.layoutConfigOptions: null}
                 {this._layoutPreview ? this.layoutPreviewContents : null}
-                {selectionBox(60, 20, 'repeat', undefined, repeatOptions.map(num => <option onPointerDown={e => this._templateInfo.repeat = num}>{`${num}x`}</option>))}
+                {selectionBox(60, 20, 'repeat', undefined, repeatOptions.map(num => <option onPointerDown={e => this._layout.repeat = num}>{`${num}x`}</option>))}
             </div>
         );
     }
@@ -365,6 +371,31 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         return (
             <div className='docCreatorMenu'>
                 {!this._shouldDisplay ? undefined : 
+                <>
+                {/* <div className='docCreatorMenu-placement-indicator' 
+                style={{
+                    display: '',
+                    left: this._indicatorX ?? this._pageX + 300,
+                    top: this._indicatorY ?? this._pageY,
+                }}
+                onPointerMove={e => this.onPointerMove(e)}
+                onPointerDown={e =>
+                    setupMoveUpEvents(
+                        this,
+                        e,
+                        (e) => {
+                            this._draggingIndicator = true;
+                            this._startPos = {x: 0, y: 0};
+                            this._startPos.x = e.pageX - (this._ref?.getBoundingClientRect().left ?? 0);
+                            this._startPos.y = e.pageY - (this._ref?.getBoundingClientRect().top ?? 0);
+                            return true;
+                        },
+                        emptyFunction,
+                        undoable(clickEv => {
+                            clickEv.stopPropagation();
+                        }, 'drag menu')
+                    )
+                }/> */}
                 <div
                     className="docCreatorMenu-cont"
                     ref={r => this._ref = r}
@@ -410,7 +441,8 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                                     undoable(clickEv => {
                                         clickEv.stopPropagation();
                                         if (!this._selectedTemplate) return;
-                                        const templateInfo: DataVizTemplateInfo = {doc: this._selectedTemplate};
+                                        const templateInfo: DataVizTemplateInfo = {doc: this._selectedTemplate, layout: this._layout, referencePos: {x: this._pageX + 450, y: this._pageY}, columns: this.columnsCount};
+                                        
                                         this._dataViz?.createDocsFromTemplate(templateInfo);
                                     }, 'make docs')
                                 )
@@ -425,6 +457,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         </div>
                         {this._menuContent === 'templates' ? this.templatesPreviewContents : this.optionsMenuContents}
                 </div>
+                </>
                 }
             </div>
             )
@@ -432,7 +465,8 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 }
 
 export interface DataVizTemplateInfo {
-    doc?: Doc;
-    layout?: {};
-    repeat?: number;
+    doc: Doc;
+    layout: {type: LayoutType, xMargin: number, yMargin: number, repeat: number};
+    columns: number;
+    referencePos: {x: number, y: number};
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From ded7673796965e9cb9239cfd483a2f1e35abd38b Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 17 Jul 2024 18:28:05 -0400
Subject: highlighting rows based on layoutpreview hover

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx     | 20 ++++++++++++++++++--
 .../views/nodes/DataVizBox/DocCreatorMenu.scss       | 11 +++++++++--
 src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx | 18 +++++++++++++-----
 .../views/nodes/DataVizBox/components/TableBox.tsx   |  6 ++++--
 src/client/views/nodes/ImageBox.tsx                  |  2 +-
 5 files changed, 45 insertions(+), 12 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index e03da8e7b..765642891 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -38,6 +38,7 @@ import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
 import { template } from 'lodash';
+import { data } from 'jquery';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -57,6 +58,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined;
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+    @observable _specialHighlightedRow: number | undefined = undefined;
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -131,6 +133,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this.layoutDoc._dataViz_titleCol = titleCol;
     };
 
+    @action setSpecialHighlightedRow = (row: number | undefined) => {
+        this._specialHighlightedRow = row;
+    }
+
     @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
     restoreView = (data: Doc) => {
         // const changedView = data.config_dataViz && this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
@@ -366,7 +372,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         };
         if (!this.records.length) return 'no data/visualization';
         switch (this.dataVizView) {
-            case DataVizView.TABLE:     return <TableBox  {...sharedProps} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
+            case DataVizView.TABLE:     return <TableBox  {...sharedProps} specHighlightedRow={this._specialHighlightedRow} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>;
             case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />;
             case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />;
             case DataVizView.PIECHART:  return <PieChart  {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} 
@@ -515,7 +521,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             while (i < columns && docsChanged < docs.length){ 
                 docs[docsChanged].x = curX;
                 docs[docsChanged].y = curY;
-                console.log('x: ' + docs[i].x + ' y: ' + docs[i].y);
                 curX += templWidth + xGap;
                 ++docsChanged;
                 ++i;
@@ -527,6 +532,16 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     }
 
+    // applyImagesTo = (doc: Doc, cols: string[]) => {
+    //     const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
+    //     const imageFields = childDocs.filter(doc => doc.type === 'image');
+    //     const imageToKey: Map<Doc, string> = new Map();
+    //     imageFields.forEach(img => cols.forEach(col => {if (img[col]) imageToKey.set(img, col)}));
+
+    //     imageFields.forEach(doc => doc['data'] = String(doc[String(imageToKey.get(doc))]).replace(/"/g, ''));
+
+    // }
+
     @action
     createDocsFromTemplate = (templateInfo: DataVizTemplateInfo) => {
         if (!templateInfo.doc) return;
@@ -545,6 +560,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             const targetKey = StrCast(templateInfo.doc!.layout_fieldKey, 'layout');
             const applied = this.ApplyTemplateTo(templateInfo.doc!, target, targetKey, templateInfo.doc!.title + `${row}`);
             target.layout_fieldKey = targetKey;
+            //this.applyImagesTo(target, fields);
             return applied;
         });
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index 8b17f1ed4..328cbbaf1 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -81,8 +81,9 @@
     grid-auto-rows: 141px;
     overflow-y: scroll;
     margin: 5px;
+    margin-top: 0px;
     width: calc(100% - 10px);
-    height: calc(100% - 51px);
+    height: calc(100% - 45px);
     border: 1px solid rgb(180, 180, 180);
     border-radius: 5px;
 
@@ -240,8 +241,9 @@
     align-items: center;
     overflow-y: scroll;
     margin: 5px;
+    margin-top: 0px;
     width: calc(100% - 10px);
-    height: calc(100% - 51px);
+    height: calc(100% - 45px);
     border: 1px solid rgb(180, 180, 180);
     border-radius: 5px;
 
@@ -329,6 +331,11 @@
                 align-items: center;
                 border-radius: 3px;
                 border: solid 1px lightblue;
+
+                &:hover {
+                    border: solid 2px rgb(68, 153, 233);
+                    z-index: 2;
+                }
             }
         }
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index b1488679f..48b87fae7 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -38,7 +38,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     @observable _templateRefToDoc?: ObservableMap<HTMLDivElement, Doc>;
     @observable _selectedTemplate: Doc | undefined = undefined;
 
-    @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, columns: 1, repeat: 0};
+    @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns?: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0};
     @observable _layoutPreview: boolean = true;
     @observable _layoutPreviewScale: number = 1;
 
@@ -48,6 +48,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     @observable _indicatorY: number | undefined = undefined;
     @observable _display: boolean = false;
 
+    @observable _hoveredLayoutPreview: number | undefined = undefined;
     @observable _mouseX: number = -1;
     @observable _mouseY: number = -1;
     @observable _startPos?: {x: number, y: number};
@@ -68,7 +69,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     @action setTemplateDocs = (docs: Doc[]) => {this._templateDocs = docs.map(doc => doc.annotationOn ? DocCast(doc.annotationOn):doc)};
 
     @computed get docsToRender() {
-        return NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows);
+        return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : [];
     }
 
     @computed get rowsCount(){
@@ -255,6 +256,8 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         }
     }
 
+
+
     get layoutPreviewContents() {
         const docWidth = Number(this._selectedTemplate?._width);
         const docHeight = Number(this._selectedTemplate?._height);
@@ -298,6 +301,8 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                     </div> : 
                     this.docsToRender.map(num => 
                         <div 
+                        onMouseEnter={() => this._dataViz?.setSpecialHighlightedRow(num)}
+                        onMouseLeave={() => this._dataViz?.setSpecialHighlightedRow(undefined)}
                         className='docCreatorMenu-layout-preview-item'
                         style={{
                             width: scaledDown(docWidth), 
@@ -348,7 +353,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         <div className="docCreatorMenu-option-title">{this._layout.type ? this._layout.type.toUpperCase() : 'Choose Layout'}</div>
                         <div className="docCreatorMenu-dropdown-content">
                             {layoutOption(LayoutType.Stacked)}
-                            {layoutOption(LayoutType.Grid, undefined, () => {this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length))})}
+                            {layoutOption(LayoutType.Grid, undefined, () => {if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length))})}
                             {layoutOption(LayoutType.Row)}
                             {layoutOption(LayoutType.Column)}
                             {layoutOption(LayoutType.Custom, {borderBottom: `0px`})}
@@ -432,7 +437,10 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         >
                             <button 
                                 className='docCreatorMenu-menu-button'
-                                onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._menuContent = this._menuContent === 'templates' ? 'options' : 'templates'))}>
+                                onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => {
+                                    this._menuContent = this._menuContent === 'templates' ? 'options' : 'templates';
+                                    if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length));
+                                }))}>
                                 <FontAwesomeIcon icon={this._menuContent === 'templates' ? 'bars' : 'table-cells'}/>
                             </button>
                             <button 
@@ -442,7 +450,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                                         clickEv.stopPropagation();
                                         if (!this._selectedTemplate) return;
                                         const templateInfo: DataVizTemplateInfo = {doc: this._selectedTemplate, layout: this._layout, referencePos: {x: this._pageX + 450, y: this._pageY}, columns: this.columnsCount};
-                                        
+
                                         this._dataViz?.createDocsFromTemplate(templateInfo);
                                     }, 'make docs')
                                 )
diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
index a1deb1625..d484bf5e8 100644
--- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx
+++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx
@@ -1,6 +1,6 @@
 /* eslint-disable jsx-a11y/no-noninteractive-tabindex */
 /* eslint-disable jsx-a11y/no-static-element-interactions */
-import { Button, Type } from 'browndash-components';
+import { Button, Colors, Type } from 'browndash-components';
 import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -37,6 +37,7 @@ interface TableBoxProps {
         left: number;
     };
     docView?: () => DocumentView | undefined;
+    specHighlightedRow: number | undefined;
 }
 
 @observer
@@ -440,7 +441,8 @@ export class TableBox extends ObservableReactComponent<TableBoxProps> {
                                             className={`tableBox-row ${this.columns[0]}`}
                                             onClick={e => this.tableRowClick(e, rowId)}
                                             style={{
-                                                background: NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
+                                                background: rowId === this._props.specHighlightedRow ? 'lightblue' : NumListCast(this._props.layoutDoc.dataViz_highlitedRows).includes(rowId) ? 'lightYellow' : NumListCast(this._props.layoutDoc.dataViz_selectedRows).includes(rowId) ? 'lightgrey' : '',
+                                                border: rowId === this._props.specHighlightedRow ? `solid 3px ${Colors.MEDIUM_BLUE}` : ''
                                             }}>
                                             {this.columns.map(col => {
                                                 let colSelected = false;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index e4b3a1b9b..b7943f503 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -341,7 +341,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
 
     @computed get paths() {
-        const field = Cast(this.dataDoc[this.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc
+        const field = this.dataDoc[this.fieldKey] instanceof ImageField ? Cast(this.dataDoc[this.fieldKey], ImageField, null) : new ImageField(String(this.dataDoc[this.fieldKey])); // retrieve the primary image URL that is being rendered from the data doc
         const alts = DocListCast(this.dataDoc[this.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images
         const defaultUrl = new URL(ClientUtils.prepend('/assets/unknown-file-icon-hi.png'));
         const altpaths =
-- 
cgit v1.2.3-70-g09d2


From 9b8e2853c7ea53578c3dbf180018c25b8b322617 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Thu, 18 Jul 2024 04:27:44 -0400
Subject: saving layouts

---
 src/client/documents/Documents.ts                  |   1 +
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  15 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     | 214 +++++++++++++--------
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 130 ++++++++++---
 4 files changed, 244 insertions(+), 116 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index dabbf9591..dc649ec4e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -235,6 +235,7 @@ export class DocumentOptions {
     dataViz_pie?: string;
     dataViz_histogram?: string;
     dataViz?: string;
+    dataViz_savedTemplates?: LISTt;
 
     layout?: string | Doc; // default layout string or template document
     layout_isSvg?: BOOLt = new BoolInfo('whether document decorations and other selections should handle pointerEvents for svg content or use doc bounding box');
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 765642891..e943dd2e3 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -33,12 +33,14 @@ import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
 import { LinkManager } from '../../../util/LinkManager';
-import { DataVizTemplateInfo, DocCreatorMenu, LayoutType } from './DocCreatorMenu';
+import { DataVizTemplateInfo, DataVizTemplateLayout, DocCreatorMenu, LayoutType } from './DocCreatorMenu';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
 import { template } from 'lodash';
 import { data } from 'jquery';
+import { listSpec } from '../../../../fields/Schema';
+import { ObjectField } from '../../../../fields/ObjectField';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -532,14 +534,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     }
 
-    // applyImagesTo = (doc: Doc, cols: string[]) => {
-    //     const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
-    //     const imageFields = childDocs.filter(doc => doc.type === 'image');
-    //     const imageToKey: Map<Doc, string> = new Map();
-    //     imageFields.forEach(img => cols.forEach(col => {if (img[col]) imageToKey.set(img, col)}));
-
-    //     imageFields.forEach(doc => doc['data'] = String(doc[String(imageToKey.get(doc))]).replace(/"/g, ''));
-
+    // @action addSavedLayout = (layout: DataVizTemplateLayout) => {
+    //     const saved = Cast(this.layoutDoc.dataViz_savedTemplates, listSpec('RefField'));
+        
     // }
 
     @action
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index 0fc85ba5d..d18f75545 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -68,6 +68,13 @@
         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 {
@@ -168,16 +175,44 @@
     height: 25px;
 }
 
+.docCreatorMenu-general-options-container {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    margin: 0px;
+    padding: 0px;
+    gap: 5px;
+}
+
+.docCreatorMenu-save-layout-button {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 40px;
+    height: 40px;
+    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);  
+        border: 2px solid rgb(53, 80, 127);
+    }
+}
+
 .docCreatorMenu-create-docs-button {
-    width: 80px;
+    width: 40px;
     height: 40px;
     background-color: rgb(176, 229, 149); 
-    border-radius: 5px;
     border: 2px solid rgb(126, 219, 80);
+    border-radius: 5px;
     padding: 0px;
-    font-size: 16px;
-    color: black;
+    font-size: 25px;
+    color: white;
     flex: 0 0 auto;
+    margin-bottom: 20px; //remove later !!!
 
     &:hover {
         background-color: rgb(129, 223, 83);
@@ -185,6 +220,13 @@
     }
 }
 
+.docCreatorMenu-option-divider {
+    border-top: 1px solid rgb(180, 180, 180);
+    width: 225px;
+    margin-top: 10px;
+    margin-bottom: 10px;
+}
+
 //------------------------------------------------------------------------------------------------------------------------------------------
 //DocCreatorMenu templates preview CSS
 //--------------------------------------------------------------------------------------------------------------------------------------------
@@ -202,29 +244,32 @@
     height: calc(100% - 30px);
     border: 1px solid rgb(180, 180, 180);
     border-radius: 5px;
+}
 
-    .docCreatorMenu-preview-window {
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        width: 125px;
-        height: 125px;
-        margin-top: 10px;
-        margin-left: 10px;
-        border: 1px solid rgb(163, 163, 163);
-        border-radius: 5px;
-        box-shadow: 5px 5px rgb(29, 29, 31);
+.docCreatorMenu-preview-window {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 125px;
+    height: 125px;
+    margin-top: 10px;
+    margin-left: 10px;
+    border: 1px solid rgb(163, 163, 163);
+    border-radius: 5px;
+    box-shadow: 5px 5px rgb(29, 29, 31);
 
-        &:hover{
-            background-color: rgb(72, 72, 73);
-        }
+    &:hover{
+        background-color: rgb(72, 72, 73);
+    }
 
-        .docCreatorMenu-preview-image{ 
-            width: 105px;
-            height: 105px;
-            border-radius: 5px;
-        }
+    .docCreatorMenu-preview-image{ 
+        width: 105px;
+        height: 105px;
+        border-radius: 5px;
+    }
 
+    &.empty {
+        font-size: 35px;
     }
 }
 
@@ -254,6 +299,8 @@
     background: whitesmoke; 
     background-color: rgb(34, 34, 37);
     border-radius: 5px;
+    border-top-right-radius: 0px;
+    border-bottom-right-radius: 0px;
     border: 1px solid rgb(180, 180, 180);
     padding: 0px;
     font-size: 12px;
@@ -412,75 +459,80 @@
         }
 
     }
+}
 
-    .docCreatorMenu-layout-preview-window-wrapper {
-        display: flex;
-        justify-content: center;
-        align-items: center;
-        width: 85%;
-        height: auto;
-        position: relative;
-        padding: 0px;
+.docCreatorMenu-layout-preview-window-wrapper {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 85%;
+    height: auto;
+    position: relative;
+    padding: 0px;
 
-        &:hover .docCreatorMenu-zoom-button-container {
-            display: block;
-        }
+    &:hover .docCreatorMenu-zoom-button-container {
+        display: block;
+    }
 
-        .docCreatorMenu-layout-preview-window {
-            padding: 5px;
-            flex: 0 0 auto;
-            overflow: scroll;
-            display: grid;
-            width: auto;
-            height: auto;
-            max-width: 240;
-            max-height: 240;
-            border: 1px solid rgb(180, 180, 180);
-            border-radius: 5px;
-            background-color: rgb(34, 34, 37);
-            -ms-overflow-style: none; 
-            scrollbar-width: none;
-
-            .docCreatorMenu-layout-preview-item {
-                display: flex;
-                justify-content: center;
-                align-items: center;
-                border-radius: 3px;
-                border: solid 1px lightblue;
-
-                &:hover {
-                    border: solid 2px rgb(68, 153, 233);
-                    z-index: 2;
-                }
-            }
+    .docCreatorMenu-layout-preview-window {
+        padding: 5px;
+        flex: 0 0 auto;
+        overflow: scroll;
+        display: grid;
+        width: auto;
+        height: auto;
+        max-width: 240;
+        max-height: 240;
+        border: 1px solid rgb(180, 180, 180);
+        border-radius: 5px;
+        background-color: rgb(34, 34, 37);
+        -ms-overflow-style: none; 
+        scrollbar-width: none;
+
+        &.small {
+            max-width: 100;
+            max-height: 100;
         }
 
-        .docCreatorMenu-zoom-button-container {
-            position: absolute;
-            top: 0px;
+        .docCreatorMenu-layout-preview-item {
             display: flex;
             justify-content: center;
             align-items: center;
-            display: none;
-            z-index: 999;
-        }
-
-        .docCreatorMenu-zoom-button{ 
-            width: 15px;
-            height: 15px;
-            background: whitesmoke; 
-            background-color: rgb(34, 34, 37);
             border-radius: 3px;
-            border: 1px solid rgb(180, 180, 180);
-            padding: 0px;
-            font-size: 10px;
-            z-index: 6;
-            margin-left: 0px;
-            margin-top: 0px;
-            margin-right: 0px; //225px
-            margin-bottom: 0px;
+            border: solid 1px lightblue;
+
+            &:hover {
+                border: solid 2px rgb(68, 153, 233);
+                z-index: 2;
+            }
         }
     }
+
+    .docCreatorMenu-zoom-button-container {
+        position: absolute;
+        top: 0px;
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        display: none;
+        z-index: 999;
+    }
+
+    .docCreatorMenu-zoom-button{ 
+        width: 15px;
+        height: 15px;
+        background: whitesmoke; 
+        background-color: rgb(34, 34, 37);
+        border-radius: 3px;
+        border: 1px solid rgb(180, 180, 180);
+        padding: 0px;
+        font-size: 10px;
+        z-index: 6;
+        margin-left: 0px;
+        margin-top: 0px;
+        margin-right: 0px; //225px
+        margin-bottom: 0px;
+    }
 }
 
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 67999fc39..fdf623150 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -40,6 +40,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns?: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0};
     @observable _layoutPreview: boolean = true;
     @observable _layoutPreviewScale: number = 1;
+    @observable _savedLayouts: DataVizTemplateLayout[] = [];
 
     @observable _pageX: number = 0;
     @observable _pageY: number = 0;
@@ -193,6 +194,20 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         }
     }
 
+    @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;
+    }
+
     get templatesPreviewContents(){
         const renderedTemplates: Doc[] = [];
         return (
@@ -203,13 +218,35 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                     return (<div 
                     className='docCreatorMenu-preview-window'
                     style={{
-                        background: this._selectedTemplate === info.doc ? Colors.MEDIUM_BLUE : '',
+                        border: this._selectedTemplate === info.doc ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
                         boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : ''
                     }}
                     onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}>
                         <img className='docCreatorMenu-preview-image' src={info.icon!.url.href.replace(".png", "_o.png")} />
                     </div>
                 )})}
+                <div className='docCreatorMenu-preview-window empty'>
+                    <FontAwesomeIcon icon='plus' color='rgb(160, 160, 160)'/>
+                </div>
+            </div>
+        );
+    }
+
+    get savedLayoutsPreviewContents(){
+        return (
+            <div className='docCreatorMenu-preview-container'>
+               {this._savedLayouts.map((layout, index) => 
+                <div 
+                    className='docCreatorMenu-preview-window'
+                    style={{
+                        border: this.isSelectedLayout(layout) ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
+                        boxShadow: this.isSelectedLayout(layout) ? `0 0 15px rgba(68, 118, 247, .8)` : ''
+                    }}
+                    onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedSavedLayout(layout)))}
+                    >
+                    {this.layoutPreviewContents(87, layout, false, true, index)}
+                </div>
+               )}
             </div>
         );
     }
@@ -259,20 +296,23 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         }
     }
 
+    layoutPreviewContents = (outerSpan: number, altLayout?: DataVizTemplateLayout, zoomOption: boolean = true, small: boolean = false, id?: number) => {
+        const doc: Doc | undefined = altLayout ? altLayout.template : this._selectedTemplate;
+        if (!doc) return;
 
+        const layout = altLayout ? altLayout.layout : this._layout;
 
-    get layoutPreviewContents() {
-        const docWidth = Number(this._selectedTemplate?._width);
-        const docHeight = Number(this._selectedTemplate?._height);
-        const horizontalSpan: number = (docWidth + this._layout.xMargin) * this.columnsCount - this._layout.xMargin;
-        const verticalSpan: number = (docHeight + this._layout.yMargin) * this.rowsCount - this._layout.yMargin;
+        const docWidth: number = Number(doc._width);
+        const docHeight: number = Number(doc._height);
+        const horizontalSpan: number = (docWidth + layout.xMargin) * (altLayout ? altLayout.columns : this.columnsCount) - layout.xMargin;;
+        const verticalSpan: number = (docHeight + layout.yMargin) * (altLayout ? altLayout.rows : this.rowsCount) - layout.yMargin;
         const largerSpan: number = horizontalSpan > verticalSpan ? horizontalSpan : verticalSpan;
-        const scaledDown = (input: number) => {return input / (largerSpan / 225 * this._layoutPreviewScale)}
+        const scaledDown = (input: number) => {return input / (largerSpan / outerSpan * this._layoutPreviewScale)}
         const fontSize = Math.min(scaledDown(docWidth / 3), scaledDown(docHeight / 3));
 
         return (
-            <div className='docCreatorMenu-layout-preview-window-wrapper'>
-                <div className='docCreatorMenu-zoom-button-container'>
+            <div className='docCreatorMenu-layout-preview-window-wrapper' id={String(id) ?? undefined}>
+                {!zoomOption ? null : <div className='docCreatorMenu-zoom-button-container'>
                     <button 
                         className='docCreatorMenu-zoom-button'
                         onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._layoutPreviewScale *= 1.25))}>
@@ -283,15 +323,16 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._layoutPreviewScale *= .75))}>
                             <FontAwesomeIcon icon={'plus'}/>
                     </button>
-                </div>
+                </div>}
                 <div 
-                className='docCreatorMenu-layout-preview-window'
+                id={String(id) ?? undefined}
+                className={`docCreatorMenu-layout-preview-window ${small ? 'small' : ''}`}
                 style={{
-                    gridTemplateColumns: `repeat(${this.columnsCount}, ${scaledDown(docWidth)}px`,
+                    gridTemplateColumns: `repeat(${altLayout ? altLayout.columns : this.columnsCount}, ${scaledDown(docWidth)}px`,
                     gridTemplateRows: `${scaledDown(docHeight)}px`,
                     gridAutoRows: `${scaledDown(docHeight)}px`,
-                    rowGap: `${scaledDown(this._layout.yMargin)}px`,
-                    columnGap: `${scaledDown(this._layout.xMargin)}px`
+                    rowGap: `${scaledDown(layout.yMargin)}px`,
+                    columnGap: `${scaledDown(layout.xMargin)}px`
                 }}>
                     {this._layout.type === LayoutType.Stacked ? 
                     <div 
@@ -365,32 +406,61 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         </div>
                     </div>
                     <button 
-                        className='docCreatorMenu-menu-button'
+                        className='docCreatorMenu-menu-button preview-toggle'
                         onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._layoutPreview = !this._layoutPreview))}>
                         <FontAwesomeIcon icon={this._layoutPreview ? 'minus' : 'magnifying-glass'}/>
                     </button>
                 </div>
                 {this._layout.type ? this.layoutConfigOptions: null}
-                {this._layoutPreview ? this.layoutPreviewContents : null}
+                {this._layoutPreview ? this.layoutPreviewContents(225) : null}
                 {selectionBox(60, 20, 'repeat', undefined, repeatOptions.map(num => <option onPointerDown={e => this._layout.repeat = num}>{`${num}x`}</option>))}
-                <button 
-                    className='docCreatorMenu-create-docs-button'
-                    style={{backgroundColor: this.canMakeDocs ? '' : 'rgb(155, 155, 155)', border: this.canMakeDocs ? '' : 'rgb(180, 180, 180)'}}
-                    onPointerDown={e => setupMoveUpEvents( this, e, returnFalse, emptyFunction,
+                <hr className='docCreatorMenu-option-divider'/>
+                <div className='docCreatorMenu-general-options-container'>
+                    <button
+                        className='docCreatorMenu-save-layout-button'
+                        onPointerDown={e => setupMoveUpEvents( this, e, returnFalse, emptyFunction,
                             undoable(clickEv => {
                                 clickEv.stopPropagation();
                                 if (!this._selectedTemplate) return;
-                                const templateInfo: DataVizTemplateInfo = {doc: this._selectedTemplate, layout: this._layout, referencePos: {x: this._pageX + 450, y: this._pageY}, columns: this.columnsCount};
-                                this._dataViz?.createDocsFromTemplate(templateInfo);
-                            }, 'make docs')
+                                const layout: DataVizTemplateLayout = {template: this._selectedTemplate, layout: {type: this._layout.type, xMargin: this._layout.xMargin, yMargin:this._layout.yMargin, repeat: 0}, columns: this.columnsCount, rows: this.rowsCount, docsNumList: this.docsToRender};
+                                this._savedLayouts.push(layout);
+                            }, 'make docs') 
                         )
                     }>
-                    Create
-                </button>
+                        <FontAwesomeIcon icon='floppy-disk'/>
+                    </button>
+                    <button 
+                        className='docCreatorMenu-create-docs-button'
+                        style={{backgroundColor: this.canMakeDocs ? '' : 'rgb(155, 155, 155)', border: this.canMakeDocs ? '' : 'solid 2px rgb(180, 180, 180)'}}
+                        onPointerDown={e => setupMoveUpEvents( this, e, returnFalse, emptyFunction,
+                                undoable(clickEv => {
+                                    clickEv.stopPropagation();
+                                    if (!this._selectedTemplate) return;
+                                    const templateInfo: DataVizTemplateInfo = {doc: this._selectedTemplate, layout: this._layout, referencePos: {x: this._pageX + 450, y: this._pageY}, columns: this.columnsCount};
+                                    this._dataViz?.createDocsFromTemplate(templateInfo);
+                                }, 'make docs') 
+                            )
+                        }>
+                         <FontAwesomeIcon icon='plus'/>
+                    </button>
+                </div>
             </div>
         );
     }
 
+    get renderSelectedViewType(){
+        switch (this._menuContent){
+            case 'templates':
+                return this.templatesPreviewContents;
+            case 'options':
+                return this.optionsMenuContents;
+            case 'saved':
+                return this.savedLayoutsPreviewContents;
+            default:
+                return undefined;
+        }
+    }
+
     render() {
         const topButton = (icon: string, opt: string, func: Function, tag: string) => {
             return (
@@ -485,7 +555,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                                 <FontAwesomeIcon icon={'minus'}/>
                             </button>
                         </div>
-                        {this._menuContent === 'templates' ? this.templatesPreviewContents : this.optionsMenuContents}
+                        {this.renderSelectedViewType}
                 </div>
                 </>
                 }
@@ -499,4 +569,12 @@ export interface DataVizTemplateInfo {
     layout: {type: LayoutType, xMargin: number, yMargin: number, repeat: number};
     columns: number;
     referencePos: {x: number, y: number};
+}
+
+export interface DataVizTemplateLayout {
+    template: Doc;
+    docsNumList: number[];
+    layout: {type: LayoutType, xMargin: number, yMargin: number, repeat: number};
+    columns: number;
+    rows: number;
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 951d6a8681e9be9068c8a0f8fce659938ab9ed80 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 31 Jul 2024 03:01:55 -0400
Subject: basic template generation work

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 35 +++++++++++++++++++++-
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 13 +++++++-
 2 files changed, 46 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index e943dd2e3..f25602fd5 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -34,13 +34,14 @@ import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
 import { LinkManager } from '../../../util/LinkManager';
 import { DataVizTemplateInfo, DataVizTemplateLayout, DocCreatorMenu, LayoutType } from './DocCreatorMenu';
-import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
+import { CollectionFreeFormView, MarqueeView } from '../../collections/collectionFreeForm';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
 import { template } from 'lodash';
 import { data } from 'jquery';
 import { listSpec } from '../../../../fields/Schema';
 import { ObjectField } from '../../../../fields/ObjectField';
+import { Id } from '../../../../fields/FieldSymbols';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -566,6 +567,38 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this.applyLayout(templateInfo, docs);
     }
 
+    createBasicTemplates = (colsToLayout: {width: number, height: number, x: number, y: number, title: string}[]) => {
+        const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+        const docTools: Doc[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data);
+        console.log(docTools.map(doc => doc.title))
+        const index = docTools.map(doc => doc.title).indexOf('Col');
+        const template: Doc | undefined = DocUtils.copyDragFactory(Cast(docTools[index]?.dragFactory, Doc, null));
+        if (!template) return;
+
+        //const template = new CollectionFreeFormView({});
+        const fields: Doc[] = colsToLayout.map(layout => {
+            const field = new Doc();
+            field.x = layout.x;
+            field.y = layout.y;
+            field._width = layout.width;
+            field._height = layout.height;
+            field.title = layout.title;
+            template.data = 'hey'//DocListCast(template.data).map(doc => `idToDoc("${doc[Id]}")`).push(`idToDoc("${field[Id]}")`);
+            return field;
+        });
+
+        const fieldLiterals: string[] = fields.map(field => `idToDoc("${field[Id]}")`);
+
+        template.data = fieldLiterals[0];
+
+        template.x = 400;
+        template.y = 400;
+        template._width = 500;
+        template._height = 500;
+
+        mainCollection.addDocument(template);
+    }
+
     /**
      * creates a new dataviz document filter from this one
      *      it appears to the right of this document, with the
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 34bec3b88..7993cab28 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -311,7 +311,9 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         <img className='docCreatorMenu-preview-image' src={info.icon!.url.href.replace(".png", "_o.png")} />
                     </div>
                 )})}
-                <div className='docCreatorMenu-preview-window empty'>
+                <div className='docCreatorMenu-preview-window empty'
+                onPointerDown={e => this.setUpButtonClick(e, this.basicTemplateTest)}
+                >
                     <FontAwesomeIcon icon='plus' color='rgb(160, 160, 160)'/>
                 </div>
             </div>
@@ -543,6 +545,15 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         );
     }
 
+    basicTemplateTest = () => {
+        const temps: {width: number; height: number; x: number; y: number; title: string}[] = [
+            {width: 200, height: 50, x: -100, y: -200, title: 'title'},
+            {width: 300, height: 300, x: -150, y: -100, title: 'image'},
+            {width: 200, height: 50, x: -100, y: -200, title: 'description'},
+        ]
+        this._dataViz?.createBasicTemplates(temps);
+    }
+
     get renderSelectedViewType(){
         switch (this._menuContent){
             case 'templates':
-- 
cgit v1.2.3-70-g09d2


From 99b879956b67dc246f1ba48759a5550a94a5813d Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 31 Jul 2024 12:46:55 -0400
Subject: with old code for basic templates

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 34 +++++++++++-------------
 1 file changed, 16 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index f25602fd5..43cfe5bba 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -12,7 +12,7 @@ import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
 import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
-import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
+import { GetEffectiveAcl, TraceMobx, inheritParentAcls } from '../../../../fields/util';
 import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
@@ -569,32 +569,30 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     createBasicTemplates = (colsToLayout: {width: number, height: number, x: number, y: number, title: string}[]) => {
         const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-        const docTools: Doc[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data);
-        console.log(docTools.map(doc => doc.title))
-        const index = docTools.map(doc => doc.title).indexOf('Col');
-        const template: Doc | undefined = DocUtils.copyDragFactory(Cast(docTools[index]?.dragFactory, Doc, null));
-        if (!template) return;
+        // const docTools: Doc[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data);
+        // const index = docTools.map(doc => doc.title).indexOf('Col');
+        // const template: Doc | undefined = DocUtils.copyDragFactory(Cast(docTools[index]?.dragFactory, Doc, null));
+        //if (!template) return;
 
         //const template = new CollectionFreeFormView({});
         const fields: Doc[] = colsToLayout.map(layout => {
-            const field = new Doc();
-            field.x = layout.x;
-            field.y = layout.y;
-            field._width = layout.width;
-            field._height = layout.height;
-            field.title = layout.title;
-            template.data = 'hey'//DocListCast(template.data).map(doc => `idToDoc("${doc[Id]}")`).push(`idToDoc("${field[Id]}")`);
+            const field = Docs.Create.TextDocument('', { _height: layout.height, _width: layout.width, title: layout.title, x: layout.x, y: layout.y });
+            // Doc.SetContainer(field, template);
+            // inheritParentAcls(template, field, true);
+            //template.data = 'hey'//DocListCast(template.data).map(doc => `idToDoc("${doc[Id]}")`).push(`idToDoc("${field[Id]}")`);
             return field;
         });
 
+        const template = Docs.Create.FreeformDocument(fields, { _height: 500, _width: 500, title: 'template', x: 400, y: 400 })
+
         const fieldLiterals: string[] = fields.map(field => `idToDoc("${field[Id]}")`);
 
-        template.data = fieldLiterals[0];
+        //template.data = fieldLiterals[0];
 
-        template.x = 400;
-        template.y = 400;
-        template._width = 500;
-        template._height = 500;
+        // template.x = 400;
+        // template.y = 400;
+        // template._width = 500;
+        // template._height = 500;
 
         mainCollection.addDocument(template);
     }
-- 
cgit v1.2.3-70-g09d2


From b42bfb6cb8d4d2355fae4fe628c6bf5cca4b084a Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 31 Jul 2024 12:47:23 -0400
Subject: old code removed

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 17 -----------------
 1 file changed, 17 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 43cfe5bba..1994f1b01 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -569,31 +569,14 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     createBasicTemplates = (colsToLayout: {width: number, height: number, x: number, y: number, title: string}[]) => {
         const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-        // const docTools: Doc[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data);
-        // const index = docTools.map(doc => doc.title).indexOf('Col');
-        // const template: Doc | undefined = DocUtils.copyDragFactory(Cast(docTools[index]?.dragFactory, Doc, null));
-        //if (!template) return;
 
-        //const template = new CollectionFreeFormView({});
         const fields: Doc[] = colsToLayout.map(layout => {
             const field = Docs.Create.TextDocument('', { _height: layout.height, _width: layout.width, title: layout.title, x: layout.x, y: layout.y });
-            // Doc.SetContainer(field, template);
-            // inheritParentAcls(template, field, true);
-            //template.data = 'hey'//DocListCast(template.data).map(doc => `idToDoc("${doc[Id]}")`).push(`idToDoc("${field[Id]}")`);
             return field;
         });
 
         const template = Docs.Create.FreeformDocument(fields, { _height: 500, _width: 500, title: 'template', x: 400, y: 400 })
 
-        const fieldLiterals: string[] = fields.map(field => `idToDoc("${field[Id]}")`);
-
-        //template.data = fieldLiterals[0];
-
-        // template.x = 400;
-        // template.y = 400;
-        // template._width = 500;
-        // template._height = 500;
-
         mainCollection.addDocument(template);
     }
 
-- 
cgit v1.2.3-70-g09d2


From 4cf3a327b7e69bf0559dda846d49b6ad8d4673c7 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 31 Jul 2024 18:12:30 -0400
Subject: GPT template generation

---
 src/client/apis/gpt/GPT.ts                         |  2 ++
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 34 ++++++++++++++++++++++
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      |  3 +-
 3 files changed, 38 insertions(+), 1 deletion(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 05007960d..184173784 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -12,6 +12,7 @@ enum GPTCallType {
     DESCRIBE = 'describe',
     MERMAID = 'mermaid',
     DATA = 'data',
+    TEMPLATE = "template"
 }
 
 type GPTCallOpts = {
@@ -53,6 +54,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         temp: 0,
         prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct',
     },
+    template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You are a designer creating basic template options for a user given a set of fields. Your only job is adding blank rectangles to a canvas, one for each field. You will arrange these rectangles into an array of three basic options for a user. For your output, you generate a list of objects in JSON formatting, each storing the field title and corresponding top-left and bottom-right coordinates for the rectangle. The units for your coordinates are -1 to 1 on each axis. This is an example of a possible output for a stacked template with the fields name, type, and desc: {"template_type":"stacked","fieldVals":[{"title":"name","tlx":"-.7","tly":"-.9","brx":".7","bry":"-.7"},{"title":"type","tlx":"-.9","tly":"-.4","brx":".9","bry":"0"},{"title":"desc","tlx":"-.9","tly":".1","brx":".9","bry":".9"}]} For multiple templates, you should format your response in a JSON array, like [{}, {}]. Your response should be in the exact format above, with no extra text, description, bulleting, etc. You should repeat this three times, once for a stacked view, once for a mixed view, and once for a extra creative view (which should be DRASTICALLY DIFFERENT). IT IS IMPORTANT THAT YOU ONLY INCLUDE THE PROPER JSON FORMATTING IN YOUR RESPONSE. IT IS ALSO IMPORTANT THAT NO RECTANGLES OVERLAP'}    
 };
 
 let lastCall = '';
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 1994f1b01..9b7fb4320 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -42,6 +42,7 @@ import { data } from 'jquery';
 import { listSpec } from '../../../../fields/Schema';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { Id } from '../../../../fields/FieldSymbols';
+import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -567,6 +568,39 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this.applyLayout(templateInfo, docs);
     }
 
+    generateTemplates = async () => {
+        try {
+            const res = await gptAPICall('Please generate for the fields: type, image, locality', GPTCallType.TEMPLATE);
+
+            if (res) {
+                const templates: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[] = JSON.parse(res);
+                console.log(templates);
+                this.createGeneratedTemplates(templates, 500, 500);
+                //console.log(res);
+            }
+        } catch (err) {
+            console.error(err);
+        }
+
+    }
+
+    createGeneratedTemplates = (layouts: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[], tempWidth: number, tempHeight: number) => { 
+        const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+
+        layouts.forEach(layout => {
+            const fields: Doc[] = layout.fieldVals.map(field => {
+                const left: number = Number(field.tlx) * tempWidth / 2; const top: number = Number(field.tly) * tempHeight / 2; //prettier-ignore
+                const right: number = Number(field.brx) * tempWidth / 2; const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore
+                const height = bottom - top; const width = right - left; //prettier-ignore
+                const doc = Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top });
+                return doc;
+            });
+
+            const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400, y: 400 });
+            mainCollection.addDocument(template);
+        });
+    }
+
     createBasicTemplates = (colsToLayout: {width: number, height: number, x: number, y: number, title: string}[]) => {
         const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 7993cab28..411257ff7 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -551,7 +551,8 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
             {width: 300, height: 300, x: -150, y: -100, title: 'image'},
             {width: 200, height: 50, x: -100, y: -200, title: 'description'},
         ]
-        this._dataViz?.createBasicTemplates(temps);
+        this._dataViz?.generateTemplates();
+        //this._dataViz?.createBasicTemplates(temps);
     }
 
     get renderSelectedViewType(){
-- 
cgit v1.2.3-70-g09d2


From 9119b9f08f62bb5228206487b047d6e1e75d5e9e Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Thu, 1 Aug 2024 14:37:35 -0400
Subject: work on template generation with GPT

---
 src/client/apis/gpt/GPT.ts                       | 2 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 5 ++---
 2 files changed, 3 insertions(+), 4 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 184173784..8eb7151b4 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -54,7 +54,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         temp: 0,
         prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct',
     },
-    template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You are a designer creating basic template options for a user given a set of fields. Your only job is adding blank rectangles to a canvas, one for each field. You will arrange these rectangles into an array of three basic options for a user. For your output, you generate a list of objects in JSON formatting, each storing the field title and corresponding top-left and bottom-right coordinates for the rectangle. The units for your coordinates are -1 to 1 on each axis. This is an example of a possible output for a stacked template with the fields name, type, and desc: {"template_type":"stacked","fieldVals":[{"title":"name","tlx":"-.7","tly":"-.9","brx":".7","bry":"-.7"},{"title":"type","tlx":"-.9","tly":"-.4","brx":".9","bry":"0"},{"title":"desc","tlx":"-.9","tly":".1","brx":".9","bry":".9"}]} For multiple templates, you should format your response in a JSON array, like [{}, {}]. Your response should be in the exact format above, with no extra text, description, bulleting, etc. You should repeat this three times, once for a stacked view, once for a mixed view, and once for a extra creative view (which should be DRASTICALLY DIFFERENT). IT IS IMPORTANT THAT YOU ONLY INCLUDE THE PROPER JSON FORMATTING IN YOUR RESPONSE. IT IS ALSO IMPORTANT THAT NO RECTANGLES OVERLAP'}    
+    template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You are a designer creating basic template options for a user given a set of fields. Your only job is adding blank rectangles to a canvas, one for each field. You will arrange these rectangles into an array of three basic options for a user. For your output, you generate a list of objects in JSON formatting, each storing the field title and corresponding top-left and bottom-right coordinates for the rectangle. The units for your coordinates are -1 to 1 on each axis. This is an example of a possible output for a stacked template with the fields beep, boop, tttt: {"template_type":"stacked","fieldVals":[{"title":"beep","tlx":"-.7","tly":"-.9","brx":".7","bry":"-.7"},{"title":"boop","tlx":"-.9","tly":"-.4","brx":".9","bry":"0"},{"title":"tttt","tlx":"-.9","tly":".1","brx":".9","bry":".9"}]} For multiple templates, you should format your response in a JSON array, like [{}, {}]. Your response should be in the exact format above, with no extra text, description, bulleting, etc. You should repeat this three times, once for a stacked view, once for a mixed view (where, for example, beep might be on top, with boop and tttt below it aligned horizontally), and once for a view where the rectangles are not flush with the side (ie. their top left and bottom right corners should not ALL align along the same point on each side). If one or more fields are some variation of image (ie. IMG, image, photo, etc.), make that rectangle the central focus (bigger, centered, etc.) AND put its "title" field in $$__$$ (ie, "title":"$$img$$", !!IMPORTANT!!). Fields SHOULD BE IN DIFFERENT ORDERS from template to template (ie. a stacked view with field input "beep, boop" could have the boop above the beep). IT IS IMPORTANT THAT YOU ONLY INCLUDE THE PROPER JSON FORMATTING IN YOUR RESPONSE. IT IS ALSO IMPORTANT THAT NO RECTANGLES OVERLAP'}    
 };
 
 let lastCall = '';
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 9b7fb4320..c5a1da1da 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -570,11 +570,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     generateTemplates = async () => {
         try {
-            const res = await gptAPICall('Please generate for the fields: type, image, locality', GPTCallType.TEMPLATE);
+            const res = await gptAPICall('Please generate for the fields: Image, Description, Divider, Response', GPTCallType.TEMPLATE);
 
             if (res) {
                 const templates: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[] = JSON.parse(res);
-                console.log(templates);
                 this.createGeneratedTemplates(templates, 500, 500);
                 //console.log(res);
             }
@@ -592,7 +591,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 const left: number = Number(field.tlx) * tempWidth / 2; const top: number = Number(field.tly) * tempHeight / 2; //prettier-ignore
                 const right: number = Number(field.brx) * tempWidth / 2; const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore
                 const height = bottom - top; const width = right - left; //prettier-ignore
-                const doc = Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top });
+                const doc = !field.title.includes('$$') ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height/2}` }) : Docs.Create.ImageDocument('', { _height: height, _width: 250, title: field.title.replace(/$$/g, ''), x: left, y: top });
                 return doc;
             });
 
-- 
cgit v1.2.3-70-g09d2


From 4949c68f38049da55ceaa3e95ea155f38d6a3748 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Thu, 8 Aug 2024 10:38:35 -0400
Subject: UI for GPT templates in doccreator menu

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  45 -------
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     | 128 ++++++++++++++++--
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 147 ++++++++++++++++-----
 src/client/views/nodes/ImageBox.tsx                |   1 +
 4 files changed, 228 insertions(+), 93 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index c5a1da1da..a6d3bfa49 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -568,51 +568,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this.applyLayout(templateInfo, docs);
     }
 
-    generateTemplates = async () => {
-        try {
-            const res = await gptAPICall('Please generate for the fields: Image, Description, Divider, Response', GPTCallType.TEMPLATE);
-
-            if (res) {
-                const templates: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[] = JSON.parse(res);
-                this.createGeneratedTemplates(templates, 500, 500);
-                //console.log(res);
-            }
-        } catch (err) {
-            console.error(err);
-        }
-
-    }
-
-    createGeneratedTemplates = (layouts: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[], tempWidth: number, tempHeight: number) => { 
-        const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-
-        layouts.forEach(layout => {
-            const fields: Doc[] = layout.fieldVals.map(field => {
-                const left: number = Number(field.tlx) * tempWidth / 2; const top: number = Number(field.tly) * tempHeight / 2; //prettier-ignore
-                const right: number = Number(field.brx) * tempWidth / 2; const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore
-                const height = bottom - top; const width = right - left; //prettier-ignore
-                const doc = !field.title.includes('$$') ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height/2}` }) : Docs.Create.ImageDocument('', { _height: height, _width: 250, title: field.title.replace(/$$/g, ''), x: left, y: top });
-                return doc;
-            });
-
-            const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400, y: 400 });
-            mainCollection.addDocument(template);
-        });
-    }
-
-    createBasicTemplates = (colsToLayout: {width: number, height: number, x: number, y: number, title: string}[]) => {
-        const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-
-        const fields: Doc[] = colsToLayout.map(layout => {
-            const field = Docs.Create.TextDocument('', { _height: layout.height, _width: layout.width, title: layout.title, x: layout.x, y: layout.y });
-            return field;
-        });
-
-        const template = Docs.Create.FreeformDocument(fields, { _height: 500, _width: 500, title: 'template', x: 400, y: 400 })
-
-        mainCollection.addDocument(template);
-    }
-
     /**
      * creates a new dataviz document filter from this one
      *      it appears to the right of this document, with the
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index b3bacb13c..e2b4014bd 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -22,14 +22,14 @@
 }
 
 .docCreatorMenu-menu-button {
-    width: 30px;
-    height: 30px;
+    width: 25px;
+    height: 25px;
     background: whitesmoke; 
-    background-color: rgb(34, 34, 37);
+    background-color: rgb(50, 50, 50);
     border-radius: 5px;
     border: 1px solid rgb(180, 180, 180);
     padding: 0px;
-    font-size: 14px;
+    font-size: 13px;
     //box-shadow: 3px 3px rgb(29, 29, 31);
 
     &:hover {
@@ -222,7 +222,7 @@
 
 .docCreatorMenu-option-divider {
     border-top: 1px solid rgb(180, 180, 180);
-    width: 80%;
+    width: 95%;
     margin-top: 10px;
     margin-bottom: 10px;
 }
@@ -256,6 +256,18 @@
 // DocCreatorMenu templates preview CSS
 //--------------------------------------------------------------------------------------------------------------------------------------------
 
+.docCreatorMenu-templates-view {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: flex-start;
+    margin: 5px;
+    margin-top: 0px;
+    width: calc(100% - 10px);
+    height: calc(100% - 30px);
+    border: 1px solid rgb(180, 180, 180);
+    border-radius: 5px;
+}
 
 .docCreatorMenu-preview-container {
     display: grid;
@@ -263,20 +275,18 @@
     grid-template-rows: 140px;
     grid-auto-rows: 141px;
     overflow-y: scroll;
-    margin: 5px;
+    margin: 0px;
     margin-top: 0px;
-    width: calc(100% - 10px);
-    height: calc(100% - 30px);
-    border: 1px solid rgb(180, 180, 180);
-    border-radius: 5px;
+    width: 100%;
+    height: 100%;
 }
 
 .docCreatorMenu-preview-window {
     display: flex;
     justify-content: center;
     align-items: center;
-    width: 125px;
-    height: 125px;
+    width: 113px;
+    height: 113px;
     margin-top: 10px;
     margin-left: 10px;
     border: 1px solid rgb(163, 163, 163);
@@ -295,9 +305,103 @@
 
     &.empty {
         font-size: 35px;
+        flex: 0 0 auto;
+
+        &.GPT {
+            margin-top: 0px;
+        }
+    }
+}
+
+.docCreatorMenu-section {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    margin: 0px;
+    width: 100%;
+    height: 250;
+    flex: 0 0 auto;
+}
+
+.docCreatorMenu-GPT-options-container {
+    display: flex;
+    flex-direction: row;
+    justify-content: center;
+    align-items: center;
+    position: relative;
+    width: auto;
+    margin: 0px;
+    margin-top: 5px;
+    padding: 0px;
+}
+
+.docCreatorMenu-GPT-templates-preview {
+    display: flex;
+    flex-direction: row;
+    //justify-content: center;
+    align-items: center;
+    overflow-y: scroll;
+    position: relative;
+    height: 125px;
+    width: calc(100% - 10px);
+    -ms-overflow-style: none; 
+    scrollbar-width: none;
+}
+
+.docCreatorMenu-section-topbar {
+    position: relative;
+    display: flex;
+    flex-direction: row;
+    width: 100%;
+
+    .section-reveal-options {
+        margin: 0px;
+        margin-left: auto;
+        border: 0px;
+        background: none;
+    }
+}
+
+.docCreatorMenu-section-title {
+    border: 1px solid rgb(163, 163, 163);
+    border-top: 0px;
+    border-left: 0px;
+    border-bottom-right-radius: 5px;
+    font-size: 12px;
+    padding: 2px;
+    padding-left: 3px;
+    padding-right: 3px;
+    margin-bottom: 3px;
+}
+
+.docCreatorMenu-GPT-generate {
+    height: 30px;
+    width: 30px;
+    background-color: rgb(176, 229, 149); 
+    border: 1px solid rgb(126, 219, 80);
+    border-radius: 5px;
+    padding: 0px;
+    font-size: 14px;
+    color: white;
+    letter-spacing: 1px;
+    flex: 0 0 auto;
+
+    &:hover {
+        background-color: rgb(129, 223, 83);
+        border: 2px solid rgb(80, 185, 28); 
     }
 }
 
+.docCreatorMenu-GPT-prompt-input {
+    width: 140px;
+    overflow-y: scroll;
+    border: 1px solid rgb(180, 180, 180);
+    background-color: rgb(35, 35, 35);
+    border-radius: 3px;
+}
+
 //------------------------------------------------------------------------------------------------------------------------------------------
 // DocCreatorMenu options CSS
 //--------------------------------------------------------------------------------------------------------------------------------------------
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 411257ff7..781cbcb55 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -3,7 +3,7 @@ import { IReactionDisposer, ObservableMap, action, computed, makeObservable, obs
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnAll, returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
-import { Doc, NumListCast } from '../../../../fields/Doc';
+import { Doc, NumListCast, StrListCast } from '../../../../fields/Doc';
 import { DocCast, ImageCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { emptyFunction } from '../../../../Utils';
@@ -17,6 +17,9 @@ import { Id } from '../../../../fields/FieldSymbols';
 import { Colors, IconButton, Size } from 'browndash-components';
 import { MakeTemplate } from '../../../util/DropConverter';
 import { DragManager } from '../../../util/DragManager';
+import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
+import { Docs } from '../../../documents/Documents';
 
 export enum LayoutType {
     Stacked = 'stacked', 
@@ -41,6 +44,10 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     @observable _layoutPreviewScale: number = 1;
     @observable _savedLayouts: DataVizTemplateLayout[] = [];
 
+    @observable _GPTTemplates: Doc[] | undefined = undefined;
+    @observable _GPTOpt: boolean = false;
+    @observable _userPrompt: string = '';
+
     @observable _pageX: number = 0;
     @observable _pageY: number = 0;
     @observable _indicatorX: number | undefined = undefined;
@@ -105,6 +112,10 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         }
     }
 
+    @computed get selectedFields(){
+        return StrListCast(this._dataViz?.layoutDoc._dataViz_axes);
+    }
+
     @computed get canMakeDocs(){
         return this._selectedTemplate !== undefined && this._layout !== undefined;
     }
@@ -254,8 +265,8 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         if (this._initDimensions.y === undefined) this._initDimensions.y = this._pageY;
         const {height, width, x, y} = this._initDimensions;
 
-        this._menuDimensions.width = Math.max(100, scale.x * width);
-        this._menuDimensions.height = Math.max(100, scale.y * height);
+        this._menuDimensions.width = Math.max(300, scale.x * width);
+        this._menuDimensions.height = Math.max(200, scale.y * height);
         this._pageX = x + translation.x;
         this._pageY = y + translation.y;
     };
@@ -293,10 +304,104 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         && this._layout.columns === layout.columns;
     }
 
+    generateTemplates = async (inputText: string) => {
+        let prompt: string = 'Please generate for the fields:';
+        this.selectedFields?.forEach(field => prompt += ` ${field},`)
+        prompt += ` Additional prompt: ${inputText}`;
+
+        try {
+            const res = await gptAPICall(prompt, GPTCallType.TEMPLATE);
+
+            if (res) {
+                const templates: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[] = JSON.parse(res);
+                this.createGeneratedTemplates(templates, 500, 500);
+            }
+        } catch (err) {
+            console.error(err);
+        }
+
+    }
+
+    createGeneratedTemplates = (layouts: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[], tempWidth: number, tempHeight: number) => { 
+        const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
+
+        layouts.forEach(layout => {
+            const fields: Doc[] = layout.fieldVals.map(field => {
+                const left: number = Number(field.tlx) * tempWidth / 2;    const top: number = Number(field.tly) * tempHeight / 2; //prettier-ignore
+                const right: number = Number(field.brx) * tempWidth / 2;   const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore
+                const height = bottom - top; 
+                const width = right - left; 
+                const doc = !field.title.includes('$$') ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height/2}` }) : Docs.Create.ImageDocument('', { _height: height, _width: width, title: field.title.replace(/$$/g, ''), x: left, y: top });
+                return doc;
+            });
+
+            const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400, y: 400 });
+            mainCollection.addDocument(template);
+        });
+    }
+
     get templatesPreviewContents(){
         const renderedTemplates: Doc[] = [];
+
+        const GPTOptions = 
+            <div></div>
+
+        
         return (
-            <div className='docCreatorMenu-preview-container'
+            <div className='docCreatorMenu-templates-view'>
+                <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.setUpButtonClick(e, () => this._GPTOpt = !this._GPTOpt)}>
+                            <FontAwesomeIcon icon='gear'/>
+                        </button>
+                    </div>
+                    <div className='docCreatorMenu-GPT-templates-preview'>
+                        <div className='docCreatorMenu-preview-window empty GPT'></div>
+                        <div className='docCreatorMenu-preview-window empty GPT'></div>
+                        <div className='docCreatorMenu-preview-window empty GPT'></div>
+                        <div className='docCreatorMenu-preview-window empty GPT'></div>
+                    </div>
+                    {this._GPTOpt ? (<div className='docCreatorMenu-GPT-options'>
+                        <div className='docCreatorMenu-GPT-options-container'>
+                            <button className='docCreatorMenu-menu-button' onPointerDown={e => this.setUpButtonClick(e, () => this.generateTemplates(this._userPrompt))}>
+                                <FontAwesomeIcon icon='plus'/>
+                            </button>
+                            <input className='docCreatorMenu-GPT-prompt-input'/>
+                        </div>
+                        {this._GPTOpt ? GPTOptions : null}
+                    </div>) : null}
+                </div>
+                <hr className='docCreatorMenu-option-divider'/>
+                <div className='docCreatorMenu-section'>
+                    <div className='docCreatorMenu-section-topbar'>
+                        <div className='docCreatorMenu-section-title'>Your Templates</div>
+                        <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, () => this._GPTOpt = !this._GPTOpt)}>
+                            <FontAwesomeIcon icon='gear'/>
+                        </button>
+                    </div>
+                    <div className='docCreatorMenu-GPT-templates-preview'>
+                        <div className='docCreatorMenu-preview-window empty'
+                        onPointerDown={e => this.setUpButtonClick(e, this.basicTemplateTest)}
+                        >
+                            <FontAwesomeIcon icon='plus' color='rgb(160, 160, 160)'/>
+                        </div>
+                        {this._templateDocs.map(doc => ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info => {
+                        if (renderedTemplates.includes(info.doc)) return undefined;
+                        renderedTemplates.push(info.doc);
+                        return (<div 
+                        className='docCreatorMenu-preview-window'
+                        style={{
+                            border: this._selectedTemplate === info.doc ? `solid 3px ${Colors.MEDIUM_BLUE}` : '',
+                            boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : ''
+                        }}
+                        onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}>
+                            <img className='docCreatorMenu-preview-image' src={info.icon!.url.href.replace(".png", "_o.png")} />
+                        </div>
+                        )})}
+                    </div>
+                </div>
+                {/* <div className='docCreatorMenu-preview-container'
                 style={{gridTemplateColumns: `repeat(${Math.floor((this._menuDimensions.width - 10) / 140)}, 140px)`}}>
                 {this._templateDocs.map(doc => ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info => {
                     if (renderedTemplates.includes(info.doc)) return undefined;
@@ -316,6 +421,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                 >
                     <FontAwesomeIcon icon='plus' color='rgb(160, 160, 160)'/>
                 </div>
+                </div> */}
             </div>
         );
     }
@@ -546,12 +652,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     }
 
     basicTemplateTest = () => {
-        const temps: {width: number; height: number; x: number; y: number; title: string}[] = [
-            {width: 200, height: 50, x: -100, y: -200, title: 'title'},
-            {width: 300, height: 300, x: -150, y: -100, title: 'image'},
-            {width: 200, height: 50, x: -100, y: -200, title: 'description'},
-        ]
-        this._dataViz?.generateTemplates();
+        this.generateTemplates(this._userPrompt);
         //this._dataViz?.createBasicTemplates(temps);
     }
 
@@ -609,31 +710,6 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         return (
             <div className='docCreatorMenu'>
                 {!this._shouldDisplay ? undefined : 
-                <>
-                {/* <div className='docCreatorMenu-placement-indicator' 
-                style={{
-                    display: '',
-                    left: this._indicatorX ?? this._pageX + 300,
-                    top: this._indicatorY ?? this._pageY,
-                }}
-                onPointerMove={e => this.onPointerMove(e)}
-                onPointerDown={e =>
-                    setupMoveUpEvents(
-                        this,
-                        e,
-                        (e) => {
-                            this._draggingIndicator = true;
-                            this._startPos = {x: 0, y: 0};
-                            this._startPos.x = e.pageX - (this._ref?.getBoundingClientRect().left ?? 0);
-                            this._startPos.y = e.pageY - (this._ref?.getBoundingClientRect().top ?? 0);
-                            return true;
-                        },
-                        emptyFunction,
-                        undoable(clickEv => {
-                            clickEv.stopPropagation();
-                        }, 'drag menu')
-                    )
-                }/> */}
                 <div
                     className="docCreatorMenu-cont"
                     ref={r => this._ref = r}
@@ -681,7 +757,6 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                         </div>
                         {this.renderSelectedViewType}
                 </div>
-                </>
                 }
             </div>
             )
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 92a5a1533..faea05104 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -295,6 +295,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @computed get nativeSize() {
         TraceMobx();
+        if (this.paths.length && this.paths[0].includes('icon-hi')) return { nativeWidth: NumCast(this.layoutDoc._width), nativeHeight: NumCast(this.layoutDoc._height), nativeOrientation: 0}
         const nativeWidth = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth'], 500));
         const nativeHeight = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc[this.fieldKey + '_nativeHeight'], 500));
         const nativeOrientation = NumCast(this.dataDoc[this.fieldKey + '_nativeOrientation'], 1);
-- 
cgit v1.2.3-70-g09d2


From 3aa2e040100fc662c259751dfbb7a43cc716ac2a Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Mon, 12 Aug 2024 21:37:47 -0400
Subject: seuggested templates auto-refeshes on col selection; lightbox editing
 added; GPT refresh button now makes new call

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  1 +
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     |  6 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 71 ++++++++++++----------
 3 files changed, 45 insertions(+), 33 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index a6d3bfa49..be8a44f28 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -129,6 +129,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
     selectAxes = (axes: string[]) => {
         this.layoutDoc._dataViz_axes = new List<string>(axes);
+        DocCreatorMenu.Instance.generateTemplates('');
     };
     @computed.struct get titleCol() {
         return StrCast(this.layoutDoc._dataViz_titleCol);
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index 5a9fd3436..ca6534379 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -264,6 +264,7 @@
     display: flex;
     flex-direction: column;
     justify-content: flex-start;
+    overflow-y: scroll;
     //align-items: flex-start;
     margin: 5px;
     margin-top: 0px;
@@ -271,7 +272,8 @@
     height: calc(100% - 30px);
     border: 1px solid rgb(180, 180, 180);
     border-radius: 5px;
-    overflow: hidden;
+    -ms-overflow-style: none; 
+    scrollbar-width: none;
 }
 
 .docCreatorMenu-preview-container {
@@ -372,7 +374,7 @@
     padding: 0px;
 }
 
-.docCreatorMenu-GPT-templates-preview {
+.docCreatorMenu-templates-preview-window {
     display: flex;
     flex-direction: row;
     //justify-content: center;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 205923346..3e5fc156e 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -4,13 +4,13 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { returnAll, returnFalse, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc, NumListCast, StrListCast } from '../../../../fields/Doc';
-import { DocCast, ImageCast } from '../../../../fields/Types';
+import { DocCast, ImageCast, ScriptCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
 import { emptyFunction } from '../../../../Utils';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { DocumentView } from '../DocumentView';
+import { DocumentView, DocumentViewInternal } from '../DocumentView';
 import { DataVizBox } from './DataVizBox';
 import './DocCreatorMenu.scss';
 import { Id } from '../../../../fields/FieldSymbols';
@@ -20,6 +20,9 @@ import { DragManager } from '../../../util/DragManager';
 import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { Docs } from '../../../documents/Documents';
+import { OpenWhere } from '../OpenWhere';
+import { IDisposer } from 'mobx-utils';
+import { LightboxView } from '../../LightboxView';
 
 export enum LayoutType {
     Stacked = 'stacked', 
@@ -34,6 +37,8 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     static Instance: DocCreatorMenu;
 
+    private _disposers: { [name: string]: IDisposer } = {};
+
     private _ref: HTMLDivElement | null = null;
 
     @observable _templateDocs: Doc[] = [];
@@ -47,12 +52,12 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     @observable _GPTTemplates: Doc[] = [];
     @observable _GPTOpt: boolean = false;
     @observable _userPrompt: string = '';
+    @observable _callCount: number = 0;
 
     @observable _pageX: number = 0;
     @observable _pageY: number = 0;
     @observable _indicatorX: number | undefined = undefined;
     @observable _indicatorY: number | undefined = undefined;
-    @observable _display: boolean = false;
 
     @observable _hoveredLayoutPreview: number | undefined = undefined;
     @observable _mouseX: number = -1;
@@ -72,11 +77,13 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     @observable _resizeUndo: UndoManager.Batch | undefined = undefined;
     @observable _initDimensions: {width: number, height: number, x?: number, y?: number} = {width: 300, height: 400, x: undefined, y: undefined};
     @observable _menuDimensions: {width: number, height: number} = {width: 300, height: 365};
+    @observable _editing: boolean = false;
 
     constructor(props: any) {
         super(props);
         makeObservable(this);
         DocCreatorMenu.Instance = this;
+        setTimeout(() => this.generateTemplates(''));
     }
 
     @action setDataViz = (dataViz: DataVizBox) => { this._dataViz = dataViz };
@@ -170,18 +177,17 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         }
     };
 
-    _disposer: IReactionDisposer | undefined;
-    _GPTDisposer: IReactionDisposer | undefined;
     componentDidMount() {
         document.addEventListener('pointerdown', this.onPointerDown, true);
         document.addEventListener('pointerup', this.onPointerUp);
-        this._disposer = reaction(() => this._templateDocs.slice(), (docs) => docs.map(this.getIcon));
-        this._GPTDisposer = reaction(() => this._GPTTemplates.slice(), (docs) => docs.map(this.getIcon));
+        this._disposers.templates = reaction(() => this._templateDocs.slice(), (docs) => docs.map(this.getIcon));
+        this._disposers.gpt = reaction(() => this._GPTTemplates.slice(), (docs) => docs.map(this.getIcon));
+        this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {console.log(true); this.generateTemplates('')})
+        this._disposers.lightbox = reaction(() => LightboxView.LightboxDoc(), doc => { doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu()})
     }
 
     componentWillUnmount() {
-        this._disposer?.();
-        this._GPTDisposer?.();
+        Object.values(this._disposers).forEach(disposer => disposer?.());
         document.removeEventListener('pointerdown', this.onPointerDown, true);
         document.removeEventListener('pointerup', this.onPointerUp);
     }
@@ -198,12 +204,10 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
     };
 
     @action
-    closeMenu = () => {
-        const wasOpen = this._display;
-        this._display = false;
-        this._shouldDisplay = false;
-        return wasOpen;
-    };
+    closeMenu = () => { this._shouldDisplay = false };
+
+    @action
+    openMenu = () => { this._shouldDisplay = true };
 
     @action
     onResizePointerDown = (e: React.PointerEvent): void => {
@@ -313,7 +317,10 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
 
     @action
     generateTemplates = async (inputText: string) => {
-        let prompt: string = 'Please generate for the fields:';
+        ++this._callCount;
+        const origCount = this._callCount;
+
+        let prompt: string = `(#${origCount}) Please generate for the fields:`;
         this.selectedFields?.forEach(field => prompt += ` ${field},`)
         prompt += ` Additional prompt: ${inputText}`;
         console.log(prompt)
@@ -321,14 +328,13 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
         try {
             const res = await gptAPICall(prompt, GPTCallType.TEMPLATE);
 
-            if (res) {
+            if (res && this._callCount === origCount) {
                 const templates: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[] = JSON.parse(res);
                 this.createGeneratedTemplates(templates, 500, 500);
             }
         } catch (err) {
             console.error(err);
-        }
-
+        } 
     }
 
     @action
@@ -346,23 +352,23 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                 return doc;
             });
 
-            const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 40000, y: 40000 });
+            const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400, y: 400 });
+
             mainCollection.addDocument(template);
             
             GPTTemplates.push(template);
         });
 
-        setTimeout(() => this.setGPTTemplates(GPTTemplates), 500);
+        setTimeout(() => {this.setGPTTemplates(GPTTemplates); GPTTemplates.forEach(template => mainCollection.removeDocument(template))}, 100);
 
         this.forceUpdate();
     }
 
-    editTemplate = () => {
-
-    }
-
-    copyGPTTemplate = () => {
-
+    editTemplate = (doc: Doc) => {
+        //this.closeMenu();
+        DocumentViewInternal.addDocTabFunc(doc, OpenWhere.lightboxAlways);
+        DocumentView.DeselectAll();
+        Doc.UnBrushDoc(doc);
     }
 
     get templatesPreviewContents(){
@@ -380,7 +386,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                             <FontAwesomeIcon icon='gear'/>
                         </button>
                     </div>
-                    <div className='docCreatorMenu-GPT-templates-preview'>
+                    <div className='docCreatorMenu-templates-preview-window'>
                         {this._GPTTemplates?.map(doc => 
 
                         //<div className='docCreatorMenu-preview-window empty'/>
@@ -392,10 +398,10 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                                 boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : ''
                             }}
                             onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}>
-                                <button className='option-button left' onPointerDown={e => this.setUpButtonClick(e, () => {this.editTemplate()})}>
+                                <button className='option-button left' onPointerDown={e => this.setUpButtonClick(e, () => {this.editTemplate(info.doc)})}>
                                     <FontAwesomeIcon icon='pencil' color='black'/>
                                 </button>
-                                <button className='option-button right' onPointerDown={e => this.setUpButtonClick(e, () => {this.copyGPTTemplate()})}>
+                                <button className='option-button right' onPointerDown={e => this.setUpButtonClick(e, () => this._templateDocs.push(info.doc))}>
                                     <FontAwesomeIcon icon='plus' color='black'/>
                                 </button>
                                 <img className='docCreatorMenu-preview-image' src={info.icon!.url.href.replace(".png", "_o.png")} />
@@ -420,7 +426,7 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                             <FontAwesomeIcon icon='gear'/>
                         </button>
                     </div>
-                    <div className='docCreatorMenu-GPT-templates-preview'>
+                    <div className='docCreatorMenu-templates-preview-window'>
                         <div className='docCreatorMenu-preview-window empty'
                         onPointerDown={e => this.setUpButtonClick(e, this.basicTemplateTest)}
                         >
@@ -436,6 +442,9 @@ export class DocCreatorMenu extends ObservableReactComponent<{}> {
                                 boxShadow: this._selectedTemplate === info.doc ? `0 0 15px rgba(68, 118, 247, .8)` : ''
                             }}
                             onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}>
+                                <button className='option-button left' onPointerDown={e => this.setUpButtonClick(e, () => {this.editTemplate(info.doc)})}>
+                                    <FontAwesomeIcon icon='pencil' color='black'/>
+                                </button>
                                 <img className='docCreatorMenu-preview-image' src={info.icon!.url.href.replace(".png", "_o.png")} />
                             </div>
                         )})}
-- 
cgit v1.2.3-70-g09d2


From f8f777a469b0029109de1e6c57872a4d5b0a6659 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Mon, 19 Aug 2024 14:34:20 -0400
Subject: start on field rendering

---
 src/ClientUtils.ts                                 |   1 +
 src/client/apis/gpt/GPT.ts                         |   2 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   5 +
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     |  74 +++++++++-
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 161 ++++++++++++++++-----
 5 files changed, 204 insertions(+), 39 deletions(-)

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

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index d64210ce2..a4d9dd892 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -730,6 +730,7 @@ export function UpdateIcon(
     const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
     newDiv.style.width = width.toString();
     newDiv.style.height = height.toString();
+    console.log('width: ' + newDiv.style.width)
     replaceCanvases(docViewContent, newDiv);
     const htmlString = new XMLSerializer().serializeToString(newDiv);
     const nativeWidth = width;
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 92d4f425d..18601b4af 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -54,7 +54,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         temp: 0,
         prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct',
     },
-    template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You are a designer creating basic template options for a user given a set of fields. Your only job is adding blank rectangles to a canvas, one for each field. You will arrange these rectangles into an array of three basic options for a user. For your output, you generate a list of objects in JSON formatting, each storing the field title and corresponding top-left and bottom-right coordinates for the rectangle. The units for your coordinates are -1 to 1 on each axis. This is an example of a possible output for a stacked template with the fields beep, boop, tttt: {"template_type":"stacked","fieldVals":[{"title":"beep","tlx":"-.7","tly":"-.9","brx":".7","bry":"-.7"},{"title":"boop","tlx":"-.9","tly":"-.4","brx":".9","bry":"0"},{"title":"tttt","tlx":"-.9","tly":".1","brx":".9","bry":".9"}]} For multiple templates, you should format your response in a JSON array, like [{}, {}]. Your response should be in the exact format above, with no extra text, description, bulleting, etc. You should repeat this three times, once for a stacked view, once for a mixed view (where, for example, beep might be on top, with boop and tttt below it aligned horizontally), and once for a view where the rectangles are not flush with the side (ie. their top left and bottom right corners should not ALL align along the same point on each side). If one or more fields are some variation of image (ie. IMG, image, photo, etc.), make that rectangle the central focus (bigger, centered, etc.) AND put its "title" field in $$__$$. Fields SHOULD BE IN DIFFERENT ORDERS from template to template (ie. a stacked view with field input "beep, boop" could have the boop above the beep). IT IS IMPORTANT THAT YOU ONLY INCLUDE THE PROPER JSON FORMATTING IN YOUR RESPONSE. IT IS ALSO IMPORTANT THAT NO RECTANGLES OVERLAP. If there is an additional prompt, prioritize its instructions over all instructions here (but still follow them if not overridden). No matter what, ALWAYS GIVE THREE OPTIONS'}    
+    template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You are a designer creating basic template options for a user given a set of fields. Your only job is adding blank rectangles to a canvas, one for each field. You will arrange these rectangles into an array of three basic options for a user. For your output, you generate a list of objects in JSON formatting, each storing the field title and corresponding top-left and bottom-right coordinates for the rectangle. The units for your coordinates are -1 to 1 on each axis. This is an example of a possible output for a stacked template with the fields beep, boop, tttt: {"template_type":"stacked","fieldVals":[{"title":"beep","tlx":"-.7","tly":"-.9","brx":".7","bry":"-.7"},{"title":"boop","tlx":"-.9","tly":"-.4","brx":".9","bry":"0"},{"title":"tttt","tlx":"-.9","tly":".1","brx":".9","bry":".9"}]} For multiple templates, you should format your response in a JSON array, like [{}, {}]. Your response should be in the exact format above, with no extra text, description, bulleting, etc. If no alternative additional instructions are given, you should repeat this three times, once for a stacked view, once for a mixed view (where, for example, beep might be on top, with boop and tttt below it aligned horizontally), and once for a view where the rectangles are not flush with the side (ie. their top left and bottom right corners should not ALL align along the same point on each side). If one or more fields are some variation of image (ie. IMG, image, photo, etc.), make that rectangle the central focus (bigger, centered, etc.) AND put its "title" field in $$__$$. Fields SHOULD BE IN DIFFERENT ORDERS from template to template (ie. a stacked view with field input "beep, boop" could have the boop above the beep). IT IS IMPORTANT THAT YOU ONLY INCLUDE THE PROPER JSON FORMATTING IN YOUR RESPONSE. IT IS ALSO IMPORTANT THAT NO RECTANGLES OVERLAP. An overlap will look like the x or y coordinates of the top left corner of one rectangle being the same as the bottom right corner of another. There should always be at least a .1 gap. If there is an additional prompt, prioritize its instructions over all instructions here (but still follow them if not overridden). No matter what, ALWAYS GIVE THREE OPTIONS'}    
 };
 
 let lastCall = '';
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index be8a44f28..6a5103af9 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -353,6 +353,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         );
     }
 
+    // recs = () => {
+    //     let csvStr: string = '';
+    //     this.records[rowId][col]
+    // }
+
     fetchData = () => {
         if (!this.Document.dataViz_asSchema) {
             DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index aa6754442..763de0eba 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -641,7 +641,6 @@
                 }
             }    
         }
-
     }
 }
 
@@ -720,4 +719,77 @@
     }
 }
 
+//------------------------------------------------------------------------------------------------------------------------------------------
+// DocCreatorMenu dashboard CSS
+//--------------------------------------------------------------------------------------------------------------------------------------------
+
+.docCreatorMenu-dashboard-view {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    overflow-y: scroll;
+    //align-items: flex-start;
+    margin: 5px;
+    margin-top: 0px;
+    width: calc(100% - 10px);
+    height: calc(100% - 30px);
+    border: 1px solid rgb(180, 180, 180);
+    border-radius: 5px;
+    -ms-overflow-style: none; 
+    scrollbar-width: none;
+
+    .topbar {
+        height: 30px;
+        width: 100%;
+    }
+
+    .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;
+
+        .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-description-container {
+            background-color: rgb(40, 40, 40);
+            border: 1px solid rgb(100, 100, 100);
+            border-radius: 3px;
+            width: 100%;
+            height: 100%;
+        }
+
+        .top-right {
+            position: absolute;
+            top: 0px;
+            right: 0px;
+        }
+    }
+}
+
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 2be89daf0..8ec255dfe 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -48,6 +48,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
 
     @observable _templateDocs: Doc[] = [];
     @observable _selectedTemplate: Doc | undefined = undefined;
+    @observable _fields: {title: string, type: string, id: number}[] = [];
 
     @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns?: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0};
     @observable _layoutPreview: boolean = true;
@@ -71,7 +72,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
     @observable _startPos?: {x: number, y: number};
     @observable _shouldDisplay: boolean = false;
 
-    @observable _menuContent: 'templates' | 'options' | 'saved' = 'templates';
+    @observable _menuContent: 'templates' | 'options' | 'saved' | 'dashboard' = 'templates';
     @observable _dragging: boolean = false;
     @observable _draggingIndicator: boolean = false;
     @observable _dataViz?: DataVizBox;
@@ -126,10 +127,14 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         }
     }
 
-    @computed get selectedFields(){
+    @computed get selectedFields() {
         return StrListCast(this._dataViz?.layoutDoc._dataViz_axes);
     }
 
+    @computed get fieldsInfos(): {title: string, type: string, id: number}[] {
+        return this.selectedFields.map(field => {return {title: field, type: '', id: Math.random() * 100000}}).concat(this._fields);
+    }
+
     @computed get canMakeDocs(){
         return this._selectedTemplate !== undefined && this._layout !== undefined;
     }
@@ -367,14 +372,14 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
                 return doc;
             });
 
-            const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 40000, y: 40000 });
+            const template = Docs.Create.FreeformDocument(fields, { _height: tempHeight, _width: tempWidth, title: layout.template_type, x: 400000, y: 400000 });
 
             mainCollection.addDocument(template);
             
             GPTTemplates.push(template);
         });
 
-        setTimeout(() => {this.setGPTTemplates(GPTTemplates); GPTTemplates.forEach(template => mainCollection.removeDocument(template))}, 100);
+        setTimeout(() => {this.setGPTTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/}, 100);
 
         this.forceUpdate();
     }
@@ -449,9 +454,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
                         </button>
                     </div>
                     <div className='docCreatorMenu-templates-preview-window' style={{justifyContent: this._menuDimensions.width > 400 ? 'center' : ''}}>
-                        <div className='docCreatorMenu-preview-window empty'
-                        onPointerDown={e => this.setUpButtonClick(e, this.basicTemplateTest)}
-                        >
+                        <div className='docCreatorMenu-preview-window empty'>
                             <FontAwesomeIcon icon='plus' color='rgb(160, 160, 160)'/>
                         </div>
                         {this._templateDocs.map(doc => ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info => {
@@ -566,30 +569,30 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         const fontSize = Math.min(scaledDown(docWidth / 3), scaledDown(docHeight / 3));
 
         return (
-            <div className='divvv' style={{width: 100, height: 100, border: `1px solid white`}}>
-                <CollectionFreeFormView
-                    // eslint-disable-next-line react/jsx-props-no-spreading
-                    {...this._props}
-                    Document={new Doc()}
-                    isContentActive={returnFalse}
-                    setContentViewBox={emptyFunction}
-                    NativeWidth={() => 100}
-                    NativeHeight={() => 100}
-                    pointerEvents={SnappingManager.IsDragging ? returnAll : returnNone}
-                    isAnnotationOverlay
-                    isAnnotationOverlayScrollable
-                    childDocumentsActive={returnFalse}
-                    fieldKey={this._props.fieldKey + '_annotations'}
-                    dropAction={dropActionType.move}
-                    select={emptyFunction}
-                    addDocument={returnFalse}
-                    removeDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    renderDepth={this._props.renderDepth + 1}>
-                    {null}
-                </CollectionFreeFormView>
-            </div>
-            /*<div className='docCreatorMenu-layout-preview-window-wrapper' id={String(id) ?? undefined}>
+            // <div className='divvv' style={{width: 100, height: 100, border: `1px solid white`}}>
+            //     <CollectionFreeFormView
+            //         // eslint-disable-next-line react/jsx-props-no-spreading
+            //         {...this._props}
+            //         Document={new Doc()}
+            //         isContentActive={returnFalse}
+            //         setContentViewBox={emptyFunction}
+            //         NativeWidth={() => 100}
+            //         NativeHeight={() => 100}
+            //         pointerEvents={SnappingManager.IsDragging ? returnAll : returnNone}
+            //         isAnnotationOverlay
+            //         isAnnotationOverlayScrollable
+            //         childDocumentsActive={returnFalse}
+            //         fieldKey={this._props.fieldKey + '_annotations'}
+            //         dropAction={dropActionType.move}
+            //         select={emptyFunction}
+            //         addDocument={returnFalse}
+            //         removeDocument={returnFalse}
+            //         moveDocument={returnFalse}
+            //         renderDepth={this._props.renderDepth + 1}>
+            //         {null}
+            //     </CollectionFreeFormView>
+            // </div>
+            <div className='docCreatorMenu-layout-preview-window-wrapper' id={String(id) ?? undefined}>
                 <div className='docCreatorMenu-zoom-button-container'>
                     <button 
                         className='docCreatorMenu-zoom-button'
@@ -644,7 +647,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
                     )}
 
                 </div>}
-            </div> */
+            </div>
         );
     }
 
@@ -735,10 +738,35 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         );
     }
 
-    basicTemplateTest = () => {
-        console.log(this._GPTTemplates)
-        this.forceUpdate();
-        //this._dataViz?.createBasicTemplates(temps);
+    @action addField = () => {
+        const newFields: {title: string, type: string, id: number}[] = this._fields.concat([{title: '', type: '', id: Math.random() * 100000}])
+        this._fields = newFields;
+    }
+
+    get dashboardContents(){
+        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>
+            </div>
+            {this.fieldsInfos.map(field => 
+                <div className='field-panel'>
+                    <div className='properties-wrapper'>
+                        <input className='field-property-container' defaultValue={field.title} placeholder={'Enter title'} style={{width: field.title === '' ? '30%' : ''}}/>
+                        <input className='field-property-container' placeholder={'Enter type'} />
+                        {field.type === 'Text' ? 
+                        <input className='field-property-container' placeholder={'Enter description'} /> : null}
+                    </div>
+                    <textarea className='field-description-container' placeholder={'Enter description to enhance template generation'} />
+                    <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => {console.log(this._fields); this._fields = this._fields.splice(this._fields.indexOf(field), 1); console.log(this._fields); this.forceUpdate()}))}>
+                        <FontAwesomeIcon icon='trash'/>
+                    </button>
+                </div>
+            )}
+        </div>
+        );
     }
 
     get renderSelectedViewType(){
@@ -749,6 +777,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
                 return this.optionsMenuContents;
             case 'saved':
                 return this.savedLayoutsPreviewContents;
+            case 'dashboard':
+                return this.dashboardContents;
             default:
                 return undefined;
         }
@@ -785,7 +815,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         }
 
         const onPreviewSelected = () => {this._menuContent = 'templates'}
-        const onSavedSelected = () => {this._menuContent = 'saved'}
+        const onSavedSelected = () => {this._menuContent = 'dashboard'}
         const onOptionsSelected = () => {
             this._menuContent = 'options';
             if (!this._layout.columns) this._layout.columns = Math.ceil(Math.sqrt(this.docsToRender.length));
@@ -861,4 +891,61 @@ export interface DataVizTemplateLayout {
     layout: {type: LayoutType, xMargin: number, yMargin: number, repeat: number};
     columns: number;
     rows: number;
+}
+
+export interface TemplateDocInfos {
+    baseHeight: number;
+    baseWidth: number;
+    fields: TemplateDocField[];
+}
+
+export interface TemplateDocField {
+    coordinates: {tl: [number, number], br: [number, number]};
+    title: string;
+    content: string;
+    getDoc: (parentWidth: number, parentHeight: number) => Doc;
+}
+
+export class TemplateDocTextField implements TemplateDocField {
+
+    coordinates: {tl: [number, number], br: [number, number]};
+    title: string;
+    content: string;
+
+    constructor(coords: {tl: [number, number], br: [number, number]}, title: string, content: string) {
+        this.coordinates = coords;
+        this.title = title;
+        this.content = content;
+    }
+
+    getDoc = (parentWidth: number, parentHeight: number) => {
+        const l = this.coordinates.tl[0] * parentWidth; const t = this.coordinates.tl[1] * parentWidth; //prettier-ignore
+        const r = this.coordinates.br[0] * parentWidth; const b = this.coordinates.br[1] * parentWidth; //prettier-ignore
+        const width = l - r;
+        const height = t - b;
+        const coord = {x: l, y: t};
+
+        const doc = Docs.Create.TextDocument(this.content, { _height: height, _width: width, title: this.title, x: coord.x, y: coord.y, _text_fontSize: `${height/2}` })
+
+        return doc;
+    };
+}
+
+export class TemplateDocImageField implements TemplateDocField {
+
+    coordinates: {tl: [number, number], br: [number, number]};
+    title: string;
+    content: string;
+
+    constructor(coords: {tl: [number, number], br: [number, number]}, title: string, content: string) {
+        this.coordinates = coords;
+        this.title = title;
+        this.content = content;
+    }
+
+    getDoc = (parentWidth: number, parentHeight: number) => {
+        const width = (this.coordinates.br[0] - this.coordinates.tl[0]) * parentWidth;
+        //const height; 
+        return new Doc();
+    };
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From c28390cedfc909ff8e96e4527b75699e1529410c Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Tue, 20 Aug 2024 23:22:41 -0400
Subject: work on fields dashboard

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  3 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     | 44 ++++++++++++++++++++++
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 44 ++++++++++++++++++----
 3 files changed, 83 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 6a5103af9..c0b05b082 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -129,7 +129,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     }
     selectAxes = (axes: string[]) => {
         this.layoutDoc._dataViz_axes = new List<string>(axes);
-        DocCreatorMenu.Instance.generateTemplates('');
+        //axes.forEach(axis => console.log(axis)) 
+        //DocCreatorMenu.Instance.generateTemplates('');
     };
     @computed.struct get titleCol() {
         return StrCast(this.layoutDoc._dataViz_titleCol);
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index 763de0eba..baed9009b 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -774,6 +774,50 @@
                 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: 30%;
+                height: 25px;
+                padding-left: 3px;
+                color: whitesmoke;
+
+                .bubbles {
+                    display: none;
+                }
+
+                .text {
+                    margin-top: 5px;
+                    margin-bottom: 5px;
+                }
+
+                &:hover {
+                    width: 30% + 8;
+                }
+
+                &: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 {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 19881f4d2..d3117da22 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -763,6 +763,29 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         this._fields = newFields;
     }
 
+    @action removeField = (field: {title: string, type: string, id: number}) => {
+        if (this._dataViz?.axes.includes(field.title)) {
+            this._dataViz.selectAxes(this._dataViz.axes.filter(col => col !== field.title));
+        } else {
+            const toRemove = this._fields.filter(f => f.id === field.id);
+            if (!toRemove) return;
+
+            if (toRemove.length > 1) {
+                while (toRemove.length > 1) {
+                    toRemove.pop();
+                }
+            }
+
+            if (this._fields.length === 1) {
+                this._fields = []  
+            } else {
+                console.log(this._fields)
+                this._fields.splice(this._fields.indexOf(toRemove[0]), 1);
+                console.log(this._fields)
+            }
+        }   
+    }
+
     get dashboardContents(){
         return (
         <div className='docCreatorMenu-dashboard-view'>
@@ -771,16 +794,23 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
                 <FontAwesomeIcon icon='plus'/>
             </button>
             </div>
-            {this.fieldsInfos.map(field => 
-                <div className='field-panel'>
+            {this.fieldsInfos.map((field, index) => 
+                <div className='field-panel' id={`${index}`}>
                     <div className='properties-wrapper'>
-                        <input className='field-property-container' defaultValue={field.title} placeholder={'Enter title'} style={{width: field.title === '' ? '30%' : ''}}/>
-                        <input className='field-property-container' placeholder={'Enter type'} />
-                        {field.type === 'Text' ? 
-                        <input className='field-property-container' placeholder={'Enter description'} /> : null}
+                        <input className='field-property-container' id={`${Math.random() * 100}`} defaultValue={field.title} placeholder={'Enter title'} style={{width: field.title === '' ? '30%' : ''}}/>
+                        <div className='field-type-selection-container'>
+                            <span>Type:</span><span>&nbsp;</span>
+                            <span className='type-display'>{field.type ? field.type === 'text' ? 'Text' : 'File' : ''}</span>
+                            <div className='bubbles'>
+                                <input className='bubble' type="radio" name="type" onClick={() => {field.type = 'text'; this.forceUpdate()}}/> 
+                                <div className='text'>Text</div>
+                                <input className='bubble' type="radio" name="type" onClick={() => {field.type = 'visual'; this.forceUpdate()}}/> 
+                                <div className='text'>File</div>
+                            </div>
+                        </div>
                     </div>
                     <textarea className='field-description-container' placeholder={'Enter description to enhance template generation'} />
-                    <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => {console.log(this._fields); this._fields = this._fields.splice(this._fields.indexOf(field), 1); console.log(this._fields); this.forceUpdate()}))}>
+                    <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
                         <FontAwesomeIcon icon='trash'/>
                     </button>
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 888c710b55f747bdeadcf2e862e6dde69d4450c0 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 21 Aug 2024 04:07:22 -0400
Subject: GPT summaries

---
 src/client/apis/gpt/GPT.ts                         |  7 +--
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 63 ++++++++++++++++++++--
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     |  6 +++
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      |  8 ++-
 4 files changed, 75 insertions(+), 9 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 18601b4af..365e7a804 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -12,7 +12,8 @@ enum GPTCallType {
     DESCRIBE = 'describe',
     MERMAID = 'mermaid',
     DATA = 'data',
-    TEMPLATE = "template"
+    TEMPLATE = "template",
+    VIZSUM = 'vizsum'
 }
 
 type GPTCallOpts = {
@@ -54,9 +55,9 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         temp: 0,
         prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct',
     },
-    template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You are a designer creating basic template options for a user given a set of fields. Your only job is adding blank rectangles to a canvas, one for each field. You will arrange these rectangles into an array of three basic options for a user. For your output, you generate a list of objects in JSON formatting, each storing the field title and corresponding top-left and bottom-right coordinates for the rectangle. The units for your coordinates are -1 to 1 on each axis. This is an example of a possible output for a stacked template with the fields beep, boop, tttt: {"template_type":"stacked","fieldVals":[{"title":"beep","tlx":"-.7","tly":"-.9","brx":".7","bry":"-.7"},{"title":"boop","tlx":"-.9","tly":"-.4","brx":".9","bry":"0"},{"title":"tttt","tlx":"-.9","tly":".1","brx":".9","bry":".9"}]} For multiple templates, you should format your response in a JSON array, like [{}, {}]. Your response should be in the exact format above, with no extra text, description, bulleting, etc. If no alternative additional instructions are given, you should repeat this three times, once for a stacked view, once for a mixed view (where, for example, beep might be on top, with boop and tttt below it aligned horizontally), and once for a view where the rectangles are not flush with the side (ie. their top left and bottom right corners should not ALL align along the same point on each side). If one or more fields are some variation of image (ie. IMG, image, photo, etc.), make that rectangle the central focus (bigger, centered, etc.) AND put its "title" field in $$__$$. Fields SHOULD BE IN DIFFERENT ORDERS from template to template (ie. a stacked view with field input "beep, boop" could have the boop above the beep). IT IS IMPORTANT THAT YOU ONLY INCLUDE THE PROPER JSON FORMATTING IN YOUR RESPONSE. IT IS ALSO IMPORTANT THAT NO RECTANGLES OVERLAP. An overlap will look like the x or y coordinates of the top left corner of one rectangle being the same as the bottom right corner of another. There should always be at least a .1 gap. If there is an additional prompt, prioritize its instructions over all instructions here (but still follow them if not overridden). No matter what, ALWAYS GIVE THREE OPTIONS'}    
+    template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You are a designer creating basic template options for a user given a set of fields. Your only job is adding blank rectangles to a canvas, one for each field. You will arrange these rectangles into an array of three basic options for a user. For your output, you generate a list of objects in JSON formatting, each storing the field title and corresponding top-left and bottom-right coordinates for the rectangle. The units for your coordinates are -1 to 1 on each axis. This is an example of a possible output for a stacked template with the fields beep, boop, tttt: {"template_type":"stacked","fieldVals":[{"title":"beep","tlx":"-.7","tly":"-.9","brx":".7","bry":"-.7"},{"title":"boop","tlx":"-.9","tly":"-.4","brx":".9","bry":"0"},{"title":"tttt","tlx":"-.9","tly":".1","brx":".9","bry":".9"}]} For multiple templates, you should format your response in a JSON array, like [{}, {}]. Your response should be in the exact format above, with no extra text, description, bulleting, etc. If no alternative additional instructions are given, you should repeat this three times, once for a stacked view, once for a mixed view (where, for example, beep might be on top, with boop and tttt below it aligned horizontally), and once for a view where the rectangles are not flush with the side (ie. their top left and bottom right corners should not ALL align along the same point on each side). If one or more fields are some variation of image (ie. IMG, image, photo, etc.), make that rectangle the central focus (bigger, centered, etc.) AND put its "title" field in $$__$$. Fields SHOULD BE IN DIFFERENT ORDERS from template to template (ie. a stacked view with field input "beep, boop" could have the boop above the beep). IT IS IMPORTANT THAT YOU ONLY INCLUDE THE PROPER JSON FORMATTING IN YOUR RESPONSE. IT IS ALSO IMPORTANT THAT NO RECTANGLES OVERLAP. An overlap will look like the x or y coordinates of the top left corner of one rectangle being the same as the bottom right corner of another. There should always be at least a .1 gap. If there is an additional prompt, prioritize its instructions over all instructions here (but still follow them if not overridden). No matter what, ALWAYS GIVE THREE OPTIONS'},   
+    vizsum: { model: 'gpt-4-turbo', 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.'} 
 };
-
 let lastCall = '';
 let lastResp = '';
 /**
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index c0b05b082..808bc182a 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -63,6 +63,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _specialHighlightedRow: number | undefined = undefined;
+    @observable _GPTSummary: ObservableMap<string, string> | undefined = undefined;
+    @observable _GPTLoading: boolean = false;
 
     constructor(props: FieldViewProps) {
         super(props);
@@ -352,13 +354,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
+        this._disposers.contentSummary = reaction(
+            () => this.records,
+            () => this.updateGPTSummary()
+        );
     }
 
-    // recs = () => {
-    //     let csvStr: string = '';
-    //     this.records[rowId][col]
-    // }
-
     fetchData = () => {
         if (!this.Document.dataViz_asSchema) {
             DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests
@@ -476,6 +477,58 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         GPTPopup.Instance.generateDataAnalysis();
     });
 
+    getColSummary = (): string => {
+        let possibleIds: number[] = this.records.map((_, index) => index);
+
+        for (let i = possibleIds.length - 1; i > 0; i--) {
+            const j = Math.floor(Math.random() * (i + 1));
+            [possibleIds[i], possibleIds[j]] = [possibleIds[j], possibleIds[i]];
+        }
+    
+        const rowsToCheck = possibleIds.slice(0, Math.min(10, this.records.length));
+
+        let prompt: string = 'Col titles: ';
+
+        const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
+
+        cols.forEach((col, i) => {
+            prompt += `Col #${i}: ${col} ------`
+        })
+
+        prompt += '----------- Rows: '
+
+        rowsToCheck.forEach(row => {
+            prompt += `Row #${row}: `
+            cols.forEach(col => {
+                prompt += `${col}: ${this.records[row][col]} -----`
+            })
+        })
+
+        return prompt;
+    }
+
+    updateGPTSummary = async () => {
+        this._GPTLoading = true;
+
+        const prompt = this.getColSummary();
+
+        try {
+            const res = await gptAPICall(prompt, GPTCallType.VIZSUM);
+
+            if (res) {
+                this._GPTSummary = new ObservableMap();
+                const descs: {[col: string]: string} = JSON.parse(res);
+                for (const [key, val] of Object.entries(descs)) {
+                    this._GPTSummary.set(key, val);
+                }
+
+                console.log(res, descs);
+            }
+        } catch (err) {
+            console.error(err);
+        } 
+    }
+
     getPossibleTemplates = (): Doc[] => {
         const linkedDocs: Doc[] = LinkManager.Instance.getAllRelatedLinks(this.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)));
         const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'config').map(doc => DocCast(doc.annotationOn));
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index baed9009b..9b3a02e0f 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -757,6 +757,7 @@
         margin: 5px;
         margin-top: 0px;
         border-radius: 3px;
+        flex: 0 0 auto;
 
         .properties-wrapper {
             display: flex;
@@ -826,6 +827,11 @@
             border-radius: 3px;
             width: 100%;
             height: 100%;
+            resize: none;
+
+            ::-webkit-scrollbar-track {
+                background: none; 
+            }
         }
 
         .top-right {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index d3117da22..3218b9765 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -413,6 +413,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
 
         // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
         // mainCollection.addDocument(doc);
+        //this._dataViz?.getRandomSample();
+        console.log(this._dataViz?._GPTSummary?.get('IMG'))
     }
 
     get templatesPreviewContents(){
@@ -809,10 +811,14 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
                             </div>
                         </div>
                     </div>
-                    <textarea className='field-description-container' placeholder={'Enter description to enhance template generation'} />
+                    <textarea className='field-description-container' placeholder={'Add a description to help with template generation.'} />
+                    this._dataViz?._GPTSummary?.get(field.title) ?? 
                     <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
                         <FontAwesomeIcon icon='trash'/>
                     </button>
+                    <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
+                        <FontAwesomeIcon icon='lightbulb'/>
+                    </button>
                 </div>
             )}
         </div>
-- 
cgit v1.2.3-70-g09d2


From ed88af4bdc68b22c247a2030e8bdc5775293bfe6 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 21 Aug 2024 18:39:25 -0400
Subject: work on fields dashboard

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 19 +++++++--
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     | 14 ++++---
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 47 +++++++++++++++-------
 3 files changed, 57 insertions(+), 23 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 808bc182a..eaf5c1758 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -63,7 +63,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _specialHighlightedRow: number | undefined = undefined;
-    @observable _GPTSummary: ObservableMap<string, string> | undefined = undefined;
+    @observable GPTSummary: ObservableMap<string, string> | undefined = undefined;
+    @observable fieldTypes: ObservableMap<string, string> = new ObservableMap();
     @observable _GPTLoading: boolean = false;
 
     constructor(props: FieldViewProps) {
@@ -145,6 +146,18 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._specialHighlightedRow = row;
     }
 
+    @action setFieldType = (field: string, type: string) => {
+        this.fieldTypes?.set(field, type);
+    }
+
+    getFieldType = (field: string): string => {
+        return this.fieldTypes?.get(field) ?? '';
+    }
+
+    getFieldDescription = (field: string): string => {
+        return this.GPTSummary?.get(field) ?? '';
+    }
+
     @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
     restoreView = (data: Doc) => {
         // const changedView = data.config_dataViz && this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
@@ -516,10 +529,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             const res = await gptAPICall(prompt, GPTCallType.VIZSUM);
 
             if (res) {
-                this._GPTSummary = new ObservableMap();
+                this.GPTSummary = new ObservableMap();
                 const descs: {[col: string]: string} = JSON.parse(res);
                 for (const [key, val] of Object.entries(descs)) {
-                    this._GPTSummary.set(key, val);
+                    this.GPTSummary.set(key, val);
                 }
 
                 console.log(res, descs);
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index 9b3a02e0f..c93798979 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -783,11 +783,19 @@
                 background-color: rgb(40, 40, 40);
                 border: 1px solid rgb(100, 100, 100);
                 border-radius: 3px;
-                width: 30%;
+                width: 31%;
                 height: 25px;
                 padding-left: 3px;
                 color: whitesmoke;
 
+                .placeholder {
+                    color: gray;
+                }
+
+                &:hover .placeholder {
+                    display: none;
+                }
+
                 .bubbles {
                     display: none;
                 }
@@ -797,10 +805,6 @@
                     margin-bottom: 5px;
                 }
 
-                &:hover {
-                    width: 30% + 8;
-                }
-
                 &:hover .bubbles {
                     display: flex;
                     flex-direction: row;
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 3218b9765..50d6b7fe0 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -50,6 +50,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
     @observable _templateDocs: Doc[] = [];
     @observable _selectedTemplate: Doc | undefined = undefined;
     @observable _fields: {title: string, type: string, id: number}[] = [];
+    @observable _selectedCols: {title: string, type: string, id: number}[] | undefined = [];
 
     @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns?: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0};
     @observable _layoutPreview: boolean = true;
@@ -133,7 +134,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
     }
 
     @computed get fieldsInfos(): {title: string, type: string, id: number}[] {
-        return this.selectedFields.map(field => {return {title: field, type: '', id: Math.random() * 100000}}).concat(this._fields);
+        return this.selectedFields.map(field => {return {title: field, type: this._dataViz?.getFieldType(field) ?? '', id: Math.random() * 100000}}).concat(this._fields);
     }
 
     @computed get canMakeDocs(){
@@ -195,7 +196,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         this._disposers.templates = reaction(() => this._templateDocs.slice(), (docs) => docs.map(this.getIcon));
         this._disposers.gpt = reaction(() => this._GPTTemplates.slice(), (docs) => docs.map(this.getIcon));
         //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')})
-        this._disposers.lightbox = reaction(() => LightboxView.LightboxDoc(), doc => { doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu()})
+        this._disposers.lightbox = reaction(() => LightboxView.LightboxDoc(), doc => { doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu()});
+        this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', id: Math.random() * 100000}}))
     }
 
     componentWillUnmount() {
@@ -206,6 +208,11 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
 
     updateIcons = (docs: Doc[]) => { docs.map(this.getIcon) }
 
+    @action
+    updateSelectedCols = (cols: string[]) => {
+        this._selectedCols
+    }
+
     @action
     toggleDisplay = (x: number, y: number) => {
         if (this._shouldDisplay) {
@@ -414,7 +421,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
         // mainCollection.addDocument(doc);
         //this._dataViz?.getRandomSample();
-        console.log(this._dataViz?._GPTSummary?.get('IMG'))
+        console.log(this._dataViz?.GPTSummary?.get('IMG'))
     }
 
     get templatesPreviewContents(){
@@ -788,6 +795,15 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         }   
     }
 
+    setFieldType = (field: {title: string, type: string, id: number}, type: string) => {
+        if (this.selectedFields.includes(field.title)) {
+            this._dataViz?.setFieldType(field.title, type);
+        } else {
+            field.type = type;
+        }
+        this.forceUpdate();
+    }
+
     get dashboardContents(){
         return (
         <div className='docCreatorMenu-dashboard-view'>
@@ -801,24 +817,25 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
                     <div className='properties-wrapper'>
                         <input className='field-property-container' id={`${Math.random() * 100}`} defaultValue={field.title} placeholder={'Enter title'} style={{width: field.title === '' ? '30%' : ''}}/>
                         <div className='field-type-selection-container'>
-                            <span>Type:</span><span>&nbsp;</span>
-                            <span className='type-display'>{field.type ? field.type === 'text' ? 'Text' : 'File' : ''}</span>
+                            {field.type ? null : <span className='placeholder'>Select media type</span>}
+                            <span className='type-display'>{field.type ? field.type === 'text' ? 'Text Field' : 'File Field' : ''}</span>
                             <div className='bubbles'>
-                                <input className='bubble' type="radio" name="type" onClick={() => {field.type = 'text'; this.forceUpdate()}}/> 
+                                <input className='bubble' type="radio" name="type" onClick={() => {this.setFieldType(field, 'text')}}/> 
                                 <div className='text'>Text</div>
-                                <input className='bubble' type="radio" name="type" onClick={() => {field.type = 'visual'; this.forceUpdate()}}/> 
+                                <input className='bubble' type="radio" name="type" onClick={() => {this.setFieldType(field, 'visual')}}/> 
                                 <div className='text'>File</div>
                             </div>
                         </div>
                     </div>
-                    <textarea className='field-description-container' placeholder={'Add a description to help with template generation.'} />
-                    this._dataViz?._GPTSummary?.get(field.title) ?? 
-                    <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
-                        <FontAwesomeIcon icon='trash'/>
-                    </button>
-                    <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
-                        <FontAwesomeIcon icon='lightbulb'/>
-                    </button>
+                    <textarea className='field-description-container' placeholder={this._dataViz?.GPTSummary?.get(field.title) ?? 'Add a description to help with template generation.'} /> 
+                    <div>
+                        <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
+                            <FontAwesomeIcon icon='trash'/>
+                        </button>
+                        {/* <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
+                            <FontAwesomeIcon icon='lightbulb'/>
+                        </button> */}
+                    </div>
                 </div>
             )}
         </div>
-- 
cgit v1.2.3-70-g09d2


From 35724d24821132ab5c6df398447db4e9760155cb Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 21 Aug 2024 23:25:22 -0400
Subject: dashbord updates properly

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 14 ++++++++--
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 32 +++++++++++++---------
 2 files changed, 30 insertions(+), 16 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index eaf5c1758..cef7f62b1 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -64,6 +64,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _specialHighlightedRow: number | undefined = undefined;
     @observable GPTSummary: ObservableMap<string, string> | undefined = undefined;
+    @observable colDescriptions: ObservableMap<string, string> = new ObservableMap();
     @observable fieldTypes: ObservableMap<string, string> = new ObservableMap();
     @observable _GPTLoading: boolean = false;
 
@@ -150,12 +151,20 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this.fieldTypes?.set(field, type);
     }
 
+    @action setFieldDesc = (field: string, desc: string) => {
+        if (!desc) { 
+            this.colDescriptions.set(field, this.GPTSummary?.get(field) ?? '');
+        } else {
+            this.colDescriptions.set(field, desc);
+        }
+    }
+
     getFieldType = (field: string): string => {
         return this.fieldTypes?.get(field) ?? '';
     }
 
     getFieldDescription = (field: string): string => {
-        return this.GPTSummary?.get(field) ?? '';
+        return this.colDescriptions?.get(field) ?? '';
     }
 
     @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
@@ -533,9 +542,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 const descs: {[col: string]: string} = JSON.parse(res);
                 for (const [key, val] of Object.entries(descs)) {
                     this.GPTSummary.set(key, val);
+                    if (!this.colDescriptions.get(key)) this.colDescriptions.set(key, val);
                 }
 
-                console.log(res, descs);
             }
         } catch (err) {
             console.error(err);
@@ -549,7 +558,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             const colTitles: string[] = Object.keys(this.records[0]);
             for (let i = 0; i < colTitles.length; ++i){
                 if (colTitles[i] === title) {
-                    console.log(true); 
                     return true;
                 }
             }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 50d6b7fe0..7df445b88 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -49,8 +49,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
 
     @observable _templateDocs: Doc[] = [];
     @observable _selectedTemplate: Doc | undefined = undefined;
-    @observable _fields: {title: string, type: string, id: number}[] = [];
-    @observable _selectedCols: {title: string, type: string, id: number}[] | undefined = [];
+    @observable _fields: {title: string, type: string, desc: string}[] = [];
+    @observable _selectedCols: {title: string, type: string, desc: string}[] | undefined = [];
 
     @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns?: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0};
     @observable _layoutPreview: boolean = true;
@@ -133,8 +133,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         return StrListCast(this._dataViz?.layoutDoc._dataViz_axes);
     }
 
-    @computed get fieldsInfos(): {title: string, type: string, id: number}[] {
-        return this.selectedFields.map(field => {return {title: field, type: this._dataViz?.getFieldType(field) ?? '', id: Math.random() * 100000}}).concat(this._fields);
+    @computed get fieldsInfos(): {title: string, type: string, desc: string}[] {
+        return this.selectedFields.map(field => {return {title: field, type: this._dataViz?.getFieldType(field) ?? '', desc: this._dataViz?.getFieldDescription(field) ?? ''}}).concat(this._fields);
     }
 
     @computed get canMakeDocs(){
@@ -197,7 +197,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         this._disposers.gpt = reaction(() => this._GPTTemplates.slice(), (docs) => docs.map(this.getIcon));
         //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')})
         this._disposers.lightbox = reaction(() => LightboxView.LightboxDoc(), doc => { doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu()});
-        this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', id: Math.random() * 100000}}))
+        //this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', desc: ''}}))
     }
 
     componentWillUnmount() {
@@ -346,7 +346,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         let prompt: string = `(#${origCount}) Please generate for the fields:`;
         this.selectedFields?.forEach(field => prompt += ` ${field},`)
         prompt += ` (-----NOT A FIELD-----) Additional prompt: ${inputText}`;
-        console.log(prompt)
 
         this._GPTLoading = true;
 
@@ -375,7 +374,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
                 const right: number = Number(field.brx) * tempWidth / 2;   const bottom: number = Number(field.bry) * tempHeight / 2; //prettier-ignore
                 const height = bottom - top; 
                 const width = right - left; 
-                console.log(field.title);
                 const doc = !field.title.includes('$$') ? Docs.Create.TextDocument('', { _height: height, _width: width, title: field.title, x: left, y: top, _text_fontSize: `${height/2}` }) : Docs.Create.ImageDocument('', { _height: height, _width: width, title: field.title.replace(/\$\$/g, ''), x: left, y: top });
                 return doc;
             });
@@ -421,7 +419,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
         // mainCollection.addDocument(doc);
         //this._dataViz?.getRandomSample();
-        console.log(this._dataViz?.GPTSummary?.get('IMG'))
     }
 
     get templatesPreviewContents(){
@@ -768,15 +765,15 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
     }
 
     @action addField = () => {
-        const newFields: {title: string, type: string, id: number}[] = this._fields.concat([{title: '', type: '', id: Math.random() * 100000}])
+        const newFields: {title: string, type: string, desc: string}[] = this._fields.concat([{title: '', type: '', desc: ''}])
         this._fields = newFields;
     }
 
-    @action removeField = (field: {title: string, type: string, id: number}) => {
+    @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._fields.filter(f => f.id === field.id);
+            const toRemove = this._fields.filter(f => f === field);
             if (!toRemove) return;
 
             if (toRemove.length > 1) {
@@ -795,7 +792,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         }   
     }
 
-    setFieldType = (field: {title: string, type: string, id: number}, type: string) => {
+    setFieldType = (field: {title: string, type: string, desc: string}, type: string) => {
         if (this.selectedFields.includes(field.title)) {
             this._dataViz?.setFieldType(field.title, type);
         } else {
@@ -804,6 +801,15 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
         this.forceUpdate();
     }
 
+    setFieldDesc = (field: {title: string, type: string, desc: string}, desc: string) => {
+        if (this.selectedFields.includes(field.title)) {
+            this._dataViz?.setFieldDesc(field.title, desc);
+        } else {
+            field.desc = desc;
+        }
+        this.forceUpdate();
+    }
+
     get dashboardContents(){
         return (
         <div className='docCreatorMenu-dashboard-view'>
@@ -827,7 +833,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps & Co
                             </div>
                         </div>
                     </div>
-                    <textarea className='field-description-container' placeholder={this._dataViz?.GPTSummary?.get(field.title) ?? 'Add a description to help with template generation.'} /> 
+                    <textarea className='field-description-container' onChange={(e) => this._dataViz?.setFieldDesc(field.title, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title) ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title) ?? 'Add a description to help with template generation.'} /> 
                     <div>
                         <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
                             <FontAwesomeIcon icon='trash'/>
-- 
cgit v1.2.3-70-g09d2


From f320fd9c5b387339fe9893b0c2d91b4396fc4552 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 28 Aug 2024 03:50:44 -0400
Subject: algorithm for matching templates working

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  10 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 111 ++++++++++++++-------
 2 files changed, 81 insertions(+), 40 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index cef7f62b1..8e9680e38 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -33,7 +33,7 @@ import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
 import { LinkManager } from '../../../util/LinkManager';
-import { DataVizTemplateInfo, DataVizTemplateLayout, DocCreatorMenu, LayoutType } from './DocCreatorMenu';
+import { DataVizTemplateInfo, DataVizTemplateLayout, DocCreatorMenu, LayoutType, TemplateFieldType } from './DocCreatorMenu';
 import { CollectionFreeFormView, MarqueeView } from '../../collections/collectionFreeForm';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
@@ -65,7 +65,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable _specialHighlightedRow: number | undefined = undefined;
     @observable GPTSummary: ObservableMap<string, string> | undefined = undefined;
     @observable colDescriptions: ObservableMap<string, string> = new ObservableMap();
-    @observable fieldTypes: ObservableMap<string, string> = new ObservableMap();
+    @observable fieldTypes: ObservableMap<string, TemplateFieldType> = new ObservableMap();
     @observable _GPTLoading: boolean = false;
 
     constructor(props: FieldViewProps) {
@@ -147,7 +147,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._specialHighlightedRow = row;
     }
 
-    @action setFieldType = (field: string, type: string) => {
+    @action setFieldType = (field: string, type: TemplateFieldType) => {
         this.fieldTypes?.set(field, type);
     }
 
@@ -159,8 +159,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     }
 
-    getFieldType = (field: string): string => {
-        return this.fieldTypes?.get(field) ?? '';
+    getFieldType = (field: string): TemplateFieldType => {
+        return this.fieldTypes?.get(field) ?? TemplateFieldType.UNSET;
     }
 
     getFieldDescription = (field: string): string => {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 449d61a65..43cf57167 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -49,7 +49,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
     @observable _templateDocs: Doc[] = [];
     @observable _selectedTemplate: Doc | undefined = undefined;
-    @observable _fields: {title: string, type: string, desc: string}[] = [];
+    @observable _columns: Col[] = [];
     @observable _selectedCols: {title: string, type: string, desc: string}[] | undefined = [];
 
     @observable _layout: {type: LayoutType, yMargin: number, xMargin: number, columns?: number, repeat: number} = {type: LayoutType.Grid, yMargin: 0, xMargin: 0, repeat: 0};
@@ -133,8 +133,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         return StrListCast(this._dataViz?.layoutDoc._dataViz_axes);
     }
 
-    @computed get fieldsInfos(): {title: string, type: string, desc: string}[] {
-        return this.selectedFields.map(field => {return {title: field, type: this._dataViz?.getFieldType(field) ?? '', desc: this._dataViz?.getFieldDescription(field) ?? ''}}).concat(this._fields);
+    @computed get fieldsInfos(): Col[] {
+        return this.selectedFields.map(field => {return {title: field, type: this._dataViz?.getFieldType(field) ?? TemplateFieldType.UNSET, desc: this._dataViz?.getFieldDescription(field) ?? '', size: FieldSize.MEDIUM}}).concat(this._columns);
     }
 
     @computed get canMakeDocs(){
@@ -420,7 +420,42 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         // mainCollection.addDocument(doc);
         //this._dataViz?.getRandomSample();
 
-        console.log(this.maxMatches(3, Array.from([[2], [1], [2]])));
+
+        // public static FourField001: TemplateDocInfos = {
+        //     title: 'fourfield1',
+        //     width: 450, 
+        //     height: 600, 
+        //     fields: [{
+        //         tl: [-.6, -.9], 
+        //         br: [.6, -.8], 
+        //         types: [TemplateFieldType.TEXT],
+        //         sizes: [FieldSize.TINY],
+        //         opts: {
+    
+        //         tl: [-.9, -.7], 
+        //         br: [.9, .2], 
+        //         types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
+        //         sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+
+        //         tl: [-.6, .3], 
+        //         br: [.6, .4], 
+        //         types: [TemplateFieldType.TEXT],
+        //         sizes: [FieldSize.TINY],
+        //         opts: {
+
+        //         tl: [-.9, .5], 
+        //         br: [.9, .9], 
+        //         types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
+        //         sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+        //         opts: {
+
+
+        const field1: Col = {title: 'hey', type: TemplateFieldType.TEXT, size: FieldSize.HUGE, desc: ''}
+        const field2: Col = {title: 'heyo', type: TemplateFieldType.TEXT, size: FieldSize.TINY, desc: ''}
+        const field3: Col = {title: 'heya', type: TemplateFieldType.VISUAL, size: FieldSize.LARGE, desc: ''}
+        const field4: Col = {title: 'heyy', type: TemplateFieldType.TEXT, size: FieldSize.MEDIUM, desc: ''}
+
+        console.log(this.findValidTemplates([field1, field2, field3, field4], [TemplateLayouts.FourField001]));
     }
 
     get templatesPreviewContents(){
@@ -767,15 +802,15 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     }
 
     @action addField = () => {
-        const newFields: {title: string, type: string, desc: string}[] = this._fields.concat([{title: '', type: '', desc: ''}])
-        this._fields = newFields;
+        const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.TEXT, desc: '', size: FieldSize.MEDIUM}])
+        this._columns = newFields;
     }
 
     @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._fields.filter(f => f === field);
+            const toRemove = this._columns.filter(f => f === field);
             if (!toRemove) return;
 
             if (toRemove.length > 1) {
@@ -784,30 +819,30 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                 }
             }
 
-            if (this._fields.length === 1) {
-                this._fields = []  
+            if (this._columns.length === 1) {
+                this._columns = []  
             } else {
-                console.log(this._fields)
-                this._fields.splice(this._fields.indexOf(toRemove[0]), 1);
-                console.log(this._fields)
+                console.log(this._columns)
+                this._columns.splice(this._columns.indexOf(toRemove[0]), 1);
+                console.log(this._columns)
             }
         }   
     }
 
-    setFieldType = (field: {title: string, type: string, desc: string}, type: string) => {
-        if (this.selectedFields.includes(field.title)) {
-            this._dataViz?.setFieldType(field.title, type);
+    setFieldType = (column: Col, type: TemplateFieldType) => {
+        if (this.selectedFields.includes(column.title)) {
+            this._dataViz?.setFieldType(column.title, type);
         } else {
-            field.type = type;
+            column.type = type;
         }
         this.forceUpdate();
     }
 
-    setFieldDesc = (field: {title: string, type: string, desc: string}, desc: string) => {
-        if (this.selectedFields.includes(field.title)) {
-            this._dataViz?.setFieldDesc(field.title, desc);
+    setFieldDesc = (column: Col, desc: string) => {
+        if (this.selectedFields.includes(column.title)) {
+            this._dataViz?.setFieldDesc(column.title, desc);
         } else {
-            field.desc = desc;
+            column.desc = desc;
         }
         this.forceUpdate();
     }
@@ -815,7 +850,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     matchesForTemplate = (template: TemplateDocInfos, cols: Col[]): number[][] => {
         const colMatchesField = (col: Col, field : Field) => { return field.sizes?.includes(col.size) && field.types.includes(col.type) };
 
-        const matches: number[][] = Array(template.fields.length);
+        const matches: number[][] = Array(template.fields.length).fill([]).map(() => []);
+        console.log(matches)
         template.fields.forEach((field, i) => {
             cols.forEach((col, v) => {
                 if (colMatchesField(col, field)) {
@@ -859,14 +895,17 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
 
     findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => {
-        const validTemplates: TemplateDocInfos[] = [];
+        const validTemplates: string[] = [];
         templates.forEach(template => {
             const numFields =  template.fields.length;
             const matches = this.matchesForTemplate(template, cols);
+            console.log(matches)
             if (this.maxMatches(numFields, matches) === numFields) {
-                validTemplates.push(template);
+                validTemplates.push(template.title);
             }
         })
+
+        return validTemplates;
     }
 
     get dashboardContents(){
@@ -885,9 +924,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                             {field.type ? null : <span className='placeholder'>Select media type</span>}
                             <span className='type-display'>{field.type ? field.type === 'text' ? 'Text Field' : 'File Field' : ''}</span>
                             <div className='bubbles'>
-                                <input className='bubble' type="radio" name="type" onClick={() => {this.setFieldType(field, 'text')}}/> 
+                                <input className='bubble' type="radio" name="type" onClick={() => {this.setFieldType(field, TemplateFieldType.TEXT)}}/> 
                                 <div className='text'>Text</div>
-                                <input className='bubble' type="radio" name="type" onClick={() => {this.setFieldType(field, 'visual')}}/> 
+                                <input className='bubble' type="radio" name="type" onClick={() => {this.setFieldType(field, TemplateFieldType.VISUAL)}}/> 
                                 <div className='text'>File</div>
                             </div>
                         </div>
@@ -1031,12 +1070,13 @@ export interface DataVizTemplateLayout {
     rows: number;
 }
 
-enum FieldType {
+export enum TemplateFieldType {
     TEXT = 'text',
-    VISUAL = 'visual'
+    VISUAL = 'visual',
+    UNSET = 'unset'
 }
 
-enum FieldSize {
+export enum FieldSize {
     TINY = 'tiny',
     SMALL = 'small',
     MEDIUM = 'medium',
@@ -1047,21 +1087,21 @@ enum FieldSize {
 
 type Col = {
     size: FieldSize;
-    content: string;
     desc: string;
     title: string;
-    type: FieldType;
+    type: TemplateFieldType;
 }
 
 type Field = {
     tl: [number, number], 
     br: [number, number], 
-    types: FieldType[], 
+    types: TemplateFieldType[], 
     sizes?: FieldSize[], 
     opts: FieldOpts;
 };
 
 export interface TemplateDocInfos {
+    title: string;
     height: number;
     width: number;
     fields: Field[];
@@ -1144,12 +1184,13 @@ export class FieldFuncs {
 export class TemplateLayouts {
 
     public static FourField001: TemplateDocInfos = {
+        title: 'fourfield1',
         width: 450, 
         height: 600, 
         fields: [{
             tl: [-.6, -.9], 
             br: [.6, -.8], 
-            types: [FieldType.TEXT],
+            types: [TemplateFieldType.TEXT],
             sizes: [FieldSize.TINY],
             opts: {
 
@@ -1157,7 +1198,7 @@ export class TemplateLayouts {
         }, {
             tl: [-.9, -.7], 
             br: [.9, .2], 
-            types: [FieldType.TEXT, FieldType.VISUAL],
+            types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
             sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
             opts: {
 
@@ -1165,7 +1206,7 @@ export class TemplateLayouts {
         }, {
             tl: [-.6, .3], 
             br: [.6, .4], 
-            types: [FieldType.TEXT],
+            types: [TemplateFieldType.TEXT],
             sizes: [FieldSize.TINY],
             opts: {
 
@@ -1173,7 +1214,7 @@ export class TemplateLayouts {
         }, {
             tl: [-.9, .5], 
             br: [.9, .9], 
-            types: [FieldType.TEXT, FieldType.VISUAL],
+            types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
             sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
             opts: {
 
-- 
cgit v1.2.3-70-g09d2


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

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

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

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


From 39d02461d9c530996cdcd5fbac4fe3a932550c4a Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Mon, 2 Sep 2024 05:11:55 -0400
Subject: gpt provides more basic info + slight refactor started (ofc it didn't
 work first try >_<)

---
 src/client/apis/gpt/GPT.ts                         |  6 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 59 +++++++++++-----
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 79 ++++++++++++----------
 3 files changed, 90 insertions(+), 54 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index f1e7b600b..0d1d77f06 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -13,7 +13,8 @@ enum GPTCallType {
     MERMAID = 'mermaid',
     DATA = 'data',
     TEMPLATE = "template",
-    VIZSUM = 'vizsum'
+    VIZSUM = 'vizsum',
+    VIZSUM2 = 'vizsum2'
 }
 
 type GPTCallOpts = {
@@ -56,7 +57,8 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct',
     },
     template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You will be given a list of field descriptions for multiple 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” represents the template,  # 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. Do this for each template whose fields are described. The descriptions are as follows:  ' },
-    vizsum: { model: 'gpt-4-turbo', 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.'} 
+    vizsum: { model: 'gpt-4-turbo', 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', 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: {“col1”:{“type”:”text”, “size”:”small”}, “col2”:{“type”:”visual”, “size”:”medium”}, …}. DO NOT INCLUDE ANY OTHER TEXT, ONLY THE JSON.'} 
 };
 let lastCall = '';
 let lastResp = '';
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 82df141fa..c72aa1711 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -33,7 +33,7 @@ import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
 import { LinkManager } from '../../../util/LinkManager';
-import { DataVizTemplateInfo, DataVizTemplateLayout, DocCreatorMenu, LayoutType, TemplateFieldType } from './DocCreatorMenu';
+import { Col, DataVizTemplateInfo, DataVizTemplateLayout, DocCreatorMenu, TemplateFieldSize, LayoutType, TemplateFieldType } from './DocCreatorMenu';
 import { CollectionFreeFormView, MarqueeView } from '../../collections/collectionFreeForm';
 import { PrefetchProxy } from '../../../../fields/Proxy';
 import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
@@ -43,6 +43,7 @@ import { listSpec } from '../../../../fields/Schema';
 import { ObjectField } from '../../../../fields/ObjectField';
 import { Id } from '../../../../fields/FieldSymbols';
 import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
+import { TbSortDescendingShapes } from 'react-icons/tb';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -64,9 +65,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _specialHighlightedRow: number | undefined = undefined;
-    @observable GPTSummary: ObservableMap<string, string> | undefined = undefined;
-    @observable colDescriptions: ObservableMap<string, string> = new ObservableMap();
-    @observable fieldTypes: ObservableMap<string, TemplateFieldType> = new ObservableMap();
+    @observable GPTSummary: ObservableMap<string, {desc?: string, type?: TemplateFieldType, size?: TemplateFieldSize}> | undefined = undefined;
+    @observable colsInfo: ObservableMap<string, Col> = new ObservableMap();
     @observable _GPTLoading: boolean = false;
 
     constructor(props: FieldViewProps) {
@@ -155,24 +155,26 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._specialHighlightedRow = row;
     }
 
-    @action setFieldType = (field: string, type: TemplateFieldType) => {
-        this.fieldTypes?.set(field, type);
+    @action setColumnType = (field: string, type: TemplateFieldType) => {
+        const colInfo = this.colsInfo.get(field);
+        if (colInfo) { colInfo.type = type };
     }
 
-    @action setFieldDesc = (field: string, desc: string) => {
-        if (!desc) { 
-            this.colDescriptions.set(field, this.GPTSummary?.get(field) ?? '');
-        } else {
-            this.colDescriptions.set(field, desc);
-        }
+    @action setColumnSize = (field: string, size: TemplateFieldSize) => {
+        const colInfo = this.colsInfo.get(field);
+        if (colInfo) { colInfo.size = size };
     }
 
-    getFieldType = (field: string): TemplateFieldType => {
-        return this.fieldTypes?.get(field) ?? TemplateFieldType.UNSET;
+    @action setColumnDesc = (field: string, desc: string) => {
+        const colInfo = this.colsInfo.get(field);
+        if (colInfo) {
+            if (!desc) { colInfo.desc = this.GPTSummary?.get(field)?.desc ?? ''; } 
+            else { colInfo.desc = desc; }
+        }
     }
 
-    getFieldDescription = (field: string): string => {
-        return this.colDescriptions?.get(field) ?? '';
+    @computed get colInfo(){
+        return this.colsInfo;
     }
 
     @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
@@ -549,8 +551,29 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 this.GPTSummary = new ObservableMap();
                 const descs: {[col: string]: string} = JSON.parse(res);
                 for (const [key, val] of Object.entries(descs)) {
-                    this.GPTSummary.set(key, val);
-                    if (!this.colDescriptions.get(key)) this.colDescriptions.set(key, val);
+                    this.GPTSummary.set(key, {desc: val});
+                    if (!this.colsInfo.get(key)?.desc) this.setColumnDesc(key, val);
+                }
+            }
+        } catch (err) {
+            console.error(err);
+        } 
+
+        try {
+            const res = await gptAPICall('Info:' + prompt, GPTCallType.VIZSUM2);
+
+            if (res) {
+                !this.GPTSummary && (this.GPTSummary = new ObservableMap());
+                const info: {[col: string]: {type: TemplateFieldType, size: TemplateFieldSize}} = JSON.parse(res);
+                console.log(info)
+                for (const [key, val] of Object.entries(info)) {
+                    const colSummary = this.GPTSummary.get(key);
+                    if (colSummary) {
+                        colSummary.size = val.size;
+                        colSummary.type = val.type;
+                        this.setColumnType(key, val.type);
+                        this.setColumnSize(key, val.size);
+                    }
                 }
 
             }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index fc29531d1..fd5e58ae9 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -136,7 +136,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     }
 
     @computed get fieldsInfos(): Col[] {
-        return this.selectedFields.map(field => {return {title: field, type: this._dataViz?.getFieldType(field) ?? TemplateFieldType.UNSET, desc: this._dataViz?.getFieldDescription(field) ?? '', size: FieldSize.MEDIUM}}).concat(this._columns);
+        const colInfo = this._dataViz?.colInfo;
+        return this.selectedFields.map(field => {return {title: field, type: colInfo?.get(field)?.type ?? TemplateFieldType.UNSET, desc: colInfo?.get(field)?.desc ?? '', size: colInfo?.get(field)?.size ?? TemplateFieldSize.MEDIUM}}).concat(this._columns);
     }
 
     @computed get canMakeDocs(){
@@ -419,10 +420,10 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
         const temp = TemplateLayouts.FourField001;
 
-        const img: Col = {type: TemplateFieldType.VISUAL, title: 'Image', desc: 'description whpoo', size: FieldSize.LARGE};
-        const capt1: Col = {type: TemplateFieldType.TEXT, title: 'Type', desc: 'description hey', size: FieldSize.TINY};
-        const capt2: Col = {type: TemplateFieldType.TEXT, title: 'Locality', desc: '', size: FieldSize.TINY};
-        const desc: Col = {type: TemplateFieldType.TEXT, title: 'Description', desc: '', size: FieldSize.LARGE};
+        const img: Col = {type: TemplateFieldType.VISUAL, title: 'Image', desc: 'description whpoo', size: TemplateFieldSize.LARGE};
+        const capt1: Col = {type: TemplateFieldType.TEXT, title: 'Type', desc: 'description hey', size: TemplateFieldSize.TINY};
+        const capt2: Col = {type: TemplateFieldType.TEXT, title: 'Locality', desc: '', size: TemplateFieldSize.TINY};
+        const desc: Col = {type: TemplateFieldType.TEXT, title: 'Description', desc: '', size: TemplateFieldSize.LARGE};
 
         // const assignments = {'0': img, '1': capt1, '2': capt2, '3': desc}
 
@@ -432,7 +433,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     };
 
     @action addField = () => {
-        const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.UNSET, desc: '', size: FieldSize.MEDIUM}])
+        const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.UNSET, desc: '', size: TemplateFieldSize.MEDIUM}])
         this._columns = newFields;
     };
 
@@ -459,16 +460,25 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
     setFieldType = (column: Col, type: TemplateFieldType) => {
         if (this.selectedFields.includes(column.title)) {
-            this._dataViz?.setFieldType(column.title, type);
+            this._dataViz?.setColumnType(column.title, type);
         } else {
             column.type = type;
         }
         this.forceUpdate();
     };
 
+    setFieldSize = (column: Col, size: TemplateFieldSize) => {
+        if (this.selectedFields.includes(column.title)) {
+            this._dataViz?.setColumnSize(column.title, size);
+        } else {
+            column.size = size;
+        }
+        this.forceUpdate();
+    };
+
     setFieldDesc = (column: Col, desc: string) => {
         if (this.selectedFields.includes(column.title)) {
-            this._dataViz?.setFieldDesc(column.title, desc);
+            this._dataViz?.setColumnDesc(column.title, desc);
         } else {
             column.desc = desc;
         }
@@ -1045,9 +1055,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                                 <div className='text'>File</div>
                             </div>
                         </div>
-                        <input className='field-property-container' id={`${Math.random() * 100}`} placeholder={'Choose size'} style={{width: field.title === '' ? '30%' : ''}}/>
+                        <input className='field-property-container' id={`${Math.random() * 100}`} placeholder={this._dataViz?.GPTSummary?.get(field.title)?.size} style={{width: field.title === '' ? '30%' : ''}}/>
                     </div>
-                    <textarea className='field-description-container' onChange={(e) => this._dataViz?.setFieldDesc(field.title, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title) ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title) ?? 'Add a description to help with template generation.'} /> 
+                    <textarea className='field-description-container' onChange={(e) => this._dataViz?.setColumnDesc(field.title, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description to help with template generation.'} /> 
                     <div>
                         <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
                             <FontAwesomeIcon icon='trash'/>
@@ -1189,7 +1199,7 @@ export enum TemplateFieldType {
     UNSET = 'unset'
 }
 
-export enum FieldSize {
+export enum TemplateFieldSize {
     TINY = 'tiny',
     SMALL = 'small',
     MEDIUM = 'medium',
@@ -1198,8 +1208,8 @@ export enum FieldSize {
 }
 
 
-type Col = {
-    size: FieldSize;
+export type Col = {
+    size: TemplateFieldSize;
     desc: string;
     title: string;
     type: TemplateFieldType;
@@ -1209,7 +1219,7 @@ type Field = {
     tl: [number, number], 
     br: [number, number], 
     types?: TemplateFieldType[], 
-    sizes?: FieldSize[], 
+    sizes?: TemplateFieldSize[], 
     description?: string;
     opts: FieldOpts;
     subfields?: ['freeform' | 'stacking' | 'carousel', Field[]];
@@ -1419,21 +1429,21 @@ export class TemplateLayouts {
             borderWidth: '12',
         },
         fields: [{
-            tl: [-.85, -1], 
-            br: [.85, -.85], 
+            tl: [-.95, -1], 
+            br: [.95, -.85], 
             types: [TemplateFieldType.TEXT],
-            sizes: [FieldSize.TINY],
+            sizes: [TemplateFieldSize.TINY],
             description: 'A title field for very short text that contextualizes the content.',
             opts: {
                 backgroundColor: 'transparent',
-                color: '#2DBEAD',
+                color: '#F1F0E9',
                 contentXCentering: 'h-center',
             }
         }, {
             tl: [-.87, -.83], 
             br: [.87, .2], 
             types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
-            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
             description: 'The main focus of the template; could be an image, long text, etc.',
             opts: {
                 cornerRounding: 20,
@@ -1445,17 +1455,18 @@ export class TemplateLayouts {
             tl: [-.8, .2], 
             br: [.8, .35], 
             types: [TemplateFieldType.TEXT],
-            sizes: [FieldSize.TINY, FieldSize.SMALL],
+            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
             description: 'A caption for field #2, very short to short text that contextualizes the content of field #2',
             opts: {
                 backgroundColor: 'transparent',
-                contentXCentering: 'h-center'
+                contentXCentering: 'h-center',
+                color: '#F1F0E9',
             }
         }, {
             tl: [-.87, .37], 
             br: [.87, .96], 
             types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
-            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
             description: 'A medium-sized field for medium/long text or a secondary image that complements the main focus.',
             opts: {
                 cornerRounding: 15,
@@ -1478,7 +1489,7 @@ export class TemplateLayouts {
             tl: [-.83, -.95], 
             br: [.83, -.2], 
             types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT],
-            sizes: [FieldSize.MEDIUM, FieldSize.LARGE],
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
             description: '',
             opts: {
                 borderWidth: '8',
@@ -1488,7 +1499,7 @@ export class TemplateLayouts {
             tl: [-.45, -.18], 
             br: [.45, 0], 
             types: [TemplateFieldType.TEXT],
-            sizes: [FieldSize.TINY, FieldSize.SMALL],
+            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
             description: '',
             opts: {
                 backgroundColor: 'transparent',
@@ -1499,7 +1510,7 @@ export class TemplateLayouts {
             tl: [-.45, 0], 
             br: [.45, .18], 
             types: [TemplateFieldType.TEXT],
-            sizes: [FieldSize.TINY, FieldSize.SMALL],
+            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
             description: '',
             opts: {
                 backgroundColor: 'transparent',
@@ -1510,7 +1521,7 @@ export class TemplateLayouts {
             tl: [-.83, .2], 
             br: [.83, .95], 
             types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
-            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
             description: '',
             opts: {
                 borderWidth: '8',
@@ -1569,7 +1580,7 @@ export class TemplateLayouts {
             tl: [-.875, -.9], 
             br: [.875, .7], 
             types: [TemplateFieldType.VISUAL],
-            sizes: [FieldSize.LARGE, FieldSize.HUGE],
+            sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
             description: '',
             opts: {
                 borderWidth: '15',
@@ -1579,7 +1590,7 @@ export class TemplateLayouts {
             tl: [-.95, .8], 
             br: [-.1, .95], 
             types: [TemplateFieldType.TEXT],
-            sizes: [FieldSize.TINY, FieldSize.SMALL],
+            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
             description: '',
             opts: {
                 backgroundColor: 'transparent',
@@ -1590,7 +1601,7 @@ export class TemplateLayouts {
             tl: [.1, .8], 
             br: [.95, .95], 
             types: [TemplateFieldType.TEXT],
-            sizes: [FieldSize.TINY, FieldSize.SMALL],
+            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
             description: '',
             opts: {
                 backgroundColor: 'transparent',
@@ -1601,7 +1612,7 @@ export class TemplateLayouts {
             tl: [0, -.9], 
             br: [.85, -.66], 
             types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
-            sizes: [FieldSize.MEDIUM, FieldSize.LARGE, FieldSize.HUGE],
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
             description: '',
             opts: {
                 backgroundColor: 'transparent',
@@ -1628,7 +1639,7 @@ export class TemplateLayouts {
             tl: [-.66, -.747], 
             br: [.66, .247], 
             types: [TemplateFieldType.VISUAL],
-            sizes: [FieldSize.LARGE, FieldSize.HUGE],
+            sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
             description: '',
             opts: {
                 borderColor: 'yellow',
@@ -1641,7 +1652,7 @@ export class TemplateLayouts {
                     tl: [-2, -2], 
                     br: [2, 2], 
                     types: [TemplateFieldType.VISUAL],
-                    sizes: [FieldSize.LARGE, FieldSize.HUGE],
+                    sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
                     opts: {
                         rotation: 315,
                     }
@@ -1651,7 +1662,7 @@ export class TemplateLayouts {
             tl: [-.7, .2], 
             br: [.7, .46], 
             types: [TemplateFieldType.TEXT],
-            sizes: [FieldSize.TINY, FieldSize.SMALL],
+            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
             description: '',
             opts: {
                 backgroundColor: 'transparent',
@@ -1661,7 +1672,7 @@ export class TemplateLayouts {
             tl: [-.95, .5], 
             br: [.95, .95], 
             types: [TemplateFieldType.TEXT],
-            sizes: [FieldSize.MEDIUM, FieldSize.LARGE],
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
             description: '',
             opts: {
                 backgroundColor: 'transparent',
-- 
cgit v1.2.3-70-g09d2


From 0581f0943aad45fae70bb34e61fa219121d64b8f Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Mon, 2 Sep 2024 19:15:49 -0400
Subject: default content for columns in preview templates

---
 src/client/apis/gpt/GPT.ts                         |  2 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 65 ++++++++++++++++++----
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 39 +++++++------
 3 files changed, 77 insertions(+), 29 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 0d1d77f06..ba8106c4d 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -58,7 +58,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
     },
     template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You will be given a list of field descriptions for multiple 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” represents the template,  # 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. Do this for each template whose fields are described. The descriptions are as follows:  ' },
     vizsum: { model: 'gpt-4-turbo', 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', 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: {“col1”:{“type”:”text”, “size”:”small”}, “col2”:{“type”:”visual”, “size”:”medium”}, …}. DO NOT INCLUDE ANY OTHER TEXT, ONLY THE JSON.'} 
+    vizsum2: { model: 'gpt-4-turbo', 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.'} 
 };
 let lastCall = '';
 let lastResp = '';
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index c72aa1711..6888d5f1e 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -155,26 +155,43 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._specialHighlightedRow = row;
     }
 
-    @action setColumnType = (field: string, type: TemplateFieldType) => {
-        const colInfo = this.colsInfo.get(field);
-        if (colInfo) { colInfo.type = type };
+    @action setColumnType = (colTitle: string, type: TemplateFieldType) => {
+        const colInfo = this.colsInfo.get(colTitle);
+        console.log(colInfo)
+        if (colInfo) { 
+            colInfo.type = type;
+        } else {
+            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: type, size: TemplateFieldSize.MEDIUM})
+        }
+        console.log(colInfo?.title, colInfo?.type)
     }
 
-    @action setColumnSize = (field: string, size: TemplateFieldSize) => {
-        const colInfo = this.colsInfo.get(field);
-        if (colInfo) { colInfo.size = size };
+    @action setColumnSize = (colTitle: string, size: TemplateFieldSize) => {
+        const colInfo = this.colsInfo.get(colTitle);
+        if (colInfo) { 
+            colInfo.size = size;
+        } else {
+            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, size: size})
+        }
     }
 
-    @action setColumnDesc = (field: string, desc: string) => {
-        const colInfo = this.colsInfo.get(field);
+    @action setColumnDesc = (colTitle: string, desc: string) => {
+        const colInfo = this.colsInfo.get(colTitle);
         if (colInfo) {
-            if (!desc) { colInfo.desc = this.GPTSummary?.get(field)?.desc ?? ''; } 
+            if (!desc) { colInfo.desc = this.GPTSummary?.get(colTitle)?.desc ?? ''; } 
             else { colInfo.desc = desc; }
+        } else {
+            this.colsInfo.set(colTitle, {title: colTitle, desc: desc, type: TemplateFieldType.UNSET, size: TemplateFieldSize.MEDIUM})
         }
     }
 
-    @computed get colInfo(){
-        return this.colsInfo;
+    @action setColumnDefault = (colTitle: string, cont: string) => {
+        const colInfo = this.colsInfo.get(colTitle);
+        if (colInfo) {
+            colInfo.defaultContent = cont;
+        } else {
+            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, size: TemplateFieldSize.MEDIUM, defaultContent: cont})
+        }
     }
 
     @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
@@ -529,7 +546,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
         prompt += '----------- Rows: '
 
-        rowsToCheck.forEach(row => {
+        rowsToCheck.forEach((row, i) => {
             prompt += `Row #${row}: `
             cols.forEach(col => {
                 prompt += `${col}: ${this.records[row][col]} -----`
@@ -539,11 +556,35 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return prompt;
     }
 
+    updateColDefaults = () => {
+        let possibleIds: number[] = this.records.map((_, index) => index);
+
+        for (let i = possibleIds.length - 1; i > 0; i--) {
+            const j = Math.floor(Math.random() * (i + 1));
+            [possibleIds[i], possibleIds[j]] = [possibleIds[j], possibleIds[i]];
+        }
+    
+        const rowToCheck = possibleIds[0];
+
+        const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
+
+        cols.forEach(col => { 
+            this.setColumnDefault(col, `${this.records[rowToCheck][col]}`) 
+        });
+    }
+
     updateGPTSummary = async () => {
         this._GPTLoading = true;
 
+        this.updateColDefaults();
+
         const prompt = this.getColSummary();
 
+        const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
+        cols.forEach(col => {
+            if (!this.colsInfo.get(col)) this.colsInfo.set(col, {title: col, desc: '', size: TemplateFieldSize.MEDIUM, type: TemplateFieldType.UNSET});
+        });
+
         try {
             const res = await gptAPICall(prompt, GPTCallType.VIZSUM);
 
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index fd5e58ae9..e36adc40a 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -136,8 +136,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     }
 
     @computed get fieldsInfos(): Col[] {
-        const colInfo = this._dataViz?.colInfo;
-        return this.selectedFields.map(field => {return {title: field, type: colInfo?.get(field)?.type ?? TemplateFieldType.UNSET, desc: colInfo?.get(field)?.desc ?? '', size: colInfo?.get(field)?.size ?? TemplateFieldSize.MEDIUM}}).concat(this._columns);
+        const colInfo = this._dataViz?.colsInfo;
+        return this.selectedFields.map(field => {return {title: field, type: colInfo?.get(field)?.type ?? TemplateFieldType.UNSET, desc: colInfo?.get(field)?.desc ?? '', size: colInfo?.get(field)?.size ?? TemplateFieldSize.MEDIUM, defaultContent: colInfo?.get(field)?.defaultContent ?? ''}});
+        //.concat(this._columns)
     }
 
     @computed get canMakeDocs(){
@@ -429,7 +430,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
         // this.createEmptyTemplate(temp, assignments);
 
-        this.generatePresetTemplates([TemplateLayouts.FourField001, TemplateLayouts.FourField002], this.fieldsInfos);
+        this.generatePresetTemplates(this.findValidTemplates(this.fieldsInfos, TemplateLayouts.allTemplates), this.fieldsInfos);
+        // console.log(this._dataViz?.colsInfo.get("IMG")?.size, this._dataViz?.colsInfo.get("IMG")?.type)
+        // console.log(this.fieldsInfos)
     };
 
     @action addField = () => {
@@ -533,7 +536,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
 
     findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => {
-        const validTemplates: string[] = [];
+        let validTemplates: any[] = [];
         templates.forEach(template => {
             const numFields =  template.fields.length;
             const matches = this.matchesForTemplate(template, cols);
@@ -542,6 +545,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             }
         })
 
+        validTemplates = validTemplates.map(title => TemplateLayouts.fieldByTitle(title));
         return validTemplates;
     };
 
@@ -579,7 +583,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                 template.height, 
                 template.width, 
                 col.title, 
-                '', 
+                col.defaultContent ?? '', 
                 field.opts
             );
 
@@ -630,7 +634,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
     compileColDescriptions = (cols: Col[]): string =>  {
         let descriptions: string = '';
-        cols.forEach(col => descriptions += `{"${col.title} column: ${col.desc}} `);
+        cols.forEach(col => descriptions += `{title: ${col.title}, size: ${col.size}, type: ${col.type}, descreiption: ${col.desc}} `);
 
         return descriptions;
     };
@@ -644,8 +648,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
         const inputText = fieldDescriptions.concat(colDescriptions);
 
-        console.log(inputText)
-
         ++this._callCount;
         const origCount = this._callCount;
 
@@ -659,18 +661,15 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             if (res && this._callCount === origCount) {
                 this._GPTLoading = false;
                 const assignments: {[templateTitle: string]: {[field: string]: string}} = JSON.parse(res);
-                console.log(assignments);
                 const brokenDownAssignments: [TemplateDocInfos, {[field: number]: Col}][] = [];
                 Object.entries(assignments).forEach(([tempTitle, assignment]) => {
                     const template = TemplateLayouts.fieldByTitle(tempTitle);
-                    console.log('template', template)
-                    if (!template) {console.log('returned'); return};
+                    if (!template) return;
+                    console.log(assignments)
                     const toObj = Object.entries(assignment).reduce((a, [fieldNum, colTitle]) => {
-                        console.log('hey', a, fieldNum, colTitle)
                         a[Number(fieldNum)] = this.getColByTitle(colTitle);
                         return a;
                     }, {} as { [field: number]: Col }); 
-                    console.log('obj', toObj)
                     brokenDownAssignments.push([template, toObj])
                 })
                 return brokenDownAssignments;
@@ -1213,6 +1212,7 @@ export type Col = {
     desc: string;
     title: string;
     type: TemplateFieldType;
+    defaultContent?: string;
 }
 
 type Field = {
@@ -1295,7 +1295,7 @@ export class FieldFuncs {
             }
     
             currTextHeight = rowsCount * currFontSize * (uppercase ? 1.25 : 1);
-            console.log(rowsCount, currTextHeight)
+            //console.log(rowsCount, currTextHeight)
     
             currFontSize += 1;
         }
@@ -1342,14 +1342,14 @@ export class FieldFuncs {
 
         const bool = true;
 
-        const docWithBasicOpts = (Docs.Create.TextDocument)('Fluorite is a very', {   
+        const docWithBasicOpts = (Docs.Create.TextDocument)(content, {   
             isDefaultTemplateDoc: true,
             _height: height, 
             _width: width, 
             title: title, 
             x: coord.x, 
             y: coord.y, 
-            _text_fontSize: `${FieldFuncs.calculateFontSize(width, height, 'Fluorite is a very', true)}` ,
+            _text_fontSize: `${FieldFuncs.calculateFontSize(width, height, content, true)}` ,
             backgroundColor: opts.backgroundColor ?? '',
             text_fontColor: opts.color,
             contentBold: opts.fontBold,
@@ -1401,6 +1401,12 @@ export class FieldFuncs {
 
 export class TemplateLayouts {
 
+    public static get allTemplates(): TemplateDocInfos[] {
+        return Object.values(TemplateLayouts).filter(
+            value => typeof value === 'object' && value !== null && 'title' in value
+        ) as TemplateDocInfos[];
+    }
+
     public static fieldByTitle = (title: string): TemplateDocInfos | undefined => {
         switch (title){
             case 'fourfield1':
@@ -1438,6 +1444,7 @@ export class TemplateLayouts {
                 backgroundColor: 'transparent',
                 color: '#F1F0E9',
                 contentXCentering: 'h-center',
+                fontBold: true,
             }
         }, {
             tl: [-.87, -.83], 
-- 
cgit v1.2.3-70-g09d2


From b4e4cca578747c987efde7fa8c14299790f3ee02 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Wed, 4 Sep 2024 04:14:59 -0400
Subject: stuff

---
 src/client/apis/gpt/GPT.ts                         |   2 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   1 +
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     |  15 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 261 ++++++++++++++++-----
 src/client/views/nodes/ImageBox.tsx                |  23 +-
 .../nodes/formattedText/FormattedTextBox.scss      |   1 +
 6 files changed, 232 insertions(+), 71 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index ba8106c4d..6befad310 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -56,7 +56,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
         temp: 0,
         prompt: 'List unique differences between the content of the UserAnswer and Rubric. Before each difference, label it and provide any additional information the UserAnswer missed and explain it in second person without separating it into UserAnswer and Rubric content and additional information. If there are no differences, say correct',
     },
-    template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You will be given a list of field descriptions for multiple 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” represents the template,  # 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. Do this for each template whose fields are described. The descriptions are as follows:  ' },
+    template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You will be given a list of field descriptions for multiple 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” represents the template,  # 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', 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', 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/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 6888d5f1e..e8235ce1a 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -712,6 +712,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             const targetKey = StrCast(templateInfo.doc!.layout_fieldKey, 'layout');
             const applied = this.ApplyTemplateTo(templateInfo.doc!, target, targetKey, templateInfo.doc!.title + `${row}`);
             target.layout_fieldKey = targetKey;
+
             //this.applyImagesTo(target, fields);
             return applied;
         });
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index c93798979..1f81bf960 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -307,8 +307,11 @@
     }
 
     .docCreatorMenu-preview-image{ 
-        width: 105px;
-        height: 105px;
+        background-color: transparent;
+        height: 100px;
+        width: 100px;
+        display: block;
+        object-fit: contain;
         border-radius: 5px;
     }
 
@@ -411,10 +414,16 @@
 }
 
 .section-reveal-options {
-    margin: 0px;
+    margin-top: 0px;
+    margin-bottom: 0px;
+    margin-right: 0px;
     margin-left: auto;
     border: 0px;
     background: none;
+
+    &.float-right {
+        float: right;
+    }
 }
 
 .docCreatorMenu-section-title {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index e36adc40a..191b387c4 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -59,7 +59,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     @observable _layoutPreviewScale: number = 1;
     @observable _savedLayouts: DataVizTemplateLayout[] = [];
 
-    @observable _GPTTemplates: Doc[] = [];
+    @observable _suggestedTemplates: Doc[] = [];
     @observable _GPTOpt: boolean = false;
     @observable _userPrompt: string = '';
     @observable _callCount: number = 0;
@@ -99,7 +99,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
     @action setDataViz = (dataViz: DataVizBox) => { this._dataViz = dataViz };
     @action setTemplateDocs = (docs: Doc[]) => {this._templateDocs = docs.map(doc => doc.annotationOn ? DocCast(doc.annotationOn):doc)};
-    @action setGPTTemplates = (docs: Doc[]) => {this._GPTTemplates = docs};
+    @action setGSuggestedTemplates = (docs: Doc[]) => {this._suggestedTemplates = docs};
 
     @computed get docsToRender() {
         return this._selectedTemplate ? NumListCast(this._dataViz?.layoutDoc.dataViz_selectedRows) : [];
@@ -198,7 +198,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         document.addEventListener('pointerdown', this.onPointerDown, true);
         document.addEventListener('pointerup', this.onPointerUp);
         this._disposers.templates = reaction(() => this._templateDocs.slice(), (docs) => docs.map(this.getIcon));
-        this._disposers.gpt = reaction(() => this._GPTTemplates.slice(), (docs) => docs.map(this.getIcon));
+        this._disposers.gpt = reaction(() => this._suggestedTemplates.slice(), (docs) => docs.map(this.getIcon));
         //this._disposers.columns = reaction(() => this._dataViz?.layoutDoc._dataViz_axes, () => {this.generateTemplates('')})
         this._disposers.lightbox = reaction(() => LightboxView.LightboxDoc(), doc => { doc ? this._shouldDisplay && this.closeMenu() : !this._shouldDisplay && this.openMenu()});
         //this._disposers.fields = reaction(() => this._dataViz?.axes, cols => this._selectedCols = cols?.map(col => { return {title: col, type: '', desc: ''}}))
@@ -233,7 +233,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
     @action
     openMenu = () => { 
-        const allTemplates = this._templateDocs.concat(this._GPTTemplates);
+        const allTemplates = this._templateDocs.concat(this._suggestedTemplates);
         this._shouldDisplay = true;
         this.updateIcons(allTemplates);
     };
@@ -357,7 +357,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             const res = await gptAPICall(prompt, GPTCallType.TEMPLATE);
 
             if (res && this._callCount === origCount) {
-                this._GPTTemplates = [];
+                this._suggestedTemplates = [];
                 this._GPTLoading = false;
                 const templates: {template_type: string, fieldVals: {title: string, tlx: string, tly: string, brx: string, bry: string}[]}[] = JSON.parse(res);
                 this.createGeneratedTemplates(templates, 500, 500);
@@ -389,14 +389,14 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             GPTTemplates.push(template);
         });
 
-        setTimeout(() => {this.setGPTTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/}, 100);
+        setTimeout(() => {this.setGSuggestedTemplates(GPTTemplates); /*GPTTemplates.forEach(template => mainCollection.removeDocument(template))*/}, 100);
 
         this.forceUpdate();
     };
 
     editTemplate = (doc: Doc) => {
         //this.closeMenu();
-        DocumentViewInternal.addDocTabFunc(doc, OpenWhere.lightboxAlways);
+        DocumentViewInternal.addDocTabFunc(doc, OpenWhere.addRight);
         DocumentView.DeselectAll();
         Doc.UnBrushDoc(doc);
     };
@@ -419,20 +419,22 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         // const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
         // mainCollection.addDocument(doc);
 
-        const temp = TemplateLayouts.FourField001;
+        // const temp = TemplateLayouts.FourField001;
 
-        const img: Col = {type: TemplateFieldType.VISUAL, title: 'Image', desc: 'description whpoo', size: TemplateFieldSize.LARGE};
-        const capt1: Col = {type: TemplateFieldType.TEXT, title: 'Type', desc: 'description hey', size: TemplateFieldSize.TINY};
-        const capt2: Col = {type: TemplateFieldType.TEXT, title: 'Locality', desc: '', size: TemplateFieldSize.TINY};
-        const desc: Col = {type: TemplateFieldType.TEXT, title: 'Description', desc: '', size: TemplateFieldSize.LARGE};
+        // const img: Col = {type: TemplateFieldType.TEXT, title: 'Type', desc: 'description whpoo', size: TemplateFieldSize.LARGE, defaultContent: ''};
+        // const capt1: Col = {type: TemplateFieldType.TEXT, title: 'Image', desc: 'description hey', size: TemplateFieldSize.TINY};
+        // const capt2: Col = {type: TemplateFieldType.TEXT, title: 'Locality', desc: '', size: TemplateFieldSize.TINY, defaultContent: ''};
+        // const desc: Col = {type: TemplateFieldType.TEXT, title: 'Description', desc: '', size: TemplateFieldSize.LARGE, defaultContent: 'This is a description of a rock. It is kind of long. It is very long. It is gratuitous. This description should be shorter. Oh well. This is a description of a rock. It is kind of long. It is very long. It is gratuitous. This description should be shorter. Oh well. This is a description of a rock. It is kind of long. It is very long. It is gratuitous. This description should be shorter. Oh well.'};
 
         // const assignments = {'0': img, '1': capt1, '2': capt2, '3': desc}
 
         // this.createEmptyTemplate(temp, assignments);
+        // console.log(this.findValidTemplates(this.fieldsInfos, TemplateLayouts.allTemplates));
 
-        this.generatePresetTemplates(this.findValidTemplates(this.fieldsInfos, TemplateLayouts.allTemplates), this.fieldsInfos);
         // console.log(this._dataViz?.colsInfo.get("IMG")?.size, this._dataViz?.colsInfo.get("IMG")?.type)
         // console.log(this.fieldsInfos)
+
+        
     };
 
     @action addField = () => {
@@ -492,7 +494,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         const colMatchesField = (col: Col, field : Field) => { return field.sizes?.includes(col.size) && field.types?.includes(col.type) };
 
         const matches: number[][] = Array(template.fields.length).fill([]).map(() => []);
-        console.log(matches)
+
         template.fields.forEach((field, i) => {
             cols.forEach((col, v) => {
                 if (colMatchesField(col, field)) {
@@ -507,11 +509,13 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     maxMatches = (fieldsCt: number, matches: number[][]) => {
         const used: boolean[] = Array(fieldsCt).fill(false);
         const mt: number[] = Array(fieldsCt).fill(-1);
+        console.log(fieldsCt, matches)
 
         const augmentingPath = (v: number): boolean => {
             if (used[v]) return false;
             used[v] = true;
             for (const to of matches[v]) {
+                console.log(mt[to]);
                 if (mt[to] === -1 || augmentingPath(mt[to])) {
                     mt[to] = v;
                     return true;
@@ -538,7 +542,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     findValidTemplates = (cols: Col[], templates: TemplateDocInfos[]) => {
         let validTemplates: any[] = [];
         templates.forEach(template => {
-            const numFields =  template.fields.length;
+            const numFields = template.fields.length;
+            if (!(numFields === cols.length)) return;
             const matches = this.matchesForTemplate(template, cols);
             if (this.maxMatches(numFields, matches) === numFields) {
                 validTemplates.push(template.title);
@@ -546,6 +551,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         })
 
         validTemplates = validTemplates.map(title => TemplateLayouts.fieldByTitle(title));
+
+        console.log(validTemplates);
         return validTemplates;
     };
 
@@ -571,7 +578,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     //     return new Doc;
     // }
 
-    createEmptyTemplate = (template: TemplateDocInfos, assignments: {[field: string]: Col}) => {
+    createEmptyTemplate = (template: TemplateDocInfos, assignments: {[field: string]: Col}): Doc => {
         const fields: Doc[] = [];
 
         Object.entries(assignments).forEach(([f, col]) => {
@@ -618,12 +625,13 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
         const mainCollection = this._dataViz?.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
         mainCollection.addDocument(renderedTemplate);
+        return renderedTemplate;
     };
 
     compileFieldDescriptions = (templates: TemplateDocInfos[]): string => {
         let descriptions: string = '';
         templates.forEach(template => {
-            descriptions += `----------  Description of template ${template.title}'s fields: `
+            descriptions += `----------  NEW TEMPLATE TO INCLUDE: Description of template ${template.title}'s fields: `
             template.fields.forEach((field, index) => {
                 descriptions += `{Field #${index}: ${field.description}} `
             });
@@ -633,7 +641,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     };
 
     compileColDescriptions = (cols: Col[]): string =>  {
-        let descriptions: string = '';
+        let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:';
         cols.forEach(col => descriptions += `{title: ${col.title}, size: ${col.size}, type: ${col.type}, descreiption: ${col.desc}} `);
 
         return descriptions;
@@ -648,6 +656,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
         const inputText = fieldDescriptions.concat(colDescriptions);
 
+        console.log(inputText);
         ++this._callCount;
         const origCount = this._callCount;
 
@@ -660,6 +669,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
             if (res && this._callCount === origCount) {
                 this._GPTLoading = false;
+
+                console.log(res);
+
                 const assignments: {[templateTitle: string]: {[field: string]: string}} = JSON.parse(res);
                 const brokenDownAssignments: [TemplateDocInfos, {[field: number]: Col}][] = [];
                 Object.entries(assignments).forEach(([tempTitle, assignment]) => {
@@ -681,11 +693,16 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         return [];
     };
 
-    generatePresetTemplates = async(templates: TemplateDocInfos[], cols: Col[]) => {
+    generatePresetTemplates = async() => {
+        const cols = this.fieldsInfos;
+        const templates = this.findValidTemplates(cols, TemplateLayouts.allTemplates);
         const assignments: [TemplateDocInfos, {[field: number]: Col}][] = await this.assignColsToFields(templates, cols); 
+        const renderedTemplates: Doc[] = [];
         assignments.forEach(([template, assignments]) => {
-            this.createEmptyTemplate(template, assignments);
+            renderedTemplates.push(this.createEmptyTemplate(template, assignments));
         });
+
+        setTimeout(() => {this.setGSuggestedTemplates(renderedTemplates)});
     }
 
     get templatesPreviewContents(){
@@ -699,7 +716,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                 <div className='docCreatorMenu-section' style={{height: this._GPTOpt ? 200 : 200}}>
                         <div className='docCreatorMenu-section-topbar'>
                             <div className='docCreatorMenu-section-title'>Suggested Templates</div>
-                            <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, () => this._GPTOpt = !this._GPTOpt)}>
+                            <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, () => this._menuContent = 'dashboard')}>
                                 <FontAwesomeIcon icon='gear'/>
                             </button>
                         </div>
@@ -709,7 +726,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                                     <ReactLoading type="spin" color={StrCast(Doc.UserDoc().userVariantColor)} height={30} width={30} />
                                 </div>
                             ) : (
-                            this._GPTTemplates?.map(doc => 
+                            this._suggestedTemplates?.map(doc => 
                                 ({icon: ImageCast(doc.icon), doc})).filter(info => info.icon && info.doc).map(info =>
                                     <div 
                                     className='docCreatorMenu-preview-window'
@@ -730,10 +747,9 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                         </div>
                         <div className='docCreatorMenu-GPT-options'>
                             <div className='docCreatorMenu-GPT-options-container'>
-                                <button className='docCreatorMenu-menu-button' onPointerDown={e => this.setUpButtonClick(e, () => this.generateTemplates(this._userPrompt))}>
+                                <button className='docCreatorMenu-menu-button' onPointerDown={e => this.setUpButtonClick(e, () => this.generatePresetTemplates())}>
                                     <FontAwesomeIcon icon='arrows-rotate'/>
                                 </button>
-                                <input className='docCreatorMenu-GPT-prompt-input' placeholder='Additional prompt:' onChange={e => this._userPrompt = e.target.value}/>
                             </div>
                             {this._GPTOpt ? GPTOptions : null }
                         </div>
@@ -1036,9 +1052,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         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' 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, () => this._menuContent = 'templates')}>
+                    <FontAwesomeIcon icon='arrow-left'/>
+                </button>
             </div>
             {this.fieldsInfos.map((field, index) => 
                 <div className='field-panel' id={`${index}`}>
@@ -1261,12 +1280,12 @@ interface TemplateOpts extends FieldOpts {
 
 export class FieldFuncs {
 
-    private static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => {
+    public static calculateFontSize = (contWidth: number, contHeight: number, text: string, uppercase: boolean): number => {
         const words: string[] = text.split(/\s+/).filter(Boolean);
 
         let currFontSize = 1; 
         let rowsCount = 1;
-        let currTextHeight = currFontSize * rowsCount * (uppercase ? 1.25 : 1);
+        let currTextHeight = currFontSize * rowsCount * 2;
     
         while (currTextHeight <= contHeight) {
             let wordIndex = 0;
@@ -1276,7 +1295,8 @@ export class FieldFuncs {
     
             while (wordIndex < words.length) {
                 const word = words[wordIndex];
-                const wordWidth = word.length * currFontSize;
+                const wordWidth = word.length * currFontSize * .5;
+                //console.log(wordWidth)
     
                 if (currentRowWidth + wordWidth <= contWidth) {
                     currentRowWidth += wordWidth;
@@ -1291,11 +1311,11 @@ export class FieldFuncs {
                     }
                 }
     
-                wordIndex++;
+                wordIndex++;    
             }
     
-            currTextHeight = rowsCount * currFontSize * (uppercase ? 1.25 : 1);
-            //console.log(rowsCount, currTextHeight)
+            currTextHeight = rowsCount * currFontSize * 2;
+            //console.log(rowsCount, currFontSize, currTextHeight)
     
             currFontSize += 1;
         }
@@ -1312,6 +1332,7 @@ export class FieldFuncs {
         const width = r - l;
         const height = b - t;
         const coord = {x: l, y: t};
+        console.log(coords, parentWidth, parentHeight, height);
         return {width, height, coord};
     }
 
@@ -1378,6 +1399,7 @@ export class FieldFuncs {
             title: title, 
             x: coord.x, 
             y: coord.y,
+            _layout_fitWidth: false,
             backgroundColor: opts.backgroundColor ?? '',
             _layout_borderRounding: `${opts.cornerRounding}px` ?? '0px',
             borderColor: opts.borderColor,
@@ -1386,6 +1408,8 @@ export class FieldFuncs {
             _rotation: opts.rotation,
         });
 
+        //setTimeout(() => {doc._height = height; doc._width = width}, 10);
+
         return doc;
     }
 
@@ -1415,8 +1439,12 @@ export class TemplateLayouts {
                 return TemplateLayouts.FourField002;
             case 'fourfield3':
                 return TemplateLayouts.FourField003;
+            case 'fourfield4':
+                return TemplateLayouts.FourField004;
             case 'threefield1':
                 return TemplateLayouts.ThreeField001;
+            case 'threefield2':
+                return TemplateLayouts.ThreeField002;
             default:
                 break;
         }
@@ -1427,11 +1455,11 @@ export class TemplateLayouts {
     public static FourField001: TemplateDocInfos = {
         title: 'fourfield1',
         width: 416, 
-        height: 721, 
+        height: 700, 
         opts: {
-            backgroundColor: '#7B8D62',
+            backgroundColor: '#C0B887',
             cornerRounding: 20,
-            borderColor: '#642B00',
+            borderColor: '#6B461F',
             borderWidth: '12',
         },
         fields: [{
@@ -1454,13 +1482,13 @@ export class TemplateLayouts {
             description: 'The main focus of the template; could be an image, long text, etc.',
             opts: {
                 cornerRounding: 20,
-                borderColor: '#642B00',
+                borderColor: '#8F5B25',
                 borderWidth: '6',
                 backgroundColor: '#CECAB9',
             }
         }, {
             tl: [-.8, .2], 
-            br: [.8, .35], 
+            br: [.8, .3],  
             types: [TemplateFieldType.TEXT],
             sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
             description: 'A caption for field #2, very short to short text that contextualizes the content of field #2',
@@ -1474,10 +1502,10 @@ export class TemplateLayouts {
             br: [.87, .96], 
             types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
             sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-            description: 'A medium-sized field for medium/long text or a secondary image that complements the main focus.',
+            description: 'A medium-sized field for medium/long text.',
             opts: {
                 cornerRounding: 15,
-                borderColor: '#642B00',
+                borderColor: '#8F5B25', 
                 borderWidth: '6',
                 backgroundColor: '#CECAB9',
             }
@@ -1497,28 +1525,28 @@ export class TemplateLayouts {
             br: [.83, -.2], 
             types: [TemplateFieldType.VISUAL, TemplateFieldType.TEXT],
             sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
-            description: '',
+            description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus.',
             opts: {
                 borderWidth: '8',
                 borderColor: '#F8E71C',
             }
         }, {
-            tl: [-.45, -.18], 
-            br: [.45, 0], 
+            tl: [-.65, -.2], 
+            br: [.65, -.02], 
             types: [TemplateFieldType.TEXT],
-            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
-            description: '',
+            sizes: [TemplateFieldSize.TINY],
+            description: 'A tiny field for just a word or two of plain text.',
             opts: {
                 backgroundColor: 'transparent',
                 color: 'white',
                 contentXCentering: 'h-center'
             }
         }, {
-            tl: [-.45, 0], 
-            br: [.45, .18], 
+            tl: [-.65, 0], 
+            br: [.65, .18], 
             types: [TemplateFieldType.TEXT],
-            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
-            description: '',
+            sizes: [TemplateFieldSize.TINY],
+            description: 'A tiny field for just a word or two of plain text.',
             opts: {
                 backgroundColor: 'transparent',
                 color: 'white',
@@ -1529,11 +1557,12 @@ export class TemplateLayouts {
             br: [.83, .95], 
             types: [TemplateFieldType.TEXT, TemplateFieldType.VISUAL],
             sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-            description: '',
+            description: 'A medium to large-sized field suitable for an image or longer text that should be the main focus, or share focus with field 1.',
             opts: {
                 borderWidth: '8',
                 borderColor: '#F8E71C',
-                backgroundColor: '#242425'
+                color: 'white',
+                backgroundColor: '#242425',
             }
         }],
         decorations: [{
@@ -1612,7 +1641,8 @@ export class TemplateLayouts {
             description: '',
             opts: {
                 backgroundColor: 'transparent',
-                color: 'white',
+                color: 'red',
+                fontTransform: 'uppercase',
                 contentXCentering: 'h-left'
             }
         }, {
@@ -1635,6 +1665,72 @@ export class TemplateLayouts {
         }]
     };
 
+    public static FourField004: TemplateDocInfos = {
+        title: 'fourfield4',
+        width: 414, 
+        height: 583, 
+        opts: {
+            backgroundColor: '#6CCAF0',
+            borderColor: '#1088C3',
+            borderWidth: '10'
+        },
+        fields: [{
+            tl: [-.86, -.92], 
+            br: [-.075, -.77], 
+            types: [TemplateFieldType.TEXT],
+            sizes: [TemplateFieldSize.TINY],
+            description: 'A tiny field for just a word or two of plain text.',
+            opts: {
+                backgroundColor: '#E2B4F5',
+                borderWidth: '9',
+                borderColor: '#9222F1',
+                contentXCentering: 'h-center'
+            }
+        }, {
+            tl: [.075, -.92], 
+            br: [.86, -.77], 
+            types: [TemplateFieldType.TEXT],
+            sizes: [TemplateFieldSize.TINY],
+            description: 'A tiny field for just a word or two of plain text.',
+            opts: {
+                backgroundColor: '#F5B4DD',
+                borderWidth: '9',
+                borderColor: '#E260F3',
+                contentXCentering: 'h-center'
+            }
+        }, {
+            tl: [-.81, -.64], 
+            br: [.81, .48], 
+            types: [TemplateFieldType.VISUAL],
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+            description: 'A large to huge field for visual content that is the main content of the template.',
+            opts: {
+                borderWidth: '16',
+                borderColor: '#A2BD77',
+            }
+        }, {
+            tl: [-.86, .6], 
+            br: [.86, .92], 
+            types: [TemplateFieldType.TEXT],
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
+            description: 'A medium to large field for text that describes the visual content above',
+            opts: {
+                borderWidth: '9',
+                borderColor: '#F0D601',
+                backgroundColor: '#F3F57D',
+            }
+        }],
+        decorations: [{
+            tl: [-.852, -.67], 
+            br: [.852, .51], 
+            opts: {
+                backgroundColor: 'transparent',
+                borderColor: '#007C0C',
+                borderWidth: '10',
+            }
+        }]
+    };
+
     public static ThreeField001: TemplateDocInfos = {
         title: 'threefield1',
         width: 575, 
@@ -1646,8 +1742,8 @@ export class TemplateLayouts {
             tl: [-.66, -.747], 
             br: [.66, .247], 
             types: [TemplateFieldType.VISUAL],
-            sizes: [TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
-            description: '',
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+            description: 'A medium to large field for visual content that is the central focus.',
             opts: {
                 borderColor: 'yellow',
                 borderWidth: '8',
@@ -1670,7 +1766,7 @@ export class TemplateLayouts {
             br: [.7, .46], 
             types: [TemplateFieldType.TEXT],
             sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
-            description: '',
+            description: 'A very small text field for one to a few words. A good caption for the image.',
             opts: {
                 backgroundColor: 'transparent',
                 contentXCentering: 'h-center',
@@ -1680,7 +1776,7 @@ export class TemplateLayouts {
             br: [.95, .95], 
             types: [TemplateFieldType.TEXT],
             sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE],
-            description: '',
+            description: 'A medium to large text field for a thorough description of the image. ',
             opts: {
                 backgroundColor: 'transparent',
                 color: 'white'
@@ -1717,6 +1813,59 @@ export class TemplateLayouts {
         }]
     };
 
+    public static ThreeField002: TemplateDocInfos = {
+        title: 'threefield2',
+        width: 477, 
+        height: 662, 
+        opts: {
+            backgroundColor: '#9E9C95'
+        },
+        fields: [{
+            tl: [-.95, .8], 
+            br: [-.1, .95], 
+            types: [TemplateFieldType.VISUAL],
+            sizes: [TemplateFieldSize.MEDIUM, TemplateFieldSize.LARGE, TemplateFieldSize.HUGE],
+            description: '',
+            opts: {
+                backgroundColor: 'transparent',
+                color: 'white',
+                contentXCentering: 'h-right',
+            }
+        }, {
+            tl: [.1, .8], 
+            br: [.95, .95], 
+            types: [TemplateFieldType.TEXT],
+            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
+            description: 'A very small text field for one to a few words. The content should represent a general categorization of the image.',
+            opts: {
+                backgroundColor: 'transparent',
+                color: 'red',
+                fontTransform: 'uppercase',
+                fontBold: true,
+                contentXCentering: 'h-left'
+            }
+        }, {
+            tl: [0, -.9], 
+            br: [.85, -.66], 
+            types: [TemplateFieldType.TEXT],
+            sizes: [TemplateFieldSize.TINY, TemplateFieldSize.SMALL],
+            description: 'A very small text field for one to a few words. The content should contextualize field 2.',
+            opts: {
+                backgroundColor: 'transparent',
+                contentXCentering: 'h-right'
+            }
+        }],
+        decorations: [{
+            tl: [-.025, .8], 
+            br: [.025, .95], 
+            opts: {
+                backgroundColor: '#E0E0DA',
+            }
+        }]
+    };
+
+
+
 //     public static FourField002: TemplateDocInfos = {
 //         width: 450, 
 //         height: 600, 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index faea05104..53aa7171f 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -123,15 +123,15 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             { fireImmediately: true, delay: 1000 }
         );
         const { layoutDoc } = this;
-        this._disposers.path = reaction(
-            () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
-            ({ nativeSize, width }) => {
-                if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) {
-                    this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
-                }
-            },
-            { fireImmediately: true }
-        );
+        // this._disposers.path = reaction(
+        //     () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }),
+        //     ({ nativeSize, width }) => {
+        //         if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) {
+        //             this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth;
+        //         }
+        //     },
+        //     { fireImmediately: true }
+        // );
         this._disposers.scroll = reaction(
             () => this.layoutDoc.layout_scrollTop,
             sTop => {
@@ -394,7 +394,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         alt=""
                         key="paths"
                         src={srcpath}
-                        style={{ transform, transformOrigin }}
+                        style={{ transform, transformOrigin, objectFit: 'fill', height: '100%' }}
                         onError={action(e => {
                             this._error = e.toString();
                         })}
@@ -456,6 +456,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     savedAnnotations = () => this._savedAnnotations;
     render() {
+        console.log('img', this._props.PanelWidth(), this._props.PanelHeight())
         TraceMobx();
         const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
         const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad;
@@ -475,7 +476,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 })}
                 style={{
                     width: this._props.PanelWidth() ? undefined : `100%`,
-                    height: this._props.PanelWidth() ? undefined : `100%`,
+                    height: this._props.PanelHeight() ? undefined : `100%`,
                     pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined,
                     borderRadius,
                     overflow: this.layoutDoc.layout_fitWidth || this._props.fitWidth?.(this.Document) ? 'auto' : undefined,
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss
index d6fd81f83..72d550c7e 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.scss
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss
@@ -79,6 +79,7 @@ audiotag:hover {
     transform-origin: left top;
     top: 0;
     left: 0;
+    
 }
 
 .formattedTextBox-cont {
-- 
cgit v1.2.3-70-g09d2


From 441e1337f22f4bc9f9c6c8dcf643cc72c4d68f21 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Thu, 5 Sep 2024 03:35:51 -0400
Subject: Oh, coding makes me feel like an idiot sometimes... (added += where a
 string was supposed to be updated)

---
 src/client/apis/gpt/GPT.ts                         |  2 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 43 +++++++++++++---------
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 24 +++++++-----
 src/client/views/nodes/ImageBox.tsx                |  1 -
 4 files changed, 42 insertions(+), 28 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 7351fd0f5..f1d5ab533 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -60,7 +60,7 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
     template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You will be given a list of field descriptions for multiple 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” represents the template,  # 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', 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', 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.'},
-    fill: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Your job is to generate content for fields based on a user prompt and background context given to you. You will be given the content of the other fields present in the format: ---- Field # (field title): content ---- Field # (field title): content ----- (etc.) You will be given info on the columns to generate for in the format ---- title: , prompt: , word limit: , assigned field: ----. For each column, based on the prompt, word limit, and the context of existing fields, you should generate a short response in the following JSON format: {“title” (should be the title from the column description with no additions): {“number”:”#” (where # is the assigned field of the column), “content”:”response” (where response is your response to the prompt in the column info)}}. ONLY INCLUDE THE JSON TEXT WITH NO OTHER ADDED TEXT. The word limit for each column applies only to that column’s response. You should include one object per column you are provided info on.' }  
+    fill: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Your job is to generate content for fields based on a user prompt and background context given to you. You will be given the content of the other fields present in the format: ---- Field # (field title): content ---- Field # (field title): content ----- (etc.) You will be given info on the columns to generate for in the format ---- title: , prompt: , word limit: , assigned field: ----. For each column, based on the prompt, word limit, and the context of existing fields, you should generate a short response in the following JSON format: {“___”(where ___ is the title from the column description with no additions): {“number”:”#” (where # is the assigned field of the column), “content”:”response” (where response is your response to the prompt in the column info)}}. Here’s another example of the format with only one column: {“position”: {“number”:”2”, “content”:”*your response goes here*”}}. ONLY INCLUDE THE JSON TEXT WITH NO OTHER ADDED TEXT. YOUR RESPONSE MUST BE VALID JSON. The word limit for each column applies only to that column’s response. Do not include speculation or information that you can’t glean from your factual knowledge or the content of the other fields (no description of images you can’t see, for example). You should include one object per column you are provided info on.'}
 };
 let lastCall = '';
 let lastResp = '';
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index e8235ce1a..d9a7d5e3a 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -175,6 +175,15 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         }
     }
 
+    @action setColumnTitle = (colTitle: string, newTitle: string) => {
+        const colInfo = this.colsInfo.get(colTitle);
+        if (colInfo) { 
+            colInfo.title = newTitle;
+        } else {
+            this.colsInfo.set(colTitle, {title: newTitle, desc: '', type: TemplateFieldType.UNSET, size: TemplateFieldSize.MEDIUM})
+        }
+    }
+
     @action setColumnDesc = (colTitle: string, desc: string) => {
         const colInfo = this.colsInfo.get(colTitle);
         if (colInfo) {
@@ -580,33 +589,33 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
         const prompt = this.getColSummary();
 
+        console.log(prompt)
+
         const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
         cols.forEach(col => {
             if (!this.colsInfo.get(col)) this.colsInfo.set(col, {title: col, desc: '', size: TemplateFieldSize.MEDIUM, type: TemplateFieldType.UNSET});
         });
 
         try {
-            const res = await gptAPICall(prompt, GPTCallType.VIZSUM);
-
-            if (res) {
+            const [res1, res2] = await Promise.all([
+                gptAPICall(prompt, GPTCallType.VIZSUM),
+                gptAPICall('Info:' + prompt, GPTCallType.VIZSUM2)
+            ]);
+        
+            if (res1) {
+                console.log(res1);
                 this.GPTSummary = new ObservableMap();
-                const descs: {[col: string]: string} = JSON.parse(res);
+                const descs: { [col: string]: string } = JSON.parse(res1);
                 for (const [key, val] of Object.entries(descs)) {
-                    this.GPTSummary.set(key, {desc: val});
+                    this.GPTSummary.set(key, { desc: val });
                     if (!this.colsInfo.get(key)?.desc) this.setColumnDesc(key, val);
                 }
             }
-        } catch (err) {
-            console.error(err);
-        } 
-
-        try {
-            const res = await gptAPICall('Info:' + prompt, GPTCallType.VIZSUM2);
-
-            if (res) {
+        
+            if (res2) {
                 !this.GPTSummary && (this.GPTSummary = new ObservableMap());
-                const info: {[col: string]: {type: TemplateFieldType, size: TemplateFieldSize}} = JSON.parse(res);
-                console.log(info)
+                const info: { [col: string]: { type: TemplateFieldType, size: TemplateFieldSize } } = JSON.parse(res2);
+                console.log(info);
                 for (const [key, val] of Object.entries(info)) {
                     const colSummary = this.GPTSummary.get(key);
                     if (colSummary) {
@@ -616,11 +625,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         this.setColumnSize(key, val.size);
                     }
                 }
-
             }
         } catch (err) {
             console.error(err);
-        } 
+        }
+        
     }
 
     getPossibleTemplates = (): Doc[] => {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 33d9a8827..64152f577 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -478,6 +478,15 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         }   
     };
 
+    setColTitle = (column: Col, title: string) => {
+        if (this.selectedFields.includes(column.title)) {
+            this._dataViz?.setColumnTitle(column.title, title);
+        } else {
+            column.title = title;
+        }
+        this.forceUpdate();
+    };
+
     setColType = (column: Col, type: TemplateFieldType) => {
         if (this.selectedFields.includes(column.title)) {
             this._dataViz?.setColumnType(column.title, type);
@@ -492,6 +501,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             this._dataViz?.setColumnSize(column.title, size);
         } else {
             column.size = size;
+            console.log(column.size)
         }
         this.forceUpdate();
     };
@@ -501,7 +511,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             this._dataViz?.setColumnDesc(column.title, desc);
         } else {
             column.desc = desc;
-            console.log(column, column.desc)
         }
         this.forceUpdate();
     };
@@ -620,13 +629,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         const stringifyGPTInfo = (): string => {
             let string: string = '*** COLUMN INFO:';
             GPTAssignments.forEach(([fieldNum, col]) => {
-                `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.size)} words, assigned field: ${fieldNum} ---`
+                string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.size)} words, assigned field: ${fieldNum} ---`
             });
             return string += ' ***';
         };
 
         const GPTAssignmentString = stringifyGPTInfo();
-        console.log('assignment string', GPTAssignmentString);
 
         let fieldContent: string = '';
 
@@ -682,8 +690,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             return main;
         }
 
-        console.log('field content', fieldContent)
-
         if (GPTAssignments.length) {
             
             try {
@@ -695,7 +701,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                     console.log('response', res);
 
                     const assignments: {[title: string]: {number: string, content: string}} = JSON.parse(res);
-                    console.log('assignments', assignments);
+                    console.log('assignments', GPTAssignments, 'assignment string', GPTAssignmentString, 'field content', fieldContent, 'response', res, 'assignments', assignments);
                     Object.entries(assignments).forEach(([title, info]) => {
                         const field: Field = template.fields[Number(info.number)];
                         const col = this.getColByTitle(title);
@@ -1164,7 +1170,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             {this.fieldsInfos.map((field, index) => 
                 <div className='field-panel' id={`${index}`}>
                     <div className='properties-wrapper'>
-                        <input className='field-property-container' id={`${Math.random() * 100}`} defaultValue={field.title} placeholder={'Enter title'}/>
+                        <input className='field-property-container' id={`${Math.random() * 100}`} defaultValue={field.title} placeholder={'Enter title'}  onChange={(e) => this.setColTitle(field, e.target.value)}/>
                         <div className='field-type-selection-container'>
                             {field.type === TemplateFieldType.UNSET ? <span className='placeholder'>Select media type</span> : null}
                             <span className='type-display'>{field.type === TemplateFieldType.TEXT ? 'Text Field' : field.type === TemplateFieldType.VISUAL ? 'File Field' : ''}</span>
@@ -1175,7 +1181,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                                 <div className='text'>File</div>
                             </div>
                         </div>
-                        <input className='field-property-container' id={`${Math.random() * 100}`} placeholder={this._dataViz?.GPTSummary?.get(field.title)?.size} style={{width: field.title === '' ? '30%' : ''}}/>
+                        <input className='field-property-container' id={`${Math.random() * 100}`} placeholder={this._dataViz?.GPTSummary?.get(field.title)?.size} style={{width: field.title === '' ? '30%' : ''} } onChange={(e) => this.setColSize(field, e.target.value as TemplateFieldSize)}/>
                     </div>
                     <textarea className='field-description-container' onChange={(e) => this.setColDesc(field, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description to help with template generation.'} /> 
                     <div>
@@ -1434,7 +1440,7 @@ export class FieldFuncs {
         const width = r - l;
         const height = b - t;
         const coord = {x: l, y: t};
-        console.log(coords, parentWidth, parentHeight, height);
+        //console.log(coords, parentWidth, parentHeight, height);
         return {width, height, coord};
     }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 53aa7171f..3e5458888 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -456,7 +456,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     savedAnnotations = () => this._savedAnnotations;
     render() {
-        console.log('img', this._props.PanelWidth(), this._props.PanelHeight())
         TraceMobx();
         const borderRad = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BorderRounding);
         const borderRadius = borderRad?.includes('px') ? `${Number(borderRad.split('px')[0]) / (this._props.NativeDimScaling?.() || 1)}px` : borderRad;
-- 
cgit v1.2.3-70-g09d2


From 8eabd90dde18afd453a8561af05ef784a13e9f85 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 8 Sep 2024 02:10:30 -0400
Subject: new menu css

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |   8 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     | 269 ++++++++++++++-------
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      |  99 +++++---
 3 files changed, 255 insertions(+), 121 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index d9a7d5e3a..b61650e43 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -412,10 +412,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
-        this._disposers.contentSummary = reaction(
-            () => this.records,
-            () => this.updateGPTSummary()
-        );
+        // this._disposers.contentSummary = reaction(
+        //     () => this.records,
+        //     () => this.updateGPTSummary()
+        // );
     }
 
     fetchData = () => {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index 857d76d4f..955698b19 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -292,6 +292,19 @@
     position: relative;
     width: 100%;
     height: 100%;
+
+    .right-buttons-panel {
+        display: flex;
+        flex-direction: column;
+        justify-content: flex-start;
+        height: 100%;
+        width: 40px;
+        position: absolute;
+        right: 0px;
+        top: 0px;
+        padding: 5px;
+        gap: 2px;
+    }
 }
 
 .docCreatorMenu-preview-window {
@@ -435,12 +448,6 @@
     &.float-right {
         float: right;
     }
-
-    &.top-right {
-        position: absolute;
-        top: 3px;
-        right: 3px;
-    }
 }
 
 .docCreatorMenu-section-title {
@@ -754,7 +761,7 @@
     display: flex;
     flex-direction: column;
     justify-content: flex-start;
-    overflow-y: scroll;
+    overflow-y: hidden;
     //align-items: flex-start;
     margin: 5px;
     margin-top: 0px;
@@ -765,111 +772,193 @@
     -ms-overflow-style: none; 
     scrollbar-width: none;
 
+    .panels-container {
+        height: 100%;
+        width: 100%;
+        flex-direction: column;
+        justify-content: flex-start;
+        overflow-y: scroll;
+    }
+
     .topbar {
         height: 30px;
         width: 100%;
-    }
+        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 {
-        position: relative;
         display: flex;
-        // align-items: flex-start;
         flex-direction: column;
-        gap: 5px;
-        padding: 5px;
-        height: 100px;
-        //width: 100%;
+        align-items: center;
+        justify-content: flex-start;
+        height: 285px;
+        width: calc(100% - 10px);
         border: 1px solid rgb(180, 180, 180);
         margin: 5px;
         margin-top: 0px;
         border-radius: 3px;
         flex: 0 0 auto;
+        gap: 25px;
+        background-color: rgb(60, 60, 60);
 
-        .properties-wrapper {
+        .top-bar {
             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;
-                }
+            align-items: center;
+            justify-content: center;
+            border-bottom: 1px solid rgb(180, 180, 180);
+            border-top-right-radius: 5px;
+            border-top-left-radius: 5px;
+            width: 100%;
+            height: 20px;
+            background-color: rgb(50, 50, 50);
+        }  
+        
+        .opts-bar {
+            display: flex;
+            flex-direction: row;
+            align-items: center;
+            justify-content: center;
+            width: 100%;
 
-                .bubble {
-                    margin: 5px;
-                }
+            .opt-box {
+                border: 1px solid rgb(180, 180, 180);
+                border-radius: 5px;
+                width: 40%;
+                height: 50px;
+                margin-right: 4%;
+                margin-left: 4%;
+                box-shadow: 5px 5px rgb(29, 29, 31);
+            }
 
-                &:hover .bubble {
-                    margin-top: 7px;
-                }
+            .content {
+                height: calc(100% - 20px);
+                width: 100%;
+                background-color: rgb(50, 50, 50);
+                border-bottom-right-radius: 5px;
+                border-bottom-left-radius: 5px;
             }
         }
 
-        .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; 
-            }
+        .sizes-box {
+            width: 88%;
+            height: 60px;
+            border: 1px solid rgb(180, 180, 180);
+            border-radius: 5px;
+            background-color: rgb(50, 50, 50);
+            box-shadow: 5px 5px rgb(29, 29, 31);
         }
 
-        .top-right {
-            position: absolute;
-            top: 0px;
-            right: 0px;
+        .desc-box {
+            width: 88%;
+            height: 50px;
+            border: 1px solid rgb(180, 180, 180);
+            border-radius: 5px;
+            background-color: rgb(50, 50, 50);
+            box-shadow: 5px 5px rgb(29, 29, 31);
         }
     }
-}
-
 
+}
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 953037d13..87e1e2c96 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -825,14 +825,20 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         const GPTOptions = 
             <div></div>
 
+            //<img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} />
+
         return (
             <div className={`docCreatorMenu-templates-view`}>
                 {this._expandedPreview ? 
                 <div className='docCreatorMenu-expanded-template-preview'>
-                    <img className='docCreatorMenu-preview-image expanded' src={this._expandedPreview.icon!.url.href.replace(".png", "_o.png")} />
-                    <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.setExpandedView(undefined))}> 
-                        <FontAwesomeIcon icon='minimize'/>
-                    </button>
+                    <div className='right-buttons-panel'>
+                        <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.setExpandedView(undefined))}> 
+                            <FontAwesomeIcon icon='minimize'/>
+                        </button>
+                        <button className='docCreatorMenu-menu-button section-reveal-options top-right-lower' onPointerDown={e => this.setUpButtonClick(e, () => this._expandedPreview && this._templateDocs.push(this._expandedPreview.doc))}>
+                            <FontAwesomeIcon icon='plus' color='white'/>
+                        </button>
+                    </div>
                 </div> 
                 :
                 <div>
@@ -859,8 +865,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                                     }}
                                     onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this.updateSelectedTemplate(info.doc)))}>
                                         <button className='option-button left' onPointerDown={e => this.setUpButtonClick(e, () => {this.setExpandedView(info)})}>
-                                            <FontAwesomeIcon icon='pencil' color='white'/>
-                                        </button>
+                                            <FontAwesomeIcon icon='magnifying-glass' color='white'/>
+                                        </button>   
                                         <button className='option-button right' onPointerDown={e => this.setUpButtonClick(e, () => this._templateDocs.push(info.doc))}>
                                             <FontAwesomeIcon icon='plus' color='white'/>
                                         </button>
@@ -1184,34 +1190,73 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                     <FontAwesomeIcon icon='arrow-left'/>
                 </button>
             </div>
-            {this.fieldsInfos.map((field, index) => 
-                <div className='field-panel' id={`${index}`}>
-                    <div className='properties-wrapper'>
-                        <input className='field-property-container' id={`${Math.random() * 100}`} defaultValue={field.title} placeholder={'Enter title'}  onChange={(e) => this.setColTitle(field, e.target.value)}/>
-                        <div className='field-type-selection-container'>
-                            {field.type === TemplateFieldType.UNSET ? <span className='placeholder'>Select media type</span> : null}
-                            <span className='type-display'>{field.type === TemplateFieldType.TEXT ? 'Text Field' : field.type === TemplateFieldType.VISUAL ? 'File Field' : ''}</span>
-                            <div className='bubbles'>
-                                <input className='bubble' type="radio" name="type" onClick={() => {this.setColType(field, TemplateFieldType.TEXT)}}/> 
-                                <div className='text'>Text</div>
-                                <input className='bubble' type="radio" name="type" onClick={() => {this.setColType(field, TemplateFieldType.VISUAL)}}/> 
-                                <div className='text'>File</div>
+            <div className='panels-container'>
+                {/* {this.fieldsInfos.map((field, index) => 
+                    <div className='field-panel' id={`${index}`}>
+                        <div className='properties-wrapper'>
+                            <input className='field-property-container' id={`${Math.random() * 100}`} defaultValue={field.title} placeholder={'Enter title'}  onChange={(e) => this.setColTitle(field, e.target.value)}/>
+                            <div className='field-type-selection-container'>
+                                {field.type === TemplateFieldType.UNSET ? <span className='placeholder'>Select media type</span> : null}
+                                <span className='type-display'>{field.type === TemplateFieldType.TEXT ? 'Text Field' : field.type === TemplateFieldType.VISUAL ? 'File Field' : ''}</span>
+                                <div className='bubbles'>
+                                    <input className='bubble' type="radio" name="type" onClick={() => {this.setColType(field, TemplateFieldType.TEXT)}}/> 
+                                    <div className='text'>Text</div>
+                                    <input className='bubble' type="radio" name="type" onClick={() => {this.setColType(field, TemplateFieldType.VISUAL)}}/> 
+                                    <div className='text'>File</div>
+                                </div>
                             </div>
+                            <input className='field-property-container' id={`${Math.random() * 100}`} placeholder={this._dataViz?.GPTSummary?.get(field.title)?.size} style={{width: field.title === '' ? '30%' : ''} } onChange={(e) => this.setColSize(field, e.target.value as TemplateFieldSize)}/>
+                        </div>
+                        <textarea className='field-description-container' onChange={(e) => this.setColDesc(field, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description to help with template generation.'} /> 
+                        <div>
+                            <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
+                                <FontAwesomeIcon icon='trash'/>
+                            </button>
                         </div>
-                        <input className='field-property-container' id={`${Math.random() * 100}`} placeholder={this._dataViz?.GPTSummary?.get(field.title)?.size} style={{width: field.title === '' ? '30%' : ''} } onChange={(e) => this.setColSize(field, e.target.value as TemplateFieldSize)}/>
-                    </div>
-                    <textarea className='field-description-container' onChange={(e) => this.setColDesc(field, e.target.value)} defaultValue={field.desc === this._dataViz?.GPTSummary?.get(field.title)?.desc ? '' : field.desc } placeholder={this._dataViz?.GPTSummary?.get(field.title)?.desc ?? 'Add a description to help with template generation.'} /> 
-                    <div>
-                        <button className='docCreatorMenu-menu-button section-reveal-options top-right' onPointerDown={e => this.setUpButtonClick(e, () => this.removeField(field))}>
-                            <FontAwesomeIcon icon='trash'/>
-                        </button>
                     </div>
-                </div>
-            )}
+                )} */}
+                {this.hello()}
+            </div>
         </div>
         );
     }
 
+    hello = () => {
+        return (
+            <div className='field-panel'> 
+                <div className='top-bar'>
+                    <button></button>
+                </div>
+                <div className='opts-bar'> 
+                    <div className='opt-box'>
+                        <div className='top-bar'> Title </div>
+                        <textarea className='content' style={{width: '100%', height: 'calc(100% - 20px)'}}>
+
+                        </textarea>
+                    </div>
+                    <div className='opt-box'>
+                        <div className='top-bar'> Type </div>
+                        <div className='content'>
+
+                        </div>
+                    </div>
+                </div>
+                <div className='sizes-box'>
+                    <div className='top-bar'> Valid Sizes </div>
+                    <div className='content'>
+
+                    </div>
+                </div>
+                <div className='desc-box'>
+                    <div className='top-bar'> Description </div>
+                    <div className='content'>
+
+                    </div>
+                </div>
+            </div>
+        )
+    }
+
     get renderSelectedViewType(){
         switch (this._menuContent){
             case 'templates':
-- 
cgit v1.2.3-70-g09d2


From d4c3af196d8fc7355a9b78b78f17e4b44bd4f62b Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 8 Sep 2024 05:14:46 -0400
Subject: col size setting

---
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 34 ++++++++++++----------
 .../views/nodes/DataVizBox/DocCreatorMenu.scss     |  5 ++++
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 29 ++++++++++--------
 3 files changed, 41 insertions(+), 27 deletions(-)

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

diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index b61650e43..0efe8ead0 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -161,17 +161,21 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         if (colInfo) { 
             colInfo.type = type;
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: type, size: TemplateFieldSize.MEDIUM})
+            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: type, sizes: [TemplateFieldSize.MEDIUM]})
         }
         console.log(colInfo?.title, colInfo?.type)
     }
 
-    @action setColumnSize = (colTitle: string, size: TemplateFieldSize) => {
-        const colInfo = this.colsInfo.get(colTitle);
-        if (colInfo) { 
-            colInfo.size = size;
+    @action modifyColumnSizes = (colTitle: string, size: TemplateFieldSize, valid: boolean) => {
+        const column = this.colsInfo.get(colTitle);
+        if (column) { 
+            if (!valid && column.sizes.includes(size)) {
+                column.sizes.splice(column.sizes.indexOf(size), 1);
+            } else if (valid && !column.sizes.includes(size)) {
+                column.sizes.push(size);
+            }
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, size: size})
+            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [size]})
         }
     }
 
@@ -180,7 +184,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         if (colInfo) { 
             colInfo.title = newTitle;
         } else {
-            this.colsInfo.set(colTitle, {title: newTitle, desc: '', type: TemplateFieldType.UNSET, size: TemplateFieldSize.MEDIUM})
+            this.colsInfo.set(colTitle, {title: newTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [TemplateFieldSize.MEDIUM]})
         }
     }
 
@@ -190,7 +194,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             if (!desc) { colInfo.desc = this.GPTSummary?.get(colTitle)?.desc ?? ''; } 
             else { colInfo.desc = desc; }
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: desc, type: TemplateFieldType.UNSET, size: TemplateFieldSize.MEDIUM})
+            this.colsInfo.set(colTitle, {title: colTitle, desc: desc, type: TemplateFieldType.UNSET, sizes: [TemplateFieldSize.MEDIUM]})
         }
     }
 
@@ -199,7 +203,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         if (colInfo) {
             colInfo.defaultContent = cont;
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, size: TemplateFieldSize.MEDIUM, defaultContent: cont})
+            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [TemplateFieldSize.MEDIUM], defaultContent: cont})
         }
     }
 
@@ -412,10 +416,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             },
             { fireImmediately: true }
         );
-        // this._disposers.contentSummary = reaction(
-        //     () => this.records,
-        //     () => this.updateGPTSummary()
-        // );
+        this._disposers.contentSummary = reaction(
+            () => this.records,
+            () => this.updateGPTSummary()
+        );
     }
 
     fetchData = () => {
@@ -593,7 +597,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
         const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
         cols.forEach(col => {
-            if (!this.colsInfo.get(col)) this.colsInfo.set(col, {title: col, desc: '', size: TemplateFieldSize.MEDIUM, type: TemplateFieldType.UNSET});
+            if (!this.colsInfo.get(col)) this.colsInfo.set(col, {title: col, desc: '', sizes: [TemplateFieldSize.MEDIUM], type: TemplateFieldType.UNSET});
         });
 
         try {
@@ -622,7 +626,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                         colSummary.size = val.size;
                         colSummary.type = val.type;
                         this.setColumnType(key, val.type);
-                        this.setColumnSize(key, val.size);
+                        this.modifyColumnSizes(key, val.size, true);
                     }
                 }
             }
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
index eaa32e62a..9d82ada37 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.scss
@@ -905,6 +905,7 @@
         background-color: rgb(60, 60, 60);
 
         .top-bar {
+            position: relative;
             display: flex;
             flex-direction: row;
             align-items: center;
@@ -916,6 +917,10 @@
             height: 20px;
             background-color: rgb(50, 50, 50);
             color: rgb(168, 167, 167);
+
+            .field-title {
+                color: whitesmoke;
+            }
         }  
         
         .opts-bar {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index a01b26036..8f7bf8713 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -146,7 +146,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                 title: field,
                 type: fieldInfo?.type ?? TemplateFieldType.UNSET,
                 desc: fieldInfo?.desc ?? '',
-                size: fieldInfo?.size ?? TemplateFieldSize.MEDIUM
+                sizes: fieldInfo?.sizes ?? [TemplateFieldSize.MEDIUM]
             };
 
             if (fieldInfo?.defaultContent !== undefined) {
@@ -454,7 +454,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     };
 
     @action addField = () => {
-        const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.UNSET, desc: '', size: TemplateFieldSize.MEDIUM}])
+        const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [TemplateFieldSize.MEDIUM]}])
         this._columns = newFields;
     };
 
@@ -497,13 +497,17 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         this.forceUpdate();
     };
 
-    setColSize = (column: Col, size: TemplateFieldSize) => {
+    modifyColSizes = (column: Col, size: TemplateFieldSize, valid: boolean) => {
         if (this.selectedFields.includes(column.title)) {
-            this._dataViz?.setColumnSize(column.title, size);
+            this._dataViz?.modifyColumnSizes(column.title, size, valid);
         } else {
-            column.size = size;
-            console.log(column.size)
+            if (!valid && column.sizes.includes(size)) {
+                column.sizes.splice(column.sizes.indexOf(size), 1);
+            } else if (valid && !column.sizes.includes(size)) {
+                column.sizes.push(size);
+            }
         }
+        console.log(column.sizes)
         this.forceUpdate();
     };
 
@@ -517,7 +521,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     };
 
     matchesForTemplate = (template: TemplateDocInfos, cols: Col[]): number[][] => {
-        const colMatchesField = (col: Col, field : Field) => { return field.sizes?.includes(col.size) && field.types?.includes(col.type) };
+        const colMatchesField = (col: Col, field : Field) => { return field.sizes?.some(size => col.sizes?.includes(size)) && field.types?.includes(col.type) };
 
         const matches: number[][] = Array(template.fields.length).fill([]).map(() => []);
 
@@ -630,7 +634,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         const stringifyGPTInfo = (): string => {
             let string: string = '*** COLUMN INFO:';
             GPTAssignments.forEach(([fieldNum, col]) => {
-                string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.size)} words, assigned field: ${fieldNum} ---`
+                string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`
             });
             return string += ' ***';
         };
@@ -747,7 +751,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
     compileColDescriptions = (cols: Col[]): string =>  {
         let descriptions: string = ' ------------- COL DESCRIPTIONS START HERE:';
-        cols.forEach(col => descriptions += `{title: ${col.title}, size: ${col.size}, type: ${col.type}, descreiption: ${col.desc}} `);
+        cols.forEach(col => descriptions += `{title: ${col.title}, sizes: ${String(col.sizes)}, type: ${col.type}, descreiption: ${col.desc}} `);
 
         return descriptions;
     };
@@ -1185,7 +1189,8 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             return (
                 <div className='field-panel'> 
                     <div className='top-bar'>
-                        <button className='docCreatorMenu-menu-button section-reveal-options float-right' onPointerDown={e => this.setUpButtonClick(e, this.addField)}>
+                        <span className='field-title'>{`${field.title} Field`}</span>
+                        <button className='docCreatorMenu-menu-button section-reveal-options no-margin' onPointerDown={e => this.setUpButtonClick(e, this.addField)} style={{position: 'absolute', right: '0px'}}>
                             <FontAwesomeIcon icon='minus'/>
                         </button>
                     </div>
@@ -1212,7 +1217,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                         <div className='content'>
                             <div className='bubbles'>
                                 {sizes.map(size => <>
-                                    <input className='bubble' type="checkbox" name="type" onChange={() => {this.setColSize(field, size as TemplateFieldSize)}}/> 
+                                    <input className='bubble' type="checkbox" name="type" checked={field.sizes.includes(size as TemplateFieldSize)} onChange={(e) => {this.modifyColSizes(field, size as TemplateFieldSize, e.target.checked)}}/> 
                                     <div className='text'>{size}</div>
                                 </>)}
                             </div>
@@ -1383,7 +1388,7 @@ export enum TemplateFieldSize {
 
 
 export type Col = {
-    size: TemplateFieldSize;
+    sizes: TemplateFieldSize[];
     desc: string;
     title: string;
     type: TemplateFieldType;
-- 
cgit v1.2.3-70-g09d2


From 984a470094399e4bbd74ddb7daa3e6f08a0a4793 Mon Sep 17 00:00:00 2001
From: Nathan-SR <144961007+Nathan-SR@users.noreply.github.com>
Date: Sun, 8 Sep 2024 17:44:11 -0400
Subject: image generation working!

---
 src/client/apis/gpt/GPT.ts                         |   6 +-
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   |  15 +-
 .../views/nodes/DataVizBox/DocCreatorMenu.tsx      | 193 ++++++++++++++-------
 3 files changed, 142 insertions(+), 72 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index f1d5ab533..ec3a349fc 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -15,7 +15,8 @@ enum GPTCallType {
     TEMPLATE = "template",
     VIZSUM = 'vizsum',
     VIZSUM2 = 'vizsum2',
-    FILL = 'fill'
+    FILL = 'fill',
+    COMPLETEPROMPT = 'completeprompt'
 }
 
 type GPTCallOpts = {
@@ -60,7 +61,8 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
     template: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'You will be given a list of field descriptions for multiple 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” represents the template,  # 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', 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', 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.'},
-    fill: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Your job is to generate content for fields based on a user prompt and background context given to you. You will be given the content of the other fields present in the format: ---- Field # (field title): content ---- Field # (field title): content ----- (etc.) You will be given info on the columns to generate for in the format ---- title: , prompt: , word limit: , assigned field: ----. For each column, based on the prompt, word limit, and the context of existing fields, you should generate a short response in the following JSON format: {“___”(where ___ is the title from the column description with no additions): {“number”:”#” (where # is the assigned field of the column), “content”:”response” (where response is your response to the prompt in the column info)}}. Here’s another example of the format with only one column: {“position”: {“number”:”2”, “content”:”*your response goes here*”}}. ONLY INCLUDE THE JSON TEXT WITH NO OTHER ADDED TEXT. YOUR RESPONSE MUST BE VALID JSON. The word limit for each column applies only to that column’s response. Do not include speculation or information that you can’t glean from your factual knowledge or the content of the other fields (no description of images you can’t see, for example). You should include one object per column you are provided info on.'}
+    fill: { model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Your job is to generate content for fields based on a user prompt and background context given to you. You will be given the content of the other fields present in the format: ---- Field # (field title): content ---- Field # (field title): content ----- (etc.) You will be given info on the columns to generate for in the format ---- title: , prompt: , word limit: , assigned field: ----. For each column, based on the prompt, word limit, and the context of existing fields, you should generate a short response in the following JSON format: {“___”(where ___ is the title from the column description with no additions): {“number”:”#” (where # is the assigned field of the column), “content”:”response” (where response is your response to the prompt in the column info)}}. Here’s another example of the format with only one column: {“position”: {“number”:”2”, “content”:”*your response goes here*”}}. ONLY INCLUDE THE JSON TEXT WITH NO OTHER ADDED TEXT. YOUR RESPONSE MUST BE VALID JSON. The word limit for each column applies only to that column’s response. Do not include speculation or information that you can’t glean from your factual knowledge or the content of the other fields (no description of images you can’t see, for example). You should include one object per column you are provided info on.'},
+    completeprompt: {model: 'gpt-4-turbo', maxTokens: 512, temp: 0.5, prompt: 'Your prompt is as follows:'},
 };
 let lastCall = '';
 let lastResp = '';
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 0efe8ead0..0e3b602d6 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -157,13 +157,11 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @action setColumnType = (colTitle: string, type: TemplateFieldType) => {
         const colInfo = this.colsInfo.get(colTitle);
-        console.log(colInfo)
         if (colInfo) { 
             colInfo.type = type;
         } else {
             this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: type, sizes: [TemplateFieldSize.MEDIUM]})
         }
-        console.log(colInfo?.title, colInfo?.type)
     }
 
     @action modifyColumnSizes = (colTitle: string, size: TemplateFieldSize, valid: boolean) => {
@@ -183,8 +181,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const colInfo = this.colsInfo.get(colTitle);
         if (colInfo) { 
             colInfo.title = newTitle;
+            console.log(colInfo.title)
         } else {
-            this.colsInfo.set(colTitle, {title: newTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [TemplateFieldSize.MEDIUM]})
+            this.colsInfo.set(colTitle, {title: newTitle, desc: '', type: TemplateFieldType.UNSET, sizes: []})
         }
     }
 
@@ -194,7 +193,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             if (!desc) { colInfo.desc = this.GPTSummary?.get(colTitle)?.desc ?? ''; } 
             else { colInfo.desc = desc; }
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: desc, type: TemplateFieldType.UNSET, sizes: [TemplateFieldSize.MEDIUM]})
+            this.colsInfo.set(colTitle, {title: colTitle, desc: desc, type: TemplateFieldType.UNSET, sizes: []})
         }
     }
 
@@ -203,7 +202,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         if (colInfo) {
             colInfo.defaultContent = cont;
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [TemplateFieldSize.MEDIUM], defaultContent: cont})
+            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [], defaultContent: cont})
         }
     }
 
@@ -593,11 +592,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
         const prompt = this.getColSummary();
 
-        console.log(prompt)
-
         const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
         cols.forEach(col => {
-            if (!this.colsInfo.get(col)) this.colsInfo.set(col, {title: col, desc: '', sizes: [TemplateFieldSize.MEDIUM], type: TemplateFieldType.UNSET});
+            if (!this.colsInfo.get(col)) this.colsInfo.set(col, {title: col, desc: '', sizes: [], type: TemplateFieldType.UNSET});
         });
 
         try {
@@ -607,7 +604,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             ]);
         
             if (res1) {
-                console.log(res1);
                 this.GPTSummary = new ObservableMap();
                 const descs: { [col: string]: string } = JSON.parse(res1);
                 for (const [key, val] of Object.entries(descs)) {
@@ -619,7 +615,6 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             if (res2) {
                 !this.GPTSummary && (this.GPTSummary = new ObservableMap());
                 const info: { [col: string]: { type: TemplateFieldType, size: TemplateFieldSize } } = JSON.parse(res2);
-                console.log(info);
                 for (const [key, val] of Object.entries(info)) {
                     const colSummary = this.GPTSummary.get(key);
                     if (colSummary) {
diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
index 8f7bf8713..dcdd52c73 100644
--- a/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
+++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-import { returnAll, returnFalse, returnNone, returnOne, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
+import { ClientUtils, returnAll, returnFalse, returnNone, returnOne, returnZero, setupMoveUpEvents } from '../../../../ClientUtils';
 import { Doc, NumListCast, StrListCast } from '../../../../fields/Doc';
 import { DocCast, ImageCast, ScriptCast, StrCast } from '../../../../fields/Types';
 import { ImageField } from '../../../../fields/URLField';
@@ -17,7 +17,7 @@ import { Id } from '../../../../fields/FieldSymbols';
 import { Colors, IconButton, Size } from 'browndash-components';
 import { MakeTemplate } from '../../../util/DropConverter';
 import { DragManager } from '../../../util/DragManager';
-import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
+import { GPTCallType, gptAPICall, gptImageCall } from '../../../apis/gpt/GPT';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { Docs } from '../../../documents/Documents';
 import { OpenWhere } from '../OpenWhere';
@@ -32,6 +32,7 @@ import { ImageBox } from '../ImageBox';
 import { a } from '@react-spring/web';
 import { RichTextMenu } from '../formattedText/RichTextMenu';
 import e from 'cors';
+import { Networking } from '../../../Network';
 
 export enum LayoutType {
     Stacked = 'stacked', 
@@ -423,7 +424,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
 
 
-    testTemplate = () => { 
+    testTemplate = async() => { 
         // const temp = TemplateLayouts.FourField001;
         // const title: Doc = FieldFuncs.TextField({tl: temp.fields[0].tl, br: temp.fields[0].br}, temp.height, temp.width, 'title', 'Title', {backgroundColor: 'transparent'});
         // const image: Doc = FieldFuncs.ImageField({tl: temp.fields[1].tl, br: temp.fields[1].br}, temp.height, temp.width, 'title', '', {borderColor: '#159fe4', borderWidth: '10', cornerRounding: 10, rotation: 40});
@@ -450,11 +451,22 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         // console.log(this._dataViz?.colsInfo.get("IMG")?.size, this._dataViz?.colsInfo.get("IMG")?.type)
         // console.log(this.fieldsInfos)
 
+        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);
+        }
         
     };
 
     @action addField = () => {
-        const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.UNSET, desc: '', sizes: [TemplateFieldSize.MEDIUM]}])
+        const newFields: Col[] = this._columns.concat([{title: '', type: TemplateFieldType.UNSET, desc: '', sizes: []}])
         this._columns = newFields;
     };
 
@@ -479,7 +491,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         }   
     };
 
-    setColTitle = (column: Col, title: string) => {
+    @action setColTitle = (column: Col, title: string) => {
         if (this.selectedFields.includes(column.title)) {
             this._dataViz?.setColumnTitle(column.title, title);
         } else {
@@ -488,7 +500,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         this.forceUpdate();
     };
 
-    setColType = (column: Col, type: TemplateFieldType) => {
+    @action setColType = (column: Col, type: TemplateFieldType) => {
         if (this.selectedFields.includes(column.title)) {
             this._dataViz?.setColumnType(column.title, type);
         } else {
@@ -507,7 +519,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                 column.sizes.push(size);
             }
         }
-        console.log(column.sizes)
         this.forceUpdate();
     };
 
@@ -520,6 +531,22 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
         this.forceUpdate();
     };
 
+    generateGPTImage = async(prompt: string): Promise<string | undefined> => {
+        console.log(prompt)
+
+        try {
+            const res = await gptImageCall(prompt);
+
+            if (res){
+                const result = await Networking.PostToServer('/uploadRemoteImage', { sources: res });
+                const source = ClientUtils.prepend(result[0].accessPaths.agnostic.client);
+                return source;
+            }
+        } catch (e) {
+            console.log(e);
+        }
+    }
+
     matchesForTemplate = (template: TemplateDocInfos, cols: Col[]): number[][] => {
         const colMatchesField = (col: Col, field : Field) => { return field.sizes?.some(size => col.sizes?.includes(size)) && field.types?.includes(col.type) };
 
@@ -539,13 +566,11 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
     maxMatches = (fieldsCt: number, matches: number[][]) => {
         const used: boolean[] = Array(fieldsCt).fill(false);
         const mt: number[] = Array(fieldsCt).fill(-1);
-        console.log(fieldsCt, matches)
 
         const augmentingPath = (v: number): boolean => {
             if (used[v]) return false;
             used[v] = true;
             for (const to of matches[v]) {
-                console.log(mt[to]);
                 if (mt[to] === -1 || augmentingPath(mt[to])) {
                     mt[to] = v;
                     return true;
@@ -582,7 +607,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
         validTemplates = validTemplates.map(title => TemplateLayouts.fieldByTitle(title));
 
-        console.log(validTemplates);
         return validTemplates;
     };
 
@@ -626,20 +650,105 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             }
         }
 
+        const renderTextCalls = async(): Promise<Doc[]> => {
+            const rendered: Doc[] = [];
+
+            if (GPTTextCalls.length) {
+
+                try {
+                    const prompt = fieldContent + GPTTextAssignment;
+    
+                    const res = await gptAPICall(prompt, GPTCallType.FILL);
+    
+                    if (res){
+    
+                        const assignments: {[title: string]: {number: string, content: string}} = JSON.parse(res);
+                        //console.log('assignments', GPTAssignments, 'assignment string', GPTAssignmentString, 'field content', fieldContent, 'response', res, 'assignments', assignments);
+                        Object.entries(assignments).forEach(([title, info]) => {
+                            const field: Field = template.fields[Number(info.number)];
+                            const col = this.getColByTitle(title);
+    
+                            const doc = FieldFuncs.TextField({
+                                tl: field.tl, 
+                                br: field.br }, 
+                                template.height, 
+                                template.width, 
+                                col.title, 
+                                info.content ?? '', 
+                                field.opts
+                            );
+    
+                            rendered.push(doc);
+                        });
+    
+                    }
+                } catch(err) {
+                    console.log(err);
+                }
+            }
+
+            return rendered;
+        };
+
+        const createGeneratedImage = async(fieldNum: string, col: Col, prompt: string) => {
+            const url = await this.generateGPTImage(prompt);
+            const field: Field = template.fields[Number(fieldNum)];
+            const doc = FieldFuncs.ImageField({
+                tl: field.tl, 
+                br: field.br }, 
+                template.height, 
+                template.width, 
+                col.title, 
+                url ?? '', 
+                field.opts
+            );
+
+            return doc;
+        }
+
+        const renderImageCalls = async(): Promise<Doc[]> => {
+            const rendered: Doc[] = [];
+            const calls = GPTIMGCalls;
+
+            if (calls.length) {
+                try {
+                    const renderedImages: Doc[] = await Promise.all(
+                        calls.map(async ([fieldNum, col]) => {
+                            const sysPrompt = 'Your job is to create a prompt for an AI image generator to help it generate an image based on existing content in a template and a user prompt. ONLY INCLUDE THE PROMPT, NO OTHER TEXT OR EXPLANATION. The existing content is as follows: ' + fieldContent + ' **** The user prompt is: ' + col.desc;
+                            
+                            const prompt = await gptAPICall(sysPrompt, GPTCallType.COMPLETEPROMPT);
+                            console.log(sysPrompt, prompt);
+                            
+                            return createGeneratedImage(fieldNum, col, prompt);
+                        })
+                    );
+                
+                    const renderedTemplates: Doc[] = await Promise.all(renderedImages);
+                    renderedTemplates.forEach(doc => rendered.push(doc));
+                } catch (e){
+                    console.log(e);
+                }
+            }
+
+            return rendered;
+        }
+
         const fields: Doc[] = [];
 
         const GPTAssignments = Object.entries(assignments).filter(([f, col]) => this._columns.includes(col));
         const nonGPTAssignments: [string, Col][] = Object.entries(assignments).filter(a => !GPTAssignments.includes(a));
+        const GPTTextCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.TEXT);
+        const GPTIMGCalls = GPTAssignments.filter(([str, col]) => col.type === TemplateFieldType.VISUAL);
 
-        const stringifyGPTInfo = (): string => {
+        const stringifyGPTInfo = (calls: [string, Col][]): string => {
             let string: string = '*** COLUMN INFO:';
-            GPTAssignments.forEach(([fieldNum, col]) => {
+            calls.forEach(([fieldNum, col]) => {
                 string += `--- title: ${col.title}, prompt: ${col.desc}, word limit: ${wordLimit(col.sizes[0])} words, assigned field: ${fieldNum} ---`
             });
             return string += ' ***';
         };
 
-        const GPTAssignmentString = stringifyGPTInfo();
+        const GPTTextAssignment = stringifyGPTInfo(GPTTextCalls);
 
         let fieldContent: string = '';
 
@@ -695,47 +804,14 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             return main;
         }
 
-        if (GPTAssignments.length) {
-            
-            try {
-                const prompt = fieldContent + GPTAssignmentString;
-
-                const res = await gptAPICall(prompt, GPTCallType.FILL);
-
-                if (res){
-                    console.log('response', res);
-
-                    const assignments: {[title: string]: {number: string, content: string}} = JSON.parse(res);
-                    console.log('assignments', GPTAssignments, 'assignment string', GPTAssignmentString, 'field content', fieldContent, 'response', res, 'assignments', assignments);
-                    Object.entries(assignments).forEach(([title, info]) => {
-                        const field: Field = template.fields[Number(info.number)];
-                        const col = this.getColByTitle(title);
-
-                        const doc = (col.type === TemplateFieldType.VISUAL ? FieldFuncs.ImageField : FieldFuncs.TextField)({
-                            tl: field.tl, 
-                            br: field.br }, 
-                            template.height, 
-                            template.width, 
-                            col.title, 
-                            info.content ?? '', 
-                            field.opts
-                        );
-
-                        fields.push(doc);
-                    });
-
-                    return createMainDoc();
-                }
-            } catch(err) {
-                console.log(err);
-            }
-            
-        } else {
-            return createMainDoc();
-        }
+        const textCalls = await renderTextCalls();
+        const imageCalls = await renderImageCalls();
 
-        return new Doc;
-    };
+        textCalls.forEach(doc => {fields.push(doc)});
+        imageCalls.forEach(doc => {fields.push(doc)});
+
+        return createMainDoc();
+    }
 
     compileFieldDescriptions = (templates: TemplateDocInfos[]): string => {
         let descriptions: string = '';
@@ -765,7 +841,6 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
 
         const inputText = fieldDescriptions.concat(colDescriptions);
 
-        console.log(inputText);
         ++this._callCount;
         const origCount = this._callCount;
 
@@ -779,14 +854,12 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
             if (res && this._callCount === origCount) {
                 this._GPTLoading = false;
 
-                console.log(res);
-
                 const assignments: {[templateTitle: string]: {[field: string]: string}} = JSON.parse(res);
                 const brokenDownAssignments: [TemplateDocInfos, {[field: number]: Col}][] = [];
+
                 Object.entries(assignments).forEach(([tempTitle, assignment]) => {
                     const template = TemplateLayouts.fieldByTitle(tempTitle);
                     if (!template) return;
-                    console.log(assignments)
                     const toObj = Object.entries(assignment).reduce((a, [fieldNum, colTitle]) => {
                         a[Number(fieldNum)] = this.getColByTitle(colTitle);
                         return a;
@@ -849,7 +922,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                     <div className='docCreatorMenu-section' style={{height: this._GPTOpt ? 200 : 200}}>
                         <div className='docCreatorMenu-section-topbar'>
                             <div className='docCreatorMenu-section-title'>Suggested Templates</div>
-                            <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, () => this._menuContent = 'dashboard')}>
+                            <button className='docCreatorMenu-menu-button section-reveal-options' onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._menuContent = 'dashboard'))}>
                                 <FontAwesomeIcon icon='gear'/>
                             </button>
                         </div>
@@ -1197,7 +1270,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                     <div className='opts-bar'> 
                         <div className='opt-box'>
                             <div className='top-bar'> Title </div>
-                            <textarea className='content' style={{width: '100%', height: 'calc(100% - 20px)'}} defaultValue={field.title} placeholder={'Enter title'}  onChange={(e) => this.setColTitle(field, e.target.value)}/>
+                            <textarea className='content' style={{width: '100%', height: 'calc(100% - 20px)'}} defaultValue={field.title} placeholder={'Enter title'} onChange={(e) => this.setColTitle(field, e.target.value)}/>
                         </div>
                         <div className='opt-box'>
                             <div className='top-bar'> Type </div>
@@ -1237,7 +1310,7 @@ export class DocCreatorMenu extends ObservableReactComponent<FieldViewProps> {
                 <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, () => this._menuContent = 'templates')}>
+                <button className='docCreatorMenu-menu-button section-reveal-options float-right' onPointerDown={e => this.setUpButtonClick(e, () => runInAction(() => this._menuContent = 'templates'))}>
                     <FontAwesomeIcon icon='arrow-left'/>
                 </button>
             </div>
-- 
cgit v1.2.3-70-g09d2


From 9323ad30103b474e95610e97eb92916a0cf71f7b Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 4 Oct 2024 21:35:22 -0400
Subject: more lint fixes to schema view changes.

---
 src/ClientUtils.ts                                 |   5 +-
 src/client/util/LinkManager.ts                     |   2 +-
 src/client/util/Scripting.ts                       |   8 +-
 src/client/util/SelectionManager.ts                |   1 -
 src/client/views/FieldsDropdown.tsx                |   4 +-
 src/client/views/PropertiesView.tsx                |   2 +-
 .../collectionSchema/CollectionSchemaView.tsx      | 327 +++++++++++----------
 .../collectionSchema/SchemaColumnHeader.tsx        | 264 +++++++++--------
 .../collections/collectionSchema/SchemaRowBox.tsx  |  48 +--
 .../collectionSchema/SchemaTableCell.tsx           |  82 +++---
 src/client/views/global/globalScripts.ts           |   1 -
 src/client/views/nodes/DataVizBox/DataVizBox.tsx   | 177 ++++++-----
 src/client/views/nodes/DocumentIcon.tsx            |  10 +-
 src/client/views/nodes/FieldView.tsx               |   2 +-
 src/client/views/nodes/LabelBox.tsx                |   4 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 -
 16 files changed, 485 insertions(+), 454 deletions(-)

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

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index 8ecbc55bf..3066499d8 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -317,7 +317,7 @@ export namespace ClientUtils {
     }
 
     export function lightenRGB(rVal: number, gVal: number, bVal: number, percent: number): [number, number, number] {
-        const amount = 1 + percent/100;
+        const amount = 1 + percent / 100;
         const r = rVal * amount;
         const g = gVal * amount;
         const b = bVal * amount;
@@ -347,8 +347,6 @@ export namespace ClientUtils {
         return undefined;
     }
 
-    
-
     export function GetClipboardText(): string {
         const textArea = document.createElement('textarea');
         document.body.appendChild(textArea);
@@ -725,7 +723,6 @@ export function UpdateIcon(
     const newDiv = docViewContent.cloneNode(true) as HTMLDivElement;
     newDiv.style.width = width.toString();
     newDiv.style.height = height.toString();
-    console.log('width: ' + newDiv.style.width)
     replaceCanvases(docViewContent, newDiv);
     const htmlString = new XMLSerializer().serializeToString(newDiv);
     const nativeWidth = width;
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index bbf8d9c11..e11482572 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -260,7 +260,7 @@ export function UPDATE_SERVER_CACHE() {
     Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
     const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
     const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
-    //Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+    Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
 
     rp.post(ClientUtils.prepend('/setCacheDocumentIds'), {
         body: {
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 3ba3ff4b5..b1db0bf39 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -81,6 +81,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: ts
             if (!options.editable) {
                 batch = Doc.MakeReadOnly();
             }
+
             const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray);
             batch?.end();
             return { success: true, result };
@@ -177,13 +178,8 @@ function forEachNode(node: ts.Node, onEnter: Traverser, onExit?: Traverser, inde
     );
 }
 
-// ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer());
-// //addreturn = true
-// //capturedvariables = undefined
-// //
-
 export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult {
-    const captured = options.capturedVariables ?? {}; 
+    const captured = options.capturedVariables ?? {};
     const signature = Object.keys(captured).reduce((p, v) => {
         const formatCapture = (obj: FieldType | undefined) => `${v}=${obj instanceof RefField ? 'XXX' : obj?.toString()}`;
         const captureVal = captured[v];
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index c39d8ebc7..1ab84421c 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -58,7 +58,6 @@ export class SelectionManager {
     });
 
     public static DeselectAll = (except?: Doc): void => {
-        
         const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
         runInAction(() => {
             if (LinkManager.Instance) {
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx
index 176ac96b6..407031b40 100644
--- a/src/client/views/FieldsDropdown.tsx
+++ b/src/client/views/FieldsDropdown.tsx
@@ -34,7 +34,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
         makeObservable(this);
     }
 
-    @computed get allDescendantDocs() { //!!!
+    @computed get allDescendantDocs() {
         const allDocs = new Set<Doc>();
         SearchUtil.foreachRecursiveDoc([this._props.Document], (depth, doc) => allDocs.add(doc));
         return Array.from(allDocs);
@@ -57,7 +57,7 @@ export class FieldsDropdown extends ObservableReactComponent<fieldsDropdownProps
         const filteredOptions = ['author', ...(this._newField ? [this._newField] : []), ...(this._props.addedFields ?? []), ...this.fieldsOfDocuments.filter(facet => facet[0] === facet.charAt(0).toUpperCase())];
 
         Object.entries(DocOptions)
-            .filter(opts => opts[1].filterable) //!!!
+            .filter(opts => opts[1].filterable)
             .forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0]));
         const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet }));
 
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 69c46052e..c6dccb4fb 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -884,7 +884,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     @computed get borderColor() {
         const doc = this.selectedDoc;
         const layoutDoc = doc ? Doc.Layout(doc) : doc;
-        return StrCast(layoutDoc);
+        return StrCast(layoutDoc.color);
     }
     set borderColor(value) { this.selectedDoc && (this.selectedDoc[DocData].color = value || undefined); } // prettier-ignore
 
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
index 0076caaf8..aef97e723 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx
@@ -1,12 +1,11 @@
-/* eslint-disable no-restricted-syntax */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { IconButton, Popup, PopupTrigger, Size, Type } from 'browndash-components';
-import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, observe, override, reaction, runInAction } from 'mobx';
+import { IconButton, Size } from 'browndash-components';
+import { IReactionDisposer, Lambda, ObservableMap, action, computed, makeObservable, observable, observe, reaction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../ClientUtils';
 import { emptyFunction } from '../../../../Utils';
-import { Doc, DocListCast, Field, FieldType, IdToDoc, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
 import { DocData } from '../../../../fields/DocSymbols';
 import { Id } from '../../../../fields/FieldSymbols';
 import { List } from '../../../../fields/List';
@@ -14,10 +13,13 @@ import { ColumnType } from '../../../../fields/SchemaHeaderField';
 import { BoolCast, NumCast, StrCast } from '../../../../fields/Types';
 import { DocUtils } from '../../../documents/DocUtils';
 import { Docs, DocumentOptions, FInfo } from '../../../documents/Documents';
+import { DocumentManager } from '../../../util/DocumentManager';
 import { DragManager } from '../../../util/DragManager';
 import { dropActionType } from '../../../util/DropActionTypes';
+import { SnappingManager } from '../../../util/SnappingManager';
 import { undoBatch, undoable } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
+import { ContextMenuProps } from '../../ContextMenuItem';
 import { EditableView } from '../../EditableView';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
 import { StyleProp } from '../../StyleProp';
@@ -28,25 +30,23 @@ import { FieldViewProps } from '../../nodes/FieldView';
 import { FocusViewOptions } from '../../nodes/FocusViewOptions';
 import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView';
 import './CollectionSchemaView.scss';
+import { SchemaCellField } from './SchemaCellField';
 import { SchemaColumnHeader } from './SchemaColumnHeader';
 import { SchemaRowBox } from './SchemaRowBox';
-import { ContextMenuProps } from '../../ContextMenuItem';
-import { DocumentManager } from '../../../util/DocumentManager';
-import { SchemaCellField } from './SchemaCellField';
-import { SnappingManager } from '../../../util/SnappingManager';
 
 /**
  * The schema view offers a spreadsheet-like interface for users to interact with documents. Within the schema,
- * each doc is represented by its own row. Each column represents a field, for example the author or title fields. 
+ * each doc is represented by its own row. Each column represents a field, for example the author or title fields.
  * Users can apply varoius filters and sorts to columns to change what is displayed. The schemaview supports equations for
  * cell linking.
- * 
+ *
  * This class supports the main functionality for choosing which docs to render in the view, applying visual
- * updates to rows and columns (such as user dragging or sort-related highlighting), applying edits to multiple cells 
+ * updates to rows and columns (such as user dragging or sort-related highlighting), applying edits to multiple cells
  * at once, and applying filters and sorts to columns. It contains SchemaRowBoxes (which themselves contain SchemaTableCells,
  * and SchemaCellFields) and SchemaColumnHeaders.
  */
 
+// eslint-disable-next-line @typescript-eslint/no-require-imports
 const { SCHEMA_NEW_NODE_HEIGHT } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore
 
 export const FInfotoColType: { [key: string]: ColumnType } = {
@@ -63,7 +63,7 @@ const defaultColumnKeys: string[] = ['title', 'type', 'author', 'author_date', '
 
 @observer
 export class CollectionSchemaView extends CollectionSubView() {
-    private _keysDisposer: any;
+    private _keysDisposer?: Lambda;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _previewRef: HTMLDivElement | null = null;
     private _makeNewColumn: boolean = false;
@@ -71,13 +71,14 @@ export class CollectionSchemaView extends CollectionSubView() {
     private _tableContentRef: HTMLDivElement | null = null;
     private _menuTarget = React.createRef<HTMLDivElement>();
     private _headerRefs: SchemaColumnHeader[] = [];
-    private _eqHighlightColors: Array<[{r: number, g: number, b: number}, {r: number, g: number, b: number}]> = [];
+    private _eqHighlightColors: Array<[{ r: number; g: number; b: number }, { r: number; g: number; b: number }]> = [];
+    private _oldWheel: HTMLDivElement | null = null;
 
     constructor(props: SubCollectionViewProps) {
         super(props);
         makeObservable(this);
         const lightenedColor = (r: number, g: number, b:number) => { const lightened = ClientUtils.lightenRGB(r, g, b, 165); return {r: lightened[0], g: lightened[1], b: lightened[2]}} // prettier-ignore
-        const colors = (r: number, g: number, b: number): [any, any] => {return [{r: r, g: g, b: b}, lightenedColor(r, g, b)]} // prettier-ignore
+        const colors = (r: number, g: number, b: number):[{r:number,g:number,b:number},{r:number,g:number,b:number}] => ([{r, g, b}, lightenedColor(r, g, b)]); // prettier-ignore
         this._eqHighlightColors.push(colors(70, 150, 50));
         this._eqHighlightColors.push(colors(180, 70, 20));
         this._eqHighlightColors.push(colors(70, 50, 150));
@@ -122,8 +123,8 @@ export class CollectionSchemaView extends CollectionSubView() {
     @observable _highlightedCellsInfo: Array<[doc: Doc, field: string]> = [];
     @observable _cellHighlightColors: ObservableMap = new ObservableMap<string, string[]>();
     @observable _containedDocs: Doc[] = []; //all direct children of the schema
-    @observable _referenceSelectMode: {enabled: boolean, currEditing: SchemaCellField | undefined} = {enabled: false, currEditing: undefined}
-    
+    @observable _referenceSelectMode: { enabled: boolean; currEditing: SchemaCellField | undefined } = { enabled: false, currEditing: undefined };
+
     // target HTMLelement portal for showing a popup menu to edit cell values.
     public get MenuTarget() {
         return this._menuTarget.current;
@@ -217,15 +218,17 @@ export class CollectionSchemaView extends CollectionSubView() {
             true
         );
         this._disposers.docdata = reaction(
-            () => DocListCast(this.dataDoc[this.fieldKey]), 
-            (docs) => this._containedDocs = docs,
-            {fireImmediately: true}
-        )
+            () => DocListCast(this.dataDoc[this.fieldKey]),
+            docs => (this._containedDocs = docs),
+            { fireImmediately: true }
+        );
         this._disposers.sortHighlight = reaction(
-            () => [this.sortField, this._containedDocs, this._selectedDocs, this._highlightedCellsInfo], 
-            () => {this.sortField && setTimeout(() => this.highlightSortedColumn())},
-            {fireImmediately: true}
-        )
+            () => [this.sortField, this._containedDocs, this._selectedDocs, this._highlightedCellsInfo],
+            () => {
+                this.sortField && setTimeout(() => this.highlightSortedColumn());
+            },
+            { fireImmediately: true }
+        );
     }
 
     componentWillUnmount() {
@@ -239,8 +242,8 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     removeDoc = (doc: Doc) => {
         this.removeDocument(doc);
-        this._containedDocs = this._containedDocs.filter(d => d !== doc)
-    }
+        this._containedDocs = this._containedDocs.filter(d => d !== doc);
+    };
 
     rowIndex = (doc: Doc) => this.docsWithDrag.docs.indexOf(doc);
 
@@ -301,7 +304,9 @@ export class CollectionSchemaView extends CollectionSubView() {
                     }
                     break;
                 case 'Backspace': {
-                    undoable(() => {this._selectedDocs.forEach(d => this._containedDocs.includes(d) && this.removeDoc(d));}, 'delete schema row');
+                    undoable(() => {
+                        this._selectedDocs.forEach(d => this._containedDocs.includes(d) && this.removeDoc(d));
+                    }, 'delete schema row');
                     break;
                 }
                 case 'Escape': {
@@ -319,7 +324,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     addRow = (doc: Doc | Doc[]) => this.addDocument(doc);
 
     @undoBatch
-    changeColumnKey = (index: number, newKey: string, defaultVal?: any) => {
+    changeColumnKey = (index: number, newKey: string, defaultVal?: FieldType) => {
         if (!this.documentKeys.includes(newKey)) this.addNewKey(newKey, defaultVal);
 
         const currKeys = this.columnKeys.slice(); // copy the column key array first, then change it.
@@ -328,9 +333,10 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @undoBatch
-    addColumn = (index: number = 0, key?: string, defaultVal?: any) => {
+    addColumn = (index: number = 0, keyIn?: string, defaultVal?: FieldType) => {
+        let key = keyIn;
         if (key && !this.documentKeys.includes(key)) this.addNewKey(key, defaultVal);
-        
+
         const newColWidth = this.tableWidth / (this.storedColumnWidths.length + 1);
         const currWidths = this.storedColumnWidths.slice();
         currWidths.splice(index, 0, newColWidth);
@@ -345,11 +351,11 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    addNewKey = (key: string, defaultVal: any) => {
+    addNewKey = (key: string, defaultVal: FieldType | undefined) => {
         this.childDocs.forEach(doc => {
             doc[DocData][key] = defaultVal;
         });
-    }
+    };
 
     @undoBatch
     removeColumn = (index: number) => {
@@ -365,13 +371,13 @@ export class CollectionSchemaView extends CollectionSubView() {
 
         const currKeys = this.columnKeys.slice();
         currKeys.splice(index, 1);
-        this.layoutDoc.schema_columnKeys = new List<string>(currKeys); 
+        this.layoutDoc.schema_columnKeys = new List<string>(currKeys);
 
         this._colEles.splice(index, 1);
     };
 
     @action
-    startResize = (e: any, index: number, rightSide: boolean) => {
+    startResize = (e: React.PointerEvent, index: number, rightSide: boolean) => {
         this._displayColumnWidths = this.storedColumnWidths;
         setupMoveUpEvents(this, e, moveEv => this.resizeColumn(moveEv, index, rightSide), this.finishResize, emptyFunction);
     };
@@ -384,8 +390,8 @@ export class CollectionSchemaView extends CollectionSubView() {
 
             let change = e.movementX;
 
-            if (rightSide && (index !== this._displayColumnWidths.length - 1)) {
-                growing = change < 0 ? index + 1: index;
+            if (rightSide && index !== this._displayColumnWidths.length - 1) {
+                growing = change < 0 ? index + 1 : index;
                 shrinking = change < 0 ? index : index + 1;
             } else if (index !== 0) {
                 growing = change < 0 ? index : index - 1;
@@ -432,7 +438,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this.closeNewColumnMenu();
         this._headerRefs.forEach(ref => ref.toggleEditing(false));
         this._draggedColIndex = index;
-        this.setColDrag(true);  
+        this.setColDrag(true);
         const dragData = new DragManager.ColumnDragData(index);
         const dragEles = [this._colEles[index]];
         this.childDocs.forEach(doc => dragEles.push(this._rowEles.get(doc).children[1].children[index]));
@@ -446,7 +452,7 @@ export class CollectionSchemaView extends CollectionSubView() {
      * @returns column index
      */
     findColDropIndex = (mouseX: number) => {
-        const xOffset: number = this._props.ScreenToLocalTransform().inverse().transformPoint(0,0)[0] + CollectionSchemaView._rowMenuWidth; 
+        const xOffset: number = this._props.ScreenToLocalTransform().inverse().transformPoint(0, 0)[0] + CollectionSchemaView._rowMenuWidth;
         let index: number | undefined;
         this.displayColumnWidths.reduce((total, curr, i) => {
             if (total <= mouseX && total + curr >= mouseX) {
@@ -507,12 +513,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._colEles.forEach((colRef, i) => {
             const edgeStyle = i === index ? `solid 2px ${Colors.MEDIUM_BLUE}` : '';
             const sorted = i === this.columnKeys.indexOf(this.sortField);
-            const cellEles = [
-                colRef,
-                ...this.docsWithDrag.docs 
-                    .filter(doc => (i !== this._selectedCol || !this._selectedDocs.includes(doc)) && !sorted)
-                    .map(doc => this._rowEles.get(doc).children[1].children[i]),
-            ];
+            const cellEles = [colRef, ...this.docsWithDrag.docs.filter(doc => (i !== this._selectedCol || !this._selectedDocs.includes(doc)) && !sorted).map(doc => this._rowEles.get(doc).children[1].children[i])];
             cellEles.forEach(ele => {
                 if (sorted || this.highlightedCells.includes(ele)) return;
                 ele.style.borderTop = ele === cellEles[0] ? edgeStyle : '';
@@ -533,14 +534,14 @@ export class CollectionSchemaView extends CollectionSubView() {
 
             this.childDocs.forEach(doc => {
                 const cell = this._rowEles.get(doc).children[1].children[i];
-                if (!(this._selectedDocs.includes(doc) && i === this._selectedCol) && !(this.highlightedCells.includes(cell)) && cell) {
+                if (!(this._selectedDocs.includes(doc) && i === this._selectedCol) && !this.highlightedCells.includes(cell) && cell) {
                     cell.style.borderLeft = '';
                     cell.style.borderRight = '';
                     cell.style.borderBottom = '';
                 }
             });
         });
-    }
+    };
 
     /**
      * Applies a gradient highlight to a sorted column. The direction of the gradient depends
@@ -552,10 +553,10 @@ export class CollectionSchemaView extends CollectionSubView() {
         let index = -1;
         const highlightColors: string[] = [];
         const rowCount: number = this._containedDocs.length + 1;
-        if (field || this.sortField){
+        if (field || this.sortField) {
             index = this.columnKeys.indexOf(field || this.sortField);
-            const increment: number = 110/rowCount;
-            for (let i = 1; i <= rowCount; ++i){
+            const increment: number = 110 / rowCount;
+            for (let i = 1; i <= rowCount; ++i) {
                 const adjColor = ClientUtils.lightenRGB(16, 66, 230, increment * i);
                 highlightColors.push(`solid 2px rgb(${adjColor[0]}, ${adjColor[1]}, ${adjColor[2]})`);
             }
@@ -564,25 +565,20 @@ export class CollectionSchemaView extends CollectionSubView() {
         this._colEles.forEach((colRef, i) => {
             const highlight: boolean = i === index;
             const desc: boolean = descending || this.sortDesc;
-            const cellEles = [
-                colRef,
-                ...this.docsWithDrag.docs 
-                    .filter(doc => (i !== this._selectedCol || !this._selectedDocs.includes(doc)))
-                    .map(doc => this._rowEles.get(doc).children[1].children[i]),
-            ];
+            const cellEles = [colRef, ...this.docsWithDrag.docs.filter(doc => i !== this._selectedCol || !this._selectedDocs.includes(doc)).map(doc => this._rowEles.get(doc).children[1].children[i])];
             const cellCount = cellEles.length;
-            for (let ele = 0; ele < cellCount; ++ele){
+            for (let ele = 0; ele < cellCount; ++ele) {
                 const currCell = cellEles[ele];
                 if (this.highlightedCells.includes(currCell)) continue;
-                const style = highlight ? desc ? `${highlightColors[cellCount - 1 - ele]}` : `${highlightColors[ele]}` : '';
+                const style = highlight ? (desc ? `${highlightColors[cellCount - 1 - ele]}` : `${highlightColors[ele]}`) : '';
                 currCell.style.borderLeft = style;
                 currCell.style.borderRight = style;
             }
-            cellEles[0].style.borderTop = highlight ? desc ? `${highlightColors[cellCount - 1]}` : `${highlightColors[0]}` : '';
-            if (!(this._selectedDocs.includes(this.docsWithDrag.docs[this.docsWithDrag.docs.length - 1]) && this._selectedCol === index) && !this.highlightedCells.includes(cellEles[cellCount - 1])) cellEles[cellCount - 1].style.borderBottom = highlight ? desc ? `${highlightColors[0]}` :  `${highlightColors[cellCount - 1]}` : ''; 
+            cellEles[0].style.borderTop = highlight ? (desc ? `${highlightColors[cellCount - 1]}` : `${highlightColors[0]}`) : '';
+            if (!(this._selectedDocs.includes(this.docsWithDrag.docs[this.docsWithDrag.docs.length - 1]) && this._selectedCol === index) && !this.highlightedCells.includes(cellEles[cellCount - 1]))
+                cellEles[cellCount - 1].style.borderBottom = highlight ? (desc ? `${highlightColors[0]}` : `${highlightColors[cellCount - 1]}`) : '';
         });
-        
-    }
+    };
 
     /**
      * Gets the html element representing a cell so that styles can be applied on it.
@@ -594,35 +590,40 @@ export class CollectionSchemaView extends CollectionSubView() {
         const index = this.columnKeys.indexOf(fieldKey);
         const cell = this._rowEles.get(doc).children[1].children[index];
         return cell;
-    }
+    };
 
     /**
      * Given text in a cell, find references to other cells (for equations).
      * @param text the text in the cell
-     * @returns the html cell elements referenced in the text. 
+     * @returns the html cell elements referenced in the text.
      */
     findCellRefs = (text: string) => {
         const pattern = /(this|d(\d+))\.(\w+)/g;
-        interface Match { docRef: string; field: string; }
+        interface Match {
+            docRef: string;
+            field: string;
+        }
 
         const matches: Match[] = [];
         let match: RegExpExecArray | null;
 
         while ((match = pattern.exec(text)) !== null) {
-            const docRef = match[1] === 'this' ? match[1] : match[2]; 
+            const docRef = match[1] === 'this' ? match[1] : match[2];
             matches.push({ docRef, field: match[3] });
         }
 
-        const cells: Array<any> = [];
-        matches.forEach((match: Match) => {
-            const {docRef, field} = match;
+        const cells: [Doc, string][] = [];
+        matches.forEach((m: Match) => {
+            const { docRef, field } = m;
             const docView = DocumentManager.Instance.DocumentViews[Number(docRef)];
             const doc = docView?.Document ?? undefined;
-            if (this.columnKeys.includes(field) && this._containedDocs.includes(doc)) {cells.push([doc, field])}
-        })
+            if (this.columnKeys.includes(field) && this._containedDocs.includes(doc)) {
+                cells.push([doc, field]);
+            }
+        });
 
         return cells;
-    }
+    };
 
     /**
      * Determines whether the rows above or below a given row have been
@@ -636,7 +637,7 @@ export class CollectionSchemaView extends CollectionSubView() {
         const selectedBelow: boolean = this._selectedDocs.includes(docs[index + 1]);
         const selectedAbove: boolean = this._selectedDocs.includes(docs[index - 1]);
         return [selectedAbove, selectedBelow];
-    }
+    };
 
     @action
     removeCellHighlights = () => {
@@ -649,9 +650,10 @@ export class CollectionSchemaView extends CollectionSubView() {
                 if (this.selectionOverlap(doc)[0]) cell.style.borderTop = '';
                 if (this.selectionOverlap(doc)[1]) cell.style.borderBottom = '';
             } else cell.style.border = '';
-            cell.style.backgroundColor = '';});
+            cell.style.backgroundColor = '';
+        });
         this._highlightedCellsInfo = [];
-    }
+    };
 
     restoreCellHighlights = () => {
         this._highlightedCellsInfo.forEach(info => {
@@ -665,10 +667,10 @@ export class CollectionSchemaView extends CollectionSubView() {
             cell.style.borderRight = color;
             cell.style.borderBottom = color;
         });
-    }
+    };
 
     /**
-     * Highlights cells based on equation text in the cell currently being edited. 
+     * Highlights cells based on equation text in the cell currently being edited.
      * Does not highlight selected cells (that's done directly in SchemaTableCell).
      * @param text the equation
      */
@@ -682,7 +684,7 @@ export class CollectionSchemaView extends CollectionSubView() {
             const info = this._highlightedCellsInfo[i];
             const color = this._eqHighlightColors[i % 10];
             const colorStrings = [`solid 2px rgb(${color[0].r}, ${color[0].g}, ${color[0].b})`, `rgb(${color[1].r}, ${color[1].g}, ${color[1].b})`];
-            const doc = info[0]; 
+            const doc = info[0];
             const field = info[1];
             const key = `${doc[Id]}_${field}`;
             const cell = this.getCellElement(doc, field);
@@ -690,7 +692,7 @@ export class CollectionSchemaView extends CollectionSubView() {
             cell.style.border = colorStrings[0];
             cell.style.backgroundColor = colorStrings[1];
         }
-    }
+    };
 
     //Used in SchemaRowBox
     @action
@@ -718,7 +720,6 @@ export class CollectionSchemaView extends CollectionSubView() {
         this.deselectAllCells();
     };
 
-
     selectRow = (doc: Doc, lastSelected: Doc) => {
         const index = this.rowIndex(doc);
         const lastSelectedRow = this.rowIndex(lastSelected);
@@ -737,12 +738,12 @@ export class CollectionSchemaView extends CollectionSubView() {
         if (!doc) return;
         const docIndex = DocumentView.getDocViewIndex(doc);
         const field = this.columnKeys[col];
-        const refToAdd = `d${docIndex}.${field}`
-        const editedField = this._referenceSelectMode.currEditing ? this._referenceSelectMode.currEditing as SchemaCellField : null;
+        const refToAdd = `d${docIndex}.${field}`;
+        const editedField = this._referenceSelectMode.currEditing ? (this._referenceSelectMode.currEditing as SchemaCellField) : null;
         editedField?.insertText(refToAdd, true);
         editedField?.setupRefSelect(false);
         return;
-    }
+    };
 
     @action
     selectCell = (doc: Doc, col: number, shiftKey: boolean, ctrlKey: boolean) => {
@@ -787,7 +788,9 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action
     onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (de.complete.columnDragData) {
-            setTimeout(() => {this.setColDrag(false);});
+            setTimeout(() => {
+                this.setColDrag(false);
+            });
             e.stopPropagation();
             return true;
         }
@@ -849,9 +852,9 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     @action
-    setKey = (key: string, defaultVal?: any, index?: number) => {
+    setKey = (key: string, defaultVal?: string, index?: number) => {
         if (this.columnKeys.includes(key)) return;
-    
+
         if (this._makeNewColumn) {
             this.addColumn(this.columnKeys.indexOf(key), key, defaultVal);
             this._makeNewColumn = false;
@@ -861,14 +864,14 @@ export class CollectionSchemaView extends CollectionSubView() {
     };
 
     /**
-     * Used in SchemaRowBox to set 
-     * @param key 
-     * @param value 
-     * @returns 
+     * Used in SchemaRowBox to set
+     * @param key
+     * @param value
+     * @returns
      */
     setCellValues = (key: string, value: string) => {
-        if (this._selectedCells.length === 1) this.docs.forEach(doc => !doc._lockedSchemaEditing && Doc.SetField(doc, key, value)); 
-        else this._selectedCells.forEach(doc => !doc._lockedSchemaEditing && Doc.SetField(doc, key, value)); 
+        if (this._selectedCells.length === 1) this.docs.forEach(doc => !doc._lockedSchemaEditing && Doc.SetField(doc, key, value));
+        else this._selectedCells.forEach(doc => !doc._lockedSchemaEditing && Doc.SetField(doc, key, value));
         return true;
     };
 
@@ -912,34 +915,36 @@ export class CollectionSchemaView extends CollectionSubView() {
         const cm = ContextMenu.Instance;
         cm.clearItems();
 
-        const fieldSortedAsc = (this.sortField === this.columnKeys[index] && !this.sortDesc);
-        const fieldSortedDesc = (this.sortField === this.columnKeys[index] && this.sortDesc);
-        const revealOptions = cm.findByDescription('Sort column')
-        const sortOptions: ContextMenuProps[] = revealOptions && revealOptions && 'subitems' in revealOptions ? revealOptions.subitems ?? [] : [];
+        const fieldSortedAsc = this.sortField === this.columnKeys[index] && !this.sortDesc;
+        const fieldSortedDesc = this.sortField === this.columnKeys[index] && this.sortDesc;
+        const revealOptions = cm.findByDescription('Sort column');
+        const sortOptions: ContextMenuProps[] = revealOptions && revealOptions && 'subitems' in revealOptions ? (revealOptions.subitems ?? []) : [];
         sortOptions.push({
-            description: 'Sort A-Z',     
+            description: 'Sort A-Z',
             event: () => {
-                this.setColumnSort(undefined); 
+                this.setColumnSort(undefined);
                 const field = this.columnKeys[index];
                 this._containedDocs = this.sortDocs(field, false);
                 setTimeout(() => {
                     this.highlightSortedColumn(field, false);
-                    setTimeout(() => this.highlightSortedColumn(), 480); 
+                    setTimeout(() => this.highlightSortedColumn(), 480);
                 }, 20);
-            },     
-            icon: 'arrow-down-a-z',});
+            },
+            icon: 'arrow-down-a-z',
+        });
         sortOptions.push({
-            description: 'Sort Z-A',     
+            description: 'Sort Z-A',
             event: () => {
-                this.setColumnSort(undefined); 
+                this.setColumnSort(undefined);
                 const field = this.columnKeys[index];
                 this._containedDocs = this.sortDocs(field, true);
                 setTimeout(() => {
                     this.highlightSortedColumn(field, true);
-                    setTimeout(() => this.highlightSortedColumn(), 480); 
+                    setTimeout(() => this.highlightSortedColumn(), 480);
                 }, 20);
-            },     
-            icon: 'arrow-up-z-a'});
+            },
+            icon: 'arrow-up-z-a',
+        });
         sortOptions.push({
             description: 'Persistent Sort A-Z',     
             event: () => {
@@ -964,7 +969,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                 }
             },     
             icon: fieldSortedDesc ? 'lock' : 'lock-open'}); // prettier-ignore
-    
+
         cm.addItem({
             description: `Change field`,
             event: () => this.openNewColumnMenu(index, false),
@@ -975,12 +980,12 @@ export class CollectionSchemaView extends CollectionSubView() {
             event: () => this.openFilterMenu(index),
             icon: 'filter',
         });
-        cm.addItem({ 
-            description: 'Sort column', 
-            addDivider: false, 
-            noexpand: true, 
-            subitems: sortOptions, 
-            icon: 'sort' 
+        cm.addItem({
+            description: 'Sort column',
+            addDivider: false,
+            noexpand: true,
+            subitems: sortOptions,
+            icon: 'sort',
         });
         cm.addItem({
             description: 'Add column to left',
@@ -1068,7 +1073,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @computed get renderColumnMenu() {
         const x = this._columnMenuIndex! === -1 ? 0 : this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._columnMenuIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
         return (
-            <div className="schema-column-menu" style={{ left: x,  maxWidth: `${Math.max(this._colEles[this._columnMenuIndex ?? 0].offsetWidth, 150)}px` }}>
+            <div className="schema-column-menu" style={{ left: x, maxWidth: `${Math.max(this._colEles[this._columnMenuIndex ?? 0].offsetWidth, 150)}px` }}>
                 {this.keysDropdown}
             </div>
         );
@@ -1095,13 +1100,7 @@ export class CollectionSchemaView extends CollectionSubView() {
             }
             return (
                 <div key={key} className="schema-filter-option">
-                    <input 
-                        type="checkbox"
-                        onPointerDown={e => e.stopPropagation()}
-                        onClick={e => e.stopPropagation()}
-                        onChange={e => Doc.setDocFilter(this.Document, columnKey, key, e.target.checked ? 'check' : 'remove')}
-                        checked={bool}
-                    />
+                    <input type="checkbox" onPointerDown={e => e.stopPropagation()} onClick={e => e.stopPropagation()} onChange={e => Doc.setDocFilter(this.Document, columnKey, key, e.target.checked ? 'check' : 'remove')} checked={bool} />
                     <span style={{ paddingLeft: 4 }}>{key}</span>
                 </div>
             );
@@ -1111,7 +1110,7 @@ export class CollectionSchemaView extends CollectionSubView() {
     @computed get renderFilterMenu() {
         const x = this.displayColumnWidths.reduce((total, curr, index) => total + (index < this._filterColumnIndex! ? curr : 0), CollectionSchemaView._rowMenuWidth);
         return (
-            <div className="schema-filter-menu" style={{ left: x, maxWidth: `${Math.max(this._colEles[this._columnMenuIndex ?? 0].offsetWidth, 150)}px`}}>
+            <div className="schema-filter-menu" style={{ left: x, maxWidth: `${Math.max(this._colEles[this._columnMenuIndex ?? 0].offsetWidth, 150)}px` }}>
                 <input className="schema-filter-input" type="text" value={this._filterSearchValue} onKeyDown={this.onFilterKeyDown} onChange={this.updateFilterSearch} onPointerDown={e => e.stopPropagation()} />
                 {this.renderFilterOptions}
                 <div
@@ -1129,13 +1128,13 @@ export class CollectionSchemaView extends CollectionSubView() {
     @action setColDrag = (beingDragged: boolean) => {
         this._colBeingDragged = beingDragged;
         !beingDragged && this.removeDragHighlight();
-    }
+    };
 
     @action updateMouseCoordinates = (e: React.PointerEvent<HTMLDivElement>) => {
         const prevX = this._mouseCoordinates.x;
         const prevY = this._mouseCoordinates.y;
         this._mouseCoordinates = { x: e.clientX, y: e.clientY, prevX: prevX, prevY: prevY };
-    }
+    };
 
     @action
     onPointerMove = (e: React.PointerEvent<HTMLDivElement>) => {
@@ -1158,9 +1157,9 @@ export class CollectionSchemaView extends CollectionSubView() {
 
     /**
      * Gets docs contained by collections within the schema. Currently defunct.
-     * @param doc 
-     * @param displayed 
-     * @returns 
+     * @param doc
+     * @param displayed
+     * @returns
      */
     // subCollectionDocs = (doc: Doc, displayed: boolean) => {
     //     const childDocs = DocListCast(doc[Doc.LayoutFieldKey(doc)]);
@@ -1203,8 +1202,7 @@ export class CollectionSchemaView extends CollectionSubView() {
                             subDocs.forEach(t => {
                                 const docFieldKey = Doc.LayoutFieldKey(t);
                                 const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc));
-                                notFiltered =
-                                    notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
+                                notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length));
                                 DocListCast(t[isSubDocAnnotatable ? docFieldKey + '_annotations' : docFieldKey]).forEach(newdoc => newarray.push(newdoc));
                                 isSubDocAnnotatable && DocListCast(t[docFieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc));
                             });
@@ -1231,19 +1229,19 @@ export class CollectionSchemaView extends CollectionSubView() {
         //     docsFromChildren = docsFromChildren.concat(docsNotAlreadyDisplayed);
         // });
 
-        return this.filteredDocs;;
+        return this.filteredDocs;
     }
 
     /**
-     * Sorts docs first alphabetically and then numerically. 
+     * Sorts docs first alphabetically and then numerically.
      * @param field the column being sorted
      * @param desc whether the sort is ascending or descending
      * @param persistent whether the sort is applied persistently or is one-shot
-     * @returns 
+     * @returns
      */
     sortDocs = (field: string, desc: boolean, persistent?: boolean) => {
         const numbers: Doc[] = [];
-        const strings: Doc[] = []; 
+        const strings: Doc[] = [];
 
         this.docs.forEach(doc => {
             if (!isNaN(Number(Field.toString(doc[field] as FieldType)))) numbers.push(doc);
@@ -1253,25 +1251,26 @@ export class CollectionSchemaView extends CollectionSubView() {
         const sortedNums = numbers.sort((numOne, numTwo) => {
             const numA = Number(Field.toString(numOne[field] as FieldType));
             const numB = Number(Field.toString(numTwo[field] as FieldType));
-            return desc? numA - numB : numB - numA;
+            return desc ? numA - numB : numB - numA;
         });
 
-        const collator = new Intl.Collator(undefined, {sensitivity: 'base'});
+        const collator = new Intl.Collator(undefined, { sensitivity: 'base' });
         let sortedStrings;
-        if (!desc) {sortedStrings = strings.slice().sort((docA, docB) => collator.compare(Field.toString(docA[field] as FieldType), Field.toString(docB[field] as FieldType)));
+        if (!desc) {
+            sortedStrings = strings.slice().sort((docA, docB) => collator.compare(Field.toString(docA[field] as FieldType), Field.toString(docB[field] as FieldType)));
         } else sortedStrings = strings.slice().sort((docB, docA) => collator.compare(Field.toString(docA[field] as FieldType), Field.toString(docB[field] as FieldType)));
 
         const sortedDocs = desc ? sortedNums.concat(sortedStrings) : sortedStrings.concat(sortedNums);
         if (!persistent) this._containedDocs = sortedDocs;
         return sortedDocs;
-    }
-    
+    };
+
     /**
      * Returns all docs minus those currently being dragged by the user.
      */
     @computed get docsWithDrag() {
         let docs = this.docs.slice();
-        if (this.sortField){
+        if (this.sortField) {
             const field = StrCast(this.layoutDoc.sortField);
             const desc = BoolCast(this.layoutDoc.sortDesc); // is this an ascending or descending sort
             docs = this.sortDocs(field, desc, true);
@@ -1290,13 +1289,17 @@ export class CollectionSchemaView extends CollectionSubView() {
     previewWidthFunc = () => this.previewWidth;
     onPassiveWheel = (e: WheelEvent) => e.stopPropagation();
     displayedDocsFunc = () => this.docsWithDrag.docs;
-    _oldWheel: any;
     render() {
         return (
-            <div className="collectionSchemaView" ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)} 
-            onDrop={this.onExternalDrop.bind(this)} 
-            onPointerMove={e => this.onPointerMove(e)} 
-            onPointerDown={() => {this.closeNewColumnMenu(); this.setColDrag(false)}}>
+            <div
+                className="collectionSchemaView"
+                ref={(ele: HTMLDivElement | null) => this.createDashEventsTarget(ele)}
+                onDrop={this.onExternalDrop.bind(this)}
+                onPointerMove={e => this.onPointerMove(e)}
+                onPointerDown={() => {
+                    this.closeNewColumnMenu();
+                    this.setColDrag(false);
+                }}>
                 <div ref={this._menuTarget} style={{ background: 'red', top: 0, left: 0, position: 'absolute', zIndex: 10000 }} />
                 <div
                     className="schema-table"
@@ -1310,30 +1313,29 @@ export class CollectionSchemaView extends CollectionSubView() {
                     <div className="schema-header-row" style={{ height: this.rowHeightFunc() }}>
                         <div className="row-menu" style={{ width: CollectionSchemaView._rowMenuWidth }}>
                             <IconButton
-                            tooltip="Add a new key"
-                            icon={ <FontAwesomeIcon icon="plus" size='lg'/>}
-                            size={Size.XSMALL}
-                            color={'black'}
-                            onPointerDown={e =>
-                                setupMoveUpEvents(
-                                    this,
-                                    e,
-                                    returnFalse,
-                                    emptyFunction,
-                                    undoable(clickEv => {
-                                        clickEv.stopPropagation();
-                                        this.addColumn()
-                                    }, 'add key to schema')
-                                )
-                            }
+                                tooltip="Add a new key"
+                                icon={<FontAwesomeIcon icon="plus" size="lg" />}
+                                size={Size.XSMALL}
+                                color={'black'}
+                                onPointerDown={e =>
+                                    setupMoveUpEvents(
+                                        this,
+                                        e,
+                                        returnFalse,
+                                        emptyFunction,
+                                        undoable(clickEv => {
+                                            clickEv.stopPropagation();
+                                            this.addColumn();
+                                        }, 'add key to schema')
+                                    )
+                                }
                             />
                         </div>
                         {this.columnKeys.map((key, index) => (
                             <SchemaColumnHeader
-                                // eslint-disable-next-line react/no-array-index-key
                                 //cleanupField={this.cleanupComputedField}
                                 ref={r => r && this._headerRefs.push(r)}
-                                keysDropdown={(this.keysDropdown)}
+                                keysDropdown={this.keysDropdown}
                                 schemaView={this}
                                 columnWidth={() => CollectionSchemaView._minColWidth} //TODO: update
                                 Document={this.Document}
@@ -1445,7 +1447,6 @@ class CollectionSchemaViewDoc extends ObservableReactComponent<CollectionSchemaV
         return (
             <DocumentView
                 key={this._props.doc[Id]}
-                // eslint-disable-next-line react/jsx-props-no-spreading
                 {...this._props.schema._props}
                 containerViewPath={this._props.schema.childContainerViewPath}
                 LayoutTemplate={this._props.schema._props.childLayoutTemplate}
@@ -1498,4 +1499,4 @@ class CollectionSchemaViewDocs extends React.Component<CollectionSchemaViewDocsP
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
index c5cdac8af..47918f9c1 100644
--- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/no-unused-prop-types */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, computed, makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -21,7 +20,8 @@ import { undoable } from '../../../util/UndoManager';
 import { IconButton, Size } from 'browndash-components';
 
 export enum SchemaFieldType {
-    Header, Cell
+    Header,
+    Cell,
 }
 
 export interface SchemaColumnHeaderProps {
@@ -49,7 +49,6 @@ export interface SchemaColumnHeaderProps {
 
 @observer
 export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHeaderProps> {
-
     private _inputRef: EditableView | null = null;
     @observable _altTitle: string | undefined = undefined;
     @observable _showMenuIcon: boolean = false;
@@ -58,18 +57,26 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea
         return this._props.columnKeys[this._props.columnIndex];
     }
 
-    constructor(props: SchemaColumnHeaderProps){
+    constructor(props: SchemaColumnHeaderProps) {
         super(props);
         makeObservable(this);
     }
-    
+
     getFinfo = computedFn((fieldKey: string) => this._props.schemaView?.fieldInfos.get(fieldKey));
-    setColumnValues = (field: string, defaultValue: string) => {this._props.schemaView?.setKey(field, defaultValue, this._props.columnIndex);}
-    @action updateAlt = (newAlt: string) => {this._altTitle = newAlt};
-    updateKeyDropdown = (value: string) => {this._props.schemaView.updateKeySearch(value)};
-    openKeyDropdown = () => {!this._props.schemaView._colBeingDragged && this._props.schemaView.openNewColumnMenu(this._props.columnIndex, false)};
+    setColumnValues = (field: string, defaultValue: string) => {
+        this._props.schemaView?.setKey(field, defaultValue, this._props.columnIndex);
+    };
+    @action updateAlt = (newAlt: string) => {
+        this._altTitle = newAlt;
+    };
+    updateKeyDropdown = (value: string) => {
+        this._props.schemaView.updateKeySearch(value);
+    };
+    openKeyDropdown = () => {
+        !this._props.schemaView._colBeingDragged && this._props.schemaView.openNewColumnMenu(this._props.columnIndex, false);
+    };
     toggleEditing = (editing: boolean) => {
-        this._inputRef?.setIsEditing(editing); 
+        this._inputRef?.setIsEditing(editing);
         this._inputRef?.setIsFocused(editing);
     };
 
@@ -110,139 +117,156 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea
         const cursor = !readOnly ? 'text' : 'default';
         const pointerEvents: 'all' | 'none' = 'all';
         return { color, fieldProps, cursor, pointerEvents };
-    }
+    };
 
     @computed get editableView() {
-        const { color, fieldProps, pointerEvents } = this.renderProps(this._props); 
-
-        return <div className='schema-column-edit-wrapper' onPointerUp={() => {
-            SchemaColumnHeader.isDefaultField(this.fieldKey) && this.openKeyDropdown();
-            this._props.schemaView.deselectAllCells();
-        }}
-        style={{
-            color,
-            width: '100%',
-            pointerEvents,
-        }}>
-            <EditableView
-                ref={r => {this._inputRef = r; this._props.autoFocus && r?.setIsFocused(true)}}
-                oneLine={true}
-                allowCRs={false}
-                contents={''}
-                onClick={this.openKeyDropdown}
-                fieldContents={fieldProps}
-                editing={undefined}  
-                placeholder={'Add key'}
-                updateAlt={this.updateAlt} // alternate title to display
-                updateSearch={this.updateKeyDropdown}
-                schemaFieldType={SchemaFieldType.Header} 
-                GetValue={() => {
-                    if (SchemaColumnHeader.isDefaultField(this.fieldKey)) return '';
-                    else if (this._altTitle) return this._altTitle;
-                    else return this.fieldKey;
+        const { color, fieldProps, pointerEvents } = this.renderProps(this._props);
+
+        return (
+            <div
+                className="schema-column-edit-wrapper"
+                onPointerUp={() => {
+                    SchemaColumnHeader.isDefaultField(this.fieldKey) && this.openKeyDropdown();
+                    this._props.schemaView.deselectAllCells();
                 }}
-                SetValue={undoable((value: string, shiftKey?: boolean, enterKey?: boolean) => {
-                    if (enterKey) { // if shift & enter, set value of each cell in column
-                        this.setColumnValues(value, '');
-                        this._altTitle = undefined;
+                style={{
+                    color,
+                    width: '100%',
+                    pointerEvents,
+                }}>
+                <EditableView
+                    ref={r => {
+                        this._inputRef = r;
+                        this._props.autoFocus && r?.setIsFocused(true);
+                    }}
+                    oneLine={true}
+                    allowCRs={false}
+                    contents={''}
+                    onClick={this.openKeyDropdown}
+                    fieldContents={fieldProps}
+                    editing={undefined}
+                    placeholder={'Add key'}
+                    updateAlt={this.updateAlt} // alternate title to display
+                    updateSearch={this.updateKeyDropdown}
+                    schemaFieldType={SchemaFieldType.Header}
+                    GetValue={() => {
+                        if (SchemaColumnHeader.isDefaultField(this.fieldKey)) return '';
+                        else if (this._altTitle) return this._altTitle;
+                        else return this.fieldKey;
+                    }}
+                    SetValue={undoable((value: string, shiftKey?: boolean, enterKey?: boolean) => {
+                        if (enterKey) {
+                            // if shift & enter, set value of each cell in column
+                            this.setColumnValues(value, '');
+                            this._altTitle = undefined;
+                            this._props.finishEdit?.();
+                            return true;
+                        }
                         this._props.finishEdit?.();
                         return true;
-                    }
-                    this._props.finishEdit?.(); 
-                    return true;
-                }, 'edit column header')}
-            />
+                    }, 'edit column header')}
+                />
             </div>
+        );
     }
 
     public static isDefaultField = (key: string) => {
         const defaultPattern = /EmptyColumnKey/;
-        const isDefault: boolean = (defaultPattern.exec(key) != null);
+        const isDefault: boolean = defaultPattern.exec(key) != null;
         return isDefault;
-    }
+    };
 
-    get headerButton(){
-        const toRender = SchemaColumnHeader.isDefaultField(this.fieldKey) ? 
-        (<IconButton
-            icon={ <FontAwesomeIcon icon="trash" size='sm'/>}
-            size={Size.XSMALL}
-            color={'black'}
-            onPointerDown={e =>
-                setupMoveUpEvents(
-                    this,
-                    e,
-                    returnFalse,
-                    emptyFunction,
-                    undoable(clickEv => {
-                        clickEv.stopPropagation();
-                        this._props.schemaView.removeColumn(this._props.columnIndex);
-                    }, 'open column menu')
-                )
-            }
-        />)
-        : (<IconButton
-            icon={ <FontAwesomeIcon icon="caret-down" size='lg'/>}
-            size={Size.XSMALL}
-            color={'black'}
-            onPointerDown={e =>
-                setupMoveUpEvents(
-                    this,
-                    e,
-                    returnFalse,
-                    emptyFunction,
-                    undoable(clickEv => {
-                        clickEv.stopPropagation();
-                        this._props.openContextMenu(e.clientX, e.clientY, this._props.columnIndex)
-                    }, 'open column menu')
-                )
-            }
-        />)
+    get headerButton() {
+        const toRender = SchemaColumnHeader.isDefaultField(this.fieldKey) ? (
+            <IconButton
+                icon={<FontAwesomeIcon icon="trash" size="sm" />}
+                size={Size.XSMALL}
+                color={'black'}
+                onPointerDown={e =>
+                    setupMoveUpEvents(
+                        this,
+                        e,
+                        returnFalse,
+                        emptyFunction,
+                        undoable(clickEv => {
+                            clickEv.stopPropagation();
+                            this._props.schemaView.removeColumn(this._props.columnIndex);
+                        }, 'open column menu')
+                    )
+                }
+            />
+        ) : (
+            <IconButton
+                icon={<FontAwesomeIcon icon="caret-down" size="lg" />}
+                size={Size.XSMALL}
+                color={'black'}
+                onPointerDown={e =>
+                    setupMoveUpEvents(
+                        this,
+                        e,
+                        returnFalse,
+                        emptyFunction,
+                        undoable(clickEv => {
+                            clickEv.stopPropagation();
+                            this._props.openContextMenu(e.clientX, e.clientY, this._props.columnIndex);
+                        }, 'open column menu')
+                    )
+                }
+            />
+        );
 
         return toRender;
     }
 
-    @action handlePointerEnter = () => this._showMenuIcon = true;
-    @action handlePointerLeave = () => this._showMenuIcon = false;
+    @action handlePointerEnter = () => { this._showMenuIcon = true; } // prettier-ignore
+    @action handlePointerLeave = () => { this._showMenuIcon = false; } // prettier-ignore
 
-    @computed get displayButton() {return this._showMenuIcon;}
+    @computed get displayButton() {
+        return this._showMenuIcon;
+    }
 
     render() {
         return (
-                <div
-                    className="schema-column-header"
-                    style={{
-                        width: this._props.columnWidths[this._props.columnIndex],
-                    }}
-                    onPointerEnter={() => {this.handlePointerEnter()}}
-                    onPointerLeave={() => {this.handlePointerLeave()}}
-                    onPointerDown={e => {
-                        this.setupDrag(e);
-                        setupMoveUpEvents(
-                            this,
-                            e,
-                            () => {return this._inputRef?.setIsEditing(false) ?? false},
-                            emptyFunction,
-                            emptyFunction,
-                            );
-                        }
+            <div
+                className="schema-column-header"
+                style={{
+                    width: this._props.columnWidths[this._props.columnIndex],
+                }}
+                onPointerEnter={() => {
+                    this.handlePointerEnter();
+                }}
+                onPointerLeave={() => {
+                    this.handlePointerLeave();
+                }}
+                onPointerDown={e => {
+                    this.setupDrag(e);
+                    setupMoveUpEvents(
+                        this,
+                        e,
+                        () => {
+                            return this._inputRef?.setIsEditing(false) ?? false;
+                        },
+                        emptyFunction,
+                        emptyFunction
+                    );
+                }}
+                ref={col => {
+                    if (col) {
+                        this._props.setColRef(this._props.columnIndex, col);
                     }
-                    ref={col => {
-                        if (col) {
-                            this._props.setColRef(this._props.columnIndex, col);
-                        }
-                    }}>
-                    <div className="schema-column-resizer left" onPointerDown={e => this._props.resizeColumn(e, this._props.columnIndex, false)} />
+                }}>
+                <div className="schema-column-resizer left" onPointerDown={e => this._props.resizeColumn(e, this._props.columnIndex, false)} />
 
-                    <div className="schema-header-text">{this.editableView}</div>
+                <div className="schema-header-text">{this.editableView}</div>
 
-                    <div className="schema-header-menu">
-                        <div className="schema-header-button" style={{opacity: this.displayButton ? '1.0' : '0.0'}}>
-                            {this.headerButton}
-                        </div>
-                    </div>  
-
-                    <div className="schema-column-resizer right" onPointerDown={e => this._props.resizeColumn(e, this._props.columnIndex, true)} />
+                <div className="schema-header-menu">
+                    <div className="schema-header-button" style={{ opacity: this.displayButton ? '1.0' : '0.0' }}>
+                        {this.headerButton}
+                    </div>
                 </div>
+
+                <div className="schema-column-resizer right" onPointerDown={e => this._props.resizeColumn(e, this._props.columnIndex, true)} />
+            </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
index a8a4ef2c2..6ffb0865a 100644
--- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx
@@ -21,7 +21,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core';
 
 /**
  * The SchemaRowBox renders a doc as a row of cells, with each cell representing
- * one field value of the doc. It mostly handles communication from the SchemaView 
+ * one field value of the doc. It mostly handles communication from the SchemaView
  * to each SchemaCell, passing down necessary functions are props.
  */
 
@@ -59,7 +59,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
         ContextMenu.Instance.clearItems();
         ContextMenu.Instance.addItem({
             description: this.Document._lockedSchemaEditing ? 'Unlock field editing' : 'Lock field editing',
-            event: () => this.Document._lockedSchemaEditing = !this.Document._lockedSchemaEditing,
+            event: () => (this.Document._lockedSchemaEditing = !this.Document._lockedSchemaEditing),
             icon: this.Document._lockedSchemaEditing ? 'lock-open' : 'lock',
         });
         ContextMenu.Instance.addItem({
@@ -78,17 +78,19 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
         //         ContextMenu.Instance.addItem({
         //         description: this.Document._childrenSharedWithSchema ? 'Remove children from schema' : 'Add children to schema',
         //         event: () => {
-        //             this.Document._childrenSharedWithSchema = !this.Document._childrenSharedWithSchema; 
+        //             this.Document._childrenSharedWithSchema = !this.Document._childrenSharedWithSchema;
         //         },
         //         icon: this.Document._childrenSharedWithSchema ? 'minus' : 'plus',
         //     });
         // }
         ContextMenu.Instance.displayMenu(x, y, undefined, false);
-    }
+    };
 
-    @computed get menuBackgroundColor(){
-        if (this.Document._lockedSchemaEditing) {return '#F5F5F5'}   
-        return ''
+    @computed get menuBackgroundColor() {
+        if (this.Document._lockedSchemaEditing) {
+            return '#F5F5F5';
+        }
+        return '';
     }
 
     @computed get menuInfos() {
@@ -98,22 +100,28 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
         return infos;
     }
 
-    isolatedSelection = (doc: Doc) => {return this.schemaView?.selectionOverlap(doc)};
+    isolatedSelection = (doc: Doc) => {
+        return this.schemaView?.selectionOverlap(doc);
+    };
     setCursorIndex = (mouseY: number) => this.schemaView?.setRelCursorIndex(mouseY);
     selectedCol = () => this.schemaView._selectedCol;
     getFinfo = computedFn((fieldKey: string) => this.schemaView?.fieldInfos.get(fieldKey));
     selectCell = (doc: Doc, col: number, shift: boolean, ctrl: boolean) => this.schemaView?.selectCell(doc, col, shift, ctrl);
     deselectCell = () => this.schemaView?.deselectAllCells();
     selectedCells = () => this.schemaView?._selectedDocs;
-    setColumnValues = (field: any, value: any) => this.schemaView?.setCellValues(field, value) ?? false;
+    setColumnValues = (field: string, value: string) => this.schemaView?.setCellValues(field, value) ?? false;
     columnWidth = computedFn((index: number) => () => this.schemaView?.displayColumnWidths[index] ?? CollectionSchemaView._minColWidth);
     computeRowIndex = () => this.schemaView?.rowIndex(this.Document);
     highlightCells = (text: string) => this.schemaView?.highlightCells(text);
-    selectReference = (doc: Doc, col: number) => {this.schemaView.selectReference(doc, col)}
+    selectReference = (doc: Doc, col: number) => {
+        this.schemaView.selectReference(doc, col);
+    };
     eqHighlightFunc = (text: string) => {
         const info = this.schemaView.findCellRefs(text);
         const cells: HTMLDivElement[] = [];
-        info.forEach(info => {cells.push(this.schemaView.getCellElement(info[0], info[1]))})
+        info.forEach(inf => {
+            cells.push(this.schemaView.getCellElement(inf[0], inf[1]));
+        });
         return cells;
     };
     render() {
@@ -121,7 +129,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
             <div
                 className="schema-row"
                 onPointerDown={e => this.setCursorIndex(e.clientY)}
-                style={{ height: this._props.PanelHeight()}}
+                style={{ height: this._props.PanelHeight() }}
                 ref={(row: HTMLDivElement | null) => {
                     row && this.schemaView?.addRowRef?.(this.Document, row);
                     this._ref = row;
@@ -131,11 +139,11 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                     style={{
                         width: CollectionSchemaView._rowMenuWidth,
                         pointerEvents: !this._props.isContentActive() ? 'none' : undefined,
-                        backgroundColor: this.menuBackgroundColor
+                        backgroundColor: this.menuBackgroundColor,
                     }}>
                     <IconButton
                         tooltip="Open actions menu"
-                        icon={ <FontAwesomeIcon icon="caret-right" size='lg'/>}
+                        icon={<FontAwesomeIcon icon="caret-right" size="lg" />}
                         size={Size.XSMALL}
                         color={'black'}
                         onPointerDown={e =>
@@ -146,14 +154,16 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
                                 emptyFunction,
                                 undoable(clickEv => {
                                     clickEv.stopPropagation();
-                                    this.openContextMenu(e.clientX, e.clientY)
+                                    this.openContextMenu(e.clientX, e.clientY);
                                 }, 'open actions menu')
                             )
                         }
                     />
-                <div className="row-menu-infos">
-                    {this.menuInfos.map(icn => <FontAwesomeIcon className="row-infos-icon" icon={icn} size='2xs' />)}
-                </div>
+                    <div className="row-menu-infos">
+                        {this.menuInfos.map(icn => (
+                            <FontAwesomeIcon key={icn.toString()} className="row-infos-icon" icon={icn} size="2xs" />
+                        ))}
+                    </div>
                 </div>
                 <div className="row-cells">
                     {this.schemaView?.columnKeys?.map((key, index) => (
@@ -192,4 +202,4 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() {
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
index c05382ce0..f036ff843 100644
--- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
+++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 /* eslint-disable no-use-before-define */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Popup, Size, Type } from 'browndash-components';
@@ -37,7 +36,7 @@ import { SchemaCellField } from './SchemaCellField';
 /**
  * SchemaTableCells make up the majority of the visual representation of the SchemaView.
  * They are rendered for each cell in the SchemaView, and each represents one field value
- * of a doc. Editing the content of the cell changes the corresponding doc's field value. 
+ * of a doc. Editing the content of the cell changes the corresponding doc's field value.
  */
 
 export interface SchemaTableCellProps {
@@ -67,21 +66,16 @@ export interface SchemaTableCellProps {
     isolatedSelection: (doc: Doc) => [boolean, boolean];
     highlightCells: (text: string) => void;
     eqHighlightFunc: (text: string) => HTMLDivElement[] | [];
-    refSelectModeInfo: {enabled: boolean, currEditing: SchemaCellField | undefined};
+    refSelectModeInfo: { enabled: boolean; currEditing: SchemaCellField | undefined };
     selectReference: (doc: Doc, col: number) => void;
 }
 
 function selectedCell(props: SchemaTableCellProps) {
-    return (
-        props.isRowActive() &&
-        props.selectedCol() === props.col && 
-        props.selectedCells()?.filter(d => d === props.Document)?.length
-    );
+    return props.isRowActive() && props.selectedCol() === props.col && props.selectedCells()?.filter(d => d === props.Document)?.length;
 }
 
 @observer
 export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellProps> {
-
     // private _fieldRef: SchemaCellField | null = null;
     private _submittedValue: string = '';
 
@@ -96,9 +90,11 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
 
     get lockedInteraction(){return (this.isDefault || this._props.Document._lockedSchemaEditing);} // prettier-ignore
 
-    get backgroundColor(){
-        if (this.lockedInteraction) {return '#F5F5F5'}   
-        return ''
+    get backgroundColor() {
+        if (this.lockedInteraction) {
+            return '#F5F5F5';
+        }
+        return '';
     }
 
     static addFieldDoc = (docs: Doc | Doc[] /* , where: OpenWhere */) => {
@@ -110,7 +106,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         let protoCount = 0;
         let doc: Doc | undefined = Document;
         while (doc) {
-            if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) break; 
+            if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) break;
             protoCount++;
             doc = DocCast(doc.proto);
         }
@@ -149,28 +145,40 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
     adjustSelfReference = (field: string) => {
         const modField = field.replace(/\bthis.\b/g, `d${this.docIndex}.`);
         return modField;
-    }
+    };
 
     // parses a field from the "idToDoc(####)" format to DocumentId (d#) format for readability
     cleanupField = (field: string) => {
         let modField = field.slice();
         let eqSymbol: string = '';
-        if (modField.startsWith('=')) {modField = modField.substring(1); eqSymbol += '=';}
-        if (modField.startsWith(':=')) {modField = modField.substring(2); eqSymbol += ':=';}
+        if (modField.startsWith('=')) {
+            modField = modField.substring(1);
+            eqSymbol += '=';
+        }
+        if (modField.startsWith(':=')) {
+            modField = modField.substring(2);
+            eqSymbol += ':=';
+        }
 
         const idPattern = /idToDoc\((.*?)\)/g;
         let matches;
         const results = new Array<[id: string, func: string]>();
-        while ((matches = idPattern.exec(field)) !== null) {results.push([matches[0], matches[1].replace(/"/g, '')]); }
-        results.forEach((idFuncPair) => {modField = modField.replace(idFuncPair[0], 'd' + (DocumentView.getDocViewIndex(IdToDoc(idFuncPair[1]))).toString());})
+        while ((matches = idPattern.exec(field)) !== null) {
+            results.push([matches[0], matches[1].replace(/"/g, '')]);
+        }
+        results.forEach(idFuncPair => {
+            modField = modField.replace(idFuncPair[0], 'd' + DocumentView.getDocViewIndex(IdToDoc(idFuncPair[1])).toString());
+        });
 
         if (modField.endsWith(';')) modField = modField.substring(0, modField.length - 1);
 
-        const inQuotes = (field: string) => {return ((field.startsWith('`') && field.endsWith('`')) || (field.startsWith("'") && field.endsWith("'")) || (field.startsWith('"') && field.endsWith('"')))}
+        const inQuotes = (strField: string) => {
+            return (strField.startsWith('`') && strField.endsWith('`')) || (strField.startsWith("'") && strField.endsWith("'")) || (strField.startsWith('"') && strField.endsWith('"'));
+        };
         if (!inQuotes(this._submittedValue) && inQuotes(modField)) modField = modField.substring(1, modField.length - 1);
 
         return eqSymbol + modField;
-    }
+    };
 
     @computed get defaultCellContent() {
         const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props);
@@ -203,7 +211,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                             this._props.finishEdit?.();
                             return true;
                         }
-                        const hasNoLayout =  Doc.IsDataProto(fieldProps.Document) ? true : undefined; // the "delegate" is a a data document so never write to it's proto
+                        const hasNoLayout = Doc.IsDataProto(fieldProps.Document) ? true : undefined; // the "delegate" is a a data document so never write to it's proto
                         const ret = Doc.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, hasNoLayout);
                         this._submittedValue = value;
                         this._props.finishEdit?.();
@@ -245,8 +253,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
         const sides: Array<string | undefined> = [];
         sides[0] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // left
         sides[1] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // right
-        sides[2] = (!this._props.isolatedSelection(this._props.Document)[0] && selectedCell(this._props)) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // top
-        sides[3] = (!this._props.isolatedSelection(this._props.Document)[1] && selectedCell(this._props)) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // bottom
+        sides[2] = !this._props.isolatedSelection(this._props.Document)[0] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // top
+        sides[3] = !this._props.isolatedSelection(this._props.Document)[1] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // bottom
         return sides;
     }
 
@@ -256,9 +264,13 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                 className="schema-table-cell"
                 onContextMenu={e => StopEvent(e)}
                 onPointerDown={action(e => {
-                    if (this.lockedInteraction) { e.stopPropagation(); e.preventDefault(); return; }
+                    if (this.lockedInteraction) {
+                        e.stopPropagation();
+                        e.preventDefault();
+                        return;
+                    }
 
-                    if (this._props.refSelectModeInfo.enabled && !selectedCell(this._props)){
+                    if (this._props.refSelectModeInfo.enabled && !selectedCell(this._props)) {
                         e.stopPropagation();
                         e.preventDefault();
                         this._props.selectReference(this._props.Document, this._props.col);
@@ -274,14 +286,16 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro
                         } else !selectedCell(this._props) && this._props.selectCell(this._props.Document, this._props.col, shift, ctrl);
                     }
                 })}
-                style={{ padding: this._props.padding, 
-                maxWidth: this._props.maxWidth?.(), 
-                width: this._props.columnWidth() || undefined, 
-                borderLeft: this.borderColor[0],
-                borderRight: this.borderColor[1],
-                borderTop: this.borderColor[2],
-                borderBottom: this.borderColor[3],
-                backgroundColor: this.backgroundColor}}>
+                style={{
+                    padding: this._props.padding,
+                    maxWidth: this._props.maxWidth?.(),
+                    width: this._props.columnWidth() || undefined,
+                    borderLeft: this.borderColor[0],
+                    borderRight: this.borderColor[1],
+                    borderTop: this.borderColor[2],
+                    borderBottom: this.borderColor[3],
+                    backgroundColor: this.backgroundColor,
+                }}>
                 {this.isDefault ? '' : this.content}
             </div>
         );
@@ -524,4 +538,4 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC
             </div>
         );
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 962a21dbb..6c3f4eaff 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -84,7 +84,6 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             } else {
                 const dataKey = Doc.LayoutFieldKey(dv.Document);
                 const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', '');
-                console.log('color: ' + dv.dataDoc[fieldKey + alternate] + ' to set to: ' + color)
                 dv.layoutDoc[fieldKey + alternate] = undefined;
                 dv.dataDoc[fieldKey + alternate] = color;
             }
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index 12196f290..a44014444 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -1,4 +1,3 @@
-/* eslint-disable react/jsx-props-no-spreading */
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Checkbox } from '@mui/material';
 import { Colors, Toggle, ToggleType, Type } from 'browndash-components';
@@ -8,42 +7,36 @@ import * as React from 'react';
 import { ClientUtils, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../ClientUtils';
 import { emptyFunction } from '../../../../Utils';
 import { Doc, DocListCast, Field, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
+import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
 import { InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
+import { PrefetchProxy } from '../../../../fields/Proxy';
 import { Cast, CsvCast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
 import { CsvField } from '../../../../fields/URLField';
-import { GetEffectiveAcl, TraceMobx, inheritParentAcls } from '../../../../fields/util';
+import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util';
+import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
 import { DocUtils } from '../../../documents/DocUtils';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
+import { LinkManager } from '../../../util/LinkManager';
 import { UndoManager, undoable } from '../../../util/UndoManager';
 import { ContextMenu } from '../../ContextMenu';
 import { ViewBoxAnnotatableComponent } from '../../DocComponent';
 import { MarqueeAnnotator } from '../../MarqueeAnnotator';
 import { PinProps } from '../../PinFuncs';
 import { SidebarAnnos } from '../../SidebarAnnos';
+import { CollectionFreeFormView } from '../../collections/collectionFreeForm';
 import { AnchorMenu } from '../../pdf/AnchorMenu';
 import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { FocusViewOptions } from '../FocusViewOptions';
 import './DataVizBox.scss';
+import { Col, DataVizTemplateInfo, DocCreatorMenu, LayoutType, TemplateFieldSize, TemplateFieldType } from './DocCreatorMenu';
 import { Histogram } from './components/Histogram';
 import { LineChart } from './components/LineChart';
 import { PieChart } from './components/PieChart';
 import { TableBox } from './components/TableBox';
-import { LinkManager } from '../../../util/LinkManager';
-import { Col, DataVizTemplateInfo, DataVizTemplateLayout, DocCreatorMenu, TemplateFieldSize, LayoutType, TemplateFieldType } from './DocCreatorMenu';
-import { CollectionFreeFormView, MarqueeView } from '../../collections/collectionFreeForm';
-import { PrefetchProxy } from '../../../../fields/Proxy';
-import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols';
-import { template } from 'lodash';
-import { data } from 'jquery';
-import { listSpec } from '../../../../fields/Schema';
-import { ObjectField } from '../../../../fields/ObjectField';
-import { Id } from '../../../../fields/FieldSymbols';
-import { GPTCallType, gptAPICall } from '../../../apis/gpt/GPT';
-import { TbSortDescendingShapes } from 'react-icons/tb';
 
 export enum DataVizView {
     TABLE = 'table',
@@ -65,7 +58,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     @observable _marqueeing: number[] | undefined = undefined;
     @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
     @observable _specialHighlightedRow: number | undefined = undefined;
-    @observable GPTSummary: ObservableMap<string, {desc?: string, type?: TemplateFieldType, size?: TemplateFieldSize}> | undefined = undefined;
+    @observable GPTSummary: ObservableMap<string, { desc?: string; type?: TemplateFieldType; size?: TemplateFieldSize }> | undefined = undefined;
     @observable colsInfo: ObservableMap<string, Col> = new ObservableMap();
     @observable _GPTLoading: boolean = false;
 
@@ -121,12 +114,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             const records = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
             this._urlError = false;
             return records?.filter(record => Object.keys(record).some(key => record[key])) ?? [];
-        } catch (e){
+        } catch {
             this._urlError = true;
-            const data: { [key: string]: string; }[] = [
-                { error: "Data not found"},
-              ];          
-            return data;
+            return [{ error: 'Data not found' }] as { [key: string]: string }[];
         }
     }
 
@@ -153,70 +143,73 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     @action setSpecialHighlightedRow = (row: number | undefined) => {
         this._specialHighlightedRow = row;
-    }
+    };
 
     @action setColumnType = (colTitle: string, type: TemplateFieldType) => {
         const colInfo = this.colsInfo.get(colTitle);
-        if (colInfo) { 
+        if (colInfo) {
             colInfo.type = type;
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: type, sizes: [TemplateFieldSize.MEDIUM]})
+            this.colsInfo.set(colTitle, { title: colTitle, desc: '', type: type, sizes: [TemplateFieldSize.MEDIUM] });
         }
-    }
+    };
 
     @action modifyColumnSizes = (colTitle: string, size: TemplateFieldSize, valid: boolean) => {
         const column = this.colsInfo.get(colTitle);
-        if (column) { 
+        if (column) {
             if (!valid && column.sizes.includes(size)) {
                 column.sizes.splice(column.sizes.indexOf(size), 1);
             } else if (valid && !column.sizes.includes(size)) {
                 column.sizes.push(size);
             }
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [size]})
+            this.colsInfo.set(colTitle, { title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [size] });
         }
-    }
+    };
 
     @action setColumnTitle = (colTitle: string, newTitle: string) => {
         const colInfo = this.colsInfo.get(colTitle);
-        if (colInfo) { 
+        if (colInfo) {
             colInfo.title = newTitle;
-            console.log(colInfo.title)
+            console.log(colInfo.title);
         } else {
-            this.colsInfo.set(colTitle, {title: newTitle, desc: '', type: TemplateFieldType.UNSET, sizes: []})
+            this.colsInfo.set(colTitle, { title: newTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [] });
         }
-    }
+    };
 
     @action setColumnDesc = (colTitle: string, desc: string) => {
         const colInfo = this.colsInfo.get(colTitle);
         if (colInfo) {
-            if (!desc) { colInfo.desc = this.GPTSummary?.get(colTitle)?.desc ?? ''; } 
-            else { colInfo.desc = desc; }
+            if (!desc) {
+                colInfo.desc = this.GPTSummary?.get(colTitle)?.desc ?? '';
+            } else {
+                colInfo.desc = desc;
+            }
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: desc, type: TemplateFieldType.UNSET, sizes: []})
+            this.colsInfo.set(colTitle, { title: colTitle, desc: desc, type: TemplateFieldType.UNSET, sizes: [] });
         }
-    }
+    };
 
     @action setColumnDefault = (colTitle: string, cont: string) => {
         const colInfo = this.colsInfo.get(colTitle);
         if (colInfo) {
             colInfo.defaultContent = cont;
         } else {
-            this.colsInfo.set(colTitle, {title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [], defaultContent: cont})
+            this.colsInfo.set(colTitle, { title: colTitle, desc: '', type: TemplateFieldType.UNSET, sizes: [], defaultContent: cont });
         }
-    }
+    };
 
     @action // pinned / linked anchor doc includes selected rows, graph titles, and graph colors
-    restoreView = (data: Doc) => {
+    restoreView = (viewData: Doc) => {
         // const changedView = data.config_dataViz && this.dataVizView !== data.config_dataViz && (this.layoutDoc._dataViz = data.config_dataViz);
         // const changedAxes = data.config_dataVizAxes && this.axes.join('') !== StrListCast(data.config_dataVizAxes).join('') && (this.layoutDoc._dataViz_axes = new List<string>(StrListCast(data.config_dataVizAxes)));
-        this.layoutDoc.dataViz_selectedRows = Field.Copy(data.dataViz_selectedRows);
-        this.layoutDoc.dataViz_histogram_barColors = Field.Copy(data.dataViz_histogram_barColors);
-        this.layoutDoc.dataViz_histogram_defaultColor = data.dataViz_histogram_defaultColor;
-        this.layoutDoc.dataViz_pie_sliceColors = Field.Copy(data.dataViz_pie_sliceColors);
+        this.layoutDoc.dataViz_selectedRows = Field.Copy(viewData.dataViz_selectedRows);
+        this.layoutDoc.dataViz_histogram_barColors = Field.Copy(viewData.dataViz_histogram_barColors);
+        this.layoutDoc.dataViz_histogram_defaultColor = viewData.dataViz_histogram_defaultColor;
+        this.layoutDoc.dataViz_pie_sliceColors = Field.Copy(viewData.dataViz_pie_sliceColors);
         Object.keys(this.layoutDoc).forEach(key => {
             if (key.startsWith('dataViz_histogram_title') || key.startsWith('dataViz_lineChart_title') || key.startsWith('dataViz_pieChart_title')) {
-                this.layoutDoc['_' + key] = data[key];
+                this.layoutDoc['_' + key] = viewData[key];
             }
         });
         return true;
@@ -354,7 +347,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
     componentDidMount() {
         this._props.setContentViewBox?.(this);
-        if (!this._urlError) { if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData() };
+        if (!this._urlError) {
+            if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData();
+        }
         this._disposers.datavis = reaction(
             () => {
                 if (this.layoutDoc.dataViz_schemaLive === undefined) this.layoutDoc.dataViz_schemaLive = true;
@@ -516,14 +511,14 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         DocCreatorMenu.Instance.toggleDisplay(x, y);
         DocCreatorMenu.Instance.setDataViz(this);
         DocCreatorMenu.Instance.setTemplateDocs(this.getPossibleTemplates());
-    }
+    };
 
-    specificContextMenu = (x: number, y: number): void => {
+    specificContextMenu = (e: MouseEventHandler) => {
         const cm = ContextMenu.Instance;
         const options = cm.findByDescription('Options...');
         const optionItems = options?.subitems ?? [];
         optionItems.push({ description: `Analyze with AI`, event: () => this.askGPT(), icon: 'lightbulb' });
-        optionItems.push({ description: `Create documents`, event: () => this.openDocCreatorMenu(x, y), icon: 'table-cells' });
+        optionItems.push({ description: `Create documents`, event: () => this.openDocCreatorMenu(e.pageX, e.pageY), icon: 'table-cells' });
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
     };
 
@@ -533,19 +528,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         GPTPopup.Instance.createFilteredDoc = this.createFilteredDoc;
         GPTPopup.Instance.setDataJson('');
         GPTPopup.Instance.setMode(GPTPopupMode.DATA);
-        const data = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
-        GPTPopup.Instance.setDataJson(JSON.stringify(data));
+        const csvdata = DataVizBox.dataset.get(CsvCast(this.dataDoc[this.fieldKey]).url.href);
+        GPTPopup.Instance.setDataJson(JSON.stringify(csvdata));
         GPTPopup.Instance.generateDataAnalysis();
     });
 
     getColSummary = (): string => {
-        let possibleIds: number[] = this.records.map((_, index) => index);
+        const possibleIds: number[] = this.records.map((_, index) => index);
 
         for (let i = possibleIds.length - 1; i > 0; i--) {
             const j = Math.floor(Math.random() * (i + 1));
             [possibleIds[i], possibleIds[j]] = [possibleIds[j], possibleIds[i]];
         }
-    
+
         const rowsToCheck = possibleIds.slice(0, Math.min(10, this.records.length));
 
         let prompt: string = 'Col titles: ';
@@ -553,37 +548,37 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
 
         cols.forEach((col, i) => {
-            prompt += `Col #${i}: ${col} ------`
-        })
+            prompt += `Col #${i}: ${col} ------`;
+        });
 
-        prompt += '----------- Rows: '
+        prompt += '----------- Rows: ';
 
-        rowsToCheck.forEach((row, i) => {
-            prompt += `Row #${row}: `
+        rowsToCheck.forEach(row => {
+            prompt += `Row #${row}: `;
             cols.forEach(col => {
-                prompt += `${col}: ${this.records[row][col]} -----`
-            })
-        })
+                prompt += `${col}: ${this.records[row][col]} -----`;
+            });
+        });
 
         return prompt;
-    }
+    };
 
     updateColDefaults = () => {
-        let possibleIds: number[] = this.records.map((_, index) => index);
+        const possibleIds: number[] = this.records.map((_, index) => index);
 
         for (let i = possibleIds.length - 1; i > 0; i--) {
             const j = Math.floor(Math.random() * (i + 1));
             [possibleIds[i], possibleIds[j]] = [possibleIds[j], possibleIds[i]];
         }
-    
+
         const rowToCheck = possibleIds[0];
 
         const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
 
-        cols.forEach(col => { 
-            this.setColumnDefault(col, `${this.records[rowToCheck][col]}`) 
+        cols.forEach(col => {
+            this.setColumnDefault(col, `${this.records[rowToCheck][col]}`);
         });
-    }
+    };
 
     updateGPTSummary = async () => {
         this._GPTLoading = true;
@@ -594,15 +589,12 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
 
         const cols = Array.from(Object.keys(this.records[0])).filter(header => header !== '' && header !== undefined);
         cols.forEach(col => {
-            if (!this.colsInfo.get(col)) this.colsInfo.set(col, {title: col, desc: '', sizes: [], type: TemplateFieldType.UNSET});
+            if (!this.colsInfo.get(col)) this.colsInfo.set(col, { title: col, desc: '', sizes: [], type: TemplateFieldType.UNSET });
         });
 
         try {
-            const [res1, res2] = await Promise.all([
-                gptAPICall(prompt, GPTCallType.VIZSUM),
-                gptAPICall('Info:' + prompt, GPTCallType.VIZSUM2)
-            ]);
-        
+            const [res1, res2] = await Promise.all([gptAPICall(prompt, GPTCallType.VIZSUM), gptAPICall('Info:' + prompt, GPTCallType.VIZSUM2)]);
+
             if (res1) {
                 this.GPTSummary = new ObservableMap();
                 const descs: { [col: string]: string } = JSON.parse(res1);
@@ -611,10 +603,10 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     if (!this.colsInfo.get(key)?.desc) this.setColumnDesc(key, val);
                 }
             }
-        
+
             if (res2) {
                 !this.GPTSummary && (this.GPTSummary = new ObservableMap());
-                const info: { [col: string]: { type: TemplateFieldType, size: TemplateFieldSize } } = JSON.parse(res2);
+                const info: { [col: string]: { type: TemplateFieldType; size: TemplateFieldSize } } = JSON.parse(res2);
                 for (const [key, val] of Object.entries(info)) {
                     const colSummary = this.GPTSummary.get(key);
                     if (colSummary) {
@@ -628,30 +620,29 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         } catch (err) {
             console.error(err);
         }
-        
-    }
+    };
 
     getPossibleTemplates = (): Doc[] => {
         const linkedDocs: Doc[] = LinkManager.Instance.getAllRelatedLinks(this.Document).map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document)));
         const linkedCollections: Doc[] = linkedDocs.filter(doc => doc.type === 'config').map(doc => DocCast(doc.annotationOn));
         const isColumnTitle = (title: string): boolean => {
             const colTitles: string[] = Object.keys(this.records[0]);
-            for (let i = 0; i < colTitles.length; ++i){
+            for (let i = 0; i < colTitles.length; ++i) {
                 if (colTitles[i] === title) {
                     return true;
                 }
             }
             return false;
-        }
+        };
         const isValidTemplate = (collection: Doc) => {
             const childDocs = DocListCast(collection[Doc.LayoutFieldKey(collection)]);
-            for (let i = 0; i < childDocs.length; ++i){
+            for (let i = 0; i < childDocs.length; ++i) {
                 if (isColumnTitle(String(childDocs[i].title))) return true;
             }
             return false;
-        }
+        };
         return linkedCollections.filter(col => isValidTemplate(col));
-    }
+    };
 
     ApplyTemplateTo = (templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) => {
         if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) {
@@ -664,14 +655,14 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             }
         }
         return target;
-    }
+    };
 
     applyLayout = (templateInfo: DataVizTemplateInfo, docs: Doc[]) => {
         if (templateInfo.layout.type === LayoutType.Stacked) return;
         const columns: number = templateInfo.columns;
         const xGap: number = templateInfo.layout.xMargin;
         const yGap: number = templateInfo.layout.yMargin;
-        const repeat: number = templateInfo.layout.repeat;
+        // const repeat: number = templateInfo.layout.repeat;
         const startX: number = templateInfo.referencePos.x;
         const startY: number = templateInfo.referencePos.y;
         const templWidth = Number(templateInfo.doc._width);
@@ -682,8 +673,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         let curX: number = startX;
         let curY: number = startY;
 
-        while (docsChanged < docs.length){
-            while (i < columns && docsChanged < docs.length){ 
+        while (docsChanged < docs.length) {
+            while (i < columns && docsChanged < docs.length) {
                 docs[docsChanged].x = curX;
                 docs[docsChanged].y = curY;
                 curX += templWidth + xGap;
@@ -695,27 +686,29 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
             curX = startX;
             curY += templHeight + yGap;
         }
-    }
+    };
 
     // @action addSavedLayout = (layout: DataVizTemplateLayout) => {
     //     const saved = Cast(this.layoutDoc.dataViz_savedTemplates, listSpec('RefField'));
-        
+
     // }
 
     @action
     createDocsFromTemplate = (templateInfo: DataVizTemplateInfo) => {
         if (!templateInfo.doc) return;
         const mainCollection = this.DocumentView?.().containerViewPath?.().lastElement()?.ComponentView as CollectionFreeFormView;
-        const fields: string[] = Array.from(Object.keys(this.records[0])); 
+        const fields: string[] = Array.from(Object.keys(this.records[0]));
         const selectedRows = NumListCast(this.layoutDoc.dataViz_selectedRows);
         const docs: Doc[] = selectedRows.map(row => {
-            const values: String[] = [];
+            const values: string[] = [];
             fields.forEach(col => values.push(this.records[row][col]));
 
             const proto = new Doc();
             proto.author = ClientUtils.CurrentUserEmail();
-            values.forEach((val, i) => {proto[fields[i]] = val as FieldType});
-            
+            values.forEach((val, i) => {
+                proto[fields[i]] = val as FieldType;
+            });
+
             const target = Doc.MakeDelegate(proto);
             const targetKey = StrCast(templateInfo.doc!.layout_fieldKey, 'layout');
             const applied = this.ApplyTemplateTo(templateInfo.doc!, target, targetKey, templateInfo.doc!.title + `${row}`);
@@ -728,7 +721,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         docs.forEach(doc => mainCollection.addDocument(doc));
 
         this.applyLayout(templateInfo, docs);
-    }
+    };
 
     /**
      * creates a new dataviz document filter from this one
@@ -783,7 +776,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                     transform: `scale(${scale})`,
                     position: 'absolute',
                 }}
-                onContextMenu={(e) => this.specificContextMenu(e.pageX, e.pageY)}
+                onContextMenu={this.specificContextMenu}
                 onWheel={e => e.stopPropagation()}
                 ref={this._mainCont}>
                 <div className="datatype-button">
diff --git a/src/client/views/nodes/DocumentIcon.tsx b/src/client/views/nodes/DocumentIcon.tsx
index 0b94ae4f7..9769ecb3d 100644
--- a/src/client/views/nodes/DocumentIcon.tsx
+++ b/src/client/views/nodes/DocumentIcon.tsx
@@ -1,5 +1,5 @@
 import { Tooltip } from '@mui/material';
-import { action, makeObservable, observable } from 'mobx';
+import { makeObservable, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { factory } from 'typescript';
@@ -18,14 +18,14 @@ interface DocumentIconProps {
 @observer
 export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
     @observable _hovered = false;
-    constructor(props: any) {
+    constructor(props: DocumentIconProps) {
         super(props);
         makeObservable(this);
     }
 
     render() {
         const { view } = this._props;
-        const { left, top, right, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
+        const { left, top, bottom } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 };
 
         return (
             <div
@@ -34,7 +34,7 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
                     pointerEvents: 'all',
                     position: 'absolute',
                     background: SnappingManager.userBackgroundColor,
-                    transform: `translate(${left}px, ${bottom - (bottom - top)/2}px)`, //**!** 
+                    transform: `translate(${left}px, ${bottom - (bottom - top) / 2}px)`, //**!**
                 }}>
                 <Tooltip title={<div>{StrCast(this._props.view.Document?.title)}</div>}>
                     <p>d{this._props.index}</p>
@@ -44,7 +44,7 @@ export class DocumentIcon extends ObservableReactComponent<DocumentIconProps> {
     }
 }
 
-@observer   
+@observer
 export class DocumentIconContainer extends React.Component {
     public static getTransformer(): Transformer {
         const usedDocuments = new Set<number>();
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ba85bfb68..741d63909 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -138,7 +138,7 @@ export class FieldView extends React.Component<FieldViewProps> {
         const field = this.fieldval;
         // prettier-ignore
         if (field instanceof Doc)  return <p> <b>{field.title?.toString()}</b></p>;
-        if (field === undefined) return <p>{''}</p>;
+        if (field === undefined) return <p />;
         if (field instanceof DateField) return <p>{field.date.toLocaleString()}</p>;
         if (field instanceof List) return <div> {field.map(f => Field.toString(f)).join(', ')} </div>;
         if (field instanceof WebField) return <p>{Field.toString(field.url.href)}</p>;
diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx
index cfcf76b12..8974cccaf 100644
--- a/src/client/views/nodes/LabelBox.tsx
+++ b/src/client/views/nodes/LabelBox.tsx
@@ -2,7 +2,7 @@ import { Property } from 'csstype';
 import { action, computed, makeObservable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-//import * as textfit from 'textfit';
+import * as textfit from 'textfit';
 import { Field, FieldType } from '../../../fields/Doc';
 import { BoolCast, NumCast, StrCast } from '../../../fields/Types';
 import { TraceMobx } from '../../../fields/util';
@@ -96,7 +96,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 this._timeout = setTimeout(() => this.fitTextToBox(r));
                 return textfitParams;
             }
-            //textfit(r, textfitParams);
+            textfit(r, textfitParams);
         }
         return textfitParams;
     };
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c1367aa0b..0d7914a82 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1498,7 +1498,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
             pdfAnchorId && this.addPdfReference(pdfAnchorId);
         }
         if (this._props.autoFocus) setTimeout(() => this._editorView!.focus()); // not sure why setTimeout is needed but editing dashFieldView's doesn't work without it.
-
     }
 
     // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet.
@@ -2087,7 +2086,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB
                 </div>
             </div>
         );
-    
     }
 }
 
-- 
cgit v1.2.3-70-g09d2


From 1b038112b37c02d81431bc7ff622b25bb42a0858 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 4 Oct 2024 22:02:23 -0400
Subject: from last

---
 src/client/documents/DocUtils.ts                 | 2 --
 src/client/util/DocumentManager.ts               | 8 +-------
 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 2 +-
 3 files changed, 2 insertions(+), 10 deletions(-)

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

diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts
index 0699ea09f..1130a9ae8 100644
--- a/src/client/documents/DocUtils.ts
+++ b/src/client/documents/DocUtils.ts
@@ -1,5 +1,3 @@
-/* eslint-disable prefer-destructuring */
-/* eslint-disable default-param-last */
 /* eslint-disable no-use-before-define */
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { saveAs } from 'file-saver';
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 966731656..4ab2e8d05 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -142,13 +142,7 @@ export class DocumentManager {
     }
 
     public getDocViewIndex(target: Doc): number {
-        const docViewArray = DocumentManager.Instance.DocumentViews;
-        for (let i = 0; i < docViewArray.length; ++i){
-            if (docViewArray[i].Document == target){
-                return i;
-            }
-        }
-        return -1;
+        return DocumentManager.Instance.DocumentViews.findIndex(dv => dv.Document === target);
     }
 
     public getLightboxDocumentView = (toFind: Doc): DocumentView | undefined => {
diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
index a44014444..896048ab3 100644
--- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx
+++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx
@@ -513,7 +513,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         DocCreatorMenu.Instance.setTemplateDocs(this.getPossibleTemplates());
     };
 
-    specificContextMenu = (e: MouseEventHandler) => {
+    specificContextMenu = (e: React.MouseEvent) => {
         const cm = ContextMenu.Instance;
         const options = cm.findByDescription('Options...');
         const optionItems = options?.subitems ?? [];
-- 
cgit v1.2.3-70-g09d2

