diff options
author | bobzel <zzzman@gmail.com> | 2021-02-10 22:32:29 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2021-02-10 22:32:29 -0500 |
commit | aed4a386bf57ba7b1b144bacd39f9f9ccabe0dfd (patch) | |
tree | 537ea7b01c98e5cdde362984292c4db3173e98c4 /src | |
parent | f240c85ff0adee914b43d9d169fa31260e03265d (diff) |
simplified focus'ing on documents. refactored scrollFocus code. changed focus in 2D to move doc into view but not center.
Diffstat (limited to 'src')
-rw-r--r-- | src/Utils.ts | 7 | ||||
-rw-r--r-- | src/client/util/DocumentManager.ts | 11 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 44 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 18 | ||||
-rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 3 | ||||
-rw-r--r-- | src/client/views/nodes/WebBox.tsx | 50 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 10 | ||||
-rw-r--r-- | src/client/views/pdf/PDFViewer.tsx | 15 |
8 files changed, 80 insertions, 78 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index c7074c3da..061c74611 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -191,6 +191,13 @@ export namespace Utils { return { h: h, s: s, l: l }; } + export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number) { + if (scrollTop + contextHgt < targetY + Math.max(targetHgt * 1.1, 100)) { + return Math.min(scrollTop, targetY + Math.max(targetHgt * 1.1, 100) - contextHgt); + } else if (scrollTop > targetY - targetHgt * .1) { + return Math.max(0, targetY - targetHgt * .1); + } + } export function clamp(n: number, lower: number, upper: number) { return Math.max(lower, Math.min(upper, n)); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 9a8b662e7..ac7710d34 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -150,29 +150,26 @@ export class DocumentManager { }; const docView = getFirstDocView(targetDoc, originatingDoc); let annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); - if (annotatedDoc && annotatedDoc !== originatingDoc?.context && !targetDoc?.isPushpin) { + if (!docView && annotatedDoc && annotatedDoc !== originatingDoc?.context && targetDoc.type === DocumentType.TEXTANCHOR) { const first = getFirstDocView(annotatedDoc); if (first) { annotatedDoc = first.rootDoc; 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? - const sameContext = annotatedDoc && annotatedDoc === originatingDoc?.context; + } else 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? if (originatingDoc?.isPushpin) { docView.props.focus(docView.rootDoc, willZoom, undefined, (didFocus: boolean) => { if (!didFocus || docView.rootDoc.hidden) { docView.rootDoc.hidden = !docView.rootDoc.hidden; } return focusAndFinish(); - }, sameContext, false);// don't want to focus the container if the source and target are in the same container, so pass 'sameContext' for dontCenter parameter - //finished?.(); + }); } else { docView.select(false); docView.rootDoc.hidden && (docView.rootDoc.hidden = undefined); // @ts-ignore - docView.props.focus(docView.rootDoc, willZoom, undefined, focusAndFinish, sameContext, false); + docView.props.focus(docView.rootDoc, willZoom, undefined, focusAndFinish); } highlight(); } else { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a52522def..68a65dfe7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -885,7 +885,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1]; } - focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, didFocus?: boolean) => { + focusDocument = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => { const state = HistoryUtil.getState(); // TODO This technically isn't correct if type !== "doc", as @@ -903,31 +903,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } SelectionManager.DeselectAll(); if (this.props.Document.scrollHeight) { - // only consider the document to be an annotation if it's an annotation on this collection's document (ignore annotations on some other document that are somehow being focused on here) - const annotOn = Doc.AreProtosEqual(doc.annotationOn as Doc, this.props.Document) ? Cast(doc.annotationOn, Doc) as Doc : undefined; - let delay = 1000; - if (!annotOn) { - !dontCenter && this.props.focus(doc); - afterFocus && setTimeout(afterFocus, delay); - } else { - const contextHgt = this.props.PanelHeight(); - const curScroll = NumCast(this.props.Document._scrollTop); - let scrollTo = curScroll; - if (curScroll + contextHgt < NumCast(doc.y)) { - scrollTo = NumCast(doc.y) + Math.max(NumCast(doc._height), 100) - contextHgt; - } else if (curScroll > NumCast(doc.y)) { - scrollTo = Math.max(0, NumCast(doc.y) - 50); - } - if (curScroll !== scrollTo || this.props.Document._viewTransition) { - delay = Math.abs(scrollTo - curScroll) > 5 ? 1000 : 0; - !dontCenter && this.props.focus(this.props.Document); - afterFocus && setTimeout(() => afterFocus?.(delay ? true : false), delay); - } else { - !dontCenter && delay && this.props.focus(this.props.Document); - afterFocus?.(!dontCenter && delay ? true : false); - } - } - + this.props.focus(doc, undefined, undefined, afterFocus); } else { const layoutdoc = Doc.Layout(doc); const savedState = { px: NumCast(this.Document._panX), py: NumCast(this.Document._panY), s: this.Document[this.scaleFieldKey], pt: this.Document._viewTransition }; @@ -937,8 +913,14 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P let newPanY = savedState.py; if (!layoutdoc.annotationOn) { // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection willZoom && this.setScaleToZoom(layoutdoc, scale); - newPanX = (NumCast(doc.x) + doc[WidthSym]() / 2) - (this.isAnnotationOverlay ? (Doc.NativeWidth(this.props.Document)) / 2 / this.zoomScaling() : 0); - newPanY = (NumCast(doc.y) + doc[HeightSym]() / 2) - (this.isAnnotationOverlay ? (Doc.NativeHeight(this.props.Document)) / 2 / this.zoomScaling() : 0); + const cx = NumCast(this.props.Document._panX); + const cy = NumCast(this.props.Document._panY); + const pwid = this.props.PanelWidth() / NumCast(this.props.Document._viewScale, 1); + const phgt = this.props.PanelHeight() / NumCast(this.props.Document._viewScale, 1); + const screen = { left: cx - pwid / 2, right: cx + pwid / 2, top: cy - phgt / 2, bot: cy + phgt / 2 }; + const bounds = { left: NumCast(doc.x) - pwid / 10, right: NumCast(doc.x) + doc[WidthSym]() + pwid / 10, top: NumCast(doc.y) - phgt / 10, bot: NumCast(doc.y) + doc[HeightSym]() + phgt / 10 }; + newPanX = cx + Math.min(0, bounds.left - screen.left) + Math.max(0, bounds.right - screen.right); + newPanY = cy + Math.min(0, bounds.top - screen.top) + Math.max(0, bounds.bot - screen.bot); newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); } @@ -949,12 +931,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } Doc.BrushDoc(this.props.Document); - const newDidFocus = didFocus || (newPanX !== savedState.px || newPanY !== savedState.py); - const newAfterFocus = (didFocus: boolean) => { afterFocus && setTimeout(() => { // @ts-ignore - if (afterFocus?.(!dontCenter && (didFocus || (newPanX !== savedState.px || newPanY !== savedState.py)))) { + if (afterFocus?.(didFocus || (newPanX !== savedState.px || newPanY !== savedState.py))) { this.Document._panX = savedState.px; this.Document._panY = savedState.py; this.Document[this.scaleFieldKey] = savedState.s; @@ -964,7 +944,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P }, newPanX !== savedState.px || newPanY !== savedState.py ? 500 : 0); return false; }; - this.props.focus(this.props.Document, undefined, undefined, newAfterFocus, undefined, newDidFocus); + this.props.focus(this.props.Document, undefined, undefined, newAfterFocus); !doc.hidden && Doc.linkFollowHighlight(doc); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 47668d0f3..a5698e138 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,6 +1,6 @@ import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; +import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, HeightSym } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -44,11 +44,11 @@ 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 DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => void; export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any; export interface DocComponentView { getAnchor: () => Doc; - scrollFocus?: (doc: Doc, smooth: boolean) => void; + scrollFocus?: (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => void; back?: () => boolean; forward?: () => boolean; url?: () => string; @@ -376,9 +376,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } } - focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, dontCenter?: boolean, focused?: boolean) => { - this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here - return this.props.focus(doc, willZoom, scale, afterFocus, dontCenter, focused); + focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => { + if (this._componentView?.scrollFocus) { + return this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo, afterFocus); // bcz: smooth parameter should really be passed into focus() instead of inferred here + } + return this.props.focus(doc, willZoom, scale, afterFocus); } onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 && @@ -917,8 +919,8 @@ export class DocumentView extends React.Component<DocumentViewProps> { 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); + focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, focused?: boolean) => { + return this.docView?.focus(doc, willZoom, scale, afterFocus, focused); } getBounds = () => { if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 496caedaa..4b926bb6f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -22,6 +22,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); +import { DocAfterFocusFunc } from './DocumentView'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @@ -79,7 +80,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum } } - scrollFocus = (doc: Doc, smooth: boolean) => doc !== this.rootDoc && this._pdfViewer?.scrollFocus(doc, smooth); + scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => { this._pdfViewer?.scrollFocus(doc, smooth, afterFocus); } getAnchor = () => this.rootDoc; componentWillUnmount() { this._selectReactionDisposer?.(); } componentDidMount() { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index ee152ddb3..d3d58b2f8 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -29,6 +29,7 @@ import { Annotation } from "../pdf/Annotation"; import { FieldView, FieldViewProps } from './FieldView'; import "./WebBox.scss"; import React = require("react"); +import { DocFocusFunc, DocAfterFocusFunc, DocumentView } from "./DocumentView"; const htmlToText = require("html-to-text"); type WebDocument = makeInterface<[typeof documentSchema]>; @@ -110,22 +111,21 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum } getAnchor = () => this.rootDoc; - scrollFocus = (doc: Doc, smooth: boolean) => { + scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => { 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); + const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1)); + if (scrollTo !== undefined) { + this._initialScroll !== undefined && (this._initialScroll = scrollTo); + this._ignoreScroll = true; + this.goTo(scrollTo, smooth ? 500 : 0); + this.layoutDoc._scrollTop = scrollTo; + this._ignoreScroll = false; + return afterFocus?.(true); } - smooth && (this.layoutDoc._scrollTop = NumCast(doc.y)); - this._ignoreScroll = false; } else { this._initialScroll = NumCast(doc.y); } + afterFocus?.(false); } async componentDidMount() { @@ -164,29 +164,35 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum } } - var quickScroll: string | undefined = ""; + var quickScroll = true; this._disposers.scrollReaction = reaction(() => NumCast(this.layoutDoc._scrollTop), (scrollTop) => { - if (quickScroll !== undefined) { + if (quickScroll) { this._initialScroll = scrollTop; } - else if (!this._ignoreScroll && this._outerRef.current && this.webpage) { + else if (!this._ignoreScroll) { 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; - } + this.goTo(scrollTop, duration); } }, { fireImmediately: true } ); - quickScroll = undefined; + quickScroll = false; + } + + goTo = (scrollTop: number, duration: number) => { + if (this._outerRef.current && this.webpage) { + 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; + } + } } componentWillUnmount() { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index c15c30803..183719e31 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -67,6 +67,7 @@ import { AnchorMenu } from '../../pdf/AnchorMenu'; import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DocumentManager } from '../../../util/DocumentManager'; import { LightboxView } from '../../LightboxView'; +import { DocAfterFocusFunc } from '../DocumentView'; const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { @@ -867,7 +868,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; } - scrollFocus = (doc: Doc, smooth: boolean) => { + scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => { const anchorId = doc[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; @@ -905,8 +906,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); const escAnchorId = anchorId[0] > '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId; addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" }); - setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), 1500); + setTimeout(() => { + clearStyleSheetRules(FormattedTextBox._highlightStyleSheet); + afterFocus?.(true); + }, 1500); } + } else { + afterFocus?.(false); } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0477192d5..dd9dfa733 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -28,6 +28,7 @@ import { AnchorMenu } from "./AnchorMenu"; import "./PDFViewer.scss"; const pdfjs = require('pdfjs-dist/es5/build/pdf.js'); import React = require("react"); +import { DocAfterFocusFunc } from "../nodes/DocumentView"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); const _global = (window /* browser */ || global /* node */) as any; @@ -179,15 +180,17 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu // scrolls to focus on a nested annotation document. if this is part a link preview then it will jump to the scroll location, // otherwise it will scroll smoothly. - scrollFocus = (doc: Doc, smooth: boolean) => { + scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => { const mainCont = this._mainCont.current; - if (mainCont) { - if (smooth) { - smoothScroll(500, mainCont, NumCast(doc.y)); - } else { - mainCont.scrollTop = NumCast(doc.y); + if (doc !== this.rootDoc && mainCont) { + const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1)); + if (scrollTo !== undefined) { + if (smooth) smoothScroll(500, mainCont, scrollTo); + else mainCont.scrollTop = scrollTo; + return afterFocus?.(true); } } + afterFocus?.(false); } @action |