aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/smartdraw/SmartDrawHandler.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/smartdraw/SmartDrawHandler.tsx')
-rw-r--r--src/client/views/smartdraw/SmartDrawHandler.tsx80
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}