From 4e75d733d91fddf1f99028fe9351aeba16a7b36e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Jan 2021 10:10:34 -0500
Subject: re-added click behavior lost in refactoring

---
 .../collections/CollectionStackedTimeline.tsx      | 47 +++++++++++++++++-----
 1 file changed, 36 insertions(+), 11 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 1775250fa..44fc0f709 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -58,10 +58,10 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     constructor(props: any) {
         super(props);
         // onClick play scripts
-        CollectionStackedTimeline.RangeScript = CollectionStackedTimeline.RangeScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this)`, { self: Doc.name, scriptContext: "any" })!;
-        CollectionStackedTimeline.LabelScript = CollectionStackedTimeline.LabelScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this)`, { self: Doc.name, scriptContext: "any" })!;
-        CollectionStackedTimeline.RangePlayScript = CollectionStackedTimeline.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this)`, { self: Doc.name, scriptContext: "any" })!;
-        CollectionStackedTimeline.LabelPlayScript = CollectionStackedTimeline.LabelPlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this)`, { self: Doc.name, scriptContext: "any" })!;
+        CollectionStackedTimeline.RangeScript = CollectionStackedTimeline.RangeScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!;
+        CollectionStackedTimeline.LabelScript = CollectionStackedTimeline.LabelScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!;
+        CollectionStackedTimeline.RangePlayScript = CollectionStackedTimeline.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!;
+        CollectionStackedTimeline.LabelPlayScript = CollectionStackedTimeline.LabelPlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!;
     }
 
     // for creating key anchors with key events
@@ -179,21 +179,46 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         return anchor;
     }
 
-    // play back the audio from time
     @action
-    playOnClick = (anchorDoc: Doc) => {
-        this.props.playFrom(this.anchorStart(anchorDoc), this.anchorEnd(anchorDoc, this.props.duration));
+    playOnClick = (anchorDoc: Doc, clientX: number) => {
+        const seekTimeInSeconds = this.anchorStart(anchorDoc);
+        const endTime = this.anchorEnd(anchorDoc);
+        if (this.layoutDoc.autoPlay) {
+            if (this.props.playing()) this.props.Pause();
+            else this.props.playFrom(seekTimeInSeconds, endTime);
+        } else {
+            if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) {
+                if (!this.layoutDoc.autoPlay && this.props.playing()) {
+                    this.props.Pause();
+                } else {
+                    this.props.Play();
+                }
+            } else {
+                this.props.playFrom(seekTimeInSeconds, endTime);
+            }
+        }
         return { select: true };
     }
 
-    // play back the audio from time
     @action
-    clickAnchor = (anchorDoc: Doc) => {
-        if (this.props.Document.autoPlay) return this.playOnClick(anchorDoc);
-        this.props.setTime(this.anchorStart(anchorDoc));
+    clickAnchor = (anchorDoc: Doc, clientX: number) => {
+        const seekTimeInSeconds = this.anchorStart(anchorDoc);
+        const endTime = this.anchorEnd(anchorDoc);
+        if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) {
+            if (this.props.playing()) this.props.Pause();
+            else if (this.layoutDoc.autoPlay) this.props.Play();
+            else if (!this.layoutDoc.autoPlay) {
+                const rect = this._timeline?.getBoundingClientRect();
+                rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+            }
+        } else {
+            if (this.layoutDoc.autoPlay) this.props.playFrom(seekTimeInSeconds, endTime);
+            else this.props.setTime(seekTimeInSeconds);
+        }
         return { select: true };
     }
 
+
     toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.props.duration, screen_delta / width * this.props.duration));
     // starting the drag event for anchor resizing
     onPointerDown = (e: React.PointerEvent, m: Doc, left: boolean): void => {
-- 
cgit v1.2.3-70-g09d2


From 31faf2b0683fc15c040d47425f931531c0d8a0f8 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Jan 2021 12:13:21 -0500
Subject: fixed size of timeline toggle on videoBox and some other tweaks to
 hide documentdeocrations on timeline items.

---
 src/client/views/DocumentDecorations.tsx                   |  2 +-
 src/client/views/collections/CollectionStackedTimeline.tsx | 11 ++++++++---
 src/client/views/nodes/DocumentView.tsx                    |  2 +-
 src/client/views/nodes/VideoBox.scss                       |  7 ++++---
 src/client/views/nodes/VideoBox.tsx                        | 13 ++++---------
 5 files changed, 18 insertions(+), 17 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index fb85684fb..250b93188 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -613,7 +613,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
                 }}>
                     {closeIcon}
                     {bounds.r - bounds.x < 100 ? null : titleArea}
-                    {seldoc.rootDoc.anchorStartTime !== undefined ? (null) :
+                    {seldoc.props.hideDecorations ? (null) :
                         <>
                             {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
                                 <Tooltip key="i" title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top">
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 44fc0f709..54cc86523 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -84,8 +84,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         }
     }
 
-    anchorStart = (anchor: Doc) => NumCast(anchor.anchorStartTime, NumCast(anchor._timecodeToShow, NumCast(anchor.videoStart)));
-    anchorEnd = (anchor: Doc, defaultVal: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor._timecodeToHide, NumCast(anchor.videoEnd, defaultVal)));
+    anchorStart = (anchor: Doc) => NumCast(anchor.anchorStartTime, NumCast(anchor._timecodeToShow, NumCast(anchor.videoStart, NumCast(anchor.audioStart))));
+    anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor._timecodeToHide, NumCast(anchor.videoEnd, NumCast(anchor.audioEnd, val))));
 
     getLinkData(l: Doc) {
         let la1 = l.anchor1 as Doc;
@@ -106,7 +106,11 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     // updates the anchor with the new time
     @action
     changeAnchor = (anchor: Opt<Doc>, time: number) => {
-        anchor && (this._left ? anchor.anchorStartTime = time : anchor.anchorEndTime = time);
+        if (anchor) {
+            const timelineOnly = Cast(anchor.anchorStartTime, "number", null) !== undefined;
+            if (timelineOnly) this._left ? anchor.anchorStartTime = time : anchor.anchorEndTime = time;
+            else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time;
+        }
     }
 
     // checks if the two anchors are the same with start and end time
@@ -288,6 +292,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                 onClick={script}
                 onDoubleClick={this.props.Document.autoPlay ? undefined : doublescript}
                 ignoreAutoHeight={false}
+                hideDecorations={true}
                 bringToFront={emptyFunction}
                 scriptContext={this} />
         };
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7b1b1bf0c..5ce8fcdcb 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -30,7 +30,6 @@ import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from "../DocComponent";
 import { EditableView } from '../EditableView';
 import { InkingStroke } from "../InkingStroke";
-import { InkStrokeProperties } from '../InkStrokeProperties';
 import { StyleLayers, StyleProp } from "../StyleProvider";
 import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView";
 import { DocumentContentsView } from "./DocumentContentsView";
@@ -83,6 +82,7 @@ export interface DocumentViewSharedProps {
 export interface DocumentViewProps extends DocumentViewSharedProps {
     // properties specific to DocumentViews but not to FieldView
     freezeDimensions?: boolean;
+    hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected 
     hideTitle?: boolean;  // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
     treeViewDoc?: Doc;
     contentPointerEvents?: string; // pointer events allowed for content of a document view.  eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 19f605278..ac4d64f12 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -183,14 +183,15 @@
     pointer-events:all;
 }
 
-.timeline-button {
+.videoBox-timelineButton {
     position: absolute;
     display: flex;
     align-items: center;
     z-index: 1010;
-    bottom: 35px;
-    right: 235px;
+    bottom: 5px;
+    right: 5px;
     color: white;
+    cursor: pointer;
     background: dimgrey;
     width: 20px;
     height: 20px;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 7f123757b..6286b5e26 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -380,16 +380,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
             <span>{"" + formatTime(curTime)}</span>
             <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
         </div>,
-        <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} >
+        <div className="videoBox-snapshot" key="snap" onClick={this.onSnapshot} >
             <FontAwesomeIcon icon="camera" size="lg" />
         </div>,
-        <div className="timeline-button" key="timeline-button" onPointerDown={action(e => this.layoutDoc._timelineShow = !this.layoutDoc._timelineShow)}
-            style={{
-                transform: `scale(${this.scaling()})`,
-                right: this.scaling() * 10 - 10,
-                bottom: this.scaling() * 10 - 10
-            }}>
-            <FontAwesomeIcon icon={this.layoutDoc._timelineShow ? "eye-slash" : "eye"} style={{ width: "100%" }} />
+        <div className="videoBox-timelineButton" key="timeline" onClick={action(e => this.layoutDoc._timelineShow = !this.layoutDoc._timelineShow)}>
+            <FontAwesomeIcon icon={this.layoutDoc._timelineShow ? "eye-slash" : "eye"} size="lg" />
         </div>,
         VideoBox._showControls ? (null) : [
             // <div className="control-background">
@@ -411,7 +406,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         e.preventDefault();
     }
 
-    onSnapshot = (e: React.PointerEvent) => {
+    onSnapshot = (e: React.MouseEvent) => {
         this.Snapshot();
         e.stopPropagation();
         e.preventDefault();
-- 
cgit v1.2.3-70-g09d2


From f43f47662293ddb2cd4d2eb217bc4f01b6d22826 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Jan 2021 13:22:01 -0500
Subject: animated expansion of video box timeline.

---
 src/client/views/DocumentDecorations.tsx           |  2 +-
 .../collections/CollectionStackedTimeline.tsx      |  2 +-
 src/client/views/nodes/VideoBox.tsx                | 32 +++++++++++++++++-----
 3 files changed, 27 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 250b93188..54e0a7ac7 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -613,7 +613,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
                 }}>
                     {closeIcon}
                     {bounds.r - bounds.x < 100 ? null : titleArea}
-                    {seldoc.props.hideDecorations ? (null) :
+                    {seldoc.props.hideResizeHandles ? (null) :
                         <>
                             {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) :
                                 <Tooltip key="i" title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top">
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 54cc86523..4b14c3508 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -292,7 +292,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                 onClick={script}
                 onDoubleClick={this.props.Document.autoPlay ? undefined : doublescript}
                 ignoreAutoHeight={false}
-                hideDecorations={true}
+                hideResizeHandles={true}
                 bringToFront={emptyFunction}
                 scriptContext={this} />
         };
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 6286b5e26..506ba8c49 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -55,16 +55,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     @observable _marqueeing: number[] | undefined;
     @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
     @observable _screenCapture = false;
-    @observable _visible: boolean = false;
+    @observable _clicking = false;
     @observable _forceCreateYouTubeIFrame = false;
     @observable _playTimer?: NodeJS.Timeout = undefined;
     @observable _fullScreen = false;
     @observable _playing = false;
     @computed get links() { return DocListCast(this.dataDoc.links); }
-    @computed get heightPercent() { return this.layoutDoc._timelineShow ? NumCast(this.layoutDoc._videoTimelineHeightPercent, VideoBox.heightPercent) : 100; }
+    @computed get heightPercent() { return NumCast(this.layoutDoc._timelineHeightPercent, 100); }
     @computed get duration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
     @computed get anchorDocs() { return DocListCast(this.dataDoc[this.annotationKey + "-timeline"]).concat(DocListCast(this.dataDoc[this.annotationKey])); }
 
+    private get transition() { return this._clicking ? "left 0.5s, width 0.5s, height 0.5s" : ""; }
     public get player(): HTMLVideoElement | null { return this._videoRef; }
 
     constructor(props: Readonly<FieldViewProps>) {
@@ -383,8 +384,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         <div className="videoBox-snapshot" key="snap" onClick={this.onSnapshot} >
             <FontAwesomeIcon icon="camera" size="lg" />
         </div>,
-        <div className="videoBox-timelineButton" key="timeline" onClick={action(e => this.layoutDoc._timelineShow = !this.layoutDoc._timelineShow)}>
-            <FontAwesomeIcon icon={this.layoutDoc._timelineShow ? "eye-slash" : "eye"} size="lg" />
+        <div className="videoBox-timelineButton" key="timeline" onPointerDown={this.onTimelineHdlDown} style={{ bottom: `${100 - this.heightPercent}%` }}>
+            <FontAwesomeIcon icon={"eye"} size="lg" />
         </div>,
         VideoBox._showControls ? (null) : [
             // <div className="control-background">
@@ -412,6 +413,23 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         e.preventDefault();
     }
 
+    onTimelineHdlDown = action((e: React.PointerEvent) => {
+        this._clicking = true;
+        setupMoveUpEvents(this, e,
+            action((e: PointerEvent) => {
+                this._clicking = false;
+                if (this.active()) {
+                    const local = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+                    this.layoutDoc._timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100));
+                }
+                return false;
+            }), emptyFunction,
+            () => {
+                this.layoutDoc._timelineHeightPercent = this.heightPercent !== 100 ? 100 : VideoBox.heightPercent;
+                setTimeout(action(() => this._clicking = false), 500);
+            }, this.active(), this.active());
+    });
+
     onResetDown = (e: React.PointerEvent) => {
         setupMoveUpEvents(this, e, (e: PointerEvent) => {
             this.Seek(Math.max(0, (this.layoutDoc._currentTimecode || 0) + Math.sign(e.movementX) * 0.0333));
@@ -478,7 +496,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
 
     // returns the timeline
     @computed get renderTimeline() {
-        return <div style={{ width: "100%", height: `${100 - this.heightPercent}%`, position: "absolute" }}>
+        return <div style={{ width: "100%", transition: this.transition, height: `${100 - this.heightPercent}%`, position: "absolute" }}>
             <CollectionStackedTimeline ref={this._stackedTimeline}
                 Document={this.props.Document}
                 fieldKey={this.annotationKey}
@@ -519,7 +537,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
 
     @computed get annotationLayer() {
-        return <div className="imageBox-annotationLayer" style={{ height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
+        return <div className="imageBox-annotationLayer" style={{ transition: this.transition, height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
     }
 
     marqueeDown = action((e: React.PointerEvent) => {
@@ -548,7 +566,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                 borderRadius
             }} >
             <div className="videoBox-viewer" onPointerDown={this.marqueeDown} >
-                <div style={{ position: "absolute", width: this.panelWidth(), height: this.panelHeight(), top: 0, left: `${(100 - this.heightPercent) / 2}%` }}>
+                <div style={{ position: "absolute", transition: this.transition, width: this.panelWidth(), height: this.panelHeight(), top: 0, left: `${(100 - this.heightPercent) / 2}%` }}>
                     <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
                         fieldKey={this.annotationKey}
                         isAnnotationOverlay={true}
-- 
cgit v1.2.3-70-g09d2


From e24584cc674f763aec5d0e8f55f95a1d4c14522f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 27 Jan 2021 17:30:36 -0500
Subject: fixed undo for timeline region creation.  fixed inking tools display
 after deleting an active text. box

---
 src/client/views/collections/CollectionStackedTimeline.tsx | 2 ++
 src/client/views/nodes/formattedText/FormattedTextBox.tsx  | 1 +
 2 files changed, 3 insertions(+)

(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 4b14c3508..21fbef1ac 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -16,6 +16,7 @@ import { CollectionSubView } from "../collections/CollectionSubView";
 import { DocumentView } from "../nodes/DocumentView";
 import { LabelBox } from "../nodes/LabelBox";
 import "./CollectionStackedTimeline.scss";
+import { undoBatch } from "../../util/UndoManager";
 
 type PanZoomDocument = makeInterface<[]>;
 const PanZoomDocument = makeInterface();
@@ -164,6 +165,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         }
     }
 
+    @undoBatch
     @action
     createAnchor(anchorStartTime?: number, anchorEndTime?: number) {
         if (anchorStartTime === undefined) return this.props.Document;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e06a324d2..f3ac837d9 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1302,6 +1302,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this.endUndoTypingBatch();
         this.unhighlightSearchTerms();
         this._editorView?.destroy();
+        RichTextMenu.Instance.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined);
         FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none");
     }
 
-- 
cgit v1.2.3-70-g09d2


From 663f888a8948cd4081ed5bc5b00c1c51e3db83a5 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Jan 2021 13:38:41 -0500
Subject: parameterized StackedTimeline with tag names for starting/ending an
 anchor

---
 .../views/collections/CollectionStackedTimeline.tsx      | 16 +++++++++-------
 src/client/views/nodes/AudioBox.tsx                      | 10 +++++-----
 src/client/views/nodes/VideoBox.tsx                      |  9 ++++-----
 3 files changed, 18 insertions(+), 17 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 21fbef1ac..801433151 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -29,6 +29,8 @@ export type CollectionStackedTimelineProps = {
     playing: () => boolean;
     setTime: (time: number) => void;
     isChildActive: () => boolean;
+    startTag: string;
+    endTag: string;
 };
 
 @observer
@@ -85,13 +87,13 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         }
     }
 
-    anchorStart = (anchor: Doc) => NumCast(anchor.anchorStartTime, NumCast(anchor._timecodeToShow, NumCast(anchor.videoStart, NumCast(anchor.audioStart))));
-    anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor._timecodeToHide, NumCast(anchor.videoEnd, NumCast(anchor.audioEnd, val))));
+    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]));
 
     getLinkData(l: Doc) {
         let la1 = l.anchor1 as Doc;
         let la2 = l.anchor2 as Doc;
-        const linkTime = NumCast(la2.anchorStartTime, NumCast(la1.anchorStartTime));
+        const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag]));
         if (Doc.AreProtosEqual(la1, this.dataDoc)) {
             la1 = l.anchor2 as Doc;
             la2 = l.anchor1 as Doc;
@@ -108,8 +110,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     @action
     changeAnchor = (anchor: Opt<Doc>, time: number) => {
         if (anchor) {
-            const timelineOnly = Cast(anchor.anchorStartTime, "number", null) !== undefined;
-            if (timelineOnly) this._left ? anchor.anchorStartTime = time : anchor.anchorEndTime = time;
+            const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined;
+            if (timelineOnly) this._left ? anchor[this.props.startTag] = time : anchor[this.props.endTag] = time;
             else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time;
         }
     }
@@ -173,10 +175,10 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
             title: ComputedField.MakeFunction(`"#" + formatToTime(self.anchorStartTime) + "-" + formatToTime(self.anchorEndTime)`) as any,
             useLinkSmallAnchor: true,
             hideLinkButton: true,
-            anchorStartTime,
-            anchorEndTime,
             annotationOn: this.props.Document
         });
+        anchor[this.props.startTag] = anchorStartTime;
+        anchor[this.props.endTag] = anchorEndTime;
         if (Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc), null) !== undefined) {
             Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc), []).push(anchor);
         } else {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c8bec74fb..9d7c9bffc 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -26,13 +26,11 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment";
 import { LinkDocPreview } from "./LinkDocPreview";
 declare class MediaRecorder {
-    // whatever MediaRecorder has
-    constructor(e: any);
+    constructor(e: any);  // whatever MediaRecorder has
 }
-export const audioSchema = createSchema({ playOnSelect: "boolean" });
 
-type AudioDocument = makeInterface<[typeof documentSchema, typeof audioSchema]>;
-const AudioDocument = makeInterface(documentSchema, audioSchema);
+type AudioDocument = makeInterface<[typeof documentSchema]>;
+const AudioDocument = makeInterface(documentSchema);
 
 @observer
 export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
@@ -352,6 +350,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
             fieldKey={this.annotationKey}
             renderDepth={this.props.renderDepth + 1}
             parentActive={this.props.parentActive}
+            startTag={"audioStart"}
+            endTag={"auidioEnd"}
             focus={emptyFunction}
             styleProvider={this.props.styleProvider}
             docFilters={this.props.docFilters}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index a9c64b0bd..c1cf858c0 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -29,11 +29,8 @@ import { LinkDocPreview } from "./LinkDocPreview";
 import "./VideoBox.scss";
 const path = require('path');
 
-export const timeSchema = createSchema({
-    _currentTimecode: "number",  // the current time of a video or other linear, time-based document.  Note, should really get set on an extension field, but that's more complicated when it needs to be set since the extension doc needs to be found first
-});
-type VideoDocument = makeInterface<[typeof documentSchema, typeof timeSchema]>;
-const VideoDocument = makeInterface(documentSchema, timeSchema);
+type VideoDocument = makeInterface<[typeof documentSchema]>;
+const VideoDocument = makeInterface(documentSchema);
 
 @observer
 export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
@@ -499,6 +496,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                 fieldKey={this.annotationKey}
                 renderDepth={this.props.renderDepth + 1}
                 parentActive={this.props.parentActive}
+                startTag={"videoStart"}
+                endTag={"videoEnd"}
                 focus={emptyFunction}
                 styleProvider={this.props.styleProvider}
                 docFilters={this.props.docFilters}
-- 
cgit v1.2.3-70-g09d2


From 61076c3ca711a8d3af01010b2375e3683e26fb8d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Jan 2021 13:57:10 -0500
Subject: from last

---
 src/client/documents/Documents.ts                          | 2 --
 src/client/views/StyleProvider.tsx                         | 2 +-
 src/client/views/collections/CollectionStackedTimeline.tsx | 2 +-
 3 files changed, 2 insertions(+), 4 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 080270321..142e14ff8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -153,8 +153,6 @@ export interface DocumentOptions {
     _currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide)
     _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays)
     _timecodeToHide?: number; // the time that a document should be hidden
-    anchorStartTime?: number; // the time when an annotation starts on a timeline (e.g., when an audio anchor starts on an audio/video timeline)
-    anchorEndTime?: number;   // the time when an annotaiton ends on a timeline 
     lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide)
     activeFrame?: number; // the active frame of a document in a frame base collection
     appearFrame?: number; // the frame in which the document appears
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 8628be014..803390055 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -108,7 +108,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
                 case DocumentType.FILTER: docColor = docColor || (darkScheme() ? "#2d2d2d" : "rgba(105, 105, 105, 0.432)"); break;
                 case DocumentType.INK: docColor = doc?.isInkMask ? "rgba(0,0,0,0.7)" : undefined; break;
                 case DocumentType.SLIDER: break;
-                case DocumentType.LABEL: docColor = docColor || (doc.audioStart !== undefined || doc?.anchorStartTime !== undefined || doc?._timecodeToShow !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break;
+                case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break;
                 case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
                 case DocumentType.LINK: return "transparent";
                 case DocumentType.VID: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 801433151..893f2c53b 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -172,7 +172,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     createAnchor(anchorStartTime?: number, anchorEndTime?: number) {
         if (anchorStartTime === undefined) return this.props.Document;
         const anchor = Docs.Create.LabelDocument({
-            title: ComputedField.MakeFunction(`"#" + formatToTime(self.anchorStartTime) + "-" + formatToTime(self.anchorEndTime)`) as any,
+            title: ComputedField.MakeFunction(`"#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"])`) as any,
             useLinkSmallAnchor: true,
             hideLinkButton: true,
             annotationOn: this.props.Document
-- 
cgit v1.2.3-70-g09d2


From bc4cabf751097e847f0c64cc5acc25bc1ee09243 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Jan 2021 14:28:27 -0500
Subject: more cleanup for switching back to parameterized audioTag/videoTag
 instead of anchorStartTag

---
 src/client/views/StyleProvider.tsx                         | 2 +-
 src/client/views/collections/CollectionStackedTimeline.tsx | 8 ++++----
 src/client/views/nodes/AudioBox.tsx                        | 8 ++++----
 src/client/views/nodes/PresBox.tsx                         | 6 +++---
 src/client/views/nodes/VideoBox.tsx                        | 4 ++--
 src/client/views/nodes/formattedText/FormattedTextBox.tsx  | 2 +-
 src/fields/documentSchemas.ts                              | 2 --
 7 files changed, 15 insertions(+), 17 deletions(-)

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

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 803390055..3956b8c5b 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -141,7 +141,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
                             `${darkScheme() ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(doc.boxShadow, "0.2vw 0.2vw 0.8vw")}`);
 
                 case DocumentType.LABEL:
-                    if (doc?.anchorStartTime !== undefined) return "black 2px 2px 1px";
+                    if (doc?.annotationOn !== undefined) return "black 2px 2px 1px";
                 default:
                     return doc.z ? `#9c9396  ${StrCast(doc?.boxShadow, "10px 10px 0.9vw")}` :  // if it's a floating doc, give it a big shadow
                         props?.ContainingCollectionDoc?._useClusters && doc.type !== DocumentType.INK ? (`${backgroundCol()} ${StrCast(doc.boxShadow, `0vw 0vw ${(isBackground() ? 100 : 50) / (docProps?.ContentScaling?.() || 1)}px`)}`) :  // if it's just in a cluster, make the shadown roughly match the cluster border extent
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 893f2c53b..1d7e40e96 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -88,7 +88,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     }
 
     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]));
+    anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val));
 
     getLinkData(l: Doc) {
         let la1 = l.anchor1 as Doc;
@@ -111,7 +111,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     changeAnchor = (anchor: Opt<Doc>, time: number) => {
         if (anchor) {
             const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined;
-            if (timelineOnly) this._left ? anchor[this.props.startTag] = time : anchor[this.props.endTag] = time;
+            if (timelineOnly) Doc.SetInPlace(anchor, this._left ? this.props.startTag : this.props.endTag, time, true);
             else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time;
         }
     }
@@ -177,8 +177,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
             hideLinkButton: true,
             annotationOn: this.props.Document
         });
-        anchor[this.props.startTag] = anchorStartTime;
-        anchor[this.props.endTag] = anchorEndTime;
+        Doc.GetProto(anchor)[this.props.startTag] = anchorStartTime;
+        Doc.GetProto(anchor)[this.props.endTag] = anchorEndTime;
         if (Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc), null) !== undefined) {
             Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc), []).push(anchor);
         } else {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 9d7c9bffc..741a03900 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -81,7 +81,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     getLinkData(l: Doc) {
         let la1 = l.anchor1 as Doc;
         let la2 = l.anchor2 as Doc;
-        const linkTime = NumCast(la2.anchorStartTime, NumCast(la1.anchorStartTime));
+        const linkTime = this._stackedTimeline.current?.anchorStart(la2) || this._stackedTimeline.current?.anchorStart(la1);
         if (Doc.AreProtosEqual(la1, this.dataDoc)) {
             la1 = l.anchor2 as Doc;
             la2 = l.anchor1 as Doc;
@@ -334,8 +334,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         else {
             this.links.filter(l => l.anchor1 === link || l.anchor2 === link).forEach(l => {
                 const { la1, la2 } = this.getLinkData(l);
-                const startTime = NumCast(la1.anchorStartTime, NumCast(la2.anchorStartTime, null));
-                const endTime = NumCast(la1.anchorEndTime, NumCast(la2.anchorEndTime, null));
+                const startTime = this._stackedTimeline.current?.anchorStart(la1) || this._stackedTimeline.current?.anchorStart(la2);
+                const endTime = this._stackedTimeline.current?.anchorEnd(la1) || this._stackedTimeline.current?.anchorEnd(la2);
                 if (startTime !== undefined) {
                     if (this.layoutDoc.playOnSelect) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
                     else this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime;
@@ -351,7 +351,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
             renderDepth={this.props.renderDepth + 1}
             parentActive={this.props.parentActive}
             startTag={"audioStart"}
-            endTag={"auidioEnd"}
+            endTag={"audioEnd"}
             focus={emptyFunction}
             styleProvider={this.props.styleProvider}
             docFilters={this.props.docFilters}
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 8d0283a12..b6feace12 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -715,9 +715,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                 if (audio) {
                     audio.mediaStart = "manual";
                     audio.mediaStop = "manual";
-                    audio.presStartTime = NumCast(doc.anchorStartTime);
-                    audio.presEndTime = NumCast(doc.anchorEndTime);
-                    audio.presDuration = NumCast(doc.anchorEndTime) - NumCast(doc.anchorStartTime);
+                    audio.presStartTime = NumCast(doc.audioStart, NumCast(doc.videoStart));
+                    audio.presEndTime = NumCast(doc.audioEnd, NumCast(doc.videoEnd));
+                    audio.presDuration = audio.presStartTime - audio.presEndTime;
                     TabDocView.PinDoc(audio, { audioRange: true });
                     setTimeout(() => this.removeDocument(doc), 0);
                     return false;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index c1cf858c0..ee5fffcc6 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -480,8 +480,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     }
 
     playLink = (doc: Doc) => {
-        const startTime = NumCast(doc.anchorStartTime, NumCast(doc._timecodeToShow));
-        const endTime = NumCast(doc.anchorEndTime, NumCast(doc._timecodeToHide, null));
+        const startTime = this._stackedTimeline.current?.anchorStart(doc) || 0;
+        const endTime = this._stackedTimeline.current?.anchorEnd(doc);
         if (startTime !== undefined) {
             if (this.layoutDoc.playOnSelect) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
             else this.Seek(startTime);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 36d268fe9..96c34860b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -361,7 +361,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             DocListCast(this.dataDoc.links).map((l, i) => {
                 let la1 = l.anchor1 as Doc;
                 let la2 = l.anchor2 as Doc;
-                this._linkTime = NumCast(la1.anchorStartTime, NumCast(la2.anchorStartTime));
+                this._linkTime = NumCast(la1.audioStart, NumCast(la2.audioStart));
                 audioState = la2.audioState;
                 if (Doc.AreProtosEqual(la2, this.dataDoc)) {
                     la1 = l.anchor2 as Doc;
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index d52f3a928..b10c2b015 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -21,8 +21,6 @@ export const documentSchema = createSchema({
     _currentTimecode: "number", // current play back time of a temporal document (video / audio)
     _timecodeToShow: "number",  // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
     isLabel: "boolean",         // whether the document is a label or not (video / audio)
-    anchorStartTime: "number",    // the time code where a document activates (eg in Audio or video timelines)
-    anchorEndTime: "number",      // the time code where a document deactivates
     markers: listSpec(Doc),     // list of markers for audio / video
     x: "number",                // x coordinate when in a freeform view 
     y: "number",                // y coordinate when in a freeform view 
-- 
cgit v1.2.3-70-g09d2


From 32361454f6582155bc84c154dc9315aa794b359e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Jan 2021 18:06:05 -0500
Subject: cleaned up video/audio/stackedTimeline css. restored SpaceKey trigger
 to start/stop creating an anchor.

---
 src/Utils.ts                                       |  12 +-
 .../collections/CollectionStackedTimeline.scss     | 407 +++------------------
 .../collections/CollectionStackedTimeline.tsx      | 213 +++++------
 src/client/views/nodes/AudioBox.scss               | 224 +-----------
 src/client/views/nodes/AudioBox.tsx                |  43 +--
 src/client/views/nodes/ImageBox.tsx                |   2 +-
 src/client/views/nodes/VideoBox.scss               | 132 +------
 src/client/views/nodes/VideoBox.tsx                |  11 +-
 8 files changed, 190 insertions(+), 854 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 3cf695a30..dcfb579ca 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -600,11 +600,16 @@ export function hasDescendantTarget(x: number, y: number, target: HTMLDivElement
     return entered;
 }
 
+export function StopEvent(e: React.PointerEvent | React.MouseEvent) {
+    e.stopPropagation();
+    e.preventDefault();
+}
+
 export function setupMoveUpEvents(
     target: object,
     e: React.PointerEvent,
     moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean,
-    upEvent: (e: PointerEvent, movement: number[]) => any,
+    upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any,
     clickEvent: (e: PointerEvent, doubleTap?: boolean) => any,
     stopPropagation: boolean = true,
     stopMovePropagation: boolean = true
@@ -632,8 +637,9 @@ export function setupMoveUpEvents(
     const _upEvent = (e: PointerEvent): void => {
         (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < 300);
         (target as any)._lastTap = Date.now();
-        upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY]);
-        if (Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4) {
+        const isClick = Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4;
+        upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY], isClick);
+        if (isClick) {
             if ((target as any)._doubleTime && (target as any)._doubleTap) {
                 clearTimeout((target as any)._doubleTime);
                 (target as any)._doubleTime = undefined;
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index 1bb5bc720..9d1b46f27 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -1,373 +1,62 @@
-.audiobox-container,
-.audiobox-container-interactive {
+.collectionStackedTimeline {
+    position: absolute;
     width: 100%;
     height: 100%;
-    position: inherit;
-    display: flex;
-    position: relative;
-    cursor: default;
-
-    .audiobox-inner {
-        width:100%;
-        height: 100%;
-    }
-
-    .audiobox-buttons {
-        display: flex;
-        width: 100%;
-        align-items: center;
-        height: 100%;
-
-        .audiobox-dictation {
-            position: relative;
-            width: 30px;
-            height: 100%;
-            align-items: center;
-            display: inherit;
-            background: dimgray;
-            left: 0px;
-        }
-
-        .audiobox-dictation:hover {
-            color: white;
-            cursor: pointer;
-        }
-    }
-
-    .audiobox-handle {
-        width: 20px;
-        height: 100%;
-        display: inline-block;
-    }
-
-    .audiobox-control,
-    .audiobox-control-interactive {
-        top: 0;
-        max-height: 32px;
-        width: 100%;
-        display: inline-block;
-        pointer-events: none;
-    }
-
-    .audiobox-control-interactive {
-        pointer-events: all;
-    }
-
-    .audiobox-record {
-        pointer-events: all;
-        width: 100%;
+    border: gray solid 1px;
+    border-radius: 3px;
+    z-index: 1000;
+    overflow: hidden;
+    top: 0px;
+
+    .collectionStackedTimeline-selector  {
+        position: absolute;
+        width: 10px;
+        top: 2.5%;
+        height: 95%;
+        background: lightblue;
+        border-radius: 5px;
+        opacity: 0.3;
+        z-index: 500;
+        border-style: solid;
+        border-color: darkblue;
+        border-width: 1px;
+    }
+
+    .collectionStackedTimeline-current {
+        width: 1px;
         height: 100%;
-        position: relative;
+        background-color: red;
+        position: absolute;
+        top: 0px;
         pointer-events: none;
     }
 
-    .audiobox-record-interactive {
-        pointer-events: all;
-        width: 100%;
-        height: 100%;
-        position: relative;
-
-        
-    }
-
-    .recording {
-        margin-top: auto;
-        margin-bottom: auto;
-        width: 100%;
-        height: 100%;
-        position: relative;
-        padding-right: 5px;
-        display: flex;
-        background-color: red;      
-
-        .time {
-            position: relative;
-            height: 100%;
-            width: 100%;
-            font-size: 20;
-            text-align: center;
-            top: 5;
-        }
-
-        .buttons {
-            position: relative;
-            margin-top: auto;
-            margin-bottom: auto;
-            width: 25px;
-            padding: 5px;
-        }
-
-        .buttons:hover {
-            background-color: crimson;
+    .collectionStackedTimeline-marker-timeline {
+        position: absolute;
+        top: 2.5%;
+        height: 95%;
+        border-radius: 4px;
+        opacity: 0.3;
+        &:hover {
+            opacity: 1;
         }
-    }
-
-    .audiobox-controls {
-        width: 100%;
-        height: 100%;
-        position: relative;
-        display: flex;
-        padding-left: 2px;
-        background: black;
 
-        .audiobox-dictation {
+        .collectionStackedTimeline-left-resizer,
+        .collectionStackedTimeline-resizer {
+            background: dimgrey;
             position: absolute;
-            width: 30px;
+            top: 0;
             height: 100%;
-            align-items: center;
-            display: inherit;
-            background: dimgray;
-            left: 0px;
+            width: 10px;
+            pointer-events: all;
+            cursor: ew-resize;
+            z-index: 100;
         }
-
-        .audiobox-player {
-            margin-top: auto;
-            margin-bottom: auto;
-            width: 100%;
-            position: relative;
-            padding-right: 5px;
-            display: flex;
-
-            .audiobox-playhead {
-                position: relative;
-                margin-top: auto;
-                margin-bottom: auto;
-                margin-right: 2px;
-                height: 25px;
-                padding: 2px;
-                border-radius: 50%;
-                background-color: black;
-                color: white;
-            }
-
-            .audiobox-playhead:hover {
-                // background-color:  black;
-                // border-radius: 5px;
-                background-color: grey;
-                color: lightgrey;
-            }
-
-            .audiobox-dictation {
-                position: relative;
-                margin-top: auto;
-                margin-bottom: auto;
-                width: 25px;
-                padding: 2px;
-                align-items: center;
-                display: inherit;
-                background: dimgray;
-            }
-
-            .audiobox-timeline {
-                position: absolute;
-                width: 100%;
-                border: gray solid 1px;
-                border-radius: 3px;
-                z-index: 1000;
-                overflow: hidden;
-
-                .audiobox-container {
-                    position: absolute;
-                    width: 10px;
-                    top: 2.5%;
-                    height: 0px;
-                    background: lightblue;
-                    border-radius: 5px;
-                    // box-shadow: black 2px 2px 1px;
-                    opacity: 0.3;
-                    z-index: 500;
-                    border-style: solid;
-                    border-color: darkblue;
-                    border-width: 1px;
-                }
-
-                .audiobox-current {
-                    width: 1px;
-                    height: 100%;
-                    background-color: red;
-                    position: absolute;
-                    top: 0px;
-                }
-
-                .waveform {
-                    position: relative;
-                    width: 100%;
-                    height: 100%;
-                    overflow: hidden;
-                    z-index: -1000;
-                    bottom: 0;
-                    pointer-events: none;
-                    div {
-                        height: 100% !important;
-                        width: 100%  !important;
-                    }
-                    canvas { 
-                        height: 100% !important;
-                        width: 100%  !important;
-                    }
-                }
-
-                .audiobox-linker,
-                .audiobox-linker-mini {
-                    position: absolute;
-                    width: 15px;
-                    min-height: 10px;
-                    height: 15px;
-                    margin-left: -2.55px;
-                    background: gray;
-                    border-radius: 100%;
-                    opacity: 0.9;
-                    box-shadow: black 2px 2px 1px;
-
-                    .linkAnchorBox-cont {
-                        position: relative !important;
-                        height: 100% !important;
-                        width: 100% !important;
-                        left: unset !important;
-                        top: unset !important;
-                    }
-                }
-
-                .audiobox-linker-mini {
-                    width: 8px;
-                    min-height: 8px;
-                    height: 8px;
-                    box-shadow: black 1px 1px 1px;
-                    margin-left: -1;
-                    margin-top: -2;
-
-                    .linkAnchorBox-cont {
-                        position: relative !important;
-                        height: 100% !important;
-                        width: 100% !important;
-                        left: unset !important;
-                        top: unset !important;
-                    }
-                }
-
-                .audiobox-linker:hover,
-                .audiobox-linker-mini:hover {
-                    opacity: 1;
-                }
-
-                .audiobox-marker-container,
-                .audiobox-marker-minicontainer {
-                    position: absolute;
-                    width: 10px;
-                    height: 10px;
-                    top: 2.5%;
-                    background: gray;
-                    border-radius: 50%;
-                    box-shadow: black 2px 2px 1px;
-                    overflow: visible;
-                    cursor: pointer;
-
-                    .audiobox-marker {
-                        position: relative;
-                        height: 100%;
-                        // height: calc(100% - 15px);
-                        width: 100%;
-                        //margin-top: 15px;
-                    }
-
-                    .audio-marker:hover {
-                        border: orange 2px solid;
-                    }
-                }
-
-                .audiobox-marker-timeline,
-                .audiobox-marker-minicontainer {
-                    position: absolute;
-                    width: 10px;
-                    height: 90%;
-                    top: 2.5%;
-                    border-radius: 5px;
-
-                    .left-resizer {
-                        background: dimgrey;     
-                    }
-                    .resizer {
-                        background: dimgrey;
-                    }
-
-                    .audiobox-marker {
-                        position: relative;
-                        height: calc(100% - 15px);
-                        margin-top: 15px;
-                    }
-
-                    .audio-marker:hover {
-                        border: orange 2px solid;
-                    }
-
-                    .resizer {
-                        position: absolute;
-                        top: 0;
-                        right: 0;
-                        pointer-events: all;
-                        cursor: ew-resize;
-                        height: 100%;
-                        width: 10px;
-                        z-index: 100;
-                    }
-
-                    .click {
-                        position: relative;
-                        height: 100%;
-                        width: 100%;
-                        z-index: 100;
-                    }
-
-                    .left-resizer {
-                        position: absolute;
-                        left: 0;
-                        top : 0;
-                        pointer-events: all;
-                        cursor: ew-resize;
-                        height: 100%;
-                        width: 10px;
-                        z-index: 100;
-                    }
-                }
-
-                .audiobox-marker-timeline:hover,
-                .audiobox-marker-minicontainer:hover {
-                    opacity: 0.8;
-                }
-
-                .audiobox-marker-minicontainer {
-                    width: 5px;
-                    border-radius: 1px;
-
-                    .audiobox-marker {
-                        position: relative;
-                        height: calc(100% - 8px);
-                        margin-top: 8px;
-                    }
-                }
-            }
+        .collectionStackedTimeline-resizer {
+            right: 0;
+        }
+        .collectionStackedTimeline-left-resizer {
+            left: 0;
         }
-    }
-}
-
-@media only screen and (max-device-width: 480px) {
-    .audiobox-dictation {
-        font-size: 5em;
-        display: flex;
-        width: 100;
-        justify-content: center;
-        flex-direction: column;
-        align-items: center;
-    }
-
-    .audiobox-container .audiobox-record,
-    .audiobox-container-interactive .audiobox-record {
-        font-size: 3em;
-    }
-
-    .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead,
-    .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation,
-    .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-playhead {
-        width: 70px;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 1d7e40e96..11a74c4bc 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -8,15 +8,15 @@ import { List } from "../../../fields/List";
 import { listSpec, makeInterface } from "../../../fields/Schema";
 import { ComputedField, ScriptField } from "../../../fields/ScriptField";
 import { Cast, NumCast } from "../../../fields/Types";
-import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } from "../../../Utils";
+import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents, StopEvent } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { Scripting } from "../../util/Scripting";
 import { SelectionManager } from "../../util/SelectionManager";
+import { undoBatch } from "../../util/UndoManager";
 import { CollectionSubView } from "../collections/CollectionSubView";
 import { DocumentView } from "../nodes/DocumentView";
 import { LabelBox } from "../nodes/LabelBox";
 import "./CollectionStackedTimeline.scss";
-import { undoBatch } from "../../util/UndoManager";
 
 type PanZoomDocument = makeInterface<[]>;
 const PanZoomDocument = makeInterface();
@@ -35,28 +35,26 @@ export type CollectionStackedTimelineProps = {
 
 @observer
 export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument, CollectionStackedTimelineProps>(PanZoomDocument) {
+    @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
     static RangeScript: ScriptField;
     static LabelScript: ScriptField;
     static RangePlayScript: ScriptField;
     static LabelPlayScript: ScriptField;
 
-    _disposers: { [name: string]: IReactionDisposer } = {};
-    _doubleTime: NodeJS.Timeout | undefined; // bcz: Hack!  this must be called _doubleTime since setupMoveDragEvents will use that field name
-    _ele: HTMLAudioElement | null = null;
-    _start: number = 0;
-    _left: boolean = false;
-    _dragging = false;
-    _play: any = null;
-    _audioRef = React.createRef<HTMLDivElement>();
-    _timeline: Opt<HTMLDivElement>;
-    _markerStart: number = 0;
-    _currAnchor: Opt<Doc>;
-
-    @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
+    private _doubleTime: NodeJS.Timeout | undefined; // bcz: Hack!  this must be called _doubleTime since setupMoveDragEvents will use that field name
+    private _timeline: HTMLDivElement | null = null;
+    private _markerStart: number = 0;
     @observable _markerEnd: number = 0;
-    @observable _position: number = 0;
+
+    get duration() { return this.props.duration; }
     @computed get anchorDocs() { return this.childDocs; }
-    @computed get currentTime() { return NumCast(this.props.Document._currentTimecode); }
+    @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); }
+    @computed get selectionContainer() {
+        return CollectionStackedTimeline.SelectingRegion !== this ? (null) : <div className="collectionStackedTimeline-selector" style={{
+            left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.duration * 100}%`,
+            width: `${Math.abs(this._markerStart - this._markerEnd) / this.duration * 100}%`
+        }} />;
+    }
 
     constructor(props: any) {
         super(props);
@@ -67,28 +65,37 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         CollectionStackedTimeline.LabelPlayScript = CollectionStackedTimeline.LabelPlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!;
     }
 
-    // for creating key anchors with key events
+    componentDidMount() { document.addEventListener("keydown", this.keyEvents, true); }
     @action
-    keyEvents = (e: KeyboardEvent) => {
-        if (e.target instanceof HTMLInputElement) return;
-        if (!this.props.playing()) return; // can't create if video is not playing
-        switch (e.key) {
-            case "x": // currently set to x, but can be a different key
-                const currTime = this.currentTime;
-                if (this._start) {
-                    this._markerStart = currTime;
-                    // this._start = false;
-                    // this._visible = true;
-                } else {
-                    this.createAnchor(this._markerStart, currTime);
-                    // this._start = true;
-                    // this._visible = false;
-                }
-        }
+    componentWillUnmount() {
+        document.removeEventListener("keydown", this.keyEvents, true);
+        if (CollectionStackedTimeline.SelectingRegion === this) CollectionStackedTimeline.SelectingRegion = undefined;
     }
 
     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));
+    toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration));
+    rangeClickScript = () => CollectionStackedTimeline.RangeScript;
+    labelClickScript = () => CollectionStackedTimeline.LabelScript;
+    rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
+    labelPlayScript = () => CollectionStackedTimeline.LabelPlayScript;
+
+    // for creating key anchors with key events
+    @action
+    keyEvents = (e: KeyboardEvent) => {
+        if (!(e.target instanceof HTMLInputElement) && this.props.isSelected(true)) {
+            switch (e.key) {
+                case " ":
+                    if (!CollectionStackedTimeline.SelectingRegion) {
+                        this._markerStart = this._markerEnd = this.currentTime;
+                        CollectionStackedTimeline.SelectingRegion = this;
+                    } else {
+                        this.createAnchor(this._markerStart, this.currentTime);
+                        CollectionStackedTimeline.SelectingRegion = undefined;
+                    }
+            }
+        }
+    }
 
     getLinkData(l: Doc) {
         let la1 = l.anchor1 as Doc;
@@ -101,69 +108,48 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         return { la1, la2, linkTime };
     }
 
-    // ref for timeline
-    timelineRef = (timeline: HTMLDivElement) => {
-        this._timeline = timeline;
-    }
-
-    // updates the anchor with the new time
-    @action
-    changeAnchor = (anchor: Opt<Doc>, time: number) => {
-        if (anchor) {
-            const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined;
-            if (timelineOnly) Doc.SetInPlace(anchor, this._left ? this.props.startTag : this.props.endTag, time, true);
-            else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time;
-        }
-    }
-
-    // checks if the two anchors are the same with start and end time
-    isSame = (m1: any, m2: any) => {
-        return this.anchorStart(m1) === this.anchorStart(m2) && this.anchorEnd(m1) === this.anchorEnd(m2);
-    }
-
-    @computed get selectionContainer() {
-        return CollectionStackedTimeline.SelectingRegion !== this ? (null) : <div className="audiobox-container" style={{
-            left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.props.duration * 100}%`,
-            width: `${Math.abs(this._markerStart - this._markerEnd) / this.props.duration * 100}%`, height: "100%", top: "0%"
-        }} />;
-    }
-
     // starting the drag event for anchor resizing
     @action
     onPointerDownTimeline = (e: React.PointerEvent): void => {
-        const rect = this._timeline?.getBoundingClientRect();// (e.target as any).getBoundingClientRect();
-        if (rect && e.target !== this._audioRef.current && this.props.active()) {
+        const rect = this._timeline?.getBoundingClientRect();
+        const clientX = e.clientX;
+        if (rect && this.props.active()) {
             const wasPlaying = this.props.playing();
             if (wasPlaying) this.props.Pause();
             else if (!this._doubleTime) {
                 this._doubleTime = setTimeout(() => {
                     this._doubleTime = undefined;
-                    this.props.setTime((e.clientX - rect.x) / rect.width * this.props.duration);
-                }, 300);
+                    this.props.setTime((clientX - rect.x) / rect.width * this.duration);
+                }, 300); // 300ms is double-tap timeout
+            }
+            const wasSelecting = CollectionStackedTimeline.SelectingRegion === this;
+            if (!wasSelecting) {
+                this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
+                CollectionStackedTimeline.SelectingRegion = this;
             }
-            this._markerStart = this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
-            CollectionStackedTimeline.SelectingRegion = this;
             setupMoveUpEvents(this, e,
                 action(e => {
                     this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
                     return false;
                 }),
-                action((e, movement) => {
+                action((e, movement, isClick) => {
                     this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
                     if (this._markerEnd < this._markerStart) {
                         const tmp = this._markerStart;
                         this._markerStart = this._markerEnd;
                         this._markerEnd = tmp;
                     }
-                    CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd);
-                    CollectionStackedTimeline.SelectingRegion = undefined;
+                    if (!isClick) {
+                        CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd);
+                    }
+                    (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined);
                 }),
                 (e, doubleTap) => {
                     this.props.select(false);
                     e.shiftKey && this.createAnchor(this.currentTime);
                     !wasPlaying && doubleTap && this.props.Play();
-                }
-                , this.props.isSelected(true) || this.props.isChildActive());
+                },
+                this.props.isSelected(true) || this.props.isChildActive());
         }
     }
 
@@ -227,36 +213,33 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     }
 
 
-    toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.props.duration, screen_delta / width * this.props.duration));
     // starting the drag event for anchor resizing
-    onPointerDown = (e: React.PointerEvent, m: Doc, left: boolean): void => {
-        this._currAnchor = m;
-        this._left = left;
+    onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
         this._timeline?.setPointerCapture(e.pointerId);
+        const newTime = (e: PointerEvent) => {
+            const rect = (e.target as any).getBoundingClientRect();
+            return this.toTimeline(e.clientX - rect.x, rect.width);
+        }
+        const changeAnchor = (anchor: Doc, left: boolean, time: number) => {
+            const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined;
+            if (timelineOnly) Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true);
+            else left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time;
+            return false;
+        }
         setupMoveUpEvents(this, e,
+            (e) => changeAnchor(anchor, left, newTime(e)),
             (e) => {
-                const rect = (e.target as any).getBoundingClientRect();
-                this.changeAnchor(this._currAnchor, this.toTimeline(e.clientX - rect.x, rect.width));
-                return false;
-            },
-            (e) => {
-                const rect = (e.target as any).getBoundingClientRect();
-                this.props.setTime(this.toTimeline(e.clientX - rect.x, rect.width));
+                this.props.setTime(newTime(e));
                 this._timeline?.releasePointerCapture(e.pointerId);
             },
             emptyFunction);
     }
 
-    rangeClickScript = () => CollectionStackedTimeline.RangeScript;
-    labelClickScript = () => CollectionStackedTimeline.LabelScript;
-    rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
-    labelPlayScript = () => CollectionStackedTimeline.LabelPlayScript;
-
     // makes sure no anchors overlaps each other by setting the correct position and width
     getLevel = (m: Doc, placed: { anchorStartTime: number, anchorEndTime: number, level: number }[]) => {
         const timelineContentWidth = this.props.PanelWidth();
         const x1 = this.anchorStart(m);
-        const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.props.duration);
+        const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.duration);
         let max = 0;
         const overlappedLevels = new Set(placed.map(p => {
             const y1 = p.anchorStartTime;
@@ -281,18 +264,15 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                 ref={action((r: DocumentView | null) => anchor.view = r)}
                 Document={mark}
                 DataDoc={undefined}
-                PanelWidth={() => width}
-                PanelHeight={() => height}
                 renderDepth={this.props.renderDepth + 1}
-                focus={() => this.props.playLink(mark)}
-                rootSelected={returnFalse}
                 LayoutTemplate={undefined}
                 LayoutTemplateString={LabelBox.LayoutString("data")}
-                ContainingCollectionDoc={this.props.Document}
-                removeDocument={this.props.removeDocument}
+                PanelWidth={() => width}
+                PanelHeight={() => height}
                 ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-x, -y)}
-                parentActive={(out) => this.props.isSelected(out) || this.props.isChildActive()}
-                whenActiveChanged={this.props.whenActiveChanged}
+                focus={() => this.props.playLink(mark)}
+                parentActive={out => this.props.isSelected(out) || this.props.isChildActive()}
+                rootSelected={returnFalse}
                 onClick={script}
                 onDoubleClick={this.props.Document.autoPlay ? undefined : doublescript}
                 ignoreAutoHeight={false}
@@ -307,8 +287,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
             {inner.view}
             {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? (null) :
                 <>
-                    <div key="left" className="left-resizer" onPointerDown={e => this.onPointerDown(e, mark, true)} />
-                    <div key="right" className="resizer" onPointerDown={e => this.onPointerDown(e, mark, false)} />
+                    <div key="left" className="collectionStackedTimeline-left-resizer" onPointerDown={e => this.onAnchorDown(e, mark, true)} />
+                    <div key="right" className="collectionStackedTimeline-resizer" onPointerDown={e => this.onAnchorDown(e, mark, false)} />
                 </>}
         </>;
     });
@@ -319,39 +299,28 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = [];
         const drawAnchors = this.anchorDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor }));
         const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
-        return <div className="audiobox-timeline" style={{ height: "100%", width: "100%" }} ref={this.timelineRef}
-            onClick={e => {
-                if (this.props.isChildActive() || this.props.isSelected(false)) {
-                    e.stopPropagation(); e.preventDefault();
-                }
-            }}
-            onPointerDown={e => {
-                if (this.props.isChildActive() || this.props.isSelected(false)) {
-                    e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e);
-                }
-            }}>
+        const isActive = this.props.isChildActive() || this.props.isSelected(false);
+        return <div className="collectionStackedTimeline" ref={(timeline: HTMLDivElement | null) => this._timeline = timeline}
+            onClick={e => isActive && StopEvent(e)} onPointerDown={e => isActive && this.onPointerDownTimeline(e)}>
             {drawAnchors.map(d => {
-                const m = d.anchor;
-                const start = this.anchorStart(m);
-                const end = this.anchorEnd(m, start + 10 / timelineContentWidth * this.props.duration);
-                const left = start / this.props.duration * timelineContentWidth;
+                const start = this.anchorStart(d.anchor);
+                const end = this.anchorEnd(d.anchor, start + 10 / timelineContentWidth * this.duration);
+                const left = start / this.duration * timelineContentWidth;
                 const top = d.level / maxLevel * timelineContentHeight;
                 const timespan = end - start;
                 return this.props.Document.hideAnchors ? (null) :
-                    <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}timeline`} key={m[Id]}
-                        style={{ left, top, width: `${timespan / this.props.duration * 100}%`, height: `${1 / maxLevel * 100}%` }}
-                        onClick={e => { this.props.playFrom(start, this.anchorEnd(m)); e.stopPropagation(); }} >
-                        {this.renderAnchor(m, this.rangeClickScript, this.rangePlayScript,
+                    <div className={"collectionStackedTimeline-marker-timeline"} key={d.anchor[Id]}
+                        style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${timelineContentHeight / maxLevel}px` }}
+                        onClick={e => { this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} >
+                        {this.renderAnchor(d.anchor, this.rangeClickScript, this.rangePlayScript,
                             left,
                             top,
-                            timelineContentWidth * timespan / this.props.duration,
+                            timelineContentWidth * timespan / this.duration,
                             timelineContentHeight / maxLevel)}
                     </div>;
             })}
             {this.selectionContainer}
-            <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }}
-                style={{ left: `${this.currentTime / this.props.duration * 100}%`, pointerEvents: "none" }}
-            />
+            <div className="collectionStackedTimeline-current" style={{ left: `${this.currentTime / this.duration * 100}%` }} />
         </div>;
     }
 }
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index 4a3bbf8d8..fc881ca25 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -7,11 +7,6 @@
     position: relative;
     cursor: default;
 
-    .audiobox-inner {
-        width:100%;
-        height: 100%;
-    }
-
     .audiobox-buttons {
         display: flex;
         width: 100%;
@@ -26,20 +21,13 @@
             display: inherit;
             background: dimgray;
             left: 0px;
-        }
-
-        .audiobox-dictation:hover {
-            color: white;
-            cursor: pointer;
+            &:hover {
+                color: white;
+                cursor: pointer;
+            }
         }
     }
 
-    .audiobox-handle {
-        width: 20px;
-        height: 100%;
-        display: inline-block;
-    }
-
     .audiobox-control,
     .audiobox-control-interactive {
         top: 0;
@@ -53,21 +41,16 @@
         pointer-events: all;
     }
 
+    .audiobox-record-interactive,
     .audiobox-record {
         pointer-events: all;
         width: 100%;
         height: 100%;
         position: relative;
-        pointer-events: none;
     }
 
-    .audiobox-record-interactive {
-        pointer-events: all;
-        width: 100%;
-        height: 100%;
-        position: relative;
-
-        
+    .audiobox-record {
+        pointer-events: none;
     }
 
     .recording {
@@ -95,10 +78,9 @@
             margin-bottom: auto;
             width: 25px;
             padding: 5px;
-        }
-
-        .buttons:hover {
-            background-color: crimson;
+            &:hover{
+                background-color: crimson;
+            }
         }
     }
 
@@ -138,13 +120,10 @@
                 border-radius: 50%;
                 background-color: black;
                 color: white;
-            }
-
-            .audiobox-playhead:hover {
-                // background-color:  black;
-                // border-radius: 5px;
-                background-color: grey;
-                color: lightgrey;
+                &:hover {
+                    background-color: grey;
+                    color: lightgrey;
+                }
             }
 
             .audiobox-dictation {
@@ -166,29 +145,6 @@
                 z-index: 1000;
                 overflow: hidden;
 
-                .audiobox-container {
-                    position: absolute;
-                    width: 10px;
-                    top: 2.5%;
-                    height: 0px;
-                    background: lightblue;
-                    border-radius: 5px;
-                    // box-shadow: black 2px 2px 1px;
-                    opacity: 0.3;
-                    z-index: 500;
-                    border-style: solid;
-                    border-color: darkblue;
-                    border-width: 1px;
-                }
-
-                .audiobox-current {
-                    width: 1px;
-                    height: 100%;
-                    background-color: red;
-                    position: absolute;
-                    top: 0px;
-                }
-
                 .waveform {
                     position: relative;
                     width: 100%;
@@ -206,161 +162,21 @@
                         width: 100%  !important;
                     }
                 }
-
-                .audiobox-linker,
-                .audiobox-linker-mini {
-                    position: absolute;
-                    width: 15px;
-                    min-height: 10px;
-                    height: 15px;
-                    margin-left: -2.55px;
-                    background: gray;
-                    border-radius: 100%;
-                    opacity: 0.9;
-                    box-shadow: black 2px 2px 1px;
-
-                    .linkAnchorBox-cont {
-                        position: relative !important;
-                        height: 100% !important;
-                        width: 100% !important;
-                        left: unset !important;
-                        top: unset !important;
-                    }
-                }
-
-                .audiobox-linker-mini {
-                    width: 8px;
-                    min-height: 8px;
-                    height: 8px;
-                    box-shadow: black 1px 1px 1px;
-                    margin-left: -1;
-                    margin-top: -2;
-
-                    .linkAnchorBox-cont {
-                        position: relative !important;
-                        height: 100% !important;
-                        width: 100% !important;
-                        left: unset !important;
-                        top: unset !important;
-                    }
-                }
-
-                .audiobox-linker:hover,
-                .audiobox-linker-mini:hover {
-                    opacity: 1;
-                }
-
-                .audiobox-marker-container,
-                .audiobox-marker-minicontainer {
-                    position: absolute;
-                    width: 10px;
-                    height: 10px;
-                    top: 2.5%;
-                    background: gray;
-                    border-radius: 50%;
-                    box-shadow: black 2px 2px 1px;
-                    overflow: visible;
-                    cursor: pointer;
-
-                    .audiobox-marker {
-                        position: relative;
-                        height: 100%;
-                        // height: calc(100% - 15px);
-                        width: 100%;
-                        //margin-top: 15px;
-                    }
-
-                    .audio-marker:hover {
-                        border: orange 2px solid;
-                    }
-                }
-
-                .audiobox-marker-timeline,
-                .audiobox-marker-minicontainer {
-                    position: absolute;
-                    width: 10px;
-                    height: 90%;
-                    top: 2.5%;
-                    border-radius: 5px;
-
-                    .left-resizer {
-                        background: dimgrey;     
-                    }
-                    .resizer {
-                        background: dimgrey;
-                    }
-
-                    .audiobox-marker {
-                        position: relative;
-                        height: calc(100% - 15px);
-                        margin-top: 15px;
-                    }
-
-                    .audio-marker:hover {
-                        border: orange 2px solid;
-                    }
-
-                    .resizer {
-                        position: absolute;
-                        top: 0;
-                        right: 0;
-                        pointer-events: all;
-                        cursor: ew-resize;
-                        height: 100%;
-                        width: 10px;
-                        z-index: 100;
-                    }
-
-                    .click {
-                        position: relative;
-                        height: 100%;
-                        width: 100%;
-                        z-index: 100;
-                    }
-
-                    .left-resizer {
-                        position: absolute;
-                        left: 0;
-                        top : 0;
-                        pointer-events: all;
-                        cursor: ew-resize;
-                        height: 100%;
-                        width: 10px;
-                        z-index: 100;
-                    }
-                }
-
-                .audiobox-marker-timeline:hover,
-                .audiobox-marker-minicontainer:hover {
-                    opacity: 0.8;
-                }
-
-                .audiobox-marker-minicontainer {
-                    width: 5px;
-                    border-radius: 1px;
-
-                    .audiobox-marker {
-                        position: relative;
-                        height: 100%;
-                        margin-top: 8px;
-                    }
-                }
             }
 
-            .current-time {
+            .audioBox-total-time,
+            .audioBox-current-time {
                 position: absolute;
                 font-size: 8;
                 top: 100%;
-                left: 30px;
                 color: white;
             }
+            .audioBox-current-time {
+                left: 30px;
+            }
 
-            .total-time {
-                position: absolute;
-                top: 100%;
-                font-size: 8;
+            .audioBox-total-time {
                 right: 2px;
-                color: white;
             }
         }
     }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 741a03900..9f343e904 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -81,7 +81,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     getLinkData(l: Doc) {
         let la1 = l.anchor1 as Doc;
         let la2 = l.anchor2 as Doc;
-        const linkTime = this._stackedTimeline.current?.anchorStart(la2) || this._stackedTimeline.current?.anchorStart(la1);
+        const linkTime = this._stackedTimeline.current?.anchorStart(la2) || this._stackedTimeline.current?.anchorStart(la1) || 0;
         if (Doc.AreProtosEqual(la1, this.dataDoc)) {
             la1 = l.anchor2 as Doc;
             la2 = l.anchor1 as Doc;
@@ -344,42 +344,35 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         }
     }
 
+    isActiveChild = () => this._isChildActive;
+    timelineWhenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(runInAction(() => this._isChildActive = isActive));
+    timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-AudioBox.playheadWidth, -(100 - this.heightPercent) / 200 * this.props.PanelHeight());
+    setAnchorTime = (time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time;
+    timelineHeight = () => this.props.PanelHeight() * this.heightPercent / 100 * this.heightPercent / 100; // panelHeight * heightPercent is player height.  * heightPercent is timeline height (as per css inline)
+    timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth;
     @computed get renderTimeline() {
-        return <CollectionStackedTimeline ref={this._stackedTimeline}
-            Document={this.props.Document}
+        return <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
             fieldKey={this.annotationKey}
             renderDepth={this.props.renderDepth + 1}
-            parentActive={this.props.parentActive}
             startTag={"audioStart"}
             endTag={"audioEnd"}
             focus={emptyFunction}
-            styleProvider={this.props.styleProvider}
-            docFilters={this.props.docFilters}
-            docRangeFilters={this.props.docRangeFilters}
-            searchFilterDocs={this.props.searchFilterDocs}
-            rootSelected={this.props.rootSelected}
-            addDocTab={this.props.addDocTab}
-            pinToPres={this.props.pinToPres}
             bringToFront={emptyFunction}
-            ContainingCollectionDoc={this.props.ContainingCollectionDoc}
-            ContainingCollectionView={this.props.ContainingCollectionView}
             CollectionView={undefined}
             duration={this.duration}
             playFrom={this.playFrom}
-            setTime={(time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time}
+            setTime={this.setAnchorTime}
             playing={this.playing}
-            select={this.props.select}
-            isSelected={this.props.isSelected}
-            whenActiveChanged={action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive))}
+            whenActiveChanged={this.timelineWhenActiveChanged}
             removeDocument={this.removeDocument}
-            ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(0, -(100 - this.heightPercent) / 200 * this.props.PanelHeight())}
-            isChildActive={() => this._isChildActive}
+            ScreenToLocalTransform={this.timelineScreenToLocal}
+            isChildActive={this.isActiveChild}
             Play={this.Play}
             Pause={this.Pause}
             active={this.active}
             playLink={this.playLink}
-            PanelWidth={this.props.PanelWidth}
-            PanelHeight={() => this.props.PanelHeight() * this.heightPercent / 100 * this.heightPercent / 100}// panelHeight * heightPercent is player height.  * heightPercent is timeline height (as per css inline)
+            PanelWidth={this.timelineWidth}
+            PanelHeight={this.timelineHeight}
         />;
     }
 
@@ -413,17 +406,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
                     <div className="audiobox-dictation" />
                     <div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} >
                         <div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.audioState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.audioState === "paused" ? "play" : "pause"} size={"1x"} /></div>
-                        <div className="audiobox-timeline" style={{ height: `100%`, left: AudioBox.playheadWidth, width: `calc(100% - ${AudioBox.playheadWidth}px)`, background: "white" }}>
+                        <div className="audiobox-timeline" style={{ top: 0, height: `100%`, left: AudioBox.playheadWidth, width: `calc(100% - ${AudioBox.playheadWidth}px)`, background: "white" }}>
                             <div className="waveform">
                                 {this.waveform}
                             </div>
+                            {this.renderTimeline}
                         </div>
-                        {this.renderTimeline}
                         {this.audio}
-                        <div className="current-time">
+                        <div className="audioBox-current-time">
                             {formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}
                         </div>
-                        <div className="total-time">
+                        <div className="audioBox-total-time">
                             {formatTime(Math.round(this.duration))}
                         </div>
                     </div>
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 1da3a2779..92d6e2612 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -421,7 +421,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
     }
     @action
     marqueeDown = (e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.Document._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY];
+        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY];
     }
     @action
     finishMarquee = () => {
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index ac4d64f12..b9123587b 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -10,138 +10,8 @@
     .inkingCanvas-paths-markers {
         opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround
     }
-
-    .audiobox-timeline {
-        position: absolute;
-        width: 100%;
+    .collectionStackedTimeline {
         background: beige;
-        border: gray solid 1px;
-        border-radius: 3px;
-        z-index: 1000;
-        overflow: hidden;
-        bottom: 0;
-
-        .audiobox-current {
-            width: 1px;
-            height: 100%;
-            background-color: red;
-            position: absolute;
-            top: 0px;
-        }
-
-        .audiobox-container {
-            position: absolute;
-            width: 10px;
-            top: 2.5%;
-            height: 0px;
-            background: lightblue;
-            border-radius: 5px;
-            // box-shadow: black 2px 2px 1px;
-            opacity: 0.3;
-            z-index: 500;
-            border-style: solid;
-            border-color: darkblue;
-            border-width: 1px;
-        }
-
-        .audiobox-marker-timeline,
-        .audiobox-marker-minicontainer {
-            position: absolute;
-            width: 10px;
-            height: 10px;
-            top: 2.5%;
-            border-radius: 50%;
-            box-shadow: black 2px 2px 1px;
-            overflow: visible;
-            cursor: pointer;
-
-            .left-resizer {
-                background: dimgrey;     
-            }
-            .resizer {
-                background: dimgrey;
-            }
-            .audiobox-marker {
-                position: relative;
-                height: 100%;
-                // height: calc(100% - 15px);
-                width: 100%;
-                //margin-top: 15px;
-            }
-
-            .audio-marker:hover {
-                border: orange 2px solid;
-            }
-        }
-
-        .audiobox-marker-timeline,
-        .audiobox-marker-minicontainer {
-            position: absolute;
-            width: 10px;
-            height: 90%;
-            top: 2.5%;
-            border-radius: 5px;
-            box-shadow: black 2px 2px 1px;
-
-            .audiobox-marker {
-                position: relative;
-                height: calc(100% - 15px);
-                margin-top: 15px;
-            }
-
-            .audio-marker:hover {
-                border: orange 2px solid;
-            }
-
-            .resizer {
-                position: absolute;
-                top: 0;
-                right: 0;
-                pointer-events: all;
-                cursor: ew-resize;
-                height: 100%;
-                width: 10px;
-                z-index: 100;
-            }
-
-            .click {
-                position: relative;
-                height: 100%;
-                width: 100%;
-                z-index: 100;
-            }
-
-            .left-resizer {
-                position: absolute;
-                left: 0;
-                top: 0;
-                cursor: ew-resize;
-                height: 100%;
-                width: 10px;
-                z-index: 100;
-            }
-            
-            // .contentFittingDocumentView-previewDoc {
-            //     width: 100% !important;
-            //     transform: none !important;
-            // }
-        }
-
-        .audiobox-marker-container1:hover,
-        .audiobox-marker-minicontainer:hover {
-            opacity: 0.8;
-        }
-
-        .audiobox-marker-minicontainer {
-            width: 5px;
-            border-radius: 1px;
-
-            .audiobox-marker {
-                position: relative;
-                height: calc(100% - 8px);
-                margin-top: 8px;
-            }
-        }
     }
 }
 
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 63e463e8b..c360a924e 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -7,7 +7,7 @@ import { Dictionary } from "typescript-collections";
 import { Doc, DocListCast } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { InkTool } from "../../../fields/InkField";
-import { createSchema, makeInterface } from "../../../fields/Schema";
+import { makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
 import { VideoField } from "../../../fields/URLField";
 import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils } from "../../../Utils";
@@ -86,13 +86,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         this.dataDoc[this.fieldKey + "-duration"] = this.player!.duration;
     }
 
-    static keyEventsWrapper = (e: KeyboardEvent) => {
-        VideoBox.Instance._stackedTimeline.current?.keyEvents(e);
-    }
-
     @action public Play = (update: boolean = true) => {
-        document.removeEventListener("keydown", VideoBox.keyEventsWrapper, true);
-        document.addEventListener("keydown", VideoBox.keyEventsWrapper, true);
         this._playing = true;
         try {
             update && this.player?.play();
@@ -248,7 +242,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     componentWillUnmount() {
         this.Pause();
         Object.keys(this._disposers).forEach(d => this._disposers[d]?.());
-        document.removeEventListener("keydown", VideoBox.keyEventsWrapper, true);
     }
 
     @action
@@ -535,7 +528,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     }
 
     marqueeDown = action((e: React.PointerEvent) => {
-        if (!e.altKey && e.button === 0 && this.props.Document._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY];
+        if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY];
     });
 
     finishMarquee = action(() => {
-- 
cgit v1.2.3-70-g09d2


From b11652e06205bf214f9504330df3980af643a7cc Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 28 Jan 2021 20:51:39 -0500
Subject: fixed up double click behavior in general but specifically for
 Audio/Video timelines

---
 src/Utils.ts                                       | 15 ++++++++----
 .../collections/CollectionStackedTimeline.tsx      | 27 ++++++++--------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  5 ++--
 3 files changed, 23 insertions(+), 24 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index dcfb579ca..39fc3dae4 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -612,10 +612,20 @@ export function setupMoveUpEvents(
     upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any,
     clickEvent: (e: PointerEvent, doubleTap?: boolean) => any,
     stopPropagation: boolean = true,
-    stopMovePropagation: boolean = true
+    stopMovePropagation: boolean = true,
+    noDoubleTapTimeout?: () => void
 ) {
+    const doubleTapTimeout = 300;
+    (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < doubleTapTimeout);
+    (target as any)._lastTap = Date.now();
     (target as any)._downX = (target as any)._lastX = e.clientX;
     (target as any)._downY = (target as any)._lastY = e.clientY;
+    if (!(target as any)._doubleTime && noDoubleTapTimeout) {
+        (target as any)._doubleTime = setTimeout(() => {
+            noDoubleTapTimeout?.();
+            (target as any)._doubleTime = undefined;
+        }, doubleTapTimeout);
+    }
 
     const _moveEvent = (e: PointerEvent): void => {
         if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) {
@@ -633,10 +643,7 @@ export function setupMoveUpEvents(
         (target as any)._lastY = e.clientY;
         stopMovePropagation && e.stopPropagation();
     };
-    (target as any)._doubleTap = false;
     const _upEvent = (e: PointerEvent): void => {
-        (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < 300);
-        (target as any)._lastTap = Date.now();
         const isClick = Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4;
         upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY], isClick);
         if (isClick) {
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 11a74c4bc..1ccf474f2 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,5 +1,5 @@
 import React = require("react");
-import { action, computed, IReactionDisposer, observable } from "mobx";
+import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
 import { Doc, Opt } from "../../../fields/Doc";
@@ -41,7 +41,6 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     static RangePlayScript: ScriptField;
     static LabelPlayScript: ScriptField;
 
-    private _doubleTime: NodeJS.Timeout | undefined; // bcz: Hack!  this must be called _doubleTime since setupMoveDragEvents will use that field name
     private _timeline: HTMLDivElement | null = null;
     private _markerStart: number = 0;
     @observable _markerEnd: number = 0;
@@ -66,10 +65,9 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     }
 
     componentDidMount() { document.addEventListener("keydown", this.keyEvents, true); }
-    @action
     componentWillUnmount() {
         document.removeEventListener("keydown", this.keyEvents, true);
-        if (CollectionStackedTimeline.SelectingRegion === this) CollectionStackedTimeline.SelectingRegion = undefined;
+        if (CollectionStackedTimeline.SelectingRegion === this) runInAction(() => CollectionStackedTimeline.SelectingRegion = undefined);
     }
 
     anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
@@ -116,19 +114,13 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         if (rect && this.props.active()) {
             const wasPlaying = this.props.playing();
             if (wasPlaying) this.props.Pause();
-            else if (!this._doubleTime) {
-                this._doubleTime = setTimeout(() => {
-                    this._doubleTime = undefined;
-                    this.props.setTime((clientX - rect.x) / rect.width * this.duration);
-                }, 300); // 300ms is double-tap timeout
-            }
             const wasSelecting = CollectionStackedTimeline.SelectingRegion === this;
-            if (!wasSelecting) {
-                this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
-                CollectionStackedTimeline.SelectingRegion = this;
-            }
             setupMoveUpEvents(this, e,
                 action(e => {
+                    if (!wasSelecting && CollectionStackedTimeline.SelectingRegion !== this) {
+                        this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
+                        CollectionStackedTimeline.SelectingRegion = this;
+                    }
                     this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
                     return false;
                 }),
@@ -149,7 +141,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                     e.shiftKey && this.createAnchor(this.currentTime);
                     !wasPlaying && doubleTap && this.props.Play();
                 },
-                this.props.isSelected(true) || this.props.isChildActive());
+                this.props.isSelected(true) || this.props.isChildActive(), undefined,
+                () => !wasPlaying && this.props.setTime((clientX - rect.x) / rect.width * this.duration));
         }
     }
 
@@ -219,13 +212,13 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         const newTime = (e: PointerEvent) => {
             const rect = (e.target as any).getBoundingClientRect();
             return this.toTimeline(e.clientX - rect.x, rect.width);
-        }
+        };
         const changeAnchor = (anchor: Doc, left: boolean, time: number) => {
             const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined;
             if (timelineOnly) Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true);
             else left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time;
             return false;
-        }
+        };
         setupMoveUpEvents(this, e,
             (e) => changeAnchor(anchor, left, newTime(e)),
             (e) => {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4d277dc1c..6c7512f7c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -818,9 +818,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
             e.stopPropagation();
             e.preventDefault();
             if (!e.ctrlKey && MarqueeView.DragMarquee) this.setPan(this.panX() + e.deltaX, this.panY() + e.deltaY, "None", true);
-            else
-                // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
-                this.zoom(e.clientX, e.clientY, e.deltaY);
+            else this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+
         }
         this.props.Document.targetScale = NumCast(this.props.Document[this.scaleFieldKey]);
     }
-- 
cgit v1.2.3-70-g09d2


From 42d8bd5f673341682452c7c1f59b6b4b3a33d346 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 29 Jan 2021 00:00:20 -0500
Subject: fixed automatic linking to audio recordings.  now it creates anchors
 and it inserts carriage returns properly when adding timestamps and
 suppressing timestamps for code blocks.

---
 src/client/documents/Documents.ts                  |  2 --
 .../collections/CollectionStackedTimeline.tsx      | 18 +++++-----
 src/client/views/nodes/AudioBox.tsx                |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 39 ++++++++--------------
 5 files changed, 24 insertions(+), 40 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 142e14ff8..1a4aae17e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -720,8 +720,6 @@ export namespace Docs {
             linkDocProto.treeViewOpen = true;// setting this in the instance creator would set it on the view document. 
             linkDocProto.anchor1 = source.doc;
             linkDocProto.anchor2 = target.doc;
-            linkDocProto.anchor1_timecode = source.doc._currentTimecode || source.doc._timecodeToShow;
-            linkDocProto.anchor2_timecode = target.doc._currentTimecode || target.doc._timecodeToShow;
 
             if (linkDocProto.linkBoxExcludedKeys === undefined) {
                 Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]);
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 1ccf474f2..02e88d939 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -148,20 +148,20 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
 
     @undoBatch
     @action
-    createAnchor(anchorStartTime?: number, anchorEndTime?: number) {
-        if (anchorStartTime === undefined) return this.props.Document;
+    static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime?: number, anchorEndTime?: number) {
+        if (anchorStartTime === undefined) return rootDoc;
         const anchor = Docs.Create.LabelDocument({
-            title: ComputedField.MakeFunction(`"#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"])`) as any,
+            title: ComputedField.MakeFunction(`"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`) as any,
             useLinkSmallAnchor: true,
             hideLinkButton: true,
-            annotationOn: this.props.Document
+            annotationOn: rootDoc
         });
-        Doc.GetProto(anchor)[this.props.startTag] = anchorStartTime;
-        Doc.GetProto(anchor)[this.props.endTag] = anchorEndTime;
-        if (Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc), null) !== undefined) {
-            Cast(this.dataDoc[this.props.fieldKey], listSpec(Doc), []).push(anchor);
+        Doc.GetProto(anchor)[startTag] = anchorStartTime;
+        Doc.GetProto(anchor)[endTag] = anchorEndTime;
+        if (Cast(dataDoc[fieldKey], listSpec(Doc), null) !== undefined) {
+            Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
         } else {
-            this.dataDoc[this.props.fieldKey] = new List<Doc>([anchor]);
+            dataDoc[fieldKey] = new List<Doc>([anchor]);
         }
         return anchor;
     }
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 9f343e904..6b9d12ac0 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -90,7 +90,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     }
 
     getAnchor = () => {
-        return this._stackedTimeline.current?.createAnchor(this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) || this.rootDoc;
+        return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "audioStart", "audioEnd", this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) || this.rootDoc;
     }
 
     componentWillUnmount() {
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 57077d113..79d584a1d 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -60,7 +60,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     @computed get links() { return DocListCast(this.dataDoc.links); }
     @computed get heightPercent() { return NumCast(this.layoutDoc._timelineHeightPercent, 100); }
     @computed get duration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
-    @computed get anchorDocs() { return DocListCast(this.dataDoc[this.annotationKey + "-timeline"]).concat(DocListCast(this.dataDoc[this.annotationKey])); }
 
     private get transition() { return this._clicking ? "left 0.5s, width 0.5s, height 0.5s" : ""; }
     public get player(): HTMLVideoElement | null { return this._videoRef; }
@@ -71,7 +70,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     }
 
     getAnchor = () => {
-        return this._stackedTimeline.current?.createAnchor(Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc;
+        return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "videoStart", "videoEnd", Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc;
     }
 
     choosePath(url: string) {
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 96c34860b..6914c20b4 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -99,10 +99,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     private _undoTyping?: UndoManager.Batch;
     private _disposers: { [name: string]: IReactionDisposer } = {};
     private _dropDisposer?: DragManager.DragDropDisposer;
-    private _first: Boolean = true;
     private _recordingStart: number = 0;
-    private _currentTime: number = 0;
-    private _linkTime: number | null = null;
     private _pause: boolean = false;
     private _animatingScroll: number = 0; // hack to prevent scroll values from being written to document when scroll is animating
 
@@ -356,24 +353,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
 
     // for inserting timestamps 
     insertTime = () => {
-        let audioState;
-        if (this._first) {
-            DocListCast(this.dataDoc.links).map((l, i) => {
-                let la1 = l.anchor1 as Doc;
-                let la2 = l.anchor2 as Doc;
-                this._linkTime = NumCast(la1.audioStart, NumCast(la2.audioStart));
-                audioState = la2.audioState;
-                if (Doc.AreProtosEqual(la2, this.dataDoc)) {
-                    la1 = l.anchor2 as Doc;
-                    la2 = l.anchor1 as Doc;
-                    audioState = la1.audioState;
-                }
-            });
-        }
-        this._currentTime = Date.now();
-        let time;
-        this._linkTime ? time = this.formatTime(Math.round(this._linkTime + this._currentTime / 1000 - this._recordingStart / 1000)) : time = null;
-
+        let linkTime;
+        DocListCast(this.dataDoc.links).forEach((l, i) => {
+            const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
+            if (anchor && (anchor.annotationOn as Doc).audioState === "recording") linkTime = NumCast(anchor.audioStart);
+        });
         if (this._editorView) {
             const state = this._editorView.state;
             const now = Date.now();
@@ -388,13 +372,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                     }
                 }
             }
-            if (time && audioState === "recording") {
-                let value = "";
+
+            const path = (this._editorView.state.selection.$from as any).path;
+            if (linkTime && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) {
+                const time = this.formatTime(Math.round(linkTime + Date.now() / 1000 - this._recordingStart / 1000));
                 this._break = false;
-                value = this.layoutDoc._timeStampOnEnter ? "[" + time + "] " : "\n" + "[" + time + "] ";
+                const value = (this.layoutDoc._timeStampOnEnter ? "" : "\n") + "[" + time + "]";
                 const from = state.selection.from;
-                const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark);
-                this._editorView.dispatch(this._editorView.state.tr.insertText(value));
+                const para = this._editorView.state.schema.nodes.paragraph.create();
+                const replaced = this._editorView.state.tr.insertText(value).addMark(from, from + value.length + 1, mark).insert(from + value.length, para);
+                this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + value.length + 1))));
             }
         }
     }
-- 
cgit v1.2.3-70-g09d2


From a695e0dd4e2541d6e093e41b88fdd41a32ebadb1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 29 Jan 2021 20:11:20 -0500
Subject: fixed taking videoBox snapshots. select video/audio doc after
 deleting an annotation.  Fixed following links to videoBox timeline anchors. 
 Fixed rendering timeline annotations by putting them in
 data-annotations-timelines to distinguish from regular annotations.

---
 src/client/views/DocComponent.tsx                  |  2 ++
 src/client/views/MarqueeAnnotator.tsx              |  6 ++++--
 .../collections/CollectionStackedTimeline.tsx      | 16 +++++++++------
 src/client/views/nodes/AudioBox.tsx                |  9 ++++----
 src/client/views/nodes/VideoBox.tsx                | 24 ++++++++++++++++------
 src/client/views/nodes/formattedText/nodes_rts.ts  |  2 +-
 6 files changed, 40 insertions(+), 19 deletions(-)

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

diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 99f13295d..b800ba777 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -74,6 +74,7 @@ export interface ViewBoxAnnotatableProps {
     fieldKey: string;
     layerProvider?: (doc: Doc) => boolean;
     active: () => boolean;
+    select: (isCtrlPressed: boolean) => void;
     whenActiveChanged: (isActive: boolean) => void;
     isSelected: (outsideReaction?: boolean) => boolean;
     rootSelected: (outsideReaction?: boolean) => boolean;
@@ -145,6 +146,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T
                         Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc);
                         recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
                     });
+                    this.props.select(false);
                     return true;
                 }
             }
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 8ef69802b..4ffccbc90 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -21,6 +21,7 @@ export interface MarqueeAnnotatorProps {
     rootDoc: Doc;
     down: number[];
     scaling?: () => number;
+    containerOffset?: () => number[];
     mainCont: HTMLDivElement;
     savedAnnotations: Dictionary<number, HTMLDivElement[]>;
     annotationLayer: HTMLDivElement;
@@ -69,9 +70,10 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
         if ((this.props.savedAnnotations.values()[0][0] as any).marqueeing) {
             const scale = this.props.scaling?.() || 1;
             const anno = this.props.savedAnnotations.values()[0][0];
+            const containerOffset = this.props.containerOffset?.() || [0, 0];
             const mainAnnoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title });
-            if (anno.style.left) mainAnnoDoc.x = parseInt(anno.style.left) / scale;
-            if (anno.style.top) mainAnnoDoc.y = (NumCast(this.props.rootDoc._scrollTop) + parseInt(anno.style.top)) / scale;
+            if (anno.style.left) mainAnnoDoc.x = (parseInt(anno.style.left) - containerOffset[0]) / scale;
+            if (anno.style.top) mainAnnoDoc.y = (parseInt(anno.style.top) - containerOffset[1] + NumCast(this.props.rootDoc._scrollTop)) / scale;
             if (anno.style.height) mainAnnoDoc._height = parseInt(anno.style.height) / scale;
             if (anno.style.width) mainAnnoDoc._width = parseInt(anno.style.width) / scale;
             mainAnnoDoc.group = mainAnnoDoc;
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 02e88d939..a59ac109f 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
 import { observer } from "mobx-react";
 import { computedFn } from "mobx-utils";
-import { Doc, Opt } from "../../../fields/Doc";
+import { Doc, Opt, DocListCast } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 import { List } from "../../../fields/List";
 import { listSpec, makeInterface } from "../../../fields/Schema";
@@ -31,6 +31,7 @@ export type CollectionStackedTimelineProps = {
     isChildActive: () => boolean;
     startTag: string;
     endTag: string;
+    fieldKeySuffix?: string;
 };
 
 @observer
@@ -46,7 +47,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     @observable _markerEnd: number = 0;
 
     get duration() { return this.props.duration; }
-    @computed get anchorDocs() { return this.childDocs; }
+    @computed get anchorDocs() { return this.props.fieldKeySuffix ? this.childDocs.concat(...DocListCast(this.rootDoc[this.props.fieldKey + this.props.fieldKeySuffix])) : this.childDocs; }
     @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); }
     @computed get selectionContainer() {
         return CollectionStackedTimeline.SelectingRegion !== this ? (null) : <div className="collectionStackedTimeline-selector" style={{
@@ -71,7 +72,10 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
     }
 
     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));
+    anchorEnd = (anchor: Doc, val: any = null) => {
+        const endVal = NumCast(anchor[this.props.endTag], val);
+        return NumCast(anchor._timecodeToHide, endVal === undefined ? null : endVal);
+    }
     toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration));
     rangeClickScript = () => CollectionStackedTimeline.RangeScript;
     labelClickScript = () => CollectionStackedTimeline.LabelScript;
@@ -88,7 +92,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                         this._markerStart = this._markerEnd = this.currentTime;
                         CollectionStackedTimeline.SelectingRegion = this;
                     } else {
-                        this.createAnchor(this._markerStart, this.currentTime);
+                        CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime);
                         CollectionStackedTimeline.SelectingRegion = undefined;
                     }
             }
@@ -132,13 +136,13 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                         this._markerEnd = tmp;
                     }
                     if (!isClick) {
-                        CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd);
+                        CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag);
                     }
                     (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined);
                 }),
                 (e, doubleTap) => {
                     this.props.select(false);
-                    e.shiftKey && this.createAnchor(this.currentTime);
+                    e.shiftKey && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime);
                     !wasPlaying && doubleTap && this.props.Play();
                 },
                 this.props.isSelected(true) || this.props.isChildActive(), undefined,
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 57b5f3ec7..692eaae66 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -326,15 +326,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
 
     playing = () => this.audioState === "playing";
     playLink = (link: Doc) => {
+        const stack = this._stackedTimeline.current;
         if (link.annotationOn === this.rootDoc) {
-            if (this.layoutDoc.playOnSelect) this.playFrom(this._stackedTimeline.current?.anchorStart(link) || 0, this._stackedTimeline.current?.anchorEnd(link));
-            else this._ele!.currentTime = this.layoutDoc._currentTimecode = (this._stackedTimeline.current?.anchorStart(link) || 0);
+            if (this.layoutDoc.playOnSelect) this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link));
+            else this._ele!.currentTime = this.layoutDoc._currentTimecode = (stack?.anchorStart(link) || 0);
         }
         else {
             this.links.filter(l => l.anchor1 === link || l.anchor2 === link).forEach(l => {
                 const { la1, la2 } = this.getLinkData(l);
-                const startTime = this._stackedTimeline.current?.anchorStart(la1) || this._stackedTimeline.current?.anchorStart(la2);
-                const endTime = this._stackedTimeline.current?.anchorEnd(la1) || this._stackedTimeline.current?.anchorEnd(la2);
+                const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2);
+                const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2);
                 if (startTime !== undefined) {
                     if (this.layoutDoc.playOnSelect) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
                     else this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 79d584a1d..8a1cefbd9 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -70,7 +70,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
     }
 
     getAnchor = () => {
-        return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "videoStart", "videoEnd", Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc;
+        return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey + "-timeline", "videoStart", "videoEnd", Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc;
     }
 
     choosePath(url: string) {
@@ -167,7 +167,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
             //convert to desired file format
             const dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
             // if you want to preview the captured image,
-            const filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.rootDoc.title).replace(/\..*$/, "") + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_")));
+            const retitled = StrCast(this.rootDoc.title).replace(/[ -\.]/g, "");
+            const filename = path.basename(encodeURIComponent("snapshot" + retitled + "_" + (this.layoutDoc._currentTimecode || 0).toString().replace(/\./, "_")));
             VideoBox.convertDataUri(dataUrl, filename).then((returnedFilename: string) =>
                 returnedFilename && this.createRealSummaryLink(returnedFilename));
         }
@@ -179,14 +180,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         const height = this.layoutDoc._height || 0;
         const imageSummary = Docs.Create.ImageDocument(url, {
             _nativeWidth: Doc.NativeWidth(this.layoutDoc), _nativeHeight: Doc.NativeHeight(this.layoutDoc),
-            x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 0),
+            x: (this.layoutDoc.x || 0) + width, y: (this.layoutDoc.y || 0), isLinkButton: true,
             _width: 150, _height: height / width * 150, title: "--snapshot" + (this.layoutDoc._currentTimecode || 0) + " image-"
         });
         Doc.SetNativeWidth(Doc.GetProto(imageSummary), Doc.NativeWidth(this.layoutDoc));
         Doc.SetNativeHeight(Doc.GetProto(imageSummary), Doc.NativeHeight(this.layoutDoc));
-        imageSummary.isLinkButton = true;
         this.props.addDocument?.(imageSummary);
-        DocUtils.MakeLink({ doc: imageSummary }, { doc: this.rootDoc }, "video snapshot");
+        DocUtils.MakeLink({ doc: imageSummary }, { doc: this.getAnchor() }, "video snapshot");
     }
 
     @action
@@ -495,6 +495,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                 renderDepth={this.props.renderDepth + 1}
                 startTag={"videoStart"}
                 endTag={"videoEnd"}
+                fieldKeySuffix={"-timeline"}
                 focus={emptyFunction}
                 bringToFront={emptyFunction}
                 CollectionView={undefined}
@@ -536,6 +537,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
         return this.props.ScreenToLocalTransform().translate(-offset, 0).scale(100 / this.heightPercent);
     }
+    marqueeFitScaling = () => (this.props.scaling?.() || 1) * this.heightPercent / 100;
+    marqueeOffset = () => [this.panelWidth() / 2 * (1 - this.heightPercent / 100) / (this.heightPercent / 100), 0];
 
     render() {
         const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
@@ -570,7 +573,16 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                 {this.annotationLayer}
                 {this.renderTimeline}
                 {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
-                    <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocWithTimecode} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
+                    <MarqueeAnnotator
+                        rootDoc={this.rootDoc}
+                        down={this._marqueeing}
+                        scaling={this.marqueeFitScaling}
+                        containerOffset={this.marqueeOffset}
+                        addDocument={this.addDocWithTimecode}
+                        finishMarquee={this.finishMarquee}
+                        savedAnnotations={this._savedAnnotations}
+                        annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current}
+                    />}
             </div>
         </div >);
     }
diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts
index f5bc05a2d..722c0a836 100644
--- a/src/client/views/nodes/formattedText/nodes_rts.ts
+++ b/src/client/views/nodes/formattedText/nodes_rts.ts
@@ -36,7 +36,7 @@ export const nodes: { [index: string]: NodeSpec } = {
                     "data-audioid": node.attrs.audioId,
                 },
                 formatAudioTime(node.attrs.timeCode.toString())
-            ]
+            ];
         },
         parseDOM: [
             {
-- 
cgit v1.2.3-70-g09d2


From 41bb365dd4f787aec2262dcb07508e0de3837e10 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 29 Jan 2021 22:19:35 -0500
Subject: fixed dragging on timeline to create anchor

---
 src/client/views/collections/CollectionStackedTimeline.scss | 1 -
 src/client/views/collections/CollectionStackedTimeline.tsx  | 5 +++--
 2 files changed, 3 insertions(+), 3 deletions(-)

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

diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index 9d1b46f27..cc56831f3 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -36,7 +36,6 @@
         top: 2.5%;
         height: 95%;
         border-radius: 4px;
-        opacity: 0.3;
         &:hover {
             opacity: 1;
         }
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index a59ac109f..d4eb66fcc 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -135,8 +135,9 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                         this._markerStart = this._markerEnd;
                         this._markerEnd = tmp;
                     }
-                    if (!isClick) {
-                        CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag);
+                    if (!isClick && CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15)) {
+                        CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag,
+                            this._markerStart, this._markerEnd);
                     }
                     (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined);
                 }),
-- 
cgit v1.2.3-70-g09d2

