From 43496b4cd19a06d2682f2416a4273e25cdcb55a0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 7 Oct 2021 08:47:22 -0400 Subject: Revert "Revert "Merge pull request #34 from brown-dash/linking-anh"" This reverts commit 4e6a1d7a37c8c28014a9f7cd0d92f17c8f29454d. --- src/client/util/LinkManager.ts | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/client/util/LinkManager.ts') diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 53bd13fb3..3f7f16113 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -107,8 +107,10 @@ export class LinkManager { if (!Doc.UserDoc().linkRelationshipList && !Doc.UserDoc().linkColorList) { const linkRelationshipList = new List(); const linkColorList = new List(); + const linkRelationshipSizes = new List(); Doc.UserDoc().linkRelationshipList = linkRelationshipList; Doc.UserDoc().linkColorList = linkColorList; + Doc.UserDoc().linkRelationshipSizes = linkRelationshipSizes; } } -- cgit v1.2.3-70-g09d2 From 8f1a6266cf0a728ab2b7df11d45a023de266fae4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 7 Oct 2021 09:43:19 -0400 Subject: link menu ui tweaking. edge case checks for linkrelationship stuff. default links to have linkAutoMove --- src/Utils.ts | 2 +- src/client/documents/Documents.ts | 2 ++ src/client/util/LinkManager.ts | 11 +++-------- .../collectionFreeForm/CollectionFreeFormLinkView.tsx | 8 +++++--- src/client/views/linking/LinkEditor.tsx | 14 +++++++++++--- src/client/views/linking/LinkMenuGroup.tsx | 1 - src/client/views/linking/LinkMenuItem.tsx | 18 +++++++++--------- 7 files changed, 31 insertions(+), 25 deletions(-) (limited to 'src/client/util/LinkManager.ts') diff --git a/src/Utils.ts b/src/Utils.ts index 7ec4f69f3..53182cc9c 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -575,7 +575,7 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe export function DashColor(color: string) { try { - return Color(color.toLowerCase()); + return color ? Color(color.toLowerCase()) : Color("transparent"); } catch (e) { console.log("COLOR error:", e); return Color("red"); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6d64b3286..da5c8efa9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -191,6 +191,7 @@ export class DocumentOptions { opacity?: number; defaultBackgroundColor?: string; _isLinkButton?: boolean; // marks a document as a button that will follow its primary link when clicked + _linkAutoMove?: boolean; // whether link endpoint should move around the edges of a document to make shortest path to other link endpoint isFolder?: boolean; lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide) activeFrame?: number; // the active frame of a document in a frame base collection @@ -1106,6 +1107,7 @@ export namespace DocUtils { "_acl-Public": SharingPermissions.Augment, linkDisplay: true, _hidden: true, + _linkAutoMove: true, linkRelationship, _showCaption: "description", _showTitle: "linkRelationship", diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 3f7f16113..90a8f2737 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -104,14 +104,9 @@ export class LinkManager { public createLinkrelationshipLists = () => { //create new lists for link relations and their associated colors if the lists don't already exist - if (!Doc.UserDoc().linkRelationshipList && !Doc.UserDoc().linkColorList) { - const linkRelationshipList = new List(); - const linkColorList = new List(); - const linkRelationshipSizes = new List(); - Doc.UserDoc().linkRelationshipList = linkRelationshipList; - Doc.UserDoc().linkColorList = linkColorList; - Doc.UserDoc().linkRelationshipSizes = linkRelationshipSizes; - } + !Doc.UserDoc().linkRelationshipList && (Doc.UserDoc().linkRelationshipList = new List()); + !Doc.UserDoc().linkColorList && (Doc.UserDoc().linkColorList = new List()); + !Doc.UserDoc().linkRelationshipSizes && (Doc.UserDoc().linkRelationshipSizes = new List()); } public addLink(linkDoc: Doc, checkExists = false) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 88cd0feb3..bb4cae8c6 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -189,15 +189,17 @@ export class CollectionFreeFormLinkView extends React.Component; const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship); + const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex]; + //access stroke color using index of the relationship in the color list (default black) - const strokeColor = currRelationshipIndex == -1 ? "black" : linkColorList[linkRelationshipList.indexOf(linkRelationship)]; + const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? "black" : linkColorList[currRelationshipIndex]; //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) //thickness varies linearly from 3px to 12px for increasing link count - const strokeWidth: string = currRelationshipIndex == -1 ? "3px" : Math.floor(2 + 10 * (linkRelationshipSizes[currRelationshipIndex] / Math.max(...linkRelationshipSizes))) + "px"; + 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) : (<> - {textX === undefined ? (null) : {Field.toString(this.props.LinkDocs[0].description as any as Field)} diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 58c57a23b..5b5c3cd01 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -50,18 +50,26 @@ export class LinkEditor extends React.Component { const linkColorList = StrListCast(Doc.UserDoc().linkColorList); // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color - if (linkRelationshipList && !linkRelationshipList.includes(value)) { + if (!linkRelationshipList?.includes(value)) { linkRelationshipList.push(value); linkRelationshipSizes.push(1); const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; linkColorList.push(randColor) // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes } else if (linkRelationshipList && value != prevRelationship) { + const index = linkRelationshipList.indexOf(value); //increment size of new relationship size - linkRelationshipSizes[linkRelationshipList.indexOf(value)] = linkRelationshipSizes[linkRelationshipList.indexOf(value)] + 1; + if (index !== -1 && index < linkRelationshipSizes.length) { + const pvalue = linkRelationshipSizes[index]; + linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1); + } //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) if (linkRelationshipList.includes(prevRelationship)) { - linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] = linkRelationshipSizes[linkRelationshipList.indexOf(prevRelationship)] - 1; + const pindex = linkRelationshipList.indexOf(prevRelationship); + if (pindex !== -1 && pindex < linkRelationshipSizes.length) { + const pvalue = linkRelationshipSizes[pindex]; + linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1)); + } } } diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index cb6571f92..03377ad4e 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -31,7 +31,6 @@ export class LinkMenuGroup extends React.Component { if (RGBcolor) { //set opacity to 0.25 by modifiying the rgb string color = RGBcolor.slice(0, RGBcolor.length - 1) + ", 0.25)"; - console.log(color); } } return color; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index e0b20ef6f..f19064e49 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -165,19 +165,19 @@ export class LinkMenuItem extends React.Component {
-
{this.props.linkDoc.hidden ? "Show Anchor" : "Hide Anchor"}
}> -
e.stopPropagation()}> -
+
{this.props.linkDoc.hidden ? "Show Link Anchor" : "Hide Link Anchor"}
}> +
e.stopPropagation()}> +
-
{!this.props.linkDoc.linkDisplay ? "Show link" : "Hide link"}
}> -
e.stopPropagation()}> -
+
{this.props.linkDoc.linkDisplay ? "Hide Link Line" : "Show Link Line"}
}> +
e.stopPropagation()}> +
-
{!this.props.linkDoc.linkAutoMove ? "Auto move dot" : "Freeze dot position"}
}> -
e.stopPropagation()}> -
+
{this.props.linkDoc.linkAutoMove ? "Click to freeze link anchor position" : "Click to auto move link anchor"}
}> +
e.stopPropagation()}> +
Edit Link
}> -- cgit v1.2.3-70-g09d2 From 3e0a9ff2c708891a15a681e5af549caf0b18ff60 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 26 Oct 2021 12:06:36 -0400 Subject: when document is in lightbox view and a link is followed to its sidebar, we no longer reopen the document in its context. fixed selecting annotations on web/pdf. don't change document height when shown in a linkPreview. webBox fixes for pages with scripts. fixed range bounds when clicking on web text several fixes to web pages on server and client. client webbox allows clicks on divs with onclick instead of doing selection also hacky fix so that google search url doesn't keep expanding by removing 'q=' regions also added prevent/allow script menu item server grabs all html and hides id="google.." which are ads. also rewrites hrefs starting with http to route through corsProxy also removes target=_blank tags to prevent pages from opening outside of dash. also cleaned up routes and comments also when not logged in, references to anything in dash domain route to /home --- package-lock.json | 5 + package.json | 1 + src/Utils.ts | 2 +- src/client/util/LinkManager.ts | 6 +- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/InkControlPtHandles.tsx | 16 +-- src/client/views/InkStrokeProperties.ts | 72 +++++++------ src/client/views/InkTangentHandles.tsx | 6 +- src/client/views/InkingStroke.tsx | 9 +- src/client/views/collections/CollectionSubView.tsx | 5 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 77 +++++++++++--- src/server/server_Initialization.ts | 115 ++++++++++++--------- 14 files changed, 206 insertions(+), 122 deletions(-) (limited to 'src/client/util/LinkManager.ts') diff --git a/package-lock.json b/package-lock.json index c1dd8506f..3aff3a549 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9264,6 +9264,11 @@ "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", "optional": true }, + "memorystream": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" + }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", diff --git a/package.json b/package.json index 5d10c0d54..99c818062 100644 --- a/package.json +++ b/package.json @@ -194,6 +194,7 @@ "libxmljs": "^0.19.7", "lodash": "^4.17.15", "material-ui": "^0.20.2", + "memorystream": "^0.3.1", "mobile-detect": "^1.4.4", "mobx": "^5.15.7", "mobx-react": "^5.4.4", diff --git a/src/Utils.ts b/src/Utils.ts index 53182cc9c..bfb29fe8d 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -599,7 +599,7 @@ export function getWordAtPoint(elem: any, x: number, y: number): string | undefi range.selectNodeContents(elem); var currentPos = 0; const endPos = range.endOffset; - while (currentPos + 1 < endPos) { + while (currentPos + 1 <= endPos) { range.setStart(elem, currentPos); range.setEnd(elem, currentPos + 1); const rangeRect = range.getBoundingClientRect(); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 90a8f2737..62b13e2c6 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -234,8 +234,10 @@ export class LinkManager { setTimeout(LightboxView.Next); finished?.(); } else { - const containerDoc = Cast(target.annotationOn, Doc, null) || target; - const targetContext = Cast(containerDoc?.context, Doc, null); + const containerAnnoDoc = Cast(target.annotationOn, Doc, null); + const containerDoc = containerAnnoDoc || target; + const containerDocContext = Cast(containerDoc?.context, Doc, null); + const targetContext = LightboxView.LightboxDoc ? containerAnnoDoc || containerDocContext : containerDocContext; const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 17a81149c..5b44a0552 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -195,11 +195,13 @@ 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 }; 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)); - InkStrokeProperties.Instance?.rotateInk(2 * movement.X / angle * (Math.PI / 180)); + const selectedInk = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK); + InkStrokeProperties.Instance?.rotateInk(selectedInk, 2 * movement.X / angle * (Math.PI / 180), pt); return false; }, () => { diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 0644488b3..73de4a3e0 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -3,18 +3,20 @@ import { action, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; import { ControlPoint, InkData, PointData } from "../../fields/InkField"; +import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { Cast } from "../../fields/Types"; import { setupMoveUpEvents } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; -import { InkStrokeProperties } from "./InkStrokeProperties"; -import { List } from "../../fields/List"; import { InkingStroke } from "./InkingStroke"; +import { InkStrokeProperties } from "./InkStrokeProperties"; +import { DocumentView } from "./nodes/DocumentView"; export interface InkControlProps { inkDoc: Doc; + inkView: DocumentView; inkCtrlPoints: InkData; screenCtrlPoints: InkData; screenSpaceLineWidth: number; @@ -51,12 +53,12 @@ export class InkControlPtHandles extends React.Component { setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt"); - InkStrokeProperties.Instance?.moveControlPtHandle(delta[0] * screenScale, delta[1] * screenScale, controlIndex); + InkStrokeProperties.Instance?.moveControlPtHandle(this.props.inkView, delta[0] * screenScale, delta[1] * screenScale, controlIndex); return false; }), action(() => { if (this.controlUndo) { - InkStrokeProperties.Instance?.snapControl(this.props.inkDoc, controlIndex); + InkStrokeProperties.Instance?.snapControl(this.props.inkView, controlIndex); } this.controlUndo?.end(); this.controlUndo = undefined; @@ -71,11 +73,11 @@ export class InkControlPtHandles extends React.Component { } else { if (brokenIndices?.includes(equivIndex)) { if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth"); - InkStrokeProperties.Instance?.snapHandleTangent(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(controlIndex, handleIndexA, handleIndexB); + InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB); } } this.controlUndo?.end(); @@ -98,7 +100,7 @@ export class InkControlPtHandles extends React.Component { @action onDelete = (e: KeyboardEvent) => { if (["-", "Backspace", "Delete"].includes(e.key)) { - InkStrokeProperties.Instance?.deletePoints(); + InkStrokeProperties.Instance?.deletePoints(this.props.inkView); e.stopPropagation(); } } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index ee30caa3d..33e25bbbb 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,16 +1,15 @@ import { Bezier } from "bezier-js"; -import { action, computed, observable, reaction } from "mobx"; -import { Doc } from "../../fields/Doc"; -import { Document } from "../../fields/documentSchemas"; +import { action, observable, reaction } from "mobx"; +import { Doc, Opt } from "../../fields/Doc"; import { InkData, InkField, InkTool, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; import { listSpec } from "../../fields/Schema"; import { Cast, NumCast } from "../../fields/Types"; import { DocumentType } from "../documents/DocumentTypes"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { SelectionManager } from "../util/SelectionManager"; import { undoBatch } from "../util/UndoManager"; import { InkingStroke } from "./InkingStroke"; +import { DocumentView } from "./nodes/DocumentView"; export class InkStrokeProperties { static Instance: InkStrokeProperties | undefined; @@ -25,21 +24,16 @@ export class InkStrokeProperties { reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false)); } - @computed get selectedInk() { - const inks = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK); - return inks.length ? inks : undefined; - } - /** * Helper function that enables other functions to be applied to a particular ink instance. * @param func The inputted function. * @param requireCurrPoint Indicates whether the current selected point is needed. */ - applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => { + applyFunction = (strokes: Opt, func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => { var appliedFunc = false; - this.selectedInk?.forEach(action(inkView => { - if (this.selectedInk?.length === 1 && (!requireCurrPoint || this._currentPoint !== -1)) { - const doc = Document(inkView.rootDoc); + (strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(action(inkView => { + if (!requireCurrPoint || this._currentPoint !== -1) { + const doc = inkView.rootDoc; if (doc.type === DocumentType.INK && doc.width && doc.height) { const ink = Cast(doc.data, InkField)?.inkData; if (ink) { @@ -47,7 +41,7 @@ export class InkStrokeProperties { const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); const ptsXscale = ((NumCast(doc._width) - NumCast(doc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1; const ptsYscale = ((NumCast(doc._height) - NumCast(doc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1; - const newPoints = func(doc, ink, ptsXscale, ptsYscale); + const newPoints = func(inkView, ink, ptsXscale, ptsYscale, NumCast(doc.strokeWidth)); if (newPoints) { const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X)); const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y)); @@ -73,8 +67,9 @@ export class InkStrokeProperties { */ @undoBatch @action - addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => { - this.applyFunction((doc: Doc, ink: InkData) => { + addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number, Y: number }[]) => { + this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { + const doc = view.rootDoc; const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]]; const newsegs = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).split(t); const splicepts = [...newsegs.left.points, ...newsegs.right.points]; @@ -143,9 +138,10 @@ export class InkStrokeProperties { */ @undoBatch @action - deletePoints = () => this.applyFunction((doc: Doc, ink: InkData) => { + deletePoints = (inkView: DocumentView) => this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { + const doc = view.rootDoc; const newPoints: { X: number, Y: number }[] = []; - const toRemove = Math.floor(((this._currentPoint + 2) / 4)); + const toRemove = Math.floor((this._currentPoint + 2) / 4); const last = this._currentPoint === ink.length - 1; for (let i = 0; i < ink.length; i++) { if (Math.floor((i + 2) / 4) !== toRemove && (toRemove !== 0 || i > 3)) { @@ -164,17 +160,22 @@ export class InkStrokeProperties { */ @undoBatch @action - rotateInk = (angle: number) => { - this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { - const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); - const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const centerPoint = { X: (oldXrange.min + oldXrange.max) / 2, Y: (oldYrange.min + oldYrange.max) / 2 }; + 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 - centerPoint.X, Y: i.Y - centerPoint.Y }; + 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 + centerPoint.X, Y: newY + centerPoint.Y }; + return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y }; }); + const doc = view.rootDoc; doc.rotation = NumCast(doc.rotation) + angle; return newPoints; }); @@ -185,8 +186,8 @@ export class InkStrokeProperties { */ @undoBatch @action - moveControlPtHandle = (deltaX: number, deltaY: number, controlIndex: number) => - this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { + moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number) => + this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => { const order = controlIndex % 4; const closed = InkingStroke.IsClosed(ink); @@ -235,7 +236,8 @@ export class InkStrokeProperties { /** * Handles the movement/scaling of a control point. */ - snapControl = (inkDoc: Doc, controlIndex: number) => { + snapControl = (inkView: DocumentView, controlIndex: number) => { + const inkDoc = inkView.rootDoc; const ink = Cast(inkDoc.data, InkField)?.inkData; if (ink) { const closed = InkingStroke.IsClosed(ink); @@ -257,8 +259,8 @@ export class InkStrokeProperties { 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 / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) { - return this.moveControlPtHandle((nearestPt.X - ink[controlIndex].X) * ptsXscale, (nearestPt.Y - ink[controlIndex].Y) * ptsYscale, controlIndex); + 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); } } return false; @@ -269,8 +271,9 @@ export class InkStrokeProperties { * @param handleIndexA The handle point that retains its current position. * @param handleIndexB The handle point that is rotated to be 180 degrees from its opposite. */ - snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => { - this.applyFunction((doc: Doc, ink: InkData) => { + snapHandleTangent = (inkView: DocumentView, controlIndex: number, handleIndexA: number, handleIndexB: number) => { + this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { + const doc = view.rootDoc; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"), []); const ind = brokenIndices.findIndex(value => value === controlIndex); if (ind !== -1) { @@ -330,8 +333,9 @@ export class InkStrokeProperties { */ @undoBatch @action - moveTangentHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => - this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => { + moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => + this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => { + const doc = view.rootDoc; const closed = InkingStroke.IsClosed(ink); const oldHandlePoint = ink[handleIndex]; const oppositeHandlePoint = ink[oppositeHandleIndex]; diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index df5bebf31..f88a20448 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -10,11 +10,13 @@ import { emptyFunction, setupMoveUpEvents } from "../../Utils"; import { Transform } from "../util/Transform"; import { UndoManager } from "../util/UndoManager"; import { Colors } from "./global/globalEnums"; -import { InkStrokeProperties } from "./InkStrokeProperties"; import { InkingStroke } from "./InkingStroke"; +import { InkStrokeProperties } from "./InkStrokeProperties"; +import { DocumentView } from "./nodes/DocumentView"; export interface InkHandlesProps { inkDoc: Doc; + inkView: DocumentView; screenCtrlPoints: InkData; screenSpaceLineWidth: number; ScreenToLocalTransform: () => Transform; @@ -37,7 +39,7 @@ export class InkTangentHandles extends React.Component { 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(-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(); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 752db1413..d312331d0 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -1,17 +1,17 @@ import React = require("react"); -import { Bezier } from "bezier-js"; import { action, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; import { documentSchema } from "../../fields/documentSchemas"; import { InkData, InkField, InkTool } from "../../fields/InkField"; import { makeInterface } from "../../fields/Schema"; -import { Cast, NumCast, StrCast, BoolCast } from "../../fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; import { TraceMobx } from "../../fields/util"; import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; import { CognitiveServices } from "../cognitive_services/CognitiveServices"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { InteractionUtils } from "../util/InteractionUtils"; +import { SnappingManager } from "../util/SnappingManager"; import { ContextMenu } from "./ContextMenu"; import { ViewBoxBaseComponent } from "./DocComponent"; import { Colors } from "./global/globalEnums"; @@ -20,7 +20,6 @@ import "./InkStroke.scss"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { InkTangentHandles } from "./InkTangentHandles"; import { FieldView, FieldViewProps } from "./nodes/FieldView"; -import { SnappingManager } from "../util/SnappingManager"; import Color = require("color"); type InkDocument = makeInterface<[typeof documentSchema]>; @@ -83,7 +82,7 @@ export class InkingStroke extends ViewBoxBaseComponent (schemaCtor: (doc: Doc) => T, moreProps?: // } } if (uriList) { - console.log("Web URI = ", uriList); // const existingWebDoc = await Hypothesis.findWebDoc(uriList); // if (existingWebDoc) { // const alias = Doc.MakeAlias(existingWebDoc); @@ -390,7 +389,6 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: // addDocument(alias); // } else { - console.log("Adding ..."); const newDoc = Docs.Create.WebDocument(uriList.split("#annotations:")[0], {// clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig) ...options, title: uriList.split("#annotations:")[0], @@ -399,8 +397,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: _nativeWidth: 850, useCors: true }); - console.log(" ... " + newDoc.title); - console.log(" ... " + addDocument(newDoc) + " " + newDoc.title); + addDocument(newDoc); } return; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7dcd63b80..febccbfcc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -75,7 +75,7 @@ export type collectionFreeformViewProps = { scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) engineProps?: any; - dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are trnasparent or not. + dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not. // However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents. }; @@ -1472,7 +1472,7 @@ export class CollectionFreeFormView extends CollectionSubView -
+
{this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)} this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; - setHeight = (height: number) => this.layoutDoc._height = height; + setHeight = (height: number) => { + if (this.props.renderDepth !== -1) { + this.layoutDoc._height = height; + } + } setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view); isContentActive = (outsideReaction?: boolean) => { return CurrentUserUtils.SelectedTool !== InkTool.None || diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 37d716993..9956cc36b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -58,8 +58,10 @@ export class WebBox extends ViewBoxAnnotatableComponent(); private _searchRef = React.createRef(); private _searchString = ""; + @observable private _webUrl = ""; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't wan the src parameter to also change as that would cause an unnecessary re-render. + @observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe. so thisforces the ifrmae to be rebuilt when allowScripts is toggled @observable private _searching: boolean = false; - @observable _showSidebar = false; + @observable private _showSidebar = false; @observable private _scrollTimer: any; @observable private _overlayAnnoInfo: Opt; @observable private _marqueeing: number[] | undefined; @@ -79,6 +81,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this._webUrl = this._url); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it. } @action @@ -239,7 +242,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. } else { this._iframeClick = this._iframe ?? undefined; @@ -269,9 +272,24 @@ export class WebBox extends ViewBoxAnnotatableComponent { const iframe = this._iframe; - this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp); + let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend("") + "/corsProxy/", "") ?? this._url.toString()); + if (requrlraw !== this._url.toString()) { + if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) { + const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g); + const newsearch = matches?.lastElement()!; + if (matches) { + requrlraw = requrlraw.substring(0, requrlraw.indexOf(newsearch)); + for (let i = 1; i < Array.from(matches)?.length; i++) { + requrlraw = requrlraw.replace(matches[i], ""); + } + } + requrlraw = requrlraw.replace(/q=[^&]*/, newsearch.substring(1)).replace("search&", "search?").replace("?gbv=1", ""); + } + this.submitURL(requrlraw, undefined, true); + } if (iframe?.contentDocument) { - iframe?.contentDocument.addEventListener("pointerdown", this.iframeDown); + iframe.contentDocument.addEventListener("pointerup", this.iframeUp); + iframe.contentDocument.addEventListener("pointerdown", this.iframeDown); this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight); setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000); const initialScroll = this._initialScroll; @@ -281,13 +299,15 @@ export class WebBox extends ViewBoxAnnotatableComponent { + iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => { let href = ""; - for (let ele = e.target; ele; ele = ele.parentElement) { + for (let ele = e.target as any; ele; ele = ele.parentElement) { href = (typeof (ele.href) === "string" ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href; } - if (href && this.webField?.origin) { - this.submitURL(href.replace(Utils.prepend(""), this.webField?.origin)); + const origin = this.webField?.origin; + if (href && origin) { + e.stopPropagation(); + setTimeout(() => this.submitURL(href.replace(Utils.prepend(""), origin))); if (this._outerRef.current) { this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop); this._outerRef.current.scrollLeft = 0; @@ -338,8 +358,15 @@ export class WebBox extends ViewBoxAnnotatableComponent([...history, this._url]); this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!)); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } return true; } return false; @@ -350,10 +377,16 @@ export class WebBox extends ViewBoxAnnotatableComponent([this._url]); else this.dataDoc[this.fieldKey + "-future"] = new List([...future, this._url]); this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!)); - console.log(this._urlHash); + if (this._webUrl === this._url) { + this._webUrl = curUrl; + setTimeout(action(() => this._webUrl = this._url)); + } else { + this._webUrl = this._url; + } return true; } return false; @@ -362,9 +395,8 @@ export class WebBox extends ViewBoxAnnotatableComponent { return Math.abs(s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0)); } - @action - submitURL = (newUrl?: string, preview?: boolean) => { + submitURL = (newUrl?: string, preview?: boolean, dontUpdateIframe?: boolean) => { if (!newUrl) return; if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl; try { @@ -376,7 +408,10 @@ export class WebBox extends ViewBoxAnnotatableComponent this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); + funcs.push({ + description: (this.layoutDoc.allowScripts ? "Prevent" : "Allow") + " Scripts", event: () => { + this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts; + if (this._iframe) { + runInAction(() => this._hackHide = true); + setTimeout(action(() => this._hackHide = false)); + } + }, icon: "snowflake" + }); funcs.push({ description: (!this.layoutDoc.forceReflow ? "Force" : "Prevent") + " Reflow", event: () => { const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1); @@ -468,18 +512,19 @@ export class WebBox extends ViewBoxAnnotatableComponent; } else if (field instanceof WebField) { - const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._url) : this._url; + const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._webUrl) : this._webUrl; view =