From 932ecd6092bd1b0ac3391309a550bac76cfb0e04 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 29 Jun 2023 13:56:41 -0400
Subject: undo and redo

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

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0dc186eaf..3fa5c099b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -882,6 +882,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     animateRes = (resIndex: number) => {
+        console.log(this.gptRes);
         if (resIndex < this.gptRes.length) {
             this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex];
             setTimeout(() => {
-- 
cgit v1.2.3-70-g09d2


From 162ca319eae256be523f2ee75e7aae7a9a408e37 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Tue, 18 Jul 2023 12:06:21 -0400
Subject: .

---
 src/client/views/nodes/formattedText/FormattedTextBox.tsx | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 3fa5c099b..307e52cff 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -882,7 +882,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     animateRes = (resIndex: number) => {
-        console.log(this.gptRes);
+        console.log(this.dataDoc.text);
         if (resIndex < this.gptRes.length) {
             this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex];
             setTimeout(() => {
@@ -891,6 +891,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     };
 
+    // breakupdictiation for reference
+    // prepareForTyping
+    // stored marks - userMark and timestamp, setStoredMarks
+    //the._editorView.dispatch(state.tr.setSelection(updatted).insertText('\n, to))
+
     askGPT = action(async () => {
         try {
             let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
-- 
cgit v1.2.3-70-g09d2


From 078d595cb498592667a653a937b8ba25bcbf41bb Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 20 Jul 2023 00:54:07 -0400
Subject: refactoring gpt functionalities

---
 src/client/views/MainView.tsx                      |   2 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |   2 +-
 src/client/views/pdf/AnchorMenu.tsx                | 157 ++++++---------------
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |  12 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         | 102 +++++++++----
 src/client/views/pdf/PDFViewer.tsx                 |   5 +
 6 files changed, 130 insertions(+), 150 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a129c35cf..f6829a4cb 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -67,6 +67,7 @@ import { PropertiesView } from './PropertiesView';
 import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
 import GenerativeFill from './nodes/generativeFill/GenerativeFill';
+import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -1032,6 +1033,7 @@ export class MainView extends React.Component {
                 <InkTranscription />
                 {this.snapLines}
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+                <GPTPopup key="gptpopup" />
                 <GenerativeFill imageEditorOpen={this.imageEditorOpen} imageEditorSource={this.imageEditorSource} imageRootDoc={this.imageRootDoc} addDoc={this.addDoc} />
                 {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
             </div>
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 20a176c58..212d24165 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -909,7 +909,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     // breakupdictiation for reference
     // prepareForTyping
     // stored marks - userMark and timestamp, setStoredMarks
-    //the._editorView.dispatch(state.tr.setSelection(updatted).insertText('\n, to))
+    // the._editorView.dispatch(state.tr.setSelection(updated).insertText('\n, to))
 
     askGPT = action(async () => {
         try {
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 07b2afd91..e6b9cb382 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -48,9 +48,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable public Status: 'marquee' | 'annotation' | '' = '';
 
     // GPT additions
-    @observable private GPTpopupText: string = '';
-    @observable private loadingGPT: boolean = false;
-    @observable private showGPTPopup: boolean = false;
     @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY;
     @observable private selectedText: string = '';
     @observable private editorView?: EditorView;
@@ -58,25 +55,11 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     @observable private highlightRange: number[] | undefined;
     private selectionRange: number[] | undefined;
 
-    @action
-    setGPTPopupVis = (vis: boolean) => {
-        this.showGPTPopup = vis;
-    };
     @action
     setGPTMode = (mode: GPTPopupMode) => {
         this.GPTMode = mode;
     };
 
-    @action
-    setGPTPopupText = (txt: string) => {
-        this.GPTpopupText = txt;
-    };
-
-    @action
-    setLoading = (loading: boolean) => {
-        this.loadingGPT = loading;
-    };
-
     @action
     setHighlightRange(r: number[] | undefined) {
         this.highlightRange = r;
@@ -130,19 +113,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     componentDidMount() {
         this._disposer2 = reaction(
             () => this._opacity,
-            opacity => {
-                if (!opacity) {
-                    this.setGPTPopupVis(false);
-                    this.setGPTPopupText('');
-                }
-            },
+            opacity => {},
             { fireImmediately: true }
         );
         this._disposer = reaction(
             () => SelectionManager.Views().slice(),
             selected => {
-                this.setGPTPopupVis(false);
-                this.setGPTPopupText('');
                 AnchorMenu.Instance.fadeOut(true);
             }
         );
@@ -153,23 +129,22 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * @param e pointer down event
      */
     gptSummarize = async (e: React.PointerEvent) => {
+        GPTPopup.Instance.setVisible(true);
         this.setHighlightRange(undefined);
-        this.setGPTPopupVis(true);
         this.setGPTMode(GPTPopupMode.SUMMARY);
-        this.setLoading(true);
+        GPTPopup.Instance.setLoading(true);
 
         try {
             const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
             if (res) {
-                this.setGPTPopupText(res);
+                GPTPopup.Instance.setText(res);
             } else {
-                this.setGPTPopupText('Something went wrong.');
+                GPTPopup.Instance.setText('Something went wrong.');
             }
         } catch (err) {
             console.error(err);
         }
-
-        this.setLoading(false);
+        GPTPopup.Instance.setLoading(false);
     };
 
     /**
@@ -184,9 +159,9 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         const fullText = state.doc.textBetween(0, this.editorView.state.doc.content.size, ' \n');
         const selectedText = state.doc.textBetween(sel.from, sel.to);
 
-        this.setGPTPopupVis(true);
+        GPTPopup.Instance.setVisible(true);
         this.setGPTMode(GPTPopupMode.EDIT);
-        this.setLoading(true);
+        GPTPopup.Instance.setLoading(true);
 
         try {
             let res = await gptAPICall(selectedText, GPTCallType.EDIT);
@@ -196,16 +171,16 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1);
 
             if (res) {
-                this.setGPTPopupText(resultText);
+                GPTPopup.Instance.setText(resultText);
                 this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]);
             } else {
-                this.setGPTPopupText('Something went wrong.');
+                GPTPopup.Instance.setText('Something went wrong.');
             }
         } catch (err) {
             console.error(err);
         }
 
-        this.setLoading(false);
+        GPTPopup.Instance.setLoading(false);
     };
 
     /**
@@ -253,21 +228,18 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     };
 
     @computed get highlighter() {
-        return <Group>
-            <IconButton 
-                icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />}
-                tooltip={'Click to Highlight'}
-                onClick={this.highlightClicked}
-                colorPicker={this.highlightColor}
-                color={StrCast(Doc.UserDoc().userColor)}
-            />
-            <ColorPicker
-                colorPickerType={'github'}
-                selectedColor={this.highlightColor}
-                setSelectedColor={color => this.changeHighlightColor(color)}
-                size={Size.XSMALL}
-            />
-        </Group>
+        return (
+            <Group>
+                <IconButton
+                    icon={<FontAwesomeIcon icon="highlighter" style={{ transition: 'transform 0.1s', transform: 'rotate(-45deg)' }} />}
+                    tooltip={'Click to Highlight'}
+                    onClick={this.highlightClicked}
+                    colorPicker={this.highlightColor}
+                    color={StrCast(Doc.UserDoc().userColor)}
+                />
+                <ColorPicker colorPickerType={'github'} selectedColor={this.highlightColor} setSelectedColor={color => this.changeHighlightColor(color)} size={Size.XSMALL} />
+            </Group>
+        );
     }
 
     @action changeHighlightColor = (color: string) => {
@@ -312,12 +284,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             this.Status === 'marquee' ? (
                 <>
                     {this.highlighter}
-                    <IconButton 
-                        tooltip={'Drag to Place Annotation'} 
-                        onPointerDown={this.pointerDown} 
-                        icon={<FontAwesomeIcon icon="comment-alt"/>}
-                        color={StrCast(Doc.UserDoc().userColor)}
-                    />
+                    <IconButton tooltip={'Drag to Place Annotation'} onPointerDown={this.pointerDown} icon={<FontAwesomeIcon icon="comment-alt" />} color={StrCast(Doc.UserDoc().userColor)} />
                     {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
                         <Tooltip key="gpt" title={<div className="dash-tooltip">Summarize with AI</div>}>
@@ -326,7 +293,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             </button>
                         </Tooltip>
                     )}
-                    <GPTPopup
+                    {/* <GPTPopup
                         key="gptpopup"
                         visible={this.showGPTPopup}
                         text={this.GPTpopupText}
@@ -336,68 +303,32 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         callEditApi={this.gptEdit}
                         replaceText={this.replaceText}
                         mode={this.GPTMode}
-                    />
+                    /> */}
                     {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
-                        <IconButton 
-                            tooltip={'Click to Record Annotation'}
-                            onPointerDown={this.audioDown}
-                            icon={<FontAwesomeIcon icon="microphone" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
-                        />
-                    )}
-                    {this.canEdit() && (
-                        <IconButton 
-                            tooltip={'AI edit suggestions'}
-                            onPointerDown={this.gptEdit}
-                            icon={<FontAwesomeIcon icon="pencil-alt" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
-                        />
+                        <IconButton tooltip={'Click to Record Annotation'} onPointerDown={this.audioDown} icon={<FontAwesomeIcon icon="microphone" />} color={StrCast(Doc.UserDoc().userColor)} />
                     )}
-                    <Popup 
-                        tooltip='Find document to link to selected text'
-                        type={Type.PRIM}
-                        icon={<FontAwesomeIcon icon={'search'} />}
-                        popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />}
-                        color={StrCast(Doc.UserDoc().userColor)}
-                    />
+                    {this.canEdit() && <IconButton tooltip={'AI edit suggestions'} onPointerDown={this.gptEdit} icon={<FontAwesomeIcon icon="pencil-alt" />} color={StrCast(Doc.UserDoc().userColor)} />}
+                    <Popup tooltip="Find document to link to selected text" type={Type.PRIM} icon={<FontAwesomeIcon icon={'search'} />} popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />} color={StrCast(Doc.UserDoc().userColor)} />
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
-                        <IconButton 
-                            tooltip={'Click/Drag to create cropped image'}
-                            onPointerDown={this.cropDown}
-                            icon={<FontAwesomeIcon icon="image"/>}
-                            color={StrCast(Doc.UserDoc().userColor)}
-                        />
+                        <IconButton tooltip={'Click/Drag to create cropped image'} onPointerDown={this.cropDown} icon={<FontAwesomeIcon icon="image" />} color={StrCast(Doc.UserDoc().userColor)} />
                     )}
                 </>
             ) : (
                 <>
-                    {this.Delete !== returnFalse && <IconButton 
-                        tooltip={'Remove Link Anchor'}
-                        onPointerDown={this.Delete}
-                        icon={<FontAwesomeIcon icon="trash-alt" />}
-                        color={StrCast(Doc.UserDoc().userColor)}
-                    />}
-                    {this.PinToPres !== returnFalse && <IconButton 
-                        tooltip={'Pin to Presentation'}
-                        onPointerDown={this.PinToPres}
-                        icon={<FontAwesomeIcon icon="map-pin" />}
-                        color={StrCast(Doc.UserDoc().userColor)}
-                    />}
-                    {this.ShowTargetTrail !== returnFalse && <IconButton 
-                        tooltip={'Show Linked Trail'}
-                        onPointerDown={this.ShowTargetTrail}
-                        icon={<FontAwesomeIcon icon="taxi" />}
-                        color={StrCast(Doc.UserDoc().userColor)}
-                    />}
-                    {this.IsTargetToggler !== returnFalse && <Toggle
-                        tooltip={'Make target visibility toggle on click'}
-                        type={Type.PRIM}
-                        toggleType={ToggleType.BUTTON}
-                        toggleStatus={this.IsTargetToggler()}
-                        onClick={this.MakeTargetToggle}
-                        icon={<FontAwesomeIcon icon="thumbtack" />}
-                        color={StrCast(Doc.UserDoc().userColor)}
-                    />}
+                    {this.Delete !== returnFalse && <IconButton tooltip={'Remove Link Anchor'} onPointerDown={this.Delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={StrCast(Doc.UserDoc().userColor)} />}
+                    {this.PinToPres !== returnFalse && <IconButton tooltip={'Pin to Presentation'} onPointerDown={this.PinToPres} icon={<FontAwesomeIcon icon="map-pin" />} color={StrCast(Doc.UserDoc().userColor)} />}
+                    {this.ShowTargetTrail !== returnFalse && <IconButton tooltip={'Show Linked Trail'} onPointerDown={this.ShowTargetTrail} icon={<FontAwesomeIcon icon="taxi" />} color={StrCast(Doc.UserDoc().userColor)} />}
+                    {this.IsTargetToggler !== returnFalse && (
+                        <Toggle
+                            tooltip={'Make target visibility toggle on click'}
+                            type={Type.PRIM}
+                            toggleType={ToggleType.BUTTON}
+                            toggleStatus={this.IsTargetToggler()}
+                            onClick={this.MakeTargetToggle}
+                            icon={<FontAwesomeIcon icon="thumbtack" />}
+                            color={StrCast(Doc.UserDoc().userColor)}
+                        />
+                    )}
                 </>
             );
 
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 44413ede7..2f0ff83e2 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -6,12 +6,6 @@ $button: #5b97ff;
 $highlightedText: #82e0ff;
 
 .summary-box {
-    display: flex;
-    flex-direction: column;
-    justify-content: space-between;
-    background-color: #ffffff;
-    box-shadow: 0 2px 5px #7474748d;
-    color: $textgrey;
     position: fixed;
     bottom: 10px;
     right: 10px;
@@ -21,6 +15,12 @@ $highlightedText: #82e0ff;
     padding: 15px;
     padding-bottom: 0;
     z-index: 999;
+    display: flex;
+    flex-direction: column;
+    justify-content: space-between;
+    background-color: #ffffff;
+    box-shadow: 0 2px 5px #7474748d;
+    color: $textgrey;
 
     .summary-heading {
         display: flex;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 8bd060d4f..9f28cb5d1 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -5,56 +5,96 @@ import { observer } from 'mobx-react';
 import ReactLoading from 'react-loading';
 import Typist from 'react-typist';
 import { Doc } from '../../../../fields/Doc';
-import { Docs } from '../../../documents/Documents';
+import { DocUtils, Docs } from '../../../documents/Documents';
 import './GPTPopup.scss';
+import { Button, IconButton } from 'browndash-components';
+import { StrCast } from '../../../../fields/Types';
 
 export enum GPTPopupMode {
     SUMMARY,
     EDIT,
 }
 
-interface GPTPopupProps {
-    visible: boolean;
-    text: string;
-    loading: boolean;
-    mode: GPTPopupMode;
-    callSummaryApi: (e: React.PointerEvent) => Promise<void>;
-    callEditApi: (e: React.PointerEvent) => Promise<void>;
-    replaceText: (replacement: string) => void;
-    highlightRange?: number[];
-}
+interface GPTPopupProps {}
 
 @observer
 export class GPTPopup extends React.Component<GPTPopupProps> {
     static Instance: GPTPopup;
 
     @observable
-    private done: boolean = false;
+    public visible: boolean = false;
+    @action
+    public setVisible = (vis: boolean) => {
+        this.visible = vis;
+    };
     @observable
-    private sidebarId: string = '';
+    public loading: boolean = false;
+    @action
+    public setLoading = (loading: boolean) => {
+        this.loading = loading;
+    };
+    @observable
+    public text: string = '';
+    @action
+    public setText = (text: string) => {
+        this.text = text;
+    };
+    @observable
+    public mode: GPTPopupMode = GPTPopupMode.SUMMARY;
+    @action
+    public setMode = (mode: GPTPopupMode) => {
+        this.mode = mode;
+    };
 
+    @observable
+    public highlightRange: number[] = [];
+    @action callSummaryApi = () => {};
+    @action callEditApi = () => {};
+    @action replaceText = (replacement: string) => {};
+
+    @observable
+    private done: boolean = false;
     @action
     public setDone = (done: boolean) => {
         this.done = done;
     };
+    @observable
+    private sidebarId: string = '';
     @action
     public setSidebarId = (id: string) => {
         this.sidebarId = id;
     };
 
+    // reference
+    // if (focusNode) {
+    //     const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
+    //     anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
+    // }
+    @observable
+    private pdfAnchor: Doc | undefined;
+    @action
+    public setPdfAnchor = (anchor: Doc) => {
+        this.pdfAnchor = anchor;
+    };
+
     public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
 
     /**
      * Transfers the summarization text to a sidebar annotation text document.
      */
     private transferToText = () => {
-        const newDoc = Docs.Create.TextDocument(this.props.text.trim(), {
+        const newDoc = Docs.Create.TextDocument(this.text.trim(), {
             _width: 200,
             _height: 50,
             _layout_fitWidth: true,
             _layout_autoHeight: true,
         });
         this.addDoc(newDoc, this.sidebarId);
+        if (this.pdfAnchor) {
+            DocUtils.MakeLink(newDoc, this.pdfAnchor, {
+                link_relationship: 'GPT Summary',
+            });
+        }
     };
 
     constructor(props: GPTPopupProps) {
@@ -63,7 +103,7 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
     }
 
     componentDidUpdate = () => {
-        if (this.props.loading) {
+        if (this.loading) {
             this.setDone(false);
         }
     };
@@ -73,10 +113,10 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
             <div>
                 {this.heading('SUMMARY')}
                 <div className="content-wrapper">
-                    {!this.props.loading &&
+                    {!this.loading &&
                         (!this.done ? (
                             <Typist
-                                key={this.props.text}
+                                key={this.text}
                                 avgTypingDelay={15}
                                 cursor={{ hideWhenDone: true }}
                                 onTypingDone={() => {
@@ -84,18 +124,18 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
                                         this.setDone(true);
                                     }, 500);
                                 }}>
-                                {this.props.text}
+                                {this.text}
                             </Typist>
                         ) : (
-                            this.props.text
+                            this.text
                         ))}
                 </div>
             </div>
-            {!this.props.loading && (
+            {!this.loading && (
                 <div className="btns-wrapper">
                     {this.done ? (
                         <>
-                            <button className="icon-btn" onPointerDown={e => this.props.callSummaryApi(e)}>
+                            {/* <button className="icon-btn" onClick={this.callSummaryApi}>
                                 <FontAwesomeIcon icon="redo-alt" size="lg" />
                             </button>
                             <button
@@ -104,7 +144,9 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
                                     this.transferToText();
                                 }}>
                                 Transfer to Text
-                            </button>
+                            </button> */}
+                            <IconButton onClick={this.callSummaryApi} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
+                            <Button text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} />
                         </>
                     ) : (
                         <div className="summarizing">
@@ -125,7 +167,7 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
     );
 
     editBox = () => {
-        const hr = this.props.highlightRange;
+        const hr = this.highlightRange;
         return (
             <>
                 <div>
@@ -133,22 +175,22 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
                     <div className="content-wrapper">
                         {hr && (
                             <div>
-                                {this.props.text.slice(0, hr[0])} <span className="highlighted-text">{this.props.text.slice(hr[0], hr[1])}</span> {this.props.text.slice(hr[1])}
+                                {this.text.slice(0, hr[0])} <span className="highlighted-text">{this.text.slice(hr[0], hr[1])}</span> {this.text.slice(hr[1])}
                             </div>
                         )}
                     </div>
                 </div>
-                {hr && !this.props.loading && (
+                {hr && !this.loading && (
                     <>
                         <div className="btns-wrapper">
                             <>
-                                <button className="icon-btn" onPointerDown={e => this.props.callEditApi(e)}>
+                                <button className="icon-btn" onPointerDown={this.callEditApi}>
                                     <FontAwesomeIcon icon="redo-alt" size="lg" />
                                 </button>
                                 <button
                                     className="text-btn"
                                     onClick={e => {
-                                        this.props.replaceText(this.props.text);
+                                        this.replaceText(this.text);
                                     }}>
                                     Replace Text
                                 </button>
@@ -174,14 +216,14 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
     heading = (headingText: string) => (
         <div className="summary-heading">
             <label className="summary-text">{headingText}</label>
-            {this.props.loading && <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />}
+            {this.loading && <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />}
         </div>
     );
 
     render() {
         return (
-            <div className="summary-box" style={{ display: this.props.visible ? 'flex' : 'none' }}>
-                {this.props.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()}
+            <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
+                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()}
             </div>
         );
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 0fd93868a..4fc31ffe3 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -423,6 +423,11 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
         // Changing which document to add the annotation to (the currently selected PDF)
         GPTPopup.Instance.setSidebarId('data_sidebar');
+        const anchor = this._getAnchor(undefined, false);
+        if (anchor) {
+            console.log(anchor);
+            GPTPopup.Instance.setPdfAnchor(anchor);
+        }
         GPTPopup.Instance.addDoc = this.props.sidebarAddDoc;
     };
 
-- 
cgit v1.2.3-70-g09d2


From fec79d2b5b8feb361e489c9ee41ee720507d0806 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 27 Jul 2023 13:23:42 -0400
Subject: changes

---
 report.20230720.011057.28881.0.001.json            | 1269 ++++++++++++++++++++
 src/client/apis/gpt/GPT.ts                         |   28 +-
 src/client/util/DragManager.ts                     |    4 +-
 src/client/views/collections/TabDocView.tsx        |    6 +-
 .../CollectionFreeFormLinkView.tsx                 |    8 +-
 src/client/views/global/globalScripts.ts           |   50 +-
 src/client/views/nodes/WebBox.tsx                  |    5 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |   89 +-
 src/client/views/pdf/AnchorMenu.tsx                |   21 +-
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |   52 +
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  129 +-
 src/client/views/pdf/PDFViewer.tsx                 |    3 +-
 12 files changed, 1530 insertions(+), 134 deletions(-)
 create mode 100644 report.20230720.011057.28881.0.001.json

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

diff --git a/report.20230720.011057.28881.0.001.json b/report.20230720.011057.28881.0.001.json
new file mode 100644
index 000000000..5c72c0c91
--- /dev/null
+++ b/report.20230720.011057.28881.0.001.json
@@ -0,0 +1,1269 @@
+
+{
+  "header": {
+    "reportVersion": 1,
+    "event": "Allocation failed - JavaScript heap out of memory",
+    "trigger": "FatalError",
+    "filename": "report.20230720.011057.28881.0.001.json",
+    "dumpEventTime": "2023-07-20T01:10:57Z",
+    "dumpEventTimeStamp": "1689829857721",
+    "processId": 28881,
+    "cwd": "/Users/smallwhale/Desktop/Projects/Dash-Web",
+    "commandLine": [
+      "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node",
+      "--max-old-space-size=2048",
+      "/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/ts-node-dev/lib/wrap.js",
+      "/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/fork-ts-checker-webpack-plugin/lib/service.js"
+    ],
+    "nodejsVersion": "v12.16.0",
+    "wordSize": 64,
+    "arch": "x64",
+    "platform": "darwin",
+    "componentVersions": {
+      "node": "12.16.0",
+      "v8": "7.8.279.23-node.31",
+      "uv": "1.34.0",
+      "zlib": "1.2.11",
+      "brotli": "1.0.7",
+      "ares": "1.15.0",
+      "modules": "72",
+      "nghttp2": "1.40.0",
+      "napi": "5",
+      "llhttp": "2.0.4",
+      "http_parser": "2.9.3",
+      "openssl": "1.1.1d",
+      "cldr": "35.1",
+      "icu": "64.2",
+      "tz": "2019c",
+      "unicode": "12.1"
+    },
+    "release": {
+      "name": "node",
+      "lts": "Erbium",
+      "headersUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0-headers.tar.gz",
+      "sourceUrl": "https://nodejs.org/download/release/v12.16.0/node-v12.16.0.tar.gz"
+    },
+    "osName": "Darwin",
+    "osRelease": "22.4.0",
+    "osVersion": "Darwin Kernel Version 22.4.0: Mon Mar  6 21:01:02 PST 2023; root:xnu-8796.101.5~3/RELEASE_ARM64_T8112",
+    "osMachine": "x86_64",
+    "cpus": [
+      {
+        "model": "Apple M2",
+        "speed": 2400,
+        "user": 40342390,
+        "nice": 0,
+        "sys": 32818540,
+        "idle": 263532590,
+        "irq": 0
+      },
+      {
+        "model": "Apple M2",
+        "speed": 2400,
+        "user": 37882510,
+        "nice": 0,
+        "sys": 28750670,
+        "idle": 270292020,
+        "irq": 0
+      },
+      {
+        "model": "Apple M2",
+        "speed": 2400,
+        "user": 32806560,
+        "nice": 0,
+        "sys": 24772270,
+        "idle": 279705870,
+        "irq": 0
+      },
+      {
+        "model": "Apple M2",
+        "speed": 2400,
+        "user": 28917140,
+        "nice": 0,
+        "sys": 21505900,
+        "idle": 287180070,
+        "irq": 0
+      },
+      {
+        "model": "Apple M2",
+        "speed": 2400,
+        "user": 52175300,
+        "nice": 0,
+        "sys": 11670040,
+        "idle": 274363170,
+        "irq": 0
+      },
+      {
+        "model": "Apple M2",
+        "speed": 2400,
+        "user": 30406640,
+        "nice": 0,
+        "sys": 8969580,
+        "idle": 298959950,
+        "irq": 0
+      },
+      {
+        "model": "Apple M2",
+        "speed": 2400,
+        "user": 21936370,
+        "nice": 0,
+        "sys": 6476860,
+        "idle": 310114030,
+        "irq": 0
+      },
+      {
+        "model": "Apple M2",
+        "speed": 2400,
+        "user": 16048180,
+        "nice": 0,
+        "sys": 4956530,
+        "idle": 317649610,
+        "irq": 0
+      }
+    ],
+    "networkInterfaces": [
+      {
+        "name": "lo0",
+        "internal": true,
+        "mac": "00:00:00:00:00:00",
+        "address": "127.0.0.1",
+        "netmask": "255.0.0.0",
+        "family": "IPv4"
+      },
+      {
+        "name": "lo0",
+        "internal": true,
+        "mac": "00:00:00:00:00:00",
+        "address": "::1",
+        "netmask": "ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff",
+        "family": "IPv6",
+        "scopeid": 0
+      },
+      {
+        "name": "lo0",
+        "internal": true,
+        "mac": "00:00:00:00:00:00",
+        "address": "fe80::1",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 1
+      },
+      {
+        "name": "anpi0",
+        "internal": false,
+        "mac": "42:10:d5:5b:93:9e",
+        "address": "fe80::4010:d5ff:fe5b:939e",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 4
+      },
+      {
+        "name": "anpi1",
+        "internal": false,
+        "mac": "42:10:d5:5b:93:9f",
+        "address": "fe80::4010:d5ff:fe5b:939f",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 5
+      },
+      {
+        "name": "en0",
+        "internal": false,
+        "mac": "9c:3e:53:8e:59:0e",
+        "address": "fe80::2b:dde:e859:46b8",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 12
+      },
+      {
+        "name": "en0",
+        "internal": false,
+        "mac": "9c:3e:53:8e:59:0e",
+        "address": "192.168.1.154",
+        "netmask": "255.255.255.0",
+        "family": "IPv4"
+      },
+      {
+        "name": "en0",
+        "internal": false,
+        "mac": "9c:3e:53:8e:59:0e",
+        "address": "2600:4040:572e:2f00:18fe:e894:e23c:f4c2",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 0
+      },
+      {
+        "name": "en0",
+        "internal": false,
+        "mac": "9c:3e:53:8e:59:0e",
+        "address": "2600:4040:572e:2f00:c182:d0ad:6e64:1fae",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 0
+      },
+      {
+        "name": "awdl0",
+        "internal": false,
+        "mac": "b6:6c:9a:3c:a7:93",
+        "address": "fe80::b46c:9aff:fe3c:a793",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 13
+      },
+      {
+        "name": "llw0",
+        "internal": false,
+        "mac": "b6:6c:9a:3c:a7:93",
+        "address": "fe80::b46c:9aff:fe3c:a793",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 14
+      },
+      {
+        "name": "utun0",
+        "internal": false,
+        "mac": "00:00:00:00:00:00",
+        "address": "fe80::be27:76ec:8f11:5429",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 15
+      },
+      {
+        "name": "utun1",
+        "internal": false,
+        "mac": "00:00:00:00:00:00",
+        "address": "fe80::ce81:b1c:bd2c:69e",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 16
+      },
+      {
+        "name": "utun2",
+        "internal": false,
+        "mac": "00:00:00:00:00:00",
+        "address": "fe80::7707:8801:aef4:1907",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 17
+      },
+      {
+        "name": "utun3",
+        "internal": false,
+        "mac": "00:00:00:00:00:00",
+        "address": "fe80::a4f8:a3ce:851e:23dd",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 18
+      },
+      {
+        "name": "utun4",
+        "internal": false,
+        "mac": "00:00:00:00:00:00",
+        "address": "fe80::8d47:6641:b300:bde8",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 19
+      },
+      {
+        "name": "utun5",
+        "internal": false,
+        "mac": "00:00:00:00:00:00",
+        "address": "fe80::7c36:c665:d5f7:49d7",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 20
+      },
+      {
+        "name": "utun6",
+        "internal": false,
+        "mac": "00:00:00:00:00:00",
+        "address": "fe80::10d8:db7a:d714:7746",
+        "netmask": "ffff:ffff:ffff:ffff::",
+        "family": "IPv6",
+        "scopeid": 21
+      }
+    ],
+    "host": "Sophies-MBP"
+  },
+  "javascriptStack": {
+    "message": "No stack.",
+    "stack": [
+      "Unavailable."
+    ]
+  },
+  "nativeStack": [
+    {
+      "pc": "0x000000010015c8ca",
+      "symbol": "report::TriggerNodeReport(v8::Isolate*, node::Environment*, char const*, char const*, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char>> const&, v8::Local<v8::String>) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x0000000100080f3e",
+      "symbol": "node::OnFatalError(char const*, char const*) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x0000000100185467",
+      "symbol": "v8::Utils::ReportOOMFailure(v8::internal::Isolate*, char const*, bool) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x0000000100185403",
+      "symbol": "v8::internal::V8::FatalProcessOutOfMemory(v8::internal::Isolate*, char const*, bool) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x000000010030b5f5",
+      "symbol": "v8::internal::Heap::FatalProcessOutOfMemory(char const*) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x000000010030ccc4",
+      "symbol": "v8::internal::Heap::RecomputeLimits(v8::internal::GarbageCollector) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x0000000100309b37",
+      "symbol": "v8::internal::Heap::PerformGarbageCollection(v8::internal::GarbageCollector, v8::GCCallbackFlags) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x0000000100307afd",
+      "symbol": "v8::internal::Heap::CollectGarbage(v8::internal::AllocationSpace, v8::internal::GarbageCollectionReason, v8::GCCallbackFlags) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x00000001003132ba",
+      "symbol": "v8::internal::Heap::AllocateRawWithLightRetry(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x0000000100313341",
+      "symbol": "v8::internal::Heap::AllocateRawWithRetryOrFail(int, v8::internal::AllocationType, v8::internal::AllocationOrigin, v8::internal::AllocationAlignment) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x00000001002e065b",
+      "symbol": "v8::internal::Factory::NewFillerObject(int, bool, v8::internal::AllocationType, v8::internal::AllocationOrigin) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x0000000100618a18",
+      "symbol": "v8::internal::Runtime_AllocateInYoungGeneration(int, unsigned long*, v8::internal::Isolate*) [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x0000000100950c19",
+      "symbol": "Builtins_CEntry_Return1_DontSaveFPRegs_ArgvOnStack_NoBuiltinExit [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    },
+    {
+      "pc": "0x00000001008d2291",
+      "symbol": "Builtins_FastNewObject [/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node]"
+    }
+  ],
+  "javascriptHeap": {
+    "totalMemory": 2152435712,
+    "totalCommittedMemory": 2150538320,
+    "usedMemory": 2139294040,
+    "availableMemory": 47735856,
+    "memoryLimit": 2197815296,
+    "heapSpaces": {
+      "read_only_space": {
+        "memorySize": 262144,
+        "committedMemory": 33088,
+        "capacity": 32808,
+        "used": 32808,
+        "available": 0
+      },
+      "new_space": {
+        "memorySize": 2097152,
+        "committedMemory": 1236968,
+        "capacity": 1047456,
+        "used": 203328,
+        "available": 844128
+      },
+      "old_space": {
+        "memorySize": 1956003840,
+        "committedMemory": 1955767472,
+        "capacity": 1948209048,
+        "used": 1947744360,
+        "available": 464688
+      },
+      "code_space": {
+        "memorySize": 14061568,
+        "committedMemory": 13610624,
+        "capacity": 12121760,
+        "used": 12121760,
+        "available": 0
+      },
+      "map_space": {
+        "memorySize": 1576960,
+        "committedMemory": 1456120,
+        "capacity": 1257120,
+        "used": 1257120,
+        "available": 0
+      },
+      "large_object_space": {
+        "memorySize": 178384896,
+        "committedMemory": 178384896,
+        "capacity": 177931880,
+        "used": 177931880,
+        "available": 0
+      },
+      "code_large_object_space": {
+        "memorySize": 49152,
+        "committedMemory": 49152,
+        "capacity": 2784,
+        "used": 2784,
+        "available": 0
+      },
+      "new_large_object_space": {
+        "memorySize": 0,
+        "committedMemory": 0,
+        "capacity": 1047456,
+        "used": 0,
+        "available": 1047456
+      }
+    }
+  },
+  "resourceUsage": {
+    "userCpuSeconds": 312.946,
+    "kernelCpuSeconds": 137.981,
+    "cpuConsumptionPercent": 62.0257,
+    "maxRss": 2062897119232,
+    "pageFaults": {
+      "IORequired": 658,
+      "IONotRequired": 43579208
+    },
+    "fsActivity": {
+      "reads": 0,
+      "writes": 0
+    }
+  },
+  "libuv": [
+  ],
+  "environmentVariables": {
+    "npm_config_save_dev": "",
+    "npm_config_legacy_bundling": "",
+    "npm_config_dry_run": "",
+    "npm_package_dependencies_translate_google_api": "^1.0.4",
+    "npm_package_dependencies_request": "^2.88.2",
+    "npm_package_dependencies_express_flash": "0.0.2",
+    "npm_package_dependencies__fortawesome_fontawesome_svg_core": "^6.3.0",
+    "NVM_INC": "/Users/smallwhale/.nvm/versions/node/v12.16.0/include/node",
+    "npm_config_viewer": "man",
+    "npm_config_only": "",
+    "npm_config_commit_hooks": "true",
+    "npm_config_browser": "",
+    "npm_package_gitHead": "e11aa60b774d457cb016bb0f375ce092f0a733af",
+    "npm_package_dependencies_webpack_dev_middleware": "^5.3.1",
+    "npm_package_dependencies_webpack_cli": "^4.10.0",
+    "npm_package_devDependencies_prettier": "^2.7.1",
+    "npm_package_devDependencies_awesome_typescript_loader": "^5.2.1",
+    "npm_package_devDependencies__types_archiver": "^3.1.1",
+    "MANPATH": "/Users/smallwhale/.nvm/versions/node/v12.16.0/share/man:/Users/smallwhale/.nvm/versions/node/v18.16.0/share/man:/opt/homebrew/share/man:/usr/share/man:/usr/local/share/man:/Users/smallwhale/.nvm/versions/node/v18.16.0/share/man:/opt/homebrew/share/man::",
+    "npm_config_also": "",
+    "npm_package_dependencies_react_jsx_parser": "^1.29.0",
+    "npm_package_dependencies_mongoose": "^5.13.14",
+    "npm_package_dependencies_connect_flash": "^0.1.1",
+    "npm_package_browser_child_process": "false",
+    "npm_config_sign_git_commit": "",
+    "npm_config_rollback": "true",
+    "npm_package_dependencies_material_ui": "^0.20.2",
+    "npm_package_devDependencies__types_sharp": "^0.23.1",
+    "npm_package_devDependencies__types_passport_local": "^1.0.34",
+    "npm_package_devDependencies__types_dotenv": "^6.1.1",
+    "npm_package_devDependencies__types_cookie_parser": "^1.4.2",
+    "TERM_PROGRAM": "vscode",
+    "NODE": "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node",
+    "npm_config_usage": "",
+    "npm_config_audit": "true",
+    "npm_package_dependencies_reveal_js": "^4.3.0",
+    "npm_package_dependencies_process": "^0.11.10",
+    "npm_package_dependencies_pdfjs": "^2.4.7",
+    "npm_package_dependencies_html_to_image": "^0.1.3",
+    "npm_package_devDependencies_file_loader": "^3.0.1",
+    "npm_package_devDependencies__types_express_flash": "0.0.0",
+    "npm_package_scripts_monitor": "cross-env MONITORED=true NODE_OPTIONS=--max_old_space_size=4096 ts-node src/server/index.ts",
+    "INIT_CWD": "/Users/smallwhale/Desktop/Projects/Dash-Web",
+    "npm_package_dependencies_rehype_raw": "^6.1.1",
+    "npm_package_dependencies_react_audio_waveform": "0.0.5",
+    "npm_package_dependencies_path_browserify": "^1.0.1",
+    "npm_package_dependencies_nodemailer": "^5.1.1",
+    "npm_package_dependencies_axios": "^0.19.2",
+    "npm_package_devDependencies_typescript": "^4.7.4",
+    "NVM_CD_FLAGS": "-q",
+    "npm_config_globalignorefile": "/Users/smallwhale/.nvm/versions/node/v12.16.0/etc/npmignore",
+    "npm_package_dependencies_react_grid_layout": "^1.3.4",
+    "npm_package_dependencies_prosemirror_find_replace": "^0.9.0",
+    "npm_package_dependencies_normalize_css": "^8.0.1",
+    "npm_package_devDependencies_mocha": "^5.2.0",
+    "npm_package_devDependencies__types_express_session": "^1.17.5",
+    "TERM": "xterm-256color",
+    "SHELL": "/bin/zsh",
+    "npm_config_shell": "/bin/zsh",
+    "npm_config_maxsockets": "50",
+    "npm_config_init_author_url": "",
+    "npm_package_dependencies_prosemirror_dev_tools": "^3.1.0",
+    "npm_package_dependencies_p_limit": "^2.2.0",
+    "npm_package_dependencies_bson": "^4.6.1",
+    "npm_package_dependencies__types_dom_speech_recognition": "0.0.1",
+    "npm_package_dependencies__emotion_styled": "^11.11.0",
+    "npm_package_devDependencies_style_loader": "^0.23.1",
+    "npm_package_devDependencies__types_react_datepicker": "^3.1.8",
+    "npm_config_shrinkwrap": "true",
+    "npm_config_parseable": "",
+    "npm_config_metrics_registry": "https://registry.npmjs.org/",
+    "npm_package_dependencies_xregexp": "^4.4.1",
+    "npm_package_dependencies_shelljs": "^0.8.5",
+    "npm_package_dependencies_bezier_curve": "^1.0.0",
+    "npm_package_dependencies__mui_icons_material": "^5.11.16",
+    "npm_package_devDependencies_tslint": "^5.20.1",
+    "npm_package_devDependencies__types_react_transition_group": "^4.4.5",
+    "npm_package_scripts_tsc": "tsc",
+    "HOMEBREW_REPOSITORY": "/opt/homebrew",
+    "TMPDIR": "/var/folders/bv/1xck6d7j7bz14bvhgdsllbj40000gn/T/",
+    "npm_config_timing": "",
+    "npm_config_init_license": "ISC",
+    "npm_package_dependencies_socket_io": "^2.5.0",
+    "npm_package_dependencies_probe_image_size": "^4.0.0",
+    "npm_package_dependencies_canvas": "^2.9.3",
+    "npm_package_dependencies__hig_theme_data": "^2.23.1",
+    "npm_package_devDependencies__types_react_select": "^3.1.2",
+    "npm_package_devDependencies__types_prosemirror_model": "^1.16.1",
+    "npm_config_if_present": "",
+    "npm_package_dependencies_typescript_collections": "^1.3.3",
+    "npm_package_dependencies_rimraf": "^3.0.0",
+    "npm_package_dependencies_react_autosuggest": "^9.4.3",
+    "npm_package_dependencies_flexlayout_react": "^0.3.11",
+    "npm_package_dependencies_find_in_files": "^0.5.0",
+    "npm_package_devDependencies__types_chai": "^4.3.0",
+    "TERM_PROGRAM_VERSION": "1.79.2",
+    "npm_package_dependencies_prosemirror_inputrules": "^1.1.3",
+    "npm_package_dependencies_bcrypt_nodejs": "0.0.3",
+    "npm_package_dependencies_async": "^2.6.2",
+    "npm_config_sign_git_tag": "",
+    "npm_config_init_author_email": "",
+    "npm_config_cache_max": "Infinity",
+    "npm_package_dependencies_uuid": "^3.4.0",
+    "npm_package_dependencies_supercluster": "^7.1.4",
+    "npm_package_dependencies_remark_gfm": "^3.0.1",
+    "npm_package_dependencies_connect_mongo": "^2.0.3",
+    "npm_package_dependencies_browser_assert": "^1.2.1",
+    "npm_package_devDependencies_sass_loader": "^7.3.1",
+    "ZDOTDIR": "/Users/smallwhale",
+    "ORIGINAL_XDG_CURRENT_DESKTOP": "undefined",
+    "MallocNanoZone": "0",
+    "npm_config_preid": "",
+    "npm_config_long": "",
+    "npm_config_local_address": "",
+    "npm_config_git_tag_version": "true",
+    "npm_config_cert": "",
+    "npm_package_dependencies_js_datepicker": "^4.6.6",
+    "npm_package_devDependencies__types_webpack_hot_middleware": "^2.25.6",
+    "npm_package_devDependencies__types_mongodb": "^3.6.20",
+    "npm_package_devDependencies__types_mocha": "^5.2.6",
+    "TERM_SESSION_ID": "F2049B14-CDC7-4078-A2C4-82A8C869F43E",
+    "npm_config_registry": "https://registry.npmjs.org/",
+    "npm_config_noproxy": "",
+    "npm_config_fetch_retries": "2",
+    "npm_package_dependencies_react_compound_slider": "^2.5.0",
+    "npm_package_dependencies_prosemirror_history": "^1.2.0",
+    "npm_package_devDependencies__types_react_color": "^2.17.6",
+    "npm_package_devDependencies__types_google_maps_react": "^2.0.5",
+    "npm_package_devDependencies__types_color": "^3.0.3",
+    "npm_package_dependencies_react_dom": "^18.2.0",
+    "npm_package_dependencies_passport_local": "^1.0.0",
+    "npm_package_dependencies__octokit_core": "^4.0.4",
+    "npm_package_devDependencies__types_async": "^2.4.1",
+    "npm_package_scripts_debug": "cross-env NODE_OPTIONS=--max_old_space_size=8192 ts-node-dev --transpile-only --inspect -- src/server/index.ts",
+    "npm_package_scripts_oldstart": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug -- src/server/index.ts",
+    "npm_config_versions": "",
+    "npm_config_message": "%s",
+    "npm_config_key": "",
+    "npm_package_readmeFilename": "README.md",
+    "npm_package_dependencies_react_refresh_typescript": "^2.0.7",
+    "npm_package_dependencies_image_size": "^0.7.5",
+    "npm_package_dependencies_html_to_text": "^5.1.1",
+    "npm_package_dependencies_express_validator": "^5.3.1",
+    "npm_package_devDependencies_eslint_plugin_jsx_a11y": "^6.6.0",
+    "npm_package_node_child_process": "empty",
+    "npm_package_dependencies_react_resizable_rotatable_draggable": "^0.2.0",
+    "npm_package_dependencies_got": "^12.0.1",
+    "npm_package_dependencies__types_d3_color": "^2.0.3",
+    "npm_package_devDependencies_webpack": "^5.69.1",
+    "npm_package_devDependencies__types_nodemailer": "^4.6.6",
+    "npm_package_description": "Install Node.js, then, from the project directory, run",
+    "NVM_DIR": "/Users/smallwhale/.nvm",
+    "USER": "smallwhale",
+    "npm_package_dependencies__types_d3_scale": "^3.3.2",
+    "npm_package_devDependencies_dotenv": "^8.6.0",
+    "npm_package_devDependencies__types_react": "^18.0.15",
+    "npm_package_devDependencies__types_prosemirror_transform": "^1.1.5",
+    "npm_package_devDependencies__types_prosemirror_history": "^1.0.3",
+    "npm_package_dependencies_readline": "^1.3.0",
+    "npm_package_dependencies__types_supercluster": "^7.1.0",
+    "COMMAND_MODE": "unix2003",
+    "npm_config_globalconfig": "/Users/smallwhale/.nvm/versions/node/v12.16.0/etc/npmrc",
+    "npm_package_dependencies_depcheck": "^0.9.2",
+    "npm_package_dependencies__types_web": "0.0.53",
+    "npm_config_prefer_online": "",
+    "npm_config_logs_max": "10",
+    "npm_config_always_auth": "",
+    "npm_package_dependencies_react_icons": "^4.3.1",
+    "npm_package_dependencies_passport_google_oauth20": "^2.0.0",
+    "npm_package_devDependencies_webpack_dev_server": "^3.11.3",
+    "npm_package_devDependencies__types_brotli": "^1.3.1",
+    "npm_package_dependencies_url_loader": "^1.1.2",
+    "npm_package_dependencies_stream_browserify": "^3.0.0",
+    "npm_package_dependencies_prosemirror_transform": "^1.3.4",
+    "npm_package_dependencies_lodash": "^4.17.21",
+    "npm_package_dependencies_i": "^0.3.7",
+    "npm_package_devDependencies_tslint_loader": "^3.6.0",
+    "SSH_AUTH_SOCK": "/private/tmp/com.apple.launchd.iMTrb4REX6/Listeners",
+    "npm_package_dependencies_words_to_numbers": "^1.5.1",
+    "npm_package_dependencies_valid_url": "^1.0.9",
+    "npm_package_dependencies_styled_components": "^4.4.1",
+    "npm_package_dependencies_csv_parser": "^3.0.0",
+    "npm_package_dependencies_class_transformer": "^0.2.0",
+    "npm_package_devDependencies_eslint": "^8.36.0",
+    "npm_package_devDependencies__types_prosemirror_inputrules": "^1.0.4",
+    "npm_package_devDependencies__types_express": "^4.17.13",
+    "__CF_USER_TEXT_ENCODING": "0x1F5:0x0:0x0",
+    "npm_execpath": "/Users/smallwhale/.nvm/versions/node/v12.16.0/lib/node_modules/npm/bin/npm-cli.js",
+    "npm_config_global_style": "",
+    "npm_config_cache_lock_retries": "10",
+    "npm_package_dependencies_wikijs": "^6.3.3",
+    "npm_package_dependencies_bluebird": "^3.7.2",
+    "npm_package_devDependencies__types_react_typist": "^2.0.3",
+    "npm_config_update_notifier": "true",
+    "npm_config_cafile": "",
+    "npm_package_dependencies_util": "^0.12.4",
+    "npm_package_dependencies_raw_loader": "^1.0.0",
+    "npm_package_dependencies_https_browserify": "^1.0.0",
+    "npm_package_dependencies_brotli": "^1.3.3",
+    "npm_package_dependencies__mui_material": "^5.13.1",
+    "npm_package_dependencies__fortawesome_react_fontawesome": "^0.2.0",
+    "npm_package_devDependencies__types_passport_google_oauth20": "^2.0.11",
+    "npm_package_dependencies_cors": "^2.8.5",
+    "npm_package_dependencies_bezier_js": "^4.1.1",
+    "npm_package_dependencies__fortawesome_free_brands_svg_icons": "^6.3.0",
+    "npm_config_heading": "npm",
+    "npm_config_audit_level": "low",
+    "npm_package_dependencies_chrome": "^0.1.0",
+    "npm_package_dependencies__react_three_fiber": "^6.2.3",
+    "npm_package_devDependencies_eslint_plugin_prettier": "^4.2.1",
+    "npm_package_devDependencies_copy_webpack_plugin": "^4.6.0",
+    "npm_package_devDependencies__types_react_measure": "^2.0.8",
+    "npm_package_devDependencies__types_react_dom": "^18.0.6",
+    "npm_package_devDependencies__types_mobile_detect": "^1.3.4",
+    "npm_config_searchlimit": "20",
+    "npm_config_read_only": "",
+    "npm_config_offline": "",
+    "npm_config_fetch_retry_mintimeout": "10000",
+    "npm_package_dependencies_react_typist": "^2.0.5",
+    "npm_package_dependencies_mobx_react_devtools": "^6.1.1",
+    "npm_package_dependencies_md5_file": "^5.0.0",
+    "npm_package_dependencies_forever_agent": "^0.6.1",
+    "npm_package_devDependencies__types_xregexp": "^4.4.0",
+    "npm_package_devDependencies__types_typescript": "^2.0.0",
+    "npm_package_devDependencies__types_request": "^2.48.8",
+    "npm_package_devDependencies__types_prosemirror_commands": "^1.0.4",
+    "npm_config_json": "",
+    "npm_config_access": "",
+    "npm_config_argv": "{\"remain\":[],\"cooked\":[\"start\"],\"original\":[\"start\"]}",
+    "npm_package_dependencies__fortawesome_free_solid_svg_icons": "^6.3.0",
+    "npm_package_devDependencies__types_socket_io": "^2.1.13",
+    "PATH": "/Users/smallwhale/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/npm-lifecycle/node-gyp-bin:/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/.bin:/Users/smallwhale/.nvm/versions/node/v12.16.0/bin:/Users/smallwhale/.nvm/versions/node/v18.16.0/bin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Users/smallwhale/.nvm/versions/node/v18.16.0/bin:/Library/Frameworks/Python.framework/Versions/3.11/bin:/opt/homebrew/bin:/opt/homebrew/sbin",
+    "npm_config_allow_same_version": "",
+    "npm_package_dependencies_webrtc_adapter": "^7.7.1",
+    "npm_package_dependencies_react_reveal": "^1.2.2",
+    "npm_package_dependencies_prosemirror_schema_list": "^1.1.6",
+    "npm_package_dependencies__material_ui_core": "^4.12.3",
+    "npm_package_devDependencies__types_rimraf": "^2.0.5",
+    "npm_package_devDependencies__types_connect_flash": "0.0.34",
+    "npm_config_https_proxy": "",
+    "npm_config_engine_strict": "",
+    "npm_config_description": "true",
+    "npm_package_dependencies_pug": "^2.0.4",
+    "npm_package_dependencies_prosemirror_keymap": "^1.1.5",
+    "npm_package_dependencies_pdfjs_dist": "^2.14.305",
+    "npm_package_dependencies_mobile_detect": "^1.4.5",
+    "npm_package_dependencies_image_size_stream": "^1.1.0",
+    "npm_package_dependencies_golden_layout": "^1.5.9",
+    "npm_package_dependencies_child_process": "^1.0.2",
+    "npm_package_dependencies__types_d3_axis": "^2.1.3",
+    "_": "/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/.bin/cross-env",
+    "LaunchInstanceID": "57D8446D-0E37-4DF2-8110-E0AA2FF019D8",
+    "npm_config_userconfig": "/Users/smallwhale/.npmrc",
+    "npm_config_init_module": "/Users/smallwhale/.npm-init.js",
+    "npm_package_dependencies__react_google_maps_api": "^2.7.0",
+    "USER_ZDOTDIR": "/Users/smallwhale",
+    "__CFBundleIdentifier": "com.microsoft.VSCode",
+    "npm_config_cidr": "",
+    "npm_package_dependencies_puppeteer": "^3.3.0",
+    "npm_package_dependencies_prosemirror_view": "^1.26.5",
+    "npm_package_dependencies_mongodb": "^3.7.3",
+    "npm_package_dependencies_google_auth_library": "^4.2.4",
+    "npm_package_dependencies_bootstrap": "^4.6.1",
+    "npm_package_devDependencies_eslint_config_airbnb": "^19.0.4",
+    "PWD": "/Users/smallwhale/Desktop/Projects/Dash-Web",
+    "npm_config_user": "501",
+    "npm_config_node_version": "12.16.0",
+    "npm_package_dependencies_node_sass": "^4.14.1",
+    "npm_package_dependencies_howler": "^2.2.3",
+    "npm_package_dependencies_expressjs": "^1.0.1",
+    "npm_package_dependencies_core_js": "^3.28.0",
+    "npm_package_dependencies_browndash_components": "0.0.82",
+    "npm_package_devDependencies_eslint_plugin_react_hooks": "^4.6.0",
+    "npm_package_devDependencies__types_lodash": "^4.14.179",
+    "npm_lifecycle_event": "start",
+    "npm_package_dependencies_react_table": "^6.11.5",
+    "npm_package_dependencies_react_loading": "^2.0.3",
+    "npm_package_dependencies_mobx": "^5.15.7",
+    "npm_package_dependencies_babel": "^6.23.0",
+    "npm_package_devDependencies_jsdom": "^15.2.1",
+    "npm_package_devDependencies_chai": "^4.3.6",
+    "npm_config_save": "true",
+    "npm_config_ignore_prepublish": "",
+    "npm_config_editor": "vi",
+    "npm_config_auth_type": "legacy",
+    "npm_package_dependencies_npm": "^6.14.18",
+    "npm_package_dependencies_node_stream_zip": "^1.15.0",
+    "npm_package_dependencies_image_data_uri": "^2.0.1",
+    "npm_package_scripts_start_release": "cross-env RELEASE=true NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev -- src/server/index.ts",
+    "npm_package_name": "dash",
+    "LANG": "en_US.UTF-8",
+    "npm_config_tag": "latest",
+    "npm_config_script_shell": "",
+    "npm_package_dependencies_query_string": "^6.14.1",
+    "npm_package_dependencies_mobx_utils": "^5.6.2",
+    "npm_package_dependencies_file_saver": "^2.0.5",
+    "npm_package_dependencies_body_parser": "^1.19.2",
+    "npm_package_dependencies__types_reveal": "^3.3.33",
+    "npm_package_devDependencies_eslint_plugin_import": "^2.26.0",
+    "npm_package_devDependencies__types_prosemirror_view": "^1.23.1",
+    "npm_config_progress": "true",
+    "npm_config_global": "",
+    "npm_config_before": "",
+    "npm_package_dependencies_xoauth2": "^1.2.0",
+    "npm_package_dependencies_standard_http_error": "^2.0.1",
+    "npm_package_dependencies_react_loader_spinner": "^5.3.4",
+    "npm_package_dependencies_http_browserify": "^1.7.0",
+    "npm_package_dependencies__types_d3_selection": "^2.0.1",
+    "npm_package_dependencies__hig_flyout": "^1.3.1",
+    "npm_package_devDependencies_fork_ts_checker_webpack_plugin": "^1.6.0",
+    "npm_package_scripts_build": "cross-env NODE_OPTIONS=--max_old_space_size=8192 webpack --env production",
+    "npm_package_scripts_start": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
+    "npm_config_searchstaleness": "900",
+    "npm_config_optional": "true",
+    "npm_config_ham_it_up": "",
+    "npm_package_dependencies_sharp": "^0.23.4",
+    "npm_package_dependencies_rc_switch": "^1.9.2",
+    "npm_package_dependencies_googlephotos": "^0.2.5",
+    "npm_package_dependencies_exifr": "^7.1.3",
+    "npm_package_dependencies__types_google_maps": "^3.2.3",
+    "npm_package_dependencies__types_bezier_js": "^4.1.0",
+    "npm_package_dependencies__ffmpeg_core": "0.10.0",
+    "npm_package_devDependencies_ts_loader": "^5.3.3",
+    "npm_package_devDependencies__types_bcrypt_nodejs": "0.0.30",
+    "VSCODE_GIT_ASKPASS_EXTRA_ARGS": "--ms-enable-electron-run-as-node",
+    "XPC_FLAGS": "0x0",
+    "npm_config_save_prod": "",
+    "npm_config_force": "",
+    "npm_config_bin_links": "true",
+    "npm_package_devDependencies__types_youtube": "0.0.39",
+    "npm_config_searchopts": "",
+    "npm_package_dependencies_react_beautiful_dnd": "^13.1.0",
+    "npm_package_dependencies_jszip": "^3.7.1",
+    "npm_package_dependencies_csv_stringify": "^6.3.0",
+    "npm_package_devDependencies__types_react_icons": "^3.0.0",
+    "npm_config_node_gyp": "/Users/smallwhale/.nvm/versions/node/v12.16.0/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js",
+    "npm_config_depth": "Infinity",
+    "npm_package_dependencies_google_maps_react": "^2.0.6",
+    "npm_package_dependencies_express_session": "^1.17.2",
+    "npm_package_devDependencies_eslint_plugin_node": "^11.1.0",
+    "npm_package_devDependencies_eslint_config_prettier": "^8.5.0",
+    "npm_package_main": "index.js",
+    "npm_config_sso_poll_frequency": "500",
+    "npm_config_rebuild_bundle": "true",
+    "npm_package_dependencies_chart_js": "^3.8.0",
+    "npm_package_dependencies__emotion_react": "^11.11.0",
+    "npm_package_devDependencies__types_prosemirror_menu": "^1.0.6",
+    "npm_package_devDependencies__types_prosemirror_keymap": "^1.0.4",
+    "npm_package_devDependencies__types_pdfjs_dist": "^2.10.378",
+    "npm_package_devDependencies__types_exif": "^0.6.3",
+    "npm_package_version": "1.0.0",
+    "XPC_SERVICE_NAME": "0",
+    "npm_config_unicode": "true",
+    "VSCODE_INJECTION": "1",
+    "npm_package_dependencies_typescript_language_server": "^0.4.0",
+    "npm_package_dependencies_prosemirror_model": "^1.18.1",
+    "npm_package_dependencies__ffmpeg_ffmpeg": "0.10.0",
+    "SHLVL": "4",
+    "HOME": "/Users/smallwhale",
+    "npm_config_fetch_retry_maxtimeout": "60000",
+    "npm_package_dependencies_request_promise": "^4.2.6",
+    "npm_package_dependencies_react_markdown": "^8.0.3",
+    "npm_package_dependencies__hig_theme_context": "^2.1.3",
+    "npm_package_devDependencies__types_react_autosuggest": "^9.3.14",
+    "npm_package_devDependencies__types_mongoose": "^5.11.97",
+    "npm_package_devDependencies__types_d3": "^7.4.0",
+    "npm_package_devDependencies__types_animejs": "^2.0.2",
+    "npm_package_scripts_test": "mocha -r ts-node/register test/**/*.ts",
+    "VSCODE_GIT_ASKPASS_MAIN": "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass-main.js",
+    "npm_config_tag_version_prefix": "v",
+    "npm_config_strict_ssl": "true",
+    "npm_config_sso_type": "oauth",
+    "npm_config_scripts_prepend_node_path": "warn-only",
+    "npm_config_save_prefix": "^",
+    "npm_config_loglevel": "notice",
+    "npm_config_ca": "",
+    "npm_package_dependencies_three": "^0.127.0",
+    "npm_package_dependencies_openai": "^3.2.1",
+    "npm_package_dependencies_mobx_react": "^5.4.4",
+    "npm_package_dependencies_cookie_parser": "^1.4.6",
+    "npm_package_dependencies_adm_zip": "^0.4.16",
+    "npm_package_devDependencies_eslint_config_node": "^4.1.0",
+    "npm_config_save_exact": "",
+    "npm_config_group": "20",
+    "npm_config_fetch_retry_factor": "10",
+    "npm_config_dev": "",
+    "npm_package_devDependencies_webpack_hot_middleware": "^2.25.1",
+    "npm_package_devDependencies_cross_env": "^5.2.1",
+    "HOMEBREW_PREFIX": "/opt/homebrew",
+    "npm_config_version": "",
+    "npm_config_prefer_offline": "",
+    "npm_config_cache_lock_stale": "60000",
+    "npm_package_devDependencies__types_prosemirror_state": "^1.2.8",
+    "npm_package_devDependencies__types_body_parser": "^1.19.2",
+    "npm_config_otp": "",
+    "npm_config_cache_min": "10",
+    "npm_package_dependencies_react_color": "^2.19.3",
+    "npm_package_dependencies_d3": "^7.6.1",
+    "npm_package_devDependencies_ts_node": "^10.9.1",
+    "npm_package_devDependencies__types_react_grid_layout": "^1.3.2",
+    "npm_config_searchexclude": "",
+    "npm_config_cache": "/Users/smallwhale/.npm",
+    "npm_package_dependencies_tough_cookie": "^4.0.0",
+    "npm_package_dependencies_googleapis": "^40.0.0",
+    "npm_package_devDependencies__types_valid_url": "^1.0.3",
+    "npm_package_devDependencies__types_passport": "^1.0.9",
+    "npm_package_devDependencies__types_adm_zip": "^0.4.34",
+    "LOGNAME": "smallwhale",
+    "npm_lifecycle_script": "cross-env NODE_OPTIONS=--max_old_space_size=4096 ts-node-dev --debug --transpile-only -- src/server/index.ts",
+    "npm_config_color": "true",
+    "npm_package_dependencies_solr_node": "^1.2.1",
+    "npm_package_dependencies_react_transition_group": "^4.4.2",
+    "npm_package_dependencies_iink_js": "^1.5.4",
+    "npm_package_dependencies_html_webpack_plugin": "^5.5.0",
+    "npm_config_proxy": "",
+    "npm_config_package_lock": "true",
+    "npm_package_dependencies_prosemirror_state": "^1.4.1",
+    "npm_package_dependencies_nodemon": "^1.19.4",
+    "npm_package_dependencies_function_plot": "^1.22.8",
+    "npm_package_dependencies_equation_editor_react": "github:bobzel/equation-editor-react#useLocally",
+    "npm_package_devDependencies__types_socket_io_parser": "^3.0.0",
+    "VSCODE_GIT_IPC_HANDLE": "/var/folders/bv/1xck6d7j7bz14bvhgdsllbj40000gn/T/vscode-git-b8c8318074.sock",
+    "npm_config_package_lock_only": "",
+    "npm_config_fund": "true",
+    "npm_package_dependencies_react": "^18.2.0",
+    "npm_package_dependencies_bingmaps_react": "^1.2.10",
+    "npm_package_devDependencies_scss_loader": "0.0.1",
+    "npm_package_devDependencies__types_cookie_session": "^2.0.44",
+    "npm_config_save_optional": "",
+    "npm_package_dependencies_textarea_caret": "^3.1.0",
+    "npm_package_dependencies_react_measure": "^2.5.2",
+    "npm_package_dependencies_exif": "^0.6.0",
+    "NVM_BIN": "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin",
+    "npm_config_ignore_scripts": "",
+    "npm_config_user_agent": "npm/6.13.4 node/v12.16.0 darwin x64",
+    "npm_package_dependencies_react_resizable": "^1.11.1",
+    "npm_package_dependencies_prosemirror_commands": "^1.2.1",
+    "npm_package_dependencies_memorystream": "^0.3.1",
+    "npm_package_dependencies_formidable": "1.2.1",
+    "npm_package_devDependencies__types_uuid": "^3.4.10",
+    "npm_config_cache_lock_wait": "10000",
+    "npm_package_dependencies_socket_io_client": "^2.5.0",
+    "npm_package_dependencies_recharts": "^2.1.12",
+    "npm_package_dependencies_react_chartjs_2": "^4.3.0",
+    "npm_package_dependencies_fluent_ffmpeg": "^2.1.2",
+    "npm_package_dependencies__types_cors": "^2.8.12",
+    "npm_package_devDependencies__types_node": "^10.17.60",
+    "npm_package_devDependencies__types_file_saver": "^2.0.5",
+    "VSCODE_GIT_ASKPASS_NODE": "/Applications/Visual Studio Code.app/Contents/Frameworks/Code Helper (Plugin).app/Contents/MacOS/Code Helper (Plugin)",
+    "GIT_ASKPASS": "/Applications/Visual Studio Code.app/Contents/Resources/app/extensions/git/dist/askpass.sh",
+    "HOMEBREW_CELLAR": "/opt/homebrew/Cellar",
+    "INFOPATH": "/opt/homebrew/share/info:/opt/homebrew/share/info:",
+    "npm_config_production": "",
+    "npm_package_dependencies_jsonschema": "^1.4.0",
+    "npm_package_dependencies_ffmpeg": "0.0.4",
+    "npm_package_dependencies_cookie_session": "^2.0.0",
+    "npm_package_dependencies_color": "^3.2.1",
+    "npm_package_devDependencies__types_webpack": "^4.41.32",
+    "npm_package_devDependencies__types_request_promise": "^4.1.48",
+    "npm_package_devDependencies__types_prosemirror_schema_list": "^1.0.3",
+    "npm_config_send_metrics": "",
+    "npm_config_save_bundle": "",
+    "npm_package_dependencies_web_request": "^1.0.7",
+    "npm_package_dependencies_react_datepicker": "^3.8.0",
+    "npm_package_dependencies_express": "^4.17.3",
+    "npm_package_dependencies_D": "^1.0.0",
+    "npm_package_dependencies__types_formidable": "1.0.31",
+    "npm_package_devDependencies__types_rc_switch": "^1.9.2",
+    "npm_package_devDependencies__types_prosemirror_dev_tools": "^2.1.0",
+    "npm_package_devDependencies__types_jquery": "^3.5.14",
+    "npm_config_umask": "0022",
+    "npm_config_node_options": "",
+    "npm_config_init_version": "1.0.0",
+    "npm_package_dependencies_https": "^1.0.0",
+    "npm_package_dependencies_array_batcher": "^1.2.3",
+    "npm_package_dependencies__fortawesome_free_regular_svg_icons": "^6.3.0",
+    "npm_package_devDependencies__types_shelljs": "^0.8.11",
+    "npm_package_devDependencies__types_libxmljs": "^0.18.7",
+    "npm_package_devDependencies__types_express_validator": "^3.0.0",
+    "npm_package_devDependencies__types_bluebird": "^3.5.36",
+    "npm_config_init_author_name": "",
+    "npm_config_git": "git",
+    "npm_config_scope": "",
+    "npm_package_dependencies_react_select": "^3.2.0",
+    "npm_package_dependencies_pdf_parse": "^1.1.1",
+    "npm_package_dependencies_colors": "^1.4.0",
+    "npm_package_dependencies_archiver": "^3.1.1",
+    "npm_package_devDependencies_css_loader": "^2.1.1",
+    "npm_package_devDependencies__types_socket_io_client": "^1.4.36",
+    "SECURITYSESSIONID": "186a4",
+    "npm_config_unsafe_perm": "true",
+    "npm_config_tmp": "/var/folders/bv/1xck6d7j7bz14bvhgdsllbj40000gn/T",
+    "npm_config_onload_script": "",
+    "npm_package_dependencies_serializr": "^1.5.4",
+    "npm_package_dependencies_fit_curve": "^0.1.7",
+    "npm_package_dependencies__webscopeio_react_textarea_autocomplete": "^4.9.1",
+    "npm_package_dependencies__types_three": "^0.126.2",
+    "npm_package_devDependencies_ts_node_dev": "^2.0.0",
+    "npm_node_execpath": "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node",
+    "npm_config_prefix": "/Users/smallwhale/.nvm/versions/node/v12.16.0",
+    "npm_config_link": "",
+    "npm_config_format_package_lock": "true",
+    "npm_package_dependencies_passport": "^0.4.0",
+    "npm_package_devDependencies_eslint_plugin_react": "^7.30.1",
+    "npm_package_devDependencies__types_react_table": "^6.8.9",
+    "npm_package_devDependencies__types_react_reconciler": "^0.26.4",
+    "COLORTERM": "truecolor",
+    "NODE_OPTIONS": "--max_old_space_size=4096",
+    "TS_NODE_DEV": "true",
+    "_CLIENT_OPENAI_KEY": "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",
+    "_CLIENT_GITHUB_ACCESS_TOKEN": "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",
+    "VIPSHOME": "/usr/local/Cellar/vips/8.8.1",
+    "TYPESCRIPT_PATH": "/Users/smallwhale/Desktop/Projects/Dash-Web/node_modules/typescript/lib/typescript.js",
+    "TSCONFIG": "/Users/smallwhale/Desktop/Projects/Dash-Web/tsconfig.json",
+    "COMPILER_OPTIONS": "{}",
+    "TSLINT": "true",
+    "CONTEXT": "/Users/smallwhale/Desktop/Projects/Dash-Web",
+    "TSLINTAUTOFIX": "false",
+    "ESLINT": "false",
+    "ESLINT_OPTIONS": "{}",
+    "WATCH": "",
+    "WORK_DIVISION": "1",
+    "MEMORY_LIMIT": "2048",
+    "CHECK_SYNTACTIC_ERRORS": "false",
+    "USE_INCREMENTAL_API": "true",
+    "VUE": "false"
+  },
+  "userLimits": {
+    "core_file_size_blocks": {
+      "soft": 0,
+      "hard": "unlimited"
+    },
+    "data_seg_size_kbytes": {
+      "soft": "unlimited",
+      "hard": "unlimited"
+    },
+    "file_size_blocks": {
+      "soft": "unlimited",
+      "hard": "unlimited"
+    },
+    "max_locked_memory_bytes": {
+      "soft": "unlimited",
+      "hard": "unlimited"
+    },
+    "max_memory_size_kbytes": {
+      "soft": "unlimited",
+      "hard": "unlimited"
+    },
+    "open_files": {
+      "soft": 1048575,
+      "hard": "unlimited"
+    },
+    "stack_size_bytes": {
+      "soft": 8372224,
+      "hard": 67092480
+    },
+    "cpu_time_seconds": {
+      "soft": "unlimited",
+      "hard": "unlimited"
+    },
+    "max_user_processes": {
+      "soft": 1333,
+      "hard": 2000
+    },
+    "virtual_memory_kbytes": {
+      "soft": "unlimited",
+      "hard": "unlimited"
+    }
+  },
+  "sharedObjects": [
+    "/Users/smallwhale/.nvm/versions/node/v12.16.0/bin/node",
+    "/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation",
+    "/usr/lib/libobjc.A.dylib",
+    "/System/Library/PrivateFrameworks/CoreServicesInternal.framework/Versions/A/CoreServicesInternal",
+    "/usr/lib/liboah.dylib",
+    "/usr/lib/libfakelink.dylib",
+    "/usr/lib/libicucore.A.dylib",
+    "/usr/lib/libSystem.B.dylib",
+    "/System/Library/PrivateFrameworks/SoftLinking.framework/Versions/A/SoftLinking",
+    "/usr/lib/libc++abi.dylib",
+    "/usr/lib/libc++.1.dylib",
+    "/usr/lib/system/libcache.dylib",
+    "/usr/lib/system/libcommonCrypto.dylib",
+    "/usr/lib/system/libcompiler_rt.dylib",
+    "/usr/lib/system/libcopyfile.dylib",
+    "/usr/lib/system/libcorecrypto.dylib",
+    "/usr/lib/system/libdispatch.dylib",
+    "/usr/lib/system/libdyld.dylib",
+    "/usr/lib/system/libkeymgr.dylib",
+    "/usr/lib/system/libmacho.dylib",
+    "/usr/lib/system/libquarantine.dylib",
+    "/usr/lib/system/libremovefile.dylib",
+    "/usr/lib/system/libsystem_asl.dylib",
+    "/usr/lib/system/libsystem_blocks.dylib",
+    "/usr/lib/system/libsystem_c.dylib",
+    "/usr/lib/system/libsystem_collections.dylib",
+    "/usr/lib/system/libsystem_configuration.dylib",
+    "/usr/lib/system/libsystem_containermanager.dylib",
+    "/usr/lib/system/libsystem_coreservices.dylib",
+    "/usr/lib/system/libsystem_darwin.dylib",
+    "/usr/lib/system/libsystem_dnssd.dylib",
+    "/usr/lib/system/libsystem_featureflags.dylib",
+    "/usr/lib/system/libsystem_info.dylib",
+    "/usr/lib/system/libsystem_m.dylib",
+    "/usr/lib/system/libsystem_malloc.dylib",
+    "/usr/lib/system/libsystem_networkextension.dylib",
+    "/usr/lib/system/libsystem_notify.dylib",
+    "/usr/lib/system/libsystem_sandbox.dylib",
+    "/usr/lib/system/libsystem_secinit.dylib",
+    "/usr/lib/system/libsystem_kernel.dylib",
+    "/usr/lib/system/libsystem_platform.dylib",
+    "/usr/lib/system/libsystem_pthread.dylib",
+    "/usr/lib/system/libsystem_symptoms.dylib",
+    "/usr/lib/system/libsystem_trace.dylib",
+    "/usr/lib/system/libunwind.dylib",
+    "/usr/lib/system/libxpc.dylib",
+    "/System/Library/Frameworks/IOKit.framework/Versions/A/IOKit",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/CoreServices",
+    "/usr/lib/libDiagnosticMessagesClient.dylib",
+    "/usr/lib/libenergytrace.dylib",
+    "/usr/lib/libbsm.0.dylib",
+    "/usr/lib/libz.1.dylib",
+    "/usr/lib/system/libkxld.dylib",
+    "/System/Library/Frameworks/CFNetwork.framework/Versions/A/CFNetwork",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/FSEvents.framework/Versions/A/FSEvents",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/Metadata.framework/Versions/A/Metadata",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/OSServices.framework/Versions/A/OSServices",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SearchKit.framework/Versions/A/SearchKit",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/AE.framework/Versions/A/AE",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/LaunchServices.framework/Versions/A/LaunchServices",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/DictionaryServices.framework/Versions/A/DictionaryServices",
+    "/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/SharedFileList.framework/Versions/A/SharedFileList",
+    "/System/Library/Frameworks/Security.framework/Versions/A/Security",
+    "/System/Library/Frameworks/SystemConfiguration.framework/Versions/A/SystemConfiguration",
+    "/usr/lib/libapple_nghttp2.dylib",
+    "/usr/lib/libcompression.dylib",
+    "/usr/lib/libnetwork.dylib",
+    "/usr/lib/libsqlite3.dylib",
+    "/System/Library/Frameworks/Foundation.framework/Versions/C/Foundation",
+    "/System/Library/Frameworks/Network.framework/Versions/A/Network",
+    "/usr/lib/libCoreEntitlements.dylib",
+    "/System/Library/PrivateFrameworks/MessageSecurity.framework/Versions/A/MessageSecurity",
+    "/System/Library/PrivateFrameworks/ProtocolBuffer.framework/Versions/A/ProtocolBuffer",
+    "/usr/lib/libMobileGestalt.dylib",
+    "/System/Library/PrivateFrameworks/AppleFSCompression.framework/Versions/A/AppleFSCompression",
+    "/usr/lib/libcoretls.dylib",
+    "/usr/lib/libcoretls_cfhelpers.dylib",
+    "/usr/lib/libpam.2.dylib",
+    "/usr/lib/libxar.1.dylib",
+    "/System/Library/PrivateFrameworks/CoreAutoLayout.framework/Versions/A/CoreAutoLayout",
+    "/System/Library/Frameworks/DiskArbitration.framework/Versions/A/DiskArbitration",
+    "/usr/lib/libarchive.2.dylib",
+    "/usr/lib/libxml2.2.dylib",
+    "/usr/lib/liblangid.dylib",
+    "/System/Library/Frameworks/Combine.framework/Versions/A/Combine",
+    "/usr/lib/swift/libswiftCore.dylib",
+    "/usr/lib/swift/libswiftCoreFoundation.dylib",
+    "/usr/lib/swift/libswiftDarwin.dylib",
+    "/usr/lib/swift/libswiftDispatch.dylib",
+    "/usr/lib/swift/libswiftIOKit.dylib",
+    "/usr/lib/swift/libswiftObjectiveC.dylib",
+    "/usr/lib/swift/libswiftXPC.dylib",
+    "/usr/lib/swift/libswift_Concurrency.dylib",
+    "/usr/lib/swift/libswift_StringProcessing.dylib",
+    "/usr/lib/swift/libswiftos.dylib",
+    "/System/Library/PrivateFrameworks/AppleSystemInfo.framework/Versions/A/AppleSystemInfo",
+    "/System/Library/PrivateFrameworks/IOMobileFramebuffer.framework/Versions/A/IOMobileFramebuffer",
+    "/System/Library/Frameworks/IOSurface.framework/Versions/A/IOSurface",
+    "/usr/lib/libpcap.A.dylib",
+    "/usr/lib/libdns_services.dylib",
+    "/usr/lib/liblzma.5.dylib",
+    "/usr/lib/libbz2.1.0.dylib",
+    "/usr/lib/libiconv.2.dylib",
+    "/usr/lib/libcharset.1.dylib",
+    "/usr/lib/swift/libswift_RegexParser.dylib",
+    "/usr/lib/libheimdal-asn1.dylib",
+    "/usr/lib/libCheckFix.dylib",
+    "/System/Library/PrivateFrameworks/TCC.framework/Versions/A/TCC",
+    "/System/Library/PrivateFrameworks/CoreNLP.framework/Versions/A/CoreNLP",
+    "/System/Library/PrivateFrameworks/MetadataUtilities.framework/Versions/A/MetadataUtilities",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Accelerate",
+    "/usr/lib/libmecab.dylib",
+    "/usr/lib/libCRFSuite.dylib",
+    "/usr/lib/libgermantok.dylib",
+    "/usr/lib/libThaiTokenizer.dylib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vImage.framework/Versions/A/vImage",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/vecLib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvMisc.dylib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libvDSP.dylib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLAPACK.dylib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libLinearAlgebra.dylib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparseBLAS.dylib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libQuadrature.dylib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBNNS.dylib",
+    "/System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libSparse.dylib",
+    "/System/Library/PrivateFrameworks/MIL.framework/Versions/A/MIL",
+    "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/Frameworks/CFOpenDirectory.framework/Versions/A/CFOpenDirectory",
+    "/System/Library/Frameworks/OpenDirectory.framework/Versions/A/OpenDirectory",
+    "/System/Library/PrivateFrameworks/APFS.framework/Versions/A/APFS",
+    "/System/Library/Frameworks/SecurityFoundation.framework/Versions/A/SecurityFoundation",
+    "/usr/lib/libutil.dylib",
+    "/System/Library/PrivateFrameworks/InstalledContentLibrary.framework/Versions/A/InstalledContentLibrary",
+    "/System/Library/PrivateFrameworks/CoreServicesStore.framework/Versions/A/CoreServicesStore",
+    "/usr/lib/libapp_launch_measurement.dylib",
+    "/System/Library/PrivateFrameworks/AppleMobileFileIntegrity.framework/Versions/A/AppleMobileFileIntegrity",
+    "/usr/lib/libmis.dylib",
+    "/System/Library/PrivateFrameworks/MobileSystemServices.framework/Versions/A/MobileSystemServices",
+    "/System/Library/PrivateFrameworks/ConfigProfileHelper.framework/Versions/A/ConfigProfileHelper",
+    "/System/Library/PrivateFrameworks/CoreAnalytics.framework/Versions/A/CoreAnalytics",
+    "/System/Library/PrivateFrameworks/AppleSauce.framework/Versions/A/AppleSauce",
+    "/System/Library/PrivateFrameworks/LanguageModeling.framework/Versions/A/LanguageModeling",
+    "/usr/lib/libxslt.1.dylib",
+    "/usr/lib/libcmph.dylib",
+    "/System/Library/PrivateFrameworks/CoreEmoji.framework/Versions/A/CoreEmoji",
+    "/System/Library/PrivateFrameworks/LinguisticData.framework/Versions/A/LinguisticData",
+    "/System/Library/PrivateFrameworks/Lexicon.framework/Versions/A/Lexicon",
+    "/System/Library/PrivateFrameworks/BackgroundTaskManagement.framework/Versions/A/BackgroundTaskManagement",
+    "/usr/lib/libTLE.dylib",
+    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices",
+    "/System/Library/Frameworks/CoreGraphics.framework/Versions/A/CoreGraphics",
+    "/System/Library/Frameworks/CoreText.framework/Versions/A/CoreText",
+    "/System/Library/Frameworks/ImageIO.framework/Versions/A/ImageIO",
+    "/System/Library/Frameworks/ColorSync.framework/Versions/A/ColorSync",
+    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/ATS",
+    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/HIServices.framework/Versions/A/HIServices",
+    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/PrintCore.framework/Versions/A/PrintCore",
+    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/QD.framework/Versions/A/QD",
+    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ColorSyncLegacy.framework/Versions/A/ColorSyncLegacy",
+    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/SpeechSynthesis.framework/Versions/A/SpeechSynthesis",
+    "/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/SkyLight",
+    "/System/Library/PrivateFrameworks/FontServices.framework/libFontParser.dylib",
+    "/System/Library/PrivateFrameworks/RunningBoardServices.framework/Versions/A/RunningBoardServices",
+    "/System/Library/PrivateFrameworks/IOSurfaceAccelerator.framework/Versions/A/IOSurfaceAccelerator",
+    "/System/Library/PrivateFrameworks/WatchdogClient.framework/Versions/A/WatchdogClient",
+    "/System/Library/Frameworks/CoreDisplay.framework/Versions/A/CoreDisplay",
+    "/System/Library/Frameworks/CoreMedia.framework/Versions/A/CoreMedia",
+    "/System/Library/PrivateFrameworks/IOAccelerator.framework/Versions/A/IOAccelerator",
+    "/System/Library/Frameworks/Metal.framework/Versions/A/Metal",
+    "/System/Library/Frameworks/CoreVideo.framework/Versions/A/CoreVideo",
+    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/MetalPerformanceShaders",
+    "/System/Library/PrivateFrameworks/MultitouchSupport.framework/Versions/A/MultitouchSupport",
+    "/System/Library/Frameworks/QuartzCore.framework/Versions/A/QuartzCore",
+    "/System/Library/Frameworks/VideoToolbox.framework/Versions/A/VideoToolbox",
+    "/System/Library/PrivateFrameworks/BaseBoard.framework/Versions/A/BaseBoard",
+    "/System/Library/PrivateFrameworks/AppleJPEG.framework/Versions/A/AppleJPEG",
+    "/usr/lib/libexpat.1.dylib",
+    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libPng.dylib",
+    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libTIFF.dylib",
+    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libGIF.dylib",
+    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJP2.dylib",
+    "/usr/lib/libate.dylib",
+    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libJPEG.dylib",
+    "/System/Library/Frameworks/ImageIO.framework/Versions/A/Resources/libRadiance.dylib",
+    "/System/Library/PrivateFrameworks/GPUWrangler.framework/Versions/A/GPUWrangler",
+    "/System/Library/PrivateFrameworks/IOPresentment.framework/Versions/A/IOPresentment",
+    "/System/Library/PrivateFrameworks/DSExternalDisplay.framework/Versions/A/DSExternalDisplay",
+    "/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/31001/Libraries/libllvm-flatbuffers.dylib",
+    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreFSCache.dylib",
+    "/System/Library/PrivateFrameworks/GPUCompiler.framework/Versions/31001/Libraries/libGPUCompilerUtils.dylib",
+    "/System/Library/PrivateFrameworks/CMCaptureCore.framework/Versions/A/CMCaptureCore",
+    "/usr/lib/libspindump.dylib",
+    "/System/Library/Frameworks/CoreAudio.framework/Versions/A/CoreAudio",
+    "/System/Library/Frameworks/ExtensionFoundation.framework/Versions/A/ExtensionFoundation",
+    "/System/Library/PrivateFrameworks/CoreTime.framework/Versions/A/CoreTime",
+    "/System/Library/PrivateFrameworks/AppServerSupport.framework/Versions/A/AppServerSupport",
+    "/System/Library/PrivateFrameworks/perfdata.framework/Versions/A/perfdata",
+    "/System/Library/PrivateFrameworks/AudioToolboxCore.framework/Versions/A/AudioToolboxCore",
+    "/System/Library/PrivateFrameworks/caulk.framework/Versions/A/caulk",
+    "/usr/lib/libAudioStatistics.dylib",
+    "/System/Library/PrivateFrameworks/SystemPolicy.framework/Versions/A/SystemPolicy",
+    "/usr/lib/libSMC.dylib",
+    "/System/Library/Frameworks/CoreMIDI.framework/Versions/A/CoreMIDI",
+    "/usr/lib/libAudioToolboxUtility.dylib",
+    "/System/Library/PrivateFrameworks/OSAServicesClient.framework/Versions/A/OSAServicesClient",
+    "/usr/lib/libperfcheck.dylib",
+    "/System/Library/PrivateFrameworks/PlugInKit.framework/Versions/A/PlugInKit",
+    "/System/Library/PrivateFrameworks/AssertionServices.framework/Versions/A/AssertionServices",
+    "/System/Library/Frameworks/OpenGL.framework/Versions/A/OpenGL",
+    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLU.dylib",
+    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGFXShared.dylib",
+    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGL.dylib",
+    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libGLImage.dylib",
+    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCVMSPluginSupport.dylib",
+    "/usr/lib/libRosetta.dylib",
+    "/System/Library/Frameworks/OpenGL.framework/Versions/A/Libraries/libCoreVMClient.dylib",
+    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSCore.framework/Versions/A/MPSCore",
+    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSImage.framework/Versions/A/MPSImage",
+    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNeuralNetwork.framework/Versions/A/MPSNeuralNetwork",
+    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSMatrix.framework/Versions/A/MPSMatrix",
+    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSRayIntersector.framework/Versions/A/MPSRayIntersector",
+    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSNDArray.framework/Versions/A/MPSNDArray",
+    "/System/Library/Frameworks/MetalPerformanceShaders.framework/Versions/A/Frameworks/MPSFunctions.framework/Versions/A/MPSFunctions",
+    "/System/Library/PrivateFrameworks/MetalTools.framework/Versions/A/MetalTools",
+    "/System/Library/PrivateFrameworks/AggregateDictionary.framework/Versions/A/AggregateDictionary",
+    "/usr/lib/libIOReport.dylib",
+    "/System/Library/Frameworks/CoreImage.framework/Versions/A/CoreImage",
+    "/System/Library/PrivateFrameworks/PhotosensitivityProcessing.framework/Versions/A/PhotosensitivityProcessing",
+    "/System/Library/Frameworks/OpenCL.framework/Versions/A/OpenCL",
+    "/System/Library/PrivateFrameworks/GraphVisualizer.framework/Versions/A/GraphVisualizer",
+    "/System/Library/PrivateFrameworks/FontServices.framework/Versions/A/FontServices",
+    "/System/Library/Frameworks/UniformTypeIdentifiers.framework/Versions/A/UniformTypeIdentifiers",
+    "/System/Library/PrivateFrameworks/OTSVG.framework/Versions/A/OTSVG",
+    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATS.framework/Versions/A/Resources/libFontRegistry.dylib",
+    "/System/Library/PrivateFrameworks/FontServices.framework/libhvf.dylib",
+    "/System/Library/PrivateFrameworks/FontServices.framework/libXTFontStaticRegistryData.dylib",
+    "/System/Library/PrivateFrameworks/VideoToolboxParavirtualizationSupport.framework/Versions/A/VideoToolboxParavirtualizationSupport",
+    "/System/Library/PrivateFrameworks/AppleVA.framework/Versions/A/AppleVA",
+    "/System/Library/Frameworks/ApplicationServices.framework/Versions/A/Frameworks/ATSUI.framework/Versions/A/ATSUI",
+    "/usr/lib/libcups.2.dylib",
+    "/System/Library/Frameworks/Kerberos.framework/Versions/A/Kerberos",
+    "/System/Library/Frameworks/GSS.framework/Versions/A/GSS",
+    "/usr/lib/libresolv.9.dylib",
+    "/System/Library/PrivateFrameworks/Heimdal.framework/Versions/A/Heimdal",
+    "/System/Library/Frameworks/Kerberos.framework/Versions/A/Libraries/libHeimdalProxy.dylib",
+    "/System/Library/PrivateFrameworks/CommonAuth.framework/Versions/A/CommonAuth",
+    "/System/Library/Frameworks/AVFAudio.framework/Versions/A/AVFAudio",
+    "/System/Library/PrivateFrameworks/AXCoreUtilities.framework/Versions/A/AXCoreUtilities",
+    "/System/Library/Frameworks/AudioToolbox.framework/Versions/A/AudioToolbox",
+    "/System/Library/PrivateFrameworks/AudioSession.framework/Versions/A/AudioSession",
+    "/System/Library/Frameworks/IOBluetooth.framework/Versions/A/IOBluetooth",
+    "/System/Library/PrivateFrameworks/MediaExperience.framework/Versions/A/MediaExperience",
+    "/System/Library/PrivateFrameworks/AudioSession.framework/libSessionUtility.dylib",
+    "/System/Library/PrivateFrameworks/AudioResourceArbitration.framework/Versions/A/AudioResourceArbitration",
+    "/System/Library/PrivateFrameworks/PowerLog.framework/Versions/A/PowerLog",
+    "/System/Library/Frameworks/CoreData.framework/Versions/A/CoreData",
+    "/System/Library/Frameworks/CoreBluetooth.framework/Versions/A/CoreBluetooth",
+    "/System/Library/Frameworks/AudioUnit.framework/Versions/A/AudioUnit",
+    "/System/Library/PrivateFrameworks/CoreUtils.framework/Versions/A/CoreUtils",
+    "/System/Library/PrivateFrameworks/CoreUtilsExtras.framework/Versions/A/CoreUtilsExtras",
+    "/System/Library/PrivateFrameworks/IO80211.framework/Versions/A/IO80211",
+    "/System/Library/PrivateFrameworks/MobileKeyBag.framework/Versions/A/MobileKeyBag",
+    "/System/Library/PrivateFrameworks/MallocStackLogging.framework/Versions/A/MallocStackLogging"
+  ]
+}
\ No newline at end of file
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 18222b32a..6b4106f56 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -14,7 +14,7 @@ type GPTCallOpts = {
 };
 
 const callTypeMap: { [type: string]: GPTCallOpts } = {
-    summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text briefly: ' },
+    summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' },
     edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' },
     completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' },
 };
@@ -47,7 +47,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
     }
 };
 
-const gptImageCall = async (prompt: string) => {
+const gptImageCall = async (prompt: string, n?: number) => {
     try {
         const configuration = new Configuration({
             apiKey: process.env.OPENAI_KEY,
@@ -55,33 +55,15 @@ const gptImageCall = async (prompt: string) => {
         const openai = new OpenAIApi(configuration);
         const response = await openai.createImage({
             prompt: prompt,
-            n: 1,
+            n: n ?? 1,
             size: '1024x1024',
         });
-        return response.data.data[0].url;
+        return response.data.data.map(data => data.url);
+        // return response.data.data[0].url;
     } catch (err) {
         console.error(err);
         return;
     }
 };
 
-// const gptEditCall = async (selectedText: string, fullText: string) => {
-//     try {
-//         const configuration = new Configuration({
-//             apiKey: process.env.OPENAI_KEY,
-//         });
-//         const openai = new OpenAIApi(configuration);
-//         const response = await openai.createCompletion({
-//             model: 'text-davinci-003',
-//             max_tokens: 256,
-//             temperature: 0.1,
-//             prompt: `Replace the phrase ${selectedText} inside of ${fullText}.`,
-//         });
-//         return response.data.choices[0].text.trim();
-//     } catch (err) {
-//         console.log(err);
-//         return 'Error connecting with API.';
-//     }
-// };
-
 export { gptAPICall, gptImageCall, GPTCallType };
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 85101fcab..ac948c7c7 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -195,7 +195,7 @@ export namespace DragManager {
     }
 
     // drag a document and drop it (or make an embed/copy on drop)
-    export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, dropEvent?: () => any) {
+    export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) {
         const addAudioTag = (dropDoc: any) => {
             dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField());
             dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
@@ -203,7 +203,7 @@ export namespace DragManager {
         };
         const finishDrag = async (e: DragCompleteEvent) => {
             const docDragData = e.docDragData;
-            dropEvent?.(); // glr: optional additional function to be called - in this case with presentation trails
+            onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
             if (docDragData && !docDragData.droppedDocuments.length) {
                 docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
                 docDragData.droppedDocuments = (
diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx
index 67b7b39dd..1ff5688f9 100644
--- a/src/client/views/collections/TabDocView.tsx
+++ b/src/client/views/collections/TabDocView.tsx
@@ -137,7 +137,11 @@ export class TabDocView extends React.Component<TabDocViewProps> {
                     setupMoveUpEvents(
                         this,
                         e,
-                        e => !e.defaultPrevented && DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY),
+                        e =>
+                            !e.defaultPrevented &&
+                            DragManager.StartDocumentDrag([iconWrap], new DragManager.DocumentDragData([doc], doc.dropAction as dropActionType), e.clientX, e.clientY, undefined, () => {
+                                CollectionDockingView.CloseSplit(doc);
+                            }),
                         returnFalse,
                         action(e => {
                             if (this.view) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index db000d5de..747ab9249 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -238,11 +238,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             textX,
             textY,
             // fully connected
-            // pt1,
-            // pt2,
+            pt1,
+            pt2,
             // this code adds space between links
-            pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
-            pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
+            // pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
+            // pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
         };
     }
 
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index b906065a0..2ea8a7846 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -1,26 +1,28 @@
-import { Colors } from "browndash-components";
-import { runInAction, action } from "mobx";
-import { aggregateBounds } from "../../../Utils";
-import { Doc } from "../../../fields/Doc";
-import { Width, Height } from "../../../fields/DocSymbols";
-import { InkTool } from "../../../fields/InkField";
-import { Cast, StrCast, NumCast, BoolCast } from "../../../fields/Types";
-import { WebField } from "../../../fields/URLField";
-import { GestureUtils } from "../../../pen-gestures/GestureUtils";
-import { LinkManager } from "../../util/LinkManager";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { SelectionManager } from "../../util/SelectionManager";
-import { UndoManager } from "../../util/UndoManager";
-import { GestureOverlay } from "../GestureOverlay";
-import { InkTranscription } from "../InkTranscription";
-import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from "../InkingStroke";
-import { CollectionFreeFormView } from "../collections/collectionFreeForm";
-import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
-import { WebBox } from "../nodes/WebBox";
-import { RichTextMenu } from "../nodes/formattedText/RichTextMenu";
-import { DocumentType } from "../../documents/DocumentTypes";
+import { Colors } from 'browndash-components';
+import { runInAction, action } from 'mobx';
+import { aggregateBounds } from '../../../Utils';
+import { Doc } from '../../../fields/Doc';
+import { Width, Height } from '../../../fields/DocSymbols';
+import { InkTool } from '../../../fields/InkField';
+import { Cast, StrCast, NumCast, BoolCast } from '../../../fields/Types';
+import { WebField } from '../../../fields/URLField';
+import { GestureUtils } from '../../../pen-gestures/GestureUtils';
+import { LinkManager } from '../../util/LinkManager';
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SelectionManager } from '../../util/SelectionManager';
+import { UndoManager } from '../../util/UndoManager';
+import { GestureOverlay } from '../GestureOverlay';
+import { InkTranscription } from '../InkTranscription';
+import { ActiveFillColor, SetActiveFillColor, ActiveIsInkMask, SetActiveIsInkMask, ActiveInkWidth, SetActiveInkWidth, ActiveInkColor, SetActiveInkColor } from '../InkingStroke';
+import { CollectionFreeFormView } from '../collections/collectionFreeForm';
+import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView';
+import { WebBox } from '../nodes/WebBox';
+import { RichTextMenu } from '../nodes/formattedText/RichTextMenu';
+import { DocumentType } from '../../documents/DocumentTypes';
 
-ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views().length <= 0; }, "are no document selected");
+ScriptingGlobals.add(function IsNoneSelected() {
+    return SelectionManager.Views().length <= 0;
+}, 'are no document selected');
 
 // toggle: Set overlay status of selected document
 ScriptingGlobals.add(function setView(view: string) {
@@ -52,7 +54,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b
             if (contentFrameNumber !== undefined) {
                 CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color });
             } else {
-                console.log('setting color to: ', color)
+                console.log('setting color to: ', color);
                 dv.rootDoc['_' + fieldKey] = color;
             }
         });
@@ -119,7 +121,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli
    ]);
 
     if (checkResult) {
-        console.log(attr, map.get(attr)?.checkResult(selected))
+        console.log(attr, map.get(attr)?.checkResult(selected));
         return map.get(attr)?.checkResult(selected);
     }
     const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} };
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 34a1229ba..5cdd6b5f2 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -354,6 +354,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 AnchorMenu.Instance.jumpTo(e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale);
                 // Changing which document to add the annotation to (the currently selected WebBox)
                 GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash}_sidebar`);
+                const anchor = this._getAnchor(undefined, false);
+                if (anchor) {
+                    console.log(anchor);
+                    GPTPopup.Instance.setTargetAnchor(anchor);
+                }
                 GPTPopup.Instance.addDoc = this.sidebarAddDocument;
             }
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 212d24165..9f4483e8d 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -70,6 +70,7 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
+import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
 const translateGoogleApi = require('translate-google-api');
 export const GoogleRef = 'googleDocId';
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@@ -896,12 +897,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         this._downX = this._downY = Number.NaN;
     };
 
-    animateRes = (resIndex: number) => {
-        console.log(this.dataDoc.text);
-        if (resIndex < this.gptRes.length) {
-            this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + this.gptRes[resIndex];
+    animateRes = (resIndex: number, newText: string) => {
+        if (resIndex < newText.length) {
+            const marks = this._editorView?.state.storedMarks ?? [];
+            // if (!marks) return;
+            this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText(newText[resIndex]).setStoredMarks(marks));
             setTimeout(() => {
-                this.animateRes(resIndex + 1);
+                this.animateRes(resIndex + 1, newText);
             }, 20);
         }
     };
@@ -912,47 +914,68 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     // the._editorView.dispatch(state.tr.setSelection(updated).insertText('\n, to))
 
     askGPT = action(async () => {
+        // const state = this._editorView?.state;
+        // if (!state) return;
+        // const to = state.selection.to;
+        // const updated = TextSelection.create(state.doc, to, to);
+        // this._editorView?.dispatch(state.tr.setSelection(updated).insertText('\n', to));
+        // this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText('\nTesting').setStoredMarks(marks));
+        // console.log('After ', this._editorView?.state.storedMarks);
         try {
             let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
-            if (res) {
-                this.gptRes = res;
-                this.animateRes(0);
+            if (!res) {
+                console.error('GPT call failed');
+                this.animateRes(0, 'Something went wrong.');
+            } else {
+                this.animateRes(0, res);
             }
         } catch (err) {
-            console.log(err);
-            this.dataDoc.text = (this.dataDoc.text as RichTextField)?.Text + 'Something went wrong';
+            console.error('GPT call failed');
+            this.animateRes(0, 'Something went wrong.');
         }
     });
 
     generateImage = async () => {
         console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
+        GPTPopup.Instance?.setImgTargetDoc(this.rootDoc);
+        GPTPopup.Instance.setImgUrls([]);
+        GPTPopup.Instance.setMode(GPTPopupMode.IMAGE);
+        GPTPopup.Instance.setVisible(true);
+        GPTPopup.Instance.addToCollection = this.props.addDocument;
+        GPTPopup.Instance.setLoading(true);
+
         try {
-            let image_url = await gptImageCall((this.dataDoc.text as RichTextField)?.Text);
-            if (image_url) {
-                const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_url] });
+            // make this support multiple images
+            let image_urls = await gptImageCall((this.dataDoc.text as RichTextField)?.Text);
+            console.log(image_urls);
+            if (image_urls) {
+                const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
                 const source = Utils.prepend(result.accessPaths.agnostic.client);
-                const newDoc = Docs.Create.ImageDocument(source, {
-                    x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10,
-                    y: NumCast(this.rootDoc.y),
-                    _height: 200,
-                    _width: 200,
-                    data_nativeWidth: result.nativeWidth,
-                    data_nativeHeight: result.nativeHeight,
-                });
-                if (Doc.IsInMyOverlay(this.rootDoc)) {
-                    newDoc.overlayX = this.rootDoc.x;
-                    newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
-                    Doc.AddToMyOverlay(newDoc);
-                } else {
-                    this.props.addDocument?.(newDoc);
-                }
-                // Create link between prompt and image
-                DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' });
+                GPTPopup.Instance.setImgUrls([source]);
+
+                // const newDoc = Docs.Create.ImageDocument(source, {
+                //     x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10,
+                //     y: NumCast(this.rootDoc.y),
+                //     _height: 200,
+                //     _width: 200,
+                //     data_nativeWidth: result.nativeWidth,
+                //     data_nativeHeight: result.nativeHeight,
+                // });
+                // if (Doc.IsInMyOverlay(this.rootDoc)) {
+                //     newDoc.overlayX = this.rootDoc.x;
+                //     newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
+                //     Doc.AddToMyOverlay(newDoc);
+                // } else {
+                //     this.props.addDocument?.(newDoc);
+                // }
+                // // Create link between prompt and image
+                // DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' });
             }
         } catch (err) {
             console.log(err);
             return '';
         }
+        GPTPopup.Instance.setLoading(false);
     };
 
     breakupDictation = () => {
@@ -1249,8 +1272,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 }
                 // Accessing editor and text doc for gpt assisted text edits
                 if (this._editorView && selected) {
-                    AnchorMenu.Instance?.setEditorView(this._editorView);
-                    AnchorMenu.Instance?.setTextDoc(this.dataDoc);
+                    console.log('Setting');
+                    GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
+                    // AnchorMenu.Instance?.setEditorView(this._editorView);
+                    // AnchorMenu.Instance?.setTextDoc(this.dataDoc);
                 }
             }),
             { fireImmediately: true }
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index e6b9cb382..dd8cf7002 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -131,7 +131,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     gptSummarize = async (e: React.PointerEvent) => {
         GPTPopup.Instance.setVisible(true);
         this.setHighlightRange(undefined);
-        this.setGPTMode(GPTPopupMode.SUMMARY);
+        GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
         GPTPopup.Instance.setLoading(true);
 
         try {
@@ -183,6 +183,10 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         GPTPopup.Instance.setLoading(false);
     };
 
+    gptImage = async () => {
+        console.log(this.GetAnchor(undefined, false));
+    };
+
     /**
      * Replaces text suggestions from GPT.
      */
@@ -293,21 +297,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             </button>
                         </Tooltip>
                     )}
-                    {/* <GPTPopup
-                        key="gptpopup"
-                        visible={this.showGPTPopup}
-                        text={this.GPTpopupText}
-                        highlightRange={this.highlightRange}
-                        loading={this.loadingGPT}
-                        callSummaryApi={this.gptSummarize}
-                        callEditApi={this.gptEdit}
-                        replaceText={this.replaceText}
-                        mode={this.GPTMode}
-                    /> */}
                     {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
                         <IconButton tooltip={'Click to Record Annotation'} onPointerDown={this.audioDown} icon={<FontAwesomeIcon icon="microphone" />} color={StrCast(Doc.UserDoc().userColor)} />
                     )}
-                    {this.canEdit() && <IconButton tooltip={'AI edit suggestions'} onPointerDown={this.gptEdit} icon={<FontAwesomeIcon icon="pencil-alt" />} color={StrCast(Doc.UserDoc().userColor)} />}
+                    {/* Removed text editing for now, not quite ready */}
+                    {/* {this.canEdit() && <IconButton tooltip={'AI edit suggestions'} onPointerDown={this.gptEdit} icon={<FontAwesomeIcon icon="pencil-alt" />} color={StrCast(Doc.UserDoc().userColor)} />} */}
+                    {<IconButton tooltip={'Generate DALL-E Image'} onPointerDown={this.gptImage} icon={<FontAwesomeIcon icon="image" />} color={StrCast(Doc.UserDoc().userColor)} />}
                     <Popup tooltip="Find document to link to selected text" type={Type.PRIM} icon={<FontAwesomeIcon icon={'search'} />} popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />} color={StrCast(Doc.UserDoc().userColor)} />
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
                         <IconButton tooltip={'Click/Drag to create cropped image'} onPointerDown={this.cropDown} icon={<FontAwesomeIcon icon="image" />} color={StrCast(Doc.UserDoc().userColor)} />
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 2f0ff83e2..478b7d4ba 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -24,6 +24,7 @@ $highlightedText: #82e0ff;
 
     .summary-heading {
         display: flex;
+        justify-content: space-between;
         align-items: center;
         border-bottom: 1px solid $greyborder;
         padding-bottom: 5px;
@@ -110,6 +111,57 @@ $highlightedText: #82e0ff;
     }
 }
 
+.image-content-wrapper {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    gap: 8px;
+    padding-bottom: 16px;
+
+    .img-wrapper {
+        position: relative;
+        cursor: pointer;
+
+        .img-container {
+            position: relative;
+
+            img {
+                position: relative;
+            }
+        }
+
+        .img-container::after {
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+            background-color: rgba(0, 0, 0, 0.5);
+            opacity: 0;
+            transition: opacity 0.3s ease;
+        }
+
+        .btn-container {
+            position: absolute;
+            right: 8px;
+            bottom: 8px;
+            opacity: 0;
+            transition: opacity 0.3s ease;
+        }
+
+        &:hover {
+            .img-container::after {
+                opacity: 1;
+            }
+
+            .btn-container {
+                opacity: 1;
+            }
+        }
+    }
+}
+
 // Typist CSS
 .Typist .Cursor {
     display: inline-block;
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 9f28cb5d1..aeee90d16 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -1,4 +1,5 @@
 import React = require('react');
+import './GPTPopup.scss';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
@@ -6,13 +7,14 @@ import ReactLoading from 'react-loading';
 import Typist from 'react-typist';
 import { Doc } from '../../../../fields/Doc';
 import { DocUtils, Docs } from '../../../documents/Documents';
-import './GPTPopup.scss';
-import { Button, IconButton } from 'browndash-components';
-import { StrCast } from '../../../../fields/Types';
+import { Button, IconButton, Type } from 'browndash-components';
+import { NumCast, StrCast } from '../../../../fields/Types';
+import { CgClose } from 'react-icons/cg';
 
 export enum GPTPopupMode {
     SUMMARY,
     EDIT,
+    IMAGE,
 }
 
 interface GPTPopupProps {}
@@ -39,6 +41,14 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
     public setText = (text: string) => {
         this.text = text;
     };
+
+    @observable
+    public imageUrls: string[] = [];
+    @action
+    public setImgUrls = (imgs: string[]) => {
+        this.imageUrls = imgs;
+    };
+
     @observable
     public mode: GPTPopupMode = GPTPopupMode.SUMMARY;
     @action
@@ -58,6 +68,8 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
     public setDone = (done: boolean) => {
         this.done = done;
     };
+
+    // change what can be a ref into a ref
     @observable
     private sidebarId: string = '';
     @action
@@ -65,19 +77,30 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
         this.sidebarId = id;
     };
 
-    // reference
-    // if (focusNode) {
-    //     const anchor = srcWeb?.ComponentView?.getAnchor?.(true);
-    //     anchor && DocUtils.MakeLink(htmlDoc, anchor, {});
-    // }
+    // pdfs and webpages
+    @observable
+    private targetAnchor: Doc | undefined;
+    @action
+    public setTargetAnchor = (anchor: Doc) => {
+        this.targetAnchor = anchor;
+    };
+
     @observable
-    private pdfAnchor: Doc | undefined;
+    private imgTargetDoc: Doc | undefined;
     @action
-    public setPdfAnchor = (anchor: Doc) => {
-        this.pdfAnchor = anchor;
+    public setImgTargetDoc = (anchor: Doc) => {
+        this.imgTargetDoc = anchor;
+    };
+
+    @observable
+    private textAnchor: Doc | undefined;
+    @action
+    public setTextAnchor = (anchor: Doc) => {
+        this.textAnchor = anchor;
     };
 
     public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
+    public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
 
     /**
      * Transfers the summarization text to a sidebar annotation text document.
@@ -90,13 +113,42 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
             _layout_autoHeight: true,
         });
         this.addDoc(newDoc, this.sidebarId);
-        if (this.pdfAnchor) {
-            DocUtils.MakeLink(newDoc, this.pdfAnchor, {
+        if (this.targetAnchor) {
+            DocUtils.MakeLink(newDoc, this.targetAnchor, {
                 link_relationship: 'GPT Summary',
             });
         }
     };
 
+    /**
+     * Transfers the image urls to actual image docs
+     */
+    private transferToImage = (source: string) => {
+        console.log('Text Anchor', this.textAnchor);
+        console.log('Whole doc anchor', this.imgTargetDoc);
+        const textAnchor = this.textAnchor ?? this.imgTargetDoc;
+        if (!textAnchor) return;
+        const newDoc = Docs.Create.ImageDocument(source, {
+            x: NumCast(textAnchor.x) + NumCast(textAnchor._width) + 10,
+            y: NumCast(textAnchor.y),
+            _height: 200,
+            _width: 200,
+            data_nativeWidth: 1024,
+            data_nativeHeight: 1024,
+        });
+        if (Doc.IsInMyOverlay(textAnchor)) {
+            newDoc.overlayX = textAnchor.x;
+            newDoc.overlayY = NumCast(textAnchor.y) + NumCast(textAnchor._height);
+            Doc.AddToMyOverlay(newDoc);
+        } else {
+            this.addToCollection?.(newDoc);
+        }
+        // Create link between prompt and image
+        DocUtils.MakeLink(textAnchor, newDoc, { link_relationship: 'Image Prompt' });
+    };
+
+    private getPreviewUrl = (source: string) => source.split('.').join('_m.');
+
     constructor(props: GPTPopupProps) {
         super(props);
         GPTPopup.Instance = this;
@@ -108,6 +160,26 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
         }
     };
 
+    imageBox = () => {
+        return (
+            <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
+                {this.heading('GENERATED IMAGE')}
+                <div className="image-content-wrapper">
+                    {this.imageUrls.map(rawSrc => (
+                        <div className="img-wrapper">
+                            <div className="img-container">
+                                <img key={rawSrc} src={this.getPreviewUrl(rawSrc)} width={150} height={150} alt="dalle generation" />
+                            </div>
+                            <div className="btn-container">
+                                <Button text="Save Image" onClick={() => this.transferToImage(rawSrc)} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+                            </div>
+                        </div>
+                    ))}
+                </div>
+            </div>
+        );
+    };
+
     summaryBox = () => (
         <>
             <div>
@@ -135,30 +207,21 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
                 <div className="btns-wrapper">
                     {this.done ? (
                         <>
-                            {/* <button className="icon-btn" onClick={this.callSummaryApi}>
-                                <FontAwesomeIcon icon="redo-alt" size="lg" />
-                            </button>
-                            <button
-                                className="text-btn"
-                                onClick={e => {
-                                    this.transferToText();
-                                }}>
-                                Transfer to Text
-                            </button> */}
-                            <IconButton onClick={this.callSummaryApi} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
-                            <Button text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} />
+                            <IconButton tooltip="Generate Again" onClick={this.callSummaryApi} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
+                            <Button tooltip="Transfer to text" text="Transfer To Text" onClick={this.transferToText} color={StrCast(Doc.UserDoc().userVariantColor)} type={Type.TERT} />
                         </>
                     ) : (
                         <div className="summarizing">
                             <span>Summarizing</span>
                             <ReactLoading type="bubbles" color="#bcbcbc" width={20} height={20} />
-                            <button
-                                className="btn-secondary"
-                                onClick={e => {
+                            <Button
+                                text="Stop Animation"
+                                onClick={() => {
                                     this.setDone(true);
-                                }}>
-                                Stop Animation
-                            </button>
+                                }}
+                                color={StrCast(Doc.UserDoc().userVariantColor)}
+                                type={Type.TERT}
+                            />
                         </div>
                     )}
                 </div>
@@ -216,14 +279,14 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
     heading = (headingText: string) => (
         <div className="summary-heading">
             <label className="summary-text">{headingText}</label>
-            {this.loading && <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} />}
+            {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />}
         </div>
     );
 
     render() {
         return (
             <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
-                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.editBox()}
+                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.editBox()}
             </div>
         );
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 4fc31ffe3..1d1c34f4f 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -425,8 +425,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         GPTPopup.Instance.setSidebarId('data_sidebar');
         const anchor = this._getAnchor(undefined, false);
         if (anchor) {
-            console.log(anchor);
-            GPTPopup.Instance.setPdfAnchor(anchor);
+            GPTPopup.Instance.setTargetAnchor(anchor);
         }
         GPTPopup.Instance.addDoc = this.props.sidebarAddDoc;
     };
-- 
cgit v1.2.3-70-g09d2


From 314f62bb5335e3fb3f25501823d41cf0cdc53cac Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Sun, 13 Aug 2023 17:02:58 -0400
Subject: update

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  18 +++-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  15 +--
 .../views/nodes/generativeFill/GenerativeFill.scss |   1 +
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 103 +++++++++---------
 .../generativeFillUtils/BrushHandler.ts            | 116 +++++++--------------
 .../generativeFillUtils/ImageHandler.ts            |   2 +-
 .../generativeFillUtils/generativeFillConstants.ts |   4 +-
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |   4 +-
 8 files changed, 118 insertions(+), 145 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index ba31916a7..34f707b30 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -237,7 +237,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
     onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
     elementFunc = () => this._layoutElements;
-    shrinkWrap = () => {
+    fitContentOnce = () => {
         if (this.props.DocumentView?.().nativeWidth) return;
         const vals = this.fitToContentVals;
         this.layoutDoc._freeform_panX = vals.bounds.cx;
@@ -1592,6 +1592,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     elements => (this._layoutElements = elements || []),
                     { fireImmediately: true, name: 'doLayout' }
                 );
+
+                this._disposers.fitContent = reaction(
+                    () => this.rootDoc.fitContentOnce,
+                    fitContentOnce => {
+                        if (fitContentOnce) this.fitContentOnce();
+                    },
+                    { fireImmediately: true, name: 'fitContent' }
+                );
             })
         );
     }
@@ -1782,6 +1790,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
             !Doc.noviceMode &&
             optionItems.push({ description: (this._showAnimTimeline ? 'Close' : 'Open') + ' Animation Timeline', event: action(() => (this._showAnimTimeline = !this._showAnimTimeline)), icon: 'eye' });
         this.props.renderDepth && optionItems.push({ description: 'Use Background Color as Default', event: () => (Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor)), icon: 'palette' });
+        this.props.renderDepth &&
+            optionItems.push({
+                description: 'Fit Content Once',
+                event: () => {
+                    this.fitContentOnce();
+                },
+                icon: 'object-group',
+            });
         if (!Doc.noviceMode) {
             optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? 'Freeze' : 'Unfreeze') + ' Aspect', event: this.toggleNativeDimensions, icon: 'snowflake' });
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9f4483e8d..072977150 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -937,6 +937,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     generateImage = async () => {
         console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
+        GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
         GPTPopup.Instance?.setImgTargetDoc(this.rootDoc);
         GPTPopup.Instance.setImgUrls([]);
         GPTPopup.Instance.setMode(GPTPopupMode.IMAGE);
@@ -1270,13 +1271,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
                     setTimeout(this.autoLink, 20);
                 }
-                // Accessing editor and text doc for gpt assisted text edits
-                if (this._editorView && selected) {
-                    console.log('Setting');
-                    GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
-                    // AnchorMenu.Instance?.setEditorView(this._editorView);
-                    // AnchorMenu.Instance?.setTextDoc(this.dataDoc);
-                }
+                // // Accessing editor and text doc for gpt assisted text edits
+                // if (this._editorView && selected) {
+                //     console.log('Setting');
+                //     GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
+                //     // AnchorMenu.Instance?.setEditorView(this._editorView);
+                //     // AnchorMenu.Instance?.setTextDoc(this.dataDoc);
+                // }
             }),
             { fireImmediately: true }
         );
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.scss b/src/client/views/nodes/generativeFill/GenerativeFill.scss
index b1e570cf1..c2669a950 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.scss
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.scss
@@ -16,6 +16,7 @@ $scale: 0.5;
     .generativeFillControls {
         flex-shrink: 0;
         height: $navHeight;
+        color: #000000;
         background-color: #ffffff;
         z-index: 999;
         width: 100%;
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 2b3cfb920..f8f9fe077 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -3,21 +3,19 @@ import React = require('react');
 import { useEffect, useRef, useState } from 'react';
 import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
 import { BrushHandler } from './generativeFillUtils/BrushHandler';
-import { IconButton, TextField } from '@mui/material';
+import { Box, IconButton, Slider, TextField } from '@mui/material';
 import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces';
 import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
 import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import { BsBrush, BsEraser, BsX } from 'react-icons/bs';
-import { AiOutlineUpload } from 'react-icons/ai';
+import { BsEraser, BsX } from 'react-icons/bs';
 import { CiUndo, CiRedo } from 'react-icons/ci';
 import Buttons from './GenerativeFillButtons';
-import { EditableText } from 'browndash-components';
 import { MainView } from '../../MainView';
 import { Doc } from '../../../../fields/Doc';
 import { Networking } from '../../../Network';
 import { Utils } from '../../../../Utils';
 import { DocUtils, Docs } from '../../../documents/Documents';
-import { DocCast, NumCast } from '../../../../fields/Types';
+import { Cast, DocCast, NumCast } from '../../../../fields/Types';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { OpenWhere, OpenWhereMod } from '../DocumentView';
 import { Oval } from 'react-loader-spinner';
@@ -25,12 +23,11 @@ import { Oval } from 'react-loader-spinner';
 /**
  * For images not 1024x1024 fill in the rest in solid black, or a
  * reflected version of the image.
+ *
+ * add a branch from image directly checkbox
  */
 
 /**
- * TODO: Look into img onload, sometimes the canvas doesn't update properly
- *
- * Ref:
  *
  *
  * CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right);
@@ -88,12 +85,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const freeformPosition = useRef<number[]>([0, 0]);
 
     // which urls were already saved to canvas
-    const savedSrcs = useRef<string[]>([]);
+    const savedSrcs = useRef<Set<string>>(new Set());
 
     // references to keep track of tree structure
     const newCollectionRef = useRef<Doc | null>(null);
     const parentDoc = useRef<Doc | null>(null);
     const childrenDocs = useRef<Doc[]>([]);
+    const addToExistingCollection = useRef<boolean>(false);
 
     // Undo and Redo
     const handleUndo = () => {
@@ -250,18 +248,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         }));
     };
 
-    // File upload
-    const uploadImg = (e: React.ChangeEvent<HTMLInputElement>) => {
-        if (e.target.files) {
-            const file = e.target.files[0];
-            const image = new Image();
-            const imgUrl = URL.createObjectURL(file);
-            image.src = imgUrl;
-            ImageUtility.drawImgToCanvas(image, canvasRef);
-            currImg.current = image;
-        }
-    };
-
     // Get AI Edit
     const getEdit = async () => {
         const img = currImg.current;
@@ -280,6 +266,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
             // create first image
             if (!newCollectionRef.current) {
+                if (addToExistingCollection.current) {
+                }
                 if (!(originalImg.current && imageRootDoc)) return;
                 console.log('creating first image');
                 // create new collection and add it to the view
@@ -292,7 +280,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 });
                 DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
                 // add the doc to the main freeform
-                addDoc?.(newCollectionRef.current);
+                // addDoc?.(newCollectionRef.current);
                 await createNewImgDoc(originalImg.current, true);
             } else {
                 parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1];
@@ -321,7 +309,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         if (!parentDoc.current) return;
         const startY = NumCast(parentDoc.current.y);
         const len = childrenDocs.current.length;
-        console.log(len);
         let initialYPositions: number[] = [];
         for (let i = 0; i < len; i++) {
             initialYPositions.push(startY + i * offsetDistanceY);
@@ -398,9 +385,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     };
 
     const handleViewClose = () => {
-        // if (newCollectionRef.current) {
-        //     CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
-        // }
+        if (newCollectionRef.current) {
+            newCollectionRef.current.fitContentOnce = true;
+            CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+        }
         MainView.Instance.setImageEditorOpen(false);
         MainView.Instance.setImageEditorSource('');
         setEdits([]);
@@ -409,12 +397,17 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     return (
         <div className="generativeFillContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
             <div className="generativeFillControls">
-                <h1>Generative Fill</h1>
+                <h1>AI Image Editor</h1>
                 <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
                     <Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onSave={onSave} onReset={handleReset} />
                     <IconButton onClick={handleViewClose}>
                         <BsX color={activeColor} />
                     </IconButton>
+                    {saveLoading && (
+                        <span style={{ height: '100%', display: 'flex', alignItems: 'center', gap: '8px' }}>
+                            Saving image... <Oval height={20} width={20} color="#000000" visible={true} ariaLabel="oval-loading" secondaryColor="#ffffff89" strokeWidth={3} strokeWidthSecondary={3} />
+                        </span>
+                    )}
                 </div>
             </div>
             {/* Main canvas for editing */}
@@ -439,20 +432,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 </div>
                 {/* Icons */}
                 <div className="iconContainer">
-                    <input ref={fileRef} type="file" accept="image/*" onChange={uploadImg} style={{ display: 'none' }} />
-                    <IconButton
-                        onClick={() => {
-                            if (fileRef.current) {
-                                fileRef.current.click();
-                            }
-                        }}>
-                        <AiOutlineUpload />
-                    </IconButton>
                     <IconButton
                         onClick={() => {
                             setBrushStyle(BrushStyle.ADD);
                         }}>
-                        <BsBrush color={brushStyle === BrushStyle.ADD ? activeColor : 'inherit'} />
+                        <BsEraser color={brushStyle === BrushStyle.ADD ? activeColor : 'inherit'} />
                     </IconButton>
                     {/* Undo and Redo */}
                     <IconButton
@@ -475,6 +459,28 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                         }}>
                         <CiRedo />
                     </IconButton>
+                    <Box
+                        sx={{
+                            height: 225,
+                            width: '100%',
+                            display: 'flex',
+                            justifyContent: 'center',
+                        }}>
+                        <Slider
+                            sx={{
+                                '& input[type="range"]': {
+                                    WebkitAppearance: 'slider-vertical',
+                                },
+                            }}
+                            orientation="vertical"
+                            min={10}
+                            max={500}
+                            defaultValue={150}
+                            onChange={(e, val) => {
+                                setCursorData(prev => ({ ...prev, width: val as number }));
+                            }}
+                        />
+                    </Box>
                 </div>
                 {/* Edits thumbnails*/}
                 <div className="editsBox">
@@ -484,12 +490,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                             width={100}
                             height={100}
                             src={edit}
-                            onClick={() => {
+                            onClick={async () => {
+                                // if (savedSrcs.current.has(edit)) return;
                                 const img = new Image();
                                 img.src = edit;
                                 ImageUtility.drawImgToCanvas(img, canvasRef);
                                 currImg.current = img;
-                                onSave();
+                                savedSrcs.current.add(edit);
+                                await onSave();
                             }}
                         />
                     ))}
@@ -521,14 +529,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                                     currImg.current = img;
                                 }}
                             />
-                            <div
-                                style={{
-                                    position: 'absolute',
-                                    top: 10,
-                                    right: 10,
-                                }}>
-                                {saveLoading && <Oval height={20} width={20} color="#ffffff" visible={true} ariaLabel="oval-loading" secondaryColor="#ffffff89" strokeWidth={3} strokeWidthSecondary={3} />}
-                            </div>
                         </div>
                     )}
                 </div>
@@ -541,15 +541,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                     type="text"
                     label="Prompt"
                     placeholder="Prompt..."
-                    InputLabelProps={{ style: { fontSize: '1.5rem' } }}
-                    inputProps={{ style: { fontSize: '1.5rem' } }}
+                    InputLabelProps={{ style: { fontSize: '16px' } }}
+                    inputProps={{ style: { fontSize: '16px' } }}
                     sx={{
                         backgroundColor: '#ffffff',
                         position: 'absolute',
-                        bottom: '1rem',
+                        bottom: '16px',
                         transform: 'translateX(calc(50vw - 50%))',
-                        width: 'calc(100vw - 4rem)',
-                        scale: 1.2,
+                        width: 'calc(100vw - 64px)',
                     }}
                 />
             </div>
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
index c2716e083..f84f04190 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
@@ -1,87 +1,45 @@
-import { GenerativeFillMathHelpers } from "./GenerativeFillMathHelpers";
-import { eraserColor } from "./generativeFillConstants";
-import { Point } from "./generativeFillInterfaces";
+import { GenerativeFillMathHelpers } from './GenerativeFillMathHelpers';
+import { eraserColor } from './generativeFillConstants';
+import { Point } from './generativeFillInterfaces';
 
 export class BrushHandler {
-  static brushCircle = (
-    x: number,
-    y: number,
-    brushRadius: number,
-    ctx: CanvasRenderingContext2D
-  ) => {
-    ctx.globalCompositeOperation = "destination-out";
-    ctx.shadowColor = "#ffffffeb";
-    ctx.shadowBlur = 5;
-    ctx.beginPath();
-    ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
-    ctx.fill();
-    ctx.closePath();
-  };
+    static brushCircle = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D) => {
+        ctx.globalCompositeOperation = 'destination-out';
+        ctx.shadowColor = '#ffffffeb';
+        ctx.shadowBlur = 5;
+        ctx.beginPath();
+        ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
+        ctx.fill();
+        ctx.closePath();
+    };
 
-  static brushCircleOverlay = (
-    x: number,
-    y: number,
-    brushRadius: number,
-    ctx: CanvasRenderingContext2D,
-    fillColor: string,
-    erase: boolean
-  ) => {
-    ctx.globalCompositeOperation = "destination-out";
-    // ctx.globalCompositeOperation = erase ? "destination-out" : "source-over";
-    ctx.fillStyle = fillColor;
-    ctx.shadowColor = eraserColor;
-    ctx.shadowBlur = 5;
-    ctx.beginPath();
-    ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
-    ctx.fill();
-    ctx.closePath();
-  };
+    static brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
+        ctx.globalCompositeOperation = 'destination-out';
+        // ctx.globalCompositeOperation = erase ? "destination-out" : "source-over";
+        ctx.fillStyle = fillColor;
+        ctx.shadowColor = eraserColor;
+        ctx.shadowBlur = 5;
+        ctx.beginPath();
+        ctx.arc(x, y, brushRadius, 0, 2 * Math.PI);
+        ctx.fill();
+        ctx.closePath();
+    };
 
-  static createBrushPath = (
-    startPoint: Point,
-    endPoint: Point,
-    brushRadius: number,
-    ctx: CanvasRenderingContext2D
-  ) => {
-    const dist = GenerativeFillMathHelpers.distanceBetween(
-      startPoint,
-      endPoint
-    );
+    static createBrushPath = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D) => {
+        const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
 
-    for (let i = 0; i < dist; i += 5) {
-      const s = i / dist;
-      BrushHandler.brushCircle(
-        startPoint.x * (1 - s) + endPoint.x * s,
-        startPoint.y * (1 - s) + endPoint.y * s,
-        brushRadius,
-        ctx
-      );
-    }
-  };
+        for (let i = 0; i < dist; i += 5) {
+            const s = i / dist;
+            BrushHandler.brushCircle(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx);
+        }
+    };
 
-  static createBrushPathOverlay = (
-    startPoint: Point,
-    endPoint: Point,
-    brushRadius: number,
-    ctx: CanvasRenderingContext2D,
-    fillColor: string,
-    erase: boolean
-  ) => {
-    const dist = GenerativeFillMathHelpers.distanceBetween(
-      startPoint,
-      endPoint
-    );
+    static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
+        const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
 
-    for (let i = 0; i < dist; i += 5) {
-      const s = i / dist;
-      BrushHandler.brushCircleOverlay(
-        startPoint.x * (1 - s) + endPoint.x * s,
-        startPoint.y * (1 - s) + endPoint.y * s,
-        brushRadius,
-        ctx,
-        fillColor,
-        erase
-      );
-    }
-  };
+        for (let i = 0; i < dist; i += 5) {
+            const s = i / dist;
+            BrushHandler.brushCircleOverlay(startPoint.x * (1 - s) + endPoint.x * s, startPoint.y * (1 - s) + endPoint.y * s, brushRadius, ctx, fillColor, erase);
+        }
+    };
 }
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 48055903c..45cf7196b 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -28,7 +28,7 @@ export class ImageUtility {
         fd.append('image', imgBlob, 'image.png');
         fd.append('mask', maskBlob, 'mask.png');
         fd.append('prompt', prompt);
-        fd.append('size', '1024x1024');
+        fd.append('size', '512x512');
         fd.append('n', n ? JSON.stringify(n) : '1');
         fd.append('response_format', 'b64_json');
 
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
index 5a8d33742..412a4d238 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -1,9 +1,9 @@
 // constants
-export const canvasSize = 1024;
+export const canvasSize = 512;
 export const freeformRenderSize = 300;
 export const offsetDistanceY = freeformRenderSize + 200;
 export const offsetX = 200;
-export const newCollectionSize = 1000;
+export const newCollectionSize = 500;
 
 export const activeColor = '#1976d2';
 export const eraserColor = '#e1e9ec';
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index aeee90d16..fc6fc1af8 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -124,9 +124,7 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
      * Transfers the image urls to actual image docs
      */
     private transferToImage = (source: string) => {
-        console.log('Text Anchor', this.textAnchor);
-        console.log('Whole doc anchor', this.imgTargetDoc);
-        const textAnchor = this.textAnchor ?? this.imgTargetDoc;
+        const textAnchor = this.imgTargetDoc;
         if (!textAnchor) return;
         const newDoc = Docs.Create.ImageDocument(source, {
             x: NumCast(textAnchor.x) + NumCast(textAnchor._width) + 10,
-- 
cgit v1.2.3-70-g09d2


From d3ecace5f03233e5d5ab2354c3f482418287ca9a Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 17 Aug 2023 16:14:53 -0400
Subject: broken version

---
 package-lock.json                                  |  97 +++++++++++++++--
 src/client/util/CurrentUserUtils.ts                |   2 +
 src/client/views/MainView.tsx                      |   2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 src/client/views/global/globalScripts.ts           |   8 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  28 +++++
 src/client/views/nodes/ImageBox.tsx                |   6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  76 +++++++-------
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 115 +++++++++++++--------
 .../nodes/generativeFill/GenerativeFillButtons.tsx |   6 +-
 src/client/views/pdf/AnchorMenu.tsx                |   1 +
 src/client/views/pdf/GPTPopup/GPTPopup.scss        |   2 +
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  99 +++++++++---------
 src/client/views/pdf/PDFViewer.tsx                 |   4 -
 14 files changed, 301 insertions(+), 146 deletions(-)

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

diff --git a/package-lock.json b/package-lock.json
index 658e783f9..20d582e5b 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -6507,9 +6507,9 @@
             }
         },
         "browndash-components": {
-            "version": "0.0.92",
-            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.92.tgz",
-            "integrity": "sha512-eE/6WQNZiLnaXUKyoaMm0PDYjExUsFJ9VTAIIxROpYPosIBKWNZ743xaOfmehib5us9hEXJb0CvUFJQb8rzDVw==",
+            "version": "0.0.93",
+            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.0.93.tgz",
+            "integrity": "sha512-JL9Hi7Nq+zHZtjLCQSuGhID0Hd7X6sARutyJqbECfsLWACsIC/JfeEQZdUeH5CqO+R7pJKeoWM82AIkWGtN2Xw==",
             "requires": {
                 "@emotion/react": "^11.11.1",
                 "@emotion/styled": "^11.11.0",
@@ -7063,12 +7063,97 @@
                                         "strip-ansi": "^7.0.1"
                                     }
                                 },
+                                "string-width-cjs": {
+                                    "version": "npm:string-width@4.2.3",
+                                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                                    "requires": {
+                                        "emoji-regex": "^8.0.0",
+                                        "is-fullwidth-code-point": "^3.0.0",
+                                        "strip-ansi": "^6.0.1"
+                                    },
+                                    "dependencies": {
+                                        "ansi-regex": {
+                                            "version": "5.0.1",
+                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                                        },
+                                        "emoji-regex": {
+                                            "version": "8.0.0",
+                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                                        },
+                                        "strip-ansi": {
+                                            "version": "6.0.1",
+                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                                            "requires": {
+                                                "ansi-regex": "^5.0.1"
+                                            }
+                                        }
+                                    }
+                                },
                                 "strip-ansi": {
                                     "version": "7.1.0",
                                     "bundled": true,
                                     "requires": {
                                         "ansi-regex": "^6.0.1"
                                     }
+                                },
+                                "strip-ansi-cjs": {
+                                    "version": "npm:strip-ansi@6.0.1",
+                                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                                    "requires": {
+                                        "ansi-regex": "^5.0.1"
+                                    },
+                                    "dependencies": {
+                                        "ansi-regex": {
+                                            "version": "5.0.1",
+                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                                        }
+                                    }
+                                },
+                                "wrap-ansi-cjs": {
+                                    "version": "npm:wrap-ansi@7.0.0",
+                                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+                                    "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+                                    "requires": {
+                                        "ansi-styles": "^4.0.0",
+                                        "string-width": "^4.1.0",
+                                        "strip-ansi": "^6.0.0"
+                                    },
+                                    "dependencies": {
+                                        "ansi-regex": {
+                                            "version": "5.0.1",
+                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+                                        },
+                                        "emoji-regex": {
+                                            "version": "8.0.0",
+                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+                                        },
+                                        "string-width": {
+                                            "version": "4.2.3",
+                                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+                                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+                                            "requires": {
+                                                "emoji-regex": "^8.0.0",
+                                                "is-fullwidth-code-point": "^3.0.0",
+                                                "strip-ansi": "^6.0.1"
+                                            }
+                                        },
+                                        "strip-ansi": {
+                                            "version": "6.0.1",
+                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+                                            "requires": {
+                                                "ansi-regex": "^5.0.1"
+                                            }
+                                        }
+                                    }
                                 }
                             }
                         },
@@ -8573,7 +8658,7 @@
                             }
                         },
                         "string-width-cjs": {
-                            "version": "npm:string-width@4.2.3",
+                            "version": "npm:string-width-cjs@4.2.3",
                             "bundled": true,
                             "requires": {
                                 "emoji-regex": "^8.0.0",
@@ -8596,7 +8681,7 @@
                             }
                         },
                         "strip-ansi-cjs": {
-                            "version": "npm:strip-ansi@6.0.1",
+                            "version": "npm:strip-ansi-cjs@6.0.1",
                             "bundled": true,
                             "requires": {
                                 "ansi-regex": "^5.0.1"
@@ -8755,7 +8840,7 @@
                             }
                         },
                         "wrap-ansi-cjs": {
-                            "version": "npm:wrap-ansi@7.0.0",
+                            "version": "npm:wrap-ansi-cjs@7.0.0",
                             "bundled": true,
                             "requires": {
                                 "ansi-styles": "^4.0.0",
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index c9a5175bb..6592ecffc 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -633,6 +633,8 @@ export class CurrentUserUtils {
             { title: "Snap", icon: "th",                    toolTip: "Show Snap Lines",     btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines",  funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Grid",          icon: "border-all",   toolTip: "Show Grid",           btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid",       funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "View All",   icon: "object-group", toolTip: "Fit all Docs to View",   btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"viewAll",    funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
+            // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true,
+            { title: "Fit All",   icon: "arrows-left-right", toolTip: "Fit all Docs to View (persistent)", btnType: ButtonType.IconClickButton, ignoreClick: false, expertMode: false, toolType:"viewAllPersist",    funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Clusters",      icon: "braille",      toolTip: "Show Doc Clusters",   btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters",   funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Cards",         icon: "brain",        toolTip: "Flashcards",          btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
             { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange",  btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange",    funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 60eb64caa..8fba9b886 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -80,7 +80,6 @@ export class MainView extends React.Component {
     @observable public imageEditorSource: string = '';
     @action public setImageEditorSource = (source: string) => (this.imageEditorSource = source);
     @observable public imageRootDoc: Doc | undefined;
-    @action public setImageRootDoc = (doc: Doc) => (this.imageRootDoc = doc);
     @observable public addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
 
     @observable public LastButton: Opt<Doc>;
@@ -350,6 +349,7 @@ export class MainView extends React.Component {
                 fa.faMousePointer,
                 fa.faMusic,
                 fa.faObjectGroup,
+                fa.faArrowsLeftRight,
                 fa.faPause,
                 fa.faPen,
                 fa.faPenNib,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 29122cb91..f353dfab7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1599,6 +1599,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
                     () => this.rootDoc.fitContentOnce,
                     fitContentOnce => {
                         if (fitContentOnce) this.fitContentOnce();
+                        this.rootDoc.fitContentOnce = false;
                     },
                     { fireImmediately: true, name: 'fitContent' }
                 );
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 256377758..38bf1042d 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -85,10 +85,10 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) {
     selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed');
 });
 
-ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) {
+ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'viewAllPersist', checkResult?: boolean) {
     const selected = SelectionManager.Docs().lastElement();
     // prettier-ignore
-    const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
+    const map: Map<'flashcards' | 'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'viewAllPersist', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([
         ['grid', {
             checkResult: (doc:Doc) => BoolCast(doc._freeform_backgroundGrid, false),
             setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid,
@@ -101,6 +101,10 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'grid' | 'snapli
             checkResult: (doc:Doc) => BoolCast(doc._freeform_fitContentsToBox, false),
             setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox,
         }],
+        ['viewAllPersist', {
+            checkResult: (doc:Doc) => false,
+            setDoc: (doc:Doc) => doc.fitContentOnce = true
+        }],
         ['clusters', {
             waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire
             checkResult: (doc:Doc) => BoolCast(doc._freeform_useClusters, false),
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 91eac675f..90ec212fc 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -29,6 +29,7 @@ export enum ButtonType {
     DropdownList = 'dropdownList',
     DropdownButton = 'dropdownBtn',
     ClickButton = 'clickBtn',
+    IconClickButton = 'iconClickBtn',
     ToggleButton = 'toggleBtn',
     ColorButton = 'colorBtn',
     ToolButton = 'toolBtn',
@@ -346,6 +347,32 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         );
     }
 
+    @computed get iconClickButton() {
+        // Determine the type of toggle button
+        const buttonText: string = StrCast(this.rootDoc.buttonText);
+        const tooltip: string = StrCast(this.rootDoc.toolTip);
+
+        const script = ScriptCast(this.rootDoc.onClick);
+        const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false;
+        // Colors
+        const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color);
+        const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor);
+
+        return (
+            <Toggle
+                tooltip={`${tooltip}`}
+                toggleType={ToggleType.BUTTON}
+                type={Type.PRIM}
+                toggleStatus={toggleStatus}
+                text={buttonText}
+                color={color}
+                icon={this.Icon(color)!}
+                label={this.label}
+                onPointerDown={() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })}
+            />
+        );
+    }
+
     /**
      * Default
      */
@@ -393,6 +420,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
             case ButtonType.DropdownButton:     return this.dropdownButton;
             case ButtonType.MultiToggleButton:  return this.multiToggleButton;
             case ButtonType.ToggleButton:       return this.toggleButton;
+            case ButtonType.IconClickButton: return this.iconClickButton;
             case ButtonType.ClickButton:
             case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />;
             case ButtonType.TextButton: return <Button     {...btnProps} text={StrCast(this.rootDoc.buttonText)}/>;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 4ce359f3f..352e0fbdc 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -252,8 +252,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             funcs.push({
                 description: 'Open Image Editor',
                 event: action(() => {
-                    MainView.Instance.setImageEditorOpen(true);
-                    MainView.Instance.setImageEditorSource(this.choosePath(field.url));
+                    MainView.Instance.imageEditorOpen = true;
+                    MainView.Instance.imageEditorSource = this.choosePath(field.url);
                     MainView.Instance.addDoc = this.props.addDocument;
                     MainView.Instance.imageRootDoc = this.rootDoc;
                 }),
@@ -306,7 +306,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     choosePath(url: URL) {
         const lower = url.href.toLowerCase();
         if (url.protocol === 'data') return url.href;
-        if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf("dashblobstore") === -1) return Utils.CorsProxy(url.href);
+        if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf('dashblobstore') === -1) return Utils.CorsProxy(url.href);
         if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`;
 
         const ext = extname(url.href);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 332f0f467..5acc45d02 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -946,44 +946,46 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
         GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
         GPTPopup.Instance?.setImgTargetDoc(this.rootDoc);
-        GPTPopup.Instance.setImgUrls([]);
-        GPTPopup.Instance.setMode(GPTPopupMode.IMAGE);
-        GPTPopup.Instance.setVisible(true);
+        // GPTPopup.Instance.setImgUrls([]);
+        // GPTPopup.Instance.setMode(GPTPopupMode.IMAGE);
+        // GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.addToCollection = this.props.addDocument;
-        GPTPopup.Instance.setLoading(true);
-
-        try {
-            // make this support multiple images
-            let image_urls = await gptImageCall((this.dataDoc.text as RichTextField)?.Text);
-            console.log(image_urls);
-            if (image_urls) {
-                const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
-                const source = Utils.prepend(result.accessPaths.agnostic.client);
-                GPTPopup.Instance.setImgUrls([source]);
-
-                // const newDoc = Docs.Create.ImageDocument(source, {
-                //     x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10,
-                //     y: NumCast(this.rootDoc.y),
-                //     _height: 200,
-                //     _width: 200,
-                //     data_nativeWidth: result.nativeWidth,
-                //     data_nativeHeight: result.nativeHeight,
-                // });
-                // if (Doc.IsInMyOverlay(this.rootDoc)) {
-                //     newDoc.overlayX = this.rootDoc.x;
-                //     newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
-                //     Doc.AddToMyOverlay(newDoc);
-                // } else {
-                //     this.props.addDocument?.(newDoc);
-                // }
-                // // Create link between prompt and image
-                // DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' });
-            }
-        } catch (err) {
-            console.log(err);
-            return '';
-        }
-        GPTPopup.Instance.setLoading(false);
+        // GPTPopup.Instance.setLoading(true);
+        GPTPopup.Instance.setImgDesc((this.dataDoc.text as RichTextField)?.Text);
+        GPTPopup.Instance.generateImage();
+
+        // try {
+        //     // make this support multiple images
+        //     let image_urls = await gptImageCall((this.dataDoc.text as RichTextField)?.Text);
+        //     console.log(image_urls);
+        //     if (image_urls) {
+        //         const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
+        //         const source = Utils.prepend(result.accessPaths.agnostic.client);
+        //         GPTPopup.Instance.setImgUrls([source]);
+
+        //         // const newDoc = Docs.Create.ImageDocument(source, {
+        //         //     x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10,
+        //         //     y: NumCast(this.rootDoc.y),
+        //         //     _height: 200,
+        //         //     _width: 200,
+        //         //     data_nativeWidth: result.nativeWidth,
+        //         //     data_nativeHeight: result.nativeHeight,
+        //         // });
+        //         // if (Doc.IsInMyOverlay(this.rootDoc)) {
+        //         //     newDoc.overlayX = this.rootDoc.x;
+        //         //     newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
+        //         //     Doc.AddToMyOverlay(newDoc);
+        //         // } else {
+        //         //     this.props.addDocument?.(newDoc);
+        //         // }
+        //         // // Create link between prompt and image
+        //         // DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' });
+        //     }
+        // } catch (err) {
+        //     console.log(err);
+        //     return '';
+        // }
+        // GPTPopup.Instance.setLoading(false);
     };
 
     breakupDictation = () => {
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index f8f9fe077..99068d647 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -3,7 +3,7 @@ import React = require('react');
 import { useEffect, useRef, useState } from 'react';
 import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
 import { BrushHandler } from './generativeFillUtils/BrushHandler';
-import { Box, IconButton, Slider, TextField } from '@mui/material';
+import { Box, Checkbox, FormControlLabel, IconButton, Slider, TextField } from '@mui/material';
 import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces';
 import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
 import { PointerHandler } from './generativeFillUtils/PointerHandler';
@@ -19,6 +19,7 @@ import { Cast, DocCast, NumCast } from '../../../../fields/Types';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { OpenWhere, OpenWhereMod } from '../DocumentView';
 import { Oval } from 'react-loader-spinner';
+import { CheckBox } from '../../search/CheckBox';
 
 /**
  * For images not 1024x1024 fill in the rest in solid black, or a
@@ -72,6 +73,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const [input, setInput] = useState('');
     const [loading, setLoading] = useState(false);
     const [saveLoading, setSaveLoading] = useState(false);
+    const [isNewCollection, setIsNewCollection] = useState(false);
     // the current image in the main canvas
     const currImg = useRef<HTMLImageElement | null>(null);
     // the unedited version of each generation (parent)
@@ -81,9 +83,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     // stores redo stack
     const redoStack = useRef<string[]>([]);
 
-    // early stage properly, likely will get rid of
-    const freeformPosition = useRef<number[]>([0, 0]);
-
     // which urls were already saved to canvas
     const savedSrcs = useRef<Set<string>>(new Set());
 
@@ -91,7 +90,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const newCollectionRef = useRef<Doc | null>(null);
     const parentDoc = useRef<Doc | null>(null);
     const childrenDocs = useRef<Doc[]>([]);
-    const addToExistingCollection = useRef<boolean>(false);
 
     // Undo and Redo
     const handleUndo = () => {
@@ -111,10 +109,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     };
 
     const handleRedo = () => {
-        // TODO: handle undo as well
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx || !currImg.current || !canvasRef.current) return;
+
         const target = redoStack.current[redoStack.current.length - 1];
         if (!target) {
         } else {
+            undoStack.current = [...undoStack.current, canvasRef.current?.toDataURL()];
             const img = new Image();
             img.src = target;
             ImageUtility.drawImgToCanvas(img, canvasRef);
@@ -188,7 +189,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         ImageUtility.drawImgToCanvas(img, canvasRef);
         currImg.current = img;
         originalImg.current = img;
-        freeformPosition.current = [0, 0];
 
         return () => {
             console.log('cleanup');
@@ -197,7 +197,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             childrenDocs.current = [];
             currImg.current = null;
             originalImg.current = null;
-            freeformPosition.current = [0, 0];
             undoStack.current = [];
             redoStack.current = [];
         };
@@ -266,24 +265,25 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
             // create first image
             if (!newCollectionRef.current) {
-                if (addToExistingCollection.current) {
+                if (!isNewCollection) {
+                    // newcollection should stay null
+                } else {
+                    if (!(originalImg.current && imageRootDoc)) return;
+                    // create new collection and add it to the view
+                    newCollectionRef.current = Docs.Create.FreeformDocument([], {
+                        x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX,
+                        y: NumCast(imageRootDoc.y),
+                        _width: newCollectionSize,
+                        _height: newCollectionSize,
+                        title: 'Image edit collection',
+                    });
+                    DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
+                    // add the doc to the main freeform
+                    addDoc?.(newCollectionRef.current);
+                    await createNewImgDoc(originalImg.current, true);
                 }
-                if (!(originalImg.current && imageRootDoc)) return;
-                console.log('creating first image');
-                // create new collection and add it to the view
-                newCollectionRef.current = Docs.Create.FreeformDocument([], {
-                    x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + offsetX,
-                    y: NumCast(imageRootDoc.y),
-                    _width: newCollectionSize,
-                    _height: newCollectionSize,
-                    title: 'Image edit collection',
-                });
-                DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
-                // add the doc to the main freeform
-                // addDoc?.(newCollectionRef.current);
-                await createNewImgDoc(originalImg.current, true);
             } else {
-                parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1];
+                // parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1];
                 childrenDocs.current = [];
             }
 
@@ -292,12 +292,24 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             const { urls } = res as APISuccess;
             const image = new Image();
             image.src = urls[0];
+
+            // need to crop images agh
+            // save to dash
+
+            // const imageRes = await Promise.all(
+            //     urls.map(async url => {
+            //         const newImg = new Image();
+            //         newImg.src = url;
+            //         await onSave(newImg);
+            //     })
+            // );
+
+            // map each url to [url, imgDoc]
             setEdits(urls);
+
             ImageUtility.drawImgToCanvas(image, canvasRef);
             currImg.current = image;
-            onSave();
-            freeformPosition.current[0] += 1;
-            freeformPosition.current[1] = 0;
+            // onSave(currImg.current);
         } catch (err) {
             console.log(err);
         }
@@ -324,7 +336,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
     // creates a new image document and returns its reference
     const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => {
-        if (!newCollectionRef.current || !imageRootDoc) return;
+        if (!imageRootDoc) return;
         const src = img.src;
         const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
         const source = Utils.prepend(result.accessPaths.agnostic.client);
@@ -332,7 +344,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         if (firstDoc) {
             const x = 0;
             const initialY = 0;
-            console.log('first doc');
 
             const newImg = Docs.Create.ImageDocument(source, {
                 x: x,
@@ -343,16 +354,19 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 data_nativeHeight: result.nativeHeight,
             });
 
-            Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+            if (isNewCollection && newCollectionRef.current) {
+                Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+            } else {
+                addDoc?.(newImg);
+            }
+
             parentDoc.current = newImg;
             return newImg;
         } else {
             if (!parentDoc.current) return;
             const x = NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
             // dummy position
-            console.log('creating child elements');
             const initialY = 0;
-
             const newImg = Docs.Create.ImageDocument(source, {
                 x: x,
                 y: initialY,
@@ -363,21 +377,26 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             });
 
             childrenDocs.current.push(newImg);
+
+            if (isNewCollection && newCollectionRef.current) {
+                Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+            } else {
+                addDoc?.(newImg);
+            }
+
             DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit', link_displayLine: true });
             adjustImgPositions();
-
-            Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
             return newImg;
         }
     };
 
     // need to maybe call on every img click, not just when the save btn is clicked
-    const onSave = async () => {
+    const onSave = async (img: HTMLImageElement) => {
         setSaveLoading(true);
-        if (!currImg.current || !originalImg.current || !imageRootDoc) return;
+        // if (!currImg.current || !originalImg.current || !imageRootDoc) return;
         try {
             console.log('creating another image');
-            await createNewImgDoc(currImg.current, false);
+            await createNewImgDoc(img, false);
         } catch (err) {
             console.log(err);
         }
@@ -387,7 +406,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const handleViewClose = () => {
         if (newCollectionRef.current) {
             newCollectionRef.current.fitContentOnce = true;
-            CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+            // CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
         }
         MainView.Instance.setImageEditorOpen(false);
         MainView.Instance.setImageEditorSource('');
@@ -399,7 +418,20 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             <div className="generativeFillControls">
                 <h1>AI Image Editor</h1>
                 <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
-                    <Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onSave={onSave} onReset={handleReset} />
+                    <FormControlLabel
+                        control={
+                            <Checkbox
+                                checked={isNewCollection}
+                                onChange={e => {
+                                    setIsNewCollection(prev => !prev);
+                                }}
+                            />
+                        }
+                        label={'Create New Collection'}
+                        labelPlacement="end"
+                        sx={{ whiteSpace: 'nowrap' }}
+                    />
+                    <Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onReset={handleReset} />
                     <IconButton onClick={handleViewClose}>
                         <BsX color={activeColor} />
                     </IconButton>
@@ -491,13 +523,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                             height={100}
                             src={edit}
                             onClick={async () => {
-                                // if (savedSrcs.current.has(edit)) return;
                                 const img = new Image();
                                 img.src = edit;
                                 ImageUtility.drawImgToCanvas(img, canvasRef);
                                 currImg.current = img;
                                 savedSrcs.current.add(edit);
-                                await onSave();
+                                undoStack.current = [];
+                                redoStack.current = [];
+                                await onSave(img);
                             }}
                         />
                     ))}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 53c6cec84..b4d56b408 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -11,14 +11,10 @@ interface ButtonContainerProps {
     currImg: React.MutableRefObject<HTMLImageElement | null>;
     getEdit: () => Promise<void>;
     loading: boolean;
-    onSave: () => Promise<void>;
     onReset: () => void;
 }
 
-const Buttons = ({ canvasRef, currImg, loading, getEdit, onSave, onReset }: ButtonContainerProps) => {
-    const handleSave = () => {
-        onSave();
-    };
+const Buttons = ({ canvasRef, currImg, loading, getEdit, onReset }: ButtonContainerProps) => {
 
     return (
         <div className="generativeFillBtnContainer">
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 7404650d6..1be8fe6ab 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -130,6 +130,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * @param e pointer down event
      */
     gptSummarize = async (e: React.PointerEvent) => {
+        // move this logic to gptpopup, need to implement generate again
         GPTPopup.Instance.setVisible(true);
         this.setHighlightRange(undefined);
         GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.scss b/src/client/views/pdf/GPTPopup/GPTPopup.scss
index 478b7d4ba..5d966395c 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.scss
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.scss
@@ -123,9 +123,11 @@ $highlightedText: #82e0ff;
         cursor: pointer;
 
         .img-container {
+            pointer-events: none;
             position: relative;
 
             img {
+                pointer-events: all;
                 position: relative;
             }
         }
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index fc6fc1af8..943c38d42 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -10,6 +10,11 @@ import { DocUtils, Docs } from '../../../documents/Documents';
 import { Button, IconButton, Type } from 'browndash-components';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { CgClose } from 'react-icons/cg';
+import { AnchorMenu } from '../AnchorMenu';
+import { gptAPICall, gptImageCall } from '../../../apis/gpt/GPT';
+import { RichTextField } from '../../../../fields/RichTextField';
+import { Networking } from '../../../Network';
+import { Utils } from '../../../../Utils';
 
 export enum GPTPopupMode {
     SUMMARY,
@@ -43,10 +48,17 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
     };
 
     @observable
-    public imageUrls: string[] = [];
+    public imgDesc: string = '';
     @action
-    public setImgUrls = (imgs: string[]) => {
-        this.imageUrls = imgs;
+    public setImgDesc = (text: string) => {
+        this.imgDesc = text;
+    };
+
+    @observable
+    public imgUrls: string[][] = [];
+    @action
+    public setImgUrls = (imgs: string[][]) => {
+        this.imgUrls = imgs;
     };
 
     @observable
@@ -102,6 +114,30 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
     public addDoc: (doc: Doc | Doc[], sidebarKey?: string | undefined) => boolean = () => false;
     public addToCollection: ((doc: Doc | Doc[], annotationKey?: string | undefined) => boolean) | undefined;
 
+    generateImage = async () => {
+        if (this.imgDesc === '') return;
+        this.setImgUrls([]);
+        this.setMode(GPTPopupMode.IMAGE);
+        this.setVisible(true);
+        this.setLoading(true);
+
+        try {
+            // make this support multiple images
+            let image_urls = await gptImageCall(this.imgDesc);
+            console.log(image_urls);
+            if (image_urls && image_urls[0]) {
+                const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
+                const source = Utils.prepend(result.accessPaths.agnostic.client);
+                console.log('Source', source);
+                this.setImgUrls([[image_urls[0], source]]);
+            }
+        } catch (err) {
+            console.log(err);
+            return '';
+        }
+        GPTPopup.Instance.setLoading(false);
+    };
+
     /**
      * Transfers the summarization text to a sidebar annotation text document.
      */
@@ -113,8 +149,9 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
             _layout_autoHeight: true,
         });
         this.addDoc(newDoc, this.sidebarId);
-        if (this.targetAnchor) {
-            DocUtils.MakeLink(newDoc, this.targetAnchor, {
+        const anchor = AnchorMenu.Instance?.GetAnchor(undefined, false);
+        if (anchor) {
+            DocUtils.MakeLink(newDoc, anchor, {
                 link_relationship: 'GPT Summary',
             });
         }
@@ -163,17 +200,22 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
             <div style={{ display: 'flex', flexDirection: 'column', gap: '1rem' }}>
                 {this.heading('GENERATED IMAGE')}
                 <div className="image-content-wrapper">
-                    {this.imageUrls.map(rawSrc => (
+                    {this.imgUrls.map(rawSrc => (
                         <div className="img-wrapper">
                             <div className="img-container">
-                                <img key={rawSrc} src={this.getPreviewUrl(rawSrc)} width={150} height={150} alt="dalle generation" />
+                                <img key={rawSrc[0]} src={rawSrc[0]} width={150} height={150} alt="dalle generation" />
                             </div>
                             <div className="btn-container">
-                                <Button text="Save Image" onClick={() => this.transferToImage(rawSrc)} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
+                                <Button text="Save Image" onClick={() => this.transferToImage(rawSrc[1])} color={StrCast(Doc.UserDoc().userColor)} type={Type.TERT} />
                             </div>
                         </div>
                     ))}
                 </div>
+                {!this.loading && (
+                    <>
+                        <IconButton tooltip="Generate Again" onClick={this.generateImage} icon={<FontAwesomeIcon icon="redo-alt" size="lg" />} color={StrCast(Doc.UserDoc().userVariantColor)} />
+                    </>
+                )}
             </div>
         );
     };
@@ -227,43 +269,6 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
         </>
     );
 
-    editBox = () => {
-        const hr = this.highlightRange;
-        return (
-            <>
-                <div>
-                    {this.heading('TEXT EDIT SUGGESTIONS')}
-                    <div className="content-wrapper">
-                        {hr && (
-                            <div>
-                                {this.text.slice(0, hr[0])} <span className="highlighted-text">{this.text.slice(hr[0], hr[1])}</span> {this.text.slice(hr[1])}
-                            </div>
-                        )}
-                    </div>
-                </div>
-                {hr && !this.loading && (
-                    <>
-                        <div className="btns-wrapper">
-                            <>
-                                <button className="icon-btn" onPointerDown={this.callEditApi}>
-                                    <FontAwesomeIcon icon="redo-alt" size="lg" />
-                                </button>
-                                <button
-                                    className="text-btn"
-                                    onClick={e => {
-                                        this.replaceText(this.text);
-                                    }}>
-                                    Replace Text
-                                </button>
-                            </>
-                        </div>
-                        {this.aiWarning()}
-                    </>
-                )}
-            </>
-        );
-    };
-
     aiWarning = () =>
         this.done ? (
             <div className="ai-warning">
@@ -277,14 +282,14 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
     heading = (headingText: string) => (
         <div className="summary-heading">
             <label className="summary-text">{headingText}</label>
-            {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />}
+            {this.loading ? <ReactLoading type="spin" color="#bcbcbc" width={14} height={14} /> : <IconButton color={StrCast(Doc.UserDoc().userVariantColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={() => this.setVisible(false)} />}
         </div>
     );
 
     render() {
         return (
             <div className="summary-box" style={{ display: this.visible ? 'flex' : 'none' }}>
-                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : this.editBox()}
+                {this.mode === GPTPopupMode.SUMMARY ? this.summaryBox() : this.mode === GPTPopupMode.IMAGE ? this.imageBox() : <></>}
             </div>
         );
     }
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index c9fee4813..1319a236d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -424,10 +424,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
 
         // Changing which document to add the annotation to (the currently selected PDF)
         GPTPopup.Instance.setSidebarId('data_sidebar');
-        const anchor = this._getAnchor(undefined, false);
-        if (anchor) {
-            GPTPopup.Instance.setTargetAnchor(anchor);
-        }
         GPTPopup.Instance.addDoc = this.props.sidebarAddDoc;
     };
 
-- 
cgit v1.2.3-70-g09d2


From ad2f3695ddfb4a7bec17ddbdf6e7011862936bc4 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Fri, 18 Aug 2023 17:41:51 -0400
Subject: added gen_fill_children field

---
 .../views/nodes/formattedText/FormattedTextBox.tsx | 37 ----------------------
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 24 ++++++++------
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  5 +--
 3 files changed, 16 insertions(+), 50 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fd30c43d7..daf8719eb 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -946,46 +946,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
         GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
         GPTPopup.Instance?.setImgTargetDoc(this.rootDoc);
-        // GPTPopup.Instance.setImgUrls([]);
-        // GPTPopup.Instance.setMode(GPTPopupMode.IMAGE);
-        // GPTPopup.Instance.setVisible(true);
         GPTPopup.Instance.addToCollection = this.props.addDocument;
-        // GPTPopup.Instance.setLoading(true);
         GPTPopup.Instance.setImgDesc((this.dataDoc.text as RichTextField)?.Text);
         GPTPopup.Instance.generateImage();
-
-        // try {
-        //     // make this support multiple images
-        //     let image_urls = await gptImageCall((this.dataDoc.text as RichTextField)?.Text);
-        //     console.log(image_urls);
-        //     if (image_urls) {
-        //         const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
-        //         const source = Utils.prepend(result.accessPaths.agnostic.client);
-        //         GPTPopup.Instance.setImgUrls([source]);
-
-        //         // const newDoc = Docs.Create.ImageDocument(source, {
-        //         //     x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10,
-        //         //     y: NumCast(this.rootDoc.y),
-        //         //     _height: 200,
-        //         //     _width: 200,
-        //         //     data_nativeWidth: result.nativeWidth,
-        //         //     data_nativeHeight: result.nativeHeight,
-        //         // });
-        //         // if (Doc.IsInMyOverlay(this.rootDoc)) {
-        //         //     newDoc.overlayX = this.rootDoc.x;
-        //         //     newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height);
-        //         //     Doc.AddToMyOverlay(newDoc);
-        //         // } else {
-        //         //     this.props.addDocument?.(newDoc);
-        //         // }
-        //         // // Create link between prompt and image
-        //         // DocUtils.MakeLink(this.rootDoc, newDoc, { link_relationship: 'Image Prompt' });
-        //     }
-        // } catch (err) {
-        //     console.log(err);
-        //     return '';
-        // }
-        // GPTPopup.Instance.setLoading(false);
     };
 
     breakupDictation = () => {
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 92ce3a8a8..06819d08c 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -12,12 +12,11 @@ import { CiUndo, CiRedo } from 'react-icons/ci';
 import { MainView } from '../../MainView';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { Networking } from '../../../Network';
-import { Utils, returnEmptyDoclist } from '../../../../Utils';
+import { Utils } from '../../../../Utils';
 import { DocUtils, Docs } from '../../../documents/Documents';
-import { DocCast, NumCast } from '../../../../fields/Types';
+import { NumCast } from '../../../../fields/Types';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { OpenWhereMod } from '../DocumentView';
-import { Oval } from 'react-loader-spinner';
 import Buttons from './GenerativeFillButtons';
 import { List } from '../../../../fields/List';
 
@@ -314,12 +313,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const adjustImgPositions = () => {
         if (!parentDoc.current) return;
         const startY = NumCast(parentDoc.current.y);
-        const len = childrenDocs.current.length;
+        const children = DocListCast(parentDoc.current.gen_fill_children);
+        const len = children.length;
         let initialYPositions: number[] = [];
         for (let i = 0; i < len; i++) {
             initialYPositions.push(startY + i * offsetDistanceY);
         }
-        childrenDocs.current.forEach((doc, i) => {
+        children.forEach((doc, i) => {
             if (len % 2 === 1) {
                 doc.y = initialYPositions[i] - Math.floor(len / 2) * offsetDistanceY;
             } else {
@@ -368,9 +368,15 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 data_nativeWidth: result.nativeWidth,
                 data_nativeHeight: result.nativeHeight,
             });
-            newImg.gen_fill_children = new List<Doc>([]);
-            childrenDocs.current.push(newImg);
-            // DocListCast(parentDoc.current.gen_fill_children).push(newImg);
+
+            const parentList = DocListCast(parentDoc.current.gen_fill_children);
+            if (parentList.length > 0) {
+                parentList.push(newImg);
+                parentDoc.current.gen_fill_children = new List<Doc>(parentList);
+            } else {
+                parentDoc.current.gen_fill_children = new List<Doc>([newImg]);
+            }
+
             DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true });
             adjustImgPositions();
 
@@ -408,7 +414,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     return (
         <div className="generativeFillContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
             <div className="generativeFillControls">
-                <h1>AI Image Editor</h1>
+                <h1>Image Editor</h1>
                 <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
                     <FormControlLabel
                         control={
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 943c38d42..43fd8df16 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -11,8 +11,7 @@ import { Button, IconButton, Type } from 'browndash-components';
 import { NumCast, StrCast } from '../../../../fields/Types';
 import { CgClose } from 'react-icons/cg';
 import { AnchorMenu } from '../AnchorMenu';
-import { gptAPICall, gptImageCall } from '../../../apis/gpt/GPT';
-import { RichTextField } from '../../../../fields/RichTextField';
+import { gptImageCall } from '../../../apis/gpt/GPT';
 import { Networking } from '../../../Network';
 import { Utils } from '../../../../Utils';
 
@@ -122,9 +121,7 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
         this.setLoading(true);
 
         try {
-            // make this support multiple images
             let image_urls = await gptImageCall(this.imgDesc);
-            console.log(image_urls);
             if (image_urls && image_urls[0]) {
                 const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_urls[0]] });
                 const source = Utils.prepend(result.accessPaths.agnostic.client);
-- 
cgit v1.2.3-70-g09d2


From 6913400a92d518989e831926567429c31703e4b4 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Fri, 18 Aug 2023 18:46:27 -0400
Subject: cleanup

---
 .../views/nodes/formattedText/FormattedTextBox.tsx   | 20 --------------------
 1 file changed, 20 deletions(-)

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

diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2562c48e4..da277826a 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -907,7 +907,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     animateRes = (resIndex: number, newText: string) => {
         if (resIndex < newText.length) {
             const marks = this._editorView?.state.storedMarks ?? [];
-            // if (!marks) return;
             this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText(newText[resIndex]).setStoredMarks(marks));
             setTimeout(() => {
                 this.animateRes(resIndex + 1, newText);
@@ -915,19 +914,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     };
 
-    // breakupdictiation for reference
-    // prepareForTyping
-    // stored marks - userMark and timestamp, setStoredMarks
-    // the._editorView.dispatch(state.tr.setSelection(updated).insertText('\n, to))
-
     askGPT = action(async () => {
-        // const state = this._editorView?.state;
-        // if (!state) return;
-        // const to = state.selection.to;
-        // const updated = TextSelection.create(state.doc, to, to);
-        // this._editorView?.dispatch(state.tr.setSelection(updated).insertText('\n', to));
-        // this._editorView?.dispatch(this._editorView.state.tr.setStoredMarks(marks).insertText('\nTesting').setStoredMarks(marks));
-        // console.log('After ', this._editorView?.state.storedMarks);
         try {
             let res = await gptAPICall((this.dataDoc.text as RichTextField)?.Text, GPTCallType.COMPLETION);
             if (!res) {
@@ -1243,13 +1230,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props);
                     setTimeout(this.autoLink, 20);
                 }
-                // // Accessing editor and text doc for gpt assisted text edits
-                // if (this._editorView && selected) {
-                //     console.log('Setting');
-                //     GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
-                //     // AnchorMenu.Instance?.setEditorView(this._editorView);
-                //     // AnchorMenu.Instance?.setTextDoc(this.dataDoc);
-                // }
             }),
             { fireImmediately: true }
         );
-- 
cgit v1.2.3-70-g09d2


From cf5ffaf0dcf4c2b8ad9e408026b494106401db8e Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Fri, 25 Aug 2023 13:42:41 -0400
Subject: changed icon, added gen ai doc link

---
 src/client/util/RTFMarkup.tsx                      | 12 +++++++++-
 src/client/views/ContextMenuItem.tsx               | 28 +++++++++++++++-------
 src/client/views/MainView.tsx                      |  3 ++-
 src/client/views/nodes/DocumentView.tsx            |  4 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 ++--
 .../nodes/generativeFill/GenerativeFillButtons.tsx |  4 +++-
 6 files changed, 39 insertions(+), 16 deletions(-)

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

diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index a0fc617ab..78069d323 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -3,6 +3,8 @@ import { observer } from 'mobx-react';
 import * as React from 'react';
 import { MainViewModal } from '../views/MainViewModal';
 import { SettingsManager } from './SettingsManager';
+import { Doc } from '../../fields/Doc';
+import { StrCast } from '../../fields/Types';
 
 @observer
 export class RTFMarkup extends React.Component<{}> {
@@ -133,6 +135,14 @@ export class RTFMarkup extends React.Component<{}> {
     }
 
     render() {
-        return <MainViewModal contents={this.cheatSheet} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />;
+        return (
+            <MainViewModal
+                dialogueBoxStyle={{ backgroundColor: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor), padding: '16px' }}
+                contents={this.cheatSheet}
+                isDisplayed={this.isOpen}
+                interactive={true}
+                closeOnExternalClick={this.close}
+            />
+        );
     }
 }
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index daa2c152a..445406a89 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -11,7 +11,7 @@ export interface OriginalMenuProps {
     description: string;
     event: (stuff?: any) => void;
     undoable?: boolean;
-    icon: IconProp; //maybe should be optional (icon?)
+    icon: IconProp | JSX.Element; //maybe should be optional (icon?)
     closeMenu?: () => void;
 }
 
@@ -82,19 +82,28 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
         );
     };
 
+    isJSXElement(val: any): val is JSX.Element {
+        return React.isValidElement(val);
+    }
+
     render() {
         if ('event' in this.props) {
             return (
                 <div className={'contextMenu-item' + (this.props.selected ? ' contextMenu-itemSelected' : '')} onPointerDown={this.handleEvent}>
                     {this.props.icon ? (
-                        <span className="contextMenu-item-icon-background">
-                            <FontAwesomeIcon icon={this.props.icon} size="sm" />
-                        </span>
+                        this.isJSXElement(this.props.icon) ? (
+                            <span className="contextMenu-item-icon-background">{this.props.icon}</span>
+                        ) : (
+                            <span className="contextMenu-item-icon-background">
+                                <FontAwesomeIcon icon={this.props.icon} size="sm" />
+                            </span>
+                        )
                     ) : null}
                     <div className="contextMenu-description">{this.props.description.replace(':', '')}</div>
-                    <div className={`contextMenu-item-background`}
+                    <div
+                        className={`contextMenu-item-background`}
                         style={{
-                            background: StrCast(Doc.UserDoc().userColor)
+                            background: StrCast(Doc.UserDoc().userColor),
                         }}
                     />
                 </div>
@@ -110,7 +119,7 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
                     style={{
                         marginLeft: window.innerHeight - this._overPosX - 50 > 0 ? '90%' : '20%',
                         marginTop,
-                        background: StrCast(Doc.UserDoc().userBackgroundColor)
+                        background: StrCast(Doc.UserDoc().userBackgroundColor),
                     }}>
                     {this._items.map(prop => (
                         <ContextMenuItem {...prop} key={prop.description} closeMenu={this.props.closeMenu} />
@@ -141,9 +150,10 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select
                         {this.props.description}
                         <FontAwesomeIcon icon={'angle-right'} size="lg" style={{ position: 'absolute', right: '10px' }} />
                     </div>
-                    <div className={`contextMenu-item-background`}
+                    <div
+                        className={`contextMenu-item-background`}
                         style={{
-                            background: StrCast(Doc.UserDoc().userColor)
+                            background: StrCast(Doc.UserDoc().userColor),
                         }}
                     />
                     {submenu}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a785ffd42..8565941fd 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -447,6 +447,7 @@ export class MainView extends React.Component {
                 fa.faSortUp,
                 fa.faSortDown,
                 fa.faTable,
+                fa.faTableColumns,
                 fa.faTh,
                 fa.faThList,
                 fa.faProjectDiagram,
@@ -1019,7 +1020,7 @@ export class MainView extends React.Component {
                 <OverlayView />
                 <GPTPopup key="gptpopup" />
                 <GenerativeFill imageEditorOpen={ImageBox.imageEditorOpen} imageEditorSource={ImageBox.imageEditorSource} imageRootDoc={ImageBox.imageRootDoc} addDoc={ImageBox.addDoc} />
-               {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
+                {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */}
             </div>
         );
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 533a047b1..b72864567 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -748,7 +748,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         event: undoBatch(action(() => (this.rootDoc._raiseWhenDragged = this.rootDoc._raiseWhenDragged === undefined ? false : undefined))),
                         icon: 'hand-point-up',
                     });
-                    !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'compass' });
+                    !zorders && cm.addItem({ description: 'Z Order...', addDivider: true, noexpand: true, subitems: zorderItems, icon: 'layer-group' });
                 }
 
                 onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' });
@@ -806,7 +806,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' });
             }
         }
-        constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' });
+        constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'table-columns' });
         cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' });
 
         const help = cm.findByDescription('Help...');
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index da277826a..200d06a0b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -71,6 +71,7 @@ import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
 import { GPTPopup, GPTPopupMode } from '../../pdf/GPTPopup/GPTPopup';
+import { BsMarkdownFill } from 'react-icons/bs';
 const translateGoogleApi = require('translate-google-api');
 export const GoogleRef = 'googleDocId';
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@@ -899,7 +900,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 event: () => (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight),
                 icon: this.Document._layout_autoHeight ? 'lock' : 'unlock',
             });
-        optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: 'text' });
+        optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: <BsMarkdownFill /> });
         !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
         this._downX = this._downY = Number.NaN;
     };
@@ -930,7 +931,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     });
 
     generateImage = async () => {
-        console.log('Generate image from text: ', (this.dataDoc.text as RichTextField)?.Text);
         GPTPopup.Instance?.setTextAnchor(this.getAnchor(false));
         GPTPopup.Instance?.setImgTargetDoc(this.rootDoc);
         GPTPopup.Instance.addToCollection = this.props.addDocument;
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 0dfcebea3..10eca358e 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -2,7 +2,8 @@ import './GenerativeFillButtons.scss';
 import React = require('react');
 import ReactLoading from 'react-loading';
 import { activeColor } from './generativeFillUtils/generativeFillConstants';
-import { Button, Type } from 'browndash-components';
+import { Button, IconButton, Type } from 'browndash-components';
+import { AiOutlineInfo } from 'react-icons/ai';
 
 interface ButtonContainerProps {
     getEdit: () => Promise<void>;
@@ -35,6 +36,7 @@ const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
                     }}
                 />
             )}
+            <IconButton type={Type.SEC} color={activeColor} tooltip="Open Documentation" icon={<AiOutlineInfo size={'16px'} />} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/generativeai/#editing', '_blank')} />
         </div>
     );
 };
-- 
cgit v1.2.3-70-g09d2

