[IMP] web_editor, *: improve file media widget views & selection

* = web_unsplash, website_blog

Rename "image" to "attachment" when appropriate in FileWidget

Improve image selection / highlight code (now based on attachment id)

Improve image grid view:
 - Remove the row mechanism and replace it by a single loop over the images.
 - Take advantage of row wrap for a responsive display.
 - Uniformize the height (prevent tall images from moving down everything).
 - Load a miniature of the images when possible, remove the loading placeholder.
 	This should significantly reduce the download size when opening the dialog,
 	at the cost of CPU usage on the server.
 - Split the existing image view into one view for the loop and one view for the
 	image itself. This way in a following commit we can reuse the image view
 	when adding a new one, instead of having to render everything again.

task-1930726
PR: #31208
This commit is contained in:
Sébastien Theys
2019-06-04 22:14:13 +02:00
parent 86d304fdb0
commit 191c556d6b
11 changed files with 167 additions and 182 deletions
+3 -3
View File
@@ -159,7 +159,7 @@ class Web_Editor(http.Controller):
@http.route('/web_editor/add_image_base64', type='json', auth='user', methods=['POST'], website=True)
def add_image_base64(self, res_model, res_id, image_base64, filename, disable_optimization=None, **kwargs):
attachment = self._image_to_attachment(res_model, res_id, image_base64, filename, filename, disable_optimization=disable_optimization)
return attachment.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token'])[0]
return attachment.read(['name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'access_token'])[0]
def _image_to_attachment(self, res_model, res_id, image_base64, name, datas_fname, disable_optimization=None):
Attachments = request.env['ir.attachment']
@@ -209,7 +209,7 @@ class Web_Editor(http.Controller):
'res_model': res_model,
})
attachment.generate_access_token()
uploads += attachment.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token'])
uploads += attachment.read(['name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'access_token'])
else: # images provided
try:
attachments = request.env['ir.attachment']
@@ -220,7 +220,7 @@ class Web_Editor(http.Controller):
if filters:
datas_fname = filters + '_' + datas_fname
attachments += self._image_to_attachment(res_model, res_id, image_base64, name, datas_fname, disable_optimization=disable_optimization)
uploads += attachments.read(['name', 'mimetype', 'checksum', 'url', 'res_id', 'res_model', 'access_token'])
uploads += attachments.read(['name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'access_token'])
except Exception as e:
logger.exception("Failed to upload image to attachment")
message = str(e)
@@ -103,19 +103,18 @@ var FileWidget = SearchableMediaWidget.extend({
events: _.extend({}, SearchableMediaWidget.prototype.events || {}, {
'click .o_upload_media_button': '_onUploadButtonClick',
'click .o_upload_media_button_no_optimization': '_onUploadButtonNoOptimizationClick',
'change .o_file_input': '_onImageSelection',
'change .o_file_input': '_onFileInputChange',
'click .o_upload_media_url_button': '_onUploadURLButtonClick',
'input .o_we_url_input': '_onURLInputChange',
'click .o_existing_attachment_cell [data-src]': '_onImageClick',
'dblclick .o_existing_attachment_cell [data-src]': '_onImageDblClick',
'click .o_existing_attachment_cell': '_onAttachmentClick',
'dblclick .o_existing_attachment_cell': '_onAttachmentDblClick',
'click .o_existing_attachment_remove': '_onRemoveClick',
'click .o_load_more': '_onLoadMoreClick',
}),
existingAttachmentsTemplate: undefined,
IMAGE_MIMETYPES: ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png'],
IMAGES_PER_ROW: 6,
IMAGES_ROWS: 5,
NUMBER_OF_ATTACHMENTS_TO_DISPLAY: 30,
/**
* @constructor
@@ -124,15 +123,15 @@ var FileWidget = SearchableMediaWidget.extend({
this._super.apply(this, arguments);
this._mutex = new concurrency.Mutex();
this.imagesRows = this.IMAGES_ROWS;
this.IMAGES_DISPLAYED_TOTAL = this.IMAGES_PER_ROW * this.imagesRows;
this.numberOfAttachmentsToDisplay = this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY;
this.options = _.extend({
firstFilters: [],
lastFilters: [],
}, options || {});
this.images = [];
this.attachments = [];
this.selectedAttachments = [];
},
/**
* @override
@@ -159,8 +158,10 @@ var FileWidget = SearchableMediaWidget.extend({
this.$urlError = this.$('.o_we_url_error');
this.$errorText = this.$('.o_we_error_text');
this._renderImages(true);
this._renderImages();
// If there is already an attachment on the target, select by default
// that attachment if it is among the loaded images.
var o = {
url: null,
alt: null,
@@ -172,7 +173,9 @@ var FileWidget = SearchableMediaWidget.extend({
o.id = +o.url.match(/\/web\/content\/(\d+)/, '')[1];
}
if (o.url) {
self._toggleImage(_.find(self.records, function (record) { return record.url === o.url;}) || o, true);
self._toggleImage(_.find(self.attachments, function (attachment) {
return attachment.url === o.url;
}) || o, true);
}
return def;
@@ -193,12 +196,11 @@ var FileWidget = SearchableMediaWidget.extend({
},
/**
* @override
* @param {boolean} noRender: if true, do not render the found attachments
*/
search: function (needle, noRender) {
var self = this;
if (!noRender) {
this.$urlInput.val('').trigger('input').trigger('change');
}
return this._rpc({
model: 'ir.attachment',
method: 'search_read',
@@ -209,8 +211,8 @@ var FileWidget = SearchableMediaWidget.extend({
order: [{name: 'id', asc: false}],
context: this.options.context,
},
}).then(function (records) {
self.records = _.chain(records)
}).then(function (attachments) {
self.attachments = _.chain(attachments)
.filter(function (r) {
return (r.type === "binary" || r.url && r.url.length > 0);
})
@@ -234,13 +236,13 @@ var FileWidget = SearchableMediaWidget.extend({
})
.value();
_.each(self.records, function (record) {
record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, encodeURI(record.name)); // Name is added for SEO purposes
record.isDocument = !(/gif|jpe|jpg|png/.test(record.mimetype));
_.each(self.attachments, function (attachment) {
// Name is added for SEO purposes
attachment.src = attachment.url || _.str.sprintf('/web/image/%s/%s', attachment.id, encodeURI(attachment.name));
attachment.isDocument = !(/gif|jpe|jpg|png/.test(attachment.mimetype));
});
if (!noRender) {
self._renderImages();
self._adaptLoadMore();
}
});
},
@@ -249,14 +251,6 @@ var FileWidget = SearchableMediaWidget.extend({
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_adaptLoadMore: function () {
var noMoreImgToLoad = this.IMAGES_DISPLAYED_TOTAL >= this.records.length;
this.$('.o_load_more').toggleClass('d-none', noMoreImgToLoad);
this.$('.o_load_done_msg').toggleClass('d-none', !noMoreImgToLoad);
},
/**
* @override
*/
@@ -318,27 +312,20 @@ var FileWidget = SearchableMediaWidget.extend({
/**
* @private
*/
_highlightSelectedImages: function () {
_highlightSelected: function () {
var self = this;
this.$('.o_existing_attachment_cell.o_selected').removeClass("o_selected");
var $select = this.$('.o_existing_attachment_cell [data-src]').filter(function () {
var $img = $(this);
return !!_.find(self.images, function (v) {
return (v.url === $img.data("src") || ($img.data("url") && v.url === $img.data("url")) || v.id === $img.data("id"));
});
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");
});
$select.closest('.o_existing_attachment_cell').addClass("o_selected");
return $select;
},
/**
* @private
*/
_loadMoreImages: function (forceSearch) {
this.imagesRows += 2;
this.IMAGES_DISPLAYED_TOTAL = this.imagesRows * this.IMAGES_PER_ROW;
this.numberOfAttachmentsToDisplay += 10;
if (!forceSearch) {
this._renderImages();
this._adaptLoadMore();
} else {
this.search(this.$('.o_we_search').val() || '');
}
@@ -346,58 +333,34 @@ var FileWidget = SearchableMediaWidget.extend({
/**
* Renders the existing attachments and returns the result as a string.
*
* @param {Object[]} rows
* @param {boolean} withEffect
* @param {Object[]} attachments
* @returns {string}
*/
_renderExisting: function (rows, withEffect) {
_renderExisting: function (attachments) {
return QWeb.render(this.existingAttachmentsTemplate, {
rows: rows,
attachments: attachments,
widget: this,
withEffect: withEffect,
});
},
/**
* @private
*/
_renderImages: function (withEffect) {
var self = this;
var rows = _(this.records).chain()
.slice(0, this.IMAGES_DISPLAYED_TOTAL)
.groupBy(function (a, index) { return Math.floor(index / self.IMAGES_PER_ROW); })
.values()
.value();
_renderImages: function () {
var attachments = this.attachments.slice(0, this.numberOfAttachmentsToDisplay);
this.$errorText.empty();
// Render menu & content
// Render menu & content
this.$('.existing-attachments').replaceWith(
this._renderExisting(rows, withEffect)
this._renderExisting(attachments)
);
var $divs = this.$('.o_image');
var imageDefs = _.map($divs, function (el) {
var $div = $(el);
if (/gif|jpe|jpg|png/.test($div.data('mimetype'))) {
var $img = $('<img/>', {
class: 'img-fluid',
src: $div.data('url') || $div.data('src'),
});
var prom = new Promise(function (resolve, reject) {
$img[0].onload = resolve();
$div.addClass('o_webimage').append($img);
});
return prom;
}
});
if (withEffect) {
Promise.all(imageDefs).then(function () {
_.delay(function () {
$divs.removeClass('o_image_loading');
}, 400);
});
}
this._highlightSelectedImages();
this._highlightSelected();
// adapt load more
var noMoreImgToLoad = this.numberOfAttachmentsToDisplay >= this.attachments.length;
this.$('.o_load_more').toggleClass('d-none', noMoreImgToLoad);
this.$('.o_load_done_msg').toggleClass('d-none', !noMoreImgToLoad);
},
/**
* @private
@@ -407,10 +370,10 @@ var FileWidget = SearchableMediaWidget.extend({
var self = this;
if (this.options.multiImages) {
return Promise.resolve(this.images);
return Promise.resolve(this.selectedAttachments);
}
var img = this.images[0];
var img = this.selectedAttachments[0];
if (!img) {
return Promise.resolve(this.media);
}
@@ -475,24 +438,24 @@ var FileWidget = SearchableMediaWidget.extend({
/**
* @private
*/
_toggleImage: function (attachment, clearSearch, forceSelect) {
_toggleImage: function (attachment, doubleClick) {
if (this.options.multiImages) {
var img = _.select(this.images, function (v) { return v.id === attachment.id; });
if (img.length) {
if (!forceSelect) {
this.images.splice(this.images.indexOf(img[0]),1);
// if the clicked image is already selected, then unselect it
// unless it was a double click
var index = this.selectedAttachments.indexOf(attachment);
if (index !== -1) {
if (!doubleClick) {
this.selectedAttachments.splice(index, 1);
}
} else {
this.images.push(attachment);
// if the clicked image is not selected, then select it
this.selectedAttachments.push(attachment);
}
} else {
this.images = [attachment];
}
this._highlightSelectedImages();
if (clearSearch) {
this.search('');
// select the clicked image
this.selectedAttachments = [attachment];
}
this._highlightSelected();
},
/**
* Updates the add by URL UI.
@@ -536,14 +499,15 @@ var FileWidget = SearchableMediaWidget.extend({
self.$('.well > span').remove();
self.$('.well > div').show();
_.each(attachments, function (record) {
record.src = record.url || _.str.sprintf('/web/image/%s/%s', record.id, encodeURI(record.name)); // Name is added for SEO purposes
record.isDocument = !(/gif|jpe|jpg|png/.test(record.mimetype));
_.each(attachments, function (attachment) {
// Name is added for SEO purposes
attachment.src = attachment.url || _.str.sprintf('/web/image/%s/%s', attachment.id, encodeURI(attachment.name));
attachment.isDocument = !(/gif|jpe|jpg|png/.test(attachment.mimetype));
});
if (error || !attachments.length) {
_processFile(null, error || !attachments.length);
}
self.images = attachments;
self.attachments = attachments;
for (var i = 0 ; i < attachments.length ; i++) {
_processFile(attachments[i], error);
}
@@ -584,24 +548,22 @@ var FileWidget = SearchableMediaWidget.extend({
/**
* @private
*/
_onImageClick: function (ev, force_select) {
var $img = $(ev.currentTarget);
var attachment = _.find(this.records, function (record) {
return record.id === $img.data('id');
});
this._toggleImage(attachment, false, force_select);
_onAttachmentClick: function (ev, doubleClick) {
var $attachment = $(ev.currentTarget);
var attachment = _.find(this.attachments, {id: $attachment.data('id')});
this._toggleImage(attachment, doubleClick);
},
/**
* @private
*/
_onImageDblClick: function (ev) {
this._onImageClick(ev, true);
_onAttachmentDblClick: function (ev) {
this._onAttachmentClick(ev, true);
this.trigger_up('save_request');
},
/**
* @private
*/
_onImageSelection: function () {
_onFileInputChange: function () {
this.$el.addClass('nosave');
this.$form.removeClass('o_has_error').find('.form-control, .custom-select').removeClass('is-invalid');
this.$errorText.empty();
@@ -616,9 +578,9 @@ var FileWidget = SearchableMediaWidget.extend({
Dialog.confirm(this, _t("Are you sure you want to delete this file ?"), {
confirm_callback: function () {
var $helpBlock = self.$errorText.empty();
var $a = $(ev.currentTarget);
var $a = $(ev.currentTarget).closest('.o_existing_attachment_cell');
var id = parseInt($a.data('id'), 10);
var attachment = _.findWhere(self.records, {id: id});
var attachment = _.findWhere(self.attachments, {id: id});
return self._rpc({
route: '/web_editor/attachment/remove',
params: {
@@ -626,7 +588,7 @@ var FileWidget = SearchableMediaWidget.extend({
},
}).then(function (prevented) {
if (_.isEmpty(prevented)) {
self.records = _.without(self.records, attachment);
self.attachments = _.without(self.attachments, attachment);
self._renderImages();
return;
}
@@ -683,8 +645,7 @@ var FileWidget = SearchableMediaWidget.extend({
* @override
*/
_onSearchInput: function () {
this.imagesRows = this.IMAGES_ROWS;
this.IMAGES_DISPLAYED_TOTAL = this.IMAGES_PER_ROW * this.imagesRows;
this.numberOfAttachmentsToDisplay = this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY;
this._super.apply(this, arguments);
},
});
@@ -885,10 +846,10 @@ var IconWidget = SearchableMediaWidget.extend({
*/
_highlightSelectedIcon: function () {
var self = this;
this.$icons.removeClass('o_selected');
this.$icons.removeClass('o_we_attachment_selected');
this.$icons.filter(function (i, el) {
return _.contains($(el).data('alias').split(','), self.selectedIcon);
}).addClass('o_selected');
}).addClass('o_we_attachment_selected');
},
//--------------------------------------------------------------------------
+27 -26
View File
@@ -287,51 +287,52 @@ body .modal {
min-height: 300px;
}
.existing-attachments {
width: 100%;
.o_we_images .o_existing_attachment_cell {
// give enough space for the hover buttons
min-width: 64px;
img {
// prevent url images from being too big
max-width: 256px;
max-height: 192px;
}
}
.o_existing_attachment_cell {
$o-border-image: 2px;
cursor: pointer;
list-style: none;
// prevent hover/select border from flickering
border: 1px solid transparent;
.o_existing_attachment_remove {
@include o-position-absolute($o-border-image, 15px + $o-border-image);
padding: 3px 5px 5px 5px;
background-color: rgba(white, 0.8);
@include o-position-absolute(0, 0);
background-color: scale-color($card-border-color, $alpha: -50%);
text-shadow: 0 0 1px white;
border-radius: 0 0 0 2px;
opacity: 0;
cursor: pointer;
&:hover {
background-color: white;
background-color: $card-border-color;
color: $o-wysiwyg-color-danger;
}
}
.o_image {
border: $o-border-image solid transparent;
cursor: pointer;
transition: opacity 400ms ease 0s;
&.o_webimage {
width: 100%;
height: auto;
background-image: none !important;
&:hover {
.o_existing_attachment_remove {
opacity: 1;
}
&.o_image_loading {
opacity: 0;
&.o_we_attachment_highlight, .o_we_attachment_highlight {
border-color: $card-border-color;
box-shadow: 0px 0px 2px 2px $card-border-color;
}
}
&.o_selected .o_image {
border-color: $o-brand-odoo;
box-shadow: 0px 0px 2px 2px $o-brand-secondary;
}
&:hover .o_existing_attachment_remove {
opacity: 1;
&.o_we_attachment_selected {
&.o_we_attachment_highlight, .o_we_attachment_highlight {
border-color: $o-brand-primary;
box-shadow: 0px 0px 2px 2px $o-brand-primary;
}
}
}
+36 -11
View File
@@ -194,22 +194,47 @@
</div>
</div>
</t>
<t t-name="wysiwyg.widgets.image.existing.attachments">
<div class="existing-attachments">
<div class="row mt16 ml-auto" t-as="row" t-foreach="rows">
<div class="col-2 o_existing_attachment_cell" t-as="attachment" t-foreach="row">
<i t-if="attachment.res_model === 'ir.ui.view'" class="fa fa-trash o_existing_attachment_remove" title="This file is a public view attachment" role="img" aria-label="This file is a public view attachment" t-att-data-id="attachment.id"/>
<i t-else="" class="fa fa-trash o_existing_attachment_remove" title="This file is attached to the current record" role="img" aria-label="This file is attached to the current record" t-att-data-id="attachment.id"/>
<div class="o_attachment_border" t-att-style="attachment.res_model === 'ir.ui.view' ? null : 'border: 1px solid #5cb85c;'"><div t-att-data-src="attachment.src" t-att-data-url="attachment.url" role="img" t-att-aria-label="attachment.name" t-att-title="attachment.name" t-att-data-id="attachment.id" t-att-data-mimetype="attachment.mimetype" t-attf-class="o_image {{withEffect ? 'o_image_loading' : ''}}"/></div>
</div>
<div class="o_we_images">
<ol class="existing-attachments d-flex flex-wrap mx-auto my-0 p-2">
<t t-foreach="attachments" t-as="attachment">
<t t-call="wysiwyg.widgets.image.existing.attachment"/>
</t>
</ol>
</div>
</t>
<t t-name="wysiwyg.widgets.image.existing.attachment">
<li class="o_existing_attachment_cell position-relative m-2" t-att-data-id="attachment.id">
<t t-call="wysiwyg.widgets.file.existing.remove"/>
<div t-attf-class="d-flex align-items-center h-100">
<img class="img img-fluid o_we_attachment_highlight" t-att-src="attachment.src + '?width=256&amp;height=192'" t-att-alt="attachment.name" t-att-title="attachment.name"/>
</div>
</li>
</t>
<t t-name="wysiwyg.widgets.document.existing.attachments">
<div class="o_we_documents">
<div class="row mx-auto existing-attachments">
<t t-foreach="attachments" t-as="attachment">
<div class="col-2 o_existing_attachment_cell o_we_attachment_highlight my-2" t-att-data-id="attachment.id">
<t t-call="wysiwyg.widgets.file.existing.remove"/>
<div t-att-data-url="attachment.url" role="img" t-att-aria-label="attachment.name" t-att-title="attachment.name" t-att-data-mimetype="attachment.mimetype" t-attf-class="o_image d-flex align-items-center justify-content-center"/>
<small class="o_file_name" t-esc="attachment.name"/>
</div>
</t>
</div>
</div>
</t>
<t t-name="wysiwyg.widgets.document.existing.attachments" t-extend="wysiwyg.widgets.image.existing.attachments">
<t t-jquery=".o_existing_attachment_cell" t-operation="append">
<small class="o_file_name"><t t-esc="attachment.name"/></small>
</t>
<t t-name="wysiwyg.widgets.file.existing.remove">
<t t-set="removeTitle" t-if="attachment.res_model === 'ir.ui.view'">This file is a public view attachment.</t>
<t t-set="removeTitle" t-else="">This file is attached to the current record.</t>
<i class="fa fa-trash o_existing_attachment_remove p-2" t-att-title="removeTitle" role="img" t-att-aria-label="removeTitle"/>
</t>
<t t-name="wysiwyg.widgets.image.existing.error">
<div class="form-text">
<p>The image could not be deleted because it is used in the
@@ -194,6 +194,9 @@ QUnit.module('web_editor', {}, function () {
return Promise.resolve();
}
}
if (route.indexOf('/web_editor/static/src/img/') === 0) {
return Promise.resolve();
}
return this._super(route, args);
},
});
@@ -216,7 +219,7 @@ QUnit.module('web_editor', {}, function () {
// load static xml file (dialog, media dialog, unsplash image widget)
await defMediaDialog;
await testUtils.dom.click($('.modal #editor-media-image .o_image:first'));
await testUtils.dom.click($('.modal #editor-media-image .o_existing_attachment_cell:first'));
await testUtils.dom.click($('.modal .modal-footer button.btn-primary'));
var $editable = form.$('.oe_form_field[name="body"] .note-editable');
@@ -330,6 +330,12 @@ QUnit.test('background-image', function (assert) {
' </div>' +
'</section>',
},
mockRPC: function (route, args) {
if (route.indexOf('/web_editor/static/src/img/') === 0) {
return Promise.resolve();
}
return this._super(route, args);
},
}).then(function (wysiwyg) {
var $editable = wysiwyg.getEditable();
@@ -352,7 +358,7 @@ QUnit.test('background-image', function (assert) {
$('#oe_manipulators .oe_overlay_options a.snippet-option-background[data-choose-image]').click();
defMediaDialogInit.then(function () {
$('.o_select_media_dialog .existing-attachments .o_image').click();
$('.o_select_media_dialog .existing-attachments .o_existing_attachment_cell').click();
$('.o_select_media_dialog .modal-footer .btn-primary').click();
});
@@ -3412,6 +3412,9 @@ QUnit.module('Media', {
if (route.indexOf('youtube') !== -1) {
return Promise.resolve();
}
if (route.indexOf('/web_editor/static/src/img/') === 0) {
return Promise.resolve();
}
return this._super(route, args);
},
};
+1 -1
View File
@@ -116,7 +116,7 @@ class Web_Unsplash(http.Controller):
'res_model': res_model,
})
attachment.generate_access_token()
uploads.extend(attachment.read(['name', 'mimetype', 'checksum', 'res_id', 'res_model', 'access_token', 'url']))
uploads.extend(attachment.read(['name', 'mimetype', 'checksum', 'url', 'type', 'res_id', 'res_model', 'access_token']))
# Notifies Unsplash from an image download. (API requirement)
self._notify_download(value.get('download_url'))
@@ -76,7 +76,7 @@ widgetsMedia.ImageWidget.include({
/**
* @override
*/
save: function () {
_save: function () {
if (!this._unsplash.query) {
return this._super.apply(this, arguments);
}
@@ -96,7 +96,8 @@ widgetsMedia.ImageWidget.include({
image.src = image.url;
image.isDocument = !(/gif|jpe|jpg|png/.test(image.mimetype));
});
self.images = images;
self.attachments = images;
self.selectedAttachments = images;
return _super.apply(self, args);
});
},
@@ -114,10 +115,9 @@ widgetsMedia.ImageWidget.include({
var always = function () {
if (!noRender) {
self._renderImages();
self._adaptLoadMore();
}
};
return this.unsplashAPI.getImages(needle, this.IMAGES_DISPLAYED_TOTAL).then(function (res) {
return this.unsplashAPI.getImages(needle, this.numberOfAttachmentsToDisplay).then(function (res) {
self._unsplash.isMaxed = res.isMaxed;
self._unsplash.records = res.images;
self._unsplash.error = false;
@@ -130,21 +130,10 @@ widgetsMedia.ImageWidget.include({
// Private
//--------------------------------------------------------------------------
/**
* @private
*/
_adaptLoadMore: function () {
if (!this._unsplash.isActive) {
return this._super.apply(this, arguments);
}
this.$('.o_load_more').toggleClass('d-none', !!this._unsplash.error || this._unsplash.isMaxed);
this.$('.o_load_done_msg').toggleClass('d-none', !!this._unsplash.error || !this._unsplash.isMaxed);
},
/**
* @override
*/
_highlightSelectedImages: function () {
_highlightSelected: function () {
var self = this;
if (!this._unsplash.isActive) {
return this._super.apply(this, arguments);
@@ -167,7 +156,6 @@ widgetsMedia.ImageWidget.include({
* @override
*/
_renderImages: function () {
var self = this;
if (!this._unsplash.isActive) {
return this._super.apply(this, arguments);
}
@@ -181,13 +169,11 @@ widgetsMedia.ImageWidget.include({
return;
}
var rows = _(this._unsplash.records).chain()
.groupBy(function (a, index) { return Math.floor(index / self.IMAGES_PER_ROW); })
.values()
.value();
this.$('.unsplash_img_container').html(core.qweb.render('web_unsplash.dialog.image.content', {records: this._unsplash.records}));
this._highlightSelected();
this.$('.unsplash_img_container').html(core.qweb.render('web_unsplash.dialog.image.content', {rows: rows}));
this._highlightSelectedImages();
this.$('.o_load_more').toggleClass('d-none', !!this._unsplash.error || this._unsplash.isMaxed);
this.$('.o_load_done_msg').toggleClass('d-none', !!this._unsplash.error || !this._unsplash.isMaxed);
},
/**
* @private
@@ -248,7 +234,7 @@ widgetsMedia.ImageWidget.include({
} else {
this._unsplash.selectedImages[imgid] = {url: url, download_url: downloadURL};
}
this._highlightSelectedImages();
this._highlightSelected();
},
/**
* @private
@@ -27,15 +27,15 @@
</t>
<t t-name="web_unsplash.dialog.image.content">
<div class="row mt16" t-as="row" t-foreach="rows">
<div class="col-sm-2 o_unsplash_img_cell text-center" t-as="img" t-foreach="row">
<div class="o_image o_webimage" t-att-data-imgid="img.id" t-att-data-url="img.urls.regular" t-att-data-download-url="img.links.download_location">
<div class="row mt16">
<div class="col-sm-2 o_unsplash_img_cell text-center" t-as="img" t-foreach="records">
<div class="o_image o_webimage text-center" t-att-data-imgid="img.id" t-att-data-url="img.urls.regular" t-att-data-download-url="img.links.download_location">
<img class="img-fluid" t-att-src="img.urls.thumb"/>
</div>
<div class="author_name"><a t-att-href="img.user.links.html" target="_blank"><t t-esc="img.user.name"/></a></div>
</div>
</div>
<div class="row mt16" t-if="rows.length == 0">
<div class="row mt16" t-if="records.length == 0">
<div class="m-auto text-muted not-found">
<i class="fa fa-camera mr8"/> Photos not found
</div>
@@ -34,7 +34,7 @@ odoo.define("website_blog.tour", function (require) {
position: "top",
}, {
trigger: ".o_select_media_dialog .modal-footer > .btn-primary",
extra_trigger: ".o_existing_attachment_cell.o_selected",
extra_trigger: ".o_existing_attachment_cell.o_we_attachment_selected",
content: _t("Click on <b>Save</b> to set the picture as cover."),
position: "top",
}, {