aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2021-11-30 23:37:49 -0500
committerbobzel <zzzman@gmail.com>2021-11-30 23:37:49 -0500
commit91247d583e5e4c7205a1ed764dd0e3a12af3be25 (patch)
tree57ab31e582d1af356242c1805b7d4d27e91970ac
parentf313cfa5ae644eadb57d936bc81bd355e0c88e17 (diff)
fixed warnings/errors. added inkingStroke comments. need to double-click now to add a point to an ink stroke.
-rw-r--r--src/client/util/bezierFit.ts242
-rw-r--r--src/client/views/DocumentDecorations.tsx8
-rw-r--r--src/client/views/GlobalKeyHandler.ts2
-rw-r--r--src/client/views/InkControlPtHandles.tsx34
-rw-r--r--src/client/views/InkStroke.scss5
-rw-r--r--src/client/views/InkStrokeProperties.ts15
-rw-r--r--src/client/views/InkTangentHandles.tsx28
-rw-r--r--src/client/views/InkingStroke.tsx174
-rw-r--r--src/client/views/MainView.tsx1
-rw-r--r--src/client/views/PropertiesView.tsx21
-rw-r--r--src/client/views/StyleProvider.tsx2
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx2
-rw-r--r--src/client/views/search/SearchBox.tsx18
13 files changed, 269 insertions, 283 deletions
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 57c6dbbde..784bb2e18 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,5 +1,4 @@
import { Point } from "../../pen-gestures/ndollar";
-import { max } from "lodash";
class SmartRect {
minx: number = 0;
@@ -21,45 +20,38 @@ class SmartRect {
public ContainsPercentage(other: SmartRect, axis: Point) {
var ret = 0;
- var minx = Math.max(other.TopLeft.X * axis.X + other.TopLeft.Y * axis.Y, this.TopLeft.X * axis.X + this.TopLeft.Y * axis.Y);
- var maxx = Math.max(other.BotRight.X * axis.X + other.BotRight.Y * axis.Y, this.BotRight.X * axis.X + this.BotRight.Y * axis.Y);
- ret = maxx > minx ? (maxx - minx) / (axis == new Point(1, 0) ? other.Width : other.Height) : 0;
+ const minx = Math.max(other.TopLeft.X * axis.X + other.TopLeft.Y * axis.Y, this.TopLeft.X * axis.X + this.TopLeft.Y * axis.Y);
+ const maxx = Math.max(other.BotRight.X * axis.X + other.BotRight.Y * axis.Y, this.BotRight.X * axis.X + this.BotRight.Y * axis.Y);
+ ret = maxx > minx ? (maxx - minx) / (axis === new Point(1, 0) ? other.Width : other.Height) : 0;
return ret;
}
public static Bounds(p: Point[]) {
- var r = new SmartRect();
+ const r = new SmartRect();
if (p.length > 0) {
r.minx = p[0].X; // These are the most likely to be extremal
r.maxx = p.lastElement().X;
r.miny = p[0].Y;
r.maxy = p.lastElement().Y;
- if (r.minx > r.maxx) {
- var tmp = r.minx;
- r.minx = r.maxx;
- r.maxx = tmp;
- }
- if (r.miny > r.maxy) {
- var tmp = r.miny;
- r.miny = r.maxy;
- r.maxy = tmp;
- }
+ if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
+ if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny];
- for (var pt of p) {
- if (pt.X < r.minx)
+ for (const pt of p) {
+ if (pt.X < r.minx) {
r.minx = pt.X;
- else if (pt.X > r.maxx)
+ } else if (pt.X > r.maxx) {
r.maxx = pt.X;
-
- if (pt.Y < r.miny)
+ }
+ if (pt.Y < r.miny) {
r.miny = pt.Y;
- else if (pt.Y > r.maxy)
+ } else if (pt.Y > r.maxy) {
r.maxy = pt.Y;
+ }
}
}
return r;
}
-};
+}
function Normalize(p: Point) {
const len = Math.sqrt(p.X * p.X + p.Y * p.Y);
@@ -67,7 +59,7 @@ function Normalize(p: Point) {
}
function ReparameterizeBezier(d: Point[], first: number, last: number, u: number[], bezCurve: Point[]) {
- var uPrime = new Array<number>(last - first + 1); // New parameter values
+ const uPrime = new Array<number>(last - first + 1); // New parameter values
for (var i = first; i <= last; i++) {
uPrime[i - first] = NewtonRaphsonRootFind(bezCurve, d[i], u[i - first]);
@@ -76,36 +68,36 @@ function ReparameterizeBezier(d: Point[], first: number, last: number, u: number
}
function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Point[], u: number[]) {
var maxError = 0; // Maximum error
-
var splitPoint2D = (last - first + 1) / 2;
for (var i = first + 1; i < last; i++) {
- var P = [0, 0]; // point on curve
+ const P = [0, 0]; // point on curve
EvalBezierFast(bezCurve, u[i - first], P);
- var dx = P[0] - d[i].X;// offset from point to curve
- var dy = P[1] - d[i].Y;
- var dist = Math.sqrt(dx * dx + dy * dy); // Current error
+ const dx = P[0] - d[i].X;// offset from point to curve
+ const dy = P[1] - d[i].Y;
+ const dist = Math.sqrt(dx * dx + dy * dy); // Current error
if (dist >= maxError) {
maxError = dist;
- if (splitPoint2D)
+ if (splitPoint2D) {
splitPoint2D = i;
+ }
}
}
return { maxError, splitPoint2D };
}
function ChordLengthParameterize(d: Point[], first: number, last: number) {
- var u = new Array<number>(last - first + 1);// Parameterization
+ const u = new Array<number>(last - first + 1);// Parameterization
var prev = 0.0;
u[0] = prev;
for (var i = first + 1; i <= last; i++) {
- var lastd = d[i - 1];
- var curd = d[i];
- var dx = lastd.X - curd.X;
- var dy = lastd.Y - curd.Y;
+ const lastd = d[i - 1];
+ const curd = d[i];
+ const dx = lastd.X - curd.X;
+ const dy = lastd.Y - curd.Y;
prev = u[i - first] = prev + Math.sqrt(dx * dx + dy * dy);
}
- var ulastfirst = u[last - first];
+ const ulastfirst = u[last - first];
for (var i = first + 1; i <= last; i++) {
u[i - first] /= ulastfirst;
}
@@ -116,12 +108,12 @@ function ChordLengthParameterize(d: Point[], first: number, last: number) {
* B0, B1, B2, B3 :
* Bezier multipliers
*/
-function B0(u: number) { var tmp = 1.0 - u; return tmp * tmp * tmp; }
-function B1(u: number) { var tmp = 1.0 - u; return 3 * u * tmp * tmp; }
-function B2(u: number) { var tmp = 1.0 - u; return 3 * u * u * tmp; }
+function B0(u: number) { const tmp = 1.0 - u; return tmp * tmp * tmp; }
+function B1(u: number) { const tmp = 1.0 - u; return 3 * u * tmp * tmp; }
+function B2(u: number) { const tmp = 1.0 - u; return 3 * u * u * tmp; }
function B3(u: number) { return u * u * u; }
function bounds(p: Point[]) {
- var r = new SmartRect(p[0].X, p[0].Y, p[3].X, p[3].Y); // These are the most likely to be extremal
+ const r = new SmartRect(p[0].X, p[0].Y, p[3].X, p[3].Y); // These are the most likely to be extremal
if (r.minx > r.maxx) (r.minx, r.maxx);
if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny]; // swap min & max
@@ -137,10 +129,9 @@ function bounds(p: Point[]) {
}
-
function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
- var sz = 4;
- var Vtemp = new Array<Array<Point>>(4);
+ const sz = 4;
+ const Vtemp = new Array<Array<Point>>(4);
for (var i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
/* Copy control points */
@@ -153,8 +144,8 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
/* Triangle computation */
for (var i = 1; i < sz; i++) {
for (var j = 0; j < sz - i; j++) {
- var a = Vtemp[i - 1][j];
- var b = Vtemp[i - 1][j + 1];
+ const a = Vtemp[i - 1][j];
+ const b = Vtemp[i - 1][j + 1];
Vtemp[i][j].X = b.X * t + a.X * (1 - t);
Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t); // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
}
@@ -214,14 +205,14 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
*/
function recursively_intersect(a: Point[], t0: number, t1: number, deptha: number, b: Point[], u0: number, u1: number, depthb: number, parameters: number[][]) {
if (deptha > 0) {
- var a1 = new Array<Point>(4), a2 = new Array<Point>(4);
+ const a1 = new Array<Point>(4), a2 = new Array<Point>(4);
splitCubic(a, 0.5, a1, a2);
- var tmid = (t0 + t1) * 0.5;
+ const tmid = (t0 + t1) * 0.5;
deptha--;
if (depthb > 0) {
- var b1 = new Array<Point>(4), b2 = new Array<Point>(4);
+ const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
splitCubic(b, 0.5, b1, b2);
- var umid = (u0 + u1) * 0.5;
+ const umid = (u0 + u1) * 0.5;
depthb--;
if (SmartRect.Intersect(bounds(a1), bounds(b1))) {
recursively_intersect(a1, t0, tmid, deptha, b1, u0, umid, depthb, parameters);
@@ -247,9 +238,9 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
}
else {
if (depthb > 0) {
- var b1 = new Array<Point>(4), b2 = new Array<Point>(4);
+ const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
splitCubic(b, 0.5, b1, b2);
- var umid = (u0 + u1) * 0.5;
+ const umid = (u0 + u1) * 0.5;
depthb--;
if (SmartRect.Intersect(bounds(a), bounds(b1))) {
recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
@@ -260,20 +251,20 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
}
else // Both segments are fully subdivided; now do line segments
{
- var xlk = a[3].X - a[0].X;
- var ylk = a[3].Y - a[0].Y;
- var xnm = b[3].X - b[0].X;
- var ynm = b[3].Y - b[0].Y;
- var xmk = b[0].X - a[0].X;
- var ymk = b[0].Y - a[0].Y;
- var det = xnm * ylk - ynm * xlk;
- if (1.0 + det == 1.0) {
+ const xlk = a[3].X - a[0].X;
+ const ylk = a[3].Y - a[0].Y;
+ const xnm = b[3].X - b[0].X;
+ const ynm = b[3].Y - b[0].Y;
+ const xmk = b[0].X - a[0].X;
+ const ymk = b[0].Y - a[0].Y;
+ const det = xnm * ylk - ynm * xlk;
+ if (1.0 + det === 1.0) {
return;
}
else {
- var detinv = 1.0 / det;
- var s = (xnm * ymk - ynm * xmk) * detinv;
- var t = (xlk * ymk - ylk * xmk) * detinv;
+ const detinv = 1.0 / det;
+ const s = (xnm * ymk - ynm * xmk) * detinv;
+ const t = (xlk * ymk - ylk * xmk) * detinv;
if ((s < 0.0) || (s > 1.0) || (t < 0.0) || (t > 1.0) || Number.isNaN(s) || Number.isNaN(t)) {
return;
}
@@ -296,7 +287,7 @@ function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
return;
}
- var Vtemp = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)]; // Local copy of control points
+ const Vtemp = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)]; // Local copy of control points
/* Copy array */
for (var i = 0; i <= degree; i++) {
@@ -317,14 +308,11 @@ function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
}
function EvalBezierFast(p: Point[], t: number, result: number[]) {
- var n = 3;
- var u: number, bc: number, tn: number, tmpX: number, tmpY: number;
- u = 1.0 - t;
- bc = 1;
- tn = 1;
-
- tmpX = p[0].X * u;
- tmpY = p[0].Y * u;
+ const n = 3;
+ const u = 1.0 - t;
+ var bc = 1, tn = 1;
+ var tmpX = p[0].X * u;
+ var tmpY = p[0].Y * u;
tn = tn * t;
bc = bc * (n - 1 + 1) / 1;
tmpX = (tmpX + tn * bc * p[1].X) * u;
@@ -342,22 +330,21 @@ function EvalBezierFast(p: Point[], t: number, result: number[]) {
*Approximate unit tangents at endpoints and "center" of digitized curve
*/
function ComputeLeftTangent(d: Point[], end: number) {
- var use = 1;
- var tHat1 = new Point(d[end + use].X - d[end].X, d[end + use].Y - d[end].Y);
+ const use = 1;
+ const tHat1 = new Point(d[end + use].X - d[end].X, d[end + use].Y - d[end].Y);
return Normalize(tHat1);
}
function ComputeRightTangent(d: Point[], end: number) {
- var available = end;
- var use = 1;
- var tHat2 = new Point(d[end - use].X - d[end].X, d[end - use].Y - d[end].Y);
+ const use = 1;
+ const tHat2 = new Point(d[end - use].X - d[end].X, d[end - use].Y - d[end].Y);
return Normalize(tHat2);
}
function ComputeCenterTangent(d: Point[], center: number) {
- if (center == 0) {
+ if (center === 0) {
return ComputeLeftTangent(d, center);
}
- var V1 = ComputeLeftTangent(d, center); // d[center] - d[center-1];
- var V2 = ComputeRightTangent(d, center); // d[center] - d[center + 1];
+ const V1 = ComputeLeftTangent(d, center); // d[center] - d[center-1];
+ const V2 = ComputeRightTangent(d, center); // d[center] - d[center + 1];
var tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
if (tHatCenter === new Point(0, 0)) {
tHatCenter = new Point(-V1.Y, -V1.X);// V1.Perp();
@@ -365,15 +352,15 @@ function ComputeCenterTangent(d: Point[], center: number) {
return Normalize(tHatCenter);
}
function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[], tHat1: Point, tHat2: Point, result: Point[] /* must be prealloacted to size 4 */) {
- var nPts = last - first + 1; // Number of pts in sub-curve
- var Ax = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
- var Ay = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
+ const nPts = last - first + 1; // Number of pts in sub-curve
+ const Ax = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
+ const Ay = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
/* Compute the A's */
for (var i = 0; i < nPts; i++) {
- var uprime = uPrime[i];
- var b1 = B1(uprime);
- var b2 = B2(uprime);
+ const uprime = uPrime[i];
+ const b1 = B1(uprime);
+ const b2 = B2(uprime);
Ax[i] = tHat1.X * b1;
Ay[i] = tHat1.Y * b1;
Ax[i + 1 * nPts] = tHat2.X * b2;
@@ -381,44 +368,41 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
}
/* Create the C and X matrices */
- var C = [[0, 0], [0, 0]];
- var df = d[first];
- var dl = d[last];
+ const C = [[0, 0], [0, 0]];
+ const df = d[first];
+ const dl = d[last];
- var X = [0, 0]; // Matrix X
+ const X = [0, 0]; // Matrix X
for (var i = 0; i < nPts; i++) {
C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i]; //A[i+0*nPts].Dot(A[i+0*nPts]);
C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts];//A[i+0*nPts].Dot(A[i+1*nPts]);
C[1][0] = C[0][1];
C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts];// A[i+1*nPts].Dot(A[i+1*nPts]);
- var uprime = uPrime[i];
- var b0plb1 = B0(uprime) + B1(uprime);
- var b2plb3 = B2(uprime) + B3(uprime);
- var df1 = d[first + i];
- var tmpX = df1.X - (df.X * b0plb1 + (dl.X * b2plb3));
- var tmpY = df1.Y - (df.Y * b0plb1 + (dl.Y * b2plb3));
+ const uprime = uPrime[i];
+ const b0plb1 = B0(uprime) + B1(uprime);
+ const b2plb3 = B2(uprime) + B3(uprime);
+ const df1 = d[first + i];
+ const tmpX = df1.X - (df.X * b0plb1 + (dl.X * b2plb3));
+ const tmpY = df1.Y - (df.Y * b0plb1 + (dl.Y * b2plb3));
X[0] += Ax[i] * tmpX + Ay[i] * tmpY; // A[i+0*nPts].Dot(tmp)
X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; //A[i+1*nPts].Dot(tmp)
}
/* Compute the determinants of C and X */
- var det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1];
- var det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
- var det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
+ const det_C0_C1 = (C[0][0] * C[1][1] - C[1][0] * C[0][1]) || (C[0][0] * C[1][1]) * 10e-12;
+ const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
+ const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
/* Finally, derive alpha values */
- if (det_C0_C1 == 0.0) {
- det_C0_C1 = (C[0][0] * C[1][1]) * 10e-12;
- }
- var alpha_l = (det_C0_C1 == 0) ? 0.0 : det_X_C1 / det_C0_C1;
- var alpha_r = (det_C0_C1 == 0) ? 0.0 : det_C0_X / det_C0_C1;
+ var alpha_l = (det_C0_C1 === 0) ? 0.0 : det_X_C1 / det_C0_C1;
+ var alpha_r = (det_C0_C1 === 0) ? 0.0 : det_C0_X / det_C0_C1;
/* If alpha negative, use the Wu/Barsky heuristic (see text) */
/* (if alpha is 0, you get coincident control points that lead to
* divide by zero in any subsequent NewtonRaphsonRootFind() call. */
- var segLength = Math.sqrt((df.X - dl.X) * (df.X - dl.X) + (df.Y - dl.Y) * (df.Y - dl.Y));
- var epsilon = 1.0e-6 * segLength;
+ const segLength = Math.sqrt((df.X - dl.X) * (df.X - dl.X) + (df.Y - dl.Y) * (df.Y - dl.Y));
+ const epsilon = 1.0e-6 * segLength;
if (alpha_l < epsilon || alpha_r < epsilon) {
/* fall back on standard (probably inaccurate) formula, and subdivide further if needed. */
alpha_l = alpha_r = segLength / 3.0;
@@ -432,15 +416,15 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
result[3] = dl;
result[1] = new Point(df.X + (tHat1.X * alpha_l), df.Y + (tHat1.Y * alpha_l));
result[2] = new Point(dl.X + (tHat2.X * alpha_r), dl.Y + (tHat2.Y * alpha_r));
-
}
+
/*
* NewtonRaphsonRootFind :
* Use Newton-Raphson iteration to find better root.
*/
function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
- var Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)], Q2 = [new Point(0, 0), new Point(0, 0)]; // Q' and Q''
- var Q_u = [0, 0], Q1_u = [0, 0], Q2_u = [0, 0]; //u evaluated at Q, Q', & Q''
+ const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)], Q2 = [new Point(0, 0), new Point(0, 0)]; // Q' and Q''
+ const Q_u = [0, 0], Q1_u = [0, 0], Q2_u = [0, 0]; //u evaluated at Q, Q', & Q''
/* Compute Q(u) */
var uPrime: number; // Improved u
@@ -463,25 +447,24 @@ function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
EvalBezier(Q2, 1, u, Q2_u);
/* Compute f(u)/f'(u) */
- var numerator = (Q_u[0] - P.X) * (Q1_u[0]) + (Q_u[1] - P.Y) * (Q1_u[1]);
- var denominator = (Q1_u[0]) * (Q1_u[0]) + (Q1_u[1]) * (Q1_u[1]) + (Q_u[0] - P.X) * (Q2_u[0]) + (Q_u[1] - P.Y) * (Q2_u[1]);
- if (denominator == 0.0)
+ const numerator = (Q_u[0] - P.X) * (Q1_u[0]) + (Q_u[1] - P.Y) * (Q1_u[1]);
+ const denominator = (Q1_u[0]) * (Q1_u[0]) + (Q1_u[1]) * (Q1_u[1]) + (Q_u[0] - P.X) * (Q2_u[0]) + (Q_u[1] - P.Y) * (Q2_u[1]);
+ if (denominator === 0.0) {
uPrime = u;
- else uPrime = u - (numerator / denominator);/* u = u - f(u)/f'(u) */
+ } else uPrime = u - (numerator / denominator);/* u = u - f(u)/f'(u) */
return uPrime;
}
function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2: Point, error: number, result: Point[]) {
- var bezCurve = new Array<Point>(4); // Control points of fitted Bezier curve
- var splitPoint2D: number; // Point2D to split point set at
- var maxIterations = 4; // Max times to try iterating
+ const bezCurve = new Array<Point>(4); // Control points of fitted Bezier curve
+ const maxIterations = 4; // Max times to try iterating
- var iterationError = error * error; // Error below which you try iterating
- var nPts = last - first + 1; // Number of points in subset
+ const iterationError = error * error; // Error below which you try iterating
+ const nPts = last - first + 1; // Number of points in subset
/* Use heuristic if region only has two points in it */
- if (nPts == 2) {
- var dist = Math.sqrt((d[first].X - d[last].X) * (d[first].X - d[last].X) + (d[first].Y - d[last].Y) * (d[first].Y - d[last].Y)) / 3;
+ if (nPts === 2) {
+ const dist = Math.sqrt((d[first].X - d[last].X) * (d[first].X - d[last].X) + (d[first].Y - d[last].Y) * (d[first].Y - d[last].Y)) / 3;
bezCurve[0] = d[first];
bezCurve[3] = d[last];
@@ -499,7 +482,7 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
GenerateBezier(d, first, last, u, tHat1, tHat2, bezCurve);
/* Find max deviation of points to fitted curve */
- var { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u); // Maximum fitting error
+ const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u); // Maximum fitting error
if (maxError < Math.abs(error)) {
result.push(bezCurve[1]);
result.push(bezCurve[2]);
@@ -511,9 +494,9 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
/* and iteration */
if (maxError < iterationError) {
for (var i = 0; i < maxIterations; i++) {
- var uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); // Improved parameter values
+ const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); // Improved parameter values
GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve);
- var { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, uPrime);
+ const { maxError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
if (maxError < error) {
result.push(bezCurve[1]);
result.push(bezCurve[2]);
@@ -525,16 +508,15 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
}
/* Fitting failed -- split at max error point and fit recursively */
- var tHatCenter = splitPoint2D >= last - 1 ? ComputeRightTangent(d, splitPoint2D) : ComputeCenterTangent(d, splitPoint2D);
+ const tHatCenter = splitPoint2D >= last - 1 ? ComputeRightTangent(d, splitPoint2D) : ComputeCenterTangent(d, splitPoint2D);
FitCubic(d, first, splitPoint2D, tHat1, tHatCenter, error, result);
- var negThatCenter = new Point(-tHatCenter.X, -tHatCenter.Y);
+ const negThatCenter = new Point(-tHatCenter.X, -tHatCenter.Y);
FitCubic(d, splitPoint2D, last, negThatCenter, tHat2, error, result);
}
export function FitCurve(d: Point[], error: number) {
- var tHat1 = ComputeLeftTangent(d, 0); // Unit tangent vectors at endpoints
- var tHat2 = ComputeRightTangent(d, d.length - 1);
- var result: Point[] = [];
- result.push(d[0]);
+ const tHat1 = ComputeLeftTangent(d, 0); // Unit tangent vectors at endpoints
+ const tHat2 = ComputeRightTangent(d, d.length - 1);
+ const result = [d[0]];
FitCubic(d, 0, d.length - 1, tHat1, tHat2, error, result);
return result;
}
@@ -543,14 +525,14 @@ export function FitOneCurve(d: Point[], tHat1?: Point, tHat2?: Point) {
tHat2 = tHat2 ?? Normalize(ComputeRightTangent(d, d.length - 1));
tHat2 = new Point(-tHat2.X, -tHat2.Y);
var u = ChordLengthParameterize(d, 0, d.length - 1);
- var bezCurveCtrls = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)];
+ const bezCurveCtrls = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)];
GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls); /* Find max deviation of points to fitted curve */
var finalCtrls = bezCurveCtrls.slice();
var { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
for (var i = 0; i < 10; i++) {
- var uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls); // Improved parameter values
+ const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls); // Improved parameter values
GenerateBezier(d, 0, d.length - 1, uPrime, tHat1, tHat2, bezCurveCtrls);
- var { maxError } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, uPrime);
+ const { maxError } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, uPrime);
if (maxError < error) {
error = maxError;
finalCtrls = bezCurveCtrls.slice();
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 1c0b1b995..7d3959eba 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -203,9 +203,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
const centerPoint = { X: (left + right) / 2, Y: (top + bottom) / 2 };
const previousPoint = { X: e.clientX, Y: e.clientY };
const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
- const angle = InkStrokeProperties.Instance?.angleChange(previousPoint, movedPoint, centerPoint);
+ const angle = InkStrokeProperties.Instance.angleChange(previousPoint, movedPoint, centerPoint);
const selectedInk = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK);
- angle && InkStrokeProperties.Instance?.rotateInk(selectedInk, -angle, pt);
+ angle && InkStrokeProperties.Instance.rotateInk(selectedInk, -angle, pt);
return false;
},
() => {
@@ -226,7 +226,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
this._inkDragDocs = DragManager.docsBeingDragged
.filter(doc => doc.type === DocumentType.INK)
.map(doc => {
- if (InkStrokeProperties.Instance?._lock) {
+ if (InkStrokeProperties.Instance._lock) {
Doc.SetNativeHeight(doc, NumCast(doc._height));
Doc.SetNativeWidth(doc, NumCast(doc._width));
}
@@ -249,7 +249,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
const first = SelectionManager.Views()[0];
let thisPt = { x: e.clientX - this._offX, y: e.clientY - this._offY };
var fixedAspect = Doc.NativeAspect(first.layoutDoc);
- InkStrokeProperties.Instance?._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
+ InkStrokeProperties.Instance._lock && SelectionManager.Views().filter(dv => dv.rootDoc.type === DocumentType.INK)
.forEach(dv => fixedAspect = Doc.NativeAspect(dv.rootDoc));
const resizeHdl = this._resizeHdlId.split(" ")[0];
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 364bf05e2..d5e0ed962 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -112,7 +112,7 @@ export class KeyManager {
case "escape":
DocumentLinksButton.StartLink = undefined;
DocumentLinksButton.StartLinkView = undefined;
- InkStrokeProperties.Instance && (InkStrokeProperties.Instance._controlButton = false);
+ InkStrokeProperties.Instance._controlButton = false;
CurrentUserUtils.SelectedTool = InkTool.None;
var doDeselect = true;
if (SnappingManager.GetIsDragging()) {
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index a91e74c44..76ce73b0d 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -44,23 +44,23 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
@action
onControlDown = (e: React.PointerEvent, controlIndex: number): void => {
const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen;
- if (InkStrokeProperties.Instance && ptFromScreen) {
+ if (ptFromScreen) {
const order = controlIndex % 4;
const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length;
const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length;
const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec("number"));
- const wasSelected = InkStrokeProperties.Instance?._currentPoint === controlIndex;
+ const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
setupMoveUpEvents(this, e,
action((e: PointerEvent, down: number[], delta: number[]) => {
if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
- InkStrokeProperties.Instance?.moveControlPtHandle(this.props.inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
+ InkStrokeProperties.Instance.moveControlPtHandle(this.props.inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
return false;
}),
action(() => {
if (this.controlUndo) {
- InkStrokeProperties.Instance?.snapControl(this.props.inkView, controlIndex);
+ InkStrokeProperties.Instance.snapControl(this.props.inkView, controlIndex);
}
this.controlUndo?.end();
this.controlUndo = undefined;
@@ -75,11 +75,11 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
} else {
if (brokenIndices?.includes(equivIndex)) {
if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
- InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB);
+ InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB);
}
if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
- InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB);
+ InkStrokeProperties.Instance.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB);
}
}
this.controlUndo?.end();
@@ -102,7 +102,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
@action
onDelete = (e: KeyboardEvent) => {
if (["-", "Backspace", "Delete"].includes(e.key)) {
- InkStrokeProperties.Instance?.deletePoints(this.props.inkView, e.shiftKey);
+ InkStrokeProperties.Instance.deletePoints(this.props.inkView, e.shiftKey);
e.stopPropagation();
}
}
@@ -111,11 +111,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
* Changes the current selected control point.
*/
@action
- changeCurrPoint = (i: number) => {
- if (InkStrokeProperties.Instance) {
- InkStrokeProperties.Instance._currentPoint = i;
- }
- }
+ changeCurrPoint = (i: number) => InkStrokeProperties.Instance._currentPoint = i
render() {
// Accessing the current ink's data and extracting all control points.
@@ -202,9 +198,9 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
const scaling = v2len / v1len;
const v1n = { x: v1.x / v1len, y: v1.y / v1len };
const v2n = { x: v2.x / v2len, y: v2.y / v2len };
- const angle = Math.acos(v1n.x * v2n.x + v1n.y * v2n.y) * Math.sign(v1.x * v2.y - v2.x * v1.y)
- InkStrokeProperties.Instance?.stretchInk([this.props.inkView], scaling, { x: p2().X, y: p2().Y }, v1n);
- InkStrokeProperties.Instance?.rotateInk([this.props.inkView], angle, { x: p2().X, y: p2().Y });
+ const angle = Math.acos(v1n.x * v2n.x + v1n.y * v2n.y) * Math.sign(v1.x * v2.y - v2.x * v1.y);
+ InkStrokeProperties.Instance.stretchInk([this.props.inkView], scaling, { x: p2().X, y: p2().Y }, v1n);
+ InkStrokeProperties.Instance.rotateInk([this.props.inkView], angle, { x: p2().X, y: p2().Y });
return false;
}, action(() => {
this.controlUndo?.end();
@@ -214,7 +210,7 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
}
render() {
- const hdl = (pt: PointData, dragFunc: (e: React.PointerEvent) => void) => <circle key={"npt"}
+ const hdl = (key: string, pt: PointData, dragFunc: (e: React.PointerEvent) => void) => <circle key={key}
cx={pt.X}
cy={pt.Y}
r={this.props.screenSpaceLineWidth * 2}
@@ -223,12 +219,12 @@ export class InkEndPtHandles extends React.Component<InkEndProps> {
strokeWidth={0}
onPointerLeave={action(() => this._overStart = false)}
onPointerEnter={action(() => this._overStart = true)}
- onPointerDown={e => dragFunc(e)}
+ onPointerDown={dragFunc}
pointerEvents="all"
/>;
return (<svg>
- {hdl(this.props.startPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.startPt, () => this.props.endPt))}
- {hdl(this.props.endPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.endPt, () => this.props.startPt))}
+ {hdl("start", this.props.startPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.startPt, () => this.props.endPt))}
+ {hdl("end", this.props.endPt, (e: React.PointerEvent) => this.dragRotate(e, () => this.props.endPt, () => this.props.startPt))}
</svg>
);
}
diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss
index 2127826b4..664f2448b 100644
--- a/src/client/views/InkStroke.scss
+++ b/src/client/views/InkStroke.scss
@@ -14,6 +14,9 @@
}
.inkStroke-wrapper {
+ display: flex;
+ align-items: center;
+ height: 100%;
.inkStroke {
mix-blend-mode: multiply;
stroke-linejoin: round;
@@ -22,7 +25,7 @@
transform-origin: top left;
width: 100%;
height: 100%;
-
+ pointer-events: none;
svg:not(:root) {
overflow: visible !important;
}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 4808bbc77..7ab631b03 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -15,14 +15,15 @@ import { InkingStroke } from "./InkingStroke";
import { DocumentView } from "./nodes/DocumentView";
export class InkStrokeProperties {
- static Instance: InkStrokeProperties | undefined;
+ static _Instance: InkStrokeProperties | undefined;
+ public static get Instance() { return this._Instance || new InkStrokeProperties(); }
@observable _lock = false;
@observable _controlButton = false;
@observable _currentPoint = -1;
constructor() {
- InkStrokeProperties.Instance = this;
+ InkStrokeProperties._Instance = this;
reaction(() => this._controlButton, button => button && (CurrentUserUtils.SelectedTool = InkTool.None));
reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false));
}
@@ -150,15 +151,15 @@ export class InkStrokeProperties {
} else {
const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4;
const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8));
- var samples: Point[] = [];
+ const samples: Point[] = [];
var startDir = { x: 0, y: 0 };
var endDir = { x: 0, y: 0 };
for (var i = 0; i < splicedPoints.length / 4; i++) {
- var bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
+ const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y })));
if (i === 0) startDir = bez.derivative(0);
if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1);
for (var t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) {
- var pt = bez.compute(t);
+ const pt = bez.compute(t);
samples.push(new Point(pt.x, pt.y));
}
}
@@ -209,12 +210,12 @@ export class InkStrokeProperties {
const ptFromScreen = view.ComponentView?.ptFromScreen;
const ptToScreen = view.ComponentView?.ptToScreen;
return !ptToScreen || !ptFromScreen ? ink :
- ink.map(i => ptToScreen(i)).map(i => {
+ ink.map(ptToScreen).map(i => {
const pvec = { X: i.X - scrpt.x, Y: i.Y - scrpt.y };
const svec = pvec.X * scrVec.x * scaling + pvec.Y * scrVec.y * scaling;
const ovec = -pvec.X * scrVec.y + pvec.Y * (scrVec.x);
const newscrpt = { X: scrpt.x + svec * scrVec.x - ovec * scrVec.y, Y: scrpt.y + svec * scrVec.y + ovec * scrVec.x };
- const newpt = ptFromScreen!(newscrpt);
+ const newpt = ptFromScreen(newscrpt);
return newpt;
});
});
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index f88a20448..ab73e58a4 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -29,24 +29,23 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
* @param handleNum The index of the currently selected handle point.
*/
onHandleDown = (e: React.PointerEvent, handleIndex: number): void => {
- if (InkStrokeProperties.Instance) {
- var controlUndo: UndoManager.Batch | undefined;
- const screenScale = this.props.ScreenToLocalTransform().Scale;
- const order = handleIndex % 4;
- const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
- const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length;
- const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length;
- setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
+ var controlUndo: UndoManager.Batch | undefined;
+ const screenScale = this.props.ScreenToLocalTransform().Scale;
+ const order = handleIndex % 4;
+ const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3;
+ const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length;
+ const controlIndex = (order === 1 ? handleIndex - 1 : handleIndex + 2) % this.props.screenCtrlPoints.length;
+ setupMoveUpEvents(this, e,
+ (e: PointerEvent, down: number[], delta: number[]) => {
if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent");
if (e.altKey) this.onBreakTangent(controlIndex);
- InkStrokeProperties.Instance?.moveTangentHandle(this.props.inkView, -delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
+ InkStrokeProperties.Instance.moveTangentHandle(this.props.inkView, -delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
return false;
}, () => {
controlUndo?.end();
UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
}, emptyFunction
- );
- }
+ );
}
/**
@@ -66,9 +65,6 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
}
render() {
- const formatInstance = InkStrokeProperties.Instance;
- if (!formatInstance) return (null);
-
// Accessing the current ink's data and extracting all handle points and handle lines.
const data = this.props.screenCtrlPoints;
const tangentHandles: HandlePoint[] = [];
@@ -107,7 +103,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
onPointerDown={e => this.onHandleDown(e, pts.I)}
pointerEvents="all"
cursor="default"
- display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />
+ display={(pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint) ? "inherit" : "none"} />
</svg>)}
{tangentLines.map((pts, i) => {
const tangentLine = (x1: number, y1: number, x2: number, y2: number) =>
@@ -119,7 +115,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
stroke={Colors.MEDIUM_BLUE}
strokeDasharray={"1 1"}
strokeWidth={1}
- display={(pts.dot1 === formatInstance._currentPoint || pts.dot2 === formatInstance._currentPoint) ? "inherit" : "none"} />;
+ display={(pts.dot1 === InkStrokeProperties.Instance._currentPoint || pts.dot2 === InkStrokeProperties.Instance._currentPoint) ? "inherit" : "none"} />;
return <svg height="100" width="100" key={`line${i}`}>
{tangentLine(pts.X1, pts.Y1, pts.X2, pts.Y2)}
{tangentLine(pts.X2, pts.Y2, pts.X3, pts.Y3)}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 8ff080f81..0b3619b22 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,3 +1,25 @@
+/*
+ InkingStroke - a document that represents an individual vector stroke drawn as a Bezier curve (open or closed) and optionally filled.
+
+ The primary data is:
+ data - an InkField which is an array of PointData (X,Y values). The data is laid out as a sequence of simple bezier segments:
+ point 1, tangent pt 1, tangent pt 2, point 2, point 3, tangent pt 3, ... (Note that segment endpoints are duplicated ie Point2 = Point 3)
+ brokenIndices - an array of indexes into the data field where the incoming and outgoing tangents are not constrained to be equal
+ text - a text field that will be centered within a closed ink stroke
+ isInkMask - a flag that makes the ink stroke render as a mask over its collection where the stroke itself is mixBlendMode multiplied by
+ the underlying collection content, and everything outside the stroke is covered by a semi-opaque dark gray mask.
+
+ The coordinates of the ink data need to be mapped to the screen since ink points are not changed when the DocumentView is translated or scaled.
+ Thus the mapping can roughly be described by:
+ the Top/Left of the ink data (minus 1/2 the ink width) maps to the Top/Left of the DocumentView
+ the Width/Height of the ink data (minus the ink width) is scaled to the PanelWidth/PanelHeight of the documentView
+ NOTE: use ptToScreen() and ptFromScreen() to transform between ink and screen space
+
+ InkStrokes have a specialized 'componentUI' method that is called by MainView to render all of the interactive editing controls in
+ screen space (to avoid scaling artifacts)
+
+ Most of the operations that can be performed on an InkStroke (eg delete a point, rotate, stretch) are implemented in the InkStrokeProperties helper class
+*/
import React = require("react");
import { action, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
@@ -30,35 +52,32 @@ const InkDocument = makeInterface(documentSchema);
@observer
export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocument>(InkDocument) {
+ static readonly MaskDim = 50000; // choose a really big number to make sure mask fits over container (which in theory can be arbitrarily big)
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
- static readonly MaskDim = 50000;
public static IsClosed(inkData: InkData) {
return inkData && inkData.lastElement().X === inkData[0].X && inkData.lastElement().Y === inkData[0].Y;
}
- @observable private _properties?: InkStrokeProperties;
- _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
- _selDisposer: IReactionDisposer | undefined;
+ private _handledClick = false; // flag denoting whether ink stroke has handled a psuedo-click onPointerUp so that the real onClick event can be stopPropagated
+ private _selDisposer?: IReactionDisposer;
- @observable _nearestT: number | undefined;
- @observable _nearestSeg: number | undefined;
- @observable _nearestScrPt: { X: number, Y: number } | undefined;
- @observable _inkSamplePts: { X: number, Y: number }[] | undefined;
-
- constructor(props: FieldViewProps & InkDocument) {
- super(props);
-
- this._properties = InkStrokeProperties.Instance;
- }
+ @observable _nearestSeg?: number; // nearest Bezier segment along the ink stroke to the cursor (used for displaying the Add Point highlight)
+ @observable _nearestT?: number; // nearest t value within the nearest Bezier segment "
+ @observable _nearestScrPt?: { X: number, Y: number }; // nearst screen point on the ink stroke ""
componentDidMount() {
this.props.setContentView?.(this);
this._selDisposer = reaction(() => this.props.isSelected(), // react to stroke being deselected by turning off ink handles
- selected => !selected && this.toggleControlButton());
+ selected => !selected && (InkStrokeProperties.Instance._controlButton = false));
}
componentWillUnmount() {
this._selDisposer?.();
}
+ /**
+ * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space);
+ * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since
+ * the center of the ink stroke changes as the stroke is rotated.
+ */
getCenter = (xf: Transform) => {
const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
const angle = -NumCast(this.layoutDoc.rotation);
@@ -80,6 +99,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]);
}
+ /**
+ * Toggles whether the ink stroke is displayed as an overlay mask or as a regular stroke.
+ * When displayed as a mask, the stroke is rendered with mixBlendMode set to multiply so that the stroke will
+ * appear to illuminate what it covers up. At the same time, all pixels that are not under the stroke will be
+ * dimmed by a semi-opaque overlay mask.
+ */
public static toggleMask = action((inkDoc: Doc) => {
inkDoc.isInkMask = !inkDoc.isInkMask;
inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined;
@@ -88,70 +113,47 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
inkDoc._stayInCollection = inkDoc.isInkMask ? true : undefined;
});
/**
- * Handles the movement of the entire ink object when the user clicks and drags.
+ * Drags the a simple bezier segment of the stroke.
+ * Also adds a control point when double clicking on the stroke.
*/
@action
onPointerDown = (e: React.PointerEvent) => {
- const ptFromScreen = this.ptFromScreen;
this._handledClick = false;
- if (InkStrokeProperties.Instance && ptFromScreen) {
- const inkView = this.props.docViewPath().lastElement();
- const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
- const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
- (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
- (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
- const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
- const controlIndex = nearestSeg;
- const wasSelected = InkStrokeProperties.Instance?._currentPoint === controlIndex;
- var controlUndo: UndoManager.Batch | undefined;
- const isEditing = this._properties?._controlButton && this.props.isSelected();
- setupMoveUpEvents(this, e,
- !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => {
- if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
- const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] });
- const inkMoveStart = ptFromScreen({ X: 0, Y: 0 });
- InkStrokeProperties.Instance?.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
- InkStrokeProperties.Instance?.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
- return false;
- }),
- !isEditing ? returnFalse : action(() => {
- if (controlUndo) {
- InkStrokeProperties.Instance?.snapControl(inkView, controlIndex);
- InkStrokeProperties.Instance?.snapControl(inkView, controlIndex + 3);
- }
- controlUndo?.end();
- controlUndo = undefined;
- UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
- }),
- action((e: PointerEvent, doubleTap: boolean | undefined) => {
- doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
- if (doubleTap && this._properties) {
- this._properties._controlButton = true;
- InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1);
- this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
- } else if (isEditing) {
- this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
+ const inkView = this.props.docViewPath().lastElement();
+ const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData();
+ const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint(
+ (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2,
+ (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] }));
+ const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY });
+ const controlIndex = nearestSeg;
+ const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex;
+ var controlUndo: UndoManager.Batch | undefined;
+ const isEditing = InkStrokeProperties.Instance._controlButton && this.props.isSelected();
+ setupMoveUpEvents(this, e,
+ !isEditing ? returnFalse : action((e: PointerEvent, down: number[], delta: number[]) => {
+ if (!controlUndo) controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
+ const inkMoveEnd = this.ptFromScreen({ X: delta[0], Y: delta[1] });
+ const inkMoveStart = this.ptFromScreen({ X: 0, Y: 0 });
+ InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex);
+ InkStrokeProperties.Instance.moveControlPtHandle(inkView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex + 3);
+ return false;
+ }),
+ !isEditing ? returnFalse : action(() => {
+ controlUndo?.end();
+ controlUndo = undefined;
+ UndoManager.FilterBatches(["data", "x", "y", "width", "height"]);
+ }),
+ action((e: PointerEvent, doubleTap: boolean | undefined) => {
+ doubleTap = doubleTap || this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick;
+ if (doubleTap) {
+ InkStrokeProperties.Instance._controlButton = true;
+ InkStrokeProperties.Instance._currentPoint = -1;
+ this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
+ if (isEditing) {
+ this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
}
- }), isEditing, isEditing, action(() => wasSelected && InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1)));
- }
- }
-
- /**
- * Ensures the ink controls and handles aren't rendered when the current ink stroke is reselected.
- */
- @action
- toggleControlButton = () => {
- if (!this.props.isSelected() && this._properties) {
- this._properties._controlButton = false;
- }
- }
-
- @action
- checkHighlighter = () => {
- if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) {
- // this._previousColor = ActiveInkColor();
- SetActiveInkColor("rgba(245, 230, 95, 0.75)");
- }
+ }
+ }), isEditing, isEditing, action(() => wasSelected && (InkStrokeProperties.Instance._currentPoint = -1)));
}
ptFromScreen = (scrPt: { X: number, Y: number }) => {
@@ -173,13 +175,23 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
return { X: scrPt[0], Y: scrPt[1] };
}
+ /**
+ * Snaps a screen space point to this stroke, optionally skipping bezier segments indicated by 'excludeSegs'
+ * @param scrPt - the point to snap to this stroke
+ * @param excludeSegs - optional segments in this stroke to skip (this is used when dragging a point on the stroke and not wanting the drag point to snap to its neighboring segments)
+ *
+ * @returns the nearest ink space point on this stroke to the screen point AND the screen space distance from the snapped point to the nearest point
+ */
snapPt = (scrPt: { X: number, Y: number }, excludeSegs?: number[]) => {
const { inkData } = this.inkScaledData();
- const inkPt = this.ptFromScreen(scrPt);
- const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, inkPt, excludeSegs ?? []);
+ const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, this.ptFromScreen(scrPt), excludeSegs ?? []);
return { nearestPt, distance: distance * this.props.ScreenToLocalTransform().inverse().Scale };
}
+ /**
+ * extracts key features from the inkData, including: the data points, the ink width, the ink bounds (top,left, width, height), and the scale
+ * factor for converting between ink and screen space.
+ */
inkScaledData = () => {
const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? [];
const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1);
@@ -229,7 +241,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const startMarker = StrCast(this.layoutDoc.strokeStartMarker);
const endMarker = StrCast(this.layoutDoc.strokeEndMarker);
return SnappingManager.GetIsDragging() ? (null) :
- !this._properties?._controlButton ?
+ !InkStrokeProperties.Instance._controlButton ?
(!this.props.isSelected() || InkingStroke.IsClosed(inkData) ? (null) :
<div className="inkstroke-UI" style={{ clip: `rect(${boundsTop}px, 10000px, 10000px, ${boundsLeft}px)` }}>
<InkEndPtHandles
@@ -287,15 +299,11 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
false, downHdlr);
// Set of points rendered upon the ink that can be added if a user clicks on one.
- return <div className="inkStroke-wrapper" style={{ display: "flex", alignItems: "center", height: "100%" }}>
+ return <div className="inkStroke-wrapper">
<svg className="inkStroke"
style={{
- width: "100%",
- height: "100%",
- pointerEvents: "none",
transform: this.props.Document.isInkMask ? `translate(${InkingStroke.MaskDim / 2}px, ${InkingStroke.MaskDim / 2}px)` : undefined,
mixBlendMode: this.layoutDoc.tool === InkTool.Highlighter ? "multiply" : "unset",
- overflow: "visible",
cursor: this.props.isSelected() ? "default" : undefined
}}
onPointerLeave={action(e => this._nearestScrPt = undefined)}
@@ -305,7 +313,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
const cm = ContextMenu.Instance;
!Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" });
cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" });
- cm?.addItem({ description: "Edit Points", event: action(() => { if (this._properties) { this._properties._controlButton = !this._properties._controlButton; } }), icon: "paint-brush" });
+ cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" });
}}
>
{clickableLine(this.onPointerDown)}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 546b0e360..a9fea4a78 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -116,7 +116,6 @@ export class MainView extends React.Component {
}, 0);
setTimeout(() => ele.outerHTML = '', 1000);
}
- new InkStrokeProperties();
this._sidebarContent.proto = undefined;
if (!MainView.Live) {
DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition",
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 1083e0075..18d5f1642 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -535,16 +535,17 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
@computed
get controlPointsButton() {
- const formatInstance = InkStrokeProperties.Instance;
- return !formatInstance ? (null) : <div className="inking-button">
+ return <div className="inking-button">
<Tooltip title={<div className="dash-tooltip">{"Edit points"}</div>}>
- <div className="inking-button-points" onPointerDown={action(() => formatInstance._controlButton = !formatInstance._controlButton)} style={{ backgroundColor: formatInstance._controlButton ? "black" : "" }}>
+ <div className="inking-button-points"
+ style={{ backgroundColor: InkStrokeProperties.Instance._controlButton ? "black" : "" }}
+ onPointerDown={action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)} >
<FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
</div>
</Tooltip>
- <Tooltip title={<div className="dash-tooltip">{formatInstance._lock ? "Unlock ratio" : "Lock ratio"}</div>}>
- <div className="inking-button-lock" onPointerDown={action(() => formatInstance._lock = !formatInstance._lock)} >
- <FontAwesomeIcon icon={formatInstance._lock ? "lock" : "unlock"} color="white" size="lg" />
+ <Tooltip title={<div className="dash-tooltip">{InkStrokeProperties.Instance._lock ? "Unlock ratio" : "Lock ratio"}</div>}>
+ <div className="inking-button-lock" onPointerDown={action(() => InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock)} >
+ <FontAwesomeIcon icon={InkStrokeProperties.Instance._lock ? "lock" : "unlock"} color="white" size="lg" />
</div>
</Tooltip>
<Tooltip title={<div className="dash-tooltip">{"Rotate 90˚"}</div>}>
@@ -603,7 +604,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const oldX = NumCast(this.selectedDoc?.x);
const oldY = NumCast(this.selectedDoc?.y);
this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10));
- InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
+ InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
const doc = this.selectedDoc;
if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) {
const ink = Cast(doc.data, InkField)?.inkData;
@@ -625,7 +626,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
const oX = NumCast(this.selectedDoc?.x);
const oY = NumCast(this.selectedDoc?.y);
this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10));
- InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
+ InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
const docu = this.selectedDoc;
if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
const ink = Cast(docu.data, InkField)?.inkData;
@@ -663,12 +664,12 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
set shapeWid(value) {
const oldWidth = NumCast(this.selectedDoc?._width);
this.selectedDoc && (this.selectedDoc._width = Number(value));
- InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
+ InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
}
set shapeHgt(value) {
const oldHeight = NumCast(this.selectedDoc?._height);
this.selectedDoc && (this.selectedDoc._height = Number(value));
- InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
+ InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
}
@computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); }
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 8d8630907..f09d532ad 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -93,7 +93,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
case StyleProp.HideLinkButton: return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
case StyleProp.FontSize: return StrCast(doc?.[fieldKey + "fontSize"], StrCast(doc?.fontSize, StrCast(Doc.UserDoc().fontSize)));
- case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + "fontFamily"], StrCast(doc?.["fontFamily"], StrCast(Doc.UserDoc().fontFamily)));
+ case StyleProp.FontFamily: return StrCast(doc?.[fieldKey + "fontFamily"], StrCast(doc?.fontFamily, StrCast(Doc.UserDoc().fontFamily)));
case StyleProp.ShowTitle: return (doc && !doc.presentationTargetDoc &&
StrCast(doc._showTitle,
props?.showTitle?.() ||
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 9d0402075..72e84c141 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1146,7 +1146,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
if (startupText) {
dispatch(state.tr.insertText(startupText));
} else if (!FormattedTextBox.LiveTextUndo) {
- selectAll(this._editorView!.state, (tr) => {
+ selectAll(this._editorView.state, (tr) => {
this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: "center" })));
});
}
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index fe297782c..09cfb2077 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -251,7 +251,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
this._results.forEach((_, doc) => {
this._pageRanks.set(doc, 1.0 / this._results.size);
- if (Doc.GetProto(doc)[DirectLinksSym].size == 0) {
+ if (Doc.GetProto(doc)[DirectLinksSym].size === 0) {
this._linkedDocsOut.set(doc, new Set(this._results.keys()));
this._results.forEach((_, linkedDoc) => {
@@ -259,20 +259,20 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
});
}
else {
- let linkedDocSet: Set<Doc> = new Set();
+ const linkedDocSet = new Set<Doc>();
Doc.GetProto(doc)[DirectLinksSym].forEach((link) => {
- let d1 = link?.anchor1 as Doc;
- let d2 = link?.anchor2 as Doc;
- if (doc == d1 && this._results.has(d2)) {
+ const d1 = link?.anchor1 as Doc;
+ const d2 = link?.anchor2 as Doc;
+ if (doc === d1 && this._results.has(d2)) {
linkedDocSet.add(d2);
this._linkedDocsIn.get(d2)?.add(doc);
}
- else if (doc == d2 && this._results.has(d1)) {
+ else if (doc === d2 && this._results.has(d1)) {
linkedDocSet.add(d1);
this._linkedDocsIn.get(d1)?.add(doc);
}
- })
+ });
this._linkedDocsOut.set(doc, linkedDocSet);
}
@@ -291,7 +291,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
let converged = true;
const pageRankFromAll = (1 - DAMPENING_FACTOR) / this._results.size;
- let nextPageRanks: Map<Doc, number> = new Map<Doc, number>();
+ const nextPageRanks = new Map<Doc, number>();
this._results.forEach((_, doc) => {
let nextPageRank = pageRankFromAll;
@@ -397,7 +397,7 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps, SearchBoxDoc
const isLinkSearch: boolean = this.props.linkSearch;
- const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)) // sorted by page rank
+ const sortedResults = Array.from(this._results.entries()).sort((a, b) => (this._pageRanks.get(b[0]) ?? 0) - (this._pageRanks.get(a[0]) ?? 0)); // sorted by page rank
const resultsJSX = Array();