From b1c83a54b8794c91bf2c218939891e87ad2a2776 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 2 Feb 2021 14:08:07 -0500 Subject: playing with translations in textbox sidebars. --- .../views/nodes/formattedText/FormattedTextBox.tsx | 55 ++++++++++++++++------ 1 file changed, 40 insertions(+), 15 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 d24ccd9ad..634165bab 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -67,12 +67,14 @@ import { StyleProp } from '../../StyleProvider'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DocumentManager } from '../../../util/DocumentManager'; +var translateGoogleApi = require("translate-google-api") export interface FormattedTextBoxProps { makeLink?: () => Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text hideOnLeave?: boolean; // used by DocumentView for setting caption's hide on leave (bcz: would prefer to have caption-hideOnLeave field set or something similar) xMargin?: number; // used to override document's settings for xMargin --- see CollectionCarouselView yMargin?: number; + noSidebar?: boolean; dontSelectOnLoad?: boolean; // suppress selecting the text box when loaded } export const GoogleRef = "googleDocId"; @@ -94,7 +96,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _ref: React.RefObject = React.createRef(); private _scrollRef: React.RefObject = React.createRef(); private _editorView: Opt; - private _applyingChange: boolean = false; + private _applyingChange: string = ""; private _searchIndex = 0; private _cachedLinks: Doc[] = []; private _undoTyping?: UndoManager.Batch; @@ -246,6 +248,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(Math.min(coordsT.left, coordsB.left), Math.max(coordsT.bottom, coordsB.bottom)); } + _lastText = ""; dispatchTransaction = (tx: Transaction) => { let timeStamp; clearTimeout(timeStamp); @@ -296,8 +299,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }; if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { - if (!this._applyingChange && removeSelection(json) !== removeSelection(curProto?.Data)) { - this._applyingChange = true; + if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { + this._applyingChange = this.fieldKey; (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (removeSelection(json) !== removeSelection(curLayout?.Data)) { @@ -325,7 +328,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have unchanged = false; } - this._applyingChange = false; + this._applyingChange = ""; if (!unchanged) { this.updateTitle(); this.tryUpdateHeight(); @@ -931,16 +934,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp () => { if (!this.dataDoc || !this.layoutDoc) return undefined; if (this.dataDoc?.[this.props.fieldKey + "-noTemplate"] || !this.layoutDoc[this.props.fieldKey]) { - return Cast(this.dataDoc[this.props.fieldKey], RichTextField, null)?.Data; + return { data: Cast(this.dataDoc[this.props.fieldKey], RichTextField, null), str: StrCast(this.dataDoc[this.props.fieldKey]) }; } - return Cast(this.layoutDoc[this.props.fieldKey], RichTextField, null)?.Data; + return { data: Cast(this.layoutDoc[this.props.fieldKey], RichTextField, null), str: StrCast(this.layoutDoc[this.props.fieldKey]) }; }, incomingValue => { - if (incomingValue !== undefined && this._editorView && !this._applyingChange) { - const updatedState = JSON.parse(incomingValue); - if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) { - this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); - this.tryUpdateHeight(); + if (this._editorView && this._applyingChange !== this.fieldKey) { + if (incomingValue?.data) { + const updatedState = JSON.parse(incomingValue.data.Data); + if (JSON.stringify(this._editorView.state.toJSON()) !== JSON.stringify(updatedState)) { + this._editorView.updateState(EditorState.fromJSON(this.config, updatedState)); + this.tryUpdateHeight(); + } + } else if (incomingValue?.str) { + selectAll(this._editorView!.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str))); } } }, @@ -1541,6 +1548,19 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBox.LiveTextUndo?.end(); FormattedTextBox.LiveTextUndo = undefined; + + const state = this._editorView!.state; + const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); + if (!this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) { + try { + translateGoogleApi(curText, { from: "en", to: "es", }).then(result => { + this.dataDoc[this.fieldKey + "-translation"] = result[0]; + }); + } catch (e) { + console.log(e.message); + } + this._lastText = curText; + } } _lastTimedMark: Mark | undefined = undefined; @@ -1636,7 +1656,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @computed get sidebarHandle() { const annotated = DocListCast(this.dataDoc[this.annotationKey]).filter(d => d?.author).length; - return !this.props.isSelected() && !(annotated && !this.sidebarWidth()) ? (null) : + return this.props.noSidebar || (!this.props.isSelected() && !(annotated && !this.sidebarWidth())) ? (null) :
- {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ? + + {/* {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ? : - } + } */}
; } -- cgit v1.2.3-70-g09d2 From 5666aa3b921024c0f7e6ebb48e0e8f50bb770e79 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 2 Feb 2021 23:00:17 -0500 Subject: fixed document decorations on lightbox. made translations an option for text boxes. --- src/client/views/DocumentDecorations.scss | 1 + src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/MainView.tsx | 6 +++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 27 ++++++++++------------ 4 files changed, 18 insertions(+), 18 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 22e120167..461ea0fef 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -4,6 +4,7 @@ $linkGap : 3px; .documentDecorations { position: absolute; + z-index: 20000; } .documentDecorations-container { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b8500c263..226eef658 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -604,7 +604,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2, pointerEvents: KeyManager.Instance.ShiftPressed || this.Interacting ? "none" : "all", - zIndex: SelectionManager.Views().length > 1 ? 900 : 0, + display: SelectionManager.Views().length <= 1 ? "none" : undefined }} onPointerDown={this.onBackgroundDown} onContextMenu={e => { e.preventDefault(); e.stopPropagation(); }} > {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 08d590fd4..8598a7303 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -59,7 +59,7 @@ import { AnchorMenu } from './pdf/AnchorMenu'; import { PreviewCursor } from './PreviewCursor'; import { PropertiesView } from './PropertiesView'; import { SearchBox } from './search/SearchBox'; -import { DefaultStyleProvider, StyleProp } from './StyleProvider'; +import { DefaultStyleProvider, StyleProp, DefaultLayerProvider } from './StyleProvider'; const _global = (window /* browser */ || global /* node */) as any; @observer @@ -596,6 +596,7 @@ export class MainView extends React.Component { lightboxWidth = () => window.innerWidth - Math.min(window.innerWidth / 4, 200) * 2; lightboxHeight = () => window.innerHeight - Math.min(window.innerHeight / 4, 100) * 2; + lightboxScreenToLocal = () => new Transform(-Math.min(window.innerWidth / 4, 200), -Math.min(window.innerHeight / 4, 100), 1); @computed get lightboxView() { return !this.LightboxDoc ? (null) :
this.LightboxDoc = undefined)} > @@ -614,7 +615,8 @@ export class MainView extends React.Component { rootSelected={returnTrue} removeDocument={undefined} styleProvider={DefaultStyleProvider} - ScreenToLocalTransform={Transform.Identity} + layerProvider={DefaultLayerProvider(this.LightboxDoc)} + ScreenToLocalTransform={this.lightboxScreenToLocal} PanelWidth={this.lightboxWidth} PanelHeight={this.lightboxHeight} focus={emptyFunction} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 634165bab..8b1e58ceb 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -67,7 +67,7 @@ import { StyleProp } from '../../StyleProvider'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DocumentManager } from '../../../util/DocumentManager'; -var translateGoogleApi = require("translate-google-api") +const translateGoogleApi = require("translate-google-api") export interface FormattedTextBoxProps { makeLink?: () => Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text @@ -1551,14 +1551,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const state = this._editorView!.state; const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); - if (!this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) { + if (this.layoutDoc.sidebarViewType === "translation" && !this.fieldKey.includes("translation") && curText.endsWith(" ") && curText !== this._lastText) { try { - translateGoogleApi(curText, { from: "en", to: "es", }).then(result => { - this.dataDoc[this.fieldKey + "-translation"] = result[0]; + translateGoogleApi(curText, { from: "en", to: "es", }).then((result1: any) => { + setTimeout(() => translateGoogleApi(result1[0], { from: "es", to: "en", }).then((result: any) => { + this.dataDoc[this.fieldKey + "-translation"] = result1 + "\r\n\r\n" + result[0]; + }), 1000); }); - } catch (e) { - console.log(e.message); - } + } catch (e) { console.log(e.message); } this._lastText = curText; } } @@ -1693,14 +1693,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return this.props.noSidebar || !this.layoutDoc._showSidebar || this.sidebarWidthPercent === "0%" ? (null) :
- - {/* {this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ? - : - } */} + {this.layoutDoc.sidebarViewType === "translation" ? + : + this.layoutDoc.sidebarViewType === CollectionViewType.Freeform ? + : + }
; } -- cgit v1.2.3-70-g09d2 From a774d7f55755703faa316401dd8724dd82a9ee98 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 3 Feb 2021 12:08:27 -0500 Subject: added linkRelationship to linkEditor. fixed warnings. --- src/client/views/GestureOverlay.tsx | 18 +---- src/client/views/collections/CollectionMenu.tsx | 11 ++- src/client/views/linking/LinkEditor.tsx | 84 +++++++++++++++------- src/client/views/nodes/WebBox.tsx | 1 + .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- 5 files changed, 71 insertions(+), 47 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 0cf958681..ae27e78d5 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -32,10 +32,6 @@ export class GestureOverlay extends Touchable { @observable public InkShape: string = ""; @observable public SavedColor?: string; @observable public SavedWidth?: string; - @observable public SavedFill?: string; - @observable public SavedArrowStart: string = "none"; - @observable public SavedArrowEnd: string = "none"; - @observable public SavedDash: String = "0"; @observable public Tool: ToolglassTools = ToolglassTools.None; @observable private _thumbX?: number; @@ -653,15 +649,7 @@ export class GestureOverlay extends Touchable { } else { this._points = []; } - //get out of ink mode after each stroke= - if (CollectionFreeFormViewChrome.Instance && !CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { - Doc.SetSelectedTool(InkTool.None); - CollectionFreeFormViewChrome.Instance._selectedPrimitive = CollectionFreeFormViewChrome.Instance._shapesPrimNum; - SetActiveArrowStart("none"); - GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart(); - SetActiveArrowEnd("none"); - GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd(); - } + CollectionFreeFormViewChrome.Instance.primCreated(); } makePolygon = (shape: string, gesture: boolean) => { @@ -969,13 +957,9 @@ Scripting.addGlobal(function setPen(width: any, color: any, fill: any, arrowStar SetActiveInkColor(color); GestureOverlay.Instance.SavedWidth = ActiveInkWidth(); SetActiveInkWidth(width); - GestureOverlay.Instance.SavedFill = ActiveFillColor(); SetActiveFillColor(fill); - GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart(); SetActiveArrowStart(arrowStart); - GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd(); SetActiveArrowStart(arrowEnd); - GestureOverlay.Instance.SavedDash = ActiveDash(); SetActiveDash(dash); }); }); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 4e2d81e61..7ba857449 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -25,7 +25,7 @@ import { undoBatch } from "../../util/UndoManager"; import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu"; import { EditableView } from "../EditableView"; import { GestureOverlay } from "../GestureOverlay"; -import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke"; +import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, ActiveArrowStart, ActiveArrowEnd } from "../InkingStroke"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocumentView } from "../nodes/DocumentView"; import { RichTextMenu } from "../nodes/formattedText/RichTextMenu"; @@ -591,12 +591,19 @@ export class CollectionFreeFormViewChrome extends React.Component { diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index 435b9d904..f74b422d3 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -4,7 +4,6 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc } from "../../../fields/Doc"; import { DateCast, StrCast } from "../../../fields/Types"; -import { Utils } from "../../../Utils"; import { LinkManager } from "../../util/LinkManager"; import { undoBatch } from "../../util/UndoManager"; import './LinkEditor.scss'; @@ -21,31 +20,41 @@ interface LinkEditorProps { export class LinkEditor extends React.Component { @observable description = StrCast(LinkManager.currentLink?.description); + @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); @observable openDropdown: boolean = false; @observable showInfo: boolean = false; @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; } @observable private buttonColor: string = ""; - + @observable private relationshipButtonColor: string = ""; //@observable description = this.props.linkDoc.description ? StrCast(this.props.linkDoc.description) : "DESCRIPTION"; - @undoBatch @action + @undoBatch deleteLink = (): void => { LinkManager.Instance.deleteLink(this.props.linkDoc); this.props.showLinks(); } - @undoBatch @action - setDescripValue = (value: string) => { + @undoBatch + setRelationshipValue = action((value: string) => { + if (LinkManager.currentLink) { + LinkManager.currentLink.linkRelationship = value; + this.relationshipButtonColor = "rgb(62, 133, 55)"; + setTimeout(action(() => this.relationshipButtonColor = ""), 750); + return true; + } + }); + + @undoBatch + setDescripValue = action((value: string) => { if (LinkManager.currentLink) { LinkManager.currentLink.description = value; this.buttonColor = "rgb(62, 133, 55)"; setTimeout(action(() => this.buttonColor = ""), 750); return true; } - } + }); - @action onKey = (e: React.KeyboardEvent) => { if (e.key === "Enter") { this.setDescripValue(this.description); @@ -53,22 +62,48 @@ export class LinkEditor extends React.Component { } } - @action - onDown = () => { - this.setDescripValue(this.description); + onRelationshipKey = (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + this.setRelationshipValue(this.relationship); + document.getElementById('input')?.blur(); + } } + onDown = () => this.setDescripValue(this.description); + onRelationshipDown = () => this.setRelationshipValue(this.description); + @action - handleChange = (e: React.ChangeEvent) => { - this.description = e.target.value; - } + handleChange = (e: React.ChangeEvent) => { this.description = e.target.value; } + @action + handleRelationshipChange = (e: React.ChangeEvent) => { this.relationship = e.target.value; } + @computed + get editRelationship() { + return
+
Link Relationship:
+
+
+ +
+
Set
+
+
; + } @computed get editDescription() { return
-
- Link Label:
+
Link Description:
{
Set
-
; +
+
; } @action - changeDropdown = () => { - this.openDropdown = !this.openDropdown; - } + changeDropdown = () => { this.openDropdown = !this.openDropdown; } @undoBatch changeFollowBehavior = action((follow: string) => { @@ -101,8 +135,7 @@ export class LinkEditor extends React.Component { @computed get followingDropdown() { return
-
- Follow Behavior:
+
Follow Behavior:
@@ -157,9 +190,7 @@ export class LinkEditor extends React.Component { } @action - changeInfo = () => { - this.showInfo = !this.showInfo; - } + changeInfo = () => { this.showInfo = !this.showInfo; } render() { const destination = LinkManager.getOppositeAnchor(this.props.linkDoc, this.props.sourceDoc); @@ -186,8 +217,9 @@ export class LinkEditor extends React.Component { {DateCast(this.props.linkDoc.creationDate).toString()}
: null}
: null} -
{this.editDescription}
-
{this.followingDropdown}
+ {this.editDescription} + {this.editRelationship} + {this.followingDropdown} ); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 438395ab0..352abd1e7 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -256,6 +256,7 @@ export class WebBox extends ViewBoxAnnotatableComponent Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text @@ -947,7 +947,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.tryUpdateHeight(); } } else if (incomingValue?.str) { - selectAll(this._editorView!.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str))); + selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue.str))); } } }, -- cgit v1.2.3-70-g09d2 From 0e5891eab7f53697b764b7e9da5163db0351a0a2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 8 Feb 2021 19:25:48 -0500 Subject: overhaul of link anchors on text boxes to use actual Documents to represent selected text. Also got rid of _scrollY and _scrollPreviewY so that all document regions can be focused on using focus() and the new scrollFocus() mechanisim --- src/client/documents/DocumentTypes.ts | 4 +- src/client/documents/Documents.ts | 7 + src/client/util/DocumentManager.ts | 8 +- src/client/views/GestureOverlay.tsx | 17 +- src/client/views/MainView.tsx | 2 +- src/client/views/StyleProvider.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 +- src/client/views/nodes/DocumentLinksButton.tsx | 14 +- src/client/views/nodes/DocumentView.tsx | 14 +- src/client/views/nodes/LinkDocPreview.tsx | 15 +- src/client/views/nodes/PDFBox.tsx | 5 +- src/client/views/nodes/PresBox.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 184 ++++++++---------- .../views/nodes/formattedText/FormattedTextBox.tsx | 207 ++++++++++----------- .../formattedText/FormattedTextBoxComment.tsx | 18 +- .../views/nodes/formattedText/RichTextMenu.tsx | 24 +-- src/client/views/nodes/formattedText/marks_rts.ts | 20 +- src/client/views/pdf/PDFViewer.tsx | 116 +++++------- src/fields/documentSchemas.ts | 7 +- src/fields/util.ts | 4 +- 20 files changed, 306 insertions(+), 372 deletions(-) (limited to 'src/client/views/nodes/formattedText/FormattedTextBox.tsx') diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 37a148e55..080657fd8 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -38,5 +38,7 @@ export enum DocumentType { LINKDB = "linkdb", // database of links ??? why do we have this SCRIPTDB = "scriptdb", // database of scripts - GROUPDB = "groupdb" // database of groups + GROUPDB = "groupdb", // database of groups + + TEXTANCHOR = "textanchor" // selection of text in a text box } \ No newline at end of file diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1a4aae17e..7d6db06d5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -373,6 +373,9 @@ export namespace Docs { }], [DocumentType.GROUP, { layout: { view: EmptyBox, dataField: defaultDataKey } + }], + [DocumentType.TEXTANCHOR, { + layout: { view: EmptyBox, dataField: defaultDataKey } }] ]); @@ -785,6 +788,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.DOCHOLDER), document, { title: document ? document.title + "" : "container", targetDropAction: "move", ...options }); } + export function TextanchorDocument(options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.TEXTANCHOR), document, { targetDropAction: "move", ...options }); + } + export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id); } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 7af16ed6e..f5f6b6f67 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -149,7 +149,8 @@ export class DocumentManager { const highlight = () => { const finalDocView = getFirstDocView(targetDoc); if (finalDocView) { - finalDocView.layoutDoc.scrollToLinkID = linkDoc?.[Id]; + const parent = targetDoc?.annotationOn as Doc; + if (parent) finalDocView.layoutDoc.scrollToAnchorID = targetDoc?.[Id]; Doc.linkFollowHighlight(finalDocView.props.Document); } }; @@ -159,7 +160,7 @@ export class DocumentManager { const first = getFirstDocView(annotatedDoc); if (first) { annotatedDoc = first.props.Document; - first.props.focus(annotatedDoc, false); + first.focus(targetDoc, false); } } if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? @@ -190,7 +191,6 @@ export class DocumentManager { highlight(); } else { // otherwise try to get a view of the context of the target const targetDocContextView = getFirstDocView(targetDocContext); - targetDocContext._scrollY = targetDocContext._scrollPreviewY = NumCast(targetDocContext._scrollTop, 0); // this will force PDFs to activate and load their annotations / allow scrolling if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. targetDocContext._viewTransition = "transform 500ms"; targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom); @@ -208,7 +208,7 @@ export class DocumentManager { } else if (delay > 1500) { // we didn't find the target, so it must have moved out of the context. Go back to just creating it. if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.props.Document); - if (targetDoc.layout) { + if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document Doc.SetInPlace(targetDoc, "annotationOn", undefined, false); createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target } diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 76cb0112e..9306cf9ae 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -516,7 +516,7 @@ export class GestureOverlay extends Touchable { } handleLineGesture = (): boolean => { - let actionPerformed = false; + const actionPerformed = false; const B = this.svgBounds; // get the two targets at the ends of the line @@ -525,21 +525,6 @@ export class GestureOverlay extends Touchable { const target1 = document.elementFromPoint(ep1.X, ep1.Y); const target2 = document.elementFromPoint(ep2.X, ep2.Y); - // callback function to be called by each target - const callback = (doc: Doc) => { - if (!this._d1) { - this._d1 = doc; - } - // we don't want to create a link of both endpoints are the same document (doing so makes drawing an l very hard) - else if (this._d1 !== doc && !LinkManager.Instance.doesLinkExist(this._d1, doc)) { - // we don't want to create a link between ink strokes (doing so makes drawing a t very hard) - if (this._d1.type !== "ink" && doc.type !== "ink") { - DocUtils.MakeLink({ doc: this._d1 }, { doc: doc }, "gestural link", ""); - actionPerformed = true; - } - } - }; - const ge = new CustomEvent("dashOnGesture", { bubbles: true, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 44aa41f85..bc3d05005 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -101,7 +101,7 @@ export class MainView extends React.Component { } new InkStrokeProperties(); this._sidebarContent.proto = undefined; - DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's + DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg))); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 3956b8c5b..058d21c92 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -12,7 +12,7 @@ import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; import { CollectionViewType } from './collections/CollectionView'; import { MainView } from './MainView'; -import { DocumentViewProps } from "./nodes/DocumentView"; +import { DocumentViewProps, DocumentView } from "./nodes/DocumentView"; import { FieldViewProps } from './nodes/FieldView'; import "./StyleProvider.scss"; import React = require("react"); @@ -111,6 +111,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { const [xp, yp] = this.getTransform().transformPoint(de.x, de.y); if (this.isAnnotationOverlay !== true && de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp); - if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalPdfAnnoDrop(e, de.complete.annoDragData, xp, yp); + if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp); if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp); return false; } @@ -920,7 +919,6 @@ export class CollectionFreeFormView extends CollectionSubView 5 ? 1000 : 0; !dontCenter && this.props.focus(this.props.Document); afterFocus && setTimeout(() => afterFocus?.(delay ? true : false), delay); diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 2cac2d0b8..defa4dbf0 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -58,8 +58,6 @@ export class DocumentLinksButton extends React.Component this.props.View.LinkBeingCreated = dropEv.linkDocument); - setTimeout(action(() => this.props.View.LinkBeingCreated = undefined), 0); } linkDrag?.end(); }, @@ -118,7 +116,7 @@ export class DocumentLinksButton extends React.Component { - DocumentLinksButton.StartLinkView && (DocumentLinksButton.StartLinkView.LinkBeingCreated = undefined); - endLinkView.LinkBeingCreated = undefined; - }), 0); - } + LinkManager.currentLink = linkDoc; if (DocumentLinksButton.AnnotationId && DocumentLinksButton.AnnotationUri) { // if linking from a Hypothes.is annotation diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2f56f5b00..463e59bd1 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -40,12 +40,15 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { PresBox } from './PresBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); +import { LinkDocPreview } from "./LinkDocPreview"; +import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment"; export type DocAfterFocusFunc = (notFocused: boolean) => boolean; export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => void; export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; export interface DocComponentView { getAnchor: () => Doc; + scrollFocus?: (doc: Doc, smooth: boolean) => void; back?: () => boolean; forward?: () => boolean; url?: () => string; @@ -373,6 +376,10 @@ export class DocumentViewInternal extends DocComponent { + this._componentView?.scrollFocus?.(doc, !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc); // bcz: smooth parameter should really be passed into focus() instead of inferred here + return this.props.focus(doc, willZoom, scale, afterFocus, dontCenter, focused); + } onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 && (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) { @@ -710,7 +717,6 @@ export class DocumentViewInternal extends DocComponent this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; - makeLink = () => this.props.DocumentView.LinkBeingCreated; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined. setContentView = (view: { getAnchor: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view; @observable contentsActive: () => boolean = returnFalse; @action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive; @@ -729,9 +735,9 @@ export class DocumentViewInternal extends DocComponent {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors} {this.hideLinkButton ? (null) : @@ -865,7 +871,6 @@ export class DocumentView extends React.Component { public get displayName() { return "DocumentView(" + this.props.Document?.title + ")"; } // this makes mobx trace() statements more descriptive public ContentRef = React.createRef(); - @observable LinkBeingCreated: Opt; // see DocumentLinksButton for explanation of how this works @observable public docView: DocumentViewInternal | undefined | null; get Document() { return this.props.Document; } @@ -907,6 +912,9 @@ export class DocumentView extends React.Component { toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); contentsActive = () => this.docView?.contentsActive(); + focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => { + return this.docView?.focus(doc, willZoom, scale, afterFocus, dontCenter, focused); + } getBounds = () => { if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { return undefined; diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 75ca6059e..8051568ff 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -26,6 +26,7 @@ export class LinkDocPreview extends React.Component { @observable public static LinkInfo: Opt<{ linkDoc?: Doc; linkSrc: Doc; href?: string; Location: number[], docprops: DocumentViewSharedProps }>; @observable _targetDoc: Opt; @observable _toolTipText = ""; + _linkTarget: Opt; _editRef = React.createRef(); @action @@ -58,17 +59,13 @@ export class LinkDocPreview extends React.Component { runInAction(() => this._toolTipText = "external => " + this.props.href); } } else if (linkDoc && linkSrc) { - const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), linkSrc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc); - const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; + const anchor1 = linkDoc.anchor1 as Doc; + const anchor2 = linkDoc.anchor2 as Doc; + this._linkTarget = Doc.AreProtosEqual(anchor1, linkSrc) || Doc.AreProtosEqual(anchor1.annotationOn as Doc, linkSrc) ? anchor2 : anchor1; + const target = this._linkTarget?.annotationOn ? await DocCastAsync(this._linkTarget.annotationOn) : this._linkTarget; runInAction(() => { this._toolTipText = ""; LinkDocPreview.TargetDoc = this._targetDoc = target; - if (this._targetDoc) { - this._targetDoc._scrollToPreviewLinkID = linkDoc?.[Id]; - if (anchor !== this._targetDoc && anchor) { - this._targetDoc._scrollPreviewY = NumCast(anchor?.y); - } - } }); } } @@ -89,7 +86,7 @@ export class LinkDocPreview extends React.Component { : - this._linkTarget !== this._targetDoc && this._linkTarget && r?.focus(this._linkTarget)} Document={this._targetDoc} moveDocument={returnFalse} rootSelected={returnFalse} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index ec9a75302..496caedaa 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -79,8 +79,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent doc !== this.rootDoc && this._pdfViewer?.scrollFocus(doc, smooth); + getAnchor = () => this.rootDoc; componentWillUnmount() { this._selectReactionDisposer?.(); } componentDidMount() { + this.props.setContentView?.(this); this._selectReactionDisposer = reaction(() => this.props.isSelected(), () => { document.removeEventListener("keydown", this.onKeyDown); @@ -218,7 +221,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent>(); render() { TraceMobx(); - if (true) {//this.props.isSelected() || (this.props.active() && this.props.renderDepth === 0) || this.props.Document._scrollY !== undefined) { + if (true) {//this.props.isSelected() || (this.props.active() && this.props.renderDepth === 0)) { this._displayPdfLive = true; } if (this._displayPdfLive) { diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index b6feace12..589a1c2ae 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -364,7 +364,7 @@ export class PresBox extends ViewBoxBaseComponent bestTarget && runInAction(() => { if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; - bestTarget._scrollY = activeItem.presPinViewScroll; + bestTarget._scrollTop = activeItem.presPinViewScroll; } else if (bestTarget.type === DocumentType.COMPARISON) { bestTarget._clipWidth = activeItem.presPinClipWidth; } else if (bestTarget.type === DocumentType.VID) { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 1dfe88f78..ee152ddb3 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from "mobx-react"; import { Dictionary } from "typescript-collections"; import * as WebRequest from 'web-request'; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, WidthSym, Opt } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { HtmlField } from "../../../fields/HtmlField"; @@ -24,11 +24,11 @@ import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; +import { MarqueeAnnotator } from "../MarqueeAnnotator"; import { Annotation } from "../pdf/Annotation"; import { FieldView, FieldViewProps } from './FieldView'; import "./WebBox.scss"; import React = require("react"); -import { MarqueeAnnotator } from "../MarqueeAnnotator"; const htmlToText = require("html-to-text"); type WebDocument = makeInterface<[typeof documentSchema]>; @@ -44,15 +44,14 @@ export class WebBox extends ViewBoxAnnotatableComponent = React.createRef(); private _iframeIndicatorRef = React.createRef(); private _iframeDragRef = React.createRef(); + private _ignoreScroll = false; + private _initialScroll: Opt; @observable private _marqueeing: number[] | undefined; @observable private _url: string = "hello"; @observable private _pressX: number = 0; @observable private _pressY: number = 0; @observable private _iframe: HTMLIFrameElement | null = null; @observable private _savedAnnotations: Dictionary = new Dictionary(); - - get _collapsed() { return StrCast(this.layoutDoc._chromeStatus) !== "enabled"; } - set _collapsed(value) { this.layoutDoc._chromeStatus = !value ? "enabled" : "disabled"; } get scrollHeight() { return this.webpage?.scrollHeight || 1000; } get webpage() { return this._iframe?.contentDocument?.children[0]; } @@ -65,9 +64,14 @@ export class WebBox extends ViewBoxAnnotatableComponent { + iframeLoaded = (e: any) => { const iframe = this._iframe; if (iframe?.contentDocument) { + if (this._initialScroll !== undefined && this._outerRef.current && this.webpage) { + this.webpage.scrollTop = this._initialScroll; + this._outerRef.current.scrollTop = this._initialScroll; + this._initialScroll = undefined; + } iframe.setAttribute("enable-annotation", "true"); iframe.contentDocument.addEventListener("click", undoBatch(action(e => { let href = ""; @@ -76,81 +80,53 @@ export class WebBox extends ViewBoxAnnotatableComponent ({ scrollY: this.layoutDoc._scrollY, scrollX: this.layoutDoc._scrollX }), - ({ scrollY, scrollX }) => { - const delay = this._outerRef.current ? 0 : 250; // wait for mainCont and try again to scroll - const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); - const duration = durationStr ? Number(durationStr[1]) : 1000; - if (scrollY !== undefined) { - this._forceSmoothScrollUpdate = true; - setTimeout(() => this.layoutDoc._scrollY = undefined, duration); - setTimeout(() => this.webpage && smoothScroll(duration, this.webpage as any as HTMLElement, Math.abs(scrollY || 0)), delay); - setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollY || 0), () => this.layoutDoc._scrollTop = scrollY), delay); - } - if (scrollX !== undefined) { - this._forceSmoothScrollUpdate = true; - setTimeout(() => this.layoutDoc._scrollX = undefined, duration); - setTimeout(() => this.webpage && smoothScroll(duration, this.webpage as any as HTMLElement, Math.abs(scrollX || 0)), delay); - setTimeout(() => this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollX || 0), () => this.layoutDoc._scrollLeft = scrollX), delay); - } - }, - { fireImmediately: true } - ); - this._disposers.scrollTop = reaction(() => this.layoutDoc._scrollTop, - scrollTop => { - const durationStr = StrCast(this.Document._viewTransition).match(/([0-9]*)ms/); - const duration = durationStr ? Number(durationStr[1]) : 1000; - if (scrollTop !== this._outerRef.current?.scrollTop && scrollTop !== undefined && this._forceSmoothScrollUpdate) { - this.webpage!.scrollTop = scrollTop; - this._outerRef.current && smoothScroll(duration, this._outerRef.current, Math.abs(scrollTop || 0), () => this._forceSmoothScrollUpdate = true); - } else this._forceSmoothScrollUpdate = true; - }, - { fireImmediately: true } - ); - }); - _forceSmoothScrollUpdate = true; - - updateScroll = (x: Opt, y: Opt) => { - if (y !== undefined) { - this._outerRef.current!.scrollTop = y; - this.layoutDoc._scrollY = undefined; - } - if (x !== undefined) { - this._outerRef.current!.scrollLeft = x; - this.layoutDoc.scrollX = undefined; } } - iframeWheel = (e: any) => { - if (this._forceSmoothScrollUpdate && e.target?.children) { - this.webpage && setTimeout(action(() => { - this.webpage!.scrollLeft = 0; - const scrollTop = this.webpage!.scrollTop; - const scrollLeft = this.webpage!.scrollLeft; - this._outerRef.current!.scrollTop = scrollTop; - this._outerRef.current!.scrollLeft = scrollLeft; - if (this.layoutDoc._scrollTop !== scrollTop) { - this._forceSmoothScrollUpdate = false; - this.layoutDoc._scrollTop = scrollTop; - } - if (this.layoutDoc._scrollLeft !== scrollLeft) { - this._forceSmoothScrollUpdate = false; - this.layoutDoc._scrollLeft = scrollLeft; - } - })); + @action + onWheelScroll = (scrollTop: number) => { + if (this.webpage && this._outerRef.current) { + this.webpage.scrollLeft = 0; + this._outerRef.current.scrollTop = scrollTop; + this._outerRef.current.scrollLeft = 0; + this._ignoreScroll = true; + if (this.layoutDoc._scrollTop !== scrollTop) { + this.layoutDoc._scrollTop = scrollTop; + } + this._ignoreScroll = false; } } + iframeWheel = (e: any) => this.webpage && e.target?.children && this.onWheelScroll(this.webpage.scrollTop); + onWheel = (e: React.WheelEvent) => { + this._outerRef.current && this.onWheelScroll(this._outerRef.current.scrollTop); + e.stopPropagation(); + } getAnchor = () => this.rootDoc; + scrollFocus = (doc: Doc, smooth: boolean) => { + if (doc !== this.rootDoc && this.webpage && this._outerRef.current) { + this._initialScroll !== undefined && (this._initialScroll = NumCast(doc.y)); + this._ignoreScroll = true; + if (smooth) { + smoothScroll(500, this.webpage as any as HTMLElement, NumCast(doc.y)); + smoothScroll(500, this._outerRef.current, NumCast(doc.y)); + } else { + this.webpage.scrollTop = NumCast(doc.y); + this._outerRef.current.scrollTop = NumCast(doc.y); + } + smooth && (this.layoutDoc._scrollTop = NumCast(doc.y)); + this._ignoreScroll = false; + } else { + this._initialScroll = NumCast(doc.y); + } + } async componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. @@ -158,9 +134,6 @@ export class WebBox extends ViewBoxAnnotatableComponent this._url = urlField?.url.toString() || ""); - this._disposers.scrollMove = reaction(() => this.layoutDoc.x || this.layoutDoc.y, - () => this.updateScroll(this.layoutDoc._scrollLeft, this.layoutDoc._scrollTop)); - this._disposers.selection = reaction(() => this.props.isSelected(), selected => { if (!selected) { @@ -190,6 +163,30 @@ export class WebBox extends ViewBoxAnnotatableComponent NumCast(this.layoutDoc._scrollTop), + (scrollTop) => { + if (quickScroll !== undefined) { + this._initialScroll = scrollTop; + } + else if (!this._ignoreScroll && this._outerRef.current && this.webpage) { + const viewTrans = StrCast(this.Document._viewTransition); + const durationMiliStr = viewTrans.match(/([0-9]*)ms/); + const durationSecStr = viewTrans.match(/([0-9.]*)s/); + const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0; + if (duration) { + smoothScroll(duration, this.webpage as any as HTMLElement, scrollTop); + smoothScroll(duration, this._outerRef.current, scrollTop); + } else { + this.webpage.scrollTop = scrollTop; + this._outerRef.current.scrollTop = scrollTop; + } + } + }, + { fireImmediately: true } + ); + quickScroll = undefined; } componentWillUnmount() { @@ -353,22 +350,14 @@ export class WebBox extends ViewBoxAnnotatableComponent { // this._pressX = e.clientX; // this._pressY = e.clientY; } - onLongPressUp = (e: PointerEvent) => { - if (this._longPressSecondsHack) { - clearTimeout(this._longPressSecondsHack); - } - if (this._iframeIndicatorRef.current) { - this._iframeIndicatorRef.current.classList.remove("active"); - } - if (this._iframeDragRef.current) { - while (this._iframeDragRef.current.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild); - } + this._longPressSecondsHack && clearTimeout(this._longPressSecondsHack); + this._iframeIndicatorRef.current?.classList.remove("active"); + while (this._iframeDragRef.current?.firstChild) this._iframeDragRef.current.removeChild(this._iframeDragRef.current.firstChild); } specificContextMenu = (e: React.MouseEvent): void => { @@ -377,7 +366,6 @@ export class WebBox extends ViewBoxAnnotatableComponent this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); funcs.push({ description: (this.layoutDoc[this.fieldKey + "-contentWidth"] ? "Unfreeze" : "Freeze") + " Content Width", event: () => this.layoutDoc[this.fieldKey + "-contentWidth"] = this.layoutDoc[this.fieldKey + "-contentWidth"] ? undefined : Doc.NativeWidth(this.layoutDoc), icon: "snowflake" }); cm.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); - } @computed @@ -389,7 +377,6 @@ export class WebBox extends ViewBoxAnnotatableComponent { e.currentTarget.before((e.currentTarget.contentDocument?.body || e.currentTarget.contentDocument)?.children[0]!); e.currentTarget.remove(); }} - view =