From bf64efb5a0684bd61f3c2d520253721fe98c27c4 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Wed, 20 Jun 2018 16:03:53 +0200 Subject: [FIX] web_editor: do not 'reset' crop feature when changing ratio Before this commit, when the user changed the ratio option in the crop dialog, the image zoom/rotation were reset for no apparent reason. --- addons/web_editor/static/src/js/widgets/widgets.js | 1 - 1 file changed, 1 deletion(-) (limited to 'addons/web_editor/static/src/js/widgets/widgets.js') diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index d0df042fa82..ec747badabb 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -1799,7 +1799,6 @@ var CropImageDialog = Dialog.extend({ case 'ratio': $option.siblings().removeClass('active'); $option.addClass('active'); - this.$cropperImage.cropper('reset'); this.imageData.aspectRatio = value; this.$cropperImage.cropper('setAspectRatio', this.imageData.aspectRatio); break; -- cgit v1.2.3 From a9366de8f90ea4106d306c585043a42f089c86d4 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Wed, 20 Jun 2018 16:25:04 +0200 Subject: [FIX] web_editor: do not show cropped images in media dialog Cropped image attachment were not meant to appear in the media dialog, only original ones. --- addons/web_editor/static/src/js/widgets/widgets.js | 1 + 1 file changed, 1 insertion(+) (limited to 'addons/web_editor/static/src/js/widgets/widgets.js') diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index ec747badabb..e04b02ade6f 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -363,6 +363,7 @@ var ImageWidget = MediaWidget.extend({ if (needle && needle.length) { domain.push('|', ['datas_fname', 'ilike', needle], ['name', 'ilike', needle]); } + domain.push('!', ['datas_fname', '=like', '%.crop'], '!', ['name', '=like', '%.crop']); return this._rpc({ model: 'ir.attachment', method: 'search_read', -- cgit v1.2.3 From cd812bd6f46988959535288d657353d273fb6d61 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Wed, 20 Jun 2018 16:59:48 +0200 Subject: [FIX] web_editor: fix saving and restoring of crop aspect ratio When an image was cropped and saved with a specific aspect ratio, it was not restored properly on further crop editions. --- addons/web_editor/static/src/js/widgets/widgets.js | 31 ++++++++++++++-------- 1 file changed, 20 insertions(+), 11 deletions(-) (limited to 'addons/web_editor/static/src/js/widgets/widgets.js') diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index e04b02ade6f..4bff0d45257 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -1581,21 +1581,21 @@ var CropImageDialog = Dialog.extend({ this.$media = $(this.media); var srcSplit = this.$media.attr('src').split('?'); var src = srcSplit[0]; + this.aspectRatioList = [ + [_t("Free"), '0/0', 0], + ["16:9", '16/9', 16 / 9], + ["4:3", '4/3', 4 / 3], + ["1:1", '1/1', 1], + ["2:3", '2/3', 2 / 3], + ]; this.imageData = { initialSrc: src, src: src, // the original src for cropped DB images will be fetched later srcParams: $.deparam(srcSplit[1] || ''), mimetype: _.str.endsWith(src, '.png') ? 'image/png' : 'image/jpeg', // the mimetype for DB images will be fetched later - aspectRatio: this.$media.data('aspectRatio') || 0, + aspectRatio: this.$media.data('aspectRatio') || this.aspectRatioList[0][1], isExternalImage: src[0] !== '/' && src.indexOf(window.location.host) < 0, }; - this.aspectRatioList = [ - [_t("Free"), 0], - ['16:9', 16 / 9], - ['4:3', 4 / 3], - ['1:1', 1], - ['2:3', 2 / 3], - ]; this.options = _.extend({ title: _t("Crop Image"), buttons: this.imageData.isExternalImage ? [{ @@ -1652,10 +1652,19 @@ var CropImageDialog = Dialog.extend({ start: function () { this.$cropperImage = this.$('.o_cropper_image'); if (this.$cropperImage.length) { + var data = this.$media.data(); + var ratio = 0; + for (var i = 0 ; i < this.aspectRatioList.length ; i++) { + if (this.aspectRatioList[i][1] === data.aspectRatio) { + ratio = this.aspectRatioList[i][2]; + break; + } + } this.$cropperImage.cropper({ viewMode: 1, autoCropArea: 1, - data: _.pick(this.$media.data(), 'aspectRatio', 'x', 'y', 'width', 'height', 'rotate', 'scaleX', 'scaleY') + aspectRatio: ratio, + data: _.pick(data, 'x', 'y', 'width', 'height', 'rotate', 'scaleX', 'scaleY') }); } return this._super.apply(this, arguments); @@ -1800,8 +1809,8 @@ var CropImageDialog = Dialog.extend({ case 'ratio': $option.siblings().removeClass('active'); $option.addClass('active'); - this.imageData.aspectRatio = value; - this.$cropperImage.cropper('setAspectRatio', this.imageData.aspectRatio); + this.imageData.aspectRatio = $option.data('label'); + this.$cropperImage.cropper('setAspectRatio', value); break; case 'zoom': case 'rotate': -- cgit v1.2.3 From a473453b3166a381130d9bd6ec4851b9e1ecab26 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Wed, 20 Jun 2018 18:08:28 +0200 Subject: [FIX] web_editor: review crop attachment creation/discard Previous implementation was wrong as it did not care about the ability to discard a crop edition. Indeed, whenever a crop dialog was saved, a crop attachment was created or updated at once. Discarding the editor would then either: - Seem to discard a first-time cropped image but would have created an attachment for nothing (just the image src was reset) OR - Fail to discard a second-time cropped image as the cropped attachment would have been directly updated and the image src would not have changed This commit changes the way it works: - On crop dialog save, the image is marked with required data for a potential future save of the cropped image (as a DB attachment) and the src is set to the base64 cropped image directly. Note that the code ends up being clearer and efficient that way too. --- addons/web_editor/static/src/js/widgets/widgets.js | 142 ++++++--------------- 1 file changed, 36 insertions(+), 106 deletions(-) (limited to 'addons/web_editor/static/src/js/widgets/widgets.js') diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index 4bff0d45257..829d37ca31d 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -1579,8 +1579,7 @@ var CropImageDialog = Dialog.extend({ init: function (parent, options, $editable, media) { this.media = media; this.$media = $(this.media); - var srcSplit = this.$media.attr('src').split('?'); - var src = srcSplit[0]; + var src = this.$media.attr('src').split('?')[0]; this.aspectRatioList = [ [_t("Free"), '0/0', 0], ["16:9", '16/9', 16 / 9], @@ -1589,9 +1588,8 @@ var CropImageDialog = Dialog.extend({ ["2:3", '2/3', 2 / 3], ]; this.imageData = { - initialSrc: src, - src: src, // the original src for cropped DB images will be fetched later - srcParams: $.deparam(srcSplit[1] || ''), + imageSrc: src, + originalSrc: src, // the original src for cropped DB images will be fetched later mimetype: _.str.endsWith(src, '.png') ? 'image/png' : 'image/jpeg', // the mimetype for DB images will be fetched later aspectRatio: this.$media.data('aspectRatio') || this.aspectRatioList[0][1], isExternalImage: src[0] !== '/' && src.indexOf(window.location.host) < 0, @@ -1625,12 +1623,12 @@ var CropImageDialog = Dialog.extend({ var defs = [def, ajax.loadLibs(this)]; var params = {}; var isDBImage = false; - var matchImageID = this.imageData.src.match(/^\/web\/image\/(\d+)/); + var matchImageID = this.imageData.imageSrc.match(/^\/web\/image\/(\d+)/); if (matchImageID) { params['image_id'] = parseInt(matchImageID[1]); isDBImage = true; } else { - var matchXmlID = this.imageData.src.match(/^\/web\/image\/([^/?]+)/); + var matchXmlID = this.imageData.imageSrc.match(/^\/web\/image\/([^/?]+)/); if (matchXmlID) { params['xml_id'] = matchXmlID[1]; isDBImage = true; @@ -1679,115 +1677,47 @@ var CropImageDialog = Dialog.extend({ this._super.apply(this, arguments); }, /** - * Stores the cropped image in database. + * Updates the DOM image with cropped data and associates required + * information for a potential future save (where required cropped data + * attachments will be created). * - * @private - * @returns {Deferred} + * @override */ save: function () { - var _super = this._super.bind(this); - var def; - if (this.imageData.initialSrc === this.imageData.src) { - // When this is a new image being cropped - def = this._createImage(); - } else { - // When this is a previously cropped image - def = this._updateImage(); - } - return def.then(_super); - }, + var self = this; + var cropperData = this.$cropperImage.cropper('getData'); - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- + // Mark the media for later creation of required cropped attachments... + this.$media.addClass('o_cropped_img_to_save'); + + // ... and attach required data + this.$media.data('crop:resModel', this.options.res_model); + this.$media.data('crop:resID', this.options.res_id); + this.$media.data('crop:id', this.imageData.id); + this.$media.data('crop:mimetype', this.imageData.mimetype); + this.$media.data('crop:originalSrc', this.imageData.originalSrc); + + // Mark the media with the cropping information which is required for + // a future crop edition + this.$media + .attr('data-aspect-ratio', this.imageData.aspectRatio) + .data('aspectRatio', this.imageData.aspectRatio); + _.each(cropperData, function (value, key) { + key = _.str.dasherize(key); + self.$media.attr('data-' + key, value); + self.$media.data(key, value); + }); - /** - * Gets the base64 data from the cropper canvas. - * - * @private - * @returns {string} base64 image data - */ - _getImageBase64: function () { - var cropperData = this.$cropperImage.cropper('getData'); + // Update the media with base64 source for preview before saving var canvas = this.$cropperImage.cropper('getCroppedCanvas', { width: cropperData.width, height: cropperData.height, }); - return canvas.toDataURL(this.imageData.mimetype).split(',')[1]; - }, - /** - * Creates a new attachment for the cropped image data and updates the image - * with the correct URL. - * - * @private - * @returns {Deferred} - */ - _createImage: function () { - var self = this; - var name = this.imageData.src + '.crop'; - return this._rpc({ - model: 'ir.attachment', - method: 'create', - args: [{ - res_model: this.options.res_model, - res_id: this.options.res_id, - name: name, - datas_fname: name, - datas: this._getImageBase64(), - mimetype: this.imageData.mimetype, - url: this.imageData.src, // To save the original image that was cropped - }], - }).then(function (attachmentID) { - return self._rpc({ - model: 'ir.attachment', - method: 'generate_access_token', - args: [[attachmentID]], - }).then(function (access_token) { - var params = _.omit(self.imageData.srcParams, 'unique'); - params.access_token = access_token[0]; - self.$media.attr('src', '/web/image/' + attachmentID + '?' + $.param(params)); - self._setCropBoxData(); - }); - }); - }, - /** - * Writes the cropped image attachment with the new data and forces a - * refresh of the image. - * - * @private - * @returns {Deferred} - */ - _updateImage: function () { - var self = this; - return this._rpc({ - model: 'ir.attachment', - method: 'write', - args: [[this.imageData.id], {datas: this._getImageBase64()}], - }).then(function () { - self.$media.addClass('o_cropped_img_to_clean'); - var params = _.clone(self.imageData.srcParams); - params.unique = new Date().getTime(); - self.$media.attr('src', '/web/image/' + self.imageData.id + '?' + $.param(params)); - self._setCropBoxData(); - }); - }, - /** - * Saves current crop configuration data as image attributes for future - * edition. - * - * @private - */ - _setCropBoxData: function () { - var self = this; - var cropBoxData = _.extend({ - aspectRatio: this.imageData.aspectRatio, // Library's getData does not export aspect ratio - }, this.$cropperImage.cropper('getData')); - _.each(cropBoxData, function (value, key) { - key = _.str.dasherize(key); - self.$media.attr('data-' + key, value); - self.$media.data(key, value); - }); + this.$media.attr('src', canvas.toDataURL(this.imageData.mimetype)); + this.$media.trigger('content_changed'); + + return this._super.apply(this, arguments); }, //-------------------------------------------------------------------------- -- cgit v1.2.3 From 6209b129377735a67e5ada990ca2261175a17e74 Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Fri, 22 Jun 2018 12:13:08 +0200 Subject: [FIX] web_editor: do 'reset' crop feature when changing ratio This reverts commit bf64efb5a0684bd61f3c2d520253721fe98c27c4. Indeed, it was in fact necessary as more complex bugs of the library can appear if we do not reset before. They even do that themself in their library demo: https://fengyuanchen.github.io/cropperjs/ --- addons/web_editor/static/src/js/widgets/widgets.js | 1 + 1 file changed, 1 insertion(+) (limited to 'addons/web_editor/static/src/js/widgets/widgets.js') diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index 829d37ca31d..d7662951999 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -1739,6 +1739,7 @@ var CropImageDialog = Dialog.extend({ case 'ratio': $option.siblings().removeClass('active'); $option.addClass('active'); + this.$cropperImage.cropper('reset'); this.imageData.aspectRatio = $option.data('label'); this.$cropperImage.cropper('setAspectRatio', value); break; -- cgit v1.2.3 From a17cd938640a0597f6a9781bb6b8b4b6abafbded Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Fri, 22 Jun 2018 12:58:40 +0200 Subject: [FIX] web_editor: allow recropping a newly cropped image Since https://github.com/odoo/odoo/commit/a473453b3166a381130d9bd6ec4851b9e1ecab26, a newly cropped image gets a base64 src until the user choose to save the page (where we create an attachment for the cropped picture). The problem is that when reentering the crop edition at once... it was not able to be performed because we did not handle base64 images there. This commit solves this and accidentally adds a feature: allow cropping base64 images. --- addons/web_editor/static/src/js/widgets/widgets.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'addons/web_editor/static/src/js/widgets/widgets.js') diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index d7662951999..92ebf9ec4fe 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -1589,10 +1589,10 @@ var CropImageDialog = Dialog.extend({ ]; this.imageData = { imageSrc: src, - originalSrc: src, // the original src for cropped DB images will be fetched later - mimetype: _.str.endsWith(src, '.png') ? 'image/png' : 'image/jpeg', // the mimetype for DB images will be fetched later + originalSrc: this.$media.data('crop:originalSrc') || src, // the original src for cropped DB images will be fetched later + mimetype: this.$media.data('crop:mimetype') || (_.str.endsWith(src, '.png') ? 'image/png' : 'image/jpeg'), // the mimetype for DB images will be fetched later aspectRatio: this.$media.data('aspectRatio') || this.aspectRatioList[0][1], - isExternalImage: src[0] !== '/' && src.indexOf(window.location.host) < 0, + isExternalImage: src.substr(0, 5) !== 'data:' && src[0] !== '/' && src.indexOf(window.location.host) < 0, }; this.options = _.extend({ title: _t("Crop Image"), -- cgit v1.2.3 From 22c9d60ad36ddbf750f45ae883fb378b23b668eb Mon Sep 17 00:00:00 2001 From: qsm-odoo Date: Wed, 27 Jun 2018 11:18:06 +0200 Subject: [FIX] web_editor: properly save media dialog without any image Commit https://github.com/odoo/odoo/commit/50418c481182cb414792da32f2a0529503e59f9c was correctly forward-ported up to 11.0. This commit fixes the forward-port from saas-11.2. --- addons/web_editor/static/src/js/widgets/widgets.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'addons/web_editor/static/src/js/widgets/widgets.js') diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js index 7aa300b5bbb..1b0abcecb14 100644 --- a/addons/web_editor/static/src/js/widgets/widgets.js +++ b/addons/web_editor/static/src/js/widgets/widgets.js @@ -291,6 +291,10 @@ var ImageWidget = MediaWidget.extend({ } var img = this.images[0]; + if (!img) { + return this.media; + } + var def = $.when(); if (!img.access_token) { def = this._rpc({ @@ -1214,7 +1218,7 @@ var MediaDialog = Dialog.extend({ } return $.when(this.active.save()).then(function (media) { - if (!self.media) { + if (!self.media && media) { self.range.insertNode(media, true); } self.media = media; @@ -1226,7 +1230,7 @@ var MediaDialog = Dialog.extend({ // Update editor bar after image edition (in case the image change to icon or other) _.defer(function () { - if (!self.media.parentNode) { + if (!self.media || !self.media.parentNode) { return; } range.createFromNode(self.media).select(); -- cgit v1.2.3