From b4ef60552f226e4022fac5092ea66f1b685bb08c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Mon, 22 Jul 2019 18:50:26 -0400
Subject: Started adding schema functionality

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 63 +++++++++++-----------
 1 file changed, 32 insertions(+), 31 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 703873681..dd577a467 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -477,43 +477,44 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }, "arrange contents");
             }
         });
-        ContextMenu.Instance.addItem({
-            description: "Add freeform arrangement",
-            event: () => {
-                let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
-                    let overlayDisposer: () => void = emptyFunction;
-                    const script = this.Document[key];
-                    let originalText: string | undefined = undefined;
-                    if (script) originalText = script.script.originalScript;
-                    // tslint:disable-next-line: no-unnecessary-callback-wrapper
-                    let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
-                        const script = CompileScript(text, {
-                            params,
-                            requiredType,
-                            typecheck: false
-                        });
-                        if (!script.compiled) {
-                            onError(script.errors.map(error => error.messageText).join("\n"));
-                            return;
-                        }
-                        const docs = DocListCast(this.Document[this.props.fieldKey]);
-                        docs.map(d => d.transition = "transform 1s");
-                        this.Document[key] = new ScriptField(script);
-                        overlayDisposer();
-                        setTimeout(() => docs.map(d => d.transition = undefined), 1200);
-                    }} />;
-                    overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
-                };
-                addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
-                addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
-            }
-        });
     }
 
     private childViews = () => [
         <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
         ...this.views
     ]
+
+    public static AddCustomLayout(doc: Doc, dataKey: string): () => void {
+        return () => {
+            let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+                let overlayDisposer: () => void = emptyFunction;
+                const script = Cast(doc[key], ScriptField);
+                let originalText: string | undefined = undefined;
+                if (script) originalText = script.script.originalScript;
+                // tslint:disable-next-line: no-unnecessary-callback-wrapper
+                let scriptingBox = <ScriptBox initialText={originalText} onCancel={() => overlayDisposer()} onSave={(text, onError) => {
+                    const script = CompileScript(text, {
+                        params,
+                        requiredType,
+                        typecheck: false
+                    });
+                    if (!script.compiled) {
+                        onError(script.errors.map(error => error.messageText).join("\n"));
+                        return;
+                    }
+                    const docs = DocListCast(doc[dataKey]);
+                    docs.map(d => d.transition = "transform 1s");
+                    doc[key] = new ScriptField(script);
+                    overlayDisposer();
+                    setTimeout(() => docs.map(d => d.transition = undefined), 1200);
+                }} />;
+                overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
+            };
+            addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
+            addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+        };
+    }
+
     render() {
         const easing = () => this.props.Document.panTransformType === "Ease";
 
-- 
cgit v1.2.3-70-g09d2


From 6d25848a9e8614c1debc8550e42fc706f77883db Mon Sep 17 00:00:00 2001
From: bob <bcz@cs.brown.edu>
Date: Tue, 23 Jul 2019 19:07:08 -0400
Subject: more template stuff

---
 src/client/views/collections/CollectionTreeView.tsx  | 20 +++++++++++---------
 .../collectionFreeForm/CollectionFreeFormView.tsx    |  9 +++++++--
 src/new_fields/Doc.ts                                |  9 +++++++--
 3 files changed, 25 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f98629c5b..ca5812eba 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -74,9 +74,10 @@ class TreeView extends React.Component<TreeViewProps> {
     @observable _collapsed: boolean = true;
 
     @computed get fieldKey() {
-        let keys = Array.from(Object.keys(this.dataDoc));  // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
-        if (this.dataDoc.proto instanceof Doc) {
-            let arr = Array.from(Object.keys(this.dataDoc.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+        let target = this.props.document.layout && this.dataDoc ? this.dataDoc : this.props.document;
+        let keys = Array.from(Object.keys(target));  // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
+        if (target.proto instanceof Doc) {
+            let arr = Array.from(Object.keys(target.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
             keys.push(...arr);
             while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
         }
@@ -191,16 +192,17 @@ class TreeView extends React.Component<TreeViewProps> {
     />)
 
     @computed get keyList() {
-        let keys = Array.from(Object.keys(this.dataDoc));
-        if (this.dataDoc.proto instanceof Doc) {
-            keys.push(...Array.from(Object.keys(this.dataDoc.proto)));
+        let target = this.props.document.layout && this.dataDoc ? this.dataDoc : this.props.document;
+        let keys = Array.from(Object.keys(target));
+        if (target.proto instanceof Doc) {
+            keys.push(...Array.from(Object.keys(target.proto)));
         }
         let keyList: string[] = keys.reduce((l, key) => {
-            let listspec = DocListCast(this.dataDoc[key]);
+            let listspec = DocListCast(target[key]);
             if (listspec && listspec.length) return [...l, key];
             return l;
         }, [] as string[]);
-        keys.map(key => Cast(this.dataDoc[key], Doc) instanceof Doc && (Cast(this.dataDoc[key], Doc) as Doc).type !== undefined && keyList.push(key));
+        keys.map(key => Cast(target[key], Doc) instanceof Doc && keyList.push(key));
         if (LinkManager.Instance.getAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
         if (keyList.indexOf(this.fieldKey) !== -1) {
             keyList.splice(keyList.indexOf(this.fieldKey), 1);
@@ -361,7 +363,7 @@ class TreeView extends React.Component<TreeViewProps> {
         let docList = Cast(this.dataDoc[this._chosenKey], listSpec(Doc));
         let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
         let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this._chosenKey, doc, addBefore, before);
-        let doc = Cast(this.dataDoc[this._chosenKey], Doc);
+        let doc = Cast((this.props.document.layout ? this.dataDoc : this.props.document)[this._chosenKey], Doc);
 
         if (!this._collapsed) {
             if (!this.props.document.embed) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 6bb082b66..e7d2a66e5 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -31,6 +31,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
 import { OverlayView, OverlayElementOptions } from "../../OverlayView";
 import { ScriptBox } from "../../ScriptBox";
 import { CompileScript } from "../../../util/Scripting";
+import { resolve } from "bluebird";
 
 
 export const panZoomSchema = createSchema({
@@ -358,8 +359,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
         let self = this;
         let resolvedDataDoc = !this.props.Document.isTemplate && this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
-        resolvedDataDoc && Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
-        let layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
+        let layoutDoc = childDocLayout;
+        if (resolvedDataDoc && Doc.WillExpandTemplateLayout(childDocLayout, resolvedDataDoc)) {
+            Doc.UpdateDocumentExtensionForField(resolvedDataDoc, this.props.fieldKey);
+            let fieldExtensionDoc = Doc.resolvedFieldDataDoc(resolvedDataDoc, StrCast(childDocLayout.templateField, StrCast(childDocLayout.title)), "dummy");
+            layoutDoc = Doc.expandTemplateLayout(childDocLayout, fieldExtensionDoc !== resolvedDataDoc ? fieldExtensionDoc : undefined);
+        } else layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
         return {
             DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined,
             Document: layoutDoc,
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 5ae0753d8..e4bf4a6c1 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -337,11 +337,15 @@ export namespace Doc {
         return Doc.MakeDelegate(doc); // bcz?
     }
 
-    export function expandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) {
+    export function WillExpandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) {
         let resolvedDataDoc = (templateLayoutDoc !== dataDoc) ? dataDoc : undefined;
         if (!dataDoc || !(templateLayoutDoc && !(Cast(templateLayoutDoc.layout, Doc) instanceof Doc) && resolvedDataDoc && resolvedDataDoc !== templateLayoutDoc)) {
-            return templateLayoutDoc;
+            return false;
         }
+        return true;
+    }
+    export function expandTemplateLayout(templateLayoutDoc: Doc, dataDoc?: Doc) {
+        if (!WillExpandTemplateLayout(templateLayoutDoc, dataDoc) || !dataDoc) return templateLayoutDoc;
         // if we have a data doc that doesn't match the layout, then we're rendering a template.
         // ... which means we change the layout to be an expanded view of the template layout.  
         // This allows the view override the template's properties and be referenceable as its own document.
@@ -433,6 +437,7 @@ export namespace Doc {
         layoutDelegate.layout = layout;
 
         fieldTemplate.title = metaKey;
+        fieldTemplate.templateField = metaKey;
         fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
         fieldTemplate.backgroundLayout = backgroundLayout;
         fieldTemplate.nativeWidth = nw;
-- 
cgit v1.2.3-70-g09d2


From cb95d4b43238538f59babcaa0b0d9dabb9b393b7 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 23 Jul 2019 22:07:13 -0400
Subject: Ink recognition and presentation view toggle

---
 src/client/cognitive_services/CognitiveServices.ts | 86 +++++++++++++++++++++-
 src/client/views/InkingCanvas.tsx                  |  4 +-
 src/client/views/MainView.tsx                      |  2 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 11 +++
 .../views/presentationview/PresentationView.tsx    | 12 ++-
 src/new_fields/FieldSymbols.ts                     |  2 +-
 src/new_fields/InkField.ts                         |  6 +-
 src/server/index.ts                                | 19 ++---
 8 files changed, 124 insertions(+), 18 deletions(-)

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

diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index d4085cf76..71e4276dc 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -8,10 +8,12 @@ import { RouteStore } from "../../server/RouteStore";
 import { Utils } from "../../Utils";
 import { CompileScript } from "../util/Scripting";
 import { ComputedField } from "../../new_fields/ScriptField";
+import { InkData } from "../../new_fields/InkField";
 
 export enum Services {
     ComputerVision = "vision",
-    Face = "face"
+    Face = "face",
+    Handwriting = "handwriting"
 }
 
 export enum Confidence {
@@ -132,4 +134,86 @@ export namespace CognitiveServices {
 
     }
 
+    export namespace Inking {
+
+        export interface AzureStrokeData {
+            id: number;
+            points: string;
+            language?: string;
+        }
+
+        export interface HandwritingUnit {
+            version: number;
+            language: string;
+            unit: string;
+            strokes: AzureStrokeData[];
+        }
+
+        export const analyze = async (inkData: InkData, target: Doc) => {
+            return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${Services.Handwriting}`)).then(async response => {
+                let apiKey = await response.text();
+                if (!apiKey) {
+                    return undefined;
+                }
+
+                let xhttp = new XMLHttpRequest();
+                let serverAddress = "https://api.cognitive.microsoft.com";
+                let endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
+
+                let results = await new Promise<any>((resolve, reject) => {
+                    let result: any;
+                    xhttp.onreadystatechange = function () {
+                        if (this.readyState === 4) {
+                            try {
+                                result = JSON.parse(xhttp.responseText);
+                            } catch (e) {
+                                return reject(e);
+                            }
+                            switch (this.status) {
+                                case 200:
+                                    return resolve(result);
+                                case 400:
+                                default:
+                                    return reject(result);
+                            }
+                        }
+                    };
+
+                    xhttp.open("PUT", endpoint, true);
+                    xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
+                    xhttp.setRequestHeader('Content-Type', 'application/json');
+                    xhttp.send(JSON.stringify(toHandwritingUnit(inkData)));
+                });
+
+                let recognizedText = results.recognitionUnits.map((unit: any) => unit.recognizedText);
+                let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
+                target.inkAnalysis = Docs.Get.DocumentHierarchyFromJson(results.recognitionUnits, "Ink Analysis");
+                target.handwriting = individualWords.join(" ");
+            });
+        };
+
+        const toHandwritingUnit = (inkData: InkData): HandwritingUnit => {
+            let entries = inkData.entries(), next = entries.next();
+            let strokes: AzureStrokeData[] = [];
+            let id = 0;
+            while (!next.done) {
+                let entry = next.value;
+                let data = {
+                    id: id++,
+                    points: entry[1].pathData.map(point => `${point.x},${point.y}`).join(","),
+                    language: "en-US"
+                };
+                strokes.push(data);
+                next = entries.next();
+            }
+            return {
+                version: 1,
+                language: "en-US",
+                unit: "mm",
+                strokes: strokes
+            };
+        };
+
+    }
+
 }
\ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 3e0d7b476..e48b45b56 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -10,6 +10,8 @@ import { undoBatch, UndoManager } from "../util/UndoManager";
 import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
+import { CognitiveServices } from "../cognitive_services/CognitiveServices";
+import { ContextMenu } from "./ContextMenu";
 
 interface InkCanvasProps {
     getScreenTransform: () => Transform;
@@ -178,7 +180,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
     render() {
         let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None ? "canSelect" : "noSelect";
         return (
-            <div className="inkingCanvas" >
+            <div className="inkingCanvas">
                 <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} />
                 {this.props.children()}
                 {this.drawnPaths}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 94a4835a1..53be0278e 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -394,6 +394,7 @@ export class MainView extends React.Component {
             <div id="add-options-content">
                 <ul id="add-options-list">
                     <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li>
+                    <li key="presentation"><button className="add-button round-button" title="Open Presentation View" onClick={() => PresentationView.Instance.toggle(undefined)}><FontAwesomeIcon icon="table" size="sm" /></button></li>
                     <li key="undo"><button className="add-button round-button" title="Undo" style={{ opacity: UndoManager.CanUndo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li>
                     <li key="redo"><button className="add-button round-button" title="Redo" style={{ opacity: UndoManager.CanRedo() ? 1 : 0.5, transition: "0.4s ease all" }} onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li>
                     {btns.map(btn =>
@@ -444,7 +445,6 @@ export class MainView extends React.Component {
         this.isSearchVisible = !this.isSearchVisible;
     }
 
-
     render() {
         return (
             <div id="main-div">
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 703873681..3fd86e950 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -31,6 +31,7 @@ import { ScriptField } from "../../../../new_fields/ScriptField";
 import { OverlayView, OverlayElementOptions } from "../../OverlayView";
 import { ScriptBox } from "../../ScriptBox";
 import { CompileScript } from "../../../util/Scripting";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 
 
 export const panZoomSchema = createSchema({
@@ -51,6 +52,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     private _lastY: number = 0;
     private get _pwidth() { return this.props.PanelWidth(); }
     private get _pheight() { return this.props.PanelHeight(); }
+    private inkKey = "ink";
 
     @computed get contentBounds() {
         let bounds = this.props.fitToBox && !NumCast(this.nativeWidth) ? Doc.ComputeContentBounds(DocListCast(this.props.Document.data)) : undefined;
@@ -477,6 +479,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }, "arrange contents");
             }
         });
+        ContextMenu.Instance.addItem({
+            description: "Analyze Strokes", event: async () => {
+                let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField);
+                if (!data) {
+                    return;
+                }
+                CognitiveServices.Inking.analyze(data.inkData, Doc.GetProto(this.props.Document));
+            }
+        });
         ContextMenu.Instance.addItem({
             description: "Add freeform arrangement",
             event: () => {
diff --git a/src/client/views/presentationview/PresentationView.tsx b/src/client/views/presentationview/PresentationView.tsx
index f80840f96..966ade3de 100644
--- a/src/client/views/presentationview/PresentationView.tsx
+++ b/src/client/views/presentationview/PresentationView.tsx
@@ -31,6 +31,8 @@ export interface PresViewProps {
     Documents: List<Doc>;
 }
 
+const expandedWidth = 400;
+
 @observer
 export class PresentationView extends React.Component<PresViewProps>  {
     public static Instance: PresentationView;
@@ -67,6 +69,14 @@ export class PresentationView extends React.Component<PresViewProps>  {
         PresentationView.Instance = this;
     }
 
+    toggle = (forcedValue: boolean | undefined) => {
+        if (forcedValue !== undefined) {
+            this.curPresentation.width = forcedValue ? expandedWidth : 0;
+        } else {
+            this.curPresentation.width = this.curPresentation.width === expandedWidth ? 0 : expandedWidth;
+        }
+    }
+
     //The first lifecycle function that gets called to set up the current presentation.
     async componentWillMount() {
         this.props.Documents.forEach(async (doc, index: number) => {
@@ -543,7 +553,7 @@ export class PresentationView extends React.Component<PresViewProps>  {
             this.curPresentation.data = new List([doc]);
         }
 
-        this.curPresentation.width = 400;
+        this.toggle(true);
     }
 
     //Function that sets the store of the children docs.
diff --git a/src/new_fields/FieldSymbols.ts b/src/new_fields/FieldSymbols.ts
index a436dcf2b..b5b3aa588 100644
--- a/src/new_fields/FieldSymbols.ts
+++ b/src/new_fields/FieldSymbols.ts
@@ -7,4 +7,4 @@ export const Id = Symbol("Id");
 export const OnUpdate = Symbol("OnUpdate");
 export const Parent = Symbol("Parent");
 export const Copy = Symbol("Copy");
-export const ToScriptString = Symbol("Copy");
\ No newline at end of file
+export const ToScriptString = Symbol("ToScriptString");
\ No newline at end of file
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index 39c6c8ce3..8f64c1c2e 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -19,6 +19,8 @@ export interface StrokeData {
     page: number;
 }
 
+export type InkData = Map<string, StrokeData>;
+
 const pointSchema = createSimpleSchema({
     x: true, y: true
 });
@@ -31,9 +33,9 @@ const strokeDataSchema = createSimpleSchema({
 @Deserializable("ink")
 export class InkField extends ObjectField {
     @serializable(map(object(strokeDataSchema)))
-    readonly inkData: Map<string, StrokeData>;
+    readonly inkData: InkData;
 
-    constructor(data?: Map<string, StrokeData>) {
+    constructor(data?: InkData) {
         super();
         this.inkData = data || new Map;
     }
diff --git a/src/server/index.ts b/src/server/index.ts
index 5b086a2cf..66c982adc 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -284,18 +284,15 @@ addSecureRoute(
     RouteStore.getCurrUser
 );
 
+const ServicesApiKeyMap = new Map<string, string | undefined>([
+    ["face", process.env.FACE],
+    ["vision", process.env.VISION],
+    ["handwriting", process.env.HANDWRITING]
+]);
+
 addSecureRoute(Method.GET, (user, res, req) => {
-    let requested = req.params.requestedservice;
-    switch (requested) {
-        case "face":
-            res.send(process.env.FACE);
-            break;
-        case "vision":
-            res.send(process.env.VISION);
-            break;
-        default:
-            res.send(undefined);
-    }
+    let service = req.params.requestedservice;
+    res.send(ServicesApiKeyMap.get(service));
 }, undefined, `${RouteStore.cognitiveServices}/:requestedservice`);
 
 class NodeCanvasFactory {
-- 
cgit v1.2.3-70-g09d2


From 52dc823c6a4c4b3e8deb994358a10acca4b6bd6c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 24 Jul 2019 12:31:07 -0400
Subject: Added transitions

---
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx    | 3 ---
 src/client/views/nodes/CollectionFreeFormDocumentView.tsx              | 3 ++-
 src/client/views/nodes/DocumentView.tsx                                | 1 -
 3 files changed, 2 insertions(+), 5 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 70c43bbec..7a9b94bfd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -503,11 +503,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                         onError(script.errors.map(error => error.messageText).join("\n"));
                         return;
                     }
-                    const docs = DocListCast(doc[dataKey]);
-                    docs.map(d => d.transition = "transform 1s");
                     doc[key] = new ScriptField(script);
                     overlayDisposer();
-                    setTimeout(() => docs.map(d => d.transition = undefined), 1200);
                 }} />;
                 overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
             };
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index b09538d1a..389b9f68b 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -82,6 +82,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     render() {
+        const hasPosition = this.props.x !== undefined || this.props.y !== undefined;
         return (
             <div className="collectionFreeFormDocumentView-container"
                 style={{
@@ -90,7 +91,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
                     backgroundColor: "transparent",
                     borderRadius: this.borderRounding(),
                     transform: this.transform,
-                    transition: StrCast(this.props.Document.transition),
+                    transition: hasPosition ? "transform 1s" : StrCast(this.props.Document.transition),
                     width: this.width,
                     height: this.height,
                     zIndex: this.Document.zIndex || 0,
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0a3633da2..138d065c6 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -565,7 +565,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
             cm.addItem({ description: "Toggle detail", event: () => Doc.ToggleDetailLayout(this.props.Document), icon: "image" });
         }
         cm.addItem({ description: "Add Repl", icon: "laptop-code", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
-        cm.addItem({ description: "Add Repl", event: () => OverlayView.Instance.addWindow(<ScriptingRepl />, { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" }) });
         cm.addItem({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" });
         cm.addItem({ description: "Zoom to Document", event: () => this.props.focus(this.props.Document, true), icon: "search" });
         if (!ClientUtils.RELEASE) {
-- 
cgit v1.2.3-70-g09d2


From 4c9c8375dc5f36f29cc38ba79b379b7a75e4a1a5 Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 24 Jul 2019 17:24:28 -0400
Subject: Added custom views to layout scripts

---
 .vscode/launch.json                                |  8 ++++++
 src/client/views/ScriptingRepl.tsx                 | 12 +++-----
 .../collectionFreeForm/CollectionFreeFormView.scss |  5 ++++
 .../collectionFreeForm/CollectionFreeFormView.tsx  | 33 ++++++++++++++++++++--
 4 files changed, 48 insertions(+), 10 deletions(-)

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

diff --git a/.vscode/launch.json b/.vscode/launch.json
index e4196600e..fd67a7647 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -7,6 +7,10 @@
         {
             "type": "chrome",
             "request": "launch",
+            "runtimeArgs": [
+                "--enable-logging",
+                "--v=1"
+            ],
             "name": "Launch Chrome against localhost",
             "sourceMaps": true,
             "breakOnLoad": true,
@@ -25,6 +29,10 @@
         {
             "type": "chrome",
             "request": "launch",
+            "runtimeArgs": [
+                "--enable-logging",
+                "--v=1"
+            ],
             "name": "Launch Chrome against Dash server",
             "sourceMaps": true,
             "breakOnLoad": true,
diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx
index 52273c357..9e538cf1b 100644
--- a/src/client/views/ScriptingRepl.tsx
+++ b/src/client/views/ScriptingRepl.tsx
@@ -16,18 +16,14 @@ library.add(faCaretRight);
 @observer
 export class DocumentIcon extends React.Component<{ view: DocumentView, index: number }> {
     render() {
-        this.props.view.props.ScreenToLocalTransform();
-        const doc = this.props.view.props.Document;
-        doc.width;
-        doc.height;
-        doc.x;
-        doc.y;
-        const screenCoords = this.props.view.screenRect();
+        const view = this.props.view;
+        const transform = view.props.ScreenToLocalTransform().scale(view.props.ContentScaling()).inverse();
+        const { x, y, width, height } = transform.transformBounds(0, 0, view.props.PanelWidth(), view.props.PanelHeight());
 
         return (
             <div className="documentIcon-outerDiv" style={{
                 position: "absolute",
-                transform: `translate(${screenCoords.left + screenCoords.width / 2}px, ${screenCoords.top}px)`,
+                transform: `translate(${x + width / 2}px, ${y}px)`,
             }}>
                 <p>${this.props.index}</p>
             </div>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 00407d39a..cca199afa 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -19,6 +19,11 @@
     transform-origin: left top;
 }
 
+.collectionFreeform-customText {
+    position: absolute;
+    text-align: center;
+}
+
 .collectionfreeformview-container {
     .collectionfreeformview>.jsx-parser {
         position: inherit;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 840f253ff..bd5c6f84b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -420,6 +420,25 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         return result.result === undefined ? {} : result.result;
     }
 
+    private viewDefToJSX(viewDef: any): JSX.Element | undefined {
+        if (viewDef.type === "text") {
+            const text = Cast(viewDef.text, "string");
+            const x = Cast(viewDef.x, "number");
+            const y = Cast(viewDef.y, "number");
+            const width = Cast(viewDef.width, "number");
+            const height = Cast(viewDef.height, "number");
+            const fontSize = Cast(viewDef.fontSize, "number");
+            if ([text, x, y].some(val => val === undefined)) {
+                return undefined;
+            }
+
+            return <div className="collectionFreeform-customText" style={{
+                transform: `translate(${x}px, ${y}px)`,
+                width, height, fontSize
+            }}>{text}</div>;
+        }
+    }
+
     @computed.struct
     get views() {
         let curPage = FieldValue(this.Document.curPage, -1);
@@ -427,10 +446,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
         const script = this.Document.arrangeScript;
         let state: any = undefined;
         const docs = this.childDocs;
+        let elements: JSX.Element[] = [];
         if (initScript) {
             const initResult = initScript.script.run({ docs, collection: this.Document });
             if (initResult.success) {
-                state = initResult.result;
+                const result = initResult.result;
+                const { state: scriptState, views } = result;
+                state = scriptState;
+                if (Array.isArray(views)) {
+                    elements = views.reduce<JSX.Element[]>((prev, ele) => {
+                        const jsx = this.viewDefToJSX(ele);
+                        jsx && prev.push(jsx);
+                        return prev;
+                    }, elements);
+                }
             }
         }
         let docviews = docs.reduce((prev, doc) => {
@@ -445,7 +474,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 }
             }
             return prev;
-        }, [] as JSX.Element[]);
+        }, elements);
 
         setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
 
-- 
cgit v1.2.3-70-g09d2


From d6cef0815815c2587b9cc791e6a37c742aba1b45 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 03:31:34 -0400
Subject: cognitive services refactor, buxton python script fixes, covered up
 imagebox context menu bug

---
 src/client/cognitive_services/CognitiveServices.ts | 151 +++++++++++----------
 src/client/views/InkingCanvas.tsx                  |   4 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   2 +-
 src/client/views/nodes/ImageBox.tsx                |   6 +-
 src/scraping/buxton/scraper.py                     |  17 +--
 5 files changed, 93 insertions(+), 87 deletions(-)

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

diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index dcd27f858..d689b424d 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,5 +1,5 @@
 import * as request from "request-promise";
-import { Doc, Field } from "../../new_fields/Doc";
+import { Doc, Field, Opt } from "../../new_fields/Doc";
 import { Cast } from "../../new_fields/Types";
 import { ImageField } from "../../new_fields/URLField";
 import { List } from "../../new_fields/List";
@@ -10,7 +10,15 @@ import { CompileScript } from "../util/Scripting";
 import { ComputedField } from "../../new_fields/ScriptField";
 import { InkData } from "../../new_fields/InkField";
 
-export enum Services {
+type APIManager<D> = { requester: RequestExecutor<D>, applier: AnalysisApplier };
+type RequestExecutor<D> = (apiKey: string, data: D, service: Service) => Promise<string>;
+type AnalysisApplier = (target: Doc, ...args: any) => any;
+type Converter = (results: any) => Field;
+
+export type Tag = { name: string, confidence: number };
+export type Rectangle = { top: number, left: number, width: number, height: number };
+
+export enum Service {
     ComputerVision = "vision",
     Face = "face",
     Handwriting = "handwriting"
@@ -25,11 +33,6 @@ export enum Confidence {
     Excellent = 0.95
 }
 
-export type Tag = { name: string, confidence: number };
-export type Rectangle = { top: number, left: number, width: number, height: number };
-export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle };
-export type Converter = (results: any) => Field;
-
 /**
  * A file that handles all interactions with Microsoft Azure's Cognitive
  * Services APIs. These machine learning endpoints allow basic data analytics for
@@ -37,19 +40,33 @@ export type Converter = (results: any) => Field;
  */
 export namespace CognitiveServices {
 
+    const executeQuery = async <D, R>(service: Service, executor: RequestExecutor<D>, data: D): Promise<Opt<R>> => {
+        return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => {
+            let apiKey = await response.text();
+            if (!apiKey) {
+                return undefined;
+            }
+
+            let results: Opt<R>;
+            try {
+                results = await executor(apiKey, data, service).then(json => JSON.parse(json));
+            } catch {
+                results = undefined;
+            }
+            return results;
+        });
+    };
+
     export namespace Image {
 
-        export const analyze = async (imageUrl: string, service: Services) => {
-            return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => {
-                let apiKey = await response.text();
-                if (!apiKey) {
-                    return undefined;
-                }
+        export const Manager: APIManager<string> = {
+
+            requester: (async (apiKey: string, imageUrl: string, service: Service) => {
                 let uriBase;
                 let parameters;
 
                 switch (service) {
-                    case Services.Face:
+                    case Service.Face:
                         uriBase = 'face/v1.0/detect';
                         parameters = {
                             'returnFaceId': 'true',
@@ -58,7 +75,7 @@ export namespace CognitiveServices {
                                 'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise'
                         };
                         break;
-                    case Services.ComputerVision:
+                    case Service.ComputerVision:
                         uriBase = 'vision/v2.0/analyze';
                         parameters = {
                             'visualFeatures': 'Categories,Description,Color,Objects,Tags,Adult',
@@ -78,35 +95,32 @@ export namespace CognitiveServices {
                     }
                 };
 
-                let results: any;
-                try {
-                    results = await request.post(options).then(response => JSON.parse(response));
-                } catch (e) {
-                    results = undefined;
-                }
-                return results;
-            });
-        };
+                return request.post(options);
+            }) as RequestExecutor<string>,
 
-        const analyzeDocument = async (target: Doc, service: Services, converter: Converter, storageKey: string) => {
-            let imageData = Cast(target.data, ImageField);
-            if (!imageData || await Cast(target[storageKey], Doc)) {
-                return;
-            }
-            let toStore: any;
-            let results = await analyze(imageData.url.href, service);
-            if (!results) {
-                toStore = "Cognitive Services could not process the given image URL.";
-            } else {
-                if (!results.length) {
-                    toStore = converter(results);
+            applier: (async (target: Doc, service: Service, converter: Converter, storageKey: string) => {
+                let imageData = Cast(target.data, ImageField);
+                if (!imageData || await Cast(target[storageKey], Doc)) {
+                    return;
+                }
+                let toStore: any;
+                let results = await executeQuery<string, any>(service, Manager.requester, imageData.url.href);
+                if (!results) {
+                    toStore = "Cognitive Services could not process the given image URL.";
                 } else {
-                    toStore = results.length > 0 ? converter(results) : "Empty list returned.";
+                    if (!results.length) {
+                        toStore = converter(results);
+                    } else {
+                        toStore = results.length > 0 ? converter(results) : "Empty list returned.";
+                    }
                 }
-            }
-            target[storageKey] = toStore;
+                target[storageKey] = toStore;
+            }) as AnalysisApplier
+
         };
 
+        export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle };
+
         export const generateMetadata = async (target: Doc, threshold: Confidence = Confidence.Excellent) => {
             let converter = (results: any) => {
                 let tagDoc = new Doc;
@@ -120,7 +134,7 @@ export namespace CognitiveServices {
                 tagDoc.confidence = threshold;
                 return tagDoc;
             };
-            analyzeDocument(target, Services.ComputerVision, converter, "generatedTags");
+            Manager.applier(target, Service.ComputerVision, converter, "generatedTags");
         };
 
         export const extractFaces = async (target: Doc) => {
@@ -129,48 +143,24 @@ export namespace CognitiveServices {
                 results.map((face: Face) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!));
                 return faceDocs;
             };
-            analyzeDocument(target, Services.Face, converter, "faces");
+            Manager.applier(target, Service.Face, converter, "faces");
         };
 
     }
 
     export namespace Inking {
 
-        export interface AzureStrokeData {
-            id: number;
-            points: string;
-            language?: string;
-        }
-
-        export interface HandwritingUnit {
-            version: number;
-            language: string;
-            unit: string;
-            strokes: AzureStrokeData[];
-        }
-
-        export const analyze = async (inkData: InkData, target: Doc) => {
-            return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${Services.Handwriting}`)).then(async response => {
-                let apiKey = await response.text();
-                if (!apiKey) {
-                    return undefined;
-                }
+        export const Manager: APIManager<InkData> = {
 
+            requester: (async (apiKey: string, inkData: InkData) => {
                 let xhttp = new XMLHttpRequest();
                 let serverAddress = "https://api.cognitive.microsoft.com";
                 let endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
 
-                let requestExecutor = (resolve: any, reject: any) => {
-                    let result: any;
-                    let body = format(inkData);
-
+                let promisified = (resolve: any, reject: any) => {
                     xhttp.onreadystatechange = function () {
                         if (this.readyState === 4) {
-                            try {
-                                result = JSON.parse(xhttp.responseText);
-                            } catch (e) {
-                                return reject(e);
-                            }
+                            let result = xhttp.responseText;
                             switch (this.status) {
                                 case 200:
                                     return resolve(result);
@@ -184,11 +174,14 @@ export namespace CognitiveServices {
                     xhttp.open("PUT", endpoint, true);
                     xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
                     xhttp.setRequestHeader('Content-Type', 'application/json');
-                    xhttp.send(body);
+                    xhttp.send(format(inkData));
                 };
 
-                let results = await new Promise<any>(requestExecutor);
+                return new Promise<any>(promisified);
+            }) as RequestExecutor<InkData>,
 
+            applier: (async (target: Doc, inkData: InkData) => {
+                let results = await executeQuery<InkData, any>(Service.Handwriting, Manager.requester, inkData);
                 if (results) {
                     results.recognitionUnits && (results = results.recognitionUnits);
                     target.inkAnalysis = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
@@ -196,9 +189,23 @@ export namespace CognitiveServices {
                     let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
                     target.handwriting = individualWords.join(" ");
                 }
-            });
+            }) as AnalysisApplier
+
         };
 
+        export interface AzureStrokeData {
+            id: number;
+            points: string;
+            language?: string;
+        }
+
+        export interface HandwritingUnit {
+            version: number;
+            language: string;
+            unit: string;
+            strokes: AzureStrokeData[];
+        }
+
         const format = (inkData: InkData): string => {
             let entries = inkData.entries(), next = entries.next();
             let strokes: AzureStrokeData[] = [], id = 0;
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index e48b45b56..c4cd863d1 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -6,12 +6,10 @@ import "./InkingCanvas.scss";
 import { InkingControl } from "./InkingControl";
 import { InkingStroke } from "./InkingStroke";
 import React = require("react");
-import { undoBatch, UndoManager } from "../util/UndoManager";
+import { UndoManager } from "../util/UndoManager";
 import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
 import { Doc } from "../../new_fields/Doc";
 import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
-import { CognitiveServices } from "../cognitive_services/CognitiveServices";
-import { ContextMenu } from "./ContextMenu";
 
 interface InkCanvasProps {
     getScreenTransform: () => Transform;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index bd5c6f84b..4b7ddd49d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -519,7 +519,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 if (!data) {
                     return;
                 }
-                CognitiveServices.Inking.analyze(data.inkData, Doc.GetProto(this.props.Document));
+                CognitiveServices.Inking.Manager.applier(Doc.GetProto(this.props.Document), data.inkData);
             }
         });
     }
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 89ad18dd6..3d77696fe 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -216,9 +216,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             });
 
             let modes: ContextMenuProps[] = [];
-            let dataDoc = Doc.GetProto(this.Document);
-            modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(dataDoc), icon: "tag" });
-            modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(dataDoc), icon: "camera" });
+            // let dataDoc = Doc.GetProto(this.Document);
+            modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(this.Document), icon: "tag" });
+            modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(this.Document), icon: "camera" });
 
             ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs });
             ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes });
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 700269727..14490cfe4 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -15,6 +15,7 @@ source = "./source"
 dist = "../../server/public/files"
 
 db = MongoClient("localhost", 27017)["Dash"]
+target_collection = db.newDocuments
 schema_guids = []
 
 
@@ -84,7 +85,7 @@ def write_schema(parse_results, display_fields, storage_key):
             "height": 600,
             "panX": 0,
             "panY": 0,
-            "zoomBasis": 0.5,
+            "zoomBasis": 1,
             "zIndex": 2,
             "libraryBrush": False,
             "viewType": 2
@@ -106,8 +107,8 @@ def write_schema(parse_results, display_fields, storage_key):
     fields["isPrototype"] = True
     fields["page"] = -1
 
-    db.newDocuments.insert_one(data_doc)
-    db.newDocuments.insert_one(view_doc)
+    target_collection.insert_one(data_doc)
+    target_collection.insert_one(view_doc)
 
     data_doc_guid = data_doc["_id"]
     print(f"inserted view document ({view_doc_guid})")
@@ -158,8 +159,8 @@ def write_text_doc(content):
         "__type": "Doc"
     }
 
-    db.newDocuments.insert_one(view_doc)
-    db.newDocuments.insert_one(data_doc)
+    target_collection.insert_one(view_doc)
+    target_collection.insert_one(data_doc)
 
     return view_doc_guid
 
@@ -209,8 +210,8 @@ def write_image(folder, name):
         "__type": "Doc"
     }
 
-    db.newDocuments.insert_one(view_doc)
-    db.newDocuments.insert_one(data_doc)
+    target_collection.insert_one(view_doc)
+    target_collection.insert_one(data_doc)
 
     return view_doc_guid
 
@@ -372,7 +373,7 @@ parent_guid = write_schema({
 }, ["title", "short_description", "original_price"], "data")
 
 print("appending parent schema to main workspace...\n")
-db.newDocuments.update_one(
+target_collection.update_one(
     {"fields.title": "WS collection 1"},
     {"$push": {"fields.data.fields": {"fieldId": parent_guid, "__type": "proxy"}}}
 )
-- 
cgit v1.2.3-70-g09d2


From 1846c217adb814a2b0297b8ab808c2f587741936 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 09:26:33 -0400
Subject: final cognitive services refactor

---
 src/client/cognitive_services/CognitiveServices.ts | 84 +++++++++++-----------
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  4 +-
 2 files changed, 47 insertions(+), 41 deletions(-)

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

diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index d689b424d..0cda17325 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -10,9 +10,10 @@ import { CompileScript } from "../util/Scripting";
 import { ComputedField } from "../../new_fields/ScriptField";
 import { InkData } from "../../new_fields/InkField";
 
-type APIManager<D> = { requester: RequestExecutor<D>, applier: AnalysisApplier };
-type RequestExecutor<D> = (apiKey: string, data: D, service: Service) => Promise<string>;
-type AnalysisApplier = (target: Doc, ...args: any) => any;
+type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor, analyzer: AnalysisApplier };
+type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
+type AnalysisApplier = (target: Doc, relevantKeys: string[], ...args: any) => any;
+type BodyConverter<D> = (data: D) => string;
 type Converter = (results: any) => Field;
 
 export type Tag = { name: string, confidence: number };
@@ -40,7 +41,7 @@ export enum Confidence {
  */
 export namespace CognitiveServices {
 
-    const executeQuery = async <D, R>(service: Service, executor: RequestExecutor<D>, data: D): Promise<Opt<R>> => {
+    const executeQuery = async <D, R>(service: Service, manager: APIManager<D>, data: D): Promise<Opt<R>> => {
         return fetch(Utils.prepend(`${RouteStore.cognitiveServices}/${service}`)).then(async response => {
             let apiKey = await response.text();
             if (!apiKey) {
@@ -49,7 +50,7 @@ export namespace CognitiveServices {
 
             let results: Opt<R>;
             try {
-                results = await executor(apiKey, data, service).then(json => JSON.parse(json));
+                results = await manager.requester(apiKey, manager.converter(data), service).then(json => JSON.parse(json));
             } catch {
                 results = undefined;
             }
@@ -61,7 +62,9 @@ export namespace CognitiveServices {
 
         export const Manager: APIManager<string> = {
 
-            requester: (async (apiKey: string, imageUrl: string, service: Service) => {
+            converter: (imageUrl: string) => JSON.stringify({ url: imageUrl }),
+
+            requester: async (apiKey: string, body: string, service: Service) => {
                 let uriBase;
                 let parameters;
 
@@ -88,7 +91,7 @@ export namespace CognitiveServices {
                 const options = {
                     uri: 'https://eastus.api.cognitive.microsoft.com/' + uriBase,
                     qs: parameters,
-                    body: `{"url": "${imageUrl}"}`,
+                    body: body,
                     headers: {
                         'Content-Type': 'application/json',
                         'Ocp-Apim-Subscription-Key': apiKey
@@ -96,15 +99,16 @@ export namespace CognitiveServices {
                 };
 
                 return request.post(options);
-            }) as RequestExecutor<string>,
+            },
 
-            applier: (async (target: Doc, service: Service, converter: Converter, storageKey: string) => {
+            analyzer: async (target: Doc, keys: string[], service: Service, converter: Converter) => {
                 let imageData = Cast(target.data, ImageField);
+                let storageKey = keys[0];
                 if (!imageData || await Cast(target[storageKey], Doc)) {
                     return;
                 }
                 let toStore: any;
-                let results = await executeQuery<string, any>(service, Manager.requester, imageData.url.href);
+                let results = await executeQuery<string, any>(service, Manager, imageData.url.href);
                 if (!results) {
                     toStore = "Cognitive Services could not process the given image URL.";
                 } else {
@@ -115,7 +119,7 @@ export namespace CognitiveServices {
                     }
                 }
                 target[storageKey] = toStore;
-            }) as AnalysisApplier
+            }
 
         };
 
@@ -134,7 +138,7 @@ export namespace CognitiveServices {
                 tagDoc.confidence = threshold;
                 return tagDoc;
             };
-            Manager.applier(target, Service.ComputerVision, converter, "generatedTags");
+            Manager.analyzer(target, ["generatedTags"], Service.ComputerVision, converter);
         };
 
         export const extractFaces = async (target: Doc) => {
@@ -143,7 +147,7 @@ export namespace CognitiveServices {
                 results.map((face: Face) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`)!));
                 return faceDocs;
             };
-            Manager.applier(target, Service.Face, converter, "faces");
+            Manager.analyzer(target, ["faces"], Service.Face, converter);
         };
 
     }
@@ -152,7 +156,26 @@ export namespace CognitiveServices {
 
         export const Manager: APIManager<InkData> = {
 
-            requester: (async (apiKey: string, inkData: InkData) => {
+            converter: (inkData: InkData): string => {
+                let entries = inkData.entries(), next = entries.next();
+                let strokes: AzureStrokeData[] = [], id = 0;
+                while (!next.done) {
+                    strokes.push({
+                        id: id++,
+                        points: next.value[1].pathData.map(point => `${point.x},${point.y}`).join(","),
+                        language: "en-US"
+                    });
+                    next = entries.next();
+                }
+                return JSON.stringify({
+                    version: 1,
+                    language: "en-US",
+                    unit: "mm",
+                    strokes: strokes
+                });
+            },
+
+            requester: async (apiKey: string, body: string) => {
                 let xhttp = new XMLHttpRequest();
                 let serverAddress = "https://api.cognitive.microsoft.com";
                 let endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
@@ -174,22 +197,22 @@ export namespace CognitiveServices {
                     xhttp.open("PUT", endpoint, true);
                     xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
                     xhttp.setRequestHeader('Content-Type', 'application/json');
-                    xhttp.send(format(inkData));
+                    xhttp.send(body);
                 };
 
                 return new Promise<any>(promisified);
-            }) as RequestExecutor<InkData>,
+            },
 
-            applier: (async (target: Doc, inkData: InkData) => {
-                let results = await executeQuery<InkData, any>(Service.Handwriting, Manager.requester, inkData);
+            analyzer: async (target: Doc, keys: string[], inkData: InkData) => {
+                let results = await executeQuery<InkData, any>(Service.Handwriting, Manager, inkData);
                 if (results) {
                     results.recognitionUnits && (results = results.recognitionUnits);
-                    target.inkAnalysis = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
+                    target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis");
                     let recognizedText = results.map((item: any) => item.recognizedText);
                     let individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
-                    target.handwriting = individualWords.join(" ");
+                    target[keys[1]] = individualWords.join(" ");
                 }
-            }) as AnalysisApplier
+            }
 
         };
 
@@ -206,25 +229,6 @@ export namespace CognitiveServices {
             strokes: AzureStrokeData[];
         }
 
-        const format = (inkData: InkData): string => {
-            let entries = inkData.entries(), next = entries.next();
-            let strokes: AzureStrokeData[] = [], id = 0;
-            while (!next.done) {
-                strokes.push({
-                    id: id++,
-                    points: next.value[1].pathData.map(point => `${point.x},${point.y}`).join(","),
-                    language: "en-US"
-                });
-                next = entries.next();
-            }
-            return JSON.stringify({
-                version: 1,
-                language: "en-US",
-                unit: "mm",
-                strokes: strokes
-            });
-        };
-
     }
 
 }
\ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4b7ddd49d..d39d6f255 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -519,7 +519,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 if (!data) {
                     return;
                 }
-                CognitiveServices.Inking.Manager.applier(Doc.GetProto(this.props.Document), data.inkData);
+                let target = Doc.GetProto(this.props.Document);
+                let relevantKeys = ["inkAnalysis", "handwriting"];
+                CognitiveServices.Inking.Manager.analyzer(target, relevantKeys, data.inkData);
             }
         });
     }
-- 
cgit v1.2.3-70-g09d2


From b4888c53bcc4eb9a37ed4b3dac740bfffc64d3c5 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 11:46:28 -0400
Subject: postponed dealing with circular import bug imagebox

---
 src/client/DocServer.ts                                             | 1 -
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 5 ++++-
 src/client/views/nodes/ImageBox.tsx                                 | 6 +++---
 3 files changed, 7 insertions(+), 5 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 077c8e5ba..df9828029 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -5,7 +5,6 @@ import { Utils, emptyFunction } from '../Utils';
 import { SerializationHelper } from './util/SerializationHelper';
 import { RefField } from '../new_fields/RefField';
 import { Id, HandleUpdate } from '../new_fields/FieldSymbols';
-import { CurrentUserUtils } from '../server/authentication/models/current_user_utils';
 
 /**
  * This class encapsulates the transfer and cross-client synchronization of
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index d39d6f255..701574cfc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -32,7 +32,10 @@ import { OverlayView, OverlayElementOptions } from "../../OverlayView";
 import { ScriptBox } from "../../ScriptBox";
 import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { library } from "@fortawesome/fontawesome-svg-core";
+import { faEye } from "@fortawesome/free-regular-svg-icons";
 
+library.add(faEye);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -522,7 +525,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 let target = Doc.GetProto(this.props.Document);
                 let relevantKeys = ["inkAnalysis", "handwriting"];
                 CognitiveServices.Inking.Manager.analyzer(target, relevantKeys, data.inkData);
-            }
+            }, icon: "eye"
         });
     }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3d77696fe..c0c64b6d5 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -216,9 +216,9 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             });
 
             let modes: ContextMenuProps[] = [];
-            // let dataDoc = Doc.GetProto(this.Document);
-            modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(this.Document), icon: "tag" });
-            modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(this.Document), icon: "camera" });
+            let dataDoc = Doc.GetProto(this.props.Document);
+            modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(dataDoc), icon: "tag" });
+            modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(dataDoc), icon: "camera" });
 
             ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs });
             ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes });
-- 
cgit v1.2.3-70-g09d2


From 6c111fdc538775f9f7b04f91d0b5701fd2a1aaca Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 12:59:41 -0400
Subject: icon and stop prevention of undo redo propagation

---
 src/client/views/GlobalKeyHandler.ts                                  | 4 +++-
 .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx   | 4 +++-
 2 files changed, 6 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts
index e8a588e58..7477c5b4f 100644
--- a/src/client/views/GlobalKeyHandler.ts
+++ b/src/client/views/GlobalKeyHandler.ts
@@ -1,4 +1,4 @@
-import { UndoManager, undoBatch } from "../util/UndoManager";
+import { UndoManager } from "../util/UndoManager";
 import { SelectionManager } from "../util/SelectionManager";
 import { CollectionDockingView } from "./collections/CollectionDockingView";
 import { MainView } from "./MainView";
@@ -144,9 +144,11 @@ export default class KeyManager {
                 break;
             case "y":
                 UndoManager.Redo();
+                stopPropagation = false;
                 break;
             case "z":
                 UndoManager.Undo();
+                stopPropagation = false;
                 break;
             case "a":
             case "c":
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 701574cfc..15734ce0d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,8 +34,9 @@ import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
+import { faTable } from "@fortawesome/free-solid-svg-icons";
 
-library.add(faEye);
+library.add(faEye, faTable);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -492,6 +493,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
     onContextMenu = () => {
         ContextMenu.Instance.addItem({
             description: "Arrange contents in grid",
+            icon: "table",
             event: async () => {
                 const docs = await DocListCastAsync(this.Document[this.props.fieldKey]);
                 UndoManager.RunInBatch(() => {
-- 
cgit v1.2.3-70-g09d2


From 8e567d94618fcac3345a1f495cdea71a543f7804 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Thu, 25 Jul 2019 15:09:24 -0400
Subject: added misc icons and updated metadata storange onto field extension
 doc

---
 src/client/views/InkingControl.tsx                            |  2 +-
 .../collections/collectionFreeForm/CollectionFreeFormView.tsx |  9 ++++-----
 src/client/views/nodes/ImageBox.tsx                           | 11 ++++++-----
 3 files changed, 11 insertions(+), 11 deletions(-)

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

diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 1910e409b..da388e532 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -20,7 +20,7 @@ export class InkingControl extends React.Component {
     static Instance: InkingControl = new InkingControl({});
     @observable private _selectedTool: InkTool = InkTool.None;
     @observable private _selectedColor: string = "rgb(244, 67, 54)";
-    @observable private _selectedWidth: string = "25";
+    @observable private _selectedWidth: string = "5";
     @observable public _open: boolean = false;
 
     constructor(props: Readonly<{}>) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 15734ce0d..6500b3273 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -34,9 +34,9 @@ import { CompileScript } from "../../../util/Scripting";
 import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
 import { library } from "@fortawesome/fontawesome-svg-core";
 import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faTable } from "@fortawesome/free-solid-svg-icons";
+import { faTable, faPaintBrush, faAsterisk } from "@fortawesome/free-solid-svg-icons";
 
-library.add(faEye, faTable);
+library.add(faEye, faTable, faPaintBrush);
 
 export const panZoomSchema = createSchema({
     panX: "number",
@@ -524,10 +524,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
                 if (!data) {
                     return;
                 }
-                let target = Doc.GetProto(this.props.Document);
                 let relevantKeys = ["inkAnalysis", "handwriting"];
-                CognitiveServices.Inking.Manager.analyzer(target, relevantKeys, data.inkData);
-            }, icon: "eye"
+                CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData);
+            }, icon: "paint-brush"
         });
     }
 
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c0c64b6d5..5e93251a3 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faImage, faFileAudio } from '@fortawesome/free-solid-svg-icons';
+import { faImage, faFileAudio, faPaintBrush, faAsterisk } from '@fortawesome/free-solid-svg-icons';
 import { action, observable, computed, runInAction } from 'mobx';
 import { observer } from "mobx-react";
 import Lightbox from 'react-image-lightbox';
@@ -27,13 +27,14 @@ import { Font } from '@react-pdf/renderer';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { CognitiveServices } from '../../cognitive_services/CognitiveServices';
 import FaceRectangles from './FaceRectangles';
+import { faEye } from '@fortawesome/free-regular-svg-icons';
 var requestImageSize = require('../../util/request-image-size');
 var path = require('path');
 const { Howl, Howler } = require('howler');
 
 
-library.add(faImage);
-library.add(faFileAudio);
+library.add(faImage, faEye, faPaintBrush);
+library.add(faFileAudio, faAsterisk);
 
 
 export const pageSchema = createSchema({
@@ -220,8 +221,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             modes.push({ description: "Generate Tags", event: () => CognitiveServices.Image.generateMetadata(dataDoc), icon: "tag" });
             modes.push({ description: "Find Faces", event: () => CognitiveServices.Image.extractFaces(dataDoc), icon: "camera" });
 
-            ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs });
-            ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes });
+            ContextMenu.Instance.addItem({ description: "Image Funcs...", subitems: funcs, icon: "asterisk" });
+            ContextMenu.Instance.addItem({ description: "Analyze...", subitems: modes, icon: "eye" });
         }
     }
 
-- 
cgit v1.2.3-70-g09d2

