From 377fbac452d8dddc9ecae5b8c22f6ca37c275907 Mon Sep 17 00:00:00 2001 From: Rory Schadler Date: Tue, 22 Feb 2022 14:42:53 -0500 Subject: [PATCH] #132: Add MCR specific tool page Renamed tool template to specify that it is MCR tool specific -- at least given the tools we've added so far. Added a dependency for EditImage.vue, a component of ImageUpload.vue that is itself a component of MCRToolTemplate.vue. Removed the old MCR homepage. Added a pageContent getter for tools as well. --- app/package.json | 2 + app/src/components/nanomine/EditImage.vue | 344 ++++++++++ app/src/components/nanomine/ImageUpload.vue | 644 ++++++++++++++++++ .../MCRToolTemplate/MCRToolTemplate.html | 91 +++ .../tools/MCRToolTemplate/MCRToolTemplate.js | 293 ++++++++ .../tools/MCRToolTemplate/MCRToolTemplate.vue | 14 + 6 files changed, 1388 insertions(+) create mode 100644 app/src/components/nanomine/EditImage.vue create mode 100644 app/src/components/nanomine/ImageUpload.vue create mode 100644 app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.html create mode 100644 app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.js create mode 100644 app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.vue diff --git a/app/package.json b/app/package.json index fce00e9d..feccf1cf 100644 --- a/app/package.json +++ b/app/package.json @@ -27,6 +27,7 @@ "d3": "^7.3.0", "datavoyager": "^2.0.0-alpha.24", "jsonschema": "^1.4.0", + "jszip": "^3.5.0", "rdf-literal": "^1.1.0", "register-service-worker": "^1.7.1", "smiles-drawer": "^1.2.0", @@ -37,6 +38,7 @@ "vega-lite4": "npm:vega-lite@^4.17.0", "vega-lite5": "npm:vega-lite@^5.1.1", "vue": "^2.6.11", + "vue-advanced-cropper": "^0.16.8", "vue-material": "^1.0.0-beta-15", "vue-router": "^3.2.0", "vue-splitpane": "^1.0.6", diff --git a/app/src/components/nanomine/EditImage.vue b/app/src/components/nanomine/EditImage.vue new file mode 100644 index 00000000..c0cf8f3a --- /dev/null +++ b/app/src/components/nanomine/EditImage.vue @@ -0,0 +1,344 @@ + + + + + + + + + diff --git a/app/src/components/nanomine/ImageUpload.vue b/app/src/components/nanomine/ImageUpload.vue new file mode 100644 index 00000000..ec2c3caf --- /dev/null +++ b/app/src/components/nanomine/ImageUpload.vue @@ -0,0 +1,644 @@ + + + + + + + diff --git a/app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.html b/app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.html new file mode 100644 index 00000000..b0fedc9e --- /dev/null +++ b/app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.html @@ -0,0 +1,91 @@ +
+
+
+

{{ pageContent.title }}

+
+ + + + + + + + +
+
+

Description

+
+

{{ sentence }}

+
+
+
+

Input Options

+
    +
  1. + {{ uploadOption.title }}: +
  2. +
+ +
+
+ +
+

Results

+
+

+
+
+ +

Image Upload

+ + + + {{ job.submit.submitButtonTitle }} + + +
+

Submission Results

+
+ + Creating zipped file... + Download results + +
+
+

Inputs

+
+ +
+
+
+

Outputs

+
+ +
+
+
+
+

Loading...

+
+ +
+ +
+
+
\ No newline at end of file diff --git a/app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.js b/app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.js new file mode 100644 index 00000000..1b916e4f --- /dev/null +++ b/app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.js @@ -0,0 +1,293 @@ +import ReferenceContainer from '@/components/nanomine/ReferenceContainer' +import Dialog from '@/components/Dialog' +import ImageUpload from '@/components/nanomine/ImageUpload' +import { JobMgr } from '@/modules/JobMgr.js' + +import { mapGetters, mapMutations } from 'vuex' +// import {Auth} from '@/modules/Auth.js' +import Jszip from 'jszip' + +export default { + name: 'MCRToolTemplate', + components: { + ReferenceContainer, + ImageUpload, + dialogbox: Dialog + }, + props: { + toolProp: { + type: String, + required: true + } + }, + sockets: { + finished: function (data) { + this.results.jobid = data + this.results.uri = '/nmf/jobdata/' + data + this.setLoading() + return fetch(this.results.uri + '/job_output_parameters.json') + .then(function (response) { + this.results.files = response.data.files // use files array instead of individual file references + this.results.obtained = true + this.resetLoading() + }) + .catch(function (err) { + this.renderDialog({ + title: 'Socket Error', + content: err.message, + reason: 'socketError' + }) + this.resetLoading() + }) + }, + hello: function (data) { + if (data === 'connection received' && this.job.useWebsocket === true) { + this.useWebsocket = true + } + } + }, + data: function () { + return { + pageContent: { + references: [] + }, + job: { + submit: { + submitButtonTitle: '' + } + }, + jobId: '', + dialog: { + title: '', + content: '', + reason: '' + }, + files: undefined, + selectedOptions: {}, + useWebsocket: false, + results: { + obtained: false, + files: undefined, + uri: undefined, + jobid: undefined, + submitted: false, + downloading: false + }, + auth: { + // auth mocked because auth is not yet implemented + isLoggedIn: () => false, + isTestUser: () => false + } + } + }, + beforeMount: function () { + // this.auth = new Auth() + if (!this.auth.isLoggedIn()) { + this.renderDialog({ + title: 'Authorization Error', + content: 'Login is required, please log in', + reason: 'loginRequired' + }) + } + }, + mounted () { + this.resetContent() + this.$socket.emit('testConnection') + }, + computed: { + tool: function () { + return this.toolProp + }, + toolName: function () { + if (this.pageContent) { + return this.pageContent.name || '' + } else { + return this.toolProp + } + }, + ...mapGetters({ + dialogBoxActive: 'dialogBox' + }) + }, + methods: { + resetContent () { + this.job = this.$store.getters[`${this.tool}/jobInfo`] + console.log(this.job) + this.pageContent = this.$store.getters[`${this.tool}/pageContent`] + this.$store.commit('setAppHeaderInfo', { icon: 'workspaces', name: this.pageContent.title }) + }, + ...mapMutations({ + toggleDialogBox: 'setDialogBox' + }), + renderDialog ({ title, content, reason }) { + if (!this.dialogBoxActive) { + this.dialog = { + title, + content, + reason + } + } + this.toggleDialogBox() + }, + successDlg () { + let contentSockets + if (this.job.useWebsocket) { + contentSockets = 'Please stay on this page. Results may take up to a few minutes to load.' + } else { + contentSockets = 'You should receive an email with a link to the job output.' + } + this.renderDialog({ + title: `${this.job.jobTitle} Job Submitted Successfully`, + content: `Your ${this.job.jobTitle} job is: ${this.jobId}
${contentSockets}`, + reason: 'successDlg' + }) + }, + uploadError (errorMsg) { + this.renderDialog({ + title: 'Upload Error', + content: errorMsg, + reason: 'uploadError' + }) + }, + download: async function () { + const jszipObj = new Jszip() + const vm = this + vm.results.downloading = true + + function getBase64 (image) { + return new Promise((resolve, reject) => { + image.onload = function () { + canvas.width = image.width + canvas.height = image.height + ctx.drawImage(image, 0, 0) + resolve(canvas.toDataURL()) + } + }) + } + + // add images to zip file + for (let i = 0; i < vm.results.files.length; i++) { + var canvas = document.createElement('canvas') + var ctx = canvas.getContext('2d') + var image = new Image() + image.src = vm.results.uri + '/' + vm.results.files[i].output + + var base64Image = await getBase64(image) + + jszipObj.file('output-' + (i + 1) + '.jpg', base64Image.split(',').pop(), { base64: true }) + } + + // create zip file & download + jszipObj.generateAsync({ type: 'base64', compression: 'DEFLATE' }) + .then(function (base64) { + var downloadFile = 'data:application/zip;base64,' + base64 + var link = document.createElement('a') + link.href = downloadFile + link.download = 'output.zip' + document.body.appendChild(link) + link.click() + document.body.removeChild(link) + vm.results.downloading = false + }) + }, + getResultImage: function (index, type) { + if (type === 'input') { + return this.results.uri + '/' + this.results.files[index].input + } else { + return this.results.uri + '/' + this.results.files[index].output + } + }, + + setFiles: function (files) { + this.files = files // the actual file object + }, + + setSelectors: function (selectedOptions) { + this.selectedOptions = selectedOptions + }, + + setLoading: function () { + this.$store.commit('isLoading') + }, + + resetLoading: function () { + this.$store.commit('notLoading') + }, + + submit: function () { + this.setLoading() + + if (this.files === undefined) { + this.renderDialog({ + title: 'File Error', + content: 'Please select a file to process.', + reason: 'noFiles' + }) + this.resetLoading() + return + } + + const jm = new JobMgr() + jm.setJobType(this.job.submit.submitJobTitle) + + var jobParameters = { InputType: this.files.fileType, useWebsocket: this.useWebsocket } // Figure out which file type + for (var key in this.selectedOptions) { + if (key === 'phase') { + jobParameters[key] = this.phaseToString(this.selectedOptions[key]) + } else if (key === 'dimensions') { + jobParameters[key] = this.dimensionToString(this.selectedOptions[key]) + } else { + jobParameters[key] = this.selectedOptions[key] + } + } + if ('submitJobType' in this.job.submit) { + jobParameters.jobtype = this.job.submit.submitJobType + } + jm.setJobParameters(jobParameters) + + jm.addInputFile(this.files.name, this.files.url) + + return jm.submitJob(function (jobId) { + this.$socket.emit('newJob', jobId) + this.results.submitted = true + this.results.obtained = false + this.jobId = jobId + this.resetLoading() + this.successDlg = true + }, function (errCode, errMsg) { + this.renderDialog({ + title: 'Job Error', + content: `error: ${errCode} msg: ${errMsg}`, + reason: `jobError-${errCode}` + }) + this.resetLoading() + }) + }, + + phaseToString: function (phaseObj) { + var returnString = '' + for (var key in phaseObj) { + if (returnString !== '') { + returnString += '|' + } + returnString += phaseObj[key].x_offset + '*' + phaseObj[key].y_offset + } + return returnString + }, + + dimensionToString: function (dimensionObj) { + if ('ratio' in dimensionObj === false) { + return '1' + } else if (dimensionObj.ratio === null || dimensionObj.ratio === 0) { + return '1' + } + return dimensionObj.ratio.toString() + // return dimensionObj['width'] + '*' + dimensionObj['height'] + '*' + dimensionObj['units'] + } + }, + watch: { + $route (to, from) { + this.resetContent() + } + } +} diff --git a/app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.vue b/app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.vue new file mode 100644 index 00000000..44221aa1 --- /dev/null +++ b/app/src/pages/nanomine/tools/MCRToolTemplate/MCRToolTemplate.vue @@ -0,0 +1,14 @@ + + + +