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 f124d1841f0..5b493dfef0d 100644
--- a/addons/web_editor/static/src/js/editor/snippets.options.js
+++ b/addons/web_editor/static/src/js/editor/snippets.options.js
@@ -838,23 +838,18 @@ registry.background = SnippetOption.extend({
* @see this.selectClass for parameters
*/
chooseImage: function (previewMode, value, $opt) {
- // Put fake image in the DOM, edit it and use it as background-image
- var $image = $('
', {class: 'd-none', src: value === 'true' ? '' : value}).appendTo(this.$target);
+ var options = this._getMediaDialogOptions();
+ var media = this._getEditableMedia();
- var $editable = this.$target.closest('.o_editable');
- var _editor = new weWidgets.MediaDialog(this, {
- 'onlyImages': true,
- 'firstFilters': ['background'],
- 'res_model': $editable.data('oe-model'),
- 'res_id': $editable.data('oe-id'),
- }, $image[0]).open();
-
- _editor.on('save', this, function () {
- this._setCustomBackground($image.attr('src'));
+ var _editor = new weWidgets.MediaDialog(this, options, media).open();
+ _editor.on('save', this, data => {
+ this._onSaveMediaDialog(data);
this.$target.trigger('content_changed');
});
- _editor.on('closed', this, function () {
- $image.remove();
+ _editor.on('closed', this, () => {
+ if (media.classList.contains('o_we_fake_image')) {
+ media.parentNode.removeChild(media);
+ }
});
},
@@ -871,16 +866,7 @@ registry.background = SnippetOption.extend({
return;
}
this.$target.off('.background-option')
- .on('background-color-event.background-option', (function (e, previewMode) {
- e.stopPropagation();
- if (e.currentTarget !== e.target) {
- return;
- }
- if (previewMode === false) {
- this.__customImageSrc = undefined;
- }
- this.background(previewMode);
- }).bind(this));
+ .on('background-color-event.background-option', this._onBackgroundColorUpdate.bind(this));
},
/**
* @override
@@ -896,6 +882,37 @@ registry.background = SnippetOption.extend({
// Private
//--------------------------------------------------------------------------
+ /**
+ * Returns a media element the media dialog will be able to edit to use
+ * the result as the snippet's background somehow.
+ *
+ * @private
+ * @returns {HTMLElement}
+ */
+ _getEditableMedia: function () {
+ var $image = $('
', {
+ class: 'd-none o_we_fake_image',
+ }).appendTo(this.$target);
+ return $image[0];
+ },
+ /**
+ * Returns the options to be given to the MediaDialog instance when choosing
+ * a snippet's background.
+ *
+ * @private
+ * @returns {Object}
+ */
+ _getMediaDialogOptions: function () {
+ var $editable = this.$target.closest('.o_editable');
+ return {
+ noDocuments: true,
+ noIcons: true,
+ noVideos: true,
+ firstFilters: ['background'],
+ res_model: $editable.data('oe-model'),
+ res_id: $editable.data('oe-id'),
+ };
+ },
/**
* Returns the src value from a css value related to a background image
* (e.g. "url('blabla')" => "blabla" / "none" => "").
@@ -941,6 +958,42 @@ registry.background = SnippetOption.extend({
this._setActive();
this.$target.trigger('snippet-option-change', [this]);
},
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * Called on background-color update (useful to remove the background to be
+ * able to see the chosen color).
+ *
+ * @private
+ * @param {Event} ev
+ * @param {boolean|string} previewMode
+ * @returns {boolean} true if the color has been applied (removing the
+ * background)
+ */
+ _onBackgroundColorUpdate: function (ev, previewMode) {
+ ev.stopPropagation();
+ if (ev.currentTarget !== ev.target) {
+ return false;
+ }
+ if (previewMode === false) {
+ this.__customImageSrc = undefined;
+ }
+ this.background(previewMode);
+ return true;
+ },
+ /**
+ * Called on media dialog save (when choosing a snippet's background) ->
+ * sets the resulting media as the snippet's background somehow.
+ *
+ * @private
+ * @param {Object} data
+ */
+ _onSaveMediaDialog: function (data) {
+ this._setCustomBackground(data.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 cbb39ea6173..a6a9fb675fd 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media.js
@@ -1009,8 +1009,9 @@ var VideoWidget = MediaWidget.extend({
/**
* @constructor
*/
- init: function (parent, media) {
+ init: function (parent, media, options) {
this._super.apply(this, arguments);
+ this.isForBgVideo = !!options.isForBgVideo;
this._onVideoCodeInput = _.debounce(this._onVideoCodeInput, 1000);
},
/**
@@ -1021,7 +1022,7 @@ var VideoWidget = MediaWidget.extend({
if (this.media) {
var $media = $(this.media);
- var src = $media.data('oe-expression') || $media.data('src') || '';
+ var src = $media.data('oe-expression') || $media.data('src') || ($media.is('iframe') ? $media.attr('src') : '') || '';
this.$('textarea#o_video_text').val(src);
this.$('input#o_video_autoplay').prop('checked', src.indexOf('autoplay=1') >= 0);
@@ -1047,6 +1048,9 @@ var VideoWidget = MediaWidget.extend({
*/
save: function () {
this._updateVideo();
+ if (this.isForBgVideo) {
+ return Promise.resolve({bgVideoSrc: this.$content.attr('src')});
+ }
if (this.$('.o_video_dialog_iframe').is('iframe')) {
this.$media = $(
'
' +
@@ -1200,13 +1204,13 @@ var VideoWidget = MediaWidget.extend({
var url = embedMatch ? embedMatch[1] : code;
var query = this._createVideoNode(url, {
- autoplay: this.$('input#o_video_autoplay').is(':checked'),
- hide_controls: this.$('input#o_video_hide_controls').is(':checked'),
- loop: this.$('input#o_video_loop').is(':checked'),
- hide_fullscreen: this.$('input#o_video_hide_fullscreen').is(':checked'),
- hide_yt_logo: this.$('input#o_video_hide_yt_logo').is(':checked'),
- hide_dm_logo: this.$('input#o_video_hide_dm_logo').is(':checked'),
- hide_dm_share: this.$('input#o_video_hide_dm_share').is(':checked'),
+ 'autoplay': this.isForBgVideo || this.$('input#o_video_autoplay').is(':checked'),
+ 'hide_controls': this.isForBgVideo || this.$('input#o_video_hide_controls').is(':checked'),
+ 'loop': this.isForBgVideo || this.$('input#o_video_loop').is(':checked'),
+ 'hide_fullscreen': this.isForBgVideo || this.$('input#o_video_hide_fullscreen').is(':checked'),
+ 'hide_yt_logo': this.isForBgVideo || this.$('input#o_video_hide_yt_logo').is(':checked'),
+ 'hide_dm_logo': this.isForBgVideo || this.$('input#o_video_hide_dm_logo').is(':checked'),
+ 'hide_dm_share': this.isForBgVideo || this.$('input#o_video_hide_dm_share').is(':checked'),
});
var $optBox = this.$('.o_video_dialog_options');
@@ -1222,8 +1226,9 @@ var VideoWidget = MediaWidget.extend({
// Individually show / hide options base on the video provider
$optBox.find('div.o_' + query.type + '_option').removeClass('d-none');
- // Hide the entire options box if no options are available
- $optBox.toggleClass('d-none', $optBox.find('div:not(.d-none)').length === 0);
+ // Hide the entire options box if no options are available or if the
+ // dialog is opened for a background-video
+ $optBox.toggleClass('d-none', this.isForBgVideo || $optBox.find('div:not(.d-none)').length === 0);
if (query.type === 'yt') {
// Youtube only: If 'hide controls' is checked, hide 'fullscreen'
diff --git a/addons/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js b/addons/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js
index 84dd8e7f650..6d1f2b642c7 100644
--- a/addons/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js
+++ b/addons/web_editor/static/src/js/wysiwyg/widgets/media_dialog.js
@@ -76,7 +76,7 @@ var MediaDialog = Dialog.extend({
this.activeWidget = this.imageWidget;
} else if (this.documentWidget && $media.is('a.o_image')) {
this.activeWidget = this.documentWidget;
- } else if (this.videoWidget && $media.hasClass('media_iframe_video')) {
+ } else if (this.videoWidget && $media.is('.media_iframe_video, .o_bg_video_iframe')) {
this.activeWidget = this.videoWidget;
} else if (this.iconWidget && $media.is('span, i')) {
this.activeWidget = this.iconWidget;
diff --git a/addons/web_editor/static/src/xml/wysiwyg.xml b/addons/web_editor/static/src/xml/wysiwyg.xml
index b9b29a8984e..bcf3a480a93 100644
--- a/addons/web_editor/static/src/xml/wysiwyg.xml
+++ b/addons/web_editor/static/src/xml/wysiwyg.xml
@@ -383,7 +383,7 @@
- Accepts Youtube, Instagram, Vine.co, Vimeo, Dailymotion and Youku videos
+ Accepts Youtube, Vimeo, Dailymotion and Youku videos
diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js
index 681f6719354..7acb36d284b 100644
--- a/addons/website/static/src/js/content/snippets.animation.js
+++ b/addons/website/static/src/js/content/snippets.animation.js
@@ -6,9 +6,9 @@ odoo.define('website.content.snippets.animation', function (require) {
*/
var Class = require('web.Class');
+var config = require('web.config');
var core = require('web.core');
var mixins = require('web.mixins');
-var utils = require('web.utils');
var publicWidget = require('web.public.widget');
var utils = require('web.utils');
@@ -681,6 +681,113 @@ registry.mediaVideo = publicWidget.Widget.extend({
},
});
+registry.backgroundVideo = publicWidget.Widget.extend({
+ selector: '.o_background_video',
+ xmlDependencies: ['/website/static/src/xml/website.background.video.xml'],
+ disabledInEditableMode: false,
+
+ /**
+ * @override
+ */
+ start: function () {
+ var proms = [this._super(...arguments)];
+
+ this.videoSrc = this.el.dataset.bgVideoSrc;
+ this.iframeID = _.uniqueId('o_bg_video_iframe_');
+
+ this.isYoutubeVideo = this.videoSrc.indexOf('youtube') >= 0;
+ this.isMobileEnv = config.device.size_class <= config.device.SIZES.LG && config.device.touch;
+ if (this.isYoutubeVideo && this.isMobileEnv) {
+ this.videoSrc = this.videoSrc + "&enablejsapi=1";
+
+ if (!window.YT) {
+ proms.push(new Promise(resolve => {
+ window.onYouTubeIframeAPIReady = () => resolve();
+ }));
+ $('', {
+ src: 'https://www.youtube.com/iframe_api',
+ }).appendTo('head');
+ }
+ }
+
+ $(window).on('resize.' + this.iframeID, _.throttle(() => this._adjustIframe(), 50));
+
+ return Promise.all(proms).then(() => this._appendBgVideo());
+ },
+ /**
+ * @override
+ */
+ destroy: function () {
+ this._super.apply(this, arguments);
+
+ $(window).off('resize.' + this.iframeID);
+
+ if (this.$bgVideoContainer) {
+ this.$bgVideoContainer.remove();
+ }
+ },
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * Adjusts iframe sizes and position so that it fills the container and so
+ * that it is centered in it.
+ *
+ * @private
+ */
+ _adjustIframe: function () {
+ // Adjust the iframe
+ var wrapperWidth = this.$target.innerWidth();
+ var wrapperHeight = this.$target.innerHeight();
+ var relativeRatio = (wrapperWidth / wrapperHeight) / (16 / 9);
+ var style = {};
+ if (relativeRatio >= 1.0) {
+ style['width'] = '100%';
+ style['height'] = (relativeRatio * 100) + '%';
+ style['left'] = '0';
+ style['top'] = (-(relativeRatio - 1.0) / 2 * 100) + '%';
+ } else {
+ style['width'] = ((1 / relativeRatio) * 100) + '%';
+ style['height'] = '100%';
+ style['left'] = (-((1 / relativeRatio) - 1.0) / 2 * 100) + '%';
+ style['top'] = '0';
+ }
+ this.$iframe.css(style);
+ },
+ /**
+ * Append background video related elements to the target.
+ *
+ * @private
+ */
+ _appendBgVideo: function () {
+ var $oldContainer = this.$bgVideoContainer || this.$('> .o_bg_video_container');
+ this.$bgVideoContainer = $(qweb.render('website.background.video', {
+ videoSrc: this.videoSrc,
+ iframeID: this.iframeID,
+ }));
+ this.$iframe = this.$bgVideoContainer.find('.o_bg_video_iframe');
+ this.$iframe.one('load', () => {
+ this.$bgVideoContainer.find('.o_bg_video_loading').remove();
+ });
+ this.$bgVideoContainer.prependTo(this.$target);
+ $oldContainer.remove();
+
+ this._adjustIframe();
+
+ // YouTube does not allow to auto-play video in mobile devices, so we
+ // have to play the video manually.
+ if (this.isMobileEnv && this.isYoutubeVideo) {
+ new window.YT.Player(this.iframeID, {
+ events: {
+ onReady: ev => ev.target.playVideo(),
+ }
+ });
+ }
+ },
+});
+
registry.ul = publicWidget.Widget.extend({
selector: 'ul.o_ul_folded, ol.o_ul_folded',
events: {
diff --git a/addons/website/static/src/js/editor/snippets.options.js b/addons/website/static/src/js/editor/snippets.options.js
index baa1d8c5a7c..a5f46382dcf 100644
--- a/addons/website/static/src/js/editor/snippets.options.js
+++ b/addons/website/static/src/js/editor/snippets.options.js
@@ -29,6 +29,102 @@ options.Class.include({
},
});
+options.registry.background.include({
+
+ //--------------------------------------------------------------------------
+ // Private
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _getEditableMedia: function () {
+ if (!this._hasBgvideo()) {
+ return this._super(...arguments);
+ }
+ return this.$('.o_bg_video_iframe')[0];
+ },
+ /**
+ * @override
+ */
+ _getMediaDialogOptions: function () {
+ return _.extend(this._super(...arguments), {
+ // For now, disable the possibility to have a parallax video bg
+ noVideos: this.$target.is('.parallax, .s_parallax_bg'),
+ isForBgVideo: true,
+ });
+ },
+ /**
+ * @override
+ */
+ _setActive: function () {
+ this._super(...arguments);
+ if (this._hasBgvideo()) {
+ this.$el.find('[data-choose-image]').addClass('active');
+ }
+ },
+ /**
+ * Updates the background video used by the snippet.
+ *
+ * @private
+ * @see this.selectClass for parameters
+ */
+ _setBgVideo: function (previewMode, value) {
+ this.$('> .o_bg_video_container').toggleClass('d-none', previewMode === true);
+
+ if (previewMode !== false) {
+ return;
+ }
+
+ var target = this.$target[0];
+ target.classList.toggle('o_background_video', !!(value && value.length));
+ if (value && value.length) {
+ target.dataset.bgVideoSrc = value;
+ } else {
+ delete target.dataset.bgVideoSrc;
+ }
+ this._refreshPublicWidgets();
+ this._setActive();
+ },
+ /**
+ * Returns whether the current target has a background video or not.
+ *
+ * @private
+ * @returns {boolean}
+ */
+ _hasBgvideo: function () {
+ return this.$target[0].classList.contains('o_background_video');
+ },
+
+ //--------------------------------------------------------------------------
+ // Handlers
+ //--------------------------------------------------------------------------
+
+ /**
+ * @override
+ */
+ _onBackgroundColorUpdate: function (ev, previewMode) {
+ var ret = this._super(...arguments);
+ if (ret) {
+ this._setBgVideo(previewMode);
+ }
+ return ret;
+ },
+ /**
+ * @override
+ */
+ _onSaveMediaDialog: function (data) {
+ if (!data.bgVideoSrc) {
+ this._setBgVideo(false);
+ this._super(...arguments);
+ return;
+ }
+ // if the user chose a video, only add the video without removing the
+ // background
+ this._setBgVideo(false, data.bgVideoSrc);
+ },
+});
+
options.registry.menu_data = options.Class.extend({
xmlDependencies: ['/website/static/src/xml/website.editor.xml'],
diff --git a/addons/website/static/src/scss/website.scss b/addons/website/static/src/scss/website.scss
index 9e2d187f8a1..e2866e56a89 100644
--- a/addons/website/static/src/scss/website.scss
+++ b/addons/website/static/src/scss/website.scss
@@ -257,6 +257,26 @@ font[class*='bg-'] {
}
}
+// Background videos
+.o_background_video {
+ position: relative;
+
+ > * {
+ position: relative;
+ }
+}
+.o_bg_video_container {
+ @include o-position-absolute(0, 0 ,0 ,0);
+ overflow: hidden;
+}
+.o_bg_video_iframe {
+ position: relative;
+ pointer-events: none !important;
+}
+.o_bg_video_loading {
+ @include o-position-absolute(0, 0 ,0 ,0);
+}
+
// Probably outdated
// Lists
.o_ul_toggle {
diff --git a/addons/website/static/src/xml/website.background.video.xml b/addons/website/static/src/xml/website.background.video.xml
new file mode 100644
index 00000000000..548ce2aabb2
--- /dev/null
+++ b/addons/website/static/src/xml/website.background.video.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+
diff --git a/addons/website/views/snippets.xml b/addons/website/views/snippets.xml
index bbda42f5ffb..5d6059c2c35 100644
--- a/addons/website/views/snippets.xml
+++ b/addons/website/views/snippets.xml
@@ -1317,7 +1317,7 @@
data-selector="section, .parallax, :not(.o_gallery > .container) > .carousel"
data-exclude=".s_hr, .s_image_gallery">
- Background Image
+ Background