From c97b98c165e318da787361b8fc11382b0b98afa4 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 24 Oct 2019 11:54:03 -0400
Subject: several fixes to audioboxes.

---
 src/client/views/nodes/AudioBox.tsx | 19 ++++++++-----------
 1 file changed, 8 insertions(+), 11 deletions(-)

(limited to 'src/client/views/nodes/AudioBox.tsx')

diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index cc1c63d44..bd1caaf1b 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -12,12 +12,11 @@ import { RouteStore } from "../../../server/RouteStore";
 import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
 import { DateField } from "../../../new_fields/DateField";
 import { SelectionManager } from "../../util/SelectionManager";
-import { Doc, DocListCast, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
 import { ContextMenuProps } from "../ContextMenuItem";
 import { ContextMenu } from "../ContextMenu";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { DocumentManager } from "../../util/DocumentManager";
 import { DocumentView } from "./DocumentView";
 
 interface Window {
@@ -59,7 +58,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                 scrollLinkId && DocListCast(this.dataDoc.links).filter(l => l[Id] === scrollLinkId).map(l => {
                     let la1 = l.anchor1 as Doc;
                     let linkTime = Doc.AreProtosEqual(la1, this.dataDoc) ? NumCast(l.anchor1Timecode) : NumCast(l.anchor2Timecode);
-                    setTimeout(() => this.playFrom(linkTime), 250);
+                    setTimeout(() => { this.playFrom(linkTime); Doc.linkFollowHighlight(l); }, 250);
                 });
                 scrollLinkId && Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
             }, { fireImmediately: true });
@@ -75,11 +74,9 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
     }
 
     timecodeChanged = () => {
-        const extensionDoc = this.extensionDoc;
         const htmlEle = this._ele;
-        const start = extensionDoc && DateCast(extensionDoc.recordingStart);
-        if (start && htmlEle) {
-            htmlEle && htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration);
+        if (this._audioState === "recorded" && htmlEle) {
+            htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc.duration = htmlEle.duration);
             DocListCast(this.dataDoc.links).map(l => {
                 let la1 = l.anchor1 as Doc;
                 let linkTime = NumCast(l.anchor2Timecode);
@@ -195,7 +192,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
     }
 
     setRef = (e: HTMLAudioElement | null) => {
-        e && e.addEventListener("timeupdate", action(() => this.Document.currentTimecode = e!.currentTime));
+        e && e.addEventListener("timeupdate", this.timecodeChanged);
         this._ele = e;
     }
 
@@ -225,13 +222,13 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                     </button> :
                     <div className="audiobox-controls">
                         <div className="audiobox-player" onClick={this.onPlay}>
-                            <FontAwesomeIcon className="audiobox-playhead" icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
-                            <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon className="audiobox-playhead" icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
+                            <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this._playing ? "pause" : "play"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
+                            <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%" }} icon="stop" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
                             <div className="audiobox-timeline" onClick={e => e.stopPropagation()}
                                 onPointerDown={e => {
                                     if (e.button === 0 && !e.ctrlKey) {
                                         let rect = (e.target as any).getBoundingClientRect();
-                                        this._ele!.currentTime = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
+                                        this._ele!.currentTime = this.Document.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
                                         this.pause();
                                         e.stopPropagation();
                                     }
-- 
cgit v1.2.3-70-g09d2


From cf9fff0cf86e120cd4d26169ce8455bd8b438b24 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 24 Oct 2019 14:01:35 -0400
Subject: minor fixes including fix to focus() to restore animation panning

---
 src/client/views/collections/CollectionSubView.tsx        |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx         | 15 +++++++--------
 src/client/views/nodes/AudioBox.tsx                       |  2 +-
 src/client/views/nodes/DocuLinkBox.tsx                    |  3 ++-
 src/client/views/nodes/DocumentView.tsx                   |  4 ++--
 5 files changed, 13 insertions(+), 13 deletions(-)

(limited to 'src/client/views/nodes/AudioBox.tsx')

diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 55365de8c..d7e9494a3 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -74,7 +74,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this._childLayoutDisposer && this._childLayoutDisposer();
         }
 
-        @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc!) : Doc.GetProto(this.props.Document); }
+        @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); }
         @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); }
 
         // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc.
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index f6518a01d..6e0f75bc1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -354,14 +354,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @action
-    setPan(panX: number, panY: number) {
-        if (!this.props.Document.lockedPosition || this.props.Document.inOverlay) {
-            this.props.Document.panTransformType = "None";
+    setPan(panX: number, panY: number, panType: string = "None") {
+        if (!this.Document.lockedPosition || this.Document.inOverlay) {
+            this.Document.panTransformType = panType;
             var scale = this.getLocalTransform().inverse().Scale;
             const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
-            const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.props.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
-            this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
-            this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
+            const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : (1 - 1 / scale) * this.nativeHeight), Math.max(0, panY));
+            this.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
+            this.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
         }
     }
 
@@ -415,8 +415,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
             let savedState = { px: this.Document.panX, py: this.Document.panY, s: this.Document.scale, pt: this.Document.panTransformType };
 
-            this.setPan(newPanX, newPanY);
-            this.Document.panTransformType = "Ease";
+            this.setPan(newPanX, newPanY, "Ease");
             Doc.BrushDoc(this.props.Document);
             this.props.focus(this.props.Document);
             willZoom && this.setScaleToZoom(layoutdoc, scale);
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index bd1caaf1b..268255b46 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -95,7 +95,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
     pause = action(() => {
         this._ele!.pause();
         this._playing = false;
-    })
+    });
 
     playFrom = (seekTimeInSeconds: number) => {
         if (this._ele) {
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index 8f68effe8..f4c7d43aa 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -36,7 +36,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
         (e.button === 0 && !e.ctrlKey) && e.stopPropagation();
     }
     onPointerMove = action((e: PointerEvent) => {
-        let cdiv = this._ref.current!.parentElement;
+        let cdiv = this._ref && this._ref.current && this._ref.current.parentElement;
         if (cdiv && (Math.abs(e.clientX - this._downx) > 5 || Math.abs(e.clientY - this._downy) > 5)) {
             let bounds = cdiv.getBoundingClientRect();
             let pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY);
@@ -65,6 +65,7 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
         }
         e.stopPropagation();
     }
+
     render() {
         let anchorDoc = Cast(this.props.Document[this.props.fieldKey], Doc);
         let hasAnchor = anchorDoc instanceof Doc && anchorDoc.type === DocumentType.PDFANNO;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ed93aa83e..11066145b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -667,9 +667,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
                 onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)}
             >
-                {this.Document.links && DocListCast(this.Document.links).filter(this.isNonTemporalLink).map((d, i) =>
+                {this.Document.links && DocListCast(this.Document.links).filter((d) => !DocListCast(this.layoutDoc.hiddenLinks).some(hidden => Doc.AreProtosEqual(hidden, d))).filter(this.isNonTemporalLink).map((d, i) =>
                     <div style={{ pointerEvents: "none", position: "absolute", transformOrigin: "top left", width: "100%", height: "100%", transform: `scale(${this.layoutDoc.fitWidth ? 1 : 1 / this.props.ContentScaling()})` }}>
-                        <DocumentView {...this.props} backgroundColor={returnTransparent} Document={d} layoutKey={this.linkEndpoint(d)} />
+                        <DocumentView {...this.props} backgroundColor={returnTransparent} Document={d} removeDocument={undoBatch((doc: Doc) => Doc.AddDocToList(this.layoutDoc, "hiddenLinks", doc))} layoutKey={this.linkEndpoint(d)} />
                     </div>)}
                 {!showTitle && !showCaption ?
                     this.Document.searchFields ?
-- 
cgit v1.2.3-70-g09d2


From cb7078190a06ea19cb443626b9023b93c1930e3a Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 4 Nov 2019 11:35:50 -0500
Subject: getting back in the saddle: small changes -audio play icons, link
 titling, dictation.

---
 src/client/views/nodes/AudioBox.tsx         | 1 +
 src/client/views/nodes/DocuLinkBox.tsx      | 5 ++++-
 src/client/views/nodes/FormattedTextBox.tsx | 2 +-
 3 files changed, 6 insertions(+), 2 deletions(-)

(limited to 'src/client/views/nodes/AudioBox.tsx')

diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 268255b46..d652a11d5 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -193,6 +193,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
 
     setRef = (e: HTMLAudioElement | null) => {
         e && e.addEventListener("timeupdate", this.timecodeChanged);
+        e && e.addEventListener("ended", this.pause);
         this._ele = e;
     }
 
diff --git a/src/client/views/nodes/DocuLinkBox.tsx b/src/client/views/nodes/DocuLinkBox.tsx
index f4c7d43aa..d73407903 100644
--- a/src/client/views/nodes/DocuLinkBox.tsx
+++ b/src/client/views/nodes/DocuLinkBox.tsx
@@ -72,7 +72,10 @@ export class DocuLinkBox extends DocComponent<FieldViewProps, DocLinkSchema>(Doc
         let y = NumCast(this.props.Document[this.props.fieldKey + "_y"], 100);
         let x = NumCast(this.props.Document[this.props.fieldKey + "_x"], 100);
         let c = StrCast(this.props.Document.backgroundColor, "lightblue");
-        return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={StrCast((this.props.Document[this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1"]! as Doc).title)}
+        let anchor = this.props.fieldKey === "anchor1" ? "anchor2" : "anchor1";
+        let timecode = this.props.Document[anchor + "Timecode"];
+        let targetTitle = StrCast((this.props.Document[anchor]! as Doc).title) + (timecode !== undefined ? ":" + timecode : "");
+        return <div className="docuLinkBox-cont" onPointerDown={this.onPointerDown} onClick={this.onClick} title={targetTitle}
             ref={this._ref} style={{
                 background: c, left: `calc(${x}% - 12.5px)`, top: `calc(${y}% - 12.5px)`,
                 transform: `scale(${hasAnchor ? 0.333 : 1 / this.props.ContentScaling()})`
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index cf55a10ba..037c92a62 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1059,7 +1059,7 @@ export class FormattedTextBox extends DocExtendableComponent<(FieldViewProps & F
                         e.stopPropagation();
                     }} >
                     <FontAwesomeIcon className="formattedTExtBox-audioFont"
-                        style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.2 }} icon={"file-audio"} size="sm" />
+                        style={{ color: this._recording ? "red" : "blue", opacity: this._recording ? 1 : 0.2 }} icon={"microphone"} size="sm" />
                 </div>
             </div>
         );
-- 
cgit v1.2.3-70-g09d2


From 70a6f10779729cff4a1b46d56dd5ff84f41ed14e Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Mon, 4 Nov 2019 13:08:47 -0500
Subject: cleaned up some exceptions, runtime warnings, fixed borderRounding,
 and adding to Library->Documents

---
 src/client/views/MainView.tsx                      |  2 ++
 .../views/collections/CollectionStackingView.tsx   |  1 -
 .../views/collections/CollectionTreeView.tsx       |  6 +++---
 src/client/views/nodes/AudioBox.tsx                | 24 ++++++++++++----------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  8 +-------
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/ImageBox.tsx                |  2 +-
 7 files changed, 21 insertions(+), 24 deletions(-)

(limited to 'src/client/views/nodes/AudioBox.tsx')

diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index cc5c5bf2b..383300b22 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -38,6 +38,7 @@ import PDFMenu from './pdf/PDFMenu';
 import { PreviewCursor } from './PreviewCursor';
 import { Scripting } from '../util/Scripting';
 import { LinkManager } from '../util/LinkManager';
+import { AudioBox } from './nodes/AudioBox';
 
 @observer
 export class MainView extends React.Component {
@@ -136,6 +137,7 @@ export class MainView extends React.Component {
 
     globalPointerDown = action((e: PointerEvent) => {
         this.isPointerDown = true;
+        AudioBox.AudioEnabled = true;
         const targets = document.elementsFromPoint(e.x, e.y);
         if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) {
             ContextMenu.Instance.closeMenu();
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 7f0165ad4..37d897088 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -393,7 +393,6 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             let entries = Array.from(this.Sections.entries());
             sections = entries.sort(this.sortFunc);
         }
-        console.log("NUM = " + this.numGroupColumns);
         return (
             <div className="collectionStackingMasonry-cont" >
                 <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 47c355fc8..89cc69ccf 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -221,10 +221,10 @@ class TreeView extends React.Component<TreeViewProps> {
             }
             let movedDocs = (de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments);
             return (de.data.dropAction || de.data.userDropAction) ?
-                de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, undefined, before) || added, false)
+                de.data.droppedDocuments.reduce((added, d) => addDoc(d) || added, false)
                 : de.data.moveDocument ?
                     movedDocs.reduce((added, d) => de.data.moveDocument(d, undefined, addDoc) || added, false)
-                    : de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, undefined, before), false);
+                    : de.data.droppedDocuments.reduce((added, d) => addDoc(d), false);
         }
         return false;
     }
@@ -474,7 +474,7 @@ class TreeView extends React.Component<TreeViewProps> {
                 let aspect = NumCast(childLayout.nativeWidth, 0) / NumCast(childLayout.nativeHeight, 0);
                 return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]();
             };
-            return <TreeView
+            return !(child instanceof Doc) ? (null) : <TreeView
                 document={pair.layout}
                 dataDoc={pair.data}
                 containingCollection={containingCollection}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index d652a11d5..86bd23b67 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -37,6 +37,7 @@ const AudioDocument = makeInterface(documentSchema, audioSchema);
 @observer
 export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
+    public static Enabled = false;
 
     _linkPlayDisposer: IReactionDisposer | undefined;
     _reactionDisposer: IReactionDisposer | undefined;
@@ -98,7 +99,7 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
     });
 
     playFrom = (seekTimeInSeconds: number) => {
-        if (this._ele) {
+        if (this._ele && AudioBox.Enabled) {
             if (seekTimeInSeconds < 0) {
                 this.pause();
             } else if (seekTimeInSeconds <= this._ele.duration) {
@@ -243,16 +244,17 @@ export class AudioBox extends DocExtendableComponent<FieldViewProps, AudioDocume
                                         la2 = l.anchor1 as Doc;
                                         linkTime = NumCast(l.anchor1Timecode);
                                     }
-                                    return !linkTime ? (null) : <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
-                                        <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
-                                            <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)}
-                                                parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne}
-                                                backgroundColor={returnTransparent} />
-                                        </div>
-                                        <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
-                                            onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }}
-                                            onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} />
-                                    </div>;
+                                    return !linkTime ? (null) :
+                                        <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
+                                            <div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
+                                                <DocumentView {...this.props} Document={l} layoutKey={Doc.LinkEndpoint(l, la2)}
+                                                    parentActive={returnTrue} bringToFront={emptyFunction} zoomToScale={emptyFunction} getScale={returnOne}
+                                                    backgroundColor={returnTransparent} />
+                                            </div>
+                                            <div key={i} className="audiobox-marker" onPointerEnter={() => Doc.linkFollowHighlight(la1)}
+                                                onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { this.playFrom(linkTime); e.stopPropagation(); } }}
+                                                onClick={e => { if (e.button === 0 && !e.ctrlKey) { this.pause(); e.stopPropagation(); } }} />
+                                        </div>;
                                 })}
                                 <div className="audiobox-current" style={{ left: `${NumCast(this.Document.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
                                 {this.audio}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 58cb831f8..581e5b736 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -4,7 +4,6 @@ import { observer } from "mobx-react";
 import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { percent2frac } from "../../../Utils";
 import { Transform } from "../../util/Transform";
 import { DocComponent } from "../DocComponent";
 import "./CollectionFreeFormDocumentView.scss";
@@ -71,12 +70,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined;
         let ld = this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] instanceof Doc ? this.layoutDoc[StrCast(this.layoutDoc.layoutKey, "layout")] as Doc : undefined;
         let br = StrCast((ld || this.props.Document).borderRounding);
-        br = !br && ruleRounding ? ruleRounding : br;
-        if (br.endsWith("%")) {
-            let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight));
-            return percent2frac(br) * (nativeDim ? nativeDim : Math.min(this.props.PanelWidth(), this.props.PanelHeight()));
-        }
-        return undefined;
+        return !br && ruleRounding ? ruleRounding : br;
     }
 
     @computed
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 11066145b..f593a5363 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -668,7 +668,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                 onPointerEnter={() => Doc.BrushDoc(this.props.Document)} onPointerLeave={() => Doc.UnBrushDoc(this.props.Document)}
             >
                 {this.Document.links && DocListCast(this.Document.links).filter((d) => !DocListCast(this.layoutDoc.hiddenLinks).some(hidden => Doc.AreProtosEqual(hidden, d))).filter(this.isNonTemporalLink).map((d, i) =>
-                    <div style={{ pointerEvents: "none", position: "absolute", transformOrigin: "top left", width: "100%", height: "100%", transform: `scale(${this.layoutDoc.fitWidth ? 1 : 1 / this.props.ContentScaling()})` }}>
+                    <div key={`${d[Id]}`} style={{ pointerEvents: "none", position: "absolute", transformOrigin: "top left", width: "100%", height: "100%", transform: `scale(${this.layoutDoc.fitWidth ? 1 : 1 / this.props.ContentScaling()})` }}>
                         <DocumentView {...this.props} backgroundColor={returnTransparent} Document={d} removeDocument={undoBatch((doc: Doc) => Doc.AddDocToList(this.layoutDoc, "hiddenLinks", doc))} layoutKey={this.linkEndpoint(d)} />
                     </div>)}
                 {!showTitle && !showCaption ?
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 188440292..212c99f9d 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -323,7 +323,7 @@ export class ImageBox extends DocAnnotatableComponent<FieldViewProps, ImageDocum
                     style={{ height: `calc(${.1 * nativeHeight / nativeWidth * 100}%)` }}
                 >
                     <FontAwesomeIcon className="imageBox-audioFont"
-                        style={{ color: [DocListCast(extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={faFileAudio} size="sm" />
+                        style={{ color: [DocListCast(extensionDoc.audioAnnotations).length ? "blue" : "gray", "green", "red"][this._audioState] }} icon={!DocListCast(extensionDoc.audioAnnotations).length ? "microphone" : faFileAudio} size="sm" />
                 </div>
                 {this.considerGooglePhotosLink()}
                 <FaceRectangles document={extensionDoc} color={"#0000FF"} backgroundColor={"#0000FF"} />
-- 
cgit v1.2.3-70-g09d2

