From 380ee1acac1c0b7972d7d423cf804af146dc0edf Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 10 Dec 2023 20:19:27 -0500
Subject: massive changes to use mobx 6 which means not accessing props
 directly in @computed functions.

---
 .../collections/CollectionStackedTimeline.tsx      | 217 +++++++++++----------
 1 file changed, 118 insertions(+), 99 deletions(-)

(limited to 'src/client/views/collections/CollectionStackedTimeline.tsx')

diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 92b5470ae..28d15be71 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,7 +1,7 @@
-import * as React from 'react';
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, makeObservable, observable, override, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
+import * as React from 'react';
 import { Doc, Opt } from '../../../fields/Doc';
 import { Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
@@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema';
 import { ComputedField, ScriptField } from '../../../fields/ScriptField';
 import { Cast, NumCast } from '../../../fields/Types';
 import { ImageField } from '../../../fields/URLField';
-import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
+import { copyProps, emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils';
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
@@ -20,9 +20,9 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals';
 import { SnappingManager } from '../../util/SnappingManager';
 import { Transform } from '../../util/Transform';
 import { undoBatch, UndoManager } from '../../util/UndoManager';
-import { AudioWaveform } from '../AudioWaveform';
-import { CollectionSubView } from '../collections/CollectionSubView';
+import { CollectionSubView, SubCollectionViewProps } from '../collections/CollectionSubView';
 import { LightboxView } from '../LightboxView';
+import { AudioWaveform } from '../nodes/audio/AudioWaveform';
 import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView';
 import { LabelBox } from '../nodes/LabelBox';
 import { VideoBox } from '../nodes/VideoBox';
@@ -53,17 +53,28 @@ export enum TrimScope {
 }
 
 @observer
-export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
+export class CollectionStackedTimeline extends CollectionSubView<SubCollectionViewProps & CollectionStackedTimelineProps>() {
     @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
     @observable public static CurrentlyPlaying: DocumentView[];
 
+    _prevProps: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>;
+    @override _props: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>;
+    constructor(props: React.PropsWithChildren<SubCollectionViewProps & CollectionStackedTimelineProps>) {
+        super(props);
+        this._props = this._prevProps = props;
+        makeObservable(this);
+    }
+    componentDidUpdate() {
+        copyProps(this);
+    }
+
     static LabelScript: ScriptField;
     static LabelPlayScript: ScriptField;
 
     private _timeline: HTMLDivElement | null = null; // ref to actual timeline div
     private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling
     private _markerStart: number = 0;
-    @observable _markerEnd: number | undefined;
+    @observable _markerEnd: number | undefined = undefined;
     @observable _trimming: number = TrimScope.None;
     @observable _trimStart: number = 0; // trim controls start pos
     @observable _trimEnd: number = 0; // trim controls end pos
@@ -73,14 +84,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     @observable _hoverTime: number = 0;
 
-    @observable _thumbnail: string | undefined;
+    @observable _thumbnail: string | undefined = undefined;
 
     // ensures that clip doesn't get trimmed so small that controls cannot be adjusted anymore
     get minTrimLength() {
         return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5);
     }
     @computed get thumbnails() {
-        return this.props.thumbnails?.();
+        return this._props.thumbnails?.();
     }
 
     @computed get trimStart() {
@@ -100,7 +111,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         return this.clipEnd - this.clipStart;
     }
     @computed get clipEnd() {
-        return this.IsTrimming === TrimScope.All ? this.props.rawDuration : NumCast(this.layoutDoc.clipEnd, this.props.rawDuration);
+        return this.IsTrimming === TrimScope.All ? this._props.rawDuration : NumCast(this.layoutDoc.clipEnd, this._props.rawDuration);
     }
 
     @computed get currentTime() {
@@ -115,11 +126,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         document.addEventListener('keydown', this.keyEvents, true);
     }
 
-    @action
     componentWillUnmount() {
         document.removeEventListener('keydown', this.keyEvents, true);
         if (CollectionStackedTimeline.SelectingRegion === this) {
-            CollectionStackedTimeline.SelectingRegion = undefined;
+            runInAction(() => (CollectionStackedTimeline.SelectingRegion = undefined));
         }
     }
 
@@ -149,8 +159,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         this._zoomFactor = zoom;
     }
 
-    anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
-    anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val) ?? null);
+    anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this._props.startTag]));
+    anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null);
 
     // converts screen pixel offset to time
     toTimeline = (screen_delta: number, width: number) => {
@@ -177,13 +187,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         if (
             // need to include range inputs because after dragging video time slider it becomes target element
             !(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) &&
-            this.props.isContentActive()
+            this._props.isContentActive()
         ) {
             // if shift pressed scrub 1 second otherwise 1/10th
             const jump = e.shiftKey ? 1 : 0.1;
             switch (e.key) {
                 case ' ':
-                    this.props.playing() ? this.props.Pause() : this.props.Play();
+                    this._props.playing() ? this._props.Pause() : this._props.Play();
                     break;
                 case '^':
                     if (!CollectionStackedTimeline.SelectingRegion) {
@@ -191,7 +201,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                         CollectionStackedTimeline.SelectingRegion = this;
                     } else {
                         this._markerEnd = this.currentTime;
-                        CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
+                        CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
                         this._markerEnd = undefined;
                         CollectionStackedTimeline.SelectingRegion = undefined;
                     }
@@ -204,11 +214,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                     this._trimming = TrimScope.None;
                     break;
                 case 'ArrowLeft':
-                    this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
+                    this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
                     e.stopPropagation();
                     break;
                 case 'ArrowRight':
-                    this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
+                    this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
                     e.stopPropagation();
                     break;
             }
@@ -218,7 +228,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     getLinkData(l: Doc) {
         let la1 = l.link_anchor_1 as Doc;
         let la2 = l.link_anchor_2 as Doc;
-        const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag]));
+        const linkTime = NumCast(la2[this._props.startTag], NumCast(la1[this._props.startTag]));
         if (Doc.AreProtosEqual(la1, this.dataDoc)) {
             la1 = l.link_anchor_2 as Doc;
             la2 = l.link_anchor_1 as Doc;
@@ -233,9 +243,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         const clientX = e.clientX;
         const diff = rect ? clientX - rect?.x : null;
         const shiftKey = e.shiftKey;
-        if (rect && this.props.isContentActive()) {
-            const wasPlaying = this.props.playing();
-            if (wasPlaying) this.props.Pause();
+        if (rect && this._props.isContentActive()) {
+            const wasPlaying = this._props.playing();
+            if (wasPlaying) this._props.Pause();
             var wasSelecting = this._markerEnd !== undefined;
             setupMoveUpEvents(
                 this,
@@ -257,7 +267,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                         this._markerEnd = tmp;
                     }
                     if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) {
-                        const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
+                        const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true);
                         setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
                     }
                     (!isClick || !wasSelecting) && (this._markerEnd = undefined);
@@ -265,17 +275,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                 }),
                 (e, doubleTap) => {
                     if (e.button !== 2) {
-                        this.props.select(false);
-                        !wasPlaying && doubleTap && this.props.Play();
+                        this._props.select(false);
+                        !wasPlaying && doubleTap && this._props.Play();
                     }
                 },
-                this.props.isSelected() || this.props.isContentActive(),
+                this._props.isSelected() || this._props.isContentActive(),
                 undefined,
                 () => {
                     if (shiftKey) {
-                        CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true);
+                        CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this.currentTime, undefined, undefined, true);
                     } else {
-                        !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+                        !wasPlaying && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
                     }
                 }
             );
@@ -290,7 +300,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         if (rect) {
             this._hoverTime = this.toTimeline(clientX - rect.x, rect.width);
             if (this.thumbnails) {
-                const nearest = Math.floor((this._hoverTime / this.props.rawDuration) * VideoBox.numThumbnails);
+                const nearest = Math.floor((this._hoverTime / this._props.rawDuration) * VideoBox.numThumbnails);
                 const imgField = this.thumbnails.length > 0 ? new ImageField(this.thumbnails[nearest]) : undefined;
                 this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined;
             }
@@ -305,7 +315,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
             this,
             e,
             action((e, [], []) => {
-                if (rect && this.props.isContentActive()) {
+                if (rect && this._props.isContentActive()) {
                     this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
                 }
                 return false;
@@ -325,7 +335,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
             this,
             e,
             action((e, [], []) => {
-                if (rect && this.props.isContentActive()) {
+                if (rect && this._props.isContentActive()) {
                     this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
                 }
                 return false;
@@ -348,8 +358,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     @action
     scrollToTime = (time: number) => {
         if (this._timelineWrapper) {
-            if (time > this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) {
-                this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth());
+            if (time > this.toTimeline(this._scroll + this._props.PanelWidth(), this.timelineContentWidth)) {
+                this._scroll = Math.min(this._scroll + this._props.PanelWidth(), this.timelineContentWidth - this._props.PanelWidth());
                 smoothScrollHorizontal(200, this._timelineWrapper, this._scroll);
             } else if (time < this.toTimeline(this._scroll, this.timelineContentWidth)) {
                 this._scroll = (time / this.timelineContentWidth) * this.clipDuration;
@@ -363,15 +373,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
         if (super.onInternalDrop(e, de)) {
             // determine x coordinate of drop and assign it to the documents being dragged  --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
-            const localPt = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+            const localPt = this._props.ScreenToLocalTransform().transformPoint(de.x, de.y);
             const x = localPt[0] - docDragData.offset[0];
             const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
             docDragData.droppedDocuments.forEach(drop => {
                 const anchorEnd = this.anchorEnd(drop);
                 if (anchorEnd !== undefined) {
-                    Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
+                    Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this._props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
                 }
-                Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false);
+                Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this._props.startTag : 'timecodeToShow', timelinePt, false);
             });
 
             return true;
@@ -386,7 +396,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
 
     // creates marker on timeline
     @undoBatch
-    @action
     static createAnchor(doc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) {
         if (anchorStartTime === undefined) return doc;
         const startTag = '_timecodeToShow';
@@ -422,20 +431,20 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
         const endTime = this.anchorEnd(anchorDoc);
         if (this.layoutDoc.autoPlayAnchors) {
-            if (this.props.playing()) this.props.Pause();
+            if (this._props.playing()) this._props.Pause();
             else {
-                this.props.playFrom(seekTimeInSeconds, endTime);
+                this._props.playFrom(seekTimeInSeconds, endTime);
                 this.scrollToTime(seekTimeInSeconds);
             }
         } else {
             if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) {
-                if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
-                    this.props.Pause();
+                if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) {
+                    this._props.Pause();
                 } else {
-                    this.props.Play();
+                    this._props.Play();
                 }
             } else {
-                this.props.playFrom(seekTimeInSeconds, endTime);
+                this._props.playFrom(seekTimeInSeconds, endTime);
                 this.scrollToTime(seekTimeInSeconds);
             }
         }
@@ -450,17 +459,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05;
         const endTime = this.anchorEnd(anchorDoc);
         if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._layout_currentTimecode) - 1e-4) {
-            if (this.props.playing()) this.props.Pause();
-            else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
+            if (this._props.playing()) this._props.Pause();
+            else if (this.layoutDoc.autoPlayAnchors) this._props.Play();
             else if (!this.layoutDoc.autoPlayAnchors) {
                 const rect = this._timeline?.getBoundingClientRect();
-                rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+                rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width));
             }
         } else {
             if (this.layoutDoc.autoPlayAnchors) {
-                this.props.playFrom(seekTimeInSeconds, endTime);
+                this._props.playFrom(seekTimeInSeconds, endTime);
             } else {
-                this.props.setTime(seekTimeInSeconds);
+                this._props.setTime(seekTimeInSeconds);
             }
         }
         return { select: true };
@@ -490,18 +499,18 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     };
 
     dictationHeightPercent = 50;
-    dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
+    dictationHeight = () => (this._props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100;
 
     @computed get timelineContentHeight() {
-        return (this.props.PanelHeight() * this.dictationHeightPercent) / 100;
+        return (this._props.PanelHeight() * this.dictationHeightPercent) / 100;
     }
     @computed get timelineContentWidth() {
-        return this.props.PanelWidth() * this.zoomFactor;
+        return this._props.PanelWidth() * this.zoomFactor;
     } // subtract size of container border
 
-    dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
+    dictationScreenToLocalTransform = () => this._props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
 
-    isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+    isContentActive = () => this._props.isSelected() || this._props.isContentActive();
 
     currentTimecode = () => this.currentTime;
 
@@ -520,7 +529,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
     }
 
     @computed get timelineEvents() {
-        return this.props.isContentActive() ? 'all' : this.props.isContentActive() === false ? 'none' : undefined;
+        return this._props.isContentActive() ? 'all' : this._props.isContentActive() === false ? 'none' : undefined;
     }
     render() {
         const overlaps: {
@@ -537,7 +546,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
             <div ref={this.createDashEventsTarget} style={{ pointerEvents: this.timelineEvents }}>
                 <div
                     className="collectionStackedTimeline-timelineContainer"
-                    style={{ width: this.props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
+                    style={{ width: this._props.PanelWidth(), cursor: SnappingManager.GetIsDragging() ? 'grab' : '' }}
                     onWheel={e => this.isContentActive() && e.stopPropagation()}
                     onScroll={this.setScroll}
                     onMouseMove={e => this.isContentActive() && this.onHover(e)}
@@ -553,11 +562,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                             const end = this.anchorEnd(d.anchor, start + (10 / this.timelineContentWidth) * this.clipDuration);
                             if (end < this.clipStart || start > this.clipEnd) return null;
                             const left = Math.max(((start - this.clipStart) / this.clipDuration) * this.timelineContentWidth, 0);
-                            const top = (d.level / maxLevel) * this.props.PanelHeight();
+                            const top = (d.level / maxLevel) * this._props.PanelHeight();
                             const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart);
                             const width = (timespan / this.clipDuration) * this.timelineContentWidth;
-                            const height = this.props.PanelHeight() / maxLevel;
-                            return this.props.Document.hideAnchors ? null : (
+                            const height = this._props.PanelHeight() / maxLevel;
+                            return this._props.Document.hideAnchors ? null : (
                                 <div
                                     className={'collectionStackedTimeline-marker-timeline'}
                                     key={d.anchor[Id]}
@@ -569,7 +578,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                                         pointerEvents: 'none',
                                     }}>
                                     <StackedTimelineAnchor
-                                        {...this.props}
+                                        {...this._props}
                                         mark={d.anchor}
                                         rangeClickScript={this.rangeClickScript}
                                         rangePlayScript={this.rangePlayScript}
@@ -590,18 +599,19 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
                             );
                         })}
                         {!this.IsTrimming && this.selectionContainer}
-                        {!this.props.PanelHeight() ? null : (
+                        {!this._props.PanelHeight() ? null : (
                             <AudioWaveform
-                                rawDuration={this.props.rawDuration}
-                                fieldKey={this.props.dataFieldKey}
+                                rawDuration={this._props.rawDuration}
+                                fieldKey={this._props.dataFieldKey}
                                 duration={this.clipDuration}
-                                mediaPath={this.props.mediaPath}
+                                mediaPath={this._props.mediaPath}
                                 layoutDoc={this.layoutDoc}
                                 clipStart={this.clipStart}
                                 clipEnd={this.clipEnd}
                                 zoomFactor={this.zoomFactor}
                                 PanelHeight={this.timelineContentHeight}
                                 PanelWidth={this.timelineContentWidth}
+                                progress={(this.currentTime - this.clipStart) / this.clipDuration}
                             />
                         )}
 
@@ -691,37 +701,44 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     _lastTimecode: number;
     _disposer: IReactionDisposer | undefined;
 
-    constructor(props: any) {
+    _prevProps: React.PropsWithChildren<StackedTimelineAnchorProps>;
+    @observable _props: React.PropsWithChildren<StackedTimelineAnchorProps>;
+    constructor(props: React.PropsWithChildren<StackedTimelineAnchorProps>) {
         super(props);
-        this._lastTimecode = this.props.currentTimecode();
+        this._props = this._prevProps = props;
+        makeObservable(this);
+        this._lastTimecode = this._props.currentTimecode();
+    }
+    componentDidUpdate() {
+        copyProps(this);
     }
 
     // updates marker document title to reflect correct timecodes
     computeTitle = () => {
-        if (this.props.mark.type !== DocumentType.LABEL) return undefined;
-        const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
-        const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
+        if (this._props.mark.type !== DocumentType.LABEL) return undefined;
+        const start = Math.max(NumCast(this._props.mark[this._props.startTag]), this._props.trimStart) - this._props.trimStart;
+        const end = Math.min(NumCast(this._props.mark[this._props.endTag]), this._props.trimEnd) - this._props.trimStart;
         return `#${formatTime(start)}-${formatTime(end)}`;
     };
 
     componentDidMount() {
         this._disposer = reaction(
-            () => this.props.currentTimecode(),
+            () => this._props.currentTimecode(),
             time => {
-                const dictationDoc = Cast(this.props.layoutDoc.data_dictation, Doc, null);
-                const isDictation = dictationDoc && LinkManager.Links(this.props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
+                const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null);
+                const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc);
                 if (
                     !LightboxView.LightboxDoc &&
                     // bcz: when should links be followed?  we don't want to move away from the video to follow a link but we can open it in a sidebar/etc.  But we don't know that upfront.
                     // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video.
-                    /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this.props.layoutDoc))*/
-                    !this.props.layoutDoc.dontAutoFollowLinks &&
-                    LinkManager.Links(this.props.mark).length &&
-                    time > NumCast(this.props.mark[this.props.startTag]) &&
-                    time < NumCast(this.props.mark[this.props.endTag]) &&
-                    this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5
+                    /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc))*/
+                    !this._props.layoutDoc.dontAutoFollowLinks &&
+                    LinkManager.Links(this._props.mark).length &&
+                    time > NumCast(this._props.mark[this._props.startTag]) &&
+                    time < NumCast(this._props.mark[this._props.endTag]) &&
+                    this._lastTimecode < NumCast(this._props.mark[this._props.startTag]) - 1e-5
                 ) {
-                    LinkFollower.FollowLink(undefined, this.props.mark, false);
+                    LinkFollower.FollowLink(undefined, this._props.mark, false);
                 }
                 this._lastTimecode = time;
             }
@@ -736,16 +753,16 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     // starting the drag event for anchor resizing
     @action
     onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
-        //this.props._timeline?.setPointerCapture(e.pointerId);
+        //this._props._timeline?.setPointerCapture(e.pointerId);
         const newTime = (e: PointerEvent) => {
             const rect = (e.target as any).getBoundingClientRect();
-            return this.props.toTimeline(e.clientX - rect.x, rect.width);
+            return this._props.toTimeline(e.clientX - rect.x, rect.width);
         };
         const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
-            const timelineOnly = Cast(anchor[this.props.startTag], 'number', null) !== undefined;
+            const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined;
             if (timelineOnly) {
-                if (!left && time !== undefined && time <= NumCast(anchor[this.props.startTag])) time = undefined;
-                Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true);
+                if (!left && time !== undefined && time <= NumCast(anchor[this._props.startTag])) time = undefined;
+                Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, time, true);
                 if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true);
             } else {
                 anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
@@ -759,11 +776,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
             e,
             e => {
                 if (!undo) undo = UndoManager.StartBatch('drag anchor');
-                this.props.setTime(newTime(e));
+                this._props.setTime(newTime(e));
                 return changeAnchor(anchor, left, newTime(e));
             },
             action(e => {
-                this.props.setTime(newTime(e));
+                this._props.setTime(newTime(e));
                 undo?.end();
                 this.noEvents = false;
             }),
@@ -774,7 +791,9 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     // context menu
     contextMenuItems = () => {
         const resetTitle = {
-            script: ScriptField.MakeFunction(`this.title = this["${this.props.endTag}"] ? "#" + formatToTime(this["${this.props.startTag}"]) + "-" + formatToTime(this["${this.props.endTag}"]) : "#" + formatToTime(this["${this.props.startTag}"])`)!,
+            script: ScriptField.MakeFunction(
+                `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])`
+            )!,
             icon: 'folder-plus',
             label: 'Reset Title',
         };
@@ -785,7 +804,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
     renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) {
         const anchor = observable({ view: undefined as Opt<DocumentView> | null });
         const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => {
-            this.props.playLink(mark, options);
+            this._props.playLink(mark, options);
             return undefined;
         };
         return {
@@ -793,7 +812,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
             view: (
                 <DocumentView
                     key="view"
-                    {...this.props}
+                    {...this._props}
                     NativeWidth={returnZero}
                     NativeHeight={returnZero}
                     ref={action((r: DocumentView | null) => (anchor.view = r))}
@@ -801,11 +820,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     TemplateDataDocument={undefined}
                     docViewPath={returnEmptyDoclist}
                     pointerEvents={this.noEvents ? returnNone : undefined}
-                    styleProvider={this.props.styleProvider}
-                    renderDepth={this.props.renderDepth + 1}
+                    styleProvider={this._props.styleProvider}
+                    renderDepth={this._props.renderDepth + 1}
                     LayoutTemplate={undefined}
                     LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())}
-                    isDocumentActive={this.props.isDocumentActive}
+                    isDocumentActive={this._props.isDocumentActive}
                     PanelWidth={width}
                     PanelHeight={height}
                     layout_fitWidth={returnTrue}
@@ -817,7 +836,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
                     childFilters={returnEmptyFilter}
                     childFiltersByRanges={returnEmptyFilter}
                     onClick={script}
-                    onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
+                    onDoubleClick={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
                     ignoreAutoHeight={false}
                     hideResizeHandles={true}
                     bringToFront={emptyFunction}
@@ -827,19 +846,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
         };
     });
 
-    anchorScreenToLocalXf = () => this.props.ScreenToLocalTransform().translate(-this.props.left, -this.props.top);
-    width = () => this.props.width;
-    height = () => this.props.height;
+    anchorScreenToLocalXf = () => this._props.ScreenToLocalTransform().translate(-this._props.left, -this._props.top);
+    width = () => this._props.width;
+    height = () => this._props.height;
 
     render() {
-        const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
+        const inner = this.renderInner(this._props.mark, this._props.rangeClickScript, this._props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
         return (
             <div style={{ pointerEvents: this.noEvents ? 'none' : undefined }}>
                 {inner.view}
                 {!inner.anchor.view || !inner.anchor.view.SELECTED ? null : (
                     <>
-                        <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
-                        <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
+                        <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, true)} />
+                        <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this._props.mark, false)} />
                     </>
                 )}
             </div>
-- 
cgit v1.2.3-70-g09d2

