diff options
| author | Samuel Degueldre <sad@odoo.com> | 2020-08-12 10:15:51 +0000 |
|---|---|---|
| committer | Samuel Degueldre <sad@odoo.com> | 2020-09-16 07:01:15 +0000 |
| commit | 7a1308b426f94b33e2bd55e5d58e661067e26ff3 (patch) | |
| tree | 651d7c12b8175c995247eb9a6b520e4faf46687e /addons/web_editor/static/src | |
| parent | a2cee3e630634004b8aeeb0869aedadf99913647 (diff) | |
[IMP] web_editor, *: make media-dialog search odoo media-library
*: website, web_unsplash, website_blog
We have recently created an odoo media library that exposes a public API
for searching for open-source media, including dynamic SVGs that can be
used with the dynamic background-shape system to create illustrations
whose colors can be changed by the user. This commit adds the
corresponding client-side functionalty: search for the images in the
media-library, creating a corresponding attachment when selected, as
well as an option to customize the colors of the dynamic SVGs.
In the process, adaptations have been made in the web_unsplash module to
avoid duplicating code and to adapt code that was making assumptions
that are no longer valid.
To make new users more aware of this feature, all default images have
been hidden from the media-dialog, so that the user is prompted so
search for images. Some tests and tours that were relying on having
default images in the media-dialog (eg in website_blog) have been
adapted to match.
task-2316651
Linked to: odoo/iap-apps#235
closes odoo/odoo#56479
Signed-off-by: Jérémy Kersten (jke) <jke@openerp.com>
Diffstat (limited to 'addons/web_editor/static/src')
8 files changed, 398 insertions, 25 deletions
diff --git a/addons/web_editor/static/src/img/curved_arrow.svg b/addons/web_editor/static/src/img/curved_arrow.svg new file mode 100644 index 00000000000..a8ed9acd242 --- /dev/null +++ b/addons/web_editor/static/src/img/curved_arrow.svg @@ -0,0 +1,14 @@ +<svg version="1.0" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1144 1280"> +<g transform="translate(0,1280) scale(0.1,-0.1)" fill="#000000" stroke="none"> +<path d="M7166 11004 c-603 -901 -1094 -1639 -1092 -1641 1 -2 369 42 817 97 +448 55 825 101 838 103 30 3 26 14 86 -278 131 -634 218 -1317 255 -2010 13 +-246 13 -898 0 -1135 -71 -1262 -367 -2292 -883 -3065 -162 -242 -308 -418 +-541 -650 -661 -658 -1465 -1094 -2581 -1400 -1069 -293 -2283 -471 -3660 +-536 -132 -6 -242 -13 -244 -15 -6 -5 16 -190 30 -255 9 -43 13 -47 47 -53 +108 -17 1148 -2 1627 24 2760 152 4778 866 6094 2155 506 496 887 1018 1216 +1670 444 877 715 1860 819 2970 35 365 41 510 41 1000 0 581 -17 880 -76 1355 +-19 158 -65 452 -74 477 -5 14 -54 7 730 104 363 44 662 83 665 85 2 3 0 8 -6 +12 -6 4 -682 594 -1503 1312 -822 718 -1497 1306 -1501 1308 -4 2 -501 -734 +-1104 -1634z"/> +</g> +</svg> diff --git a/addons/web_editor/static/src/js/common/utils.js b/addons/web_editor/static/src/js/common/utils.js index c66bb75a47c..6e92bd6955b 100644 --- a/addons/web_editor/static/src/js/common/utils.js +++ b/addons/web_editor/static/src/js/common/utils.js @@ -32,6 +32,18 @@ const CSS_UNITS_CONVERSION = { 'rem-px': () => _computePxByRem(), 'px-rem': () => _computePxByRem(true), }; +/** + * Colors of the default palette, used for substitution in shapes/illustrations. + * key: number of the color in the palette (ie, o-color-<1-5>) + * value: color hex code + */ +const DEFAULT_PALETTE = { + '1': '#3AADAA', + '2': '#7C6576', + '3': '#F6F6F6', + '4': '#FFFFFF', + '5': '#383E45', +}; /** * Computes the number of "px" needed to make a "rem" unit. Subsequent calls @@ -218,6 +230,7 @@ function _getBgImageURL(el) { return { CSS_SHORTHANDS: CSS_SHORTHANDS, CSS_UNITS_CONVERSION: CSS_UNITS_CONVERSION, + DEFAULT_PALETTE: DEFAULT_PALETTE, computePxByRem: _computePxByRem, convertValueToUnit: _convertValueToUnit, convertNumericToUnit: _convertNumericToUnit, diff --git a/addons/web_editor/static/src/js/editor/snippets.editor.js b/addons/web_editor/static/src/js/editor/snippets.editor.js index 4254afe270f..37eda9910d1 100644 --- a/addons/web_editor/static/src/js/editor/snippets.editor.js +++ b/addons/web_editor/static/src/js/editor/snippets.editor.js @@ -10,6 +10,7 @@ var options = require('web_editor.snippets.options'); var Wysiwyg = require('web_editor.wysiwyg'); const {ColorPaletteWidget} = require('web_editor.ColorPalette'); const SmoothScrollOnDrag = require('web/static/src/js/core/smooth_scroll_on_drag.js'); +const {getCSSVariableValue} = require('web_editor.utils'); var _t = core._t; @@ -1853,6 +1854,12 @@ var SnippetsMenu = Widget.extend({ } $toInsert = $baseBody.clone(); + // Color-customize dynamic SVGs in dropped snippets with current theme colors. + [...$toInsert.find('img[src^="/web_editor/shape/"]')].forEach(dynamicSvg => { + const colorCustomizedURL = new URL(dynamicSvg.getAttribute('src'), window.location.origin); + colorCustomizedURL.searchParams.set('c1', getCSSVariableValue('o-color-1')); + dynamicSvg.src = colorCustomizedURL.pathname + colorCustomizedURL.search; + }); if (!$selectorSiblings.length && !$selectorChildren.length) { console.warn($snippet.find('.oe_snippet_thumbnail_title').text() + " have not insert action: data-drop-near or data-drop-in"); diff --git a/addons/web_editor/static/src/js/editor/snippets.options.js b/addons/web_editor/static/src/js/editor/snippets.options.js index 8e236bb93d1..1a0e8d34935 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -3578,6 +3578,30 @@ registry.BackgroundImage = SnippetOptionWidget.extend({ this.$target.trigger('background_changed', [previewMode]); } }, + /** + * Changes the main color of dynamic SVGs. + * + * @see this.selectClass for parameters + */ + async dynamicColor(previewMode, widgetValue, params) { + const currentSrc = getBgImageURL(this.$target[0]); + switch (previewMode) { + case true: + this.previousSrc = currentSrc; + break; + case 'reset': + this.$target.css('background-image', `url('${this.previousSrc}')`); + return; + } + const newURL = new URL(currentSrc, window.location.origin); + newURL.searchParams.set('c1', normalizeColor(widgetValue)); + const src = newURL.pathname + newURL.search; + await loadImage(src); + this.$target.css('background-image', `url('${src}')`); + if (!previewMode) { + this.previousSrc = src; + } + }, //-------------------------------------------------------------------------- // Public @@ -3608,8 +3632,21 @@ registry.BackgroundImage = SnippetOptionWidget.extend({ * @override */ _computeWidgetState: function (methodName) { - if (methodName === 'background') { - return getBgImageURL(this.$target[0]); + switch (methodName) { + case 'background': + return getBgImageURL(this.$target[0]); + case 'dynamicColor': + return new URL(getBgImageURL(this.$target[0]), window.location.origin).searchParams.get('c1'); + } + return this._super(...arguments); + }, + /** + * @override + */ + _computeWidgetVisibility(widgetName, params) { + if (widgetName === 'dynamic_color_opt') { + const src = new URL(getBgImageURL(this.$target[0]), window.location.origin); + return src.origin === window.location.origin && src.pathname.startsWith('/web_editor/shape/'); } return this._super(...arguments); }, @@ -4505,6 +4542,86 @@ registry.SnippetSave = SnippetOptionWidget.extend({ }, }); +/** + * Handles the dynamic colors for dynamic SVGs. + */ +registry.DynamicSvg = SnippetOptionWidget.extend({ + /** + * @override + */ + start() { + this.$target.on('image_changed.DynamicSvg', this._onImageChanged.bind(this)); + return this._super(...arguments); + }, + /** + * @override + */ + destroy() { + this.$target.off('.DynamicSvg'); + return this._super(...arguments); + }, + + //-------------------------------------------------------------------------- + // Options + //-------------------------------------------------------------------------- + + /** + * Sets the dynamic SVG's dynamic color. + * + * @see this.selectClass for params + */ + async color(previewMode, widgetValue, params) { + const target = this.$target[0]; + switch (previewMode) { + case true: + this.previousSrc = target.getAttribute('src'); + break; + case 'reset': + target.src = this.previousSrc; + return; + } + const newURL = new URL(target.src, window.location.origin); + newURL.searchParams.set('c1', normalizeColor(widgetValue)); + const src = newURL.pathname + newURL.search; + await loadImage(src); + target.src = src; + if (!previewMode) { + this.previousSrc = src; + } + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + _computeWidgetState(methodName, params) { + switch (methodName) { + case 'color': + return new URL(this.$target[0].src, window.location.origin).searchParams.get('c1'); + } + return this._super(...arguments); + }, + /** + * @override + */ + _computeVisibility(methodName, params) { + return this.$target.is("img[src^='/web_editor/shape/']"); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * @override + */ + _onImageChanged(methodName, params) { + return this.updateUI(); + }, +}); return { SnippetOptionWidget: SnippetOptionWidget, 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 d405d647ce7..3134f55b373 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js @@ -10,6 +10,7 @@ var utils = require('web.utils'); var Widget = require('web.Widget'); var session = require('web.session'); const {removeOnImageChangeAttrs} = require('web_editor.image_processing'); +const {getCSSVariableValue, DEFAULT_PALETTE} = require('web_editor.utils'); var QWeb = core.qweb; var _t = core._t; @@ -106,6 +107,7 @@ var SearchableMediaWidget = MediaWidget.extend({ * @private */ _onSearchInput: function (ev) { + this.attachments = []; this.search($(ev.currentTarget).val() || '').then(() => this._renderThumbnails()); this.hasSearched = true; }, @@ -128,6 +130,7 @@ var FileWidget = SearchableMediaWidget.extend({ IMAGE_MIMETYPES: ['image/gif', 'image/jpe', 'image/jpeg', 'image/jpg', 'image/gif', 'image/png', 'image/svg+xml'], NUMBER_OF_ATTACHMENTS_TO_DISPLAY: 30, + MAX_DB_ATTACHMENTS: 5, /** * @constructor @@ -144,6 +147,8 @@ var FileWidget = SearchableMediaWidget.extend({ this.attachments = []; this.selectedAttachments = []; + this.libraryMedia = []; + this.selectedMedia = []; this._onUploadURLButtonClick = dom.makeAsyncHandler(this._onUploadURLButtonClick); }, @@ -204,7 +209,6 @@ var FileWidget = SearchableMediaWidget.extend({ * @override */ search: function (needle) { - this.attachments = []; this.needle = needle; return this.fetchAttachments(this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY, 0); }, @@ -231,6 +235,12 @@ var FileWidget = SearchableMediaWidget.extend({ Array.prototype.splice.apply(this.attachments, [offset, attachments.length].concat(attachments)); }); }, + /** + * Computes whether there is content to display in the template. + */ + hasContent() { + return this.attachments.length; + }, //-------------------------------------------------------------------------- // Private @@ -292,7 +302,7 @@ var FileWidget = SearchableMediaWidget.extend({ domain.push(['name', 'ilike', needle]); } domain.push('!', ['name', '=like', '%.crop']); - domain.push('|', ['type', '=', 'binary'], ['url', '!=', false]); + domain.push('|', ['type', '=', 'binary'], '!', ['url', '=like', '/%/static/%']); return domain; }, /** @@ -357,6 +367,7 @@ var FileWidget = SearchableMediaWidget.extend({ this._highlightSelected(); // adapt load more + this.$('.o_we_load_more').toggleClass('d-none', !this.hasContent()); var noLoadMoreButton = this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY >= this.attachments.length; var noMoreImgToLoad = this.numberOfAttachmentsToDisplay >= this.attachments.length; this.$('.o_load_done_msg').toggleClass('d-none', noLoadMoreButton || !noMoreImgToLoad); @@ -367,11 +378,36 @@ var FileWidget = SearchableMediaWidget.extend({ * @returns {Promise} */ _save: async function () { + // Create all media-library attachments. + const toSave = Object.fromEntries(this.selectedMedia.map(media => [ + media.id, { + query: media.query || '', + is_dynamic_svg: !!media.isDynamicSVG, + } + ])); + let mediaAttachments = []; + if (Object.keys(toSave).length !== 0) { + mediaAttachments = await this._rpc({ + route: '/web_editor/save_library_media', + params: { + media: toSave, + }, + }); + } + const selected = this.selectedAttachments.concat(mediaAttachments).map(attachment => { + // Color-customize dynamic SVGs with the primary theme color + if (attachment.image_src.startsWith('/web_editor/shape/')) { + const colorCustomizedURL = new URL(attachment.image_src, window.location.origin); + colorCustomizedURL.searchParams.set('c1', getCSSVariableValue('o-color-1')); + attachment.image_src = colorCustomizedURL.pathname + colorCustomizedURL.search; + } + return attachment; + }); if (this.options.multiImages) { - return this.selectedAttachments; + return selected; } - var img = this.selectedAttachments[0]; + const img = selected[0]; if (!img || !img.id) { return this.media; } @@ -435,22 +471,30 @@ var FileWidget = SearchableMediaWidget.extend({ * and to close the media dialog * @private */ - _selectAttachement: function (attachment, save) { + _selectAttachement: function (attachment, save, {type = 'attachment'} = {}) { + const possibleProps = { + 'attachment': 'selectedAttachments', + 'media': 'selectedMedia' + }; + const prop = possibleProps[type]; if (this.options.multiImages) { // if the clicked attachment is already selected then unselect it // unless it was a save request (then keep the current selection) - var index = this.selectedAttachments.indexOf(attachment); + const index = this[prop].indexOf(attachment); if (index !== -1) { if (!save) { - this.selectedAttachments.splice(index, 1); + this[prop].splice(index, 1); } } else { // if the clicked attachment is not selected, add it to selected - this.selectedAttachments.push(attachment); + this[prop].push(attachment); } } else { + Object.values(possibleProps).forEach(prop => { + this[prop] = []; + }); // select the clicked attachment - this.selectedAttachments = [attachment]; + this[prop] = [attachment]; } this._highlightSelected(); if (save) { @@ -481,9 +525,15 @@ var FileWidget = SearchableMediaWidget.extend({ * @private */ _onAttachmentClick: function (ev) { - var $attachment = $(ev.currentTarget); - var attachment = _.find(this.attachments, {id: $attachment.data('id')}); - this._selectAttachement(attachment, !this.options.multiImages); + const attachment = ev.currentTarget; + const {id: attachmentID, mediaId} = attachment.dataset; + if (attachmentID) { + const attachment = this.attachments.find(attachment => attachment.id === parseInt(attachmentID)); + this._selectAttachement(attachment, !this.options.multiImages); + } else if (mediaId) { + const media = this.libraryMedia.find(media => media.id === parseInt(mediaId)); + this._selectAttachement(media, !this.options.multiImages, {type: 'media'}); + } }, /** * Handles change of the file input: create attachments with the new files @@ -639,6 +689,7 @@ var FileWidget = SearchableMediaWidget.extend({ * @override */ _onSearchInput: function () { + this.attachments = []; this.numberOfAttachmentsToDisplay = this.NUMBER_OF_ATTACHMENTS_TO_DISPLAY; this._super.apply(this, arguments); }, @@ -652,6 +703,7 @@ var ImageWidget = FileWidget.extend({ existingAttachmentsTemplate: 'wysiwyg.widgets.image.existing.attachments', events: Object.assign({}, FileWidget.prototype.events, { 'change input.o_we_show_optimized': '_onShowOptimizedChange', + 'change .o_we_search_select': '_onSearchSelect', }), MIN_ROW_HEIGHT: 128, @@ -659,6 +711,7 @@ var ImageWidget = FileWidget.extend({ * @constructor */ init: function (parent, media, options) { + this.searchService = 'all'; options = _.extend({ accept: 'image/*', mimetypeDomain: [['mimetype', 'in', this.IMAGE_MIMETYPES]], @@ -681,6 +734,60 @@ var ImageWidget = FileWidget.extend({ this.el.removeEventListener('load', this._onAttachmentImageLoad, true); return this._super(...arguments); }, + /** + * @override + */ + async fetchAttachments(number, offset) { + if (this.needle && this.searchService !== 'database') { + number = this.MAX_DB_ATTACHMENTS; + offset = 0; + } + const result = await this._super(number, offset); + // Color-substitution for dynamic SVG attachment + const primaryColor = getCSSVariableValue('o-color-1'); + this.attachments.forEach(attachment => { + if (attachment.image_src.startsWith('/')) { + const newURL = new URL(attachment.image_src, window.location.origin); + // Set the main color of dynamic SVGs to o-color-1 + if (attachment.image_src.startsWith('/web_editor/shape/')) { + newURL.searchParams.set('c1', primaryColor); + } else { + // Set height so that db images load faster + newURL.searchParams.set('height', 2 * this.MIN_ROW_HEIGHT); + } + attachment.thumbnail_src = newURL.pathname + newURL.search; + } + }); + if (this.needle) { + try { + const response = await this._rpc({ + route: '/web_editor/media_library_search', + params: { + 'query': this.needle, + 'offset': this.libraryMedia.length, + }, + }); + const newMedia = response.media; + this.nbMediaResults = response.results; + this.libraryMedia.push(...newMedia); + } catch (e) { + // Either API endpoint doesn't exist or is misconfigured. + console.error(`Couldn't reach API endpoint.`); + } + } + return result; + }, + /** + * @override + */ + hasContent() { + if (this.searchService === 'all') { + return this._super(...arguments) || this.libraryMedia.length; + } else if (this.searchService === 'media-library') { + return !!this.libraryMedia.length; + } + return this._super(...arguments); + }, //-------------------------------------------------------------------------- // Private @@ -708,7 +815,7 @@ var ImageWidget = FileWidget.extend({ this.$('.o_existing_attachment_cell').addClass('d-none'); // Replace images that had been previously loaded if any to prevent scroll resetting to top alreadyLoaded.each((index, el) => { - const toReplace = this.$(`.o_existing_attachment_cell[data-id="${el.dataset.id}"]`); + const toReplace = this.$(`.o_existing_attachment_cell[data-id="${el.dataset.id}"], .o_existing_attachment_cell[data-media-id="${el.dataset.mediaId}"]`); if (toReplace.length) { toReplace.replaceWith(el); } @@ -720,13 +827,23 @@ var ImageWidget = FileWidget.extend({ flexGrow: placeholderWidth, flexBasis: placeholderWidth, }); + if (this.needle && ['media-library', 'all'].includes(this.searchService)) { + const noMoreImgToLoad = this.libraryMedia.length === this.nbMediaResults; + const noLoadMoreButton = noMoreImgToLoad && this.libraryMedia.length <= 15; + this.$('.o_load_done_msg').toggleClass('d-none', noLoadMoreButton || !noMoreImgToLoad); + this.$('.o_load_more').toggleClass('d-none', noMoreImgToLoad); + } }, /** * @override */ _renderExisting: function (attachments) { + if (this.needle && this.searchService !== 'database') { + attachments = attachments.slice(0, this.MAX_DB_ATTACHMENTS); + } return QWeb.render(this.existingAttachmentsTemplate, { attachments: attachments, + libraryMedia: this.libraryMedia, widget: this, }); }, @@ -738,6 +855,16 @@ var ImageWidget = FileWidget.extend({ _toggleOptimized: function (value) { this.$('.o_we_attachment_optimized').each((i, cell) => cell.style.setProperty('display', value ? null : 'none', 'important')); }, + /** + * @override + */ + _highlightSelected: function () { + this._super(...arguments); + this.selectedMedia.forEach(media => { + this.$(`.o_existing_attachment_cell[data-media-id=${media.id}]`) + .addClass("o_we_attachment_selected"); + }); + }, //-------------------------------------------------------------------------- // Handlers @@ -746,12 +873,37 @@ var ImageWidget = FileWidget.extend({ /** * @override */ - _onAttachmentImageLoad: function (ev) { + _onAttachmentImageLoad: async function (ev) { const img = ev.target; const cell = img.closest('.o_existing_attachment_cell'); if (!cell) { return; } + if (cell.dataset.mediaId && !img.src.startsWith('blob')) { + const mediaUrl = img.src; + try { + const response = await fetch(mediaUrl); + if (response.headers.get('content-type') === 'image/svg+xml') { + const svg = await response.text(); + const colorRegex = new RegExp(DEFAULT_PALETTE['1'], 'gi'); + if (colorRegex.test(svg)) { + const fileName = mediaUrl.split('/').pop(); + const file = new File([svg.replace(colorRegex, getCSSVariableValue('o-color-1'))], fileName, { + type: "image/svg+xml", + }); + img.src = URL.createObjectURL(file); + const media = this.libraryMedia.find(media => media.id === parseInt(cell.dataset.mediaId)); + if (media) { + media.isDynamicSVG = true; + } + // We changed the src: wait for the next load event to do the styling + return; + } + } + } catch (e) { + console.error('CORS is misconfigured on the API server, image will be treated as non-dynamic.'); + } + } const aspectRatio = img.naturalWidth / img.naturalHeight; const width = aspectRatio * this.MIN_ROW_HEIGHT; cell.style.flexGrow = width; @@ -766,6 +918,21 @@ var ImageWidget = FileWidget.extend({ _onShowOptimizedChange: function (ev) { this._toggleOptimized(ev.target.checked); }, + /** + * @override + */ + _onSearchSelect: function (ev) { + const {value} = ev.target; + this.searchService = value; + this.$('.o_we_search').trigger('input'); + }, + /** + * @private + */ + _onSearchInput: function (ev) { + this.libraryMedia = []; + this._super(...arguments); + }, }); diff --git a/addons/web_editor/static/src/scss/web_editor.common.scss b/addons/web_editor/static/src/scss/web_editor.common.scss index 0cd22143290..ea7aee164e6 100644 --- a/addons/web_editor/static/src/scss/web_editor.common.scss +++ b/addons/web_editor/static/src/scss/web_editor.common.scss @@ -469,6 +469,31 @@ a.o_underline { } } +.o_we_search_prompt { + position: relative; + min-height: 250px; + width: 100%; + display: flex; + align-items: center; + justify-content: center; + + & > h2 { + max-width: 500px; + text-align: center; + } + + &::before { + content: ""; + @include o-position-absolute($top: 0, $right: 50px); + width: 100px; + height: 150px; + opacity: .5; + background-image: url('/web_editor/static/src/img/curved_arrow.svg'); + background-size: 100%; + background-repeat: no-repeat; + } +} + @include media-breakpoint-down(sm) { odoo-wysiwyg-container { .panel-heading.note-toolbar { diff --git a/addons/web_editor/static/src/scss/wysiwyg.scss b/addons/web_editor/static/src/scss/wysiwyg.scss index 6eaa65c6e56..39fa326c737 100644 --- a/addons/web_editor/static/src/scss/wysiwyg.scss +++ b/addons/web_editor/static/src/scss/wysiwyg.scss @@ -394,6 +394,15 @@ img.o_we_selected_image { img::selection { background: transparent; } +.o_we_media_author { + font-size: 11px; + @include o-position-absolute($bottom: 0, $left: 0, $right: 0); + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; + background-color: rgba(255, 255, 255, .7); +} @include media-breakpoint-down(md) { #web_editor-top-edit { diff --git a/addons/web_editor/static/src/xml/wysiwyg.xml b/addons/web_editor/static/src/xml/wysiwyg.xml index 714d0913e5d..96823fde263 100644 --- a/addons/web_editor/static/src/xml/wysiwyg.xml +++ b/addons/web_editor/static/src/xml/wysiwyg.xml @@ -125,7 +125,7 @@ <t t-call="wysiwyg.widgets.files.submenu"/> <div class="form-text o_we_error_text"/> <div class="o_we_existing_attachments"/> - <div class="mt-4 text-center mx-auto"> + <div class="mt-4 text-center mx-auto o_we_load_more"> <button class="btn btn-primary o_load_more d-none" type="button">Load more...</button> <div class="mt-4 o_load_done_msg d-none"> <span><i>All images have been loaded</i></span> @@ -170,12 +170,17 @@ <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> <div class="d-flex justify-content-end flex-grow-1 pr-3"> - <div t-attf-class="custom-control custom-switch #{__debug__ or 'd-none'}"> + <div t-attf-class="custom-control custom-switch #{__debug__ and 'd-flex' or 'd-none'} align-items-center mr-2"> <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> + <select class="custom-select o_we_search_select"> + <option value="all">All</option> + <option value="database">My Images</option> + <option value="media-library">Illustrations</option> + </select> </div> </t> </t> @@ -288,14 +293,22 @@ <t t-name="wysiwyg.widgets.image.existing.attachments"> <div class="o_we_existing_attachments o_we_images d-flex flex-wrap w-100 justify-content-between align-items-stretch my-0"> - <div t-if="!attachments.length" class="o_nocontent_help"> - <p class="o_empty_folder_image">No images found.</p> - <p class="o_empty_folder_subtitle">You can upload images with the button located in the top left of the screen.</p> - </div> + <t t-if="!widget.hasContent()"> + <div t-if="widget.needle" class="o_nocontent_help"> + <p class="o_empty_folder_image">No images found.</p> + <p class="o_empty_folder_subtitle">You can upload images with the button located in the top left of the screen.</p> + </div> + <div t-else="" class="o_we_search_prompt"> + <h2>Get the perfect image by searching in our library of copyright free photos and illustrations.</h2> + </div> + </t> <t t-else=""> - <t t-foreach="attachments" t-as="attachment"> + <t t-if="['all', 'database'].includes(widget.searchService)" t-foreach="attachments" t-as="attachment"> <t t-call="wysiwyg.widgets.image.existing.attachment"/> </t> + <t t-if="['all', 'media-library'].includes(widget.searchService)" t-foreach="libraryMedia" t-as="media"> + <t t-call="wysiwyg.widgets.image.library_media"/> + </t> <!-- 20 placeholders is just enough for a 5K screen, change this if ImageWidget.MIN_ROW_HEIGHT changes --> <t t-foreach="20"> <div class="o_we_attachment_placeholder"/> @@ -309,13 +322,21 @@ <div t-attf-class="o_existing_attachment_cell position-relative bg-light #{isOptimized and 'o_we_attachment_optimized d-none' or ''} align-items-center justify-content-center" t-att-data-id="attachment.id"> <t t-call="wysiwyg.widgets.file.existing.remove"/> <div class="o_we_media_dialog_img_wrapper"> - <!-- 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"/> + <img class="img img-fluid o_we_attachment_highlight" t-attf-src="#{attachment.thumbnail_src or attachment.image_src}" t-att-alt="attachment.name" t-att-title="attachment.name"/> </div> <span t-if="isOptimized" class="badge badge-success">Optimized</span> </div> </t> + <t t-name="wysiwyg.widgets.image.library_media"> + <div t-attf-class="o_existing_attachment_cell position-relative bg-light align-items-center justify-content-center o_library_media_cell" t-att-data-media-id="media.id"> + <div class="o_we_media_dialog_img_wrapper"> + <img class="img img-fluid o_we_attachment_highlight" t-attf-src="#{media.thumbnail_url}" t-att-title="media.tooltip or ''" crossorigin="anonymous"/> + <a t-if="media.author" class="o_we_media_author" t-att-href="media.author_link" target="_blank" t-esc="media.author"/> + </div> + </div> + </t> + <t t-name="wysiwyg.widgets.document.existing.attachments"> <div class="o_we_existing_attachments o_we_documents"> <div t-if="!attachments.length" class="o_nocontent_help"> |
