From 3d14f06ad1297a6adc851945804211efb9dff7ff Mon Sep 17 00:00:00 2001
From: eleanor-park <eleanor_park@brown.edu>
Date: Thu, 20 Jun 2024 16:42:44 -0400
Subject: user customization added

---
 .../collectionFreeForm/SmartDrawHandler.tsx        | 261 +++++++++++++++------
 1 file changed, 185 insertions(+), 76 deletions(-)

(limited to 'src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx')

diff --git a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
index 956a8d7e9..edb814172 100644
--- a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
+++ b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
@@ -4,16 +4,20 @@ import { observer } from 'mobx-react';
 import React from 'react';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { ObservableReactComponent } from '../../ObservableReactComponent';
-import { Button, IconButton } from 'browndash-components';
+import { Button, IconButton, Size } from 'browndash-components';
 import ReactLoading from 'react-loading';
 import { AiOutlineSend } from 'react-icons/ai';
-import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
 import './ImageLabelHandler.scss';
 import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
 import { InkData } from '../../../../fields/InkField';
-import { ButtonType } from '../../nodes/FontIconBox/FontIconBox';
 import { SVGToBezier } from '../../../util/bezierFit';
-const { parse, stringify } = require('svgson');
+const { parse } = require('svgson');
+import { Slider, Switch } from '@mui/material';
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { Flex } from '@adobe/react-spectrum';
+import { Row } from 'react-aria-components';
+import { UndoManager } from '../../../util/UndoManager';
+import e from 'cors';
 
 @observer
 export class SmartDrawHandler extends ObservableReactComponent<{}> {
@@ -25,11 +29,15 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
     @observable private _yRelativeToTop: boolean = true;
     @observable private _isLoading: boolean = false;
     @observable private _userInput: string = '';
-    @observable private _drawingTypeToolTip = 'Create Geometric Drawing';
-    @observable private _drawingTypeIcon: 'star' | 'splotch' = 'star';
-    @observable private _alpha: number | undefined = undefined; // number between 0 and 1 that determines how rounded a drawing will be
-    // @observable public strokes: InkData[] = [];
-    private _addToDocFunc: (strokeList: InkData[], alpha?: number) => void = () => {};
+    @observable private _showOptions: boolean = false;
+    @observable private _menuIcon: string = 'caret-right';
+    @observable private _complexity: number = 5;
+    @observable private _size: number = 300;
+    @observable private _autoColor: boolean = true;
+    @observable private _showRegenerate: boolean = false;
+    private _addToDocFunc: (strokeList: [InkData, string, string][]) => void = () => {};
+    private _lastX: number = 0;
+    private _lastY: number = 0;
 
     constructor(props: any) {
         super(props);
@@ -37,97 +45,82 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
         SmartDrawHandler.Instance = this;
     }
 
-    @action
-    setIsLoading = (isLoading: boolean) => {
-        this._isLoading = isLoading;
-    };
-
     @action
     setUserInput = (input: string) => {
         this._userInput = input;
     };
 
     @action
-    displaySmartDrawHandler = (x: number, y: number, addToDoc: (strokeList: InkData[], alpha?: number) => void) => {
+    displaySmartDrawHandler = (x: number, y: number, addToDoc: (strokeData: [InkData, string, string][]) => void) => {
         this._pageX = x;
         this._pageY = y;
         this._display = true;
         this._addToDocFunc = addToDoc;
     };
 
-    @action
     hideSmartDrawHandler = () => {
+        this._showRegenerate = false;
         this._display = false;
+        this._isLoading = false;
+        this._showOptions = false;
+        this._menuIcon = 'caret-right';
+    };
+
+    hideRegenerate = () => {
+        this._showRegenerate = false;
+        this._userInput = '';
+        this._complexity = 5;
+        this._size = 300;
+        this._autoColor = true;
+        this._isLoading = false;
+    };
+
+    toggleMenu = () => {
+        this._showOptions = !this._showOptions;
+        this._menuIcon === 'caret-right' ? (this._menuIcon = 'caret-down') : (this._menuIcon = 'caret-right');
     };
 
     @action
-    drawWithGPT = async (startPoint: { X: number; Y: number }, input: string) => {
-        console.log('start point is', startPoint);
-        this.setIsLoading(true);
+    drawWithGPT = async (e: React.MouseEvent<Element, MouseEvent>, startPoint: { X: number; Y: number }, input: string, regenerate: boolean = false) => {
+        if (this._userInput === '') return;
+        e.stopPropagation();
+        this._lastX = startPoint.X;
+        this._lastY = startPoint.Y;
+        this._isLoading = true;
+        this._showOptions = false;
         try {
-            const res = await gptAPICall(input, GPTCallType.DRAW);
+            const res = await gptAPICall(`"${input}", "${this._complexity}", "${this._size}"`, GPTCallType.DRAW);
             if (!res) {
                 console.error('GPT call failed');
                 return;
             }
-            console.log('GPT response:', res);
             const svg = res.match(/<svg[^>]*>([\s\S]*?)<\/svg>/g);
-            console.log('svg', svg);
             if (svg) {
                 const svgObject = await parse(svg[0]);
-                console.log('svg object', svgObject);
                 const svgStrokes: any = svgObject.children;
-                const beziers: InkData[] = [];
-                svgStrokes.forEach((stroke: any) => {
-                    const convertedBezier: InkData = SVGToBezier(stroke.name, stroke.attributes);
-                    beziers.push(
+                const strokeData: [InkData, string, string][] = [];
+                svgStrokes.forEach((child: any) => {
+                    const convertedBezier: InkData = SVGToBezier(child.name, child.attributes);
+                    strokeData.push([
                         convertedBezier.map(point => {
-                            return { X: point.X + startPoint.X, Y: point.Y + startPoint.Y };
-                        })
-                    );
+                            return { X: point.X + startPoint.X - this._size / 1.5, Y: point.Y + startPoint.Y - this._size / 2 };
+                        }),
+                        this._autoColor ? child.attributes.stroke : undefined,
+                        this._autoColor ? child.attributes.fill : undefined,
+                    ]);
                 });
-                this._addToDocFunc(beziers);
+                if (regenerate) UndoManager.Undo();
+                this._addToDocFunc(strokeData);
             }
-
-            // const strokes = res.trim().split(/\s*(?=\s*M)/); // prettier-ignore
-            // const parsedSegments: InkData[] = [];
-            // console.log('strokes', strokes);
-            // strokes.forEach(stroke => {
-            //     stroke = stroke.replace(/C\s*\((\d+,\d+)\)\s*\((\d+,\d+)\)\s*\((\d+,\d+)\)/g, (c, p1, p2, p3) => {
-            //         return `C (${p1}) (${p2}) (${p3}) (${p3})`;
-            //     });
-            //     const coordStrings = stroke.match(/(\d+,\d+)/g);
-            //     const coords: InkData = [];
-            //     if (coordStrings) {
-            //         coordStrings.forEach(coord => {
-            //             const xy = coord.split(',');
-            //             coords.push({ X: parseInt(xy[0]), Y: parseInt(xy[1]) });
-            //         });
-            //         coords.pop();
-            //         parsedSegments.push(coords);
-            //     }
-            //     console.log('coords', coords);
-            // });
-            // this._addToDocFunc(parsedSegments);
         } catch (err) {
             console.error('GPT call failed', err);
         }
-
-        this.setIsLoading(false);
-        this.setUserInput('');
         this.hideSmartDrawHandler();
+        this._showRegenerate = true;
     };
 
-    changeDrawingType = () => {
-        if (this._drawingTypeIcon === 'star') {
-            this._drawingTypeIcon = 'splotch';
-            this._drawingTypeToolTip = 'Create Rounded Drawing';
-            this._alpha = 0.2;
-        } else {
-            this._drawingTypeIcon = 'star';
-            this._drawingTypeToolTip = 'Create Geometric Drawing';
-            this._alpha = 0;
-        }
+    regenerate = (e: React.MouseEvent<Element, MouseEvent>) => {
+        this.drawWithGPT(e, { X: this._lastX, Y: this._lastY }, `Regenerate the item "${this._userInput}"`, true);
     };
 
     render() {
@@ -144,7 +137,16 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
                         color: SettingsManager.userColor,
                     }}>
                     <div>
-                        <IconButton tooltip={'Cancel'} onClick={this.hideSmartDrawHandler} icon={<FontAwesomeIcon icon="xmark" />} color={MarqueeOptionsMenu.Instance.userColor} style={{ width: '19px' }} />
+                        <IconButton
+                            tooltip={'Cancel'}
+                            onClick={() => {
+                                this.hideSmartDrawHandler();
+                                this.hideRegenerate();
+                            }}
+                            icon={<FontAwesomeIcon icon="xmark" />}
+                            color={SettingsManager.userColor}
+                            style={{ width: '19px' }}
+                        />
                         <input
                             aria-label="label-input"
                             id="new-label"
@@ -156,24 +158,131 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
                             }}
                             placeholder="Enter item to draw"
                         />
-                        <IconButton tooltip={this._drawingTypeToolTip} icon={<FontAwesomeIcon icon={this._drawingTypeIcon} />} color={MarqueeOptionsMenu.Instance.userColor} style={{ width: '14px' }} onClick={this.changeDrawingType} />
-                        {/* <IconButton
-                            tooltip="Create Geometric Drawing"
-                            icon={<FontAwesomeIcon icon="star" />}
-                            color={MarqueeOptionsMenu.Instance.userColor}
+                        <IconButton
+                            tooltip="Advanced Options"
+                            icon={<FontAwesomeIcon icon={this._showOptions ? 'caret-down' : 'caret-right'} />}
+                            color={SettingsManager.userColor}
                             style={{ width: '14px' }}
                             onClick={() => {
-                                this._alpha = 0;
+                                this._showOptions = !this._showOptions;
                             }}
-                        /> */}
+                        />
                         <Button
                             style={{ alignSelf: 'flex-end' }}
                             text="Send"
-                            icon={this._isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
+                            icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
                             iconPlacement="right"
-                            color={MarqueeOptionsMenu.Instance.userColor}
+                            color={SettingsManager.userColor}
+                            onClick={e => {
+                                this.drawWithGPT(e, { X: e.clientX, Y: e.clientY }, this._userInput);
+                            }}
+                        />
+                    </div>
+                    {this._showOptions && (
+                        <>
+                            <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-around' }}>
+                                <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', width: '30%' }}>
+                                    Auto color
+                                    <Switch
+                                        sx={{
+                                            '& .MuiSwitch-switchBase.Mui-checked': {
+                                                color: SettingsManager.userColor,
+                                            },
+                                            '& .MuiSwitch-switchBase.Mui-checked + .MuiSwitch-track': {
+                                                backgroundColor: SettingsManager.userVariantColor,
+                                            },
+                                        }}
+                                        defaultChecked={true}
+                                        size="small"
+                                        onChange={() => (this._autoColor = !this._autoColor)}
+                                    />
+                                </div>
+                                <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', width: '31%' }}>
+                                    Complexity
+                                    <Slider
+                                        sx={{
+                                            '& .MuiSlider-thumb': {
+                                                color: SettingsManager.userColor,
+                                                '&.Mui-focusVisible, &:hover, &.Mui-active': {
+                                                    boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}10`,
+                                                },
+                                            },
+                                            '& .MuiSlider-track': {
+                                                color: SettingsManager.userVariantColor,
+                                            },
+                                            '& .MuiSlider-rail': {
+                                                color: SettingsManager.userColor,
+                                            },
+                                        }}
+                                        style={{ width: '80%' }}
+                                        min={1}
+                                        max={10}
+                                        step={1}
+                                        size="small"
+                                        value={this._complexity}
+                                        onChange={(e, val) => {
+                                            this._complexity = val as number;
+                                        }}
+                                        valueLabelDisplay="auto"
+                                    />
+                                </div>
+                                <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', width: '39%' }}>
+                                    Size (in pixels)
+                                    <Slider
+                                        sx={{
+                                            '& .MuiSlider-thumb': {
+                                                color: SettingsManager.userColor,
+                                                '&.Mui-focusVisible, &:hover, &.Mui-active': {
+                                                    boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20`,
+                                                },
+                                            },
+                                            '& .MuiSlider-track': {
+                                                color: SettingsManager.userVariantColor,
+                                            },
+                                            '& .MuiSlider-rail': {
+                                                color: SettingsManager.userColor,
+                                            },
+                                        }}
+                                        style={{ width: '80%' }}
+                                        min={50}
+                                        max={700}
+                                        step={10}
+                                        size="small"
+                                        value={this._size}
+                                        onChange={(e, val) => {
+                                            this._size = val as number;
+                                        }}
+                                        valueLabelDisplay="auto"
+                                    />
+                                </div>
+                            </div>
+                        </>
+                    )}
+                </div>
+            );
+        } else if (this._showRegenerate) {
+            return (
+                <div
+                    id="smartdraw-options-menu"
+                    className="contextMenu-cont"
+                    style={{
+                        left: this._pageX,
+                        ...(this._yRelativeToTop ? { top: Math.max(0, this._pageY) } : { bottom: this._pageY }),
+                        background: SettingsManager.userBackgroundColor,
+                        color: SettingsManager.userColor,
+                    }}>
+                    <div
+                        style={{
+                            display: 'flex',
+                            flexDirection: 'row',
+                        }}>
+                        <IconButton tooltip="Cancel" onClick={this.hideRegenerate} icon={<FontAwesomeIcon icon="xmark" />} color={SettingsManager.userColor} style={{ width: '19px' }} />
+                        <IconButton
+                            tooltip="Regenerate"
+                            icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon={'rotate'} />}
+                            color={SettingsManager.userColor}
                             onClick={e => {
-                                this.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._userInput);
+                                this.regenerate(e);
                             }}
                         />
                     </div>
-- 
cgit v1.2.3-70-g09d2

