From 553fb2a11aa638c35192a1a2f7da99729867e542 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Thu, 13 Feb 2020 22:30:32 -0500
Subject: starting a broad cleanup of code.  trying to make standard doc field
 names be more regular

---
 src/client/views/collections/CollectionLinearView.tsx | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 67062ae41..7eb316cf0 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -38,8 +38,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
 
     componentDidMount() {
         // is there any reason this needs to exist? -syip.  yes, it handles autoHeight for stacking views (masonry isn't yet supported).
-        this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.isExpanded ? 1 : 0),
-            () => this.props.Document._width = 5 + (this.props.Document.isExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
+        this._widthDisposer = reaction(() => this.props.Document[HeightSym]() + this.childDocs.length + (this.props.Document.linearViewIsExpanded ? 1 : 0),
+            () => this.props.Document._width = 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10),
             { fireImmediately: true }
         );
 
@@ -84,8 +84,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         const guid = Utils.GenerateGuid();
         return <div className="collectionLinearView-outer">
             <div className="collectionLinearView" ref={this.createDashEventsTarget} >
-                <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.isExpanded)} ref={this.addMenuToggle}
-                    onChange={action((e: any) => this.props.Document.isExpanded = this.addMenuToggle.current!.checked)} />
+                <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
+                    onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
                 <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
 
                 <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document._width, 25) }}>
@@ -97,7 +97,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
                         return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
                             style={{
                                 width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize,
-                                height: nested && pair.layout.isExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
+                                height: nested && pair.layout.linearViewIsExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
                             }}  >
                             <DocumentView
                                 Document={pair.layout}
-- 
cgit v1.2.3-70-g09d2


From b42e1e1e2da941955d7751b6003f18fecd5f2f8d Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 16 Feb 2020 12:22:10 -0500
Subject: collection sub view and google authentication manager cleanup,
 deleteAssets route added

---
 src/client/apis/GoogleAuthenticationManager.tsx    |  46 ++--
 .../views/collections/CollectionCarouselView.tsx   |  28 +--
 .../views/collections/CollectionLinearView.tsx     |   2 +-
 .../collections/CollectionMasonryViewFieldRow.tsx  |   2 +-
 .../views/collections/CollectionSchemaView.tsx     |   7 +-
 .../views/collections/CollectionStackingView.tsx   |  12 +-
 .../CollectionStackingViewFieldColumn.tsx          |   2 +-
 src/client/views/collections/CollectionSubView.tsx | 264 +++++++++++----------
 .../views/collections/CollectionTreeView.tsx       |   6 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  10 +-
 .../CollectionMulticolumnView.tsx                  |   6 +-
 .../CollectionMultirowView.tsx                     |   6 +-
 src/server/ApiManagers/DeleteManager.ts            |  31 +--
 13 files changed, 213 insertions(+), 209 deletions(-)

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

diff --git a/src/client/apis/GoogleAuthenticationManager.tsx b/src/client/apis/GoogleAuthenticationManager.tsx
index ce1277667..417dc3c3b 100644
--- a/src/client/apis/GoogleAuthenticationManager.tsx
+++ b/src/client/apis/GoogleAuthenticationManager.tsx
@@ -12,8 +12,8 @@ const prompt = "Paste authorization code here...";
 @observer
 export default class GoogleAuthenticationManager extends React.Component<{}> {
     public static Instance: GoogleAuthenticationManager;
-    @observable private openState = false;
     private authenticationLink: Opt<string> = undefined;
+    @observable private openState = false;
     @observable private authenticationCode: Opt<string> = undefined;
     @observable private clickedState = false;
     @observable private success: Opt<boolean> = undefined;
@@ -39,24 +39,18 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
                 const disposer = reaction(
                     () => this.authenticationCode,
                     async authenticationCode => {
-                        if (!authenticationCode) {
-                            return;
+                        if (authenticationCode) {
+                            disposer();
+                            const { access_token, avatar, name } = await Networking.PostToServer("/writeGoogleAccessToken", { authenticationCode });
+                            runInAction(() => {
+                                this.avatar = avatar;
+                                this.username = name;
+                                this.hasBeenClicked = false;
+                                this.success = false;
+                            });
+                            this.beginFadeout();
+                            resolve(access_token);
                         }
-                        const { access_token, avatar, name } = await Networking.PostToServer(
-                            "/writeGoogleAccessToken",
-                            { authenticationCode }
-                        );
-                        runInAction(() => {
-                            this.avatar = avatar;
-                            this.username = name;
-                        });
-                        this.beginFadeout();
-                        disposer();
-                        resolve(access_token);
-                        action(() => {
-                            this.hasBeenClicked = false;
-                            this.success = false;
-                        });
                     }
                 );
             });
@@ -86,26 +80,20 @@ export default class GoogleAuthenticationManager extends React.Component<{}> {
         GoogleAuthenticationManager.Instance = this;
     }
 
-    private handleClick = () => {
-        window.open(this.authenticationLink);
-        setTimeout(() => this.hasBeenClicked = true, 500);
-    }
-
-    private handlePaste = action((e: React.ChangeEvent<HTMLInputElement>) => {
-        this.authenticationCode = e.currentTarget.value;
-    });
-
     private get renderPrompt() {
         return (
             <div className={'authorize-container'}>
                 {this.displayLauncher ? <button
                     className={"dispatch"}
-                    onClick={this.handleClick}
+                    onClick={() => {
+                        window.open(this.authenticationLink);
+                        setTimeout(() => this.hasBeenClicked = true, 500);
+                    }}
                     style={{ marginBottom: this.clickedState ? 15 : 0 }}
                 >Authorize a Google account...</button> : (null)}
                 {this.clickedState ? <input
                     className={'paste-target'}
-                    onChange={this.handlePaste}
+                    onChange={action(e => this.authenticationCode = e.currentTarget.value)}
                     placeholder={prompt}
                 /> : (null)}
                 {this.avatar ? <img
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index 8167416c6..a0cb1fe19 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -27,7 +27,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer?.();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
         }
     }
 
@@ -41,21 +41,21 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
     }
 
     panelHeight = () => this.props.PanelHeight() - 50;
-    @computed get content() { 
+    @computed get content() {
         const index = NumCast(this.layoutDoc._itemIndex);
         return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) :
-        <>
-            <div className="collectionCarouselView-image" key="image">
-                <ContentFittingDocumentView {...this.props}
-                    Document={this.childLayoutPairs[index].layout}
-                    DataDocument={this.childLayoutPairs[index].data}
-                    PanelHeight={this.panelHeight}
-                    getTransform={this.props.ScreenToLocalTransform} />
-            </div>
-            <div className="collectionCarouselView-caption" key="caption" style={{ background: this.props.backgroundColor?.(this.props.Document) }}>
-                <FormattedTextBox key={index} {...this.props} Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
-            </div>
-        </>
+            <>
+                <div className="collectionCarouselView-image" key="image">
+                    <ContentFittingDocumentView {...this.props}
+                        Document={this.childLayoutPairs[index].layout}
+                        DataDocument={this.childLayoutPairs[index].data}
+                        PanelHeight={this.panelHeight}
+                        getTransform={this.props.ScreenToLocalTransform} />
+                </div>
+                <div className="collectionCarouselView-caption" key="caption" style={{ background: this.props.backgroundColor?.(this.props.Document) }}>
+                    <FormattedTextBox key={index} {...this.props} Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox>
+                </div>
+            </>;
     }
     @computed get buttons() {
         return <>
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 7eb316cf0..9bbc9f1b6 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -67,7 +67,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view
         this._dropDisposer && this._dropDisposer();
         if (ele) {
-            this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
         }
     }
 
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index e25a2f5eb..f3d512a97 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -80,7 +80,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
             const key = StrCast(this.props.parent.props.Document.sectionFilter);
             const castedValue = this.getValue(this._heading);
             de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue);
-            this.props.parent.drop(e, de);
+            this.props.parent.onInternalDrop(e, de);
             e.stopPropagation();
         }
     });
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index fa8be5177..c422c38f1 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -14,7 +14,6 @@ import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../../new_fields/ScriptField";
 import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { Docs, DocumentOptions } from "../../documents/Documents";
-import { DocumentType } from "../../documents/DocumentTypes";
 import { Gateway } from "../../northstar/manager/Gateway";
 import { CompileScript, Transformer, ts } from "../../util/Scripting";
 import { Transform } from "../../util/Transform";
@@ -175,7 +174,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
             moveDocument={this.props.moveDocument}
             ScreenToLocalTransform={this.props.ScreenToLocalTransform}
             active={this.props.active}
-            onDrop={this.onDrop}
+            onDrop={this.onExternalDrop}
             addDocTab={this.props.addDocTab}
             pinToPres={this.props.pinToPres}
             isSelected={this.props.isSelected}
@@ -199,7 +198,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
 
     render() {
         return <div className="collectionSchemaView-container">
-            <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onDrop(e, {})} ref={this.createTarget}>
+            <div className="collectionSchemaView-tableContainer" onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}>
                 {this.schemaTable}
             </div>
             {this.dividerDragger}
@@ -692,7 +691,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
     onContextMenu = (e: React.MouseEvent): void => {
         if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
             // ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB, icon: "table" });
-            ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" })
+            ContextMenu.Instance.addItem({ description: "Toggle text wrapping", event: this.toggleTextwrap, icon: "table" });
         }
     }
 
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 055035b3e..a9cefae6a 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -189,7 +189,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     getDocHeight(d?: Doc) {
         if (!d) return 0;
-        let layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
+        const layoutDoc = Doc.Layout(d, this.props.childLayoutTemplate?.());
         const nw = NumCast(layoutDoc._nativeWidth);
         const nh = NumCast(layoutDoc._nativeHeight);
         let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1);
@@ -234,7 +234,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         const where = [de.x, de.y];
         let targInd = -1;
         let plusOne = 0;
@@ -248,7 +248,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                     plusOne = where[axis] > (pos[axis] + pos1[axis]) / 2 ? 1 : 0;
                 }
             });
-            if (super.drop(e, de)) {
+            if (super.onInternalDrop(e, de)) {
                 const newDoc = de.complete.docDragData.droppedDocuments[0];
                 const docs = this.childDocList;
                 if (docs) {
@@ -264,7 +264,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
     @undoBatch
     @action
-    onDrop = async (e: React.DragEvent): Promise<void> => {
+    onExternalDrop = async (e: React.DragEvent): Promise<void> => {
         const where = [e.clientX, e.clientY];
         let targInd = -1;
         this._docXfs.map((cd, i) => {
@@ -274,7 +274,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                 targInd = i;
             }
         });
-        super.onDrop(e, {}, () => {
+        super.onExternalDrop(e, {}, () => {
             if (targInd !== -1) {
                 const newDoc = this.childDocs[this.childDocs.length - 1];
                 const docs = this.childDocList;
@@ -405,7 +405,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
                         transformOrigin: "top left",
                     }}
                     onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)}
-                    onDrop={this.onDrop.bind(this)}
+                    onDrop={this.onExternalDrop.bind(this)}
                     onContextMenu={this.onContextMenu}
                     onWheel={e => this.props.active() && e.stopPropagation()} >
                     {this.renderedSections}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 2ff477c57..87c35679f 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -71,7 +71,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
             else {
                 de.complete.docDragData.droppedDocuments.forEach(d => d[key] = undefined);
             }
-            this.props.parent.drop(e, de);
+            this.props.parent.onInternalDrop(e, de);
             e.stopPropagation();
         }
     });
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e80e1c802..042385dcd 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -57,7 +57,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             this.gestureDisposer?.();
             this.multiTouchDisposer?.();
             if (ele) {
-                this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+                this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
                 this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this));
                 this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this));
             }
@@ -156,7 +156,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         @action
-        protected drop(e: Event, de: DragManager.DropEvent): boolean {
+        protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean {
             const docDragData = de.complete.docDragData;
             (this.props.Document.dropConverter instanceof ScriptField) &&
                 this.props.Document.dropConverter.script.run({ dragData: docDragData }); /// bcz: check this 
@@ -195,158 +195,172 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
 
         @undoBatch
         @action
-        protected async onDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
+        protected async onExternalDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => void) {
             if (e.ctrlKey) {
                 e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
                 return;
             }
-            const html = e.dataTransfer.getData("text/html");
-            const text = e.dataTransfer.getData("text/plain");
+
+            const { dataTransfer } = e;
+            const html = dataTransfer.getData("text/html");
+            const text = dataTransfer.getData("text/plain");
 
             if (text && text.startsWith("<div")) {
                 return;
             }
+
             e.stopPropagation();
             e.preventDefault();
-
-            if (html && FormattedTextBox.IsFragment(html)) {
-                const href = FormattedTextBox.GetHref(html);
-                if (href) {
-                    const docid = FormattedTextBox.GetDocFromUrl(href);
-                    if (docid) { // prosemirror text containing link to dash document
-                        DocServer.GetRefField(docid).then(f => {
-                            if (f instanceof Doc) {
-                                if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
-                                (f instanceof Doc) && this.props.addDocument(f);
-                            }
-                        });
-                    } else {
-                        this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
-                    }
-                } else if (text) {
-                    this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
-                }
+            const { addDocument } = this.props;
+            if (!addDocument) {
+                alert("this.props.addDocument does not exist. Aborting drop operation.");
                 return;
             }
-            if (html && !html.startsWith("<a")) {
-                const tags = html.split("<");
-                if (tags[0] === "") tags.splice(0, 1);
-                const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
-                if (img) {
-                    const split = img.split("src=\"")[1].split("\"")[0];
-                    let source = split;
-                    if (split.startsWith("data:image") && split.includes("base64")) {
-                        const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
-                        source = Utils.prepend(accessPaths.agnostic.client);
+
+            if (html) {
+                if (FormattedTextBox.IsFragment(html)) {
+                    const href = FormattedTextBox.GetHref(html);
+                    if (href) {
+                        const docid = FormattedTextBox.GetDocFromUrl(href);
+                        if (docid) { // prosemirror text containing link to dash document
+                            DocServer.GetRefField(docid).then(f => {
+                                if (f instanceof Doc) {
+                                    if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
+                                    (f instanceof Doc) && addDocument(f);
+                                }
+                            });
+                        } else {
+                            addDocument(Docs.Create.WebDocument(href, { ...options, title: href }));
+                        }
+                    } else if (text) {
+                        addDocument(Docs.Create.TextDocument(text, { ...options, _width: 100, _height: 25 }));
                     }
-                    const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
-                    ImageUtils.ExtractExif(doc);
-                    this.props.addDocument(doc);
                     return;
-                } else {
-                    const path = window.location.origin + "/doc/";
-                    if (text.startsWith(path)) {
-                        const docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0];
-                        DocServer.GetRefField(docid).then(f => {
-                            if (f instanceof Doc) {
-                                if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
-                                (f instanceof Doc) && this.props.addDocument(f);
-                            }
-                        });
+                }
+                if (!html.startsWith("<a")) {
+                    const tags = html.split("<");
+                    if (tags[0] === "") tags.splice(0, 1);
+                    const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
+                    if (img) {
+                        const split = img.split("src=\"")[1].split("\"")[0];
+                        let source = split;
+                        if (split.startsWith("data:image") && split.includes("base64")) {
+                            const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] });
+                            source = Utils.prepend(accessPaths.agnostic.client);
+                        }
+                        const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 });
+                        ImageUtils.ExtractExif(doc);
+                        addDocument(doc);
+                        return;
                     } else {
-                        const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
-                        Doc.GetProto(htmlDoc)["data-text"] = text;
-                        this.props.addDocument(htmlDoc);
+                        const path = window.location.origin + "/doc/";
+                        if (text.startsWith(path)) {
+                            const docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0];
+                            DocServer.GetRefField(docid).then(f => {
+                                if (f instanceof Doc) {
+                                    if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
+                                    (f instanceof Doc) && this.props.addDocument(f);
+                                }
+                            });
+                        } else {
+                            const htmlDoc = Docs.Create.HtmlDocument(html, { ...options, title: "-web page-", _width: 300, _height: 300 });
+                            Doc.GetProto(htmlDoc)["data-text"] = text;
+                            this.props.addDocument(htmlDoc);
+                        }
+                        return;
                     }
-                    return;
                 }
             }
-            if (text && text.indexOf("www.youtube.com/watch") !== -1) {
-                const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
-                this.props.addDocument(Docs.Create.VideoDocument(url, { ...options, title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5 }));
-                return;
-            }
-            let matches: RegExpExecArray | null;
-            if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
-                const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
-                const proto = newBox.proto!;
-                const documentId = matches[2];
-                proto[GoogleRef] = documentId;
-                proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
-                proto.backgroundColor = "#eeeeff";
-                this.props.addDocument(newBox);
-                // const parent = Docs.Create.StackingDocument([newBox], { title: `Google Doc Import (${documentId})` });
-                // CollectionDockingView.Instance.AddRightSplit(parent, undefined);
-                // proto.height = parent[HeightSym]();
-                return;
-            }
-            if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
-                const albums = await GooglePhotos.Transactions.ListAlbums();
-                const albumId = matches[3];
-                const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
-                console.log(mediaItems);
-                return;
+
+            if (text) {
+                if (text.includes("www.youtube.com/watch")) {
+                    const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+                    addDocument(Docs.Create.VideoDocument(url, {
+                        ...options,
+                        title: url,
+                        _width: 400,
+                        _height: 315,
+                        _nativeWidth: 600,
+                        _nativeHeight: 472.5
+                    }));
+                    return;
+                }
+                let matches: RegExpExecArray | null;
+                if ((matches = /(https:\/\/)?docs\.google\.com\/document\/d\/([^\\]+)\/edit/g.exec(text)) !== null) {
+                    const newBox = Docs.Create.TextDocument("", { ...options, _width: 400, _height: 200, title: "Awaiting title from Google Docs..." });
+                    const proto = newBox.proto!;
+                    const documentId = matches[2];
+                    proto[GoogleRef] = documentId;
+                    proto.data = "Please select this document and then click on its pull button to load its contents from from Google Docs...";
+                    proto.backgroundColor = "#eeeeff";
+                    addDocument(newBox);
+                    return;
+                }
+                if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
+                    const albumId = matches[3];
+                    const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
+                    console.log(mediaItems);
+                    return;
+                }
             }
+
             const { items } = e.dataTransfer;
             const { length } = items;
             const files: File[] = [];
-            if (length) {
-                const batch = UndoManager.StartBatch("collection view drop");
-                const promises: Promise<void>[] = [];
-                // tslint:disable-next-line:prefer-for-of
-                for (let i = 0; i < length; i++) {
-                    const item = e.dataTransfer.items[i];
-                    if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
-                        let str: string;
-                        const prom = new Promise<string>(resolve => item.getAsString(resolve))
-                            .then(action((s: string) => rp.head(Utils.CorsProxy(str = s))))
-                            .then(result => {
-                                const type = result["content-type"];
-                                if (type) {
-                                    Docs.Get.DocumentFromType(type, str, options)
-                                        .then(doc => doc && this.props.addDocument(doc));
-                                }
-                            });
-                        promises.push(prom);
-                    }
-                    if (item.kind === "file") {
-                        const file = item.getAsFile();
-                        file && file.type && files.push(file);
+            const generatedDocuments: Doc[] = [];
+            if (!length) {
+                alert("No uploadable content found.");
+                return;
+            }
+
+            const batch = UndoManager.StartBatch("collection view drop");
+            for (let i = 0; i < length; i++) {
+                const item = e.dataTransfer.items[i];
+                if (item.kind === "string" && item.type.includes("uri")) {
+                    const stringContents = await new Promise<string>(resolve => item.getAsString(resolve));
+                    const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"];
+                    if (type) {
+                        const doc = await Docs.Get.DocumentFromType(type, stringContents, options);
+                        doc && generatedDocuments.push(doc);
                     }
                 }
-                promises.push(Networking.UploadFilesToServer(files).then(responses => responses.forEach(({ source: { name, type }, result }) => {
-                    if (result instanceof Error) {
-                        alert(`Upload failed: ${result.message}`);
-                        return;
-                    }
-                    const full = { ...options, _width: 300, title: name };
-                    const pathname = Utils.prepend(result.accessPaths.agnostic.client);
-                    Docs.Get.DocumentFromType(type, pathname, full).then(doc => {
-                        if (doc) {
-                            const proto = Doc.GetProto(doc);
-                            proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
-                            if (Upload.isImageInformation(result)) {
-                                proto["data-nativeWidth"] = result.nativeWidth;
-                                proto["data-nativeHeight"] = result.nativeHeight;
-                                proto.contentSize = result.contentSize;
-                            }
-                            this.props?.addDocument(doc);
-                        }
-                    });
-                })));
-                if (promises.length) {
-                    Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
-                } else {
-                    if (text && !text.includes("https://")) {
-                        this.props.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
-                    }
-                    batch.end();
+                if (item.kind === "file") {
+                    const file = item.getAsFile();
+                    file && file.type && files.push(file);
+                }
+            }
+            for (const { source: { name, type }, result } of await Networking.UploadFilesToServer(files)) {
+                if (result instanceof Error) {
+                    alert(`Upload failed: ${result.message}`);
+                    return;
                 }
+                const full = { ...options, _width: 300, title: name };
+                const pathname = Utils.prepend(result.accessPaths.agnostic.client);
+                const doc = await Docs.Get.DocumentFromType(type, pathname, full);
+                if (!doc) {
+                    continue;
+                }
+                const proto = Doc.GetProto(doc);
+                proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, "");
+                if (Upload.isImageInformation(result)) {
+                    proto["data-nativeWidth"] = result.nativeWidth;
+                    proto["data-nativeHeight"] = result.nativeHeight;
+                    proto.contentSize = result.contentSize;
+                }
+                generatedDocuments.push(doc);
+            }
+            if (generatedDocuments.length) {
+                generatedDocuments.forEach(addDocument);
+                completed && completed();
             } else {
-                alert("No uploadable content found.");
+                if (text && !text.includes("https://")) {
+                    addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
+                }
             }
+            batch.end();
         }
     }
+
     return CollectionSubView;
 }
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8720ce002..13ab7c1a4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -210,7 +210,7 @@ class TreeView extends React.Component<TreeViewProps> {
             } else {
                 ContextMenu.Instance.addItem({ description: "Delete Workspace", event: () => this.props.deleteDoc(this.props.document), icon: "trash-alt" });
                 ContextMenu.Instance.addItem({ description: "Create New Workspace", event: () => MainView.Instance.createNewWorkspace(), icon: "plus" });
-            }    
+            }
             ContextMenu.Instance.addItem({ description: "Toggle Theme Colors", event: () => this.props.document.darkScheme = !this.props.document.darkScheme, icon: "minus" });
             ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { const kvp = Docs.Create.KVPDocument(this.props.document, { _width: 300, _height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
             ContextMenu.Instance.addItem({ description: "Publish", event: () => DocUtils.Publish(this.props.document, StrCast(this.props.document.title), () => { }, () => { }), icon: "file" });
@@ -594,7 +594,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
     protected createTreeDropTarget = (ele: HTMLDivElement) => {
         this.treedropDisposer && this.treedropDisposer();
         if (this._mainEle = ele) {
-            this.treedropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this));
+            this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this));
         }
     }
 
@@ -702,7 +702,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
         !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
     }
     outerXf = () => Utils.GetScreenTransform(this._mainEle!);
-    onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
+    onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
 
     @computed get renderClearButton() {
         return <div id="toolbar" key="toolbar">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 969d6b3c8..bdc5e03e3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -117,20 +117,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @action
-    onDrop = (e: React.DragEvent): Promise<void> => {
+    onExternalDrop = (e: React.DragEvent): Promise<void> => {
         const pt = this.getTransform().transformPoint(e.pageX, e.pageY);
-        return super.onDrop(e, { x: pt[0], y: pt[1] });
+        return super.onExternalDrop(e, { x: pt[0], y: pt[1] });
     }
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
         if (this.props.Document.isBackground) return false;
         const xf = this.getTransform();
         const xfo = this.getTransformOverlay();
         const [xp, yp] = xf.transformPoint(de.x, de.y);
         const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
-        if (super.drop(e, de)) {
+        if (super.onInternalDrop(e, de)) {
             if (de.complete.docDragData) {
                 if (de.complete.docDragData.droppedDocuments.length) {
                     const firstDoc = de.complete.docDragData.droppedDocuments[0];
@@ -1079,7 +1079,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return <div className={"collectionfreeformview-container"}
             ref={this.createDashEventsTarget}
             onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
-            onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}
+            onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
             style={{
                 pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
                 transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index db5673573..785f90212 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -189,8 +189,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (super.drop(e, de)) {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (super.onInternalDrop(e, de)) {
             de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
                 d.dimUnit = "*";
                 d.dimMagnitude = 1;
@@ -214,7 +214,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
             getTransform={dxf}
             onClick={this.onChildClickHandler}
             renderDepth={this.props.renderDepth + 1}
-        />
+        />;
     }
     /**
      * @returns the resolved list of rendered child documents, displayed
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 630a178cf..5e59f8237 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -190,8 +190,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
 
     @undoBatch
     @action
-    drop = (e: Event, de: DragManager.DropEvent) => {
-        if (super.drop(e, de)) {
+    onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+        if (super.onInternalDrop(e, de)) {
             de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => {
                 d.dimUnit = "*";
                 d.dimMagnitude = 1;
@@ -215,7 +215,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
             getTransform={dxf}
             onClick={this.onChildClickHandler}
             renderDepth={this.props.renderDepth + 1}
-        />
+        />;
     }
     /**
      * @returns the resolved list of rendered child documents, displayed
diff --git a/src/server/ApiManagers/DeleteManager.ts b/src/server/ApiManagers/DeleteManager.ts
index be452c0ff..9e70af2eb 100644
--- a/src/server/ApiManagers/DeleteManager.ts
+++ b/src/server/ApiManagers/DeleteManager.ts
@@ -2,6 +2,11 @@ import ApiManager, { Registration } from "./ApiManager";
 import { Method, _permission_denied, PublicHandler } from "../RouteManager";
 import { WebSocket } from "../Websocket/Websocket";
 import { Database } from "../database";
+import rimraf = require("rimraf");
+import { pathToDirectory, Directory } from "./UploadManager";
+import { filesDirectory } from "..";
+import { DashUploadUtils } from "../DashUploadUtils";
+import { mkdirSync } from "fs";
 
 export default class DeleteManager extends ApiManager {
 
@@ -31,21 +36,19 @@ export default class DeleteManager extends ApiManager {
             }
         });
 
-        const hi: PublicHandler = async ({ res, isRelease }) => {
-            if (isRelease) {
-                return _permission_denied(res, deletionPermissionError);
+        register({
+            method: Method.GET,
+            subscription: "/deleteAssets",
+            secureHandler: async ({ res, isRelease }) => {
+                if (isRelease) {
+                    return _permission_denied(res, deletionPermissionError);
+                }
+                rimraf.sync(filesDirectory);
+                mkdirSync(filesDirectory);
+                await DashUploadUtils.buildFileDirectories();
+                res.redirect("/delete");
             }
-            await Database.Instance.deleteAll('users');
-            res.redirect("/home");
-        };
-
-        // register({
-        //     method: Method.GET,
-        //     subscription: "/deleteUsers",
-        //     onValidation: hi,
-        //     onUnauthenticated: hi
-        // });
-
+        });
 
         register({
             method: Method.GET,
-- 
cgit v1.2.3-70-g09d2


From 1c73798b14bc91e59ab95225b341008271d0159a Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 22 Feb 2020 01:04:55 -0500
Subject: removed LinkFollow box.

---
 src/client/documents/DocumentTypes.ts              |   1 -
 src/client/documents/Documents.ts                  |   8 -
 src/client/views/OverlayView.tsx                   |   2 +-
 .../views/collections/CollectionLinearView.tsx     |   2 +-
 .../views/collections/CollectionStackingView.tsx   |   2 +-
 .../views/collections/CollectionTreeView.tsx       |   1 -
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   6 +-
 src/client/views/linking/LinkFollowBox.scss        |  93 ----
 src/client/views/linking/LinkFollowBox.tsx         | 571 ---------------------
 src/client/views/linking/LinkMenuItem.tsx          |   1 -
 src/client/views/nodes/DocumentContentsView.tsx    |   3 +-
 .../authentication/models/current_user_utils.ts    |   2 -
 12 files changed, 7 insertions(+), 685 deletions(-)
 delete mode 100644 src/client/views/linking/LinkFollowBox.scss
 delete mode 100644 src/client/views/linking/LinkFollowBox.tsx

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

diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 0e6b59b33..06b15d78c 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -19,7 +19,6 @@ export enum DocumentType {
     WEBCAM = "webcam",
     FONTICON = "fonticonbox",
     PRES = "presentation",
-    LINKFOLLOW = "linkfollow",
     PRESELEMENT = "preselement",
     QUERY = "search",
     COLOR = "color",
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index aed14e4f6..ff64489bb 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -40,7 +40,6 @@ import { PresBox } from "../views/nodes/PresBox";
 import { ComputedField, ScriptField } from "../../new_fields/ScriptField";
 import { ProxyField } from "../../new_fields/Proxy";
 import { DocumentType } from "./DocumentTypes";
-import { LinkFollowBox } from "../views/linking/LinkFollowBox";
 import { PresElementBox } from "../views/presentationview/PresElementBox";
 import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
 import { QueryBox } from "../views/nodes/QueryBox";
@@ -250,9 +249,6 @@ export namespace Docs {
                 layout: { view: FontIconBox, dataField: data },
                 options: { _width: 40, _height: 40, borderRounding: "100%" },
             }],
-            [DocumentType.LINKFOLLOW, {
-                layout: { view: LinkFollowBox, dataField: data }
-            }],
             [DocumentType.WEBCAM, {
                 layout: { view: DashWebRTCVideo, dataField: data }
             }],
@@ -658,10 +654,6 @@ export namespace Docs {
             return InstanceFromProto(Prototypes.get(DocumentType.FONTICON), undefined, { ...(options || {}) });
         }
 
-        export function LinkFollowBoxDocument(options?: DocumentOptions) {
-            return InstanceFromProto(Prototypes.get(DocumentType.LINKFOLLOW), undefined, { ...(options || {}) });
-        }
-
         export function PresElementBoxDocument(options?: DocumentOptions) {
             return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) });
         }
diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx
index 7a99bf0ae..220efd4a8 100644
--- a/src/client/views/OverlayView.tsx
+++ b/src/client/views/OverlayView.tsx
@@ -169,7 +169,7 @@ export class OverlayView extends React.Component {
                 document.addEventListener("pointermove", onPointerMove);
                 document.addEventListener("pointerup", onPointerUp);
             };
-            return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)`, display: d.isMinimized ? "none" : "" }}>
+            return <div className="overlayView-doc" key={d[Id]} onPointerDown={onPointerDown} style={{ transform: `translate(${d.x}px, ${d.y}px)` }}>
                 <DocumentView
                     Document={d}
                     LibraryPath={emptyPath}
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 9bbc9f1b6..4a14a30cd 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -71,7 +71,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         }
     }
 
-    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+    public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
     dimension = () => NumCast(this.props.Document._height); // 2 * the padding
     getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 0d5d3e449..a1cc21319 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -38,7 +38,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @observable _scroll = 0; // used to force the document decoration to update when scrolling
     @computed get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
     @computed get sectionFilter() { return StrCast(this.props.Document.sectionFilter); }
-    @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout); }
+    @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); }
     @computed get xMargin() { return NumCast(this.props.Document._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); }
     @computed get yMargin() { return Math.max(this.props.Document._showTitle && !this.props.Document._showTitleHover ? 30 : 0, NumCast(this.props.Document._yMargin, 0)); } // 2 * this.gridGap)); }
     @computed get gridGap() { return NumCast(this.props.Document._gridGap, 10); }
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ed1d674c6..7eeeb6ff1 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -421,7 +421,6 @@ class TreeView extends React.Component<TreeViewProps> {
         return <>
             <div className="docContainer" title="click to edit title" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
                 style={{
-                    color: this.props.document.isMinimized ? "red" : "inherit",
                     background: Doc.IsHighlighted(this.props.document) ? "orange" : Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
                     fontWeight: this.props.document.searchMatch ? "bold" : undefined,
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 5902cc22e..4458c7dcf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -111,7 +111,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         SelectionManager.DeselectAll();
         docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
     }
-    public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+    public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
 
     public getActiveDocuments = () => {
         return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
@@ -433,7 +433,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
         let x = this.Document._panX || 0;
         let y = this.Document._panY || 0;
-        const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout);
+        const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
         const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
         if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) {
             PDFMenu.Instance.fadeOut(true);
@@ -869,7 +869,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             }));
         this._cachedPool.clear();
         Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
-        const elements:ViewDefResult[] = computedElementData.slice();
+        const elements: ViewDefResult[] = computedElementData.slice();
         this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair =>
             elements.push({
                 ele: <CollectionFreeFormDocumentView key={pair.layout[Id]}  {...this.getChildDocumentViewProps(pair.layout, pair.data)}
diff --git a/src/client/views/linking/LinkFollowBox.scss b/src/client/views/linking/LinkFollowBox.scss
deleted file mode 100644
index 9eeed1cc8..000000000
--- a/src/client/views/linking/LinkFollowBox.scss
+++ /dev/null
@@ -1,93 +0,0 @@
-@import "../globalCssVariables";
-
-.linkFollowBox-main {
-    position: absolute;
-    background: whitesmoke;
-    color: grey;
-    border-radius: 15px;
-    box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
-    border: solid #BBBBBBBB 5px;
-    pointer-events: all;
-
-    .linkFollowBox-header {
-        height: 50px;
-        text-align: center;
-        text-transform: uppercase;
-        letter-spacing: 2px;
-        font-size: 16px;
-        width: 100%;
-    }
-
-    .direction-indicator {
-        font-size: 12px;
-    }
-
-    .closeDocument {
-        position: relative;
-        max-width: 30px;
-        top: -20px;
-        left: 460px;
-        color: $darker-alt-accent
-    }
-
-    .closeDocument:hover {
-        color: $main-accent;
-    }
-
-    .topHeader {
-        width: 100%;
-        height: 25px;
-    }
-
-    .linkFollowBox-footer {
-        height: 50px;
-        text-align: center;
-        display: flex;
-        justify-content: center;
-        align-items: center;
-
-        button {
-            background-color: $darker-alt-accent;
-            width: 30%;
-        }
-    }
-
-    .linkFollowBox-content {
-        display: grid;
-        grid-template-columns: 1fr 1fr 1fr;
-        grid-column-gap: 5px;
-        margin-left: 5px;
-        margin-right: 5px;
-
-        .linkFollowBox-item {
-            background-color: $light-color;
-            width: 100%;
-            height: 100%;
-
-            .linkFollowBox-itemContent {
-                padding: 5px;
-                font-size: 12px;
-                overflow: scroll;
-
-                input[type=radio] {
-                    border: 0px;
-                    margin-right: 5px;
-                }
-            }
-
-            .title {
-                display: flex;
-                justify-content: center;
-                align-items: center;
-                text-transform: uppercase;
-                color: $light-color;
-                background-color: $lighter-alt-accent;
-                width: 100%;
-                height: 30px;
-                border-bottom: solid $darker-alt-accent 5px;
-                font-size: 12px;
-                text-align: center;
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx
deleted file mode 100644
index b47464517..000000000
--- a/src/client/views/linking/LinkFollowBox.tsx
+++ /dev/null
@@ -1,571 +0,0 @@
-import { observable, computed, action, runInAction, reaction, IReactionDisposer } from "mobx";
-import React = require("react");
-import { observer } from "mobx-react";
-import { FieldViewProps, FieldView } from "../nodes/FieldView";
-import { Doc, DocListCastAsync, Opt } from "../../../new_fields/Doc";
-import { undoBatch } from "../../util/UndoManager";
-import { NumCast, FieldValue, Cast, StrCast } from "../../../new_fields/Types";
-import { CollectionViewType } from "../collections/CollectionView";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { SelectionManager } from "../../util/SelectionManager";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DocumentView } from "../nodes/DocumentView";
-import "./LinkFollowBox.scss";
-import { SearchUtil } from "../../util/SearchUtil";
-import { Id } from "../../../new_fields/FieldSymbols";
-import { listSpec } from "../../../new_fields/Schema";
-import { DocServer } from "../../DocServer";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { faTimes } from '@fortawesome/free-solid-svg-icons';
-import { docs_v1 } from "googleapis";
-import { Utils } from "../../../Utils";
-import { Link } from "@react-pdf/renderer";
-
-enum FollowModes {
-    OPENTAB = "Open in Tab",
-    OPENRIGHT = "Open in Right Split",
-    OPENFULL = "Open Full Screen",
-    PAN = "Pan to Document",
-    INPLACE = "Open In Place"
-}
-
-enum FollowOptions {
-    ZOOM = "Zoom",
-    NOZOOM = "No Zoom",
-}
-
-@observer
-export class LinkFollowBox extends React.Component<FieldViewProps> {
-
-    public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkFollowBox, fieldKey); }
-    public static Instance: LinkFollowBox | undefined;
-    @observable static linkDoc: Doc | undefined = undefined;
-    @observable static destinationDoc: Doc | undefined = undefined;
-    @observable static sourceDoc: Doc | undefined = undefined;
-    @observable selectedMode: string = "";
-    @observable selectedContext: Doc | undefined = undefined;
-    @observable selectedContextAliases: Doc[] | undefined = undefined;
-    @observable selectedOption: string = "";
-    @observable selectedContextString: string = "";
-    @observable sourceView: DocumentView | undefined = undefined;
-    @observable canPan: boolean = false;
-    @observable shouldUseOnlyParentContext = false;
-    _contextDisposer?: IReactionDisposer;
-
-    @observable private _docs: { col: Doc, target: Doc }[] = [];
-    @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
-
-    constructor(props: FieldViewProps) {
-        super(props);
-        LinkFollowBox.Instance = this;
-        this.resetVars();
-        this.props.Document.isBackground = true;
-    }
-
-    componentDidMount = () => {
-        this.resetVars();
-
-        this._contextDisposer = reaction(
-            () => this.selectedContextString,
-            async () => {
-                const ref = await DocServer.GetRefField(this.selectedContextString);
-                runInAction(() => {
-                    if (ref instanceof Doc) {
-                        this.selectedContext = ref;
-                    }
-                });
-                if (this.selectedContext instanceof Doc) {
-                    const aliases = await SearchUtil.GetViewsOfDocument(this.selectedContext);
-                    runInAction(() => { this.selectedContextAliases = aliases; });
-                }
-            }
-        );
-    }
-
-    componentWillUnmount = () => {
-        this._contextDisposer && this._contextDisposer();
-    }
-
-    async resetPan() {
-        if (LinkFollowBox.destinationDoc && this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-            runInAction(() => this.canPan = false);
-            if (this.sourceView.props.ContainingCollectionDoc._viewType === CollectionViewType.Freeform) {
-                const docs = Cast(this.sourceView.props.ContainingCollectionDoc.data, listSpec(Doc), []);
-                const aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(LinkFollowBox.destinationDoc));
-
-                aliases.forEach(alias => {
-                    if (docs.filter(doc => doc === alias).length > 0) {
-                        runInAction(() => { this.canPan = true; });
-                    }
-                });
-            }
-        }
-    }
-
-    @action
-    resetVars = () => {
-        this.selectedContext = undefined;
-        this.selectedContextString = "";
-        this.selectedMode = "";
-        this.selectedOption = "";
-        LinkFollowBox.linkDoc = undefined;
-        LinkFollowBox.sourceDoc = undefined;
-        LinkFollowBox.destinationDoc = undefined;
-        this.sourceView = undefined;
-        this.canPan = false;
-        this.shouldUseOnlyParentContext = false;
-    }
-
-    async fetchDocuments() {
-        if (LinkFollowBox.destinationDoc) {
-            const dest: Doc = LinkFollowBox.destinationDoc;
-            const aliases = await SearchUtil.GetViewsOfDocument(Doc.GetProto(dest));
-            const { docs } = await SearchUtil.Search("", true, { fq: `data_l:"${dest[Id]}"` });
-            const map: Map<Doc, Doc> = new Map;
-            const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search("", true, { fq: `data_l:"${doc[Id]}"` }).then(result => result.docs)));
-            allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
-            docs.forEach(doc => map.delete(doc));
-            runInAction(async () => {
-                this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest }));
-                this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
-                const tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.anchor2Context, Doc)) as Doc;
-                runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest }));
-            });
-        }
-    }
-
-    @action
-    setLinkDocs = (linkDoc: Doc, source: Doc, dest: Doc) => {
-        this.resetVars();
-
-        LinkFollowBox.linkDoc = linkDoc;
-        LinkFollowBox.sourceDoc = source;
-        LinkFollowBox.destinationDoc = dest;
-        this.fetchDocuments();
-
-        SelectionManager.SelectedDocuments().forEach(dv => {
-            if (dv.props.Document === LinkFollowBox.sourceDoc) {
-                this.sourceView = dv;
-            }
-        });
-
-        this.resetPan();
-    }
-
-    highlightDoc = () => LinkFollowBox.destinationDoc && Doc.linkFollowHighlight(LinkFollowBox.destinationDoc);
-
-    @undoBatch
-    openFullScreen = () => {
-        if (LinkFollowBox.destinationDoc) {
-            const view = DocumentManager.Instance.getDocumentView(LinkFollowBox.destinationDoc);
-            view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
-        }
-    }
-
-    @undoBatch
-    openColFullScreen = (options: { context: Doc }) => {
-        if (LinkFollowBox.destinationDoc) {
-            if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
-                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
-                options.context._panX = newPanX;
-                options.context._panY = newPanY;
-            }
-            const view = DocumentManager.Instance.getDocumentView(options.context);
-            view && CollectionDockingView.Instance && CollectionDockingView.Instance.OpenFullScreen(view);
-            this.highlightDoc();
-        }
-    }
-
-    // should container be a doc or documentview or what? This one needs work and is more long term
-    @undoBatch
-    openInContainer = (options: { container: Doc }) => {
-
-    }
-
-    static _addDocTab: (undefined | ((doc: Doc, where: string) => boolean));
-
-    static setAddDocTab = (addFunc: (doc: Doc, where: string) => boolean) => {
-        LinkFollowBox._addDocTab = addFunc;
-    }
-
-    @undoBatch
-    openLinkColRight = (options: { context: Doc, shouldZoom: boolean }) => {
-        if (LinkFollowBox.destinationDoc) {
-            options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
-            if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
-                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
-                options.context._panX = newPanX;
-                options.context._panY = newPanY;
-            }
-            (LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, "onRight");
-
-            if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
-
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-    }
-
-    @undoBatch
-    openLinkRight = () => {
-        if (LinkFollowBox.destinationDoc) {
-            const alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
-            (LinkFollowBox._addDocTab || this.props.addDocTab)(alias, "onRight");
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-
-    }
-
-    @undoBatch
-    jumpToLink = async (options: { shouldZoom: boolean }) => {
-        if (LinkFollowBox.sourceDoc && LinkFollowBox.linkDoc) {
-            const focus = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, "inTab"); SelectionManager.DeselectAll(); };
-            //let focus = (doc: Doc, maxLocation: string) => this.props.focus(docthis.props.focus(LinkFollowBox.destinationDoc, true, 1, () => this.props.addDocTab(doc, maxLocation));
-
-            DocumentManager.Instance.FollowLink(LinkFollowBox.linkDoc, LinkFollowBox.sourceDoc, focus, options && options.shouldZoom, false, undefined);
-        }
-    }
-
-    @undoBatch
-    openLinkTab = () => {
-        if (LinkFollowBox.destinationDoc) {
-            const fullScreenAlias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
-            // this.prosp.addDocTab is empty -- use the link source's addDocTab 
-            (LinkFollowBox._addDocTab || this.props.addDocTab)(fullScreenAlias, "inTab");
-
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-    }
-
-    @undoBatch
-    openLinkColTab = (options: { context: Doc, shouldZoom: boolean }) => {
-        if (LinkFollowBox.destinationDoc) {
-            options.context = Doc.IsPrototype(options.context) ? Doc.MakeDelegate(options.context) : options.context;
-            if (NumCast(options.context._viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
-                const newPanX = NumCast(LinkFollowBox.destinationDoc.x) + NumCast(LinkFollowBox.destinationDoc._width) / 2;
-                const newPanY = NumCast(LinkFollowBox.destinationDoc.y) + NumCast(LinkFollowBox.destinationDoc._height) / 2;
-                options.context._panX = newPanX;
-                options.context._panY = newPanY;
-            }
-            (LinkFollowBox._addDocTab || this.props.addDocTab)(options.context, "inTab");
-            if (options.shouldZoom) this.jumpToLink({ shouldZoom: options.shouldZoom });
-
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-    }
-
-    @undoBatch
-    openLinkInPlace = (options: { shouldZoom: boolean }) => {
-
-        if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) {
-            if (this.sourceView && this.sourceView.props.addDocument) {
-                const destViews = DocumentManager.Instance.getDocumentViews(LinkFollowBox.destinationDoc);
-                if (!destViews.find(dv => dv.props.ContainingCollectionView === this.sourceView!.props.ContainingCollectionView)) {
-                    const alias = Doc.MakeAlias(LinkFollowBox.destinationDoc);
-                    const y = NumCast(LinkFollowBox.sourceDoc.y);
-                    const x = NumCast(LinkFollowBox.sourceDoc.x);
-
-                    const width = NumCast(LinkFollowBox.sourceDoc._width);
-                    const height = NumCast(LinkFollowBox.sourceDoc._height);
-
-                    alias.x = x + width + 30;
-                    alias.y = y;
-                    alias._width = width;
-                    alias._height = height;
-
-                    this.sourceView.props.addDocument(alias);
-                }
-            }
-
-            this.jumpToLink({ shouldZoom: options.shouldZoom });
-
-            this.highlightDoc();
-            SelectionManager.DeselectAll();
-        }
-    }
-
-    //set this to be the default link behavior, can be any of the above
-    public defaultLinkBehavior: (options?: any) => void = this.jumpToLink;
-
-    @action
-    currentLinkBehavior = () => {
-        // this.resetPan();
-        if (LinkFollowBox.destinationDoc) {
-            if (this.selectedContextString === "") {
-                this.selectedContextString = "self";
-                this.selectedContext = LinkFollowBox.destinationDoc;
-            }
-            if (this.selectedOption === "") this.selectedOption = FollowOptions.NOZOOM;
-            const shouldZoom: boolean = this.selectedOption === FollowOptions.NOZOOM ? false : true;
-            const notOpenInContext: boolean = this.selectedContextString === "self" || this.selectedContextString === LinkFollowBox.destinationDoc[Id];
-
-            if (this.selectedMode === FollowModes.INPLACE) {
-                if (shouldZoom !== undefined) this.openLinkInPlace({ shouldZoom: shouldZoom });
-            }
-            else if (this.selectedMode === FollowModes.OPENFULL) {
-                if (notOpenInContext) this.openFullScreen();
-                else this.selectedContext && this.openColFullScreen({ context: this.selectedContext });
-            }
-            else if (this.selectedMode === FollowModes.OPENRIGHT) {
-                if (notOpenInContext) this.openLinkRight();
-                else this.selectedContext && this.openLinkColRight({ context: this.selectedContext, shouldZoom: shouldZoom });
-            }
-            else if (this.selectedMode === FollowModes.OPENTAB) {
-                if (notOpenInContext) this.openLinkTab();
-                else this.selectedContext && this.openLinkColTab({ context: this.selectedContext, shouldZoom: shouldZoom });
-            }
-            else if (this.selectedMode === FollowModes.PAN) {
-                this.jumpToLink({ shouldZoom: shouldZoom });
-            }
-            else return;
-        }
-    }
-
-    @action
-    handleModeChange = (e: React.ChangeEvent) => {
-        const target = e.target as HTMLInputElement;
-        this.selectedMode = target.value;
-        this.selectedContext = undefined;
-        this.selectedContextString = "";
-
-        this.shouldUseOnlyParentContext = (this.selectedMode === FollowModes.INPLACE || this.selectedMode === FollowModes.PAN);
-
-        if (this.shouldUseOnlyParentContext) {
-            if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-                this.selectedContext = this.sourceView.props.ContainingCollectionDoc;
-                this.selectedContextString = (StrCast(this.sourceView.props.ContainingCollectionDoc.title));
-            }
-        }
-    }
-
-    @action
-    handleOptionChange = (e: React.ChangeEvent) => {
-        const target = e.target as HTMLInputElement;
-        this.selectedOption = target.value;
-    }
-
-    @action
-    handleContextChange = (e: React.ChangeEvent) => {
-        const target = e.target as HTMLInputElement;
-        this.selectedContextString = target.value;
-        // selectedContext is updated in reaction
-        this.selectedOption = "";
-    }
-
-    @computed
-    get canOpenInPlace() {
-        if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-            const colDoc = this.sourceView.props.ContainingCollectionDoc;
-            if (colDoc._viewType === CollectionViewType.Freeform) return true;
-        }
-        return false;
-    }
-
-    @computed
-    get availableModes() {
-        return (
-            <div>
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.OPENRIGHT}
-                    checked={this.selectedMode === FollowModes.OPENRIGHT}
-                    onChange={this.handleModeChange}
-                    disabled={false} />
-                    {FollowModes.OPENRIGHT}
-                </label><br />
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.OPENTAB}
-                    checked={this.selectedMode === FollowModes.OPENTAB}
-                    onChange={this.handleModeChange}
-                    disabled={false} />
-                    {FollowModes.OPENTAB}
-                </label><br />
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.OPENFULL}
-                    checked={this.selectedMode === FollowModes.OPENFULL}
-                    onChange={this.handleModeChange}
-                    disabled={false} />
-                    {FollowModes.OPENFULL}
-                </label><br />
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.PAN}
-                    checked={this.selectedMode === FollowModes.PAN}
-                    onChange={this.handleModeChange}
-                    disabled={!this.canPan} />
-                    {FollowModes.PAN}
-                </label><br />
-                <label><input
-                    type="radio"
-                    name="mode"
-                    value={FollowModes.INPLACE}
-                    checked={this.selectedMode === FollowModes.INPLACE}
-                    onChange={this.handleModeChange}
-                    disabled={!this.canOpenInPlace} />
-                    {FollowModes.INPLACE}
-                </label><br />
-            </div>
-        );
-    }
-
-    @computed
-    get parentName() {
-        if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-            return this.sourceView.props.ContainingCollectionDoc.title;
-        }
-    }
-
-    @computed
-    get parentID(): string {
-        if (this.sourceView && this.sourceView.props.ContainingCollectionDoc) {
-            return StrCast(this.sourceView.props.ContainingCollectionDoc[Id]);
-        }
-        return "col";
-    }
-
-    @computed
-    get availableContexts() {
-        return (
-            this.shouldUseOnlyParentContext ?
-                <label><input
-                    type="radio" disabled={true}
-                    name="context"
-                    value={this.parentID}
-                    checked={true} />
-                    {this.parentName} (Parent Collection)
-                </label>
-                :
-                <div>
-                    <label><input
-                        type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
-                        name="context"
-                        value={LinkFollowBox.destinationDoc ? StrCast(LinkFollowBox.destinationDoc[Id]) : "self"}
-                        checked={LinkFollowBox.destinationDoc ? this.selectedContextString === StrCast(LinkFollowBox.destinationDoc[Id]) || this.selectedContextString === "self" : true}
-                        onChange={this.handleContextChange} />
-                        Open Self
-                </label><br />
-                    {[...this._docs, ...this._otherDocs].map(doc => {
-                        if (doc && doc.target && doc.col.title !== "Recently Closed") {
-                            return <div key={doc.col[Id] + doc.target[Id]}><label key={doc.col[Id] + doc.target[Id]}>
-                                <input
-                                    type="radio" disabled={LinkFollowBox.linkDoc ? false : true}
-                                    name="context"
-                                    value={StrCast(doc.col[Id])}
-                                    checked={this.selectedContextString === StrCast(doc.col[Id])}
-                                    onChange={this.handleContextChange} />
-                                {doc.col.title}
-                            </label><br /></div>;
-                        }
-                    })}
-                </div>
-        );
-    }
-
-    @computed
-    get shouldShowZoom(): boolean {
-        if (this.selectedMode === FollowModes.OPENFULL) return false;
-        if (this.shouldUseOnlyParentContext) return true;
-        if (LinkFollowBox.destinationDoc ? this.selectedContextString === LinkFollowBox.destinationDoc[Id] : "self") return false;
-
-        let contextMatch: boolean = false;
-        if (this.selectedContextAliases) {
-            this.selectedContextAliases.forEach(alias => {
-                if (alias._viewType === CollectionViewType.Freeform) contextMatch = true;
-            });
-        }
-        if (contextMatch) return true;
-
-        return false;
-    }
-
-    @computed
-    get availableOptions() {
-        if (LinkFollowBox.destinationDoc) {
-            return (
-                this.shouldShowZoom ?
-                    <div>
-                        <label><input
-                            type="radio"
-                            name="option"
-                            value={FollowOptions.ZOOM}
-                            checked={this.selectedOption === FollowOptions.ZOOM}
-                            onChange={this.handleOptionChange}
-                            disabled={false} />
-                            {FollowOptions.ZOOM}
-                        </label><br />
-                        <label><input
-                            type="radio"
-                            name="option"
-                            value={FollowOptions.NOZOOM}
-                            checked={this.selectedOption === FollowOptions.NOZOOM}
-                            onChange={this.handleOptionChange}
-                            disabled={false} />
-                            {FollowOptions.NOZOOM}
-                        </label><br />
-                    </div>
-                    :
-                    <div>No Available Options</div>
-            );
-        }
-        return null;
-    }
-
-    render() {
-        return (
-            <div className="linkFollowBox-main" style={{ height: NumCast(this.props.Document._height), width: NumCast(this.props.Document._width) }}>
-                <div className="linkFollowBox-header">
-                    <div className="topHeader">
-                        {LinkFollowBox.linkDoc ? "Link Title: " + StrCast(LinkFollowBox.linkDoc.title) : "No Link Selected"}
-                        <div onClick={() => this.props.Document.isMinimized = true} className="closeDocument"><FontAwesomeIcon icon={faTimes} size="lg" /></div>
-                    </div>
-                    <div className=" direction-indicator">{LinkFollowBox.linkDoc ?
-                        LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc ? "Source: " + StrCast(LinkFollowBox.sourceDoc.title) + ", Destination: " + StrCast(LinkFollowBox.destinationDoc.title)
-                            : "" : ""}</div>
-                </div>
-                <div className="linkFollowBox-content" style={{ height: NumCast(this.props.Document._height) - 110 }}>
-                    <div className="linkFollowBox-item">
-                        <div className="linkFollowBox-item title">Mode</div>
-                        <div className="linkFollowBox-itemContent">
-                            {LinkFollowBox.linkDoc ? this.availableModes : "Please select a link to view modes"}
-                        </div>
-                    </div>
-                    <div className="linkFollowBox-item">
-                        <div className="linkFollowBox-item title">Context</div>
-                        <div className="linkFollowBox-itemContent">
-                            {this.selectedMode !== "" ? this.availableContexts : "Please select a mode to view contexts"}
-                        </div>
-                    </div>
-                    <div className="linkFollowBox-item">
-                        <div className="linkFollowBox-item title">Options</div>
-                        <div className="linkFollowBox-itemContent">
-                            {this.selectedContextString !== "" ? this.availableOptions : "Please select a context to view options"}
-                        </div>
-                    </div>
-                </div>
-                <div className="linkFollowBox-footer">
-                    <button
-                        onClick={this.resetVars}>
-                        Clear Link
-                    </button>
-                    <div style={{ width: 20 }}></div>
-                    <button
-                        onClick={this.currentLinkBehavior}
-                        disabled={(LinkFollowBox.linkDoc) ? false : true}>
-                        Follow Link
-                    </button>
-                </div>
-            </div>
-        );
-    }
-}
\ No newline at end of file
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 376afa64b..5fd6e4630 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -8,7 +8,6 @@ import { Cast, StrCast } from '../../../new_fields/Types';
 import { DragManager } from '../../util/DragManager';
 import { LinkManager } from '../../util/LinkManager';
 import { ContextMenu } from '../ContextMenu';
-import { LinkFollowBox } from './LinkFollowBox';
 import './LinkMenuItem.scss';
 import React = require("react");
 import { DocumentManager } from '../../util/DocumentManager';
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 73a53f5cc..bfda13eb3 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -9,7 +9,6 @@ import { CollectionDockingView } from "../collections/CollectionDockingView";
 import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
 import { CollectionSchemaView } from "../collections/CollectionSchemaView";
 import { CollectionView } from "../collections/CollectionView";
-import { LinkFollowBox } from "../linking/LinkFollowBox";
 import { YoutubeBox } from "./../../apis/youtube/YoutubeBox";
 import { AudioBox } from "./AudioBox";
 import { ButtonBox } from "./ButtonBox";
@@ -108,7 +107,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
                 components={{
                     FormattedTextBox, ImageBox, DirectoryImportBox, FontIconBox, ButtonBox, SliderBox, FieldView,
                     CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
-                    PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
+                    PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, PresElementBox, QueryBox,
                     ColorBox, DashWebRTCVideo, DocuLinkBox, InkingStroke, DocumentBox, LinkBox
                 }}
                 bindings={this.CreateBindings()}
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 32439757c..8fe2257fc 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -265,8 +265,6 @@ export class CurrentUserUtils {
     // sets up the default set of documents to be shown in the Overlay layer
     static setupOverlays(doc: Doc) {
         doc.overlays = Docs.Create.FreeformDocument([], { title: "Overlays", backgroundColor: "#aca3a6" });
-        doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, _width: 500, _height: 370, title: "Link Follower" });
-        Doc.AddDocToList(doc.overlays as Doc, "data", doc.linkFollowBox as Doc);
     }
 
     // the initial presentation Doc to use
-- 
cgit v1.2.3-70-g09d2


From 8b71007c98f3c5f5092d15c6ce91142729bcec22 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 3 Mar 2020 14:02:17 -0500
Subject: fixed dragging from linear view because of screentransform.  added
 [[key=value]] syntax.  added button for seeing enumerated values.    added
 step up button in DocDec for selecting parent collection

---
 src/client/util/RichTextRules.ts                   |  8 ++-
 src/client/util/RichTextSchema.tsx                 | 57 +++++++++++++++-------
 src/client/views/DocumentDecorations.scss          | 11 +++++
 src/client/views/DocumentDecorations.tsx           | 16 +++++-
 .../views/collections/CollectionLinearView.tsx     |  2 +-
 src/new_fields/Doc.ts                              | 34 ++++++-------
 6 files changed, 89 insertions(+), 39 deletions(-)

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

diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
index 70a1a5154..7ffc2dd9c 100644
--- a/src/client/util/RichTextRules.ts
+++ b/src/client/util/RichTextRules.ts
@@ -81,10 +81,11 @@ export class RichTextRules {
 
             // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]]   // [[:Doc]] => hyperlink   [[fieldKey]] => show field   [[fieldKey:Doc]] => show field of doc
             new InputRule(
-                new RegExp(/\[\[([a-zA-Z_#@\? \-0-9]*)(:[a-zA-Z_#@\? \-0-9]+)?\]\]$/),
+                new RegExp(/\[\[([a-zA-Z_#@\? \-0-9]*)(=[a-zA-Z_#@\? \-0-9]*)?(:[a-zA-Z_#@\? \-0-9]+)?\]\]$/),
                 (state, match, start, end) => {
                     const fieldKey = match[1];
-                    const docid = match[2]?.substring(1);
+                    const docid = match[3]?.substring(1);
+                    const value = match[2]?.substring(1);
                     if (!fieldKey) {
                         if (docid) {
                             DocServer.GetRefField(docid).then(docx => {
@@ -97,6 +98,9 @@ export class RichTextRules {
                         }
                         return state.tr;
                     }
+                    if (value !== "") {
+                        this.Document[DataSym][fieldKey] = value;
+                    }
                     const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid });
                     return state.tr.deleteRange(start, end).insert(start, fieldView);
                 }),
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 2ef22b6a0..034f5d55a 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -8,28 +8,25 @@ import { EditorState, NodeSelection, Plugin, TextSelection } from "prosemirror-s
 import { StepMap } from "prosemirror-transform";
 import { EditorView } from "prosemirror-view";
 import * as ReactDOM from 'react-dom';
-import { Doc, Field, HeightSym, WidthSym, DocListCast } from "../../new_fields/Doc";
+import { Doc, DocListCast, Field, HeightSym, WidthSym } from "../../new_fields/Doc";
 import { Id } from "../../new_fields/FieldSymbols";
+import { List } from "../../new_fields/List";
 import { ObjectField } from "../../new_fields/ObjectField";
+import { listSpec } from "../../new_fields/Schema";
+import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 import { ComputedField } from "../../new_fields/ScriptField";
-import { BoolCast, NumCast, StrCast, Cast } from "../../new_fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../new_fields/Types";
 import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils";
 import { DocServer } from "../DocServer";
+import { Docs } from "../documents/Documents";
+import { CollectionViewType } from "../views/collections/CollectionView";
+import { ContextMenu } from "../views/ContextMenu";
 import { DocumentView } from "../views/nodes/DocumentView";
 import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
 import { DocumentManager } from "./DocumentManager";
 import ParagraphNodeSpec from "./ParagraphNodeSpec";
 import { Transform } from "./Transform";
 import React = require("react");
-import { CollectionSchemaBooleanCell } from "../views/collections/CollectionSchemaCells";
-import { ContextMenu } from "../views/ContextMenu";
-import { ContextMenuProps } from "../views/ContextMenuItem";
-import { Docs } from "../documents/Documents";
-import { CollectionView, CollectionViewType } from "../views/collections/CollectionView";
-import { toBlob } from "html-to-image";
-import { listSpec } from "../../new_fields/Schema";
-import { List } from "../../new_fields/List";
-import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 
 const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
     preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0];
@@ -839,8 +836,8 @@ export class DashDocView {
                 zoomToScale={emptyFunction}
                 getScale={returnOne}
                 dontRegisterView={false}
-                ContainingCollectionView={undefined}
-                ContainingCollectionDoc={undefined}
+                ContainingCollectionView={this._textBox.props.ContainingCollectionView}
+                ContainingCollectionDoc={this._textBox.props.ContainingCollectionDoc}
                 ContentScaling={this.contentScaling}
             />, this._dashSpan);
             if (node.attrs.width !== dashDoc._width + "px" || node.attrs.height !== dashDoc._height + "px") {
@@ -863,6 +860,7 @@ export class DashFieldView {
     _fieldWrapper: HTMLDivElement; // container for label and value
     _labelSpan: HTMLSpanElement; // field label
     _fieldSpan: HTMLDivElement;  // field value
+    _enumerables: HTMLDivElement;  // field value
     _reactionDisposer: IReactionDisposer | undefined;
     _textBoxDoc: Doc;
     @observable _dashDoc: Doc | undefined;
@@ -879,6 +877,19 @@ export class DashFieldView {
         this._fieldWrapper.style.display = "inline-block";
 
         const self = this;
+        this._enumerables = document.createElement("div");
+        this._enumerables.style.width = "10px";
+        this._enumerables.style.height = "10px";
+        this._enumerables.style.position = "relative";
+        this._enumerables.style.display = "none";
+        this._enumerables.style.background = "dimGray";
+
+        this._enumerables.onpointerdown = async (e) => {
+            e.stopPropagation();
+            const collview = await Doc.addEnumerationToTextField(self._textBoxDoc, node.attrs.fieldKey, [Docs.Create.TextDocument(self._fieldSpan.innerText, { title: self._fieldSpan.innerText })]);
+            collview instanceof Doc && tbox.props.addDocTab(collview, "onRight");
+        }
+
         this._fieldSpan = document.createElement("div");
         this._fieldSpan.id = Utils.GenerateGuid();
         this._fieldSpan.contentEditable = "true";
@@ -888,7 +899,10 @@ export class DashFieldView {
         this._fieldSpan.style.backgroundColor = "rgba(155, 155, 155, 0.24)";
         this._fieldSpan.onkeypress = function (e: any) { e.stopPropagation(); };
         this._fieldSpan.onkeyup = function (e: any) { e.stopPropagation(); };
-        this._fieldSpan.onmousedown = function (e: any) { e.stopPropagation(); };
+        this._fieldSpan.onmousedown = function (e: any) {
+            e.stopPropagation();
+            self._enumerables.style.display = "inline-block";
+        };
         this._fieldSpan.oncontextmenu = function (e: any) {
             ContextMenu.Instance.addItem({
                 description: "Show Enumeration Templates", event: () => {
@@ -898,15 +912,17 @@ export class DashFieldView {
             });
         };
         this._fieldSpan.onblur = function (e: any) {
+            self._enumerables.style.display = "none";
             let newText = self._fieldSpan.innerText.startsWith(":=") ? ":=-computed-" : self._fieldSpan.innerText;
+
             // look for a document whose id === the fieldKey being displayed.  If there's a match, then that document
             // holds the different enumerated values for the field in the titles of its collected documents.
             // if there's a partial match from the start of the input text, complete the text --- TODO: make this an auto suggest box and select from a drop down.
-
-            // alternatively, if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
             DocServer.GetRefField(node.attrs.fieldKey).then(options => {
                 (options instanceof Doc) && DocListCast(options.data).forEach(opt => StrCast(opt.title).startsWith(newText) && (newText = StrCast(opt.title)));
                 self._fieldSpan.innerHTML = self._dashDoc![self._fieldKey] = newText;
+
+                // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key
                 if (newText.startsWith(":=") && self._dashDoc && e.data === null && !e.inputType.includes("delete")) {
                     Doc.Layout(tbox.props.Document)[self._fieldKey] = ComputedField.MakeFunction(self._fieldSpan.innerText.substring(2));
                 }
@@ -947,8 +963,12 @@ export class DashFieldView {
         this._labelSpan.title = "click to see related tags";
         this._labelSpan.onpointerdown = function (e: any) {
             e.stopPropagation();
-            if (tbox.props.ContainingCollectionDoc) {
-                const alias = Doc.MakeAlias(tbox.props.ContainingCollectionDoc);
+            let container = tbox.props.ContainingCollectionView;
+            while (container?.props.Document.isTemplateForField || container?.props.Document.isTemplateDoc) {
+                container = container.props.ContainingCollectionView;
+            }
+            if (container) {
+                const alias = Doc.MakeAlias(container.props.Document);
                 alias.viewType = CollectionViewType.Time;
                 let list = Cast(alias.schemaColumns, listSpec(SchemaHeaderField));
                 if (!list) {
@@ -974,6 +994,7 @@ export class DashFieldView {
 
         this._fieldWrapper.appendChild(this._labelSpan);
         this._fieldWrapper.appendChild(this._fieldSpan);
+        this._fieldWrapper.appendChild(this._enumerables);
         (this as any).dom = this._fieldWrapper;
     }
     destroy() {
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 1992c5efa..fdf7c3f42 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -27,6 +27,17 @@ $linkGap : 3px;
         opacity: 1;
     }
 
+    .documentDecorations-selector {
+        pointer-events: auto;
+        height: 15px;
+        width: 15px;
+        left: -20px;
+        top: 20px;
+        display: inline-block;
+        position: absolute;
+        opacity: 0.5;
+    }
+
     .documentDecorations-radius {
         pointer-events: auto;
         background: black;
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4922411e8..c98be0d4a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -8,7 +8,7 @@ import { PositionDocument } from '../../new_fields/documentSchemas';
 import { ScriptField } from '../../new_fields/ScriptField';
 import { Cast, StrCast, NumCast } from "../../new_fields/Types";
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { Utils, setupMoveUpEvents } from "../../Utils";
+import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from "../../Utils";
 import { DocUtils } from "../documents/Documents";
 import { DocumentType } from '../documents/DocumentTypes';
 import { DragManager } from "../util/DragManager";
@@ -244,6 +244,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         SelectionManager.DeselectAll();
     }
 
+    @action
+    onSelectorUp = (e: React.PointerEvent): void => {
+        setupMoveUpEvents(this, e, returnFalse, emptyFunction, action((e) => {
+            const selDoc = SelectionManager.SelectedDocuments()?.[0];
+            if (selDoc) {
+                selDoc.props.ContainingCollectionView?.props.select(false);
+            }
+        }));
+    }
+
     @action
     onRadiusDown = (e: React.PointerEvent): void => {
         setupMoveUpEvents(this, e, this.onRadiusMove, (e) => this._resizeUndo?.end(), (e) => { });
@@ -496,6 +506,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
                 <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer"
                     onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+                {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) : <div id="documentDecorations-levelSelector" className="documentDecorations-selector" title="tap to select containing document"
+                    onPointerDown={this.onSelectorUp} onContextMenu={(e) => e.preventDefault()}>
+                    <FontAwesomeIcon className="documentdecorations-times" icon={faArrowAltCircleUp} size="lg" />
+                </div>}
                 <div id="documentDecorations-borderRadius" className="documentDecorations-radius"
                     onPointerDown={this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}></div>
 
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 9384eb381..79ec6d518 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -77,7 +77,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
     getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
         if (!ele.current) return Transform.Identity();
         const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current);
-        return new Transform(-translateX, -translateY, 1 / scale);
+        return new Transform(-translateX, -translateY, 1);
     }
 
     render() {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 0f3896055..6d94f050c 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -865,25 +865,25 @@ export namespace Doc {
         return (await DocServer.GetRefField(enumeratedFieldKey)) as Doc;
     }
 
-    export function addEnumerationToTextField(doc: Opt<Doc>, enumeratedFieldKey: string, enumeratedDocs: Doc[]) {
-        DocServer.GetRefField(enumeratedFieldKey).then(optionsCollection => {
-            if (!(optionsCollection instanceof Doc)) {
-                optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set` }, enumeratedFieldKey);
-                Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc);
+    export async function addEnumerationToTextField(doc: Opt<Doc>, enumeratedFieldKey: string, enumeratedDocs: Doc[]) {
+        let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey);
+        if (!(optionsCollection instanceof Doc)) {
+            optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set` }, enumeratedFieldKey);
+            Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc);
+        }
+        const options = optionsCollection as Doc;
+        doc && (Doc.GetProto(doc).backgroundColor = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.expandedTemplate||this).${enumeratedFieldKey})?._backgroundColor || "white"`, undefined, { options }));
+        doc && (Doc.GetProto(doc).color = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.expandedTemplate||this).${enumeratedFieldKey}).color || "black"`, undefined, { options }));
+        enumeratedDocs.map(enumeratedDoc => {
+            const found = DocListCast(options.data).find(d => d.title === enumeratedDoc.title);
+            if (found) {
+                found._backgroundColor = enumeratedDoc._backgroundColor || found._backgroundColor;
+                found._color = enumeratedDoc._color || found._color;
+            } else {
+                Doc.AddDocToList(options, "data", enumeratedDoc);
             }
-            const options = optionsCollection as Doc;
-            doc && (Doc.GetProto(doc).backgroundColor = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.expandedTemplate||this).${enumeratedFieldKey})?._backgroundColor || "white"`, undefined, { options }));
-            doc && (Doc.GetProto(doc).color = ComputedField.MakeFunction(`options.data.find(doc => doc.title === (this.expandedTemplate||this).${enumeratedFieldKey}).color || "black"`, undefined, { options }));
-            enumeratedDocs.map(enumeratedDoc => {
-                const found = DocListCast(options.data).find(d => d.title === enumeratedDoc.title);
-                if (found) {
-                    found._backgroundColor = enumeratedDoc._backgroundColor || found._backgroundColor;
-                    found._color = enumeratedDoc._color || found._color;
-                } else {
-                    Doc.AddDocToList(options, "data", enumeratedDoc);
-                }
-            });
         });
+        return optionsCollection;
     }
 }
 
-- 
cgit v1.2.3-70-g09d2


From 786572f3cd674459f55b7f66e8eb257026f373ef Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Mon, 23 Mar 2020 21:24:50 -0400
Subject: fixed RichTextMenu to not obscure selection. fixed templateMenu to
 display a TreeView document of options.  fixed resize in docDecorations for
 dragging corners other than bottom-right.

---
 src/client/util/DropConverter.ts                   |  2 +-
 src/client/views/DocumentButtonBar.tsx             |  4 +-
 src/client/views/DocumentDecorations.scss          |  3 +
 src/client/views/DocumentDecorations.tsx           |  6 +-
 src/client/views/TemplateMenu.tsx                  | 94 ++++++++++++++++++----
 .../views/collections/CollectionDockingView.tsx    | 27 +++++--
 .../views/collections/CollectionLinearView.tsx     | 20 ++---
 .../views/collections/CollectionTreeView.scss      |  1 +
 .../views/collections/CollectionTreeView.tsx       |  7 +-
 .../views/collections/ParentDocumentSelector.tsx   | 22 ++---
 .../collections/collectionFreeForm/MarqueeView.tsx | 12 ++-
 src/client/views/nodes/DocumentView.tsx            | 15 ++--
 src/client/views/nodes/FormattedTextBox.tsx        | 25 ++++--
 src/client/views/search/SearchItem.tsx             |  2 +-
 src/new_fields/Doc.ts                              |  2 -
 src/new_fields/ScriptField.ts                      |  4 +-
 src/new_fields/util.ts                             |  1 -
 .../authentication/models/current_user_utils.ts    | 16 +++-
 18 files changed, 189 insertions(+), 74 deletions(-)

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

diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index 861cde5de..a6a43d06a 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -49,7 +49,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
                 (layoutDoc.layout instanceof Doc) && !data.userDropAction;
             }
             layoutDoc.isTemplateDoc = true;
-            dbox = Docs.Create.FontIconDocument({ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" });
+            dbox = Docs.Create.FontIconDocument({ _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, backgroundColor: StrCast(doc.backgroundColor), title: StrCast(layoutDoc.title), icon: layoutDoc.isTemplateDoc ? "font" : "bolt" });
             dbox.dragFactory = layoutDoc;
             dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined;
             dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)');
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 23875fa33..8a33232b4 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -108,7 +108,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
         this._pullColorAnimating = false;
     });
 
-    get view0() { return this.props.views && this.props.views.length ? this.props.views[0] : undefined; }
+    get view0() { return this.props.views?.[0]; }
 
     @action
     onLinkButtonMoved = (e: PointerEvent): void => {
@@ -250,7 +250,7 @@ export class DocumentButtonBar extends React.Component<{ views: (DocumentView |
 
     @computed
     get contextButton() {
-        return !this.view0 ? (null) : <ParentDocSelector Views={this.props.views.filter(v => v).map(v => v as DocumentView)} Document={this.view0.props.Document} addDocTab={(doc, where) => {
+        return !this.view0 ? (null) : <ParentDocSelector Document={this.view0.props.Document} addDocTab={(doc, where) => {
             where === "onRight" ? CollectionDockingView.AddRightSplit(doc) :
                 this.props.stack ? CollectionDockingView.Instance.AddTab(this.props.stack, doc) :
                     this.view0?.props.addDocTab(doc, "onRight");
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 8d6002f8f..353520026 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -80,6 +80,7 @@ $linkGap : 3px;
     #documentDecorations-topLeftResizer,
     #documentDecorations-bottomRightResizer {
         cursor: nwse-resize;
+        background: dimGray;
     }
 
     #documentDecorations-bottomRightResizer {
@@ -89,6 +90,7 @@ $linkGap : 3px;
     #documentDecorations-topRightResizer,
     #documentDecorations-bottomLeftResizer {
         cursor: nesw-resize;
+        background: dimGray;
     }
 
     #documentDecorations-topResizer,
@@ -158,6 +160,7 @@ $linkGap : 3px;
     padding-top: 5px;
     width: $MINIMIZED_ICON_SIZE;
     height: $MINIMIZED_ICON_SIZE;
+    max-height: 20px;
 }
 
 .documentDecorations-background {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c4abc935f..ff72592d8 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -266,7 +266,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let dist = Math.sqrt((e.clientX - down[0]) * (e.clientX - down[0]) + (e.clientY - down[1]) * (e.clientY - down[1]));
         dist = dist < 3 ? 0 : dist;
         SelectionManager.SelectedDocuments().map(dv => dv.props.Document).map(doc => doc.layout instanceof Doc ? doc.layout : doc.isTemplateForField ? doc : Doc.GetProto(doc)).
-            map(d => d.borderRounding = `${Math.max(0, dist)}px`);
+            map(d => d.borderRounding = `${Math.max(0, dist)}%`);
         return false;
     }
 
@@ -330,6 +330,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 const width = (layoutDoc._width || 0);
                 const height = (layoutDoc._height || (nheight / nwidth * width));
                 const scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling();
+                if (nwidth && nheight) {
+                    if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth;
+                    else dW = dH * nwidth / nheight;
+                }
                 const actualdW = Math.max(width + (dW * scale), 20);
                 const actualdH = Math.max(height + (dH * scale), 20);
                 doc.x = (doc.x || 0) + dX * (actualdW - width);
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 5029b4074..cf6ce2c6f 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,4 +1,4 @@
-import { action, observable, runInAction, ObservableSet } from "mobx";
+import { action, observable, runInAction, ObservableSet, trace, computed } from "mobx";
 import { observer } from "mobx-react";
 import { SelectionManager } from "../util/SelectionManager";
 import { undoBatch } from "../util/UndoManager";
@@ -7,8 +7,13 @@ import { DocumentView } from "./nodes/DocumentView";
 import { Template } from "./Templates";
 import React = require("react");
 import { Doc, DocListCast } from "../../new_fields/Doc";
+import { Docs, } from "../documents/Documents";
 import { StrCast, Cast } from "../../new_fields/Types";
-import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
+import { CollectionTreeView } from "./collections/CollectionTreeView";
+import { returnTrue, emptyFunction, returnFalse, returnOne, emptyPath } from "../../Utils";
+import { Transform } from "../util/Transform";
+import { ScriptField, ComputedField } from "../../new_fields/ScriptField";
+import { Scripting } from "../util/Scripting";
 
 @observer
 class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
@@ -50,7 +55,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
 
     toggleLayout = (e: React.ChangeEvent<HTMLInputElement>, layout: string): void => {
-        this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout));//.setCustomView(e.target.checked, layout));
+        this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout));
+    }
+    toggleDefault = (e: React.ChangeEvent<HTMLInputElement>): void => {
+        this.props.docViews.map(dv => dv.switchViews(false, "layout"));
     }
 
     toggleFloat = (e: React.ChangeEvent<HTMLInputElement>): void => {
@@ -94,27 +102,81 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))).
             filter(key => key.startsWith("layout_")).
             map(key => runInAction(() => this._addedKeys.add(key.replace("layout_", ""))));
-        DocListCast(Cast(CurrentUserUtils.UserDocument.expandingButtons, Doc, null)?.data)?.map(btnDoc => {
-            if (StrCast(Cast(btnDoc?.dragFactory, Doc, null)?.title)) {
-                runInAction(() => this._addedKeys.add(StrCast(Cast(btnDoc?.dragFactory, Doc, null)?.title)));
-            }
-        });
     }
 
+    return100 = () => 100;
+    @computed get scriptField() {
+        return ScriptField.MakeScript("switchView(firstDoc, this)", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name },
+            { firstDoc: this.props.docViews[0].props.Document });
+    }
     render() {
-        const layout = Doc.Layout(this.props.docViews[0].Document);
+        const firstDoc = this.props.docViews[0].props.Document;
+        const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", "");
+        const noteTypesDoc = Cast(Doc.UserDoc().noteTypes, Doc, null);
+        const noteTypes = DocListCast(noteTypesDoc?.data);
+        const addedTypes = DocListCast(Cast(Doc.UserDoc().templateButtons, Doc, null)?.data)
+        const layout = Doc.Layout(firstDoc);
         const templateMenu: Array<JSX.Element> = [];
         this.props.templates.forEach((checked, template) =>
             templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />));
-        templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={this.props.docViews[0].Document._showAudio ? true : false} toggle={this.toggleAudio} />);
-        templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={this.props.docViews[0].Document.z ? true : false} toggle={this.toggleFloat} />);
+        templateMenu.push(<OtherToggle key={"audio"} name={"Audio"} checked={firstDoc._showAudio ? true : false} toggle={this.toggleAudio} />);
+        templateMenu.push(<OtherToggle key={"float"} name={"Float"} checked={firstDoc.z ? true : false} toggle={this.toggleFloat} />);
         templateMenu.push(<OtherToggle key={"chrome"} name={"Chrome"} checked={layout._chromeStatus !== "disabled"} toggle={this.toggleChrome} />);
-        this._addedKeys && Array.from(this._addedKeys).map(layout =>
-            templateMenu.push(<OtherToggle key={layout} name={layout} checked={StrCast(this.props.docViews[0].Document.layoutKey, "layout") === "layout_" + layout} toggle={e => this.toggleLayout(e, layout)} />)
-        );
+        templateMenu.push(<OtherToggle key={"default"} name={"Default"} checked={templateName === "layout"} toggle={this.toggleDefault} />);
+        if (noteTypesDoc) {
+            addedTypes.concat(noteTypes).map(template => template.treeViewChecked = ComputedField.MakeFunction("templateIsUsed(this, firstDoc)", { firstDoc: "string" }, { firstDoc: StrCast(firstDoc.title) }));
+            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)} />));
+            templateMenu.push(
+                <CollectionTreeView
+                    Document={Doc.UserDoc().templateDocs as Doc}
+                    CollectionView={undefined}
+                    ContainingCollectionDoc={undefined}
+                    ContainingCollectionView={undefined}
+                    onCheckedClick={this.scriptField!}
+                    onChildClick={this.scriptField!}
+                    LibraryPath={emptyPath}
+                    dropAction={undefined}
+                    active={returnTrue}
+                    ContentScaling={returnOne}
+                    bringToFront={emptyFunction}
+                    focus={emptyFunction}
+                    whenActiveChanged={emptyFunction}
+                    ScreenToLocalTransform={Transform.Identity}
+                    isSelected={returnFalse}
+                    pinToPres={emptyFunction}
+                    select={emptyFunction}
+                    renderDepth={1}
+                    addDocTab={returnFalse}
+                    PanelWidth={this.return100}
+                    PanelHeight={this.return100}
+                    treeViewHideHeaderFields={true}
+                    annotationsKey={""}
+                    dontRegisterView={true}
+                    fieldKey={"data"}
+                    moveDocument={(doc: Doc) => false}
+                    removeDocument={(doc: Doc) => false}
+                    addDocument={(doc: Doc) => false} />
+            );
+        }
         return <ul className="template-list" style={{ display: "block" }}>
-            {templateMenu}
             <input placeholder="+ layout" ref={this._customRef} onKeyPress={this.onCustomKeypress}></input>
+            {templateMenu}
         </ul>;
     }
-}
\ No newline at end of file
+}
+
+Scripting.addGlobal(function switchView(doc: Doc, template: Doc) {
+    if (template.dragFactory) {
+        template = Cast(template.dragFactory, Doc, null);
+    }
+    let templateTitle = StrCast(template?.title);
+    return templateTitle && DocumentView.makeCustomViewClicked(doc, undefined, Docs.Create.FreeformDocument, templateTitle, template)
+});
+
+Scripting.addGlobal(function templateIsUsed(templateDoc: Doc, firstDocTitlte: string) {
+    const firstDoc = SelectionManager.SelectedDocuments()[0].props.Document;
+    const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title);
+    return StrCast(firstDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked';
+    // return SelectionManager.SelectedDocuments().some(view => StrCast(view.props.Document.layoutKey) === "layout_" + template) ? 'check' : 'unchecked'
+})
\ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 00e22d6fb..1fb78f625 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -2,7 +2,7 @@ import { library } from '@fortawesome/fontawesome-svg-core';
 import { faFile } from '@fortawesome/free-solid-svg-icons';
 import 'golden-layout/src/css/goldenlayout-base.css';
 import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, computed, Lambda, observable, reaction, runInAction } from "mobx";
+import { action, computed, Lambda, observable, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import * as ReactDOM from 'react-dom';
 import Measure from "react-measure";
@@ -20,7 +20,7 @@ import { DocServer } from "../../DocServer";
 import { Docs } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { DocumentManager } from '../../util/DocumentManager';
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
 import { Scripting } from '../../util/Scripting';
 import { SelectionManager } from '../../util/SelectionManager';
 import { Transform } from '../../util/Transform';
@@ -504,15 +504,25 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                     }
                     tab.setActive(true);
                 };
-                ReactDOM.render(<span title="Drag as document"
-                    className="collectionDockingView-dragAsDocument"
-                    onPointerDown={e => {
+                const onDown = (e: React.PointerEvent) => {
+                    if (!(e.nativeEvent as any).defaultPrevented) {
                         e.preventDefault();
                         e.stopPropagation();
                         const dragData = new DragManager.DocumentDragData([doc]);
-                        dragData.dropAction = doc.dropAction === "alias" ? "alias" : doc.dropAction === "copy" ? "copy" : undefined;
+                        dragData.dropAction = doc.dropAction as dropActionType;
                         DragManager.StartDocumentDrag([gearSpan], dragData, e.clientX, e.clientY);
-                    }}><DockingViewButtonSelector Document={doc} Stack={stack} /></span>, gearSpan);
+                    }
+                }
+                let rendered = false;
+                tab.buttonDisposer = reaction(() => ((view: Opt<DocumentView>) => view ? [view] : [])(DocumentManager.Instance.getDocumentView(doc)),
+                    (views) => {
+                        !rendered && ReactDOM.render(<span title="Drag as document" className="collectionDockingView-dragAsDocument" onPointerDown={onDown} >
+                            <DockingViewButtonSelector views={views} Stack={stack} />
+                        </span>,
+                            gearSpan);
+                        rendered = true;
+                    });
+
                 tab.reactComponents = [gearSpan];
                 tab.element.append(gearSpan);
                 tab.reactionDisposer = reaction(() => ({ title: doc.title, degree: Doc.IsBrushedDegree(doc) }), ({ title, degree }) => {
@@ -526,7 +536,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
         }
         tab.closeElement.off('click') //unbind the current click handler
             .click(async function () {
-                tab.reactionDisposer && tab.reactionDisposer();
+                tab.reactionDisposer?.();
+                tab.buttonDisposer?.();
                 const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
                 if (doc instanceof Doc) {
                     const theDoc = doc;
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 79ec6d518..344605412 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -31,7 +31,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         this._dropDisposer && this._dropDisposer();
         this._widthDisposer && this._widthDisposer();
         this._selectedDisposer && this._selectedDisposer();
-        this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => {
+        this.childLayoutPairs.map((pair, ind) => {
             Cast(pair.layout.proto?.onPointerUp, ScriptField)?.script.run({ this: pair.layout.proto }, console.log);
         });
     }
@@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
             (i) => runInAction(() => {
                 this._selectedIndex = i;
                 let selected: any = undefined;
-                this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map(async (pair, ind) => {
+                this.childLayoutPairs.map(async (pair, ind) => {
                     const isSelected = this._selectedIndex === ind;
                     if (isSelected) {
                         selected = pair;
@@ -71,8 +71,6 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
         }
     }
 
-    public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
-
     dimension = () => NumCast(this.props.Document._height); // 2 * the padding
     getTransform = (ele: React.RefObject<HTMLDivElement>) => () => {
         if (!ele.current) return Transform.Identity();
@@ -87,17 +85,21 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
             <div className="collectionLinearView" ref={this.createDashEventsTarget} >
                 <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
                     onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
-                <label htmlFor={`${guid}`} style={{ marginTop: "auto", marginBottom: "auto", background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} title="Close Menu"><p>+</p></label>
+                <label htmlFor={`${guid}`} title="Close Menu" style={{ marginTop: "auto", marginBottom: "auto", 
+                background: StrCast(this.props.Document.backgroundColor, "black") === StrCast(this.props.Document.color, "white") ? "black" : StrCast(this.props.Document.backgroundColor, "black") }} >
+                    <p>+</p>
+                </label>
 
-                <div className="collectionLinearView-content" style={{ height: this.dimension(), width: NumCast(this.props.Document._width, 25), flexDirection: flexDir }}>
-                    {this.childLayoutPairs.filter((pair) => this.isCurrent(pair.layout)).map((pair, ind) => {
+                <div className="collectionLinearView-content" style={{ height: this.dimension(), flexDirection: flexDir }}>
+                    {this.childLayoutPairs.map((pair, ind) => {
                         const nested = pair.layout._viewType === CollectionViewType.Linear;
                         const dref = React.createRef<HTMLDivElement>();
                         const nativeWidth = NumCast(pair.layout._nativeWidth, this.dimension());
                         const deltaSize = nativeWidth * .15 / 2;
-                        return <div className={`collectionLinearView-docBtn` + (pair.layout.onClick || pair.layout.onDragStart ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
+                        const scalable = pair.layout.onClick || pair.layout.onDragStart;
+                        return <div className={`collectionLinearView-docBtn` + (scalable ? "-scalable" : "")} key={pair.layout[Id]} ref={dref}
                             style={{
-                                width: nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize,
+                                width: scalable ? (nested ? pair.layout[WidthSym]() : this.dimension() - deltaSize) : undefined,
                                 height: nested && pair.layout.linearViewIsExpanded ? pair.layout[HeightSym]() : this.dimension() - deltaSize,
                             }}  >
                             <DocumentView
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index fd4a963c3..5efd97a6b 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -22,6 +22,7 @@
     ul {
         list-style: none;
         padding-left: 20px;
+        margin-bottom: 1px;// otherwise vertical scrollbars may pop up for no apparent reason....
     }
 
 
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8af4b6dfd..53df7f041 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -778,7 +778,12 @@ export class CollectionTreeView extends CollectionSubView(Document, undefined as
         const childDocs = this.props.overrideDocuments ? this.props.overrideDocuments : this.childDocs;
         return !childDocs ? (null) : (
             <div className="collectionTreeView-dropTarget" id="body"
-                style={{ background: this.props.backgroundColor?.(this.props.Document), paddingTop: `${NumCast(this.props.Document._yMargin, 20)}px` }}
+                style={{
+                    background: this.props.backgroundColor?.(this.props.Document),
+                    paddingLeft: `${NumCast(this.props.Document._xPadding, 10)}px`,
+                    paddingRight: `${NumCast(this.props.Document._xPadding, 10)}px`,
+                    paddingTop: `${NumCast(this.props.Document._yPadding, 20)}px`
+                }}
                 onContextMenu={this.onContextMenu}
                 onWheel={(e: React.WheelEvent) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
                 onDrop={this.onTreeDrop}
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 43ba5c614..35e3a8958 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -2,7 +2,7 @@ import * as React from "react";
 import './ParentDocumentSelector.scss';
 import { Doc } from "../../../new_fields/Doc";
 import { observer } from "mobx-react";
-import { observable, action, runInAction } from "mobx";
+import { observable, action, runInAction, trace, computed } from "mobx";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { SearchUtil } from "../../util/SearchUtil";
 import { CollectionDockingView } from "./CollectionDockingView";
@@ -14,6 +14,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faCog, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { DocumentView } from "../nodes/DocumentView";
+import { SelectionManager } from "../../util/SelectionManager";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -22,7 +23,6 @@ library.add(faCog);
 
 type SelectorProps = {
     Document: Doc,
-    Views: DocumentView[],
     Stack?: any,
     addDocTab(doc: Doc, location: string): void
 };
@@ -93,7 +93,7 @@ export class ParentDocSelector extends React.Component<SelectorProps> {
 }
 
 @observer
-export class DockingViewButtonSelector extends React.Component<{ Document: Doc, Stack: any }> {
+export class DockingViewButtonSelector extends React.Component<{ views: DocumentView[], Stack: any }> {
     @observable hover = false;
 
     customStylesheet(styles: any) {
@@ -106,15 +106,19 @@ export class DockingViewButtonSelector extends React.Component<{ Document: Doc,
         };
     }
 
-    render() {
-        const view = DocumentManager.Instance.getDocumentView(this.props.Document);
-        const flyout = (
+    @computed get flyout() {
+        trace();
+        return (
             <div className="ParentDocumentSelector-flyout" title=" ">
-                <DocumentButtonBar views={[view]} stack={this.props.Stack} />
+                <DocumentButtonBar views={this.props.views} stack={this.props.Stack} />
             </div>
         );
-        return <span title="Tap for menu, drag tab as document" onPointerDown={e => !this.props.Stack && e.stopPropagation()} className="buttonSelector">
-            <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout} stylesheet={this.customStylesheet}>
+    }
+
+    render() {
+        trace();
+        return <span title="Tap for menu, drag tab as document" onPointerDown={e => { this.props.views[0].select(false); e.stopPropagation(); }} className="buttonSelector">
+            <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}>
                 <FontAwesomeIcon icon={"cog"} size={"sm"} />
             </Flyout>
         </span>;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4bf3329eb..17bba9568 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -107,8 +107,14 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
             });
         } else if (!e.ctrlKey) {
             FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
-            this.props.addLiveTextDocument(
-                Docs.Create.TextDocument("", { _width: NumCast((FormattedTextBox.DefaultLayout as Doc)?._width) || 200, _height: 100, layout: FormattedTextBox.DefaultLayout, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
+            let tbox = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
+            const template = FormattedTextBox.DefaultLayout;
+            if (template instanceof Doc) {
+                tbox._width = NumCast(template._width);
+                tbox.layoutKey = "layout_"+StrCast(template.title);
+                tbox[StrCast(tbox.layoutKey)] = template;
+            }
+            this.props.addLiveTextDocument(tbox);
         }
         e.stopPropagation();
     }
@@ -334,7 +340,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
                 this.props.removeDocument(d);
                 d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
                 d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
-                d.displayTimecode = undefined;
+                d.displayTimecode = undefined;  // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
                 return d;
             });
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 874d075e6..9a7568518 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -516,7 +516,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     static makeCustomViewClicked = (doc: Doc, dataDoc: Opt<Doc>, creator: (documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc, name: string = "custom", docLayoutTemplate?: Doc) => {
         const batch = UndoManager.StartBatch("CustomViewClicked");
         const customName = "layout_" + name;
-        if (!StrCast(doc.title).endsWith(name)) doc.title = doc.title + "_" + name;
         if (doc[customName] === undefined) {
             const _width = NumCast(doc._width);
             const _height = NumCast(doc._height);
@@ -634,13 +633,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             // } else 
             if (custom) {
                 DocumentView.makeNativeViewClicked(this.props.Document);
+                const userDoc = Doc.UserDoc();
 
-                const imgView = Cast(Doc.UserDoc().iconView, Doc, null);
-                const iconImgView = Cast(Doc.UserDoc().iconImageView, Doc, null);
-                const iconColView = Cast(Doc.UserDoc().iconColView, Doc, null);
+                const imgView = Cast(userDoc.iconView, Doc, null);
+                const iconImgView = Cast(userDoc.iconImageView, Doc, null);
+                const iconColView = Cast(userDoc.iconColView, Doc, null);
                 const iconViews = [imgView, iconImgView, iconColView];
-                const expandingButtons = DocListCast(Cast(Doc.UserDoc().expandingButtons, Doc, null)?.data);
-                const allTemplates = iconViews.concat(expandingButtons);
+                const templateButtons = DocListCast(Cast(userDoc.templateButtons, Doc, null)?.data);
+                const noteTypes = DocListCast(Cast(userDoc.noteTypes, Doc, null)?.data);
+                const allTemplates = iconViews.concat(templateButtons).concat(noteTypes);
                 let foundLayout: Opt<Doc>;
                 allTemplates.map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc).forEach(tempDoc => {
                     if (StrCast(tempDoc.title) === this.props.Document.type + "_" + layout) {
@@ -1028,7 +1029,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             </div>);
         const captionView = (!showCaption ? (null) :
             <div className="documentView-captionWrapper">
-                <FormattedTextBox {...this.props} onClick={this.onClickHandler}
+                <FormattedTextBox {...this.props} onClick={this.onClickHandler} dropAction="alias"
                     DataDoc={this._dataDoc} active={returnTrue} Document={this._layoutDoc || this.props.Document}
                     isSelected={this.isSelected} focus={emptyFunction} select={this.select}
                     hideOnLeave={true} fieldKey={showCaption}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 1fa603cbd..e7c9e5e6b 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -47,6 +47,7 @@ import "./FormattedTextBox.scss";
 import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment';
 import React = require("react");
 import { PrefetchProxy } from '../../../new_fields/Proxy';
+import { makeTemplate } from '../../util/DropConverter';
 
 library.add(faEdit);
 library.add(faSmile, faTextHeight, faUpload);
@@ -249,7 +250,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     }
     protected createDropTarget = (ele: HTMLDivElement) => {
         this.ProseRef = ele;
-        this.dropDisposer && this.dropDisposer();
+        this.dropDisposer?.();
         ele && (this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
     }
 
@@ -386,9 +387,14 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
     }
     specificContextMenu = (e: React.MouseEvent): void => {
         const funcs: ContextMenuProps[] = [];
-        this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document.proto as Doc), icon: "eye" });
+        this.props.Document.isTemplateDoc && funcs.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.props.Document), icon: "eye" });
         funcs.push({ description: "Reset Default Layout", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" });
-        !this.props.Document.expandedTemplate && funcs.push({ description: "Make Template", event: () => { this.props.Document.isTemplateDoc = true; Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document); }, icon: "eye" });
+        !this.props.Document.expandedTemplate && funcs.push({
+            description: "Make Template", event: () => {
+                this.props.Document.isTemplateDoc = makeTemplate(this.props.Document, true);
+                Doc.AddDocToList(Cast(Doc.UserDoc().noteTypes, Doc, null), "data", this.props.Document);
+            }, icon: "eye"
+        });
         funcs.push({ description: "Toggle Single Line", event: () => this.props.Document._singleLine = !this.props.Document._singleLine, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Sidebar", event: () => this.props.Document._showSidebar = !this.props.Document._showSidebar, icon: "expand-arrows-alt" });
         funcs.push({ description: "Toggle Audio", event: () => this.props.Document._showAudio = !this.props.Document._showAudio, icon: "expand-arrows-alt" });
@@ -901,6 +907,7 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) {
             e.stopPropagation();
         }
+        this._downX = this._downY = Number.NaN;
     }
 
     @action
@@ -914,12 +921,16 @@ export class FormattedTextBox extends DocAnnotatableComponent<(FieldViewProps &
         prosediv && (prosediv.keeplocation = undefined);
         const pos = this._editorView?.state.selection.$from.pos || 1;
         keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos))));
+        const coords = !Number.isNaN(this._downX) ? { left: this._downX, top: this._downY, bottom: this._downY, right: this._downX } : this._editorView?.coordsAtPos(pos);
 
         // jump rich text menu to this textbox
-        const { current } = this._ref;
-        if (current && this.props.Document._chromeStatus !== "disabled") {
-            const x = Math.min(Math.max(current.getBoundingClientRect().left, 0), window.innerWidth - RichTextMenu.Instance.width);
-            const y = this._ref.current!.getBoundingClientRect().top - RichTextMenu.Instance.height - 50;
+        const bounds = this._ref.current?.getBoundingClientRect();
+        if (bounds && this.props.Document._chromeStatus !== "disabled") {
+            const x = Math.min(Math.max(bounds.left, 0), window.innerWidth - RichTextMenu.Instance.width);
+            let y = Math.min(Math.max(0, bounds.top - RichTextMenu.Instance.height - 50), window.innerHeight - RichTextMenu.Instance.height);
+            if (coords && coords.left > x && coords.left < x + RichTextMenu.Instance.width && coords.top > y && coords.top < y + RichTextMenu.Instance.height + 50) {
+                y = Math.min(bounds.bottom, window.innerHeight - RichTextMenu.Instance.height);
+            }
             RichTextMenu.Instance.jumpTo(x, y);
         }
     }
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 2cbb24da7..63cef5101 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -272,7 +272,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
 
     @computed
     get contextButton() {
-        return <ParentDocSelector Views={DocumentManager.Instance.DocumentViews} Document={this.props.doc} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />;
+        return <ParentDocSelector Document={this.props.doc} addDocTab={(doc, where) => CollectionDockingView.AddRightSplit(doc)} />;
     }
 
     render() {
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 50adf0392..322b57272 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -795,8 +795,6 @@ export namespace Doc {
         const prevLayout = StrCast(doc.layoutKey).split("_")[1];
         const deiconify = prevLayout === "icon" && StrCast(doc.deiconifyLayout) ? "layout_" + StrCast(doc.deiconifyLayout) : "";
         prevLayout === "icon" && (doc.deiconifyLayout = undefined);
-        if (StrCast(doc.title).endsWith("_" + prevLayout) && deiconify) doc.title = StrCast(doc.title).replace("_" + prevLayout, deiconify);
-        else doc.title = undefined;
         doc.layoutKey = deiconify || "layout";
     }
     export function setDocFilterRange(target: Doc, key: string, range?: number[]) {
diff --git a/src/new_fields/ScriptField.ts b/src/new_fields/ScriptField.ts
index 9288fea9e..606f55b7c 100644
--- a/src/new_fields/ScriptField.ts
+++ b/src/new_fields/ScriptField.ts
@@ -119,8 +119,8 @@ export class ScriptField extends ObjectField {
         return compiled.compiled ? new ScriptField(compiled) : undefined;
     }
 
-    public static MakeScript(script: string, params: object = {}) {
-        const compiled = ScriptField.CompileScript(script, params, false);
+    public static MakeScript(script: string, params: object = {}, capturedVariables?: { [name: string]: Field }) {
+        const compiled = ScriptField.CompileScript(script, params, false, capturedVariables);
         return compiled.compiled ? new ScriptField(compiled) : undefined;
     }
 }
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 6d7f6b56e..3ab1b299b 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -7,7 +7,6 @@ import { ObjectField } from "./ObjectField";
 import { action, trace } from "mobx";
 import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
 import { DocServer } from "../client/DocServer";
-import { props } from "bluebird";
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 31588bf82..9c39ce7de 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -53,7 +53,7 @@ export class CurrentUserUtils {
         ];
         doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations" });
         Doc.addFieldEnumerations(Doc.GetProto(noteTemplates[4]), "taskStatus", taskStatusValues);
-        doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(noteTemplates.map(nt => makeTemplate(nt, true, StrCast(nt.style)) ? nt : nt), { title: "Note Types", _height: 75 }));
+        doc.noteTypes = new PrefetchProxy(Docs.Create.TreeDocument(noteTemplates.map(nt => makeTemplate(nt, true, StrCast(nt.style)) ? nt : nt), { title: "Note Layouts", _height: 75 }));
     }
 
     // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
@@ -292,12 +292,20 @@ export class CurrentUserUtils {
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: slideTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "sticky-note" });
         doc.descriptionBtn = Docs.Create.FontIconDocument(
             { _nativeWidth: 100, _nativeHeight: 100, _width: 100, _height: 100, dropAction: "alias", onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), dragFactory: descriptionTemplate, removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "sticky-note" });
-        doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.slidesBtn as Doc, doc.descriptionBtn as Doc,
-        ...DocListCast(Cast(doc.noteTypes, Doc, null))], {
+        doc.templateButtons = Docs.Create.LinearDocument([doc.slidesBtn as Doc, doc.descriptionBtn as Doc], {
+            title: "template buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", dontSelect: true,
+            backgroundColor: "black", treeViewPreventOpen: true, forceActive: true, lockedPosition: true, _chromeStatus: "disabled", linearViewIsExpanded: true,
+            dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
+        });
+        doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc, doc.templateButtons as Doc], {
             title: "expanding buttons", _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0",
-            backgroundColor: "black", treeViewPreventOpen: true, forceActive: true, lockedPosition: true,
+            backgroundColor: "black", treeViewPreventOpen: true, forceActive: true, lockedPosition: true, linearViewIsExpanded: true,
             dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name })
         });
+        doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([doc.noteTypes as Doc, doc.templateButtons as Doc], {
+            title: "template layouts", _xPadding: 0, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)",
+                { dragData: DragManager.DocumentDragData.name })
+        }));
     }
 
     // sets up the default set of documents to be shown in the Overlay layer
-- 
cgit v1.2.3-70-g09d2

