From 188e1e57860f58e9ebe3536a0e1f7cd84ea0db80 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 18 Mar 2021 00:08:13 -0400
Subject: cleaned up link making.  Documents don't add to the Undo stack when
 being created and Initializing is set.  Links to text regions automatically
 update their link line endpoints even if autoMove isn't set. regularized
 initialization fields to avoid special cases about setting delegate keys
 without a leading "_"

---
 src/client/views/nodes/DocumentView.tsx | 16 ++++++++--------
 1 file changed, 8 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a1bedcf6e..a9372aa64 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -558,9 +558,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @undoBatch @action
     toggleFollowLink = (location: Opt<string>, zoom: boolean, setPushpin: boolean): void => {
         this.Document.ignoreClick = false;
-        this.Document.isLinkButton = !this.Document.isLinkButton;
-        setPushpin && (this.Document.isPushpin = this.Document.isLinkButton);
-        if (this.Document.isLinkButton && !this.onClickHandler) {
+        this.Document._isLinkButton = !this.Document._isLinkButton;
+        setPushpin && (this.Document.isPushpin = this.Document._isLinkButton);
+        if (this.Document._isLinkButton && !this.onClickHandler) {
             this.Document.followLinkZoom = zoom;
             this.Document.followLinkLocation = location;
         } else {
@@ -570,13 +570,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @undoBatch @action
     toggleTargetOnClick = (): void => {
         this.Document.ignoreClick = false;
-        this.Document.isLinkButton = true;
+        this.Document._isLinkButton = true;
         this.Document.isPushpin = true;
     }
     @undoBatch @action
     followLinkOnClick = (location: Opt<string>, zoom: boolean,): void => {
         this.Document.ignoreClick = false;
-        this.Document.isLinkButton = true;
+        this.Document._isLinkButton = true;
         this.Document.isPushpin = false;
         this.Document.followLinkZoom = zoom;
         this.Document.followLinkLocation = location;
@@ -584,14 +584,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     @undoBatch @action
     selectOnClick = (): void => {
         this.Document.ignoreClick = false;
-        this.Document.isLinkButton = false;
+        this.Document._isLinkButton = false;
         this.Document.isPushpin = false;
         this.Document.onClick = this.layoutDoc.onClick = undefined;
     }
     @undoBatch
     noOnClick = (): void => {
         this.Document.ignoreClick = false;
-        this.Document.isLinkButton = false;
+        this.Document._isLinkButton = false;
     }
 
     @undoBatch deleteClicked = () => this.props.removeDocument?.(this.props.Document);
@@ -630,7 +630,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         }
         this.Document.followLinkLocation = "inPlace";
         this.Document.followLinkZoom = true;
-        this.Document.isLinkButton = true;
+        this.Document._isLinkButton = true;
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From ed83093961b7e19df4675c9e2cf0a78eae333614 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 18 Mar 2021 11:31:20 -0400
Subject: made linkanchorbuttons not show up next to pdf annos. gave pdfannos a
 baseProto.   made textboxes dynamically remove hashtags when deleted from
 text.

---
 src/client/documents/Documents.ts                  |  7 +++++++
 src/client/views/DocumentButtonBar.tsx             | 22 +++++++++++++++++-----
 src/client/views/MarqueeAnnotator.tsx              |  2 +-
 src/client/views/StyleProvider.tsx                 |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  4 ++--
 .../views/nodes/formattedText/FormattedTextBox.tsx | 12 ++++++++++++
 6 files changed, 40 insertions(+), 9 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 332f7304b..a2850fd98 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -420,6 +420,10 @@ export namespace Docs {
             [DocumentType.PRESELEMENT, {
                 layout: { view: PresElementBox, dataField: defaultDataKey }
             }],
+            [DocumentType.PDFANNO, {
+                layout: { view: CollectionView, dataField: defaultDataKey },
+                options: { hideLinkButton: true }
+            }],
             [DocumentType.INK, {
                 layout: { view: InkingStroke, dataField: defaultDataKey },
                 options: { _fontFamily: "cursive", backgroundColor: "transparent", links: ComputedField.MakeFunction("links(self)") as any }
@@ -801,6 +805,9 @@ export namespace Docs {
             documents.map(d => d.context = inst);
             return inst;
         }
+        export function PdfAnnoDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
+            return InstanceFromProto(Prototypes.get(DocumentType.PDFANNO), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id);
+        }
 
         export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
             return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 4a1ec4d6c..07b419e5f 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -184,6 +184,18 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             </div></Tooltip>;
     }
     @computed
+    get followLinkButton() {
+        const targetDoc = this.view0?.props.Document;
+        return !targetDoc ? (null) : <Tooltip title={
+            <div className="dash-tooltip">{"follow primary link on click"}</div>}>
+            <div className="documentButtonBar-linker"
+                style={{ color: targetDoc.isLinkButton ? "black" : "white", backgroundColor: targetDoc.isLinkButton ? "white" : "black" }}
+                onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false, false)))}>
+                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="hand-point-right" />
+            </div>
+        </Tooltip>;
+    }
+    @computed
     get pinButton() {
         const targetDoc = this.view0?.props.Document;
         return !targetDoc ? (null) : <Tooltip title={
@@ -231,9 +243,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 17, transform: 'translate(0, 1px)' }} />;
         const targetDoc = this.view0?.props.Document;
         return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin with current view"}</div></>}>
-            <div
-                className="documentButtonBar-linker"
-                onClick={() => this.pinWithView(targetDoc)}>
+            <div className="documentButtonBar-linker" onClick={() => this.pinWithView(targetDoc)}>
                 {presPinWithViewIcon}
             </div>
         </Tooltip>;
@@ -244,8 +254,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const targetDoc = this.view0?.props.Document;
         return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Open Sharing Manager"}</div></>}>
             <div className="documentButtonBar-linker" style={{ color: "white" }} onClick={e => SharingManager.Instance.open(this.view0, targetDoc)}>
-                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="users"
-                />
+                <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="users" />
             </div></Tooltip >;
     }
 
@@ -357,6 +366,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             <div className="documentButtonBar-button">
                 {this.contextButton}
             </div> */}
+            {!SelectionManager.Views()?.some(v => v.allLinks.length) ? (null) : <div className="documentButtonBar-button">
+                {this.followLinkButton}
+            </div>}
             <div className="documentButtonBar-button">
                 {this.pinButton}
             </div>
diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index 2e50c9b6d..046f327fc 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -114,7 +114,7 @@ export class MarqueeAnnotator extends React.Component<MarqueeAnnotatorProps> {
             return marqueeAnno;
         }
 
-        const textRegionAnno = Docs.Create.FreeformDocument([], { type: DocumentType.PDFANNO, annotationOn: this.props.rootDoc, title: "Selection on " + this.props.rootDoc.title, _width: 1, _height: 1 });
+        const textRegionAnno = Docs.Create.PdfAnnoDocument([], { annotationOn: this.props.rootDoc, title: "Selection on " + this.props.rootDoc.title, _width: 1, _height: 1 });
         let maxX = -Number.MAX_VALUE;
         let minY = Number.MAX_VALUE;
         const annoDocs: Doc[] = [];
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index d9ab0d397..5cbbcce79 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -74,7 +74,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps |
         case StyleProp.DocContents: return undefined;
         case StyleProp.WidgetColor: return isAnnotated ? "lightBlue" : darkScheme() ? "lightgrey" : "dimgrey";
         case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null));
-        case StyleProp.HideLinkButton: return isAnchor || props?.dontRegisterView || ((!selected || doc?.type === DocumentType.PDFANNO) && (doc?.isLinkButton || doc?.hideLinkButton));
+        case StyleProp.HideLinkButton: return props?.dontRegisterView || doc?.type === DocumentType.PDFANNO || (!selected && (doc?.isLinkButton || doc?.hideLinkButton));
         case StyleProp.ShowTitle: return doc && !doc.presentationTargetDoc && StrCast(doc._showTitle,
             !Doc.IsSystem(doc) && doc.type === DocumentType.RTF ?
                 (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") : "") || "";
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a9372aa64..0e759c780 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -791,7 +791,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 onClick={this.onClickFunc}
                 focus={this.focus}
                 layoutKey={this.finalLayoutKey} />
-            {this.layoutDoc.hideAllLinks ? (null) : this.allAnchors}
+            {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
             {this.hideLinkButton ? (null) :
                 <DocumentLinksButton View={this.props.DocumentView()} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
 
@@ -808,7 +808,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
     @computed get directLinks() { TraceMobx(); return LinkManager.Instance.getAllDirectLinks(this.rootDoc); }
     @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); }
-    @computed get allAnchors() {
+    @computed get allLinkEndpoints() {  // the small blue dots that mark the endpoints of links
         TraceMobx();
         if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
         if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index d44867541..af9aeea00 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -250,6 +250,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\"");
 
             if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) {
+                const accumTags = [] as string[]
+                state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => {
+                    if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) {
+                        accumTags.push(node.attrs.fieldKey)
+                    }
+                });
+                const curTags = Object.keys(this.dataDoc).filter(key => key.startsWith("#"));
+                const added = accumTags.filter(tag => !curTags.includes(tag));
+                const removed = curTags.filter(tag => !accumTags.includes(tag));
+                removed.forEach(r => this.dataDoc[r] = undefined);
+                added.forEach(a => this.dataDoc[a] = a);
+
                 let unchanged = true;
                 if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) {
                     this._applyingChange = this.fieldKey;
-- 
cgit v1.2.3-70-g09d2


From e8ef90478cec13d45de0fe42e32a594e2630c47d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 19 Mar 2021 10:35:57 -0400
Subject: fixing up some baseProto initialization.  fixing stacking view with
 hidden docs.

---
 src/client/documents/Documents.ts                  | 56 +++++++++++-----------
 src/client/util/CurrentUserUtils.ts                | 28 ++++++-----
 .../util/Import & Export/DirectoryImportBox.tsx    |  1 +
 src/client/views/PropertiesButtons.tsx             |  4 +-
 src/client/views/TemplateMenu.tsx                  |  2 +-
 .../views/collections/CollectionPileView.tsx       |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 src/client/views/nodes/DocumentView.tsx            |  2 +-
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 10 files changed, 54 insertions(+), 47 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 91369d325..f4657d824 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -338,11 +338,11 @@ export namespace Docs {
             }],
             [DocumentType.COL, {
                 layout: { view: CollectionView, dataField: defaultDataKey },
-                options: { _panX: 0, _panY: 0, _viewScale: 1, links: ComputedField.MakeFunction("links(self)") as any }
+                options: { _chromeStatus: "collapsed", _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.KVP, {
                 layout: { view: KeyValueBox, dataField: defaultDataKey },
-                options: { _height: 150 }
+                options: { _fitWidth: true, _height: 150 }
             }],
             [DocumentType.VID, {
                 layout: { view: VideoBox, dataField: defaultDataKey },
@@ -363,7 +363,11 @@ export namespace Docs {
             [DocumentType.LINK, {
                 layout: { view: LinkBox, dataField: defaultDataKey },
                 options: {
-                    _height: 150, description: "", links: ComputedField.MakeFunction("links(self)") as any,
+                    childDontRegisterViews: true, _isLinkButton: true, treeViewHideTitle: true,
+                    treeViewOpen: true, _height: 150, description: "",
+                    backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area
+                    treeViewExpandedView: "fields", _removeDropProperties: new List(["_layerTags", "isLinkButton"]),
+                    links: ComputedField.MakeFunction("links(self)") as any,
                     linkBoxExcludedKeys: new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "_removeDropProperties",
                         "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"])
                 }
@@ -434,7 +438,7 @@ export namespace Docs {
             }],
             [DocumentType.COMPARISON, {
                 layout: { view: ComparisonBox, dataField: defaultDataKey },
-                options: { links: ComputedField.MakeFunction("links(self)") as any }
+                options: { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.GROUPDB, {
                 data: new List<Doc>(),
@@ -447,7 +451,7 @@ export namespace Docs {
             }],
             [DocumentType.TEXTANCHOR, {
                 layout: { view: EmptyBox, dataField: defaultDataKey },
-                options: { links: ComputedField.MakeFunction("links(self)") as any, useSmallLinkButton: true, hideLinkButton: true }
+                options: { targetDropAction: "move", links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true }
             }]
         ]);
 
@@ -702,12 +706,12 @@ export namespace Docs {
         }
 
         export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", ...options });
+            return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", options);
         }
 
         export function AudioDocument(url: string, options: DocumentOptions = {}) {
             return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)),
-                { useLinkSmallAnchor: true, ...options, backgroundColor: ComputedField.MakeFunction("this._audioState === 'playing' ? 'green':'gray'") as any }); // hideLinkButton: false, useLinkSmallAnchor: false,
+                { ...options, backgroundColor: ComputedField.MakeFunction("this._audioState === 'playing' ? 'green':'gray'") as any });
         }
 
         export function SearchDocument(options: DocumentOptions = {}) {
@@ -741,9 +745,7 @@ export namespace Docs {
 
         export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) {
             const linkDoc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, {
-                childDontRegisterViews: true, treeViewOpen: true, anchor1: source.doc, anchor2: target.doc,
-                _isLinkButton: true, treeViewHideTitle: true, backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area
-                treeViewExpandedView: "fields", _removeDropProperties: new List(["_layerTags", "isLinkButton"]), ...options
+                anchor1: source.doc, anchor2: target.doc, ...options
             }, id);
 
             LinkManager.Instance.addLink(linkDoc);
@@ -781,11 +783,11 @@ export namespace Docs {
         }
 
         export function PdfDocument(url: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(new URL(url)), { ...options });
+            return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(new URL(url)), options);
         }
 
         export function WebDocument(url: string, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(new URL(url)) : undefined, { _chromeStatus: url ? undefined : "enabled", _lockedTransform: true, ...options });
+            return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(new URL(url)) : undefined, options);
         }
 
         export function HtmlDocument(html: string, options: DocumentOptions = {}) {
@@ -793,63 +795,63 @@ export namespace Docs {
         }
 
         export function KVPDocument(document: Doc, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { _fitWidth: true, title: document.title + ".kvp", ...options });
+            return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options });
         }
 
         export function TextanchorDocument(options: DocumentOptions = {}, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.TEXTANCHOR), undefined, { targetDropAction: "move", ...options }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.TEXTANCHOR), undefined, options, id);
         }
 
         export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id);
+            const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Freeform }, id);
             documents.map(d => d.context = inst);
             return inst;
         }
         export function HTMLAnchorDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.HTMLANCHOR), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.HTMLANCHOR), new List(documents), options, id);
         }
 
         export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id);
         }
 
         export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", ...options, _viewType: CollectionViewType.Linear }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Linear }, id);
         }
 
         export function MapDocument(documents: Array<Doc>, options: DocumentOptions = {}) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), options);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Map });
         }
 
         export function CarouselDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, ...options, _viewType: CollectionViewType.Carousel });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Carousel });
         }
 
         export function Carousel3DDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, ...options, _viewType: CollectionViewType.Carousel3D });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Carousel3D });
         }
 
         export function SchemaDocument(schemaHeaders: SchemaHeaderField[], documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, schemaHeaders: schemaHeaders.length ? new List(schemaHeaders) : undefined, ...options, _viewType: CollectionViewType.Schema });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { schemaHeaders: new List(schemaHeaders), ...options, _viewType: CollectionViewType.Schema });
         }
 
         export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, childDontRegisterViews: true, ...options, _viewType: CollectionViewType.Tree }, id);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { childDontRegisterViews: true, ...options, _viewType: CollectionViewType.Tree }, id);
         }
 
         export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", _fitWidth: true, ...options, _viewType: CollectionViewType.Stacking }, id, undefined, protoId);
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Stacking }, id, undefined, protoId);
         }
 
         export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Multicolumn });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Multicolumn });
         }
         export function MultirowDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Multirow });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Multirow });
         }
 
         export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Masonry });
+            return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Masonry });
         }
 
         export function LabelDocument(options?: DocumentOptions) {
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index d7ab3564c..fc70a6282 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -203,7 +203,7 @@ export class CurrentUserUtils {
                 new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true),
                 new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true),
             ]);
-            const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts, system: true });
+            const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts, _chromeStatus: "", system: true });
             detailView.isTemplateDoc = makeTemplate(detailView);
 
             details.title = "Details";
@@ -225,7 +225,7 @@ export class CurrentUserUtils {
         ];
         if (doc["template-buttons"] === undefined) {
             doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, {
-                title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title",
+                title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeStatus: "",
                 hidden: ComputedField.MakeFunction("IsNoviceMode()") as any,
                 _stayInCollection: true, _hideContextMenu: true,
                 _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
@@ -492,13 +492,13 @@ export class CurrentUserUtils {
             _stayInCollection: true,
             dragFactory,
             clickFactory,
-            hidden: noviceMode ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined,
+            hidden: !noviceMode ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined,
             system: true,
         }));
 
         if (dragCreatorSet === undefined) {
             doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
-                title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true,
+                title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeStatus: "",
                 _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true
             }));
@@ -559,6 +559,7 @@ export class CurrentUserUtils {
             doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, {
                 title: "menuItemPanel",
                 childDropAction: "alias",
+                _chromeStatus: "",
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
                 backgroundColor: "black", ignoreClick: true,
                 _gridGap: 0,
@@ -596,7 +597,7 @@ export class CurrentUserUtils {
     // Sets up mobileMenu stacking document
     static setupMobileMenu() {
         const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), {
-            _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true
+            _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeStatus: ""
         }));
         return menu;
     }
@@ -693,10 +694,10 @@ export class CurrentUserUtils {
             title: "Upload Images From the Web", _chromeStatus: "enabled", _lockedPosition: true, system: true
         });
         const uploadDoc = Docs.Create.StackingDocument([], {
-            title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true
+            title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true, _chromeStatus: ""
         });
         return Docs.Create.StackingDocument([webDoc, uploadDoc], {
-            _width: screen.width, _lockedPosition: true, title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true
+            _width: screen.width, _lockedPosition: true, title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true, _chromeStatus: ""
         });
     }
 
@@ -716,7 +717,8 @@ export class CurrentUserUtils {
         if (doc.myCreators === undefined) {
             doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], {
                 title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true,
-                _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true
+                _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true,
+                _chromeStatus: ""
             }));
         }
         // setup a color picker
@@ -729,7 +731,8 @@ export class CurrentUserUtils {
 
         if (doc.myTools === undefined) {
             const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], {
-                title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, system: true, _stayInCollection: true, _hideContextMenu: true,
+                title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true,
+                system: true, _stayInCollection: true, _hideContextMenu: true, _chromeStatus: ""
             })) as any as Doc;
 
             doc.myTools = toolsStack;
@@ -907,7 +910,8 @@ export class CurrentUserUtils {
             if (!sharedDocs) {
                 sharedDocs = Docs.Create.StackingDocument([], {
                     title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
-                    _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add
+                    _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add,
+                    _chromeStatus: ""
                 }, sharingDocumentId + "outer", sharingDocumentId);
             }
             if (sharedDocs instanceof Doc) {
@@ -922,13 +926,13 @@ export class CurrentUserUtils {
         if (doc.myImportDocs === undefined) {
             doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
                 title: "My ImportDocuments", _forceActive: true, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
-                childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, system: true
+                childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, system: true, _chromeStatus: ""
             }));
         }
         if (doc.myImportPanel === undefined) {
             const uploads = Cast(doc.myImportDocs, Doc, null);
             const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true });
-            doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true }));
+            doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, _chromeStatus: "", _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true }));
         }
     }
 
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index d9f010557..244e96599 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -145,6 +145,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
             title: `Import of ${directory}`,
             _width: 1105,
             _height: 500,
+            _chromeStatus: "",
             x: NumCast(doc.x),
             y: NumCast(doc.y) + offset
         };
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 7a83295c7..9afcdc28d 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -32,7 +32,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
 
     propertyToggleBtn = (label: string, property: string, tooltip: (on?: any) => string, icon: (on: boolean) => string, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void) => {
         const targetDoc = this.selectedDoc;
-        const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => (dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? undefined : true;
+        const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => (dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true;
         return !targetDoc ? (null) :
             <Tooltip title={<div className={`dash-tooltip`}>{tooltip(targetDoc?.[property])} </div>} placement="top">
                 <div>
@@ -74,7 +74,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return this.propertyToggleBtn("Caption", "_showCaption", on => `${on ? "Hide" : "Show"} caption footer`, on => "closed-captioning", (dv, doc) => (dv?.rootDoc || doc)._showCaption = (dv?.rootDoc || doc)._showCaption === undefined ? "caption" : undefined);
     }
     @computed get chromeButton() {
-        return this.propertyToggleBtn("Controls", "_chromeStatus", on => `${on === "enabled" ? "Hide" : "Show"} editing UI`, on => "edit", (dv, doc) => (dv?.rootDoc || doc)._chromeStatus = (dv?.rootDoc || doc)._chromeStatus === undefined ? "enabled" : undefined);
+        return this.propertyToggleBtn("Controls", "_chromeStatus", on => `${on === "enabled" ? "Hide" : "Show"} editing UI`, on => "edit", (dv, doc) => (dv?.rootDoc || doc)._chromeStatus = !(dv?.rootDoc || doc)._chromeStatus ? "enabled" : "");
     }
     @computed get titleButton() {
         return this.propertyToggleBtn("Title", "_showTitle", on => "Switch between title styles", on => "text-width", (dv, doc) => (dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? "title" : (dv?.rootDoc || doc)._showTitle === "title" ? "title:hover" : undefined);
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 23d8baeaf..3a329c7d9 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -81,7 +81,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @action
     toggleChrome = (): void => {
         this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout =>
-            layout._chromeStatus = (layout._chromeStatus ? undefined : StrCast(layout._replacedChrome, "enabled")));
+            layout._chromeStatus = (layout._chromeStatus ? "" : StrCast(layout._replacedChrome, "enabled")));
     }
 
     // todo: add brushes to brushMap to save with a style name
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 22b5c2b2a..7998b3277 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -21,7 +21,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
             this.Document._pileLayoutEngine = "pass";
         }
         this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
-        this.layoutDoc._chromeStatus = undefined;
+        this.layoutDoc._chromeStatus = "";
     }
     componentWillUnmount() {
         this.layoutDoc._chromeStatus = this._originalChrome;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8128c18d7..2324c0eb4 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -74,7 +74,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
             const { Document, DataDoc } = this.props;
             const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc)).
                 filter(pair => {  // filter out any documents that have a proto that we don't have permissions to (which we determine by not having any keys
-                    return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));// Object.keys(pair.layout.proto).length));
+                    return pair.layout && !pair.layout.hidden && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate));// Object.keys(pair.layout.proto).length));
                 });
             return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types
         }
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index c82baf691..f0d99611a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1239,7 +1239,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
         if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
         (e as any).handlePan = true;
 
-        if (!this.props.Document._noAutoscroll && !this.props.renderDepth && this._marqueeRef?.current) {
+        if (!this.layoutDoc._noAutoscroll && !this.props.renderDepth && this._marqueeRef?.current) {
             const dragX = e.detail.clientX;
             const dragY = e.detail.clientY;
             const bounds = this._marqueeRef.current?.getBoundingClientRect();
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0e759c780..e15ade241 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -721,7 +721,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
             if (!Doc.UserDoc().noviceMode) {
                 moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
-                moreItems.push({ description: `${this.Document._chromeStatus ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus ? undefined : "enabled"), icon: "project-diagram" });
+                moreItems.push({ description: `${this.Document._chromeStatus ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus ? "" : "enabled"), icon: "project-diagram" });
 
                 if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
                     moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 383650e89..f27c2c6a7 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -163,7 +163,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     getTemplate = async () => {
-        const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template" });
+        const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template", _chromeStatus: "" });
         parent._columnWidth = 100;
         for (const row of this.rows.filter(row => row.isChecked)) {
             await this.createTemplateField(parent, row);
-- 
cgit v1.2.3-70-g09d2


From 3e2709a68e32650074e2bbc9af89fa0d66536d77 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 19 Mar 2021 13:54:45 -0400
Subject: simplified chromeStatus to chromeHidden.  fixed presBox item widths.
 fixed colorBox layout.

---
 src/client/documents/Documents.ts                  |  4 +-
 src/client/util/CurrentUserUtils.ts                | 32 +++++------
 .../util/Import & Export/DirectoryImportBox.tsx    |  2 +-
 src/client/views/MainView.tsx                      |  4 +-
 src/client/views/PropertiesButtons.tsx             |  5 +-
 src/client/views/SidebarAnnos.tsx                  |  1 -
 src/client/views/TemplateMenu.tsx                  |  7 +--
 .../views/collections/CollectionCarousel3DView.tsx |  2 +-
 .../views/collections/CollectionCarouselView.tsx   |  2 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |  9 ++-
 .../views/collections/CollectionPileView.tsx       | 10 ++--
 .../views/collections/CollectionStackingView.tsx   | 31 +++++------
 .../CollectionStackingViewFieldColumn.tsx          |  9 ++-
 .../views/collections/CollectionTimeView.tsx       |  2 +-
 src/client/views/collections/CollectionView.tsx    |  1 +
 src/client/views/collections/SchemaTable.tsx       |  3 +-
 src/client/views/nodes/ColorBox.scss               |  1 -
 src/client/views/nodes/ColorBox.tsx                | 65 ++++++++++------------
 src/client/views/nodes/DocumentView.tsx            | 16 +++---
 src/client/views/nodes/KeyValueBox.tsx             |  2 +-
 src/client/views/nodes/PresBox.tsx                 |  6 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  1 -
 src/fields/documentSchemas.ts                      |  2 -
 src/fields/util.ts                                 |  6 --
 24 files changed, 98 insertions(+), 125 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index f4657d824..366275b6e 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -119,7 +119,7 @@ export class DocumentOptions {
     _showCaption?: string; // which field to display in the caption area.  leave empty to have no caption
     _scrollTop?: number; // scroll location for pdfs
     _noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged
-    _chromeStatus?: string;
+    _chromeHidden?: boolean; // whether the editing chrome for a document is hidden
     _layerTags?: List<string>; // layer tags a document has (used for tab filtering "layers" in document tab)
     _searchDoc?: boolean; // is this a search document (used to change UI for search results in schema view)
     _forceActive?: boolean; // flag to handle pointer events when not selected (or otherwise active)
@@ -338,7 +338,7 @@ export namespace Docs {
             }],
             [DocumentType.COL, {
                 layout: { view: CollectionView, dataField: defaultDataKey },
-                options: { _chromeStatus: "collapsed", _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: ComputedField.MakeFunction("links(self)") as any }
+                options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: ComputedField.MakeFunction("links(self)") as any }
             }],
             [DocumentType.KVP, {
                 layout: { view: KeyValueBox, dataField: defaultDataKey },
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index fc70a6282..05e560f51 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -203,7 +203,7 @@ export class CurrentUserUtils {
                 new SchemaHeaderField("[Long Description]", "dimGray", undefined, undefined, undefined, true),
                 new SchemaHeaderField("[Details]", "dimGray", undefined, undefined, undefined, true),
             ]);
-            const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts, _chromeStatus: "", system: true });
+            const detailView = Docs.Create.StackingDocument([carousel, descriptionWrapper], { ...shared, ...detailViewOpts, _chromeHidden: true, system: true });
             detailView.isTemplateDoc = makeTemplate(detailView);
 
             details.title = "Details";
@@ -225,7 +225,7 @@ export class CurrentUserUtils {
         ];
         if (doc["template-buttons"] === undefined) {
             doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, {
-                title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeStatus: "",
+                title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeHidden: true,
                 hidden: ComputedField.MakeFunction("IsNoviceMode()") as any,
                 _stayInCollection: true, _hideContextMenu: true,
                 _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
@@ -367,7 +367,7 @@ export class CurrentUserUtils {
     }[] {
         if (doc.emptyPresentation === undefined) {
             doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(),
-                { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true, cloneFieldFilter: new List<string>(["system"]) });
+                { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0", system: true, cloneFieldFilter: new List<string>(["system"]) });
             ((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0;
         }
         if (doc.emptyCollection === undefined) {
@@ -498,7 +498,7 @@ export class CurrentUserUtils {
 
         if (dragCreatorSet === undefined) {
             doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, {
-                title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeStatus: "",
+                title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
                 _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true,
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true
             }));
@@ -559,7 +559,7 @@ export class CurrentUserUtils {
             doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, {
                 title: "menuItemPanel",
                 childDropAction: "alias",
-                _chromeStatus: "",
+                _chromeHidden: true,
                 dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }),
                 backgroundColor: "black", ignoreClick: true,
                 _gridGap: 0,
@@ -597,7 +597,7 @@ export class CurrentUserUtils {
     // Sets up mobileMenu stacking document
     static setupMobileMenu() {
         const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), {
-            _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeStatus: ""
+            _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,
         }));
         return menu;
     }
@@ -691,13 +691,13 @@ export class CurrentUserUtils {
     static setupMobileUploadDoc(userDoc: Doc) {
         // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" })
         const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", {
-            title: "Upload Images From the Web", _chromeStatus: "enabled", _lockedPosition: true, system: true
+            title: "Upload Images From the Web", _lockedPosition: true, system: true
         });
         const uploadDoc = Docs.Create.StackingDocument([], {
-            title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true, _chromeStatus: ""
+            title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, system: true, _chromeHidden: true,
         });
         return Docs.Create.StackingDocument([webDoc, uploadDoc], {
-            _width: screen.width, _lockedPosition: true, title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true, _chromeStatus: ""
+            _width: screen.width, _lockedPosition: true, title: "Upload", _autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", system: true, _chromeHidden: true,
         });
     }
 
@@ -717,14 +717,13 @@ export class CurrentUserUtils {
         if (doc.myCreators === undefined) {
             doc.myCreators = new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], {
                 title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true,
-                _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true,
-                _chromeStatus: ""
+                _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true,
             }));
         }
         // setup a color picker
         if (doc.myColorPicker === undefined) {
             const color = Docs.Create.ColorDocument({
-                title: "color picker", _width: 300, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List<string>(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true
+                title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List<string>(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true
             });
             doc.myColorPicker = new PrefetchProxy(color);
         }
@@ -732,7 +731,7 @@ export class CurrentUserUtils {
         if (doc.myTools === undefined) {
             const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], {
                 title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true,
-                system: true, _stayInCollection: true, _hideContextMenu: true, _chromeStatus: ""
+                system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true,
             })) as any as Doc;
 
             doc.myTools = toolsStack;
@@ -910,8 +909,7 @@ export class CurrentUserUtils {
             if (!sharedDocs) {
                 sharedDocs = Docs.Create.StackingDocument([], {
                     title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15,
-                    _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add,
-                    _chromeStatus: ""
+                    _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add, _chromeHidden: true,
                 }, sharingDocumentId + "outer", sharingDocumentId);
             }
             if (sharedDocs instanceof Doc) {
@@ -926,13 +924,13 @@ export class CurrentUserUtils {
         if (doc.myImportDocs === undefined) {
             doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], {
                 title: "My ImportDocuments", _forceActive: true, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0,
-                childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, system: true, _chromeStatus: ""
+                childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, _lockedPosition: true, system: true, _chromeHidden: true,
             }));
         }
         if (doc.myImportPanel === undefined) {
             const uploads = Cast(doc.myImportDocs, Doc, null);
             const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _stayInCollection: true, _hideContextMenu: true, title: "Import", icon: "upload", system: true });
-            doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, _chromeStatus: "", _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true }));
+            doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, _chromeHidden: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, system: true }));
         }
     }
 
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 244e96599..925b74efa 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -145,7 +145,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> {
             title: `Import of ${directory}`,
             _width: 1105,
             _height: 500,
-            _chromeStatus: "",
+            _chromeHidden: true,
             x: NumCast(doc.x),
             y: NumCast(doc.y) + offset
         };
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index c59c67574..4444a3944 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -101,7 +101,7 @@ export class MainView extends React.Component {
         }
         new InkStrokeProperties();
         this._sidebarContent.proto = undefined;
-        DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's
+        DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeHidden"]); // can play with these fields on someone else's
 
         DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg)));
 
@@ -232,7 +232,7 @@ export class MainView extends React.Component {
             }));
         }
         const pres = Docs.Create.PresDocument(new List<Doc>(),
-            { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0" });
+            { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" });
         CollectionDockingView.AddSplit(pres, "right");
         this.userDoc.activePresentation = pres;
         Doc.AddDocToList(this.userDoc.myPresentations as Doc, "data", pres);
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 9afcdc28d..71f753d51 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -74,7 +74,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
         return this.propertyToggleBtn("Caption", "_showCaption", on => `${on ? "Hide" : "Show"} caption footer`, on => "closed-captioning", (dv, doc) => (dv?.rootDoc || doc)._showCaption = (dv?.rootDoc || doc)._showCaption === undefined ? "caption" : undefined);
     }
     @computed get chromeButton() {
-        return this.propertyToggleBtn("Controls", "_chromeStatus", on => `${on === "enabled" ? "Hide" : "Show"} editing UI`, on => "edit", (dv, doc) => (dv?.rootDoc || doc)._chromeStatus = !(dv?.rootDoc || doc)._chromeStatus ? "enabled" : "");
+        return this.propertyToggleBtn("Controls", "_chromeHidden", on => `${on ? "Show" : "Hide"} editing UI`, on => "edit", (dv, doc) => (dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden);
     }
     @computed get titleButton() {
         return this.propertyToggleBtn("Title", "_showTitle", on => "Switch between title styles", on => "text-width", (dv, doc) => (dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? "title" : (dv?.rootDoc || doc)._showTitle === "title" ? "title:hover" : undefined);
@@ -165,8 +165,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
     }
     @computed
     get onPerspectiveFlyout() {
-        const excludedViewTypes = Doc.UserDoc().noviceMode ? [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Stacking, CollectionViewType.Map, CollectionViewType.Linear] :
-            [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear];
+        const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear];
 
         const makeLabel = (value: string, label: string) => <div className="radio">
             <label>
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index bbe874eac..c201fc2c2 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -96,7 +96,6 @@ export class SidebarAnnos extends React.Component<FieldViewProps & extraProps> {
                         xMargin={0}
                         yMargin={0}
                         docFilters={this.docFilters}
-                        chromeStatus={"enabled"}
                         scaleField={this.sidebarKey() + "-scale"}
                         isAnnotationOverlay={false}
                         select={emptyFunction}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 3a329c7d9..dc6e2fe65 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -80,8 +80,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @undoBatch
     @action
     toggleChrome = (): void => {
-        this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout =>
-            layout._chromeStatus = (layout._chromeStatus ? "" : StrCast(layout._replacedChrome, "enabled")));
+        this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout => layout._chromeHidden = !layout._chromeHidden);
     }
 
     // todo: add brushes to brushMap to save with a style name
@@ -115,11 +114,11 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         const addedTypes = Doc.UserDoc().noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data);
         const layout = Doc.Layout(firstDoc);
         const templateMenu: Array<JSX.Element> = [];
-        this.props.templates.forEach((checked, template) =>
+        this.props.templates?.forEach((checked, template) =>
             templateMenu.push(<TemplateToggle key={template} template={template} checked={checked} toggle={this.toggleTemplate} />));
         templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
         templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
-        !Doc.UserDoc().noviceMode && templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
+        !Doc.UserDoc().noviceMode && templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={!layout._chromeHidden} toggle={this.toggleChrome} />);
         addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template));
         this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push(
             <OtherToggle key={template} name={template} checked={templateName === template} toggle={e => this.toggleLayout(e, template)} />));
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index c5a05da00..f0b9b5240 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -172,7 +172,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
             <div className="carousel-wrapper" style={{ transform: `translateX(${translateX}px)` }}>
                 {this.content}
             </div>
-            {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)}
+            {this.props.Document._chromeHidden ? (null) : this.buttons}
             <div className="dot-bar">
                 {this.dots}
             </div>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 512328835..f400ac5a2 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -109,7 +109,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     render() {
         return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
             {this.content}
-            {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)}
+            {!this.props.Document._chromeHidden ? (null) : this.buttons}
         </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 581520619..ba701b2a4 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -26,7 +26,7 @@ interface CMVFieldRowProps {
     rows: () => number;
     headings: () => object[];
     Document: Doc;
-    chromeStatus: string;
+    chromeHidden?: boolean;
     heading: string;
     headingObject: SchemaHeaderField | undefined;
     docList: Doc[];
@@ -190,7 +190,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     @action
     headerDown = (e: React.PointerEvent<HTMLDivElement>) => {
         if (e.button === 0 && !e.ctrlKey) {
-            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this.props.chromeStatus && this.collapseSection(e));
+            setupMoveUpEvents(this, e, this.headerMove, emptyFunction, e => !this.props.chromeHidden && this.collapseSection(e));
             this._createAliasSelected = false;
         }
     }
@@ -254,8 +254,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
 
     @computed get contentLayout() {
         const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap))));
-        const chromeStatus = this.props.chromeStatus;
-        const showChrome = (chromeStatus !== 'view-mode' && chromeStatus);
+        const showChrome = !this.props.chromeHidden;
         const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `;
         return this.collapsed ? (null) :
             <div style={{ position: "relative" }}>
@@ -288,7 +287,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
     }
 
     @computed get headingView() {
-        const noChrome = !this.props.chromeStatus;
+        const noChrome = this.props.chromeHidden;
         const key = this.props.pivotField;
         const evContents = this.heading ? this.heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`;
         const editableHeaderView = <EditableView
diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx
index 7998b3277..e57647181 100644
--- a/src/client/views/collections/CollectionPileView.tsx
+++ b/src/client/views/collections/CollectionPileView.tsx
@@ -14,17 +14,17 @@ import React = require("react");
 
 @observer
 export class CollectionPileView extends CollectionSubView(doc => doc) {
-    _originalChrome: string = "";
+    _originalChrome: any = "";
 
     componentDidMount() {
         if (this.layoutEngine() !== "pass" && this.layoutEngine() !== "starburst") {
             this.Document._pileLayoutEngine = "pass";
         }
-        this._originalChrome = StrCast(this.layoutDoc._chromeStatus);
-        this.layoutDoc._chromeStatus = "";
+        this._originalChrome = this.layoutDoc._chromeHidden
+        this.layoutDoc._chromeHidden = true;
     }
     componentWillUnmount() {
-        this.layoutDoc._chromeStatus = this._originalChrome;
+        this.layoutDoc._chromeHidden = this._originalChrome;
     }
 
     layoutEngine = () => StrCast(this.Document._pileLayoutEngine);
@@ -119,7 +119,7 @@ export class CollectionPileView extends CollectionSubView(doc => doc) {
 
     render() {
         return <div className={`collectionPileView`} onClick={this.onClick} onPointerDown={this.pointerDown}
-            style={{ width: this.props.PanelWidth(), height: `calc(100%  - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+            style={{ width: this.props.PanelWidth(), height: "100%" }}>
             {this.contents}
         </div>;
     }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 3f821bbcc..146992277 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -36,7 +36,7 @@ type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentS
 const StackingDocument = makeInterface(collectionSchema, documentSchema);
 
 export type collectionStackingViewProps = {
-    chromeStatus?: string;
+    chromeHidden?: boolean;
     viewType?: CollectionViewType;
     NativeWidth?: () => number;
     NativeHeight?: () => number;
@@ -53,7 +53,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
     @observable _heightMap = new Map<string, number>();
     @observable _cursor: CursorProperty = "grab";
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
-    @computed get chromeStatus() { return this.props.chromeStatus || StrCast(this.layoutDoc._chromeStatus); }
+    @computed get chromeHidden() { return this.props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); }
     @computed get columnHeaders() { return Cast(this.layoutDoc._columnHeaders, listSpec(SchemaHeaderField), null); }
     @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); }
     @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); }
@@ -63,7 +63,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
     @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); }
     @computed get isStackingView() { return (this.props.viewType ?? this.layoutDoc._viewType) === CollectionViewType.Stacking; }
     @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; }
-    @computed get showAddAGroup() { return (this.pivotField && (this.chromeStatus !== 'view-mode' && this.chromeStatus)); }
+    @computed get showAddAGroup() { return this.pivotField && !this.chromeHidden; }
     @computed get columnWidth() {
         return Math.min(this.props.PanelWidth() - 2 * this.xMargin,
             this.isStackingView ? Number.MAX_VALUE : this.layoutDoc._columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.layoutDoc._columnWidth, 250));
@@ -223,11 +223,12 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
             styleProvider={this.styleProvider}
             layerProvider={this.props.layerProvider}
             docViewPath={this.props.docViewPath}
+            fitWidth={this.props.childFitWidth}
             LayoutTemplate={this.props.childLayoutTemplate}
             LayoutTemplateString={this.props.childLayoutString}
             freezeDimensions={this.props.childFreezeDimensions}
-            NativeWidth={this.props.childIgnoreNativeSize ? returnZero : doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined}  // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
-            NativeHeight={this.props.childIgnoreNativeSize ? returnZero : doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined}
+            NativeWidth={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.() || doc._fitWidth && !Doc.NativeWidth(doc) ? width : undefined}  // explicitly ignore nativeWidth/height if childIgnoreNativeSize is set- used by PresBox
+            NativeHeight={this.props.childIgnoreNativeSize ? returnZero : this.props.childFitWidth?.() || doc._fitWidth && !Doc.NativeHeight(doc) ? height : undefined}
             dontCenter={this.props.childIgnoreNativeSize ? "xy" : undefined}
             dontRegisterView={dataDoc ? true : BoolCast(this.layoutDoc.childDontRegisterViews, this.props.dontRegisterView)}
             rootSelected={this.rootSelected}
@@ -266,7 +267,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         if (!d) return 0;
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const maxWidth = this.columnWidth / this.numGroupColumns;
-        if (!this.layoutDoc._columnsFill && !childLayoutDoc._fitWidth) {
+        if (!this.layoutDoc._columnsFill && !(childLayoutDoc._fitWidth || this.props.childFitWidth?.())) {
             return Math.min(d[WidthSym](), maxWidth);
         }
         return maxWidth;
@@ -276,8 +277,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         const childLayoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const childDataDoc = (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc;
         const maxHeight = (lim => lim === 0 ? this.props.PanelWidth() : lim === -1 ? 10000 : lim)(NumCast(this.layoutDoc.childLimitHeight, -1));
-        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!childLayoutDoc._fitWidth ? d[WidthSym]() : 0);
-        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!childLayoutDoc._fitWidth ? d[HeightSym]() : 0);
+        const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.()) ? d[WidthSym]() : 0);
+        const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._fitWidth || this.props.childFitWidth?.()) ? d[HeightSym]() : 0);
         if (nw && nh) {
             const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
             const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d), colWid);
@@ -286,7 +287,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                 docWid * nh / nw);
         }
         const childHeight = NumCast(childLayoutDoc._height);
-        const panelHeight = this.props.PanelHeight() - 2 * this.yMargin;
+        const panelHeight = (childLayoutDoc._fitWidth || this.props.childFitWidth?.()) ? Number.MAX_SAFE_INTEGER : this.props.PanelHeight() - 2 * this.yMargin;
         return Math.min(childHeight, maxHeight, panelHeight);
     }
 
@@ -412,7 +413,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                 }
             }}
             addDocument={this.addDocument}
-            chromeStatus={this.chromeStatus}
+            chromeHidden={this.chromeHidden}
             columnHeaders={this.columnHeaders}
             Document={this.props.Document}
             DataDoc={this.props.DataDoc}
@@ -445,7 +446,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
         return <CollectionMasonryViewFieldRow
             showHandle={first}
             Document={this.props.Document}
-            chromeStatus={this.chromeStatus}
+            chromeHidden={this.chromeHidden}
             pivotField={this.pivotField}
             unobserveHeight={(ref) => this.refList.splice(this.refList.indexOf(ref), 1)}
             observeHeight={(ref) => {
@@ -534,10 +535,6 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                     ref={this.createRef}
                     style={{
                         overflowY: this.props.active() ? "auto" : "hidden",
-                        transform: `scale(${this.scaling}`,
-                        height: `${1 / this.scaling * 100}%`,
-                        width: `${1 / this.scaling * 100}%`,
-                        transformOrigin: "top left",
                         background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor),
                         pointerEvents: this.backgroundEvents ? "all" : undefined
                     }}
@@ -551,11 +548,11 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument,
                             style={{ width: !this.isStackingView ? "100%" : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}>
                             <EditableView {...editableViewProps} />
                         </div>}
-                    {/* {!this.chromeStatus || !this.props.isSelected() ? (null) :
+                    {/* {this.chromeHidden || !this.props.isSelected() ? (null) :
                         <Switch
                             onChange={this.onToggle}
                             onClick={this.onToggle}
-                            defaultChecked={this.chromeStatus !== 'view-mode'}
+                            defaultChecked={true}
                             checkedChildren="edit"
                             unCheckedChildren="view"
                         />} */}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 70ec1f925..caf4c59c1 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -32,7 +32,7 @@ interface CSVFieldColumnProps {
     docList: Doc[];
     heading: string;
     pivotField: string;
-    chromeStatus: string;
+    chromeHidden?: boolean;
     columnHeaders: SchemaHeaderField[] | undefined;
     headingObject: SchemaHeaderField | undefined;
     yMargin: number;
@@ -249,8 +249,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                 style={{
                     marginTop: this.props.yMargin,
                     width: (this.props.columnWidth) /
-                        ((uniqueHeadings.length +
-                            ((this.props.chromeStatus !== 'view-mode' && this.props.chromeStatus) ? 1 : 0)) || 1)
+                        ((uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1)) || 1)
                 }}>
                 <div className={"collectionStackingView-collapseBar" + (this.props.headingObject.collapsed === true ? " active" : "")} onClick={this.collapseSection}></div>
                 {/* the default bucket (no key value) has a tooltip that describes what it is.
@@ -307,7 +306,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
                             }}>
                             {this.props.renderChildren(this.props.docList)}
                         </div>
-                        {(this.props.chromeStatus !== 'view-mode' && this.props.chromeStatus && type !== DocumentType.PRES) ?
+                        {!this.props.chromeHidden && type !== DocumentType.PRES ?
                             <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
                                 style={{ width: this.props.columnWidth / this.props.numGroupColumns, marginBottom: 10 }}>
                                 <EditableView
@@ -332,7 +331,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
         return (
             <div className={"collectionStackingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
                 style={{
-                    width: `${100 / ((uniqueHeadings.length + ((this.props.chromeStatus !== 'view-mode' && this.props.chromeStatus) ? 1 : 0)) || 1)}%`,
+                    width: `${100 / (uniqueHeadings.length + (this.props.chromeHidden ? 0 : 1) || 1)}%`,
                     height: undefined, // DraggingManager.GetIsDragging() ? "100%" : undefined,
                     background: this._background
                 }}
diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx
index f3f81ee26..0702febae 100644
--- a/src/client/views/collections/CollectionTimeView.tsx
+++ b/src/client/views/collections/CollectionTimeView.tsx
@@ -230,7 +230,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) {
         }
 
         return <div className={"collectionTimeView" + (doTimeline ? "" : "-pivot")} onContextMenu={this.specificMenu}
-            style={{ width: this.props.PanelWidth(), height: `calc(100%  - ${this.layoutDoc._chromeStatus === "enabled" ? 51 : 0}px)` }}>
+            style={{ width: this.props.PanelWidth(), height: "100%" }}>
             {this.pivotKeyUI}
             {this.contents}
             {!this.props.isSelected() || !doTimeline ? (null) : <>
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 34fd20f1a..535bf539e 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -71,6 +71,7 @@ export interface CollectionViewProps extends FieldViewProps {
     // property overrides for child documents
     children?: never | (() => JSX.Element[]) | React.ReactNode;
     childDocuments?: Doc[]; // used to override the documents shown by the sub collection to an explicit list (see LinkBox)
+    childFitWidth?: () => boolean;
     childOpacity?: () => number;
     childHideTitle?: () => boolean; // whether to hide the documentdecorations title for children
     childHideDecorationTitle?: () => boolean;
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index d26f53e28..4005c751e 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -559,8 +559,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             onPointerDown={this.props.onPointerDown} onClick={this.props.onClick} onWheel={e => this.props.active(true) && e.stopPropagation()}
             onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} >
             {this.reactTable}
-            {this.props.Document._chromeStatus ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>
-                : undefined}
+            {this.props.Document._chromeHidden ? undefined : <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>}
             {!this._showDoc ? (null) :
                 <div className="collectionSchemaView-documentPreview"
                     style={{
diff --git a/src/client/views/nodes/ColorBox.scss b/src/client/views/nodes/ColorBox.scss
index da3266dc1..d5f2a7ec7 100644
--- a/src/client/views/nodes/ColorBox.scss
+++ b/src/client/views/nodes/ColorBox.scss
@@ -6,7 +6,6 @@
      transform-origin: top left;
      
     .sketch-picker {
-        margin:auto;
         div {
             cursor: crosshair;
         }
diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx
index 61cc8b3d1..d2a0b2be5 100644
--- a/src/client/views/nodes/ColorBox.tsx
+++ b/src/client/views/nodes/ColorBox.tsx
@@ -2,7 +2,7 @@ import React = require("react");
 import { action } from "mobx";
 import { observer } from "mobx-react";
 import { ColorState, SketchPicker } from 'react-color';
-import { Doc } from "../../../fields/Doc";
+import { Doc, WidthSym, HeightSym } from '../../../fields/Doc';
 import { Utils } from "../../../Utils";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { InkTool } from "../../../fields/InkField";
@@ -28,54 +28,45 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument
     @undoBatch
     @action
     static switchColor(color: ColorState) {
-        Doc.UserDoc().backgroundColor = Utils.colorString(color);
+        // Doc.UserDoc().backgroundColor = Utils.colorString(color); // bcz: this can't go here ... needs a proper home in the settings panel
         SetActiveInkColor(color.hex);
 
-        if (CurrentUserUtils.SelectedTool === InkTool.None) {
-            const selected = SelectionManager.Views();
-            selected.map(view => {
-                const targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory :
-                    view.props.Document.layout instanceof Doc ? view.props.Document.layout :
-                        view.props.Document.isTemplateForField ? view.props.Document : Doc.GetProto(view.props.Document);
-                if (targetDoc) {
-                    if (view.props.LayoutTemplate?.() || view.props.LayoutTemplateString) {  // this situation typically occurs when you have a link dot 
-                        targetDoc.backgroundColor = Doc.UserDoc().backgroundColor;  // bcz: don't know how to change the color of an inline template...
-                    }
-                    else if (RichTextMenu.Instance?.TextViewFieldKey && window.getSelection()?.toString() !== "") {
-                        Doc.Layout(view.props.Document)[RichTextMenu.Instance.TextViewFieldKey + "-color"] = Doc.UserDoc().backgroundColor;
-                    } else {
-                        Doc.Layout(view.props.Document)._backgroundColor = Doc.UserDoc().backgroundColor; // '_backgroundColor' is template specific.  'backgroundColor' would apply to all templates, but has no UI at the moment
-                    }
+        SelectionManager.Views().map(view => {
+            const targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory :
+                view.props.Document.layout instanceof Doc ? view.props.Document.layout :
+                    view.props.Document.isTemplateForField ? view.props.Document : Doc.GetProto(view.props.Document);
+            if (targetDoc) {
+                if (view.props.LayoutTemplate?.() || view.props.LayoutTemplateString) {  // this situation typically occurs when you have a link dot 
+                    targetDoc.backgroundColor = Doc.UserDoc().backgroundColor;  // bcz: don't know how to change the color of an inline template...
                 }
-            });
-        }
+                else if (RichTextMenu.Instance?.TextViewFieldKey && window.getSelection()?.toString() !== "") {
+                    Doc.Layout(view.props.Document)[RichTextMenu.Instance.TextViewFieldKey + "-color"] = Doc.UserDoc().backgroundColor;
+                } else {
+                    Doc.Layout(view.props.Document)._backgroundColor = Doc.UserDoc().backgroundColor; // '_backgroundColor' is template specific.  'backgroundColor' would apply to all templates, but has no UI at the moment
+                }
+            }
+        });
     }
 
-    constructor(props: any) {
-        super(props);
-    }
     render() {
-        const selDoc = SelectionManager.Views()?.[0]?.rootDoc;
+        const scaling = Math.min(this.layoutDoc.fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[HeightSym](), this.props.PanelWidth() / this.rootDoc[WidthSym]());
         return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`}
-            onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} onClick={e => { (e.nativeEvent as any).stuff = true; e.stopPropagation(); }}
-            style={{ width: `${100}%`, height: `${100}%` }} >
+            onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} onClick={e => e.stopPropagation()}
+            style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }} >
 
-            <SketchPicker onChange={ColorBox.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
-                color={StrCast(selDoc?._backgroundColor, ActiveInkColor())} />
+            <SketchPicker
+                onChange={c => CurrentUserUtils.SelectedTool === InkTool.None && ColorBox.switchColor(c)}
+                color={StrCast(SelectionManager.Views()?.[0]?.rootDoc?._backgroundColor, ActiveInkColor())}
+                presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986',
+                    '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']}
+            />
 
-            <div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}>
-                <div> {ActiveInkWidth() ?? 2}</div>
-                <input type="range" defaultValue={ActiveInkWidth() ?? 2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+            <div style={{ width: this.props.PanelWidth() / scaling, display: "flex", paddingTop: "10px" }}>
+                <div> {ActiveInkWidth()}</div>
+                <input type="range" defaultValue={ActiveInkWidth()} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                     SetActiveInkWidth(e.target.value);
                     SelectionManager.Views().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeWidth = Number(e.target.value));
                 }} />
-                {/* <div> {ActiveInkBezierApprox() ?? 2}</div>
-                <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
-                    SetActiveBezierApprox(e.target.value);
-                    SelectionManager.SelectedDocuments().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeBezier = e.target.value);
-                }} /> */}
-                <br />
-                <br />
             </div>
         </div>;
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e15ade241..6d18f5f3e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -101,6 +101,7 @@ export interface DocumentViewSharedProps {
     layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean);
     styleProvider: Opt<StyleProviderFunc>;
     focus: DocFocusFunc;
+    fitWidth?: () => boolean;
     docFilters: () => string[];
     docRangeFilters: () => string[];
     searchFilterDocs: () => Doc[];
@@ -340,7 +341,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             Doc.SetNativeWidth(layoutDoc, actualdW / (layoutDoc._width || 1) * Doc.NativeWidth(layoutDoc));
                         }
                         layoutDoc._width = actualdW;
-                        if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
+                        if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._height = nheight / nwidth * layoutDoc._width;
                         else layoutDoc._height = actualdH;
                     }
                     else {
@@ -348,7 +349,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                             Doc.SetNativeHeight(layoutDoc, actualdH / (layoutDoc._height || 1) * Doc.NativeHeight(doc));
                         }
                         layoutDoc._height = actualdH;
-                        if (fixedAspect && !layoutDoc._fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
+                        if (fixedAspect && !this.props.DocumentView().fitWidth) layoutDoc._width = nwidth / nheight * layoutDoc._height;
                         else layoutDoc._width = actualdW;
                     }
                 } else {
@@ -721,7 +722,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
             (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.UserDoc().noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" });
             if (!Doc.UserDoc().noviceMode) {
                 moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" });
-                moreItems.push({ description: `${this.Document._chromeStatus ? "Hide" : "Show"} Chrome`, event: () => this.Document._chromeStatus = (this.Document._chromeStatus ? "" : "enabled"), icon: "project-diagram" });
+                moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" });
 
                 if (Cast(Doc.GetProto(this.props.Document).data, listSpec(Doc))) {
                     moreItems.push({ description: "Export to Google Photos Album", event: () => GooglePhotos.Export.CollectionToAlbum({ collection: this.props.Document }).then(console.log), icon: "caret-square-right" });
@@ -994,6 +995,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     get ComponentView() { return this.docView?._componentView; }
     get allLinks() { return this.docView?.allLinks || []; }
     get LayoutFieldKey() { return this.docView?.LayoutFieldKey || "layout"; }
+    get fitWidth() { return this.props.fitWidth?.() || this.layoutDoc.fitWidth; }
 
     @computed get docViewPath() { return this.props.docViewPath ? [...this.props.docViewPath(), this] : [this]; }
     @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); }
@@ -1005,13 +1007,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
         return this.docView?._componentView?.reverseNativeScaling?.() ? 0 :
             returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions));
     }
-    @computed get shouldNotScale() { return (this.layoutDoc._fitWidth && !this.nativeWidth) || [CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._viewType as any); }
+    @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || [CollectionViewType.Docking, CollectionViewType.Tree].includes(this.Document._viewType as any); }
     @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); }
     @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); }
     @computed get nativeScaling() {
         if (this.shouldNotScale) return 1;
         const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
-        if (this.layoutDoc._fitWidth || this.props.PanelHeight() / this.effectiveNativeHeight > this.props.PanelWidth() / this.effectiveNativeWidth) {
+        if (this.fitWidth || this.props.PanelHeight() / this.effectiveNativeHeight > this.props.PanelWidth() / this.effectiveNativeWidth) {
             return Math.max(minTextScale, this.props.PanelWidth() / this.effectiveNativeWidth);  // width-limited or fitWidth
         }
         return Math.max(minTextScale, this.props.PanelHeight() / this.effectiveNativeHeight); // height-limited or unscaled
@@ -1027,7 +1029,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; }
     @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; }
     @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; }
-    @computed get centeringY() { return this.props.Document._fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; }
+    @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; }
 
     toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight());
     contentsActive = () => this.docView?.contentsActive();
@@ -1109,7 +1111,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
                         position: this.props.Document.isInkMask ? "absolute" : undefined,
                         transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
                         width: xshift ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
-                        height: yshift ?? (this.props.Document._fitWidth ? `${this.panelHeight}px` :
+                        height: yshift ?? (this.fitWidth ? `${this.panelHeight}px` :
                             `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
                     }}>
                     <DocumentViewInternal {...this.props}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index f27c2c6a7..111509fdb 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -163,7 +163,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     }
 
     getTemplate = async () => {
-        const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template", _chromeStatus: "" });
+        const parent = Docs.Create.StackingDocument([], { _width: 800, _height: 800, title: "Template", _chromeHidden: true });
         parent._columnWidth = 100;
         for (const row of this.rows.filter(row => row.isChecked)) {
             await this.createTemplateField(parent, row);
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 3c1fda465..88406b181 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -13,7 +13,7 @@ import { PrefetchProxy } from "../../../fields/Proxy";
 import { listSpec, makeInterface } from "../../../fields/Schema";
 import { ScriptField } from "../../../fields/ScriptField";
 import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { returnFalse, returnOne } from "../../../Utils";
+import { returnFalse, returnOne, returnTrue } from '../../../Utils';
 import { Docs } from "../../documents/Documents";
 import { DocumentType } from "../../documents/DocumentTypes";
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -195,7 +195,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     componentDidMount() {
         this.rootDoc.presBox = this.rootDoc;
         this.rootDoc._forceRenderEngine = "timeline";
-        this.rootDoc._replacedChrome = "replaced";
         this.layoutDoc.presStatus = PresStatus.Edit;
         this.layoutDoc._gridGap = 0;
         this.layoutDoc._yMargin = 0;
@@ -2262,7 +2261,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
         const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
         const isMini: boolean = this.toolbarWidth <= 100;
         return (
-            <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeStatus ? "none" : undefined }}>
+            <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? "none" : undefined }}>
                 {isMini ? (null) : <select className="presBox-viewPicker"
                     style={{ display: this.layoutDoc.presStatus === "edit" ? "block" : "none" }}
                     onPointerDown={e => e.stopPropagation()}
@@ -2449,6 +2448,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
                             PanelHeight={this.panelHeight}
                             childIgnoreNativeSize={true}
                             moveDocument={returnFalse}
+                            childFitWidth={returnTrue}
                             childOpacity={returnOne}
                             childLayoutTemplate={this.childLayoutTemplate}
                             filterAddDocument={this.addDocumentFilter}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index af9aeea00..f6fa41145 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1438,7 +1438,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                 PanelWidth={this.sidebarWidth}
                 xMargin={0}
                 yMargin={0}
-                chromeStatus={"enabled"}
                 scaleField={this.SidebarKey + "-scale"}
                 isAnnotationOverlay={false}
                 select={emptyFunction}
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index 0bf942474..275249840 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -45,8 +45,6 @@ export const documentSchema = createSchema({
     _showAudio: "boolean",      // whether to show the audio record icon on documents
     _freeformLOD: "boolean",    // whether to enable LOD switching for CollectionFreeFormViews
     _pivotField: "string",      // specifies which field key should be used as the timeline/pivot axis
-    _replacedChrome: "string",  // what the default chrome is replaced with. Currently only supports the value of 'replaced' for PresBox's.
-    _chromeStatus: "string",    // determines the state of the collection chrome. values allowed are 'replaced', 'enabled', 'disabled', 'collapsed'
     _columnsFill: "boolean",    // whether documents in a stacking view column should be sized to fill the column
     _columnsSort: "string",     // how a document should be sorted "ascending", "descending", undefined (none)   
     _columnsHideIfEmpty: "boolean",   // whether empty stacking view column headings should be hidden
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 86f68ea6e..3e0a314ef 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -277,8 +277,6 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
     dataDocChanged && updateCachedAcls(dataDoc);
 }
 
-const layoutProps = ["panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "fitWidth", "fitToBox",
-    "chromeStatus", "viewType", "gridGap", "xMargin", "yMargin", "autoHeight"];
 export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean {
     let prop = in_prop;
     const effectiveAcl = getPropAcl(target, prop);
@@ -288,10 +286,6 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
     // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true;
 
     if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) {
-        // if (!prop.startsWith("_")) {
-        //     console.log(prop + " is deprecated - switch to _" + prop);
-        //     prop = "_" + prop;
-        // }
         if (!prop.startsWith("__")) prop = prop.substring(1);
         if (target.__LAYOUT__) {
             target.__LAYOUT__[prop] = value;
-- 
cgit v1.2.3-70-g09d2


From fac7cb468478060fa8de0b0fbd5490f2a0a85e7c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 19 Mar 2021 14:40:50 -0400
Subject: fixed clicking on web box and immediately scrolling to work.

---
 src/client/views/nodes/DocumentView.tsx | 10 +++++-----
 src/client/views/nodes/WebBox.tsx       |  2 +-
 2 files changed, 6 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 6d18f5f3e..830da9dc0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -158,6 +158,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
 export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps, Document>(Document) {
     @observable _animateScalingTo = 0;
     @observable _audioState = 0;
+    @observable _pendingDoubleClick = false;
     private _downX: number = 0;
     private _downY: number = 0;
     private _firstX: number = -1;
@@ -170,7 +171,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer;
     protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
-    _componentView: Opt<DocComponentView>;
+    _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class
 
     private get topMost() { return this.props.renderDepth === 0; }
     private get active() { return this.props.isSelected(true) || this.props.parentActive(true); }
@@ -482,10 +483,9 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) {  // onDragStart implies a button doc that we don't want to select when clicking.   RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part
                     stopPropagate = false; // don't stop propagation for field templates -- want the selection to propagate up to the root document of the template
                 } else {
-                    const ctrlPressed = e.ctrlKey || e.shiftKey;
-                    if (this.props.Document.type === DocumentType.WEB) {
-                        this._timeout = setTimeout(() => { this._timeout = undefined; this.props.select(ctrlPressed); }, 350);
-                    } else this.props.select(ctrlPressed);
+                    runInAction(() => this._pendingDoubleClick = true);
+                    this._timeout = setTimeout(action(() => { this._pendingDoubleClick = false; this._timeout = undefined; }), 350);
+                    this.props.select(e.ctrlKey || e.shiftKey);
                 }
                 preventDefault = false;
             }
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index adfe5d694..a60a35b01 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -422,7 +422,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum
     sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth);
 
     @computed get content() {
-        return <div className={"webBox-cont" + (this.active() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
+        return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.active() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")}
             style={{ width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / (this.props.scaling?.() || 1)}%`, }}>
             {this.urlContent}
         </div>;
-- 
cgit v1.2.3-70-g09d2


From 4b699bd0ded39983227d11e75c8d187546190f00 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 23 Mar 2021 16:05:52 -0400
Subject: changed audio/video to use playFrom() viewbox method instead of
 audioTrigger/videoTrigger doc field.  added a preliminiary form of
 transcription to screenshot boxes.

---
 src/client/documents/Documents.ts                  | 10 +++---
 src/client/util/DocumentManager.ts                 |  1 +
 src/client/views/SidebarAnnos.tsx                  | 10 +++---
 src/client/views/nodes/DocumentView.tsx            |  1 +
 src/client/views/nodes/PresBox.tsx                 | 23 ++++++++------
 src/client/views/nodes/ScreenshotBox.tsx           | 21 ++++++++++++
 .../views/nodes/formattedText/FormattedTextBox.tsx | 37 +++++++++++++++-------
 7 files changed, 70 insertions(+), 33 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index da434ab77..cdbf43cd7 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1048,12 +1048,10 @@ export namespace DocUtils {
 
     export let ActiveRecordings: { props: FieldViewProps, getAnchor: () => Doc }[] = [];
 
-    export function MakeLinkToActiveAudio(doc: Doc) {
-        let lastLink: Doc | undefined;
-        DocUtils.ActiveRecordings.map(audio => {
-            lastLink = DocUtils.MakeLink({ doc: doc }, { doc: audio.getAnchor() || audio.props.Document }, "recording link", "recording timeline");
-        });
-        return lastLink;
+    export function MakeLinkToActiveAudio(doc: Doc, broadcastEvent = true) {
+        broadcastEvent && runInAction(() => DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1);
+        return DocUtils.ActiveRecordings.map(audio =>
+            DocUtils.MakeLink({ doc: doc }, { doc: audio.getAnchor() || audio.props.Document }, "recording link", "recording timeline")).lastElement();
     }
 
     export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 34ff03335..d4623be28 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -14,6 +14,7 @@ export class DocumentManager {
 
     //global holds all of the nodes (regardless of which collection they're in)
     @observable public DocumentViews: DocumentView[] = [];
+    @observable public RecordingEvent = 0;
     @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = [];
 
     private static _instance: DocumentManager;
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index a859dcab4..87887483f 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -71,7 +71,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
     filtersHeight = () => 50;
     screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.props.dataDoc), 0).scale(this.props.scaling?.() || 1);
     panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 : (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth);
-    panelHeight = () => this.props.PanelHeight() - this.filtersHeight() - 20;
+    panelHeight = () => this.props.PanelHeight() - this.filtersHeight();
     addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey());
     moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey());
     removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey());
@@ -103,6 +103,10 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                 width: `${this.panelWidth()}px`,
                 height: "100%"
             }}>
+                <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight(), width: this.panelWidth() }}>
+                    {this.allUsers.map(renderUsers)}
+                    {this.allHashtags.map(renderTag)}
+                </div>
                 <div style={{ width: "100%", height: this.panelHeight(), position: "relative" }}>
                     <CollectionStackingView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} ref={this._stackRef}
                         NativeWidth={returnZero}
@@ -131,10 +135,6 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
                         pointerEvents={"all"}
                     />
                 </div>
-                <div className="sidebarAnnos-tagList" style={{ height: this.filtersHeight(), width: this.panelWidth() }}>
-                    {this.allUsers.map(renderUsers)}
-                    {this.allHashtags.map(renderTag)}
-                </div>
             </div>;
     }
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 830da9dc0..3f76db46d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -85,6 +85,7 @@ export interface DocComponentView {
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
     getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
     setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown)
+    playFrom?: (time: number, endTime?: number) => void;
 }
 export interface DocumentViewSharedProps {
     renderDepth: number;
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 6da215c5f..2aba461e0 100644
--- a/src/client/views/nodes/PresBox.tsx
+++ b/src/client/views/nodes/PresBox.tsx
@@ -230,17 +230,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
     // 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played
     startTempMedia = (targetDoc: Doc, activeItem: Doc) => {
         const duration: number = NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime);
-        if (targetDoc.type === DocumentType.AUDIO) {
-            if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]);
-            targetDoc._triggerAudio = NumCast(activeItem.presStartTime);
-            this._mediaTimer = [setTimeout(() => targetDoc._audioStop = true, duration * 1000), targetDoc];
-        } else if (targetDoc.type === DocumentType.VID) {
-            if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]);
-            targetDoc._triggerVideoStop = true;
-            setTimeout(() => targetDoc._currentTimecode = NumCast(activeItem.presStartTime), 10);
-            setTimeout(() => targetDoc._triggerVideo = true, 20);
-            this._mediaTimer = [setTimeout(() => targetDoc._triggerVideoStop = true, (duration * 1000) + 20), targetDoc];
+        if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
+            const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
+            targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.presStartTime), NumCast(activeItem.presStartTime) + duration);
         }
+        // if (targetDoc.type === DocumentType.AUDIO) {
+        //     if (this._mediaTimer && this._mediaTimer[1] === targetDoc) clearTimeout(this._mediaTimer[0]);
+        //     targetDoc._triggerAudio = NumCast(activeItem.presStartTime);
+        //     this._mediaTimer = [setTimeout(() => targetDoc._audioStop = true, duration * 1000), targetDoc];
+        // } else if (targetDoc.type === DocumentType.VID) {
+        //     targetDoc._triggerVideoStop = true;
+        //     setTimeout(() => targetDoc._currentTimecode = NumCast(activeItem.presStartTime), 10);
+        //     setTimeout(() => targetDoc._triggerVideo = true, 20);
+        //     this._mediaTimer = [setTimeout(() => targetDoc._triggerVideoStop = true, (duration * 1000) + 20), targetDoc];
+        // }
     }
 
     stopTempMedia = (targetDoc: Doc) => {
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 0e69725ca..a481cbbc5 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -24,6 +24,9 @@ import { VideoBox } from "./VideoBox";
 import { Id } from "../../../fields/FieldSymbols";
 import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline";
 import { DateField } from "../../../fields/DateField";
+import { ComputedField } from "../../../fields/ScriptField";
+import { DocumentManager } from "../../util/DocumentManager";
+import { DocumentView } from "./DocumentView";
 const path = require('path');
 declare class MediaRecorder {
     constructor(e: any, options?: any);  // whatever MediaRecorder has
@@ -161,6 +164,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
     _achunks: any;
     _vrecorder: any;
     _arecorder: any;
+    _dictation: Doc | undefined;
+    _dictationView: DocumentView | undefined;
 
     toggleRecording = action(async () => {
         this._screenCapture = !this._screenCapture;
@@ -190,17 +195,33 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
                     this.dataDoc[this.props.fieldKey] = new VideoField(Utils.prepend(result.accessPaths.agnostic.client));
                 } else alert("video conversion failed");
             };
+            this._dictation = this.setupDictation();
+            setTimeout(() => this._dictationView = DocumentManager.Instance.getDocumentView(this._dictation!));
             this._arecorder.start();
             this._vrecorder.start();
+            this.dataDoc.audioState = "recording";
             DocUtils.ActiveRecordings.push(this);
         } else {
             this._arecorder.stop();
             this._vrecorder.stop();
+            this.dataDoc.audioState = "paused";
             const ind = DocUtils.ActiveRecordings.indexOf(this);
             ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
         }
     });
 
+    setupDictation = () => {
+        const dictationText = CurrentUserUtils.GetNewTextDoc("",
+            NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10,
+            NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
+        const dictationTextProto = Doc.GetProto(dictationText);
+        dictationTextProto.recordingSource = this.dataDoc;
+        dictationTextProto.recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`);
+        dictationTextProto.audioState = ComputedField.MakeFunction("self.recordingSource.audioState");
+        this.props.addDocument?.(dictationText);
+        return dictationText;
+    }
+
     private get uIButtons() {
         return (<div className="screenshotBox-uiButtons">
             <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording} >
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 0c0b09b9a..47fad9112 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -61,7 +61,6 @@ import { RichTextRules } from "./RichTextRules";
 import { schema } from "./schema_rts";
 import { SummaryView } from "./SummaryView";
 import applyDevTools = require("prosemirror-dev-tools");
-
 import React = require("react");
 const translateGoogleApi = require("translate-google-api");
 
@@ -636,6 +635,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this._downX = this._downY = Number.NaN;
     }
 
+    breakupDictation = () => {
+        if (this._editorView) {
+            this.stopDictation(true);
+            this._break = true;
+            const state = this._editorView.state;
+            const to = state.selection.to;
+            const updated = TextSelection.create(state.doc, to, to);
+            this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to));
+            if (this._recording) setTimeout(() => this.recordDictation(), 500);
+        }
+    }
     recordDictation = () => {
         DictationManager.Controls.listen({
             interimHandler: this.setDictationContent,
@@ -654,7 +664,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             if (this._recordingStart) {
                 let from = this._editorView.state.selection.from;
                 if (this._break) {
-                    const l = DocUtils.MakeLinkToActiveAudio(this.rootDoc);
+                    const l = DocUtils.MakeLinkToActiveAudio(this.rootDoc, false);
                     if (!l) return;
                     const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined;
                     if (!anchor) return;
@@ -758,6 +768,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
         this.props.contentsActive?.(this.active);
         this._cachedLinks = DocListCast(this.Document.links);
+        this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, () => this.breakupDictation());
         this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight());
         this._disposers.scrollHeight = reaction(() => ({ scrollHeight: this.scrollHeight, autoHeight: this.autoHeight, width: NumCast(this.layoutDoc._width) }),
             ({ width, scrollHeight, autoHeight }) => width && autoHeight && this.resetNativeHeight(scrollHeight)
@@ -842,7 +853,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             setTimeout(() => this.recordDictation(), 500);
                         }), 500);
                     } else setTimeout(() => this.stopDictation(true), 0);
-                }
+                },
+                { fireImmediately: true }
             );
         }
         var quickScroll: string | undefined = "";
@@ -1122,21 +1134,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
             DocServer.GetRefField(audioid).then(anchor => {
                 if (anchor instanceof Doc) {
                     const audiodoc = anchor.annotationOn as Doc;
-                    audiodoc._triggerAudio = Number(time);
-                    !DocumentManager.Instance.getDocumentView(audiodoc) && this.props.addDocTab(audiodoc, "add:bottom");
+                    const func = () => {
+                        const docView = DocumentManager.Instance.getDocumentView(audiodoc);
+                        if (!docView) {
+                            this.props.addDocTab(audiodoc, "add:bottom");
+                            setTimeout(() => func());
+                        }
+                        else docView.ComponentView?.playFrom?.(Number(time), Number(time) + 3); // bcz: would be nice to find the next audio tag in the doc and play until that
+                    }
+                    func();
                 }
             });
         }
         if (this._recording && !e.ctrlKey && e.button === 0) {
-            this.stopDictation(true);
-            this._break = true;
-            const state = this._editorView!.state;
-            const to = state.selection.to;
-            const updated = TextSelection.create(state.doc, to, to);
-            this._editorView!.dispatch(this._editorView!.state.tr.setSelection(updated).insertText("\n", to));
+            this.breakupDictation();
             e.preventDefault();
             e.stopPropagation();
-            if (this._recording) setTimeout(() => this.recordDictation(), 500);
         }
         this._downX = e.clientX;
         this._downY = e.clientY;
-- 
cgit v1.2.3-70-g09d2


From 7f5cee959475b4e42af3b4ea39dcf92f450fa564 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Mar 2021 04:00:15 -0400
Subject: renamed audioState to mediaState and cleaned up screenshotBox a
 little.

---
 src/client/documents/Documents.ts                  |   2 +-
 src/client/views/nodes/AudioBox.tsx                |  28 ++---
 src/client/views/nodes/DocumentView.tsx            |  14 +--
 src/client/views/nodes/ScreenshotBox.tsx           | 130 +++++----------------
 .../views/nodes/formattedText/FormattedTextBox.tsx |   6 +-
 5 files changed, 51 insertions(+), 129 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index cdbf43cd7..4e05793d4 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -714,7 +714,7 @@ export namespace Docs {
 
         export function AudioDocument(url: string, options: DocumentOptions = {}) {
             return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(new URL(url)),
-                { ...options, backgroundColor: ComputedField.MakeFunction("this._audioState === 'playing' ? 'green':'gray'") as any });
+                { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any });
         }
 
         export function SearchDocument(options: DocumentOptions = {}) {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 393a80648..2f7a6cfd8 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -57,8 +57,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     @observable _position: number = 0;
     @observable _waveHeight: Opt<number> = this.layoutDoc._height;
     @observable _paused: boolean = false;
-    @computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); }
-    set audioState(value) { this.dataDoc.audioState = value; }
+    @computed get mediaState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.mediaState as (undefined | "recording" | "paused" | "playing"); }
+    set mediaState(value) { this.dataDoc.mediaState = value; }
     public static SetScrubTime = action((timeInMillisFrom1970: number) => { AudioBox._scrubTime = 0; AudioBox._scrubTime = timeInMillisFrom1970; });
     @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); }
     @computed get duration() { return NumCast(this.dataDoc[`${this.fieldKey}-duration`]); }
@@ -90,7 +90,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     getAnchor = () => {
         return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey,
             "_timecodeToShow" /* audioStart */, "_timecodeToHide" /* audioEnd */, this._ele?.currentTime ||
-            Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined))
+            Cast(this.props.Document._currentTimecode, "number", null) || (this.mediaState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined))
             || this.rootDoc;
     }
 
@@ -104,7 +104,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     componentDidMount() {
         this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document.  this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
 
-        this.audioState = this.path ? "paused" : undefined;
+        this.mediaState = this.path ? "paused" : undefined;
 
         this._disposers.triggerAudio = reaction(
             () => !LinkDocPreview.LinkInfo && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined,
@@ -131,7 +131,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     // for updating the timecode
     timecodeChanged = () => {
         const htmlEle = this._ele;
-        if (this.audioState !== "recording" && htmlEle) {
+        if (this.mediaState !== "recording" && htmlEle) {
             htmlEle.duration && htmlEle.duration !== Infinity && runInAction(() => this.dataDoc[this.fieldKey + "-duration"] = htmlEle.duration);
             this.links.map(l => this.getLinkData(l)).forEach(({ la1, la2, linkTime }) => {
                 if (linkTime > NumCast(this.layoutDoc._currentTimecode) && linkTime < htmlEle.currentTime) {
@@ -145,7 +145,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     // pause play back
     Pause = action(() => {
         this._ele!.pause();
-        this.audioState = "paused";
+        this.mediaState = "paused";
     });
 
     // play audio for documents created during recording
@@ -169,7 +169,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
             } else if (seekTimeInSeconds <= this._ele.duration) {
                 this._ele.currentTime = seekTimeInSeconds;
                 this._ele.play();
-                runInAction(() => this.audioState = "playing");
+                runInAction(() => this.mediaState = "playing");
                 if (endTime !== this.duration) {
                     this._play = setTimeout(() => this.Pause(), (endTime - seekTimeInSeconds) * 1000); // use setTimeout to play a specific duration
                 }
@@ -181,7 +181,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
 
     // update the recording time
     updateRecordTime = () => {
-        if (this.audioState === "recording") {
+        if (this.mediaState === "recording") {
             setTimeout(this.updateRecordTime, 30);
             if (this._paused) {
                 this._pausedTime += (new Date().getTime() - this._recordStart) / 1000;
@@ -204,7 +204,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
             }
         };
         this._recordStart = new Date().getTime();
-        runInAction(() => this.audioState = "recording");
+        runInAction(() => this.mediaState = "recording");
         setTimeout(this.updateRecordTime, 0);
         this._recorder.start();
         setTimeout(() => this._recorder && this.stopRecording(), 60 * 60 * 1000); // stop after an hour
@@ -224,7 +224,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         this._recorder.stop();
         this._recorder = undefined;
         this.dataDoc[this.fieldKey + "-duration"] = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
-        this.audioState = "paused";
+        this.mediaState = "paused";
         this._stream?.getAudioTracks()[0].stop();
         const ind = DocUtils.ActiveRecordings.indexOf(this);
         ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
@@ -250,7 +250,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
             NumCast(this.props.Document._width), 2 * NumCast(this.props.Document._height));
         Doc.GetProto(newDoc).recordingSource = this.dataDoc;
         Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`);
-        Doc.GetProto(newDoc).audioState = ComputedField.MakeFunction("self.recordingSource.audioState");
+        Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState");
         this.props.addDocument?.(newDoc);
         e.stopPropagation();
     }
@@ -324,7 +324,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
             );
     }
 
-    playing = () => this.audioState === "playing";
+    playing = () => this.mediaState === "playing";
     playLink = (link: Doc) => {
         const stack = this._stackedTimeline.current;
         if (link.annotationOn === this.rootDoc) {
@@ -387,7 +387,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
                     <div className="audiobox-dictation" onClick={this.onFile}>
                         <FontAwesomeIcon style={{ width: "30px", background: !this.layoutDoc.dontAutoPlayFollowedLinks ? "yellow" : "rgba(0,0,0,0)" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
                     </div>
-                    {this.audioState === "recording" || this.audioState === "paused" ?
+                    {this.mediaState === "recording" || this.mediaState === "paused" ?
                         <div className="recording" onClick={e => e.stopPropagation()}>
                             <div className="buttons" onClick={this.recordClick}>
                                 <FontAwesomeIcon icon={"stop"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
@@ -405,7 +405,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
                 <div className="audiobox-controls" style={{ pointerEvents: this._isChildActive || this.active() ? "all" : "none" }} >
                     <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-playhead" style={{ width: AudioBox.playheadWidth }} title={this.mediaState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.mediaState === "paused" ? "play" : "pause"} size={"1x"} /></div>
                         <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}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3f76db46d..aff0efdc7 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -158,7 +158,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps {
 @observer
 export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps, Document>(Document) {
     @observable _animateScalingTo = 0;
-    @observable _audioState = 0;
+    @observable _mediaState = 0;
     @observable _pendingDoubleClick = false;
     private _downX: number = 0;
     private _downY: number = 0;
@@ -772,7 +772,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 style={{ height: 25, position: "absolute", top: 10, left: 10 }}
             >
                 <FontAwesomeIcon className="documentView-audioFont"
-                    style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._audioState] }}
+                    style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "blue" : "gray", "green", "red"][this._mediaState] }}
                     icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]).length ? "microphone" : "file-audio"} size="sm" />
             </div>;
         return <div className="documentView-contentsView"
@@ -835,7 +835,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     onPointerEnter = () => {
         const self = this;
         const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]);
-        if (audioAnnos && audioAnnos.length && this._audioState === 0) {
+        if (audioAnnos && audioAnnos.length && this._mediaState === 0) {
             const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
             anno.data instanceof AudioField && new Howl({
                 src: [anno.data.url.href],
@@ -844,10 +844,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 loop: false,
                 volume: 0.5,
                 onend: function () {
-                    runInAction(() => self._audioState = 0);
+                    runInAction(() => self._mediaState = 0);
                 }
             });
-            this._audioState = 1;
+            this._mediaState = 1;
         }
     }
     recordAudioAnnotation = () => {
@@ -872,11 +872,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                     }
                 }
             };
-            runInAction(() => self._audioState = 2);
+            runInAction(() => self._mediaState = 2);
             recorder.start();
             setTimeout(() => {
                 recorder.stop();
-                runInAction(() => self._audioState = 0);
+                runInAction(() => self._mediaState = 0);
                 gumStream.getAudioTracks()[0].stop();
             }, 5000);
         });
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 8e1a43fd1..bed6566a6 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -1,38 +1,36 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable } from "mobx";
 import { observer } from "mobx-react";
-import * as rp from 'request-promise';
+import { DateField } from "../../../fields/DateField";
 import { Doc, WidthSym } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
+import { Id } from "../../../fields/FieldSymbols";
 import { InkTool } from "../../../fields/InkField";
-import { listSpec, makeInterface } from "../../../fields/Schema";
+import { makeInterface } from "../../../fields/Schema";
+import { ComputedField } from "../../../fields/ScriptField";
 import { Cast, NumCast } from "../../../fields/Types";
-import { VideoField, AudioField } from "../../../fields/URLField";
-import { emptyFunction, returnFalse, returnOne, returnZero, Utils, OmitKeys } from "../../../Utils";
-import { Docs, DocUtils } from "../../documents/Documents";
+import { AudioField, VideoField } from "../../../fields/URLField";
+import { emptyFunction, OmitKeys, returnFalse, returnOne, Utils } from "../../../Utils";
+import { DocUtils } from "../../documents/Documents";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { Networking } from "../../Network";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
+import { DocumentManager } from "../../util/DocumentManager";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline";
 import { ContextMenu } from "../ContextMenu";
 import { ContextMenuProps } from "../ContextMenuItem";
-import { ViewBoxBaseComponent, ViewBoxAnnotatableComponent } from "../DocComponent";
+import { ViewBoxAnnotatableComponent } from "../DocComponent";
+import { DocumentView } from "./DocumentView";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ScreenshotBox.scss";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
-import { Networking } from "../../Network";
-import { DocumentType } from "../../documents/DocumentTypes";
 import { VideoBox } from "./VideoBox";
-import { Id } from "../../../fields/FieldSymbols";
-import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline";
-import { DateField } from "../../../fields/DateField";
-import { ComputedField } from "../../../fields/ScriptField";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DocumentView } from "./DocumentView";
 const path = require('path');
 declare class MediaRecorder {
     constructor(e: any, options?: any);  // whatever MediaRecorder has
 }
 
-
 type ScreenshotDocument = makeInterface<[typeof documentSchema]>;
 const ScreenshotDocument = makeInterface(documentSchema);
 
@@ -40,12 +38,15 @@ const ScreenshotDocument = makeInterface(documentSchema);
 export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) {
     private _reactionDisposer?: IReactionDisposer;
     private _videoRef: HTMLVideoElement | null = null;
+    private _vchunks: any;
+    private _achunks: any;
+    private _vrecorder: any;
+    private _arecorder: any;
+    private _dictation: Doc | undefined;
+    private _dictationView: DocumentView | undefined;
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); }
     @computed get recordingStart() { return Cast(this.dataDoc[this.props.fieldKey + "-recordingStart"], DateField)?.date.getTime(); }
 
-    public get player(): HTMLVideoElement | null {
-        return this._videoRef;
-    }
 
     getAnchor = () => {
         const startTime = Cast(this.layoutDoc._currentTimecode, "number", null) || (this._vrecorder ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined);
@@ -55,7 +56,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
     }
 
     videoLoad = () => {
-        const aspect = this.player!.videoWidth / this.player!.videoHeight;
+        const aspect = this._videoRef!.videoWidth / this._videoRef!.videoHeight;
         const nativeWidth = Doc.NativeWidth(this.layoutDoc);
         const nativeHeight = Doc.NativeHeight(this.layoutDoc);
         if (!nativeWidth || !nativeHeight) {
@@ -65,49 +66,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
         }
     }
 
-    @action public Snapshot() {
-        const width = NumCast(this.layoutDoc._width);
-        const height = NumCast(this.layoutDoc._height);
-        const canvas = document.createElement('canvas');
-        canvas.width = 640;
-        canvas.height = 640 / (Doc.NativeAspect(this.layoutDoc) || 1);
-        const ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
-        if (ctx) {
-            ctx.rect(0, 0, canvas.width, canvas.height);
-            ctx.fillStyle = "blue";
-            ctx.fill();
-            this._videoRef && ctx.drawImage(this._videoRef, 0, 0, canvas.width, canvas.height);
-        }
-
-        if (this._videoRef) {
-            //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("screenshot" + Utils.GenerateGuid().replace(/\..*$/, "").replace(" ", "_")));
-            ScreenshotBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
-                setTimeout(() => {
-                    if (returnedFilename) {
-                        const imageSummary = Docs.Create.ImageDocument(Utils.prepend(returnedFilename), {
-                            x: NumCast(this.layoutDoc.x) + width, y: NumCast(this.layoutDoc.y),
-                            _width: 150, _height: height / width * 150, title: "--screenshot--"
-                        });
-                        if (!this.props.addDocument || this.props.addDocument === returnFalse) {
-                            const spt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
-                            imageSummary.x = spt[0];
-                            imageSummary.y = spt[1];
-                            Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null)?.data, listSpec(Doc), []).push(imageSummary);
-                        } else {
-                            this.props.addDocument?.(imageSummary);
-                        }
-                    }
-                }, 500);
-            });
-        }
-    }
-
-    componentDidMount() {
-    }
-
     componentWillUnmount() {
         this._reactionDisposer?.();
         const ind = DocUtils.ActiveRecordings.indexOf(this);
@@ -115,33 +73,13 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
     }
 
     @action
-    setVideoRef = (vref: HTMLVideoElement | null) => {
-        this._videoRef = vref;
-    }
-
-    public static async convertDataUri(imageUri: string, returnedFilename: string) {
-        try {
-            const posting = Utils.prepend("/uploadURI");
-            const returnedUri = await rp.post(posting, {
-                body: {
-                    uri: imageUri,
-                    name: returnedFilename
-                },
-                json: true,
-            });
-            return returnedUri;
+    setVideoRef = (vref: HTMLVideoElement | null) => this._videoRef = vref;
 
-        } catch (e) {
-            console.log("ScreenShotBox:" + e);
-        }
-    }
     @observable _screenCapture = false;
     specificContextMenu = (e: React.MouseEvent): void => {
         const field = Cast(this.dataDoc[this.fieldKey], VideoField);
         if (field) {
-            const url = field.url.href;
             const subitems: ContextMenuProps[] = [];
-            subitems.push({ description: "Take Snapshot", event: () => this.Snapshot(), icon: "expand-arrows-alt" });
             subitems.push({ description: "Screen Capture", event: this.toggleRecording, icon: "expand-arrows-alt" });
             ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
         }
@@ -160,13 +98,6 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
             </video>;
     }
 
-    _vchunks: any;
-    _achunks: any;
-    _vrecorder: any;
-    _arecorder: any;
-    _dictation: Doc | undefined;
-    _dictationView: DocumentView | undefined;
-
     toggleRecording = action(async () => {
         this._screenCapture = !this._screenCapture;
         if (this._screenCapture) {
@@ -199,12 +130,12 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
             setTimeout(() => this._dictationView = DocumentManager.Instance.getDocumentView(this._dictation!));
             this._arecorder.start();
             this._vrecorder.start();
-            this.dataDoc.audioState = "recording";
+            this.dataDoc.mediaState = "recording";
             DocUtils.ActiveRecordings.push(this);
         } else {
             this._arecorder.stop();
             this._vrecorder.stop();
-            this.dataDoc.audioState = "paused";
+            this.dataDoc.mediaState = "paused";
             const ind = DocUtils.ActiveRecordings.indexOf(this);
             ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
         }
@@ -217,7 +148,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
         const dictationTextProto = Doc.GetProto(dictationText);
         dictationTextProto.recordingSource = this.dataDoc;
         dictationTextProto.recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.props.fieldKey}-recordingStart"]`);
-        dictationTextProto.audioState = ComputedField.MakeFunction("self.recordingSource.audioState");
+        dictationTextProto.mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState");
         this.props.addDocument?.(dictationText) || this.props.addDocTab(dictationText, "add:bottom");
         return dictationText;
     }
@@ -227,18 +158,9 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
             <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording} >
                 <FontAwesomeIcon icon="file" size="lg" />
             </div>
-            <div className="screenshotBox-snapshot" key="cam" onPointerDown={this.onSnapshot} >
-                <FontAwesomeIcon icon="camera" size="lg" />
-            </div>
         </div>);
     }
 
-    onSnapshot = (e: React.PointerEvent) => {
-        this.Snapshot();
-        e.stopPropagation();
-        e.preventDefault();
-    }
-
     contentFunc = () => [this.content];
     render() {
         return (<div className="videoBox" onContextMenu={this.specificContextMenu}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 85c2a7cb3..2a1cc854c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -124,9 +124,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
     @computed get scrollHeight() { return NumCast(this.rootDoc[this.fieldKey + "-scrollHeight"]); }
     @computed get sidebarHeight() { return NumCast(this.rootDoc[this.SidebarKey + "-height"]); }
     @computed get titleHeight() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; }
-    @computed get _recording() { return this.dataDoc?.audioState === "recording"; }
+    @computed get _recording() { return this.dataDoc?.mediaState === "recording"; }
     set _recording(value) {
-        !this.dataDoc.recordingSource && (this.dataDoc.audioState = value ? "recording" : undefined);
+        !this.dataDoc.recordingSource && (this.dataDoc.mediaState = value ? "recording" : undefined);
     }
     @computed get config() {
         this._keymap = buildKeymap(schema, this.props);
@@ -307,7 +307,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
         let link;
         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") {
+            if (anchor && (anchor.annotationOn as Doc).mediaState === "recording") {
                 linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
                 linkAnchor = anchor;
                 link = l;
-- 
cgit v1.2.3-70-g09d2


From 147cd8618023884b9eb60a79d5efe53abefe9c47 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 24 Mar 2021 18:50:27 -0400
Subject: redid how LinkManager stores links on documents by putting them on
 the Doc itself instead of as a computedFn.  This has a signifcant effect on
 efficiency since adding a link to one document will no longer invalidate
 every other view that references *any* document's links

---
 src/client/util/LinkManager.ts                 | 103 +++++++++++++++++--------
 src/client/views/DocumentButtonBar.tsx         |   4 +-
 src/client/views/Main.tsx                      |   2 +
 src/client/views/nodes/DocumentLinksButton.tsx |  22 +++---
 src/client/views/nodes/DocumentView.tsx        |   3 +-
 src/client/views/nodes/ScreenshotBox.tsx       |   2 +
 src/client/views/nodes/VideoBox.tsx            |  10 +--
 src/fields/Doc.ts                              |   2 +
 src/fields/util.ts                             |   3 +-
 9 files changed, 92 insertions(+), 59 deletions(-)

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

diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index bf973c3d6..62338e691 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,21 +1,21 @@
 import { computedFn } from "mobx-utils";
-import { Doc, DocListCast, Opt } from "../../fields/Doc";
-import { BoolCast, Cast, StrCast } from "../../fields/Types";
+import { Doc, DocListCast, Opt, DirectLinksSym, Field } from "../../fields/Doc";
+import { BoolCast, Cast, StrCast, PromiseValue } from "../../fields/Types";
 import { LightboxView } from "../views/LightboxView";
 import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView";
 import { DocumentManager } from "./DocumentManager";
 import { SharingManager } from "./SharingManager";
 import { UndoManager } from "./UndoManager";
+import { observe, observable, reaction } from "mobx";
+import { listSpec } from "../../fields/Schema";
+import { List } from "../../fields/List";
+import { ProxyField } from "../../fields/Proxy";
 
 type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void;
 /* 
  * link doc: 
- * - anchor1: doc
- * - anchor1page: number
- * - anchor1groups: list of group docs representing the groups anchor1 categorizes this link/anchor2 in 
+ * - anchor1: doc 
  * - anchor2: doc
- * - anchor2page: number
- * - anchor2groups: list of group docs representing the groups anchor2 categorizes this link/anchor1 in 
  * 
  * group doc:
  * - type: string representing the group type/name/category
@@ -26,38 +26,80 @@ type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => vo
  */
 export class LinkManager {
 
-    private static _instance: LinkManager;
+    @observable static _instance: LinkManager;
+    @observable static userDocs: Doc[] = [];
     public static currentLink: Opt<Doc>;
-    public static get Instance(): LinkManager { return this._instance || (this._instance = new this()); }
+    public static get Instance() { return LinkManager._instance; }
+    constructor() {
+        LinkManager._instance = this;
+        setTimeout(() => {
+            LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase as Doc)];
+            const addLinkToDoc = (link: Doc): any => {
+                const a1 = link?.anchor1;
+                const a2 = link?.anchor2;
+                if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => addLinkToDoc(link)));
+                if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+                    Doc.GetProto(a1)[DirectLinksSym].add(link);
+                    Doc.GetProto(a2)[DirectLinksSym].add(link);
+                }
+            }
+            const remLinkFromDoc = (link: Doc): any => {
+                const a1 = link?.anchor1;
+                const a2 = link?.anchor2;
+                if (a1 instanceof Promise || a2 instanceof Promise) return PromiseValue(a1).then(a1 => PromiseValue(a2).then(a2 => remLinkFromDoc(link)));
+                if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) {
+                    Doc.GetProto(a1)[DirectLinksSym].delete(link);
+                    Doc.GetProto(a2)[DirectLinksSym].delete(link);
+                }
+            }
+            const watchUserLinks = (userLinks: List<Doc>) => {
+                const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field;  // see List.ts.  data structure is not a simple list of Docs, but a list of ProxyField/Fields
+                observe(userLinks, change => {
+                    switch (change.type) {
+                        case "splice":
+                            (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
+                            (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
+                            break;
+                        case "update": let oldValue = change.oldValue;
+                    }
+                }, true);
+            }
+            observe(LinkManager.userDocs, change => {
+                switch (change.type) {
+                    case "splice": (change as any).added.forEach(watchUserLinks); break;
+                    case "update": let oldValue = change.oldValue;
+                }
+            }, true);
+        });
+    }
 
-    public addLink(linkDoc: Doc) { return Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc); }
+    public addLink(linkDoc: Doc) {
+        return Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc);
+    }
     public deleteLink(linkDoc: Doc) { return Doc.RemoveDocFromList(Doc.LinkDBDoc(), "data", linkDoc); }
     public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); }
 
     public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor
-    public getAllDirectLinks(anchor: Doc): Doc[] { return this.directLinker(anchor); }  // finds all links that contain the given anchor
-    public getAllLinks(): Doc[] { return this.allLinks(); }
-
-    allLinks = computedFn(function allLinks(this: any): Doc[] {
-        const linkData = Doc.LinkDBDoc().data;
-        const lset = new Set<Doc>(DocListCast(linkData));
-        SharingManager.Instance.users.forEach(user => DocListCast(user.linkDatabase?.data).forEach(doc => lset.add(doc)));
-        return Array.from(lset);
-    }, true);
+    public getAllDirectLinks(anchor: Doc): Doc[] { return Array.from(Doc.GetProto(anchor)[DirectLinksSym]); } // finds all links that contain the given anchor
+    public getAllLinks(): Doc[] { return []; }//this.allLinks(); }
 
-    directLinker = computedFn(function directLinker(this: any, anchor: Doc): Doc[] {
-        return LinkManager.Instance.allLinks().filter(link => {
-            const a1 = Cast(link?.anchor1, Doc, null);
-            const a2 = Cast(link?.anchor2, Doc, null);
-            return link && ((a1?.author !== undefined && a2?.author !== undefined) || link.author === Doc.CurrentUserEmail) && (Doc.AreProtosEqual(anchor, a1) || Doc.AreProtosEqual(anchor, a2) || Doc.AreProtosEqual(link, anchor));
-        });
-    }, true);
+    // allLinks = computedFn(function allLinks(this: any): Doc[] {
+    //     const linkData = Doc.LinkDBDoc().data;
+    //     const lset = new Set<Doc>(DocListCast(linkData));
+    //     SharingManager.Instance.users.forEach(user => DocListCast(user.linkDatabase?.data).forEach(doc => lset.add(doc)));
+    //     LinkManager.Instance.allLinks().filter(link => {
+    //         const a1 = Cast(link?.anchor1, Doc, null);
+    //         const a2 = Cast(link?.anchor2, Doc, null);
+    //         return link && ((a1?.author !== undefined && a2?.author !== undefined) || link.author === Doc.CurrentUserEmail) && (Doc.AreProtosEqual(anchor, a1) || Doc.AreProtosEqual(anchor, a2) || Doc.AreProtosEqual(link, anchor));
+    //     });
+    //     return Array.from(lset);
+    // }, true);
 
     relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
         const lfield = Doc.LayoutFieldKey(anchor);
         return DocListCast(anchor[lfield + "-annotations"]).concat(DocListCast(anchor[lfield + "-annotations-timeline"])).reduce((list, anno) =>
             [...list, ...LinkManager.Instance.relatedLinker(anno)],
-            LinkManager.Instance.directLinker(anchor).slice());
+            Array.from(Doc.GetProto(anchor)[DirectLinksSym]).slice());// LinkManager.Instance.directLinker(anchor).slice());
     }, true);
 
     // returns map of group type to anchor's links in that group type
@@ -76,13 +118,6 @@ export class LinkManager {
         return anchorGroups;
     }
 
-    // checks if a link with the given anchors exists
-    public doesLinkExist(anchor1: Doc, anchor2: Doc): boolean {
-        return -1 !== LinkManager.Instance.allLinks().findIndex(linkDoc =>
-            (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor1) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor2)) ||
-            (Doc.AreProtosEqual(Cast(linkDoc.anchor1, Doc, null), anchor2) && Doc.AreProtosEqual(Cast(linkDoc.anchor2, Doc, null), anchor1)));
-    }
-
     // finds the opposite anchor of a given anchor in a link
     //TODO This should probably return undefined if there isn't an opposite anchor
     //TODO This should also await the return value of the anchor so we don't filter out promises
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index e248ef39a..a5d80cd22 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -352,10 +352,10 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
         const considerPush = isText && this.considerGoogleDocsPush;
         return <div className="documentButtonBar">
             <div className="documentButtonBar-button">
-                <DocumentLinksButton links={this.view0.allLinks} View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
+                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={true} />
             </div>
             {DocumentLinksButton.StartLink || !Doc.UserDoc()["documentLinksButton-fullMenu"] ? <div className="documentButtonBar-button">
-                <DocumentLinksButton links={this.view0.allLinks} View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
+                <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
             </div> : (null)}
             {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button">
                 {this.templateButton}
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 92f6ae028..60327f1bf 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -7,6 +7,7 @@ import { DocServer } from "../DocServer";
 import { AssignAllExtensions } from "../../extensions/General/Extensions";
 import { Networking } from "../Network";
 import { CollectionView } from "./collections/CollectionView";
+import { LinkManager } from "../util/LinkManager";
 
 AssignAllExtensions();
 
@@ -31,5 +32,6 @@ AssignAllExtensions();
     d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000));
     const expires = "expires=" + d.toUTCString();
     document.cookie = `loadtime=${loading};${expires};path=/`;
+    new LinkManager();
     ReactDOM.render(<MainView />, document.getElementById('root'));
 })();
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx
index 57d1a41b6..a6d07374a 100644
--- a/src/client/views/nodes/DocumentLinksButton.tsx
+++ b/src/client/views/nodes/DocumentLinksButton.tsx
@@ -31,7 +31,6 @@ interface DocumentLinksButtonProps {
     AlwaysOn?: boolean;
     InMenu?: boolean;
     StartLink?: boolean;
-    links: Doc[];
 }
 @observer
 export class DocumentLinksButton extends React.Component<DocumentLinksButtonProps, {}> {
@@ -225,7 +224,6 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
         }
     }));
 
-
     @action clearLinks() {
         DocumentLinksButton.StartLink = undefined;
         DocumentLinksButton.StartLinkView = undefined;
@@ -233,15 +231,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
     @computed get filteredLinks() {
         const results = [] as Doc[];
-        Array.from(new Set<Doc>(this.props.links)).forEach(link => {
-            if (!DocUtils.FilterDocs([link], this.props.View.props.docFilters(), []).length) {
-                if (DocUtils.FilterDocs([link.anchor2 as Doc], this.props.View.props.docFilters(), []).length) {
-                    results.push(link);
-                }
-                if (DocUtils.FilterDocs([link.anchor1 as Doc], this.props.View.props.docFilters(), []).length) {
-                    results.push(link);
-                }
-            } else results.push(link);
+        const filters = this.props.View.props.docFilters();
+        Array.from(new Set<Doc>(this.props.View.allLinks)).forEach(link => {
+            if (DocUtils.FilterDocs([link], filters, []).length ||
+                DocUtils.FilterDocs([link.anchor2 as Doc], filters, []).length ||
+                DocUtils.FilterDocs([link.anchor1 as Doc], filters, []).length) {
+                results.push(link);
+            }
         });
         return results;
     }
@@ -296,12 +292,12 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp
 
         return !Array.from(this.filteredLinks).length && !this.props.AlwaysOn ? (null) :
             this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ?
-                <Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
+                <Tooltip title={<div className="dash-tooltip">{title}</div>}>
                     {this.linkButtonInner}
                 </Tooltip>
                 :
                 !DocumentLinksButton.LinkEditorDocView && !this.props.InMenu ?
-                    <Tooltip title={<><div className="dash-tooltip">{title}</div></>}>
+                    <Tooltip title={<div className="dash-tooltip">{title}</div>}>
                         {this.linkButtonInner}
                     </Tooltip>
                     : this.linkButtonInner;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index aff0efdc7..df769a407 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -795,7 +795,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 layoutKey={this.finalLayoutKey} />
             {this.layoutDoc.hideAllLinks ? (null) : this.allLinkEndpoints}
             {this.hideLinkButton ? (null) :
-                <DocumentLinksButton View={this.props.DocumentView()} links={this.allLinks} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
+                <DocumentLinksButton View={this.props.DocumentView()} Offset={[this.topMost ? 0 : -15, undefined, undefined, this.topMost ? 10 : -20]} />}
 
             {audioView}
         </div>;
@@ -814,7 +814,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         TraceMobx();
         if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
         if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
-
         // need to use allLinks for RTF since embedded linked text anchors are not rendered with DocumentViews.  All other documents render their anchors with nested DocumentViews so we just need to render the directLinks here
         const filtered = DocUtils.FilterDocs(this.rootDoc.type === DocumentType.RTF ? this.allLinks : this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden);
         return filtered.map((link, i) =>
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 999ccf5f6..ec97a11e4 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -23,6 +23,7 @@ import { ViewBoxAnnotatableComponent } from "../DocComponent";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./ScreenshotBox.scss";
 import { VideoBox } from "./VideoBox";
+import { TraceMobx } from "../../../fields/util";
 declare class MediaRecorder {
     constructor(e: any, options?: any);  // whatever MediaRecorder has
 }
@@ -133,6 +134,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
     }
     contentFunc = () => [this.content];
     render() {
+        TraceMobx();
         return <div className="videoBox" onContextMenu={this.specificContextMenu} style={{ width: "100%", height: "100%" }} >
             <div className="videoBox-viewer" >
                 <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 5fd897629..9b14ca390 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,18 +1,18 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, ObservableMap } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from 'request-promise';
-import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, StrListCast } from "../../../fields/Doc";
+import { Doc, DocListCast } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
 import { InkTool } from "../../../fields/InkField";
 import { makeInterface } from "../../../fields/Schema";
 import { Cast, NumCast, StrCast } from "../../../fields/Types";
-import { VideoField, AudioField, nullAudio } from "../../../fields/URLField";
+import { AudioField, nullAudio, VideoField } from "../../../fields/URLField";
 import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils } from "../../../Utils";
 import { Docs, DocUtils } from "../../documents/Documents";
 import { Networking } from "../../Network";
+import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 import { SelectionManager } from "../../util/SelectionManager";
 import { SnappingManager } from "../../util/SnappingManager";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
@@ -24,10 +24,8 @@ import { DocumentDecorations } from "../DocumentDecorations";
 import { MarqueeAnnotator } from "../MarqueeAnnotator";
 import { StyleProp } from "../StyleProvider";
 import { FieldView, FieldViewProps } from './FieldView';
-import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment";
 import { LinkDocPreview } from "./LinkDocPreview";
 import "./VideoBox.scss";
-import { CurrentUserUtils } from "../../util/CurrentUserUtils";
 const path = require('path');
 
 type VideoDocument = makeInterface<[typeof documentSchema]>;
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 953d96ffa..d0ccce9cf 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -85,6 +85,7 @@ export const DataSym = Symbol("Data");
 export const LayoutSym = Symbol("Layout");
 export const FieldsSym = Symbol("Fields");
 export const AclSym = Symbol("Acl");
+export const DirectLinksSym = Symbol("DirectLinks");
 export const AclUnset = Symbol("AclUnset");
 export const AclPrivate = Symbol("AclOwnerOnly");
 export const AclReadonly = Symbol("AclReadOnly");
@@ -185,6 +186,7 @@ export class Doc extends RefField {
     @observable private ___fields: any = {};
     @observable private ___fieldKeys: any = {};
     @observable public [AclSym]: { [key: string]: symbol };
+    @observable public [DirectLinksSym]: Set<Doc> = new Set();
 
     private [UpdatingFromServer]: boolean = false;
     private [ForceServerWrite]: boolean = false;
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 631cb7160..6c9c9d45c 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -19,12 +19,11 @@ function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
 }
 
-const tracing = false;
+const tracing = true;
 export function TraceMobx() {
     tracing && trace();
 }
 
-
 export interface GetterResult {
     value: FieldResult;
     shouldReturn?: boolean;
-- 
cgit v1.2.3-70-g09d2


From 36028677c61be7ab5937e864e881fa91a3b82d8c Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 26 Mar 2021 13:22:13 -0400
Subject: extracted AudioWaveform out of AudioBox in order to use in
 CollectionStackedTimelineView to share between audioBox and videoBox.

---
 src/client/util/DictationManager.ts                |  2 +-
 src/client/util/DragManager.ts                     |  2 +-
 src/client/util/LinkManager.ts                     | 20 +++---
 src/client/views/AudioWaveform.scss                | 17 +++++
 src/client/views/AudioWaveform.tsx                 | 61 +++++++++++++++++
 src/client/views/DocumentDecorations.tsx           |  2 +-
 src/client/views/SidebarAnnos.tsx                  |  2 +-
 .../collections/CollectionStackedTimeline.scss     |  7 ++
 .../collections/CollectionStackedTimeline.tsx      | 80 +++++++++++++---------
 src/client/views/nodes/AudioBox.scss               | 18 -----
 src/client/views/nodes/AudioBox.tsx                | 41 +----------
 src/client/views/nodes/DocumentView.tsx            |  8 +--
 src/client/views/nodes/ScreenshotBox.tsx           |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  9 +--
 .../views/nodes/formattedText/FormattedTextBox.tsx |  2 +-
 src/server/DashUploadUtils.ts                      |  5 +-
 16 files changed, 165 insertions(+), 113 deletions(-)
 create mode 100644 src/client/views/AudioWaveform.scss
 create mode 100644 src/client/views/AudioWaveform.tsx

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index f00cdce1e..a93b2f573 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -89,7 +89,7 @@ export namespace DictationManager {
         export const listen = async (options?: Partial<ListeningOptions>) => {
             if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options));
             return innerListen(options);
-        }
+        };
         const innerListen = async (options?: Partial<ListeningOptions>) => {
             let results: string | undefined;
 
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 1809a77bf..38d0ecaa6 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -388,7 +388,7 @@ export namespace DragManager {
             if (dragElement !== ele) {
                 const children = [Array.from(ele.children), Array.from(dragElement.children)];
                 while (children[0].length) {
-                    const childs = [children[0].pop(), children[1].pop()]
+                    const childs = [children[0].pop(), children[1].pop()];
                     if (childs[0]?.children) {
                         children[0].push(...Array.from(childs[0].children));
                         children[1].push(...Array.from(childs[1]!.children));
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 159011516..3c3d5c3b8 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -33,7 +33,7 @@ export class LinkManager {
     constructor() {
         LinkManager._instance = this;
         setTimeout(() => {
-            LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase as Doc)];
+            LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase)];
             const addLinkToDoc = (link: Doc): any => {
                 const a1 = link?.anchor1;
                 const a2 = link?.anchor2;
@@ -43,7 +43,7 @@ export class LinkManager {
                     Doc.GetProto(a2)[DirectLinksSym].add(link);
                     Doc.GetProto(link)[DirectLinksSym].add(link);
                 }
-            }
+            };
             const remLinkFromDoc = (link: Doc): any => {
                 const a1 = link?.anchor1;
                 const a2 = link?.anchor2;
@@ -53,23 +53,23 @@ export class LinkManager {
                     Doc.GetProto(a2)[DirectLinksSym].delete(link);
                     Doc.GetProto(link)[DirectLinksSym].delete(link);
                 }
-            }
+            };
             const watchUserLinks = (userLinks: List<Doc>) => {
                 const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field;  // see List.ts.  data structure is not a simple list of Docs, but a list of ProxyField/Fields
                 observe(userLinks, change => {
-                    switch (change.type) {
+                    switch (change.type as any) {
                         case "splice":
                             (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
                             (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
                             break;
-                        case "update": let oldValue = change.oldValue;
+                        case "update": //let oldValue = change.oldValue;
                     }
                 }, true);
-            }
+            };
             observe(LinkManager.userDocs, change => {
-                switch (change.type) {
+                switch (change.type as any) {
                     case "splice": (change as any).added.forEach(watchUserLinks); break;
-                    case "update": let oldValue = change.oldValue;
+                    case "update": //let oldValue = change.oldValue;
                 }
             }, true);
         });
@@ -82,7 +82,9 @@ export class LinkManager {
     public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); }
 
     public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor
-    public getAllDirectLinks(anchor: Doc): Doc[] { return Array.from(Doc.GetProto(anchor)[DirectLinksSym]); } // finds all links that contain the given anchor
+    public getAllDirectLinks(anchor: Doc): Doc[] {
+        return Array.from(Doc.GetProto(anchor)[DirectLinksSym]);
+    } // finds all links that contain the given anchor
     public getAllLinks(): Doc[] { return []; }//this.allLinks(); }
 
     // allLinks = computedFn(function allLinks(this: any): Doc[] {
diff --git a/src/client/views/AudioWaveform.scss b/src/client/views/AudioWaveform.scss
new file mode 100644
index 000000000..e20434a25
--- /dev/null
+++ b/src/client/views/AudioWaveform.scss
@@ -0,0 +1,17 @@
+.audioWaveform  {
+    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;
+    }
+}
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
new file mode 100644
index 000000000..7ff9c1071
--- /dev/null
+++ b/src/client/views/AudioWaveform.tsx
@@ -0,0 +1,61 @@
+import React = require("react");
+import axios from "axios";
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import Waveform from "react-audio-waveform";
+import { Doc } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { listSpec } from "../../fields/Schema";
+import { Cast } from "../../fields/Types";
+import { numberRange } from "../../Utils";
+import "./AudioWaveform.scss";
+
+export interface AudioWaveformProps {
+    duration: number;
+    mediaPath: string;
+    dataDoc: Doc;
+    PanelHeight: () => number;
+}
+
+@observer
+export class AudioWaveform extends React.Component<AudioWaveformProps> {
+    public static NUMBER_OF_BUCKETS = 100;
+    @computed get _waveHeight() { return Math.max(50, this.props.PanelHeight()); }
+    componentDidMount() {
+        const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []);
+        if (!audioBuckets.length) {
+            this.props.dataDoc.audioBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data
+            setTimeout(this.createWaveformBuckets);
+        }
+    }
+    // decodes the audio file into peaks for generating the waveform
+    createWaveformBuckets = async () => {
+        axios({ url: this.props.mediaPath, responseType: "arraybuffer" })
+            .then(response => {
+                const context = new window.AudioContext();
+                context.decodeAudioData(response.data,
+                    action(buffer => {
+                        const decodedAudioData = buffer.getChannelData(0);
+                        const bucketDataSize = Math.floor(decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS);
+                        const brange = Array.from(Array(bucketDataSize));
+                        this.props.dataDoc.audioBuckets = new List<number>(
+                            numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map((i: number) =>
+                                brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2));
+                    }));
+            });
+    }
+
+    render() {
+        const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []);
+        return <div className="audioWaveform">
+            <Waveform
+                color={"darkblue"}
+                height={this._waveHeight}
+                barWidth={0.1}
+                pos={this.props.duration}
+                duration={this.props.duration}
+                peaks={audioBuckets.length === AudioWaveform.NUMBER_OF_BUCKETS ? audioBuckets : undefined}
+                progressColor={"blue"} />
+        </div>;
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index da50ba4d8..a92891ee5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -94,7 +94,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
         setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => {
             !this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith("#") ? this.selectionTitle : this._titleControlString);
             this._edtingTitle = true;
-            this._keyinput.current && setTimeout(this._keyinput.current!.focus);
+            this._keyinput.current && setTimeout(this._keyinput.current.focus);
         }));
     }
 
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 87887483f..6b0b928b3 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -79,7 +79,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
 
     sidebarStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
         if (property === StyleProp.ShowTitle) return StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
-        return this.props.styleProvider?.(doc, props, property)
+        return this.props.styleProvider?.(doc, props, property);
     }
     render() {
         const renderTag = (tag: string) => {
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index cc56831f3..2b9f3d782 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -58,4 +58,11 @@
             left: 0;
         }
     }
+
+    .collectionStackedTimeline-waveform {
+        position: absolute; 
+        width: 100%; 
+        top: 0; 
+        left: 0;
+    }
 }
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 66b74277b..2191a3df5 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,26 +1,26 @@
 import React = require("react");
-import { action, computed, IReactionDisposer, observable, runInAction, reaction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast } from "../../../fields/Doc";
+import { computedFn } from "mobx-utils";
+import { Doc, DocListCast } from "../../../fields/Doc";
 import { Id } from "../../../fields/FieldSymbols";
 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, StopEvent, returnOne } from "../../../Utils";
+import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
+import { LinkManager } from "../../util/LinkManager";
 import { Scripting } from "../../util/Scripting";
 import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
 import { undoBatch } from "../../util/UndoManager";
+import { AudioWaveform } from "../AudioWaveform";
 import { CollectionSubView } from "../collections/CollectionSubView";
-import { DocumentView, DocAfterFocusFunc, DocFocusFunc, DocumentViewProps } from "../nodes/DocumentView";
+import { LightboxView } from "../LightboxView";
+import { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
 import { LabelBox } from "../nodes/LabelBox";
 import "./CollectionStackedTimeline.scss";
-import { Transform } from "../../util/Transform";
-import { LinkManager } from "../../util/LinkManager";
-import { computedFn } from "mobx-utils";
-import { LightboxView } from "../LightboxView";
-import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
 
 type PanZoomDocument = makeInterface<[]>;
 const PanZoomDocument = makeInterface();
@@ -35,6 +35,7 @@ export type CollectionStackedTimelineProps = {
     isChildActive: () => boolean;
     startTag: string;
     endTag: string;
+    mediaPath: string;
 };
 
 @observer
@@ -232,11 +233,42 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
         placed.push({ anchorStartTime: x1, anchorEndTime: x2, level });
         return level;
     }
+
+    dictationHeight = () => this.props.PanelHeight() / 3;
+    timelineContentHeight = () => this.props.PanelHeight() * 2 / 3;
+    @computed get renderDictation() {
+        const dictation = Cast(this.dataDoc[this.props.fieldKey.replace("annotations", "dictation")], Doc, null);
+        return !dictation ? (null) : <div style={{ position: "absolute", height: this.dictationHeight(), top: this.timelineContentHeight(), background: "tan" }}>
+            <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+                Document={dictation}
+                PanelHeight={this.dictationHeight}
+                isAnnotationOverlay={true}
+                select={emptyFunction}
+                active={returnFalse}
+                scaling={returnOne}
+                xMargin={25}
+                yMargin={10}
+                whenActiveChanged={emptyFunction}
+                removeDocument={returnFalse}
+                moveDocument={returnFalse}
+                addDocument={returnFalse}
+                CollectionView={undefined}
+                renderDepth={this.props.renderDepth + 1}>
+            </DocumentView>
+        </div>;
+    }
+    @computed get renderAudioWaveform() {
+        return !this.props.mediaPath ? (null) : <div style={{ position: "absolute", width: "100%", top: 0, left: 0 }}>
+            <AudioWaveform
+                duration={this.duration}
+                mediaPath={this.props.mediaPath}
+                dataDoc={this.dataDoc}
+                PanelHeight={this.timelineContentHeight} />
+        </div>;
+    }
     currentTimecode = () => this.currentTime;
     render() {
         const timelineContentWidth = this.props.PanelWidth();
-        const timelineContentHeight = this.props.PanelHeight() * 2 / 3;
-        const dictationHeight = this.props.PanelHeight() / 3;
         const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = [];
         const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor }));
         const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
@@ -247,11 +279,11 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                 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 top = d.level / maxLevel * this.timelineContentHeight();
                 const timespan = end - start;
                 return this.props.Document.hideAnchors ? (null) :
                     <div className={"collectionStackedTimeline-marker-timeline"} key={d.anchor[Id]}
-                        style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${timelineContentHeight / maxLevel}px` }}
+                        style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${this.timelineContentHeight() / maxLevel}px` }}
                         onClick={e => { this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} >
                         <StackedTimelineAnchor {...this.props}
                             mark={d.anchor}
@@ -260,7 +292,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                             left={left}
                             top={top}
                             width={timelineContentWidth * timespan / this.duration}
-                            height={timelineContentHeight / maxLevel}
+                            height={this.timelineContentHeight() / maxLevel}
                             toTimeline={this.toTimeline}
                             layoutDoc={this.layoutDoc}
                             currentTimecode={this.currentTimecode}
@@ -270,24 +302,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                     </div>;
             })}
             {this.selectionContainer}
-            <div style={{ position: "absolute", height: dictationHeight, top: timelineContentHeight, background: "tan" }}>
-                <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
-                    Document={this.dataDoc[this.props.fieldKey.replace("annotations", "dictation")]}
-                    PanelHeight={() => dictationHeight}
-                    isAnnotationOverlay={true}
-                    select={emptyFunction}
-                    active={returnFalse}
-                    scaling={returnOne}
-                    xMargin={25}
-                    yMargin={10}
-                    whenActiveChanged={emptyFunction}
-                    removeDocument={returnFalse}
-                    moveDocument={returnFalse}
-                    addDocument={returnFalse}
-                    CollectionView={undefined}
-                    renderDepth={this.props.renderDepth + 1}>
-                </DocumentView>
-            </div>
+            {this.renderAudioWaveform}
+            {this.renderDictation}
 
             <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 fc881ca25..3fcb024df 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -144,24 +144,6 @@
                 border-radius: 3px;
                 z-index: 1000;
                 overflow: hidden;
-
-                .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-total-time,
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 2f7a6cfd8..06a27c22a 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,18 +1,15 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import axios from "axios";
 import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import Waveform from "react-audio-waveform";
 import { DateField } from "../../../fields/DateField";
 import { Doc, DocListCast, Opt } from "../../../fields/Doc";
 import { documentSchema } from "../../../fields/documentSchemas";
-import { List } from "../../../fields/List";
-import { createSchema, listSpec, makeInterface } from "../../../fields/Schema";
+import { makeInterface } from "../../../fields/Schema";
 import { ComputedField } from "../../../fields/ScriptField";
 import { Cast, NumCast } from "../../../fields/Types";
 import { AudioField, nullAudio } from "../../../fields/URLField";
-import { emptyFunction, formatTime, numberRange, Utils } from "../../../Utils";
+import { emptyFunction, formatTime, Utils } from "../../../Utils";
 import { DocUtils } from "../../documents/Documents";
 import { Networking } from "../../Network";
 import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -35,7 +32,6 @@ const AudioDocument = makeInterface(documentSchema);
 export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
     public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
     public static Enabled = false;
-    public static NUMBER_OF_BUCKETS = 100;
     static playheadWidth = 30; // width of playhead
     static heightPercent = 80; // height of timeline in percent of height of audioBox.
     static Instance: AudioBox;
@@ -295,35 +291,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
         e.stopPropagation();
     }
 
-    // returns the audio waveform
-    @computed get waveform() {
-        const audioBuckets = Cast(this.dataDoc.audioBuckets, listSpec("number"), []);
-        !audioBuckets.length && setTimeout(() => this.createWaveformBuckets());
-        return <Waveform
-            color={"darkblue"}
-            height={this._waveHeight}
-            barWidth={0.1}
-            pos={this.duration}
-            duration={this.duration}
-            peaks={audioBuckets.length === AudioBox.NUMBER_OF_BUCKETS ? audioBuckets : undefined}
-            progressColor={"blue"} />;
-    }
-
-    // decodes the audio file into peaks for generating the waveform
-    createWaveformBuckets = async () => {
-        axios({ url: this.path, responseType: "arraybuffer" })
-            .then(response => (new (window.AudioContext)()).decodeAudioData(response.data,
-                action(buffer => {
-                    const decodedAudioData = buffer.getChannelData(0);
-                    const bucketDataSize = Math.floor(decodedAudioData.length / AudioBox.NUMBER_OF_BUCKETS);
-                    const brange = Array.from(Array(bucketDataSize));
-                    this.dataDoc.audioBuckets = new List<number>(
-                        numberRange(AudioBox.NUMBER_OF_BUCKETS).map(i =>
-                            brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2));
-                }))
-            );
-    }
-
     playing = () => this.mediaState === "playing";
     playLink = (link: Doc) => {
         const stack = this._stackedTimeline.current;
@@ -353,6 +320,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
     @computed get renderTimeline() {
         return <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
             fieldKey={this.annotationKey}
+            mediaPath={this.path}
             renderDepth={this.props.renderDepth + 1}
             startTag={"_timecodeToShow" /* audioStart */}
             endTag={"_timecodeToHide" /* audioEnd */}
@@ -407,9 +375,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
                     <div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} >
                         <div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.mediaState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.mediaState === "paused" ? "play" : "pause"} size={"1x"} /></div>
                         <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.audio}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index df769a407..8a2a755ac 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1102,16 +1102,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
 
     render() {
         TraceMobx();
-        const xshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
-        const yshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
+        const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+        const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
         return (<div className="contentFittingDocumentView">
             {!this.props.Document || !this.props.PanelWidth() ? (null) : (
                 <div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
                     style={{
                         position: this.props.Document.isInkMask ? "absolute" : undefined,
                         transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
-                        width: xshift ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
-                        height: yshift ?? (this.fitWidth ? `${this.panelHeight}px` :
+                        width: xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
+                        height: yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
                             `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
                     }}>
                     <DocumentViewInternal {...this.props}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 6c019d6f2..aba7be68d 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -146,7 +146,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
     }
     contentFunc = () => [this.content];
     videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 1) * this.props.PanelWidth();
-    formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight())
+    formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
     render() {
         TraceMobx();
         return <div className="videoBox" onContextMenu={this.specificContextMenu} style={{ width: "100%", height: "100%" }} >
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 4e03589d6..da05b0c13 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -297,9 +297,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
 
     // returns the path of the audio file
     @computed get audiopath() {
-        const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField);
-        const path = (field instanceof AudioField) ? field.url.href : "";
-        return path === nullAudio ? "" : path;
+        const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null);
+        const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
+        return field?.url.href ?? vfield?.url.href ?? "";
     }
     // ref for updating time
     _audioPlayer: HTMLAudioElement | null = null;
@@ -322,7 +322,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
                         <source src={field.url.href} type="video/mp4" />
                         Not supported.
                     </video>
-                    {!this.audiopath ? (null) :
+                    {!this.audiopath || this.audiopath === field.url.href ? (null) :
                         <audio ref={this.setAudioRef} className={`audiobox-control${this.active() ? "-interactive" : ""}`}>
                             <source src={this.audiopath} type="audio/mpeg" />
                         Not supported.
@@ -510,6 +510,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
         return <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}>
             <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
                 fieldKey={this.annotationKey}
+                mediaPath={this.audiopath}
                 renderDepth={this.props.renderDepth + 1}
                 startTag={"_timecodeToShow" /* videoStart */}
                 endTag={"_timecodeToHide" /* videoEnd */}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index aaf3a938e..abfc63b40 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1153,7 +1153,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
                             setTimeout(func);
                         }
                         else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, "number", null)); // bcz: would be nice to find the next audio tag in the doc and play until that
-                    }
+                    };
                     func();
                 }
             });
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index e2c0e7ac9..ff6b2381c 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -16,7 +16,7 @@ import { resolvedServerUrl } from "./server_Initialization";
 import { AcceptableMedia, Upload } from './SharedMediaTypes';
 import request = require('request-promise');
 const parse = require('pdf-parse');
-var ffmpeg = require("fluent-ffmpeg");
+const ffmpeg = require("fluent-ffmpeg");
 const requestImageSize = require("../client/util/request-image-size");
 
 export enum SizeSuffix {
@@ -75,7 +75,8 @@ export namespace DashUploadUtils {
                 if (format.includes("x-matroska")) {
                     await new Promise(res => ffmpeg(file.path)
                         .videoCodec("copy") // this will copy the data instead of reencode it
-                        .save(file.path.replace(".mkv", ".mp4")).on('end', () => res()));
+                        .save(file.path.replace(".mkv", ".mp4"))
+                        .on('end', res));
                     file.path = file.path.replace(".mkv", ".mp4");
                     format = ".mp4";
                 }
-- 
cgit v1.2.3-70-g09d2


From a1a9803096adde0cd932e703080948ef1d1724f7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 29 Mar 2021 12:22:35 -0400
Subject: fixed doc decorations for dictation.  gave dictation docs a title. 
 minor cleanup.

---
 src/client/views/PropertiesView.tsx                |  2 +-
 .../collections/CollectionStackedTimeline.tsx      |  5 +++--
 .../nodes/CollectionFreeFormDocumentView.scss      |  1 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  1 -
 src/client/views/nodes/DocumentView.tsx            |  4 ++--
 src/client/views/nodes/FieldView.tsx               | 23 +---------------------
 src/client/views/nodes/ScreenshotBox.tsx           |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  2 +-
 8 files changed, 10 insertions(+), 30 deletions(-)

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

diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 73c688029..0b2cdde6f 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -288,7 +288,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
                     addDocument={returnFalse}
                     moveDocument={undefined}
                     removeDocument={returnFalse}
-                    parentActive={() => false}
+                    parentActive={returnFalse}
                     whenActiveChanged={emptyFunction}
                     addDocTab={returnFalse}
                     pinToPres={emptyFunction}
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 73148560a..c0cebf021 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -8,7 +8,7 @@ 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, returnOne, setupMoveUpEvents, StopEvent } from "../../../Utils";
+import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, StopEvent, returnTrue } from "../../../Utils";
 import { Docs } from "../../documents/Documents";
 import { LinkManager } from "../../util/LinkManager";
 import { Scripting } from "../../util/Scripting";
@@ -236,6 +236,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
 
     dictationHeight = () => this.props.PanelHeight() / 3;
     timelineContentHeight = () => this.props.PanelHeight() * 2 / 3;
+    dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight());
     @computed get renderDictation() {
         const dictation = Cast(this.dataDoc[this.props.fieldKey.replace("annotations", "dictation")], Doc, null);
         return !dictation ? (null) : <div style={{ position: "absolute", height: this.dictationHeight(), top: this.timelineContentHeight(), background: "tan" }}>
@@ -244,10 +245,10 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
                 PanelHeight={this.dictationHeight}
                 isAnnotationOverlay={true}
                 select={emptyFunction}
-                active={returnFalse}
                 scaling={returnOne}
                 xMargin={25}
                 yMargin={10}
+                ScreenToLocalTransform={this.dictationScreenToLocalTransform}
                 whenActiveChanged={emptyFunction}
                 removeDocument={returnFalse}
                 moveDocument={returnFalse}
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
index da287649e..724394025 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
@@ -5,4 +5,5 @@
     touch-action: manipulation;
     top: 0;
     left: 0;
+    pointer-events: none;
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 58fb005b5..e96fc088e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -176,7 +176,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 zIndex: this.ZInd,
                 mixBlendMode: StrCast(this.layoutDoc.mixBlendMode) as any,
                 display: this.ZInd === -99 ? "none" : undefined,
-                pointerEvents: "none"
             }} >
 
             {Doc.UserDoc().renderStyle !== "comic" ? (null) :
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 8a2a755ac..80831708d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -293,7 +293,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (e.cancelBubble && this.active) {
             this.removeMoveListeners();
         }
-        else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
+        else if (!e.cancelBubble && (this.active || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
             const touch = me.touchEvent.changedTouches.item(0);
             if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) {
                 if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) {
@@ -530,7 +530,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         if (e.cancelBubble && this.active) {
             document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView)
         }
-        else if (!e.cancelBubble && (this.props.isSelected(true) || this.props.parentActive(true) || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
+        else if (!e.cancelBubble && (this.active || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) {
             if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
                 if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) {
                     document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 465c18309..97f53aac0 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -73,28 +73,7 @@ export class FieldView extends React.Component<FieldViewProps> {
             return <p>{field.date.toLocaleString()}</p>;
         }
         else if (field instanceof Doc) {
-            return <p><b>{field.title && field.title.toString()}</b></p>;
-            //return <p><b>{field.title + " : id= " + field[Id]}</b></p>;
-            // let returnHundred = () => 100;
-            // return (
-            //     <DocumentContentsView Document={field}
-            //         addDocument={undefined}
-            //         addDocTab={this.props.addDocTab}
-            //         removeDocument={undefined}
-            //         ScreenToLocalTransform={Transform.Identity}
-            //         ContentScaling={returnOne}
-            //         PanelWidth={returnHundred}
-            //         PanelHeight={returnHundred}
-            //         renderDepth={0} //TODO Why is renderDepth reset?
-            //         focus={emptyFunction}
-            //         isSelected={this.props.isSelected}
-            //         select={returnFalse}
-            //         layoutKey={"layout"}
-            //         ContainingCollectionView={this.props.ContainingCollectionView}
-            //         parentActive={this.props.active}
-            //         whenActiveChanged={this.props.whenActiveChanged}
-            //         bringToFront={emptyFunction} />
-            // );
+            return <p><b>{field.title?.toString()}</b></p>;
         }
         else if (field instanceof List) {
             return <div> {field.length ? field.map(f => Field.toString(f)).join(", ") : ""}  </div>;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index aba7be68d..c00c79eb9 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -134,7 +134,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
 
     setupDictation = () => {
         if (this.dataDoc[this.fieldKey + "-dictation"]) return;
-        const dictationText = CurrentUserUtils.GetNewTextDoc("",
+        const dictationText = CurrentUserUtils.GetNewTextDoc("dictation",
             NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10,
             NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height));
         dictationText._autoHeight = false;
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index da05b0c13..efcddd7b3 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -564,7 +564,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
             style={{
                 pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
                 borderRadius
-            }} >
+            }} onWheel={e => { e.stopPropagation(); e.preventDefault(); }}>
             <div className="videoBox-viewer" onPointerDown={this.marqueeDown} >
                 <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}
-- 
cgit v1.2.3-70-g09d2

