[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:
@@ -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');
|
||||
},
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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&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);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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",
|
||||
}, {
|
||||
|
||||
Reference in New Issue
Block a user