about summary refs log tree commit diff
path: root/uploader/static
diff options
context:
space:
mode:
Diffstat (limited to 'uploader/static')
-rw-r--r--uploader/static/css/styles.css182
-rw-r--r--uploader/static/js/datatables.js69
-rw-r--r--uploader/static/js/debug.js40
-rw-r--r--uploader/static/js/files.js118
-rw-r--r--uploader/static/js/populations.js36
-rw-r--r--uploader/static/js/pubmed.js113
-rw-r--r--uploader/static/js/species.js34
-rw-r--r--uploader/static/js/utils.js27
8 files changed, 618 insertions, 1 deletions
diff --git a/uploader/static/css/styles.css b/uploader/static/css/styles.css
index a88c229..df50dec 100644
--- a/uploader/static/css/styles.css
+++ b/uploader/static/css/styles.css
@@ -1,7 +1,187 @@
+* {
+    box-sizing: border-box;
+}
+
+body {
+    margin: 0.7em;
+    display: grid;
+    grid-template-columns: 2fr 8fr;
+    grid-gap: 20px;
+
+    font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
+    font-style: normal;
+    font-size: 20px;
+}
+
+#header {
+    /* Place it in the parent element */
+    grid-column-start: 1;
+    grid-column-end: 3;
+
+    /* Define layout for the children elements */
+    display: grid;
+    grid-template-columns: 8fr 2fr;
+
+    /* Content styling */
+    background-color: #336699;
+    color: #FFFFFF;
+    border-radius: 3px;
+    min-height: 30px;
+}
+
+#header #header-text {
+    /* Place it in the parent element */
+    grid-column-start: 1;
+    grid-column-end: 2;
+
+    /* Content styling */
+    padding-left: 1em;
+}
+
+#header #header-nav {
+    /* Place it in the parent element */
+    grid-column-start: 2;
+    grid-column-end: 3;
+}
+
+#header #header-nav .nav li a {
+    /* Content styling */
+    color: #FFFFFF;
+    background: #4477AA;
+    border: solid 5px #336699;
+    border-radius: 5px;
+    font-size: 0.7em;
+    text-align: center;
+    padding: 1px 7px;
+}
+
+#nav-sidebar {
+    /* Place it in the parent element */
+    grid-column-start: 1;
+    grid-column-end: 2;
+}
+
+#nav-sidebar .nav li a:hover {
+    border-radius: 0.5em;
+}
+
+#nav-sidebar .nav .activemenu {
+    border-style: solid;
+    border-radius: 0.5em;
+    border-color: #AAAAAA;
+    background-color: #EFEFEF;
+}
+
+#main {
+    /* Place it in the parent element */
+    grid-column-start: 2;
+    grid-column-end: 3;
+
+    /* Define layout for the children elements */
+    display: grid;
+    grid-template-columns: 1fr;
+    grid-template-rows: 4em 100%;
+    grid-gap: 1em;
+}
+
+#main #pagetitle {
+    /* Place it in the parent element */
+    grid-column-start: 1;
+    grid-column-end: 3;
+
+    /* Content-styling */
+    border-radius: 3px;
+    background-color: #88BBEE;
+}
+
+#main #pagetitle .title {
+    font-size: 1.4em;
+    text-transform: capitalize;
+    padding-left: 0.5em;
+}
+
+@media screen and (max-width: 20in) {
+    #main #all-content {
+        /* Place it in the parent element */
+        grid-column-start: 1;
+        grid-column-end: 3;
+
+        /* Define layout for the children elements */
+        max-width: 80%;
+    }
+
+    #sidebar-content {
+        display: none;
+    }
+}
+
+@media screen and (min-width: 20.1in) {
+    #main #all-content {
+        /* Place it in the parent element */
+        grid-column-start: 1;
+        grid-column-end: 3;
+
+        /* Define layout for the children elements */
+        display: grid;
+        grid-template-columns: 7fr 3fr;
+        grid-gap: 1.5em;
+    }
+}
+
+#main #all-content .row {
+    margin: 0 2px;
+}
+
+#main #all-content #main-content {
+    background: #FFFFFF;
+    max-width: 950px;
+}
+
+#pagetitle .breadcrumb {
+    background: none;
+    text-transform: capitalize;
+    font-size: 0.75em;
+}
+
+#pagetitle .breadcrumb .active a {
+    color: #333333;
+}
+
+#pagetitle .breadcrumb a {
+    color: #666666;
+}
+
 .heading {
+    border-bottom: solid #EEBB88;
     text-transform: capitalize;
 }
 
-label {
+.subheading {
+    padding: 1em 0 0.1em 0.5em;
+    border-bottom: solid #88BBEE;
     text-transform: capitalize;
 }
+
+input[type="search"] {
+    border-radius: 5px;
+}
+
+.btn {
+    text-transform: Capitalize;
+}
+
+table.dataTable thead th, table.dataTable tfoot th{
+  border-right: 1px solid white;
+  color: white;
+  background-color: #369 !important;
+}
+
+table.dataTable tbody tr.selected td {
+    background-color: #ffee99 !important;
+}
+
+.form-group {
+    margin-bottom: 2em;
+    padding-bottom: 0.2em;
+    border-bottom: solid gray 1px;
+}
diff --git a/uploader/static/js/datatables.js b/uploader/static/js/datatables.js
new file mode 100644
index 0000000..82fd696
--- /dev/null
+++ b/uploader/static/js/datatables.js
@@ -0,0 +1,69 @@
+/** Handlers for events in datatables **/
+
+var addTableLength = (menuList, lengthToAdd, dataLength) => {
+    if(dataLength >= lengthToAdd) {
+        newList = structuredClone(menuList);//menuList.slice(0, menuList.length); // shallow copy
+        newList.push(lengthToAdd);
+        return newList;
+    }
+    return menuList;
+};
+
+var defaultLengthMenu = (data) => {
+    menuList = []
+    var lengths = [10, 25, 50, 100, 1000, data.length];
+    lengths.forEach((len) => {
+        menuList = addTableLength(menuList, len, data.length);
+    });
+    return menuList;
+};
+
+var buildDataTable = (tableId, data = [], columns = [], userSettings = {}) => {
+    var defaultSettings = {
+        responsive: true,
+        layout: {
+            topStart: null,
+            topEnd: null,
+            bottomStart: null,
+            bottomEnd: null,
+        },
+        select: true,
+        lengthMenu: defaultLengthMenu(data),
+        language: {
+            processing: "Processing… Please wait.",
+            loadingRecords: "Loading table data… Please wait.",
+            lengthMenu: "",
+            info: ""
+        },
+        data: data,
+        columns: columns,
+        drawCallback: (settings) => {
+            $(this[0]).find("tbody tr").each((idx, row) => {
+                var arow = $(row);
+                var checkboxOrRadio = arow.find(".chk-row-select");
+                if (checkboxOrRadio) {
+                    if (arow.hasClass("selected")) {
+                        checkboxOrRadio.prop("checked", true);
+                    } else {
+                        checkboxOrRadio.prop("checked", false);
+                    }
+                }
+            });
+        }
+    }
+    var theDataTable = $(tableId).DataTable({
+        ...defaultSettings,
+        ...userSettings
+    });
+    theDataTable.on("select", (event, datatable, type, cell, originalEvent) => {
+        datatable.rows({selected: true}).nodes().each((node, index) => {
+            $(node).find(".chk-row-select").prop("checked", true)
+        });
+    });
+    theDataTable.on("deselect", (event, datatable, type, cell, originalEvent) => {
+        datatable.rows({selected: false}).nodes().each((node, index) => {
+            $(node).find(".chk-row-select").prop("checked", false)
+        });
+    });
+    return theDataTable;
+};
diff --git a/uploader/static/js/debug.js b/uploader/static/js/debug.js
new file mode 100644
index 0000000..eb01209
--- /dev/null
+++ b/uploader/static/js/debug.js
@@ -0,0 +1,40 @@
+/**
+ * The entire purpose of this function is for use to debug values inline
+ * without changing the flow of the code too much.
+ *
+ * This **MUST** be a non-arrow function to allow access to the `arguments`
+ * object.
+ *
+ * This function expects at least one argument.
+ *
+ * If more than one argument is provided, then:
+ * a) the last argument is considered the value, and will be returned
+ * b) all other arguments will be converted to string and output
+ *
+ * If only one argument is provided, it is considered the value, and will be
+ * returned.
+ *
+ * Zero arguments is an error condition.
+ **/
+function __pk__(val) {
+    /* Handle zero arguments */
+    if (arguments.length < 1) {
+        throw new Error("Invalid arguments: Expected at least one argument.");
+    }
+
+    msg = "/********** DEBUG **********/";
+    if (arguments.length > 1) {
+        msg = Array.from(
+            arguments
+        ).slice(
+            0,
+            arguments.length - 1
+        ).map((val) => {
+            return String(val);
+        }).join("; ")
+    }
+
+    value = arguments[arguments.length - 1];
+    console.debug("/********** " + msg + " **********/", value);
+    return value;
+}
diff --git a/uploader/static/js/files.js b/uploader/static/js/files.js
new file mode 100644
index 0000000..0bde6f7
--- /dev/null
+++ b/uploader/static/js/files.js
@@ -0,0 +1,118 @@
+var readFirstNLines = (thefile, count, process_content_fns) => {
+    var reader = new FileReader();
+    if(typeof thefile !== "undefined" && thefile !== null) {
+        reader.addEventListener("load", (event) => {
+            var content = event
+                .target
+                .result
+                .split("\n")
+                .slice(0, count)
+                .map((line) => {return line.trim("\r");});
+            process_content_fns.forEach((fn) => {fn(content);});
+        });
+        reader.readAsText(thefile);
+    }
+};
+var read_first_n_lines = readFirstNLines;
+
+
+var readBinaryFile = (file) => {
+    return new Promise((resolve, reject) => {
+        var _reader = new FileReader();
+        _reader.onload = (event) => {resolve(_reader.result);};
+        _reader.readAsArrayBuffer(file);
+    });
+};
+
+
+var Uint8ArrayToHex = (arr) => {
+    var toHex = (val) => {
+        _hex = val.toString(16);
+        if(_hex.length < 2) {
+            return "0" + val;
+        }
+        return _hex;
+    };
+    _hexstr = ""
+    arr.forEach((val) => {_hexstr += toHex(val)});
+    return _hexstr
+};
+
+
+var computeFileChecksum = (file) => {
+    return readBinaryFile(file)
+        .then((content) => {
+            return window.crypto.subtle.digest(
+                "SHA-256", new Uint8Array(content));
+        }).then((digest) => {
+            return Uint8ArrayToHex(new Uint8Array(digest))
+        });
+};
+
+
+var defaultResumableHandler = (event) => {
+    throw new Error("Please provide a valid event handler!");
+};
+
+var addHandler = (resumable, handlername, handler) => {
+    if(resumable.support) {
+        resumable.on(handlername, (handler || defaultResumableHandler));
+    }
+    return resumable;
+};
+
+
+var makeResumableHandler = (handlername) => {
+    return (resumable, handler) => {
+        return addHandler(resumable, handlername, handler);
+    };
+};
+
+
+var fileSuccessHandler = makeResumableHandler("fileSuccess");
+var fileProgressHandler = makeResumableHandler("fileProgress");
+var fileAddedHandler = makeResumableHandler("fileAdded");
+var filesAddedHandler = makeResumableHandler("filesAdded");
+var filesRetryHandler = makeResumableHandler("filesRetry");
+var filesErrorHandler = makeResumableHandler("filesError");
+var uploadStartHandler = makeResumableHandler("uploadStart");
+var completeHandler = makeResumableHandler("complete");
+var progressHandler = makeResumableHandler("progress");
+var errorHandler = makeResumableHandler("error");
+
+
+var markResumableDragAndDropElement = (resumable, fileinput, droparea, browsebutton) => {
+    if(resumable.support) {
+        //Hide file input element and display drag&drop UI
+        add_class(fileinput, "visually-hidden");
+        remove_class(droparea, "visually-hidden");
+
+        // Define UI elements for browse and drag&drop
+        resumable.assignDrop(droparea);
+        resumable.assignBrowse(browsebutton);
+    }
+
+    return resumable;
+};
+
+
+var makeResumableElement = (targeturi, fileinput, droparea, uploadbutton, filetype) => {
+    var resumable = Resumable({
+        target: targeturi,
+        fileType: filetype,
+        maxFiles: 1,
+        forceChunkSize: true,
+        generateUniqueIdentifier: (file, event) => {
+            return computeFileChecksum(file).then((checksum) => {
+                var _relativePath = (file.webkitRelativePath
+                                     || file.relativePath
+                                     || file.fileName
+                                     || file.name);
+                return checksum + "-" + _relativePath.replace(
+                    /[^a-zA-Z0-9_-]/img, "");
+            });
+        }
+    });
+
+    return resumable;
+};
diff --git a/uploader/static/js/populations.js b/uploader/static/js/populations.js
new file mode 100644
index 0000000..89ededa
--- /dev/null
+++ b/uploader/static/js/populations.js
@@ -0,0 +1,36 @@
+$(() => {
+    var populationsDataTable = buildDataTable(
+        "#tbl-select-population",
+        JSON.parse(
+            $("#tbl-select-population").attr("data-populations-list")),
+        [
+            {
+                data: (apopulation) => {
+                    return `<input type="radio" name="population_id"`
+                        + `id="rdo_population_id_${apopulation.InbredSetId}" `
+                        + `value="${apopulation.InbredSetId}" `
+                        + `class="chk-row-select">`;
+                }
+            },
+            {
+                searchable: true,
+                data: (apopulation) => {
+                    return `${apopulation.FullName} (${apopulation.InbredSetName})`;
+                }
+            }
+        ],
+        {
+            select: "single",
+            paging: true,
+            scrollY: 700,
+            deferRender: true,
+            scroller: true,
+            scrollCollapse: true,
+            layout: {
+                topStart: "info",
+                topEnd: "search",
+                bottomStart: "pageLength",
+                bottomEnd: false
+            }
+        });
+});
diff --git a/uploader/static/js/pubmed.js b/uploader/static/js/pubmed.js
new file mode 100644
index 0000000..9afd4c3
--- /dev/null
+++ b/uploader/static/js/pubmed.js
@@ -0,0 +1,113 @@
+var extract_details = (pubmed_id, details) => {
+    var months = {
+        "jan": "January",
+        "feb": "February",
+        "mar": "March",
+        "apr": "April",
+        "may": "May",
+        "jun": "June",
+        "jul": "July",
+        "aug": "August",
+        "sep": "September",
+        "oct": "October",
+        "nov": "November",
+        "dec": "December"
+    };
+    var _date = details[pubmed_id].pubdate.split(" ");
+    return {
+        "authors": details[pubmed_id].authors.map((authobj) => {
+            return authobj.name;
+        }),
+        "title": details[pubmed_id].title,
+        "journal": details[pubmed_id].fulljournalname,
+        "volume": details[pubmed_id].volume,
+        "pages": details[pubmed_id].pages,
+        "month": _date.length > 1 ? months[_date[1].toLowerCase()] : "jan",
+        "year": _date[0],
+    };
+};
+
+var update_publication_details = (details) => {
+    Object.entries(details).forEach((entry) => {;
+                                                switch(entry[0]) {
+                                                case "authors":
+                                                    $("#txt-publication-authors").val(entry[1].join(", "));
+                                                    break;
+                                                case "month":
+                                                    $("#select-publication-month")
+                                                        .children("option")
+                                                        .each((index, child) => {
+                                                            console.debug(entry[1].toLowerCase());
+                                                            child.selected = child.value == entry[1].toLowerCase();
+                                                        });
+                                                default:
+                                                    $("#txt-publication-" + entry[0]).val(entry[1]);
+                                                    break;
+                                                }
+                                               });
+};
+
+var fetch_publication_abstract = (pubmed_id, pub_details) => {
+    $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi",
+           {
+               "method": "GET",
+               "data": {
+                   "db": "pubmed",
+                   "id": pubmed_id,
+                   "rettype": "abstract",
+                   "retmode": "xml"
+               },
+               "success": (data, textStatus, jqXHR) => {
+                   update_publication_details({
+                       ...pub_details,
+                       ...{
+                           "abstract": Array.from(data
+                                                  .getElementsByTagName(
+                                                      "Abstract")[0]
+                                                  .children)
+                               .map((elt) => {return elt.textContent.trim();})
+                               .join("\r\n")
+                       }});
+               },
+               "error": (jqXHR, textStatus, errorThrown) => {},
+               "complete": (jqXHR, textStatus) => {},
+               "dataType": "xml"
+           });
+};
+
+var fetch_publication_details = (pubmed_id, complete_thunks) => {
+    error_display = $("#search-pubmed-id-error");
+    error_display.text("");
+    add_class(error_display, "visually-hidden");
+    $.ajax("https://eutils.ncbi.nlm.nih.gov/entrez/eutils/esummary.fcgi",
+           {
+               "method": "GET",
+               "data": {"db": "pubmed", "id": pubmed_id, "format": "json"},
+               "success": (data, textStatus, jqXHR) => {
+                   // process and update publication details
+                   hasError = (
+                       Object.hasOwn(data, "error") ||
+                           Object.hasOwn(data.result[pubmed_id], "error"));
+                   if(hasError) {
+                       error_display.text(
+                           "There was an error fetching a publication with " +
+                               "the given PubMed ID! The error received " +
+                               "was: '" + (
+                                   data.error ||
+                                       data.result[pubmed_id].error) +
+                               "'. Please check ID you provided and try " +
+                               "again.");
+                       remove_class(error_display, "visually-hidden");
+                   } else {
+                       fetch_publication_abstract(
+                           pubmed_id,
+                           extract_details(pubmed_id, data.result));
+                   }
+               },
+               "error": (jqXHR, textStatus, errorThrown) => {},
+               "complete": () => {
+                   complete_thunks.forEach((thunk) => {thunk()});
+               },
+               "dataType": "json"
+           });
+};
diff --git a/uploader/static/js/species.js b/uploader/static/js/species.js
new file mode 100644
index 0000000..d42e081
--- /dev/null
+++ b/uploader/static/js/species.js
@@ -0,0 +1,34 @@
+$(() => {
+    var speciesDataTable = buildDataTable(
+        "#tbl-select-species",
+        JSON.parse(
+            $("#tbl-select-species").attr("data-species-list")),
+        [
+            {
+                data: (aspecies) => {
+                    return `<input type="radio" name="species_id"`
+                        + `id="rdo_species_id_${aspecies.SpeciesId}" `
+                        + `value="${aspecies.SpeciesId}" class="chk-row-select">`;
+                }
+            },
+            {
+                data: (aspecies) => {
+                    return `${aspecies.FullName} (${aspecies.SpeciesName})`;
+                }
+            }
+        ],
+        {
+            select: "single",
+            paging: true,
+            scrollY: 700,
+            deferRender: true,
+            scroller: true,
+            scrollCollapse: true,
+            layout: {
+                topStart: "info",
+                topEnd: "search",
+                bottomStart: "pageLength",
+                bottomEnd: false
+            }
+        });
+});
diff --git a/uploader/static/js/utils.js b/uploader/static/js/utils.js
index 045dd47..1b31661 100644
--- a/uploader/static/js/utils.js
+++ b/uploader/static/js/utils.js
@@ -8,3 +8,30 @@ function trigger_change_event(element) {
     evt = new Event("change");
     element.dispatchEvent(evt);
 }
+
+
+var remove_class = (element, classvalue) => {
+    new_classes = (element.attr("class") || "").split(" ").map((val) => {
+        return val.trim();
+    }).filter((val) => {
+        return ((val !== classvalue) &&
+                (val !== ""))
+    }).join(" ");
+
+    if(new_classes === "") {
+        element.removeAttr("class");
+    } else {
+        element.attr("class", new_classes);
+    }
+};
+
+
+var add_class = (element, classvalue) => {
+    remove_class(element, classvalue);
+    element.attr("class", (element.attr("class") || "") + " " + classvalue);
+};
+
+$(".not-implemented").click((event) => {
+    event.preventDefault();
+    alert("This feature is not implemented yet. Please bear with us.");
+});