diff options
-rw-r--r-- | src/client/apis/gpt/GPT.ts | 1 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 2 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 2 | ||||
-rw-r--r-- | src/client/views/SmartDraw.tsx | 0 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 44 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx | 154 | ||||
-rw-r--r-- | src/fields/InkField.ts | 1 |
7 files changed, 195 insertions, 9 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index 3a5e49731..5afb345a0 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -12,6 +12,7 @@ enum GPTCallType { DESCRIBE = 'describe', MERMAID = 'mermaid', DATA = 'data', + DRAW = 'draw', } type GPTCallOpts = { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 4e379219f..3250f10a8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -745,7 +745,7 @@ pie title Minerals in my tap water { title: "Labels", toolTip: "Lab els", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1}, { title: "Ink", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: "strokeColor", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'} }, - { title: "Smart Draw", toolTip: "Draw with GPT", btnType: ButtonType.ToggleButton, icon: "user-pen", toolType: "smartDraw", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {hidden: "IsNoviceMode()"}}, + { title: "Smart Draw", toolTip: "Draw with GPT", btnType: ButtonType.ToggleButton, icon: "user-pen", toolType: "smartdraw", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {hidden: "IsNoviceMode()"}}, ]; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8430db883..f7e1617fc 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -55,6 +55,7 @@ import { TabDocView } from './collections/TabDocView'; import './collections/TreeView.scss'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { ImageLabelHandler } from './collections/collectionFreeForm/ImageLabelHandler'; +import { SmartDrawHandler } from './collections/collectionFreeForm/SmartDrawHandler'; import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { LinkMenu } from './linking/LinkMenu'; @@ -1090,6 +1091,7 @@ export class MainView extends ObservableReactComponent<{}> { <TaskCompletionBox /> <ContextMenu /> <ImageLabelHandler /> + <SmartDrawHandler /> <AnchorMenu /> <MapAnchorMenu /> <DirectionsAnchorMenu /> diff --git a/src/client/views/SmartDraw.tsx b/src/client/views/SmartDraw.tsx deleted file mode 100644 index e69de29bb..000000000 --- a/src/client/views/SmartDraw.tsx +++ /dev/null diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c6db8290d..194c99c3d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -54,6 +54,8 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; +import { SmartDrawHandler } from './SmartDrawHandler'; +import { ImageLabelHandler } from './ImageLabelHandler'; @observer class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> { @@ -496,30 +498,33 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (!this.Document.isGroup) { // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag // prettier-ignore + const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); switch (Doc.ActiveTool) { - case InkTool.Highlighter: break; - case InkTool.Write: break; - case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views + case InkTool.Highlighter: + break; + case InkTool.Write: + break; + case InkTool.Pen: + break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views case InkTool.StrokeEraser: case InkTool.SegmentEraser: this._batch = UndoManager.StartBatch('collectionErase'); this._eraserPts.length = 0; - const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction, hit !== -1, false); break; case InkTool.RadiusEraser: this._batch = UndoManager.StartBatch('collectionErase'); this._eraserPts.length = 0; - // const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); - // setupMoveUpEvents(this, e, this.onRadiusEraserMove, this.onEraserUp, emptyFunction, hit !== -1, false); + setupMoveUpEvents(this, e, this.onRadiusEraserMove, this.onEraserUp, emptyFunction, hit !== -1, false); break; + case InkTool.SmartDraw: + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, this.createDrawing, hit !== -1, false); case InkTool.None: if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) { - const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1, false); } break; - default: + default: } } } @@ -1226,6 +1231,29 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; @action + createDrawing = (e: PointerEvent, doubleTap?: boolean) => { + SmartDrawHandler.Instance.displaySmartDrawHandler(e.pageX, e.pageY); + if (SmartDrawHandler.Instance.coords) { + // const coords: InkData = SmartDrawHandler.Instance.coords; + // const inkField = new InkField(coords); + // const points = coords.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[]); + // const bounds = InkField.getBounds(points); + // const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height); + // const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale; + // return Docs.Create.InkDocument( + // points, + // { title: 'stroke', + // x: B.x - inkWidth / 2, + // y: B.y - inkWidth / 2, + // _width: B.width + inkWidth, + // _height: B.height + inkWidth, + // stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore + // inkWidth + // ); + } + }; + + @action zoom = (pointX: number, pointY: number, deltaY: number): void => { if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return; let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05; diff --git a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx new file mode 100644 index 000000000..fc88b5cc6 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx @@ -0,0 +1,154 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, makeObservable, observable } from 'mobx'; +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 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 { InkingStroke } from '../../InkingStroke'; +import { InkData } from '../../../../fields/InkField'; + +@observer +export class SmartDrawHandler extends ObservableReactComponent<{}> { + static Instance: SmartDrawHandler; + + @observable private _display: boolean = false; + @observable private _pageX: number = 0; + @observable private _pageY: number = 0; + @observable private _yRelativeToTop: boolean = true; + @observable private _isLoading: boolean = false; + @observable private _userInput: string = ''; + @observable public coords: InkData | undefined = undefined; + + constructor(props: any) { + super(props); + makeObservable(this); + SmartDrawHandler.Instance = this; + } + + @action + setIsLoading = (isLoading: boolean) => { + this._isLoading = isLoading; + }; + + @action + setUserInput = (input: string) => { + this._userInput = input; + }; + + @action + displaySmartDrawHandler = (x: number, y: number) => { + this._pageX = x; + this._pageY = y; + this._display = true; + }; + + @action + hideLabelhandler = () => { + this._display = false; + }; + + @action + drawWithGPT = async (startPoint: {X: number, Y: number}, input: string) => { + this.setIsLoading(true); + try { + const res = await gptAPICall(input, GPTCallType.DRAW); + if (!res) { + console.error('GPT call failed'); + return; + } + console.log("GPT response:", res); + // controlPts: {X: number, Y: number}[] = [] + // code to extract list of coords from the string + // this.coords = controlPts.map(pt: {X: number, Y: number } => {pt.X + startPoint.X, pt.Y + startPoint.Y}); + + + } catch (err) { + console.error('GPT call failed'); + } + + this.setIsLoading(false); + this.setUserInput(''); + }; + + render() { + if (this._display) { + return ( + <div + id="label-handler" + className="contextMenu-cont" + style={{ + display: this._display ? '' : 'none', + left: this._pageX, + ...(this._yRelativeToTop ? { top: Math.max(0, this._pageY) } : { bottom: this._pageY }), + background: SettingsManager.userBackgroundColor, + color: SettingsManager.userColor, + }}> + <div> + <IconButton tooltip={'Cancel'} onPointerDown={this.hideLabelhandler} icon={<FontAwesomeIcon icon="xmark" />} color={MarqueeOptionsMenu.Instance.userColor} style={{ width: '19px' }} /> + <input + aria-label="label-input" + id="new-label" + type="text" + style={{ color: 'black' }} + value={this._userInput} + onChange={e => { + this.setUserInput(e.target.value); + }} + placeholder="Enter item to draw" + /> + <Button + style={{ alignSelf: 'flex-end' }} + text="Send" + icon={this._isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />} + iconPlacement="right" + color={MarqueeOptionsMenu.Instance.userColor} + onClick={e => { + this.drawWithGPT({X: e.clientX, Y: e.clientY}, this._userInput); + }} + /> + {/* <IconButton + tooltip={'Generate Drawing'} + onPointerDown={() => { + const input = document.getElementById('new-label') as HTMLInputElement; + const newLabel = input.value; + // this.addLabel(newLabel); + // this._currentLabel = ''; + input.value = ''; + }} + icon={<FontAwesomeIcon icon="plus" />} + color={MarqueeOptionsMenu.Instance.userColor} + style={{ width: '19px' }} + /> + <IconButton tooltip={'Group Images'} icon={<FontAwesomeIcon icon="object-group" />} color={MarqueeOptionsMenu.Instance.userColor} style={{ width: '19px' }} /> */} + </div> + <div> + {/* {this._labelGroups.map(group => { + return ( + <div> + <p>{group}</p> + <IconButton + tooltip={'Remove Label'} + onPointerDown={() => { + this.removeLabel(group); + }} + icon={'x'} + color={MarqueeOptionsMenu.Instance.userColor} + style={{ width: '19px' }} + /> + </div> + ); + })} */} + </div> + </div> + ); + } else { + return <></>; + } + } +} diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 32abf0076..123d32301 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -17,6 +17,7 @@ export enum InkTool { Stamp = 'stamp', Write = 'write', PresentationPin = 'presentationpin', + SmartDraw = 'smartdraw', } export type Segment = Array<Bezier>; |