aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/apis/gpt/GPT.ts6
-rw-r--r--src/client/util/CurrentUserUtils.ts2
-rw-r--r--src/client/util/bezierFit.ts26
-rw-r--r--src/client/views/MainView.tsx4
-rw-r--r--src/client/views/MarqueeAnnotator.tsx5
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx26
-rw-r--r--src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx95
7 files changed, 86 insertions, 78 deletions
diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts
index e02488607..0c993680e 100644
--- a/src/client/apis/gpt/GPT.ts
+++ b/src/client/apis/gpt/GPT.ts
@@ -56,10 +56,10 @@ const callTypeMap: { [type: string]: GPTCallOpts } = {
},
draw: {
model: 'gpt-4o',
- maxTokens: 256,
+ maxTokens: 1024,
temp: 0.5,
- prompt: 'Given an item to draw, generate Bezier control points that will represent the item. Answer only with a list of lists of coordinates, where each list of coordinates is one Bezier ink stroke. Remember that Bezier curves will smooth out along control points, so try to keep as much in one stroke as possible. However, if there is an edge or corner be sure to split into a new stroke. Make sure you generate control handle points as well as the actual anchor points. Do not include any text, description, or comments. ONLY USE INTEGERS, NOT DECIMALS.',
- }
+ prompt: 'I would like you to generate me vector art with Bezier curves. Given a prompt, generate a sequence of cubic Bezier coordinates in the range of 0 to 200 (unless specified larger/smaller) that creates a line drawing of the object. Respond only with the coordinates',
+ },
};
let lastCall = '';
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 1eb2d9cc1..b6a7cacba 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -730,7 +730,7 @@ pie title Minerals in my tap water
static inkTools():Button[] {
return [
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
- { title: "Highlight", toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter",toolType: "highlighter", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
+ { title: "Highlight", toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter",toolType: "highlighter", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
{ title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.MultiToggleButton, scripts: {onClick: '{ return setActiveTool(this.toolType, false, _readOnly_);}'}, funcs: {toolType:"activeEraserTool()"},
subMenu: [
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index bb3b6b1eb..fbc2bb7cd 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -561,7 +561,7 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
* Convert polyline coordinates to a (multi) segment bezier curve
* @param d - polyline coordinates
* @param error - how much error to allow in fitting (measured in pixels)
- * @returns
+ * @returns
*/
export function FitCurve(d: Point[], error: number) {
const tHat1 = ComputeLeftTangent(d, 0); // Unit tangent vectors at endpoints
@@ -592,6 +592,30 @@ export function FitOneCurve(d: Point[], tHat1?: Point, tHat2?: Point) {
return { finalCtrls, error };
}
+// alpha determines how far away the tangents are, or the "tightness" of the bezier
+export function GenerateControlPoints(coordinates: Point[], alpha = 0.1) {
+ const firstEnd = coordinates.length ? [coordinates[0], coordinates[0]] : [];
+ const lastEnd = coordinates.length ? [coordinates.lastElement(), coordinates.lastElement()] : [];
+ const points: Point[] = coordinates.slice(1, coordinates.length - 1).flatMap((pt, index, inkData) => {
+ const prevPt: Point = index === 0 ? firstEnd[0] : inkData[index - 1];
+ const nextPt: Point = index === inkData.length - 1 ? lastEnd[0] : inkData[index + 1];
+ if (prevPt.X === nextPt.X) {
+ const verticalDist = nextPt.Y - prevPt.Y;
+ return [{ X: pt.X, Y: pt.Y - alpha * verticalDist }, pt, pt, { X: pt.X, Y: pt.Y + alpha * verticalDist }];
+ } else if (prevPt.Y === nextPt.Y) {
+ const horizDist = nextPt.X - prevPt.X;
+ return [{ X: pt.X - alpha * horizDist, Y: pt.Y }, pt, pt, { X: pt.X + alpha * horizDist, Y: pt.Y }];
+ }
+ // tangent vectors between the adjacent points
+ const tanX = nextPt.X - prevPt.X;
+ const tanY = nextPt.Y - prevPt.Y;
+ const ctrlPt1: Point = { X: pt.X - alpha * tanX, Y: pt.Y - alpha * tanY };
+ const ctrlPt2: Point = { X: pt.X + alpha * tanX, Y: pt.Y + alpha * tanY };
+ return [ctrlPt1, pt, pt, ctrlPt2];
+ });
+ return [...firstEnd, ...points, ...lastEnd];
+}
+
/*
static double GetTValueFromSValue (const BezierRep &parent, double t, double endT, bool left, double influenceDistance, double &excess) {
double dist = 0;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index a1cb44106..44e00396e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -55,7 +55,6 @@ 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';
@@ -318,6 +317,7 @@ export class MainView extends ObservableReactComponent<{}> {
fa.faCompass,
fa.faSnowflake,
fa.faStar,
+ fa.faSplotch,
fa.faMicrophone,
fa.faCircleHalfStroke,
fa.faKeyboard,
@@ -402,7 +402,6 @@ export class MainView extends ObservableReactComponent<{}> {
fa.faPortrait,
fa.faRedoAlt,
fa.faStamp,
- fa.faTape,
fa.faStickyNote,
fa.faArrowsAltV,
fa.faTimesCircle,
@@ -1092,7 +1091,6 @@ export class MainView extends ObservableReactComponent<{}> {
<TaskCompletionBox />
<ContextMenu />
<ImageLabelHandler />
- <SmartDrawHandler />
<AnchorMenu />
<MapAnchorMenu />
<DirectionsAnchorMenu />
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index db48e095d..f06f3efe0 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -74,7 +74,8 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
onClick: isLinkButton ? FollowLinkScript() : undefined,
backgroundColor: color,
annotationOn: this.props.Document,
- title: 'Annotation on ' + this.props.Document.title,a
+ title: 'Annotation on ' + this.props.Document.title,
+ a,
});
marqueeAnno.x = NumCast(doc.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale;
marqueeAnno.y = NumCast(doc.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale;
@@ -215,7 +216,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
marqueeAnno.y = NumCast(doc.freeform_panY_min) / scale;
marqueeAnno._height = parseInt('100') / scale;
marqueeAnno._width = parseInt('100') / scale;
- return marqueeAnno;
+ return marqueeAnno;
// }
// const textRegionAnno = Docs.Create.ConfigDocument({
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index e66dbd796..a27ac2a0c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,12 +1,13 @@
/* eslint-disable react/jsx-props-no-spreading */
/* eslint-disable jsx-a11y/click-events-have-key-events */
/* eslint-disable jsx-a11y/no-static-element-interactions */
-import { Bezier } from 'bezier-js';
+import { Bezier, Point } from 'bezier-js';
import { Colors } from 'browndash-components';
import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import * as React from 'react';
+import { TbAlpha } from 'react-icons/tb';
import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils';
import { DateField } from '../../../../fields/DateField';
import { ActiveEraserWidth, ActiveInkWidth, Doc, DocListCast, Field, FieldType, Opt, SetActiveInkColor, SetActiveInkWidth } from '../../../../fields/Doc';
@@ -26,6 +27,7 @@ import { aggregateBounds, clamp, emptyFunction, intersectRect, Utils } from '../
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocUtils } from '../../../documents/DocUtils';
+import { FitCurve, GenerateControlPoints } from '../../../util/bezierFit';
import { DragManager } from '../../../util/DragManager';
import { dropActionType } from '../../../util/DropActionTypes';
import { CompileScript } from '../../../util/Scripting';
@@ -55,7 +57,6 @@ import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCurso
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
import { SmartDrawHandler } from './SmartDrawHandler';
-import { ImageLabelHandler } from './ImageLabelHandler';
@observer
class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> {
@@ -144,10 +145,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
: this._props.childPointerEvents?.() ??
(this._props.viewDefDivClick || //
(this.layoutEngine === computePassLayout.name && !this._props.isSelected()) ||
- this.isContentActive() === false ||
- Doc.ActiveTool === InkTool.RadiusEraser ||
- Doc.ActiveTool === InkTool.SegmentEraser ||
- Doc.ActiveTool === InkTool.StrokeEraser
+ this.isContentActive() === false
? 'none'
: this._props.pointerEvents?.());
}
@@ -685,6 +683,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onEraserClick = (e: PointerEvent, doubleTap?: boolean) => {
this.erase(e, [0, 0]);
+ e.stopPropagation();
return false;
};
@@ -1265,19 +1264,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
createDrawing = (e: PointerEvent, doubleTap?: boolean) => {
- SmartDrawHandler.Instance.displaySmartDrawHandler(e.pageX, e.pageY, this.createInkStroke);
+ SmartDrawHandler.Instance.displaySmartDrawHandler(e.pageX, e.pageY, this.createInkStrokes);
};
@action
- createInkStroke = (strokeList: InkData[]) => {
- strokeList.forEach(coords => {
- // const stroke = 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(coords);
+ createInkStrokes = (strokeList: InkData[], alpha?: number) => {
+ console.log(strokeList.length);
+ strokeList.forEach(inkData => {
+ // const points: InkData = FitCurve(inkData, 20) as InkData;
+ const allPts = GenerateControlPoints(inkData, alpha);
+ const bounds = InkField.getBounds(allPts);
const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height);
const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale;
const inkDoc = Docs.Create.InkDocument(
- coords,
+ allPts,
{ title: 'stroke',
x: B.x - inkWidth / 2,
y: B.y - inkWidth / 2,
diff --git a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
index fc8f7a429..4c2e78e31 100644
--- a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
+++ b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
@@ -11,6 +11,7 @@ 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';
@observer
export class SmartDrawHandler extends ObservableReactComponent<{}> {
@@ -22,8 +23,11 @@ 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[]) => void = () => {};
+ private _addToDocFunc: (strokeList: InkData[], alpha?: number) => void = () => {};
constructor(props: any) {
super(props);
@@ -42,7 +46,7 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
};
@action
- displaySmartDrawHandler = (x: number, y: number, addToDoc: (strokeList: InkData[]) => void) => {
+ displaySmartDrawHandler = (x: number, y: number, addToDoc: (strokeList: InkData[], alpha?: number) => void) => {
this._pageX = x;
this._pageY = y;
this._display = true;
@@ -50,14 +54,11 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
};
@action
- hideLabelhandler = () => {
+ hideSmartDrawHandler = () => {
this._display = false;
};
@action
- waitForCoords = async () => {};
-
- @action
drawWithGPT = async (startPoint: { X: number; Y: number }, input: string) => {
console.log('start point is', startPoint);
this.setIsLoading(true);
@@ -68,25 +69,18 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
return;
}
console.log('GPT response:', res);
- // const controlPts: [number, number][][] = JSON.parse(res) as [number, number][][];
- // console.log("Control Points", controlPts);
- // const transformedPts: { X: number; Y: number }[][] = [];
- // controlPts.forEach(stroke => {
- // stroke.map(pt => {
- // pt.X += startPoint.X, pt.Y += startPoint.Y;
- // });
- // transformedPts.push(stroke);
- // });
const simplifiedRes: string = res.replace(/[^\d\[\],]/g, '');
- console.log(simplifiedRes)
try {
- const controlPts: { X: number; Y: number }[][] = JSON.parse(simplifiedRes).map((stroke: [number, number][]) => stroke.map(([X, Y]) => ({ X: X + startPoint.X, Y: Y + startPoint.Y })));
- console.log('transformed points', controlPts);
-
- // this.strokes = controlPts;
- this._addToDocFunc(controlPts);
+ const parsedPts = JSON.parse(simplifiedRes);
+ if (parsedPts[0][0][0]) {
+ const controlPts = (parsedPts as [number, number][][]).map((stroke: [number, number][]) => stroke.map(([X, Y]) => ({ X: X + startPoint.X - 100, Y: Y + startPoint.Y - 100 })));
+ this._addToDocFunc(controlPts, this._alpha);
+ } else {
+ const controlPts = (parsedPts as [number, number][]).map(([X, Y]) => ({ X: X + startPoint.X - 100, Y: Y + startPoint.Y - 100 }));
+ this._addToDocFunc([controlPts], this._alpha);
+ }
} catch (err) {
- console.error('Incompatible GPT output type');
+ console.error('Error likely from bad GPT output type');
}
} catch (err) {
console.error('GPT call failed', err);
@@ -94,6 +88,19 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
this.setIsLoading(false);
this.setUserInput('');
+ this.hideSmartDrawHandler();
+ };
+
+ 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;
+ }
};
render() {
@@ -110,7 +117,7 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
color: SettingsManager.userColor,
}}>
<div>
- <IconButton tooltip={'Cancel'} onPointerDown={this.hideLabelhandler} icon={<FontAwesomeIcon icon="xmark" />} color={MarqueeOptionsMenu.Instance.userColor} style={{ width: '19px' }} />
+ <IconButton tooltip={'Cancel'} onClick={this.hideSmartDrawHandler} icon={<FontAwesomeIcon icon="xmark" />} color={MarqueeOptionsMenu.Instance.userColor} style={{ width: '19px' }} />
<input
aria-label="label-input"
id="new-label"
@@ -122,6 +129,16 @@ 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}
+ style={{ width: '14px' }}
+ onClick={() => {
+ this._alpha = 0;
+ }}
+ /> */}
<Button
style={{ alignSelf: 'flex-end' }}
text="Send"
@@ -132,38 +149,6 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
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>
);