From e0316c21838613df0fbf43df6a9ca5d696c52f47 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 30 Jul 2019 20:19:58 -0400
Subject: more interesting speech commands and command manager pattern for
 DictationManager

---
 src/client/views/nodes/DocumentView.tsx | 48 ++++++++++++++++++++++++++++++++-
 1 file changed, 47 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dc56c1c8f..00416ca42 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -43,6 +43,7 @@ import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-i
 import { DocumentDecorations } from '../DocumentDecorations';
 import { CognitiveServices } from '../../cognitive_services/CognitiveServices';
 import DictationManager from '../../util/DictationManager';
+import { CollectionViewType } from '../collections/CollectionBaseView';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -153,6 +154,43 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     constructor(props: DocumentViewProps) {
         super(props);
+        let fixed = DictationManager.Instance.registerStatic;
+        let dynamic = DictationManager.Instance.registerDynamic;
+        fixed(["Open Fields"], DocumentView.OpenFieldsDictation);
+        fixed(["Clear"], DocumentView.ClearChildren);
+        dynamic(/create (\w+) documents of type (image|nested collection)/g, DocumentView.BuildLayout);
+        dynamic(/view as (freeform|stacking|masonry|schema|tree)/g, DocumentView.SetViewMode);
+    }
+
+    public static ClearChildren = (target: DocumentView) => {
+        Doc.GetProto(target.props.Document).data = new List();
+    }
+
+    public static BuildLayout = (target: DocumentView, matches: RegExpExecArray) => {
+        let count = DictationManager.Instance.interpretNumber(matches[1]);
+        let what = matches[2];
+        if (!("viewType" in target.props.Document)) {
+            return;
+        }
+        let dataDoc = Doc.GetProto(target.props.Document);
+        let fieldKey = "data";
+        for (let i = 0; i < count; i++) {
+            let created: Doc | undefined;
+            switch (what) {
+                case "image":
+                    created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg");
+                    break;
+                case "nested collection":
+                    created = Docs.Create.FreeformDocument([], {});
+                    break;
+            }
+            created && Doc.AddDocToList(dataDoc, fieldKey, created);
+        }
+    }
+
+    public static SetViewMode = (target: DocumentView, matches: RegExpExecArray) => {
+        let mode = CollectionViewType.ValueOf(matches[1]);
+        mode && (target.props.Document.viewType = mode);
     }
 
     _animateToIconDisposer?: IReactionDisposer;
@@ -413,7 +451,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); }
 
     @undoBatch
-    fieldsClicked = (): void => { let kvp = Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.dataDoc, "onRight"); }
+    fieldsClicked = (): void => {
+        let kvp = Docs.Create.KVPDocument(this.props.Document, { width: 300, height: 300 });
+        this.props.addDocTab(kvp, this.dataDoc, "onRight");
+    }
+
+    public static OpenFieldsDictation = (target: DocumentView) => {
+        let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
+        target.props.addDocTab(kvp, target.dataDoc, "onRight");
+    }
 
     @undoBatch
     makeBtnClicked = (): void => {
-- 
cgit v1.2.3-70-g09d2


From 31a8bb32e696d58816329b66cfc5b92907494d1b Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 30 Jul 2019 23:29:23 -0400
Subject: refactor

---
 src/client/util/DictationManager.ts     | 88 +++++++++++++++++++++++++++------
 src/client/views/nodes/DocumentView.tsx | 46 -----------------
 2 files changed, 72 insertions(+), 62 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 60b25afc5..6d67f6d6d 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -4,6 +4,10 @@ import { SelectionManager } from "./SelectionManager";
 import { DocumentView } from "../views/nodes/DocumentView";
 import { UndoManager } from "./UndoManager";
 import * as converter from "words-to-numbers";
+import { Doc } from "../../new_fields/Doc";
+import { List } from "../../new_fields/List";
+import { Docs } from "../documents/Documents";
+import { CollectionViewType } from "../views/collections/CollectionBaseView";
 
 namespace CORE {
     export interface IWindow extends Window {
@@ -12,14 +16,12 @@ namespace CORE {
 }
 
 const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
-export type Action = (target: DocumentView) => any | Promise<any>;
-export type DynamicAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
-export type RegexEntry = { key: RegExp, value: DynamicAction };
+export type IndependentAction = (target: DocumentView) => any | Promise<any>;
+export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
+export type RegexEntry = { key: RegExp, value: DependentAction };
 
 export default class DictationManager {
     public static Instance = new DictationManager();
-    private registeredCommands = new Map<string, Action>();
-    private registeredRegexes: RegexEntry[] = [];
     private isListening = false;
     private recognizer: any;
 
@@ -60,13 +62,13 @@ export default class DictationManager {
         return title.replace("...", "").toLowerCase().trim();
     }
 
-    public registerStatic = (keys: Array<string>, action: Action, overwrite = false) => {
+    public registerStatic = (keys: Array<string>, action: IndependentAction, overwrite = false) => {
         let success = true;
         keys.forEach(key => {
             key = this.sanitize(key);
-            let existing = this.registeredCommands.get(key);
+            let existing = RegisteredCommands.Independent.get(key);
             if (!existing || overwrite) {
-                this.registeredCommands.set(key, action);
+                RegisteredCommands.Independent.set(key, action);
             } else {
                 success = false;
             }
@@ -86,8 +88,8 @@ export default class DictationManager {
         return typeof converted === "string" ? parseInt(converted) : converted;
     }
 
-    public registerDynamic = (dynamicKey: RegExp, action: DynamicAction) => {
-        this.registeredRegexes.push({
+    public registerDynamic = (dynamicKey: RegExp, action: DependentAction) => {
+        RegisteredCommands.Dependent.push({
             key: dynamicKey,
             value: action
         });
@@ -101,20 +103,20 @@ export default class DictationManager {
         let batch = UndoManager.StartBatch("Dictation Action");
         phrase = this.sanitize(phrase);
 
-        let registeredAction = this.registeredCommands.get(phrase);
-        if (registeredAction) {
-            await registeredAction(target);
+        let independentAction = RegisteredCommands.Independent.get(phrase);
+        if (independentAction) {
+            await independentAction(target);
             return true;
         }
 
         let success = false;
-        for (let entry of this.registeredRegexes) {
+        for (let entry of RegisteredCommands.Dependent) {
             let regex = entry.key;
-            let registeredDynamicAction = entry.value;
+            let dependentAction = entry.value;
             let matches = regex.exec(phrase);
             regex.lastIndex = 0;
             if (matches !== null) {
-                await registeredDynamicAction(target, matches);
+                await dependentAction(target, matches);
                 success = true;
                 break;
             }
@@ -124,4 +126,58 @@ export default class DictationManager {
         return success;
     }
 
+}
+
+export namespace RegisteredCommands {
+
+    export const Independent = new Map<string, IndependentAction>([
+
+        ["clear", (target: DocumentView) => {
+            Doc.GetProto(target.props.Document).data = new List();
+        }],
+
+        ["open fields", (target: DocumentView) => {
+            let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
+            target.props.addDocTab(kvp, target.dataDoc, "onRight");
+        }]
+
+    ]);
+
+    export const Dependent = new Array<RegexEntry>(
+
+        {
+            key: /create (\w+) documents of type (image|nested collection)/g,
+            value: (target: DocumentView, matches: RegExpExecArray) => {
+                let count = DictationManager.Instance.interpretNumber(matches[1]);
+                let what = matches[2];
+                if (!("viewType" in target.props.Document)) {
+                    return;
+                }
+                let dataDoc = Doc.GetProto(target.props.Document);
+                let fieldKey = "data";
+                for (let i = 0; i < count; i++) {
+                    let created: Doc | undefined;
+                    switch (what) {
+                        case "image":
+                            created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg");
+                            break;
+                        case "nested collection":
+                            created = Docs.Create.FreeformDocument([], {});
+                            break;
+                    }
+                    created && Doc.AddDocToList(dataDoc, fieldKey, created);
+                }
+            }
+        },
+
+        {
+            key: /view as (freeform|stacking|masonry|schema|tree)/g,
+            value: (target: DocumentView, matches: RegExpExecArray) => {
+                let mode = CollectionViewType.ValueOf(matches[1]);
+                mode && (target.props.Document.viewType = mode);
+            }
+        }
+
+    );
+
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 00416ca42..db8203167 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -152,47 +152,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     set templates(templates: List<string>) { this.props.Document.templates = templates; }
     screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
 
-    constructor(props: DocumentViewProps) {
-        super(props);
-        let fixed = DictationManager.Instance.registerStatic;
-        let dynamic = DictationManager.Instance.registerDynamic;
-        fixed(["Open Fields"], DocumentView.OpenFieldsDictation);
-        fixed(["Clear"], DocumentView.ClearChildren);
-        dynamic(/create (\w+) documents of type (image|nested collection)/g, DocumentView.BuildLayout);
-        dynamic(/view as (freeform|stacking|masonry|schema|tree)/g, DocumentView.SetViewMode);
-    }
-
-    public static ClearChildren = (target: DocumentView) => {
-        Doc.GetProto(target.props.Document).data = new List();
-    }
-
-    public static BuildLayout = (target: DocumentView, matches: RegExpExecArray) => {
-        let count = DictationManager.Instance.interpretNumber(matches[1]);
-        let what = matches[2];
-        if (!("viewType" in target.props.Document)) {
-            return;
-        }
-        let dataDoc = Doc.GetProto(target.props.Document);
-        let fieldKey = "data";
-        for (let i = 0; i < count; i++) {
-            let created: Doc | undefined;
-            switch (what) {
-                case "image":
-                    created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg");
-                    break;
-                case "nested collection":
-                    created = Docs.Create.FreeformDocument([], {});
-                    break;
-            }
-            created && Doc.AddDocToList(dataDoc, fieldKey, created);
-        }
-    }
-
-    public static SetViewMode = (target: DocumentView, matches: RegExpExecArray) => {
-        let mode = CollectionViewType.ValueOf(matches[1]);
-        mode && (target.props.Document.viewType = mode);
-    }
-
     _animateToIconDisposer?: IReactionDisposer;
     _reactionDisposer?: IReactionDisposer;
     @action
@@ -456,11 +415,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.props.addDocTab(kvp, this.dataDoc, "onRight");
     }
 
-    public static OpenFieldsDictation = (target: DocumentView) => {
-        let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
-        target.props.addDocTab(kvp, target.dataDoc, "onRight");
-    }
-
     @undoBatch
     makeBtnClicked = (): void => {
         let doc = Doc.GetProto(this.props.Document);
-- 
cgit v1.2.3-70-g09d2


From 49faa4e76f91fed04bb1923d81dd23d57a157a63 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Wed, 31 Jul 2019 07:35:41 -0400
Subject: grouping experiment

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 49 +++++++++++++++++++++-
 src/client/views/nodes/DocumentView.tsx            |  1 +
 2 files changed, 49 insertions(+), 1 deletion(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8dac785e1..c4c3ba084 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -38,6 +38,7 @@ import { faTable, faPaintBrush, faAsterisk, faExpandArrowsAlt, faCompressArrowsA
 import { undo } from "prosemirror-history";
 import { number } from "prop-types";
 import { ContextMenu } from "../../ContextMenu";
+import { SwatchesPicker } from "react-color";
 
 library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass);
 
@@ -125,7 +126,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true");
     }
 
-
+    intersectRect(r1: { left: number, top: number, width: number, height: number },
+        r2: { left: number, top: number, width: number, height: number }) {
+        return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
+    }
+    bounsdSelect(doc: Doc, doc2: Doc) {
+        var x2 = NumCast(doc2.x) - 25;
+        var y2 = NumCast(doc2.y) - 25;
+        var w2 = NumCast(doc2.width) + 25;
+        var h2 = NumCast(doc2.height) + 25;
+        var x = NumCast(doc.x) - 25;
+        var y = NumCast(doc.y) - 25;
+        var w = NumCast(doc.width) + 25;
+        var h = NumCast(doc.height) + 25;
+        if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
+            return true;
+        }
+        return false;
+    }
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
@@ -151,6 +169,35 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         }
                         this.bringToFront(d);
                     });
+
+                    let sets: (Doc[])[] = []
+                    this.childDocs.map(c => {
+                        let included = []
+                        for (let i = 0; i < sets.length; i++) {
+                            for (let j = 0; j < sets[i].length; j++) {
+                                if (this.bounsdSelect(c, sets[i][j])) {
+                                    included.push(i);
+                                    break;
+                                }
+                            }
+                        }
+                        if (included.length === 0)
+                            sets.push([c]);
+                        else if (included.length === 1)
+                            sets[included[0]].push(c);
+                        else {
+                            sets[included[0]].push(c);
+                            for (let s = 1; s < included.length; s++) {
+                                sets[included[0]].push(...sets[included[s]]);
+                                sets[included[s]].length = 0;
+                            }
+                        }
+                    });
+                    for (let s = 0; s < sets.length; s++) {
+                        for (let i = 0; i < sets[s].length; i++) {
+                            Doc.GetProto(sets[s][i]).cluster = s;
+                        }
+                    }
                 }
             }
             else if (de.data instanceof DragManager.AnnotationDragData) {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4b5cf3a43..2dc2c18a3 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -694,6 +694,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
+                    boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-- 
cgit v1.2.3-70-g09d2


From ec224416fc454c7fdbb62943408226c973d8c751 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 1 Aug 2019 02:30:47 -0400
Subject: fixed import ordering issue, allow for multiple targets and valid
 document types

---
 src/client/documents/Documents.ts                  | 45 ++++++++-------
 src/client/util/DictationManager.ts                | 66 ++++++++--------------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  8 ---
 src/client/views/nodes/DocumentView.tsx            |  1 -
 src/client/views/search/FilterBox.tsx              |  2 +-
 src/client/views/search/SearchItem.tsx             |  2 +-
 src/client/views/search/ToggleBar.tsx              |  2 +-
 7 files changed, 49 insertions(+), 77 deletions(-)

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

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2a1f63d59..3c7de17c8 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,3 +1,24 @@
+export enum DocumentType {
+    NONE = "none",
+    TEXT = "text",
+    HIST = "histogram",
+    IMG = "image",
+    WEB = "web",
+    COL = "collection",
+    KVP = "kvp",
+    VID = "video",
+    AUDIO = "audio",
+    PDF = "pdf",
+    ICON = "icon",
+    IMPORT = "import",
+    LINK = "link",
+    LINKDOC = "linkdoc",
+    BUTTON = "button",
+    TEMPLATE = "template",
+    EXTENSION = "extension",
+    YOUTUBE = "youtube",
+}
+
 import { HistogramField } from "../northstar/dash-fields/HistogramField";
 import { HistogramBox } from "../northstar/dash-nodes/HistogramBox";
 import { HistogramOperation } from "../northstar/operations/HistogramOperation";
@@ -25,14 +46,13 @@ import { OmitKeys, JSONUtils } from "../../Utils";
 import { ImageField, VideoField, AudioField, PdfField, WebField, YoutubeField } from "../../new_fields/URLField";
 import { HtmlField } from "../../new_fields/HtmlField";
 import { List } from "../../new_fields/List";
-import { Cast, NumCast, StrCast, ToConstructor, InterfaceValue, FieldValue } from "../../new_fields/Types";
+import { Cast, NumCast } from "../../new_fields/Types";
 import { IconField } from "../../new_fields/IconField";
 import { listSpec } from "../../new_fields/Schema";
 import { DocServer } from "../DocServer";
 import { dropActionType } from "../util/DragManager";
 import { DateField } from "../../new_fields/DateField";
 import { UndoManager } from "../util/UndoManager";
-import { RouteStore } from "../../server/RouteStore";
 import { YoutubeBox } from "../apis/youtube/YoutubeBox";
 import { CollectionDockingView } from "../views/collections/CollectionDockingView";
 import { LinkManager } from "../util/LinkManager";
@@ -44,27 +64,6 @@ import { SchemaHeaderField, RandomPastel } from "../../new_fields/SchemaHeaderFi
 var requestImageSize = require('../util/request-image-size');
 var path = require('path');
 
-export enum DocumentType {
-    NONE = "none",
-    TEXT = "text",
-    HIST = "histogram",
-    IMG = "image",
-    WEB = "web",
-    COL = "collection",
-    KVP = "kvp",
-    VID = "video",
-    AUDIO = "audio",
-    PDF = "pdf",
-    ICON = "icon",
-    IMPORT = "import",
-    LINK = "link",
-    LINKDOC = "linkdoc",
-    BUTTON = "button",
-    TEMPLATE = "template",
-    EXTENSION = "extension",
-    YOUTUBE = "youtube",
-}
-
 export interface DocumentOptions {
     x?: number;
     y?: number;
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 49afe5371..b0866a826 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -6,9 +6,9 @@ import { Doc } from "../../new_fields/Doc";
 import { List } from "../../new_fields/List";
 import { Docs, DocumentType } from "../documents/Documents";
 import { CollectionViewType } from "../views/collections/CollectionBaseView";
-import { listSpec } from "../../new_fields/Schema";
 import { Cast, CastCtor } from "../../new_fields/Types";
-import { ImageField, AudioField } from "../../new_fields/URLField";
+import { listSpec } from "../../new_fields/Schema";
+import { AudioField, ImageField } from "../../new_fields/URLField";
 import { HistogramField } from "../northstar/dash-fields/HistogramField";
 
 namespace CORE {
@@ -17,7 +17,7 @@ namespace CORE {
     }
 }
 
-const Mapping = new Map<DocumentType, CastCtor>([
+const ConstructorMap = new Map<DocumentType, CastCtor>([
     [DocumentType.COL, listSpec(Doc)],
     [DocumentType.AUDIO, AudioField],
     [DocumentType.IMG, ImageField],
@@ -26,7 +26,7 @@ const Mapping = new Map<DocumentType, CastCtor>([
 ]);
 
 const tryCast = (view: DocumentView, type: DocumentType) => {
-    let ctor = Mapping.get(type);
+    let ctor = ConstructorMap.get(type);
     if (!ctor) {
         return false;
     }
@@ -82,28 +82,6 @@ export default class DictationManager {
         });
     }
 
-    private sanitize = (title: string) => {
-        return title.replace("...", "").toLowerCase().trim();
-    }
-
-    public registerStatic = (keys: Array<string>, action: IndependentAction, filter?: ActionPredicate) => {
-        let success = true;
-        keys.forEach(key => {
-            key = this.sanitize(key);
-            let existing = RegisteredCommands.Independent.get(key);
-            if (!existing) {
-                let unit = {
-                    action: action,
-                    filter: filter
-                };
-                RegisteredCommands.Independent.set(key, unit);
-            } else {
-                success = false;
-            }
-        });
-        return success;
-    }
-
     public interpretNumber = (number: string) => {
         let initial = parseInt(number);
         if (!isNaN(initial)) {
@@ -116,34 +94,38 @@ export default class DictationManager {
         return typeof converted === "string" ? parseInt(converted) : converted;
     }
 
-    public registerDynamic = (dynamicKey: RegExp, action: DependentAction) => {
-        RegisteredCommands.Dependent.push({
-            expression: dynamicKey,
-            action: action
-        });
-    }
-
     @undoBatch
     public execute = async (phrase: string) => {
-        let target = SelectionManager.SelectedDocuments()[0];
-        if (!target) {
+        let targets = SelectionManager.SelectedDocuments();
+        if (!targets || !targets.length) {
             return;
         }
-        phrase = this.sanitize(phrase);
 
         let entry = RegisteredCommands.Independent.get(phrase);
-        if (entry && (!entry.restrictTo || validate(target, entry.restrictTo))) {
-            await entry.action(target);
-            return true;
+        if (entry) {
+            let success = false;
+            for (let target of targets) {
+                if (!entry.restrictTo || validate(target, entry.restrictTo)) {
+                    await entry.action(target);
+                    success = true;
+                }
+            }
+            return success;
         }
 
         for (let entry of RegisteredCommands.Dependent) {
             let regex = entry.expression;
             let matches = regex.exec(phrase);
             regex.lastIndex = 0;
-            if (matches !== null && (!entry.restrictTo || validate(target, entry.restrictTo))) {
-                await entry.action(target, matches);
-                return true;
+            if (matches !== null) {
+                let success = false;
+                for (let target of targets) {
+                    if (!entry.restrictTo || validate(target, entry.restrictTo)) {
+                        await entry.action(target, matches);
+                        success = true;
+                    }
+                }
+                return success;
             }
         }
 
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 59c77f1c9..fa49e7175 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -123,14 +123,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         });
     }
 
-    constructor(props: SubCollectionViewProps) {
-        super(props);
-        let fixed = DictationManager.Instance.registerStatic;
-        fixed(["Unset Fit To Container", "Set Fit To Container"], this.fitToContainer);
-        fixed(["Arrange contents in grid"], this.arrangeContents);
-        fixed(["Analyze Strokes"], this.analyzeStrokes);
-    }
-
     @computed get fieldExtensionDoc() {
         return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true");
     }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 39574db0f..51661d1ae 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -41,7 +41,6 @@ import { ClientUtils } from '../../util/ClientUtils';
 import { EditableView } from '../EditableView';
 import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons';
 import { DocumentDecorations } from '../DocumentDecorations';
-import { CognitiveServices } from '../../cognitive_services/CognitiveServices';
 import DictationManager from '../../util/DictationManager';
 import { CollectionViewType } from '../collections/CollectionBaseView';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 995ddd5c3..3e8582d61 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -384,7 +384,7 @@ export class FilterBox extends React.Component {
                     <div className="active-icon description">Collection Filters Active</div>
                 </div> : undefined}
             </div>
-        )
+        );
     }
 
     // Useful queries:
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 0390359b3..6fbc92007 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -134,7 +134,7 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> {
                 <div className="collection"><a onMouseEnter={this.highlightDoc(this.props.doc1)} onMouseLeave={this.unHighlightDoc(this.props.doc1)} onClick={this.getOnClick(this.props.doc1)}>Doc 1: {this.props.doc2.title}</a></div>
                 <div><a onMouseEnter={this.highlightDoc(this.props.doc2)} onMouseLeave={this.unHighlightDoc(this.props.doc2)} onClick={this.getOnClick(this.props.doc2)}>Doc 2: {this.props.doc1.title}</a></div>
             </div>
-        )
+        );
     }
 
 }
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
index a30104089..ed5ecd3ba 100644
--- a/src/client/views/search/ToggleBar.tsx
+++ b/src/client/views/search/ToggleBar.tsx
@@ -59,7 +59,7 @@ export class ToggleBar extends React.Component<ToggleBarProps>{
         this._forwardTimeline.play();
         this._forwardTimeline.reverse();
         this.props.handleChange();
-        console.log(this.props.getStatus())
+        console.log(this.props.getStatus());
     }
 
     @action.bound
-- 
cgit v1.2.3-70-g09d2


From 6f3d4a7015e15e0523fc194f7f911f6d45259165 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 1 Aug 2019 13:57:59 -0400
Subject: added floating docs

---
 src/client/util/DragManager.ts                     |  7 +++-
 src/client/views/TemplateMenu.tsx                  | 44 ++++++++++++++++++++-
 .../collectionFreeForm/CollectionFreeFormView.scss |  1 +
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 46 +++++++++++++++-------
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  1 +
 src/client/views/nodes/DocumentView.tsx            | 42 +++++++++-----------
 6 files changed, 100 insertions(+), 41 deletions(-)

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

diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index abcc3a4e1..0b5c785a4 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -10,6 +10,7 @@ import { LinkManager } from "./LinkManager";
 import { SelectionManager } from "./SelectionManager";
 import { SchemaHeaderField } from "../../new_fields/SchemaHeaderField";
 import { DocumentDecorations } from "../views/DocumentDecorations";
+import { NumberLiteralType } from "typescript";
 
 export type dropActionType = "alias" | "copy" | undefined;
 export function SetupDrag(
@@ -140,6 +141,10 @@ export namespace DragManager {
         dragHasStarted?: () => void;
 
         withoutShiftDrag?: boolean;
+
+        offsetX?: number;
+
+        offsetY?: number;
     }
 
     export interface DragDropDisposer {
@@ -423,7 +428,7 @@ export namespace DragManager {
             lastX = e.pageX;
             lastY = e.pageY;
             dragElements.map((dragElement, i) => (dragElement.style.transform =
-                `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`)
+                `translate(${(xs[i] += moveX) + (options ? (options.offsetX || 0) : 0)}px, ${(ys[i] += moveY) + (options ? (options.offsetY || 0) : 0)}px)  scale(${scaleXs[i]}, ${scaleYs[i]})`)
             );
         };
 
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 1b32f0ddd..c413650f0 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -7,6 +7,10 @@ import { DocumentView } from "./nodes/DocumentView";
 import { Template } from "./Templates";
 import React = require("react");
 import { undoBatch } from "../util/UndoManager";
+import { DocumentManager } from "../util/DocumentManager";
+import { NumCast } from "../../new_fields/Types";
+import { DragManager } from "../util/DragManager";
+import { SelectionManager } from "../util/SelectionManager";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -35,11 +39,46 @@ export interface TemplateMenuProps {
 @observer
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
     @observable private _hidden: boolean = true;
+    dragRef = React.createRef<HTMLDivElement>();
 
     constructor(props: TemplateMenuProps) {
         super(props);
     }
 
+    toggleFloat = (e: React.MouseEvent): void => {
+        SelectionManager.DeselectAll();
+        let topDocView = this.props.docs[0];
+        let topDoc = topDocView.props.Document;
+        let xf = topDocView.props.ScreenToLocalTransform();
+        let ex = e.clientX;
+        let ey = e.clientY;
+        undoBatch(action(() => topDoc.z = topDoc.z ? 0 : 1))();
+        if (!topDoc.z) {
+            setTimeout(() => {
+                let newDocView = DocumentManager.Instance.getDocumentView(topDoc);
+                if (newDocView) {
+                    let de = new DragManager.DocumentDragData([topDoc], [undefined]);
+                    de.moveDocument = topDocView.props.moveDocument;
+                    let xf = newDocView.ContentDiv!.getBoundingClientRect();
+                    console.log("ex = " + ex + " " + xf.left + " " + (ex - xf.left));
+                    DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, {
+                        offsetX: (ex - xf.left), offsetY: (ey - xf.top),
+                        handlers: {
+                            dragComplete: () => { },
+                        },
+                        hideSource: false
+                    });
+                }
+            }, 10);
+        } else if (topDocView.props.ContainingCollectionView) {
+            let collView = topDocView.props.ContainingCollectionView!;
+            let [sx, sy] = xf.inverse().transformPoint(0, 0);
+            let [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy);
+            topDoc.x = x;
+            topDoc.y = y;
+        }
+    }
+
     @undoBatch
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
@@ -89,9 +128,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         return (
             <div className="templating-menu" >
                 <div title="Template Options" className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
-                <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+                <ul id="template-list" ref={this.dragRef} style={{ display: this._hidden ? "none" : "block" }}>
                     {templateMenu}
-                    <button style={{ display: this._hidden ? "none" : "block" }} onClick={this.clearTemplates}>Clear</button>
+                    <button onClick={this.toggleFloat}>Float</button>
+                    <button onClick={this.clearTemplates}>Clear</button>
                 </ul>
             </div>
         );
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index cca199afa..c4311fa52 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -46,6 +46,7 @@
     border-radius: inherit;
     box-sizing: border-box;
     position: absolute;
+    overflow: hidden;
 
     .marqueeView {
         overflow: hidden;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 29f9b1429..9cbc652b9 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -77,7 +77,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     @computed get contentBounds() {
-        let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds).map(e => e.bounds!)) : undefined;
+        let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined;
         let res = {
             panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0,
             panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0,
@@ -98,6 +98,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0;  // shift so pan position is at center of window for non-overlay collections
     private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
     private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
+    private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1);
     private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
     private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
     private addLiveTextBox = (newBox: Doc) => {
@@ -131,12 +132,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
         let xf = this.getTransform();
+        let xfo = this.getTransformOverlay();
         let [xp, yp] = xf.transformPoint(de.x, de.y);
+        let [xpo, ypo] = xfo.transformPoint(de.x, de.y);
         if (super.drop(e, de)) {
             if (de.data instanceof DragManager.DocumentDragData) {
                 if (de.data.droppedDocuments.length) {
-                    let x = xp - de.data.xOffset;
-                    let y = yp - de.data.yOffset;
+                    let z = NumCast(de.data.draggedDocuments[0].z);
+                    let x = (z ? xpo : xp) - de.data.xOffset;
+                    let y = (z ? ypo : yp) - de.data.yOffset;
                     let dropX = NumCast(de.data.droppedDocuments[0].x);
                     let dropY = NumCast(de.data.droppedDocuments[0].y);
                     de.data.droppedDocuments.forEach(d => {
@@ -292,8 +296,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
         this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
         this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY;
-        // this.props.Document.panX = panX;
-        // this.props.Document.panY = panY;
         if (this.props.Document.scrollY) {
             this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym]();
         }
@@ -396,7 +398,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             addDocument: this.props.addDocument,
             removeDocument: this.props.removeDocument,
             moveDocument: this.props.moveDocument,
-            ScreenToLocalTransform: this.getTransform,
+            ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform,
             renderDepth: this.props.renderDepth + 1,
             selectOnLoad: pair.layout[Id] === this._selectOnLoaded,
             PanelWidth: pair.layout[WidthSym],
@@ -436,23 +438,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         };
     }
 
-    getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, width?: number, height?: number, state?: any } {
+    getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } {
         const result = script.script.run(params);
         if (!result.success) {
             return {};
         }
-        return result.result === undefined ? {} : result.result;
+        let doc = params.doc;
+        return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result;
     }
 
-    private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } } | undefined {
+    private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } } | undefined {
         if (viewDef.type === "text") {
             const text = Cast(viewDef.text, "string");
             const x = Cast(viewDef.x, "number");
             const y = Cast(viewDef.y, "number");
+            const z = Cast(viewDef.z, "number");
             const width = Cast(viewDef.width, "number");
             const height = Cast(viewDef.height, "number");
             const fontSize = Cast(viewDef.fontSize, "number");
-            if ([text, x, y, width, height].some(val => val === undefined)) {
+            if ([text, x, y, z, width, height].some(val => val === undefined)) {
                 return undefined;
             }
 
@@ -460,7 +464,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 ele: <div className="collectionFreeform-customText" style={{
                     transform: `translate(${x}px, ${y}px)`,
                     width, height, fontSize
-                }}>{text}</div>, bounds: { x: x!, y: y!, width: width!, height: height! }
+                }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! }
             };
         }
     }
@@ -472,7 +476,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const script = this.Document.arrangeScript;
         let state: any = undefined;
         const docs = this.childDocs;
-        let elements: { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } }[] = [];
+        let elements: { ele: JSX.Element, bounds?: { x: number, y: number, z?: number, width: number, height: number } }[] = [];
         if (initScript) {
             const initResult = initScript.script.run({ docs, collection: this.Document });
             if (initResult.success) {
@@ -494,13 +498,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 let minim = BoolCast(doc.isMinimized);
                 if (minim === undefined || !minim) {
                     const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) :
-                        { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
+                        { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") };
                     state = pos.state === undefined ? state : pos.state;
                     prev.push({
                         ele: <CollectionFreeFormDocumentView key={doc[Id]}
                             x={script ? pos.x : undefined} y={script ? pos.y : undefined}
                             width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />,
-                        bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, width: pos.width, height: pos.height } : undefined
+                        bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: pos.width, height: pos.height } : undefined
                     });
                 }
             }
@@ -514,8 +518,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
 
     @computed.struct
     get views() {
-        return this.elements.map(ele => ele.ele);
+        return this.elements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele);
     }
+    @computed.struct
+    get overlayViews() {
+        return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+    }
+
 
     @action
     onCursorMove = (e: React.PointerEvent) => {
@@ -603,6 +612,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
         ...this.views
     ]
+    private overlayChildViews = () => {
+        console.log(this.overlayViews.length);
+        return [
+            ...this.overlayViews
+        ];
+    }
 
     public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
         return () => {
@@ -651,6 +666,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
                     </CollectionFreeFormViewPannableContents>
                 </MarqueeView>
+                {this.overlayChildViews()}
                 <CollectionFreeFormOverlayView  {...this.props} {...this.getDocumentViewProps(this.props.Document)} />
             </div>
         );
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7ffd760e0..3b6c443c2 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -77,6 +77,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: "left top",
                     position: "absolute",
                     backgroundColor: "transparent",
+                    boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` : undefined,
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
                     transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d4adcfca1..45b5c328c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,19 +1,24 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, trace, observable, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc";
+import * as rp from "request-promise";
+import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Copy, Id } from '../../../new_fields/FieldSymbols';
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, StrCast, NumCast, PromiseValue } from "../../../new_fields/Types";
+import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { emptyFunction, Utils, returnFalse, returnTrue } from "../../../Utils";
+import { RouteStore } from '../../../server/RouteStore';
+import { emptyFunction, returnTrue, Utils } from "../../../Utils";
 import { DocServer } from "../../DocServer";
-import { Docs, DocUtils, DocumentType } from "../../documents/Documents";
+import { Docs, DocUtils } from "../../documents/Documents";
+import { ClientUtils } from '../../util/ClientUtils';
+import DictationManager from '../../util/DictationManager';
 import { DocumentManager } from "../../util/DocumentManager";
 import { DragManager, dropActionType } from "../../util/DragManager";
-import { SearchUtil } from "../../util/SearchUtil";
+import { LinkManager } from '../../util/LinkManager';
 import { SelectionManager } from "../../util/SelectionManager";
 import { Transform } from "../../util/Transform";
 import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -22,27 +27,17 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
 import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
 import { DocComponent } from "../DocComponent";
+import { EditableView } from '../EditableView';
+import { OverlayView } from '../OverlayView';
 import { PresentationView } from "../presentationview/PresentationView";
-import { Template, Templates } from "./../Templates";
+import { ScriptingRepl } from '../ScriptingRepl';
+import { Template } from "./../Templates";
 import { DocumentContentsView } from "./DocumentContentsView";
-import * as rp from "request-promise";
 import "./DocumentView.scss";
-import React = require("react");
-import { Id, Copy } from '../../../new_fields/FieldSymbols';
-import { ContextMenuProps } from '../ContextMenuItem';
-import { list, object, createSimpleSchema } from 'serializr';
-import { LinkManager } from '../../util/LinkManager';
-import { RouteStore } from '../../../server/RouteStore';
 import { FormattedTextBox } from './FormattedTextBox';
-import { OverlayView } from '../OverlayView';
-import { ScriptingRepl } from '../ScriptingRepl';
-import { ClientUtils } from '../../util/ClientUtils';
-import { EditableView } from '../EditableView';
-import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons';
-import { DocumentDecorations } from '../DocumentDecorations';
-import { CognitiveServices } from '../../cognitive_services/CognitiveServices';
-import DictationManager from '../../util/DictationManager';
+import React = require("react");
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -121,6 +116,7 @@ export const positionSchema = createSchema({
     height: "number",
     x: "number",
     y: "number",
+    z: "number",
 });
 
 export type PositionDocument = makeInterface<[typeof positionSchema]>;
-- 
cgit v1.2.3-70-g09d2


From 65f23d993c091ee7fa61becea85b5f60ec2e9104 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 1 Aug 2019 15:44:22 -0400
Subject: added dragging of clusters.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 37 +++++++++++++++++-----
 src/client/views/nodes/DocumentView.tsx            |  3 +-
 2 files changed, 31 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 8cac60edb..12e5924b4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -131,15 +131,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         r2: { left: number, top: number, width: number, height: number }) {
         return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
     }
+    _groupingBorder = 150;
     bounsdSelect(doc: Doc, doc2: Doc) {
-        var x2 = NumCast(doc2.x) - 25;
-        var y2 = NumCast(doc2.y) - 25;
-        var w2 = NumCast(doc2.width) + 25;
-        var h2 = NumCast(doc2.height) + 25;
-        var x = NumCast(doc.x) - 25;
-        var y = NumCast(doc.y) - 25;
-        var w = NumCast(doc.width) + 25;
-        var h = NumCast(doc.height) + 25;
+        var x2 = NumCast(doc2.x) - this._groupingBorder;
+        var y2 = NumCast(doc2.y) - this._groupingBorder;
+        var w2 = NumCast(doc2.width) + this._groupingBorder;
+        var h2 = NumCast(doc2.height) + this._groupingBorder;
+        var x = NumCast(doc.x) - this._groupingBorder;
+        var y = NumCast(doc.y) - this._groupingBorder;
+        var w = NumCast(doc.width) + this._groupingBorder;
+        var h = NumCast(doc.height) + this._groupingBorder;
         if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
             return true;
         }
@@ -242,6 +243,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble) {
+            let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
+            let cluster = this.childDocs.reduce((cluster, cd) => {
+                let cx = NumCast(cd.x) - this._groupingBorder;
+                let cy = NumCast(cd.y) - this._groupingBorder;
+                let cw = NumCast(cd.width) + this._groupingBorder;
+                let ch = NumCast(cd.height) + this._groupingBorder;
+                if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
+                    return NumCast(cd.cluster);
+                return cluster;
+            }, -1);
+            if (cluster !== -1) {
+                let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
+                this.selectDocuments(eles);
+                DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(v => v.ContentDiv!), new DragManager.DocumentDragData(eles, eles.map(d => undefined)), e.clientX, e.clientY);
+                e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
+                e.preventDefault();
+                document.removeEventListener("pointermove", this.onPointerMove);
+                document.removeEventListener("pointerup", this.onPointerUp);
+                return;
+            }
             let x = this.Document.panX || 0;
             let y = this.Document.panY || 0;
             let docs = this.childDocs || [];
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c5cf2e23a..fa1e824f8 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -695,6 +695,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
+        let colors = ["red", "green", "blue", "purple", "orange", "cyan", "black"];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -703,7 +704,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`,
+                    boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-- 
cgit v1.2.3-70-g09d2


From 8cd098ab0e348c614640bc0971f3859161b8c2b4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 2 Aug 2019 16:39:03 -0400
Subject: fixed drag interactions with groups.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx      | 18 ++++++++++++++++--
 src/client/views/nodes/DocumentView.tsx                |  4 ++--
 2 files changed, 18 insertions(+), 4 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 12e5924b4..3ff816c46 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -131,7 +131,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         r2: { left: number, top: number, width: number, height: number }) {
         return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
     }
-    _groupingBorder = 150;
+    _groupingBorder = 100;
     bounsdSelect(doc: Doc, doc2: Doc) {
         var x2 = NumCast(doc2.x) - this._groupingBorder;
         var y2 = NumCast(doc2.y) - this._groupingBorder;
@@ -256,7 +256,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             if (cluster !== -1) {
                 let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
                 this.selectDocuments(eles);
-                DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(v => v.ContentDiv!), new DragManager.DocumentDragData(eles, eles.map(d => undefined)), e.clientX, e.clientY);
+                let clusterDocs = SelectionManager.SelectedDocuments();
+                SelectionManager.DeselectAll();
+                let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
+                de.moveDocument = this.props.moveDocument;
+                const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
+                const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top);
+                de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
+                de.xOffset = xoff;
+                de.yOffset = yoff;
+                DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
+                    handlers: {
+                        dragComplete: action(emptyFunction)
+                    },
+                    hideSource: !de.dropAction
+                });
                 e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                 e.preventDefault();
                 document.removeEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index fa1e824f8..d79b58a7b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -695,7 +695,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
-        let colors = ["red", "green", "blue", "purple", "orange", "cyan", "black"];
+        let colors = ["#da42429e", "#31ea318c", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "#000000ad"];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -704,7 +704,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, "0vw 0vw 11vw")}`,
+                    boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-- 
cgit v1.2.3-70-g09d2


From fe9017c3cdc2481f4bcce40dcc1514a6f7af37dc Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Fri, 2 Aug 2019 18:21:23 -0400
Subject: improvements to grouping with backgrounds

---
 .../views/collections/CollectionBaseView.tsx       |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 68 ++++++++++++----------
 src/client/views/nodes/DocumentView.tsx            |  9 ++-
 3 files changed, 44 insertions(+), 35 deletions(-)

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

diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index c595a4c56..6801b94fd 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -147,7 +147,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
             <div id="collectionBaseView"
                 style={{
                     pointerEvents: this.props.Document.isBackground ? "none" : "all",
-                    boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
+                    boxShadow: this.props.Document.isBackground ? undefined : `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`
                 }}
                 className={this.props.className || "collectionView-cont"}
                 onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 232ac22c9..bc1aee159 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -108,6 +108,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
         this.props.addDocument(newBox, false);
         this.bringToFront(newBox);
+        this.updateClusters();
         return true;
     }
     private selectDocuments = (docs: Doc[]) => {
@@ -175,34 +176,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         this.bringToFront(d);
                     });
 
-                    let sets: (Doc[])[] = []
-                    this.childDocs.map(c => {
-                        let included = []
-                        for (let i = 0; i < sets.length; i++) {
-                            for (let j = 0; j < sets[i].length; j++) {
-                                if (this.bounsdSelect(c, sets[i][j])) {
-                                    included.push(i);
-                                    break;
-                                }
-                            }
-                        }
-                        if (included.length === 0)
-                            sets.push([c]);
-                        else if (included.length === 1)
-                            sets[included[0]].push(c);
-                        else {
-                            sets[included[0]].push(c);
-                            for (let s = 1; s < included.length; s++) {
-                                sets[included[0]].push(...sets[included[s]]);
-                                sets[included[s]].length = 0;
-                            }
-                        }
-                    });
-                    for (let s = 0; s < sets.length; s++) {
-                        for (let i = 0; i < sets[s].length; i++) {
-                            Doc.GetProto(sets[s][i]).cluster = s;
-                        }
-                    }
+                    this.updateClusters();
                 }
             }
             else if (de.data instanceof DragManager.AnnotationDragData) {
@@ -223,6 +197,38 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return false;
     }
 
+    @action
+    updateClusters() {
+        let sets: (Doc[])[] = []
+        this.childDocs.map(c => {
+            let included = []
+            for (let i = 0; i < sets.length; i++) {
+                for (let j = 0; j < sets[i].length; j++) {
+                    if (this.bounsdSelect(c, sets[i][j])) {
+                        included.push(i);
+                        break;
+                    }
+                }
+            }
+            if (included.length === 0)
+                sets.push([c]);
+            else if (included.length === 1)
+                sets[included[0]].push(c);
+            else {
+                sets[included[0]].push(c);
+                for (let s = 1; s < included.length; s++) {
+                    sets[included[0]].push(...sets[included[s]]);
+                    sets[included[s]].length = 0;
+                }
+            }
+        });
+        for (let s = 0; s < sets.length; s++) {
+            for (let i = 0; i < sets[s].length; i++) {
+                Doc.GetProto(sets[s][i]).cluster = s;
+            }
+        }
+    }
+
     @action
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
@@ -247,8 +253,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             let cluster = this.childDocs.reduce((cluster, cd) => {
                 let cx = NumCast(cd.x) - this._groupingBorder;
                 let cy = NumCast(cd.y) - this._groupingBorder;
-                let cw = NumCast(cd.width) + this._groupingBorder;
-                let ch = NumCast(cd.height) + this._groupingBorder;
+                let cw = NumCast(cd.width) + 2 * this._groupingBorder;
+                let ch = NumCast(cd.height) + 2 * this._groupingBorder;
                 if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
                     return NumCast(cd.cluster);
                 return cluster;
@@ -392,7 +398,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     }
 
     bringToFront = (doc: Doc, sendToBack?: boolean) => {
-        if (sendToBack) {
+        if (sendToBack || doc.isBackground) {
             doc.zIndex = 0;
             return;
         }
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d79b58a7b..16ae5471d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -695,7 +695,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
-        let colors = ["#da42429e", "#31ea318c", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "#000000ad"];
+        let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
+        let groupCol = colors[NumCast(this.props.Document.cluster) % colors.length];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -704,7 +705,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    boxShadow: `${colors[NumCast(this.props.Document.cluster) % colors.length]} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`,
+                    boxShadow: this.layoutDoc.isBackground ?
+                        `0px 0px 50px 50px ${groupCol}` :
+                        `${groupCol} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
@@ -714,7 +717,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
-                    background: backgroundColor,
+                    background: this.layoutDoc.isBackground ? groupCol : backgroundColor,
                     width: nativeWidth,
                     height: nativeHeight,
                     transform: `scale(${this.props.ContentScaling()})`,
-- 
cgit v1.2.3-70-g09d2


From 982961c71fdd9d5dcd02ea33a2b631076a6a1f4b Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 3 Aug 2019 13:31:12 -0400
Subject: refactor

---
 src/client/util/DictationManager.ts                | 307 +++++++++++----------
 src/client/views/GlobalKeyHandler.ts               |  14 +-
 .../views/collections/CollectionBaseView.tsx       |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   4 +-
 4 files changed, 165 insertions(+), 162 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index b0866a826..b51d309a1 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -11,183 +11,190 @@ import { listSpec } from "../../new_fields/Schema";
 import { AudioField, ImageField } from "../../new_fields/URLField";
 import { HistogramField } from "../northstar/dash-fields/HistogramField";
 
-namespace CORE {
-    export interface IWindow extends Window {
-        webkitSpeechRecognition: any;
-    }
-}
-
-const ConstructorMap = new Map<DocumentType, CastCtor>([
-    [DocumentType.COL, listSpec(Doc)],
-    [DocumentType.AUDIO, AudioField],
-    [DocumentType.IMG, ImageField],
-    [DocumentType.HIST, HistogramField],
-    [DocumentType.IMPORT, listSpec(Doc)]
-]);
-
-const tryCast = (view: DocumentView, type: DocumentType) => {
-    let ctor = ConstructorMap.get(type);
-    if (!ctor) {
-        return false;
-    }
-    return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined;
-};
+export namespace DictationManager {
 
-const validate = (target: DocumentView, types: DocumentType[]) => {
-    for (let type of types) {
-        if (tryCast(target, type)) {
-            return true;
+    namespace CORE {
+        export interface IWindow extends Window {
+            webkitSpeechRecognition: any;
         }
     }
-    return false;
-};
-
-const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
-export type IndependentAction = (target: DocumentView) => any | Promise<any>;
-export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
-export type RegistrationEntry = { action: IndependentAction, restrictTo?: DocumentType[] };
-export type ActionPredicate = (target: DocumentView) => boolean;
-export type RegexEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] };
-
-export default class DictationManager {
-    public static Instance = new DictationManager();
-    private recognizer: any;
-    private isListening = false;
-
-    constructor() {
-        this.recognizer = new webkitSpeechRecognition();
-        this.recognizer.interimResults = false;
-        this.recognizer.continuous = true;
-    }
 
-    finish = (handler: any, data: any) => {
-        handler(data);
-        this.stop();
-    }
+    const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
 
-    stop = () => {
-        this.recognizer.stop();
-        this.isListening = false;
-    }
+    let isListening = false;
+    const recognizer = (() => {
+        let initialized = new webkitSpeechRecognition();
+        initialized.interimResults = false;
+        initialized.continuous = true;
+        return initialized;
+    })();
 
-    listen = () => {
-        if (this.isListening) {
-            return undefined;
-        }
-        this.isListening = true;
-        this.recognizer.start();
-        return new Promise<string>((resolve, reject) => {
-            this.recognizer.onresult = (e: any) => this.finish(resolve, e.results[0][0].transcript);
-            this.recognizer.onerror = (e: any) => this.finish(reject, e);
-        });
-    }
+    export namespace Controls {
+
+        export const listen = () => {
+            if (isListening) {
+                return undefined;
+            }
+            isListening = true;
+            recognizer.start();
+            return new Promise<string>((resolve, reject) => {
+                recognizer.onresult = (e: any) => {
+                    resolve(e.results[0][0].transcript);
+                    stop();
+                };
+                recognizer.onerror = (e: any) => {
+                    reject(e);
+                    stop();
+                };
+            });
+        };
+
+        export const stop = () => {
+            recognizer.stop();
+            isListening = false;
+        };
 
-    public interpretNumber = (number: string) => {
-        let initial = parseInt(number);
-        if (!isNaN(initial)) {
-            return initial;
-        }
-        let converted = converter.wordsToNumbers(number, { fuzzy: true });
-        if (converted === null) {
-            return NaN;
-        }
-        return typeof converted === "string" ? parseInt(converted) : converted;
     }
 
-    @undoBatch
-    public execute = async (phrase: string) => {
-        let targets = SelectionManager.SelectedDocuments();
-        if (!targets || !targets.length) {
-            return;
-        }
+    export namespace Commands {
 
-        let entry = RegisteredCommands.Independent.get(phrase);
-        if (entry) {
-            let success = false;
-            for (let target of targets) {
-                if (!entry.restrictTo || validate(target, entry.restrictTo)) {
-                    await entry.action(target);
-                    success = true;
-                }
+        export type IndependentAction = (target: DocumentView) => any | Promise<any>;
+        export type IndependentEntry = { action: IndependentAction, restrictTo?: DocumentType[] };
+
+        export type DependentAction = (target: DocumentView, matches: RegExpExecArray) => any | Promise<any>;
+        export type DependentEntry = { expression: RegExp, action: DependentAction, restrictTo?: DocumentType[] };
+
+        export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
+        export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
+
+        export const execute = async (phrase: string) => {
+            let targets = SelectionManager.SelectedDocuments();
+            if (!targets || !targets.length) {
+                return;
             }
-            return success;
-        }
 
-        for (let entry of RegisteredCommands.Dependent) {
-            let regex = entry.expression;
-            let matches = regex.exec(phrase);
-            regex.lastIndex = 0;
-            if (matches !== null) {
+            let entry = Independent.get(phrase);
+            if (entry) {
                 let success = false;
+                let restrictTo = entry.restrictTo;
                 for (let target of targets) {
-                    if (!entry.restrictTo || validate(target, entry.restrictTo)) {
-                        await entry.action(target, matches);
+                    if (!restrictTo || validate(target, restrictTo)) {
+                        await entry.action(target);
                         success = true;
                     }
                 }
                 return success;
             }
-        }
 
-        return false;
-    }
+            for (let entry of Dependent) {
+                let regex = entry.expression;
+                let matches = regex.exec(phrase);
+                regex.lastIndex = 0;
+                if (matches !== null) {
+                    let success = false;
+                    let restrictTo = entry.restrictTo;
+                    for (let target of targets) {
+                        if (!restrictTo || validate(target, restrictTo)) {
+                            await entry.action(target, matches);
+                            success = true;
+                        }
+                    }
+                    return success;
+                }
+            }
 
-}
+            return false;
+        };
+
+        const ConstructorMap = new Map<DocumentType, CastCtor>([
+            [DocumentType.COL, listSpec(Doc)],
+            [DocumentType.AUDIO, AudioField],
+            [DocumentType.IMG, ImageField],
+            [DocumentType.HIST, HistogramField],
+            [DocumentType.IMPORT, listSpec(Doc)]
+        ]);
+
+        const tryCast = (view: DocumentView, type: DocumentType) => {
+            let ctor = ConstructorMap.get(type);
+            if (!ctor) {
+                return false;
+            }
+            return Cast(Doc.GetProto(view.props.Document).data, ctor) !== undefined;
+        };
 
-export namespace RegisteredCommands {
+        const validate = (target: DocumentView, types: DocumentType[]) => {
+            for (let type of types) {
+                if (tryCast(target, type)) {
+                    return true;
+                }
+            }
+            return false;
+        };
 
-    export const Independent = new Map<string, RegistrationEntry>([
+        const interpretNumber = (number: string) => {
+            let initial = parseInt(number);
+            if (!isNaN(initial)) {
+                return initial;
+            }
+            let converted = converter.wordsToNumbers(number, { fuzzy: true });
+            if (converted === null) {
+                return NaN;
+            }
+            return typeof converted === "string" ? parseInt(converted) : converted;
+        };
 
-        ["clear", {
-            action: (target: DocumentView) => {
-                Doc.GetProto(target.props.Document).data = new List();
-            },
-            restrictTo: [DocumentType.COL]
-        }],
+        const Independent = new Map<string, IndependentEntry>([
 
-        ["open fields", {
-            action: (target: DocumentView) => {
-                let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
-                target.props.addDocTab(kvp, target.dataDoc, "onRight");
-            }
-        }]
-
-    ]);
-
-    export const Dependent = new Array<RegexEntry>(
-
-        {
-            expression: /create (\w+) documents of type (image|nested collection)/g,
-            action: (target: DocumentView, matches: RegExpExecArray) => {
-                let count = DictationManager.Instance.interpretNumber(matches[1]);
-                let what = matches[2];
-                let dataDoc = Doc.GetProto(target.props.Document);
-                let fieldKey = "data";
-                for (let i = 0; i < count; i++) {
-                    let created: Doc | undefined;
-                    switch (what) {
-                        case "image":
-                            created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg");
-                            break;
-                        case "nested collection":
-                            created = Docs.Create.FreeformDocument([], {});
-                            break;
-                    }
-                    created && Doc.AddDocToList(dataDoc, fieldKey, created);
+            ["clear", {
+                action: (target: DocumentView) => Doc.GetProto(target.props.Document).data = new List(),
+                restrictTo: [DocumentType.COL]
+            }],
+
+            ["open fields", {
+                action: (target: DocumentView) => {
+                    let kvp = Docs.Create.KVPDocument(target.props.Document, { width: 300, height: 300 });
+                    target.props.addDocTab(kvp, target.dataDoc, "onRight");
                 }
+            }]
+
+        ]);
+
+        const Dependent = new Array<DependentEntry>(
+
+            {
+                expression: /create (\w+) documents of type (image|nested collection)/g,
+                action: (target: DocumentView, matches: RegExpExecArray) => {
+                    let count = interpretNumber(matches[1]);
+                    let what = matches[2];
+                    let dataDoc = Doc.GetProto(target.props.Document);
+                    let fieldKey = "data";
+                    for (let i = 0; i < count; i++) {
+                        let created: Doc | undefined;
+                        switch (what) {
+                            case "image":
+                                created = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg");
+                                break;
+                            case "nested collection":
+                                created = Docs.Create.FreeformDocument([], {});
+                                break;
+                        }
+                        created && Doc.AddDocToList(dataDoc, fieldKey, created);
+                    }
+                },
+                restrictTo: [DocumentType.COL]
             },
-            restrictTo: [DocumentType.COL]
-        },
-
-        {
-            expression: /view as (freeform|stacking|masonry|schema|tree)/g,
-            action: (target: DocumentView, matches: RegExpExecArray) => {
-                let mode = CollectionViewType.ValueOf(matches[1]);
-                mode && (target.props.Document.viewType = mode);
-            },
-            restrictTo: [DocumentType.COL]
-        }
 
-    );
+            {
+                expression: /view as (freeform|stacking|masonry|schema|tree)/g,
+                action: (target: DocumentView, matches: RegExpExecArray) => {
+                    let mode = CollectionViewType.valueOf(matches[1]);
+                    mode && (target.props.Document.viewType = mode);
+                },
+                restrictTo: [DocumentType.COL]
+            }
+
+        );
+
+    }
 
 }
\ No newline at end of file
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 99345f04e..609136bb5 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -5,10 +5,7 @@ import { MainView } from "./MainView";
 import { DragManager } from "../util/DragManager";
 import { action, runInAction } from "mobx";
 import { Doc } from "../../new_fields/Doc";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import DictationManager from "../util/DictationManager";
-import { ContextMenu } from "./ContextMenu";
-import { ContextMenuProps } from "./ContextMenuItem";
+import { DictationManager } from "../util/DictationManager";
 
 const modifiers = ["control", "meta", "shift", "alt"];
 type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo | Promise<KeyControlInfo>;
@@ -74,7 +71,7 @@ export default class KeyManager {
                 }
                 main.toggleColorPicker(true);
                 SelectionManager.DeselectAll();
-                DictationManager.Instance.stop();
+                DictationManager.Controls.stop();
                 main.dictationOverlayVisible = false;
                 main.dictationSuccess = undefined;
                 main.overlayTimeout && clearTimeout(main.overlayTimeout);
@@ -114,19 +111,18 @@ export default class KeyManager {
                 let main = MainView.Instance;
                 main.dictationOverlayVisible = true;
                 main.isListening = true;
-                let dictation = DictationManager.Instance;
-                let command = await dictation.listen();
+                let command = await DictationManager.Controls.listen();
                 main.isListening = false;
                 if (!command) {
                     break;
                 }
                 command = command.toLowerCase();
                 main.dictatedPhrase = command;
-                main.dictationSuccess = await dictation.execute(command);
+                main.dictationSuccess = await DictationManager.Commands.execute(command);
                 main.overlayTimeout = setTimeout(() => {
                     main.dictationOverlayVisible = false;
                     main.dictationSuccess = undefined;
-                }, 3000);
+                }, 2000);
                 stopPropagation = true;
                 preventDefault = true;
         }
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 3f88ed98c..24604c812 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -34,7 +34,7 @@ export namespace CollectionViewType {
         ["masonry", CollectionViewType.Masonry]
     ]);
 
-    export const ValueOf = (value: string) => {
+    export const valueOf = (value: string) => {
         return stringMapping.get(value.toLowerCase());
     };
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 51661d1ae..c5d526a5a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -41,7 +41,7 @@ import { ClientUtils } from '../../util/ClientUtils';
 import { EditableView } from '../EditableView';
 import { faHandPointer, faHandPointRight } from '@fortawesome/free-regular-svg-icons';
 import { DocumentDecorations } from '../DocumentDecorations';
-import DictationManager from '../../util/DictationManager';
+import { DictationManager } from '../../util/DictationManager';
 import { CollectionViewType } from '../collections/CollectionBaseView';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
@@ -539,7 +539,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     listen = async () => {
-        let transcript = await DictationManager.Instance.listen();
+        let transcript = await DictationManager.Controls.listen();
         transcript && (Doc.GetProto(this.props.Document).transcript = transcript);
     }
 
-- 
cgit v1.2.3-70-g09d2


From 753615c80d4cf08605ebaaeeaf0a44a0fd88d898 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 3 Aug 2019 15:21:10 -0400
Subject: working version of clustering

---
 src/Utils.ts                                       |   2 +
 src/client/documents/Documents.ts                  |   3 +-
 src/client/util/DragManager.ts                     |   8 +-
 src/client/views/MainView.tsx                      |   4 +-
 src/client/views/TemplateMenu.tsx                  |   5 +-
 .../views/collections/CollectionDockingView.tsx    |   3 +-
 .../views/collections/CollectionSchemaView.tsx     |   3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 142 +++++++++++++--------
 .../collections/collectionFreeForm/MarqueeView.tsx |   1 +
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  12 +-
 src/client/views/nodes/DocumentView.tsx            |  17 +--
 .../views/presentationview/PresentationElement.tsx |   3 +-
 src/client/views/search/SearchItem.tsx             |   3 +-
 13 files changed, 125 insertions(+), 81 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 8df67df5d..502540eb0 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -140,6 +140,8 @@ export function returnOne() { return 1; }
 
 export function returnZero() { return 0; }
 
+export function returnEmptyString() { return ""; }
+
 export function emptyFunction() { }
 
 export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 09bafcf43..07e38a4c0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -84,6 +84,7 @@ export interface DocumentOptions {
     templates?: List<string>;
     viewType?: number;
     backgroundColor?: string;
+    defaultBackgroundColor?: string;
     dropAction?: dropActionType;
     backgroundLayout?: string;
     chromeStatus?: string;
@@ -124,7 +125,7 @@ export namespace Docs {
         const TemplateMap: TemplateMap = new Map([
             [DocumentType.TEXT, {
                 layout: { view: FormattedTextBox },
-                options: { height: 150, backgroundColor: "#f1efeb" }
+                options: { height: 150, backgroundColor: "#f1efeb", defaultBackgroundColor: "#f1efeb" }
             }],
             [DocumentType.HIST, {
                 layout: { view: HistogramBox, collectionView: [CollectionView, data] as CollectionViewType },
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 0b5c785a4..a7aaaed7c 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -404,7 +404,8 @@ export namespace DragManager {
                 hideSource = options.hideSource();
             }
         }
-        eles.map(ele => (ele.hidden = hideSource));
+        eles.map(ele => (ele.hidden = hideSource) &&
+            (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = hideSource)));
 
         let lastX = downX;
         let lastY = downY;
@@ -434,7 +435,10 @@ export namespace DragManager {
 
         let hideDragElements = () => {
             dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
-            eles.map(ele => (ele.hidden = false));
+            eles.map(ele => {
+                ele.hidden = false;
+                (ele.parentElement && ele.parentElement.className.indexOf("collectionFreeFormDocumentView") !== -1 && (ele.parentElement.hidden = false));
+            });
         };
         let endDrag = () => {
             document.removeEventListener("pointermove", moveHandler, true);
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 2ecf5fd85..53f589684 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -15,7 +15,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { Cast, FieldValue, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnOne, returnTrue, Utils } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils, returnEmptyString } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { SetupDrag } from '../util/DragManager';
@@ -270,6 +270,7 @@ export class MainView extends React.Component {
                             PanelWidth={this.getPWidth}
                             PanelHeight={this.getPHeight}
                             renderDepth={0}
+                            backgroundColor={returnEmptyString}
                             selectOnLoad={false}
                             focus={emptyFunction}
                             parentActive={returnTrue}
@@ -334,6 +335,7 @@ export class MainView extends React.Component {
             renderDepth={0}
             selectOnLoad={false}
             focus={emptyFunction}
+            backgroundColor={returnEmptyString}
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
             bringToFront={emptyFunction}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e654a0644..6dd908445 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -60,12 +60,9 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                     let de = new DragManager.DocumentDragData([topDoc], [undefined]);
                     de.moveDocument = topDocView.props.moveDocument;
                     let xf = newDocView.ContentDiv!.getBoundingClientRect();
-                    console.log("ex = " + ex + " " + xf.left + " " + (ex - xf.left));
                     DragManager.StartDocumentDrag([newDocView.ContentDiv!], de, ex, ey, {
                         offsetX: (ex - xf.left), offsetY: (ey - xf.top),
-                        handlers: {
-                            dragComplete: () => { },
-                        },
+                        handlers: { dragComplete: () => { }, },
                         hideSource: false
                     });
                 }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 588102f01..f559480ed 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -10,7 +10,7 @@ import { Id } from '../../../new_fields/FieldSymbols';
 import { FieldId } from "../../../new_fields/RefField";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { emptyFunction, returnTrue, Utils, returnOne } from "../../../Utils";
+import { emptyFunction, returnTrue, Utils, returnOne, returnEmptyString } from "../../../Utils";
 import { DocServer } from "../../DocServer";
 import { DocumentManager } from '../../util/DocumentManager';
 import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
@@ -607,6 +607,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
             parentActive={returnTrue}
             whenActiveChanged={emptyFunction}
             focus={emptyFunction}
+            backgroundColor={returnEmptyString}
             addDocTab={this.addDocTab}
             ContainingCollectionView={undefined}
             zoomToScale={emptyFunction}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 9efd0d3ec..8218877ba 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -6,7 +6,7 @@ import { action, computed, observable, trace, untracked } from "mobx";
 import { observer } from "mobx-react";
 import ReactTable, { CellInfo, ComponentPropsGetterR, Column, RowInfo, ResizedChangeFunction, Resize } from "react-table";
 import "react-table/react-table.css";
-import { emptyFunction, returnOne } from "../../../Utils";
+import { emptyFunction, returnOne, returnEmptyString } from "../../../Utils";
 import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
@@ -999,6 +999,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
                         PanelHeight={this.PanelHeight}
                         ContainingCollectionView={this.props.CollectionView}
                         focus={emptyFunction}
+                        backgroundColor={returnEmptyString}
                         parentActive={this.props.active}
                         whenActiveChanged={this.props.whenActiveChanged}
                         bringToFront={emptyFunction}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bc1aee159..ee21c5a4d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,7 @@
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
 import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed } from "mobx";
+import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
 import { Id } from "../../../../new_fields/FieldSymbols";
@@ -9,7 +9,7 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
 import { createSchema, makeInterface } from "../../../../new_fields/Schema";
 import { ScriptField } from "../../../../new_fields/ScriptField";
 import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnOne, Utils } from "../../../../Utils";
+import { emptyFunction, returnOne, Utils, returnFalse, returnEmptyString } from "../../../../Utils";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { DocServer } from "../../../DocServer";
 import { DocumentManager } from "../../../util/DocumentManager";
@@ -37,6 +37,8 @@ import "./CollectionFreeFormView.scss";
 import { MarqueeView } from "./MarqueeView";
 import React = require("react");
 import v5 = require("uuid/v5");
+import { setScheduler } from "bluebird";
+import { DocumentType, Docs } from "../../../documents/Documents";
 
 library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
 
@@ -132,17 +134,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         r2: { left: number, top: number, width: number, height: number }) {
         return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
     }
-    _groupingBorder = 100;
+    _clusterDistance = 75;
     bounsdSelect(doc: Doc, doc2: Doc) {
-        var x2 = NumCast(doc2.x) - this._groupingBorder;
-        var y2 = NumCast(doc2.y) - this._groupingBorder;
-        var w2 = NumCast(doc2.width) + this._groupingBorder;
-        var h2 = NumCast(doc2.height) + this._groupingBorder;
-        var x = NumCast(doc.x) - this._groupingBorder;
-        var y = NumCast(doc.y) - this._groupingBorder;
-        var w = NumCast(doc.width) + this._groupingBorder;
-        var h = NumCast(doc.height) + this._groupingBorder;
-        if (this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
+        var x2 = NumCast(doc2.x) - this._clusterDistance;
+        var y2 = NumCast(doc2.y) - this._clusterDistance;
+        var w2 = NumCast(doc2.width) + this._clusterDistance;
+        var h2 = NumCast(doc2.height) + this._clusterDistance;
+        var x = NumCast(doc.x) - this._clusterDistance;
+        var y = NumCast(doc.y) - this._clusterDistance;
+        var w = NumCast(doc.width) + this._clusterDistance;
+        var h = NumCast(doc.height) + this._clusterDistance;
+        if (doc.z === doc2.z && this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) {
             return true;
         }
         return false;
@@ -197,36 +199,83 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return false;
     }
 
+    tryDragCluster(e: PointerEvent) {
+        let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
+        let cluster = this.childDocs.reduce((cluster, cd) => {
+            let cx = NumCast(cd.x) - this._clusterDistance;
+            let cy = NumCast(cd.y) - this._clusterDistance;
+            let cw = NumCast(cd.width) + 2 * this._clusterDistance;
+            let ch = NumCast(cd.height) + 2 * this._clusterDistance;
+            if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
+                return NumCast(cd.cluster);
+            return cluster;
+        }, -1);
+        if (cluster !== -1) {
+            let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
+            this.selectDocuments(eles);
+            let clusterDocs = SelectionManager.SelectedDocuments();
+            SelectionManager.DeselectAll();
+            let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
+            de.moveDocument = this.props.moveDocument;
+            const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
+            const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top);
+            de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
+            de.xOffset = xoff;
+            de.yOffset = yoff;
+            DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
+                handlers: { dragComplete: action(emptyFunction) },
+                hideSource: !de.dropAction
+            });
+            return true;
+        }
+
+        return false;
+    }
+    @observable sets: (Doc[])[] = [];
     @action
     updateClusters() {
-        let sets: (Doc[])[] = []
+        this.sets.length = 0;
         this.childDocs.map(c => {
             let included = []
-            for (let i = 0; i < sets.length; i++) {
-                for (let j = 0; j < sets[i].length; j++) {
-                    if (this.bounsdSelect(c, sets[i][j])) {
+            for (let i = 0; i < this.sets.length; i++) {
+                for (let j = 0; j < this.sets[i].length; j++) {
+                    if (this.bounsdSelect(c, this.sets[i][j])) {
                         included.push(i);
                         break;
                     }
                 }
             }
             if (included.length === 0)
-                sets.push([c]);
+                this.sets.push([c]);
             else if (included.length === 1)
-                sets[included[0]].push(c);
+                this.sets[included[0]].push(c);
             else {
-                sets[included[0]].push(c);
+                this.sets[included[0]].push(c);
                 for (let s = 1; s < included.length; s++) {
-                    sets[included[0]].push(...sets[included[s]]);
-                    sets[included[s]].length = 0;
+                    this.sets[included[0]].push(...this.sets[included[s]]);
+                    this.sets[included[s]].length = 0;
                 }
             }
         });
-        for (let s = 0; s < sets.length; s++) {
-            for (let i = 0; i < sets[s].length; i++) {
-                Doc.GetProto(sets[s][i]).cluster = s;
+        for (let s = 0; s < this.sets.length; s++) {
+            for (let i = 0; i < this.sets[s].length; i++) {
+                this.sets[s][i].cluster = s;
+            }
+        }
+    }
+
+    getClusterColor = (doc: Doc) => {
+        if (this.props.Document.useClusters) {
+            let cluster = NumCast(doc.cluster);
+            let set = this.sets.length > cluster ? this.sets[NumCast(doc.cluster)] : undefined;
+            let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
+            let clusterColor = colors[cluster % colors.length];
+            for (let i = 0; set && i < set.length; i++) {
+                if (set[i].backgroundColor && set[i].backgroundColor !== set[i].defaultBackgroundColor) clusterColor = StrCast(set[i].backgroundColor);
             }
+            return clusterColor;
         }
+        return "";
     }
 
     @action
@@ -249,34 +298,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     @action
     onPointerMove = (e: PointerEvent): void => {
         if (!e.cancelBubble) {
-            let probe = this.getTransform().transformPoint(e.clientX, e.clientY);
-            let cluster = this.childDocs.reduce((cluster, cd) => {
-                let cx = NumCast(cd.x) - this._groupingBorder;
-                let cy = NumCast(cd.y) - this._groupingBorder;
-                let cw = NumCast(cd.width) + 2 * this._groupingBorder;
-                let ch = NumCast(cd.height) + 2 * this._groupingBorder;
-                if (this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
-                    return NumCast(cd.cluster);
-                return cluster;
-            }, -1);
-            if (cluster !== -1) {
-                let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
-                this.selectDocuments(eles);
-                let clusterDocs = SelectionManager.SelectedDocuments();
-                SelectionManager.DeselectAll();
-                let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
-                de.moveDocument = this.props.moveDocument;
-                const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
-                const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top);
-                de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined;
-                de.xOffset = xoff;
-                de.yOffset = yoff;
-                DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, {
-                    handlers: {
-                        dragComplete: action(emptyFunction)
-                    },
-                    hideSource: !de.dropAction
-                });
+            if (this.props.Document.useClusters && this.tryDragCluster(e)) {
                 e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document'  however it does mark the event as cancelBubble=true which we test for in the move event handlers
                 e.preventDefault();
                 document.removeEventListener("pointermove", this.onPointerMove);
@@ -493,6 +515,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
+            backgroundColor: this.getClusterColor,
             parentActive: this.props.active,
             whenActiveChanged: this.props.whenActiveChanged,
             bringToFront: this.bringToFront,
@@ -516,6 +539,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             ContentScaling: returnOne,
             ContainingCollectionView: this.props.CollectionView,
             focus: this.focusDocument,
+            backgroundColor: returnEmptyString,
             parentActive: this.props.active,
             whenActiveChanged: this.props.whenActiveChanged,
             bringToFront: this.bringToFront,
@@ -625,6 +649,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             event: async () => this.props.Document.fitToBox = !this.fitToBox,
             icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt"
         });
+        layoutItems.push({
+            description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`,
+            event: async () => {
+                Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb";
+                Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white";
+                this.props.Document.useClusters = !this.props.Document.useClusters;
+            },
+            icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt"
+        });
         layoutItems.push({
             description: "Arrange contents in grid",
             icon: "table",
@@ -700,10 +733,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         ...this.views
     ]
     private overlayChildViews = () => {
-        console.log(this.overlayViews.length);
-        return [
-            ...this.overlayViews
-        ];
+        return [...this.overlayViews];
     }
 
     public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b9ee588dd..ff96bd993 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -278,6 +278,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
                 panX: 0,
                 panY: 0,
                 backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
+                defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
                 width: bounds.width,
                 height: bounds.height,
                 title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection",
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 3b6c443c2..ee596c841 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -8,6 +8,7 @@ import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView"
 import "./DocumentView.scss";
 import React = require("react");
 import { Doc } from "../../../new_fields/Doc";
+import { returnEmptyString } from "../../../Utils";
 
 export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
     x?: number;
@@ -69,6 +70,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
         return undefined;
     }
 
+    @computed
+    get clusterColor() { return this.props.backgroundColor(this.props.Document); }
+
+    clusterColorFunc = (doc: Doc) => this.clusterColor;
+
     render() {
         const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
         return (
@@ -77,7 +83,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     transformOrigin: "left top",
                     position: "absolute",
                     backgroundColor: "transparent",
-                    boxShadow: this.props.Document.z ? `#9c9396 ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` : undefined,
+                    boxShadow: this.props.Document.z ? `#9c9396  ${StrCast(this.props.Document.boxShadow, "10px 10px 0.9vw")}` :
+                        this.clusterColor ? (
+                            this.props.Document.isBackground ? `0px 0px 50px 50px ${this.clusterColor}` :
+                                `${this.clusterColor} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`) : undefined,
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
                     transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
@@ -88,6 +97,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                 <DocumentView {...this.props}
                     ContentScaling={this.contentScaling}
                     ScreenToLocalTransform={this.getTransform}
+                    backgroundColor={this.clusterColorFunc}
                     PanelWidth={this.panelWidth}
                     PanelHeight={this.panelHeight}
                     animateBetweenIcon={this.animateBetweenIcon}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 16ae5471d..ae6a01ab4 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
 import * as fa from '@fortawesome/free-solid-svg-icons';
-import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction, trace } from "mobx";
 import { observer } from "mobx-react";
 import * as rp from "request-promise";
 import { Doc, DocListCast, DocListCastAsync, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
@@ -95,6 +95,7 @@ export interface DocumentViewProps {
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
     collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
     zoomToScale: (scale: number) => void;
+    backgroundColor: (doc: Doc) => string;
     getScale: () => number;
     animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void;
     ChromeHeight?: () => number;
@@ -675,12 +676,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string.
         return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
     }
+
     render() {
-        if (this.Document.hidden) {
-            return null;
-        }
-        let self = this;
-        let backgroundColor = StrCast(this.layoutDoc.backgroundColor);
+        let backgroundColor = this.props.backgroundColor(this.props.Document) || StrCast(this.layoutDoc.backgroundColor);
         let foregroundColor = StrCast(this.layoutDoc.color);
         var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
@@ -695,8 +693,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
-        let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
-        let groupCol = colors[NumCast(this.props.Document.cluster) % colors.length];
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
@@ -705,9 +701,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     color: foregroundColor,
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    boxShadow: this.layoutDoc.isBackground ?
-                        `0px 0px 50px 50px ${groupCol}` :
-                        `${groupCol} ${StrCast(this.props.Document.boxShadow, `0vw 0vw ${50 / this.props.ContentScaling()}px`)}`,
                     outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
                     marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
@@ -717,7 +710,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
                         `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
-                    background: this.layoutDoc.isBackground ? groupCol : backgroundColor,
+                    background: backgroundColor,
                     width: nativeWidth,
                     height: nativeHeight,
                     transform: `scale(${this.props.ContentScaling()})`,
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index 11f3eb846..e2d8daea9 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -9,7 +9,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils, returnFalse, emptyFunction, returnOne } from "../../../Utils";
+import { Utils, returnFalse, emptyFunction, returnOne, returnEmptyString } from "../../../Utils";
 import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
 import { SelectionManager } from "../../util/SelectionManager";
 import { ContextMenu } from "../ContextMenu";
@@ -843,6 +843,7 @@ export default class PresentationElement extends React.Component<PresentationEle
                     PanelWidth={() => 350}
                     PanelHeight={() => 90}
                     focus={emptyFunction}
+                    backgroundColor={returnEmptyString}
                     selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 0390359b3..1b9bba5c6 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
 import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnFalse, returnOne, Utils } from "../../../Utils";
+import { emptyFunction, returnFalse, returnOne, Utils, returnEmptyString } from "../../../Utils";
 import { DocumentType } from "../../documents/Documents";
 import { DocumentManager } from "../../util/DocumentManager";
 import { SetupDrag, DragManager } from "../../util/DragManager";
@@ -223,6 +223,7 @@ export class SearchItem extends React.Component<SearchItemProps> {
                     PanelWidth={returnXDimension}
                     PanelHeight={returnYDimension}
                     focus={emptyFunction}
+                    backgroundColor={returnEmptyString}
                     selectOnLoad={false}
                     parentActive={returnFalse}
                     whenActiveChanged={returnFalse}
-- 
cgit v1.2.3-70-g09d2


From b7c70cf115ea3d23e06fc7a3837e983b62993334 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 3 Aug 2019 17:30:58 -0400
Subject: a few updates and an option to switch between default backgrounds and
 cluster backgrounds.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx    | 20 +++++++++++++++-----
 src/client/views/nodes/DocumentView.tsx              |  4 +++-
 2 files changed, 18 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 24439ef4c..b4d065d26 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -267,12 +267,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getClusterColor = (doc: Doc) => {
         if (this.props.Document.useClusters) {
             let cluster = NumCast(doc.cluster);
-            let set = this.sets.length > cluster ? this.sets[NumCast(doc.cluster)] : undefined;
+            if (this.sets.length <= cluster) {
+                setTimeout(() => this.updateClusters(), 0);
+                return;
+            }
+            let set = this.sets.length > cluster ? this.sets[cluster] : undefined;
             let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
             let clusterColor = colors[cluster % colors.length];
-            for (let i = 0; set && i < set.length; i++) {
-                if (set[i].backgroundColor && set[i].backgroundColor !== set[i].defaultBackgroundColor) clusterColor = StrCast(set[i].backgroundColor);
-            }
+            set && set.filter(s => !s.isBackground).map(s =>
+                s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor)));
+            set && set.filter(s => s.isBackground).map(s =>
+                s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor)));
             return clusterColor;
         }
         return "";
@@ -652,12 +657,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         layoutItems.push({
             description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`,
             event: async () => {
-                Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb";
+                Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes
                 Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white";
                 this.props.Document.useClusters = !this.props.Document.useClusters;
             },
             icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt"
         });
+        layoutItems.push({
+            description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`,
+            event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground,
+            icon: !this.props.Document.useClusters ? "expand-arrows-alt" : "compress-arrows-alt"
+        });
         layoutItems.push({
             description: "Arrange contents in grid",
             icon: "table",
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ae6a01ab4..0347fc9b2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -678,7 +678,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     render() {
-        let backgroundColor = this.props.backgroundColor(this.props.Document) || StrCast(this.layoutDoc.backgroundColor);
+        let backgroundColor = this.layoutDoc.isBackground || (this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document.clusterOverridesDefaultBackground && this.layoutDoc.backgroundColor === this.layoutDoc.defaultBackgroundColor) ?
+            this.props.backgroundColor(this.layoutDoc) || StrCast(this.layoutDoc.backgroundColor) :
+            StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.layoutDoc);
         let foregroundColor = StrCast(this.layoutDoc.color);
         var nativeWidth = this.nativeWidth > 0 && !BoolCast(this.props.Document.ignoreAspect) ? `${this.nativeWidth}px` : "100%";
         var nativeHeight = BoolCast(this.props.Document.ignoreAspect) ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%";
-- 
cgit v1.2.3-70-g09d2


From d6fda11588f1a117e8acc30ea5600d34ff22e01b Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 3 Aug 2019 21:29:56 -0400
Subject: now differentiate between continuous, indefinite and, separately,
 interim vs final only dictation results

---
 src/client/util/DictationManager.ts     | 81 +++++++++++++++++++++------------
 src/client/views/GlobalKeyHandler.ts    |  3 +-
 src/client/views/MainView.tsx           |  2 +-
 src/client/views/nodes/DocumentView.tsx |  3 +-
 src/new_fields/Doc.ts                   |  2 +-
 5 files changed, 59 insertions(+), 32 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index b02a5ecbe..89797f101 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -39,57 +39,82 @@ export namespace DictationManager {
     const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
 
     let isListening = false;
-    const recognizer = (() => {
-        let initialized = new webkitSpeechRecognition();
-        initialized.continuous = true;
-        initialized.language = "en-US";
-        return initialized;
-    })();
+    let isManuallyStopped = false;
+    const recognizer: SpeechRecognition = new webkitSpeechRecognition();
 
     export namespace Controls {
 
+        let newestResult: string;
         export type InterimResultHandler = (results: any) => any;
+        export type ContinuityArgs = { indefinite: boolean } | false;
+        export interface ListeningOptions {
+            language: string;
+            continuous: ContinuityArgs;
+            interimHandler: InterimResultHandler;
+            delimiter: string;
+        }
 
-        export const listen = (handler: Opt<InterimResultHandler> = undefined) => {
+        export const listen = (options?: Partial<ListeningOptions>) => {
             if (isListening) {
                 return undefined;
             }
             isListening = true;
 
+            let handler = options ? options.interimHandler : undefined;
+            let continuous = options ? options.continuous : undefined;
+            let language = options ? options.language : undefined;
+            let delimiter = options ? options.delimiter : undefined;
+
             recognizer.interimResults = handler !== undefined;
+            recognizer.continuous = continuous === undefined ? false : continuous !== false;
+            recognizer.lang = language === undefined ? "en-US" : language;
+
             recognizer.start();
 
             return new Promise<string>((resolve, reject) => {
+
                 recognizer.onerror = (e: any) => {
                     reject(e);
                     stop();
                 };
-                if (handler) {
-                    let newestResult: string;
-                    recognizer.onresult = (e: any) => {
-                        newestResult = e.results[0][0].transcript;
-                        handler(newestResult);
-                    };
-                    recognizer.onend = (e: any) => {
+
+                recognizer.onresult = (e: SpeechRecognitionEvent) => {
+                    newestResult = synthesize(e, delimiter);
+                    handler && handler(newestResult);
+                };
+
+                recognizer.onend = (e: Event) => {
+                    if (continuous && continuous.indefinite && !isManuallyStopped) {
+                        recognizer.start();
+                    } else {
                         resolve(newestResult);
-                        stop();
-                    };
-                } else {
-                    recognizer.onresult = (e: any) => {
-                        let finalResult = e.results[0][0].transcript;
-                        resolve(finalResult);
-                        stop();
-                    };
-                }
+                        reset();
+                    }
+                };
+
             });
         };
 
-        export const stop = () => {
-            recognizer.stop();
+        export const stop = (saveCumulative = true) => {
+            saveCumulative ? recognizer.stop() : recognizer.abort();
+            reset();
+        };
+
+        const reset = () => {
             isListening = false;
-            recognizer.onresult = undefined;
-            recognizer.onend = undefined;
-            recognizer.onerror = undefined;
+            isManuallyStopped = false;
+            recognizer.onresult = null;
+            recognizer.onend = null;
+            recognizer.onerror = null;
+        };
+
+        const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
+            let results = e.results;
+            let transcripts: string[] = [];
+            for (let i = 0; i < results.length; i++) {
+                transcripts.push(results.item(i).item(0).transcript.trim());
+            }
+            return transcripts.join(delimiter || "...");
         };
 
     }
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index 82289c249..98df43a1e 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -112,7 +112,8 @@ export default class KeyManager {
                 main.dictationOverlayVisible = true;
 
                 main.isListening = true;
-                let command = await DictationManager.Controls.listen((results: any) => console.log(results));
+                // let printer = (results: any) => console.log(results);
+                let command = await DictationManager.Controls.listen();
                 main.isListening = false;
 
                 if (!command) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 748e1e634..5cec34293 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -49,7 +49,7 @@ export class MainView extends React.Component {
     @observable public pwidth: number = 0;
     @observable public pheight: number = 0;
 
-    @observable private dictationState = "Listening...";
+    @observable private dictationState = "";
     @observable private dictationSuccessState: boolean | undefined = undefined;
     @observable private dictationDisplayState = false;
     @observable private dictationListeningState = false;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c5d526a5a..bd87bf21b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -539,7 +539,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     listen = async () => {
-        let transcript = await DictationManager.Controls.listen();
+        let options = { continuous: { indefinite: true }, delimiter: " " };
+        let transcript = await DictationManager.Controls.listen(options);
         transcript && (Doc.GetProto(this.props.Document).transcript = transcript);
     }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 84b8589dd..979574f16 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -531,7 +531,7 @@ export namespace Doc {
                 d.layout = detailLayout;
                 d.nativeWidth = d.nativeHeight = undefined;
                 if (detailLayout instanceof Doc) {
-                    let delegDetailLayout = Doc.MakeDelegate(detailLayout) as Doc;
+                    let delegDetailLayout = Doc.MakeDelegate(detailLayout);
                     d.layout = delegDetailLayout;
                     let subDetailLayout1 = await PromiseValue(delegDetailLayout.detailedLayout);
                     let subDetailLayout = await PromiseValue(delegDetailLayout.detailedLayout);
-- 
cgit v1.2.3-70-g09d2


From 02346eabdefd428ca23d6a3fbefdcd51ef62b738 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 4 Aug 2019 01:17:29 -0400
Subject: fixed errors/warnings

---
 src/client/apis/youtube/YoutubeBox.tsx             |   8 +-
 src/client/views/InkingControl.tsx                 |   4 +-
 src/client/views/TemplateMenu.tsx                  |   2 +-
 .../views/collections/CollectionSchemaCells.tsx    |   4 +-
 .../views/collections/CollectionSchemaHeaders.tsx  |   2 +-
 .../views/collections/CollectionSchemaView.tsx     |   5 +-
 .../views/collections/CollectionStackingView.tsx   | 116 +++++++++------------
 src/client/views/collections/CollectionView.tsx    |  11 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  23 ++--
 src/client/views/nodes/ButtonBox.tsx               |   2 +-
 src/client/views/nodes/DocumentView.tsx            |   2 +-
 src/client/views/nodes/PDFBox.tsx                  |   4 +-
 src/client/views/search/FilterBox.tsx              |   2 +-
 src/client/views/search/SearchItem.tsx             |   2 +-
 src/client/views/search/ToggleBar.tsx              |   2 +-
 src/new_fields/Doc.ts                              |   4 +-
 16 files changed, 82 insertions(+), 111 deletions(-)

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

diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index dc142802c..d73988bb8 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -57,7 +57,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
         if (awaitedBackUp) {
 
 
-            let jsonList = await DocListCastAsync(awaitedBackUp!.json);
+            let jsonList = await DocListCastAsync(awaitedBackUp.json);
             let jsonDetailList = await DocListCastAsync(awaitedDetails!.json);
 
             if (jsonList!.length !== 0) {
@@ -76,8 +76,8 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
                     let videoDescription = StrCast(snippet!.description);
                     let pusblishDate = (this.roundPublishTime(StrCast(snippet!.publishedAt)))!;
                     let channelTitle = StrCast(snippet!.channelTitle);
-                    let duration: string;
-                    let viewCount: string;
+                    let duration: string = "";
+                    let viewCount: string = "";
                     if (jsonDetailList!.length !== 0) {
                         let contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc);
                         let statistics = await Cast(jsonDetailList![index].statistics, Doc);
@@ -85,7 +85,7 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
                         viewCount = this.abbreviateViewCount(parseInt(StrCast(statistics!.viewCount)))!;
                     }
                     index = index + 1;
-                    let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration!, viewCount: viewCount! };
+                    let newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration, viewCount: viewCount };
                     runInAction(() => this.curVideoTemplates.push(newTemplate));
                 }
             }
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 58c83915b..3f40642b5 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,5 +1,5 @@
 import { observable, action, computed, runInAction } from "mobx";
-import { ColorState } from 'react-color';
+import { ColorResult } from 'react-color';
 import React = require("react");
 import { observer } from "mobx-react";
 import "./InkingControl.scss";
@@ -41,7 +41,7 @@ export class InkingControl extends React.Component {
     }
 
     @undoBatch
-    switchColor = action((color: ColorState): void => {
+    switchColor = action((color: ColorResult): void => {
         this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff");
         if (InkingControl.Instance.selectedTool === InkTool.None) {
             if (MainOverlayTextBox.Instance.SetColor(color.hex)) return;
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 6dd908445..393e97a7e 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -68,7 +68,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
                 }
             }, 10);
         } else if (topDocView.props.ContainingCollectionView) {
-            let collView = topDocView.props.ContainingCollectionView!;
+            let collView = topDocView.props.ContainingCollectionView;
             let [sx, sy] = xf.inverse().transformPoint(0, 0);
             let [x, y] = collView.props.ScreenToLocalTransform().transformPoint(sx, sy);
             topDoc.x = x;
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index 4ff65b277..7e3061354 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -175,11 +175,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
         };
         let onPointerEnter = (e: React.PointerEvent): void => {
             if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) {
-                dragRef!.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
+                dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
             }
         };
         let onPointerLeave = (e: React.PointerEvent): void => {
-            dragRef!.current!.className = "collectionSchemaView-cellContainer";
+            dragRef.current!.className = "collectionSchemaView-cellContainer";
         };
 
         let contents: any = "incorrect type";
diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx
index dfd65770e..d24f63fbb 100644
--- a/src/client/views/collections/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/CollectionSchemaHeaders.tsx
@@ -13,7 +13,7 @@ import { faFile } from "@fortawesome/free-regular-svg-icons";
 import { SchemaHeaderField, RandomPastel, PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField";
 import { undoBatch } from "../../util/UndoManager";
 
-library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile, faSortAmountDown, faSortAmountUp, faTimes);
+library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes);
 
 export interface HeaderProps {
     keyValue: SchemaHeaderField;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 2d4c88b94..75787c0a8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -303,14 +303,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
             return resized;
         }, [] as { "id": string, "value": number }[]);
     }
-
-    @computed get sorted(): { "id": string, "desc": boolean }[] {
+    @computed get sorted(): { "id": string, "desc"?: true }[] {
         return this.columns.reduce((sorted, shf) => {
             if (shf.desc) {
                 sorted.push({ "id": shf.heading, "desc": shf.desc });
             }
             return sorted;
-        }, [] as { "id": string, "desc": boolean }[]);
+        }, [] as { "id": string, "desc"?: true }[]);
     }
 
     @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index d7c3ac3b8..4a751c84c 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,26 +1,25 @@
 import React = require("react");
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, reaction, untracked, observable, runInAction } from "mobx";
+import { CursorProperty } from "csstype";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
-import { Doc, HeightSym, WidthSym, DocListCast } from "../../../new_fields/Doc";
+import Switch from 'rc-switch';
+import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
-import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, Utils, returnTrue } from "../../../Utils";
-import { CollectionSchemaPreview } from "./CollectionSchemaView";
-import "./CollectionStackingView.scss";
-import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView";
-import { undoBatch } from "../../util/UndoManager";
-import { DragManager } from "../../util/DragManager";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField";
+import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction } from "../../../Utils";
 import { DocumentType } from "../../documents/Documents";
+import { DragManager } from "../../util/DragManager";
 import { Transform } from "../../util/Transform";
-import { CursorProperty } from "csstype";
-import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
-import { listSpec } from "../../../new_fields/Schema";
-import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField";
-import { List } from "../../../new_fields/List";
+import { undoBatch } from "../../util/UndoManager";
 import { EditableView } from "../EditableView";
-import { CollectionViewProps } from "./CollectionBaseView";
-import Switch from 'rc-switch';
+import { CollectionSchemaPreview } from "./CollectionSchemaView";
+import "./CollectionStackingView.scss";
+import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn";
+import { CollectionSubView } from "./CollectionSubView";
 
 @observer
 export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -32,13 +31,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     _columnStart: number = 0;
     @observable private cursor: CursorProperty = "grab";
     get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); }
-    @computed get chromeCollapsed() { return this.props.chromeCollapsed; }
     @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
     @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); }
     @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
     @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); }
     @computed get columnWidth() { return this.singleColumn ? (this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin) : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); }
     @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
+    @computed get sectionFilter() { return this.singleColumn ? StrCast(this.props.Document.sectionFilter) : ""; }
 
     get layoutDoc() {
         // if this document's layout field contains a document (ie, a rendering template), then we will use that
@@ -46,37 +45,32 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document;
     }
 
-    @computed
-    get SectionFilter() { return this.singleColumn ? StrCast(this.props.Document.sectionFilter) : "" }
 
     get Sections() {
-        let sectionHeaders = this.sectionHeaders;
-        if (!sectionHeaders) {
-            this.props.Document.sectionHeaders = sectionHeaders = new List();
+        if (!this.sectionFilter) return new Map<SchemaHeaderField, Doc[]>();
+
+        if (this.sectionHeaders === undefined) {
+            this.props.Document.sectionHeaders = new List<SchemaHeaderField>();
         }
-        let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []]));
-        if (this.SectionFilter) {
-            this.filteredChildren.map(d => {
-                let sectionValue = (d[this.SectionFilter] ? d[this.SectionFilter] : `NO ${this.SectionFilter.toUpperCase()} VALUE`) as object;
-                // the next five lines ensures that floating point rounding errors don't create more than one section -syip
-                let parsed = parseInt(sectionValue.toString());
-                let castedSectionValue: any = sectionValue;
-                if (!isNaN(parsed)) {
-                    castedSectionValue = parsed;
-                }
+        const sectionHeaders = this.sectionHeaders!;
+        let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []]));
+        this.filteredChildren.map(d => {
+            let sectionValue = (d[this.sectionFilter] ? d[this.sectionFilter] : `NO ${this.sectionFilter.toUpperCase()} VALUE`) as object;
+            // the next five lines ensures that floating point rounding errors don't create more than one section -syip
+            let parsed = parseInt(sectionValue.toString());
+            let castedSectionValue = !isNaN(parsed) ? parsed : sectionValue;
 
-                // look for if header exists already
-                let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.SectionFilter.toUpperCase()} VALUE`));
-                if (existingHeader) {
-                    fields.get(existingHeader)!.push(d);
-                }
-                else {
-                    let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.SectionFilter.toUpperCase()} VALUE`);
-                    fields.set(newSchemaHeader, [d]);
-                    sectionHeaders!.push(newSchemaHeader);
-                }
-            });
-        } else fields.clear();
+            // look for if header exists already
+            let existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`));
+            if (existingHeader) {
+                fields.get(existingHeader)!.push(d);
+            }
+            else {
+                let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`);
+                fields.set(newSchemaHeader, [d]);
+                sectionHeaders.push(newSchemaHeader);
+            }
+        });
         return fields;
     }
 
@@ -96,10 +90,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
 
         // reset section headers when a new filter is inputted
         this._sectionFilterDisposer = reaction(
-            () => this.SectionFilter,
-            () => {
-                this.props.Document.sectionHeaders = new List();
-            }
+            () => this.sectionFilter,
+            () => this.props.Document.sectionHeaders = new List()
         );
     }
     componentWillUnmount() {
@@ -186,8 +178,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     @undoBatch
     @action
     drop = (e: Event, de: DragManager.DropEvent) => {
-        let targInd = -1;
         let where = [de.x, de.y];
+        let targInd = -1;
         if (de.data instanceof DragManager.DocumentDragData) {
             this._docXfs.map((cd, i) => {
                 let pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
@@ -233,8 +225,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             }
         });
     }
+    headings = () => Array.from(this.Sections.keys());
     section = (heading: SchemaHeaderField | undefined, docList: Doc[]) => {
-        let key = this.SectionFilter;
+        let key = this.sectionFilter;
         let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined;
         let types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]);
         if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) {
@@ -245,7 +238,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return <CollectionStackingViewFieldColumn
             key={heading ? heading.heading : ""}
             cols={cols}
-            headings={() => Array.from(this.Sections.keys())}
+            headings={this.headings}
             heading={heading ? heading.heading : ""}
             headingObject={heading}
             docList={docList}
@@ -254,16 +247,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
             createDropTarget={this.createDropTarget}
             screenToLocalTransform={this.props.ScreenToLocalTransform}
         />;
-
     }
 
     @action
     addGroup = (value: string) => {
-        if (value) {
-            if (this.sectionHeaders) {
-                this.sectionHeaders.push(new SchemaHeaderField(value));
-                return true;
-            }
+        if (value && this.sectionHeaders) {
+            this.sectionHeaders.push(new SchemaHeaderField(value));
+            return true;
         }
         return false;
     }
@@ -276,11 +266,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
     }
 
     onToggle = (checked: Boolean) => {
-        if (checked) {
-            this.props.CollectionView.props.Document.chromeStatus = 'collapsed';
-        } else {
-            this.props.CollectionView.props.Document.chromeStatus = 'view-mode';
-        }
+        this.props.CollectionView.props.Document.chromeSatus = checked ? "collapsed" : "view-mode";
     }
 
     render() {
@@ -296,10 +282,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
         return (
             <div className="collectionStackingView"
                 ref={this.createRef} onDrop={this.onDrop.bind(this)} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
-                {this.SectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc).
-                    map(section => this.section(section[0], section[1])) :
+                {this.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc).
+                    map((section: [SchemaHeaderField, Doc[]]) => this.section(section[0], section[1])) :
                     this.section(undefined, this.filteredChildren)}
-                {(this.SectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ?
+                {(this.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ?
                     <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton"
                         style={{ width: (this.columnWidth / (headings.length + ((this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0))) - 10, marginTop: 10 }}>
                         <EditableView {...editableViewProps} />
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 57dc5879b..f59fee985 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -20,16 +20,7 @@ import { CollectionTreeView } from "./CollectionTreeView";
 import { CollectionViewBaseChrome } from './CollectionViewChromes';
 export const COLLECTION_BORDER_WIDTH = 2;
 
-library.add(faTh);
-library.add(faTree);
-library.add(faSquare);
-library.add(faProjectDiagram);
-library.add(faSignature);
-library.add(faThList);
-library.add(faFingerprint);
-library.add(faColumns);
-library.add(faEllipsisV);
-library.add(faImage, faEye);
+library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any);
 
 @observer
 export class CollectionView extends React.Component<FieldViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b4d065d26..c943fac74 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -40,7 +40,7 @@ import v5 = require("uuid/v5");
 import { setScheduler } from "bluebird";
 import { DocumentType, Docs } from "../../../documents/Documents";
 
-library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
+library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -206,8 +206,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
             let cy = NumCast(cd.y) - this._clusterDistance;
             let cw = NumCast(cd.width) + 2 * this._clusterDistance;
             let ch = NumCast(cd.height) + 2 * this._clusterDistance;
-            if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }))
+            if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) {
                 return NumCast(cd.cluster);
+            }
             return cluster;
         }, -1);
         if (cluster !== -1) {
@@ -236,20 +237,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     updateClusters() {
         this.sets.length = 0;
         this.childDocs.map(c => {
-            let included = []
+            let included = [];
             for (let i = 0; i < this.sets.length; i++) {
-                for (let j = 0; j < this.sets[i].length; j++) {
-                    if (this.boundsOverlap(c, this.sets[i][j])) {
+                for (let member of this.sets[i]) {
+                    if (this.boundsOverlap(c, member)) {
                         included.push(i);
                         break;
                     }
                 }
             }
-            if (included.length === 0)
+            if (included.length === 0) {
                 this.sets.push([c]);
-            else if (included.length === 1)
+            } else if (included.length === 1) {
                 this.sets[included[0]].push(c);
-            else {
+            } else {
                 this.sets[included[0]].push(c);
                 for (let s = 1; s < included.length; s++) {
                     this.sets[included[0]].push(...this.sets[included[s]]);
@@ -257,11 +258,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }
             }
         });
-        for (let s = 0; s < this.sets.length; s++) {
-            for (let i = 0; i < this.sets[s].length; i++) {
-                this.sets[s][i].cluster = s;
-            }
-        }
+        this.sets.map((set, i) => set.map(member => member.cluster = i));
     }
 
     getClusterColor = (doc: Doc) => {
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index e2c559c9a..640795789 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -16,7 +16,7 @@ import './ButtonBox.scss';
 import { observer } from 'mobx-react';
 import { DocumentIconContainer } from './DocumentIcon';
 
-library.add(faEdit);
+library.add(faEdit as any);
 
 const ButtonSchema = createSchema({
     onClick: ScriptField,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0347fc9b2..4528cf94d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -95,7 +95,7 @@ export interface DocumentViewProps {
     addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
     collapseToPoint?: (scrpt: number[], expandedDocs: Doc[] | undefined) => void;
     zoomToScale: (scale: number) => void;
-    backgroundColor: (doc: Doc) => string;
+    backgroundColor: (doc: Doc) => string | undefined;
     getScale: () => number;
     animateBetweenIcon?: (iconPos: number[], startTime: number, maximizing: boolean) => void;
     ChromeHeight?: () => number;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index fa072aecf..a49709e83 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -152,9 +152,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
     }
 
     scrollTo(y: number) {
-        if (this._mainCont.current) {
-            this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current!.offsetHeight / 2), 0), behavior: "auto" });
-        }
+        this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" });
     }
 
     settingsPanel() {
diff --git a/src/client/views/search/FilterBox.tsx b/src/client/views/search/FilterBox.tsx
index 995ddd5c3..3e8582d61 100644
--- a/src/client/views/search/FilterBox.tsx
+++ b/src/client/views/search/FilterBox.tsx
@@ -384,7 +384,7 @@ export class FilterBox extends React.Component {
                     <div className="active-icon description">Collection Filters Active</div>
                 </div> : undefined}
             </div>
-        )
+        );
     }
 
     // Useful queries:
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index 1b9bba5c6..48eb87251 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -134,7 +134,7 @@ export class LinkContextMenu extends React.Component<LinkMenuProps> {
                 <div className="collection"><a onMouseEnter={this.highlightDoc(this.props.doc1)} onMouseLeave={this.unHighlightDoc(this.props.doc1)} onClick={this.getOnClick(this.props.doc1)}>Doc 1: {this.props.doc2.title}</a></div>
                 <div><a onMouseEnter={this.highlightDoc(this.props.doc2)} onMouseLeave={this.unHighlightDoc(this.props.doc2)} onClick={this.getOnClick(this.props.doc2)}>Doc 2: {this.props.doc1.title}</a></div>
             </div>
-        )
+        );
     }
 
 }
diff --git a/src/client/views/search/ToggleBar.tsx b/src/client/views/search/ToggleBar.tsx
index a30104089..ed5ecd3ba 100644
--- a/src/client/views/search/ToggleBar.tsx
+++ b/src/client/views/search/ToggleBar.tsx
@@ -59,7 +59,7 @@ export class ToggleBar extends React.Component<ToggleBarProps>{
         this._forwardTimeline.play();
         this._forwardTimeline.reverse();
         this.props.handleChange();
-        console.log(this.props.getStatus())
+        console.log(this.props.getStatus());
     }
 
     @action.bound
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index b081588a3..c01f4e8cf 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -358,7 +358,7 @@ export namespace Doc {
         let docExtensionForField = doc[fieldKey + "_ext"] as Doc;
         if (docExtensionForField === undefined) {
             setTimeout(() => {
-                CreateDocumentExtensionForField(doc, field);
+                CreateDocumentExtensionForField(doc, fieldKey);
             }, 0);
         } else if (doc instanceof Doc) { // backward compatibility -- add fields for docs that don't have them already
             docExtensionForField.extendsDoc === undefined && setTimeout(() => docExtensionForField.extendsDoc = doc, 0);
@@ -535,7 +535,7 @@ export namespace Doc {
                 d.layout = detailLayout;
                 d.nativeWidth = d.nativeHeight = undefined;
                 if (detailLayout instanceof Doc) {
-                    let delegDetailLayout = Doc.MakeDelegate(detailLayout) as Doc;
+                    let delegDetailLayout = Doc.MakeDelegate(detailLayout);
                     d.layout = delegDetailLayout;
                     delegDetailLayout.layout = await delegDetailLayout.detailedLayout;
                 }
-- 
cgit v1.2.3-70-g09d2


From 6d718c8a243e68d23199d35592bfded285385c91 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 4 Aug 2019 05:09:56 -0400
Subject: now all listen() calls display recording UI

---
 src/client/util/DictationManager.ts     | 24 ++++++++++++++++++++----
 src/client/views/GlobalKeyHandler.ts    | 21 ++++-----------------
 src/client/views/Main.scss              |  1 -
 src/client/views/MainView.tsx           | 11 ++++++++---
 src/client/views/nodes/DocumentView.tsx | 16 +++++++++++++---
 5 files changed, 45 insertions(+), 28 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index b6f871713..a882994c1 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -10,6 +10,7 @@ import { Cast, CastCtor } from "../../new_fields/Types";
 import { listSpec } from "../../new_fields/Schema";
 import { AudioField, ImageField } from "../../new_fields/URLField";
 import { HistogramField } from "../northstar/dash-fields/HistogramField";
+import { MainView } from "../views/MainView";
 
 /**
  * This namespace provides a singleton instance of a manager that
@@ -37,9 +38,11 @@ export namespace DictationManager {
         }
     }
     const { webkitSpeechRecognition }: CORE.IWindow = window as CORE.IWindow;
+    export const placeholder = "Listening...";
 
     export namespace Controls {
 
+        const infringe = "unable to process: dictation manager still involved in previous session";
         const intraSession = ". ";
         const interSession = " ... ";
 
@@ -52,7 +55,7 @@ export namespace DictationManager {
         const recognizer: SpeechRecognition = new webkitSpeechRecognition() || new SpeechRecognition();
         recognizer.onstart = () => console.log("initiating speech recognition session...");
 
-        export type InterimResultHandler = (results: any) => any;
+        export type InterimResultHandler = (results: string) => any;
         export type ContinuityArgs = { indefinite: boolean } | false;
         export type DelimiterArgs = { inter: string, intra: string };
 
@@ -61,21 +64,33 @@ export namespace DictationManager {
             continuous: ContinuityArgs;
             delimiters: DelimiterArgs;
             interimHandler: InterimResultHandler;
+            tryExecute: boolean;
         }
 
         export const listen = async (options?: Partial<ListeningOptions>) => {
-            let results: any;
+            let results: string | undefined;
+            MainView.Instance.dictationOverlayVisible = true;
+            MainView.Instance.isListening = true;
             try {
                 results = await listenImpl(options);
+                if (results) {
+                    MainView.Instance.isListening = false;
+                    MainView.Instance.dictatedPhrase = results = results.toLowerCase();
+                    MainView.Instance.dictationSuccess = options && options.tryExecute ? await DictationManager.Commands.execute(results) : true;
+                }
             } catch (e) {
-                results = `Dictation Error: ${"error" in e ? e.error : "unknown error"}`;
+                MainView.Instance.isListening = false;
+                MainView.Instance.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
+                MainView.Instance.dictationSuccess = false;
+            } finally {
+                MainView.Instance.initiateDictationFade();
             }
             return results;
         };
 
         const listenImpl = (options?: Partial<ListeningOptions>) => {
             if (isListening) {
-                return undefined;
+                return infringe;
             }
             isListening = true;
 
@@ -126,6 +141,7 @@ export namespace DictationManager {
                     } else {
                         resolve(current);
                     }
+                    current = undefined;
                     reset();
                 };
 
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index c3e6ae6c8..0989e8db1 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -73,9 +73,11 @@ export default class KeyManager {
                 SelectionManager.DeselectAll();
                 DictationManager.Controls.stop();
                 if (main.dictationOverlayVisible) {
+                    main.cancelDictationFade();
                     main.dictationOverlayVisible = false;
+                    main.isListening = true;
+                    main.dictatedPhrase = "";
                     main.dictationSuccess = undefined;
-                    main.cancelDictationFade();
                 }
                 break;
             case "delete":
@@ -110,22 +112,7 @@ export default class KeyManager {
 
         switch (keyname) {
             case " ":
-                let main = MainView.Instance;
-                main.dictationOverlayVisible = true;
-
-                main.isListening = true;
-                // let printer = (results: any) => console.log(results);
-                let command = await DictationManager.Controls.listen();
-                main.isListening = false;
-
-                if (!command) {
-                    break;
-                }
-
-                main.dictatedPhrase = command = command.toLowerCase();
-                main.dictationSuccess = await DictationManager.Commands.execute(command);
-                main.initiateDictationFade();
-
+                DictationManager.Controls.listen({ tryExecute: true });
                 stopPropagation = true;
                 preventDefault = true;
         }
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 8e57b88c3..f76abaff3 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -292,7 +292,6 @@ ul#add-options-list {
     width: 100%;
     height: 100%;
     position: absolute;
-    background: darkslategray;
     z-index: 999;
     transition: 0.5s all ease;
     pointer-events: none;
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 4443eea6d..383efa1e3 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,7 +1,7 @@
 import { IconName, library } from '@fortawesome/fontawesome-svg-core';
 import { faArrowDown, faCloudUploadAlt, faArrowUp, faClone, faCheck, faPlay, faPause, faCaretUp, faLongArrowAltRight, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faPortrait, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faThumbtack, faTree, faUndoAlt, faCat, faBolt } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, runInAction, reaction, trace } from 'mobx';
+import { action, computed, configure, observable, runInAction, reaction, trace, autorun } from 'mobx';
 import { observer } from 'mobx-react';
 import "normalize.css";
 import * as React from 'react';
@@ -167,6 +167,8 @@ export class MainView extends React.Component {
             }
         }
 
+        autorun(() => console.log(`this.isListening = ${this.isListening}`));
+
         library.add(faFont);
         library.add(faExclamation);
         library.add(faPortrait);
@@ -523,7 +525,7 @@ export class MainView extends React.Component {
     render() {
         let display = this.dictationOverlayVisible;
         let success = this.dictationSuccess;
-        let result = this.isListening ? "Listening..." : `"${this.dictatedPhrase}"`;
+        let result = this.isListening ? DictationManager.placeholder : `"${this.dictatedPhrase}"`;
         return (
             <div id="main-div">
                 <div
@@ -536,7 +538,10 @@ export class MainView extends React.Component {
                 >{result}</div>
                 <div
                     className={"dictation-prompt-overlay"}
-                    style={{ opacity: display ? 0.4 : 0 }}
+                    style={{
+                        opacity: display ? 0.4 : 0,
+                        backgroundColor: this.isListening ? "red" : "darkslategrey"
+                    }}
                 />
                 <DocumentDecorations />
                 {this.mainContent}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1d9cb3c80..a415aefda 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -38,6 +38,7 @@ import "./DocumentView.scss";
 import { FormattedTextBox } from './FormattedTextBox';
 import React = require("react");
 import { DictationManager } from '../../util/DictationManager';
+import { MainView } from '../MainView';
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
@@ -536,9 +537,18 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
     }
 
     listen = async () => {
-        let options = { continuous: { indefinite: true }, delimiter: " " };
-        let transcript = await DictationManager.Controls.listen(options);
-        transcript && (Doc.GetProto(this.props.Document).transcript = transcript);
+        let dataDoc = Doc.GetProto(this.props.Document);
+        let options = {
+            continuous: { indefinite: true },
+            delimiter: " ",
+            interimHandler: (results: string) => {
+                MainView.Instance.isListening = false;
+                MainView.Instance.dictationSuccess = true;
+                MainView.Instance.dictatedPhrase = results;
+            }
+        };
+        let final = await DictationManager.Controls.listen(options);
+        final && (dataDoc.transcript = final);
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From 1ed381022450bc5c39238c73f732179f4b21daf2 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 4 Aug 2019 05:13:51 -0400
Subject: clean up

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

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a415aefda..458fb582d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -538,16 +538,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
 
     listen = async () => {
         let dataDoc = Doc.GetProto(this.props.Document);
-        let options = {
-            continuous: { indefinite: true },
-            delimiter: " ",
-            interimHandler: (results: string) => {
-                MainView.Instance.isListening = false;
-                MainView.Instance.dictationSuccess = true;
-                MainView.Instance.dictatedPhrase = results;
-            }
+        let handler = (results: string) => {
+            MainView.Instance.isListening = false;
+            MainView.Instance.dictationSuccess = true;
+            MainView.Instance.dictatedPhrase = results;
         };
-        let final = await DictationManager.Controls.listen(options);
+        let final = await DictationManager.Controls.listen({
+            continuous: { indefinite: true },
+            interimHandler: handler
+        });
         final && (dataDoc.transcript = final);
     }
 
-- 
cgit v1.2.3-70-g09d2


From 0010f88e1002feb14ecfb111c2c6ae56ee34cf2d Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 4 Aug 2019 13:32:19 -0400
Subject: clean up and UI fix

---
 src/client/util/DictationManager.ts     | 43 +++++++++++++++++++--------------
 src/client/views/MainView.tsx           | 11 ++++-----
 src/client/views/nodes/DocumentView.tsx | 20 ++++++---------
 3 files changed, 38 insertions(+), 36 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 2af7c53cb..ee1f11b4f 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -58,6 +58,7 @@ export namespace DictationManager {
         export type InterimResultHandler = (results: string) => any;
         export type ContinuityArgs = { indefinite: boolean } | false;
         export type DelimiterArgs = { inter: string, intra: string };
+        export type ListeningUIStatus = { interim: boolean } | false;
 
         export interface ListeningOptions {
             language: string;
@@ -69,22 +70,28 @@ export namespace DictationManager {
 
         export const listen = async (options?: Partial<ListeningOptions>) => {
             let results: string | undefined;
-            MainView.Instance.dictationOverlayVisible = true;
-            MainView.Instance.isListening = true;
+            let main = MainView.Instance;
+
+            main.dictationOverlayVisible = true;
+            let interim = options !== undefined && options.interimHandler !== undefined;
+            main.isListening = { interim: interim };
+
             try {
                 results = await listenImpl(options);
                 if (results) {
-                    MainView.Instance.isListening = false;
-                    MainView.Instance.dictatedPhrase = results = results.toLowerCase();
-                    MainView.Instance.dictationSuccess = options && options.tryExecute ? await DictationManager.Commands.execute(results) : true;
+                    main.isListening = false;
+                    main.dictatedPhrase = results;
+                    let execute = options && options.tryExecute;
+                    main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
                 }
             } catch (e) {
-                MainView.Instance.isListening = false;
-                MainView.Instance.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
-                MainView.Instance.dictationSuccess = false;
+                main.isListening = false;
+                main.dictatedPhrase = results = `dictation error: ${"error" in e ? e.error : "unknown error"}`;
+                main.dictationSuccess = false;
             } finally {
-                MainView.Instance.initiateDictationFade();
+                main.initiateDictationFade();
             }
+
             return results;
         };
 
@@ -137,11 +144,10 @@ export namespace DictationManager {
                 let complete = () => {
                     if (indefinite) {
                         current && sessionResults.push(current);
-                        resolve(sessionResults.join(inter || interSession));
+                        sessionResults.length && resolve(sessionResults.join(inter || interSession));
                     } else {
                         resolve(current);
                     }
-                    current = undefined;
                     reset();
                 };
 
@@ -154,12 +160,12 @@ export namespace DictationManager {
             }
             isManuallyStopped = true;
             salvageSession ? recognizer.stop() : recognizer.abort();
-            if (MainView.Instance.dictationOverlayVisible) {
-                MainView.Instance.cancelDictationFade();
-                MainView.Instance.dictationOverlayVisible = false;
-                MainView.Instance.isListening = true;
-                MainView.Instance.dictatedPhrase = "";
-                MainView.Instance.dictationSuccess = undefined;
+            let main = MainView.Instance;
+            if (main.dictationOverlayVisible) {
+                main.cancelDictationFade();
+                main.dictationOverlayVisible = false;
+                main.dictationSuccess = undefined;
+                setTimeout(() => main.dictatedPhrase = placeholder, 500);
             }
         };
 
@@ -173,12 +179,13 @@ export namespace DictationManager {
         };
 
         const reset = () => {
+            current = undefined;
+            sessionResults = [];
             isListening = false;
             isManuallyStopped = false;
             recognizer.onresult = null;
             recognizer.onerror = null;
             recognizer.onend = null;
-            sessionResults = [];
         };
 
     }
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 383efa1e3..631d24cb1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -49,10 +49,10 @@ export class MainView extends React.Component {
     @observable public pwidth: number = 0;
     @observable public pheight: number = 0;
 
-    @observable private dictationState = "";
+    @observable private dictationState = DictationManager.placeholder;
     @observable private dictationSuccessState: boolean | undefined = undefined;
     @observable private dictationDisplayState = false;
-    @observable private dictationListeningState = false;
+    @observable private dictationListeningState: DictationManager.Controls.ListeningUIStatus = false;
 
     public overlayTimeout: NodeJS.Timeout | undefined;
 
@@ -61,6 +61,7 @@ export class MainView extends React.Component {
         this.overlayTimeout = setTimeout(() => {
             this.dictationOverlayVisible = false;
             this.dictationSuccess = undefined;
+            setTimeout(() => this.dictatedPhrase = DictationManager.placeholder, 500);
         }, duration);
     }
 
@@ -116,7 +117,7 @@ export class MainView extends React.Component {
         return this.dictationListeningState;
     }
 
-    public set isListening(value: boolean) {
+    public set isListening(value: DictationManager.Controls.ListeningUIStatus) {
         runInAction(() => this.dictationListeningState = value);
     }
 
@@ -167,8 +168,6 @@ export class MainView extends React.Component {
             }
         }
 
-        autorun(() => console.log(`this.isListening = ${this.isListening}`));
-
         library.add(faFont);
         library.add(faExclamation);
         library.add(faPortrait);
@@ -525,7 +524,7 @@ export class MainView extends React.Component {
     render() {
         let display = this.dictationOverlayVisible;
         let success = this.dictationSuccess;
-        let result = this.isListening ? DictationManager.placeholder : `"${this.dictatedPhrase}"`;
+        let result = this.isListening && !this.isListening.interim ? DictationManager.placeholder : `"${this.dictatedPhrase}"`;
         return (
             <div id="main-div">
                 <div
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 458fb582d..d6ae17537 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -536,19 +536,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true;
     }
 
-    listen = async () => {
-        let dataDoc = Doc.GetProto(this.props.Document);
-        let handler = (results: string) => {
-            MainView.Instance.isListening = false;
-            MainView.Instance.dictationSuccess = true;
-            MainView.Instance.dictatedPhrase = results;
-        };
-        let final = await DictationManager.Controls.listen({
+    listen = async () =>
+        Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({
             continuous: { indefinite: true },
-            interimHandler: handler
-        });
-        final && (dataDoc.transcript = final);
-    }
+            interimHandler: (results: string) => {
+                MainView.Instance.isListening = { interim: true };
+                MainView.Instance.dictationSuccess = true;
+                MainView.Instance.dictatedPhrase = results;
+            }
+        })
 
     @action
     onContextMenu = async (e: React.MouseEvent): Promise<void> => {
-- 
cgit v1.2.3-70-g09d2


From 0b2d0cc563755c7b5899bc25a791359306166d9b Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sun, 4 Aug 2019 13:34:29 -0400
Subject: fixed up backend for titles and captions.

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

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

diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 4528cf94d..a7b4f33db 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -690,8 +690,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         let templates = Cast(this.layoutDoc.templates, listSpec("string"));
         if (!showOverlays && templates instanceof List) {
             templates.map(str => {
-                if (str.indexOf("{props.Document.title}") !== -1) showTitle = "title";
-                if (str.indexOf("fieldKey={\"caption\"}") !== -1) showCaption = "caption";
+                if (!showTitle && str.indexOf("{props.Document.title}") !== -1) showTitle = "title";
+                if (!showCaption && str.indexOf("fieldKey={\"caption\"}") !== -1) showCaption = "caption";
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 6c97add76..9dd0643df 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -180,14 +180,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
             }
             this._applyingChange = true;
             const fieldkey = "preview";
-            if (Object.keys(this.dataDoc).indexOf(fieldkey) !== -1) {
-                this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
-                this.dataDoc[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n");
-            }
-            else {
-                Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
-                Doc.GetProto(this.dataDoc)[this.props.fieldKey + "_text"] = state.doc.textBetween(0, state.doc.content.size, "\n\n");
-            }
             if (this.extensionDoc) this.extensionDoc.text = state.doc.textBetween(0, state.doc.content.size, "\n\n");
             if (this.extensionDoc) this.extensionDoc.lastModified = new DateField(new Date(Date.now()));
             this.dataDoc[this.props.fieldKey] = new RichTextField(JSON.stringify(state.toJSON()));
-- 
cgit v1.2.3-70-g09d2


From 857e98b16f693db968b04ede62e3f864f8b2ed35 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sun, 4 Aug 2019 14:04:22 -0400
Subject: final UI tweaks

---
 src/client/util/DictationManager.ts     |  5 ++---
 src/client/views/MainView.tsx           |  8 ++++----
 src/client/views/nodes/DocumentView.tsx | 12 +++++++-----
 3 files changed, 13 insertions(+), 12 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index ee1f11b4f..978889830 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -73,15 +73,14 @@ export namespace DictationManager {
             let main = MainView.Instance;
 
             main.dictationOverlayVisible = true;
-            let interim = options !== undefined && options.interimHandler !== undefined;
-            main.isListening = { interim: interim };
+            main.isListening = { interim: false };
 
             try {
                 results = await listenImpl(options);
                 if (results) {
                     main.isListening = false;
-                    main.dictatedPhrase = results;
                     let execute = options && options.tryExecute;
+                    main.dictatedPhrase = execute ? results.toLowerCase() : results;
                     main.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true;
                 }
             } catch (e) {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 631d24cb1..618e83bfa 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -89,7 +89,7 @@ export class MainView extends React.Component {
         }
     }
 
-    public get dictatedPhrase() {
+    @computed public get dictatedPhrase() {
         return this.dictationState;
     }
 
@@ -97,7 +97,7 @@ export class MainView extends React.Component {
         runInAction(() => this.dictationState = value);
     }
 
-    public get dictationSuccess() {
+    @computed public get dictationSuccess() {
         return this.dictationSuccessState;
     }
 
@@ -105,7 +105,7 @@ export class MainView extends React.Component {
         runInAction(() => this.dictationSuccessState = value);
     }
 
-    public get dictationOverlayVisible() {
+    @computed public get dictationOverlayVisible() {
         return this.dictationDisplayState;
     }
 
@@ -113,7 +113,7 @@ export class MainView extends React.Component {
         runInAction(() => this.dictationDisplayState = value);
     }
 
-    public get isListening() {
+    @computed public get isListening() {
         return this.dictationListeningState;
     }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index d6ae17537..57e66ff1b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -536,15 +536,17 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         this.props.Document.lockedPosition = BoolCast(this.props.Document.lockedPosition) ? undefined : true;
     }
 
-    listen = async () =>
+    listen = async () => {
         Doc.GetProto(this.props.Document).transcript = await DictationManager.Controls.listen({
             continuous: { indefinite: true },
             interimHandler: (results: string) => {
-                MainView.Instance.isListening = { interim: true };
-                MainView.Instance.dictationSuccess = true;
-                MainView.Instance.dictatedPhrase = results;
+                let main = MainView.Instance;
+                main.dictationSuccess = true;
+                main.dictatedPhrase = results;
+                main.isListening = { interim: true };
             }
-        })
+        });
+    }
 
     @action
     onContextMenu = async (e: React.MouseEvent): Promise<void> => {
-- 
cgit v1.2.3-70-g09d2


From 2978e805156bffad92e8ac23259843a7cf207fb7 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 6 Aug 2019 11:11:26 -0400
Subject: beginning link to doc in context

---
 src/client/util/DictationManager.ts     | 4 ++--
 src/client/views/nodes/DocumentView.tsx | 8 ++++++++
 2 files changed, 10 insertions(+), 2 deletions(-)

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 3668ca0d2..9c61fe125 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -11,7 +11,7 @@ import { listSpec } from "../../new_fields/Schema";
 import { AudioField, ImageField } from "../../new_fields/URLField";
 import { HistogramField } from "../northstar/dash-fields/HistogramField";
 import { MainView } from "../views/MainView";
-import { Clipboard } from "ts-clipboard";
+import { Utils } from "../../Utils";
 
 /**
  * This namespace provides a singleton instance of a manager that
@@ -79,7 +79,7 @@ export namespace DictationManager {
             try {
                 results = await listenImpl(options);
                 if (results) {
-                    Clipboard.copy(results);
+                    Utils.CopyText(results);
                     main.isListening = false;
                     let execute = options && options.tryExecute;
                     main.dictatedPhrase = execute ? results.toLowerCase() : results;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 57e66ff1b..e5b0b0b52 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -602,6 +602,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         if (!ClientUtils.RELEASE) {
             let copies: ContextMenuProps[] = [];
             copies.push({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
+            copies.push({
+                description: "Copy Context", event: () => {
+                    let parent = this.props.ContainingCollectionView;
+                    if (parent) {
+                        Utils.CopyText(Utils.prepend("/doc/" + parent.props.Document[Id]));
+                    }
+                }, icon: "link"
+            });
             copies.push({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
             cm.addItem({ description: "Copy...", subitems: copies, icon: "copy" });
         }
-- 
cgit v1.2.3-70-g09d2


From 572c4196e0f41ec6bae8cae403812f9b97d5a3c7 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 6 Aug 2019 13:27:47 -0400
Subject: post factored out, buxton pivot viewer route

---
 src/Utils.ts                            | 11 +++++++++++
 src/client/views/nodes/DocumentView.tsx |  1 +
 src/server/index.ts                     | 19 +++++++++++++++++++
 3 files changed, 31 insertions(+)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index 502540eb0..c1737a084 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -3,6 +3,7 @@ import v5 = require("uuid/v5");
 import { Socket } from 'socket.io';
 import { Message } from './server/Message';
 import { RouteStore } from './server/RouteStore';
+import requestPromise = require('request-promise');
 
 export class Utils {
 
@@ -173,4 +174,14 @@ export namespace JSONUtils {
         return results;
     }
 
+}
+
+export function PostToServer(relativeRoute: string, body: any) {
+    let options = {
+        method: "POST",
+        uri: Utils.prepend(relativeRoute),
+        json: true,
+        body: body
+    };
+    return requestPromise.post(options);
 }
\ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 547d858ce..dbbf95479 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -39,6 +39,7 @@ import { FormattedTextBox } from './FormattedTextBox';
 import React = require("react");
 import { DictationManager } from '../../util/DictationManager';
 import { MainView } from '../MainView';
+import requestPromise = require('request-promise');
 const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
 
 library.add(fa.faTrash);
diff --git a/src/server/index.ts b/src/server/index.ts
index 10a84c823..1e811c1b2 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -148,6 +148,25 @@ app.get("/pull", (req, res) =>
         res.redirect("/");
     }));
 
+app.get("/buxton/:clear", (req, res) => {
+    if (req.params.clear === "true") {
+        deleteFields().then(() => upload_buxton_docs(res));
+    } else {
+        upload_buxton_docs(res);
+    }
+});
+
+let upload_buxton_docs = (res: Response) => {
+    let buxton_scraping = path.join(__dirname, '../scraping/buxton');
+    exec('python scraper.py', { cwd: buxton_scraping }, (err, stdout, sterr) => {
+        if (err) {
+            res.send(err.message);
+            return;
+        }
+        res.redirect("/");
+    });
+};
+
 app.get("/version", (req, res) => {
     exec('"C:\\Program Files\\Git\\bin\\git.exe" rev-parse HEAD', (err, stdout, stderr) => {
         if (err) {
-- 
cgit v1.2.3-70-g09d2


From ebddce0975fa7e224e022cc075eee71abeacbe1d Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 7 Aug 2019 23:27:21 -0400
Subject: changed pdfs to render regions as collections.  changed libraryBrush
 implementation.

---
 src/client/views/SearchItem.tsx                    |  6 ++--
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 .../views/collections/CollectionSchemaView.tsx     |  2 +-
 .../views/collections/CollectionTreeView.tsx       |  8 ++---
 src/client/views/nodes/DocumentView.tsx            | 37 ++++++++++-----------
 src/client/views/nodes/FormattedTextBox.tsx        |  2 +-
 src/client/views/pdf/PDFViewer.tsx                 |  3 +-
 src/client/views/pdf/Page.tsx                      |  9 ++---
 .../views/presentationview/PresentationElement.tsx |  4 +--
 src/client/views/search/SearchItem.tsx             | 38 +++++++---------------
 src/new_fields/Doc.ts                              | 18 ++++++++++
 src/new_fields/util.ts                             |  1 -
 src/scraping/buxton/scraper.py                     |  9 ++---
 13 files changed, 65 insertions(+), 74 deletions(-)

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

diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
index 13e4b88f7..fd4b2420d 100644
--- a/src/client/views/SearchItem.tsx
+++ b/src/client/views/SearchItem.tsx
@@ -37,12 +37,10 @@ export class SearchItem extends React.Component<SearchProps> {
         return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
     }
     onPointerEnter = (e: React.PointerEvent) => {
-        this.props.doc.libraryBrush = true;
-        Doc.SetOnPrototype(this.props.doc, "protoBrush", true);
+        Doc.BrushDoc(this.props.doc);
     }
     onPointerLeave = (e: React.PointerEvent) => {
-        this.props.doc.libraryBrush = false;
-        Doc.SetOnPrototype(this.props.doc, "protoBrush", false);
+        Doc.UnBrushDoc(this.props.doc);
     }
 
     collectionRef = React.createRef<HTMLDivElement>();
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index f559480ed..3af92cbe3 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -405,7 +405,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
                             handlers: { dragComplete: emptyFunction },
                             hideSource: false
                         });
-                    }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+                    }}><FontAwesomeIcon icon="file" color={["white", "maroon", "blue"][Doc.IsBrushedDegree(doc)]} size="lg" /></span>, dragSpan);
                 ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={doc => CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv);
                 tab.reactComponents = [dragSpan, upDiv];
                 tab.element.append(dragSpan);
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 75787c0a8..ebfa737be 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -50,7 +50,7 @@ const columnTypes: Map<string, ColumnType> = new Map([
     ["title", ColumnType.String],
     ["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number],
     ["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean],
-    ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["libraryBrush", ColumnType.Boolean], ["zIndex", ColumnType.Number]
+    ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number]
 ]);
 
 @observer
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 571967743..24bd24d11 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -127,19 +127,19 @@ class TreeView extends React.Component<TreeViewProps> {
 
     onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
     onPointerEnter = (e: React.PointerEvent): void => {
-        this.props.active() && (this.props.document.libraryBrush = true);
+        this.props.active() && Doc.BrushDoc(this.dataDoc);
         if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
             this._header!.current!.className = "treeViewItem-header";
             document.addEventListener("pointermove", this.onDragMove, true);
         }
     }
     onPointerLeave = (e: React.PointerEvent): void => {
-        this.props.document.libraryBrush = false;
+        Doc.UnBrushDoc(this.dataDoc);
         this._header!.current!.className = "treeViewItem-header";
         document.removeEventListener("pointermove", this.onDragMove, true);
     }
     onDragMove = (e: PointerEvent): void => {
-        this.props.document.libraryBrush = false;
+        Doc.UnBrushDoc(this.dataDoc);
         let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
         let rect = this._header!.current!.getBoundingClientRect();
         let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
@@ -359,7 +359,7 @@ class TreeView extends React.Component<TreeViewProps> {
         return <>
             <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
                 style={{
-                    background: BoolCast(this.props.document.libraryBrush) ? "#06121212" : "0",
+                    background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0",
                     outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined,
                     pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
                 }} >
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a7b4f33db..e911dc47c 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -303,7 +303,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             fullScreenAlias.showCaption = true;
             this.props.addDocTab(fullScreenAlias, this.dataDoc, "inTab");
             SelectionManager.DeselectAll();
-            this.props.Document.libraryBrush = false;
+            Doc.UnBrushDoc(this.props.Document);
         }
         else if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
             (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
@@ -445,13 +445,11 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             targetDoc.targetContext = de.data.targetContext;
             let annotations = await DocListCastAsync(annotationDoc.annotations);
             if (annotations) {
-                annotations.forEach(anno => {
-                    anno.target = targetDoc;
-                });
+                annotations.forEach(anno => anno.target = targetDoc);
             }
-            let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc);
-            if (pdfDoc) {
-                DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title));
+            let annotDoc = await Cast(annotationDoc.annotationOn, Doc);
+            if (annotDoc) {
+                DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(annotDoc.title)}`, "", StrCast(annotDoc.title));
             }
         }
         if (de.data instanceof DragManager.LinkDragData) {
@@ -647,8 +645,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         });
     }
 
-    onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; };
-    onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; };
+    onPointerEnter = (e: React.PointerEvent): void => { Doc.BrushDoc(this.props.Document); };
+    onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.props.Document); };
 
     isSelected = () => SelectionManager.IsSelected(this);
     @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); };
@@ -695,22 +693,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             });
         }
         let showTextTitle = showTitle && StrCast(this.layoutDoc.layout).startsWith("<FormattedTextBox") ? showTitle : undefined;
+        let brushDegree = Doc.IsBrushedDegree(this.layoutDoc);
         return (
             <div className={`documentView-node${this.topMost ? "-topmost" : ""}`}
                 ref={this._mainCont}
                 style={{
                     pointerEvents: this.layoutDoc.isBackground && !this.isSelected() ? "none" : "all",
                     color: foregroundColor,
-                    outlineColor: "maroon",
-                    outlineStyle: "dashed",
-                    outlineWidth: BoolCast(this.layoutDoc.libraryBrush) && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-                        `${this.props.ScreenToLocalTransform().Scale}px` : "0px",
-                    marginLeft: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-                        `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
-                    marginTop: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-                        `${-1 * this.props.ScreenToLocalTransform().Scale}px` : undefined,
-                    border: BoolCast(this.layoutDoc.libraryBrush) && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
-                        `dashed maroon ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
+                    outlineColor: ["transparent", "maroon", "maroon"][brushDegree],
+                    outlineStyle: ["none", "dashed", "solid"][brushDegree],
+                    outlineWidth: brushDegree && !StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+                        `${brushDegree * this.props.ScreenToLocalTransform().Scale}px` : "0px",
+                    marginLeft: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+                        `${-brushDegree * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+                    marginTop: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+                        `${-brushDegree * this.props.ScreenToLocalTransform().Scale}px` : undefined,
+                    border: brushDegree && StrCast(Doc.GetProto(this.props.Document).borderRounding) ?
+                        `${["none", "dashed", "solid"][brushDegree]} ${["transparent", "maroon", "maroon"][brushDegree]} ${this.props.ScreenToLocalTransform().Scale}px` : undefined,
                     borderRadius: "inherit",
                     background: backgroundColor,
                     width: nativeWidth,
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9dd0643df..44b5d2c21 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -674,7 +674,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 style={{
                     height: this.props.height ? this.props.height : undefined,
                     background: this.props.hideOnLeave ? "rgba(0,0,0,0.4)" : undefined,
-                    opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || this.props.Document.libraryBrush ? 1 : 0.1) : 1,
+                    opacity: this.props.hideOnLeave ? (this._entered || this.props.isSelected() || Doc.IsBrushed(this.props.Document) ? 1 : 0.1) : 1,
                     color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "inherit",
                     pointerEvents: interactive,
                     fontSize: "13px"
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 95df10c58..7cd62f4e0 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -184,7 +184,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
             annoDoc.color = color;
             annoDoc.type = AnnotationTypes.Region;
             annoDocs.push(annoDoc);
-            annoDoc.isBackground = true;
+            annoDoc.isButton = true;
             anno.remove();
             this.props.addDocument && this.props.addDocument(annoDoc, false);
             mainAnnoDoc = annoDoc;
@@ -208,7 +208,6 @@ export class PDFViewer extends React.Component<IViewerProps> {
             mainAnnoDocProto.y = Math.max(minY, 0);
             mainAnnoDocProto.annotations = new List<Doc>(annoDocs);
         }
-        mainAnnoDocProto.pdfDoc = this.props.Document;
         mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title);
         mainAnnoDocProto.annotationOn = this.props.Document;
         if (sourceDoc && createLink) {
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 4986f44d5..6bd98cbaa 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -112,8 +112,8 @@ export default class Page extends React.Component<IPageProps> {
                         if (!BoolCast(annotationDoc.linkedToDoc)) {
                             let annotations = await DocListCastAsync(annotationDoc.annotations);
                             annotations && annotations.forEach(anno => anno.target = targetDoc);
-                            let pdfDoc = await Cast(annotationDoc.pdfDoc, Doc);
-                            pdfDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(pdfDoc.title)}`, "", StrCast(pdfDoc.title))
+                            let parentDoc = await Cast(annotationDoc.annotationOn, Doc);
+                            parentDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(parentDoc.title)}`, "", StrCast(parentDoc.title))
                         }
                     }
                 },
@@ -144,10 +144,7 @@ export default class Page extends React.Component<IPageProps> {
     onPointerDown = (e: React.PointerEvent): void => {
         // if alt+left click, drag and annotate
         if (NumCast(this.props.Document.scale, 1) !== 1) return;
-        if (e.altKey && e.button === 0) {
-            e.stopPropagation();
-        }
-        else if (e.button === 0) {
+        if (!e.altKey && e.button === 0) {
             PDFMenu.Instance.StartDrag = this.startDrag;
             PDFMenu.Instance.Highlight = this.highlight;
             PDFMenu.Instance.Snippet = this.createSnippet;
diff --git a/src/client/views/presentationview/PresentationElement.tsx b/src/client/views/presentationview/PresentationElement.tsx
index e2d8daea9..d98b66324 100644
--- a/src/client/views/presentationview/PresentationElement.tsx
+++ b/src/client/views/presentationview/PresentationElement.tsx
@@ -706,7 +706,7 @@ export default class PresentationElement extends React.Component<PresentationEle
      * It makes it possible to show dropping lines on drop targets.
      */
     onDragMove = (e: PointerEvent): void => {
-        this.props.document.libraryBrush = false;
+        Doc.UnBrushDoc(this.props.document);
         let x = this.ScreenToLocalListTransform(e.clientX, e.clientY);
         let rect = this.header!.getBoundingClientRect();
         let bounds = this.ScreenToLocalListTransform(rect.left, rect.top + rect.height / 2);
@@ -889,7 +889,7 @@ export default class PresentationElement extends React.Component<PresentationEle
                 style={{
                     outlineColor: "maroon",
                     outlineStyle: "dashed",
-                    outlineWidth: BoolCast(p.document.libraryBrush) ? `1px` : "0px",
+                    outlineWidth: Doc.IsBrushed(p.document) ? `1px` : "0px",
                 }}
                 onClick={e => { p.gotoDocument(p.index, NumCast(this.props.mainDocument.selectedDoc)); e.stopPropagation(); }}>
                 <strong className="presentationView-name">
diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx
index c4af30f5c..8201aa374 100644
--- a/src/client/views/search/SearchItem.tsx
+++ b/src/client/views/search/SearchItem.tsx
@@ -105,23 +105,11 @@ export interface LinkMenuProps {
 @observer
 export class LinkContextMenu extends React.Component<LinkMenuProps> {
 
-    highlightDoc = (doc: Doc) => {
-        return () => {
-            doc.libraryBrush = true;
-        };
-    }
+    highlightDoc = (doc: Doc) => () => Doc.BrushDoc(doc);
 
-    unHighlightDoc = (doc: Doc) => {
-        return () => {
-            doc.libraryBrush = false;
-        };
-    }
+    unHighlightDoc = (doc: Doc) => () => Doc.UnBrushDoc(doc);
 
-    getOnClick(col: Doc) {
-        return () => {
-            CollectionDockingView.Instance.AddRightSplit(col, undefined);
-        };
-    }
+    getOnClick = (col: Doc) => () => CollectionDockingView.Instance.AddRightSplit(col, undefined);
 
     render() {
         return (
@@ -286,14 +274,12 @@ export class SearchItem extends React.Component<SearchItemProps> {
 
                 let doc1 = Cast(this.props.doc.anchor1, Doc, null);
                 let doc2 = Cast(this.props.doc.anchor2, Doc, null);
-                doc1 && (doc1.libraryBrush = true);
-                doc2 && (doc2.libraryBrush = true);
+                Doc.BrushDoc(doc1);
+                Doc.BrushDoc(doc2);
             }
         } else {
-            let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
-            docViews.forEach(element => {
-                element.props.Document.libraryBrush = true;
-            });
+            DocumentManager.Instance.getAllDocumentViews(this.props.doc).forEach(element =>
+                Doc.BrushDoc(element.props.Document));
         }
     }
 
@@ -303,14 +289,12 @@ export class SearchItem extends React.Component<SearchItemProps> {
 
                 let doc1 = Cast(this.props.doc.anchor1, Doc, null);
                 let doc2 = Cast(this.props.doc.anchor2, Doc, null);
-                doc1 && (doc1.libraryBrush = false);
-                doc2 && (doc2.libraryBrush = false);
+                Doc.UnBrushDoc(doc1);
+                Doc.UnBrushDoc(doc2);
             }
         } else {
-            let docViews: DocumentView[] = DocumentManager.Instance.getAllDocumentViews(this.props.doc);
-            docViews.forEach(element => {
-                element.props.Document.libraryBrush = false;
-            });
+            DocumentManager.Instance.getAllDocumentViews(this.props.doc).
+                forEach(element => Doc.UnBrushDoc(element.props.Document));
         }
     }
 
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index c01f4e8cf..b3d1dc109 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -542,4 +542,22 @@ export namespace Doc {
             }
         });
     }
+
+    export class DocBrush {
+        @observable BrushedDoc: Doc[] = [];
+    }
+    const manager = new DocBrush();
+    export function IsBrushed(doc: Doc) {
+        return manager.BrushedDoc.some(d => Doc.AreProtosEqual(d, doc));
+    }
+    export function IsBrushedDegree(doc: Doc) {
+        return manager.BrushedDoc.some(d => d === doc) ? 2 : Doc.IsBrushed(doc) ? 1 : 0;
+    }
+    export function BrushDoc(doc: Doc) {
+        if (manager.BrushedDoc.indexOf(doc) === -1) runInAction(() => manager.BrushedDoc.push(doc));
+    }
+    export function UnBrushDoc(doc: Doc) {
+        let index = manager.BrushedDoc.indexOf(doc);
+        if (index !== -1) runInAction(() => manager.BrushedDoc.splice(index, 1));
+    }
 }
\ No newline at end of file
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index c6f693f7f..48ae9f216 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -6,7 +6,6 @@ import { RefField } from "./RefField";
 import { ObjectField } from "./ObjectField";
 import { action } from "mobx";
 import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
-import { ComputedField } from "./ScriptField";
 
 function _readOnlySetter(): never {
     throw new Error("Documents can't be modified in read-only mode");
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index f0f45d8f9..29cb8a256 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -88,8 +88,7 @@ def write_schema(parse_results, display_fields, storage_key):
             "panX": 0,
             "panY": 0,
             "zoomBasis": 1,
-            "zIndex": 2,
-            "libraryBrush": False,
+            "zIndex": 2
             "viewType": 2
         },
         "__type": "Doc"
@@ -130,8 +129,7 @@ def write_text_doc(content):
             "x": 10,
             "y": 10,
             "width": 400,
-            "zIndex": 2,
-            "libraryBrush": False
+            "zIndex": 2
         },
         "__type": "Doc"
     }
@@ -183,8 +181,7 @@ def write_image(folder, name):
             "x": 10,
             "y": 10,
             "width": min(800, native_width),
-            "zIndex": 2,
-            "libraryBrush": False
+            "zIndex": 2
         },
         "__type": "Doc"
     }
-- 
cgit v1.2.3-70-g09d2


From 316c241d72fb83aad5f2bf9b143c317fdc906654 Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Thu, 8 Aug 2019 12:23:47 -0400
Subject: fixed issues with jumptoDocument

---
 package.json                                       |  2 +-
 src/client/documents/Documents.ts                  |  3 +--
 src/client/util/DocumentManager.ts                 | 12 ++++++-----
 .../views/collections/CollectionBaseView.tsx       |  5 ++---
 src/client/views/nodes/DocumentView.tsx            | 24 +++++++++-------------
 src/client/views/nodes/LinkMenuItem.tsx            |  4 ++--
 src/client/views/pdf/PDFViewer.tsx                 |  2 +-
 src/client/views/pdf/Page.tsx                      |  3 +--
 8 files changed, 25 insertions(+), 30 deletions(-)

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

diff --git a/package.json b/package.json
index 29a903d71..d699d1e6f 100644
--- a/package.json
+++ b/package.json
@@ -218,4 +218,4 @@
         "xoauth2": "^1.2.0",
         "youtube": "^0.1.0"
     }
-}
\ No newline at end of file
+}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a8545206a..7dd853156 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -596,7 +596,7 @@ export namespace Docs {
 
 export namespace DocUtils {
 
-    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", tags: string = "Default", sourceContext?: Doc) {
+    export function MakeLink(source: Doc, target: Doc, targetContext?: Doc, title: string = "", description: string = "", sourceContext?: Doc) {
         if (LinkManager.Instance.doesLinkExist(source, target)) return undefined;
         let sv = DocumentManager.Instance.getDocumentView(source);
         if (sv && sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === target) return;
@@ -610,7 +610,6 @@ export namespace DocUtils {
             linkDocProto.sourceContext = sourceContext;
             linkDocProto.title = title === "" ? source.title + " to " + target.title : title;
             linkDocProto.linkDescription = description;
-            linkDocProto.linkTags = tags;
             linkDocProto.type = DocumentType.LINK;
 
             linkDocProto.anchor1 = source;
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 0d46df406..7f526b247 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,5 +1,5 @@
 import { action, computed, observable } from 'mobx';
-import { Doc } from '../../new_fields/Doc';
+import { Doc, DocListCastAsync } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
 import { Cast, NumCast } from '../../new_fields/Types';
 import { CollectionDockingView } from '../views/collections/CollectionDockingView';
@@ -140,13 +140,15 @@ export class DocumentManager {
         if (!forceDockFunc && (docView = DocumentManager.Instance.getDocumentView(doc))) {
             Doc.BrushDoc(docView.props.Document);
             if (linkPage !== undefined) docView.props.Document.curPage = linkPage;
-            UndoManager.RunInBatch(() => {
-                docView!.props.focus(docView!.props.Document, willZoom);
-            }, "focus");
+            UndoManager.RunInBatch(() => docView!.props.focus(docView!.props.Document, willZoom), "focus");
         } else {
             if (!contextDoc) {
-                if (docContext) {
+                let docs = docContext ? await DocListCastAsync(docContext.data) : undefined;
+                let found = false;
+                docs && docs.map(d => found = found || Doc.AreProtosEqual(d, docDelegate));
+                if (docContext && found) {
                     let targetContextView: DocumentView | null;
+
                     if (!forceDockFunc && docContext && (targetContextView = DocumentManager.Instance.getDocumentView(docContext))) {
                         docContext.panTransformType = "Ease";
                         targetContextView.props.focus(docDelegate, willZoom);
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index b53e83eb1..cad87ebcc 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -99,7 +99,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
     addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
         var curPage = NumCast(this.props.Document.curPage, -1);
         Doc.GetProto(doc).page = curPage;
-        if (curPage >= 0) {
+        if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer
             Doc.GetProto(doc).annotationOn = this.props.Document;
         }
         allowDuplicates = true;
@@ -126,8 +126,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
         let value = Cast(targetDataDoc[targetField], listSpec(Doc), []);
         let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1);
         PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
-            annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)
-        );
+            annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined));
 
         if (index !== -1) {
             value.splice(index, 1);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 861b53abf..c8eab85c2 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -8,7 +8,7 @@ import { Copy, Id } from '../../../new_fields/FieldSymbols';
 import { List } from "../../../new_fields/List";
 import { ObjectField } from "../../../new_fields/ObjectField";
 import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
-import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast, PromiseValue } from "../../../new_fields/Types";
 import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
 import { RouteStore } from '../../../server/RouteStore';
 import { emptyFunction, returnTrue, Utils } from "../../../Utils";
@@ -359,12 +359,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
                     if (!linkedFwdDocs.some(l => l instanceof Promise)) {
                         let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab");
                         let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined;
-                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, document => {
-                            this.props.focus(this.props.Document, true, 1);
-                            setTimeout(() =>
-                                this.props.addDocTab(document, undefined, maxLocation), 1000);
-                        }
-                            , linkedFwdPage[altKey ? 1 : 0], targetContext);
+                        DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false,
+                            document => {  // open up target if it's not already in view ...
+                                this.props.focus(this.props.Document, true, 1);  // by zooming into the button document first
+                                setTimeout(() => this.props.addDocTab(document, undefined, maxLocation), 1000); // then after the 1sec animation, open up the target in a new tab
+                            },
+                            linkedFwdPage[altKey ? 1 : 0], targetContext);
                     }
                 }
             }
@@ -445,13 +445,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             let targetDoc = this.props.Document;
             targetDoc.targetContext = de.data.targetContext;
             let annotations = await DocListCastAsync(annotationDoc.annotations);
-            if (annotations) {
-                annotations.forEach(anno => anno.target = targetDoc);
-            }
-            let annotDoc = await Cast(annotationDoc.annotationOn, Doc);
-            if (annotDoc) {
-                DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Annotation from ${StrCast(annotDoc.title)}`, "", StrCast(annotDoc.title));
-            }
+            annotations && annotations.forEach(anno => anno.target = targetDoc);
+
+            DocUtils.MakeLink(annotationDoc, targetDoc, this.props.ContainingCollectionView!.props.Document, `Link from ${StrCast(annotationDoc.title)}`);
         }
         if (de.data instanceof DragManager.LinkDragData) {
             let sourceDoc = de.data.linkSourceDocument;
diff --git a/src/client/views/nodes/LinkMenuItem.tsx b/src/client/views/nodes/LinkMenuItem.tsx
index 1d4fcad69..a119eb39b 100644
--- a/src/client/views/nodes/LinkMenuItem.tsx
+++ b/src/client/views/nodes/LinkMenuItem.tsx
@@ -6,7 +6,7 @@ import { DocumentManager } from "../../util/DocumentManager";
 import { undoBatch } from "../../util/UndoManager";
 import './LinkMenu.scss';
 import React = require("react");
-import { Doc } from '../../../new_fields/Doc';
+import { Doc, DocListCastAsync } from '../../../new_fields/Doc';
 import { StrCast, Cast, FieldValue, NumCast } from '../../../new_fields/Types';
 import { observable, action } from 'mobx';
 import { LinkManager } from '../../util/LinkManager';
@@ -52,7 +52,7 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
         }
 
         if (this.props.destinationDoc === self.props.linkDoc.anchor2 && targetContext) {
-            DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(targetContext!));
+            DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, async document => dockingFunc(document), undefined, targetContext!);
         }
         else if (this.props.destinationDoc === self.props.linkDoc.anchor1 && sourceContext) {
             DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, false, document => dockingFunc(sourceContext!));
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 7cd62f4e0..08674720d 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -211,7 +211,7 @@ export class PDFViewer extends React.Component<IViewerProps> {
         mainAnnoDocProto.title = "Annotation on " + StrCast(this.props.Document.title);
         mainAnnoDocProto.annotationOn = this.props.Document;
         if (sourceDoc && createLink) {
-            DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`, "", StrCast(this.props.Document.title));
+            DocUtils.MakeLink(sourceDoc, mainAnnoDocProto, undefined, `Annotation from ${StrCast(this.props.Document.title)}`);
         }
         this._savedAnnotations.clear();
         this.Index = -1;
diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx
index 6bd98cbaa..7ca9d2d7d 100644
--- a/src/client/views/pdf/Page.tsx
+++ b/src/client/views/pdf/Page.tsx
@@ -112,8 +112,7 @@ export default class Page extends React.Component<IPageProps> {
                         if (!BoolCast(annotationDoc.linkedToDoc)) {
                             let annotations = await DocListCastAsync(annotationDoc.annotations);
                             annotations && annotations.forEach(anno => anno.target = targetDoc);
-                            let parentDoc = await Cast(annotationDoc.annotationOn, Doc);
-                            parentDoc && DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(parentDoc.title)}`, "", StrCast(parentDoc.title))
+                            DocUtils.MakeLink(annotationDoc, targetDoc, dragData.targetContext, `Annotation from ${StrCast(this.props.Document.title)}`)
                         }
                     }
                 },
-- 
cgit v1.2.3-70-g09d2

