From 04887c8a578147015421d3909bd100c82ac5e31d Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Mon, 8 Jul 2019 19:55:15 -0400
Subject: fixed readonly and hopefully cursor proto bugs

---
 src/client/views/nodes/KeyValueBox.tsx | 81 +++++++++++++++++++++++-----------
 1 file changed, 55 insertions(+), 26 deletions(-)

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

diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 9407d742c..fbabe224e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -18,6 +18,7 @@ import { List } from "../../../new_fields/List";
 import { TextField } from "../../util/ProsemirrorCopy/prompt";
 import { RichTextField } from "../../../new_fields/RichTextField";
 import { ImageField } from "../../../new_fields/URLField";
+import { SelectionManager } from "../../util/SelectionManager";
 
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
@@ -167,44 +168,72 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         return parent;
     }
 
-    createTemplateField = async (parent: Doc, row: KeyValuePair) => {
-        let collectionKeyProp = `fieldKey={"data"}`;
-        let metaKey = row.props.keyName;
-        let metaKeyProp = `fieldKey={"${metaKey}"}`;
+    createTemplateField = async (parentStackingDoc: Doc, row: KeyValuePair) => {
+        // let collectionKeyProp = `fieldKey={"data"}`;
+        // let metaKey = row.props.keyName;
+        // let metaKeyProp = `fieldKey={"${metaKey}"}`;
+
+        // let sourceDoc = await Cast(this.props.Document.data, Doc);
+        // if (!sourceDoc) {
+        //     return;
+        // }
+        // let target = this.inferType(sourceDoc[metaKey], metaKey);
 
+        // let template = Doc.MakeAlias(target);
+        // template.proto = parent;
+        // template.title = metaKey;
+        // template.nativeWidth = 0;
+        // template.nativeHeight = 0;
+        // template.embed = true;
+        // template.isTemplate = true;
+        // template.templates = new List<string>([Templates.TitleBar(metaKey)]);
+        // if (target.backgroundLayout) {
+        //     let metaAnoKeyProp = `fieldKey={"${metaKey}"} fieldExt={"annotations"}`;
+        //     let collectionAnoKeyProp = `fieldKey={"annotations"}`;
+        //     template.layout = StrCast(target.layout).replace(collectionAnoKeyProp, metaAnoKeyProp);
+        //     template.backgroundLayout = StrCast(target.backgroundLayout).replace(collectionKeyProp, metaKeyProp);
+        // } else {
+        //     template.layout = StrCast(target.layout).replace(collectionKeyProp, metaKeyProp);
+        // }
+
+        let metaKey = row.props.keyName;
         let sourceDoc = await Cast(this.props.Document.data, Doc);
         if (!sourceDoc) {
             return;
         }
-        let target = this.inferType(sourceDoc[metaKey], metaKey);
-
-        let template = Doc.MakeAlias(target);
-        template.proto = parent;
-        template.title = metaKey;
-        template.nativeWidth = 0;
-        template.nativeHeight = 0;
-        template.embed = true;
-        template.isTemplate = true;
-        template.templates = new List<string>([Templates.TitleBar(metaKey)]);
-        if (target.backgroundLayout) {
-            let metaAnoKeyProp = `fieldKey={"${metaKey}"} fieldExt={"annotations"}`;
-            let collectionAnoKeyProp = `fieldKey={"annotations"}`;
-            template.layout = StrCast(target.layout).replace(collectionAnoKeyProp, metaAnoKeyProp);
-            template.backgroundLayout = StrCast(target.backgroundLayout).replace(collectionKeyProp, metaKeyProp);
-        } else {
-            template.layout = StrCast(target.layout).replace(collectionKeyProp, metaKeyProp);
+        let fieldTemplate = this.inferType(sourceDoc[metaKey], metaKey);
+
+        // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
+        let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
+        let layout = StrCast(fieldTemplate.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
+        if (backgroundLayout) {
+            layout = StrCast(fieldTemplate.layout).replace(/fieldKey={"annotations"}/, `fieldKey={"${metaKey}"} fieldExt={"annotations"}`);
+            backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
         }
-        Doc.AddDocToList(parent, "data", template);
+        let nw = NumCast(fieldTemplate.nativeWidth);
+        let nh = NumCast(fieldTemplate.nativeHeight);
+
+        fieldTemplate.title = metaKey;
+        fieldTemplate.layout = layout;
+        fieldTemplate.backgroundLayout = backgroundLayout;
+        fieldTemplate.nativeWidth = nw;
+        fieldTemplate.nativeHeight = nh;
+        fieldTemplate.embed = true;
+        fieldTemplate.isTemplate = true;
+        fieldTemplate.templates = new List<string>([Templates.TitleBar(metaKey)]);
+        fieldTemplate.proto = Doc.GetProto(parentStackingDoc);
+
+        Doc.AddDocToList(parentStackingDoc, "data", fieldTemplate);
         row.uncheck();
     }
 
-    inferType = (field: FieldResult, metaKey: string) => {
+    inferType = (data: FieldResult, metaKey: string) => {
         let options = { width: 300, height: 300, title: metaKey };
-        if (field instanceof RichTextField || typeof field === "string" || typeof field === "number") {
+        if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") {
             return Docs.TextDocument(options);
-        } else if (field instanceof List) {
+        } else if (data instanceof List) {
             return Docs.StackingDocument([], options);
-        } else if (field instanceof ImageField) {
+        } else if (data instanceof ImageField) {
             return Docs.ImageDocument("https://www.freepik.com/free-icon/picture-frame-with-mountain-image_748687.htm", options);
         }
         return new Doc;
-- 
cgit v1.2.3-70-g09d2


From 7fdb33cafaa3e8593d648ddba994356a9625ff56 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 9 Jul 2019 13:52:35 -0400
Subject: string list to list of text doc map in scraper and cleaned up kv pane
 templating interaction code

---
 src/client/views/nodes/DocumentContentsView.tsx |  7 +--
 src/client/views/nodes/KeyValueBox.tsx          | 20 ++++++--
 src/scraping/buxton/scraper.py                  | 61 +++++++++++++++++++++++--
 3 files changed, 75 insertions(+), 13 deletions(-)

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

diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index b4c0e844f..eb786d537 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -69,7 +69,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
             // this document as the data document for the layout.
             return this.props.Document;
         }
-        return this.props.DataDoc
+        return this.props.DataDoc;
     }
     get layoutDoc() {
         // if this document's layout field contains a document (ie, a rendering template), then we will use that
@@ -124,13 +124,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
         if (this.props.renderDepth > 7) return (null);
         if (!this.layout && (this.props.layoutKey !== "overlayLayout" || !this.templates.length)) return (null);
         return <ObserverJsxParser
-<<<<<<< HEAD
             components={{ FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
-            bindings={this.CreateBindings(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document)}
-=======
-            components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
             bindings={this.CreateBindings()}
->>>>>>> b49fdb1c42b9758e006521e0f404634ba396a2ac
             jsx={this.finalLayout}
             showWarnings={true}
             onError={(test: any) => { console.log(test); }}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index e27ab1589..e8619584e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -161,7 +161,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
     getTemplate = async () => {
         let parent = Docs.StackingDocument([], { width: 800, height: 800, title: "Template" });
         parent.singleColumn = false;
-        parent.columnWidth = 50;
+        parent.columnWidth = 100;
         for (let row of this.rows.filter(row => row.isChecked)) {
             await this.createTemplateField(parent, row);
             row.uncheck();
@@ -175,7 +175,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         if (!sourceDoc) {
             return;
         }
-        let fieldTemplate = this.inferType(sourceDoc[metaKey], metaKey);
+        let fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey);
 
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
@@ -200,12 +200,24 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
     }
 
-    inferType = (data: FieldResult, metaKey: string) => {
+    inferType = async (data: FieldResult, metaKey: string) => {
         let options = { width: 300, height: 300, title: metaKey };
         if (data instanceof RichTextField || typeof data === "string" || typeof data === "number") {
             return Docs.TextDocument(options);
         } else if (data instanceof List) {
-            return Docs.StackingDocument([], options);
+            if (data.length === 0) {
+                return Docs.StackingDocument([], options);
+            }
+            let first = await Cast(data[0], Doc);
+            if (!first) {
+                return Docs.StackingDocument([], options);
+            }
+            switch (first.type) {
+                case "image":
+                    return Docs.StackingDocument([], options);
+                case "text":
+                    return Docs.TreeDocument([], options);
+            }
         } else if (data instanceof ImageField) {
             return Docs.ImageDocument("https://www.freepik.com/free-icon/picture-frame-with-mountain-image_748687.htm", options);
         }
diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py
index 02c6d8b74..700269727 100644
--- a/src/scraping/buxton/scraper.py
+++ b/src/scraping/buxton/scraper.py
@@ -26,7 +26,7 @@ def extract_links(fileName):
         item = rels[rel]
         if item.reltype == RT.HYPERLINK and ".aspx" not in item._target:
             links.append(item._target)
-    return listify(links)
+    return text_doc_map(links)
 
 
 def extract_value(kv_string):
@@ -60,6 +60,12 @@ def protofy(fieldId):
     }
 
 
+def text_doc_map(string_list):
+    def guid_map(caption):
+        return write_text_doc(caption)
+    return listify(proxify_guids(list(map(guid_map, string_list))))
+
+
 def write_schema(parse_results, display_fields, storage_key):
     view_guids = parse_results["child_guids"]
 
@@ -110,6 +116,54 @@ def write_schema(parse_results, display_fields, storage_key):
     return view_doc_guid
 
 
+def write_text_doc(content):
+    data_doc_guid = guid()
+    view_doc_guid = guid()
+
+    view_doc = {
+        "_id": view_doc_guid,
+        "fields": {
+            "proto": protofy(data_doc_guid),
+            "x": 10,
+            "y": 10,
+            "width": 400,
+            "zIndex": 2,
+            "libraryBrush": False
+        },
+        "__type": "Doc"
+    }
+
+    data_doc = {
+        "_id": data_doc_guid,
+        "fields": {
+            "proto": protofy("textProto"),
+            "data": {
+                "Data": '{"doc":{"type":"doc","content":[{"type":"paragraph","content":[{"type":"text","text":"' + content + '"}]}]},"selection":{"type":"text","anchor":1,"head":1}' + '}',
+                "__type": "RichTextField"
+            },
+            "title": content,
+            "nativeWidth": 200,
+            "author": "Bill Buxton",
+            "creationDate": {
+                "date": datetime.datetime.utcnow().microsecond,
+                "__type": "date"
+            },
+            "isPrototype": True,
+            "autoHeight": True,
+            "page": -1,
+            "nativeHeight": 200,
+            "height": 200,
+            "data_text": content
+        },
+        "__type": "Doc"
+    }
+
+    db.newDocuments.insert_one(view_doc)
+    db.newDocuments.insert_one(data_doc)
+
+    return view_doc_guid
+
+
 def write_image(folder, name):
     path = f"http://localhost:1050/files/{folder}/{name}"
 
@@ -253,7 +307,7 @@ def parse_document(file_name: str):
     while lines[cur] != "Image":
         link_descriptions.append(lines[cur].strip())
         cur += 1
-    result["link_descriptions"] = listify(link_descriptions)
+    result["link_descriptions"] = text_doc_map(link_descriptions)
 
     result["hyperlinks"] = extract_links(source + "/" + file_name)
 
@@ -265,7 +319,8 @@ def parse_document(file_name: str):
         captions.append(lines[cur + 1])
         cur += 2
     result["images"] = listify(images)
-    result["captions"] = listify(captions)
+
+    result["captions"] = text_doc_map(captions)
 
     notes = []
     if (cur < len(lines) and lines[cur] == "NOTES:"):
-- 
cgit v1.2.3-70-g09d2


From 9d9c16939bb296a9deb38bc34cbea9870aee4f76 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 9 Jul 2019 14:33:56 -0400
Subject: collection view fix

---
 src/client/views/nodes/KeyValueBox.tsx | 5 +++++
 1 file changed, 5 insertions(+)

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

diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index e8619584e..b5c47f138 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -175,7 +175,9 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         if (!sourceDoc) {
             return;
         }
+
         let fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey);
+        let previousViewType = fieldTemplate.viewType;
 
         // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
         let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
@@ -196,6 +198,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         fieldTemplate.isTemplate = true;
         fieldTemplate.templates = new List<string>([Templates.TitleBar(metaKey)]);
         fieldTemplate.proto = Doc.GetProto(parentStackingDoc);
+        previousViewType && (fieldTemplate.viewType = previousViewType);
 
         Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
     }
@@ -214,8 +217,10 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
             }
             switch (first.type) {
                 case "image":
+                    console.log("STACKING VIEW CREATED for ", data);
                     return Docs.StackingDocument([], options);
                 case "text":
+                    console.log("TREE VIEW CREATED for ", data);
                     return Docs.TreeDocument([], options);
             }
         } else if (data instanceof ImageField) {
-- 
cgit v1.2.3-70-g09d2


From 7a33f0b56f1182c19cc7ea1b276da3a674438957 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Tue, 9 Jul 2019 14:39:23 -0400
Subject: cleanup

---
 src/client/views/nodes/KeyValueBox.tsx | 4 +---
 1 file changed, 1 insertion(+), 3 deletions(-)

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

diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index b5c47f138..2f5a0f963 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -217,14 +217,12 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
             }
             switch (first.type) {
                 case "image":
-                    console.log("STACKING VIEW CREATED for ", data);
                     return Docs.StackingDocument([], options);
                 case "text":
-                    console.log("TREE VIEW CREATED for ", data);
                     return Docs.TreeDocument([], options);
             }
         } else if (data instanceof ImageField) {
-            return Docs.ImageDocument("https://www.freepik.com/free-icon/picture-frame-with-mountain-image_748687.htm", options);
+            return Docs.ImageDocument("https://image.flaticon.com/icons/png/512/23/23765.png", options);
         }
         return new Doc;
     }
-- 
cgit v1.2.3-70-g09d2


From b1b69f8a4f9e34f0c8e667ec95f9fe16ebc8b2e4 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Wed, 10 Jul 2019 21:32:31 -0400
Subject: factored template creation code into Doc.ts

---
 src/client/views/DocumentDecorations.tsx           | 28 +-------------------
 .../views/collections/CollectionTreeView.tsx       |  1 -
 src/client/views/nodes/KeyValueBox.tsx             | 21 +--------------
 src/new_fields/Doc.ts                              | 30 +++++++++++++++++++++-
 4 files changed, 31 insertions(+), 49 deletions(-)

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

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 07ecc6b0f..95335020f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -84,33 +84,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                 let fieldTemplate = fieldTemplateView.props.Document;
                 let docTemplate = fieldTemplateView.props.ContainingCollectionView!.props.Document;
                 let metaKey = text.slice(1, text.length);
-
-                // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
-                let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
-                let fieldLayoutDoc = fieldTemplate;
-                if (fieldTemplate.layout instanceof Doc) {
-                    fieldLayoutDoc = Doc.MakeDelegate(fieldTemplate.layout);
-                }
-                let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
-                if (backgroundLayout) {
-                    layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"annotations"}/, `fieldKey={"${metaKey}"} fieldExt={"annotations"}`);
-                    backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
-                }
-                let nw = Cast(fieldTemplate.nativeWidth, "number");
-                let nh = Cast(fieldTemplate.nativeHeight, "number");
-
-                let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate;
-                layoutDelegate.layout = layout;
-
-                fieldTemplate.title = metaKey;
-                fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
-                fieldTemplate.backgroundLayout = backgroundLayout;
-                fieldTemplate.nativeWidth = nw;
-                fieldTemplate.nativeHeight = nh;
-                fieldTemplate.embed = true;
-                fieldTemplate.isTemplate = true;
-                fieldTemplate.showTitle = "title";
-                fieldTemplate.proto = Doc.GetProto(docTemplate);
+                Doc.MakeTemplate(fieldTemplate, metaKey, Doc.GetProto(docTemplate));
             }
             else {
                 if (SelectionManager.SelectedDocuments().length > 0) {
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d90c78f85..3674a743a 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -73,7 +73,6 @@ class TreeView extends React.Component<TreeViewProps> {
     @observable _collapsed: boolean = true;
 
     @computed get fieldKey() {
-        trace();
         let keys = Array.from(Object.keys(this.resolvedDataDoc));  // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
         if (this.resolvedDataDoc.proto instanceof Doc) {
             let arr = Array.from(Object.keys(this.resolvedDataDoc.proto!));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 2f5a0f963..467cb2b6b 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -178,26 +178,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
 
         let fieldTemplate = await this.inferType(sourceDoc[metaKey], metaKey);
         let previousViewType = fieldTemplate.viewType;
-
-        // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
-        let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
-        let layout = StrCast(fieldTemplate.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
-        if (backgroundLayout) {
-            layout = StrCast(fieldTemplate.layout).replace(/fieldKey={"annotations"}/, `fieldKey={"${metaKey}"} fieldExt={"annotations"}`);
-            backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
-        }
-        let nw = NumCast(fieldTemplate.nativeWidth);
-        let nh = NumCast(fieldTemplate.nativeHeight);
-
-        fieldTemplate.title = metaKey;
-        fieldTemplate.layout = layout;
-        fieldTemplate.backgroundLayout = backgroundLayout;
-        fieldTemplate.nativeWidth = nw;
-        fieldTemplate.nativeHeight = nh;
-        fieldTemplate.embed = true;
-        fieldTemplate.isTemplate = true;
-        fieldTemplate.templates = new List<string>([Templates.TitleBar(metaKey)]);
-        fieldTemplate.proto = Doc.GetProto(parentStackingDoc);
+        Doc.MakeTemplate(fieldTemplate, metaKey, Doc.GetProto(parentStackingDoc));
         previousViewType && (fieldTemplate.viewType = previousViewType);
 
         Cast(parentStackingDoc.data, listSpec(Doc))!.push(fieldTemplate);
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index c63c9ef93..3d46243e0 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -3,7 +3,7 @@ import { serializable, primitive, map, alias, list } from "serializr";
 import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
 import { DocServer } from "../client/DocServer";
 import { setter, getter, getField, updateFunction, deleteProperty, makeEditable, makeReadOnly } from "./util";
-import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast, BoolCast } from "./Types";
+import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast, BoolCast, StrCast } from "./Types";
 import { listSpec } from "./Schema";
 import { ObjectField } from "./ObjectField";
 import { RefField, FieldId } from "./RefField";
@@ -362,4 +362,32 @@ export namespace Doc {
         delegate.proto = doc;
         return delegate;
     }
+
+    export function MakeTemplate(fieldTemplate: Doc, metaKey: string, proto: Doc) {
+        // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??)
+        let backgroundLayout = StrCast(fieldTemplate.backgroundLayout);
+        let fieldLayoutDoc = fieldTemplate;
+        if (fieldTemplate.layout instanceof Doc) {
+            fieldLayoutDoc = Doc.MakeDelegate(fieldTemplate.layout);
+        }
+        let layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
+        if (backgroundLayout) {
+            layout = StrCast(fieldLayoutDoc.layout).replace(/fieldKey={"annotations"}/, `fieldKey={"${metaKey}"} fieldExt={"annotations"}`);
+            backgroundLayout = backgroundLayout.replace(/fieldKey={"[^"]*"}/, `fieldKey={"${metaKey}"}`);
+        }
+        let nw = Cast(fieldTemplate.nativeWidth, "number");
+        let nh = Cast(fieldTemplate.nativeHeight, "number");
+
+        let layoutDelegate = fieldTemplate.layout instanceof Doc ? fieldLayoutDoc : fieldTemplate;
+        layoutDelegate.layout = layout;
+
+        fieldTemplate.title = metaKey;
+        fieldTemplate.layout = layoutDelegate !== fieldTemplate ? layoutDelegate : layout;
+        fieldTemplate.backgroundLayout = backgroundLayout;
+        fieldTemplate.nativeWidth = nw;
+        fieldTemplate.nativeHeight = nh;
+        fieldTemplate.isTemplate = true;
+        fieldTemplate.showTitle = "title";
+        fieldTemplate.proto = proto;
+    }
 }
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 60ceef0a3a8c11d85434a154e542424d34d9562c Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Sat, 13 Jul 2019 16:46:49 -0400
Subject: Refactored style in DocDecs and added metadata entry to DocDecs

---
 src/client/util/Scripting.ts              |  3 +-
 src/client/views/DocumentDecorations.scss | 44 ++++++++++--------
 src/client/views/DocumentDecorations.tsx  | 31 +++++++++----
 src/client/views/MetadataEntryMenu.scss   | 10 +++++
 src/client/views/MetadataEntryMenu.tsx    | 74 +++++++++++++++++++++++++++++++
 src/client/views/nodes/KeyValueBox.tsx    | 31 ++++++++++---
 src/client/views/nodes/KeyValuePair.tsx   |  8 +---
 src/new_fields/Doc.ts                     |  9 ++++
 8 files changed, 169 insertions(+), 41 deletions(-)
 create mode 100644 src/client/views/MetadataEntryMenu.scss
 create mode 100644 src/client/views/MetadataEntryMenu.tsx

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

diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 3156c4f43..62c2cfe85 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -33,6 +33,8 @@ export interface CompileError {
     errors: any[];
 }
 
+export type CompileResult = CompiledScript | CompileError;
+
 export namespace Scripting {
     export function addGlobal(global: { name: string }): void;
     export function addGlobal(name: string, global: any): void;
@@ -61,7 +63,6 @@ export function scriptingGlobal(constructor: { new(...args: any[]): any }) {
 
 const scriptingGlobals: { [name: string]: any } = {};
 
-export type CompileResult = CompiledScript | CompileError;
 function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
     const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error);
     if ((options.typecheck !== false && errors) || !script) {
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 2d430512b..1afc5c147 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,6 +1,7 @@
 @import "globalCssVariables";
 
 $linkGap : 3px;
+
 .documentDecorations {
     position: absolute;
 }
@@ -52,15 +53,16 @@ $linkGap : 3px;
         grid-column-start: 5;
         grid-column-end: 7;
     }
-    
-    #documentDecorations-borderRadius{
+
+    #documentDecorations-borderRadius {
         grid-column-start: 5;
         grid-column-end: 7;
         border-radius: 100%;
-        .borderRadiusTooltip{
-            width:10px;
-            height:10px;
-            position:absolute;
+
+        .borderRadiusTooltip {
+            width: 10px;
+            height: 10px;
+            position: absolute;
         }
     }
 
@@ -68,8 +70,9 @@ $linkGap : 3px;
     #documentDecorations-bottomRightResizer {
         cursor: nwse-resize;
     }
+
     #documentDecorations-bottomRightResizer {
-        grid-row:4;
+        grid-row: 4;
     }
 
     #documentDecorations-topRightResizer,
@@ -86,7 +89,8 @@ $linkGap : 3px;
     #documentDecorations-rightResizer {
         cursor: ew-resize;
     }
-    .title{
+
+    .title {
         background: $alt-accent;
         grid-column-start: 3;
         grid-column-end: 4;
@@ -129,7 +133,6 @@ $linkGap : 3px;
 
 .linkFlyout {
     grid-column: 2/4;
-    margin-top: $linkGap;
 }
 
 .linkButton-empty:hover {
@@ -145,6 +148,7 @@ $linkGap : 3px;
 }
 
 .link-button-container {
+    margin-top: $linkGap;
     grid-column: 1/4;
     width: auto;
     height: auto;
@@ -152,9 +156,12 @@ $linkGap : 3px;
     flex-direction: row;
 }
 
+.linkButtonWrapper {
+    pointer-events: auto;
+    padding-right: 5px;
+}
+
 .linkButton-linker {
-    margin-left: 5px;
-    margin-top: $linkGap;
     height: 20px;
     width: 20px;
     text-align: center;
@@ -169,7 +176,8 @@ $linkGap : 3px;
     transform: scale(1.05);
 }
 
-.linkButton-empty, .linkButton-nonempty {
+.linkButton-empty,
+.linkButton-nonempty {
     height: 20px;
     width: 20px;
     border-radius: 50%;
@@ -194,9 +202,6 @@ $linkGap : 3px;
 }
 
 .templating-menu {
-    position: absolute;
-    bottom: 0;
-    left: 50px;
     pointer-events: auto;
     text-transform: uppercase;
     letter-spacing: 2px;
@@ -208,15 +213,17 @@ $linkGap : 3px;
     align-items: center;
 }
 
-.fa-icon-link {
+.documentdecorations-icon {
     margin-top: 3px;
 }
-.templating-button {
+
+.templating-button,
+.docDecs-tagButton {
     width: 20px;
     height: 20px;
     border-radius: 50%;
     opacity: 0.9;
-    font-size:14;
+    font-size: 14;
     background-color: $dark-color;
     color: $light-color;
     text-align: center;
@@ -238,6 +245,7 @@ $linkGap : 3px;
     background-color: $light-color-secondary;
     padding: 2px 12px;
     list-style: none;
+
     .templateToggle {
         text-align: left;
     }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index c24d66ee4..56fbd75a0 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,5 +1,5 @@
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { faLink } from '@fortawesome/free-solid-svg-icons';
+import { faLink, faTag } from '@fortawesome/free-solid-svg-icons';
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { action, computed, observable, reaction, runInAction } from "mobx";
 import { observer } from "mobx-react";
@@ -27,11 +27,13 @@ import React = require("react");
 import { RichTextField } from '../../new_fields/RichTextField';
 import { LinkManager } from '../util/LinkManager';
 import { ObjectField } from '../../new_fields/ObjectField';
+import { MetadataEntryMenu } from './MetadataEntryMenu';
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
 library.add(faLink);
+library.add(faTag);
 
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
@@ -596,8 +598,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         if (!canEmbed) return (null);
         return (
             <div className="linkButtonWrapper">
-                <div style={{ paddingTop: 3, marginLeft: 30 }} title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}>
-                    <FontAwesomeIcon className="fa-image" icon="image" size="sm" />
+                <div title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}>
+                    <FontAwesomeIcon className="documentdecorations-icon" icon="image" size="sm" />
                 </div>
             </div>
         );
@@ -610,10 +612,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         this._textDoc = thisDoc;
         return (
             <div className="tooltipwrapper">
-                <div style={{ paddingTop: 3, marginLeft: 30 }} title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}>
+                <div title="Hide Tooltip" className="linkButton-linker" ref={this._tooltipoff} onPointerDown={this.onTooltipOff}>
                     {/* <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> */}
-                    T
-                    </div>
+                </div>
             </div>
 
         );
@@ -634,6 +635,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         }
     }
 
+    get metadataMenu() {
+        return (
+            <div className="linkButtonWrapper">
+                <Flyout anchorPoint={anchorPoints.TOP_LEFT}
+                    content={<MetadataEntryMenu docs={() => SelectionManager.SelectedDocuments().map(dv => dv.props.Document)} />}>{/* tfs: @bcz This might need to be the data document? */}
+                    <div className="docDecs-tagButton" title="Add fields"><FontAwesomeIcon className="documentdecorations-icon" icon="tag" size="sm" /></div>
+                </Flyout>
+            </div>
+        );
+    }
+
     render() {
         var bounds = this.Bounds;
         let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
@@ -723,10 +735,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
                     </div>
                     <div className="linkButtonWrapper">
                         <div title="Drag Link" className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>
-                            <FontAwesomeIcon className="fa-icon-link" icon="link" size="sm" />
+                            <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" />
                         </div>
                     </div>
-                    <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} />
+                    <div className="linkButtonWrapper">
+                        <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} />
+                    </div>
+                    {this.metadataMenu}
                     {this.considerEmbed()}
                     {/* {this.considerTooltip()} */}
                 </div>
diff --git a/src/client/views/MetadataEntryMenu.scss b/src/client/views/MetadataEntryMenu.scss
new file mode 100644
index 000000000..73e5b6a73
--- /dev/null
+++ b/src/client/views/MetadataEntryMenu.scss
@@ -0,0 +1,10 @@
+.metadataEntry-input {
+    width: 40%;
+    margin-left: 5px;
+    margin-right: 5px;
+}
+
+.metadataEntry-outerDiv {
+    display: flex;
+    width: 300px;
+}
\ No newline at end of file
diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx
new file mode 100644
index 000000000..0dc7e0220
--- /dev/null
+++ b/src/client/views/MetadataEntryMenu.tsx
@@ -0,0 +1,74 @@
+import * as React from 'react';
+import "./MetadataEntryMenu.scss";
+import { observer } from 'mobx-react';
+import { observable, action } from 'mobx';
+import { KeyValueBox } from './nodes/KeyValueBox';
+import { Doc } from '../../new_fields/Doc';
+
+export type DocLike = Doc | Doc[] | Promise<Doc> | Promise<Doc[]>;
+export interface MetadataEntryProps {
+    docs: DocLike | (() => DocLike);
+    onError?: () => boolean;
+}
+
+@observer
+export class MetadataEntryMenu extends React.Component<MetadataEntryProps>{
+    @observable private _currentKey: string = "";
+    @observable private _currentValue: string = "";
+
+    @action
+    onKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this._currentKey = e.target.value;
+    }
+
+    @action
+    onValueChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+        this._currentValue = e.target.value;
+    }
+
+    onValueKeyDown = async (e: React.KeyboardEvent) => {
+        if (e.key === "Enter") {
+            const script = KeyValueBox.CompileKVPScript(this._currentValue);
+            if (!script) return;
+            let doc = this.props.docs;
+            if (typeof doc === "function") {
+                doc = doc();
+            }
+            doc = await doc;
+            let success: boolean;
+            if (doc instanceof Doc) {
+                success = KeyValueBox.ApplyKVPScript(doc, this._currentKey, script);
+            } else {
+                success = doc.every(d => KeyValueBox.ApplyKVPScript(d, this._currentKey, script));
+            }
+            if (!success) {
+                if (this.props.onError) {
+                    if (this.props.onError()) {
+                        this.clearInputs();
+                    }
+                } else {
+                    this.clearInputs();
+                }
+            } else {
+                this.clearInputs();
+            }
+        }
+    }
+
+    @action
+    clearInputs = () => {
+        this._currentKey = "";
+        this._currentValue = "";
+    }
+
+    render() {
+        return (
+            <div className="metadataEntry-outerDiv">
+                Key:
+                <input className="metadataEntry-input" value={this._currentKey} onChange={this.onKeyChange} />
+                Value:
+                <input className="metadataEntry-input" value={this._currentValue} onChange={this.onValueChange} onKeyDown={this.onValueKeyDown} />
+            </div>
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index eb8abf2c7..c9dd9a64e 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -2,7 +2,7 @@
 import { action, computed, observable } from "mobx";
 import { observer } from "mobx-react";
 import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { CompileScript, ScriptOptions } from "../../util/Scripting";
+import { CompileScript, ScriptOptions, CompiledScript } from "../../util/Scripting";
 import { FieldView, FieldViewProps } from './FieldView';
 import "./KeyValueBox.scss";
 import { KeyValuePair } from "./KeyValuePair";
@@ -21,6 +21,12 @@ import { ImageField } from "../../../new_fields/URLField";
 import { SelectionManager } from "../../util/SelectionManager";
 import { listSpec } from "../../../new_fields/Schema";
 
+export type KVPScript = {
+    script: CompiledScript;
+    type: "computed" | "script" | false;
+    onDelegate: boolean;
+};
+
 @observer
 export class KeyValueBox extends React.Component<FieldViewProps> {
     private _mainCont = React.createRef<HTMLDivElement>();
@@ -48,22 +54,27 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
             }
         }
     }
-    public static SetField(doc: Doc, key: string, value: string) {
+    public static CompileKVPScript(value: string): KVPScript | undefined {
         let eq = value.startsWith("=");
-        let target = eq ? doc : Doc.GetProto(doc);
         value = eq ? value.substr(1) : value;
-        let dubEq = value.startsWith(":=") ? 1 : value.startsWith(";=") ? 2 : 0;
+        const dubEq = value.startsWith(":=") ? "computed" : value.startsWith(";=") ? "script" : false;
         value = dubEq ? value.substr(2) : value;
         let options: ScriptOptions = { addReturn: true, params: { this: "Doc" } };
         if (dubEq) options.typecheck = false;
         let script = CompileScript(value, options);
         if (!script.compiled) {
-            return false;
+            return undefined;
         }
+        return { script, type: dubEq, onDelegate: eq };
+    }
+
+    public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript): boolean {
+        const { script, type, onDelegate } = kvpScript;
+        const target = onDelegate ? doc : Doc.GetProto(doc);
         let field: Field;
-        if (dubEq === 1) {
+        if (type === "computed") {
             field = new ComputedField(script);
-        } else if (dubEq === 2) {
+        } else if (type === "script") {
             field = new ScriptField(script);
         } else {
             let res = script.run({ this: target });
@@ -77,6 +88,12 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
         return false;
     }
 
+    public static SetField(doc: Doc, key: string, value: string) {
+        const script = this.CompileKVPScript(value);
+        if (!script) return false;
+        return this.ApplyKVPScript(doc, key, script);
+    }
+
     onPointerDown = (e: React.PointerEvent): void => {
         if (e.buttons === 1 && this.props.isSelected()) {
             e.stopPropagation();
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index b5db52ac7..209782242 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -92,13 +92,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
                             contents={contents}
                             height={36}
                             GetValue={() => {
-                                const onDelegate = Object.keys(props.Document).includes(props.fieldKey);
-
-                                let field = FieldValue(props.Document[props.fieldKey]);
-                                if (Field.IsField(field)) {
-                                    return (onDelegate ? "=" : "") + Field.toScriptString(field);
-                                }
-                                return "";
+                                return Field.toKeyValueString(props.Document, props.fieldKey);
                             }}
                             SetValue={(value: string) =>
                                 KeyValueBox.SetField(props.Document, props.fieldKey, value)}>
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 0e5f8b4d9..c5f9e7adf 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -12,6 +12,15 @@ import { scriptingGlobal } from "../client/util/Scripting";
 import { List } from "./List";
 
 export namespace Field {
+    export function toKeyValueString(doc: Doc, key: string): string {
+        const onDelegate = Object.keys(doc).includes(key);
+
+        let field = FieldValue(doc[key]);
+        if (Field.IsField(field)) {
+            return (onDelegate ? "=" : "") + Field.toScriptString(field);
+        }
+        return "";
+    }
     export function toScriptString(field: Field): string {
         if (typeof field === "string") {
             return `"${field}"`;
-- 
cgit v1.2.3-70-g09d2

