From 191c556d6b50b35178ea5bb91fb998bcffb86367 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Theys?= Date: Tue, 4 Jun 2019 15:10:53 +0200 Subject: [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 --- .../static/src/js/wysiwyg/widgets/media.js | 181 ++++++++------------- addons/web_editor/static/src/scss/wysiwyg.scss | 53 +++--- addons/web_editor/static/src/xml/wysiwyg.xml | 47 ++++-- 3 files changed, 134 insertions(+), 147 deletions(-) (limited to 'addons/web_editor/static/src') diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js index f901e9eb3f1..8e923acc6db 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js @@ -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 = $('', { - 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'); }, //-------------------------------------------------------------------------- diff --git a/addons/web_editor/static/src/scss/wysiwyg.scss b/addons/web_editor/static/src/scss/wysiwyg.scss index 952944fe4ce..2cdb6ee1fca 100644 --- a/addons/web_editor/static/src/scss/wysiwyg.scss +++ b/addons/web_editor/static/src/scss/wysiwyg.scss @@ -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; + } } } diff --git a/addons/web_editor/static/src/xml/wysiwyg.xml b/addons/web_editor/static/src/xml/wysiwyg.xml index a360ec2daef..9f3101cb1c6 100644 --- a/addons/web_editor/static/src/xml/wysiwyg.xml +++ b/addons/web_editor/static/src/xml/wysiwyg.xml @@ -194,22 +194,47 @@ + -
-
-
- - -
-
+
+
    + + + +
+
+ + + +
  • + +
    + +
    +
  • +
    + + +
    +
    + +
    + + +
    + +
    +
    - - - - + + + This file is a public view attachment. + This file is attached to the current record. + +

    The image could not be deleted because it is used in the -- cgit v1.2.3