diff options
| author | Samuel Degueldre <sad@odoo.com> | 2019-12-09 06:51:17 +0000 |
|---|---|---|
| committer | Samuel Degueldre <sad@odoo.com> | 2019-12-31 07:58:10 +0000 |
| commit | f8632ec6f3485912aed850a4f59934061baa1cc7 (patch) | |
| tree | 4e96fff2c286bac54ba05a0ff0157868049e7c54 /addons/web_editor/static | |
| parent | 38b5c759f3ade54aaaa620f2b5ab23c11e9cc71e (diff) | |
[IMP] web_editor: add an image-picker UserValueWidget
This commit introduces a new web_editor UserValueWidget on the
we-imagepicker tag, this widget allows the user to pick an image or a
video using the MediaDialog, and calls the containing snippet option's
method (specified via data-attribute) with the url of the chosen picture
or video as widgetValue. Videos are disabled by default and can be
accessed through the use of the data-allow-videos="selector" data
attribute, any $target that matches the selector will have videos
available in the media dialog.
Part of odoo/odoo#41561
task-2155808
Diffstat (limited to 'addons/web_editor/static')
3 files changed, 173 insertions, 73 deletions
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 9e46a728c51..39e6e0c2f11 100644 --- a/addons/web_editor/static/src/js/editor/snippets.options.js +++ b/addons/web_editor/static/src/js/editor/snippets.options.js @@ -1069,6 +1069,104 @@ const ColorpickerUserValueWidget = SelectUserValueWidget.extend({ }, }); +const ImagepickerUserValueWidget = UserValueWidget.extend({ + tagName: 'we-imagepicker', + events: { + 'click .o_we_edit_image': '_onEditImage', + 'click .o_we_remove_image': '_onRemoveImage', + }, + + /** + * @override + */ + start: async function () { + await this._super(...arguments); + const allowedSelector = this.el.dataset.allowVideos; + this.firstFilters = (this.el.dataset.firstFilters || '').split(',').filter(s => s !== ''); + this.allowVideos = allowedSelector ? this.$target.is(allowedSelector) : false; + + this.editImageButton = document.createElement('we-button'); + this.editImageButton.classList.add('o_we_edit_image', 'fa', 'fa-fw', 'fa-edit'); + + this.removeImageButton = document.createElement('we-button'); + this.removeImageButton.classList.add('o_we_remove_image', 'fa', 'fa-fw', 'fa-times'); + this.removeImageButton.title = _t("Remove"); + + this.containerEl.appendChild(this.editImageButton); + this.containerEl.appendChild(this.removeImageButton); + }, + /** + * @override + */ + getMethodsParams: function (methodName) { + return _.extend({isVideo: this.isVideo}, this._super(...arguments)); + }, + + //-------------------------------------------------------------------------- + // Private + //-------------------------------------------------------------------------- + + /** + * @override + */ + _updateUI: async function () { + await this._super(...arguments); + this.removeImageButton.classList.toggle('d-none', !this.isActive()); + }, + + //-------------------------------------------------------------------------- + // Handlers + //-------------------------------------------------------------------------- + + /** + * Called when the edit background button is clicked. + * + * @private + */ + _onEditImage: function (ev) { + // Need a dummy element for the media dialog to modify. + const dummyEl = document.createElement(this.isVideo ? 'iframe' : 'img'); + dummyEl.src = this._value; + if (this.isVideo) { + // Allows the mediaDialog to select the video tab immediately. + dummyEl.classList.add('media_iframe_video'); + } + const $editable = this.$target.closest('.o_editable'); + const mediaDialog = new weWidgets.MediaDialog(this, { + noIcons: true, + noDocuments: true, + noVideos: !this.allowVideos, + isForBgVideo: true, + res_model: $editable.data('oe-model'), + res_id: $editable.data('oe-id'), + firstFilters: this.firstFilters, + }, dummyEl).open(); + mediaDialog.on('save', this, data => { + if (data.bgVideoSrc) { + this._value = data.bgVideoSrc; + this.isVideo = true; + } else { + // Accessing the value directly through dummyEl.src converts the url to absolute + // using getAttribute allows us to keep the url as it was inserted in the DOM + // which can be useful to compare it to values stored in db. + this._value = dummyEl.getAttribute('src'); + this.isVideo = false; + } + this._notifyValueChange(false); + }); + }, + /** + * Called when the remove background button is clicked. + * + * @private + */ + _onRemoveImage: function (ev) { + this._value = ''; + this.isVideo = false; + this._notifyValueChange(false); + }, +}); + const DatetimePickerUserValueWidget = InputUserValueWidget.extend({ events: { // Explicitely not consider all InputUserValueWidget events 'blur input': '_onInputBlur', @@ -1200,6 +1298,7 @@ const userValueWidgetsRegistry = { 'we-multi': MultiUserValueWidget, 'we-colorpicker': ColorpickerUserValueWidget, 'we-datetimepicker': DatetimePickerUserValueWidget, + 'we-imagepicker': ImagepickerUserValueWidget, }; /** 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 5b1f7338683..011989f16e0 100644 --- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js +++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js @@ -1102,82 +1102,19 @@ var VideoWidget = MediaWidget.extend({ */ _createVideoNode: function (url, options) { options = options || {}; - - // Video url patterns(youtube, instagram, vimeo, dailymotion, youku, ...) - var ytRegExp = /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube(-nocookie)?\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((?:\w|-){11})(?:\S+)?$/; - var ytMatch = url.match(ytRegExp); - - var insRegExp = /(.*)instagram.com\/p\/(.[a-zA-Z0-9]*)/; - var insMatch = url.match(insRegExp); - - var vinRegExp = /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/; - var vinMatch = url.match(vinRegExp); - - var vimRegExp = /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/; - var vimMatch = url.match(vimRegExp); - - var dmRegExp = /.+dailymotion.com\/(video|hub|embed)\/([^_]+)[^#]*(#video=([^_&]+))?/; - var dmMatch = url.match(dmRegExp); - - var ykuRegExp = /(.*).youku\.com\/(v_show\/id_|embed\/)(.+)/; - var ykuMatch = url.match(ykuRegExp); - - var $video = $('<iframe>').width(1280).height(720).attr('frameborder', 0).addClass('o_video_dialog_iframe'); - var videoType = 'yt'; - - if (!/^(http:\/\/|https:\/\/|\/\/)[a-z0-9]+([\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i.test(url)){ + const videoData = this._getVideoURLData(url, options); + if (videoData.error) { return {errorCode: 0}; } - - var autoplay = options.autoplay ? '?autoplay=1&mute=1' : '?autoplay=0'; - - if (ytMatch && ytMatch[2].length === 11) { - $video.attr('src', '//www.youtube' + (ytMatch[1] || '') + '.com/embed/' + ytMatch[2] + autoplay); - } else if (insMatch && insMatch[2].length) { - $video.attr('src', '//www.instagram.com/p/' + insMatch[2] + '/embed/'); - videoType = 'ins'; - } else if (vinMatch && vinMatch[0].length) { - $video.attr('src', vinMatch[0] + '/embed/simple'); - videoType = 'vin'; - } else if (vimMatch && vimMatch[3].length) { - $video.attr('src', '//player.vimeo.com/video/' + vimMatch[3] + autoplay.replace('mute', 'muted')); - videoType = 'vim'; - } else if (dmMatch && dmMatch[2].length) { - var justId = dmMatch[2].replace('video/', ''); - $video.attr('src', '//www.dailymotion.com/embed/video/' + justId + autoplay); - videoType = 'dm'; - } else if (ykuMatch && ykuMatch[3].length) { - var ykuId = ykuMatch[3].indexOf('.html?') >= 0 ? ykuMatch[3].substring(0, ykuMatch[3].indexOf('.html?')) : ykuMatch[3]; - $video.attr('src', '//player.youku.com/embed/' + ykuId); - videoType = 'yku'; - } else { + if (!videoData.type) { return {errorCode: 1}; } + const $video = $('<iframe>').width(1280).height(720) + .attr('frameborder', 0) + .attr('src', videoData.embedURL) + .addClass('o_video_dialog_iframe'); - if (ytMatch) { - $video.attr('src', $video.attr('src') + '&rel=0'); - } - if (options.loop && (ytMatch || vimMatch)) { - var videoSrc = _.str.sprintf('%s&loop=1', $video.attr('src')); - $video.attr('src', ytMatch ? _.str.sprintf('%s&playlist=%s', videoSrc, ytMatch[2]) : videoSrc); - } - if (options.hide_controls && (ytMatch || dmMatch)) { - $video.attr('src', $video.attr('src') + '&controls=0'); - } - if (options.hide_fullscreen && ytMatch) { - $video.attr('src', $video.attr('src') + '&fs=0'); - } - if (options.hide_yt_logo && ytMatch) { - $video.attr('src', $video.attr('src') + '&modestbranding=1'); - } - if (options.hide_dm_logo && dmMatch) { - $video.attr('src', $video.attr('src') + '&ui-logo=0'); - } - if (options.hide_dm_share && dmMatch) { - $video.attr('src', $video.attr('src') + '&sharing-enable=0'); - } - - return {$video: $video, type: videoType}; + return {$video: $video, type: videoData.type}; }, /** * Updates the video preview according to video code and enabled options. @@ -1288,6 +1225,63 @@ var VideoWidget = MediaWidget.extend({ _onVideoCodeInput: function () { this._updateVideo(); }, + /** + * Parses a URL and returns the provider type and an emebedable URL. + * + * @private + */ + _getVideoURLData: function (url, options) { + if (!url.match(/^(http:\/\/|https:\/\/|\/\/)[a-z0-9]+([-.]{1}[a-z0-9]+)*\.[a-z]{2,5}(:[0-9]{1,5})?(\/.*)?$/i)) { + return { + error: true, + message: 'The provided url is invalid', + }; + } + const regexes = { + youtube: /^(?:(?:https?:)?\/\/)?(?:www\.)?(?:youtu\.be\/|youtube(-nocookie)?\.com\/(?:embed\/|v\/|watch\?v=|watch\?.+&v=))((?:\w|-){11})(?:\S+)?$/, + instagram: /(.*)instagram.com\/p\/(.[a-zA-Z0-9]*)/, + vine: /\/\/vine.co\/v\/(.[a-zA-Z0-9]*)/, + vimeo: /\/\/(player.)?vimeo.com\/([a-z]*\/)*([0-9]{6,11})[?]?.*/, + dailymotion: /.+dailymotion.com\/(video|hub|embed)\/([^_]+)[^#]*(#video=([^_&]+))?/, + youku: /(.*).youku\.com\/(v_show\/id_|embed\/)(.+)/, + }; + const matches = _.mapObject(regexes, regex => url.match(regex)); + const autoplay = options.autoplay ? '?autoplay=1&mute=1' : '?autoplay=0'; + const controls = options.hide_controls ? '&controls=0' : ''; + const loop = options.loop ? '&loop=1' : ''; + + let embedURL; + let type; + if (matches.youtube && matches.youtube[2].length === 11) { + const fullscreen = options.hide_fullscreen ? '&fs=0' : ''; + const ytLoop = loop ? loop + `&playlist=${matches.youtube[2]}` : ''; + const logo = options.hide_yt_logo ? '&modestbranding=1' : ''; + embedURL = `//www.youtube${matches.youtube[1] || ''}.com/embed/${matches.youtube[2]}${autoplay}&rel=0${ytLoop}${controls}${fullscreen}${logo}`; + type = 'youtube'; + } else if (matches.instagram && matches.instagram[2].length) { + embedURL = `//www.instagram.com/p/${matches.instagram[2]}/embed/`; + type = 'instagram'; + } else if (matches.vine && matches.vine[0].length) { + embedURL = `${matches.vine[0]}/embed/simple`; + type = 'vine'; + } else if (matches.vimeo && matches.vimeo[3].length) { + const vimeoAutoplay = autoplay.replace('mute', 'muted'); + embedURL = `//player.vimeo.com/video/${matches.vimeo[3]}${vimeoAutoplay}${loop}`; + type = 'vimeo'; + } else if (matches.dailymotion && matches.dailymotion[2].length) { + const videoId = matches.dailymotion[2].replace('video/', ''); + const logo = options.hide_dm_logo ? '&ui-logo=0' : ''; + const share = options.hide_dm_share ? '&sharing-enable=0' : ''; + embedURL = `//www.dailymotion.com/embed/video/${videoId}${autoplay}${controls}${logo}${share}`; + type = 'dailymotion'; + } else if (matches.youku && matches.youku[3].length) { + const videoId = matches.youku[3].indexOf('.html?') >= 0 ? matches.youku[3].substring(0, matches.youku[3].indexOf('.html?')) : matches.youku[3]; + embedURL = `//player.youku.com/embed/${videoId}`; + type = 'youku'; + } + + return {type: type, embedURL: embedURL}; + }, }); return { diff --git a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss index a5c1ed6195d..275166aac22 100644 --- a/addons/web_editor/static/src/scss/wysiwyg_snippets.scss +++ b/addons/web_editor/static/src/scss/wysiwyg_snippets.scss @@ -301,12 +301,19 @@ body.editor_enable.editor_has_snippets { > div { display: flex; align-items: baseline; + justify-content: flex-end; } } // Buttons - we-button.o_we_user_value_widget.active { - @include we-icon-button('\f00c', $o-we-color-success); + we-button.o_we_user_value_widget { + &.active { + @include we-icon-button('\f00c', $o-we-color-success); + } + + > div { + justify-content: flex-start; + } } // Checkboxes |
