diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/SelectionManager.ts | 5 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 24 | ||||
-rw-r--r-- | src/client/views/InkControlPtHandles.tsx | 12 | ||||
-rw-r--r-- | src/client/views/InkStrokeProperties.ts | 104 | ||||
-rw-r--r-- | src/client/views/InkingStroke.tsx | 49 | ||||
-rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 5 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx | 6 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 8 | ||||
-rw-r--r-- | src/server/DashUploadUtils.ts | 5 | ||||
-rw-r--r-- | src/server/SharedMediaTypes.ts | 2 |
10 files changed, 144 insertions, 76 deletions
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index e507ec3bf..2cba2c1f2 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -27,7 +27,7 @@ export namespace SelectionManager { manager.SelectedViews.set(docView, docView.rootDoc); docView.props.whenChildContentsActiveChanged(true); - } else if (!ctrlPressed && Array.from(manager.SelectedViews.entries()).length > 1) { + } else if (!ctrlPressed && (Array.from(manager.SelectedViews.entries()).length > 1 || manager.SelectedSchemaDocument)) { Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false)); manager.SelectedSchemaDocument = undefined; manager.SelectedViews.clear(); @@ -57,7 +57,8 @@ export namespace SelectionManager { export function SelectView(docView: DocumentView, ctrlPressed: boolean): void { manager.SelectView(docView, ctrlPressed); } - export function SelectSchemaViewDoc(document: Opt<Doc>): void { + export function SelectSchemaViewDoc(document: Opt<Doc>, deselectAllFirst?: boolean): void { + if (deselectAllFirst) manager.DeselectAll(); manager.SelectSchemaViewDoc(document); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 5b44a0552..522995479 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -1,7 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, reaction, runInAction } from "mobx"; +import { action, computed, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { DateField } from '../../fields/DateField'; import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from "../../fields/Doc"; @@ -27,8 +27,6 @@ import { LightboxView } from './LightboxView'; import { DocumentView } from "./nodes/DocumentView"; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import React = require("react"); -import { dark } from '@material-ui/core/styles/createPalette'; -import { color } from 'd3-color'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number, PanelHeight: number, boundsLeft: number, boundsTop: number }, { value: string }> { @@ -65,15 +63,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P @computed get Bounds() { - return SelectionManager.Views().map(dv => dv.getBounds()).reduce((bounds, rect) => + const views = SelectionManager.Views(); + return views.map(dv => dv.getBounds()).reduce((bounds, rect) => !rect ? bounds : { x: Math.min(rect.left, bounds.x), y: Math.min(rect.top, bounds.y), r: Math.max(rect.right, bounds.r), - b: Math.max(rect.bottom, bounds.b) + b: Math.max(rect.bottom, bounds.b), + c: views.length === 1 ? rect.center : undefined }, - { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); + { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE, c: undefined as ({ X: number, Y: number } | undefined) }); } @action @@ -195,13 +195,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P @action onRotateDown = (e: React.PointerEvent): void => { this._rotateUndo = UndoManager.StartBatch("rotatedown"); - const pt = { x: (this.Bounds.x + this.Bounds.r) / 2, y: (this.Bounds.y + this.Bounds.b) / 2 }; + const pt = { x: this.Bounds.c?.X ?? (this.Bounds.x + this.Bounds.r) / 2, y: this.Bounds.c?.Y ?? (this.Bounds.y + this.Bounds.b) / 2 }; setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { - const movement = { X: delta[0], Y: e.clientY - down[1] }; - const angle = Math.max(1, Math.abs(movement.Y / 10)); + const docView = SelectionManager.Views()[0]; + const { left, top, right, bottom } = docView.getBounds() || { left: 0, top: 0, right: 0, bottom: 0 }; + 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 selectedInk = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK); - InkStrokeProperties.Instance?.rotateInk(selectedInk, 2 * movement.X / angle * (Math.PI / 180), pt); + angle && InkStrokeProperties.Instance?.rotateInk(selectedInk, -angle, pt); return false; }, () => { diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 73de4a3e0..f24dab949 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -2,10 +2,10 @@ import React = require("react"); import { action, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; -import { ControlPoint, InkData, PointData } from "../../fields/InkField"; +import { ControlPoint, InkData, PointData, InkField } from "../../fields/InkField"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; -import { Cast } from "../../fields/Types"; +import { Cast, NumCast } from "../../fields/Types"; import { setupMoveUpEvents } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; @@ -43,8 +43,8 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { */ @action onControlDown = (e: React.PointerEvent, controlIndex: number): void => { - if (InkStrokeProperties.Instance) { - const screenScale = this.props.ScreenToLocalTransform().Scale; + const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen; + if (InkStrokeProperties.Instance && 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; @@ -53,7 +53,9 @@ export class InkControlPtHandles extends React.Component<InkControlProps> { setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); - InkStrokeProperties.Instance?.moveControlPtHandle(this.props.inkView, delta[0] * screenScale, delta[1] * screenScale, controlIndex); + 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); return false; }), action(() => { diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 33e25bbbb..6687b2bc7 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,6 +1,6 @@ import { Bezier } from "bezier-js"; import { action, observable, reaction } from "mobx"; -import { Doc, Opt } from "../../fields/Doc"; +import { Doc, Opt, DocListCast } from "../../fields/Doc"; import { InkData, InkField, InkTool, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; @@ -10,6 +10,7 @@ import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { undoBatch } from "../util/UndoManager"; import { InkingStroke } from "./InkingStroke"; import { DocumentView } from "./nodes/DocumentView"; +import { DocumentManager } from "../util/DocumentManager"; export class InkStrokeProperties { static Instance: InkStrokeProperties | undefined; @@ -155,29 +156,24 @@ export class InkStrokeProperties { }, true) /** - * Rotates the entire selected ink instance. + * Rotates ink stroke(s) about a point + * @param inkStrokes set of ink documentViews to rotate * @param angle The angle at which to rotate the ink in radians. + * @param scrpt The center point of the rotation in screen coordinates */ @undoBatch @action rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: { x: number, y: number }) => { this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => { - const oldXrangeMin = Math.min(...ink.map(p => p.X)); - const oldYrangeMin = Math.min(...ink.map(p => p.Y)); - const docViewCenterPt = view.screenToLocalTransform().transformPoint(scrpt.x, scrpt.y); - const inkCenterPt = { - X: (docViewCenterPt[0] - inkStrokeWidth / 2) / xScale + oldXrangeMin, - Y: (docViewCenterPt[1] - inkStrokeWidth / 2) / yScale + oldYrangeMin - }; - const newPoints = ink.map(i => { - const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y }; - const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale; - const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y; - return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y }; - }); - const doc = view.rootDoc; - doc.rotation = NumCast(doc.rotation) + angle; - return newPoints; + view.rootDoc.rotation = NumCast(view.rootDoc.rotation) + angle; + const inkCenterPt = view.ComponentView?.ptFromScreen?.({ X: scrpt.x, Y: scrpt.y }); + return !inkCenterPt ? ink : + ink.map(i => { + const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y }; + const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale; + const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y; + return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y }; + }); }); } @@ -203,7 +199,7 @@ export class InkStrokeProperties { (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) { - return ({ X: pt.X + deltaX / xScale, Y: pt.Y + deltaY / yScale }); + return ({ X: pt.X + deltaX, Y: pt.Y + deltaY }); } return pt; }); @@ -211,7 +207,7 @@ export class InkStrokeProperties { }) - public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refPt: { X: number, Y: number }, excludeSegs?: number[]) { + public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refInkSpacePt: { X: number, Y: number }, excludeSegs?: number[]) { var distance = Number.MAX_SAFE_INTEGER; var nearestT = -1; var nearestSeg = -1; @@ -219,9 +215,9 @@ export class InkStrokeProperties { for (var i = 0; i < ctrlPoints.length - 3; i += 4) { if (excludeSegs?.includes(i)) continue; const array = [ctrlPoints[i], ctrlPoints[i + 1], ctrlPoints[i + 2], ctrlPoints[i + 3]]; - const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refPt.X, y: refPt.Y }); + const point = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).project({ x: refInkSpacePt.X, y: refInkSpacePt.Y }); if (point.t !== undefined) { - const dist = Math.sqrt((point.x - refPt.X) * (point.x - refPt.X) + (point.y - refPt.Y) * (point.y - refPt.Y)); + const dist = Math.sqrt((point.x - refInkSpacePt.X) * (point.x - refInkSpacePt.X) + (point.y - refInkSpacePt.Y) * (point.y - refInkSpacePt.Y)); if (dist < distance) { distance = dist; nearestT = point.t; @@ -238,34 +234,54 @@ export class InkStrokeProperties { */ snapControl = (inkView: DocumentView, controlIndex: number) => { const inkDoc = inkView.rootDoc; - const ink = Cast(inkDoc.data, InkField)?.inkData; - if (ink) { - const closed = InkingStroke.IsClosed(ink); - - // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve) - const thisseg = Math.floor(controlIndex / 4) * 4; - const which = controlIndex % 4; - const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1; - const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1; - const refPt = ink[controlIndex]; - const { nearestPt } = InkStrokeProperties.nearestPtToStroke(ink, refPt, [thisseg, prevseg, nextseg]); + const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData; - // nearestPt is in inkDoc coordinates -- we need to compute the distance in screen coordinates. - // so we scale the X & Y distances by the internal ink scale factor and then transform the final distance by the ScreenToLocal.Scale of the inkDoc itself. - const oldXrange = (xs => ({ coord: NumCast(inkDoc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); - const oldYrange = (ys => ({ coord: NumCast(inkDoc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const ptsXscale = ((NumCast(inkDoc._width) - NumCast(inkDoc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1; - const ptsYscale = ((NumCast(inkDoc._height) - NumCast(inkDoc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1; - const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale + - (nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale); - - if (near / (inkView.props.ScreenToLocalTransform().Scale || 1) < 10) { - return this.moveControlPtHandle(inkView, (nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); + if (ink) { + const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]); + if (screenDragPt) { + const snapData = this.snapToAllCurves(screenDragPt, inkView, { nearestPt: { X: 0, Y: 0 }, distance: 10 }, ink, controlIndex); + if (snapData.distance < 10) { + const deltaX = (snapData.nearestPt.X - ink[controlIndex].X); + const deltaY = (snapData.nearestPt.Y - ink[controlIndex].Y); + const res = this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex); + console.log("X= " + snapData.nearestPt.X + " " + snapData.nearestPt.Y); + return res; + } } } return false; } + excludeSelfSnapSegs = (ink: InkData, controlIndex: number) => { + const closed = InkingStroke.IsClosed(ink); + + // figure out which segments we don't want to snap to - avoid the dragged control point's segment and the next and prev segments (when they exist -- ie not for endpoints of unclosed curve) + const thisseg = Math.floor(controlIndex / 4) * 4; + const which = controlIndex % 4; + const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1; + const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1; + return [thisseg, prevseg, nextseg]; + } + + snapToAllCurves = (screenDragPt: { X: number, Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number, Y: number }, distance: number }, ink: InkData, controlIndex: number) => { + const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + containingCollection?.childDocs + .filter(doc => doc.type === DocumentType.INK) + .forEach(doc => { + const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView); + const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []); + if (snapped && snapped.distance < snapData.distance) { + const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : + inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space + + if (snappedInkPt) { + snapData = { nearestPt: snappedInkPt, distance: snapped.distance }; + } + } + }); + return snapData; + } + /** * Snaps a control point with broken tangency back to synced rotation. * @param handleIndexA The handle point that retains its current position. diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index d312331d0..ecb46a5b3 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -21,6 +21,7 @@ import { InkStrokeProperties } from "./InkStrokeProperties"; import { InkTangentHandles } from "./InkTangentHandles"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; import Color = require("color"); +import { Transform } from "../util/Transform"; type InkDocument = makeInterface<[typeof documentSchema]>; const InkDocument = makeInterface(documentSchema); @@ -56,6 +57,22 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume this._selDisposer?.(); } + getCenter = (xf: Transform) => { + const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); + const angle = -NumCast(this.layoutDoc.rotation); + const newPoints = inkData.map(pt => { + const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * inkScaleY / inkScaleX; + const newY = Math.sin(angle) * pt.X * inkScaleX / inkScaleY + Math.cos(angle) * pt.Y; + return { X: newX, Y: newY }; + }); + const crx = (Math.max(...newPoints.map(np => np.X)) + Math.min(...newPoints.map(np => np.X))) / 2; + const cry = (Math.max(...newPoints.map(np => np.Y)) + Math.min(...newPoints.map(np => np.Y))) / 2; + const cx = Math.cos(-angle) * crx - Math.sin(-angle) * cry * inkScaleY / inkScaleX; + const cy = Math.sin(-angle) * crx * inkScaleX / inkScaleY + Math.cos(-angle) * cry; + const tc = xf.transformPoint((cx - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (cy - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2); + return { X: tc[0], Y: tc[1] }; + } + analyzeStrokes() { const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.dataDoc, ["inkAnalysis", "handwriting"], [data]); @@ -107,6 +124,32 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume } } + ptFromScreen = (scrPt: { X: number, Y: number }) => { + const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); + const docPt = this.props.ScreenToLocalTransform().transformPoint(scrPt.X, scrPt.Y); + const inkPt = { + X: (docPt[0] - inkStrokeWidth / 2) / inkScaleX + inkStrokeWidth / 2 + inkLeft, + Y: (docPt[1] - inkStrokeWidth / 2) / inkScaleY + inkStrokeWidth / 2 + inkTop, + }; + return inkPt; + } + ptToScreen = (inkPt: { X: number, Y: number }) => { + const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); + const docPt = { + X: (inkPt.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, + Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2 + }; + const scrPt = this.props.ScreenToLocalTransform().inverse().transformPoint(docPt.X, docPt.Y); + return { X: scrPt[0], Y: scrPt[1] }; + } + + 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 ?? []); + return { nearestPt, distance: distance * this.props.ScreenToLocalTransform().inverse().Scale }; + } + inkScaledData = () => { const inkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; const inkStrokeWidth = NumCast(this.rootDoc.strokeWidth, 1); @@ -197,15 +240,11 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker, StrCast(this.layoutDoc.strokeDash), inkScaleX, inkScaleY, "", "none", 1.0, false); - // Thin blue line indicating that the current ink stroke is selected. - // const selectedLine = InteractionUtils.CreatePolyline(data, left, top, Colors.MEDIUM_BLUE, strokeWidth, strokeWidth / 6, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "none"), - // StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", 1.0, false); - // Invisible polygonal line that enables the ink to be selected by the user. const highlightIndex = BoolCast(this.props.Document.isLinkButton) && Doc.isBrushedHighlightedDegree(this.props.Document); // bcz: Argh!! need to identify a tree view doc better than a LayoutTemlatString const highlightColor = !highlightIndex ? StrCast(this.layoutDoc.strokeOutlineColor, !closed && fillColor && fillColor !== "transparent" ? StrCast(this.layoutDoc.color, "transparent") : "transparent") : ["transparent", "rgb(68, 118, 247)", "rgb(68, 118, 247)", "yellow", "magenta", "cyan", "orange"][highlightIndex]; - + // Invisible polygonal line that enables the ink to be selected by the user. const clickableLine = InteractionUtils.CreatePolyline(inkData, inkLeft, inkTop, highlightColor, inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && (new Color(fillColor)).alpha() < 1 ? 6 : 15), StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 5325d5827..f543d924d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -44,6 +44,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { } private _reactionDisposer?: IReactionDisposer; + private _lightboxReactionDisposer?: IReactionDisposer; private _containerRef = React.createRef<HTMLDivElement>(); private _flush: UndoManager.Batch | undefined; private _ignoreStateChange = ""; @@ -298,6 +299,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { componentDidMount: () => void = () => { if (this._containerRef.current) { + this._lightboxReactionDisposer = reaction(() => LightboxView.LightboxDoc, doc => setTimeout(() => !doc && this.onResize(undefined))); new _global.ResizeObserver(this.onResize).observe(this._containerRef.current); this._reactionDisposer = reaction(() => StrCast(this.props.Document.dockingConfig), config => { @@ -320,13 +322,14 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { window.removeEventListener('resize', this.onResize); this._reactionDisposer?.(); + this._lightboxReactionDisposer?.(); } @action onResize = (event: any) => { const cur = this._containerRef.current; // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed - cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height); + !LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height); } @action diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index bb4cae8c6..9769453a0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -5,9 +5,8 @@ import { Id } from "../../../../fields/FieldSymbols"; import { List } from "../../../../fields/List"; import { NumCast } from "../../../../fields/Types"; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { LinkManager } from "../../../util/LinkManager"; -import { ColorScheme } from "../../../util/SettingsManager"; +import { SelectionManager } from "../../../util/SelectionManager"; import { SnappingManager } from "../../../util/SnappingManager"; import { DocumentView } from "../../nodes/DocumentView"; import "./CollectionFreeFormLinkView.scss"; @@ -199,7 +198,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo const strokeWidth = linkSize === -1 ? "3px" : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + "px"; return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> - <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, /*strokeDasharray: "2 2",*/ stroke, strokeWidth }} + <path className="collectionfreeformlinkview-linkLine" style={{ pointerEvents: "all", opacity: this._opacity, strokeDasharray: SelectionManager.SelectedSchemaDoc() === this.props.LinkDocs[0] ? "2 2" : undefined, stroke, strokeWidth }} + onClick={() => SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true)} d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} /> {textX === undefined ? (null) : <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} > {Field.toString(this.props.LinkDocs[0].description as any as Field)} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 949e0e168..c9b246c10 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -97,6 +97,10 @@ export interface DocComponentView { annotationKey?: string; getTitle?: () => string; getScrollHeight?: () => number; + getCenter?: (xf: Transform) => { X: number, Y: number }; + ptToScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number }; + ptFromScreen?: (pt: { X: number, Y: number }) => { X: number, Y: number }; + snapPt?: (pt: { X: number, Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number, Y: number }, distance: number }; search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; } export interface DocumentViewSharedProps { @@ -1179,9 +1183,9 @@ export class DocumentView extends React.Component<DocumentViewProps> { const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)]; if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { const docuBox = this.docView.ContentDiv.getElementsByClassName("linkAnchorBox-cont"); - if (docuBox.length) return docuBox[0].getBoundingClientRect(); + if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), center: undefined }; } - return { left, top, right, bottom }; + return { left, top, right, bottom, center: this.ComponentView?.getCenter?.(xf) }; } public iconify() { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 54b71e8ce..f13580865 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -332,7 +332,7 @@ export namespace DashUploadUtils { } }; - const parseExifData = async (source: string): Promise<Upload.EnrichedExifData> => { + const parseExifData = async (source: string) => { const image = await request.get(source, { encoding: null }); const { data, error } = await new Promise(resolve => { new ExifImage({ image }, (error, data) => { @@ -343,9 +343,8 @@ export namespace DashUploadUtils { resolve({ data, error: reason }); }); }); - return { data: await exifr.parse(image) as any, error }; //data && bufferConverterRec(data); - return { data, error }; + return { data: await exifr.parse(image), error }; }; const { pngs, jpgs, webps, tiffs } = AcceptableMedia; diff --git a/src/server/SharedMediaTypes.ts b/src/server/SharedMediaTypes.ts index fdc65188f..cde95526f 100644 --- a/src/server/SharedMediaTypes.ts +++ b/src/server/SharedMediaTypes.ts @@ -44,7 +44,7 @@ export namespace Upload { } export interface EnrichedExifData { - data: ExifData; + data: ExifData & ExifData["gps"]; error?: string; } |