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/ImageBox.tsx | 11 +++++++++++
 1 file changed, 11 insertions(+)

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 5b302e7ce..2c6f92681 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -38,6 +38,7 @@ import './ImageBox.scss';
 import { PinProps, PresBox } from './trails';
 import React = require('react');
 import Color = require('color');
+import { MainView } from '../MainView';
 
 export const pageSchema = createSchema({
     googlePhotosUrl: 'string',
@@ -248,6 +249,16 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             funcs.push({ description: `Show ${this.layoutDoc._showFullRes ? 'Dynamic Res' : 'Full Res'}`, event: this.resolution, icon: 'expand' });
             funcs.push({ description: 'Set Native Pixel Size', event: this.setNativeSize, icon: 'expand-arrows-alt' });
             funcs.push({ description: 'Copy path', event: () => Utils.CopyText(this.choosePath(field.url)), icon: 'copy' });
+            funcs.push({
+                description: 'Open Image Editor',
+                event: action(() => {
+                    MainView.Instance.setImageEditorOpen(true);
+                    MainView.Instance.setImageEditorSource(this.choosePath(field.url));
+                    MainView.Instance.addDoc = this.props.addDocument;
+                    MainView.Instance.imageRootDoc = this.rootDoc;
+                }),
+                icon: 'pencil-alt',
+            });
             if (!Doc.noviceMode) {
                 funcs.push({ description: 'Export to Google Photos', event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: 'caret-square-right' });
 
-- 
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/ImageBox.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 592e1a1b1000157db77bd812b8debfbcc45844f9 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 17 Aug 2023 17:52:12 -0400
Subject: revert

---
 src/client/views/nodes/ImageBox.tsx                |   4 +-
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 115 ++++++++-------------
 .../generativeFillUtils/ImageHandler.ts            |   2 +
 3 files changed, 46 insertions(+), 75 deletions(-)

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 352e0fbdc..603994016 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.imageEditorOpen = true;
-                    MainView.Instance.imageEditorSource = this.choosePath(field.url);
+                    MainView.Instance.setImageEditorOpen(true);
+                    MainView.Instance.setImageEditorSource(this.choosePath(field.url));
                     MainView.Instance.addDoc = this.props.addDocument;
                     MainView.Instance.imageRootDoc = this.rootDoc;
                 }),
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 99068d647..f136982bc 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, Checkbox, FormControlLabel, IconButton, Slider, 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';
@@ -19,7 +19,6 @@ 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
@@ -73,7 +72,6 @@ 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)
@@ -83,6 +81,9 @@ 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());
 
@@ -90,6 +91,7 @@ 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 = () => {
@@ -109,13 +111,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     };
 
     const handleRedo = () => {
-        const ctx = ImageUtility.getCanvasContext(canvasRef);
-        if (!ctx || !currImg.current || !canvasRef.current) return;
-
+        // TODO: handle undo as well
         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);
@@ -185,10 +184,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         console.log('first load');
         if (!imageEditorSource || imageEditorSource === '') return;
         const img = new Image();
+        console.log('source', imageEditorSource);
         img.src = imageEditorSource;
+        console.log('drawing image');
         ImageUtility.drawImgToCanvas(img, canvasRef);
         currImg.current = img;
         originalImg.current = img;
+        freeformPosition.current = [0, 0];
 
         return () => {
             console.log('cleanup');
@@ -197,6 +199,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             childrenDocs.current = [];
             currImg.current = null;
             originalImg.current = null;
+            freeformPosition.current = [0, 0];
             undoStack.current = [];
             redoStack.current = [];
         };
@@ -265,25 +268,24 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
             // create first image
             if (!newCollectionRef.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 (addToExistingCollection.current) {
                 }
+                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,24 +294,12 @@ 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(currImg.current);
+            onSave();
+            freeformPosition.current[0] += 1;
+            freeformPosition.current[1] = 0;
         } catch (err) {
             console.log(err);
         }
@@ -336,7 +326,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 (!imageRootDoc) return;
+        if (!newCollectionRef.current || !imageRootDoc) return;
         const src = img.src;
         const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
         const source = Utils.prepend(result.accessPaths.agnostic.client);
@@ -344,6 +334,7 @@ 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,
@@ -354,19 +345,16 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 data_nativeHeight: result.nativeHeight,
             });
 
-            if (isNewCollection && newCollectionRef.current) {
-                Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
-            } else {
-                addDoc?.(newImg);
-            }
-
+            Doc.AddDocToList(newCollectionRef.current, undefined, 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,
@@ -377,26 +365,21 @@ 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 (img: HTMLImageElement) => {
+    const onSave = async () => {
         setSaveLoading(true);
-        // if (!currImg.current || !originalImg.current || !imageRootDoc) return;
+        if (!currImg.current || !originalImg.current || !imageRootDoc) return;
         try {
             console.log('creating another image');
-            await createNewImgDoc(img, false);
+            await createNewImgDoc(currImg.current, false);
         } catch (err) {
             console.log(err);
         }
@@ -406,7 +389,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('');
@@ -418,19 +401,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             <div className="generativeFillControls">
                 <h1>AI Image Editor</h1>
                 <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
-                    <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} />
@@ -523,14 +493,13 @@ 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);
-                                undoStack.current = [];
-                                redoStack.current = [];
-                                await onSave(img);
+                                await onSave();
                             }}
                         />
                     ))}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 45cf7196b..1954ab3fb 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -101,7 +101,9 @@ export class ImageUtility {
             ctx.clearRect(0, 0, canvasSize, canvasSize);
             ctx.drawImage(img, 0, 0, width, height);
         } else {
+            console.log('loading image');
             img.onload = () => {
+                console.log('loaded');
                 const ctx = this.getCanvasContext(canvasRef);
                 if (!ctx) return;
                 ctx.globalCompositeOperation = 'source-over';
-- 
cgit v1.2.3-70-g09d2


From 7b668e5c4ee804aa6363e635f2a24a9683d7b803 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 21 Aug 2023 13:23:33 -0400
Subject: got rid of transition time for images .. default duration should be
 sufficient?

---
 src/client/views/nodes/ImageBox.tsx | 1 -
 1 file changed, 1 deletion(-)

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c7ccd1ea0..44da98f75 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -82,7 +82,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 config_panX: NumCast(this.layoutDoc._freeform_panX),
                 config_panY: NumCast(this.layoutDoc._freeform_panY),
                 config_viewScale: Cast(this.layoutDoc._freeform_scale, 'number', null),
-                presentation_transition: 1000,
                 annotationOn: this.rootDoc,
             });
         if (anchor) {
-- 
cgit v1.2.3-70-g09d2


From 1a87cabca08f8919539644c439f38a9da9f7815e Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Wed, 23 Aug 2023 23:25:49 -0400
Subject: protecting against bad urls especially after generating thumbnails

---
 src/client/views/nodes/ImageBox.tsx      | 5 +++--
 src/client/views/nodes/WebBoxRenderer.js | 6 +++---
 2 files changed, 6 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 44da98f75..6595689f7 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -287,10 +287,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     @computed private get url() {
         const data = Cast(this.dataDoc[this.fieldKey], ImageField);
-        return data ? data.url.href : undefined;
+        return data ? data.url?.href : undefined;
     }
 
     choosePath(url: URL) {
+        if (!url?.href) return "";
         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);
@@ -318,7 +319,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         if (!(data instanceof ImageField)) {
             return null;
         }
-        const primary = data.url.href;
+        const primary = data.url?.href;
         if (primary.includes(window.location.origin)) {
             return null;
         }
diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js
index 60d997120..321cf3638 100644
--- a/src/client/views/nodes/WebBoxRenderer.js
+++ b/src/client/views/nodes/WebBoxRenderer.js
@@ -42,21 +42,21 @@ var ForeignHtmlRenderer = function (styleSheets) {
                 url = CorsProxy(new URL(webUrl).origin + inurl);
             } else if (!inurl.startsWith('http') && !inurl.startsWith('//')) {
                 url = CorsProxy(webUrl + '/' + inurl);
-            } else if (inurl.startsWith('https') && !inurl.startsWith("https://dashblobstore.blob.core.windows.net")) {
+            } else if (inurl.startsWith('https')) {
                 url = CorsProxy(inurl);
             }
             xhr.open('GET', url);
             xhr.responseType = 'blob';
 
             xhr.onreadystatechange = async function () {
-                if (xhr.readyState === 4 && xhr.status === 200) {
+                if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
                     const resBase64 = await binaryStringToBase64(xhr.response);
 
                     resolve({
                         resourceUrl: inurl,
                         resourceBase64: resBase64,
                     });
-                } else if (xhr.readyState === 4) {
+                } else if (xhr.readyState === XMLHttpRequest.DONE) {
                     console.log("COULDN'T FIND: " + (inurl.startsWith('/') ? webUrl + inurl : inurl));
                     resolve({
                         resourceUrl: '',
-- 
cgit v1.2.3-70-g09d2


From 522e8d499d7652848396bcd6e34600ae7d795c66 Mon Sep 17 00:00:00 2001
From: geireann <geireann.lindfield@gmail.com>
Date: Thu, 24 Aug 2023 11:10:06 -0400
Subject: moved generative fill variables into ImageBox from MainView to
 prevent awkward reference cycle

---
 src/client/views/MainView.tsx                           | 14 ++++----------
 src/client/views/nodes/ImageBox.tsx                     | 17 +++++++++++------
 .../views/nodes/generativeFill/GenerativeFill.tsx       |  5 +++--
 3 files changed, 18 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 67afa5867..559a8e8d5 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -55,18 +55,19 @@ import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from './n
 import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView';
 import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox';
 import { RichTextMenu } from './nodes/formattedText/RichTextMenu';
+import GenerativeFill from './nodes/generativeFill/GenerativeFill';
+import { ImageBox } from './nodes/ImageBox';
 import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup';
 import { LinkDocPreview } from './nodes/LinkDocPreview';
 import { RadialMenu } from './nodes/RadialMenu';
 import { TaskCompletionBox } from './nodes/TaskCompletedBox';
 import { OverlayView } from './OverlayView';
 import { AnchorMenu } from './pdf/AnchorMenu';
+import { GPTPopup } from './pdf/GPTPopup/GPTPopup';
 import { PreviewCursor } from './PreviewCursor';
 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
@@ -74,13 +75,6 @@ export class MainView extends React.Component {
     public static Instance: MainView;
     public static Live: boolean = false;
     private _docBtnRef = React.createRef<HTMLDivElement>();
-    // for ai image editor
-    @observable public imageEditorOpen: boolean = false;
-    @action public setImageEditorOpen = (open: boolean) => (this.imageEditorOpen = open);
-    @observable public imageEditorSource: string = '';
-    @action public setImageEditorSource = (source: string) => (this.imageEditorSource = source);
-    @observable public imageRootDoc: Doc | undefined;
-    @observable public addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
 
     @observable public LastButton: Opt<Doc>;
     @observable private _windowWidth: number = 0;
@@ -1024,7 +1018,7 @@ export class MainView extends React.Component {
                 {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} />
+                <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]} /> */}
             </div>
         );
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c4b3f79a7..2c8e97512 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -36,8 +36,6 @@ import { FieldView, FieldViewProps } from './FieldView';
 import './ImageBox.scss';
 import { PinProps, PresBox } from './trails';
 import React = require('react');
-import Color = require('color');
-import { MainView } from '../MainView';
 
 export const pageSchema = createSchema({
     googlePhotosUrl: 'string',
@@ -56,6 +54,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(ImageBox, fieldKey);
     }
+
+    @observable public static imageRootDoc: Doc | undefined;
+    @observable public static imageEditorOpen: boolean = false;
+    @observable public static imageEditorSource: string = '';
+    @observable public static addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+    @action public static setImageEditorOpen(open: boolean) {ImageBox.imageEditorOpen = open;}
+    @action public static setImageEditorSource(source: string) {ImageBox.imageEditorSource = source;}
     private _ignoreScroll = false;
     private _forcedScroll = false;
     private _dropDisposer?: DragManager.DragDropDisposer;
@@ -250,10 +255,10 @@ 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.addDoc = this.props.addDocument;
-                    MainView.Instance.imageRootDoc = this.rootDoc;
+                    ImageBox.setImageEditorOpen(true);
+                    ImageBox.setImageEditorSource(this.choosePath(field.url));
+                    ImageBox.addDoc = this.props.addDocument;
+                    ImageBox.imageRootDoc = this.rootDoc;
                 }),
                 icon: 'pencil-alt',
             });
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 7230aa9b1..9c03600cf 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -20,6 +20,7 @@ import { OpenWhereMod } from '../DocumentView';
 import Buttons from './GenerativeFillButtons';
 import { List } from '../../../../fields/List';
 import { CgClose } from 'react-icons/cg';
+import { ImageBox } from '../ImageBox';
 
 enum BrushStyle {
     ADD,
@@ -408,8 +409,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
     // Closes the editor view
     const handleViewClose = () => {
-        MainView.Instance.setImageEditorOpen(false);
-        MainView.Instance.setImageEditorSource('');
+        ImageBox.setImageEditorOpen(false);
+        ImageBox.setImageEditorSource('');
         if (newCollectionRef.current) {
             newCollectionRef.current.fitContentOnce = true;
         }
-- 
cgit v1.2.3-70-g09d2

