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 ae4b5c6..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 @@ - {{ isCordova }} @@ -84,6 +83,9 @@ + + + @@ -221,7 +223,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, }, @@ -245,7 +250,9 @@ serverSettings: {}, debugOn: false, debugText: ['Variables loaded'], - isCordova: !!window.cordova + isCordova: !!window.cordova, + uploadUid: null, + uploadDirty: false } }, created () { @@ -301,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 }) @@ -311,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: { @@ -334,6 +350,7 @@ return; } self.resultData = JSON.parse(xhr.response) + self.uploadDirty = true } var doodsData = { @@ -399,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) { @@ -419,7 +438,7 @@ } resolve() }) - loadImage.then((imageData) => { + loadImage.then(() => { this.imageLoaded = true this.resultData = {} this.resetView() @@ -432,6 +451,17 @@ this.selectedChip = -1 const box = this.$refs.structure_box box.style.display = 'none' + }, + 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 new file mode 100644 index 0000000..bc46fd7 --- /dev/null +++ b/src/pages/submit-mixin.js @@ -0,0 +1,67 @@ +import { f7 } from 'framework7-vue' + +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('') + }, + 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 + + 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 byteArray = new Uint8Array(byteNumbers) + byteArrays.push(byteArray) + } + var imageBlob = new Blob(byteArrays, {type: 'image/jpeg'}) + + 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-Method-Override', 'PUT') + 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-Method-Override', 'PUT') + xhrTxt.setRequestHeader('X-Requested-With', 'XMLHttpRequest') + xhrTxt.setRequestHeader("Authorization", "Basic " + btoa("LKBm3H6JdSaywyg:")) + xhrTxt.send(JSON.stringify(classPayload)) + + resolve(dataUid) + }) + 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 + }) + } + } +} \ No newline at end of file