From 4aafe1c7386eae62e6159d50251fa74d246300ba Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 4 Jun 2020 16:43:17 -0400
Subject: cleaned up animation timeline a bit.  fixed interaction of clicking
 on timeline header to navigate

---
 src/client/views/animationtimeline/Timeline.tsx | 218 +++++++-----------------
 1 file changed, 60 insertions(+), 158 deletions(-)

(limited to 'src/client/views/animationtimeline/Timeline.tsx')

diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 30692944d..017623120 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -1,19 +1,19 @@
-import * as React from "react";
-import "./Timeline.scss";
-import { listSpec } from "../../../fields/Schema";
+import { faBackward, faForward, faGripLines, faPauseCircle, faPlayCircle } from "@fortawesome/free-solid-svg-icons";
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, observable, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
-import { Track } from "./Track";
-import { observable, action, computed, runInAction, IReactionDisposer, reaction, trace } from "mobx";
-import { Cast, NumCast, StrCast, BoolCast } from "../../../fields/Types";
-import { List } from "../../../fields/List";
+import * as React from "react";
 import { Doc, DocListCast } from "../../../fields/Doc";
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faPlayCircle, faBackward, faForward, faGripLines, faPauseCircle, faEyeSlash, faEye, faCheckCircle, faTimesCircle } from "@fortawesome/free-solid-svg-icons";
-import { ContextMenu } from "../ContextMenu";
-import { TimelineOverview } from "./TimelineOverview";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
+import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils";
 import { FieldViewProps } from "../nodes/FieldView";
 import { KeyframeFunc } from "./Keyframe";
-import { Utils } from "../../../Utils";
+import "./Timeline.scss";
+import { TimelineOverview } from "./TimelineOverview";
+import { Track } from "./Track";
+import { utils } from "mocha";
+import { Util } from "../../../../deploy/assets/pdf.worker";
+import clamp from "../../util/clamp";
 
 /**
  * Timeline class controls most of timeline functions besides individual keyframe and track mechanism. Main functions are 
@@ -39,7 +39,6 @@ import { Utils } from "../../../Utils";
 @observer
 export class Timeline extends React.Component<FieldViewProps> {
 
-
     //readonly constants
     private readonly DEFAULT_TICK_SPACING: number = 50;
     private readonly MAX_TITLE_HEIGHT = 75;
@@ -57,7 +56,7 @@ export class Timeline extends React.Component<FieldViewProps> {
     @observable private _infoContainer = React.createRef<HTMLDivElement>();
     @observable private _roundToggleRef = React.createRef<HTMLDivElement>();
     @observable private _roundToggleContainerRef = React.createRef<HTMLDivElement>();
-    @observable private _timeInputRef = React.createRef<HTMLInputElement>();
+
 
     //boolean vars and instance vars 
     @observable private _currentBarX: number = 0;
@@ -71,27 +70,18 @@ export class Timeline extends React.Component<FieldViewProps> {
     @observable private _tickIncrement = this.DEFAULT_TICK_INCREMENT;
     @observable private _time = 100000; //DEFAULT
     @observable private _playButton = faPlayCircle;
-    @observable private _mouseToggled = false;
-    @observable private _doubleClickEnabled = false;
     @observable private _titleHeight = 0;
 
-    // so a reaction can be made
-    @observable public _isAuthoring = this.props.Document.isATOn;
-
     /**
      * collection get method. Basically defines what defines collection's children. These will be tracked in the timeline. Do not edit. 
      */
     @computed
-    private get children(): List<Doc> {
-        const extendedDocument = ["image", "video", "pdf"].includes(StrCast(this.props.Document.type));
-        if (extendedDocument) {
-            if (this.props.Document.data_ext) {
-                return Cast((Cast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + "-annotations"], Doc) as Doc).annotations, listSpec(Doc)) as List<Doc>;
-            } else {
-                return new List<Doc>();
-            }
+    private get children(): Doc[] {
+        const annotatedDoc = ["image", "video", "pdf"].includes(StrCast(this.props.Document.type));
+        if (annotatedDoc) {
+            return DocListCast(this.props.Document[Doc.LayoutFieldKey(this.props.Document) + "-annotations"]);
         }
-        return Cast(this.props.Document[this.props.fieldKey], listSpec(Doc)) as List<Doc>;
+        return DocListCast(this.props.Document[this.props.fieldKey]);
     }
 
     /////////lifecycle functions////////////
@@ -144,9 +134,7 @@ export class Timeline extends React.Component<FieldViewProps> {
     }
 
     //for playing
-    @action
     onPlay = (e: React.MouseEvent) => {
-        e.preventDefault();
         e.stopPropagation();
         this.play();
     }
@@ -156,24 +144,15 @@ export class Timeline extends React.Component<FieldViewProps> {
      */
     @action
     play = () => {
-        if (this._isPlaying) {
-            this._isPlaying = false;
-            this._playButton = faPlayCircle;
-        } else {
-            this._isPlaying = true;
-            this._playButton = faPauseCircle;
-            const playTimeline = () => {
-                if (this._isPlaying) {
-                    if (this._currentBarX >= this._totalLength) {
-                        this.changeCurrentBarX(0);
-                    } else {
-                        this.changeCurrentBarX(this._currentBarX + this._windSpeed);
-                    }
-                    setTimeout(playTimeline, 15);
-                }
-            };
-            playTimeline();
-        }
+        const playTimeline = () => {
+            if (this._isPlaying) {
+                this.changeCurrentBarX(this._currentBarX >= this._totalLength ? 0 : this._currentBarX + this._windSpeed);
+                setTimeout(playTimeline, 15);
+            }
+        };
+        this._isPlaying = !this._isPlaying;
+        this._playButton = this._isPlaying ? faPauseCircle : faPlayCircle;
+        this._isPlaying && playTimeline();
     }
 
 
@@ -206,12 +185,7 @@ export class Timeline extends React.Component<FieldViewProps> {
      */
     @action
     onScrubberDown = (e: React.PointerEvent) => {
-        e.preventDefault();
-        e.stopPropagation();
-        document.addEventListener("pointermove", this.onScrubberMove);
-        document.addEventListener("pointerup", () => {
-            document.removeEventListener("pointermove", this.onScrubberMove);
-        });
+        setupMoveUpEvents(this, e, this.onScrubberMove, emptyFunction, emptyFunction);
     }
 
     /**
@@ -219,12 +193,11 @@ export class Timeline extends React.Component<FieldViewProps> {
      */
     @action
     onScrubberMove = (e: PointerEvent) => {
-        e.preventDefault();
-        e.stopPropagation();
         const scrubberbox = this._infoContainer.current!;
         const left = scrubberbox.getBoundingClientRect().left;
         const offsetX = Math.round(e.clientX - left) * this.props.ScreenToLocalTransform().Scale;
         this.changeCurrentBarX(offsetX + this._visibleStart); //changes scrubber to clicked scrubber position
+        return false;
     }
 
     /**
@@ -232,27 +205,8 @@ export class Timeline extends React.Component<FieldViewProps> {
      */
     @action
     onPanDown = (e: React.PointerEvent) => {
-        e.preventDefault();
-        e.stopPropagation();
-        const clientX = e.clientX;
-        if (this._doubleClickEnabled) {
-            this._doubleClickEnabled = false;
-        } else {
-            setTimeout(() => {
-                if (!this._mouseToggled && this._doubleClickEnabled) this.changeCurrentBarX(this._trackbox.current!.scrollLeft + clientX - this._trackbox.current!.getBoundingClientRect().left);
-                this._mouseToggled = false;
-                this._doubleClickEnabled = false;
-            }, 200);
-            this._doubleClickEnabled = true;
-            document.addEventListener("pointermove", this.onPanMove);
-            document.addEventListener("pointerup", () => {
-                document.removeEventListener("pointermove", this.onPanMove);
-                if (!this._doubleClickEnabled) {
-                    this._mouseToggled = false;
-                }
-            });
-
-        }
+        setupMoveUpEvents(this, e, this.onPanMove, emptyFunction, (e) =>
+            this.changeCurrentBarX(this._trackbox.current!.scrollLeft + e.clientX - this._trackbox.current!.getBoundingClientRect().left));
     }
 
     /**
@@ -260,11 +214,6 @@ export class Timeline extends React.Component<FieldViewProps> {
      */
     @action
     onPanMove = (e: PointerEvent) => {
-        e.preventDefault();
-        e.stopPropagation();
-        if (e.movementX !== 0 || e.movementY !== 0) {
-            this._mouseToggled = true;
-        }
         const trackbox = this._trackbox.current!;
         const titleContainer = this._titleContainer.current!;
         this.movePanX(this._visibleStart - e.movementX);
@@ -276,43 +225,25 @@ export class Timeline extends React.Component<FieldViewProps> {
             this._time -= KeyframeFunc.convertPixelTime(e.movementX, "mili", "time", this._tickSpacing, this._tickIncrement);
             this.props.Document.AnimationLength = this._time;
         }
-
+        return false;
     }
 
 
     @action
     movePanX = (pixel: number) => {
-        const infoContainer = this._infoContainer.current!;
-        infoContainer.scrollLeft = pixel;
-        this._visibleStart = infoContainer.scrollLeft;
+        this._infoContainer.current!.scrollLeft = pixel;
+        this._visibleStart = this._infoContainer.current!.scrollLeft;
     }
 
     /**
      * resizing timeline (in editing mode) (the hamburger drag icon)
      */
-    @action
     onResizeDown = (e: React.PointerEvent) => {
-        e.preventDefault();
-        e.stopPropagation();
-        document.addEventListener("pointermove", this.onResizeMove);
-        document.addEventListener("pointerup", () => {
-            document.removeEventListener("pointermove", this.onResizeMove);
-        });
-    }
-
-    @action
-    onResizeMove = (e: PointerEvent) => {
-        e.preventDefault();
-        e.stopPropagation();
-        const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
-        // let offset = 0;
-        if (this._containerHeight + offset <= this.MIN_CONTAINER_HEIGHT) {
-            this._containerHeight = this.MIN_CONTAINER_HEIGHT;
-        } else if (this._containerHeight + offset >= this.MAX_CONTAINER_HEIGHT) {
-            this._containerHeight = this.MAX_CONTAINER_HEIGHT;
-        } else {
-            this._containerHeight += offset;
-        }
+        setupMoveUpEvents(this, e, action((e) => {
+            const offset = e.clientY - this._timelineContainer.current!.getBoundingClientRect().bottom;
+            this._containerHeight = clamp(this.MIN_CONTAINER_HEIGHT, this._containerHeight + offset, this.MAX_CONTAINER_HEIGHT);
+            return false;
+        }), emptyFunction, emptyFunction);
     }
 
     /**
@@ -323,8 +254,8 @@ export class Timeline extends React.Component<FieldViewProps> {
         time = time / 1000;
         const inSeconds = Math.round(time * 100) / 100;
 
-        const min: (string | number) = Math.floor(inSeconds / 60);
-        const sec: (string | number) = (Math.round((inSeconds % 60) * 100) / 100);
+        const min = Math.floor(inSeconds / 60);
+        const sec = (Math.round((inSeconds % 60) * 100) / 100);
         let secString = sec.toFixed(2);
 
         if (Math.floor(sec / 10) === 0) {
@@ -346,7 +277,7 @@ export class Timeline extends React.Component<FieldViewProps> {
         const offset = e.clientX - this._infoContainer.current!.getBoundingClientRect().left;
         const prevTime = KeyframeFunc.convertPixelTime(this._visibleStart + offset, "mili", "time", this._tickSpacing, this._tickIncrement);
         const prevCurrent = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement);
-        e.deltaY < 0 ? this.zoom(true) : this.zoom(false);
+        this.zoom(e.deltaY < 0);
         const currPixel = KeyframeFunc.convertPixelTime(prevTime, "mili", "pixel", this._tickSpacing, this._tickIncrement);
         const currCurrent = KeyframeFunc.convertPixelTime(prevCurrent, "mili", "pixel", this._tickSpacing, this._tickIncrement);
         this._infoContainer.current!.scrollLeft = currPixel - offset;
@@ -405,30 +336,17 @@ export class Timeline extends React.Component<FieldViewProps> {
     private timelineToolBox = (scale: number, totalTime: number) => {
         const size = 40 * scale; //50 is default
         const iconSize = 25;
+        const width: number = this.props.PanelWidth();
+        const modeType = this.props.Document.isATOn ? "Author" : "Play";
 
         //decides if information should be omitted because the timeline is very small
         // if its less than 950 pixels then it's going to be overlapping
-        let shouldCompress = false;
-        const width: number = this.props.PanelWidth();
+        let modeString = modeType, overviewString = "", lengthString = "";
         if (width < 850) {
-            shouldCompress = true;
-        }
-
-        let modeString, overviewString, lengthString;
-        const modeType = this.props.Document.isATOn ? "Author" : "Play";
-
-        if (!shouldCompress) {
             modeString = "Mode: " + modeType;
             overviewString = "Overview:";
             lengthString = "Length: ";
         }
-        else {
-            modeString = modeType;
-            overviewString = "";
-            lengthString = "";
-        }
-
-        // let rightInfo = this.timeIndicator;
 
         return (
             <div key="timeline_toolbox" className="timeline-toolbox" style={{ height: `${size}px` }}>
@@ -451,8 +369,7 @@ export class Timeline extends React.Component<FieldViewProps> {
                     <div className="time-box overview-tool" style={{ display: "flex" }}>
                         {this.timeIndicator(lengthString, totalTime)}
                         <div className="resetView-tool" title="Return to Default View" onClick={() => this.resetView(this.props.Document)}><FontAwesomeIcon icon="compress-arrows-alt" size="lg" /></div>
-                        <div className="resetView-tool" style={{ display: this._isAuthoring ? "flex" : "none" }} title="Set Default View" onClick={() => this.setView(this.props.Document)}><FontAwesomeIcon icon="expand-arrows-alt" size="lg" /></div>
-
+                        <div className="resetView-tool" style={{ display: this.props.Document.isATOn ? "flex" : "none" }} title="Set Default View" onClick={() => this.setView(this.props.Document)}><FontAwesomeIcon icon="expand-arrows-alt" size="lg" /></div>
                     </div>
                 </div>
             </div>
@@ -498,15 +415,15 @@ export class Timeline extends React.Component<FieldViewProps> {
         const roundToggle = this._roundToggleRef.current!;
         const roundToggleContainer = this._roundToggleContainerRef.current!;
         const timelineContainer = this._timelineContainer.current!;
-        if (BoolCast(this.props.Document.isATOn)) {
+
+        this.props.Document.isATOn = !this.props.Document.isATOn;
+        if (!BoolCast(this.props.Document.isATOn)) {
             //turning on playmode...
             roundToggle.style.transform = "translate(0px, 0px)";
             roundToggle.style.animationName = "turnoff";
             roundToggleContainer.style.animationName = "turnoff";
             roundToggleContainer.style.backgroundColor = "white";
             timelineContainer.style.top = `${-this._containerHeight}px`;
-            this.props.Document.isATOn = false;
-            this._isAuthoring = false;
             this.toPlay();
         } else {
             //turning on authoring mode...
@@ -515,8 +432,6 @@ export class Timeline extends React.Component<FieldViewProps> {
             roundToggleContainer.style.animationName = "turnon";
             roundToggleContainer.style.backgroundColor = "#9acedf";
             timelineContainer.style.top = "0px";
-            this.props.Document.isATOn = true;
-            this._isAuthoring = true;
             this.toAuthoring();
         }
     }
@@ -533,10 +448,7 @@ export class Timeline extends React.Component<FieldViewProps> {
     // @computed
     getCurrentTime = () => {
         let current = KeyframeFunc.convertPixelTime(this._currentBarX, "mili", "time", this._tickSpacing, this._tickIncrement);
-        if (current > this._time) {
-            current = this._time;
-        }
-        return this.toReadTime(current);
+        return this.toReadTime(current > this._time ? this._time : current);
     }
 
     @observable private mapOfTracks: (Track | null)[] = [];
@@ -562,18 +474,13 @@ export class Timeline extends React.Component<FieldViewProps> {
 
     @action
     toAuthoring = () => {
-        let longestTime = this.findLongestTime();
-        if (longestTime === 0) longestTime = 1;
-        const adjustedTime = Math.ceil(longestTime / 100000) * 100000;
-        // console.log(adjustedTime); 
-        this._totalLength = KeyframeFunc.convertPixelTime(adjustedTime, "mili", "pixel", this._tickSpacing, this._tickIncrement);
-        this._time = adjustedTime;
+        this._time = Math.ceil((this.findLongestTime() ?? 1) / 100000) * 100000;
+        this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement);
     }
 
     @action
     toPlay = () => {
-        const longestTime = this.findLongestTime();
-        this._time = longestTime;
+        this._time = this.findLongestTime();
         this._totalLength = KeyframeFunc.convertPixelTime(this._time, "mili", "pixel", this._tickSpacing, this._tickIncrement);
     }
 
@@ -582,40 +489,35 @@ export class Timeline extends React.Component<FieldViewProps> {
      * basically the only thing you need to edit besides render methods in track (individual track lines) and keyframe (green region)
      */
     render() {
-        setTimeout(() => {
-            this.changeLengths();
-            // this.toPlay();
-            // this._time = longestTime;
-        }, 0);
+        setTimeout(() => this.changeLengths(), 0);
 
-        const longestTime = this.findLongestTime();
         trace();
         // change visible and total width
         return (
             <div style={{ visibility: "visible" }}>
-                <div key="timeline_wrapper" style={{ visibility: BoolCast(this.props.Document.isATOn) ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}>
+                <div key="timeline_wrapper" style={{ visibility: this.props.Document.isATOn ? "visible" : "hidden", left: "0px", top: "0px", position: "absolute", width: "100%", transform: "translate(0px, 0px)" }}>
                     <div key="timeline_container" className="timeline-container" ref={this._timelineContainer} style={{ height: `${this._containerHeight}px`, top: `0px` }}>
-                        <div key="timeline_info" className="info-container" ref={this._infoContainer} onWheel={this.onWheelZoom}>
+                        <div key="timeline_info" className="info-container" onPointerDown={this.onPanDown} ref={this._infoContainer} onWheel={this.onWheelZoom}>
                             {this.drawTicks()}
                             <div key="timeline_scrubber" className="scrubber" style={{ transform: `translate(${this._currentBarX}px)` }}>
                                 <div key="timeline_scrubberhead" className="scrubberhead" onPointerDown={this.onScrubberDown} ></div>
                             </div>
-                            <div key="timeline_trackbox" className="trackbox" ref={this._trackbox} onPointerDown={this.onPanDown} style={{ width: `${this._totalLength}px` }}>
-                                {DocListCast(this.children).map(doc =>
+                            <div key="timeline_trackbox" className="trackbox" ref={this._trackbox} style={{ width: `${this._totalLength}px` }}>
+                                {this.children.map(doc =>
                                     <Track ref={ref => this.mapOfTracks.push(ref)} node={doc} currentBarX={this._currentBarX} changeCurrentBarX={this.changeCurrentBarX} transform={this.props.ScreenToLocalTransform()} time={this._time} tickSpacing={this._tickSpacing} tickIncrement={this._tickIncrement} collection={this.props.Document} timelineVisible={true} />
                                 )}
                             </div>
                         </div>
                         <div className="currentTime">Current: {this.getCurrentTime()}</div>
                         <div key="timeline_title" className="title-container" ref={this._titleContainer}>
-                            {DocListCast(this.children).map(doc => <div style={{ height: `${(this._titleHeight)}px` }} className="datapane" onPointerOver={() => { Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}><p>{doc.title}</p></div>)}
+                            {this.children.map(doc => <div style={{ height: `${(this._titleHeight)}px` }} className="datapane" onPointerOver={() => { Doc.BrushDoc(doc); }} onPointerOut={() => { Doc.UnBrushDoc(doc); }}><p>{doc.title}</p></div>)}
                         </div>
                         <div key="timeline_resize" onPointerDown={this.onResizeDown}>
                             <FontAwesomeIcon className="resize" icon={faGripLines} />
                         </div>
                     </div>
                 </div>
-                {this.timelineToolBox(1, longestTime)}
+                {this.timelineToolBox(1, this.findLongestTime())}
             </div>
         );
     }
-- 
cgit v1.2.3-70-g09d2


From cdc9c7e293a656573d49c4265b5867e34e8db7c8 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 5 Jun 2020 18:30:56 -0400
Subject: changed all scroll behaviors to be _scrollTop to go to a scroll and
 _scrollY to animate to it.

---
 src/client/util/DocumentManager.ts                 |  2 +-
 src/client/views/animationtimeline/Timeline.tsx    |  2 --
 src/client/views/animationtimeline/Track.tsx       |  5 +++--
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/PDFBox.tsx                  |  2 +-
 src/client/views/nodes/WebBox.tsx                  | 14 +++++++-------
 .../views/nodes/formattedText/FormattedTextBox.tsx |  4 ++--
 .../formattedText/FormattedTextBoxComment.tsx      |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 | 22 +++++++++-------------
 src/fields/documentSchemas.ts                      |  8 ++++----
 11 files changed, 30 insertions(+), 35 deletions(-)

(limited to 'src/client/views/animationtimeline/Timeline.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 67f2f244c..78c05f572 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -168,7 +168,7 @@ export class DocumentManager {
                 highlight();
             } else {  // otherwise try to get a view of the context of the target
                 const targetDocContextView = getFirstDocView(targetDocContext);
-                targetDocContext.scrollY = 0;  // this will force PDFs to activate and load their annotations / allow scrolling
+                targetDocContext._scrollY = 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.panTransformType = "Ease";
                     targetDocContextView.props.focus(targetDocContextView.props.Document, willZoom);
diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx
index 017623120..43f15a33f 100644
--- a/src/client/views/animationtimeline/Timeline.tsx
+++ b/src/client/views/animationtimeline/Timeline.tsx
@@ -11,8 +11,6 @@ import { KeyframeFunc } from "./Keyframe";
 import "./Timeline.scss";
 import { TimelineOverview } from "./TimelineOverview";
 import { Track } from "./Track";
-import { utils } from "mocha";
-import { Util } from "../../../../deploy/assets/pdf.worker";
 import clamp from "../../util/clamp";
 
 /**
diff --git a/src/client/views/animationtimeline/Track.tsx b/src/client/views/animationtimeline/Track.tsx
index fc96c320a..4987006e7 100644
--- a/src/client/views/animationtimeline/Track.tsx
+++ b/src/client/views/animationtimeline/Track.tsx
@@ -31,13 +31,14 @@ export class Track extends React.Component<IProps> {
     @observable private _autoKfReaction: any;
     @observable private _newKeyframe: boolean = false;
     private readonly MAX_TITLE_HEIGHT = 75;
-    private _trackHeight = 0;
+    @observable private _trackHeight = 0;
     private primitiveWhitelist = [
         "x",
         "y",
         "_width",
         "_height",
         "opacity",
+        "_scrollTop"
     ];
     private objectWhitelist = [
         "data"
@@ -51,7 +52,7 @@ export class Track extends React.Component<IProps> {
         if (!regions) this.props.node.regions = new List<Doc>(); //if there is no region, then create new doc to store stuff
         //these two lines are exactly same from timeline.tsx 
         const relativeHeight = window.innerHeight / 20;
-        this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT; //for responsiveness
+        runInAction(() => this._trackHeight = relativeHeight < this.MAX_TITLE_HEIGHT ? relativeHeight : this.MAX_TITLE_HEIGHT); //for responsiveness
         this._timelineVisibleReaction = this.timelineVisibleReaction();
         this._currentBarXReaction = this.currentBarXReaction();
         if (DocListCast(this.props.node.regions).length === 0) this.createRegion(this.time);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 423eb1d90..56c6ce457 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -343,7 +343,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
                                 if (focusNode) {
                                     const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect();
                                     const x = (rect?.x || 0);
-                                    const y = NumCast(srcWeb.scrollTop) + (rect?.y || 0);
+                                    const y = NumCast(srcWeb._scrollTop) + (rect?.y || 0);
                                     const anchor = Docs.Create.FreeformDocument([], { _LODdisable: true, _backgroundColor: "transparent", _width: 25, _height: 25, x, y, annotationOn: srcWeb });
                                     anchor.context = srcWeb;
                                     const key = Doc.LayoutFieldKey(srcWeb);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index fc5e61026..80ee2a65d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -886,7 +886,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             } else {
                 const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
                 const offset = annotOn && (contextHgt / 2 * 96 / 72);
-                this.props.Document.scrollY = NumCast(doc.y) - offset;
+                this.props.Document._scrollY = NumCast(doc.y) - offset;
             }
 
             afterFocus && setTimeout(afterFocus, 1000);
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 493f23dc4..fae216f17 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -248,7 +248,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum
     _pdfjsRequested = false;
     render() {
         const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField, null);
-        if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document.scrollY !== undefined) this._everActive = true;
+        if (this.props.isSelected() || this.props.renderDepth <= 1 || this.props.Document._scrollY !== undefined) this._everActive = true;
         if (pdfUrl && (this._everActive || this.props.Document._scrollTop || (this.dataDoc[this.props.fieldKey + "-nativeWidth"] && this.props.ScreenToLocalTransform().Scale < 2.5))) {
             if (pdfUrl instanceof PdfField && this._pdf) {
                 return this.renderPdfView;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c4ab3c9e2..6aff22f41 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -58,15 +58,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             iframe.contentDocument.addEventListener('pointerdown', this.iframedown, false);
             iframe.contentDocument.addEventListener('scroll', this.iframeScrolled, false);
             this.layoutDoc.scrollHeight = iframe.contentDocument.children?.[0].scrollHeight || 1000;
-            iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc.scrollTop);
-            iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc.scrollLeft);
+            iframe.contentDocument.children[0].scrollTop = NumCast(this.layoutDoc._scrollTop);
+            iframe.contentDocument.children[0].scrollLeft = NumCast(this.layoutDoc._scrollLeft);
         }
         this._reactionDisposer?.();
-        this._reactionDisposer = reaction(() => ({ y: this.layoutDoc.scrollY, x: this.layoutDoc.scrollX }),
+        this._reactionDisposer = reaction(() => ({ y: this.layoutDoc._scrollY, x: this.layoutDoc._scrollX }),
             ({ x, y }) => {
                 if (y !== undefined) {
                     this._outerRef.current!.scrollTop = y;
-                    this.layoutDoc.scrollY = undefined;
+                    this.layoutDoc._scrollY = undefined;
                 }
                 if (x !== undefined) {
                     this._outerRef.current!.scrollLeft = x;
@@ -83,8 +83,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     iframeScrolled = (e: any) => {
         const scrollTop = e.target?.children?.[0].scrollTop;
         const scrollLeft = e.target?.children?.[0].scrollLeft;
-        this.layoutDoc.scrollTop = this._outerRef.current!.scrollTop = scrollTop;
-        this.layoutDoc.scrollLeft = this._outerRef.current!.scrollLeft = scrollLeft;
+        this.layoutDoc._scrollTop = this._outerRef.current!.scrollTop = scrollTop;
+        this.layoutDoc._scrollLeft = this._outerRef.current!.scrollLeft = scrollLeft;
     }
     async componentDidMount() {
         const urlField = Cast(this.dataDoc[this.props.fieldKey], WebField);
@@ -440,7 +440,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
             {this.urlEditor()}
         </>);
     }
-    scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc.scrollLeft), NumCast(this.layoutDoc.scrollTop));
+    scrollXf = () => this.props.ScreenToLocalTransform().translate(NumCast(this.layoutDoc._scrollLeft), NumCast(this.layoutDoc._scrollTop));
     render() {
         return (<div className={`webBox-container`}
             style={{
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index fc131cd38..8b98fd083 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -721,7 +721,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             },
             { fireImmediately: true }
         );
-        this._disposers.scroll = reaction(() => NumCast(this.layoutDoc.scrollPos),
+        this._disposers.scroll = reaction(() => NumCast(this.layoutDoc._scrollTop),
             pos => this._scrollRef.current && this._scrollRef.current.scrollTo({ top: pos }), { fireImmediately: true }
         );
 
@@ -1174,7 +1174,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     }
 
     onscrolled = (ev: React.UIEvent) => {
-        this.layoutDoc.scrollPos = this._scrollRef.current!.scrollTop;
+        this.layoutDoc._scrollTop = this._scrollRef.current!.scrollTop;
     }
     @action
     tryUpdateHeight(limitHeight?: number) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
index d47ae63af..59a6045ab 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx
@@ -183,7 +183,7 @@ export class FormattedTextBoxComment {
                             const anchor = FieldValue(Doc.AreProtosEqual(FieldValue(Cast(linkDoc.anchor1, Doc)), textBox.dataDoc) ? Cast(linkDoc.anchor2, Doc) : (Cast(linkDoc.anchor1, Doc)) || linkDoc);
                             const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor;
                             if (anchor !== target && anchor && target) {
-                                target.scrollY = NumCast(anchor?.y);
+                                target._scrollY = NumCast(anchor?.y);
                             }
                             if (target) {
                                 ReactDOM.render(<ContentFittingDocumentView
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 5bad248be..ec5c3f4ff 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -2,7 +2,6 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction
 import { observer } from "mobx-react";
 import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
-import * as rp from "request-promise";
 import { Dictionary } from "typescript-collections";
 import { Doc, DocListCast, FieldResult, HeightSym, Opt, WidthSym } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
@@ -38,9 +37,7 @@ import { Networking } from "../../Network";
 
 export const pageSchema = createSchema({
     curPage: "number",
-    fitWidth: "boolean",
     rotation: "number",
-    scrollY: "number",
     scrollHeight: "number",
     serachMatch: "boolean"
 });
@@ -95,7 +92,6 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     @observable private _showWaiting = true;
     @observable private _showCover = false;
     @observable private _zoomed = 1;
-    @observable private _scrollTop = 0;
 
     private _pdfViewer: any;
     private _retries = 0; // number of times tried to create the PDF viewer 
@@ -143,6 +139,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" };
         runInAction(() => this._showWaiting = this._showCover = true);
         this.props.startupLive && this.setupPdfJsViewer();
+        this._mainCont.current!.scrollTop = this.layoutDoc._scrollTop || 0;
         this._searchReactionDisposer = reaction(() => this.Document.searchMatch, search => {
             if (search) {
                 this.search(Doc.SearchQuery(), true);
@@ -158,14 +155,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
             () => (SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(),
             { fireImmediately: true });
         this._reactionDisposer = reaction(
-            () => this.Document.scrollY,
+            () => this.Document._scrollY,
             (scrollY) => {
                 if (scrollY !== undefined) {
-                    if (this._showCover || this._showWaiting) {
-                        this.setupPdfJsViewer();
-                    }
-                    this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document.scrollY || 0));
-                    this.Document.scrollY = undefined;
+                    (this._showCover || this._showWaiting) && this.setupPdfJsViewer();
+                    this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0));
+                    setTimeout(() => this.Document._scrollY = undefined, 1000);
                 }
             },
             { fireImmediately: true }
@@ -220,7 +215,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
         await this.initialLoad();
 
         this._scrollTopReactionDisposer = reaction(() => Cast(this.layoutDoc._scrollTop, "number", null),
-            (stop) => (stop !== undefined) && this._mainCont.current && smoothScroll(500, this._mainCont.current, stop), { fireImmediately: true });
+            (stop) => (stop !== undefined && this.layoutDoc._scrollY === undefined) && (this._mainCont.current!.scrollTop = stop), { fireImmediately: true });
+
         this._annotationReactionDisposer = reaction(
             () => DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]),
             annotations => annotations?.length && (this._annotations = annotations),
@@ -352,7 +348,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
 
     @action
     onScroll = (e: React.UIEvent<HTMLElement>) => {
-        this._scrollTop = this._mainCont.current!.scrollTop;
+        this.Document._scrollY === undefined && (this.layoutDoc._scrollTop = this._mainCont.current!.scrollTop);
         this._pdfViewer && (this.Document.curPage = this._pdfViewer.currentPageNumber);
     }
 
@@ -604,7 +600,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu
     }
 
     scrollXf = () => {
-        return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform();
+        return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this.layoutDoc._scrollTop || 0) : this.props.ScreenToLocalTransform();
     }
     onClick = (e: React.MouseEvent) => {
         this._setPreviewCursor &&
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index e7031cc39..e0a02d93c 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -22,10 +22,10 @@ export const documentSchema = createSchema({
     y: "number",                // y coordinate when in a freeform view 
     z: "number",                // z "coordinate" - non-zero specifies the overlay layer of a freeformview
     zIndex: "number",           // zIndex of a document in a freeform view
-    scrollY: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
-    scrollX: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
-    scrollTop: "number",        // scroll position of a scrollable document (pdf, text, web)
-    scrollLeft: "number",        // scroll position of a scrollable document (pdf, text, web)
+    _scrollY: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
+    _scrollX: "number",          // "command" to scroll a document to a position on load (the value will be reset to 0 after that )
+    _scrollTop: "number",        // scroll position of a scrollable document (pdf, text, web)
+    _scrollLeft: "number",        // scroll position of a scrollable document (pdf, text, web)
 
     // appearance properties on the layout
     _autoHeight: "boolean",     // whether the height of the document should be computed automatically based on its contents
-- 
cgit v1.2.3-70-g09d2

