From 0e3892e63758accd7dae274072ad7893934c3624 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 18 Oct 2022 15:07:31 -0400 Subject: cleaned up gestureutils a bit. added recognize gestures option, but it probably shouldn't be used until the geometry gesture recognition descriptions are improved. --- src/client/views/nodes/button/FontIconBox.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'src/client/views/nodes/button') diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 6d1751b25..6eaf3c31a 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -11,6 +11,7 @@ import { InkTool } from '../../../../fields/InkField'; import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { WebField } from '../../../../fields/URLField'; +import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; import { aggregateBounds, Utils } from '../../../../Utils'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; @@ -85,6 +86,12 @@ export class FontIconBox extends DocComponent() { static SetShowLabels(show: boolean) { Doc.UserDoc()._showLabel = show; } + static GetRecognizeGestures() { + return BoolCast(Doc.UserDoc()._recognizeGestures); + } + static SetRecognizeGesturs(show: boolean) { + Doc.UserDoc()._recognizeGestures = show; + } // Determining UI Specs @computed get label() { @@ -775,7 +782,7 @@ ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) if (checkResult) { return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent'; } - if (['circle', 'square', 'line'].includes(tool)) { + if ([GestureUtils.Gestures.Circle, GestureUtils.Gestures.Rectangle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Triangle].includes(tool as any)) { if (GestureOverlay.Instance.InkShape === tool) { Doc.ActiveTool = InkTool.None; GestureOverlay.Instance.InkShape = InkTool.None; -- cgit v1.2.3-70-g09d2 From a81ab2e6f75681bc4fb3a7b49d2056144e396b94 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 19 Oct 2022 12:38:19 -0400 Subject: fixed erasing straight line stroke segments caused by intersections with other strokes. cleaned up more with gestures. changed docView lock icon to be part of docDecorations. --- src/client/views/DocumentDecorations.scss | 28 ++++++- src/client/views/DocumentDecorations.tsx | 41 +++++++++- src/client/views/GestureOverlay.tsx | 21 +++--- src/client/views/StyleProvider.scss | 18 ++--- src/client/views/StyleProvider.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 58 ++++++-------- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 5 +- src/client/views/nodes/button/FontIconBox.tsx | 20 ++--- src/pen-gestures/GestureUtils.ts | 2 - src/pen-gestures/ndollar.ts | 88 +++++++++------------- 11 files changed, 148 insertions(+), 137 deletions(-) (limited to 'src/client/views/nodes/button') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 5c8dd36cc..4e0b061a6 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -1,7 +1,7 @@ @import 'global/globalCssVariables'; $linkGap: 3px; -$headerHeight: 20px; +$headerHeight: 25px; $resizeHandler: 8px; .documentDecorations-Dark, @@ -54,6 +54,8 @@ $resizeHandler: 8px; grid-column-end: 4; flex-direction: row; gap: 2px; + pointer-events: all; + cursor: move; .documentDecorations-openButton { display: flex; @@ -236,8 +238,8 @@ $resizeHandler: 8px; .documentDecorations-borderRadius { position: absolute; border-radius: 100%; - left: 3px; - top: 23px; + left: 7px; + top: 27px; background: $medium-gray; height: 10; width: 10; @@ -245,6 +247,21 @@ $resizeHandler: 8px; cursor: nwse-resize; } + .documentDecorations-lock { + position: absolute; + background: black; + right: 11; + top: 30px; + color: gray; + height: 14; + width: 14; + pointer-events: all; + margin: auto; + display: flex; + align-items: center; + flex-direction: column; + } + .documentDecorations-rotationPath { position: absolute; width: 100%; @@ -260,6 +277,7 @@ $resizeHandler: 8px; cursor: nwse-resize; background: unset; opacity: 1; + transform: scale(2); } .documentDecorations-topLeftResizer { @@ -314,6 +332,7 @@ $resizeHandler: 8px; .documentDecorations-topLeftResizer:hover, .documentDecorations-bottomRightResizer:hover { opacity: 1; + background: black; } .documentDecorations-bottomRightResizer { @@ -325,6 +344,7 @@ $resizeHandler: 8px; cursor: nesw-resize; background: unset; opacity: 1; + transform: scale(2); } .documentDecorations-topRightResizer { @@ -339,7 +359,6 @@ $resizeHandler: 8px; .documentDecorations-topRightResizer:hover, .documentDecorations-bottomLeftResizer:hover { - cursor: nesw-resize; background: black; opacity: 1; } @@ -392,6 +411,7 @@ $resizeHandler: 8px; justify-content: center; align-items: center; gap: 5px; + top: 4px; background: $light-gray; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9881ef9f1..6cf7df357 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -140,6 +140,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P } }; + @action onContainerDown = (e: React.PointerEvent): void => { + setupMoveUpEvents( + this, + e, + e => this.onBackgroundMove(true, e), + e => {}, + emptyFunction + ); + }; + @action onTitleDown = (e: React.PointerEvent): void => { setupMoveUpEvents( this, @@ -311,6 +321,27 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P ); }; + @action + onLockDown = (e: React.PointerEvent): void => { + // Call util move event function + setupMoveUpEvents( + this, // target + e, // pointerEvent + returnFalse, // moveEvent + emptyFunction, // upEvent + e => { + UndoManager.RunInBatch( + () => + SelectionManager.Views().map(dv => { + dv.rootDoc._lockedPosition = !dv.rootDoc._lockedPosition; + dv.rootDoc._pointerEvents = dv.rootDoc._lockedPosition ? 'none' : undefined; + }), + 'toggleBackground' + ); + } // clickEvent + ); + }; + setRotateCenter = (seldocview: DocumentView, rotCenter: number[]) => { const newloccentern = seldocview.props.ScreenToLocalTransform().transformPoint(rotCenter[0], rotCenter[1]); const newlocenter = [newloccentern[0] - NumCast(seldocview.layoutDoc._width) / 2, newloccentern[1] - NumCast(seldocview.layoutDoc._height) / 2]; @@ -791,7 +822,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P width: bounds.r - bounds.x + this._resizeBorderWidth + 'px', height: bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + 'px', }}> -
+
{hideDeleteButton ?
: topBtn('close', 'times', undefined, e => this.onCloseClick(true), 'Close')} {hideResizers || hideDeleteButton ?
: topBtn('minimize', 'window-maximize', undefined, e => this.onCloseClick(undefined), 'Minimize')} {hideResizers ?
: titleArea} @@ -817,21 +848,23 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P key="rad" style={{ background: `${this._isRounding ? Colors.MEDIUM_BLUE : undefined}`, - left: `${radiusHandleLocation + 3}`, - top: `${radiusHandleLocation + 23}`, + transform: `translate(${radiusHandleLocation}px, ${radiusHandleLocation}px)`, }} className={`documentDecorations-borderRadius`} onPointerDown={this.onRadiusDown} onContextMenu={e => e.preventDefault()} /> )} +
e.preventDefault()}> + +
{hideDocumentButtonBar ? null : (
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 23b03bc50..362aa3c86 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,7 +1,7 @@ import React = require('react'); import * as fitCurve from 'fit-curve'; import { action, computed, observable, runInAction, trace } from 'mobx'; -import { Doc } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; import { InkData, InkTool } from '../../fields/InkField'; import { List } from '../../fields/List'; import { ScriptField } from '../../fields/ScriptField'; @@ -49,7 +49,7 @@ interface GestureOverlayProps { export class GestureOverlay extends Touchable { static Instance: GestureOverlay; - @observable public InkShape: string = ''; + @observable public InkShape: Opt; @observable public SavedColor?: string; @observable public SavedWidth?: number; @observable public Tool: ToolglassTools = ToolglassTools.None; @@ -644,8 +644,6 @@ export class GestureOverlay extends Touchable { if (this._points.length > 1) { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); - //push first points to so interactionUtil knows pointer is up - this._points.push({ X: this._points[0].X, Y: this._points[0].Y }); const initialPoint = this._points[0]; const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height; @@ -685,10 +683,10 @@ export class GestureOverlay extends Touchable { //if any of the shape is activated in the CollectionFreeFormViewChrome else if (this.InkShape) { this.makeBezierPolygon(this.InkShape, false); - this.dispatchGesture(GestureUtils.Gestures.Stroke); + this.dispatchGesture(this.InkShape); this._points.length = 0; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { - this.InkShape = ''; + this.InkShape = undefined; Doc.ActiveTool = InkTool.None; } } @@ -703,12 +701,13 @@ export class GestureOverlay extends Touchable { case GestureUtils.Gestures.Rectangle: case GestureUtils.Gestures.Circle: this.makeBezierPolygon(result.Name, true); - case GestureUtils.Gestures.StartBracket: - case GestureUtils.Gestures.EndBracket: actionPerformed = this.dispatchGesture(result.Name); break; case GestureUtils.Gestures.Line: - actionPerformed = this.handleLineGesture(); + if (!(actionPerformed = this.handleLineGesture())) { + this.makeBezierPolygon(result.Name, true); + actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Stroke); + } break; case GestureUtils.Gestures.Scribble: console.log('scribble'); @@ -972,7 +971,7 @@ export class GestureOverlay extends Touchable { ActiveDash(), 1, 1, - this.InkShape, + this.InkShape ?? '', 'none', 1.0, false @@ -999,7 +998,7 @@ export class GestureOverlay extends Touchable { ActiveDash(), 1, 1, - this.InkShape, + this.InkShape ?? '', 'none', 1.0, false diff --git a/src/client/views/StyleProvider.scss b/src/client/views/StyleProvider.scss index 8929954c8..b1c97164a 100644 --- a/src/client/views/StyleProvider.scss +++ b/src/client/views/StyleProvider.scss @@ -1,11 +1,11 @@ .styleProvider-lock { - font-size: 12px; - width: 20; - height: 20; - position: absolute; - right: -25; - top: -5; - background: transparent; + font-size: 10; + width: 15; + height: 15; + position: absolute; + right: -0; + top: 0; + background: black; pointer-events: all; opacity: 0.3; display: flex; @@ -15,7 +15,7 @@ cursor: default; } .styleProvider-lock:hover { - opacity:1; + opacity: 1; } .styleProvider-treeView-icon, @@ -26,4 +26,4 @@ .styleProvider-treeView-icon { opacity: 0; -} \ No newline at end of file +} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index aadcd7169..a5a886f42 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -276,7 +276,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 && ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ? ( diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index daf69d4f6..983cf4f9b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -568,6 +568,8 @@ export class CollectionFreeFormView extends CollectionSubView { switch (ge.gesture) { + case GestureUtils.Gestures.Line: + break; default: case GestureUtils.Gestures.Circle: case GestureUtils.Gestures.Rectangle: @@ -602,33 +604,6 @@ export class CollectionFreeFormView extends CollectionSubView p.X)), Math.min(...ge.points.map(p => p.Y))); - const rb = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); - const bounds = { x: lt[0], r: rb[0], y: lt[1], b: rb[1] }; - const bWidth = bounds.r - bounds.x; - const bHeight = bounds.b - bounds.y; - const sel = this.getActiveDocuments().filter(doc => { - const l = NumCast(doc.x); - const r = l + doc[WidthSym](); - const t = NumCast(doc.y); - const b = t + doc[HeightSym](); - const pass = !(bounds.x > r || bounds.r < l || bounds.y > b || bounds.b < t); - if (pass) { - doc.x = l - bounds.x - bWidth / 2; - doc.y = t - bounds.y - bHeight / 2; - } - return pass; - }); - this.addDocument(Docs.Create.FreeformDocument(sel, { title: 'nested collection', x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 })); - sel.forEach(d => this.props.removeDocument?.(d)); - e.stopPropagation(); - break; - case GestureUtils.Gestures.StartBracket: - const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y))); - this._inkToTextStartX = start[0]; - this._inkToTextStartY = start[1]; - break; - case GestureUtils.Gestures.EndBracket: if (this._inkToTextStartX && this._inkToTextStartY) { const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y))); const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === 'rtf' && s.color); @@ -805,15 +780,15 @@ export class CollectionFreeFormView extends CollectionSubView ({ inkView, t: +t + Math.floor(i / 4) }))); // convert string t's to numbers and add start of curve segment to convert from local t value to t value along complete curve } @@ -862,6 +837,15 @@ export class CollectionFreeFormView extends CollectionSubView { + if ((otherCurve as any)._linear) { + return curve.lineIntersects({ p1: otherCurve.points[0], p2: otherCurve.points[3] }); + } + return curve.intersects(otherCurve); + }; + /** * Determines all possible intersections of the current curve of the intersected ink stroke with all other curves of all * ink strokes in the current collection. @@ -886,7 +870,7 @@ export class CollectionFreeFormView extends CollectionSubView ({ x: p.X, y: p.Y }))); - curve.intersects(otherCurve).forEach((val: string | number, i: number) => { + this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => { // Converting the Bezier.js Split type to a t-value number. const t = +val.toString().split('/')[0]; if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical). diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1c48d47e9..040e03150 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1082,7 +1082,7 @@ export class DocumentViewInternal extends DocComponent )} {audioView} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 959c641a8..461d6984d 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -8,7 +8,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { createSchema } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast } from '../../../fields/Types'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../Utils'; @@ -77,10 +77,11 @@ export class ImageBox extends ViewBoxAnnotatableComponent (this._curSuffix = selected ? '_o' : this.fieldKey === 'icon' ? '_m' : forceFull ? '_o' : scrSize < 0.25 ? '_s' : scrSize < 0.5 ? '_m' : scrSize < 0.8 ? '_l' : '_o'), { fireImmediately: true, delay: 1000 } ); + const layoutDoc = this.layoutDoc; this._disposers.path = reaction( () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), ({ nativeSize, width }) => { - if (true || !this.layoutDoc._height) { + if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) { this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; } }, diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 6eaf3c31a..fd0c0d141 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -771,36 +771,26 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); } -/** INK - * setActiveTool - * setStrokeWidth - * setStrokeColor - **/ - -ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) { +ScriptingGlobals.add(function setActiveTool(tool: InkTool | GestureUtils.Gestures, checkResult?: boolean) { InkTranscription.Instance?.createInkGroup(); if (checkResult) { return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent'; } - if ([GestureUtils.Gestures.Circle, GestureUtils.Gestures.Rectangle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Triangle].includes(tool as any)) { + if (Object.values(GestureUtils.Gestures).includes(tool as any)) { if (GestureOverlay.Instance.InkShape === tool) { Doc.ActiveTool = InkTool.None; - GestureOverlay.Instance.InkShape = InkTool.None; + GestureOverlay.Instance.InkShape = undefined; } else { Doc.ActiveTool = InkTool.Pen; - GestureOverlay.Instance.InkShape = tool; + GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; } } else if (tool) { // pen or eraser if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) { Doc.ActiveTool = InkTool.None; - } else if (tool == InkTool.Write) { - // console.log("write mode selected - create groupDoc here!", tool) - Doc.ActiveTool = tool; - GestureOverlay.Instance.InkShape = ''; } else { Doc.ActiveTool = tool as any; - GestureOverlay.Instance.InkShape = ''; + GestureOverlay.Instance.InkShape = undefined; } } else { Doc.ActiveTool = InkTool.None; diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 2d3b1fdb8..41917aac9 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -19,8 +19,6 @@ export namespace GestureUtils { export enum Gestures { Line = 'line', - StartBracket = 'startbracket', - EndBracket = 'endbracket', Stroke = 'stroke', Scribble = 'scribble', Text = 'text', diff --git a/src/pen-gestures/ndollar.ts b/src/pen-gestures/ndollar.ts index b10a9da17..3ee9506cb 100644 --- a/src/pen-gestures/ndollar.ts +++ b/src/pen-gestures/ndollar.ts @@ -145,7 +145,7 @@ export class Result { // // NDollarRecognizer constants // -const NumMultistrokes = 6; +let NumMultistrokes = 0; const NumPoints = 96; const SquareSize = 250.0; const OneDThreshold = 0.25; // customize to desired gesture set (usually 0.20 - 0.35) @@ -162,73 +162,59 @@ const AngleSimilarityThreshold = Deg2Rad(30.0); // NDollarRecognizer class // export class NDollarRecognizer { - public Multistrokes: Multistroke[]; + public Multistrokes: Multistroke[] = []; - /** - * @IMPORTANT - IF YOU'RE ADDING A NEW GESTURE, BE SURE TO INCREMENT THE NumMultiStrokes CONST RIGHT ABOVE THIS CLASS. - */ constructor( useBoundedRotationInvariance: boolean // constructor ) { // // one predefined multistroke for each multistroke type // - this.Multistrokes = new Array(NumMultistrokes); - this.Multistrokes[0] = new Multistroke( - GestureUtils.Gestures.Rectangle, - useBoundedRotationInvariance, - new Array( + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Rectangle, + useBoundedRotationInvariance, new Array( - new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), - new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), - new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), - new Point(106, 146), //new Point(80, 150), new Point(50, 146), - new Point(30, 143) + new Array( + new Point(30, 146), //new Point(29, 160), new Point(30, 180), new Point(31, 200), + new Point(30, 222), //new Point(50, 219), new Point(70, 225), new Point(90, 230), + new Point(106, 225), //new Point(100, 200), new Point(106, 180), new Point(110, 160), + new Point(106, 146), //new Point(80, 150), new Point(50, 146), + new Point(30, 143) + ) ) ) ); - this.Multistrokes[1] = new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347)))); - this.Multistrokes[2] = new Multistroke( - GestureUtils.Gestures.StartBracket, - useBoundedRotationInvariance, - new Array( - // new Array(new Point(145, 20), new Point(30, 21), new Point(34, 150)) - new Array(new Point(31, 25), new Point(145, 20), new Point(31, 25), new Point(34, 150)) + this.Multistrokes.push(new Multistroke(GestureUtils.Gestures.Line, useBoundedRotationInvariance, new Array(new Array(new Point(12, 347), new Point(119, 347))))); + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Triangle, // equilateral + useBoundedRotationInvariance, + new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))) ) ); - this.Multistrokes[3] = new Multistroke( - GestureUtils.Gestures.EndBracket, - useBoundedRotationInvariance, - new Array( - // new Array(new Point(150, 21), new Point(149, 150), new Point(26, 152)) - // new Array(new Point(150, 150), new Point(150, 0), new Point(150, 150), new Point(0, 150)) - new Array(new Point(10, 100), new Point(100, 100), new Point(150, 12), new Point(200, 103), new Point(300, 100)) - ) - ); - this.Multistrokes[4] = new Multistroke( - GestureUtils.Gestures.Triangle, // equilateral - useBoundedRotationInvariance, - new Array(new Array(new Point(40, 100), new Point(100, 200), new Point(140, 102), new Point(42, 100))) - ); - this.Multistrokes[5] = new Multistroke( - GestureUtils.Gestures.Circle, - useBoundedRotationInvariance, - new Array( + this.Multistrokes.push( + new Multistroke( + GestureUtils.Gestures.Circle, + useBoundedRotationInvariance, new Array( - new Point(200, 250), - new Point(240, 230), - new Point(248, 210), - new Point(248, 190), - new Point(240, 170), - new Point(200, 150), - new Point(160, 170), - new Point(151, 190), - new Point(151, 210), - new Point(160, 230), - new Point(201, 250) + new Array( + new Point(200, 250), + new Point(240, 230), + new Point(248, 210), + new Point(248, 190), + new Point(240, 170), + new Point(200, 150), + new Point(160, 170), + new Point(151, 190), + new Point(151, 210), + new Point(160, 230), + new Point(201, 250) + ) ) ) ); + NumMultistrokes = this.Multistrokes.length; // NumMultistrokes flags the end of the non user-defined gstures strokes // // PREDEFINED STROKES // -- cgit v1.2.3-70-g09d2 From 5df8e9041e7a0e8ac98f0c911a2b603c0d8e89d3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 20 Oct 2022 11:07:04 -0400 Subject: added double-tap to keep ink primitive mode. cleaned up circle drawing and some other gesture code. --- src/client/util/CurrentUserUtils.ts | 14 ++--- src/client/util/InteractionUtils.tsx | 6 +- src/client/views/GestureOverlay.tsx | 69 +++++++--------------- src/client/views/collections/CollectionMenu.tsx | 7 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/button/FontIconBox.tsx | 51 +++++++++------- 7 files changed, 68 insertions(+), 84 deletions(-) (limited to 'src/client/views/nodes/button') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index bfa868dbc..2bc464127 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -46,7 +46,7 @@ interface Button { buttonText?: string; // fields that do not correspond to DocumentOption fields - scripts?: { script?: string; onClick?: string; } + scripts?: { script?: string; onClick?: string; onDoubleClick?: string } funcs?: { [key:string]: string }; subMenu?: Button[]; } @@ -628,13 +628,13 @@ export class CurrentUserUtils { static inkTools():Button[] { return [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts: {onClick:'{ return setActiveTool("pen", _readOnly_);}' }}, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", _readOnly_);}'} }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", _readOnly_);}' }}, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts: {onClick:'{ return setActiveTool("pen", false, _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts: {onClick:'{ return setActiveTool("write", false, _readOnly_);}'} }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts: {onClick:'{ return setActiveTool("eraser", false, _readOnly_);}' }}, // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", _readOnly_);}`} }, - { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", _readOnly_);}`} }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", _readOnly_);}`} }, + { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Circle}", true, _readOnly_);}`} }, + { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Rectangle}", true, _readOnly_);}`} }, + { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", scripts: {onClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool("${GestureUtils.Gestures.Line}", true, _readOnly_);}`} }, { title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle", scripts: {onClick:'{ return setIsInkMask(_readOnly_);}'} }, { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, icon: "fill-drip",ignoreClick: true, scripts: {script: '{ return setFillColor(value, _readOnly_);}'} }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'}, numBtnType: NumButtonType.Slider, numBtnMin: 1}, diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 6622b498d..3cdf4dbd2 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -242,16 +242,16 @@ export namespace InteractionUtils { const centerX = (Math.max(left, right) + Math.min(left, right)) / 2; const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2; const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom)); - for (var x = Math.min(left, right); x < Math.max(left, right); x++) { + for (var x = centerX - radius; x < centerX + radius; x++) { const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY; points.push({ X: x, Y: y }); } - for (var x = Math.max(left, right); x > Math.min(left, right); x--) { + for (var x = centerX + radius; x > centerX - radius; x--) { const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY; const newY = centerY - (y - centerY); points.push({ X: x, Y: newY }); } - points.push({ X: Math.min(left, right), Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(Math.min(left, right) - centerX, 2)) + centerY }); + points.push({ X: centerX - radius, Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(-radius, 2)) + centerY }); break; case GestureUtils.Gestures.Line: diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 362aa3c86..a29073f14 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -16,7 +16,6 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { SelectionManager } from '../util/SelectionManager'; import { Transform } from '../util/Transform'; -import { CollectionFreeFormViewChrome } from './collections/CollectionMenu'; import './GestureOverlay.scss'; import { ActiveArrowEnd, @@ -53,6 +52,7 @@ export class GestureOverlay extends Touchable { @observable public SavedColor?: string; @observable public SavedWidth?: number; @observable public Tool: ToolglassTools = ToolglassTools.None; + @observable public KeepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode @observable private _thumbX?: number; @observable private _thumbY?: number; @@ -616,29 +616,16 @@ export class GestureOverlay extends Touchable { return false; }; - handleLineGesture = (): boolean => { - const actionPerformed = false; - const B = this.svgBounds; - - // get the two targets at the ends of the line - const ep1 = this._points[0]; - const ep2 = this._points.lastElement(); - const target1 = document.elementFromPoint(ep1.X, ep1.Y); - const target2 = document.elementFromPoint(ep2.X, ep2.Y); - - const ge = new CustomEvent('dashOnGesture', { - bubbles: true, - detail: { - points: this._points.slice(), - gesture: GestureUtils.Gestures.Line, - bounds: B, - }, - }); - target1?.dispatchEvent(ge); - target2?.dispatchEvent(ge); - return actionPerformed; - }; - + @action primCreated() { + if (!this.KeepPrimitiveMode) { + this.InkShape = undefined; + //get out of ink mode after each stroke= + //if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor); + Doc.ActiveTool = InkTool.None; + // SetActiveArrowStart('none'); + // SetActiveArrowEnd('none'); + } + } @action onPointerUp = (e: PointerEvent) => { if (this._points.length > 1) { @@ -654,7 +641,6 @@ export class GestureOverlay extends Touchable { switch (this.Tool) { case ToolglassTools.InkToText: this._strokes.push(this._points.slice()); - this._points.length = 0; CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then(results => { const wordResults = results.filter((r: any) => r.category === 'line'); const possibilities: string[] = []; @@ -676,7 +662,6 @@ export class GestureOverlay extends Touchable { break; case ToolglassTools.IgnoreGesture: this.dispatchGesture(GestureUtils.Gestures.Stroke); - this._points.length = 0; break; } } @@ -684,11 +669,7 @@ export class GestureOverlay extends Touchable { else if (this.InkShape) { this.makeBezierPolygon(this.InkShape, false); this.dispatchGesture(this.InkShape); - this._points.length = 0; - if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { - this.InkShape = undefined; - Doc.ActiveTool = InkTool.None; - } + this.primCreated(); } // if we're not drawing in a toolglass try to recognize as gesture else { @@ -697,18 +678,13 @@ export class GestureOverlay extends Touchable { let actionPerformed = false; if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) { switch (result.Name) { + case GestureUtils.Gestures.Line: case GestureUtils.Gestures.Triangle: case GestureUtils.Gestures.Rectangle: case GestureUtils.Gestures.Circle: this.makeBezierPolygon(result.Name, true); actionPerformed = this.dispatchGesture(result.Name); break; - case GestureUtils.Gestures.Line: - if (!(actionPerformed = this.handleLineGesture())) { - this.makeBezierPolygon(result.Name, true); - actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Stroke); - } - break; case GestureUtils.Gestures.Scribble: console.log('scribble'); break; @@ -741,12 +717,9 @@ export class GestureOverlay extends Touchable { // TODO: nda - check inks to group here checkInksToGroup(); } - this._points.length = 0; } - } else { - this._points.length = 0; } - CollectionFreeFormViewChrome.Instance?.primCreated(); + this._points.length = 0; }; makeBezierPolygon = (shape: string, gesture: boolean) => { @@ -901,17 +874,17 @@ export class GestureOverlay extends Touchable { return false; }; - dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, data?: any) => { - const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y); + dispatchGesture = (gesture: GestureUtils.Gestures, stroke?: InkData, text?: any) => { + const points = (stroke ?? this._points).slice(); return ( - target?.dispatchEvent( + document.elementFromPoint(points[0].X, points[0].Y)?.dispatchEvent( new CustomEvent('dashOnGesture', { bubbles: true, detail: { - points: stroke ?? this._points.slice(), - gesture: gesture as any, - bounds: this.getBounds(stroke ?? this._points), - text: data, + points, + gesture, + bounds: this.getBounds(points), + text, }, }) ) || false diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 46e8494ab..db81f28f6 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -40,6 +40,7 @@ import { CollectionLinearView } from './collectionLinear'; import './CollectionMenu.scss'; import { COLLECTION_BORDER_WIDTH } from './CollectionView'; import { TabDocView } from './TabDocView'; +import { GestureUtils } from '../../../pen-gestures/GestureUtils'; interface CollectionMenuProps { panelHeight: () => number; @@ -767,7 +768,7 @@ export class CollectionFreeFormViewChrome extends React.Component { switch (ge.gesture) { - case GestureUtils.Gestures.Line: - break; default: + case GestureUtils.Gestures.Line: case GestureUtils.Gestures.Circle: case GestureUtils.Gestures.Rectangle: case GestureUtils.Gestures.Triangle: diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 040e03150..16b5809fa 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -655,7 +655,7 @@ export class DocumentViewInternal extends DocComponent 0))) { + if ((Doc.ActiveTool === InkTool.None || this.props.addDocTab === returnFalse) && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) { // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking if ( (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index fd0c0d141..883c4460b 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -771,31 +771,42 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); } -ScriptingGlobals.add(function setActiveTool(tool: InkTool | GestureUtils.Gestures, checkResult?: boolean) { +function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) { InkTranscription.Instance?.createInkGroup(); if (checkResult) { - return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (Object.values(GestureUtils.Gestures).includes(tool as any)) { - if (GestureOverlay.Instance.InkShape === tool) { - Doc.ActiveTool = InkTool.None; - GestureOverlay.Instance.InkShape = undefined; - } else { - Doc.ActiveTool = InkTool.Pen; - GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; + return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool + ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures) + ? Colors.MEDIUM_BLUE + : Colors.MEDIUM_BLUE_ALT + : 'transparent'; + } + runInAction(() => { + if (GestureOverlay.Instance) { + GestureOverlay.Instance.KeepPrimitiveMode = keepPrim; } - } else if (tool) { - // pen or eraser - if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) { - Doc.ActiveTool = InkTool.None; + if (Object.values(GestureUtils.Gestures).includes(tool as any)) { + if (GestureOverlay.Instance.InkShape === tool) { + Doc.ActiveTool = InkTool.None; + GestureOverlay.Instance.InkShape = undefined; + } else { + Doc.ActiveTool = InkTool.Pen; + GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; + } + } else if (tool) { + // pen or eraser + if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape) { + Doc.ActiveTool = InkTool.None; + } else { + Doc.ActiveTool = tool as any; + GestureOverlay.Instance.InkShape = undefined; + } } else { - Doc.ActiveTool = tool as any; - GestureOverlay.Instance.InkShape = undefined; + Doc.ActiveTool = InkTool.None; } - } else { - Doc.ActiveTool = InkTool.None; - } -}); + }); +} + +ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); // toggle: Set overlay status of selected document ScriptingGlobals.add(function setIsInkMask(checkResult?: boolean) { -- cgit v1.2.3-70-g09d2 From 627c2aa72f61a888866840810a7d31cb0648437b Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 28 Oct 2022 14:14:05 -0400 Subject: fixed proxyfields to use cache if it's available which saves orders of magnitude opening up Files list. Fixed copying docs to appear in Files list. fixed undo for change perspective and header color --- src/client/views/MainView.tsx | 7 +-- src/client/views/collections/TreeView.tsx | 16 +++--- src/client/views/nodes/DocumentView.tsx | 74 ++++++++++++------------- src/client/views/nodes/button/FontIconBox.tsx | 12 ++-- src/fields/Doc.ts | 4 +- src/fields/List.ts | 8 ++- src/fields/Proxy.ts | 79 +++++++++++++-------------- 7 files changed, 102 insertions(+), 98 deletions(-) (limited to 'src/client/views/nodes/button') diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 4dc1ebd99..052846e71 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -841,15 +841,12 @@ export class MainView extends React.Component { } expandFlyout = action((button: Doc) => { - // bcz: What's going on here!? + // bcz: What's going on here!? --- may be fixed now, so commenting out ... // Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div // generate the wrong value from getClientRectangle() -- specifically they return an 'x' that is the flyout's width greater than it should be. // interactively adjusting the flyout fixes the problem. So does programmatically changing the value after a timeout to something *fractionally* different (ie, 1.5, not 1);) this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250; - setTimeout( - action(() => (this._leftMenuFlyoutWidth += 0.5)), - 0 - ); + //setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5))); this._sidebarContent.proto = button.target as any; this.LastButton = button; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 1e97eee37..ac8562d5a 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -55,7 +55,7 @@ export interface TreeViewProps { indentDocument?: (editTitle: boolean) => void; outdentDocument?: (editTitle: boolean) => void; ScreenToLocalTransform: () => Transform; - contextMenuItems: { script: ScriptField; filter: ScriptField; icon: string; label: string }[]; + contextMenuItems?: { script: ScriptField; filter: ScriptField; icon: string; label: string }[]; dontRegisterView?: boolean; styleProvider?: StyleProviderFunc | undefined; treeViewHideHeaderFields: () => boolean; @@ -302,7 +302,7 @@ export class TreeView extends React.Component { const pt = [e.clientX, e.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); + const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length); this._header.current!.className = 'treeView-header'; if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.rootDoc) { if (inside) this._header.current!.className += ' treeView-header-inside'; @@ -362,7 +362,7 @@ export class TreeView extends React.Component { if (!this._header.current) return; const rect = this._header.current.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); + const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.doc; @@ -401,7 +401,6 @@ export class TreeView extends React.Component { getTransform = () => this.refTransform(this._tref.current); embeddedPanelWidth = () => this.props.panelWidth() / (this.props.treeView.props.NativeDimScaling?.() || 1); embeddedPanelHeight = () => { - console.log(this.props.treeView.rootDoc.title); const layoutDoc = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; return Math.min( layoutDoc[HeightSym](), @@ -509,7 +508,7 @@ export class TreeView extends React.Component { @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; - const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }; + const sortings = (this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }) ?? {}; if (['links', 'annotations', 'aliases', this.fieldKey].includes(expandKey)) { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); const sortKeys = Object.keys(sortings); @@ -729,7 +728,7 @@ export class TreeView extends React.Component { const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' }; const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' }; return [ - ...this.props.contextMenuItems.filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), + ...(this.props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), ...(this.doc.isFolder ? folderOp : Doc.IsSystem(this.doc) @@ -831,7 +830,6 @@ export class TreeView extends React.Component { */ @computed get renderTitle() { - // TraceMobx(); const view = this._editTitle ? ( { } })} Document={this.doc} - fitWidth={(doc: Doc) => true} + fitWidth={returnTrue} DataDoc={undefined} scriptContext={this} hideDecorationTitle={this.props.treeView.outlineMode} @@ -1035,7 +1033,7 @@ export class TreeView extends React.Component { const pt = [de.clientX, de.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); + const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocs?.length ? true : false); const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, false)); }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5b26469ed..b5dde211b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,7 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Document } from '../../../fields/documentSchemas'; @@ -53,7 +53,6 @@ import { RadialMenu } from './RadialMenu'; import { ScriptingBox } from './ScriptingBox'; import { PresBox } from './trails/PresBox'; import React = require('react'); -import { CollectionTreeView } from '../collections/CollectionTreeView'; const { Howl } = require('howler'); interface Window { @@ -1056,24 +1055,36 @@ export class DocumentViewInternal extends DocComponent (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale; - @computed get contents() { - TraceMobx(); + get audioAnnoState() { + return this.dataDoc.audioAnnoState ?? 'stopped'; + } + @computed get audioAnnoView() { const audioAnnosCount = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null)?.length; const audioTextAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null); - const audioView = - (!this.props.isSelected() && !this._isHovering && this.dataDoc.audioAnnoState !== 2) || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!audioAnnosCount && !this.dataDoc.audioAnnoState) ? null : ( - {audioTextAnnos?.lastElement()}
}> -
- -
- - ); - + const audioIconColors = new Map([ + ['recording', 'red'], + ['playing', 'green'], + ['stopped', audioAnnosCount ? 'blue' : 'gray'], + ]); + return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!this.props.isSelected() && !this._isHovering && this.audioAnnoState !== 'recording') || (!audioAnnosCount && this.audioAnnoState === 'stopped') ? null : ( + {audioTextAnnos?.lastElement()}
}> +
+ +
+ + ); + } + @computed get linkCountView() { + return this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!this.props.isSelected() && !this._isHovering) || this.hideLinkButton ? null : ( + + ); + } + @computed get contents() { + TraceMobx(); return (
{ - setTimeout( - action(() => (this._retryThumb = 0)), - 0 - ); + setTimeout(action(() => (this._retryThumb = 0))); setTimeout( action(() => (this._retryThumb = 1)), 150 @@ -1124,14 +1132,8 @@ export class DocumentViewInternal extends DocComponent {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints} - {(!this.props.isSelected() && !this._isHovering) || this.hideLinkButton || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() ? null : ( - - )} - {audioView} + {this.linkCountView} + {this.audioAnnoView}
); } @@ -1210,7 +1212,7 @@ export class DocumentViewInternal extends DocComponent { - self.dataDoc.audioAnnoState = 0; - }); + runInAction(() => (self.dataDoc.audioAnnoState = 'stopped')); }, }); - this.dataDoc.audioAnnoState = 1; + this.dataDoc.audioAnnoState = 'playing'; } }; @@ -1262,12 +1262,12 @@ export class DocumentViewInternal extends DocComponent (dataDoc.audioAnnoState = 2)); + runInAction(() => (dataDoc.audioAnnoState = 'recording')); recorder.start(); setTimeout(() => { recorder.stop(); DictationManager.Controls.stop(false); - runInAction(() => (dataDoc.audioAnnoState = 0)); + runInAction(() => (dataDoc.audioAnnoState = 'stopped')); gumStream.getAudioTracks()[0].stop(); }, 5000); }); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 883c4460b..2b83e9da8 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -198,7 +198,7 @@ export class FontIconBox extends DocComponent() { e.preventDefault(); }} onClick={action(() => (this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen))}> - setValue(Number(e.target.value)))} /> + setValue(Number(e.target.value))))} />
setValue(Number(checkResult) + 1))}> @@ -221,7 +221,7 @@ export class FontIconBox extends DocComponent() {
); } else { - return
; + return
; } } @@ -303,7 +303,7 @@ export class FontIconBox extends DocComponent() { fontFamily: script.script.originalScript.startsWith('setFont') ? value : undefined, backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined, }} - onClick={() => script.script.run({ value }).result}> + onClick={undoBatch(() => script.script.run({ value }))}> {value[0].toUpperCase() + value.slice(1)}
)); @@ -352,12 +352,14 @@ export class FontIconBox extends DocComponent() { } colorPicker = (curColor: string) => { - const change = (value: ColorState) => { + const change = (value: ColorState, ev: MouseEvent) => { + ev.preventDefault(); + ev.stopPropagation(); const s = this.colorScript; s && undoBatch(() => s.script.run({ value: Utils.colorString(value), _readOnly_: false }).result)(); }; const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']; - return ; + return ; }; /** * Color button diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index fc43325fe..70cb10970 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -756,12 +756,13 @@ export namespace Doc { } cloneMap.set(doc[Id], copy); } + Doc.IsPrototype(copy) && Doc.AddDocToList(Doc.MyFileOrphans, undefined, copy); return copy; } export async function MakeClone(doc: Doc, dontCreate: boolean = false, asBranch = false, cloneMap: Map = new Map()) { const linkMap = new Map(); const rtfMap: { copy: Doc; key: string; field: RichTextField }[] = []; - const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf'], dontCreate, asBranch); + const copy = await Doc.makeClone(doc, cloneMap, linkMap, rtfMap, ['cloneOf', 'branches', 'branchOf', 'context'], dontCreate, asBranch); Array.from(linkMap.entries()).map((links: Doc[]) => LinkManager.Instance.addLink(links[1], true)); rtfMap.map(({ copy, key, field }) => { const replacer = (match: any, attr: string, id: string, offset: any, string: any) => { @@ -974,6 +975,7 @@ export namespace Doc { if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } + Doc.IsPrototype(copy) && Doc.AddDocToList(Doc.MyFileOrphans, undefined, copy); return copy; } diff --git a/src/fields/List.ts b/src/fields/List.ts index 5cc4ca543..edaa16003 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -278,7 +278,13 @@ class ListImpl extends ObjectField { const batchPromise = DocServer.GetRefFields(promised.map(p => p.promisedFieldId)); // as soon as we get the fields from the server, set all the list values in one // action to generate one React dom update. - batchPromise.then(pfields => promised.forEach(p => p.field.setValue(pfields[p.promisedFieldId]))); + batchPromise.then( + action(pfields => { + for (let i = 0; i < promised.length; i++) { + promised[i].field.setValue(pfields[promised[i].promisedFieldId]); + } + }) + ); // we also have to mark all lists items with this promise so that any calls to them // will await the batch request and return the requested field value. // This assumes the handler for 'promise' in the call above being invoked before the diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts index 2c5f38818..e924ef7a3 100644 --- a/src/fields/Proxy.ts +++ b/src/fields/Proxy.ts @@ -1,28 +1,28 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { FieldWaiting } from "./Doc"; -import { primitive, serializable } from "serializr"; -import { observable, action, runInAction } from "mobx"; -import { DocServer } from "../client/DocServer"; -import { RefField } from "./RefField"; -import { ObjectField } from "./ObjectField"; -import { Id, Copy, ToScriptString, ToString } from "./FieldSymbols"; -import { scriptingGlobal } from "../client/util/ScriptingGlobals"; -import { Plugins } from "./util"; +import { Deserializable } from '../client/util/SerializationHelper'; +import { FieldWaiting } from './Doc'; +import { primitive, serializable } from 'serializr'; +import { observable, action, runInAction } from 'mobx'; +import { DocServer } from '../client/DocServer'; +import { RefField } from './RefField'; +import { ObjectField } from './ObjectField'; +import { Id, Copy, ToScriptString, ToString } from './FieldSymbols'; +import { scriptingGlobal } from '../client/util/ScriptingGlobals'; +import { Plugins } from './util'; function deserializeProxy(field: any) { if (!field.cache) { field.cache = DocServer.GetCachedRefField(field.fieldId) as any; } } -@Deserializable("proxy", deserializeProxy) +@Deserializable('proxy', deserializeProxy) export class ProxyField extends ObjectField { constructor(); constructor(value: T); constructor(fieldId: string); constructor(value?: T | string) { super(); - if (typeof value === "string") { - this.cache = DocServer.GetCachedRefField(value) as any; + if (typeof value === 'string') { + //this.cache = DocServer.GetCachedRefField(value) as any; this.fieldId = value; } else if (value) { this.cache = value; @@ -36,16 +36,16 @@ export class ProxyField extends ObjectField { } [ToScriptString]() { - return "invalid"; + return 'invalid'; } [ToString]() { - return "ProxyField"; + return 'ProxyField'; } @serializable(primitive()) - readonly fieldId: string = ""; + readonly fieldId: string = ''; - // This getter/setter and nested object thing is + // This getter/setter and nested object thing is // because mobx doesn't play well with observable proxies @observable.ref private _cache: { readonly field: T | undefined } = { field: undefined }; @@ -59,29 +59,29 @@ export class ProxyField extends ObjectField { private failed = false; private promise?: Promise; + @action value(): T | undefined | FieldWaiting { - if (this.cache) { - return this.cache; - } - if (this.failed) { - return undefined; - } - if (!this.promise) { - const cached = DocServer.GetCachedRefField(this.fieldId); - if (cached !== undefined) { - runInAction(() => this.cache = cached as any); - return cached as any; - } - this.promise = DocServer.GetRefField(this.fieldId).then(action((field: any) => { - this.promise = undefined; - this.cache = field; - if (field === undefined) this.failed = true; - return field; - })); + if (this.cache) return this.cache; + if (this.failed) return undefined; + + const cached = DocServer.GetCachedRefField(this.fieldId) as T; + if (cached !== undefined) { + this.cache = cached; + } else if (!this.promise) { + this.promise = DocServer.GetRefField(this.fieldId).then( + action((field: any) => { + this.promise = undefined; + this.cache = field; + this.failed = field === undefined; + return field; + }) + ) as FieldWaiting; } - return DocServer.GetCachedRefField(this.fieldId) ?? (this.promise as any); + return cached ?? this.promise; + } + promisedValue(): string { + return !this.cache && !this.failed && !this.promise && !DocServer.GetCachedRefField(this.fieldId) ? this.fieldId : ''; } - promisedValue(): string { return !this.cache && !this.failed && !this.promise ? this.fieldId : ""; } setPromise(promise: any) { this.promise = promise; } @@ -127,6 +127,5 @@ function prefetchValue(proxy: PrefetchProxy) { } @scriptingGlobal -@Deserializable("prefetch_proxy", prefetchValue) -export class PrefetchProxy extends ProxyField { -} +@Deserializable('prefetch_proxy', prefetchValue) +export class PrefetchProxy extends ProxyField {} -- cgit v1.2.3-70-g09d2 From dc942e6228e003caa3754a72c0e126d64332a004 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 1 Nov 2022 18:29:11 -0400 Subject: fixes for goldenlayout to initialize more cleanly. updated all old ReactDOM.render() to ReactDom.createRoot(). fixes for PDF/Web sidebar sizing. added text from pdf selection anchors to sidebar notes. fixed PDF text selection to align properly. --- src/client/goldenLayout.js | 11 +- src/client/util/DragManager.ts | 2 +- src/client/util/HypothesisUtils.ts | 160 ++++++++++++--------- src/client/views/DocumentDecorations.scss | 5 +- src/client/views/DocumentDecorations.tsx | 9 +- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/Main.tsx | 7 +- src/client/views/MainView.scss | 9 -- src/client/views/MainView.tsx | 94 +----------- src/client/views/MarqueeAnnotator.tsx | 6 +- src/client/views/SidebarAnnos.tsx | 22 +++ src/client/views/StyleProvider.tsx | 6 +- .../views/collections/CollectionDockingView.scss | 4 + .../views/collections/CollectionDockingView.tsx | 17 ++- src/client/views/collections/TabDocView.tsx | 6 +- src/client/views/nodes/DocumentLinksButton.tsx | 3 - src/client/views/nodes/PDFBox.tsx | 13 +- src/client/views/nodes/WebBox.tsx | 14 +- src/client/views/nodes/button/FontIconBox.tsx | 12 +- .../nodes/formattedText/DashDocCommentView.tsx | 8 +- .../views/nodes/formattedText/DashDocView.tsx | 12 +- .../views/nodes/formattedText/DashFieldView.tsx | 53 +++++-- .../views/nodes/formattedText/EquationView.tsx | 10 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 1 + .../views/nodes/formattedText/RichTextRules.ts | 7 +- .../views/nodes/formattedText/SummaryView.tsx | 9 +- src/client/views/nodes/formattedText/nodes_rts.ts | 1 + src/client/views/pdf/PDFViewer.scss | 12 +- src/client/views/pdf/PDFViewer.tsx | 6 +- src/debug/Repl.tsx | 37 +++-- 30 files changed, 278 insertions(+), 280 deletions(-) (limited to 'src/client/views/nodes/button') diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index dd11e6466..bc08b4d0b 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -459,7 +459,7 @@ this._bDragging = false; this.emit('dragStop', oEvent, this._nOriginalX + this._nX); } else { // make title receive pointer events to allow setting insertion position or selecting texst range - const classname = typeof oEvent.target?.className === "string" ? oEvent.target.className : ""; + const classname = oEvent.target ? (typeof oEvent.target.className === "string" ? oEvent.target.className : ""): ""; if (classname.includes("lm_title_wrap")) { oEvent.target.children[0].style.pointerEvents = "all"; oEvent.target.children[0].focus(); @@ -5391,10 +5391,11 @@ * @returns {void} */ _render: function () { - this._reactComponent = ReactDOM.render(this._getReactComponent(), this._container.getElement()[0]); - this._originalComponentWillUpdate = this._reactComponent.componentWillUpdate || function () { + this._reactComponent = ReactDOM.createRoot(this._container.getElement()[0]) + this._reactComponent.render(this._getReactComponent()); + this._originalComponentWillUpdate = this._reactComponent.componentDidUpdate || function () { }; - this._reactComponent.componentWillUpdate = this._onUpdate.bind(this); + this._reactComponent.componentDidUpdate = this._onUpdate.bind(this); if (this._container.getState()) { this._reactComponent.setState(this._container.getState()); } @@ -5407,7 +5408,7 @@ * @returns {void} */ _destroy: function () { - ReactDOM.unmountComponentAtNode(this._container.getElement()[0]); + // ReactDOM.unmountComponentAtNode(this._container.getElement()[0]); this._container.off('open', this._render, this); this._container.off('destroy', this._destroy, this); }, diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 36e5a65fb..d0690fa10 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -450,7 +450,7 @@ export namespace DragManager { const hideDragShowOriginalElements = (hide: boolean) => { dragLabel.style.display = hide ? '' : 'none'; !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); - eles.forEach(ele => (ele.hidden = hide)); + setTimeout(() => eles.forEach(ele => (ele.hidden = hide))); }; options?.hideSource && hideDragShowOriginalElements(true); diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts index e910a9118..297520d5d 100644 --- a/src/client/util/HypothesisUtils.ts +++ b/src/client/util/HypothesisUtils.ts @@ -1,30 +1,28 @@ -import { StrCast, Cast } from "../../fields/Types"; -import { SearchUtil } from "./SearchUtil"; -import { action, runInAction } from "mobx"; -import { Doc, Opt } from "../../fields/Doc"; -import { DocumentType } from "../documents/DocumentTypes"; -import { Docs } from "../documents/Documents"; -import { SelectionManager } from "./SelectionManager"; -import { WebField } from "../../fields/URLField"; -import { DocumentManager } from "./DocumentManager"; -import { DocumentLinksButton } from "../views/nodes/DocumentLinksButton"; -import { simulateMouseClick, Utils } from "../../Utils"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { Id } from "../../fields/FieldSymbols"; +import { StrCast, Cast } from '../../fields/Types'; +import { SearchUtil } from './SearchUtil'; +import { action, runInAction } from 'mobx'; +import { Doc, Opt } from '../../fields/Doc'; +import { DocumentType } from '../documents/DocumentTypes'; +import { Docs } from '../documents/Documents'; +import { SelectionManager } from './SelectionManager'; +import { WebField } from '../../fields/URLField'; +import { DocumentManager } from './DocumentManager'; +import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton'; +import { simulateMouseClick, Utils } from '../../Utils'; +import { DocumentView } from '../views/nodes/DocumentView'; +import { Id } from '../../fields/FieldSymbols'; export namespace Hypothesis { - /** - * Retrieve a WebDocument with the given url, prioritizing results that are on screen. + * Retrieve a WebDocument with the given url, prioritizing results that are on screen. * If none exist, create and return a new WebDocument. */ export const getSourceWebDoc = async (uri: string) => { const result = await findWebDoc(uri); - console.log(result ? "existing doc found" : "existing doc NOT found"); + console.log(result ? 'existing doc found' : 'existing doc NOT found'); return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _height: 512, _width: 400, useCors: true }); // create and return a new Web doc with given uri if no matching docs are found }; - /** * Search for a WebDocument whose url field matches the given uri, return undefined if not found */ @@ -33,18 +31,18 @@ export namespace Hypothesis { if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise const results: Doc[] = []; - await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => { - const docs = res.docs; - const filteredDocs = docs.filter(doc => - doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data - ); - filteredDocs.forEach(doc => { - uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history? - }); - })); + await SearchUtil.Search('web', true).then( + action(async (res: SearchUtil.DocSearchResult) => { + const docs = res.docs; + const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data); + filteredDocs.forEach(doc => { + uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history? + }); + }) + ); const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc)); - return onScreenResults.length ? onScreenResults[0] : (results.length ? results[0] : undefined); // prioritize results that are currently on the screen + return onScreenResults.length ? onScreenResults[0] : results.length ? results[0] : undefined; // prioritize results that are currently on the screen }; /** @@ -52,17 +50,19 @@ export namespace Hypothesis { */ export const linkListener = async (e: any) => { const annotationId: string = e.detail.id; - const annotationUri: string = StrCast(e.detail.uri).split("#annotations:")[0]; // clean hypothes.is URLs that reference a specific annotation + const annotationUri: string = StrCast(e.detail.uri).split('#annotations:')[0]; // clean hypothes.is URLs that reference a specific annotation const sourceDoc: Doc = await getSourceWebDoc(annotationUri); - if (!DocumentLinksButton.StartLink || sourceDoc === DocumentLinksButton.StartLink) { // start new link if there were none already started, or if the old startLink came from the same web document (prevent links to itself) + if (!DocumentLinksButton.StartLink || sourceDoc === DocumentLinksButton.StartLink) { + // start new link if there were none already started, or if the old startLink came from the same web document (prevent links to itself) runInAction(() => { DocumentLinksButton.AnnotationId = annotationId; DocumentLinksButton.AnnotationUri = annotationUri; DocumentLinksButton.StartLink = sourceDoc; DocumentLinksButton.StartLinkView = undefined; }); - } else { // if a link has already been started, complete the link to sourceDoc + } else { + // if a link has already been started, complete the link to sourceDoc runInAction(() => { DocumentLinksButton.AnnotationId = annotationId; DocumentLinksButton.AnnotationUri = annotationUri; @@ -81,31 +81,41 @@ export namespace Hypothesis { export const makeLink = async (title: string, url: string, annotationId: string, annotationSourceDoc: Doc) => { // if the annotation's source webpage isn't currently loaded in Dash, we're not able to access and edit the annotation from the client // so we're loading the webpage and its annotations invisibly in a WebBox in MainView.tsx, until the editing is done - !DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc); + //!DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc); var success = false; const onSuccess = action(() => { - console.log("Edit success!!"); + console.log('Edit success!!'); success = true; clearTimeout(interval); - DocumentLinksButton.invisibleWebDoc = undefined; - document.removeEventListener("editSuccess", onSuccess); + //DocumentLinksButton.invisibleWebDoc = undefined; + document.removeEventListener('editSuccess', onSuccess); }); const newHyperlink = `[${title}\n](${url})`; - const interval = setInterval(() => // keep trying to edit until annotations have loaded and editing is successful - !success && document.dispatchEvent(new CustomEvent<{ newHyperlink: string, id: string }>("addLink", { - detail: { newHyperlink: newHyperlink, id: annotationId }, - bubbles: true - })), 300); - - setTimeout(action(() => { - if (!success) { - clearInterval(interval); - DocumentLinksButton.invisibleWebDoc = undefined; - } - }), 10000); // give up if no success after 10s - document.addEventListener("editSuccess", onSuccess); + const interval = setInterval( + () => + // keep trying to edit until annotations have loaded and editing is successful + !success && + document.dispatchEvent( + new CustomEvent<{ newHyperlink: string; id: string }>('addLink', { + detail: { newHyperlink: newHyperlink, id: annotationId }, + bubbles: true, + }) + ), + 300 + ); + + setTimeout( + action(() => { + if (!success) { + clearInterval(interval); + //DocumentLinksButton.invisibleWebDoc = undefined; + } + }), + 10000 + ); // give up if no success after 10s + document.addEventListener('editSuccess', onSuccess); }; /** @@ -114,33 +124,40 @@ export namespace Hypothesis { export const deleteLink = async (linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc) => { if (Cast(destinationDoc.data, WebField)?.url.href !== StrCast(linkDoc.annotationUri)) return; // check that the destinationDoc is a WebDocument containing the target annotation - !DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink + //!DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink var success = false; const onSuccess = action(() => { - console.log("Edit success!"); + console.log('Edit success!'); success = true; clearTimeout(interval); - DocumentLinksButton.invisibleWebDoc = undefined; - document.removeEventListener("editSuccess", onSuccess); + // DocumentLinksButton.invisibleWebDoc = undefined; + document.removeEventListener('editSuccess', onSuccess); }); const annotationId = StrCast(linkDoc.annotationId); const linkUrl = Doc.globalServerPath(sourceDoc); - const interval = setInterval(() => {// keep trying to edit until annotations have loaded and editing is successful - !success && document.dispatchEvent(new CustomEvent<{ targetUrl: string, id: string }>("deleteLink", { - detail: { targetUrl: linkUrl, id: annotationId }, - bubbles: true - })); + const interval = setInterval(() => { + // keep trying to edit until annotations have loaded and editing is successful + !success && + document.dispatchEvent( + new CustomEvent<{ targetUrl: string; id: string }>('deleteLink', { + detail: { targetUrl: linkUrl, id: annotationId }, + bubbles: true, + }) + ); }, 300); - setTimeout(action(() => { - if (!success) { - clearInterval(interval); - DocumentLinksButton.invisibleWebDoc = undefined; - } - }), 10000); // give up if no success after 10s - document.addEventListener("editSuccess", onSuccess); + setTimeout( + action(() => { + if (!success) { + clearInterval(interval); + //DocumentLinksButton.invisibleWebDoc = undefined; + } + }), + 10000 + ); // give up if no success after 10s + document.addEventListener('editSuccess', onSuccess); }; /** @@ -149,17 +166,20 @@ export namespace Hypothesis { export const scrollToAnnotation = (annotationId: string, target: Doc) => { var success = false; const onSuccess = () => { - console.log("Scroll success!!"); + console.log('Scroll success!!'); document.removeEventListener('scrollSuccess', onSuccess); clearInterval(interval); success = true; }; - const interval = setInterval(() => { // keep trying to scroll every 250ms until annotations have loaded and scrolling is successful - document.dispatchEvent(new CustomEvent('scrollToAnnotation', { - detail: annotationId, - bubbles: true - })); + const interval = setInterval(() => { + // keep trying to scroll every 250ms until annotations have loaded and scrolling is successful + document.dispatchEvent( + new CustomEvent('scrollToAnnotation', { + detail: annotationId, + bubbles: true, + }) + ); const targetView: Opt = DocumentManager.Instance.getFirstDocumentView(target); const position = targetView?.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false); @@ -168,4 +188,4 @@ export namespace Hypothesis { document.addEventListener('scrollSuccess', onSuccess); // listen for success message from client setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s }; -} \ No newline at end of file +} diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 4e0b061a6..fe1190e27 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -250,8 +250,8 @@ $resizeHandler: 8px; .documentDecorations-lock { position: absolute; background: black; - right: 11; - top: 30px; + right: 23px; + top: 3px; color: gray; height: 14; width: 14; @@ -260,6 +260,7 @@ $resizeHandler: 8px; display: flex; align-items: center; flex-direction: column; + border-radius: 15%; } .documentDecorations-rotationPath { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 06eb6c6d7..8e0686038 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -782,6 +782,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P bounds.r = Math.max(bounds.x, Math.max(leftBounds, Math.min(window.innerWidth, bounds.r + borderRadiusDraggerWidth + this._resizeBorderWidth / 2) - this._resizeBorderWidth / 2 - borderRadiusDraggerWidth)); bounds.b = Math.max(bounds.y, Math.max(topBounds, Math.min(window.innerHeight, bounds.b + this._resizeBorderWidth / 2 + this._linkBoxHeight) - this._resizeBorderWidth / 2 - this._linkBoxHeight)); + const useLock = bounds.r - bounds.x > 120; const useRotation = seldocview.rootDoc.type !== DocumentType.EQUATION; // when do we want an object to not rotate? const rotation = NumCast(seldocview.rootDoc._rotation); @@ -856,9 +857,11 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P onContextMenu={e => e.preventDefault()} /> )} -
e.preventDefault()}> - -
+ {!useLock ? null : ( +
e.preventDefault()}> + +
+ )} {hideDocumentButtonBar ? null : (
, document.getElementById('root')); + ReactDOM.createRoot(document.getElementById('root')!).render(); }, 0); })(); diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index c5ac1cf52..069206126 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -371,12 +371,3 @@ h1, width: 200px; height: 800px; } - -.mainVew-invisibleWebRef { - position: absolute; - left: 50; - top: 50; - display: block; - width: 500px; - height: 1000px; -} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 052846e71..9648a7807 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -7,7 +7,7 @@ import { action, computed, configure, observable, reaction, runInAction } from ' import { observer } from 'mobx-react'; import 'normalize.css'; import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { ScriptField } from '../../fields/ScriptField'; import { DocCast, StrCast } from '../../fields/Types'; @@ -948,40 +948,6 @@ export class MainView extends React.Component { ); } - @computed get invisibleWebBox() { - // see note under the makeLink method in HypothesisUtils.ts - return !DocumentLinksButton.invisibleWebDoc ? null : ( -
- 500} - PanelHeight={() => 800} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - /> -
- ); - } - render() { return (
{this.snapLines} -
); } - - makeWebRef = (ele: HTMLDivElement) => { - reaction( - () => DocumentLinksButton.invisibleWebDoc, - invisibleDoc => { - ReactDOM.unmountComponentAtNode(ele); - invisibleDoc && - ReactDOM.render( - -
- 500} - PanelHeight={() => 800} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - /> -
- ; -
, - ele - ); - - let success = false; - const onSuccess = () => { - success = true; - clearTimeout(interval); - document.removeEventListener('editSuccess', onSuccess); - }; - - // For some reason, Hypothes.is annotations don't load until a click is registered on the page, - // so we keep simulating clicks until annotations have loaded and editing is successful - const interval = setInterval(() => !success && simulateMouseClick(ele, 50, 50, 50, 50), 500); - setTimeout(() => !success && clearInterval(interval), 10000); // give up if no success after 10s - document.addEventListener('editSuccess', onSuccess); - } - ); - }; } ScriptingGlobals.add(function selectMainMenu(doc: Doc, title: string) { diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index d9a989309..ce0e58d90 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -27,6 +27,7 @@ export interface MarqueeAnnotatorProps { mainCont: HTMLDivElement; docView: DocumentView; savedAnnotations: () => ObservableMap; + selectionText: () => string; annotationLayer: HTMLDivElement; addDocument: (doc: Doc) => boolean; getPageFromScroll?: (top: number) => number; @@ -147,7 +148,7 @@ export class MarqueeAnnotator extends React.Component { return marqueeAnno; } - const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title }); + const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, text: this.props.selectionText(), backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title }); let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; @@ -271,7 +272,8 @@ export class MarqueeAnnotator extends React.Component { width: `${this._width}px`, height: `${this._height}px`, border: `${this._width === 0 ? '' : '2px dashed black'}`, - }}/> + }} + /> ); } } diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 12f41394d..df1eb72ce 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -15,6 +15,7 @@ import { SearchBox } from './search/SearchBox'; import './SidebarAnnos.scss'; import { StyleProp } from './StyleProvider'; import React = require('react'); +import { RichTextField } from '../../fields/RichTextField'; interface ExtraProps { fieldKey: string; @@ -72,6 +73,27 @@ export class SidebarAnnos extends React.Component { FormattedTextBox.DontSelectInitialText = true; this.allMetadata.map(tag => (target[tag] = tag)); DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on'); + + Doc.GetProto(target).text = new RichTextField( + JSON.stringify({ + doc: { + type: 'doc', + content: [ + { + type: 'paragraph', + attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + content: [{ type: 'dashField', attrs: { fieldKey: 'text', docid: anchor[Id], hideKey: true, editable: false } }], + }, + { + type: 'paragraph', + attrs: { align: null, color: null, id: null, indent: null, inset: null, lineSpacing: null, paddingBottom: null, paddingTop: null }, + }, + ], + }, + selection: { type: 'text', anchor: 4, head: 4 }, + }), + '' + ); this.addDocument(target); this._stackRef.current?.focusDocument(target, {}); return target; diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 869570a17..5abf4bde2 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -288,11 +288,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 && - ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ? ( + return doc && isBackground() && !Doc.IsSystem(doc) && (props?.renderDepth || 0) > 0 ? (
toggleLockedPosition(doc)}>
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index ac3541f2c..78e44dfa2 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -89,6 +89,10 @@ position: relative; } +.lm_stack { + position: relative; +} + .lm_drag_tab { padding: 0; width: 15px !important; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e9b41de25..92319d080 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,6 +1,6 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; @@ -318,7 +318,7 @@ export class CollectionDockingView extends CollectionSubView() { } }; - componentDidMount: () => void = () => { + componentDidMount: () => void = async () => { if (this._containerRef.current) { this._lightboxReactionDisposer = reaction( () => LightboxView.LightboxDoc, @@ -335,8 +335,11 @@ export class CollectionDockingView extends CollectionSubView() { this._ignoreStateChange = ''; } ); - setTimeout(this.setupGoldenLayout); - //window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window + reaction( + () => this.props.PanelWidth(), + width => !this._goldenLayout && width > 20 && setTimeout(() => this.setupGoldenLayout()), // need to wait for the collectiondockingview-container to have it's width/height since golden layout reads that to configure its windows + { fireImmediately: true } + ); } }; @@ -460,7 +463,7 @@ export class CollectionDockingView extends CollectionSubView() { }; tabDestroyed = (tab: any) => { - if (![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) { + if (tab.DashDoc && ![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) { Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc); Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); } @@ -470,8 +473,8 @@ export class CollectionDockingView extends CollectionSubView() { Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); this.tabMap.delete(tab); tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); - setTimeout(this.stateChanged); + //tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); + this.stateChanged(); } }; tabCreated = (tab: any) => { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index cde5132a3..31ed5a83b 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -4,7 +4,7 @@ import { Tooltip } from '@material-ui/core'; import { clamp } from 'lodash'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -143,8 +143,8 @@ export class TabDocView extends React.Component { const docIcon = ; const closeIcon = ; - ReactDOM.render(docIcon, iconWrap); - ReactDOM.render(closeIcon, closeWrap); + ReactDOM.createRoot(iconWrap).render(docIcon); + ReactDOM.createRoot(closeWrap).render(closeIcon); tab.reactComponents = [iconWrap, closeWrap]; tab.element[0].prepend(iconWrap); tab._disposers.layerDisposer = reaction( diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 1d455ad08..627487a9e 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -40,9 +40,6 @@ export class DocumentLinksButton extends React.Component; - public static invisibleWebRef = React.createRef(); - @action @undoBatch onLinkButtonMoved = (e: PointerEvent) => { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index c7001f846..fcb3ccb07 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -74,7 +74,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent ); } - sidebarWidth = () => - !this.SidebarShown ? 0 : PDFBox.sidebarResizerWidth + (this._previewWidth ? PDFBox.openSidebarWidth : ((NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth()) / NumCast(this.layoutDoc.nativeWidth)); - + sidebarWidth = () => { + if (!this.SidebarShown) return 0; + if (this._previewWidth) return PDFBox.sidebarResizerWidth + PDFBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target) + const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc); + return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1); + }; specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; funcs.push({ description: 'Copy path', event: () => this.pdfUrl && Utils.CopyText(Utils.prepend('') + this.pdfUrl.url.pathname), icon: 'expand-arrows-alt' }); @@ -483,7 +486,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent
-
+
- !this.SidebarShown ? 0 : WebBox.sidebarResizerWidth + (this._previewWidth ? WebBox.openSidebarWidth : ((NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth()) / NumCast(this.layoutDoc.nativeWidth)); - + sidebarWidth = () => { + if (!this.SidebarShown) return 0; + if (this._previewWidth) return WebBox.sidebarResizerWidth + WebBox.openSidebarWidth; // return default sidebar if previewing (as in viewing a link target) + const nativeDiff = NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc); + return WebBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1); + }; @computed get content() { const interactive = !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance?.Interacting; @@ -1000,6 +1003,7 @@ export class WebBox extends ViewBoxAnnotatableComponent{' '} @@ -1015,7 +1019,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.sidebarBtnDown(e, false)} /> -
+
() { // style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}> // //
; - setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView + //setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView return (
(this.colorPickerClosed = !this.colorPickerClosed))} + onClick={action(e => { + this.colorPickerClosed = !this.colorPickerClosed; + e.stopPropagation(); + })} onPointerDown={e => e.stopPropagation()}> {this.Icon(color)}
@@ -395,7 +397,7 @@ export class FontIconBox extends DocComponent() { {/* {dropdownCaret} */} {this.colorPickerClosed ? null : (
-
e.stopPropagation()} onClick={e => e.stopPropagation()}> +
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={e => e.stopPropagation()}> {this.colorPicker(curColor)}
, this.dom); + this.root = ReactDOM.createRoot(this.dom); + this.root.render(); (this as any).dom = this.dom; } destroy() { - ReactDOM.unmountComponentAtNode(this.dom); + // ReactDOM.unmountComponentAtNode(this.dom); } selectNode() {} diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 5f576be41..63ee7d1f3 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -1,7 +1,7 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { NodeSelection } from 'prosemirror-state'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, Utils } from '../../../../Utils'; @@ -15,6 +15,7 @@ import React = require('react'); export class DashDocView { dom: HTMLSpanElement; // container for label and value + root: any; constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { this.dom = document.createElement('span'); @@ -38,14 +39,13 @@ export class DashDocView { e.stopPropagation(); }; - ReactDOM.render( -
}> , - ]; - - return this.getElement(buttons); + ]); } } diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx index 4895dcdc5..0fd2a7808 100644 --- a/src/client/views/nodes/formattedText/EquationView.tsx +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -2,7 +2,7 @@ import EquationEditor from 'equation-editor-react'; import { IReactionDisposer } from 'mobx'; import { observer } from 'mobx-react'; import { TextSelection } from 'prosemirror-state'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { Doc } from '../../../../fields/Doc'; import { StrCast } from '../../../../fields/Types'; import './DashFieldView.scss'; @@ -11,7 +11,7 @@ import React = require('react'); export class EquationView { dom: HTMLDivElement; // container for label and value - + root: any; constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { this.dom = document.createElement('div'); this.dom.style.width = node.attrs.width; @@ -22,13 +22,13 @@ export class EquationView { e.stopPropagation(); }; - ReactDOM.render(, this.dom); - (this as any).dom = this.dom; + this.root = ReactDOM.createRoot(this.dom); + this.root.render(); } _editor: EquationEditor | undefined; setEditor = (editor?: EquationEditor) => (this._editor = editor); destroy() { - ReactDOM.unmountComponentAtNode(this.dom); + // ReactDOM.unmountComponentAtNode(this.dom); } setSelection() { this._editor?.mathField.focus(); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 096f9a92c..0e48dd93a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1132,6 +1132,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 47833dd43..dc5d8ada8 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -248,8 +248,7 @@ export class RichTextRules { // [[fieldKey:Doc]] => show field of doc new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => { const fieldKey = match[1]; - const rawdocid = match[3]; - const docid = rawdocid ? normalizeEmail(!rawdocid.includes('@') ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1)) : undefined; + const docid = match[3]?.replace(':', ''); const value = match[2]?.substring(1); if (!fieldKey) { if (docid) { @@ -259,7 +258,7 @@ export class RichTextRules { if (rstate) { this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); } - const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ''), _width: 500, _height: 500 }, docid); + const target = (docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500 }, docid); DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, 'portal to:portal from', undefined); const fstate = this.TextBox.EditorView?.state; @@ -275,7 +274,7 @@ export class RichTextRules { const num = value.match(/^[0-9.]$/); this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; } - const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid, hideKey: true }); return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); }), diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx index 01acc3de9..1fe6d822b 100644 --- a/src/client/views/nodes/formattedText/SummaryView.tsx +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -1,6 +1,6 @@ import { TextSelection } from 'prosemirror-state'; import { Fragment, Node, Slice } from 'prosemirror-model'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import React = require('react'); // an elidable textblock that collapses when its '<-' is clicked and expands when its '...' anchor is clicked. @@ -9,6 +9,7 @@ import React = require('react'); // method instead of changing prosemirror's text when the expand/elide buttons are clicked. export class SummaryView { dom: HTMLSpanElement; // container for label and value + root: any; constructor(node: any, view: any, getPos: any) { const self = this; @@ -35,13 +36,13 @@ export class SummaryView { return js.apply(this, arguments); }; - ReactDOM.render(, this.dom); - (this as any).dom = this.dom; + this.root = ReactDOM.createRoot(this.dom); + this.root.render(); } className = (visible: boolean) => 'formattedTextBox-summarizer' + (visible ? '' : '-collapsed'); destroy() { - ReactDOM.unmountComponentAtNode(this.dom); + // ReactDOM.unmountComponentAtNode(this.dom); } selectNode() {} diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 66d747bf7..aa2475dca 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -263,6 +263,7 @@ export const nodes: { [index: string]: NodeSpec } = { fieldKey: { default: '' }, docid: { default: '' }, hideKey: { default: false }, + editable: { default: true }, }, group: 'inline', draggable: false, diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 822af6a68..470aa3eb1 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -27,14 +27,14 @@ opacity: unset; mix-blend-mode: multiply; // bcz: makes text fuzzy! - span { - padding-right: 5px; - padding-bottom: 4px; - } + // span { + // padding-right: 5px; + // padding-bottom: 4px; + // } } .textLayer ::selection { - background: #ACCEF7; + background: #accef7; } // should match the backgroundColor in createAnnotation() @@ -111,4 +111,4 @@ .pdfViewerDash-interactive { pointer-events: all; -} \ No newline at end of file +} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 5c10c7cef..abc7336bd 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -79,6 +79,8 @@ export class PDFViewer extends React.Component { private _initialScroll: Opt; private _forcedScroll = true; + selectionText = () => this._selectionText; + @observable isAnnotating = false; // key where data is stored @computed get allAnnotations() { @@ -358,7 +360,8 @@ export class PDFViewer extends React.Component { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; this.isAnnotating = true; - if (e.target && ((e.target as any).className.includes('endOfContent') || (e.target as any).parentElement.className !== 'textLayer')) { + const target = e.target as any; + if (e.target && (target.className.includes('endOfContent') || (target.parentElement.className !== 'textLayer' && target.parentElement.parentElement?.className !== 'textLayer'))) { this._textSelecting = false; document.addEventListener('pointermove', this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called } else { @@ -574,6 +577,7 @@ export class PDFViewer extends React.Component { docView={this.props.docViewPath().lastElement()} finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotations} + selectionText={this.selectionText} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} anchorMenuCrop={this._textSelecting ? undefined : this.crop} diff --git a/src/debug/Repl.tsx b/src/debug/Repl.tsx index 1b12e208c..b8081648f 100644 --- a/src/debug/Repl.tsx +++ b/src/debug/Repl.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import * as ReactDOM from 'react-dom'; +import * as ReactDOM from 'react-dom/client'; import { observer } from 'mobx-react'; import { observable, computed } from 'mobx'; import { CompileScript } from '../client/util/Scripting'; @@ -11,39 +11,40 @@ import { resolvedPorts } from '../client/util/CurrentUserUtils'; @observer class Repl extends React.Component { - @observable text: string = ""; + @observable text: string = ''; - @observable executedCommands: { command: string, result: any }[] = []; + @observable executedCommands: { command: string; result: any }[] = []; onChange = (e: React.ChangeEvent) => { this.text = e.target.value; - } + }; onKeyDown = (e: React.KeyboardEvent) => { - if (!e.ctrlKey && e.key === "Enter") { + if (!e.ctrlKey && e.key === 'Enter') { e.preventDefault(); const script = CompileScript(this.text, { - addReturn: true, typecheck: false, - params: { makeInterface: "any" } + addReturn: true, + typecheck: false, + params: { makeInterface: 'any' }, }); if (!script.compiled) { - this.executedCommands.push({ command: this.text, result: "Compile Error" }); + this.executedCommands.push({ command: this.text, result: 'Compile Error' }); } else { const result = script.run({ makeInterface }, e => this.executedCommands.push({ command: this.text, result: e.message || e })); result.success && this.executedCommands.push({ command: this.text, result: result.result }); } - this.text = ""; + this.text = ''; } - } + }; @computed get commands() { return this.executedCommands.map(command => { return ( -
+

{command.command}

{/*
{JSON.stringify(command.result, null, 2)}
*/} -
{command.result instanceof RefField || command.result instanceof ObjectField ? "object" : String(command.result)}
+
{command.result instanceof RefField || command.result instanceof ObjectField ? 'object' : String(command.result)}
); }); @@ -52,16 +53,14 @@ class Repl extends React.Component { render() { return (
-
- {this.commands} -
-