aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/MainView.tsx1
-rw-r--r--src/client/views/MarqueeAnnotator.tsx147
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx170
-rw-r--r--src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx31
-rw-r--r--src/client/views/pdf/AnchorMenu.tsx14
-rw-r--r--src/client/views/pdf/PDFViewer.tsx3
6 files changed, 285 insertions, 81 deletions
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index f7e1617fc..a1cb44106 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -402,6 +402,7 @@ export class MainView extends ObservableReactComponent<{}> {
fa.faPortrait,
fa.faRedoAlt,
fa.faStamp,
+ fa.faTape,
fa.faStickyNote,
fa.faArrowsAltV,
fa.faTimesCircle,
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index c18ac6738..db48e095d 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -28,6 +28,7 @@ export interface MarqueeAnnotatorProps {
marqueeContainer: HTMLDivElement;
docView: () => DocumentView;
savedAnnotations: () => ObservableMap<number, HTMLDivElement[]>;
+ savedTapes: () => ObservableMap<number, HTMLDivElement[]>;
selectionText: () => string;
annotationLayer: HTMLDivElement;
addDocument: (doc: Doc) => boolean;
@@ -73,7 +74,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
onClick: isLinkButton ? FollowLinkScript() : undefined,
backgroundColor: color,
annotationOn: this.props.Document,
- title: 'Annotation on ' + this.props.Document.title,
+ 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;
@@ -127,6 +128,140 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
savedAnnoMap.clear();
return textRegionAnno;
};
+
+ // @undoBatch
+ // makeTapeDocument = (color: string, isLinkButton?: boolean, savedTapes?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
+ // const savedTapeMap = savedTapes?.values() && Array.from(savedTapes?.values()).length ? savedTapes : this.props.savedTapes();
+ // if (savedTapeMap.size === 0) return undefined;
+ // const tapes = Array.from(savedTapeMap.values())[0];
+ // const doc = this.props.Document;
+ // const scale = (this.props.annotationLayerScaling?.() || 1) * NumCast(doc._freeform_scale, 1);
+ // if (tapes.length && (tapes[0] as any).marqueeing) {
+ // const anno = tapes[0];
+ // const containerOffset = this.props.containerOffset?.() || [0, 0];
+ // const tape = Docs.Create.FreeformDocument([], {
+ // onClick: isLinkButton ? FollowLinkScript() : undefined,
+ // backgroundColor: color,
+ // annotationOn: this.props.Document,
+ // title: 'Tape on ' + this.props.Document.title,
+ // });
+ // tape.x = NumCast(doc.freeform_panX_min) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale;
+ // tape.y = NumCast(doc.freeform_panY_min) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale;
+ // tape._height = parseInt(anno.style.height || '0') / scale;
+ // tape._width = parseInt(anno.style.width || '0') / scale;
+ // anno.remove();
+ // savedTapeMap.clear();
+ // return tape;
+ // }
+
+ // const textRegionAnno = Docs.Create.ConfigDocument({
+ // annotationOn: this.props.Document,
+ // text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too.
+ // text_html: this.props.selectionText() as any,
+ // backgroundColor: 'transparent',
+ // presentation_duration: 2100,
+ // presentation_transition: 500,
+ // presentation_zoomText: true,
+ // title: '>' + this.props.Document.title,
+ // });
+ // const textRegionAnnoProto = textRegionAnno[DocData];
+ // let minX = Number.MAX_VALUE;
+ // let maxX = -Number.MAX_VALUE;
+ // let minY = Number.MAX_VALUE;
+ // let maxY = -Number.MIN_VALUE;
+ // const annoRects: string[] = [];
+ // savedAnnoMap.forEach((value: HTMLDivElement[]) =>
+ // value.forEach(anno => {
+ // const x = parseInt(anno.style.left ?? '0');
+ // const y = parseInt(anno.style.top ?? '0');
+ // const height = parseInt(anno.style.height ?? '0');
+ // const width = parseInt(anno.style.width ?? '0');
+ // annoRects.push(`${x}:${y}:${width}:${height}`);
+ // anno.remove();
+ // minY = Math.min(NumCast(y), minY);
+ // minX = Math.min(NumCast(x), minX);
+ // maxY = Math.max(NumCast(y) + NumCast(height), maxY);
+ // maxX = Math.max(NumCast(x) + NumCast(width), maxX);
+ // })
+ // );
+
+ // textRegionAnnoProto.y = Math.max(minY, 0);
+ // textRegionAnnoProto.x = Math.max(minX, 0);
+ // textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
+ // textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0);
+ // textRegionAnnoProto.backgroundColor = color;
+ // // mainAnnoDocProto.text = this._selectionText;
+ // textRegionAnnoProto.text_inlineAnnotations = new List<string>(annoRects);
+ // textRegionAnnoProto.opacity = 0;
+ // textRegionAnnoProto.layout_unrendered = true;
+ // savedAnnoMap.clear();
+ // return textRegionAnno;
+ // };
+
+ @undoBatch
+ makeTapeDocument = (color: string, isLinkButton?: boolean, savedTapes?: ObservableMap<number, HTMLDivElement[]>): Opt<Doc> => {
+ // const savedAnnoMap = savedTapes?.values() && Array.from(savedTapes?.values()).length ? savedTapes : this.props.savedTapes();
+ // if (savedAnnoMap.size === 0) return undefined;
+ // const savedAnnos = Array.from(savedAnnoMap.values())[0];
+ const doc = this.props.Document;
+ const scale = (this.props.annotationLayerScaling?.() || 1) * NumCast(doc._freeform_scale, 1);
+ const marqueeAnno = Docs.Create.FreeformDocument([], {
+ onClick: isLinkButton ? FollowLinkScript() : undefined,
+ backgroundColor: color,
+ annotationOn: this.props.Document,
+ title: 'Annotation on ' + this.props.Document.title,
+ });
+ marqueeAnno.x = NumCast(doc.freeform_panX_min) / scale;
+ marqueeAnno.y = NumCast(doc.freeform_panY_min) / scale;
+ marqueeAnno._height = parseInt('100') / scale;
+ marqueeAnno._width = parseInt('100') / scale;
+ return marqueeAnno;
+ // }
+
+ // const textRegionAnno = Docs.Create.ConfigDocument({
+ // annotationOn: this.props.Document,
+ // text: this.props.selectionText() as any, // text want an RTFfield, but strings are acceptable, too.
+ // text_html: this.props.selectionText() as any,
+ // backgroundColor: 'transparent',
+ // presentation_duration: 2100,
+ // presentation_transition: 500,
+ // presentation_zoomText: true,
+ // title: '>' + this.props.Document.title,
+ // });
+ // const textRegionAnnoProto = textRegionAnno[DocData];
+ // let minX = Number.MAX_VALUE;
+ // let maxX = -Number.MAX_VALUE;
+ // let minY = Number.MAX_VALUE;
+ // let maxY = -Number.MIN_VALUE;
+ // const annoRects: string[] = [];
+ // savedAnnoMap.forEach((value: HTMLDivElement[]) =>
+ // value.forEach(anno => {
+ // const x = parseInt(anno.style.left ?? '0');
+ // const y = parseInt(anno.style.top ?? '0');
+ // const height = parseInt(anno.style.height ?? '0');
+ // const width = parseInt(anno.style.width ?? '0');
+ // annoRects.push(`${x}:${y}:${width}:${height}`);
+ // anno.remove();
+ // minY = Math.min(NumCast(y), minY);
+ // minX = Math.min(NumCast(x), minX);
+ // maxY = Math.max(NumCast(y) + NumCast(height), maxY);
+ // maxX = Math.max(NumCast(x) + NumCast(width), maxX);
+ // })
+ // );
+
+ // textRegionAnnoProto.y = Math.max(minY, 0);
+ // textRegionAnnoProto.x = Math.max(minX, 0);
+ // textRegionAnnoProto.height = Math.max(maxY, 0) - Math.max(minY, 0);
+ // textRegionAnnoProto.width = Math.max(maxX, 0) - Math.max(minX, 0);
+ // textRegionAnnoProto.backgroundColor = color;
+ // // mainAnnoDocProto.text = this._selectionText;
+ // textRegionAnnoProto.text_inlineAnnotations = new List<string>(annoRects);
+ // textRegionAnnoProto.opacity = 0;
+ // textRegionAnnoProto.layout_unrendered = true;
+ // savedAnnoMap.clear();
+ // return textRegionAnno;
+ };
+
@action
highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => {
// creates annotation documents for current highlights
@@ -136,6 +271,15 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
return annotationDoc as Doc;
};
+ @action
+ tape = (color: string, isLinkButton: boolean, savedTapes?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => {
+ // creates annotation documents for current highlights
+ const effectiveAcl = GetEffectiveAcl(this.props.Document[DocData]);
+ const tape = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeTapeDocument(color, isLinkButton, savedTapes);
+ addAsAnnotation && tape && this.props.addDocument(tape);
+ return tape as Doc;
+ };
+
public static previewNewAnnotation = action((savedAnnotations: ObservableMap<number, HTMLDivElement[]>, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => {
div.style.backgroundColor = '#ACCEF7';
div.style.opacity = '0.5';
@@ -182,6 +326,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
AnchorMenu.Instance.OnClick = undoable(() => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
AnchorMenu.Instance.OnAudio = unimplementedFunction;
AnchorMenu.Instance.Highlight = (color: string) => this.highlight(color, false, undefined, true);
+ AnchorMenu.Instance.Tape = (color: string) => this.tape(color, false, undefined, true);
AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]> /* , addAsAnnotation?: boolean */) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d22b3569e..e66dbd796 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -144,7 +144,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
: this._props.childPointerEvents?.() ??
(this._props.viewDefDivClick || //
(this.layoutEngine === computePassLayout.name && !this._props.isSelected()) ||
- this.isContentActive() === false
+ this.isContentActive() === false ||
+ Doc.ActiveTool === InkTool.RadiusEraser ||
+ Doc.ActiveTool === InkTool.SegmentEraser ||
+ Doc.ActiveTool === InkTool.StrokeEraser
? 'none'
: this._props.pointerEvents?.());
}
@@ -501,27 +504,24 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
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.StrokeEraser:
case InkTool.SegmentEraser:
- this._batch = UndoManager.StartBatch('collectionErase');
- this._eraserPts.length = 0;
- setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction, hit !== -1, false);
- break;
case InkTool.RadiusEraser:
this._batch = UndoManager.StartBatch('collectionErase');
this._eraserPts.length = 0;
- setupMoveUpEvents(this, e, this.onRadiusEraserMove, this.onEraserUp, emptyFunction, hit !== -1, false);
+ setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, this.onEraserClick, hit !== -1);
+ e.stopPropagation();
break;
case InkTool.SmartDraw:
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, this.createDrawing, hit !== -1, false);
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, this.createDrawing, hit !== -1);
+ e.stopPropagation();
case InkTool.None:
if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1, false);
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1);
+ e.stopPropagation();
}
break;
default:
@@ -608,50 +608,83 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
_eraserLock = 0;
_eraserPts: number[][] = []; // keep track of the last few eraserPts to make the eraser circle 'stretch'
- /**
- * Erases strokes by intersecting them with an invisible "eraser stroke".
- * By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
- * and deletes the original stroke.
- */
- @action
- onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ erase = (e: PointerEvent, 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));
- // if (this._eraserLock) return false; // leaving this commented out in case the idea is revisited in the future
- this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
- if (!this._deleteList.includes(intersect.inkView)) {
- this._deleteList.push(intersect.inkView);
- SetActiveInkWidth(StrCast(intersect.inkView.Document.stroke_width?.toString()) || '1');
- SetActiveInkColor(StrCast(intersect.inkView.Document.color?.toString()) || 'black');
- // create a new curve by appending all curves of the current segment together in order to render a single new stroke.
- if (Doc.ActiveTool !== InkTool.StrokeEraser) {
- // this._eraserLock++;
- const segments = this.segmentErase(intersect.inkView, intersect.t); // intersect.t is where the eraser intersected the ink stroke - want to remove the segment that starts at the intersection just before this t value and goes to the one just after it
- const newStrokes = segments?.map(segment => {
- const points = segment.reduce((data, curve) => [...data, ...curve.points.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',
+ 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);
+ 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;
+ });
+ } else {
+ this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
+ if (!this._deleteList.includes(intersect.inkView)) {
+ this._deleteList.push(intersect.inkView);
+ SetActiveInkWidth(StrCast(intersect.inkView.Document.stroke_width?.toString()) || '1');
+ SetActiveInkColor(StrCast(intersect.inkView.Document.color?.toString()) || 'black');
+ // create a new curve by appending all curves of the current segment together in order to render a single new stroke.
+ if (Doc.ActiveTool !== InkTool.StrokeEraser) {
+ // this._eraserLock++;
+ const segments = this.segmentErase(intersect.inkView, intersect.t); // intersect.t is where the eraser intersected the ink stroke - want to remove the segment that starts at the intersection just before this t value and goes to the one just after it
+ const newStrokes = segments?.map(segment => {
+ const points = segment.reduce((data, curve) => [...data, ...curve.points.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
- );
- });
- newStrokes && this.addDocument?.(newStrokes);
- // setTimeout(() => this._eraserLock--);
+ inkWidth
+ );
+ });
+ newStrokes && this.addDocument?.(newStrokes);
+ // setTimeout(() => this._eraserLock--);
+ }
+ // Lower ink opacity to give the user a visual indicator of deletion.
+ intersect.inkView.layoutDoc.opacity = 0;
+ intersect.inkView.layoutDoc.dontIntersect = true;
}
- // Lower ink opacity to give the user a visual indicator of deletion.
- intersect.inkView.layoutDoc.opacity = 0;
- intersect.inkView.layoutDoc.dontIntersect = true;
- }
- });
+ });
+ }
+ return false;
+ };
+
+ /**
+ * Erases strokes by intersecting them with an invisible "eraser stroke".
+ * By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
+ * and deletes the original stroke.
+ */
+ @action
+ onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
+ this.erase(e, delta);
+ // if (this._eraserLock) return false; // leaving this commented out in case the idea is revisited in the future
+ return false;
+ };
+
+ @action
+ onEraserClick = (e: PointerEvent, doubleTap?: boolean) => {
+ this.erase(e, [0, 0]);
return false;
};
@@ -720,7 +753,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// increase radius slightly based on the erased stroke's width, added to make eraser look more realistic
const radius = ActiveEraserWidth() + 5 + inkStrokeWidth * 0.1; // add 5 to prevent eraser from being too small
const c = 0.551915024494; // circle tangent length to side ratio
- const movement = { x: endInkCoordsIn.X - startInkCoordsIn.X, y: endInkCoordsIn.Y - startInkCoordsIn.Y };
+ const movement = { x: Math.max(endInkCoordsIn.X - startInkCoordsIn.X, 1), y: Math.max(endInkCoordsIn.Y - startInkCoordsIn.Y, 1) };
const moveLen = Math.sqrt(movement.x ** 2 + movement.y ** 2);
const direction = { x: (movement.x / moveLen) * radius, y: (movement.y / moveLen) * radius };
const normal = { x: -direction.y, y: direction.x }; // prettier-ignore
@@ -1232,28 +1265,29 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
createDrawing = (e: PointerEvent, doubleTap?: boolean) => {
- SmartDrawHandler.Instance.displaySmartDrawHandler(e.pageX, e.pageY);
- if (SmartDrawHandler.Instance.strokes.length > 0) {
- const strokeList: InkData[] = SmartDrawHandler.Instance.strokes;
- 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);
- 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,
- { title: 'stroke',
+ SmartDrawHandler.Instance.displaySmartDrawHandler(e.pageX, e.pageY, this.createInkStroke);
+ };
+
+ @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);
+ 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,
+ { 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
- );
- this.addDocument(inkDoc);
- });
- }
+ inkWidth
+ );
+ this.addDocument(inkDoc);
+ });
};
@action
@@ -1849,8 +1883,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onCursorMove = (e: React.PointerEvent) => {
- this._eraserX = e.clientX;
- this._eraserY = e.clientY;
+ const locPt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY);
+ this._eraserX = locPt[0];
+ this._eraserY = locPt[1];
+ // Doc.ActiveTool === InkTool.RadiusEraser ? this._childPointerEvents = 'none' : this._childPointerEvents = 'all'
// super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
};
@@ -2161,8 +2197,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onPointerMove={this.onCursorMove}
style={{
position: 'fixed',
- left: this._eraserX - 60,
- top: this._eraserY - 100,
+ left: this._eraserX,
+ top: this._eraserY,
width: (ActiveEraserWidth() + 5) * 2,
height: (ActiveEraserWidth() + 5) * 2,
borderRadius: '50%',
diff --git a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
index 7e66a62d4..fc8f7a429 100644
--- a/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
+++ b/src/client/views/collections/collectionFreeForm/SmartDrawHandler.tsx
@@ -10,7 +10,6 @@ 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
@@ -23,7 +22,8 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
@observable private _yRelativeToTop: boolean = true;
@observable private _isLoading: boolean = false;
@observable private _userInput: string = '';
- @observable public strokes: InkData[] = [];
+ // @observable public strokes: InkData[] = [];
+ private _addToDocFunc: (strokeList: InkData[]) => void = () => {};
constructor(props: any) {
super(props);
@@ -42,10 +42,11 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
};
@action
- displaySmartDrawHandler = (x: number, y: number) => {
+ displaySmartDrawHandler = (x: number, y: number, addToDoc: (strokeList: InkData[]) => void) => {
this._pageX = x;
this._pageY = y;
this._display = true;
+ this._addToDocFunc = addToDoc;
};
@action
@@ -54,8 +55,11 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
};
@action
- drawWithGPT = async (startPoint: {X: number, Y: number}, input: string) => {
- console.log("start point is", startPoint);
+ waitForCoords = async () => {};
+
+ @action
+ drawWithGPT = async (startPoint: { X: number; Y: number }, input: string) => {
+ console.log('start point is', startPoint);
this.setIsLoading(true);
try {
const res = await gptAPICall(input, GPTCallType.DRAW);
@@ -63,8 +67,7 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
console.error('GPT call failed');
return;
}
- console.log("GPT response:", res);
- try {
+ 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 }[][] = [];
@@ -74,15 +77,17 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
// });
// 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);
- const controlPts: { X: number; Y: number }[][] = JSON.parse(res).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.strokes = controlPts;
+ this._addToDocFunc(controlPts);
} catch (err) {
console.error('Incompatible GPT output type');
}
-
} catch (err) {
console.error('GPT call failed', err);
}
@@ -124,7 +129,7 @@ export class SmartDrawHandler extends ObservableReactComponent<{}> {
iconPlacement="right"
color={MarqueeOptionsMenu.Instance.userColor}
onClick={e => {
- this.drawWithGPT({X: e.clientX, Y: e.clientY}, this._userInput);
+ this.drawWithGPT({ X: e.clientX, Y: e.clientY }, this._userInput);
}}
/>
{/* <IconButton
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 2f6824466..df990b0c0 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -51,6 +51,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public Highlight: (color: string) => Opt<Doc> = (/* color: string */) => undefined;
+ public Tape: (color: string) => Opt<Doc> = (/* color: string */) => undefined;
public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = emptyFunction;
public Delete: () => void = unimplementedFunction;
public PinToPres: () => void = unimplementedFunction;
@@ -172,6 +173,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
AnchorMenu.Instance.fadeOut(true);
};
+ @action
+ tapeClicked = () => {
+ this.Tape(this.highlightColor);
+ // AnchorMenu.Instance.fadeOut(true);
+ };
+
@computed get highlighter() {
return (
<Group>
@@ -182,6 +189,13 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
colorPicker={this.highlightColor}
color={SettingsManager.userColor}
/>
+ <IconButton
+ tooltip="Click to Add Tape" //
+ icon={<FontAwesomeIcon icon="tape" />}
+ onClick={this.tapeClicked}
+ colorPicker={this.highlightColor}
+ color={SettingsManager.userColor}
+ />
<ColorPicker selectedColor={this.highlightColor} setFinalColor={this.changeHighlightColor} setSelectedColor={this.changeHighlightColor} size={Size.XSMALL} />
</Group>
);
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 6c1617c38..9ca05965b 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -67,6 +67,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
@observable _pageSizes: { width: number; height: number }[] = [];
@observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>();
+ @observable _savedTapes = new ObservableMap<number, HTMLDivElement[]>();
@observable _textSelecting = true;
@observable _showWaiting = true;
@observable Index: number = -1;
@@ -583,6 +584,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
return <div className={'pdfViewerDash-text' + (this._props.pointerEvents?.() !== 'none' && this._textSelecting && this._props.isContentActive() ? '-selected' : '')} ref={this._viewer} />;
}
savedAnnotations = () => this._savedAnnotations;
+ savedTapes = () => this._savedTapes;
addDocumentWrapper = (doc: Doc | Doc[]) => this._props.addDocument!(doc);
render() {
TraceMobx();
@@ -616,6 +618,7 @@ export class PDFViewer extends ObservableReactComponent<IViewerProps> {
docView={this._props.pdfBox.DocumentView!}
finishMarquee={this.finishMarquee}
savedAnnotations={this.savedAnnotations}
+ savedTapes={this.savedTapes}
selectionText={this.selectionText}
annotationLayer={this._annotationLayer.current}
marqueeContainer={this._mainCont.current}