From aedd283fb9f9eff4145e27658bc6647982256032 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 5 Jul 2019 00:34:19 -0400
Subject: beginning implementation of key value tags for all imported documents

---
 .../util/Import & Export/DirectoryImportBox.tsx    | 181 +++++++++++++++++++++
 1 file changed, 181 insertions(+)
 create mode 100644 src/client/util/Import & Export/DirectoryImportBox.tsx

(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
new file mode 100644
index 000000000..2d77f6ae6
--- /dev/null
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -0,0 +1,181 @@
+import "fs";
+import React = require("react");
+import { Doc } from "../../../new_fields/Doc";
+import { DocServer } from "../../DocServer";
+import { RouteStore } from "../../../server/RouteStore";
+import { action, observable, runInAction } from "mobx";
+import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
+import Measure, { ContentRect } from "react-measure";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faArrowUp, faTag, faFileExcel } from '@fortawesome/free-solid-svg-icons';
+import { Docs, DocumentOptions } from "../../documents/Documents";
+import { EditableView } from "../../views/EditableView";
+
+export default class DirectoryImportBox extends React.Component<FieldViewProps> {
+    private selector = React.createRef<HTMLInputElement>();
+    @observable private top = 0;
+    @observable private left = 0;
+    private dimensions = 50;
+
+    @observable private key = "Key";
+    @observable private value = "Value";
+
+    public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); }
+
+    constructor(props: FieldViewProps) {
+        super(props);
+        library.add(faArrowUp, faTag);
+    }
+
+    updateKey = (newKey: string) => {
+        runInAction(() => this.key = newKey);
+        console.log("KEY ", this.key);
+        return true;
+    }
+
+    updateValue = (newValue: string) => {
+        runInAction(() => this.value = newValue);
+        console.log("VALUE ", this.value);
+        return true;
+    }
+
+    handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
+        let promises: Promise<void>[] = [];
+        let docs: Doc[] = [];
+
+        let files = e.target.files;
+        if (!files || files.length === 0) return;
+
+        let directory = (files.item(0) as any).webkitRelativePath.split("/", 1);
+
+        for (let i = 0; i < files.length; i++) {
+            let uploaded_file = files.item(i);
+
+            if (!uploaded_file) {
+                continue;
+            }
+
+            let formData = new FormData();
+            formData.append('file', uploaded_file);
+            let dropFileName = uploaded_file ? uploaded_file.name : "-empty-";
+            let type = uploaded_file.type;
+
+            let prom = fetch(DocServer.prepend(RouteStore.upload), {
+                method: 'POST',
+                body: formData
+            }).then(async (res: Response) => {
+                (await res.json()).map(action((file: any) => {
+                    let path = DocServer.prepend(file);
+                    console.log(path);
+                    let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName });
+                    docPromise.then(doc => doc && docs.push(doc));
+                }));
+            });
+            promises.push(prom);
+        }
+
+        await Promise.all(promises);
+
+        let doc = this.props.Document;
+        let options: DocumentOptions = { title: `Import of ${directory}`, width: 500, height: 500, x: Doc.GetT(doc, "x", "number"), y: Doc.GetT(doc, "y", "number") };
+        let parent = this.props.ContainingCollectionView;
+        if (parent) {
+            let importContainer = Docs.StackingDocument(docs, options);
+            Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
+            this.props.removeDocument && this.props.removeDocument(doc);
+        }
+    }
+
+    componentDidMount() {
+        this.selector.current!.setAttribute("directory", "");
+        this.selector.current!.setAttribute("webkitdirectory", "");
+    }
+
+    @action
+    preserveCentering = (rect: ContentRect) => {
+        let bounds = rect.offset!;
+        if (bounds.width === 0 || bounds.height === 0) {
+            return;
+        }
+        let offset = this.dimensions / 2;
+        this.left = bounds.width / 2 - offset;
+        this.top = bounds.height / 2 - offset;
+    }
+
+    render() {
+        let dimensions = 50;
+        let keyValueStyle = { paddingLeft: 5, width: "50%" };
+        return (
+            <Measure offset onResize={this.preserveCentering}>
+                {({ measureRef }) =>
+                    <div ref={measureRef} style={{ width: "100%", height: "100%", pointerEvents: "all" }} >
+                        <input
+                            id={"selector"}
+                            ref={this.selector}
+                            name={"selector"}
+                            onChange={this.handleSelection}
+                            type="file"
+                            style={{
+                                position: "absolute",
+                                display: "none"
+                            }} />
+                        <label htmlFor={"selector"}>
+                            <div style={{
+                                width: dimensions,
+                                height: dimensions,
+                                borderRadius: "50%",
+                                background: "black",
+                                position: "absolute",
+                                left: this.left,
+                                top: this.top
+                            }} />
+                            <div style={{
+                                position: "absolute",
+                                left: this.left + 12.5,
+                                top: this.top + 11
+                            }}>
+                                <FontAwesomeIcon icon={faArrowUp} color="#FFFFFF" size={"2x"} />
+                            </div>
+                        </label>
+                        <div style={{
+                            position: "absolute",
+                            top: 5,
+                            right: 5,
+                            borderRadius: "50%",
+                            width: 25,
+                            height: 25,
+                            background: "black"
+                        }} />
+                        <div style={{
+                            position: "absolute",
+                            right: 9.5,
+                            top: 11
+                        }}>
+                            <FontAwesomeIcon icon={faTag} color="#FFFFFF" size={"1x"} />
+                        </div>
+                        <div style={{ display: "flex", flexDirection: "row", borderBottom: "1px solid black", paddingBottom: 5 }} >
+                            <div className={"key_container"} style={keyValueStyle}>
+                                <EditableView
+                                    contents={this.key}
+                                    SetValue={this.updateKey}
+                                    GetValue={() => this.key}
+                                    oneLine={true}
+                                />
+                            </div>
+                            <div className={"value_container"} style={keyValueStyle}>
+                                <EditableView
+                                    contents={this.value}
+                                    SetValue={this.updateValue}
+                                    GetValue={() => this.value}
+                                    oneLine={true}
+                                />
+                            </div>
+                        </div>
+                    </div>
+                }
+            </Measure>
+        );
+    }
+
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From 12788cbe0586d0349f70b73d1b6f6a481b4bf2cf Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 5 Jul 2019 17:31:20 -0400
Subject: first pass at implementation of directory import

---
 deploy/assets/loading.gif                          | Bin 0 -> 114208 bytes
 .../util/Import & Export/DirectoryImportBox.scss   |   0
 .../util/Import & Export/DirectoryImportBox.tsx    | 194 +++++++++++++++------
 src/client/util/Import & Export/KeyValue.tsx       |  79 +++++++++
 src/client/views/MainView.tsx                      |   2 +-
 5 files changed, 217 insertions(+), 58 deletions(-)
 create mode 100644 deploy/assets/loading.gif
 delete mode 100644 src/client/util/Import & Export/DirectoryImportBox.scss
 create mode 100644 src/client/util/Import & Export/KeyValue.tsx

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

diff --git a/deploy/assets/loading.gif b/deploy/assets/loading.gif
new file mode 100644
index 000000000..0652bf021
Binary files /dev/null and b/deploy/assets/loading.gif differ
diff --git a/src/client/util/Import & Export/DirectoryImportBox.scss b/src/client/util/Import & Export/DirectoryImportBox.scss
deleted file mode 100644
index e69de29bb..000000000
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 2d77f6ae6..cbc8c6fdc 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -3,44 +3,46 @@ import React = require("react");
 import { Doc } from "../../../new_fields/Doc";
 import { DocServer } from "../../DocServer";
 import { RouteStore } from "../../../server/RouteStore";
-import { action, observable, runInAction } from "mobx";
+import { action, observable, autorun, runInAction } from "mobx";
 import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
 import Measure, { ContentRect } from "react-measure";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faArrowUp, faTag, faFileExcel } from '@fortawesome/free-solid-svg-icons';
+import { faArrowUp, faTag, faPlus } from '@fortawesome/free-solid-svg-icons';
 import { Docs, DocumentOptions } from "../../documents/Documents";
-import { EditableView } from "../../views/EditableView";
+import { observer } from "mobx-react";
+import KeyValue from "./KeyValue";
+import { Utils } from "../../../Utils";
+import { doesNotReject } from "assert";
+import { remove } from "typescript-collections/dist/lib/arrays";
 
+@observer
 export default class DirectoryImportBox extends React.Component<FieldViewProps> {
     private selector = React.createRef<HTMLInputElement>();
     @observable private top = 0;
     @observable private left = 0;
     private dimensions = 50;
 
-    @observable private key = "Key";
-    @observable private value = "Value";
+    @observable private editingMetadata = false;
+    @observable private metadata_guids: string[] = [];
+    @observable private entries: KeyValue[] = [];
+
+    @observable private quota = 1;
+    @observable private remaining = 1;
+
+    @observable private uploadBegun = false;
 
     public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); }
 
     constructor(props: FieldViewProps) {
         super(props);
-        library.add(faArrowUp, faTag);
-    }
-
-    updateKey = (newKey: string) => {
-        runInAction(() => this.key = newKey);
-        console.log("KEY ", this.key);
-        return true;
-    }
-
-    updateValue = (newValue: string) => {
-        runInAction(() => this.value = newValue);
-        console.log("VALUE ", this.value);
-        return true;
+        library.add(faArrowUp, faTag, faPlus);
     }
 
+    @action
     handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
+        this.uploadBegun = true;
+
         let promises: Promise<void>[] = [];
         let docs: Doc[] = [];
 
@@ -49,9 +51,15 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
 
         let directory = (files.item(0) as any).webkitRelativePath.split("/", 1);
 
+        let validated: File[] = [];
         for (let i = 0; i < files.length; i++) {
-            let uploaded_file = files.item(i);
+            let file = files.item(i);
+            file && validated.push(file);
+        }
+
+        this.quota = validated.length;
 
+        for (let uploaded_file of validated) {
             if (!uploaded_file) {
                 continue;
             }
@@ -61,15 +69,18 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
             let dropFileName = uploaded_file ? uploaded_file.name : "-empty-";
             let type = uploaded_file.type;
 
+            this.remaining++;
+
             let prom = fetch(DocServer.prepend(RouteStore.upload), {
                 method: 'POST',
                 body: formData
             }).then(async (res: Response) => {
                 (await res.json()).map(action((file: any) => {
                     let path = DocServer.prepend(file);
-                    console.log(path);
                     let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName });
-                    docPromise.then(doc => doc && docs.push(doc));
+                    docPromise.then(doc => {
+                        doc && docs.push(doc) && runInAction(() => this.remaining--);
+                    });
                 }));
             });
             promises.push(prom);
@@ -77,11 +88,14 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
 
         await Promise.all(promises);
 
+        docs.forEach(doc => this.entries.forEach(entry => doc[entry.key] = entry.value));
+
         let doc = this.props.Document;
         let options: DocumentOptions = { title: `Import of ${directory}`, width: 500, height: 500, x: Doc.GetT(doc, "x", "number"), y: Doc.GetT(doc, "y", "number") };
         let parent = this.props.ContainingCollectionView;
         if (parent) {
             let importContainer = Docs.StackingDocument(docs, options);
+            importContainer.singleColumn = false;
             Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
             this.props.removeDocument && this.props.removeDocument(doc);
         }
@@ -103,9 +117,29 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
         this.top = bounds.height / 2 - offset;
     }
 
+    @action
+    addMetadataEntry = () => {
+        this.metadata_guids.push(Utils.GenerateGuid());
+    }
+
+    @action
+    remove = (entry: KeyValue) => {
+        let index = this.entries.indexOf(entry);
+        let key = entry.key;
+        this.entries.splice(index, 1);
+        this.metadata_guids.splice(this.metadata_guids.indexOf(key), 1);
+    }
+
     render() {
         let dimensions = 50;
-        let keyValueStyle = { paddingLeft: 5, width: "50%" };
+        let guids = this.metadata_guids.map(el => el);
+        let isEditing = this.editingMetadata;
+        let remaining = this.remaining;
+        let quota = this.quota;
+        let percent = `${100 - (remaining / quota * 100)}`;
+        let uploadBegun = this.uploadBegun;
+        percent = percent.split(".")[0];
+        percent = percent.startsWith("100") ? "99" : percent;
         return (
             <Measure offset onResize={this.preserveCentering}>
                 {({ measureRef }) =>
@@ -113,14 +147,20 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                         <input
                             id={"selector"}
                             ref={this.selector}
-                            name={"selector"}
                             onChange={this.handleSelection}
                             type="file"
                             style={{
                                 position: "absolute",
                                 display: "none"
                             }} />
-                        <label htmlFor={"selector"}>
+                        <label
+                            htmlFor={"selector"}
+                            style={{
+                                opacity: isEditing ? 0 : 1,
+                                pointerEvents: isEditing ? "none" : "all",
+                                transition: "0.4s ease opacity"
+                            }}
+                        >
                             <div style={{
                                 width: dimensions,
                                 height: dimensions,
@@ -132,46 +172,86 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                             }} />
                             <div style={{
                                 position: "absolute",
-                                left: this.left + 12.5,
-                                top: this.top + 11
+                                left: this.left + 12.6,
+                                top: this.top + 11,
+                                opacity: uploadBegun ? 0 : 1,
+                                transition: "0.4s opacity ease"
                             }}>
                                 <FontAwesomeIcon icon={faArrowUp} color="#FFFFFF" size={"2x"} />
                             </div>
                         </label>
-                        <div style={{
-                            position: "absolute",
-                            top: 5,
-                            right: 5,
-                            borderRadius: "50%",
-                            width: 25,
-                            height: 25,
-                            background: "black"
-                        }} />
-                        <div style={{
-                            position: "absolute",
-                            right: 9.5,
-                            top: 11
-                        }}>
-                            <FontAwesomeIcon icon={faTag} color="#FFFFFF" size={"1x"} />
-                        </div>
-                        <div style={{ display: "flex", flexDirection: "row", borderBottom: "1px solid black", paddingBottom: 5 }} >
-                            <div className={"key_container"} style={keyValueStyle}>
-                                <EditableView
-                                    contents={this.key}
-                                    SetValue={this.updateKey}
-                                    GetValue={() => this.key}
-                                    oneLine={true}
-                                />
-                            </div>
-                            <div className={"value_container"} style={keyValueStyle}>
-                                <EditableView
-                                    contents={this.value}
-                                    SetValue={this.updateValue}
-                                    GetValue={() => this.value}
-                                    oneLine={true}
+                        <div
+                            style={{
+                                transition: "0.4s opacity ease",
+                                opacity: uploadBegun ? 1 : 0,
+                                pointerEvents: "none",
+                                position: "absolute",
+                                left: 10,
+                                top: this.top + 12.3,
+                                fontSize: 18,
+                                color: "white",
+                                marginLeft: this.left - 1.6
+                            }}>{percent}%</div>
+                        <div
+                            style={{
+                                position: "absolute",
+                                top: 10,
+                                right: 10,
+                                borderRadius: "50%",
+                                width: 25,
+                                height: 25,
+                                background: "black"
+                            }}
+                            onClick={action(() => this.editingMetadata = !this.editingMetadata)}
+                        />
+                        <FontAwesomeIcon
+                            style={{
+                                pointerEvents: "none",
+                                position: "absolute",
+                                right: isEditing ? 16.3 : 14.5,
+                                top: isEditing ? 15.4 : 16
+                            }}
+                            icon={isEditing ? faArrowUp : faTag}
+                            color="#FFFFFF"
+                            size={"1x"}
+                        />
+                        <div
+                            style={{
+                                transition: "0.4s ease opacity",
+                                width: "100%",
+                                height: "100%",
+                                pointerEvents: isEditing ? "all" : "none",
+                                opacity: isEditing ? 1 : 0,
+                                overflowY: "scroll"
+                            }}
+                        >
+                            <div
+                                style={{
+                                    borderRadius: "50%",
+                                    width: 25,
+                                    height: 25,
+                                    marginLeft: 10,
+                                    position: "absolute",
+                                    right: 41,
+                                    top: 10
+                                }}
+                                onClick={this.addMetadataEntry}
+                            >
+                                <FontAwesomeIcon
+                                    style={{
+                                        pointerEvents: "none",
+                                        marginLeft: 6.4,
+                                        marginTop: 5.2
+                                    }}
+                                    icon={faPlus}
+                                    size={"1x"}
                                 />
                             </div>
+                            <p style={{ paddingLeft: 10, paddingTop: 8, paddingBottom: 7 }} >Add metadata to your import...</p>
+                            <hr style={{ margin: "6px 10px 12px 10px" }} />
+                            {guids.map(guid => <KeyValue remove={this.remove} key={guid} ref={(el) => { if (el) this.entries.push(el); }} />)}
                         </div>
+                        {/* <img style={{ width: 30, height: 30 }} src={"./loading.gif"}></img> */}
                     </div>
                 }
             </Measure>
diff --git a/src/client/util/Import & Export/KeyValue.tsx b/src/client/util/Import & Export/KeyValue.tsx
new file mode 100644
index 000000000..15ef25f89
--- /dev/null
+++ b/src/client/util/Import & Export/KeyValue.tsx	
@@ -0,0 +1,79 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { EditableView } from "../../views/EditableView";
+import { observable, action } from "mobx";
+
+interface KeyValueProps {
+    remove: (self: KeyValue) => void;
+}
+
+@observer
+export default class KeyValue extends React.Component<KeyValueProps> {
+    @observable public key = "Key";
+    @observable public value = "Value";
+
+    @action
+    updateKey = (newKey: string) => {
+        this.key = newKey;
+        return true;
+    }
+
+    @action
+    updateValue = (newValue: string) => {
+        this.value = newValue;
+        return true;
+    }
+
+    render() {
+        let keyValueStyle = { paddingLeft: 10, width: "50%" };
+        let keySpecified = (this.key.length > 0 && this.key !== "Key");
+        return (
+            <div
+                style={{
+                    display: "flex",
+                    flexDirection: "row",
+                    paddingBottom: 5,
+                    paddingRight: 5,
+                    justifyContent: "center",
+                    alignItems: "center",
+                    alignContent: "center"
+                }}
+                onClick={() => this.props.remove(this)}
+            >
+                <input type="checkbox" />
+                <div className={"key_container"} style={keyValueStyle}>
+                    <EditableView
+                        contents={this.key}
+                        SetValue={this.updateKey}
+                        GetValue={() => this.key}
+                        oneLine={true}
+                    />
+                </div>
+                <div
+                    className={"value_container"}
+                    style={{
+                        opacity: keySpecified ? 1 : 0.5,
+                        pointerEvents: keySpecified ? "all" : "none",
+                        ...keyValueStyle
+                    }}>
+                    <EditableView
+                        contents={this.value}
+                        SetValue={this.updateValue}
+                        GetValue={() => this.value}
+                        oneLine={true}
+                    />
+                </div>
+                <div style={{
+                    borderRadius: "50%",
+                    width: 10,
+                    height: 10,
+                    background: "red",
+                    marginLeft: 15,
+                    marginRight: 15
+                }} />
+
+            </div>
+        );
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 3d059b2f3..60decff25 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -351,7 +351,7 @@ export class MainView extends React.Component {
         let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" }));
         let addTreeNode = action(() => CurrentUserUtils.UserDocument);
         let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
-        let addImportCollectionNode = action(() => Docs.DirectoryImportDocument({ title: "Directory Import", width: 150, height: 150 }));
+        let addImportCollectionNode = action(() => Docs.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 }));
 
         let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
             [React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
-- 
cgit v1.2.3-70-g09d2


From 07ef72f4703f99ab732ec4fdced353ec91e69947 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 5 Jul 2019 18:32:11 -0400
Subject: added gif

---
 .../util/Import & Export/DirectoryImportBox.tsx    | 24 ++++++++++++++++++----
 1 file changed, 20 insertions(+), 4 deletions(-)

(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 cbc8c6fdc..50086a306 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -13,8 +13,7 @@ import { Docs, DocumentOptions } from "../../documents/Documents";
 import { observer } from "mobx-react";
 import KeyValue from "./KeyValue";
 import { Utils } from "../../../Utils";
-import { doesNotReject } from "assert";
-import { remove } from "typescript-collections/dist/lib/arrays";
+import { DocumentManager } from "../DocumentManager";
 
 @observer
 export default class DirectoryImportBox extends React.Component<FieldViewProps> {
@@ -91,13 +90,20 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
         docs.forEach(doc => this.entries.forEach(entry => doc[entry.key] = entry.value));
 
         let doc = this.props.Document;
-        let options: DocumentOptions = { title: `Import of ${directory}`, width: 500, height: 500, x: Doc.GetT(doc, "x", "number"), y: Doc.GetT(doc, "y", "number") };
+        let options: DocumentOptions = {
+            title: `Import of ${directory}`,
+            width: 1105,
+            height: 500,
+            x: Doc.GetT(doc, "x", "number"),
+            y: Doc.GetT(doc, "y", "number")
+        };
         let parent = this.props.ContainingCollectionView;
         if (parent) {
             let importContainer = Docs.StackingDocument(docs, options);
             importContainer.singleColumn = false;
             Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
             this.props.removeDocument && this.props.removeDocument(doc);
+            DocumentManager.Instance.jumpToDocument(importContainer, true);
         }
     }
 
@@ -179,6 +185,17 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                             }}>
                                 <FontAwesomeIcon icon={faArrowUp} color="#FFFFFF" size={"2x"} />
                             </div>
+                            <img
+                                style={{
+                                    width: 80,
+                                    height: 80,
+                                    transition: "0.4s opacity ease",
+                                    opacity: uploadBegun ? 0.7 : 0,
+                                    position: "absolute",
+                                    top: this.top - 15,
+                                    left: this.left - 15
+                                }}
+                                src={"/assets/loading.gif"}></img>
                         </label>
                         <div
                             style={{
@@ -251,7 +268,6 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                             <hr style={{ margin: "6px 10px 12px 10px" }} />
                             {guids.map(guid => <KeyValue remove={this.remove} key={guid} ref={(el) => { if (el) this.entries.push(el); }} />)}
                         </div>
-                        {/* <img style={{ width: 30, height: 30 }} src={"./loading.gif"}></img> */}
                     </div>
                 }
             </Measure>
-- 
cgit v1.2.3-70-g09d2


From 7db7bad21d25ad5be63810d9c4511ccb3428b2f5 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Fri, 5 Jul 2019 20:37:06 -0400
Subject: data doc and persistence functionality added

---
 .../util/Import & Export/DirectoryImportBox.tsx    |  59 ++++++++--
 .../util/Import & Export/ImportMetadataEntry.tsx   | 119 +++++++++++++++++++++
 src/client/util/Import & Export/KeyValue.tsx       |  79 --------------
 src/client/views/EditableView.tsx                  |   9 +-
 4 files changed, 178 insertions(+), 88 deletions(-)
 create mode 100644 src/client/util/Import & Export/ImportMetadataEntry.tsx
 delete mode 100644 src/client/util/Import & Export/KeyValue.tsx

(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 50086a306..88ce9c0e9 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -1,6 +1,6 @@
 import "fs";
 import React = require("react");
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, Opt } from "../../../new_fields/Doc";
 import { DocServer } from "../../DocServer";
 import { RouteStore } from "../../../server/RouteStore";
 import { action, observable, autorun, runInAction } from "mobx";
@@ -11,7 +11,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faArrowUp, faTag, faPlus } from '@fortawesome/free-solid-svg-icons';
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { observer } from "mobx-react";
-import KeyValue from "./KeyValue";
+import ImportMetadataEntry from "./ImportMetadataEntry";
 import { Utils } from "../../../Utils";
 import { DocumentManager } from "../DocumentManager";
 
@@ -24,12 +24,14 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
 
     @observable private editingMetadata = false;
     @observable private metadata_guids: string[] = [];
-    @observable private entries: KeyValue[] = [];
+    @observable private entries: ImportMetadataEntry[] = [];
 
     @observable private quota = 1;
     @observable private remaining = 1;
 
     @observable private uploadBegun = false;
+    @observable private removeHover = false;
+    @observable private shouldKeep = false;
 
     public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); }
 
@@ -87,7 +89,12 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
 
         await Promise.all(promises);
 
-        docs.forEach(doc => this.entries.forEach(entry => doc[entry.key] = entry.value));
+        docs.forEach(doc => {
+            this.entries.forEach(entry => {
+                let target = entry.onDataDoc ? Doc.GetProto(doc) : doc;
+                target[entry.key] = entry.value;
+            });
+        });
 
         let doc = this.props.Document;
         let options: DocumentOptions = {
@@ -102,8 +109,11 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
             let importContainer = Docs.StackingDocument(docs, options);
             importContainer.singleColumn = false;
             Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
-            this.props.removeDocument && this.props.removeDocument(doc);
+            !this.shouldKeep && this.props.removeDocument && this.props.removeDocument(doc);
             DocumentManager.Instance.jumpToDocument(importContainer, true);
+            this.uploadBegun = false;
+            this.quota = 1;
+            this.remaining = 1;
         }
     }
 
@@ -129,7 +139,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
     }
 
     @action
-    remove = (entry: KeyValue) => {
+    remove = (entry: ImportMetadataEntry) => {
         let index = this.entries.indexOf(entry);
         let key = entry.key;
         this.entries.splice(index, 1);
@@ -144,6 +154,8 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
         let quota = this.quota;
         let percent = `${100 - (remaining / quota * 100)}`;
         let uploadBegun = this.uploadBegun;
+        let showRemoveLabel = this.removeHover;
+        let keep = this.shouldKeep;
         percent = percent.split(".")[0];
         percent = percent.startsWith("100") ? "99" : percent;
         return (
@@ -197,6 +209,30 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                                 }}
                                 src={"/assets/loading.gif"}></img>
                         </label>
+                        <input
+                            type={"checkbox"}
+                            onChange={e => runInAction(() => this.shouldKeep = e.target.checked)}
+                            style={{
+                                margin: 0,
+                                position: "absolute",
+                                left: 10,
+                                bottom: 10,
+                                opacity: isEditing ? 0 : 1,
+                                transition: "0.4s opacity ease",
+                                pointerEvents: isEditing ? "none" : "all"
+                            }}
+                            onPointerEnter={action(() => this.removeHover = true)}
+                            onPointerLeave={action(() => this.removeHover = false)}
+                        />
+                        <p
+                            style={{
+                                position: "absolute",
+                                left: 27,
+                                bottom: 8.4,
+                                fontSize: 12,
+                                opacity: showRemoveLabel ? 1 : 0,
+                                transition: "0.4s opacity ease"
+                            }}>Template will be {keep ? "kept" : "removed"} after upload</p>
                         <div
                             style={{
                                 transition: "0.4s opacity ease",
@@ -219,6 +255,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                                 height: 25,
                                 background: "black"
                             }}
+                            title={isEditing ? "Back to Upload" : "Add Metadata"}
                             onClick={action(() => this.editingMetadata = !this.editingMetadata)}
                         />
                         <FontAwesomeIcon
@@ -252,6 +289,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                                     right: 41,
                                     top: 10
                                 }}
+                                title={"Add Metadata Entry"}
                                 onClick={this.addMetadataEntry}
                             >
                                 <FontAwesomeIcon
@@ -266,7 +304,14 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                             </div>
                             <p style={{ paddingLeft: 10, paddingTop: 8, paddingBottom: 7 }} >Add metadata to your import...</p>
                             <hr style={{ margin: "6px 10px 12px 10px" }} />
-                            {guids.map(guid => <KeyValue remove={this.remove} key={guid} ref={(el) => { if (el) this.entries.push(el); }} />)}
+                            {guids.map(guid =>
+                                <ImportMetadataEntry
+                                    key={guid}
+                                    remove={this.remove}
+                                    ref={(el) => { if (el) this.entries.push(el); }}
+                                    next={this.addMetadataEntry}
+                                />
+                            )}
                         </div>
                     </div>
                 }
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
new file mode 100644
index 000000000..3b2a6ebb5
--- /dev/null
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx	
@@ -0,0 +1,119 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { EditableView } from "../../views/EditableView";
+import { observable, action, computed } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faPlus } from "@fortawesome/free-solid-svg-icons";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { Opt } from "../../../new_fields/Doc";
+
+interface KeyValueProps {
+    remove: (self: ImportMetadataEntry) => void;
+    next: () => void;
+}
+
+const keyPlaceholder = "Key";
+const valuePlaceholder = "Value";
+
+@observer
+export default class ImportMetadataEntry extends React.Component<KeyValueProps> {
+    @observable public key = keyPlaceholder;
+    @observable public value = valuePlaceholder;
+
+    private keyRef = React.createRef<EditableView>();
+    private valueRef = React.createRef<EditableView>();
+    private checkRef = React.createRef<HTMLInputElement>();
+
+    constructor(props: KeyValueProps) {
+        super(props);
+        library.add(faPlus);
+    }
+
+    @computed
+    public get valid() {
+        return (this.key.length > 0 && this.key !== keyPlaceholder) && (this.value.length > 0 && this.value !== valuePlaceholder);
+    }
+
+    public get onDataDoc() {
+        return this.checkRef.current && this.checkRef.current.checked;
+    }
+
+    @action
+    updateKey = (newKey: string) => {
+        this.key = newKey;
+        this.keyRef.current && this.keyRef.current.setIsFocused(false);
+        this.valueRef.current && this.valueRef.current.setIsFocused(true);
+        this.key.length === 0 && (this.key = keyPlaceholder);
+        return true;
+    }
+
+    @action
+    updateValue = (newValue: string, shiftDown: boolean) => {
+        this.value = newValue;
+        this.valueRef.current && this.valueRef.current.setIsFocused(false);
+        this.value.length > 0 && shiftDown && this.props.next();
+        this.value.length === 0 && (this.value = valuePlaceholder);
+        return true;
+    }
+
+    render() {
+        let keyValueStyle: React.CSSProperties = {
+            paddingLeft: 10,
+            width: "50%",
+            opacity: this.valid ? 1 : 0.5,
+        };
+        return (
+            <div
+                style={{
+                    display: "flex",
+                    flexDirection: "row",
+                    paddingBottom: 5,
+                    paddingRight: 5,
+                    justifyContent: "center",
+                    alignItems: "center",
+                    alignContent: "center"
+                }}
+            >
+                <input
+                    ref={this.checkRef}
+                    style={{ margin: "0 10px 0 15px" }}
+                    type="checkbox"
+                    title={"Add to Data Document?"}
+                />
+                <div className={"key_container"} style={keyValueStyle}>
+                    <EditableView
+                        ref={this.keyRef}
+                        contents={this.key}
+                        SetValue={this.updateKey}
+                        GetValue={() => ""}
+                        oneLine={true}
+                    />
+                </div>
+                <div
+                    className={"value_container"}
+                    style={keyValueStyle}>
+                    <EditableView
+                        ref={this.valueRef}
+                        contents={this.value}
+                        SetValue={this.updateValue}
+                        GetValue={() => ""}
+                        oneLine={true}
+                    />
+                </div>
+                <div onClick={() => this.props.remove(this)} title={"Delete Entry"}>
+                    <FontAwesomeIcon
+                        icon={faPlus}
+                        color={"red"}
+                        size={"1x"}
+                        style={{
+                            marginLeft: 15,
+                            marginRight: 15,
+                            transform: "rotate(45deg)"
+                        }}
+                    />
+                </div>
+            </div>
+        );
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/util/Import & Export/KeyValue.tsx b/src/client/util/Import & Export/KeyValue.tsx
deleted file mode 100644
index 15ef25f89..000000000
--- a/src/client/util/Import & Export/KeyValue.tsx	
+++ /dev/null
@@ -1,79 +0,0 @@
-import React = require("react");
-import { observer } from "mobx-react";
-import { EditableView } from "../../views/EditableView";
-import { observable, action } from "mobx";
-
-interface KeyValueProps {
-    remove: (self: KeyValue) => void;
-}
-
-@observer
-export default class KeyValue extends React.Component<KeyValueProps> {
-    @observable public key = "Key";
-    @observable public value = "Value";
-
-    @action
-    updateKey = (newKey: string) => {
-        this.key = newKey;
-        return true;
-    }
-
-    @action
-    updateValue = (newValue: string) => {
-        this.value = newValue;
-        return true;
-    }
-
-    render() {
-        let keyValueStyle = { paddingLeft: 10, width: "50%" };
-        let keySpecified = (this.key.length > 0 && this.key !== "Key");
-        return (
-            <div
-                style={{
-                    display: "flex",
-                    flexDirection: "row",
-                    paddingBottom: 5,
-                    paddingRight: 5,
-                    justifyContent: "center",
-                    alignItems: "center",
-                    alignContent: "center"
-                }}
-                onClick={() => this.props.remove(this)}
-            >
-                <input type="checkbox" />
-                <div className={"key_container"} style={keyValueStyle}>
-                    <EditableView
-                        contents={this.key}
-                        SetValue={this.updateKey}
-                        GetValue={() => this.key}
-                        oneLine={true}
-                    />
-                </div>
-                <div
-                    className={"value_container"}
-                    style={{
-                        opacity: keySpecified ? 1 : 0.5,
-                        pointerEvents: keySpecified ? "all" : "none",
-                        ...keyValueStyle
-                    }}>
-                    <EditableView
-                        contents={this.value}
-                        SetValue={this.updateValue}
-                        GetValue={() => this.value}
-                        oneLine={true}
-                    />
-                </div>
-                <div style={{
-                    borderRadius: "50%",
-                    width: 10,
-                    height: 10,
-                    background: "red",
-                    marginLeft: 15,
-                    marginRight: 15
-                }} />
-
-            </div>
-        );
-    }
-
-}
\ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 97a2d19dd..ea6f48d0f 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -14,7 +14,7 @@ export interface EditableProps {
      * @param value - The string entered by the user to set the value to
      * @returns `true` if setting the value was successful, `false` otherwise
      *  */
-    SetValue(value: string): boolean;
+    SetValue(value: string, shiftDown?: boolean): boolean;
 
     OnFillDown?(value: string): void;
 
@@ -52,7 +52,7 @@ export class EditableView extends React.Component<EditableProps> {
             this.props.OnTab && this.props.OnTab();
         } else if (e.key === "Enter") {
             if (!e.ctrlKey) {
-                if (this.props.SetValue(e.currentTarget.value)) {
+                if (this.props.SetValue(e.currentTarget.value, e.shiftKey)) {
                     this._editing = false;
                 }
             } else if (this.props.OnFillDown) {
@@ -76,6 +76,11 @@ export class EditableView extends React.Component<EditableProps> {
         e.stopPropagation();
     }
 
+    @action
+    setIsFocused = (value: boolean) => {
+        this._editing = value;
+    }
+
     render() {
         if (this._editing) {
             return <input className="editableView-input" defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus
-- 
cgit v1.2.3-70-g09d2


From 87a3063fa0d0726334125209e74930ebf8422a44 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 6 Jul 2019 03:50:33 -0400
Subject: fixed formatting, reset and document persistence

---
 .../util/Import & Export/DirectoryImportBox.tsx    | 129 ++++++++++++++-------
 .../util/Import & Export/ImportMetadataEntry.tsx   |  42 ++++++-
 src/client/views/Main.tsx                          |   2 +-
 3 files changed, 125 insertions(+), 48 deletions(-)

(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 88ce9c0e9..b75944745 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -1,9 +1,9 @@
 import "fs";
 import React = require("react");
-import { Doc, Opt } from "../../../new_fields/Doc";
+import { Doc, Opt, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
 import { DocServer } from "../../DocServer";
 import { RouteStore } from "../../../server/RouteStore";
-import { action, observable, autorun, runInAction } from "mobx";
+import { action, observable, autorun, runInAction, computed } from "mobx";
 import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
 import Measure, { ContentRect } from "react-measure";
 import { library } from '@fortawesome/fontawesome-svg-core';
@@ -11,9 +11,13 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { faArrowUp, faTag, faPlus } from '@fortawesome/free-solid-svg-icons';
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { observer } from "mobx-react";
-import ImportMetadataEntry from "./ImportMetadataEntry";
+import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry";
 import { Utils } from "../../../Utils";
 import { DocumentManager } from "../DocumentManager";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { List } from "../../../new_fields/List";
+import { Cast, BoolCast, NumCast } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
 
 @observer
 export default class DirectoryImportBox extends React.Component<FieldViewProps> {
@@ -22,27 +26,45 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
     @observable private left = 0;
     private dimensions = 50;
 
-    @observable private editingMetadata = false;
-    @observable private metadata_guids: string[] = [];
     @observable private entries: ImportMetadataEntry[] = [];
 
     @observable private quota = 1;
     @observable private remaining = 1;
 
-    @observable private uploadBegun = false;
+    @observable private uploading = false;
     @observable private removeHover = false;
-    @observable private shouldKeep = false;
 
     public static LayoutString() { return FieldView.LayoutString(DirectoryImportBox); }
 
     constructor(props: FieldViewProps) {
         super(props);
         library.add(faArrowUp, faTag, faPlus);
+        let doc = this.props.Document;
+        this.editingMetadata = this.editingMetadata || false;
+        this.persistent = this.persistent || false;
+        !Cast(doc.data, listSpec(Doc)) && (doc.data = new List<Doc>());
+    }
+
+    @computed
+    private get editingMetadata() {
+        return BoolCast(this.props.Document.editingMetadata);
+    }
+
+    private set editingMetadata(value: boolean) {
+        this.props.Document.editingMetadata = value;
+    }
+
+    @computed
+    private get persistent() {
+        return BoolCast(this.props.Document.persistent);
+    }
+
+    private set persistent(value: boolean) {
+        this.props.Document.persistent = value;
     }
 
-    @action
     handleSelection = async (e: React.ChangeEvent<HTMLInputElement>) => {
-        this.uploadBegun = true;
+        runInAction(() => this.uploading = true);
 
         let promises: Promise<void>[] = [];
         let docs: Doc[] = [];
@@ -58,7 +80,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
             file && validated.push(file);
         }
 
-        this.quota = validated.length;
+        runInAction(() => this.quota = validated.length);
 
         for (let uploaded_file of validated) {
             if (!uploaded_file) {
@@ -70,15 +92,14 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
             let dropFileName = uploaded_file ? uploaded_file.name : "-empty-";
             let type = uploaded_file.type;
 
-            this.remaining++;
+            runInAction(() => this.remaining++);
 
             let prom = fetch(DocServer.prepend(RouteStore.upload), {
                 method: 'POST',
                 body: formData
             }).then(async (res: Response) => {
                 (await res.json()).map(action((file: any) => {
-                    let path = DocServer.prepend(file);
-                    let docPromise = Docs.getDocumentFromType(type, path, { nativeWidth: 300, width: 300, title: dropFileName });
+                    let docPromise = Docs.getDocumentFromType(type, DocServer.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName });
                     docPromise.then(doc => {
                         doc && docs.push(doc) && runInAction(() => this.remaining--);
                     });
@@ -97,24 +118,30 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
         });
 
         let doc = this.props.Document;
+        let height: number = NumCast(doc.height) || 0;
+        let offset: number = this.persistent ? (height === 0 ? 0 : height + 30) : 0;
         let options: DocumentOptions = {
             title: `Import of ${directory}`,
             width: 1105,
             height: 500,
-            x: Doc.GetT(doc, "x", "number"),
-            y: Doc.GetT(doc, "y", "number")
+            x: NumCast(doc.x),
+            y: NumCast(doc.y) + offset
         };
         let parent = this.props.ContainingCollectionView;
         if (parent) {
             let importContainer = Docs.StackingDocument(docs, options);
             importContainer.singleColumn = false;
             Doc.AddDocToList(Doc.GetProto(parent.props.Document), "data", importContainer);
-            !this.shouldKeep && this.props.removeDocument && this.props.removeDocument(doc);
+            !this.persistent && this.props.removeDocument && this.props.removeDocument(doc);
             DocumentManager.Instance.jumpToDocument(importContainer, true);
-            this.uploadBegun = false;
+
+        }
+
+        runInAction(() => {
+            this.uploading = false;
             this.quota = 1;
             this.remaining = 1;
-        }
+        });
     }
 
     componentDidMount() {
@@ -134,30 +161,43 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
     }
 
     @action
-    addMetadataEntry = () => {
-        this.metadata_guids.push(Utils.GenerateGuid());
+    addMetadataEntry = async () => {
+        let entryDoc = new Doc();
+        entryDoc.checked = false;
+        entryDoc.key = keyPlaceholder;
+        entryDoc.value = valuePlaceholder;
+        Doc.AddDocToList(this.props.Document, "data", entryDoc);
     }
 
     @action
-    remove = (entry: ImportMetadataEntry) => {
-        let index = this.entries.indexOf(entry);
-        let key = entry.key;
-        this.entries.splice(index, 1);
-        this.metadata_guids.splice(this.metadata_guids.indexOf(key), 1);
+    remove = async (entry: ImportMetadataEntry) => {
+        let metadata = await DocListCastAsync(this.props.Document.data);
+        if (metadata) {
+            let index = this.entries.indexOf(entry);
+            if (index !== -1) {
+                runInAction(() => this.entries.splice(index, 1));
+                index = metadata.indexOf(entry.props.Document);
+                if (index !== -1) {
+                    metadata.splice(index, 1);
+                }
+            }
+
+        }
     }
 
     render() {
         let dimensions = 50;
-        let guids = this.metadata_guids.map(el => el);
+        let entries = DocListCast(this.props.Document.data);
         let isEditing = this.editingMetadata;
         let remaining = this.remaining;
         let quota = this.quota;
-        let percent = `${100 - (remaining / quota * 100)}`;
-        let uploadBegun = this.uploadBegun;
+        let uploading = this.uploading;
         let showRemoveLabel = this.removeHover;
-        let keep = this.shouldKeep;
+        let persistent = this.persistent;
+        let percent = `${100 - (remaining / quota * 100)}`;
         percent = percent.split(".")[0];
         percent = percent.startsWith("100") ? "99" : percent;
+        let marginOffset = (percent.length === 1 ? 5 : 0) - 1.6;
         return (
             <Measure offset onResize={this.preserveCentering}>
                 {({ measureRef }) =>
@@ -192,7 +232,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                                 position: "absolute",
                                 left: this.left + 12.6,
                                 top: this.top + 11,
-                                opacity: uploadBegun ? 0 : 1,
+                                opacity: uploading ? 0 : 1,
                                 transition: "0.4s opacity ease"
                             }}>
                                 <FontAwesomeIcon icon={faArrowUp} color="#FFFFFF" size={"2x"} />
@@ -202,7 +242,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                                     width: 80,
                                     height: 80,
                                     transition: "0.4s opacity ease",
-                                    opacity: uploadBegun ? 0.7 : 0,
+                                    opacity: uploading ? 0.7 : 0,
                                     position: "absolute",
                                     top: this.top - 15,
                                     left: this.left - 15
@@ -211,16 +251,17 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                         </label>
                         <input
                             type={"checkbox"}
-                            onChange={e => runInAction(() => this.shouldKeep = e.target.checked)}
+                            onChange={e => runInAction(() => this.persistent = e.target.checked)}
                             style={{
                                 margin: 0,
                                 position: "absolute",
                                 left: 10,
                                 bottom: 10,
-                                opacity: isEditing ? 0 : 1,
+                                opacity: isEditing || uploading ? 0 : 1,
                                 transition: "0.4s opacity ease",
-                                pointerEvents: isEditing ? "none" : "all"
+                                pointerEvents: isEditing || uploading ? "none" : "all"
                             }}
+                            checked={this.persistent}
                             onPointerEnter={action(() => this.removeHover = true)}
                             onPointerLeave={action(() => this.removeHover = false)}
                         />
@@ -232,18 +273,18 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                                 fontSize: 12,
                                 opacity: showRemoveLabel ? 1 : 0,
                                 transition: "0.4s opacity ease"
-                            }}>Template will be {keep ? "kept" : "removed"} after upload</p>
+                            }}>Template will be <span style={{ textDecoration: "underline", textDecorationColor: persistent ? "green" : "red", color: persistent ? "green" : "red" }}>{persistent ? "kept" : "removed"}</span> after upload</p>
                         <div
                             style={{
                                 transition: "0.4s opacity ease",
-                                opacity: uploadBegun ? 1 : 0,
+                                opacity: uploading ? 1 : 0,
                                 pointerEvents: "none",
                                 position: "absolute",
                                 left: 10,
                                 top: this.top + 12.3,
                                 fontSize: 18,
                                 color: "white",
-                                marginLeft: this.left - 1.6
+                                marginLeft: this.left + marginOffset
                             }}>{percent}%</div>
                         <div
                             style={{
@@ -253,7 +294,10 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                                 borderRadius: "50%",
                                 width: 25,
                                 height: 25,
-                                background: "black"
+                                background: "black",
+                                pointerEvents: uploading ? "none" : "all",
+                                opacity: uploading ? 0 : 1,
+                                transition: "0.4s opacity ease"
                             }}
                             title={isEditing ? "Back to Upload" : "Add Metadata"}
                             onClick={action(() => this.editingMetadata = !this.editingMetadata)}
@@ -263,7 +307,9 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                                 pointerEvents: "none",
                                 position: "absolute",
                                 right: isEditing ? 16.3 : 14.5,
-                                top: isEditing ? 15.4 : 16
+                                top: isEditing ? 15.4 : 16,
+                                opacity: uploading ? 0 : 1,
+                                transition: "0.4s opacity ease"
                             }}
                             icon={isEditing ? faArrowUp : faTag}
                             color="#FFFFFF"
@@ -304,9 +350,10 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                             </div>
                             <p style={{ paddingLeft: 10, paddingTop: 8, paddingBottom: 7 }} >Add metadata to your import...</p>
                             <hr style={{ margin: "6px 10px 12px 10px" }} />
-                            {guids.map(guid =>
+                            {entries.map(doc =>
                                 <ImportMetadataEntry
-                                    key={guid}
+                                    Document={doc}
+                                    key={doc[Id]}
                                     remove={this.remove}
                                     ref={(el) => { if (el) this.entries.push(el); }}
                                     next={this.addMetadataEntry}
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index 3b2a6ebb5..f5198c39b 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx	
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx	
@@ -5,20 +5,20 @@ import { observable, action, computed } from "mobx";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import { faPlus } from "@fortawesome/free-solid-svg-icons";
 import { library } from '@fortawesome/fontawesome-svg-core';
-import { Opt } from "../../../new_fields/Doc";
+import { Opt, Doc } from "../../../new_fields/Doc";
+import { StrCast, BoolCast } from "../../../new_fields/Types";
 
 interface KeyValueProps {
+    Document: Doc;
     remove: (self: ImportMetadataEntry) => void;
     next: () => void;
 }
 
-const keyPlaceholder = "Key";
-const valuePlaceholder = "Value";
+export const keyPlaceholder = "Key";
+export const valuePlaceholder = "Value";
 
 @observer
 export default class ImportMetadataEntry extends React.Component<KeyValueProps> {
-    @observable public key = keyPlaceholder;
-    @observable public value = valuePlaceholder;
 
     private keyRef = React.createRef<EditableView>();
     private valueRef = React.createRef<EditableView>();
@@ -34,8 +34,36 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
         return (this.key.length > 0 && this.key !== keyPlaceholder) && (this.value.length > 0 && this.value !== valuePlaceholder);
     }
 
+    @computed
+    private get backing() {
+        return this.props.Document;
+    }
+
+    @computed
     public get onDataDoc() {
-        return this.checkRef.current && this.checkRef.current.checked;
+        return BoolCast(this.backing.checked);
+    }
+
+    public set onDataDoc(value: boolean) {
+        this.backing.checked = value;
+    }
+
+    @computed
+    public get key() {
+        return StrCast(this.backing.key);
+    }
+
+    public set key(value: string) {
+        this.backing.key = value;
+    }
+
+    @computed
+    public get value() {
+        return StrCast(this.backing.value);
+    }
+
+    public set value(value: string) {
+        this.backing.value = value;
     }
 
     @action
@@ -75,10 +103,12 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
                 }}
             >
                 <input
+                    onChange={e => this.onDataDoc = e.target.checked}
                     ref={this.checkRef}
                     style={{ margin: "0 10px 0 15px" }}
                     type="checkbox"
                     title={"Add to Data Document?"}
+                    checked={this.onDataDoc}
                 />
                 <div className={"key_container"} style={keyValueStyle}>
                     <EditableView
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 3d9750a85..281d9159b 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -8,4 +8,4 @@ import * as React from 'react';
     await Docs.initProtos();
     await CurrentUserUtils.loadCurrentUser();
     ReactDOM.render(<MainView />, document.getElementById('root'));
-})();
+})();
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From a1bbdbbcfb9ad704c42fae48bfe88a720803f756 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 6 Jul 2019 12:03:25 -0400
Subject: added embedding of size and modified time

---
 .../util/Import & Export/DirectoryImportBox.tsx    | 23 +++++++++++++++-------
 1 file changed, 16 insertions(+), 7 deletions(-)

(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 b75944745..093cee168 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -19,6 +19,8 @@ import { List } from "../../../new_fields/List";
 import { Cast, BoolCast, NumCast } from "../../../new_fields/Types";
 import { listSpec } from "../../../new_fields/Schema";
 
+const unsupported = ["text/html", "text/plain"];
+
 @observer
 export default class DirectoryImportBox extends React.Component<FieldViewProps> {
     private selector = React.createRef<HTMLInputElement>();
@@ -77,21 +79,24 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
         let validated: File[] = [];
         for (let i = 0; i < files.length; i++) {
             let file = files.item(i);
-            file && validated.push(file);
+            console.log(file);
+            file && !unsupported.includes(file.type) && validated.push(file);
         }
 
         runInAction(() => this.quota = validated.length);
 
-        for (let uploaded_file of validated) {
-            if (!uploaded_file) {
-                continue;
-            }
+        let sizes = [];
+        let modifiedDates = [];
 
+        for (let uploaded_file of validated) {
             let formData = new FormData();
             formData.append('file', uploaded_file);
             let dropFileName = uploaded_file ? uploaded_file.name : "-empty-";
             let type = uploaded_file.type;
 
+            sizes.push(uploaded_file.size);
+            modifiedDates.push(uploaded_file.lastModified);
+
             runInAction(() => this.remaining++);
 
             let prom = fetch(DocServer.prepend(RouteStore.upload), {
@@ -99,6 +104,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                 body: formData
             }).then(async (res: Response) => {
                 (await res.json()).map(action((file: any) => {
+                    console.log(file);
                     let docPromise = Docs.getDocumentFromType(type, DocServer.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName });
                     docPromise.then(doc => {
                         doc && docs.push(doc) && runInAction(() => this.remaining--);
@@ -110,12 +116,15 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
 
         await Promise.all(promises);
 
-        docs.forEach(doc => {
+        for (let i = 0; i < docs.length; i++) {
+            let doc = docs[i];
+            doc.size = sizes[i];
+            doc.modified = modifiedDates[i];
             this.entries.forEach(entry => {
                 let target = entry.onDataDoc ? Doc.GetProto(doc) : doc;
                 target[entry.key] = entry.value;
             });
-        });
+        }
 
         let doc = this.props.Document;
         let height: number = NumCast(doc.height) || 0;
-- 
cgit v1.2.3-70-g09d2


From 5ce2d59b4544a7473a910c121daf0c34d1546214 Mon Sep 17 00:00:00 2001
From: Sam Wilkins <samwilkins333@gmail.com>
Date: Sat, 6 Jul 2019 12:20:58 -0400
Subject: removed console.log

---
 src/client/util/Import & Export/DirectoryImportBox.tsx | 1 -
 1 file changed, 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 093cee168..1a939882c 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -104,7 +104,6 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                 body: formData
             }).then(async (res: Response) => {
                 (await res.json()).map(action((file: any) => {
-                    console.log(file);
                     let docPromise = Docs.getDocumentFromType(type, DocServer.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName });
                     docPromise.then(doc => {
                         doc && docs.push(doc) && runInAction(() => this.remaining--);
-- 
cgit v1.2.3-70-g09d2


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/DocServer.ts                            |  4 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |  1 -
 src/client/views/collections/CollectionSubView.tsx |  9 ++-
 src/client/views/nodes/KeyValueBox.tsx             | 81 +++++++++++++++-------
 4 files changed, 66 insertions(+), 29 deletions(-)

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

diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index cbcf751ee..652a9b701 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -12,7 +12,9 @@ export namespace DocServer {
     const GUID: string = Utils.GenerateGuid();
 
     export function makeReadOnly() {
-        _CreateField = emptyFunction;
+        _CreateField = field => {
+            _cache[field[Id]] = field;
+        };
         _UpdateField = emptyFunction;
         _respondToUpdate = emptyFunction;
     }
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 1a939882c..ce95ba90e 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -79,7 +79,6 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
         let validated: File[] = [];
         for (let i = 0; i < files.length; i++) {
             let file = files.item(i);
-            console.log(file);
             file && !unsupported.includes(file.type) && validated.push(file);
         }
 
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index bf17088ae..a8810f336 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -19,6 +19,7 @@ import { CollectionPDFView } from "./CollectionPDFView";
 import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import React = require("react");
+import { MainView } from "../MainView";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -67,10 +68,16 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
             let email = CurrentUserUtils.email;
             let pos = { x: position[0], y: position[1] };
             if (id && email) {
-                const proto = await doc.proto;
+                const proto = Doc.GetProto(doc);
                 if (!proto) {
                     return;
                 }
+                if (proto[Id] === "collectionProto") {
+                    alert("COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...");
+                    console.log(doc);
+                    console.log(proto);
+                    throw new Error(`AHA! You were trying to set a cursor on a collection's proto, which is the original collection proto! Look at the two previously printed lines for document values!`);
+                }
                 let cursors = Cast(proto.cursors, listSpec(CursorField));
                 if (!cursors) {
                     proto.cursors = cursors = new List<CursorField>();
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 0b64d73bc4e9b081a820fe64649df22b65501e37 Mon Sep 17 00:00:00 2001
From: yipstanley <stanley_yip@brown.edu>
Date: Mon, 15 Jul 2019 13:39:16 -0400
Subject: some more upload icon changes

---
 src/client/util/Import & Export/DirectoryImportBox.tsx | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

(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 a810db0fa..c096e9ceb 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -8,7 +8,7 @@ import { FieldViewProps, FieldView } from "../../views/nodes/FieldView";
 import Measure, { ContentRect } from "react-measure";
 import { library } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { faArrowUp, faTag, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { faTag, faPlus, faCloudUploadAlt } from '@fortawesome/free-solid-svg-icons';
 import { Docs, DocumentOptions } from "../../documents/Documents";
 import { observer } from "mobx-react";
 import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from "./ImportMetadataEntry";
@@ -40,7 +40,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
 
     constructor(props: FieldViewProps) {
         super(props);
-        library.add(faArrowUp, faTag, faPlus);
+        library.add(faTag, faPlus);
         let doc = this.props.Document;
         this.editingMetadata = this.editingMetadata || false;
         this.persistent = this.persistent || false;
@@ -237,12 +237,12 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                             }} />
                             <div style={{
                                 position: "absolute",
-                                left: this.left + 12.6,
-                                top: this.top + 11,
+                                left: this.left + 8,
+                                top: this.top + 10,
                                 opacity: uploading ? 0 : 1,
                                 transition: "0.4s opacity ease"
                             }}>
-                                <FontAwesomeIcon icon={faArrowUp} color="#FFFFFF" size={"2x"} />
+                                <FontAwesomeIcon icon={faCloudUploadAlt} color="#FFFFFF" size={"2x"} />
                             </div>
                             <img
                                 style={{
@@ -318,7 +318,7 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
                                 opacity: uploading ? 0 : 1,
                                 transition: "0.4s opacity ease"
                             }}
-                            icon={isEditing ? faArrowUp : faTag}
+                            icon={isEditing ? faCloudUploadAlt : faTag}
                             color="#FFFFFF"
                             size={"1x"}
                         />
-- 
cgit v1.2.3-70-g09d2


From 5aa6bcdd4e23bb9e9d05181d0dc6b638e45e397b Mon Sep 17 00:00:00 2001
From: Tyler Schicke <tyler_schicke@brown.edu>
Date: Wed, 17 Jul 2019 20:48:14 -0400
Subject: Moved DocServer.prepend and changed how corsProxy works

---
 src/Utils.ts                                       | 13 +++++
 src/client/DocServer.ts                            |  8 ----
 src/client/util/History.ts                         |  2 +-
 .../util/Import & Export/DirectoryImportBox.tsx    |  4 +-
 src/client/util/SearchUtil.ts                      |  3 +-
 src/client/util/TooltipTextMenu.tsx                |  8 ++--
 src/client/views/MainView.tsx                      |  4 +-
 src/client/views/SearchBox.tsx                     |  3 +-
 .../views/collections/CollectionDockingView.tsx    |  2 +-
 src/client/views/collections/CollectionSubView.tsx |  7 +--
 src/client/views/nodes/DocumentView.tsx            |  4 +-
 src/client/views/nodes/FormattedTextBox.tsx        |  5 +-
 src/client/views/nodes/ImageBox.tsx                |  6 +--
 src/client/views/nodes/VideoBox.tsx                |  4 +-
 src/client/views/pdf/PDFViewer.tsx                 | 55 +++++-----------------
 src/client/views/search/SearchBox.tsx              |  6 +--
 src/mobile/ImageUpload.tsx                         |  5 +-
 .../authentication/models/current_user_utils.ts    |  5 +-
 src/server/index.ts                                |  2 +-
 19 files changed, 65 insertions(+), 81 deletions(-)

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

diff --git a/src/Utils.ts b/src/Utils.ts
index e8a80bdc3..ac6d127cc 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -2,6 +2,7 @@ import v4 = require('uuid/v4');
 import v5 = require("uuid/v5");
 import { Socket } from 'socket.io';
 import { Message } from './server/Message';
+import { RouteStore } from './server/RouteStore';
 
 export class Utils {
 
@@ -27,6 +28,18 @@ export class Utils {
         return { scale, translateX, translateY };
     }
 
+    /**
+     * A convenience method. Prepends the full path (i.e. http://localhost:1050) to the
+     * requested extension
+     * @param extension the specified sub-path to append to the window origin
+     */
+    public static prepend(extension: string): string {
+        return window.location.origin + extension;
+    }
+    public static CorsProxy(url: string): string {
+        return this.prepend(RouteStore.corsProxy + "/") + encodeURIComponent(url);
+    }
+
     public static CopyText(text: string) {
         var textArea = document.createElement("textarea");
         textArea.value = text;
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts
index 6737657c8..8c64d2b2f 100644
--- a/src/client/DocServer.ts
+++ b/src/client/DocServer.ts
@@ -47,14 +47,6 @@ export namespace DocServer {
         Utils.AddServerHandler(_socket, MessageStore.DeleteField, respondToDelete);
         Utils.AddServerHandler(_socket, MessageStore.DeleteFields, respondToDelete);
     }
-    /**
-     * A convenience method. Prepends the full path (i.e. http://localhost:1050) to the
-     * requested extension
-     * @param extension the specified sub-path to append to the window origin
-     */
-    export function prepend(extension: string): string {
-        return window.location.origin + extension;
-    }
 
     function errorFunc(): never {
         throw new Error("Can't use DocServer without calling init first");
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index cbf5b3fc8..e9ff21b22 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -129,7 +129,7 @@ export namespace HistoryUtil {
 
     function addStringifier(type: string, keys: string[], customStringifier?: (state: ParsedUrl, current: string) => string) {
         stringifiers[type] = state => {
-            let path = DocServer.prepend(`/${type}`);
+            let path = Utils.prepend(`/${type}`);
             if (customStringifier) {
                 path = customStringifier(state, path);
             }
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index c096e9ceb..75b0b52a7 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx	
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx	
@@ -98,12 +98,12 @@ export default class DirectoryImportBox extends React.Component<FieldViewProps>
 
             runInAction(() => this.remaining++);
 
-            let prom = fetch(DocServer.prepend(RouteStore.upload), {
+            let prom = fetch(Utils.prepend(RouteStore.upload), {
                 method: 'POST',
                 body: formData
             }).then(async (res: Response) => {
                 (await res.json()).map(action((file: any) => {
-                    let docPromise = Docs.Get.DocumentFromType(type, DocServer.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName });
+                    let docPromise = Docs.Get.DocumentFromType(type, Utils.prepend(file), { nativeWidth: 300, width: 300, title: dropFileName });
                     docPromise.then(doc => {
                         doc && docs.push(doc) && runInAction(() => this.remaining--);
                     });
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index abf1a7c32..ee5a83710 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -2,6 +2,7 @@ import * as rp from 'request-promise';
 import { DocServer } from '../DocServer';
 import { Doc } from '../../new_fields/Doc';
 import { Id } from '../../new_fields/FieldSymbols';
+import { Utils } from '../../Utils';
 
 export namespace SearchUtil {
     export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
@@ -29,7 +30,7 @@ export namespace SearchUtil {
     export function Search(query: string, returnDocs: false, options?: SearchParams): Promise<IdSearchResult>;
     export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) {
         query = query || "*"; //If we just have a filter query, search for * as the query
-        const result: IdSearchResult = JSON.parse(await rp.get(DocServer.prepend("/search"), {
+        const result: IdSearchResult = JSON.parse(await rp.get(Utils.prepend("/search"), {
             qs: { ...options, q: query },
         }));
         if (!returnDocs) {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 309d19016..a4c053de2 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -19,7 +19,7 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie
 import { DocumentManager } from "./DocumentManager";
 import { Id } from "../../new_fields/FieldSymbols";
 import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox";
-import { SelectionManager } from "./SelectionManager";
+import { Utils } from "../../Utils";
 
 //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
 export class TooltipTextMenu {
@@ -213,8 +213,8 @@ export class TooltipTextMenu {
                 let link = node && node.marks.find(m => m.type.name === "link");
                 if (link) {
                     let href: string = link.attrs.href;
-                    if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                        let docid = href.replace(DocServer.prepend("/doc/"), "");
+                    if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                        let docid = href.replace(Utils.prepend("/doc/"), "");
                         DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
                             if (f instanceof Doc) {
                                 if (DocumentManager.Instance.getDocumentView(f)) {
@@ -254,7 +254,7 @@ export class TooltipTextMenu {
                                 if (docView && docView.props.ContainingCollectionView) {
                                     proto.sourceContext = docView.props.ContainingCollectionView.props.Document;
                                 }
-                                linkDoc instanceof Doc && this.makeLink(DocServer.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab");
+                                linkDoc instanceof Doc && this.makeLink(Utils.prepend("/doc/" + linkDoc[Id]), ctrlKey ? "onRight" : "inTab");
                             }),
                         },
                         hideSource: false
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index ce7369220..94a4835a1 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -16,7 +16,7 @@ import { listSpec } from '../../new_fields/Schema';
 import { Cast, FieldValue, NumCast, BoolCast, StrCast } from '../../new_fields/Types';
 import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
 import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnOne, returnTrue } from '../../Utils';
+import { emptyFunction, returnOne, returnTrue, Utils } from '../../Utils';
 import { DocServer } from '../DocServer';
 import { Docs } from '../documents/Documents';
 import { SetupDrag } from '../util/DragManager';
@@ -433,7 +433,7 @@ export class MainView extends React.Component {
         return [
             this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <FilterBox /> </div> : null,
             <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
-                <button onClick={() => window.location.assign(DocServer.prepend(RouteStore.logout))}>Log Out</button></div>
+                <button onClick={() => window.location.assign(Utils.prepend(RouteStore.logout))}>Log Out</button></div>
         ];
 
     }
diff --git a/src/client/views/SearchBox.tsx b/src/client/views/SearchBox.tsx
index 1b9be841f..33cb63df5 100644
--- a/src/client/views/SearchBox.tsx
+++ b/src/client/views/SearchBox.tsx
@@ -13,6 +13,7 @@ import { Docs } from '../documents/Documents';
 import { SetupDrag } from '../util/DragManager';
 import { SearchItem } from './search/SearchItem';
 import "./SearchBox.scss";
+import { Utils } from '../../Utils';
 
 library.add(faSearch);
 library.add(faObjectGroup);
@@ -47,7 +48,7 @@ export class SearchBox extends React.Component {
 
     @action
     getResults = async (query: string) => {
-        let response = await rp.get(DocServer.prepend('/search'), {
+        let response = await rp.get(Utils.prepend('/search'), {
             qs: {
                 query
             }
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index a193ff677..ba7903419 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -465,7 +465,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
             .off('click') //unbind the current click handler
             .click(action(function () {
                 stack.config.fixed = !stack.config.fixed;
-                // var url = DocServer.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
+                // var url = Utils.prepend("/doc/" + stack.contentItems[0].tab.contentItem.config.props.documentId);
                 // let win = window.open(url, stack.contentItems[0].tab.title, "width=300,height=400");
             }));
     }
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 5d5189336..2ddefb3c0 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -20,6 +20,7 @@ import { CollectionVideoView } from "./CollectionVideoView";
 import { CollectionView } from "./CollectionView";
 import React = require("react");
 import { MainView } from "../MainView";
+import { Utils } from "../../../Utils";
 
 export interface CollectionViewProps extends FieldViewProps {
     addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -164,7 +165,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 } else {
                     let path = window.location.origin + "/doc/";
                     if (text.startsWith(path)) {
-                        let docid = text.replace(DocServer.prepend("/doc/"), "").split("?")[0];
+                        let docid = text.replace(Utils.prepend("/doc/"), "").split("?")[0];
                         DocServer.GetRefField(docid).then(f => {
                             if (f instanceof Doc) {
                                 if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
@@ -193,7 +194,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                 if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
                     let str: string;
                     let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
-                        .then(action((s: string) => rp.head(DocServer.prepend(RouteStore.corsProxy + "/" + (str = s)))))
+                        .then(action((s: string) => rp.head(Utils.CorsProxy(str = s))))
                         .then(result => {
                             let type = result["content-type"];
                             if (type) {
@@ -219,7 +220,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
                     }).then(async (res: Response) => {
                         (await res.json()).map(action((file: any) => {
                             let full = { ...options, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300, width: 300, title: dropFileName };
-                            let path = DocServer.prepend(file);
+                            let path = Utils.prepend(file);
                             Docs.Get.DocumentFromType(type, path, full).then(doc => doc && this.props.addDocument(doc));
                         }));
                     });
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 245dd319d..af1cc079b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -557,13 +557,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
         });
         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" });
-        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
+        cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(Utils.prepend("/doc/" + this.props.Document[Id])), icon: "link" });
         cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "fingerprint" });
         cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
         type User = { email: string, userDocumentId: string };
         let usersMenu: ContextMenuProps[] = [];
         try {
-            let stuff = await rp.get(DocServer.prepend(RouteStore.getUsers));
+            let stuff = await rp.get(Utils.prepend(RouteStore.getUsers));
             const users: User[] = JSON.parse(stuff);
             usersMenu = users.filter(({ email }) => email !== CurrentUserUtils.email).map(({ email, userDocumentId }) => ({
                 description: email, event: async () => {
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 066cc40e2..99801ecff 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -35,6 +35,7 @@ import "./FormattedTextBox.scss";
 import React = require("react");
 import { DateField } from '../../../new_fields/DateField';
 import { thisExpression } from 'babel-types';
+import { Utils } from '../../../Utils';
 
 library.add(faEdit);
 library.add(faSmile);
@@ -311,8 +312,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
                 href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href;
             }
             if (href) {
-                if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
-                    this._linkClicked = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
+                if (href.indexOf(Utils.prepend("/doc/")) === 0) {
+                    this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0];
                     if (this._linkClicked) {
                         DocServer.GetRefField(this._linkClicked).then(async linkDoc => {
                             if (linkDoc instanceof Doc) {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ebe3732e6..4ffd265d7 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -165,12 +165,12 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
             recorder.ondataavailable = async function (e: any) {
                 const formData = new FormData();
                 formData.append("file", e.data);
-                const res = await fetch(DocServer.prepend(RouteStore.upload), {
+                const res = await fetch(Utils.prepend(RouteStore.upload), {
                     method: 'POST',
                     body: formData
                 });
                 const files = await res.json();
-                const url = DocServer.prepend(files[0]);
+                const url = Utils.prepend(files[0]);
                 // upload to server with known URL 
                 let audioDoc = Docs.Create.AudioDocument(url, { title: "audio test", x: NumCast(self.props.Document.x), y: NumCast(self.props.Document.y), width: 200, height: 32 });
                 audioDoc.embed = true;
@@ -327,7 +327,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
         let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx
         let nativeWidth = FieldValue(this.Document.nativeWidth, pw);
         let nativeHeight = FieldValue(this.Document.nativeHeight, 0);
-        let paths: string[] = [window.origin + RouteStore.corsProxy + "/" + "http://www.cs.brown.edu/~bcz/noImage.png"];
+        let paths: string[] = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
         // this._curSuffix = "";
         // if (w > 20) {
         Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey);
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 8b8e500c4..0f2d18f6b 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -122,7 +122,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
 
     public static async convertDataUri(imageUri: string, returnedFilename: string) {
         try {
-            let posting = DocServer.prepend(RouteStore.dataUriToImage);
+            let posting = Utils.prepend(RouteStore.dataUriToImage);
             const returnedUri = await rp.post(posting, {
                 body: {
                     uri: imageUri,
@@ -164,7 +164,7 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
                     let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
                     VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => {
                         if (returnedFilename) {
-                            let url = DocServer.prepend(returnedFilename);
+                            let url = Utils.prepend(returnedFilename);
                             let imageSummary = Docs.Create.ImageDocument(url, {
                                 x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
                                 width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx
index 699a1ffd7..b7ded7e06 100644
--- a/src/client/views/pdf/PDFViewer.tsx
+++ b/src/client/views/pdf/PDFViewer.tsx
@@ -4,23 +4,18 @@ import * as Pdfjs from "pdfjs-dist";
 import "pdfjs-dist/web/pdf_viewer.css";
 import * as rp from "request-promise";
 import { Dictionary } from "typescript-collections";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
 import { Id } from "../../../new_fields/FieldSymbols";
 import { List } from "../../../new_fields/List";
-import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../new_fields/Types";
-import { emptyFunction } from "../../../Utils";
-import { DocServer } from "../../DocServer";
-import { Docs, DocUtils, DocumentOptions } from "../../documents/Documents";
-import { DocumentManager } from "../../util/DocumentManager";
+import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
+import { emptyFunction, Utils } from "../../../Utils";
+import { Docs, DocUtils } from "../../documents/Documents";
 import { DragManager } from "../../util/DragManager";
-import { DocumentView } from "../nodes/DocumentView";
-import { PDFBox, handleBackspace } from "../nodes/PDFBox";
+import { PDFBox } from "../nodes/PDFBox";
 import Page from "./Page";
 import "./PDFViewer.scss";
 import React = require("react");
-import PDFMenu from "./PDFMenu";
-import { UndoManager } from "../../util/UndoManager";
-import { CompileScript, CompiledScript, CompileResult } from "../../util/Scripting";
+import { CompileScript, CompileResult } from "../../util/Scripting";
 import { ScriptField } from "../../../new_fields/ScriptField";
 import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
 import Annotation from "./Annotation";
@@ -90,16 +85,12 @@ export class Viewer extends React.Component<IViewerProps> {
     private _annotationReactionDisposer?: IReactionDisposer;
     private _dropDisposer?: DragManager.DragDropDisposer;
     private _filterReactionDisposer?: IReactionDisposer;
-    private _activeReactionDisposer?: IReactionDisposer;
     private _viewer: React.RefObject<HTMLDivElement>;
     private _mainCont: React.RefObject<HTMLDivElement>;
     private _pdfViewer: any;
     // private _textContent: Pdfjs.TextContent[] = [];
     private _pdfFindController: any;
     private _searchString: string = "";
-    private _rendered: boolean = false;
-    private _pageIndex: number = -1;
-    private _matchIndex: number = 0;
 
     constructor(props: IViewerProps) {
         super(props);
@@ -135,23 +126,6 @@ export class Viewer extends React.Component<IViewerProps> {
             },
             { fireImmediately: true });
 
-        this._activeReactionDisposer = reaction(
-            () => this.props.parent.props.active(),
-            () => {
-                runInAction(() => {
-                    if (!this.props.parent.props.active()) {
-                        this._searching = false;
-                        this._pdfFindController = null;
-                        if (this._viewer.current) {
-                            let cns = this._viewer.current.childNodes;
-                            for (let i = cns.length - 1; i >= 0; i--) {
-                                cns.item(i).remove();
-                            }
-                        }
-                    }
-                });
-            }
-        );
 
         if (this.props.parent.props.ContainingCollectionView) {
             this._filterReactionDisposer = reaction(
@@ -346,7 +320,7 @@ export class Viewer extends React.Component<IViewerProps> {
             this._isPage[page] = "image";
             const address = this.props.url;
             try {
-                let res = JSON.parse(await rp.get(DocServer.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
+                let res = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${address.substring("files/".length, address.length - ".pdf".length)}-${page + 1}.PNG`)));
                 runInAction(() => this._visibleElements[page] =
                     <img key={res.path} src={res.path} onError={handleError}
                         style={{ width: `${parseInt(res.width) * scale}px`, height: `${parseInt(res.height) * scale}px` }} />);
@@ -476,7 +450,6 @@ export class Viewer extends React.Component<IViewerProps> {
                             phraseSearch: true,
                             query: searchString
                         });
-                    this._rendered = true;
                 });
                 container.addEventListener("pagerendered", () => {
                     console.log("rendered");
@@ -488,7 +461,6 @@ export class Viewer extends React.Component<IViewerProps> {
                             phraseSearch: true,
                             query: searchString
                         });
-                    this._rendered = true;
                 });
             }
         }
@@ -563,7 +535,6 @@ export class Viewer extends React.Component<IViewerProps> {
                     });
                     container.addEventListener("pagerendered", () => {
                         console.log("rendered");
-                        this._rendered = true;
                     });
                     this._pdfViewer.setDocument(this.props.pdf);
                     this._pdfFindController = new PDFJSViewer.PDFFindController(this._pdfViewer);
@@ -703,17 +674,17 @@ class SimpleLinkService {
     externalLinkRel: any = null;
     pdf: any = null;
 
-    navigateTo(dest: any) { }
+    navigateTo() { }
 
-    getDestinationHash(dest: any) { return "#"; }
+    getDestinationHash() { return "#"; }
 
-    getAnchorUrl(hash: any) { return "#"; }
+    getAnchorUrl() { return "#"; }
 
-    setHash(hash: any) { }
+    setHash() { }
 
-    executeNamedAction(action: any) { }
+    executeNamedAction() { }
 
-    cachePageRef(pageNum: any, pageRef: any) { }
+    cachePageRef() { }
 
     get pagesCount() {
         return this.pdf ? this.pdf.numPages : 0;
diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx
index 16c44225a..2214ac8af 100644
--- a/src/client/views/search/SearchBox.tsx
+++ b/src/client/views/search/SearchBox.tsx
@@ -9,12 +9,12 @@ import { Docs } from '../../documents/Documents';
 import { NumCast, Cast } from '../../../new_fields/Types';
 import { Doc } from '../../../new_fields/Doc';
 import { SearchItem } from './SearchItem';
-import { DocServer } from '../../DocServer';
 import * as rp from 'request-promise';
 import { Id } from '../../../new_fields/FieldSymbols';
 import { SearchUtil } from '../../util/SearchUtil';
 import { RouteStore } from '../../../server/RouteStore';
 import { FilterBox } from './FilterBox';
+import { Utils } from '../../../Utils';
 
 
 @observer
@@ -74,7 +74,7 @@ export class SearchBox extends React.Component {
 
     public static async convertDataUri(imageUri: string, returnedFilename: string) {
         try {
-            let posting = DocServer.prepend(RouteStore.dataUriToImage);
+            let posting = Utils.prepend(RouteStore.dataUriToImage);
             const returnedUri = await rp.post(posting, {
                 body: {
                     uri: imageUri,
@@ -154,7 +154,7 @@ export class SearchBox extends React.Component {
                         filteredDocs.forEach(doc => {
                             const index = this._resultsSet.get(doc);
                             const highlight = highlights[doc[Id]];
-                            const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : []
+                            const hlights = highlight ? Object.keys(highlight).map(key => key.substring(0, key.length - 2)) : [];
                             if (index === undefined) {
                                 this._resultsSet.set(doc, this._results.length);
                                 this._results.push([doc, hlights]);
diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx
index a8f94b746..33a615cbf 100644
--- a/src/mobile/ImageUpload.tsx
+++ b/src/mobile/ImageUpload.tsx
@@ -11,6 +11,7 @@ import { listSpec } from '../new_fields/Schema';
 import { List } from '../new_fields/List';
 import { observer } from 'mobx-react';
 import { observable } from 'mobx';
+import { Utils } from '../Utils';
 
 
 
@@ -57,7 +58,7 @@ class Uploader extends React.Component {
 
                         this.status = "getting user document";
 
-                        const res = await rp.get(DocServer.prepend(RouteStore.getUserDocumentId));
+                        const res = await rp.get(Utils.prepend(RouteStore.getUserDocumentId));
                         if (!res) {
                             throw new Error("No user id returned");
                         }
@@ -104,6 +105,8 @@ class Uploader extends React.Component {
 }
 
 
+DocServer.init(window.location.protocol, window.location.hostname, 4321, "image upload");
+
 ReactDOM.render((
     <Uploader />
 ),
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index e796ccb43..1c52a3f11 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -12,6 +12,7 @@ import { List } from "../../../new_fields/List";
 import { listSpec } from "../../../new_fields/Schema";
 import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
 import { RouteStore } from "../../RouteStore";
+import { Utils } from "../../../Utils";
 
 export class CurrentUserUtils {
     private static curr_email: string;
@@ -74,7 +75,7 @@ export class CurrentUserUtils {
     }
 
     public static loadCurrentUser() {
-        return rp.get(DocServer.prepend(RouteStore.getCurrUser)).then(response => {
+        return rp.get(Utils.prepend(RouteStore.getCurrUser)).then(response => {
             if (response) {
                 const result: { id: string, email: string } = JSON.parse(response);
                 return result;
@@ -87,7 +88,7 @@ export class CurrentUserUtils {
     public static async loadUserDocument({ id, email }: { id: string, email: string }) {
         this.curr_id = id;
         this.curr_email = email;
-        await rp.get(DocServer.prepend(RouteStore.getUserDocumentId)).then(id => {
+        await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => {
             if (id) {
                 return DocServer.GetRefField(id).then(async field => {
                     if (field instanceof Doc) {
diff --git a/src/server/index.ts b/src/server/index.ts
index b3859aa4f..6b4e59bfc 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -417,7 +417,7 @@ app.get(RouteStore.reset, getReset);
 app.post(RouteStore.reset, postReset);
 
 app.use(RouteStore.corsProxy, (req, res) =>
-    req.pipe(request(req.url.substring(1))).pipe(res));
+    req.pipe(request(decodeURIComponent(req.url.substring(1)))).pipe(res));
 
 app.get(RouteStore.delete, (req, res) => {
     if (release) {
-- 
cgit v1.2.3-70-g09d2

