From 3381bbb0ef5160707513f4bbbe551ca551b64b0d Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 13 Nov 2021 10:38:53 -0500 Subject: change isContentActive to a tri-state to allow turning on/off and default - fixes issues with videobox and others so that content can be turned off reliably. added annotation overlay for treeViews for ppt like slides. lots of fixes to tree view to get layout to be more robust. --- .../views/nodes/formattedText/FormattedTextBox.tsx | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index aa53f751d..1170528df 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -123,7 +123,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @computed get sidebarWidthPercent() { return this._showSidebar ? "20%" : StrCast(this.layoutDoc._sidebarWidthPercent, "0%"); } @computed get sidebarColor() { return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + "-backgroundColor"], "#e4e4e4")); } - @computed get autoHeight() { return this.layoutDoc._autoHeight && !this.props.ignoreAutoHeight; } + @computed get autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._autoHeight) && !this.props.ignoreAutoHeight; } @computed get textHeight() { return NumCast(this.rootDoc[this.fieldKey + "-height"]); } @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); } @computed get sidebarHeight() { return !this.sidebarWidth() ? 0 : NumCast(this.rootDoc[this.SidebarKey + "-height"]); } @@ -1565,7 +1565,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp setHeight={this.setSidebarHeight} fitContentsToDoc={this.fitToBox} noSidebar={true} - fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : this.SidebarKey} />; + fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />; }; return
@@ -1581,10 +1581,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); const minimal = this.props.ignoreAutoHeight; - const margins = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); - const selPad = Math.min(margins, 10); - const padding = Math.max(margins + ((selected && !this.layoutDoc._singleLine) || minimal ? -selPad : 0), 0); - const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : ""; + const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0); + const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); + const selPad = ((selected && !this.layoutDoc._singleLine) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0); + const selPaddingClass = selected && !this.layoutDoc._singleLine && paddingY >= 10 ? "-selected" : ""; const styleFromString = this.styleFromLayoutString(scale); // this converts any expressions in the format string to style props. e.g., return (styleFromString?.height === "0px" ? (null) :
-- cgit v1.2.3-70-g09d2 From 4ee47eae8735adf4c543c0de4859a09dee10cbf0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 15 Nov 2021 12:25:31 -0500 Subject: fixed initial fontsize for treeView slides. made fontsize use styleprovider. fixed caption width to cover the whole document by fixing overflow-y setting --- src/client/views/StyleProvider.tsx | 4 +++- src/client/views/nodes/DocumentView.scss | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++-- 3 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 3f120d385..8d8630907 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -46,6 +46,7 @@ export enum StyleProp { JitterRotation = "jitterRotation", // whether documents should be randomly rotated BorderPath = "customBorder", // border path for document view FontSize = "fontSize", // size of text font + FontFamily = "fontFamily", // size of text font } function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; } @@ -91,7 +92,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt Date: Tue, 16 Nov 2021 20:36:25 -0500 Subject: added textboxes inside closed ink strokes --- package-lock.json | 6 ++--- src/client/views/InkStroke.scss | 31 ++++++++++++++-------- src/client/views/InkingStroke.tsx | 30 ++++++++++++++++++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 14 +++++++--- 4 files changed, 59 insertions(+), 22 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/package-lock.json b/package-lock.json index d03f8af6b..128d6fba8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7709,14 +7709,14 @@ "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz", "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=", "requires": { - "image-size": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", + "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", "readable-stream": "^1.0.33", "tryit": "^1.0.1" }, "dependencies": { "image-size": { - "version": "git+ssh://git@github.com/netroy/image-size.git#da2c863807a3e9602617bdd357b0de3ab4a064c1", - "from": "image-size@git+https://github.com/netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1" + "version": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", + "from": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1" }, "isarray": { "version": "0.0.1", diff --git a/src/client/views/InkStroke.scss b/src/client/views/InkStroke.scss index 55e06c6ca..2127826b4 100644 --- a/src/client/views/InkStroke.scss +++ b/src/client/views/InkStroke.scss @@ -13,16 +13,25 @@ } } -.inkStroke { - mix-blend-mode: multiply; - stroke-linejoin: round; - stroke-linecap: round; - overflow: visible !important; - transform-origin: top left; - width: 100%; - height: 100%; +.inkStroke-wrapper { + .inkStroke { + mix-blend-mode: multiply; + stroke-linejoin: round; + stroke-linecap: round; + overflow: visible !important; + transform-origin: top left; + width: 100%; + height: 100%; - svg:not(:root) { - overflow: visible !important; - } + svg:not(:root) { + overflow: visible !important; + } + } + + .inkStroke-text { + position: absolute; + &:hover { + background: #9f9f9f0a; + } + } } diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index ecb46a5b3..95dad5c6e 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,13 +1,13 @@ import React = require("react"); import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc } from "../../fields/Doc"; +import { Doc, HeightSym, WidthSym } from "../../fields/Doc"; import { documentSchema } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool } from "../../fields/InkField"; import { makeInterface } from "../../fields/Schema"; import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; import { TraceMobx } from "../../fields/util"; -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; +import { emptyFunction, returnFalse, setupMoveUpEvents, OmitKeys } from "../../Utils"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { InteractionUtils } from "../util/InteractionUtils"; @@ -22,6 +22,7 @@ import { InkTangentHandles } from "./InkTangentHandles"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; import Color = require("color"); import { Transform } from "../util/Transform"; +import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @@ -252,9 +253,11 @@ export class InkingStroke extends ViewBoxBaseComponent - ); + {!closed ? (null) : +
+ +
+ } +
; } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index aa53f751d..d9c417b41 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1140,10 +1140,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, }); - const startupText = !rtfField && this._editorView && Field.toString(this.dataDoc[fieldKey] as Field); - if (startupText) { - const { state: { tr }, dispatch } = this._editorView; - dispatch(tr.insertText(startupText)); + const { state, dispatch } = this._editorView; + if (!rtfField) { + const startupText = Field.toString(this.dataDoc[fieldKey] as Field); + if (startupText) { + dispatch(state.tr.insertText(startupText)); + } else { + selectAll(this._editorView!.state, (tr) => { + this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: "center" }))); + }); + } } (this._editorView as any).TextView = this; } -- cgit v1.2.3-70-g09d2 From 1c0ca018dce4392a91b37ab20b936aa137c1e8a9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 17 Nov 2021 21:09:02 -0500 Subject: changed link line curves a bit. fixed formatted text box not to center text for typed notes. --- src/client/views/InkingStroke.tsx | 2 +- .../CollectionFreeFormLinkView.tsx | 34 ++++++++++++++++------ .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 3 files changed, 27 insertions(+), 11 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 95dad5c6e..59efb36dd 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -247,7 +247,7 @@ export class InkingStroke extends ViewBoxBaseComponent 0.1 && (aDocBounds.right - acentX) > 0.1) || + ((acentY - aDocBounds.top) > 0.1 && (aDocBounds.bottom - acentY) > 0.1); + const pt2Arc = ((bcentX - bDocBounds.left) > 0.1 && (bDocBounds.right - bcentX) > 0.1) || + ((bcentY - bDocBounds.top) > 0.1 && (bDocBounds.bottom - bcentY) > 0.1); const atop2 = this.visibleY(adiv); const btop2 = this.visibleY(bdiv); const aleft = this.visibleX(adiv); const bleft = this.visibleX(bdiv); const clipped = aleft !== a.left || atop !== a.top || bleft !== b.left || btop !== b.top; - const apt = Utils.closestPtBetweenRectangles(aleft, atop, a.width, a.height, bleft, btop, b.width, b.height, a.left + a.width / 2, a.top + a.height / 2); - const bpt = Utils.closestPtBetweenRectangles(bleft, btop, b.width, b.height, aleft, atop, a.width, a.height, apt.point.x, apt.point.y); - const pt1 = [apt.point.x, apt.point.y]; - const pt2 = [bpt.point.x, bpt.point.y]; - const pt1vec = [pt1[0] - (aleft + a.width / 2), pt1[1] - (atop + a.height / 2)]; - const pt2vec = [pt2[0] - (bleft + b.width / 2), pt2[1] - (btop + b.height / 2)]; + const pt1 = [aleft + a.width / 2, atop + a.height / 2]; + const pt2 = [bleft + b.width / 2, btop + b.width / 2]; + const pt1vec = [pt1[0] - (aDocBounds.left + aDocBounds.right) / 2, pt1[1] - (aDocBounds.top + aDocBounds.bottom) / 2]; + const pt2vec = [pt2[0] - (bDocBounds.left + bDocBounds.right) / 2, pt2[1] - (bDocBounds.top + bDocBounds.bottom) / 2]; const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1])); const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1])); const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2; - const pt1norm = clipped ? [0, 0] : [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen]; - const pt2norm = clipped ? [0, 0] : [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen]; + const pt1norm = clipped ? [0, 0] : !pt1Arc ? [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen] : + Math.abs(acentY - aDocBounds.top) < 0.01 || + Math.abs(acentY - aDocBounds.bottom) < 0.01 ? [0, (pt2[1] - pt1[1]) / 2] : [(pt2[0] - pt1[0]) / 2, 0]; + const pt2norm = clipped ? [0, 0] : !pt2Arc ? [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen] : + Math.abs(bcentY - bDocBounds.top) < 0.01 || + Math.abs(bcentY - bDocBounds.bottom) < 0.01 ? [0, (pt1[1] - pt2[1]) / 2] : [(pt1[0] - pt2[0]) / 2, 0]; + const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1; + const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1; + const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen]; + const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen]; const aActive = A.isSelected() || Doc.IsBrushed(A.rootDoc); const bActive = B.isSelected() || Doc.IsBrushed(B.rootDoc); const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX); const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY); - return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 }; + return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1: [pt1[0] + pt1normalized[0] * 13, pt1[1] + pt1normalized[1] * 13], pt2: [pt2[0] + pt2normalized[0] * 13, pt2[1] + pt2normalized[1] * 13] }; } render() { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d9c417b41..77f33b432 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1145,7 +1145,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const startupText = Field.toString(this.dataDoc[fieldKey] as Field); if (startupText) { dispatch(state.tr.insertText(startupText)); - } else { + } else if (!FormattedTextBox.LiveTextUndo) { selectAll(this._editorView!.state, (tr) => { this._editorView!.dispatch(tr.replaceSelectionWith(state.schema.nodes.paragraph.create({ align: "center" }))); }); -- cgit v1.2.3-70-g09d2 From 91247d583e5e4c7205a1ed764dd0e3a12af3be25 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 30 Nov 2021 23:37:49 -0500 Subject: fixed warnings/errors. added inkingStroke comments. need to double-click now to add a point to an ink stroke. --- src/client/util/bezierFit.ts | 242 ++++++++++----------- src/client/views/DocumentDecorations.tsx | 8 +- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/InkControlPtHandles.tsx | 34 ++- src/client/views/InkStroke.scss | 5 +- src/client/views/InkStrokeProperties.ts | 15 +- src/client/views/InkTangentHandles.tsx | 28 +-- src/client/views/InkingStroke.tsx | 174 ++++++++------- src/client/views/MainView.tsx | 1 - src/client/views/PropertiesView.tsx | 21 +- src/client/views/StyleProvider.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/search/SearchBox.tsx | 18 +- 13 files changed, 269 insertions(+), 283 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') 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(last - first + 1); // New parameter values + const uPrime = new Array(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(last - first + 1);// Parameterization + const u = new Array(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>(4); + const sz = 4; + const Vtemp = new Array>(4); for (var i = 0; i < 4; i++) Vtemp[i] = new Array(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(4), a2 = new Array(4); + const a1 = new Array(4), a2 = new Array(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(4), b2 = new Array(4); + const b1 = new Array(4), b2 = new Array(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(4), b2 = new Array(4); + const b1 = new Array(4), b2 = new Array(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(nPts * 2);// Precomputed rhs for eqn //std::vector A(nPts * 2); - var Ay = new Array(nPts * 2);// Precomputed rhs for eqn //std::vector A(nPts * 2); + const nPts = last - first + 1; // Number of pts in sub-curve + const Ax = new Array(nPts * 2);// Precomputed rhs for eqn //std::vector A(nPts * 2); + const Ay = new Array(nPts * 2);// Precomputed rhs for eqn //std::vector 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(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(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 { @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 { } 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 { @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 { * 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 { 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 { } render() { - const hdl = (pt: PointData, dragFunc: (e: React.PointerEvent) => void) => void) => { strokeWidth={0} onPointerLeave={action(() => this._overStart = false)} onPointerEnter={action(() => this._overStart = true)} - onPointerDown={e => dragFunc(e)} + onPointerDown={dragFunc} pointerEvents="all" />; return ( - {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))} ); } 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 { * @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 { } 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 { 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"} /> )} {tangentLines.map((pts, i) => { const tangentLine = (x1: number, y1: number, x2: number, y2: number) => @@ -119,7 +115,7 @@ export class InkTangentHandles extends React.Component { 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 {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(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 { inkDoc.isInkMask = !inkDoc.isInkMask; inkDoc._backgroundColor = inkDoc.isInkMask ? "rgba(0,0,0,0.7)" : undefined; @@ -88,70 +113,47 @@ export class InkingStroke extends ViewBoxBaseComponent { - 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 { 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 + return
this._nearestScrPt = undefined)} @@ -305,7 +313,7 @@ export class InkingStroke extends ViewBoxBaseComponent 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 { @computed get controlPointsButton() { - const formatInstance = InkStrokeProperties.Instance; - return !formatInstance ? (null) :
+ return
{"Edit points"}
}> -
formatInstance._controlButton = !formatInstance._controlButton)} style={{ backgroundColor: formatInstance._controlButton ? "black" : "" }}> +
InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)} >
- {formatInstance._lock ? "Unlock ratio" : "Lock ratio"}
}> -
formatInstance._lock = !formatInstance._lock)} > - + {InkStrokeProperties.Instance._lock ? "Unlock ratio" : "Lock ratio"}
}> +
InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock)} > +
{"Rotate 90˚"}
}> @@ -603,7 +604,7 @@ export class PropertiesView extends React.Component { 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 { 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 { 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, props: Opt { + 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 { 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 = new Set(); + const linkedDocSet = new Set(); 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 = new Map(); + const nextPageRanks = new Map(); this._results.forEach((_, doc) => { let nextPageRank = pageRankFromAll; @@ -397,7 +397,7 @@ export class SearchBox extends ViewBoxBaseComponent (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(); -- cgit v1.2.3-70-g09d2