From 65e0c49ab00204ecd553fea8e795cb395416c3af Mon Sep 17 00:00:00 2001 From: cbeauchesne Date: Sat, 10 Aug 2019 21:45:20 +0200 Subject: [PATCH 1/4] revamp image uploader component --- .../images-uploader/ImageUploader.vue | 310 +++++++---------- .../images-uploader/ImagesUploader.vue | 313 +++++++++--------- src/js/upload-file.js | 165 +++++++++ 3 files changed, 433 insertions(+), 355 deletions(-) create mode 100644 src/js/upload-file.js diff --git a/src/components/images-uploader/ImageUploader.vue b/src/components/images-uploader/ImageUploader.vue index 4cd728c3b..9638fdeca 100644 --- a/src/components/images-uploader/ImageUploader.vue +++ b/src/components/images-uploader/ImageUploader.vue @@ -1,35 +1,34 @@ + } + diff --git a/src/components/images-uploader/ImagesUploader.vue b/src/components/images-uploader/ImagesUploader.vue index 41ff33809..17497902e 100644 --- a/src/components/images-uploader/ImagesUploader.vue +++ b/src/components/images-uploader/ImagesUploader.vue @@ -1,25 +1,33 @@ diff --git a/src/js/upload-file.js b/src/js/upload-file.js new file mode 100644 index 000000000..f78db31d6 --- /dev/null +++ b/src/js/upload-file.js @@ -0,0 +1,165 @@ +// This file exposes a simple function that upload a file to c2c image backend + +import moment from 'moment'; +import loadImage from 'blueimp-load-image'; +import ol from '@/js/libs/ol.js'; + +import c2c from '@/js/apis/c2c'; +import Worker from '@/js/Worker'; + +// get all world extent. sometimes, geoloc in exif is outside this extent. +const worldExtent = ol.proj.get('EPSG:4326').getExtent(); + +// this worker will handle network upload : only on upload at a time +const worker = new Worker(); + +// Microsoft Edge does not implement toblob, and there is no polyfill in core.js +// https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toBlob#Polyfill +if (!HTMLCanvasElement.prototype.toBlob) { + Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { + value(callback, type, quality) { + const dataURL = this.toDataURL(type, quality).split(',')[1]; + setTimeout(function() { + const binStr = atob(dataURL); + const len = binStr.length; + const arr = new Uint8Array(len); + + for (let i = 0; i < len; i++) { + arr[i] = binStr.charCodeAt(i); + } + + callback(new Blob([arr], { type: type || 'image/png' })); + }); + } + }); +} + +// https://github.com/c2corg/v6_ui/blob/c9962a6c3bac0670eab732d563f9f480379f84d1/c2corg_ui/static/js/utils.js#L273 +const convertDMSToDecimal = function(degrees, minutes, seconds, direction) { + let decimal = Number(degrees) + (Number(minutes) / 60) + (parseFloat(seconds) / 3600); + + // Don't do anything for N or E + if (direction === 'S' || direction === 'W') { + decimal = decimal * -1; + } + + return decimal; +}; + +const parseExifDate = function(exif) { + const exifDate = exif.DateTimeOriginal || exif.DateTime; + + if (exifDate) { + const date = moment(exifDate, 'YYYY:MM:DD HH:mm:ss'); + return date.isValid() ? date.format() : null; + } else { + return undefined; + } +}; + +const parseExifGeometry = function(exif) { + if (!exif.GPSLatitude || !exif.GPSLongitude) { + return undefined; + } + + let lat = exif.GPSLatitude.split(','); + let lon = exif.GPSLongitude.split(','); + + lat = convertDMSToDecimal(lat[0], lat[1], lat[2], exif.GPSLatitudeRef); + lon = convertDMSToDecimal(lon[0], lon[1], lon[2], exif.GPSLongitudeRef); + + if (isNaN(lat) || isNaN(lon) || !ol.extent.containsXY(worldExtent, lon, lat)) { + return undefined; + } + + const location = ol.proj.transform([lon, lat], 'EPSG:4326', 'EPSG:3857'); + const geom = { 'coordinates': location, 'type': 'Point' }; + + return { 'geom': JSON.stringify(geom) }; +}; + +const parseExifElevation = function(exif) { + if (!exif.GPSAltitude) { + return undefined; + } + + const elevation = parseFloat(exif.GPSAltitude); + return isNaN(elevation) ? undefined : elevation; +}; + +const setIfDefined = function(document, name, value) { + if (value !== undefined) { + document[name] = value; + } +}; + +const uploadFile = function(file, onDocumentReady, onDataUrlReady, onUploadProgress, onSuccess, onFailure) { + const document = {}; + + const parseMetaData = function(metaData) { + const exif = metaData.exif ? metaData.exif.getAll() : null; + let orientation = 0; + + if (exif) { + orientation = metaData.exif.get('Orientation'); + + setIfDefined(document, 'exposure_time', exif.ExposureTime); + setIfDefined(document, 'iso_speed', exif.PhotographicSensitivity); + setIfDefined(document, 'focal_length', exif.FocalLengthIn35mmFilm); + setIfDefined(document, 'fnumber', exif.FNumber); + setIfDefined(document, 'camera_name', (exif.Make && exif.Model) ? (exif.Make + ' ' + exif.Model) : undefined); + setIfDefined(document, 'date_time', parseExifDate(exif)); + setIfDefined(document, 'geometry', parseExifGeometry(exif)); + setIfDefined(document, 'elevation', parseExifElevation(exif)); + } + preProcess(orientation); + }; + + const preProcess = function(orientation) { + if (orientation !== 0 && file.type === 'image/jpeg') { + loadImage( + file, + (canvas) => { + processDataUrl(canvas.toDataURL(file.type)); + + // and this function will call upload + canvas.toBlob(upload, file.type); + }, + { canvas: true, orientation } // this will fix orientation from exif + ); + } else { + const reader = new FileReader(); + reader.onload = (e) => { + processDataUrl(e.target.result); + }; + reader.readAsDataURL(file); + + upload(file); + } + }; + + const processDataUrl = function(dataUrl) { + // send data url to caller + onDataUrlReady(dataUrl); + + // and use this to get image dimensions + const img = new Image(); + + img.onload = function() { + // image is loaded; sizes are available + document.width = img.width; + document.height = img.height; + + onDocumentReady(document); + }; + + img.src = dataUrl; + }; + + const upload = function(data) { + worker.push(c2c.uploadImage.bind(c2c), data, onUploadProgress, onSuccess, onFailure); + }; + + loadImage.parseMetaData(file, parseMetaData); +}; +export default uploadFile; From 8eb3c2fa012c79f5e973649d8bcebb2d3f990dc2 Mon Sep 17 00:00:00 2001 From: cbeauchesne Date: Sat, 10 Aug 2019 22:55:27 +0200 Subject: [PATCH 2/4] Feature : upload a new version of an image #388 --- src/js/vue-plugins/font-awesome-config.js | 2 + src/views/wiki/edition/ImageEditionView.vue | 62 ++++++++++++++++++++- 2 files changed, 62 insertions(+), 2 deletions(-) diff --git a/src/js/vue-plugins/font-awesome-config.js b/src/js/vue-plugins/font-awesome-config.js index 6cd222b0e..5568f9d3b 100644 --- a/src/js/vue-plugins/font-awesome-config.js +++ b/src/js/vue-plugins/font-awesome-config.js @@ -78,6 +78,7 @@ import { faThLarge } from '@fortawesome/free-solid-svg-icons/faThLarge'; import { faThList } from '@fortawesome/free-solid-svg-icons/faThList'; import { faTrash } from '@fortawesome/free-solid-svg-icons/faTrash'; import { faUnlock } from '@fortawesome/free-solid-svg-icons/faUnlock'; +import { faUpload } from '@fortawesome/free-solid-svg-icons/faUpload'; import { faUser } from '@fortawesome/free-solid-svg-icons/faUser'; import { faUserCheck } from '@fortawesome/free-solid-svg-icons/faUserCheck'; import { faUserLock } from '@fortawesome/free-solid-svg-icons/faUserLock'; @@ -283,6 +284,7 @@ export default function install(Vue) { faTrash, // faTrashAlt, faUnlock, + faUpload, faUser, faUserCheck, faUserLock, diff --git a/src/views/wiki/edition/ImageEditionView.vue b/src/views/wiki/edition/ImageEditionView.vue index 437ab7f6f..169a91ee1 100644 --- a/src/views/wiki/edition/ImageEditionView.vue +++ b/src/views/wiki/edition/ImageEditionView.vue @@ -23,6 +23,26 @@
+
+
+ +
+
@@ -74,14 +94,52 @@ From b8f62ff605364db7d4e920f4e094ac68a91b3a76 Mon Sep 17 00:00:00 2001 From: cbeauchesne Date: Sun, 11 Aug 2019 18:30:26 +0200 Subject: [PATCH 3/4] Use dataUrl as source --- .../images-uploader/ImagesUploader.vue | 11 +++----- src/js/upload-file.js | 11 +++++--- src/views/wiki/edition/ImageEditionView.vue | 25 ++++++++++--------- 3 files changed, 24 insertions(+), 23 deletions(-) diff --git a/src/components/images-uploader/ImagesUploader.vue b/src/components/images-uploader/ImagesUploader.vue index 17497902e..f940f37f4 100644 --- a/src/components/images-uploader/ImagesUploader.vue +++ b/src/components/images-uploader/ImagesUploader.vue @@ -236,17 +236,14 @@ uploadFile( image.file, - document => { - Object.assign(image.document, document); - }, dataUrl => { image.dataUrl = dataUrl; }, event => { this.onUploadProgress(event, image); }, - event => { - this.onUploadSuccess(event, image); + document => { + this.onUploadSuccess(document, image); }, event => { this.onUploadFailure(event, image); @@ -261,9 +258,9 @@ } }, - onUploadSuccess(event, image) { + onUploadSuccess(document, image) { image.status = 'SUCCESS'; - image.document.filename = event.data.filename; + Object.assign(image.document, document); this.computeReadyForSaving(); }, diff --git a/src/js/upload-file.js b/src/js/upload-file.js index f78db31d6..95dc9a357 100644 --- a/src/js/upload-file.js +++ b/src/js/upload-file.js @@ -93,7 +93,7 @@ const setIfDefined = function(document, name, value) { } }; -const uploadFile = function(file, onDocumentReady, onDataUrlReady, onUploadProgress, onSuccess, onFailure) { +const uploadFile = function(file, onDataUrlReady, onUploadProgress, onSuccess, onFailure) { const document = {}; const parseMetaData = function(metaData) { @@ -149,15 +149,18 @@ const uploadFile = function(file, onDocumentReady, onDataUrlReady, onUploadProgr // image is loaded; sizes are available document.width = img.width; document.height = img.height; - - onDocumentReady(document); }; img.src = dataUrl; }; + const onUploadSuccess = function(event) { + document.filename = event.data.filename; + onSuccess(document); + }; + const upload = function(data) { - worker.push(c2c.uploadImage.bind(c2c), data, onUploadProgress, onSuccess, onFailure); + worker.push(c2c.uploadImage.bind(c2c), data, onUploadProgress, onUploadSuccess, onFailure); }; loadImage.parseMetaData(file, parseMetaData); diff --git a/src/views/wiki/edition/ImageEditionView.vue b/src/views/wiki/edition/ImageEditionView.vue index 169a91ee1..cccd02ba9 100644 --- a/src/views/wiki/edition/ImageEditionView.vue +++ b/src/views/wiki/edition/ImageEditionView.vue @@ -3,7 +3,7 @@ :mode="mode" :document="document" :generic-errors="genericErrors" - :is-loading="saving" + :is-loading="saving || uploadingNewFile" @save="save"> - + @@ -43,7 +43,7 @@ - + @@ -103,7 +103,8 @@ data() { return { - uploading: false + newVersionSource: null, + uploadingNewFile: false }; }, @@ -116,25 +117,25 @@ return; } - this.uploading = true; + this.uploadingNewFile = true; uploadFile( file, + dataUrl => { + this.newVersionSource = dataUrl; + }, + event => { /* onUploadProgress */ }, document => { + this.uploadingNewFile = false; + if (document.geometry) { document.geometry.version = this.document.geometry.version; } Object.assign(this.document, document); }, - dataUrl => { }, - event => { /* onUploadProgress */ }, - event => { - this.uploading = false; - this.document.filename = event.data.filename; - }, event => { /* onUploadFailure */ - this.uploading = false; + this.uploadingNewFile = false; } ); From 98381c77c3d10a6b7a96a33bd3b0a313144841ea Mon Sep 17 00:00:00 2001 From: cbeauchesne Date: Wed, 14 Aug 2019 20:26:42 +0200 Subject: [PATCH 4/4] Show images in diff --- src/views/wiki/DiffView.vue | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/src/views/wiki/DiffView.vue b/src/views/wiki/DiffView.vue index fb9551fc9..3f486f75a 100644 --- a/src/views/wiki/DiffView.vue +++ b/src/views/wiki/DiffView.vue @@ -76,12 +76,24 @@
+ +
+
+
+ +
+
+ +
+
+
+
-

{{ $gettext(key) }}

+

{{ $gettext(key) | uppercase-first-letter }}

null @@ -146,6 +158,7 @@