diff options
Diffstat (limited to 'src/client/views/smartdraw/SmartDrawHandler.tsx')
-rw-r--r-- | src/client/views/smartdraw/SmartDrawHandler.tsx | 80 |
1 files changed, 58 insertions, 22 deletions
diff --git a/src/client/views/smartdraw/SmartDrawHandler.tsx b/src/client/views/smartdraw/SmartDrawHandler.tsx index 8271c0959..e362c0c89 100644 --- a/src/client/views/smartdraw/SmartDrawHandler.tsx +++ b/src/client/views/smartdraw/SmartDrawHandler.tsx @@ -12,7 +12,6 @@ import { Doc, DocListCast } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { InkData, InkField, InkTool } from '../../../fields/InkField'; import { BoolCast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; -import { ImageField } from '../../../fields/URLField'; import { GPTCallType, gptAPICall, gptDrawingColor } from '../../apis/gpt/GPT'; import { Docs } from '../../documents/Documents'; import { SettingsManager } from '../../util/SettingsManager'; @@ -34,6 +33,21 @@ export interface DrawingOptions { y: number; } +/** + * The SmartDrawHandler allows users to generate drawings with GPT from text input. Users are able to enter + * the item to draw, how complex they want the drawing to be, how large the drawing should be, and whether + * it will be colored. If the drawing is colored, GPT will automatically define the stroke and fill of each + * stroke. Drawings are retrieved from GPT as SVG code then converted into Dash-supported Beziers. + * + * The handler is selected from the ink tools menu. To generate a drawing, users can click anywhere on the freeform + * canvas and a popup will appear that prompts them to create a drawing. Once the drawing is created, users have + * the option to regenerate or edit the drawing. + * + * When each drawing is created, it is added to Dash as a group of ink strokes. The group is tagged with metadata + * for user input, the drawing's SVG code, and its settings (size, complexity). In the context menu -> 'Options', + * users can then show the drawing editor and regenerate/edit them at any point in the future. + */ + @observer export class SmartDrawHandler extends ObservableReactComponent<object> { static Instance: SmartDrawHandler; @@ -65,8 +79,19 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { SmartDrawHandler.Instance = this; } + /** + * AddDrawing and RemoveDrawing are defined by the other classes that call the smart draw functions (i.e. + CollectionFreeForm, FormattedTextBox, AnnotationPalette) to define how a drawing document should be added + or removed in their respective locations (to the freeform canvs, to the annotation palette's preview, etc.) + */ public AddDrawing: (doc: Doc, opts: DrawingOptions, gptRes: string) => void = unimplementedFunction; - public RemoveDrawing: (doc?: Doc) => void = unimplementedFunction; + public RemoveDrawing: (useLastContainer: boolean, doc?: Doc) => void = unimplementedFunction; + /** + * This creates the ink document that represents a drawing, so it goes through the strokes that make up the drawing, + * creates ink documents for each stroke, then adds the strokes to a collection. This can also be redefined by other + * classes to customize the way the drawing docs get created. For example, the freeform canvas has a different way of + * defining document bounds, so CreateDrawingDoc is redefined when that class calls gpt draw functions. + */ public CreateDrawingDoc: (strokeList: [InkData, string, string][], opts: DrawingOptions, gptRes: string, containerDoc?: Doc) => Doc | undefined = (strokeList: [InkData, string, string][], opts: DrawingOptions) => { const drawing: Doc[] = []; strokeList.forEach((stroke: [InkData, string, string]) => { @@ -101,6 +126,11 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { this._display = true; }; + /** + * This is called in two places: 1. In this class, where the regenerate popup shows as soon as a + * drawing is created to replace the original smart draw popup. 2. From the context menu to make + * the regenerate popup show by user command. + */ @action displayRegenerate = (x: number, y: number) => { this._selectedDoc = DocumentView.SelectedDocs()?.lastElement(); @@ -113,6 +143,9 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { this._lastInput = { text: StrCast(docData.drawingInput), complexity: NumCast(docData.drawingComplexity), size: NumCast(docData.drawingSize), autoColor: BoolCast(docData.drawingColored), x: this._pageX, y: this._pageY }; }; + /** + * Hides the smart draw handler and resets its fields to their default. + */ @action hideSmartDrawHandler = () => { this.ShowRegenerate = false; @@ -126,6 +159,9 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { Doc.ActiveTool = InkTool.None; }; + /** + * Hides the popup that allows users to regenerate a drawing and resets its corresponding fields. + */ @action hideRegenerate = () => { if (!this._isLoading) { @@ -136,12 +172,20 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { } }; + /** + * This allows users to press the return/enter key to send input. + */ handleKeyPress = (event: React.KeyboardEvent) => { if (event.key === 'Enter') { this.handleSendClick(); } }; + /** + * This is called when a user hits "send" on the draw with GPT popup. It calls the drawWithGPT or regenerate + * functions depending on what mode is currently displayed, then sets various observable fields that facilitate + * what the user sees. + */ @action handleSendClick = async () => { this._isLoading = true; @@ -171,7 +215,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { }; /** - * Calls GPT API to create a drawing based on user input + * Calls GPT API to create a drawing based on user input. */ @action drawWithGPT = async (startPt: { X: number; Y: number }, input: string, complexity: number, size: number, autoColor: boolean) => { @@ -182,6 +226,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { console.error('GPT call failed'); return; } + console.log(res); const strokeData = await this.parseSvg(res, startPt, false, autoColor); const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData?.data, strokeData?.lastInput, strokeData?.lastRes); drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); @@ -213,7 +258,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { return; } const strokeData = await this.parseSvg(res, { X: this._lastInput.x, Y: this._lastInput.y }, true, lastInput?.autoColor || this._autoColor); - this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(this._selectedDoc); + this.RemoveDrawing !== unimplementedFunction && this.RemoveDrawing(true, this._selectedDoc); const drawingDoc = strokeData && this.CreateDrawingDoc(strokeData?.data, strokeData?.lastInput, strokeData?.lastRes); drawingDoc && this.AddDrawing(drawingDoc, this._lastInput, res); return strokeData; @@ -223,7 +268,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { }; /** - * Parses the svg code that GPT returns into Bezier curves. + * Parses the svg code that GPT returns into Bezier curves, with coordinates and colors. */ @action parseSvg = async (res: string, startPoint: { X: number; Y: number }, regenerate: boolean, autoColor: boolean) => { @@ -307,26 +352,18 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { }} icon={<FontAwesomeIcon icon="xmark" />} color={SettingsManager.userColor} - style={{ width: '19px' }} /> <input aria-label="Smart Draw Input" className="smartdraw-input" type="text" autoFocus - style={{ color: 'black' }} value={this._userInput} onChange={action(e => this._canInteract && (this._userInput = e.target.value))} placeholder="Enter item to draw" onKeyDown={this.handleKeyPress} /> - <IconButton - tooltip="Advanced Options" - icon={<FontAwesomeIcon icon={this._showOptions ? 'caret-down' : 'caret-right'} />} - color={SettingsManager.userColor} - style={{ width: '14px' }} - onClick={action(() => (this._showOptions = !this._showOptions))} - /> + <IconButton tooltip="Advanced Options" icon={<FontAwesomeIcon icon={this._showOptions ? 'caret-down' : 'caret-right'} />} color={SettingsManager.userColor} onClick={action(() => (this._showOptions = !this._showOptions))} /> <Button style={{ alignSelf: 'flex-end' }} text="Send" @@ -337,8 +374,8 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { /> </div> {this._showOptions && ( - <div style={{ display: 'flex', flexDirection: 'row', justifyContent: 'space-around' }}> - <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', width: '30%' }}> + <div className="smartdraw-options"> + <div className="auto-color"> Auto color <Switch sx={{ @@ -351,7 +388,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { onChange={action(e => this._canInteract && (this._autoColor = !this._autoColor))} /> </div> - <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', width: '31%' }}> + <div className="complexity"> Complexity <Slider sx={{ @@ -369,15 +406,15 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { valueLabelDisplay="auto" /> </div> - <div style={{ display: 'flex', flexDirection: 'column', justifyContent: 'center', width: '39%' }}> + <div className="size"> Size (in pixels) <Slider + className="size-slider" sx={{ '& .MuiSlider-track': { color: SettingsManager.userVariantColor }, '& .MuiSlider-rail': { color: SettingsManager.userColor }, '& .MuiSlider-thumb': { color: SettingsManager.userColor, '&.Mui-focusVisible, &:hover, &.Mui-active': { boxShadow: `0px 0px 0px 8px${SettingsManager.userColor.slice(0, 7)}20` } }, }} - style={{ width: '80%' }} min={50} max={700} step={10} @@ -403,7 +440,7 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, }}> - <div style={{ display: 'flex', flexDirection: 'row' }}> + <div className="regenerate-box"> <IconButton tooltip="Regenerate" icon={this._isLoading && this._regenInput === '' ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <FontAwesomeIcon icon={'rotate'} />} @@ -412,12 +449,11 @@ export class SmartDrawHandler extends ObservableReactComponent<object> { /> <IconButton tooltip="Edit with GPT" icon={<FontAwesomeIcon icon="pen-to-square" />} color={SettingsManager.userColor} onClick={action(() => (this._showEditBox = !this._showEditBox))} /> {this._showEditBox && ( - <div style={{ display: 'flex', flexDirection: 'row' }}> + <div className="edit-box"> <input aria-label="Edit instructions input" className="smartdraw-input" type="text" - style={{ color: 'black' }} value={this._regenInput} onChange={action(e => this._canInteract && (this._regenInput = e.target.value))} onKeyDown={this.handleKeyPress} |