aboutsummaryrefslogtreecommitdiff
path: root/uploader/static
diff options
context:
space:
mode:
Diffstat (limited to 'uploader/static')
-rw-r--r--uploader/static/css/custom-bootstrap.css23
-rw-r--r--uploader/static/css/styles.css187
-rw-r--r--uploader/static/css/two-column-with-separator.css27
-rw-r--r--uploader/static/images/CITGLogo.pngbin0 -> 11962 bytes
-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/select_platform.js70
-rw-r--r--uploader/static/js/species.js34
-rw-r--r--uploader/static/js/upload_progress.js97
-rw-r--r--uploader/static/js/upload_samples.js132
-rw-r--r--uploader/static/js/utils.js37
14 files changed, 983 insertions, 0 deletions
diff --git a/uploader/static/css/custom-bootstrap.css b/uploader/static/css/custom-bootstrap.css
new file mode 100644
index 0000000..67f1199
--- /dev/null
+++ b/uploader/static/css/custom-bootstrap.css
@@ -0,0 +1,23 @@
+/** Customize some bootstrap selectors **/
+.btn {
+ text-transform: capitalize;
+}
+
+.navbar-inverse {
+ background-color: #336699;
+ border-color: #080808;
+ color: #FFFFFF;
+ background-image: none;
+}
+
+.navbar-inverse .navbar-nav>li>a {
+ color: #FFFFFF;
+}
+
+.navbar-nav > li > a {
+ padding: 5px;
+}
+
+.navbar {
+ min-height: 30px;
+}
diff --git a/uploader/static/css/styles.css b/uploader/static/css/styles.css
new file mode 100644
index 0000000..df50dec
--- /dev/null
+++ b/uploader/static/css/styles.css
@@ -0,0 +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;
+}
+
+.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/css/two-column-with-separator.css b/uploader/static/css/two-column-with-separator.css
new file mode 100644
index 0000000..b6efd46
--- /dev/null
+++ b/uploader/static/css/two-column-with-separator.css
@@ -0,0 +1,27 @@
+.two-column-with-separator {
+ display: grid;
+ grid-template-columns: 9fr 1fr 9fr;
+}
+
+.two-col-sep-col1 {
+ grid-column: 1 / 2;
+}
+
+.two-col-sep-separator {
+ grid-column: 2 / 3;
+ text-align: center;
+ color: #FE3535;
+ font-weight: bolder;
+}
+
+.two-col-sep-col2 {
+ grid-column: 3 / 4;
+}
+
+.two-col-sep-col1, .two-col-sep-col2 {
+ border-style: solid;
+ border-color: #FE3535;
+ border-width: 1px;
+ border-radius: 2em;
+ padding: 2em 3em 2em 3em;
+}
diff --git a/uploader/static/images/CITGLogo.png b/uploader/static/images/CITGLogo.png
new file mode 100644
index 0000000..ae99fed
--- /dev/null
+++ b/uploader/static/images/CITGLogo.png
Binary files differ
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/select_platform.js b/uploader/static/js/select_platform.js
new file mode 100644
index 0000000..4fdd865
--- /dev/null
+++ b/uploader/static/js/select_platform.js
@@ -0,0 +1,70 @@
+function radio_column(chip) {
+ col = document.createElement("td");
+ radio = document.createElement("input");
+ radio.setAttribute("type", "radio");
+ radio.setAttribute("name", "genechipid");
+ radio.setAttribute("value", chip["GeneChipId"]);
+ radio.setAttribute("required", "required");
+ col.appendChild(radio);
+ return col;
+}
+
+function setup_genechips(genechip_data) {
+ columns = ["GeneChipId", "GeneChipName"]
+ submit_button = document.querySelector(
+ "#select-platform-form button[type='submit']");
+ elt = document.getElementById(
+ "genechips-table").getElementsByTagName("tbody")[0];
+ remove_children(elt);
+ if((genechip_data === undefined) || genechip_data.length === 0) {
+ row = document.createElement("tr");
+ col = document.createElement("td");
+ col.setAttribute("colspan", "3");
+ text = document.createTextNode("No chips found for selected species");
+ col.appendChild(text);
+ row.appendChild(col);
+ elt.appendChild(row);
+ submit_button.setAttribute("disabled", true);
+ return false;
+ }
+
+ submit_button.removeAttribute("disabled")
+ genechip_data.forEach(chip => {
+ row = document.createElement("tr");
+ row.appendChild(radio_column(chip));
+ columns.forEach(column => {
+ col = document.createElement("td");
+ content = document.createTextNode(chip[column]);
+ col.appendChild(content);
+ row.appendChild(col);
+ });
+ elt.appendChild(row);
+ });
+}
+
+function genechips() {
+ return JSON.parse(
+ document.getElementById("select-platform-form").getAttribute(
+ "data-genechips"));
+}
+
+function update_genechips(event) {
+ genec = genechips();
+
+ species_elt = document.getElementById("species");
+
+ if(event.target == species_elt) {
+ setup_genechips(genec[species_elt.value.toLowerCase()]);
+ }
+}
+
+function select_row_radio(row) {
+ radio = row.getElementsByTagName(
+ "td")[0].getElementsByTagName(
+ "input")[0];
+ if(radio === undefined) {
+ return false;
+ }
+ radio.setAttribute("checked", "checked");
+ return true;
+}
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/upload_progress.js b/uploader/static/js/upload_progress.js
new file mode 100644
index 0000000..9638b36
--- /dev/null
+++ b/uploader/static/js/upload_progress.js
@@ -0,0 +1,97 @@
+function make_processing_indicator(elt) {
+ var count = 0;
+ return function() {
+ var message = "Finalising upload and saving file: "
+ if(count > 5) {
+ count = 1;
+ }
+ for(i = 0; i < count; i++) {
+ message = message + ".";
+ }
+ elt.innerHTML = message
+ count = count + 1
+ };
+}
+
+function make_progress_updater(file, indicator_elt) {
+ var progress_bar = indicator_elt.querySelector("#progress-bar");
+ var progress_text = indicator_elt.querySelector("#progress-text");
+ var extra_text = indicator_elt.querySelector("#progress-extra-text");
+ return function(event) {
+ if(event.loaded <= file.size) {
+ var percent = Math.round((event.loaded / file.size) * 100);
+ progress_bar.value = percent
+ progress_text.innerHTML = "Uploading: " + percent + "%";
+ extra_text.setAttribute("class", "hidden")
+ }
+
+ if(event.loaded == event.total) {
+ progress_bar.value = 100;
+ progress_text.innerHTML = "Uploaded: 100%";
+ extra_text.setAttribute("class", null);
+ intv = setInterval(make_processing_indicator(extra_text), 400);
+ setTimeout(function() {clearTimeout(intv);}, 20000);
+ }
+ };
+}
+
+function setup_cancel_upload(request, indicator_elt) {
+ document.getElementById("btn-cancel-upload").addEventListener(
+ "click", function(event) {
+ event.preventDefault();
+ request.abort();
+ });
+}
+
+function setup_request(file, progress_indicator_elt) {
+ var request = new XMLHttpRequest();
+ var updater = make_progress_updater(file, progress_indicator_elt);
+ request.upload.addEventListener("progress", updater);
+ request.onload = function(event) {
+ document.location.assign(request.responseURL);
+ };
+ setup_cancel_upload(request, progress_indicator_elt)
+ return request;
+}
+
+function selected_filetype(radios) {
+ for(idx = 0; idx < radios.length; idx++) {
+ if(radios[idx].checked) {
+ return radios[idx].value;
+ }
+ }
+}
+
+function make_data_uploader(setup_formdata) {
+ return function(event) {
+ event.preventDefault();
+
+ var pindicator = document.getElementById("upload-progress-indicator");
+
+ var form = event.target;
+ var the_file = form.querySelector("input[type='file']").files[0];
+ if(the_file === undefined) {
+ form.querySelector("input[type='file']").parentElement.setAttribute(
+ "class", "invalid-input");
+ error_elt = form.querySelector("#no-file-error");
+ if(error_elt !== undefined) {
+ error_elt.setAttribute("style", "display: block;");
+ }
+ return false;
+ }
+ var formdata = setup_formdata(form);
+
+ document.getElementById("progress-filename").innerHTML = the_file.name;
+ var request = setup_request(the_file, pindicator);
+ request.open(form.getAttribute("method"), form.getAttribute("action"));
+ request.send(formdata);
+ return false;
+ }
+}
+
+
+function setup_upload_handlers(formid, datauploader) {
+ console.info("Setting up the upload handlers.")
+ upload_form = document.getElementById(formid);
+ upload_form.addEventListener("submit", datauploader);
+}
diff --git a/uploader/static/js/upload_samples.js b/uploader/static/js/upload_samples.js
new file mode 100644
index 0000000..aed536f
--- /dev/null
+++ b/uploader/static/js/upload_samples.js
@@ -0,0 +1,132 @@
+/*
+ * Read the file content and set the `data-preview-content` attribute on the
+ * file element
+ */
+function read_first_n_lines(event,
+ fileelement,
+ numlines,
+ firstlineheading = true) {
+ var thefile = fileelement.files[0];
+ var reader = new FileReader();
+ reader.addEventListener("load", (event) => {
+ var filecontent = event.target.result.split(
+ "\n").slice(
+ 0, (numlines + (firstlineheading ? 1 : 0))).map(
+ (line) => {return line.trim("\r");});
+ fileelement.setAttribute(
+ "data-preview-content", JSON.stringify(filecontent));
+ display_preview(event);
+ })
+ reader.readAsText(thefile);
+}
+
+function remove_rows(preview_table) {
+ var table_body = preview_table.getElementsByTagName("tbody")[0];
+ while(table_body.children.length > 0) {
+ table_body.removeChild(table_body.children.item(0));
+ }
+}
+
+/*
+ * Display error row
+ */
+function display_error_row(preview_table, error_message) {
+ remove_rows(preview_table);
+ row = document.createElement("tr");
+ cell = document.createElement("td");
+ cell.setAttribute("colspan", 4);
+ cell.innerHTML = error_message;
+ row.appendChild(cell);
+ preview_table.getElementsByTagName("tbody")[0].appendChild(row);
+}
+
+function strip(str, chars) {
+ var end = str.length;
+ var start = 0
+ for(var j = str.length; j > 0; j--) {
+ if(!chars.includes(str[j - 1])) {
+ break;
+ }
+ end = end - 1;
+ }
+ for(var i = 0; i < end; i++) {
+ if(!chars.includes(str[i])) {
+ break;
+ }
+ start = start + 1;
+ }
+ return str.slice(start, end);
+}
+
+function process_preview_data(preview_data, separator, delimiter) {
+ return preview_data.map((line) => {
+ return line.split(separator).map((field) => {
+ return strip(field, delimiter);
+ });
+ });
+}
+
+function render_preview(preview_table, preview_data) {
+ remove_rows(preview_table);
+ var table_body = preview_table.getElementsByTagName("tbody")[0];
+ preview_data.forEach((line) => {
+ var row = document.createElement("tr");
+ line.forEach((field) => {
+ var cell = document.createElement("td");
+ cell.innerHTML = field;
+ row.appendChild(cell);
+ });
+ table_body.appendChild(row);
+ });
+}
+
+/*
+ * Display a preview of the data, relying on the user's selection.
+ */
+function display_preview(event) {
+ var data_preview_table = document.getElementById("tbl:samples-preview");
+ remove_rows(data_preview_table);
+
+ var separator = document.getElementById("select:separator").value;
+ if(separator === "other") {
+ separator = document.getElementById("txt:separator").value;
+ }
+ if(separator == "") {
+ display_error_row(data_preview_table, "Please provide a separator.");
+ return false;
+ }
+
+ var delimiter = document.getElementById("txt:delimiter").value;
+
+ var firstlineheading = document.getElementById("chk:heading").checked;
+
+ var fileelement = document.getElementById("file:samples");
+ var preview_data = JSON.parse(
+ fileelement.getAttribute("data-preview-content") || "[]");
+ if(preview_data.length == 0) {
+ display_error_row(
+ data_preview_table,
+ "No file data to preview. Check that file is provided.");
+ }
+
+ render_preview(data_preview_table, process_preview_data(
+ preview_data.slice(0 + (firstlineheading ? 1 : 0)),
+ separator,
+ delimiter));
+}
+
+document.getElementById("chk:heading").addEventListener(
+ "change", display_preview);
+document.getElementById("select:separator").addEventListener(
+ "change", display_preview);
+document.getElementById("txt:separator").addEventListener(
+ "keyup", display_preview);
+document.getElementById("txt:delimiter").addEventListener(
+ "keyup", display_preview);
+document.getElementById("file:samples").addEventListener(
+ "change", (event) => {
+ read_first_n_lines(event,
+ document.getElementById("file:samples"),
+ 30,
+ document.getElementById("chk:heading").checked);
+ });
diff --git a/uploader/static/js/utils.js b/uploader/static/js/utils.js
new file mode 100644
index 0000000..1b31661
--- /dev/null
+++ b/uploader/static/js/utils.js
@@ -0,0 +1,37 @@
+function remove_children(element) {
+ Array.from(element.children).forEach(child => {
+ element.removeChild(child);
+ });
+}
+
+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.");
+});