aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx107
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx295
3 files changed, 72 insertions, 332 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b8257ff31..467191735 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -7,7 +7,6 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
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';
@@ -56,7 +55,7 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable
import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
-import { SmartDrawHandler } from './SmartDrawHandler';
+import { DrawingOptions, SmartDrawHandler } from '../../smartdraw/SmartDrawHandler';
@observer
class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> {
@@ -120,6 +119,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0
@observable _firstRender = false; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown. could be used for performance improvement
@observable _showAnimTimeline = false;
+ @observable _showDrawingEditor = false;
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeViewRef = React.createRef<MarqueeView>();
@@ -514,7 +514,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
break;
case InkTool.SmartDraw:
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, this.createDrawing, hit !== -1);
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, this.showSmartDraw, hit !== -1);
e.stopPropagation();
case InkTool.None:
if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
@@ -566,6 +566,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
};
+
@action
onEraserUp = (): void => {
this._deleteList.lastElement()?._props.removeDocument?.(this._deleteList.map(ink => ink.Document));
@@ -607,12 +608,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
_eraserPts: number[][] = []; // keep track of the last few eraserPts to make the eraser circle 'stretch'
erase = (e: PointerEvent, delta: number[]) => {
+ e.stopImmediatePropagation();
const currPoint = { X: e.clientX, Y: e.clientY };
this._eraserPts.push([currPoint.X, currPoint.Y]);
this._eraserPts = this._eraserPts.slice(Math.max(0, this._eraserPts.length - 5));
if (Doc.ActiveTool === InkTool.RadiusEraser) {
const strokeMap: Map<DocumentView, number[]> = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint);
-
strokeMap.forEach((intersects, stroke) => {
if (!this._deleteList.includes(stroke)) {
this._deleteList.push(stroke);
@@ -682,9 +683,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onEraserClick = (e: PointerEvent, doubleTap?: boolean) => {
+ e.preventDefault();
+ e.stopImmediatePropagation();
this.erase(e, [0, 0]);
- e.stopPropagation();
- return false;
};
/**
@@ -696,32 +697,32 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
* @param delta
* @returns
*/
- @action
- onRadiusEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
- const currPoint = { X: e.clientX, Y: e.clientY };
- this._eraserPts.push([currPoint.X, currPoint.Y]);
- this._eraserPts = this._eraserPts.slice(Math.max(0, this._eraserPts.length - 5));
- const strokeMap: Map<DocumentView, number[]> = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint);
-
- strokeMap.forEach((intersects, stroke) => {
- if (!this._deleteList.includes(stroke)) {
- this._deleteList.push(stroke);
- SetActiveInkWidth(StrCast(stroke.Document.stroke_width?.toString()) || '1');
- SetActiveInkColor(StrCast(stroke.Document.color?.toString()) || 'black');
- const segments = this.radiusErase(stroke, intersects.sort());
- segments?.forEach(segment =>
- this.forceStrokeGesture(
- e,
- Gestures.Stroke,
- segment.reduce((data, curve) => [...data, ...curve.points.map(p => stroke.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
- )
- );
- }
- stroke.layoutDoc.opacity = 0;
- stroke.layoutDoc.dontIntersect = true;
- });
- return false;
- };
+ // @action
+ // onRadiusEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ // const currPoint = { X: e.clientX, Y: e.clientY };
+ // this._eraserPts.push([currPoint.X, currPoint.Y]);
+ // this._eraserPts = this._eraserPts.slice(Math.max(0, this._eraserPts.length - 5));
+ // const strokeMap: Map<DocumentView, number[]> = this.getRadiusEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint);
+
+ // strokeMap.forEach((intersects, stroke) => {
+ // if (!this._deleteList.includes(stroke)) {
+ // this._deleteList.push(stroke);
+ // SetActiveInkWidth(StrCast(stroke.Document.stroke_width?.toString()) || '1');
+ // SetActiveInkColor(StrCast(stroke.Document.color?.toString()) || 'black');
+ // const segments = this.radiusErase(stroke, intersects.sort());
+ // segments?.forEach(segment =>
+ // this.forceStrokeGesture(
+ // e,
+ // Gestures.Stroke,
+ // segment.reduce((data, curve) => [...data, ...curve.points.map(p => stroke.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
+ // )
+ // );
+ // }
+ // stroke.layoutDoc.opacity = 0;
+ // stroke.layoutDoc.dontIntersect = true;
+ // });
+ // return false;
+ // };
forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => {
this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, InkField.getBounds(points), text));
@@ -1263,15 +1264,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
@action
- createDrawing = (e: PointerEvent, doubleTap?: boolean) => {
- SmartDrawHandler.Instance.displaySmartDrawHandler(e.pageX, e.pageY, this.createInkStrokes);
+ showSmartDraw = (e: PointerEvent, doubleTap?: boolean) => {
+ SmartDrawHandler.Instance.displaySmartDrawHandler(e.pageX, e.pageY, this.createDrawing, this.removeDrawing);
};
+ _drawing: Doc[] = [];
@undoBatch
- createInkStrokes = (strokeData: [InkData, string, string][]) => {
+ createDrawing = (e: React.PointerEvent<Element>, strokeData: [InkData, string, string][], opts: DrawingOptions, gptRes: string) => {
strokeData.forEach((stroke: [InkData, string, string]) => {
- // const points: InkData = FitCurve(inkData, 20) as InkData;
- // const allPts = GenerateControlPoints(inkData, alpha);
const bounds = InkField.getBounds(stroke[0]);
const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height);
const inkWidth = ActiveInkWidth() * this.ScreenToLocalBoxXf().Scale;
@@ -1288,8 +1288,33 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
undefined,
stroke[2] === 'none' ? undefined : stroke[2]
);
+ this._drawing.push(inkDoc);
this.addDocument(inkDoc);
});
+ // const collection = this._marqueeViewRef.current?.collection(undefined, false, this._drawing);
+ // if (collection) {
+ // const docData = collection[DocData];
+ // docData.title = opts.text;
+ // docData.drawingInput = opts.text;
+ // docData.drawingComplexity = opts.complexity;
+ // docData.drawingColored = opts.autoColor;
+ // docData.drawingSize = opts.size;
+ // docData.drawingData = gptRes;
+ // }
+ this._batch?.end();
+ };
+
+ removeDrawing = (doc?: Doc) => {
+ this._batch = UndoManager.StartBatch('regenerateDrawing');
+ if (doc) {
+ const docData: Doc = doc[DocData];
+ const children = docData.data as unknown as Doc[];
+ this._props.removeDocument?.(doc);
+ this._props.removeDocument?.(children);
+ } else {
+ this._props.removeDocument?.(this._drawing);
+ }
+ this._drawing = [];
};
@action
@@ -1995,6 +2020,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}),
icon: 'eye',
});
+ optionItems.push({
+ description: (this._showDrawingEditor ? 'Close' : 'Show') + ' Drawing Editor',
+ event: action(() => {
+ this._showDrawingEditor = !this._showDrawingEditor;
+ this._showDrawingEditor ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10, this.createDrawing, this.removeDrawing) : SmartDrawHandler.Instance.hideRegenerate();
+ }),
+ icon: 'pen-to-square',
+ });
this._props.renderDepth &&
optionItems.push({
description: 'Use Background Color as Default',
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index dc15c83c5..23cf487ec 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -36,6 +36,7 @@ import { CollectionFreeFormView } from './CollectionFreeFormView';
import { ImageLabelHandler } from './ImageLabelHandler';
import { MarqueeOptionsMenu } from './MarqueeOptionsMenu';
import './MarqueeView.scss';
+import { collectionOf } from '@turf/turf';
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -426,6 +427,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps
this._props.selectDocuments([newCollection]);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
+ return newCollection;
});
/**
diff --git a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
deleted file mode 100644
index edb814172..000000000
--- a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
+++ /dev/null
@@ -1,295 +0,0 @@
-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, Size } from 'browndash-components';
-import ReactLoading from 'react-loading';
-import { AiOutlineSend } from 'react-icons/ai';
-import './ImageLabelHandler.scss';
-import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT';
-import { InkData } from '../../../../fields/InkField';
-import { SVGToBezier } from '../../../util/bezierFit';
-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<{}> {
- 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 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);
- makeObservable(this);
- SmartDrawHandler.Instance = this;
- }
-
- @action
- setUserInput = (input: string) => {
- this._userInput = input;
- };
-
- @action
- displaySmartDrawHandler = (x: number, y: number, addToDoc: (strokeData: [InkData, string, string][]) => void) => {
- this._pageX = x;
- this._pageY = y;
- this._display = true;
- this._addToDocFunc = addToDoc;
- };
-
- 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 (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}", "${this._complexity}", "${this._size}"`, GPTCallType.DRAW);
- if (!res) {
- console.error('GPT call failed');
- return;
- }
- const svg = res.match(/<svg[^>]*>([\s\S]*?)<\/svg>/g);
- if (svg) {
- const svgObject = await parse(svg[0]);
- const svgStrokes: any = svgObject.children;
- 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 - this._size / 1.5, Y: point.Y + startPoint.Y - this._size / 2 };
- }),
- this._autoColor ? child.attributes.stroke : undefined,
- this._autoColor ? child.attributes.fill : undefined,
- ]);
- });
- if (regenerate) UndoManager.Undo();
- this._addToDocFunc(strokeData);
- }
- } catch (err) {
- console.error('GPT call failed', err);
- }
- this.hideSmartDrawHandler();
- this._showRegenerate = true;
- };
-
- regenerate = (e: React.MouseEvent<Element, MouseEvent>) => {
- this.drawWithGPT(e, { X: this._lastX, Y: this._lastY }, `Regenerate the item "${this._userInput}"`, true);
- };
-
- 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'}
- onClick={() => {
- this.hideSmartDrawHandler();
- this.hideRegenerate();
- }}
- icon={<FontAwesomeIcon icon="xmark" />}
- color={SettingsManager.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"
- />
- <IconButton
- tooltip="Advanced Options"
- icon={<FontAwesomeIcon icon={this._showOptions ? 'caret-down' : 'caret-right'} />}
- color={SettingsManager.userColor}
- style={{ width: '14px' }}
- onClick={() => {
- this._showOptions = !this._showOptions;
- }}
- />
- <Button
- style={{ alignSelf: 'flex-end' }}
- text="Send"
- icon={this._isLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />}
- iconPlacement="right"
- 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.regenerate(e);
- }}
- />
- </div>
- </div>
- );
- } else {
- return <></>;
- }
- }
-}