aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFrederick Muriuki Muriithi2024-06-27 10:53:54 -0500
committerFrederick Muriuki Muriithi2024-06-27 10:53:54 -0500
commitdf9da3d5b5e4382976ede1b54eb1aeb04c4c45e5 (patch)
tree1049a365445628145be075e761ffbe4897295ecc
parent9a8dddab072748a70d43416ac8e6db69ad6fb0cb (diff)
downloadgn-uploader-df9da3d5b5e4382976ede1b54eb1aeb04c4c45e5.tar.gz
Upload chunking: Provide UI and code for drag&drop and chunking
* Use resumable.js to handle the drag-and-drop feature, and chunking. * Add styling for the drag-and-drop area, and provide visual indication when a file is successfully dropped and added to the upload list.
-rw-r--r--qc_app/templates/rqtl2/upload-rqtl2-bundle-step-01.html214
1 files changed, 188 insertions, 26 deletions
diff --git a/qc_app/templates/rqtl2/upload-rqtl2-bundle-step-01.html b/qc_app/templates/rqtl2/upload-rqtl2-bundle-step-01.html
index fc87d5b..b9320ea 100644
--- a/qc_app/templates/rqtl2/upload-rqtl2-bundle-step-01.html
+++ b/qc_app/templates/rqtl2/upload-rqtl2-bundle-step-01.html
@@ -5,38 +5,92 @@
{%block title%}Upload R/qtl2 Bundle{%endblock%}
{%block contents%}
+{%macro rqtl2_file_help()%}
+<span class="form-text text-muted">
+ <p>
+ Provide a valid R/qtl2 zip file here. In particular, ensure your zip bundle
+ contains exactly one control file and the corresponding files mentioned in
+ the control file.
+ </p>
+ <p>
+ The control file can be either a YAML or JSON file. <em>ALL</em> other data
+ files in the zip bundle should be CSV files.
+ </p>
+ <p>See the
+ <a href="https://kbroman.org/qtl2/assets/vignettes/input_files.html"
+ target="_blank">
+ R/qtl2 file format specifications
+ </a>
+ for more details.
+ </p>
+</span>
+{%endmacro%}
{{upload_progress_indicator()}}
+<div id="resumable-file-display-template"
+ class="panel panel-info"
+ style="display: none">
+ <div class="panel-heading"></div>
+ <div class="panel-body"></div>
+</div>
+
+
<h2 class="heading">Upload R/qtl2 Bundle</h2>
+<div id="resumable-drop-area"
+ style="display:none;background:#eeeeee;min-height:12em;border-radius:0.5em;padding:1em;">
+ <p>
+ <a id="resumable-browse-button" href="#"
+ class="btn btn-info">Browse</a>
+ </p>
+ <p class="form-text text-muted">
+ You can drag and drop your file here, or click the browse button.
+ Click on the file to remove it.
+ </p>
+ {{rqtl2_file_help()}}
+ <div id="resumable-selected-files"
+ style="display:flex;flex-direction:row;flex-wrap: wrap;justify-content:space-around;gap:10px 20px;"></div>
+ <div id="resumable-class-buttons" style="text-align: right;">
+ <button id="resumable-upload-button"
+ class="btn btn-primary"
+ style="display: none">start upload</button>
+ <button id="resumable-cancel-upload-button"
+ class="btn btn-danger"
+ style="display: none">cancel upload</button>
+ </div>
+ <div class="progress" style="display: none">
+ <div class="progress-bar"
+ role="progress-bar"
+ aria-valuenow="60"
+ aria-valuemin="0"
+ aria-valuemax="100"
+ style="width: 60%;">
+ Uploading: 60%
+ </div>
+ </div>
+</div>
+
<form id="frm-upload-rqtl2-bundle"
action="{{url_for('upload.rqtl2.upload_rqtl2_bundle',
species_id=species.SpeciesId,
population_id=population.InbredSetId)}}"
method="POST"
- enctype="multipart/form-data">
+ enctype="multipart/form-data"
+ data-resumable-action="/no/such/endpoint">
<input type="hidden" name="species_id" value="{{species.SpeciesId}}" />
<input type="hidden" name="population_id"
value="{{population.InbredSetId}}" />
{{flash_all_messages()}}
- <div form-group>
+ <div class="form-group">
<legend class="heading">file upload</legend>
<label for="file-rqtl2-bundle" class="form-label">R/qtl2 bundle</label>
<input type="file" id="file-rqtl2-bundle" name="rqtl2_bundle_file"
accept="application/zip, .zip"
required="required"
class="form-control" />
- <span class="form-text text-muted"><p>Provide a valid R/qtl2 zip file here. In
- particular, ensure your zip bundle contains exactly one control file and
- the corresponding files mentioned in the control file.</p>
- <p>The control file can be either a YAML or JSON file. <em>ALL</em> other
- data files in the zip bundle should be CSV files.</p>
- <p>See the
- <a href="https://kbroman.org/qtl2/assets/vignettes/input_files.html"
- target="_blank">
- R/qtl2 file format specifications</a> for more details.</p></span>
+ {{rqtl2_file_help()}}
</div>
<button type="submit"
@@ -48,22 +102,130 @@
{%endblock%}
{%block javascript%}
+<script src="{{url_for('base.node_modules',
+ filename='resumablejs/resumable.js')}}"></script>
<script type="text/javascript" src="/static/js/upload_progress.js"></script>
<script type="text/javascript">
- setup_upload_handlers(
- "frm-upload-rqtl2-bundle", make_data_uploader(
- function (form) {
- var formdata = new FormData();
- formdata.append(
- "species_id",
- form.querySelector('input[name="species_id"]').value);
- formdata.append(
- "population_id",
- form.querySelector('input[name="population_id"]').value);
- formdata.append(
- "rqtl2_bundle_file",
- form.querySelector("#file-rqtl2-bundle").files[0]);
- return formdata;
- }));
+ function readBinaryFile(file) {
+ return new Promise((resolve, reject) => {
+ var _reader = new FileReader();
+ _reader.onload = (event) => {resolve(_reader.result);};
+ _reader.readAsArrayBuffer(file);
+ });
+ }
+
+ function computeFileChecksum(file) {
+ return readBinaryFile(file)
+ .then((content) => {
+ return window.crypto.subtle.digest(
+ "SHA-256", new Uint8Array(content));
+ }).then((digest) => {
+ return Uint8ArrayToHex(new Uint8Array(digest))
+ });
+ }
+
+ function 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 r = Resumable({
+ target: $("#frm-upload-rqtl2-bundle").attr("data-resumable-action"),
+ fileType: ["zip"],
+ 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, "");
+ });
+ }
+ });
+
+ if(r.support) {
+ //Hide form and display drag&drop UI
+ $("#frm-upload-rqtl2-bundle").css("display", "none");
+ $("#resumable-drop-area").css("display", "block");
+
+ // Define UI elements for browse and drag&drop
+ r.assignDrop(document.getElementById("resumable-drop-area"));
+ r.assignBrowse(document.getElementById("resumable-browse-button"));
+
+ // Event handlers
+ r.on("filesAdded", function(files) {
+ displayArea = $("#resumable-selected-files")
+ displayArea.empty();
+ files.forEach((file) => {
+ var displayElement = $(
+ "#resumable-file-display-template").clone();
+ displayElement.removeAttr("id");
+ displayElement.css("display", "");
+ displayElement.find(".panel-heading").text(file.fileName);
+ list = $("<ul></ul>");
+ list.append($("<li><strong>Name</strong>: "
+ + (file.name
+ || file.fileName
+ || file.relativePath
+ || file.webkitRelativePath)
+ + "</li>"));
+ list.append($("<li><strong>Size</strong>: "
+ + (file.size / (1024*1024)).toFixed(2)
+ + " MB</li>"));
+ list.append($("<li><strong>Unique Identifier</strong>: "
+ + file.uniqueIdentifier + "</li>"));
+ list.append($("<li><strong>Mime</strong>: "
+ + file.file.type
+ + "</li>"));
+ displayElement.find(".panel-body").append(list);
+ displayElement.appendTo("#resumable-selected-files");
+ $("#resumable-upload-button").css("display", "");
+ $("#resumable-upload-button").on("click", (event) => {
+ r.upload()
+ });
+ });
+ });
+
+ r.on("uploadStart", (event) => {
+ $("#resumable-upload-button").css("display", "none");
+ $("#resumable-cancel-upload-button").css("display", "");
+ $("#resumable-cancel-upload-button").on("click", (event) => {
+ r.files.forEach((file) => {
+ if(file.isUploading()) {
+ file.abort();
+ }
+ });
+ $("#resumable-cancel-upload-button").css("display", "none");
+ $("#resumable-upload-button").css("display", "");
+ });
+ });
+ } else {
+ setup_upload_handlers(
+ "frm-upload-rqtl2-bundle", make_data_uploader(
+ function (form) {
+ var formdata = new FormData();
+ formdata.append(
+ "species_id",
+ form.querySelector('input[name="species_id"]').value);
+ formdata.append(
+ "population_id",
+ form.querySelector('input[name="population_id"]').value);
+ formdata.append(
+ "rqtl2_bundle_file",
+ form.querySelector("#file-rqtl2-bundle").files[0]);
+ return formdata;
+ }));
+ }
</script>
{%endblock%}