diff options
| author | Samuel Degueldre <sad@odoo.com> | 2020-02-11 07:43:23 +0000 |
|---|---|---|
| committer | Samuel Degueldre <sad@odoo.com> | 2020-03-30 11:06:45 +0000 |
| commit | 5576a5f84d7d7b539498f15b7e26165b61c52ba5 (patch) | |
| tree | 6af69fe4f0e0d7b53fa82ec5e7a7ec496bc45693 /addons/web_unsplash | |
| parent | 634c00a32fe18365039f83d890965fe35de7d23b (diff) | |
[IMP] web_editor, web_unsplash: improve media-dialog UX
Previously, searching for images in the database and on unsplash was
mutually exclusive, and controlled by a dropdown menu next to the search
bar. Additionally, the display of db images and unsplash images was very
different, making using the search function inconsistent and awkward.
This commit merges the unsplash search into the db search. When
searching, the first few matches from the database are displayed, then
results from unsplash.
Additionally, the layout of images has been greatly improved,
eliminating almost all padding, using some flex-box magic to allow the
images to take up all the available space without being cropped or
deformed.
The input to add an attachment by URL is now collapsed by
default, and clicking on it will expand it to reduce visual clutter.
Finally, when not in multi-images mode, the clicked image is immediately
used, rather than the user having to click it and then click the add
button, or having to double click, which was not obvious.
task-2091417
closes odoo/odoo#45174
Diffstat (limited to 'addons/web_unsplash')
3 files changed, 124 insertions, 205 deletions
diff --git a/addons/web_unsplash/static/src/js/unsplash_image_widget.js b/addons/web_unsplash/static/src/js/unsplash_image_widget.js index 86d299c46a6..35a9771c838 100644 --- a/addons/web_unsplash/static/src/js/unsplash_image_widget.js +++ b/addons/web_unsplash/static/src/js/unsplash_image_widget.js @@ -7,17 +7,28 @@ var widgetsMedia = require('wysiwyg.widgets.media'); var unsplashAPI = null; +// Prevent base class from treating unsplash images like regular attachments +const originalEvents = widgetsMedia.ImageWidget.prototype.events; +const clickHandler = originalEvents['click .o_existing_attachment_cell']; +if (!clickHandler) { + throw new Error(`Couldn't find a handler for o_existing_attachment_cell clicks. +The unsplash image widget needs to prevent this handler from executing on unsplash attachments.`); +} +_.extend(originalEvents, { + 'click .o_existing_attachment_cell:not(.o_unsplash_attachment_cell)': clickHandler, +}); +delete originalEvents['click .o_existing_attachment_cell']; + widgetsMedia.ImageWidget.include({ xmlDependencies: widgetsMedia.ImageWidget.prototype.xmlDependencies.concat( ['/web_unsplash/static/src/xml/unsplash_image_widget.xml'] ), events: _.extend({}, widgetsMedia.ImageWidget.prototype.events, { - 'dblclick .unsplash_img_container [data-imgid]': '_onUnsplashImgDblClick', - 'click .unsplash_img_container [data-imgid]': '_onUnsplashImgClick', - 'click button.save_unsplash': '_onSaveUnsplash', - 'click .o_search_an_image, .o_search_from_unsplash': '_onSourceSwitchClick', - 'keyup .o_we_search, .o_search_an_image, .o_search_from_unsplash': '_onPressKeySearch', + 'click .o_unsplash_attachment_cell[data-imgid]': '_onUnsplashImgClick', + 'click button.save_unsplash': '_onSaveUnsplashCredentials', }), + // Max number of database attachments to show when searching. + maxDbAttachments: 5, /** * @override @@ -26,11 +37,11 @@ widgetsMedia.ImageWidget.include({ this._super.apply(this, arguments); this._unsplash = { - isActive: false, selectedImages: {}, isMaxed: false, query: false, error: false, + records: [], }; // TODO improve this @@ -55,13 +66,6 @@ widgetsMedia.ImageWidget.include({ /** * @override */ - start: function () { - this.$('.o_we_search_icon').replaceWith(core.qweb.render('web_unsplash.dialog.media.search_icon')); - return this._super.apply(this, arguments); - }, - /** - * @override - */ destroy: function () { // TODO See `hack` explained in `init`. This prevent the media dialog destroy // to destroy unsplashAPI when destroying the children @@ -76,50 +80,43 @@ widgetsMedia.ImageWidget.include({ /** * @override */ - _save: function () { - if (!this._unsplash.query) { - return this._super.apply(this, arguments); + _save: async function () { + const _super = this._super; + if (Object.keys(this._unsplash.selectedImages).length) { + this.saved = true; + const images = await this._rpc({ + route: '/web_unsplash/attachment/add', + params: { + unsplashurls: this._unsplash.selectedImages, + res_model: this.options.res_model, + res_id: this.options.res_id, + query: this._unsplash.query, + }, + }); + this.attachments.push(...images); + this.selectedAttachments.push(...images); } - var self = this; - var args = arguments; - var _super = this._super; - return this._rpc({ - route: '/web_unsplash/attachment/add', - params: { - unsplashurls: self._unsplash.selectedImages, - res_model: self.options.res_model, - res_id: self.options.res_id, - query: self._unsplash.query, - } - }).then(function (images) { - self.attachments = images; - self.selectedAttachments = images; - return _super.apply(self, args); - }); + return _super.apply(this, arguments); }, /** * @override */ - search: function (needle, noRender) { + search: async function (needle) { var self = this; - if (!this._unsplash.isActive) { - return this._super.apply(this, arguments); - } + await this._super(needle, true); this._unsplash.query = needle; + if (!needle) { + return; + } - var always = function () { - if (!noRender) { - self._renderImages(); - } - }; - return this.unsplashAPI.getImages(needle, this.numberOfAttachmentsToDisplay).then(function (res) { + await this.unsplashAPI.getImages(needle, this.numberOfAttachmentsToDisplay).then(function (res) { self._unsplash.isMaxed = res.isMaxed; self._unsplash.records = res.images; self._unsplash.error = false; }, function (err) { self._unsplash.error = err; - }).then(always).guardedCatch(always); + }); }, //-------------------------------------------------------------------------- @@ -130,60 +127,68 @@ widgetsMedia.ImageWidget.include({ * @override */ _highlightSelected: function () { - var self = this; - if (!this._unsplash.isActive) { - return this._super.apply(this, arguments); - } + this._super.apply(this, arguments); - this.$('.o_unsplash_img_cell.o_selected').removeClass('o_selected'); - var $select = this.$('.o_unsplash_img_cell [data-imgid]').filter(function () { - return $(this).data('imgid') in self._unsplash.selectedImages; - }); - $select.closest('.o_unsplash_img_cell').addClass('o_selected'); + const $select = this.$('.o_unsplash_attachment_cell[data-imgid]').filter((i, el) => { + return $(el).data('imgid') in this._unsplash.selectedImages; + }).addClass('o_we_attachment_selected'); return $select; }, /** * @private */ _loadMoreImages: function (forceSearch) { - if (!this._unsplash.isActive) { + if (!this.$('.o_we_search').val()) { return this._super(forceSearch); } this.numberOfAttachmentsToDisplay += 10; - this.search(this.$('.o_we_search').val() || ''); + this.search(this.$('.o_we_search').val()).then(() => this._renderThumbnails()); }, /** * @override */ - _renderImages: function () { - if (!this._unsplash.isActive) { - return this._super.apply(this, arguments); - } - - if (this._unsplash.error) { - this.$('.unsplash_img_container').html( + _renderThumbnails: function () { + this._super(...arguments); + if (this._unsplash.query && this._unsplash.error) { + this.$('.unsplash_error').html( core.qweb.render('web_unsplash.dialog.error.content', { status: this._unsplash.error, }) ); return; } + this.$('.unsplash_error').empty(); - this.$('.unsplash_img_container').html(core.qweb.render('web_unsplash.dialog.image.content', {records: this._unsplash.records})); - this._highlightSelected(); - - 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); + if (this._unsplash.query && !this._unsplash.isMaxed) { + this.$('.o_load_more').removeClass('d-none'); + this.$('.o_load_done_msg').addClass('d-none'); + } }, /** - * @private - * @param {boolean} show + * @override */ - _toggleUnsplashContainer: function (show) { - this._unsplash.isActive = show; - this.$('.o_we_existing_attachments').toggleClass('d-none', show); - this.$('.unsplash_img_container').toggleClass('d-none', !show); - this.$('.o_we_search_icon > span').text(show ? "Unsplash" : ""); + _renderExisting: function (attachments) { + if (this._unsplash.records.length) { + attachments.splice(this.maxDbAttachments); + } + return this._super(attachments.concat(this._unsplash.records.map(record => { + const url = new URL(record.urls.regular); + // In small windows, row height could get quite a bit larger than the min, so we keep some leeway. + url.searchParams.set('h', 2 * this.MIN_ROW_HEIGHT); + url.searchParams.delete('w'); + return Object.assign({}, record, { + url: url.toString(), + }); + }))); + }, + /** + * @override + */ + _selectAttachement: function (attachment, save) { + if (!this.options.multiImages) { + this._unsplash.selectedImages = {}; + } + this._super(...arguments); }, //-------------------------------------------------------------------------- @@ -193,7 +198,7 @@ widgetsMedia.ImageWidget.include({ /** * @private */ - _onSaveUnsplash: function () { + _onSaveUnsplashCredentials: function () { var self = this; var key = this.$('#accessKeyInput').val().trim(); var appId = this.$('#appIdInput').val().trim(); @@ -202,19 +207,14 @@ widgetsMedia.ImageWidget.include({ this.$('#appIdInput').toggleClass('is-invalid', !appId); if (key && appId) { - var params = { - 'key': key, - 'appId': appId, - }; - if (!this.$el.find('.is-invalid').length) { this._rpc({ route: '/web_unsplash/save_unsplash', - params: params, + params: {key: key, appId: appId}, }).then(function () { self.unsplashAPI.clientId = key; self._unsplash.error = false; - self.search(self._unsplash.query); + self.search(self._unsplash.query).then(() => self._renderThumbnails()); }); } } @@ -223,65 +223,26 @@ widgetsMedia.ImageWidget.include({ * @private */ _onUnsplashImgClick: function (ev) { - var imgid = $(ev.currentTarget).data('imgid'); - var url = $(ev.currentTarget).data('url'); - var downloadURL = $(ev.currentTarget).data('download-url'); + if (this.saved) { + // already saved, probably a double click. Ignore. + return; + } + const {imgid, url, downloadUrl} = ev.currentTarget.dataset; if (!this.options.multiImages) { this._unsplash.selectedImages = {}; + this.selectedAttachments = []; } if (imgid in this._unsplash.selectedImages) { delete this._unsplash.selectedImages[imgid]; } else { - this._unsplash.selectedImages[imgid] = {url: url, download_url: downloadURL}; + const _1920Url = new URL(url); + _1920Url.searchParams.set('w', '1920'); + this._unsplash.selectedImages[imgid] = {url: _1920Url.href, download_url: downloadUrl}; } this._highlightSelected(); - }, - /** - * @private - */ - _onUnsplashImgDblClick: function (ev) { - this.trigger_up('save_request'); - }, - /** - * @private - * @param {Event} ev - */ - _onSourceSwitchClick: function (ev) { - this._toggleUnsplashContainer(!$(ev.currentTarget).is('.o_search_an_image')); - this.search(this.$('.o_we_search').val() || ''); - }, - /** - * @private - */ - _onPressKeySearch: function (ev) { - switch (ev.which) { - case $.ui.keyCode.UP: - this.$('.o_search_an_image').focus(); - break; - case $.ui.keyCode.DOWN: - this.$('.o_search_from_unsplash').focus(); - break; - default: - return; - } - ev.preventDefault(); - }, - /** - * @override - */ - _onSearchInput: function (ev) { - var $input = $(ev.currentTarget); - var inputValue = $input.val(); - this.$('.o_search_value_input').text(inputValue); - - var $icon = this.$('.o_we_search_icon'); - if ($icon.parent().is('.show')) { - $icon.dropdown('update'); - } else if (!this.hasSearched) { - $icon.dropdown('toggle'); - $input.focus(); + if (!this.options.multiImages) { + this.trigger_up('save_request'); } - this._super.apply(this, arguments); }, }); }); diff --git a/addons/web_unsplash/static/src/scss/unsplash.scss b/addons/web_unsplash/static/src/scss/unsplash.scss index 39b8bdde40c..340b21581ec 100644 --- a/addons/web_unsplash/static/src/scss/unsplash.scss +++ b/addons/web_unsplash/static/src/scss/unsplash.scss @@ -1,46 +1,22 @@ -.unsplash_img_container { - $o-border-image: 2px; - .o_unsplash_img_cell { - .o_image { - cursor: pointer; - transition: opacity 400ms; - - &.o_webimage { - width: 100%; - height: auto; - background-image: none; - - img { - max-height: 150px; - margin: 0 auto; - border: $o-border-image solid transparent; - } - } - - } - &.o_selected .o_image img { - border-color: $o-brand-primary; - box-shadow: 0px 0px 2px 2px $o-brand-secondary; - } - .author_name { - font-size: 11px; - } - } - - .not-found { - font-size: 25px; - padding: 30px 0; - } - - .unsplash_error { - padding: 30px 0; - .access_key_box { - padding: 9px; - background-color: #fcfcfc; - border: 1px solid #ededee; - input { - min-width: 300px; - } +.o_we_unplash_author { + font-size: 11px; + bottom: 0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + width: 100%; + position: absolute; + text-align: center; + background-color: #FFFB; +} +.unsplash_error { + padding: 30px 0; + .access_key_box { + padding: 9px; + background-color: #fcfcfc; + border: 1px solid #ededee; + input { + min-width: 300px; } } } diff --git a/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml b/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml index 5610e977b9b..aeb6edf8094 100644 --- a/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml +++ b/addons/web_unsplash/static/src/xml/unsplash_image_widget.xml @@ -3,42 +3,24 @@ <t t-extend="wysiwyg.widgets.file"> <t t-jquery=".o_we_existing_attachments" t-operation="after"> - <div class="unsplash_img_container"></div> + <div class="unsplash_error"></div> </t> </t> - -<t t-name="web_unsplash.dialog.media.search_icon" t-extend=""> - <div class="dropdown"> - <button type="button" class="btn btn-primary dropdown-toggle o_we_search_icon" data-toggle="dropdown"> - <i class="fa fa-search"/> - <span/> - </button> - <div class="dropdown-menu dropdown-menu-right"> - <button class="dropdown-item o_search_an_image" type="button"> - Search among my images for: - <div class="font-weight-bold o_search_value_input"></div> - </button> - <button class="dropdown-item o_search_from_unsplash" type="button"> - Search from Unsplash for: - <div class="font-weight-bold o_search_value_input"></div> - </button> - </div> - </div> +<t t-extend="wysiwyg.widgets.image.existing.attachments"> + <t t-jquery="t[t-call]" t-operation="replace"> + <t t-if="typeof(attachment.id) === 'number'"> + <t t-call="wysiwyg.widgets.image.existing.attachment"/> + </t> + <t t-else=""> + <t t-call="web_unsplash.dialog.image.content"/> + </t> + </t> </t> <t t-name="web_unsplash.dialog.image.content"> - <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="records.length == 0"> - <div class="m-auto text-muted not-found"> - <i class="fa fa-camera mr8"/> Photos not found - </div> + <div class="o_existing_attachment_cell o_unsplash_attachment_cell position-relative align-items-center justify-content-center bg-light" t-att-data-imgid="attachment.id" t-att-data-id="attachment.id" t-att-data-url="attachment.urls.regular" t-att-data-download-url="attachment.links.download_location"> + <img class="img img-fluid o_we_attachment_highlight" t-att-src="attachment.url" t-att-alt="attachment.alt_description" style="max-height: 100%;"/> + <a class="o_we_unplash_author" t-att-href="attachment.user.links.html" target="_blank" t-esc="attachment.user.name" t-att-title="attachment.user.name"/> </div> </t> |
