From 952318fb56cce16c428a5b41afe27a0ee4d20589 Mon Sep 17 00:00:00 2001
From: Bob Zeleznik <zzzman@gmail.com>
Date: Sat, 11 Apr 2020 21:50:07 -0400
Subject: fixed double click on freeformviews. changed signature for
 DocumentHierarchyFromJson() to have an 'all' parameter

---
 src/client/util/Import & Export/DirectoryImportBox.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/util/Import & Export/DirectoryImportBox.tsx')

diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 3d8bcbab7..01a0eddf8 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -126,7 +126,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
             const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name });
             const { data, error } = exifData;
             if (document) {
-                Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data);
+                Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data, "", undefined, false);
                 docs.push(document);
             }
         }));
-- 
cgit v1.2.3-70-g09d2


From 3f037a4426f17c0f542a06daceecead29e63803f Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 11 Apr 2020 20:16:44 -0700
Subject: document from json improvements and documentation

---
 src/client/apis/youtube/YoutubeBox.tsx             |  4 +-
 src/client/cognitive_services/CognitiveServices.ts |  2 +-
 src/client/documents/Documents.ts                  | 92 ++++++++++++++++------
 .../util/Import & Export/DirectoryImportBox.tsx    |  2 +-
 src/client/util/Import & Export/ImageUtils.ts      |  2 +-
 src/client/views/nodes/ImageBox.tsx                |  4 +-
 6 files changed, 72 insertions(+), 34 deletions(-)

(limited to 'src/client/util/Import & Export/DirectoryImportBox.tsx')

diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx
index 4e990537f..1575e53fc 100644
--- a/src/client/apis/youtube/YoutubeBox.tsx
+++ b/src/client/apis/youtube/YoutubeBox.tsx
@@ -156,14 +156,14 @@ export class YoutubeBox extends React.Component<FieldViewProps> {
     @action
     processVideoDetails = (videoDetails: any[]) => {
         this.videoDetails = videoDetails;
-        this.props.Document.cachedDetails = Docs.Get.DocumentHierarchyFromJson(videoDetails, "detailBackUp", undefined, false);
+        this.props.Document.cachedDetails = Docs.Get.FromJson({ data: videoDetails, title: "detailBackUp" });
     }
 
     /**
      * The function that stores the search results in the props document.
      */
     backUpSearchResults = (videos: any[]) => {
-        this.props.Document.cachedSearchResults = Docs.Get.DocumentHierarchyFromJson(videos, "videosBackUp", undefined, false);
+        this.props.Document.cachedSearchResults = Docs.Get.FromJson({ data: videos, title: "videosBackUp" });
     }
 
     /**
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index e464aff55..5d83de233 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -192,7 +192,7 @@ export namespace CognitiveServices {
                 let results = await ExecuteQuery(Service.Handwriting, Manager, inkData);
                 if (results) {
                     results.recognitionUnits && (results = results.recognitionUnits);
-                    target[keys[0]] = Docs.Get.DocumentHierarchyFromJson(results, "Ink Analysis", undefined, false);
+                    target[keys[0]] = Docs.Get.FromJson({ data: results, title: "Ink Analysis" });
                     const recognizedText = results.map((item: any) => item.recognizedText);
                     const recognizedObjects = results.map((item: any) => item.recognizedObject);
                     const individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 30f72a6b2..ec864b268 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -405,7 +405,7 @@ export namespace Docs {
                     const doc = StackingDocument(deviceImages, { title: device.title, _LODdisable: true });
                     const deviceProto = Doc.GetProto(doc);
                     deviceProto.hero = new ImageField(constructed[0].url);
-                    Docs.Get.DocumentHierarchyFromJson(device, undefined, deviceProto, false);
+                    Docs.Get.FromJson({ data: device, appendToExisting: { targetDoc: deviceProto } });
                     Doc.AddDocToList(parentProto, "data", doc);
                 } else if (errors) {
                     console.log(errors);
@@ -712,6 +712,15 @@ export namespace Docs {
 
         const primitives = ["string", "number", "boolean"];
 
+        export interface JsonConversionOpts {
+            data: any;
+            title?: string;
+            appendToExisting?: { targetDoc: Doc, fieldKey?: string };
+            excludeEmptyObjects?: boolean;
+        }
+
+        const defaultKey = "json";
+
         /**
          * This function takes any valid JSON(-like) data, i.e. parsed or unparsed, and at arbitrarily
          * deep levels of nesting, converts the data and structure into nested documents with the appropriate fields.
@@ -729,25 +738,49 @@ export namespace Docs {
          * All TS/JS objects get converted directly to documents, directly preserving the key value structure. Everything else,
          * lacking the key value structure, gets stored as a field in a wrapper document.
          * 
-         * @param input for convenience and flexibility, either a valid JSON string to be parsed,
+         * @param data for convenience and flexibility, either a valid JSON string to be parsed,
          * or the result of any JSON.parse() call.
-         * @param title an optional title to give to the highest parent document in the hierarchy
-         * @param appendToTarget -???
-         * @param all whether fields should be converted even if they contain no data
+         * @param title an optional title to give to the highest parent document in the hierarchy.
+         * If whether this function creates a new document or appendToExisting is specified and that document already has a title,
+         * because this title field can be left undefined for the opposite behavior, including a title will overwrite the existing title.
+         * @param appendToExisting **if specified**, there are two cases: the json to be converted can be represented as a document, in which ase
+         * the target document will receive all the conversion results as new fields on it, or, if the json can't be represented as a document,
+         * the function will assign the converstion results to either the specified key on the target document, or to the "json" key by default.
+         * If not specified, will create a new entirely generic.
+         * 
+         * One might choose to specify this field if you want to write to a document returned from a Document.Create function call,
+         * say a TreeView document that will be rendered, not just an untyped, identityless doc that would otherwise be created
+         * from a default call to new Doc.
+         * 
+         * @param excludeEmptyObjects whether non-primitive objects (TypeScript objects and arrays) should be converted even
+         * if they contain no data. By default, empty objects and arrays are ignored.
          */
-        export function DocumentHierarchyFromJson(input: any, title: string, appendToTarget: Opt<Doc>, all?:boolean): Opt<Doc> {
-            if (input === undefined || input === null || ![...primitives, "object"].includes(typeof input)) {
+        export function FromJson({ data, title, appendToExisting, excludeEmptyObjects }: JsonConversionOpts): Opt<Doc> {
+            if (excludeEmptyObjects === undefined) {
+                excludeEmptyObjects = true;
+            }
+            if (data === undefined || data === null || ![...primitives, "object"].includes(typeof data)) {
+                return undefined;
+            }
+            let resolved: any;
+            try {
+                resolved = JSON.parse(typeof data === "string" ? data : JSON.stringify(data));
+            } catch (e) {
                 return undefined;
             }
-            input = JSON.parse(typeof input === "string" ? input : JSON.stringify(input));
-            let converted: Opt<Doc>;
-            if (typeof input === "object" && !(input instanceof Array)) {
-                converted = convertObject(input, title, appendToTarget, all);
+            let output: Opt<Doc>;
+            if (typeof resolved === "object" && !(resolved instanceof Array)) {
+                output = convertObject(resolved, excludeEmptyObjects, title, appendToExisting?.targetDoc);
             } else {
-                (converted = new Doc).json = toField(input);
+                const result = toField(resolved, excludeEmptyObjects);
+                if (appendToExisting) {
+                    (output = appendToExisting.targetDoc)[appendToExisting.fieldKey || defaultKey] = result;
+                } else {
+                    (output = new Doc).json = result;
+                }
             }
-            title && converted && (converted.title = title);
-            return converted;
+            title && output && (output.title = title);
+            return output;
         }
 
         /**
@@ -757,13 +790,19 @@ export namespace Docs {
          * @returns the object mapped from JSON to field values, where each mapping 
          * might involve arbitrary recursion (since toField might itself call convertObject)
          */
-        const convertObject = (object: any, title?: string, target?: Doc, all?:boolean): Opt<Doc> => {
-            const objkeys = Object.keys(object);
-            if (objkeys.length || all) {
+        const convertObject = (object: any, excludeEmptyObjects: boolean, title?: string, target?: Doc): Opt<Doc> => {
+            const hasEntries = Object.keys(object).length;
+            if (hasEntries || !excludeEmptyObjects) {
                 const resolved = target ?? new Doc;
-                let result: Opt<Field>;
-                Object.keys(object).map(key => (result = toField(object[key], key)) && (resolved[key] = result));
-                title && !resolved.title && (resolved.title = title);
+                if (hasEntries) {
+                    let result: Opt<Field>;
+                    Object.keys(object).map(key => {
+                        if (result = toField(object[key], excludeEmptyObjects, key)) {
+                            (resolved[key] = result);
+                        }
+                    });
+                }
+                title && (resolved.title = title);
                 return resolved;
             }
         };
@@ -775,15 +814,16 @@ export namespace Docs {
          * @returns the list mapped from JSON to field values, where each mapping 
          * might involve arbitrary recursion (since toField might itself call convertList)
          */
-        const convertList = (list: Array<any>): List<Field> => {
+        const convertList = (list: Array<any>, excludeEmptyObjects: boolean): Opt<List<Field>> => {
             const target = new List();
             let result: Opt<Field>;
-            list.map(item => (result = toField(item)) && target.push(result));
-            return target;
+            list.map(item => (result = toField(item, excludeEmptyObjects)) && target.push(result));
+            if (target.length || !excludeEmptyObjects) {
+                return target;
+            }
         };
 
-
-        const toField = (data: any, title?: string, all?:boolean): Opt<Field> => {
+        const toField = (data: any, excludeEmptyObjects: boolean, title?: string): Opt<Field> => {
             if (data === null || data === undefined) {
                 return undefined;
             }
@@ -791,7 +831,7 @@ export namespace Docs {
                 return data;
             }
             if (typeof data === "object") {
-                return data instanceof Array ? convertList(data) : convertObject(data, title, undefined, all);
+                return data instanceof Array ? convertList(data, excludeEmptyObjects) : convertObject(data, excludeEmptyObjects, title, undefined);
             }
             throw new Error(`How did ${data} of type ${typeof data} end up in JSON?`);
         };
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 01a0eddf8..438904688 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -126,7 +126,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
             const document = await Docs.Get.DocumentFromType(type, path, { _width: 300, title: name });
             const { data, error } = exifData;
             if (document) {
-                Doc.GetProto(document).exif = error || Docs.Get.DocumentHierarchyFromJson(data, "", undefined, false);
+                Doc.GetProto(document).exif = error || Docs.Get.FromJson({ data });
                 docs.push(document);
             }
         }));
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index 135454a33..9fae5ff93 100644
--- a/src/client/util/Import & Export/ImageUtils.ts	
+++ b/src/client/util/Import & Export/ImageUtils.ts	
@@ -20,7 +20,7 @@ export namespace ImageUtils {
             nativeHeight,
             exifData: { error, data }
         } = await Networking.PostToServer("/inspectImage", { source });
-        document.exif = error || Docs.Get.DocumentHierarchyFromJson(data, "", undefined, false);
+        document.exif = error || Docs.Get.FromJson({ data });
         const proto = Doc.GetProto(document);
         proto["data-nativeWidth"] = nativeWidth;
         proto["data-nativeHeight"] = nativeHeight;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index c45f14975..de1640027 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -186,9 +186,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD
 
     extractFaces = () => {
         const converter = (results: any) => {
-            const faceDocs = new List<Doc>();
-            results.reduce((face: CognitiveServices.Image.Face, faceDocs: List<Doc>) => faceDocs.push(Docs.Get.DocumentHierarchyFromJson(face, `Face: ${face.faceId}`, undefined, false)!), new List<Doc>());
-            return faceDocs;
+            return results.map((face: CognitiveServices.Image.Face) => Docs.Get.FromJson({ data: face, title: `Face: ${face.faceId}` })!);
         };
         this.url && CognitiveServices.Image.Appliers.ProcessImage(this.dataDoc, [this.fieldKey + "-faces"], this.url, Service.Face, converter);
     }
-- 
cgit v1.2.3-70-g09d2

