summaryrefslogtreecommitdiff
path: root/addons/web_editor
diff options
context:
space:
mode:
authorChristophe Simonis <chs@odoo.com>2018-06-29 12:46:00 +0200
committerChristophe Simonis <chs@odoo.com>2018-06-29 12:46:00 +0200
commit49edee1f4bd74bc342863d8ef50eb86de0b4c55f (patch)
tree20f449f08ba1f4a548c9cb2d82f67f2545048419 /addons/web_editor
parent4a08388492c6667b7f4a11f1397b8557c355b208 (diff)
parentd54a3726eca6ad48d969d5b4a6e559f783809832 (diff)
[MERGE] forward port branch saas-11.4 up to d54a3726eca
Diffstat (limited to 'addons/web_editor')
-rw-r--r--addons/web_editor/controllers/main.py14
-rw-r--r--addons/web_editor/models/ir_qweb.py1
-rw-r--r--addons/web_editor/static/src/js/editor/editor.js54
-rw-r--r--addons/web_editor/static/src/js/editor/snippets.editor.js147
-rw-r--r--addons/web_editor/static/src/js/editor/transcoder.js18
-rw-r--r--addons/web_editor/static/src/js/widgets/widgets.js186
-rw-r--r--addons/web_editor/static/src/scss/web_editor.ui.scss8
-rw-r--r--addons/web_editor/static/src/xml/editor.xml4
8 files changed, 220 insertions, 212 deletions
diff --git a/addons/web_editor/controllers/main.py b/addons/web_editor/controllers/main.py
index 688ca74aec6..22a1e514d88 100644
--- a/addons/web_editor/controllers/main.py
+++ b/addons/web_editor/controllers/main.py
@@ -255,11 +255,14 @@ class Web_Editor(http.Controller):
elif image_id:
record = request.env['ir.attachment'].browse(image_id)
result = {
- 'id': record.id,
'mimetype': record.mimetype,
}
+ # If we received the image ID and that image has an associated URL
+ # field, this should be a crop image attachment, so we return the ID
+ # and URL to confirm
if image_id and record.url:
- result['src'] = record.url
+ result['id'] = record.id
+ result['originalSrc'] = record.url
return result
## The get_assets_editor_resources route is in charge of transmitting the resources the assets
@@ -270,7 +273,7 @@ class Web_Editor(http.Controller):
## @param bundles - True if the bundles views must be fetched (default to False)
## @param bundles_restriction - Names of the bundle in which to look for scss files (if empty, search in all of them)
## @returns a dictionary with views info in the views key and style info in the scss key
- @http.route("/web_editor/get_assets_editor_resources", type="json", auth="user")
+ @http.route("/web_editor/get_assets_editor_resources", type="json", auth="user", website=True)
def get_assets_editor_resources(self, key, get_views=True, get_scss=True, bundles=False, bundles_restriction=[]):
# Related views must be fetched if the user wants the views and/or the style
views = request.env["ir.ui.view"].get_related_views(key, bundles=bundles)
@@ -387,16 +390,17 @@ class Web_Editor(http.Controller):
# Check if the file to save had already been modified
custom_attachment = IrAttachment.search([("url", "=", custom_url)])
+ datas = base64.b64encode((content or "\n").encode("utf-8"))
if custom_attachment:
# If it was already modified, simply override the corresponding attachment content
- custom_attachment.write({"datas": base64.b64encode(content.encode("utf-8"))})
+ custom_attachment.write({"datas": datas})
else:
# If not, create a new attachment to copy the original scss file content, with its modifications
IrAttachment.create(dict(
name = custom_url,
type = "binary",
mimetype = "text/scss",
- datas = base64.b64encode(content.encode("utf-8")),
+ datas = datas,
datas_fname = url.split("/")[-1],
url = custom_url, # Having an attachment of "binary" type with an non empty "url" field
# is quite of an hack. This allows to fetch the "datas" field by adding
diff --git a/addons/web_editor/models/ir_qweb.py b/addons/web_editor/models/ir_qweb.py
index 759590a379f..04b0253c3a2 100644
--- a/addons/web_editor/models/ir_qweb.py
+++ b/addons/web_editor/models/ir_qweb.py
@@ -175,6 +175,7 @@ class Contact(models.AbstractModel):
@api.model
def attributes(self, record, field_name, options, values):
attrs = super(Contact, self).attributes(record, field_name, options, values)
+ options.pop('template_options') # remove options not specific to this widget
attrs['data-oe-contact-options'] = json.dumps(options)
return attrs
diff --git a/addons/web_editor/static/src/js/editor/editor.js b/addons/web_editor/static/src/js/editor/editor.js
index 95831dd306a..7536c1f5516 100644
--- a/addons/web_editor/static/src/js/editor/editor.js
+++ b/addons/web_editor/static/src/js/editor/editor.js
@@ -145,7 +145,9 @@ var EditorMenuBar = Widget.extend({
save: function (reload) {
var self = this;
this.snippetsMenu.cleanForSave();
- return this.rte.save().then(function () {
+ this._saveCroppedImages().then(function () {
+ return self.rte.save();
+ }).then(function () {
if (reload !== false) {
return self._reload();
}
@@ -171,6 +173,56 @@ var EditorMenuBar = Widget.extend({
}
return $.Deferred();
},
+ /**
+ * @private
+ */
+ _saveCroppedImages: function () {
+ var self = this;
+ var defs = _.map(this.rte.editable().find('.o_cropped_img_to_save'), function (croppedImg) {
+ var $croppedImg = $(croppedImg);
+ $croppedImg.removeClass('o_cropped_img_to_save');
+
+ var resModel = $croppedImg.data('crop:resModel');
+ var resID = $croppedImg.data('crop:resID');
+ var cropID = $croppedImg.data('crop:id');
+ var mimetype = $croppedImg.data('crop:mimetype');
+ var originalSrc = $croppedImg.data('crop:originalSrc');
+
+ var datas = $croppedImg.attr('src').split(',')[1];
+
+ if (!cropID) {
+ var name = originalSrc + '.crop';
+ return self._rpc({
+ model: 'ir.attachment',
+ method: 'create',
+ args: [{
+ res_model: resModel,
+ res_id: resID,
+ name: name,
+ datas_fname: name,
+ datas: datas,
+ mimetype: mimetype,
+ url: originalSrc, // 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) {
+ $croppedImg.attr('src', '/web/image/' + attachmentID + '?access_token=' + access_token[0]);
+ });
+ });
+ } else {
+ return self._rpc({
+ model: 'ir.attachment',
+ method: 'write',
+ args: [[cropID], {datas: datas}],
+ });
+ }
+ });
+ return $.when.apply($, defs);
+ },
//--------------------------------------------------------------------------
// Handlers
diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js
index 708f576ebc8..cbaa7a2fbbc 100644
--- a/addons/web_editor/static/src/js/editor/snippets.editor.js
+++ b/addons/web_editor/static/src/js/editor/snippets.editor.js
@@ -90,6 +90,7 @@ var SnippetEditor = Widget.extend({
* @override
*/
destroy: function () {
+ this.cleanForSave();
this._super.apply(this, arguments);
this.$target.removeData('snippet-editor');
},
@@ -136,6 +137,57 @@ var SnippetEditor = Widget.extend({
this.$el.toggleClass('o_top_cover', offset.top < 15);
},
/**
+ * Removes the associated snippet from the DOM and destroys the associated
+ * editor (itself).
+ */
+ removeSnippet: function () {
+ this.toggleFocus(false);
+
+ this.trigger_up('call_for_each_child_snippet', {
+ $snippet: this.$target,
+ callback: function (editor, $snippet) {
+ for (var i in editor.styles) {
+ editor.styles[i].onRemove();
+ }
+ },
+ });
+
+ var $parent = this.$target.parent();
+ this.$target.find('*').andSelf().tooltip('destroy');
+ this.$target.remove();
+ this.$el.remove();
+
+ var node = $parent[0];
+ if (node && node.firstChild) {
+ $.summernote.core.dom.removeSpace(node, node.firstChild, 0, node.lastChild, 1);
+ if (!node.firstChild.tagName && node.firstChild.textContent === ' ') {
+ node.removeChild(node.firstChild);
+ }
+ }
+
+ if ($parent.closest(':data("snippet-editor")').length) {
+ while (!$parent.data('snippet-editor')) {
+ var $nextParent = $parent.parent();
+ if ($parent.children().length === 0 && $parent.text().trim() === '' && !$parent.hasClass('oe_structure')) {
+ $parent.remove();
+ }
+ $parent = $nextParent;
+ }
+ if ($parent.children().length === 0 && $parent.text().trim() === '' && !$parent.hasClass('oe_structure')) {
+ _.defer(function () {
+ $parent.data('snippet-editor').removeSnippet();
+ });
+ }
+ }
+
+ // clean editor if they are image or table in deleted content
+ $('.note-control-selection').hide();
+ $('.o_table_handler').remove();
+
+ this.trigger_up('snippet_removed');
+ this.destroy();
+ },
+ /**
* Displays/Hides the editor overlay and notifies the associated snippet
* options. Note: when it is displayed, this is here that the parent
* snippet options are moved to the editor overlay.
@@ -259,59 +311,6 @@ var SnippetEditor = Widget.extend({
return $.when.apply($, defs);
},
- /**
- * Removes the associated snippet from the DOM and destroys the associated
- * editor (itself).
- *
- * @private
- */
- _removeSnippet: function () {
- this.toggleFocus(false);
-
- this.trigger_up('call_for_each_child_snippet', {
- $snippet: this.$target,
- callback: function (editor, $snippet) {
- for (var i in editor.styles) {
- editor.styles[i].onRemove();
- }
- },
- });
-
- var $parent = this.$target.parent();
- this.$target.find('*').andSelf().tooltip('destroy');
- this.$target.remove();
- this.$el.remove();
-
- var node = $parent[0];
- if (node && node.firstChild) {
- $.summernote.core.dom.removeSpace(node, node.firstChild, 0, node.lastChild, 1);
- if (!node.firstChild.tagName && node.firstChild.textContent === ' ') {
- node.removeChild(node.firstChild);
- }
- }
-
- if ($parent.closest(':data("snippet-editor")').length) {
- while (!$parent.data('snippet-editor')) {
- var $nextParent = $parent.parent();
- if ($parent.children().length === 0 && $parent.text().trim() === '' && !$parent.hasClass('oe_structure')) {
- $parent.remove();
- }
- $parent = $nextParent;
- }
- if ($parent.children().length === 0 && $parent.text().trim() === '' && !$parent.hasClass('oe_structure')) {
- _.defer(function () {
- $parent.data('snippet-editor')._removeSnippet();
- });
- }
- }
-
- // clean editor if they are image or table in deleted content
- $('.note-control-selection').hide();
- $('.o_table_handler').remove();
-
- this.trigger_up('snippet_removed');
- this.destroy();
- },
//--------------------------------------------------------------------------
// Handlers
@@ -495,7 +494,7 @@ var SnippetEditor = Widget.extend({
_onRemoveClick: function (ev) {
ev.preventDefault();
this.trigger_up('request_history_undo_record', {$target: this.$target});
- this._removeSnippet();
+ this.removeSnippet();
},
});
@@ -511,6 +510,7 @@ var SnippetsMenu = Widget.extend({
deactivate_snippet: '_onDeactivateSnippet',
drag_and_drop_stop: '_onDragAndDropStop',
go_to_parent: '_onGoToParent',
+ remove_snippet: '_onRemoveSnippet',
snippet_removed: '_onSnippetRemoved',
},
@@ -634,9 +634,7 @@ var SnippetsMenu = Widget.extend({
*/
cleanForSave: function () {
this.trigger_up('ready_to_clean_for_save');
- _.each(this.snippetEditors, function (snippetEditor) {
- snippetEditor.cleanForSave();
- });
+ this._destroyEditors();
this.$editable.find('[contentEditable]')
.removeAttr('contentEditable')
@@ -644,17 +642,6 @@ var SnippetsMenu = Widget.extend({
this.$editable.find('.o_we_selected_image')
.removeClass('o_we_selected_image');
-
- // Removing query_string used to invalidate cache during image crop
- _.each(this.$editable.find('.o_cropped_img_to_clean'), function (croppedImg) {
- var $croppedImg = $(croppedImg);
- var s = $croppedImg.attr('src').split('?');
- var src = s[0];
- var urlParams = s[1] || '';
- urlParams = $.param(_.omit($.deparam(urlParams), 'unique'));
- $croppedImg.attr('src', src + (urlParams ? ('?' + urlParams) : ''));
- $croppedImg.removeClass('o_cropped_img_to_clean');
- });
},
//--------------------------------------------------------------------------
@@ -818,6 +805,14 @@ var SnippetsMenu = Widget.extend({
return $.when();
},
/**
+ * @private
+ */
+ _destroyEditors: function () {
+ _.each(this.snippetEditors, function (snippetEditor) {
+ snippetEditor.destroy();
+ });
+ },
+ /**
* Updates the cover dimensions of the current snippet editor.
*
* @private
@@ -1355,15 +1350,11 @@ var SnippetsMenu = Widget.extend({
/**
* Called when a snippet has moved in the page.
*
- * @todo technically, as a snippet has been moved, all editors should be
- * destroyed as their snippet options may not correspond to their
- * selector anymore. However this should rarely (maybe never ?) be the case,
- * so we might not want to do this as it would slow the editor.
- *
* @private
* @param {OdooEvent} ev
*/
_onDragAndDropStop: function (ev) {
+ this._destroyEditors();
this._activateSnippet(ev.data.$snippet);
},
/**
@@ -1378,6 +1369,16 @@ var SnippetsMenu = Widget.extend({
this._activateSnippet(ev.data.$snippet.parent());
},
/**
+ * @private
+ * @param {OdooEvent} ev
+ */
+ _onRemoveSnippet: function (ev) {
+ ev.stopPropagation();
+ this._createSnippetEditor(ev.data.$snippet).then(function (editor) {
+ editor.removeSnippet();
+ });
+ },
+ /**
* Called when a snippet is removed -> checks if there is draggable snippets
* to enable/disable as the DOM changed.
*
diff --git a/addons/web_editor/static/src/js/editor/transcoder.js b/addons/web_editor/static/src/js/editor/transcoder.js
index fb3e69eda29..84fe3cf8c93 100644
--- a/addons/web_editor/static/src/js/editor/transcoder.js
+++ b/addons/web_editor/static/src/js/editor/transcoder.js
@@ -108,9 +108,14 @@ function getMatchedCSSRules(a) {
// The css generates all the attributes separately and not in simplified form.
// In order to have a better compatibility (outlook for example) we simplify the css tags.
// e.g. border-left-style: none; border-bottom-s .... will be simplified in border-style = none
- _.each([['margin'], ['padding'], ['border', 'style']], function (attr) {
- var p = attr[0];
- var e = attr[1] ? '-' + attr[1] : '';
+ _.each([
+ {property: 'margin'},
+ {property: 'padding'},
+ {property: 'border', propertyEnd: '-style', defaultValue: 'none'},
+ ], function (propertyInfo) {
+ var p = propertyInfo.property;
+ var e = propertyInfo.propertyEnd || '';
+ var defVal = propertyInfo.defaultValue || 0;
if (style[p+'-top'+e] || style[p+'-right'+e] || style[p+'-bottom'+e] || style[p+'-left'+e]) {
if (style[p+'-top'+e] === style[p+'-right'+e] && style[p+'-top'+e] === style[p+'-bottom'+e] && style[p+'-top'+e] === style[p+'-left'+e]) {
@@ -119,7 +124,7 @@ function getMatchedCSSRules(a) {
}
else {
// keep => property: [top value] [right value] [bottom value] [left value];
- style[p+e] = (style[p+'-top'+e] || 0) + ' ' + (style[p+'-right'+e] || 0) + ' ' + (style[p+'-bottom'+e] || 0) + ' ' + (style[p+'-left'+e] || 0);
+ style[p+e] = (style[p+'-top'+e] || defVal) + ' ' + (style[p+'-right'+e] || defVal) + ' ' + (style[p+'-bottom'+e] || defVal) + ' ' + (style[p+'-left'+e] || defVal);
if (style[p+e].indexOf('inherit') !== -1 || style[p+e].indexOf('initial') !== -1) {
// keep => property-top: [top value]; property-right: [right value]; property-bottom: [bottom value]; property-left: [left value];
delete style[p+e];
@@ -277,11 +282,12 @@ function classToStyle($editable) {
}
// Outlook
- if (node.nodeName === 'A' && $target.hasClass('btn') && !$target.children().length) {
- var $hack = $('<table class="o_outlook_hack"><tr><td></td></tr></table>');
+ if (node.nodeName === 'A' && $target.hasClass('btn') && !$target.hasClass('btn-link') && !$target.children().length) {
+ var $hack = $('<table class="o_outlook_hack" style="display: inline-table;"><tr><td></td></tr></table>');
$hack.find('td')
.attr('height', $target.outerHeight())
.css({
+ 'text-align': $target.parent().css('text-align'),
'margin': $target.css('padding'),
'border-radius': $target.css('border-radius'),
'background-color': $target.css('background-color'),
diff --git a/addons/web_editor/static/src/js/widgets/widgets.js b/addons/web_editor/static/src/js/widgets/widgets.js
index 8ff47ba2c5a..e6c15aeb4ff 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({
@@ -363,6 +367,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',
@@ -1222,7 +1227,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;
@@ -1234,7 +1239,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();
@@ -1578,23 +1583,21 @@ 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];
- 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,
- isExternalImage: src[0] !== '/' && src.indexOf(window.location.host) < 0,
- };
+ var src = this.$media.attr('src').split('?')[0];
this.aspectRatioList = [
- [_t("Free"), 0],
- ['16:9', 16 / 9],
- ['4:3', 4 / 3],
- ['1:1', 1],
- ['2:3', 2 / 3],
+ [_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 = {
+ imageSrc: src,
+ 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.substr(0, 5) !== 'data:' && src[0] !== '/' && src.indexOf(window.location.host) < 0,
+ };
this.options = _.extend({
title: _t("Crop Image"),
buttons: this.imageData.isExternalImage ? [{
@@ -1624,12 +1627,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;
@@ -1651,10 +1654,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);
@@ -1669,115 +1681,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);
},
//--------------------------------------------------------------------------
@@ -1800,8 +1744,8 @@ var CropImageDialog = Dialog.extend({
$option.siblings().removeClass('active');
$option.addClass('active');
this.$cropperImage.cropper('reset');
- 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':
diff --git a/addons/web_editor/static/src/scss/web_editor.ui.scss b/addons/web_editor/static/src/scss/web_editor.ui.scss
index 787cb95f0af..179f814c4b3 100644
--- a/addons/web_editor/static/src/scss/web_editor.ui.scss
+++ b/addons/web_editor/static/src/scss/web_editor.ui.scss
@@ -440,7 +440,7 @@ html[lang] > body.editor_enable [data-oe-translation-state] {
> li {
position: relative;
&:hover {
- background-color: fadeOut($o-we-color-dark, 50%);
+ background-color: fade-out($o-we-color-dark, 0.5);
> a {
color: white;
background: transparent;
@@ -468,7 +468,7 @@ html[lang] > body.editor_enable [data-oe-translation-state] {
}
&.active > a {
color: white;
- background-color: fadeOut($o-we-color-dark, 50%);
+ background-color: fade-out($o-we-color-dark, 0.5);
&:before {
@include o-position-absolute($top: 5px, $left: 5px);
content: "\f00c";
@@ -793,7 +793,7 @@ html[lang] > body.editor_enable [data-oe-translation-state] {
padding: 4px;
border-radius: 2px;
border: 1px solid #989898;
- border-color: fadeOut($o-we-color-text-normal, 0.5);
+ border-color: fade-out($o-we-color-text-normal, 0.5);
}
.note-color-palette .note-color-row {
height: auto!important;
@@ -1587,7 +1587,7 @@ body .modal {
display: block;
width: 100%;
height: 1px;
- background: fadeOut(lighten($o-brand-optional, 30%), 50%);
+ background: fade-out(lighten($o-brand-optional, 30%), 0.5);
pointer-events: none;
&.grid-2 {
top: 66.66%
diff --git a/addons/web_editor/static/src/xml/editor.xml b/addons/web_editor/static/src/xml/editor.xml
index e0626801e68..45a16057cd5 100644
--- a/addons/web_editor/static/src/xml/editor.xml
+++ b/addons/web_editor/static/src/xml/editor.xml
@@ -313,13 +313,13 @@
</div>
<t t-else="">
<div class="o_crop_area">
- <img class="img img-responsive o_cropper_image" t-att-src="widget.imageData.src" />
+ <img class="img img-responsive o_cropper_image" t-att-src="widget.imageData.originalSrc"/>
</div>
<div class="o_crop_options text-center mt16">
<div class="btn-group" title="Aspect Ratio" data-toggle="buttons">
<t t-foreach="widget.aspectRatioList" t-as="ratio">
<t t-set="is_active" t-value="ratio[1] === widget.imageData.aspectRatio"/>
- <a href="#" t-attf-class="btn btn-default#{is_active and ' active' or ''}" data-event="ratio" t-att-data-value="ratio[1]">
+ <a href="#" t-attf-class="btn btn-default#{is_active and ' active' or ''}" data-event="ratio" t-att-data-label="ratio[1]" t-att-data-value="ratio[2]">
<t t-esc="ratio[0]"/>
</a>
</t>