From c5b4bc5788eeaf08ff65bd053885592001f2dc3b Mon Sep 17 00:00:00 2001 From: Justin Georgi Date: Sat, 16 Dec 2023 12:53:17 -0700 Subject: [PATCH 1/4] Add basic data export framework Signed-off-by: Justin Georgi --- src/pages/detect.vue | 8 +++++++- src/pages/submit-mixin.js | 13 +++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 src/pages/submit-mixin.js diff --git a/src/pages/detect.vue b/src/pages/detect.vue index ae4b5c6..06e74c1 100644 --- a/src/pages/detect.vue +++ b/src/pages/detect.vue @@ -56,7 +56,7 @@ - {{ isCordova }} + @@ -221,7 +221,10 @@ import RegionIcon from '../components/region-icon.vue' import SvgIcon from '../components/svg-icon.vue' + import submitMixin from './submit-mixin' + export default { + mixins: [submitMixin], props: { f7route: Object, }, @@ -432,6 +435,9 @@ this.selectedChip = -1 const box = this.$refs.structure_box box.style.display = 'none' + }, + submitData () { + alert(this.newUid(12)) } } } diff --git a/src/pages/submit-mixin.js b/src/pages/submit-mixin.js new file mode 100644 index 0000000..2571f5a --- /dev/null +++ b/src/pages/submit-mixin.js @@ -0,0 +1,13 @@ +export default { + methods: { + newUid (length) { + const uidLength = length || 16 + const uidChars = 'abcdefghijklmnopqrstuvwxyz0123456789' + var uid = [] + for (var i = 0; i < uidLength; i++) { + uid.push(uidChars.charAt(Math.floor(Math.random() * ((i < 4) ? 26 : 36)))) + } + return uid.join('') + } + } +} \ No newline at end of file -- 2.49.1 From ffa04dc3c75c6ed76c6f28b5551975d9a5ee37fe Mon Sep 17 00:00:00 2001 From: Justin Georgi Date: Sat, 16 Dec 2023 17:10:00 -0700 Subject: [PATCH 2/4] Add upload function Signed-off-by: Justin Georgi --- src/pages/detect.vue | 2 +- src/pages/submit-mixin.js | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/src/pages/detect.vue b/src/pages/detect.vue index 06e74c1..e3f578c 100644 --- a/src/pages/detect.vue +++ b/src/pages/detect.vue @@ -437,7 +437,7 @@ box.style.display = 'none' }, submitData () { - alert(this.newUid(12)) + this.uploadData(this.imageView.split(',')[1]) } } } diff --git a/src/pages/submit-mixin.js b/src/pages/submit-mixin.js index 2571f5a..1506d86 100644 --- a/src/pages/submit-mixin.js +++ b/src/pages/submit-mixin.js @@ -8,6 +8,28 @@ export default { uid.push(uidChars.charAt(Math.floor(Math.random() * ((i < 4) ? 26 : 36)))) } return uid.join('') + }, + uploadData (imagePayload) { + /***** + * This is the curl statement which works: + * curl -k -T file.ext -u "LKBm3H6JdSaywyg:" -H 'X-Requested-With: XMLHttpRequest' https://nextcloud.azgeorgis.net/public.php/webdav/newFile.ext + * + * Also, curl --header "Content-Type: text/plain" --data-raw "simple_body" --trace-ascii website-data-raw.log "$website" might be an example of how to put in the jpeg data. + * + * Just need to work out: + * 1) How that translates to a JS XHR + * 2) Whether I've got another @#$% CORS issue with it + * Answer: Bleep! Bleeeeeep! Bleep! Bleep! Bleep!....yes. + *****/ + + var xhr = new XMLHttpRequest() + var uploadUrl = `https://nextcloud.azgeorgis.net/public.php/webdav/${this.newUid(16)}.jpeg` + xhr.open("POST", uploadUrl) + xhr.setRequestHeader('Content-Type', 'image/jpeg') + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') + xhr.setRequestHeader("Authorization", "Basic " + btoa("LKBm3H6JdSaywyg:")) + + xhr.send(imagePayload) } } } \ No newline at end of file -- 2.49.1 From 98f3ea6fb33354e8d7eda7d7736c7739ddcb8a61 Mon Sep 17 00:00:00 2001 From: Justin Georgi Date: Sun, 17 Dec 2023 18:09:59 -0700 Subject: [PATCH 3/4] Perform upload of uid-linked resources Signed-off-by: Justin Georgi --- src/pages/detect.vue | 4 +-- src/pages/submit-mixin.js | 66 ++++++++++++++++++++++++++++----------- 2 files changed, 49 insertions(+), 21 deletions(-) diff --git a/src/pages/detect.vue b/src/pages/detect.vue index e3f578c..6662f99 100644 --- a/src/pages/detect.vue +++ b/src/pages/detect.vue @@ -422,7 +422,7 @@ } resolve() }) - loadImage.then((imageData) => { + loadImage.then(() => { this.imageLoaded = true this.resultData = {} this.resetView() @@ -437,7 +437,7 @@ box.style.display = 'none' }, submitData () { - this.uploadData(this.imageView.split(',')[1]) + this.uploadData(this.imageView.split(',')[1],this.resultData.detections) } } } diff --git a/src/pages/submit-mixin.js b/src/pages/submit-mixin.js index 1506d86..f5f9d03 100644 --- a/src/pages/submit-mixin.js +++ b/src/pages/submit-mixin.js @@ -1,3 +1,5 @@ +import { f7 } from 'framework7-vue' + export default { methods: { newUid (length) { @@ -9,27 +11,53 @@ export default { } return uid.join('') }, - uploadData (imagePayload) { - /***** - * This is the curl statement which works: - * curl -k -T file.ext -u "LKBm3H6JdSaywyg:" -H 'X-Requested-With: XMLHttpRequest' https://nextcloud.azgeorgis.net/public.php/webdav/newFile.ext - * - * Also, curl --header "Content-Type: text/plain" --data-raw "simple_body" --trace-ascii website-data-raw.log "$website" might be an example of how to put in the jpeg data. - * - * Just need to work out: - * 1) How that translates to a JS XHR - * 2) Whether I've got another @#$% CORS issue with it - * Answer: Bleep! Bleeeeeep! Bleep! Bleep! Bleep!....yes. - *****/ + uploadData (imagePayload, classPayload) { + let uploadImage =new Promise(resolve => { + const dataUid = this.newUid(16) + var byteChars = window.atob(imagePayload) + var byteArrays = [] + var len = byteChars.length + + for (var offset = 0; offset < len; offset += 1024) { + var slice = byteChars.slice(offset, offset + 1024) + var byteNumbers = new Array(slice.length) + for (var i = 0; i < slice.length; i++) { + byteNumbers[i] = slice.charCodeAt(i) + } - var xhr = new XMLHttpRequest() - var uploadUrl = `https://nextcloud.azgeorgis.net/public.php/webdav/${this.newUid(16)}.jpeg` - xhr.open("POST", uploadUrl) - xhr.setRequestHeader('Content-Type', 'image/jpeg') - xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest') - xhr.setRequestHeader("Authorization", "Basic " + btoa("LKBm3H6JdSaywyg:")) + var byteArray = new Uint8Array(byteNumbers) + byteArrays.push(byteArray) + } + var imageBlob = new Blob(byteArrays, {type: 'image/jpeg'}) - xhr.send(imagePayload) + var xhrJpg = new XMLHttpRequest() + var uploadUrl = `https://nextcloud.azgeorgis.net/public.php/webdav/${dataUid}.jpeg` + xhrJpg.open("PUT", uploadUrl) + xhrJpg.setRequestHeader('Content-Type', 'image/jpeg') + xhrJpg.setRequestHeader('X-Requested-With', 'XMLHttpRequest') + xhrJpg.setRequestHeader("Authorization", "Basic " + btoa("LKBm3H6JdSaywyg:")) + xhrJpg.send(imageBlob) + + var xhrTxt = new XMLHttpRequest() + var uploadUrl = `https://nextcloud.azgeorgis.net/public.php/webdav/${dataUid}.txt` + xhrTxt.open("PUT", uploadUrl) + xhrTxt.setRequestHeader('Content-Type', 'text/plain') + xhrTxt.setRequestHeader('X-Requested-With', 'XMLHttpRequest') + xhrTxt.setRequestHeader("Authorization", "Basic " + btoa("LKBm3H6JdSaywyg:")) + xhrTxt.send(JSON.stringify(classPayload)) + + resolve() + }) + uploadImage.then(() => { + var toast = f7.toast.create({ + text: 'Detections Uploaded: thank you.', + closeTimeout: 2000 + }) + toast.open() + }).catch((e) => { + console.log(e.message) + f7.dialog.alert(`Error uploading image: ${e.message}`) + }) } } } \ No newline at end of file -- 2.49.1 From 0f0ee803eaa8deef2de9f380c16c9be8349a8df5 Mon Sep 17 00:00:00 2001 From: Justin Georgi Date: Tue, 19 Dec 2023 09:47:05 -0700 Subject: [PATCH 4/4] Complete upload framework Signed-off-by: Justin Georgi --- src/components/svg-icon.vue | 14 ++++++++++++- src/pages/detect.vue | 42 +++++++++++++++++++++++++++++-------- src/pages/submit-mixin.js | 14 ++++++++----- 3 files changed, 55 insertions(+), 15 deletions(-) diff --git a/src/components/svg-icon.vue b/src/components/svg-icon.vue index b4d2c16..a8f80b9 100644 --- a/src/components/svg-icon.vue +++ b/src/components/svg-icon.vue @@ -6,6 +6,8 @@ + + @@ -16,7 +18,17 @@ icon: { type: String, validator(value) { - return ['image','videocam','visibility','photo_library','no_photography','photo_camera'].includes(value) + const iconList = [ + 'image', + 'videocam', + 'visibility', + 'photo_library', + 'no_photography', + 'photo_camera', + 'cloud_upload', + 'cloud_done' + ] + return iconList.includes(value) } }, fillColor: { diff --git a/src/pages/detect.vue b/src/pages/detect.vue index 6662f99..2c2cf87 100644 --- a/src/pages/detect.vue +++ b/src/pages/detect.vue @@ -24,20 +24,20 @@ @delete="deleteChip(result.resultIndex)" :style="chipGradient(result.confidence)" /> - No results. + No results. - + - + - - + + @@ -56,7 +56,6 @@ - @@ -84,6 +83,9 @@ + + + @@ -248,7 +250,9 @@ serverSettings: {}, debugOn: false, debugText: ['Variables loaded'], - isCordova: !!window.cordova + isCordova: !!window.cordova, + uploadUid: null, + uploadDirty: false } }, created () { @@ -304,6 +308,7 @@ }, showResults () { var filteredResults = this.resultData.detections + if (!filteredResults) return [] var allSelect = this.detectorLabels.every( s => { return s.detect } ) var selectedLabels = this.detectorLabels .filter( l => { return l.detect }) @@ -314,6 +319,14 @@ filteredResults[i].isSearched = allSelect || selectedLabels.includes(d.label) }) return filteredResults + }, + numResults () { + return this.showResults.filter( r => { return r.aboveThreshold && r.isSearched && !r.isDeleted }).length + }, + viewedAll () { + return this.resultData.detections + .filter( s => { return s.confidence >= this.detectorLevel}) + .every( s => { return s.beenViewed }) } }, methods: { @@ -337,6 +350,7 @@ return; } self.resultData = JSON.parse(xhr.response) + self.uploadDirty = true } var doodsData = { @@ -402,11 +416,13 @@ box.style.top = `${(img.offsetHeight - imgHeight) / 2 + this.resultData.detections[iChip].top * imgHeight}px` box.style.width = `${(Math.min(this.resultData.detections[iChip].right, 1) - Math.max(this.resultData.detections[iChip].left, 0)) * imgWidth}px` box.style.height = `${(Math.min(this.resultData.detections[iChip].bottom, 1) - Math.max(this.resultData.detections[iChip].top, 0)) * imgHeight}px` + this.resultData.detections[iChip].beenViewed = true }, deleteChip ( iChip ) { f7.dialog.confirm(`${this.resultData.detections[iChip].label} is identified with ${this.resultData.detections[iChip].confidence.toFixed(1)}% confidence. Are you sure you want to delete it?`, () => { this.resultData.detections.splice(iChip, 1) this.resetView() + this.uploadDirty = true }); }, getImage (searchImage) { @@ -436,8 +452,16 @@ const box = this.$refs.structure_box box.style.display = 'none' }, - submitData () { - this.uploadData(this.imageView.split(',')[1],this.resultData.detections) + videoStream() { + //TODO + return null + }, + async submitData () { + var uploadData = this.showResults + .filter( d => { return d.aboveThreshold && d.isSearched && !d.isDeleted }) + .map( r => { return {"top": r.top, "left": r.left, "bottom": r.bottom, "right": r.right, "label": r.label}}) + this.uploadUid = await this.uploadData(this.imageView.split(',')[1],uploadData,this.uploadUid) + if (this.uploadUid) { this.uploadDirty = false } } } } diff --git a/src/pages/submit-mixin.js b/src/pages/submit-mixin.js index f5f9d03..bc46fd7 100644 --- a/src/pages/submit-mixin.js +++ b/src/pages/submit-mixin.js @@ -11,9 +11,9 @@ export default { } return uid.join('') }, - uploadData (imagePayload, classPayload) { - let uploadImage =new Promise(resolve => { - const dataUid = this.newUid(16) + uploadData (imagePayload, classPayload, prevUid) { + let uploadImage = new Promise (resolve => { + const dataUid = prevUid || this.newUid(16) var byteChars = window.atob(imagePayload) var byteArrays = [] var len = byteChars.length @@ -34,6 +34,7 @@ export default { var uploadUrl = `https://nextcloud.azgeorgis.net/public.php/webdav/${dataUid}.jpeg` xhrJpg.open("PUT", uploadUrl) xhrJpg.setRequestHeader('Content-Type', 'image/jpeg') + xhrJpg.setRequestHeader('X-Method-Override', 'PUT') xhrJpg.setRequestHeader('X-Requested-With', 'XMLHttpRequest') xhrJpg.setRequestHeader("Authorization", "Basic " + btoa("LKBm3H6JdSaywyg:")) xhrJpg.send(imageBlob) @@ -42,21 +43,24 @@ export default { var uploadUrl = `https://nextcloud.azgeorgis.net/public.php/webdav/${dataUid}.txt` xhrTxt.open("PUT", uploadUrl) xhrTxt.setRequestHeader('Content-Type', 'text/plain') + xhrTxt.setRequestHeader('X-Method-Override', 'PUT') xhrTxt.setRequestHeader('X-Requested-With', 'XMLHttpRequest') xhrTxt.setRequestHeader("Authorization", "Basic " + btoa("LKBm3H6JdSaywyg:")) xhrTxt.send(JSON.stringify(classPayload)) - resolve() + resolve(dataUid) }) - uploadImage.then(() => { + return uploadImage.then((newUid) => { var toast = f7.toast.create({ text: 'Detections Uploaded: thank you.', closeTimeout: 2000 }) toast.open() + return newUid }).catch((e) => { console.log(e.message) f7.dialog.alert(`Error uploading image: ${e.message}`) + return null }) } } -- 2.49.1