From b6537cce6aa34eb33c052d7ec2cbbf804be08fba Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Wed, 28 Jun 2023 11:47:41 -0400
Subject: added files

---
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 308 +++++++++++++++++++++
 1 file changed, 308 insertions(+)
 create mode 100644 src/client/views/nodes/generativeFill/GenerativeFill.tsx

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
new file mode 100644
index 000000000..6dd80a5d1
--- /dev/null
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -0,0 +1,308 @@
+import { useEffect, useRef, useState } from 'react';
+import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
+import { BrushHandler } from './generativeFillUtils/BrushHandler';
+import { IconButton, TextField } from '@mui/material';
+import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces';
+import { activeColor, canvasSize, eraserColor } from './generativeFillUtils/generativeFillConstants';
+import { PointerHandler } from './generativeFillUtils/PointerHandler';
+import { BsBrush, BsEraser } from 'react-icons/bs';
+import { AiOutlineUpload } from 'react-icons/ai';
+import { CiUndo, CiRedo } from 'react-icons/ci';
+import Buttons from './GenerativeFillButtons';
+import React from 'react';
+import './GenerativeFill.scss';
+
+/**
+ * For images not 1024x1024 fill in the rest in solid black, or a
+ * reflected version of the image.
+ */
+
+/**
+ * TODO: Look into img onload, sometimes the canvas doesn't update properly
+ */
+
+enum BrushStyle {
+    ADD,
+    SUBTRACT,
+    MARQUEE,
+}
+
+interface ImageEdit {
+    imgElement: HTMLImageElement;
+    parent: ImageEdit;
+    children: ImageEdit[];
+}
+
+const GenerativeFill = () => {
+    const canvasRef = useRef<HTMLCanvasElement>(null);
+    const canvasBackgroundRef = useRef<HTMLCanvasElement>(null);
+    const drawingAreaRef = useRef<HTMLDivElement>(null);
+    const fileRef = useRef<HTMLInputElement>(null);
+    const [cursorData, setCursorData] = useState<CursorData>({
+        x: 0,
+        y: 0,
+        width: 150,
+    });
+    const [isBrushing, setIsBrushing] = useState(false);
+    const [canvasScale, setCanvasScale] = useState(0.5);
+    const [edits, setEdits] = useState<string[]>([]);
+    const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD);
+    const [input, setInput] = useState('');
+    const [loading, setLoading] = useState(false);
+    // used to store the current image loaded to the main canvas
+    const currImg = useRef<HTMLImageElement | null>(null);
+    // ref to store history
+    const undoStack = useRef<ImageData[]>([]);
+
+    // initiate brushing
+    const handlePointerDown = (e: React.PointerEvent) => {
+        const canvas = canvasRef.current;
+        if (!canvas) return;
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx) return;
+
+        setIsBrushing(true);
+        const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
+
+        BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT);
+    };
+
+    // stop brushing, push to undo stack
+    const handlePointerUp = (e: React.PointerEvent) => {
+        const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
+        if (!ctx) return;
+        if (!isBrushing) return;
+        setIsBrushing(false);
+    };
+
+    // handles brushing on pointer movement
+    useEffect(() => {
+        if (!isBrushing) return;
+        const canvas = canvasRef.current;
+        if (!canvas) return;
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx) return;
+
+        const handlePointerMove = (e: PointerEvent) => {
+            const currPoint = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
+            const lastPoint: Point = {
+                x: currPoint.x - e.movementX / canvasScale,
+                y: currPoint.y - e.movementY / canvasScale,
+            };
+            BrushHandler.createBrushPathOverlay(lastPoint, currPoint, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT);
+        };
+
+        drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove);
+        return () => drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove);
+    }, [isBrushing]);
+
+    // first load
+    useEffect(() => {
+        const img = new Image();
+        img.src = '/assets/art.jpeg';
+        ImageUtility.drawImgToCanvas(img, canvasRef);
+        currImg.current = img;
+    }, [canvasRef]);
+
+    useEffect(() => {
+        if (!canvasBackgroundRef.current) return;
+        const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
+        if (!ctx) return;
+    }, [canvasBackgroundRef]);
+
+    // handles brush sizing
+    useEffect(() => {
+        const handleKeyPress = (e: KeyboardEvent) => {
+            if (e.key === 'ArrowUp') {
+                e.preventDefault();
+                e.stopPropagation();
+                setCursorData(data => ({ ...data, width: data.width + 5 }));
+            } else if (e.key === 'ArrowDown') {
+                e.preventDefault();
+                e.stopPropagation();
+                setCursorData(data => (data.width >= 20 ? { ...data, width: data.width - 5 } : data));
+            }
+        };
+        window.addEventListener('keydown', handleKeyPress);
+        return () => window.removeEventListener('keydown', handleKeyPress);
+    }, []);
+
+    // handle pinch zoom
+    useEffect(() => {
+        const handlePinch = (e: WheelEvent) => {
+            e.preventDefault();
+            e.stopPropagation();
+            const delta = e.deltaY;
+            const scaleFactor = delta > 0 ? 0.98 : 1.02; // Adjust the scale factor as per your requirement
+            setCanvasScale(prevScale => prevScale * scaleFactor);
+        };
+
+        drawingAreaRef.current?.addEventListener('wheel', handlePinch, {
+            passive: false,
+        });
+        return () => drawingAreaRef.current?.removeEventListener('wheel', handlePinch);
+    }, [drawingAreaRef]);
+
+    // updates the current position of the cursor
+    const updateCursorData = (e: React.PointerEvent) => {
+        const drawingArea = drawingAreaRef.current;
+        if (!drawingArea) return;
+        const { x, y } = PointerHandler.getPointRelativeToElement(drawingArea, e, 1);
+        setCursorData(data => ({
+            ...data,
+            x,
+            y,
+        }));
+    };
+
+    // 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;
+        if (!img) return;
+        const canvas = canvasRef.current;
+        if (!canvas) return;
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx) return;
+        setLoading(true);
+        try {
+            const maskBlob = await ImageUtility.canvasToBlob(canvas);
+            const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img));
+
+            // const res = await ImageUtility.getEdit(
+            //   imgBlob,
+            //   maskBlob,
+            //   input !== ""
+            //     ? input + " in the same style"
+            //     : "Fill in the image in the same style",
+            //   1
+            // );
+
+            const res = await ImageUtility.mockGetEdit();
+            const { urls } = res as APISuccess;
+            const image = new Image();
+            image.src = urls[0];
+            setLoading(false);
+            setEdits(urls);
+            ImageUtility.drawImgToCanvas(image, canvasRef);
+            currImg.current = image;
+        } catch (err) {
+            console.log(err);
+        }
+    };
+
+    return (
+        <div className="generativeFillContainer">
+            <div className="controls">
+                <h1>Generative Fill</h1>
+                <Buttons canvasRef={canvasRef} backgroundref={canvasBackgroundRef} currImg={currImg} getEdit={getEdit} undoStack={undoStack} loading={loading} />
+            </div>
+            {/* Main canvas for editing */}
+            <div
+                className="drawingArea" // this only works if pointerevents: none is set on the custom pointer
+                ref={drawingAreaRef}
+                onPointerOver={updateCursorData}
+                onPointerMove={updateCursorData}
+                onPointerDown={handlePointerDown}
+                onPointerUp={handlePointerUp}>
+                <canvas ref={canvasRef} width={canvasSize} height={canvasSize} style={{ transform: `scale(${canvasScale})` }} />
+                <canvas ref={canvasBackgroundRef} width={canvasSize} height={canvasSize} style={{ transform: `scale(${canvasScale})` }} />
+                <div
+                    className="pointer"
+                    style={{
+                        left: cursorData.x,
+                        top: cursorData.y,
+                        width: cursorData.width,
+                        height: cursorData.width,
+                    }}>
+                    <div className="innerPointer"></div>
+                </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'} />
+                    </IconButton>
+                    <IconButton
+                        onClick={() => {
+                            setBrushStyle(BrushStyle.SUBTRACT);
+                        }}>
+                        <BsEraser color={brushStyle === BrushStyle.SUBTRACT ? activeColor : 'inherit'} />
+                    </IconButton>
+                    {/* Undo and Redo */}
+                    {/* <IconButton
+                        onPointerDown={e => {
+                            e.stopPropagation();
+                            console.log(undoStack.current);
+                        }}
+                        onPointerUp={e => {
+                            e.stopPropagation();
+                        }}>
+                        <CiUndo />
+                    </IconButton>
+                    <IconButton onClick={() => {}}>
+                        <CiRedo />
+                    </IconButton> */}
+                </div>
+                {/* Edits box */}
+                <div className="editsBox">
+                    {edits.map(edit => (
+                        <img
+                            key={edit}
+                            width={100}
+                            height={100}
+                            src={edit}
+                            onClick={() => {
+                                const img = new Image();
+                                img.src = edit;
+                                ImageUtility.drawImgToCanvas(img, canvasRef);
+                                currImg.current = img;
+                            }}
+                        />
+                    ))}
+                </div>
+            </div>
+            <div>
+                <TextField
+                    value={input}
+                    onChange={e => setInput(e.target.value)}
+                    disabled={isBrushing}
+                    type="text"
+                    label="Prompt"
+                    placeholder="Prompt..."
+                    sx={{
+                        backgroundColor: '#ffffff',
+                        position: 'absolute',
+                        bottom: '1rem',
+                        transform: 'translateX(calc(50vw - 50%))',
+                        width: 'calc(100vw - 4rem)',
+                    }}
+                />
+            </div>
+        </div>
+    );
+};
+
+export default GenerativeFill;
-- 
cgit v1.2.3-70-g09d2


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/apis/gpt/GPT.ts                         |   2 +-
 src/client/views/MainView.tsx                      |  11 +
 src/client/views/nodes/ImageBox.tsx                |  11 +
 .../views/nodes/formattedText/FormattedTextBox.tsx |   1 +
 .../views/nodes/generativeFill/GenerativeFill.scss |   4 +-
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 157 +++++++++++---
 .../nodes/generativeFill/GenerativeFillButtons.tsx |  36 ++--
 .../generativeFillUtils/ImageHandler.ts            | 230 ++++++++++-----------
 .../generativeFillUtils/generativeFillConstants.ts |   5 +-
 9 files changed, 273 insertions(+), 184 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 4b3960902..18222b32a 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -39,7 +39,7 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
             temperature: opts.temp,
             prompt: `${opts.prompt}${inputText}`,
         });
-        console.log(response.data.choices[0]);
+        // console.log(response.data.choices[0]);
         return response.data.choices[0].text;
     } catch (err) {
         console.log(err);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c0c473cfc..1fa1eff84 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -65,6 +65,7 @@ import { PreviewCursor } from './PreviewCursor';
 import { PropertiesView } from './PropertiesView';
 import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider';
 import { TopBar } from './topbar/TopBar';
+import GenerativeFill from './nodes/generativeFill/GenerativeFill';
 const _global = (window /* browser */ || global) /* node */ as any;
 
 @observer
@@ -72,6 +73,15 @@ 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;
+    @action public setImageRootDoc = (doc: Doc) => (this.imageRootDoc = doc);
+    @observable public addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+
     @observable public LastButton: Opt<Doc>;
     @observable private _windowWidth: number = 0;
     @observable private _windowHeight: number = 0;
@@ -1014,6 +1024,7 @@ export class MainView extends React.Component {
                 <InkTranscription />
                 {this.snapLines}
                 <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} />
+                <GenerativeFill imageEditorOpen={this.imageEditorOpen} imageEditorSource={this.imageEditorSource} imageRootDoc={this.imageRootDoc} addDoc={this.addDoc} />
             </div>
         );
     }
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' });
 
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(() => {
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.scss b/src/client/views/nodes/generativeFill/GenerativeFill.scss
index 92406ba9d..b1e570cf1 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.scss
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.scss
@@ -6,14 +6,14 @@ $scale: 0.5;
     position: absolute;
     top: 0;
     left: 0;
-    z-index: 999;
+    z-index: 9999;
     height: 100vh;
     width: 100vw;
     display: flex;
     flex-direction: column;
     overflow: hidden;
 
-    .controls {
+    .generativeFillControls {
         flex-shrink: 0;
         height: $navHeight;
         background-color: #ffffff;
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 6dd80a5d1..0df05f4d7 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -3,14 +3,21 @@ import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
 import { BrushHandler } from './generativeFillUtils/BrushHandler';
 import { IconButton, TextField } from '@mui/material';
 import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces';
-import { activeColor, canvasSize, eraserColor } from './generativeFillUtils/generativeFillConstants';
+import { activeColor, canvasSize, eraserColor, freeformRenderSize } from './generativeFillUtils/generativeFillConstants';
 import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import { BsBrush, BsEraser } from 'react-icons/bs';
+import { BsBrush, BsEraser, BsX } from 'react-icons/bs';
 import { AiOutlineUpload } from 'react-icons/ai';
 import { CiUndo, CiRedo } from 'react-icons/ci';
 import Buttons from './GenerativeFillButtons';
-import React from 'react';
+import React = require('react');
 import './GenerativeFill.scss';
+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 { NumCast } from '../../../../fields/Types';
 
 /**
  * For images not 1024x1024 fill in the rest in solid black, or a
@@ -33,7 +40,14 @@ interface ImageEdit {
     children: ImageEdit[];
 }
 
-const GenerativeFill = () => {
+interface GenerativeFillProps {
+    imageEditorOpen: boolean;
+    imageEditorSource: string;
+    imageRootDoc: Doc | undefined;
+    addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
+}
+
+const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => {
     const canvasRef = useRef<HTMLCanvasElement>(null);
     const canvasBackgroundRef = useRef<HTMLCanvasElement>(null);
     const drawingAreaRef = useRef<HTMLDivElement>(null);
@@ -51,8 +65,49 @@ const GenerativeFill = () => {
     const [loading, setLoading] = useState(false);
     // used to store the current image loaded to the main canvas
     const currImg = useRef<HTMLImageElement | null>(null);
-    // ref to store history
-    const undoStack = useRef<ImageData[]>([]);
+    const originalImg = useRef<HTMLImageElement | null>(null);
+    // stores history of data urls
+    const undoStack = useRef<string[]>([]);
+    // stores redo stack
+    const redoStack = useRef<string[]>([]);
+
+    // Undo and Redo
+    const handleUndo = () => {
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx || !currImg.current || !canvasRef.current) return;
+
+        const target = undoStack.current[undoStack.current.length - 1];
+        if (!target) {
+            ImageUtility.drawImgToCanvas(currImg.current, canvasRef);
+        } else {
+            redoStack.current = [...redoStack.current, canvasRef.current.toDataURL()];
+            const img = new Image();
+            img.src = target;
+            ImageUtility.drawImgToCanvas(img, canvasRef);
+            undoStack.current = undoStack.current.slice(0, -1);
+        }
+    };
+
+    const handleRedo = () => {
+        // TODO: handle undo as well
+        const target = redoStack.current[redoStack.current.length - 1];
+        if (!target) {
+        } else {
+            const img = new Image();
+            img.src = target;
+            ImageUtility.drawImgToCanvas(img, canvasRef);
+            redoStack.current = redoStack.current.slice(0, -1);
+        }
+    };
+
+    const handleReset = () => {
+        if (!canvasRef.current || !currImg.current) return;
+        const ctx = ImageUtility.getCanvasContext(canvasRef);
+        if (!ctx) return;
+        ctx.clearRect(0, 0, canvasSize, canvasSize);
+        undoStack.current = [];
+        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true);
+    };
 
     // initiate brushing
     const handlePointerDown = (e: React.PointerEvent) => {
@@ -61,6 +116,9 @@ const GenerativeFill = () => {
         const ctx = ImageUtility.getCanvasContext(canvasRef);
         if (!ctx) return;
 
+        undoStack.current = [...undoStack.current, canvasRef.current.toDataURL()];
+        redoStack.current = [];
+
         setIsBrushing(true);
         const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
 
@@ -98,17 +156,13 @@ const GenerativeFill = () => {
 
     // first load
     useEffect(() => {
+        if (!imageEditorSource || imageEditorSource === '') return;
         const img = new Image();
-        img.src = '/assets/art.jpeg';
+        img.src = imageEditorSource;
         ImageUtility.drawImgToCanvas(img, canvasRef);
         currImg.current = img;
-    }, [canvasRef]);
-
-    useEffect(() => {
-        if (!canvasBackgroundRef.current) return;
-        const ctx = ImageUtility.getCanvasContext(canvasBackgroundRef);
-        if (!ctx) return;
-    }, [canvasBackgroundRef]);
+        originalImg.current = img;
+    }, [canvasRef, imageEditorSource]);
 
     // handles brush sizing
     useEffect(() => {
@@ -180,16 +234,9 @@ const GenerativeFill = () => {
             const maskBlob = await ImageUtility.canvasToBlob(canvas);
             const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img));
 
-            // const res = await ImageUtility.getEdit(
-            //   imgBlob,
-            //   maskBlob,
-            //   input !== ""
-            //     ? input + " in the same style"
-            //     : "Fill in the image in the same style",
-            //   1
-            // );
+            const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
 
-            const res = await ImageUtility.mockGetEdit();
+            // const res = await ImageUtility.mockGetEdit();
             const { urls } = res as APISuccess;
             const image = new Image();
             image.src = urls[0];
@@ -202,11 +249,47 @@ const GenerativeFill = () => {
         }
     };
 
+    const onSave = async () => {
+        if (!currImg.current || !imageRootDoc) return;
+        try {
+            const src = currImg.current.src;
+            console.log(src);
+            const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
+            const source = Utils.prepend(result.accessPaths.agnostic.client);
+            console.log(source);
+            const newImg = Docs.Create.ImageDocument(source, {
+                x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20,
+                y: NumCast(imageRootDoc.y),
+                _height: freeformRenderSize,
+                _width: freeformRenderSize,
+                data_nativeWidth: result.nativeWidth,
+                data_nativeHeight: result.nativeHeight,
+            });
+
+            addDoc?.(newImg);
+            // Create link between prompt and image
+            DocUtils.MakeLink(imageRootDoc, newImg, { link_relationship: 'Image Edit' });
+            console.log('done');
+        } catch (err) {
+            console.log(err);
+        }
+    };
+
     return (
-        <div className="generativeFillContainer">
-            <div className="controls">
+        <div className="generativeFillContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
+            <div className="generativeFillControls">
                 <h1>Generative Fill</h1>
-                <Buttons canvasRef={canvasRef} backgroundref={canvasBackgroundRef} currImg={currImg} getEdit={getEdit} undoStack={undoStack} loading={loading} />
+                <div style={{ display: 'flex', alignItems: 'center', gap: '1.5rem' }}>
+                    <Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onSave={onSave} onReset={handleReset} />
+                    <IconButton
+                        onClick={() => {
+                            MainView.Instance.setImageEditorOpen(false);
+                            MainView.Instance.setImageEditorSource('');
+                            setEdits([]);
+                        }}>
+                        <BsX color={activeColor} />
+                    </IconButton>
+                </div>
             </div>
             {/* Main canvas for editing */}
             <div
@@ -245,26 +328,33 @@ const GenerativeFill = () => {
                         }}>
                         <BsBrush color={brushStyle === BrushStyle.ADD ? activeColor : 'inherit'} />
                     </IconButton>
-                    <IconButton
+                    {/* <IconButton
                         onClick={() => {
                             setBrushStyle(BrushStyle.SUBTRACT);
                         }}>
                         <BsEraser color={brushStyle === BrushStyle.SUBTRACT ? activeColor : 'inherit'} />
-                    </IconButton>
+                    </IconButton> */}
                     {/* Undo and Redo */}
-                    {/* <IconButton
+                    <IconButton
                         onPointerDown={e => {
                             e.stopPropagation();
-                            console.log(undoStack.current);
+                            handleUndo();
                         }}
                         onPointerUp={e => {
                             e.stopPropagation();
                         }}>
                         <CiUndo />
                     </IconButton>
-                    <IconButton onClick={() => {}}>
+                    <IconButton
+                        onPointerDown={e => {
+                            e.stopPropagation();
+                            handleRedo();
+                        }}
+                        onPointerUp={e => {
+                            e.stopPropagation();
+                        }}>
                         <CiRedo />
-                    </IconButton> */}
+                    </IconButton>
                 </div>
                 {/* Edits box */}
                 <div className="editsBox">
@@ -292,12 +382,15 @@ const GenerativeFill = () => {
                     type="text"
                     label="Prompt"
                     placeholder="Prompt..."
+                    InputLabelProps={{ style: { fontSize: '1.5rem' } }}
+                    inputProps={{ style: { fontSize: '1.5rem' } }}
                     sx={{
                         backgroundColor: '#ffffff',
                         position: 'absolute',
                         bottom: '1rem',
                         transform: 'translateX(calc(50vw - 50%))',
                         width: 'calc(100vw - 4rem)',
+                        scale: 1.2,
                     }}
                 />
             </div>
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 348e27a16..e8cb61ab5 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -3,44 +3,34 @@ import { ImageUtility } from './generativeFillUtils/ImageHandler';
 import { canvasSize } from './generativeFillUtils/generativeFillConstants';
 import { Oval } from 'react-loader-spinner';
 import './GenerativeFillButtons.scss';
-import React from 'react';
+import React = require('react');
+import { Doc } from '../../../../fields/Doc';
 
 interface ButtonContainerProps {
     canvasRef: React.RefObject<HTMLCanvasElement>;
-    backgroundref: React.RefObject<HTMLCanvasElement>;
     currImg: React.MutableRefObject<HTMLImageElement | null>;
-    undoStack: React.MutableRefObject<ImageData[]>;
     getEdit: () => Promise<void>;
     loading: boolean;
+    onSave: () => Promise<void>;
+    onReset: () => void;
 }
 
-const Buttons = ({ canvasRef, backgroundref, currImg, undoStack, loading, getEdit }: ButtonContainerProps) => {
-    const handleReset = () => {
-        if (!canvasRef.current || !currImg.current) return;
-        const ctx = ImageUtility.getCanvasContext(canvasRef);
-        if (!ctx) return;
-        ctx.clearRect(0, 0, canvasSize, canvasSize);
-        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true);
+const Buttons = ({ canvasRef, currImg, loading, getEdit, onSave, onReset }: ButtonContainerProps) => {
+    const handleSave = () => {
+        onSave();
     };
 
     return (
         <div className="generativeFillBtnContainer">
-            <Button onClick={handleReset}>Reset</Button>
-            <Button
+            <Button onClick={onReset}>Reset</Button>
+            <Button onClick={handleSave}>Save</Button>
+            {/* <Button
                 onClick={() => {
                     if (!canvasRef.current) return;
-                    ImageUtility.downloadCanvas(canvasRef.current);
+                    ImageUtility.downloadImageCanvas('/assets/firefly.png');
                 }}>
-                Download
-            </Button>
-            {/* <Button
-        onClick={() => {
-          if (!canvasRef.current) return;
-          ImageUtility.downloadImageCanvas("/assets/firefly.png");
-        }}
-      >
-        Download Original
-      </Button> */}
+                Download Original
+            </Button> */}
             <Button
                 variant="contained"
                 onClick={() => {
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 1c726afbb..91f12f866 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -1,146 +1,128 @@
-import { RefObject } from "react";
-import { OPENAI_KEY } from "../keys";
-import { canvasSize } from "./generativeFillConstants";
+import { RefObject } from 'react';
+import { canvasSize } from './generativeFillConstants';
 
 export interface APISuccess {
-  status: "success";
-  urls: string[];
+    status: 'success';
+    urls: string[];
 }
 
 export interface APIError {
-  status: "error";
-  message: string;
+    status: 'error';
+    message: string;
 }
 
 export class ImageUtility {
-  static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
-    return new Promise((resolve) => {
-      canvas.toBlob((blob) => {
-        if (blob) {
-          resolve(blob);
-        }
-      }, "image/png");
-    });
-  };
+    static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
+        return new Promise(resolve => {
+            canvas.toBlob(blob => {
+                if (blob) {
+                    resolve(blob);
+                }
+            }, 'image/png');
+        });
+    };
 
-  static getEdit = async (
-    imgBlob: Blob,
-    maskBlob: Blob,
-    prompt: string,
-    n?: number
-  ): Promise<APISuccess | APIError> => {
-    const apiUrl = "https://api.openai.com/v1/images/edits";
-    const fd = new FormData();
-    fd.append("image", imgBlob, "image.png");
-    fd.append("mask", maskBlob, "mask.png");
-    fd.append("prompt", prompt);
-    fd.append("size", "1024x1024");
-    fd.append("n", n ? JSON.stringify(n) : "1");
-    fd.append("response_format", "b64_json");
+    static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
+        const apiUrl = 'https://api.openai.com/v1/images/edits';
+        const fd = new FormData();
+        fd.append('image', imgBlob, 'image.png');
+        fd.append('mask', maskBlob, 'mask.png');
+        fd.append('prompt', prompt);
+        fd.append('size', '1024x1024');
+        fd.append('n', n ? JSON.stringify(n) : '1');
+        fd.append('response_format', 'b64_json');
 
-    try {
-      const res = await fetch(apiUrl, {
-        method: "POST",
-        headers: {
-          Authorization: `Bearer ${OPENAI_KEY}`,
-        },
-        body: fd,
-      });
-      const data = await res.json();
-      console.log(data.data);
-      return {
-        status: "success",
-        urls: (data.data as { b64_json: string }[]).map(
-          (data) => `data:image/png;base64,${data.b64_json}`
-        ),
-      };
-    } catch (err) {
-      console.log(err);
-      return { status: "error", message: "API error." };
-    }
-  };
+        try {
+            const res = await fetch(apiUrl, {
+                method: 'POST',
+                headers: {
+                    Authorization: `Bearer ${process.env.OPENAI_KEY}`,
+                },
+                body: fd,
+            });
+            const data = await res.json();
+            console.log(data.data);
+            return {
+                status: 'success',
+                urls: (data.data as { b64_json: string }[]).map(data => `data:image/png;base64,${data.b64_json}`),
+            };
+        } catch (err) {
+            console.log(err);
+            return { status: 'error', message: 'API error.' };
+        }
+    };
 
-  static mockGetEdit = async (): Promise<APISuccess | APIError> => {
-    return {
-      status: "success",
-      urls: [
-        "/assets/shiba.png",
-        "/assets/souffle-dalle.png",
-        "/assets/firefly.png",
-      ],
+    static mockGetEdit = async (): Promise<APISuccess | APIError> => {
+        return {
+            status: 'success',
+            urls: ['/assets/shiba.png', '/assets/souffle-dalle.png', '/assets/firefly.png'],
+        };
     };
-  };
 
-  static getCanvasContext = (
-    canvasRef: RefObject<HTMLCanvasElement>
-  ): CanvasRenderingContext2D | null => {
-    if (!canvasRef.current) return null;
-    const ctx = canvasRef.current.getContext("2d");
-    if (!ctx) return null;
-    return ctx;
-  };
+    static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
+        if (!canvasRef.current) return null;
+        const ctx = canvasRef.current.getContext('2d');
+        if (!ctx) return null;
+        return ctx;
+    };
 
-  static downloadCanvas = (canvas: HTMLCanvasElement) => {
-    const url = canvas.toDataURL();
-    const downloadLink = document.createElement("a");
-    downloadLink.href = url;
-    downloadLink.download = "canvas";
+    static downloadCanvas = (canvas: HTMLCanvasElement) => {
+        const url = canvas.toDataURL();
+        const downloadLink = document.createElement('a');
+        downloadLink.href = url;
+        downloadLink.download = 'canvas';
 
-    downloadLink.click();
-    downloadLink.remove();
-  };
+        downloadLink.click();
+        downloadLink.remove();
+    };
 
-  static downloadImageCanvas = (imgUrl: string) => {
-    const img = new Image();
-    img.src = imgUrl;
-    img.onload = () => {
-      const canvas = document.createElement("canvas");
-      canvas.width = canvasSize;
-      canvas.height = canvasSize;
-      const ctx = canvas.getContext("2d");
-      ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
+    static downloadImageCanvas = (imgUrl: string) => {
+        const img = new Image();
+        img.src = imgUrl;
+        img.onload = () => {
+            const canvas = document.createElement('canvas');
+            canvas.width = canvasSize;
+            canvas.height = canvasSize;
+            const ctx = canvas.getContext('2d');
+            ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
 
-      this.downloadCanvas(canvas);
+            this.downloadCanvas(canvas);
+        };
     };
-  };
 
-  static drawImgToCanvas = (
-    img: HTMLImageElement,
-    canvasRef: React.RefObject<HTMLCanvasElement>,
-    loaded?: boolean
-  ) => {
-    if (loaded) {
-      const ctx = this.getCanvasContext(canvasRef);
-      if (!ctx) return;
-      ctx.globalCompositeOperation = "source-over";
-      const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
-      const width = img.width * scale;
-      const height = img.height * scale;
-      ctx.clearRect(0, 0, canvasSize, canvasSize);
-      ctx.drawImage(img, 0, 0, width, height);
-    } else {
-      img.onload = () => {
-        const ctx = this.getCanvasContext(canvasRef);
-        if (!ctx) return;
-        ctx.globalCompositeOperation = "source-over";
-        const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
-        const width = img.width * scale;
-        const height = img.height * scale;
-        ctx.clearRect(0, 0, canvasSize, canvasSize);
-        ctx.drawImage(img, 0, 0, width, height);
-      };
-    }
-  };
+    static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, loaded?: boolean) => {
+        if (loaded) {
+            const ctx = this.getCanvasContext(canvasRef);
+            if (!ctx) return;
+            ctx.globalCompositeOperation = 'source-over';
+            const scale = Math.max(canvasSize / img.width, canvasSize / img.height);
+            const width = img.width * scale;
+            const height = img.height * scale;
+            ctx.clearRect(0, 0, canvasSize, canvasSize);
+            ctx.drawImage(img, 0, 0, width, height);
+        } else {
+            img.onload = () => {
+                const ctx = this.getCanvasContext(canvasRef);
+                if (!ctx) return;
+                ctx.globalCompositeOperation = 'source-over';
+                const scale = Math.max(canvasSize / img.width, canvasSize / img.height);
+                const width = img.width * scale;
+                const height = img.height * scale;
+                ctx.clearRect(0, 0, canvasSize, canvasSize);
+                ctx.drawImage(img, 0, 0, width, height);
+            };
+        }
+    };
 
-  // The image must be loaded!
-  static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement => {
-    const canvas = document.createElement("canvas");
-    canvas.width = canvasSize;
-    canvas.height = canvasSize;
-    const ctx = canvas.getContext("2d");
-    ctx?.clearRect(0, 0, canvasSize, canvasSize);
-    ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
+    // The image must be loaded!
+    static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement => {
+        const canvas = document.createElement('canvas');
+        canvas.width = canvasSize;
+        canvas.height = canvasSize;
+        const ctx = canvas.getContext('2d');
+        ctx?.clearRect(0, 0, canvasSize, canvasSize);
+        ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
 
-    return canvas;
-  };
+        return canvas;
+    };
 }
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
index 78903b9aa..fe668ef6f 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -1,5 +1,6 @@
 // constants
 export const canvasSize = 1024;
+export const freeformRenderSize = 200;
 
-export const activeColor = "#1976d2";
-export const eraserColor = "#e1e9ec";
+export const activeColor = '#1976d2';
+export const eraserColor = '#e1e9ec';
-- 
cgit v1.2.3-70-g09d2


From cffd76c34fa884068d31c4fbb0638f13d914b143 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 29 Jun 2023 13:58:53 -0400
Subject: edits

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

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 0df05f4d7..d2d6a83f3 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -106,6 +106,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         if (!ctx) return;
         ctx.clearRect(0, 0, canvasSize, canvasSize);
         undoStack.current = [];
+        redoStack.current = [];
         ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true);
     };
 
-- 
cgit v1.2.3-70-g09d2


From 94854894d0c582aadfba337ffc25e985b5b5b697 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 29 Jun 2023 14:17:26 -0400
Subject: original

---
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 35 ++++++++++++++++++++--
 .../generativeFillUtils/ImageHandler.ts            |  4 +--
 2 files changed, 35 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index d2d6a83f3..ab7e3dfef 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -70,6 +70,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const undoStack = useRef<string[]>([]);
     // stores redo stack
     const redoStack = useRef<string[]>([]);
+    // will change later, for now, stores an array [<col (generation)>, <row (edit in generation)>]
+    const freeformPosition = useRef<number[]>([0, 0]);
 
     // Undo and Redo
     const handleUndo = () => {
@@ -235,9 +237,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             const maskBlob = await ImageUtility.canvasToBlob(canvas);
             const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img));
 
-            const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
+            // const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
 
-            // const res = await ImageUtility.mockGetEdit();
+            const res = await ImageUtility.mockGetEdit(img.src);
             const { urls } = res as APISuccess;
             const image = new Image();
             image.src = urls[0];
@@ -373,6 +375,35 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                             }}
                         />
                     ))}
+                    {edits.length > 0 && (
+                        <div style={{ position: 'relative' }}>
+                            <label
+                                style={{
+                                    position: 'absolute',
+                                    bottom: 10,
+                                    left: 10,
+                                    color: '#ffffff',
+                                    fontSize: '0.8rem',
+                                    letterSpacing: '1px',
+                                    textTransform: 'uppercase',
+                                }}>
+                                Original
+                            </label>
+                            <img
+                                width={100}
+                                height={100}
+                                src={originalImg.current?.src}
+                                style={{ cursor: 'pointer' }}
+                                onClick={() => {
+                                    if (!originalImg.current) return;
+                                    const img = new Image();
+                                    img.src = originalImg.current.src;
+                                    ImageUtility.drawImgToCanvas(img, canvasRef);
+                                    currImg.current = img;
+                                }}
+                            />
+                        </div>
+                    )}
                 </div>
             </div>
             <div>
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 91f12f866..48055903c 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -52,10 +52,10 @@ export class ImageUtility {
         }
     };
 
-    static mockGetEdit = async (): Promise<APISuccess | APIError> => {
+    static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => {
         return {
             status: 'success',
-            urls: ['/assets/shiba.png', '/assets/souffle-dalle.png', '/assets/firefly.png'],
+            urls: [mockSrc, mockSrc, mockSrc],
         };
     };
 
-- 
cgit v1.2.3-70-g09d2


From f8d54bcc8cdb8d302be4ce3d5172ab59a1f574e7 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 6 Jul 2023 02:44:19 -0400
Subject: just saving progress

---
 .../CollectionFreeFormLinkView.tsx                 |  11 ++-
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 108 +++++++++++++++------
 .../generativeFillUtils/generativeFillConstants.ts |   4 +-
 3 files changed, 91 insertions(+), 32 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f1d98d22a..bdc0e1599 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -21,6 +21,8 @@ export interface CollectionFreeFormLinkViewProps {
     LinkDocs: Doc[];
 }
 
+// props.screentolocatransform
+
 @observer
 export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
     @observable _opacity: number = 0;
@@ -235,8 +237,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
             bActive,
             textX,
             textY,
-            pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
-            pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
+            // fully connected
+            pt1: pt1,
+            pt2: pt2,
+            // gaps between
+            // pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13],
+            // pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13],
         };
     }
 
@@ -294,6 +300,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
                     className="collectionfreeformlinkview-linkLine"
                     style={{ pointerEvents: 'visibleStroke', opacity: this._opacity, stroke, strokeWidth }}
                     onClick={this.onClickLine}
+                    // d={`M ${pt1[0]} ${pt1[1]} L ${pt2[0]} ${pt2[1]}`}
                     d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`}
                     markerEnd={link.link_displayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
                 />
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index ab7e3dfef..5e7b4fdca 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -1,23 +1,25 @@
+import './GenerativeFill.scss';
+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 { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces';
-import { activeColor, canvasSize, eraserColor, freeformRenderSize } from './generativeFillUtils/generativeFillConstants';
+import { activeColor, canvasSize, eraserColor, freeformRenderSize, 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 { CiUndo, CiRedo } from 'react-icons/ci';
 import Buttons from './GenerativeFillButtons';
-import React = require('react');
-import './GenerativeFill.scss';
 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 { NumCast } from '../../../../fields/Types';
+import { DocCast, NumCast } from '../../../../fields/Types';
+import { CollectionDockingView } from '../../collections/CollectionDockingView';
+import { OpenWhere, OpenWhereMod } from '../DocumentView';
 
 /**
  * For images not 1024x1024 fill in the rest in solid black, or a
@@ -63,16 +65,23 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD);
     const [input, setInput] = useState('');
     const [loading, setLoading] = useState(false);
-    // used to store the current image loaded to the main canvas
+    // the current image in the main canvas
     const currImg = useRef<HTMLImageElement | null>(null);
+    // the unedited version of each generation (parent)
     const originalImg = useRef<HTMLImageElement | null>(null);
     // stores history of data urls
     const undoStack = useRef<string[]>([]);
     // stores redo stack
     const redoStack = useRef<string[]>([]);
-    // will change later, for now, stores an array [<col (generation)>, <row (edit in generation)>]
+
+    // early stage properly, likely will get rid of
     const freeformPosition = useRef<number[]>([0, 0]);
 
+    // references to keep track of tree structure
+    const newCollectionRef = useRef<Doc | null>(null);
+    const parentDoc = useRef<Doc | null>(null);
+    const childrenDocs = useRef<Doc[]>([]);
+
     // Undo and Redo
     const handleUndo = () => {
         const ctx = ImageUtility.getCanvasContext(canvasRef);
@@ -165,6 +174,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         ImageUtility.drawImgToCanvas(img, canvasRef);
         currImg.current = img;
         originalImg.current = img;
+        freeformPosition.current = [0, 0];
     }, [canvasRef, imageEditorSource]);
 
     // handles brush sizing
@@ -237,9 +247,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             const maskBlob = await ImageUtility.canvasToBlob(canvas);
             const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img));
 
-            // const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
-
-            const res = await ImageUtility.mockGetEdit(img.src);
+            const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
+            // const res = await ImageUtility.mockGetEdit(img.src);
             const { urls } = res as APISuccess;
             const image = new Image();
             image.src = urls[0];
@@ -247,32 +256,73 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             setEdits(urls);
             ImageUtility.drawImgToCanvas(image, canvasRef);
             currImg.current = image;
+            freeformPosition.current[0] += 1;
+            freeformPosition.current[1] = 0;
         } catch (err) {
             console.log(err);
         }
     };
 
+    // adjusts all the img positions to be aligned
+    const adjustImgPositions = () => {
+        if (!parentDoc.current) return;
+        const startY = NumCast(parentDoc.current.y);
+        const len = childrenDocs.current.length;
+        let initialYPositions: number[] = [];
+        for (let i = 0; i < len; i++) {
+            initialYPositions.push(startY + i * offsetDistanceY);
+        }
+        childrenDocs.current.forEach((doc, i) => {
+            if (len % 2 === 1) {
+                doc.y = initialYPositions[i] - Math.floor(len / 2) * offsetDistanceY;
+            } else {
+                doc.y = initialYPositions[i] - (len / 2 - 1 / 2) * offsetDistanceY;
+            }
+        });
+    };
+
+    // creates a new image document and returns its reference
+    const createNewImgDoc = async (img: HTMLImageElement, firstDoc: boolean): Promise<Doc | undefined> => {
+        if (!newCollectionRef.current || !imageRootDoc || !parentDoc.current) return;
+        const src = img.src;
+        const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
+        const source = Utils.prepend(result.accessPaths.agnostic.client);
+        // the y position is dummy
+        const x = firstDoc ? 0 : NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
+        const initialY = firstDoc ? 500 : 0;
+
+        const newImg = Docs.Create.ImageDocument(source, {
+            x: x,
+            y: initialY,
+            _height: freeformRenderSize,
+            _width: freeformRenderSize,
+            data_nativeWidth: result.nativeWidth,
+            data_nativeHeight: result.nativeHeight,
+        });
+
+        childrenDocs.current.push(newImg);
+        adjustImgPositions();
+        Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+        DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit' });
+        return newImg;
+    };
+
     const onSave = async () => {
-        if (!currImg.current || !imageRootDoc) return;
+        if (!currImg.current || !originalImg.current || !imageRootDoc) return;
         try {
-            const src = currImg.current.src;
-            console.log(src);
-            const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [src] });
-            const source = Utils.prepend(result.accessPaths.agnostic.client);
-            console.log(source);
-            const newImg = Docs.Create.ImageDocument(source, {
-                x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20,
-                y: NumCast(imageRootDoc.y),
-                _height: freeformRenderSize,
-                _width: freeformRenderSize,
-                data_nativeWidth: result.nativeWidth,
-                data_nativeHeight: result.nativeHeight,
-            });
-
-            addDoc?.(newImg);
+            if (!newCollectionRef.current) {
+                // create new collection and add it to the view
+                newCollectionRef.current = Docs.Create.FreeformDocument([], { x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20, y: NumCast(imageRootDoc.y), _width: 1000, _height: 1000 });
+                addDoc?.(newCollectionRef.current);
+                const originalSrc = originalImg.current.src;
+                await createNewImgDoc(originalImg.current, true);
+            }
+            CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right);
+            // CollectionDockingView.AddSplit(newCollection,OpenWhere.inParent)
+            // mindmapping
+            // this.props.addDocTab();
+
             // Create link between prompt and image
-            DocUtils.MakeLink(imageRootDoc, newImg, { link_relationship: 'Image Edit' });
-            console.log('done');
         } catch (err) {
             console.log(err);
         }
@@ -361,9 +411,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 </div>
                 {/* Edits box */}
                 <div className="editsBox">
-                    {edits.map(edit => (
+                    {edits.map((edit, i) => (
                         <img
-                            key={edit}
+                            key={i}
                             width={100}
                             height={100}
                             src={edit}
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
index fe668ef6f..5fd0e9419 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -1,6 +1,8 @@
 // constants
 export const canvasSize = 1024;
-export const freeformRenderSize = 200;
+export const freeformRenderSize = 300;
+export const offsetDistanceY = freeformRenderSize + 100;
+export const offsetX = 100;
 
 export const activeColor = '#1976d2';
 export const eraserColor = '#e1e9ec';
-- 
cgit v1.2.3-70-g09d2


From 0523a76dd66fce71e49caea25aa3981de7a955ec Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 6 Jul 2023 03:09:11 -0400
Subject: blind, need to test

---
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 40 +++++++++++++---------
 .../nodes/generativeFill/GenerativeFillButtons.tsx |  2 +-
 2 files changed, 25 insertions(+), 17 deletions(-)

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 5e7b4fdca..0ebf370c0 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -77,6 +77,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     // early stage properly, likely will get rid of
     const freeformPosition = useRef<number[]>([0, 0]);
 
+    // which urls were already saved to canvas
+    const savedSrcs = useRef<string[]>([]);
+
     // references to keep track of tree structure
     const newCollectionRef = useRef<Doc | null>(null);
     const parentDoc = useRef<Doc | null>(null);
@@ -243,16 +246,18 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         const ctx = ImageUtility.getCanvasContext(canvasRef);
         if (!ctx) return;
         setLoading(true);
+        // need to adjust later
         try {
             const maskBlob = await ImageUtility.canvasToBlob(canvas);
             const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img));
-
             const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
             // const res = await ImageUtility.mockGetEdit(img.src);
+            parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1];
+            childrenDocs.current = [];
+            originalImg.current = currImg.current;
             const { urls } = res as APISuccess;
             const image = new Image();
             image.src = urls[0];
-            setLoading(false);
             setEdits(urls);
             ImageUtility.drawImgToCanvas(image, canvasRef);
             currImg.current = image;
@@ -261,6 +266,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         } catch (err) {
             console.log(err);
         }
+        setLoading(false);
     };
 
     // adjusts all the img positions to be aligned
@@ -301,31 +307,34 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         });
 
         childrenDocs.current.push(newImg);
-        adjustImgPositions();
+        if (!firstDoc) {
+            DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit', link_displayLine: true });
+            adjustImgPositions();
+        }
         Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
-        DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit' });
         return newImg;
     };
 
+    // need to maybe call on every img click, not just when the save btn is clicked
     const onSave = async () => {
+        setLoading(true);
         if (!currImg.current || !originalImg.current || !imageRootDoc) return;
         try {
             if (!newCollectionRef.current) {
                 // create new collection and add it to the view
                 newCollectionRef.current = Docs.Create.FreeformDocument([], { x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20, y: NumCast(imageRootDoc.y), _width: 1000, _height: 1000 });
                 addDoc?.(newCollectionRef.current);
-                const originalSrc = originalImg.current.src;
                 await createNewImgDoc(originalImg.current, true);
             }
-            CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right);
+            await createNewImgDoc(currImg.current, false);
+            // CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right);
             // CollectionDockingView.AddSplit(newCollection,OpenWhere.inParent)
-            // mindmapping
+            // mind mapping
             // this.props.addDocTab();
-
-            // Create link between prompt and image
         } catch (err) {
             console.log(err);
         }
+        setLoading(false);
     };
 
     return (
@@ -336,6 +345,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                     <Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onSave={onSave} onReset={handleReset} />
                     <IconButton
                         onClick={() => {
+                            if (newCollectionRef.current) {
+                                CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+                            }
                             MainView.Instance.setImageEditorOpen(false);
                             MainView.Instance.setImageEditorSource('');
                             setEdits([]);
@@ -381,12 +393,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                         }}>
                         <BsBrush color={brushStyle === BrushStyle.ADD ? activeColor : 'inherit'} />
                     </IconButton>
-                    {/* <IconButton
-                        onClick={() => {
-                            setBrushStyle(BrushStyle.SUBTRACT);
-                        }}>
-                        <BsEraser color={brushStyle === BrushStyle.SUBTRACT ? activeColor : 'inherit'} />
-                    </IconButton> */}
                     {/* Undo and Redo */}
                     <IconButton
                         onPointerDown={e => {
@@ -409,7 +415,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                         <CiRedo />
                     </IconButton>
                 </div>
-                {/* Edits box */}
+                {/* Edits thumbnails*/}
                 <div className="editsBox">
                     {edits.map((edit, i) => (
                         <img
@@ -422,9 +428,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                                 img.src = edit;
                                 ImageUtility.drawImgToCanvas(img, canvasRef);
                                 currImg.current = img;
+                                onSave();
                             }}
                         />
                     ))}
+                    {/* Original img thumbnail */}
                     {edits.length > 0 && (
                         <div style={{ position: 'relative' }}>
                             <label
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index e8cb61ab5..53c6cec84 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -23,7 +23,7 @@ const Buttons = ({ canvasRef, currImg, loading, getEdit, onSave, onReset }: Butt
     return (
         <div className="generativeFillBtnContainer">
             <Button onClick={onReset}>Reset</Button>
-            <Button onClick={handleSave}>Save</Button>
+            {/* <Button onClick={handleSave}>Save</Button> */}
             {/* <Button
                 onClick={() => {
                     if (!canvasRef.current) return;
-- 
cgit v1.2.3-70-g09d2


From 0e3b280ab0c20c36742e2d269a80dbaada2192b1 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 6 Jul 2023 13:57:25 -0400
Subject: it's working

---
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 125 +++++++++++++++------
 1 file changed, 90 insertions(+), 35 deletions(-)

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 0ebf370c0..1709962a2 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -166,11 +166,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         };
 
         drawingAreaRef.current?.addEventListener('pointermove', handlePointerMove);
-        return () => drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove);
+        return () => {
+            drawingAreaRef.current?.removeEventListener('pointermove', handlePointerMove);
+        };
     }, [isBrushing]);
 
     // first load
     useEffect(() => {
+        console.log('first load');
         if (!imageEditorSource || imageEditorSource === '') return;
         const img = new Image();
         img.src = imageEditorSource;
@@ -178,6 +181,18 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         currImg.current = img;
         originalImg.current = img;
         freeformPosition.current = [0, 0];
+
+        return () => {
+            console.log('cleanup');
+            newCollectionRef.current = null;
+            parentDoc.current = null;
+            childrenDocs.current = [];
+            currImg.current = null;
+            originalImg.current = null;
+            freeformPosition.current = [0, 0];
+            undoStack.current = [];
+            redoStack.current = [];
+        };
     }, [canvasRef, imageEditorSource]);
 
     // handles brush sizing
@@ -252,15 +267,30 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img));
             const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
             // const res = await ImageUtility.mockGetEdit(img.src);
-            parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1];
-            childrenDocs.current = [];
+
+            // create first image
+            if (!newCollectionRef.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) + 20, y: NumCast(imageRootDoc.y), _width: 1000, _height: 1000 });
+                DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
+                addDoc?.(newCollectionRef.current);
+                await createNewImgDoc(originalImg.current, true);
+            } else {
+                parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1];
+                childrenDocs.current = [];
+            }
+
             originalImg.current = currImg.current;
+
             const { urls } = res as APISuccess;
             const image = new Image();
             image.src = urls[0];
             setEdits(urls);
             ImageUtility.drawImgToCanvas(image, canvasRef);
             currImg.current = image;
+            onSave();
             freeformPosition.current[0] += 1;
             freeformPosition.current[1] = 0;
         } catch (err) {
@@ -274,6 +304,7 @@ 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);
@@ -289,30 +320,51 @@ 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 || !parentDoc.current) 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);
-        // the y position is dummy
-        const x = firstDoc ? 0 : NumCast(parentDoc.current.x) + freeformRenderSize + offsetX;
-        const initialY = firstDoc ? 500 : 0;
-
-        const newImg = Docs.Create.ImageDocument(source, {
-            x: x,
-            y: initialY,
-            _height: freeformRenderSize,
-            _width: freeformRenderSize,
-            data_nativeWidth: result.nativeWidth,
-            data_nativeHeight: result.nativeHeight,
-        });
 
-        childrenDocs.current.push(newImg);
-        if (!firstDoc) {
+        if (firstDoc) {
+            const x = 0;
+            const initialY = 500;
+            console.log('first doc');
+
+            const newImg = Docs.Create.ImageDocument(source, {
+                x: x,
+                y: initialY,
+                _height: freeformRenderSize,
+                _width: freeformRenderSize,
+                data_nativeWidth: result.nativeWidth,
+                data_nativeHeight: result.nativeHeight,
+            });
+
+            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,
+                _height: freeformRenderSize,
+                _width: freeformRenderSize,
+                data_nativeWidth: result.nativeWidth,
+                data_nativeHeight: result.nativeHeight,
+            });
+
+            childrenDocs.current.push(newImg);
             DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit', link_displayLine: true });
             adjustImgPositions();
+
+            Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+            return newImg;
         }
-        Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
-        return newImg;
     };
 
     // need to maybe call on every img click, not just when the save btn is clicked
@@ -320,12 +372,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         setLoading(true);
         if (!currImg.current || !originalImg.current || !imageRootDoc) return;
         try {
-            if (!newCollectionRef.current) {
-                // create new collection and add it to the view
-                newCollectionRef.current = Docs.Create.FreeformDocument([], { x: NumCast(imageRootDoc.x) + NumCast(imageRootDoc._width) + 20, y: NumCast(imageRootDoc.y), _width: 1000, _height: 1000 });
-                addDoc?.(newCollectionRef.current);
-                await createNewImgDoc(originalImg.current, true);
-            }
+            // if (!newCollectionRef.current) {
+            //     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) + 20, y: NumCast(imageRootDoc.y), _width: 1000, _height: 1000 });
+            //     addDoc?.(newCollectionRef.current);
+            //     await createNewImgDoc(originalImg.current, true);
+            // }
+            console.log('creating another image');
             await createNewImgDoc(currImg.current, false);
             // CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right);
             // CollectionDockingView.AddSplit(newCollection,OpenWhere.inParent)
@@ -337,21 +391,22 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         setLoading(false);
     };
 
+    const handleViewClose = () => {
+        // if (newCollectionRef.current) {
+        //     CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+        // }
+        MainView.Instance.setImageEditorOpen(false);
+        MainView.Instance.setImageEditorSource('');
+        setEdits([]);
+    };
+
     return (
         <div className="generativeFillContainer" style={{ display: imageEditorOpen ? 'flex' : 'none' }}>
             <div className="generativeFillControls">
                 <h1>Generative Fill</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={() => {
-                            if (newCollectionRef.current) {
-                                CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
-                            }
-                            MainView.Instance.setImageEditorOpen(false);
-                            MainView.Instance.setImageEditorSource('');
-                            setEdits([]);
-                        }}>
+                    <IconButton onClick={handleViewClose}>
                         <BsX color={activeColor} />
                     </IconButton>
                 </div>
-- 
cgit v1.2.3-70-g09d2


From 5e84fa8496331dfebfc24772f0073d086b01b5a0 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 6 Jul 2023 15:06:40 -0400
Subject: tweak

---
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 46 ++++++++++++++--------
 .../generativeFillUtils/generativeFillConstants.ts |  5 ++-
 2 files changed, 33 insertions(+), 18 deletions(-)

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 1709962a2..2b3cfb920 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -5,7 +5,7 @@ import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler';
 import { BrushHandler } from './generativeFillUtils/BrushHandler';
 import { IconButton, TextField } from '@mui/material';
 import { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces';
-import { activeColor, canvasSize, eraserColor, freeformRenderSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
+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';
@@ -20,6 +20,7 @@ import { DocUtils, Docs } from '../../../documents/Documents';
 import { DocCast, NumCast } from '../../../../fields/Types';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { OpenWhere, OpenWhereMod } from '../DocumentView';
+import { Oval } from 'react-loader-spinner';
 
 /**
  * For images not 1024x1024 fill in the rest in solid black, or a
@@ -28,6 +29,14 @@ import { OpenWhere, OpenWhereMod } from '../DocumentView';
 
 /**
  * TODO: Look into img onload, sometimes the canvas doesn't update properly
+ *
+ * Ref:
+ *
+ *
+ * CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right);
+ * CollectionDockingView.AddSplit(newCollection,OpenWhere.inParent)
+ * mind mapping
+ * this.props.addDocTab();
  */
 
 enum BrushStyle {
@@ -65,6 +74,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD);
     const [input, setInput] = useState('');
     const [loading, setLoading] = useState(false);
+    const [saveLoading, setSaveLoading] = useState(false);
     // the current image in the main canvas
     const currImg = useRef<HTMLImageElement | null>(null);
     // the unedited version of each generation (parent)
@@ -273,8 +283,15 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 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) + 20, y: NumCast(imageRootDoc.y), _width: 1000, _height: 1000 });
+                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 {
@@ -327,7 +344,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
         if (firstDoc) {
             const x = 0;
-            const initialY = 500;
+            const initialY = 0;
             console.log('first doc');
 
             const newImg = Docs.Create.ImageDocument(source, {
@@ -369,26 +386,15 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
     // need to maybe call on every img click, not just when the save btn is clicked
     const onSave = async () => {
-        setLoading(true);
+        setSaveLoading(true);
         if (!currImg.current || !originalImg.current || !imageRootDoc) return;
         try {
-            // if (!newCollectionRef.current) {
-            //     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) + 20, y: NumCast(imageRootDoc.y), _width: 1000, _height: 1000 });
-            //     addDoc?.(newCollectionRef.current);
-            //     await createNewImgDoc(originalImg.current, true);
-            // }
             console.log('creating another image');
             await createNewImgDoc(currImg.current, false);
-            // CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right);
-            // CollectionDockingView.AddSplit(newCollection,OpenWhere.inParent)
-            // mind mapping
-            // this.props.addDocTab();
         } catch (err) {
             console.log(err);
         }
-        setLoading(false);
+        setSaveLoading(false);
     };
 
     const handleViewClose = () => {
@@ -515,6 +521,14 @@ 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>
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
index 5fd0e9419..5a8d33742 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -1,8 +1,9 @@
 // constants
 export const canvasSize = 1024;
 export const freeformRenderSize = 300;
-export const offsetDistanceY = freeformRenderSize + 100;
-export const offsetX = 100;
+export const offsetDistanceY = freeformRenderSize + 200;
+export const offsetX = 200;
+export const newCollectionSize = 1000;
 
 export const activeColor = '#1976d2';
 export const eraserColor = '#e1e9ec';
-- 
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/generativeFill/GenerativeFill.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/generativeFill/GenerativeFill.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/generativeFill/GenerativeFill.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 107f718199a5b0e15f62e5c7e7034b11047ff512 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 17 Aug 2023 19:11:42 -0400
Subject: baseline working version, different image sizes work

---
 .../views/nodes/generativeFill/GenerativeFill.tsx  |  93 +++++++++-------
 .../generativeFillUtils/ImageHandler.ts            | 119 +++++++++++++++++----
 .../generativeFillUtils/generativeFillConstants.ts |   3 +-
 3 files changed, 156 insertions(+), 59 deletions(-)

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index f136982bc..4d475149d 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -42,10 +42,9 @@ enum BrushStyle {
     MARQUEE,
 }
 
-interface ImageEdit {
-    imgElement: HTMLImageElement;
-    parent: ImageEdit;
-    children: ImageEdit[];
+interface ImageDimensions {
+    width: number;
+    height: number;
 }
 
 interface GenerativeFillProps {
@@ -59,7 +58,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const canvasRef = useRef<HTMLCanvasElement>(null);
     const canvasBackgroundRef = useRef<HTMLCanvasElement>(null);
     const drawingAreaRef = useRef<HTMLDivElement>(null);
-    const fileRef = useRef<HTMLInputElement>(null);
     const [cursorData, setCursorData] = useState<CursorData>({
         x: 0,
         y: 0,
@@ -72,6 +70,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const [input, setInput] = useState('');
     const [loading, setLoading] = useState(false);
     const [saveLoading, setSaveLoading] = useState(false);
+    const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
+        width: canvasSize,
+        height: canvasSize,
+    });
     // the current image in the main canvas
     const currImg = useRef<HTMLImageElement | null>(null);
     // the unedited version of each generation (parent)
@@ -91,7 +93,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 = () => {
@@ -100,12 +101,12 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
         const target = undoStack.current[undoStack.current.length - 1];
         if (!target) {
-            ImageUtility.drawImgToCanvas(currImg.current, canvasRef);
+            ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
         } else {
             redoStack.current = [...redoStack.current, canvasRef.current.toDataURL()];
             const img = new Image();
             img.src = target;
-            ImageUtility.drawImgToCanvas(img, canvasRef);
+            ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
             undoStack.current = undoStack.current.slice(0, -1);
         }
     };
@@ -117,7 +118,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         } else {
             const img = new Image();
             img.src = target;
-            ImageUtility.drawImgToCanvas(img, canvasRef);
+            ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
             redoStack.current = redoStack.current.slice(0, -1);
         }
     };
@@ -129,7 +130,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         ctx.clearRect(0, 0, canvasSize, canvasSize);
         undoStack.current = [];
         redoStack.current = [];
-        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, true);
+        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasSize, canvasSize);
     };
 
     // initiate brushing
@@ -144,7 +145,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
         setIsBrushing(true);
         const { x, y } = PointerHandler.getPointRelativeToElement(canvas, e, canvasScale);
-
         BrushHandler.brushCircleOverlay(x, y, cursorData.width / 2 / canvasScale, ctx, eraserColor, brushStyle === BrushStyle.SUBTRACT);
     };
 
@@ -181,19 +181,21 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
     // first load
     useEffect(() => {
-        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];
+        img.onload = () => {
+            const imgWidth = img.naturalWidth;
+            const imgHeight = img.naturalHeight;
+            const scale = Math.min(canvasSize / imgWidth, canvasSize / imgHeight);
+            const width = imgWidth * scale;
+            const height = imgHeight * scale;
+            setCanvasDims({ width, height });
+        };
 
         return () => {
-            console.log('cleanup');
             newCollectionRef.current = null;
             parentDoc.current = null;
             childrenDocs.current = [];
@@ -202,9 +204,15 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             freeformPosition.current = [0, 0];
             undoStack.current = [];
             redoStack.current = [];
+            ImageUtility.clearCanvas(canvasRef);
         };
     }, [canvasRef, imageEditorSource]);
 
+    useEffect(() => {
+        if (!currImg.current) return;
+        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
+    }, [canvasDims]);
+
     // handles brush sizing
     useEffect(() => {
         const handleKeyPress = (e: KeyboardEvent) => {
@@ -261,15 +269,18 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         setLoading(true);
         // need to adjust later
         try {
-            const maskBlob = await ImageUtility.canvasToBlob(canvas);
-            const imgBlob = await ImageUtility.canvasToBlob(ImageUtility.getCanvasImg(img));
+            const canvasOriginalImg = ImageUtility.getCanvasImg(img);
+            if (!canvasOriginalImg) return;
+            const canvasMask = ImageUtility.getCanvasMask(canvas);
+            if (!canvasMask) return;
+            // ImageUtility.downloadCanvas(canvasMask);
+            const maskBlob = await ImageUtility.canvasToBlob(canvasMask);
+            const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg);
             const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
             // const res = await ImageUtility.mockGetEdit(img.src);
 
             // 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
@@ -281,25 +292,30 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                     title: 'Image edit collection',
                 });
                 DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
+
+                // opening new tab
+                newCollectionRef.current.fitContentOnce = true;
+                CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+
                 // 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 = [];
             }
 
             originalImg.current = currImg.current;
 
             const { urls } = res as APISuccess;
+            const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImageToCanvasDataURL(url, canvasDims.width, canvasDims.height)));
             const image = new Image();
-            image.src = urls[0];
-            setEdits(urls);
-            ImageUtility.drawImgToCanvas(image, canvasRef);
+            image.src = imgUrls[0];
+
+            setEdits(imgUrls);
+            ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
             currImg.current = image;
             onSave();
-            freeformPosition.current[0] += 1;
-            freeformPosition.current[1] = 0;
         } catch (err) {
             console.log(err);
         }
@@ -334,7 +350,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,
@@ -387,10 +402,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     };
 
     const handleViewClose = () => {
-        if (newCollectionRef.current) {
-            newCollectionRef.current.fitContentOnce = true;
-            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([]);
@@ -420,8 +435,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 onPointerMove={updateCursorData}
                 onPointerDown={handlePointerDown}
                 onPointerUp={handlePointerUp}>
-                <canvas ref={canvasRef} width={canvasSize} height={canvasSize} style={{ transform: `scale(${canvasScale})` }} />
-                <canvas ref={canvasBackgroundRef} width={canvasSize} height={canvasSize} style={{ transform: `scale(${canvasScale})` }} />
+                <canvas ref={canvasRef} width={canvasDims.width} height={canvasSize} style={{ transform: `scale(${canvasScale})` }} />
+                <canvas ref={canvasBackgroundRef} width={canvasDims.height} height={canvasSize} style={{ transform: `scale(${canvasScale})` }} />
                 <div
                     className="pointer"
                     style={{
@@ -489,14 +504,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                     {edits.map((edit, i) => (
                         <img
                             key={i}
-                            width={100}
-                            height={100}
+                            width={75}
                             src={edit}
                             onClick={async () => {
                                 // if (savedSrcs.current.has(edit)) return;
                                 const img = new Image();
                                 img.src = edit;
-                                ImageUtility.drawImgToCanvas(img, canvasRef);
+                                ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
                                 currImg.current = img;
                                 savedSrcs.current.add(edit);
                                 await onSave();
@@ -519,15 +533,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                                 Original
                             </label>
                             <img
-                                width={100}
-                                height={100}
+                                width={75}
                                 src={originalImg.current?.src}
                                 style={{ cursor: 'pointer' }}
                                 onClick={() => {
                                     if (!originalImg.current) return;
                                     const img = new Image();
                                     img.src = originalImg.current.src;
-                                    ImageUtility.drawImgToCanvas(img, canvasRef);
+                                    ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
                                     currImg.current = img;
                                 }}
                             />
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 1954ab3fb..8d32221bd 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -1,5 +1,5 @@
 import { RefObject } from 'react';
-import { canvasSize } from './generativeFillConstants';
+import { bgColor, canvasSize } from './generativeFillConstants';
 
 export interface APISuccess {
     status: 'success';
@@ -22,6 +22,47 @@ export class ImageUtility {
         });
     };
 
+    // given a square api image, get the cropped img
+    static getCroppedImg = (img: HTMLImageElement, width: number, height: number): HTMLCanvasElement | undefined => {
+        // Create a new canvas element
+        const canvas = document.createElement('canvas');
+        canvas.width = width;
+        canvas.height = height;
+        const ctx = canvas.getContext('2d');
+        if (ctx) {
+            // Clear the canvas
+            ctx.clearRect(0, 0, canvas.width, canvas.height);
+            if (width < height) {
+                // horizontal padding, x offset
+                const xOffset = (canvasSize - width) / 2;
+                console.log(xOffset);
+                ctx.drawImage(img, xOffset, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
+            } else {
+                // vertical padding, y offset
+                const yOffset = (canvasSize - height) / 2;
+                ctx.drawImage(img, 0, yOffset, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
+            }
+            return canvas;
+        }
+    };
+
+    static convertImageToCanvasDataURL = async (imageSrc: string, width: number, height: number): Promise<string> => {
+        return new Promise<string>((resolve, reject) => {
+            const img = new Image();
+            img.onload = () => {
+                const canvas = this.getCroppedImg(img, width, height);
+                if (canvas) {
+                    const dataUrl = canvas.toDataURL();
+                    resolve(dataUrl);
+                }
+            };
+            img.onerror = error => {
+                reject(error);
+            };
+            img.src = imageSrc;
+        });
+    };
+
     static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
         const apiUrl = 'https://api.openai.com/v1/images/edits';
         const fd = new FormData();
@@ -90,41 +131,83 @@ export class ImageUtility {
         };
     };
 
-    static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, loaded?: boolean) => {
-        if (loaded) {
+    static clearCanvas = (canvasRef: React.RefObject<HTMLCanvasElement>) => {
+        const ctx = this.getCanvasContext(canvasRef);
+        if (!ctx || !canvasRef.current) return;
+        ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
+    };
+
+    static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, width: number, height: number) => {
+        const drawImg = (img: HTMLImageElement) => {
             const ctx = this.getCanvasContext(canvasRef);
             if (!ctx) return;
             ctx.globalCompositeOperation = 'source-over';
-            const scale = Math.max(canvasSize / img.width, canvasSize / img.height);
-            const width = img.width * scale;
-            const height = img.height * scale;
-            ctx.clearRect(0, 0, canvasSize, canvasSize);
+            ctx.clearRect(0, 0, width, height);
             ctx.drawImage(img, 0, 0, width, height);
+        };
+
+        if (img.complete) {
+            drawImg(img);
         } else {
             console.log('loading image');
             img.onload = () => {
-                console.log('loaded');
-                const ctx = this.getCanvasContext(canvasRef);
-                if (!ctx) return;
-                ctx.globalCompositeOperation = 'source-over';
-                const scale = Math.max(canvasSize / img.width, canvasSize / img.height);
-                const width = img.width * scale;
-                const height = img.height * scale;
-                ctx.clearRect(0, 0, canvasSize, canvasSize);
-                ctx.drawImage(img, 0, 0, width, height);
+                drawImg(img);
             };
         }
     };
 
     // The image must be loaded!
-    static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement => {
+    static getCanvasMask = (srcCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
         const canvas = document.createElement('canvas');
         canvas.width = canvasSize;
         canvas.height = canvasSize;
         const ctx = canvas.getContext('2d');
+        if (!ctx) return;
         ctx?.clearRect(0, 0, canvasSize, canvasSize);
-        ctx?.drawImage(img, 0, 0, canvasSize, canvasSize);
+        ctx.fillStyle = bgColor;
+        ctx.fillRect(0, 0, canvasSize, canvasSize);
 
+        // extract and set padding data
+        if (srcCanvas.height > srcCanvas.width) {
+            // horizontal padding, x offset
+            const xOffset = (canvasSize - srcCanvas.width) / 2;
+            ctx?.clearRect(xOffset, 0, srcCanvas.width, srcCanvas.height);
+            ctx.drawImage(srcCanvas, xOffset, 0, srcCanvas.width, srcCanvas.height);
+        } else {
+            // vertical padding, y offset
+            const yOffset = (canvasSize - srcCanvas.height) / 2;
+            ctx?.clearRect(0, yOffset, srcCanvas.width, srcCanvas.height);
+            ctx.drawImage(srcCanvas, 0, yOffset, srcCanvas.width, srcCanvas.height);
+        }
+        return canvas;
+    };
+
+    // The image must be loaded!
+    static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => {
+        const canvas = document.createElement('canvas');
+        canvas.width = canvasSize;
+        canvas.height = canvasSize;
+        const ctx = canvas.getContext('2d');
+        if (!ctx) return;
+        // fix scaling
+        const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
+        const width = img.width * scale;
+        const height = img.height * scale;
+        ctx?.clearRect(0, 0, canvasSize, canvasSize);
+        ctx.fillStyle = bgColor;
+        ctx.fillRect(0, 0, canvasSize, canvasSize);
+
+        // extract and set padding data
+        if (img.naturalHeight > img.naturalWidth) {
+            // horizontal padding, x offset
+            const xOffset = (canvasSize - width) / 2;
+
+            ctx.drawImage(img, xOffset, 0, width, height);
+        } else {
+            // vertical padding, y offset
+            const yOffset = (canvasSize - height) / 2;
+            ctx.drawImage(img, 0, yOffset, width, height);
+        }
         return canvas;
     };
 }
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
index 412a4d238..286dc6e4c 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -1,9 +1,10 @@
 // constants
 export const canvasSize = 512;
 export const freeformRenderSize = 300;
-export const offsetDistanceY = freeformRenderSize + 200;
+export const offsetDistanceY = freeformRenderSize + 300;
 export const offsetX = 200;
 export const newCollectionSize = 500;
 
 export const activeColor = '#1976d2';
 export const eraserColor = '#e1e9ec';
+export const bgColor = '#f0f4f6';
-- 
cgit v1.2.3-70-g09d2


From 1f44bed14529f8bfc280f714ffd794813c1b7a73 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Thu, 17 Aug 2023 21:02:33 -0400
Subject: baseline before changes

---
 src/client/apis/gpt/GPT.ts                         |  1 -
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 61 ++++++----------------
 .../generativeFillUtils/ImageHandler.ts            | 10 ++--
 .../generativeFillUtils/generativeFillConstants.ts |  2 +-
 .../generativeFillInterfaces.ts                    | 19 ++++---
 5 files changed, 32 insertions(+), 61 deletions(-)

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

diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index 6b4106f56..6bde7989b 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -39,7 +39,6 @@ const gptAPICall = async (inputText: string, callType: GPTCallType) => {
             temperature: opts.temp,
             prompt: `${opts.prompt}${inputText}`,
         });
-        // console.log(response.data.choices[0]);
         return response.data.choices[0].text;
     } catch (err) {
         console.log(err);
diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 4d475149d..97d1cbf20 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -4,37 +4,21 @@ 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 { CursorData, Point } from './generativeFillUtils/generativeFillInterfaces';
+import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
 import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
 import { PointerHandler } from './generativeFillUtils/PointerHandler';
 import { BsEraser, BsX } from 'react-icons/bs';
 import { CiUndo, CiRedo } from 'react-icons/ci';
-import Buttons from './GenerativeFillButtons';
 import { MainView } from '../../MainView';
 import { Doc } from '../../../../fields/Doc';
 import { Networking } from '../../../Network';
 import { Utils } from '../../../../Utils';
 import { DocUtils, Docs } from '../../../documents/Documents';
-import { Cast, DocCast, NumCast } from '../../../../fields/Types';
+import { NumCast } from '../../../../fields/Types';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
-import { OpenWhere, OpenWhereMod } from '../DocumentView';
+import { OpenWhereMod } from '../DocumentView';
 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
- */
-
-/**
- *
- *
- * CollectionDockingView.AddSplit(Doc.MakeCopy(DocCast(Doc.UserDoc().emptyPane)), OpenWhereMod.right);
- * CollectionDockingView.AddSplit(newCollection,OpenWhere.inParent)
- * mind mapping
- * this.props.addDocTab();
- */
+import Buttons from './GenerativeFillButtons';
 
 enum BrushStyle {
     ADD,
@@ -42,11 +26,6 @@ enum BrushStyle {
     MARQUEE,
 }
 
-interface ImageDimensions {
-    width: number;
-    height: number;
-}
-
 interface GenerativeFillProps {
     imageEditorOpen: boolean;
     imageEditorSource: string;
@@ -112,10 +91,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, canvasDims.width, canvasDims.height);
@@ -123,6 +105,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         }
     };
 
+    // resets any erase strokes
     const handleReset = () => {
         if (!canvasRef.current || !currImg.current) return;
         const ctx = ImageUtility.getCanvasContext(canvasRef);
@@ -130,7 +113,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         ctx.clearRect(0, 0, canvasSize, canvasSize);
         undoStack.current = [];
         redoStack.current = [];
-        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasSize, canvasSize);
+        ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
     };
 
     // initiate brushing
@@ -236,7 +219,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             e.preventDefault();
             e.stopPropagation();
             const delta = e.deltaY;
-            const scaleFactor = delta > 0 ? 0.98 : 1.02; // Adjust the scale factor as per your requirement
+            const scaleFactor = delta > 0 ? 0.98 : 1.02;
             setCanvasScale(prevScale => prevScale * scaleFactor);
         };
 
@@ -267,13 +250,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         const ctx = ImageUtility.getCanvasContext(canvasRef);
         if (!ctx) return;
         setLoading(true);
-        // need to adjust later
         try {
             const canvasOriginalImg = ImageUtility.getCanvasImg(img);
             if (!canvasOriginalImg) return;
             const canvasMask = ImageUtility.getCanvasMask(canvas);
             if (!canvasMask) return;
-            // ImageUtility.downloadCanvas(canvasMask);
             const maskBlob = await ImageUtility.canvasToBlob(canvasMask);
             const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg);
             const res = await ImageUtility.getEdit(imgBlob, maskBlob, input !== '' ? input + ' in the same style' : 'Fill in the image in the same style', 2);
@@ -282,7 +263,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             // create first image
             if (!newCollectionRef.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,
@@ -294,19 +274,17 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false });
 
                 // opening new tab
-                newCollectionRef.current.fitContentOnce = true;
                 CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+                newCollectionRef.current.fitContentOnce = true;
 
                 // 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 = [];
             }
 
             originalImg.current = currImg.current;
-
             const { urls } = res as APISuccess;
             const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImageToCanvasDataURL(url, canvasDims.width, canvasDims.height)));
             const image = new Image();
@@ -366,8 +344,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         } 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, {
@@ -388,12 +364,10 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         }
     };
 
-    // need to maybe call on every img click, not just when the save btn is clicked
     const onSave = async () => {
         setSaveLoading(true);
         if (!currImg.current || !originalImg.current || !imageRootDoc) return;
         try {
-            console.log('creating another image');
             await createNewImgDoc(currImg.current, false);
         } catch (err) {
             console.log(err);
@@ -402,10 +376,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     };
 
     const handleViewClose = () => {
-        // if (newCollectionRef.current) {
-        //     newCollectionRef.current.fitContentOnce = true;
-        //     CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
-        // }
         MainView.Instance.setImageEditorOpen(false);
         MainView.Instance.setImageEditorSource('');
         setEdits([]);
@@ -435,8 +405,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 onPointerMove={updateCursorData}
                 onPointerDown={handlePointerDown}
                 onPointerUp={handlePointerUp}>
-                <canvas ref={canvasRef} width={canvasDims.width} height={canvasSize} style={{ transform: `scale(${canvasScale})` }} />
-                <canvas ref={canvasBackgroundRef} width={canvasDims.height} height={canvasSize} style={{ transform: `scale(${canvasScale})` }} />
+                <canvas ref={canvasRef} width={canvasDims.width} height={canvasDims.height} style={{ transform: `scale(${canvasScale})` }} />
+                <canvas ref={canvasBackgroundRef} width={canvasDims.width} height={canvasDims.height} style={{ transform: `scale(${canvasScale})` }} />
                 <div
                     className="pointer"
                     style={{
@@ -507,7 +477,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                             width={75}
                             src={edit}
                             onClick={async () => {
-                                // if (savedSrcs.current.has(edit)) return;
                                 const img = new Image();
                                 img.src = edit;
                                 ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 8d32221bd..4847bfeed 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -35,7 +35,6 @@ export class ImageUtility {
             if (width < height) {
                 // horizontal padding, x offset
                 const xOffset = (canvasSize - width) / 2;
-                console.log(xOffset);
                 ctx.drawImage(img, xOffset, 0, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);
             } else {
                 // vertical padding, y offset
@@ -156,7 +155,7 @@ export class ImageUtility {
         }
     };
 
-    // The image must be loaded!
+    // The image must be loaded
     static getCanvasMask = (srcCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
         const canvas = document.createElement('canvas');
         canvas.width = canvasSize;
@@ -164,7 +163,7 @@ export class ImageUtility {
         const ctx = canvas.getContext('2d');
         if (!ctx) return;
         ctx?.clearRect(0, 0, canvasSize, canvasSize);
-        ctx.fillStyle = bgColor;
+        ctx.fillStyle = '#000000';
         ctx.fillRect(0, 0, canvasSize, canvasSize);
 
         // extract and set padding data
@@ -182,7 +181,7 @@ export class ImageUtility {
         return canvas;
     };
 
-    // The image must be loaded!
+    // The image must be loaded
     static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => {
         const canvas = document.createElement('canvas');
         canvas.width = canvasSize;
@@ -194,10 +193,9 @@ export class ImageUtility {
         const width = img.width * scale;
         const height = img.height * scale;
         ctx?.clearRect(0, 0, canvasSize, canvasSize);
-        ctx.fillStyle = bgColor;
+        ctx.fillStyle = '#000000';
         ctx.fillRect(0, 0, canvasSize, canvasSize);
 
-        // extract and set padding data
         if (img.naturalHeight > img.naturalWidth) {
             // horizontal padding, x offset
             const xOffset = (canvasSize - width) / 2;
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
index 286dc6e4c..1fe151b46 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -1,7 +1,7 @@
 // constants
 export const canvasSize = 512;
 export const freeformRenderSize = 300;
-export const offsetDistanceY = freeformRenderSize + 300;
+export const offsetDistanceY = freeformRenderSize + 400;
 export const offsetX = 200;
 export const newCollectionSize = 500;
 
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
index 9b9b9d3c2..83a21a1a5 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
@@ -1,16 +1,21 @@
 // interfaces
 export interface CursorData {
-  x: number;
-  y: number;
-  width: number;
+    x: number;
+    y: number;
+    width: number;
 }
 
 export interface Point {
-  x: number;
-  y: number;
+    x: number;
+    y: number;
 }
 
 export enum BrushMode {
-  ADD,
-  SUBTRACT,
+    ADD,
+    SUBTRACT,
+}
+
+export interface ImageDimensions {
+    width: number;
+    height: number;
 }
-- 
cgit v1.2.3-70-g09d2


From bce4589c0bd871437c83bf44a6a818d01b9bf0f2 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Fri, 18 Aug 2023 16:52:12 -0400
Subject: reflected image padding

---
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 141 +++++++++++++--------
 .../nodes/generativeFill/GenerativeFillButtons.tsx |  11 +-
 .../generativeFillUtils/ImageHandler.ts            |  88 +++++++++++--
 3 files changed, 163 insertions(+), 77 deletions(-)

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 97d1cbf20..92ce3a8a8 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -3,22 +3,23 @@ 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, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
 import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
 import { PointerHandler } from './generativeFillUtils/PointerHandler';
 import { BsEraser, BsX } from 'react-icons/bs';
 import { CiUndo, CiRedo } from 'react-icons/ci';
 import { MainView } from '../../MainView';
-import { Doc } from '../../../../fields/Doc';
+import { Doc, DocListCast } from '../../../../fields/Doc';
 import { Networking } from '../../../Network';
-import { Utils } from '../../../../Utils';
+import { Utils, returnEmptyDoclist } from '../../../../Utils';
 import { DocUtils, Docs } from '../../../documents/Documents';
-import { NumCast } from '../../../../fields/Types';
+import { DocCast, 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';
 
 enum BrushStyle {
     ADD,
@@ -33,6 +34,8 @@ interface GenerativeFillProps {
     addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
 }
 
+// New field on image doc: gen_fill_children => list of children Docs
+
 const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => {
     const canvasRef = useRef<HTMLCanvasElement>(null);
     const canvasBackgroundRef = useRef<HTMLCanvasElement>(null);
@@ -44,15 +47,18 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     });
     const [isBrushing, setIsBrushing] = useState(false);
     const [canvasScale, setCanvasScale] = useState(0.5);
-    const [edits, setEdits] = useState<string[]>([]);
+    // format: array of [image source, corresponding image Doc]
+    const [edits, setEdits] = useState<(string | Doc)[][]>([]);
+    const [edited, setEdited] = useState(false);
     const [brushStyle, setBrushStyle] = useState<BrushStyle>(BrushStyle.ADD);
     const [input, setInput] = useState('');
     const [loading, setLoading] = useState(false);
-    const [saveLoading, setSaveLoading] = useState(false);
     const [canvasDims, setCanvasDims] = useState<ImageDimensions>({
         width: canvasSize,
         height: canvasSize,
     });
+    // whether to create a new collection or not
+    const [isNewCollection, setIsNewCollection] = useState(true);
     // the current image in the main canvas
     const currImg = useRef<HTMLImageElement | null>(null);
     // the unedited version of each generation (parent)
@@ -62,12 +68,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());
-
     // references to keep track of tree structure
     const newCollectionRef = useRef<Doc | null>(null);
     const parentDoc = useRef<Doc | null>(null);
@@ -178,13 +178,14 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             setCanvasDims({ width, height });
         };
 
+        // cleanup
         return () => {
+            setEdited(false);
             newCollectionRef.current = null;
             parentDoc.current = null;
             childrenDocs.current = [];
             currImg.current = null;
             originalImg.current = null;
-            freeformPosition.current = [0, 0];
             undoStack.current = [];
             redoStack.current = [];
             ImageUtility.clearCanvas(canvasRef);
@@ -250,10 +251,11 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         const ctx = ImageUtility.getCanvasContext(canvasRef);
         if (!ctx) return;
         setLoading(true);
+        setEdited(true);
         try {
             const canvasOriginalImg = ImageUtility.getCanvasImg(img);
             if (!canvasOriginalImg) return;
-            const canvasMask = ImageUtility.getCanvasMask(canvas);
+            const canvasMask = ImageUtility.getCanvasMask(canvas, canvasOriginalImg);
             if (!canvasMask) return;
             const maskBlob = await ImageUtility.canvasToBlob(canvasMask);
             const imgBlob = await ImageUtility.canvasToBlob(canvasOriginalImg);
@@ -262,38 +264,46 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
             // create first image
             if (!newCollectionRef.current) {
-                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 });
-
-                // opening new tab
-                CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
-                newCollectionRef.current.fitContentOnce = true;
-
-                // add the doc to the main freeform
-                await createNewImgDoc(originalImg.current, true);
+                if (!isNewCollection && imageRootDoc) {
+                    // new collection stays null
+                    parentDoc.current = imageRootDoc;
+                } 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 });
+
+                    // opening new tab
+                    CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right);
+
+                    // add the doc to the main freeform
+                    await createNewImgDoc(originalImg.current, true);
+                }
             } else {
-                parentDoc.current = childrenDocs.current[childrenDocs.current.length - 1];
                 childrenDocs.current = [];
             }
 
             originalImg.current = currImg.current;
             const { urls } = res as APISuccess;
-            const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImageToCanvasDataURL(url, canvasDims.width, canvasDims.height)));
+            const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
+            const imgRes = await Promise.all(
+                imgUrls.map(async url => {
+                    const saveRes = await onSave(url);
+                    return [url, saveRes as Doc];
+                })
+            );
+            setEdits(imgRes);
             const image = new Image();
             image.src = imgUrls[0];
 
-            setEdits(imgUrls);
             ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
             currImg.current = image;
-            onSave();
         } catch (err) {
             console.log(err);
         }
@@ -320,7 +330,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);
@@ -337,8 +347,12 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 data_nativeWidth: result.nativeWidth,
                 data_nativeHeight: result.nativeHeight,
             });
-
-            Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+            // add a new doc list field to newimg
+            if (isNewCollection && newCollectionRef.current) {
+                Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+            } else {
+                addDoc?.(newImg);
+            }
             parentDoc.current = newImg;
             return newImg;
         } else {
@@ -354,30 +368,40 @@ 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);
-            DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: 'Image Edit', link_displayLine: true });
+            // DocListCast(parentDoc.current.gen_fill_children).push(newImg);
+            DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true });
             adjustImgPositions();
 
-            Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+            if (isNewCollection && newCollectionRef.current) {
+                Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
+            } else {
+                addDoc?.(newImg);
+            }
             return newImg;
         }
     };
 
-    const onSave = async () => {
-        setSaveLoading(true);
+    const onSave = async (src: string) => {
+        const img = new Image();
+        img.src = src;
         if (!currImg.current || !originalImg.current || !imageRootDoc) return;
         try {
-            await createNewImgDoc(currImg.current, false);
+            const res = await createNewImgDoc(img, false);
+            return res;
         } catch (err) {
             console.log(err);
         }
-        setSaveLoading(false);
     };
 
     const handleViewClose = () => {
         MainView.Instance.setImageEditorOpen(false);
         MainView.Instance.setImageEditorSource('');
+        if (newCollectionRef.current) {
+            newCollectionRef.current.fitContentOnce = true;
+        }
+
         setEdits([]);
     };
 
@@ -386,15 +410,25 @@ 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
+                                // disable once edited has been clicked
+                                disabled={edited}
+                                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>
-                    {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 */}
@@ -475,14 +509,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                         <img
                             key={i}
                             width={75}
-                            src={edit}
+                            src={edit[0] as string}
                             onClick={async () => {
                                 const img = new Image();
-                                img.src = edit;
+                                img.src = edit[0] as string;
                                 ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
                                 currImg.current = img;
-                                savedSrcs.current.add(edit);
-                                await onSave();
+                                parentDoc.current = edit[1] as Doc;
                             }}
                         />
                     ))}
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index b4d56b408..e15af0a56 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -14,19 +14,10 @@ interface ButtonContainerProps {
     onReset: () => void;
 }
 
-const Buttons = ({ canvasRef, currImg, loading, getEdit, onReset }: ButtonContainerProps) => {
-
+const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
     return (
         <div className="generativeFillBtnContainer">
             <Button onClick={onReset}>Reset</Button>
-            {/* <Button onClick={handleSave}>Save</Button> */}
-            {/* <Button
-                onClick={() => {
-                    if (!canvasRef.current) return;
-                    ImageUtility.downloadImageCanvas('/assets/firefly.png');
-                }}>
-                Download Original
-            </Button> */}
             <Button
                 variant="contained"
                 onClick={() => {
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 4847bfeed..4ff70c86c 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -45,7 +45,7 @@ export class ImageUtility {
         }
     };
 
-    static convertImageToCanvasDataURL = async (imageSrc: string, width: number, height: number): Promise<string> => {
+    static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> => {
         return new Promise<string>((resolve, reject) => {
             const img = new Image();
             img.onload = () => {
@@ -148,23 +148,23 @@ export class ImageUtility {
         if (img.complete) {
             drawImg(img);
         } else {
-            console.log('loading image');
             img.onload = () => {
                 drawImg(img);
             };
         }
     };
 
-    // The image must be loaded
-    static getCanvasMask = (srcCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
+    // The image must be loaded!
+    static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
         const canvas = document.createElement('canvas');
         canvas.width = canvasSize;
         canvas.height = canvasSize;
         const ctx = canvas.getContext('2d');
         if (!ctx) return;
         ctx?.clearRect(0, 0, canvasSize, canvasSize);
-        ctx.fillStyle = '#000000';
-        ctx.fillRect(0, 0, canvasSize, canvasSize);
+        // ctx.fillStyle = bgColor;
+        // ctx.fillRect(0, 0, canvasSize, canvasSize);
+        ctx.drawImage(paddedCanvas, 0, 0);
 
         // extract and set padding data
         if (srcCanvas.height > srcCanvas.width) {
@@ -181,7 +181,63 @@ export class ImageUtility {
         return canvas;
     };
 
-    // The image must be loaded
+    static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => {
+        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+        const data = imageData.data;
+        for (let i = 0; i < canvas.height; i++) {
+            for (let j = 0; j < xOffset; j++) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceI = i;
+                const sourceJ = xOffset + (xOffset - j);
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        for (let i = 0; i < canvas.height; i++) {
+            for (let j = canvas.width - 1; j >= canvas.width - 1 - xOffset; j--) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceI = i;
+                const sourceJ = canvas.width - 1 - xOffset - (xOffset - (canvas.width - j));
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        ctx.putImageData(imageData, 0, 0);
+    };
+
+    static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => {
+        const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
+        const data = imageData.data;
+        for (let j = 0; j < canvas.width; j++) {
+            for (let i = 0; i < yOffset; i++) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceJ = j;
+                const sourceI = yOffset + (yOffset - i);
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        for (let j = 0; j < canvas.width; j++) {
+            for (let i = canvas.height - 1; i >= canvas.height - 1 - yOffset; i--) {
+                const targetIdx = 4 * (i * canvas.width + j);
+                const sourceJ = j;
+                const sourceI = canvas.height - 1 - yOffset - (yOffset - (canvas.height - i));
+                const sourceIdx = 4 * (sourceI * canvas.width + sourceJ);
+                data[targetIdx] = data[sourceIdx];
+                data[targetIdx + 1] = data[sourceIdx + 1];
+                data[targetIdx + 2] = data[sourceIdx + 2];
+            }
+        }
+        ctx.putImageData(imageData, 0, 0);
+    };
+
+    // The image must be loaded!
     static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => {
         const canvas = document.createElement('canvas');
         canvas.width = canvasSize;
@@ -190,21 +246,27 @@ export class ImageUtility {
         if (!ctx) return;
         // fix scaling
         const scale = Math.min(canvasSize / img.width, canvasSize / img.height);
-        const width = img.width * scale;
-        const height = img.height * scale;
+        const width = Math.floor(img.width * scale);
+        const height = Math.floor(img.height * scale);
         ctx?.clearRect(0, 0, canvasSize, canvasSize);
-        ctx.fillStyle = '#000000';
+        ctx.fillStyle = bgColor;
         ctx.fillRect(0, 0, canvasSize, canvasSize);
 
+        // extract and set padding data
         if (img.naturalHeight > img.naturalWidth) {
             // horizontal padding, x offset
-            const xOffset = (canvasSize - width) / 2;
-
+            const xOffset = Math.floor((canvasSize - width) / 2);
             ctx.drawImage(img, xOffset, 0, width, height);
+
+            // draw reflected image padding
+            this.drawHorizontalReflection(ctx, canvas, xOffset);
         } else {
             // vertical padding, y offset
-            const yOffset = (canvasSize - height) / 2;
+            const yOffset = Math.floor((canvasSize - height) / 2);
             ctx.drawImage(img, 0, yOffset, width, height);
+
+            // draw reflected image padding
+            this.drawVerticalReflection(ctx, canvas, yOffset);
         }
         return canvas;
     };
-- 
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/generativeFill/GenerativeFill.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 79b1be0e8904cd6e7e1591b333e81b503f8ca41e Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Fri, 18 Aug 2023 17:52:41 -0400
Subject: cleanup

---
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 12 ++---
 .../nodes/generativeFill/GenerativeFillButtons.tsx |  5 ---
 .../generativeFillUtils/BrushHandler.ts            |  1 -
 src/client/views/pdf/AnchorMenu.tsx                | 51 ----------------------
 src/client/views/pdf/GPTPopup/GPTPopup.tsx         |  4 +-
 5 files changed, 9 insertions(+), 64 deletions(-)

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 06819d08c..547a76348 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -33,7 +33,7 @@ interface GenerativeFillProps {
     addDoc: ((doc: Doc | Doc[], annotationKey?: string) => boolean) | undefined;
 }
 
-// New field on image doc: gen_fill_children => list of children Docs
+// Added field on image doc: gen_fill_children: List of children Docs
 
 const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addDoc }: GenerativeFillProps) => {
     const canvasRef = useRef<HTMLCanvasElement>(null);
@@ -191,6 +191,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         };
     }, [canvasRef, imageEditorSource]);
 
+    // once the appropriate dimensions are set, draw the image to the canvas
     useEffect(() => {
         if (!currImg.current) return;
         ImageUtility.drawImgToCanvas(currImg.current, canvasRef, canvasDims.width, canvasDims.height);
@@ -338,7 +339,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         if (firstDoc) {
             const x = 0;
             const initialY = 0;
-
             const newImg = Docs.Create.ImageDocument(source, {
                 x: x,
                 y: initialY,
@@ -347,7 +347,6 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 data_nativeWidth: result.nativeWidth,
                 data_nativeHeight: result.nativeHeight,
             });
-            // add a new doc list field to newimg
             if (isNewCollection && newCollectionRef.current) {
                 Doc.AddDocToList(newCollectionRef.current, undefined, newImg);
             } else {
@@ -389,6 +388,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         }
     };
 
+    // Saves an image to the collection
     const onSave = async (src: string) => {
         const img = new Image();
         img.src = src;
@@ -401,13 +401,13 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
         }
     };
 
+    // Closes the editor view
     const handleViewClose = () => {
         MainView.Instance.setImageEditorOpen(false);
         MainView.Instance.setImageEditorSource('');
         if (newCollectionRef.current) {
             newCollectionRef.current.fitContentOnce = true;
         }
-
         setEdits([]);
     };
 
@@ -419,7 +419,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                     <FormControlLabel
                         control={
                             <Checkbox
-                                // disable once edited has been clicked
+                                // disable once edited has been clicked (doesn't make sense to change after first edit)
                                 disabled={edited}
                                 checked={isNewCollection}
                                 onChange={e => {
@@ -431,7 +431,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                         labelPlacement="end"
                         sx={{ whiteSpace: 'nowrap' }}
                     />
-                    <Buttons canvasRef={canvasRef} currImg={currImg} getEdit={getEdit} loading={loading} onReset={handleReset} />
+                    <Buttons getEdit={getEdit} loading={loading} onReset={handleReset} />
                     <IconButton onClick={handleViewClose}>
                         <BsX color={activeColor} />
                     </IconButton>
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index e15af0a56..398ba5333 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -1,14 +1,9 @@
 import { Button } from '@mui/material';
-import { ImageUtility } from './generativeFillUtils/ImageHandler';
-import { canvasSize } from './generativeFillUtils/generativeFillConstants';
 import { Oval } from 'react-loader-spinner';
 import './GenerativeFillButtons.scss';
 import React = require('react');
-import { Doc } from '../../../../fields/Doc';
 
 interface ButtonContainerProps {
-    canvasRef: React.RefObject<HTMLCanvasElement>;
-    currImg: React.MutableRefObject<HTMLImageElement | null>;
     getEdit: () => Promise<void>;
     loading: boolean;
     onReset: () => void;
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
index f84f04190..ace7ebcae 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
@@ -15,7 +15,6 @@ export class BrushHandler {
 
     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;
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 10cf50dff..b0924888a 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -149,54 +149,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         GPTPopup.Instance.setLoading(false);
     };
 
-    /**
-     * Makes a GPT call to edit selected text.
-     * @returns nothing
-     */
-    gptEdit = async () => {
-        if (!this.editorView) return;
-        this.setHighlightRange(undefined);
-        const state = this.editorView.state;
-        const sel = state.selection;
-        const fullText = state.doc.textBetween(0, this.editorView.state.doc.content.size, ' \n');
-        const selectedText = state.doc.textBetween(sel.from, sel.to);
-
-        GPTPopup.Instance.setVisible(true);
-        this.setGPTMode(GPTPopupMode.EDIT);
-        GPTPopup.Instance.setLoading(true);
-
-        try {
-            let res = await gptAPICall(selectedText, GPTCallType.EDIT);
-            // let res = await this.mockGPTCall();
-            if (!res) return;
-            res = res.trim();
-            const resultText = fullText.slice(0, sel.from - 1) + res + fullText.slice(sel.to - 1);
-
-            if (res) {
-                GPTPopup.Instance.setText(resultText);
-                this.setHighlightRange([sel.from - 1, sel.from - 1 + res.length]);
-            } else {
-                GPTPopup.Instance.setText('Something went wrong.');
-            }
-        } catch (err) {
-            console.error(err);
-        }
-
-        GPTPopup.Instance.setLoading(false);
-    };
-
-    gptImage = async () => {
-        console.log(this.GetAnchor(undefined, false));
-    };
-
-    /**
-     * Replaces text suggestions from GPT.
-     */
-    replaceText = (replacement: string) => {
-        if (!this.editorView || !this.textDoc) return;
-        this.textDoc.text = replacement;
-    };
-
     pointerDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(
             this,
@@ -313,9 +265,6 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             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}
diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
index 43fd8df16..034470c6e 100644
--- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx
+++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx
@@ -113,6 +113,9 @@ 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;
 
+    /**
+     * Generates a Dalle image and uploads it to the server.
+     */
     generateImage = async () => {
         if (this.imgDesc === '') return;
         this.setImgUrls([]);
@@ -125,7 +128,6 @@ export class GPTPopup extends React.Component<GPTPopupProps> {
             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) {
-- 
cgit v1.2.3-70-g09d2


From 16d8c1f7af7989afd13201c1813163bc82d84622 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Fri, 18 Aug 2023 18:43:23 -0400
Subject: cleanup

---
 .../views/nodes/generativeFill/GenerativeFill.tsx  | 49 ++++++++++------------
 .../nodes/generativeFill/GenerativeFillButtons.tsx | 38 +++++++++++------
 .../generativeFillUtils/BrushHandler.ts            | 19 ---------
 .../GenerativeFillMathHelpers.ts                   | 15 ++++---
 .../generativeFillUtils/ImageHandler.ts            | 21 ++++++++--
 .../generativeFillUtils/generativeFillConstants.ts |  1 -
 .../generativeFillInterfaces.ts                    |  1 -
 7 files changed, 71 insertions(+), 73 deletions(-)

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 547a76348..998961f20 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -3,12 +3,12 @@ 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 { IconButton } from 'browndash-components';
+import { Checkbox, FormControlLabel, Slider, TextField } from '@mui/material';
 import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces';
 import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants';
 import { PointerHandler } from './generativeFillUtils/PointerHandler';
-import { BsEraser, BsX } from 'react-icons/bs';
-import { CiUndo, CiRedo } from 'react-icons/ci';
+import { IoMdUndo, IoMdRedo } from 'react-icons/io';
 import { MainView } from '../../MainView';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { Networking } from '../../../Network';
@@ -19,6 +19,7 @@ import { CollectionDockingView } from '../../collections/CollectionDockingView';
 import { OpenWhereMod } from '../DocumentView';
 import Buttons from './GenerativeFillButtons';
 import { List } from '../../../../fields/List';
+import { CgClose } from 'react-icons/cg';
 
 enum BrushStyle {
     ADD,
@@ -432,9 +433,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                         sx={{ whiteSpace: 'nowrap' }}
                     />
                     <Buttons getEdit={getEdit} loading={loading} onReset={handleReset} />
-                    <IconButton onClick={handleViewClose}>
-                        <BsX color={activeColor} />
-                    </IconButton>
+                    <IconButton color={activeColor} tooltip="close" icon={<CgClose size={'16px'} />} onClick={handleViewClose} />
                 </div>
             </div>
             {/* Main canvas for editing */}
@@ -459,40 +458,34 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                 </div>
                 {/* Icons */}
                 <div className="iconContainer">
-                    <IconButton
-                        onClick={() => {
-                            setBrushStyle(BrushStyle.ADD);
-                        }}>
-                        <BsEraser color={brushStyle === BrushStyle.ADD ? activeColor : 'inherit'} />
-                    </IconButton>
                     {/* Undo and Redo */}
                     <IconButton
+                        style={{ cursor: 'pointer' }}
                         onPointerDown={e => {
                             e.stopPropagation();
                             handleUndo();
                         }}
                         onPointerUp={e => {
                             e.stopPropagation();
-                        }}>
-                        <CiUndo />
-                    </IconButton>
+                        }}
+                        color={activeColor}
+                        tooltip="Undo"
+                        icon={<IoMdUndo />}
+                    />
                     <IconButton
+                        style={{ cursor: 'pointer' }}
                         onPointerDown={e => {
                             e.stopPropagation();
                             handleRedo();
                         }}
                         onPointerUp={e => {
                             e.stopPropagation();
-                        }}>
-                        <CiRedo />
-                    </IconButton>
-                    <Box
-                        sx={{
-                            height: 225,
-                            width: '100%',
-                            display: 'flex',
-                            justifyContent: 'center',
-                        }}>
+                        }}
+                        color={activeColor}
+                        tooltip="Redo"
+                        icon={<IoMdRedo />}
+                    />
+                    <div onPointerDown={e => e.stopPropagation()} style={{ height: 225, width: '100%', display: 'flex', justifyContent: 'center', cursor: 'pointer' }}>
                         <Slider
                             sx={{
                                 '& input[type="range"]': {
@@ -500,14 +493,16 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                                 },
                             }}
                             orientation="vertical"
-                            min={10}
+                            min={25}
                             max={500}
                             defaultValue={150}
+                            size="small"
+                            valueLabelDisplay="auto"
                             onChange={(e, val) => {
                                 setCursorData(prev => ({ ...prev, width: val as number }));
                             }}
                         />
-                    </Box>
+                    </div>
                 </div>
                 {/* Edits thumbnails*/}
                 <div className="editsBox">
diff --git a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
index 398ba5333..0dfcebea3 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFillButtons.tsx
@@ -1,7 +1,8 @@
-import { Button } from '@mui/material';
-import { Oval } from 'react-loader-spinner';
 import './GenerativeFillButtons.scss';
 import React = require('react');
+import ReactLoading from 'react-loading';
+import { activeColor } from './generativeFillUtils/generativeFillConstants';
+import { Button, Type } from 'browndash-components';
 
 interface ButtonContainerProps {
     getEdit: () => Promise<void>;
@@ -12,17 +13,28 @@ interface ButtonContainerProps {
 const Buttons = ({ loading, getEdit, onReset }: ButtonContainerProps) => {
     return (
         <div className="generativeFillBtnContainer">
-            <Button onClick={onReset}>Reset</Button>
-            <Button
-                variant="contained"
-                onClick={() => {
-                    getEdit();
-                }}>
-                <span style={{ display: 'flex', alignItems: 'center', gap: '0.5rem' }}>
-                    Get Edit
-                    {loading && <Oval height={20} width={20} color="#ffffff" visible={true} ariaLabel="oval-loading" secondaryColor="#ffffff89" strokeWidth={3} strokeWidthSecondary={3} />}
-                </span>
-            </Button>
+            <Button text="RESET" type={Type.PRIM} color={activeColor} onClick={onReset} />
+            {loading ? (
+                <Button
+                    text="GET EDITS"
+                    type={Type.TERT}
+                    color={activeColor}
+                    icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+                    iconPlacement="right"
+                    onClick={() => {
+                        if (!loading) getEdit();
+                    }}
+                />
+            ) : (
+                <Button
+                    text="GET EDITS"
+                    type={Type.TERT}
+                    color={activeColor}
+                    onClick={() => {
+                        if (!loading) getEdit();
+                    }}
+                />
+            )}
         </div>
     );
 };
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
index ace7ebcae..f4ec70fbc 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/BrushHandler.ts
@@ -3,16 +3,6 @@ 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 brushCircleOverlay = (x: number, y: number, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
         ctx.globalCompositeOperation = 'destination-out';
         ctx.fillStyle = fillColor;
@@ -24,15 +14,6 @@ export class BrushHandler {
         ctx.closePath();
     };
 
-    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);
-        }
-    };
-
     static createBrushPathOverlay = (startPoint: Point, endPoint: Point, brushRadius: number, ctx: CanvasRenderingContext2D, fillColor: string, erase: boolean) => {
         const dist = GenerativeFillMathHelpers.distanceBetween(startPoint, endPoint);
 
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
index 027b99a52..97e03ff20 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/GenerativeFillMathHelpers.ts
@@ -1,11 +1,10 @@
-import { Point } from "./generativeFillInterfaces";
+import { Point } from './generativeFillInterfaces';
 
 export class GenerativeFillMathHelpers {
-  // math helpers
-  static distanceBetween = (p1: Point, p2: Point) => {
-    return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
-  };
-  static angleBetween = (p1: Point, p2: Point) => {
-    return Math.atan2(p2.x - p1.x, p2.y - p1.y);
-  };
+    static distanceBetween = (p1: Point, p2: Point) => {
+        return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
+    };
+    static angleBetween = (p1: Point, p2: Point) => {
+        return Math.atan2(p2.x - p1.x, p2.y - p1.y);
+    };
 }
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
index 4ff70c86c..c6ecedb6b 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/ImageHandler.ts
@@ -12,6 +12,11 @@ export interface APIError {
 }
 
 export class ImageUtility {
+    /**
+     *
+     * @param canvas Canvas to convert
+     * @returns Blob of canvas
+     */
     static canvasToBlob = (canvas: HTMLCanvasElement): Promise<Blob> => {
         return new Promise(resolve => {
             canvas.toBlob(blob => {
@@ -45,6 +50,7 @@ export class ImageUtility {
         }
     };
 
+    // converts an image to a canvas data url
     static convertImgToCanvasUrl = async (imageSrc: string, width: number, height: number): Promise<string> => {
         return new Promise<string>((resolve, reject) => {
             const img = new Image();
@@ -62,6 +68,7 @@ export class ImageUtility {
         });
     };
 
+    // calls the openai api to get image edits
     static getEdit = async (imgBlob: Blob, maskBlob: Blob, prompt: string, n?: number): Promise<APISuccess | APIError> => {
         const apiUrl = 'https://api.openai.com/v1/images/edits';
         const fd = new FormData();
@@ -92,6 +99,7 @@ export class ImageUtility {
         }
     };
 
+    // mock api call
     static mockGetEdit = async (mockSrc: string): Promise<APISuccess | APIError> => {
         return {
             status: 'success',
@@ -99,6 +107,7 @@ export class ImageUtility {
         };
     };
 
+    // Gets the canvas rendering context of a canvas
     static getCanvasContext = (canvasRef: RefObject<HTMLCanvasElement>): CanvasRenderingContext2D | null => {
         if (!canvasRef.current) return null;
         const ctx = canvasRef.current.getContext('2d');
@@ -106,6 +115,7 @@ export class ImageUtility {
         return ctx;
     };
 
+    // Helper for downloading the canvas (for debugging)
     static downloadCanvas = (canvas: HTMLCanvasElement) => {
         const url = canvas.toDataURL();
         const downloadLink = document.createElement('a');
@@ -116,6 +126,7 @@ export class ImageUtility {
         downloadLink.remove();
     };
 
+    // Download the canvas (for debugging)
     static downloadImageCanvas = (imgUrl: string) => {
         const img = new Image();
         img.src = imgUrl;
@@ -130,12 +141,14 @@ export class ImageUtility {
         };
     };
 
+    // Clears the canvas
     static clearCanvas = (canvasRef: React.RefObject<HTMLCanvasElement>) => {
         const ctx = this.getCanvasContext(canvasRef);
         if (!ctx || !canvasRef.current) return;
         ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
     };
 
+    // Draws the image to the current canvas
     static drawImgToCanvas = (img: HTMLImageElement, canvasRef: React.RefObject<HTMLCanvasElement>, width: number, height: number) => {
         const drawImg = (img: HTMLImageElement) => {
             const ctx = this.getCanvasContext(canvasRef);
@@ -154,7 +167,7 @@ export class ImageUtility {
         }
     };
 
-    // The image must be loaded!
+    // Gets the image mask for the openai endpoint
     static getCanvasMask = (srcCanvas: HTMLCanvasElement, paddedCanvas: HTMLCanvasElement): HTMLCanvasElement | undefined => {
         const canvas = document.createElement('canvas');
         canvas.width = canvasSize;
@@ -162,8 +175,6 @@ export class ImageUtility {
         const ctx = canvas.getContext('2d');
         if (!ctx) return;
         ctx?.clearRect(0, 0, canvasSize, canvasSize);
-        // ctx.fillStyle = bgColor;
-        // ctx.fillRect(0, 0, canvasSize, canvasSize);
         ctx.drawImage(paddedCanvas, 0, 0);
 
         // extract and set padding data
@@ -181,6 +192,7 @@ export class ImageUtility {
         return canvas;
     };
 
+    // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
     static drawHorizontalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, xOffset: number) => {
         const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
         const data = imageData.data;
@@ -209,6 +221,7 @@ export class ImageUtility {
         ctx.putImageData(imageData, 0, 0);
     };
 
+    // Fills in the blank areas of the image with an image reflection (to fill in a square-shaped canvas)
     static drawVerticalReflection = (ctx: CanvasRenderingContext2D, canvas: HTMLCanvasElement, yOffset: number) => {
         const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
         const data = imageData.data;
@@ -237,7 +250,7 @@ export class ImageUtility {
         ctx.putImageData(imageData, 0, 0);
     };
 
-    // The image must be loaded!
+    // Gets the unaltered (besides filling in padding) version of the image for the api call
     static getCanvasImg = (img: HTMLImageElement): HTMLCanvasElement | undefined => {
         const canvas = document.createElement('canvas');
         canvas.width = canvasSize;
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
index 1fe151b46..dc94a9368 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillConstants.ts
@@ -1,4 +1,3 @@
-// constants
 export const canvasSize = 512;
 export const freeformRenderSize = 300;
 export const offsetDistanceY = freeformRenderSize + 400;
diff --git a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
index 83a21a1a5..1e7801056 100644
--- a/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
+++ b/src/client/views/nodes/generativeFill/generativeFillUtils/generativeFillInterfaces.ts
@@ -1,4 +1,3 @@
-// interfaces
 export interface CursorData {
     x: number;
     y: number;
-- 
cgit v1.2.3-70-g09d2


From b8ce16a48fc37fc94dc7ea465c652b5c376ed016 Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Fri, 18 Aug 2023 19:20:55 -0400
Subject: fix original image click

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

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index 998961f20..ce6ce672f 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -63,6 +63,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
     const currImg = useRef<HTMLImageElement | null>(null);
     // the unedited version of each generation (parent)
     const originalImg = useRef<HTMLImageElement | null>(null);
+    const originalDoc = useRef<Doc | null>(null);
     // stores history of data urls
     const undoStack = useRef<string[]>([]);
     // stores redo stack
@@ -186,6 +187,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             childrenDocs.current = [];
             currImg.current = null;
             originalImg.current = null;
+            originalDoc.current = null;
             undoStack.current = [];
             redoStack.current = [];
             ImageUtility.clearCanvas(canvasRef);
@@ -291,6 +293,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             }
 
             originalImg.current = currImg.current;
+            originalDoc.current = parentDoc.current;
             const { urls } = res as APISuccess;
             const imgUrls = await Promise.all(urls.map(url => ImageUtility.convertImgToCanvasUrl(url, canvasDims.width, canvasDims.height)));
             const imgRes = await Promise.all(
@@ -302,9 +305,9 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             setEdits(imgRes);
             const image = new Image();
             image.src = imgUrls[0];
-
             ImageUtility.drawImgToCanvas(image, canvasRef, canvasDims.width, canvasDims.height);
             currImg.current = image;
+            parentDoc.current = imgRes[0][1] as Doc;
         } catch (err) {
             console.log(err);
         }
@@ -511,6 +514,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                             key={i}
                             width={75}
                             src={edit[0] as string}
+                            style={{ cursor: 'pointer' }}
                             onClick={async () => {
                                 const img = new Image();
                                 img.src = edit[0] as string;
@@ -545,6 +549,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
                                     img.src = originalImg.current.src;
                                     ImageUtility.drawImgToCanvas(img, canvasRef, canvasDims.width, canvasDims.height);
                                     currImg.current = img;
+                                    parentDoc.current = originalDoc.current;
                                 }}
                             />
                         </div>
-- 
cgit v1.2.3-70-g09d2


From f8c2d9d029f129eb595677b1e8a09ff1ebd5880c Mon Sep 17 00:00:00 2001
From: Sophie Zhang <sophie_zhang@brown.edu>
Date: Wed, 23 Aug 2023 23:07:18 -0400
Subject: branching fix

---
 src/client/views/nodes/generativeFill/GenerativeFill.tsx | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
index ce6ce672f..7230aa9b1 100644
--- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx
+++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx
@@ -181,6 +181,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
 
         // cleanup
         return () => {
+            setInput('');
             setEdited(false);
             newCollectionRef.current = null;
             parentDoc.current = null;
@@ -268,8 +269,8 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD
             // create first image
             if (!newCollectionRef.current) {
                 if (!isNewCollection && imageRootDoc) {
-                    // new collection stays null
-                    parentDoc.current = imageRootDoc;
+                    // if the parent hasn't been set yet
+                    if (!parentDoc.current) parentDoc.current = imageRootDoc;
                 } else {
                     if (!(originalImg.current && imageRootDoc)) return;
                     // create new collection and add it to the view
-- 
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/generativeFill/GenerativeFill.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

