[IMP] web_editor: improve image optimization workflow in media dialog
Previously, when selecting an image, the user could optimize their images by clicking a little cog icon in the media dialog. They would also be prompted to optimize their images when uploading them. In most cases, it makes a lot more sense to always save the original in the database, and optimize the original whenever it's dropped in the page automatically. A second problem is that when using the optimization feature, optimized images would be displayed along the originals by default, resulting in a lot of duplicates while browsing the media dialog. This commit changes both of these things: - By default, when uploading an image, the original uploaded image is used. The user can still optimize their image manually through the media-dialog by using the cog icon. When choosing an image in the media-dialog, if it's not already an optimized image, an optimized copy is automatically created and used instead. - Optimized images are now hidden by default, in the media dialog, and can be shown by clicking a checkbox at the top of the modal when in debug mode task-2091417 closes odoo/odoo#45174
This commit is contained in:
@@ -173,7 +173,16 @@ class Web_Editor(http.Controller):
|
||||
if attachment.type == 'url':
|
||||
raise UserError(_("You cannot change the quality, the width or the name of an URL attachment."))
|
||||
if copy:
|
||||
original = attachment
|
||||
attachment = attachment.copy()
|
||||
attachment.original_id = original
|
||||
# Uniquify url by adding a path segment with the id before the name
|
||||
if attachment.url:
|
||||
url_fragments = attachment.url.split('/')
|
||||
url_fragments.insert(-1, str(attachment.id))
|
||||
attachment.url = '/'.join(url_fragments)
|
||||
elif attachment.original_id:
|
||||
attachment.datas = attachment.original_id.datas
|
||||
data = {}
|
||||
if name:
|
||||
data['name'] = name
|
||||
|
||||
@@ -14,6 +14,7 @@ class IrAttachment(models.Model):
|
||||
image_src = fields.Char(compute='_compute_image_src')
|
||||
image_width = fields.Integer(compute='_compute_image_size')
|
||||
image_height = fields.Integer(compute='_compute_image_size')
|
||||
original_id = fields.Many2one('ir.attachment', string="Original (unoptimized, unresized) attachment")
|
||||
|
||||
def _compute_local_url(self):
|
||||
for attachment in self:
|
||||
@@ -47,4 +48,4 @@ class IrAttachment(models.Model):
|
||||
def _get_media_info(self):
|
||||
"""Return a dict with the values that we need on the media dialog."""
|
||||
self.ensure_one()
|
||||
return self.read(['id', 'name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'public', 'access_token', 'image_src', 'image_width', 'image_height'])[0]
|
||||
return self._read_format(['id', 'name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'public', 'access_token', 'image_src', 'image_width', 'image_height', 'original_id'])[0]
|
||||
|
||||
@@ -171,7 +171,6 @@ var EditorMenuBar = Widget.extend({
|
||||
if (this.snippetsMenu) {
|
||||
await this.snippetsMenu.cleanForSave();
|
||||
}
|
||||
|
||||
await this.getParent().saveCroppedImages(this.rte.editable());
|
||||
await this.rte.save();
|
||||
|
||||
|
||||
@@ -33,20 +33,37 @@ var ImageOptimizeDialog = Dialog.extend({
|
||||
{text: _t("Keep Original"), close: false, click: this._onKeepOriginalClick.bind(this)}
|
||||
],
|
||||
}, options));
|
||||
|
||||
this.isExisting = params.isExisting;
|
||||
this.attachment = params.attachment;
|
||||
this.params = params;
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
willStart: async function () {
|
||||
const _super = this._super.bind(this);
|
||||
const {isExisting, attachment, optimizedWidth} = this.params;
|
||||
this.isExisting = isExisting;
|
||||
this.attachment = attachment;
|
||||
let original = attachment;
|
||||
if (attachment.original_id) {
|
||||
[original] = await this._rpc({
|
||||
model: 'ir.attachment',
|
||||
method: 'read',
|
||||
args: [[attachment.original_id[0]], ['image_width', 'image_height']],
|
||||
});
|
||||
}
|
||||
this.image_width = original.image_width;
|
||||
this.image_height = original.image_height;
|
||||
// We do not support resizing and quality for:
|
||||
// - SVG because it doesn't make sense
|
||||
// - GIF because our current code is not made to handle all the frames
|
||||
this.disableResize = ['image/jpeg', 'image/jpe', 'image/jpg', 'image/png'].indexOf(this.attachment.mimetype) === -1;
|
||||
this.disableQuality = this.disableResize;
|
||||
this.toggleQuality = this.attachment.mimetype === 'image/png';
|
||||
this.optimizedWidth = Math.min(params.optimizedWidth || this.attachment.image_width, this.attachment.image_width);
|
||||
this.optimizedWidth = Math.min(optimizedWidth || this.image_width, this.image_width);
|
||||
this.defaultQuality = this.isExisting ? 100 : 80;
|
||||
this.defaultWidth = parseInt(this.isExisting ? this.attachment.image_width : this.optimizedWidth);
|
||||
this.defaultHeight = parseInt(this.isExisting || !this.attachment.image_width ? this.attachment.image_height :
|
||||
this.optimizedWidth / this.attachment.image_width * this.attachment.image_height);
|
||||
this.defaultWidth = parseInt(this.isExisting ? this.image_width : this.optimizedWidth);
|
||||
this.defaultHeight = parseInt(this.isExisting || !this.image_width ? this.image_height :
|
||||
this.optimizedWidth / this.image_width * this.image_height);
|
||||
|
||||
this.suggestedWidths = [];
|
||||
this._addSuggestedWidth(128, '128');
|
||||
@@ -56,11 +73,12 @@ var ImageOptimizeDialog = Dialog.extend({
|
||||
this._addSuggestedWidth(this.optimizedWidth,
|
||||
_.str.sprintf(_t("%d (Suggested)"), this.optimizedWidth));
|
||||
this.suggestedWidths.push({
|
||||
'width': this.attachment.image_width,
|
||||
'text': _.str.sprintf(_t("%d (Original)"), this.attachment.image_width),
|
||||
'width': this.image_width,
|
||||
'text': _.str.sprintf(_t("%d (Original)"), this.image_width),
|
||||
});
|
||||
this.suggestedWidths = _.sortBy(this.suggestedWidths, 'width');
|
||||
this._updatePreview = _.debounce(this._updatePreview.bind(this), 300);
|
||||
return _super(...arguments);
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
@@ -104,7 +122,7 @@ var ImageOptimizeDialog = Dialog.extend({
|
||||
* @param {string} text
|
||||
*/
|
||||
_addSuggestedWidth: function (size, text) {
|
||||
if (size < this.attachment.image_width) {
|
||||
if (size < this.image_width) {
|
||||
this.suggestedWidths.push({
|
||||
width: size,
|
||||
text: text,
|
||||
@@ -154,7 +172,7 @@ var ImageOptimizeDialog = Dialog.extend({
|
||||
var width = parseInt(this.$widthInput.val() || 0);
|
||||
var height = parseInt(this.$heightInput.val() || 0);
|
||||
this.$previewImage.attr('src', _.str.sprintf('/web/image/%d/%dx%d?quality=%d',
|
||||
this.attachment.id, width, height, this._getAdaptedQuality()));
|
||||
(this.attachment.original_id || [this.attachment.id])[0], width, height, this._getAdaptedQuality()));
|
||||
this.$widthPresets.removeClass('active');
|
||||
_.each(this.$widthPresets, function (button) {
|
||||
var $button = $(button);
|
||||
@@ -189,10 +207,10 @@ var ImageOptimizeDialog = Dialog.extend({
|
||||
if (quality < 0 || quality > 100) {
|
||||
isValid = false;
|
||||
}
|
||||
if (width < 0 || width > this.attachment.image_width) {
|
||||
if (width < 0 || width > this.image_width) {
|
||||
isValid = false;
|
||||
}
|
||||
if (height < 0 || height > this.attachment.image_height) {
|
||||
if (height < 0 || height > this.image_height) {
|
||||
isValid = false;
|
||||
}
|
||||
return isValid;
|
||||
@@ -208,7 +226,7 @@ var ImageOptimizeDialog = Dialog.extend({
|
||||
_onHeightInput: function () {
|
||||
var height = parseInt(this.$heightInput.val()) || 0;
|
||||
this.$widthInput
|
||||
.val(parseInt(height / this.attachment.image_height * this.attachment.image_width));
|
||||
.val(parseInt(height / this.image_height * this.image_width));
|
||||
this._updatePreview();
|
||||
this._validateForm();
|
||||
},
|
||||
@@ -244,7 +262,7 @@ var ImageOptimizeDialog = Dialog.extend({
|
||||
'width': parseInt(this.$widthInput.val() || 0),
|
||||
'height': parseInt(this.$heightInput.val() || 0),
|
||||
};
|
||||
if (this.isExisting) {
|
||||
if (this.isExisting && !this.attachment.original_id) {
|
||||
params['copy'] = true;
|
||||
}
|
||||
return this._rpc({
|
||||
@@ -292,7 +310,7 @@ var ImageOptimizeDialog = Dialog.extend({
|
||||
_onWidthInput: function () {
|
||||
var width = parseInt(this.$widthInput.val() || 0);
|
||||
this.$heightInput
|
||||
.val(parseInt(width / this.attachment.image_width * this.attachment.image_height));
|
||||
.val(parseInt(width / this.image_width * this.image_height));
|
||||
this._updatePreview();
|
||||
this._validateForm();
|
||||
},
|
||||
|
||||
@@ -118,7 +118,6 @@ var SearchableMediaWidget = MediaWidget.extend({
|
||||
var FileWidget = SearchableMediaWidget.extend({
|
||||
events: _.extend({}, SearchableMediaWidget.prototype.events || {}, {
|
||||
'click .o_upload_media_button': '_onUploadButtonClick',
|
||||
'click .o_we_quick_upload': '_onQuickUploadClick',
|
||||
'change .o_file_input': '_onFileInputChange',
|
||||
'click .o_upload_media_url_button': '_onUploadURLButtonClick',
|
||||
'input .o_we_url_input': '_onURLInputChange',
|
||||
@@ -150,7 +149,6 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
this.numberOfAttachmentsToDisplay = this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY;
|
||||
|
||||
this.options = _.extend({
|
||||
showQuickUpload: config.isDebug(),
|
||||
mediaWidth: media && media.clientWidth,
|
||||
}, options || {});
|
||||
|
||||
@@ -192,7 +190,7 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
await this._renderThumbnails();
|
||||
if (o.url) {
|
||||
self._selectAttachement(_.find(self.attachments, function (attachment) {
|
||||
return attachment.image_src === o.url;
|
||||
return o.url === attachment.image_src;
|
||||
}) || o);
|
||||
}
|
||||
return def;
|
||||
@@ -231,7 +229,7 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
args: [],
|
||||
kwargs: {
|
||||
domain: this._getAttachmentsDomain(this.needle),
|
||||
fields: ['name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'public', 'access_token', 'image_src', 'image_width', 'image_height'],
|
||||
fields: ['name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'public', 'access_token', 'image_src', 'image_width', 'image_height', 'original_id'],
|
||||
order: [{name: 'id', asc: false}],
|
||||
context: this.options.context,
|
||||
// Try to fetch first record of next page just to know whether there is a next page.
|
||||
@@ -328,7 +326,8 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
var self = this;
|
||||
this.$('.o_existing_attachment_cell.o_we_attachment_selected').removeClass("o_we_attachment_selected");
|
||||
_.each(this.selectedAttachments, function (attachment) {
|
||||
self.$('.o_existing_attachment_cell[data-id=' + attachment.id + ']').addClass("o_we_attachment_selected");
|
||||
self.$('.o_existing_attachment_cell[data-id=' + attachment.id + ']')
|
||||
.addClass("o_we_attachment_selected").css('display', '');
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -336,6 +335,7 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
* @param {object} attachment
|
||||
*/
|
||||
_handleNewAttachment: function (attachment) {
|
||||
this.attachments = this.attachments.filter(att => att.id !== attachment.id);
|
||||
this.attachments.unshift(attachment);
|
||||
this._renderThumbnails();
|
||||
this._selectAttachement(attachment);
|
||||
@@ -368,7 +368,7 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
* @returns {Promise} resolved with the updated attachment object when the
|
||||
* optimize dialog is saved. Rejected if the dialog is otherwise closed.
|
||||
*/
|
||||
_openImageOptimizeDialog: function (attachment, isExisting) {
|
||||
_openImageOptimizeDialog: function (attachment, isExisting, $attachmentCell) {
|
||||
var self = this;
|
||||
var promise = new Promise(function (resolve, reject) {
|
||||
self.trigger_up('hide_parent_dialog_request');
|
||||
@@ -390,7 +390,7 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
}
|
||||
});
|
||||
});
|
||||
var always = function () {
|
||||
var always = () => {
|
||||
self.trigger_up('show_parent_dialog_request');
|
||||
};
|
||||
promise.then(always).guardedCatch(always);
|
||||
@@ -431,21 +431,23 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
* @private
|
||||
* @returns {Promise}
|
||||
*/
|
||||
_save: function () {
|
||||
var self = this;
|
||||
|
||||
_save: async function () {
|
||||
if (this.options.multiImages) {
|
||||
return Promise.resolve(this.selectedAttachments);
|
||||
return this.selectedAttachments;
|
||||
}
|
||||
|
||||
var img = this.selectedAttachments[0];
|
||||
if (!img || !img.id) {
|
||||
return Promise.resolve(this.media);
|
||||
return this.media;
|
||||
}
|
||||
|
||||
// Auto optimize unoptimized images.
|
||||
if (['image/jpeg', 'image/jpe', 'image/jpg', 'image/png'].includes(img.mimetype) && img.type === 'binary' && !img.original_id) {
|
||||
img = await this._optimizeAttachment(img);
|
||||
}
|
||||
|
||||
var prom;
|
||||
if (!img.public && !img.access_token) {
|
||||
prom = this._rpc({
|
||||
await this._rpc({
|
||||
model: 'ir.attachment',
|
||||
method: 'generate_access_token',
|
||||
args: [[img.id]]
|
||||
@@ -454,52 +456,67 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
});
|
||||
}
|
||||
|
||||
return Promise.resolve(prom).then(function () {
|
||||
if (img.image_src) {
|
||||
var src = img.image_src;
|
||||
if (!img.public && img.access_token) {
|
||||
src += _.str.sprintf('?access_token=%s', img.access_token);
|
||||
}
|
||||
if (!self.$media.is('img')) {
|
||||
|
||||
// Note: by default the images receive the bootstrap opt-in
|
||||
// img-fluid class. We cannot make them all responsive
|
||||
// by design because of libraries and client databases img.
|
||||
self.$media = $('<img/>', {class: 'img-fluid o_we_custom_image'});
|
||||
self.media = self.$media[0];
|
||||
}
|
||||
self.$media.attr('src', src);
|
||||
} else {
|
||||
if (!self.$media.is('a')) {
|
||||
$('.note-control-selection').hide();
|
||||
self.$media = $('<a/>');
|
||||
self.media = self.$media[0];
|
||||
}
|
||||
var href = '/web/content/' + img.id + '?';
|
||||
if (!img.public && img.access_token) {
|
||||
href += _.str.sprintf('access_token=%s&', img.access_token);
|
||||
}
|
||||
href += 'unique=' + img.checksum + '&download=true';
|
||||
self.$media.attr('href', href);
|
||||
self.$media.addClass('o_image').attr('title', img.name).attr('data-mimetype', img.mimetype);
|
||||
if (img.image_src) {
|
||||
var src = img.image_src;
|
||||
if (!img.public && img.access_token) {
|
||||
src += _.str.sprintf('?access_token=%s', img.access_token);
|
||||
}
|
||||
if (!this.$media.is('img')) {
|
||||
|
||||
self.$media.attr('alt', img.alt);
|
||||
var style = self.style;
|
||||
if (style) {
|
||||
self.$media.css(style);
|
||||
// Note: by default the images receive the bootstrap opt-in
|
||||
// img-fluid class. We cannot make them all responsive
|
||||
// by design because of libraries and client databases img.
|
||||
this.$media = $('<img/>', {class: 'img-fluid o_we_custom_image'});
|
||||
this.media = this.$media[0];
|
||||
}
|
||||
this.$media.attr('src', src);
|
||||
} else {
|
||||
if (!this.$media.is('a')) {
|
||||
$('.note-control-selection').hide();
|
||||
this.$media = $('<a/>');
|
||||
this.media = this.$media[0];
|
||||
}
|
||||
var href = '/web/content/' + img.id + '?';
|
||||
if (!img.public && img.access_token) {
|
||||
href += _.str.sprintf('access_token=%s&', img.access_token);
|
||||
}
|
||||
href += 'unique=' + img.checksum + '&download=true';
|
||||
this.$media.attr('href', href);
|
||||
this.$media.addClass('o_image').attr('title', img.name).attr('data-mimetype', img.mimetype);
|
||||
}
|
||||
|
||||
// Remove crop related attributes
|
||||
if (self.$media.attr('data-aspect-ratio')) {
|
||||
var attrs = ['aspect-ratio', 'x', 'y', 'width', 'height', 'rotate', 'scale-x', 'scale-y', 'crop:originalSrc'];
|
||||
self.$media.removeClass('o_cropped_img_to_save');
|
||||
_.each(attrs, function (attr) {
|
||||
self.$media.removeData(attr);
|
||||
self.$media.removeAttr('data-' + attr);
|
||||
});
|
||||
}
|
||||
return self.media;
|
||||
this.$media.attr('alt', img.alt);
|
||||
var style = this.style;
|
||||
if (style) {
|
||||
this.$media.css(style);
|
||||
}
|
||||
|
||||
// Remove crop related attributes
|
||||
if (this.$media.attr('data-aspect-ratio')) {
|
||||
var attrs = ['aspect-ratio', 'x', 'y', 'width', 'height', 'rotate', 'scale-x', 'scale-y', 'crop:originalSrc'];
|
||||
this.$media.removeClass('o_cropped_img_to_save');
|
||||
attrs.forEach(attr => {
|
||||
this.$media.removeData(attr);
|
||||
this.$media.removeAttr('data-' + attr);
|
||||
});
|
||||
}
|
||||
return this.media;
|
||||
},
|
||||
/**
|
||||
* Creates and returns an optimized copy of an attachment.
|
||||
*
|
||||
* @private
|
||||
* @param {object} attachment
|
||||
*/
|
||||
_optimizeAttachment: function (attachment) {
|
||||
return this._rpc({
|
||||
route: `/web_editor/attachment/${attachment.id}/update`,
|
||||
params: {
|
||||
copy: true,
|
||||
name: attachment.name,
|
||||
quality: attachment.mimetype === 'image/png' ? 0 : 80,
|
||||
width: this._computeOptimizedWidth(),
|
||||
},
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -562,13 +579,12 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
* @private
|
||||
*/
|
||||
_onExistingOptimizeClick: function (ev) {
|
||||
var self = this;
|
||||
var $a = $(ev.currentTarget).closest('.o_existing_attachment_cell');
|
||||
var id = parseInt($a.data('id'), 10);
|
||||
var attachment = _.findWhere(this.attachments, {id: id});
|
||||
ev.stopPropagation();
|
||||
return this._openImageOptimizeDialog(attachment, true).then(function (newAttachment) {
|
||||
self._handleNewAttachment(newAttachment);
|
||||
return this._openImageOptimizeDialog(attachment, true, $a).then(newAttachment => {
|
||||
this._handleNewAttachment(newAttachment);
|
||||
});
|
||||
},
|
||||
/**
|
||||
@@ -594,7 +610,6 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
_addData: function () {
|
||||
var self = this;
|
||||
var uploadMutex = new concurrency.Mutex();
|
||||
var optimizeMutex = new concurrency.Mutex();
|
||||
|
||||
// Upload the smallest file first to block the user the least possible.
|
||||
var files = _.sortBy(this.$fileInput[0].files, 'size');
|
||||
@@ -604,54 +619,30 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
// limited by bandwidth.
|
||||
uploadMutex.exec(function () {
|
||||
return utils.getDataURLFromFile(file).then(function (result) {
|
||||
var params = {
|
||||
'name': file.name,
|
||||
'data': result.split(',')[1],
|
||||
'res_id': self.options.res_id,
|
||||
'res_model': self.options.res_model,
|
||||
};
|
||||
if (self.quickUpload) {
|
||||
params['width'] = self._computeOptimizedWidth();
|
||||
params['quality'] = 80;
|
||||
} else {
|
||||
params['width'] = 0;
|
||||
params['quality'] = 0;
|
||||
}
|
||||
return self._rpc({
|
||||
route: '/web_editor/attachment/add_data',
|
||||
params: params,
|
||||
params: {
|
||||
'name': file.name,
|
||||
'data': result.split(',')[1],
|
||||
'res_id': self.options.res_id,
|
||||
'res_model': self.options.res_model,
|
||||
'width': 0,
|
||||
'quality': 0,
|
||||
},
|
||||
}).then(function (attachment) {
|
||||
if (attachment.image_src && !self.quickUpload) {
|
||||
optimizeMutex.exec(function () {
|
||||
return self._openImageOptimizeDialog(attachment).then(function (updatedAttachment) {
|
||||
self._handleNewAttachment(updatedAttachment);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
self._handleNewAttachment(attachment);
|
||||
}
|
||||
self._handleNewAttachment(attachment);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return uploadMutex.getUnlockedDef().then(function () {
|
||||
return optimizeMutex.getUnlockedDef().then(function () {
|
||||
self.quickUpload = false;
|
||||
if (!self.options.multiImages && !self.noSave) {
|
||||
self.trigger_up('save_request');
|
||||
}
|
||||
self.noSave = false;
|
||||
});
|
||||
if (!self.options.multiImages && !self.noSave) {
|
||||
self.trigger_up('save_request');
|
||||
}
|
||||
self.noSave = false;
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
_onQuickUploadClick: function () {
|
||||
this.quickUpload = true;
|
||||
this.$uploadButton.trigger('click');
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
@@ -671,6 +662,7 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
}).then(function (prevented) {
|
||||
if (_.isEmpty(prevented)) {
|
||||
self.attachments = _.without(self.attachments, attachment);
|
||||
self.attachments.filter(at => at.original_id[0] === attachment.id).forEach(at => delete at.original_id);
|
||||
if (!self.attachments.length) {
|
||||
self._renderThumbnails(); //render the message and image if empty
|
||||
} else {
|
||||
@@ -760,6 +752,9 @@ var FileWidget = SearchableMediaWidget.extend({
|
||||
var ImageWidget = FileWidget.extend({
|
||||
template: 'wysiwyg.widgets.image',
|
||||
existingAttachmentsTemplate: 'wysiwyg.widgets.image.existing.attachments',
|
||||
events: Object.assign({}, FileWidget.prototype.events, {
|
||||
'change input.o_we_show_optimized': '_onShowOptimizedChange',
|
||||
}),
|
||||
MIN_ROW_HEIGHT: 128,
|
||||
|
||||
/**
|
||||
@@ -820,6 +815,7 @@ var ImageWidget = FileWidget.extend({
|
||||
toReplace.replaceWith(el);
|
||||
}
|
||||
});
|
||||
this._toggleOptimized(this.$('input.o_we_show_optimized')[0].checked);
|
||||
// Placeholders have a 3:2 aspect ratio like most photos.
|
||||
const placeholderWidth = 3 / 2 * this.MIN_ROW_HEIGHT;
|
||||
this.$('.o_we_attachment_placeholder').css({
|
||||
@@ -827,6 +823,23 @@ var ImageWidget = FileWidget.extend({
|
||||
flexBasis: placeholderWidth,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_renderExisting: function (attachments) {
|
||||
return QWeb.render(this.existingAttachmentsTemplate, {
|
||||
attachments: attachments,
|
||||
widget: this,
|
||||
});
|
||||
},
|
||||
/**
|
||||
* @private
|
||||
*
|
||||
* @param {boolean} value whether to toggle optimized attachments on or off
|
||||
*/
|
||||
_toggleOptimized: function (value) {
|
||||
this.$('.o_we_attachment_optimized').each((i, cell) => cell.style.setProperty('display', value ? null : 'none', 'important'));
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Handlers
|
||||
@@ -849,6 +862,12 @@ var ImageWidget = FileWidget.extend({
|
||||
cell.classList.add('d-flex');
|
||||
cell.dataset.loaded = 'true';
|
||||
},
|
||||
/**
|
||||
* @override
|
||||
*/
|
||||
_onShowOptimizedChange: function (ev) {
|
||||
this._toggleOptimized(ev.target.checked);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -269,6 +269,13 @@ body .modal {
|
||||
box-shadow: 0px 0px 2px 2px $o-brand-primary;
|
||||
}
|
||||
|
||||
.o_we_attachment_optimized .badge {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
margin: 2px;
|
||||
}
|
||||
|
||||
.font-icons-icons {
|
||||
> span {
|
||||
text-align: center;
|
||||
|
||||
@@ -142,7 +142,6 @@
|
||||
<button type="button" class="btn btn-primary o_upload_media_button">
|
||||
<t t-esc="uploadText"/>
|
||||
</button>
|
||||
<t t-raw="alternateUpload"/>
|
||||
</div>
|
||||
|
||||
<div class="input-group align-items-center ml-2">
|
||||
@@ -158,7 +157,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<t t-raw="0"/>
|
||||
<t t-call="wysiwyg.widgets.media.search"/>
|
||||
</div>
|
||||
</t>
|
||||
@@ -170,12 +169,14 @@
|
||||
<t t-set="addText">Add URL</t>
|
||||
<t t-set="searchPlaceholder">Search an image</t>
|
||||
<t t-set="urlWarningTitle">The URL does not contain any image. The file will be added in the document section.</t>
|
||||
<t t-if="widget.options.showQuickUpload" t-set="alternateUpload">
|
||||
<button class="btn btn-primary o_we_quick_upload" type="button" title="Quick Upload">
|
||||
<i class="fa fa-fast-forward"/>
|
||||
<span class="sr-only">Quick Upload</span>
|
||||
</button>
|
||||
</t>
|
||||
<div class="d-flex justify-content-end flex-grow-1 pr-3">
|
||||
<div t-attf-class="custom-control custom-switch #{__debug__ or 'd-none'}">
|
||||
<input class="o_we_show_optimized ml-2 custom-control-input" type="checkbox" id="o_we_show_optimized_switch"/>
|
||||
<label class="custom-control-label" for="o_we_show_optimized_switch">
|
||||
Show optimized images
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</t>
|
||||
</t>
|
||||
|
||||
@@ -215,7 +216,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">Width</div>
|
||||
</div>
|
||||
<input type="number" class="form-control" id="o_we_width" name="width" aria-describedby="sizeHelp" min="1" t-att-max="widget.attachment.image_width" t-att-value="widget.defaultWidth"/>
|
||||
<input type="number" class="form-control" id="o_we_width" name="width" aria-describedby="sizeHelp" min="1" t-att-max="widget.image_width" t-att-value="widget.defaultWidth"/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">px</div>
|
||||
</div>
|
||||
@@ -229,7 +230,7 @@
|
||||
<div class="input-group-prepend">
|
||||
<div class="input-group-text">Height</div>
|
||||
</div>
|
||||
<input type="number" class="form-control" id="o_we_height" name="height" aria-describedby="sizeHelp" min="1" t-att-max="widget.attachment.image_height" t-att-value="widget.defaultHeight"/>
|
||||
<input type="number" class="form-control" id="o_we_height" name="height" aria-describedby="sizeHelp" min="1" t-att-max="widget.image_height" t-att-value="widget.defaultHeight"/>
|
||||
<div class="input-group-append">
|
||||
<div class="input-group-text">px</div>
|
||||
</div>
|
||||
@@ -304,13 +305,15 @@
|
||||
</t>
|
||||
|
||||
<t t-name="wysiwyg.widgets.image.existing.attachment">
|
||||
<div class="o_existing_attachment_cell position-relative bg-light" t-att-data-id="attachment.id">
|
||||
<t t-set="isOptimized" t-value="!!attachment.original_id"/>
|
||||
<div t-attf-class="o_existing_attachment_cell position-relative bg-light #{isOptimized and 'o_we_attachment_optimized d-none'}" t-att-data-id="attachment.id">
|
||||
<i t-if="attachment.type === 'binary'" class="fa fa-cog o_existing_attachment_optimize p-2" title="Optimize" role="img" aria-label="Optimize"/>
|
||||
<t t-call="wysiwyg.widgets.file.existing.remove"/>
|
||||
<div t-attf-class="d-flex align-items-center justify-content-center h-100 w-100">
|
||||
<!-- 256 is 2 * ImageWidget.MIN_ROW_HEIGHT, hopefully the highest display width of any image -->
|
||||
<img class="img img-fluid o_we_attachment_highlight" t-attf-src="#{attachment.image_src}?height=256" t-att-alt="attachment.name" t-att-title="attachment.name"/>
|
||||
</div>
|
||||
<span t-if="isOptimized" class="badge badge-success">Optimized</span>
|
||||
</div>
|
||||
</t>
|
||||
|
||||
|
||||
@@ -284,9 +284,7 @@ QUnit.module('web_editor', {}, function () {
|
||||
await testUtils.dom.click($('.modal #editor-media-image .o_existing_attachment_cell:first').removeClass('d-none'));
|
||||
|
||||
var $editable = form.$('.oe_form_field[name="body"] .note-editable');
|
||||
|
||||
assert.strictEqual($editable.data('wysiwyg').getValue(),
|
||||
'<p>t<img class="img-fluid o_we_custom_image" data-src="/web_editor/static/src/img/transparent.png">oto toto toto</p><p>tata</p>',
|
||||
assert.ok($editable.find('img')[0].dataset.src.includes('/web_editor/static/src/img/transparent.png'),
|
||||
"should have the image in the dom");
|
||||
|
||||
testUtils.mock.unpatch(MediaDialog);
|
||||
|
||||
@@ -259,6 +259,11 @@ function wysiwygData(data) {
|
||||
string: "image_height",
|
||||
type: 'integer',
|
||||
},
|
||||
original_id: {
|
||||
string: "original_id",
|
||||
type: 'many2one',
|
||||
relation: 'ir.attachment',
|
||||
},
|
||||
},
|
||||
records: [{
|
||||
id: 1,
|
||||
|
||||
Reference in New Issue
Block a user