diff options
| author | qsm-odoo <qsm@odoo.com> | 2019-12-18 01:43:51 +0100 |
|---|---|---|
| committer | qsm-odoo <qsm@odoo.com> | 2019-12-18 01:43:51 +0100 |
| commit | ade78eda648d53550dec530fddef2f0999eab51a (patch) | |
| tree | 702ae31268e5bd2cd1ae24d69a5f500ed869f5d4 /addons/website/static/src/js/content/snippets.animation.js | |
| parent | 6be021f2a2da0742f6607718b4aac433798fa239 (diff) | |
[REF] fake move of snippets.xml, website.scss and animation/options.js
Diffstat (limited to 'addons/website/static/src/js/content/snippets.animation.js')
| -rw-r--r-- | addons/website/static/src/js/content/snippets.animation.js | 1668 |
1 files changed, 0 insertions, 1668 deletions
diff --git a/addons/website/static/src/js/content/snippets.animation.js b/addons/website/static/src/js/content/snippets.animation.js deleted file mode 100644 index f9883534bd5..00000000000 --- a/addons/website/static/src/js/content/snippets.animation.js +++ /dev/null @@ -1,1668 +0,0 @@ -odoo.define('website.content.snippets.animation', function (require) { -'use strict'; - -/** - * Provides a way to start JS code for snippets' initialization and animations. - */ - -var Class = require('web.Class'); -const ColorpickerDialog = require('web.ColorpickerDialog'); -var config = require('web.config'); -var core = require('web.core'); -var mixins = require('web.mixins'); -var publicWidget = require('web.public.widget'); -var utils = require('web.utils'); - -var qweb = core.qweb; -const _t = core._t; - -// Initialize fallbacks for the use of requestAnimationFrame, -// cancelAnimationFrame and performance.now() -window.requestAnimationFrame = window.requestAnimationFrame - || window.webkitRequestAnimationFrame - || window.mozRequestAnimationFrame - || window.msRequestAnimationFrame - || window.oRequestAnimationFrame; -window.cancelAnimationFrame = window.cancelAnimationFrame - || window.webkitCancelAnimationFrame - || window.mozCancelAnimationFrame - || window.msCancelAnimationFrame - || window.oCancelAnimationFrame; -if (!window.performance || !window.performance.now) { - window.performance = { - now: function () { - return Date.now(); - } - }; -} - -/** - * Add the notion of edit mode to public widgets. - */ -publicWidget.Widget.include({ - /** - * Indicates if the widget should not be instantiated in edit. The default - * is true, indeed most (all?) defined widgets only want to initialize - * events and states which should not be active in edit mode (this is - * especially true for non-website widgets). - * - * @type {boolean} - */ - disabledInEditableMode: true, - /** - * Acts as @see Widget.events except that the events are only binded if the - * Widget instance is instanciated in edit mode. The property is not - * considered if @see disabledInEditableMode is false. - */ - edit_events: null, - /** - * Acts as @see Widget.events except that the events are only binded if the - * Widget instance is instanciated in readonly mode. The property only - * makes sense if @see disabledInEditableMode is false, you should simply - * use @see Widget.events otherwise. - */ - read_events: null, - - /** - * Initializes the events that will need to be binded according to the - * given mode. - * - * @constructor - * @param {Object} parent - * @param {Object} [options] - * @param {boolean} [options.editableMode=false] - * true if the page is in edition mode - */ - init: function (parent, options) { - this._super.apply(this, arguments); - - this.editableMode = this.options.editableMode || false; - var extraEvents = this.editableMode ? this.edit_events : this.read_events; - if (extraEvents) { - this.events = _.extend({}, this.events || {}, extraEvents); - } - }, -}); - -/** - * In charge of handling one animation loop using the requestAnimationFrame - * feature. This is used by the `Animation` class below and should not be called - * directly by an end developer. - * - * This uses a simple API: it can be started, stopped, played and paused. - */ -var AnimationEffect = Class.extend(mixins.ParentedMixin, { - /** - * @constructor - * @param {Object} parent - * @param {function} updateCallback - the animation update callback - * @param {string} [startEvents=scroll] - * space separated list of events which starts the animation loop - * @param {jQuery|DOMElement} [$startTarget=window] - * the element(s) on which the startEvents are listened - * @param {Object} [options] - * @param {function} [options.getStateCallback] - * a function which returns a value which represents the state of the - * animation, i.e. for two same value, no refreshing of the animation - * is needed. Can be used for optimization. If the $startTarget is - * the window element, this defaults to returning the current - * scoll offset of the window or the size of the window for the - * scroll and resize events respectively. - * @param {string} [options.endEvents] - * space separated list of events which pause the animation loop. If - * not given, the animation is stopped after a while (if no - * startEvents is received again) - * @param {jQuery|DOMElement} [options.$endTarget=$startTarget] - * the element(s) on which the endEvents are listened - */ - init: function (parent, updateCallback, startEvents, $startTarget, options) { - mixins.ParentedMixin.init.call(this); - this.setParent(parent); - - options = options || {}; - this._minFrameTime = 1000 / (options.maxFPS || 100); - - // Initialize the animation startEvents, startTarget, endEvents, endTarget and callbacks - this._updateCallback = updateCallback; - this.startEvents = startEvents || 'scroll'; - this.$startTarget = $($startTarget || window); - if (options.getStateCallback) { - this._getStateCallback = options.getStateCallback; - } else if (this.startEvents === 'scroll' && this.$startTarget[0] === window) { - this._getStateCallback = function () { - return window.pageYOffset; - }; - } else if (this.startEvents === 'resize' && this.$startTarget[0] === window) { - this._getStateCallback = function () { - return { - width: window.innerWidth, - height: window.innerHeight, - }; - }; - } else { - this._getStateCallback = function () { - return undefined; - }; - } - this.endEvents = options.endEvents || false; - this.$endTarget = options.$endTarget ? $(options.$endTarget) : this.$startTarget; - - this._updateCallback = this._updateCallback.bind(parent); - this._getStateCallback = this._getStateCallback.bind(parent); - - // Add a namespace to events using the generated uid - this._uid = '_animationEffect' + _.uniqueId(); - this.startEvents = _processEvents(this.startEvents, this._uid); - if (this.endEvents) { - this.endEvents = _processEvents(this.endEvents, this._uid); - } - - function _processEvents(events, namespace) { - events = events.split(' '); - return _.each(events, function (e, index) { - events[index] += ('.' + namespace); - }).join(' '); - } - }, - /** - * @override - */ - destroy: function () { - mixins.ParentedMixin.destroy.call(this); - this.stop(); - }, - - //-------------------------------------------------------------------------- - // Public - //-------------------------------------------------------------------------- - - /** - * Initializes when the animation must be played and paused and initializes - * the animation first frame. - */ - start: function () { - // Initialize the animation first frame - this._paused = false; - this._rafID = window.requestAnimationFrame((function (t) { - this._update(t); - this._paused = true; - }).bind(this)); - - // Initialize the animation play/pause events - if (this.endEvents) { - /** - * If there are endEvents, the animation should begin playing when - * the startEvents are triggered on the $startTarget and pause when - * the endEvents are triggered on the $endTarget. - */ - this.$startTarget.on(this.startEvents, (function (e) { - if (this._paused) { - _.defer(this.play.bind(this, e)); - } - }).bind(this)); - this.$endTarget.on(this.endEvents, (function () { - if (!this._paused) { - _.defer(this.pause.bind(this)); - } - }).bind(this)); - } else { - /** - * Else, if there is no endEvents, the animation should begin playing - * when the startEvents are *continuously* triggered on the - * $startTarget or fully played once. To achieve this, the animation - * begins playing and is scheduled to pause after 2 seconds. If the - * startEvents are triggered during that time, this is not paused - * for another 2 seconds. This allows to describe an "effect" - * animation (which lasts less than 2 seconds) or an animation which - * must be playing *during* an event (scroll, mousemove, resize, - * repeated clicks, ...). - */ - var pauseTimer = null; - this.$startTarget.on(this.startEvents, _.throttle((function (e) { - this.play(e); - - clearTimeout(pauseTimer); - pauseTimer = _.delay((function () { - this.pause(); - pauseTimer = null; - }).bind(this), 2000); - }).bind(this), 250, {trailing: false})); - } - }, - /** - * Pauses the animation and destroys the attached events which trigger the - * animation to be played or paused. - */ - stop: function () { - this.$startTarget.off(this.startEvents); - if (this.endEvents) { - this.$endTarget.off(this.endEvents); - } - this.pause(); - }, - /** - * Forces the requestAnimationFrame loop to start. - * - * @param {Event} e - the event which triggered the animation to play - */ - play: function (e) { - this._newEvent = e; - if (!this._paused) { - return; - } - this._paused = false; - this._rafID = window.requestAnimationFrame(this._update.bind(this)); - this._lastUpdateTimestamp = undefined; - }, - /** - * Forces the requestAnimationFrame loop to stop. - */ - pause: function () { - if (this._paused) { - return; - } - this._paused = true; - window.cancelAnimationFrame(this._rafID); - this._lastUpdateTimestamp = undefined; - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Callback which is repeatedly called by the requestAnimationFrame loop. - * It controls the max fps at which the animation is running and initializes - * the values that the update callback needs to describe the animation - * (state, elapsedTime, triggered event). - * - * @private - * @param {DOMHighResTimeStamp} timestamp - */ - _update: function (timestamp) { - if (this._paused) { - return; - } - this._rafID = window.requestAnimationFrame(this._update.bind(this)); - - // Check the elapsed time since the last update callback call. - // Consider it 0 if there is no info of last timestamp and leave this - // _update call if it was called too soon (would overflow the set max FPS). - var elapsedTime = 0; - if (this._lastUpdateTimestamp) { - elapsedTime = timestamp - this._lastUpdateTimestamp; - if (elapsedTime < this._minFrameTime) { - return; - } - } - - // Check the new animation state thanks to the get state callback and - // store its new value. If the state is the same as the previous one, - // leave this _update call, except if there is an event which triggered - // the "play" method again. - var animationState = this._getStateCallback(elapsedTime, this._newEvent); - if (!this._newEvent - && animationState !== undefined - && _.isEqual(animationState, this._animationLastState)) { - return; - } - this._animationLastState = animationState; - - // Call the update callback with frame parameters - this._updateCallback(this._animationLastState, elapsedTime, this._newEvent); - this._lastUpdateTimestamp = timestamp; // Save the timestamp at which the update callback was really called - this._newEvent = undefined; // Forget the event which triggered the last "play" call - }, -}); - -/** - * Also register AnimationEffect automatically (@see effects, _prepareEffects). - */ -var Animation = publicWidget.Widget.extend({ - /** - * The max FPS at which all the automatic animation effects will be - * running by default. - */ - maxFPS: 100, - /** - * @see this._prepareEffects - * - * @type {Object[]} - * @type {string} startEvents - * The names of the events which trigger the effect to begin playing. - * @type {string} [startTarget] - * A selector to find the target where to listen for the start events - * (if no selector, the window target will be used). If the whole - * $target of the animation should be used, use the 'selector' string. - * @type {string} [endEvents] - * The name of the events which trigger the end of the effect (if none - * is defined, the animation will stop after a while - * @see AnimationEffect.start). - * @type {string} [endTarget] - * A selector to find the target where to listen for the end events - * (if no selector, the startTarget will be used). If the whole - * $target of the animation should be used, use the 'selector' string. - * @type {string} update - * A string which refers to a method which will be used as the update - * callback for the effect. It receives 3 arguments: the animation - * state, the elapsedTime since last update and the event which - * triggered the animation (undefined if just a new update call - * without trigger). - * @type {string} [getState] - * The animation state is undefined by default, the scroll offset for - * the particular {startEvents: 'scroll'} effect and an object with - * width and height for the particular {startEvents: 'resize'} effect. - * There is the possibility to define the getState callback of the - * animation effect with this key. This allows to improve performance - * even further in some cases. - */ - effects: [], - - /** - * Initializes the animation. The method should not be called directly as - * called automatically on animation instantiation and on restart. - * - * Also, prepares animation's effects and start them if any. - * - * @override - */ - start: function () { - this._prepareEffects(); - _.each(this._animationEffects, function (effect) { - effect.start(); - }); - return this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Registers `AnimationEffect` instances. - * - * This can be done by extending this method and calling the @see _addEffect - * method in it or, better, by filling the @see effects property. - * - * @private - */ - _prepareEffects: function () { - this._animationEffects = []; - - var self = this; - _.each(this.effects, function (desc) { - self._addEffect(self[desc.update], desc.startEvents, _findTarget(desc.startTarget), { - getStateCallback: desc.getState && self[desc.getState], - endEvents: desc.endEvents || undefined, - $endTarget: _findTarget(desc.endTarget), - maxFPS: self.maxFPS, - }); - - // Return the DOM element matching the selector in the form - // described above. - function _findTarget(selector) { - if (selector) { - if (selector === 'selector') { - return self.$target; - } - return self.$(selector); - } - return undefined; - } - }); - }, - /** - * Registers a new `AnimationEffect` according to given parameters. - * - * @private - * @see AnimationEffect.init - */ - _addEffect: function (updateCallback, startEvents, $startTarget, options) { - this._animationEffects.push( - new AnimationEffect(this, updateCallback, startEvents, $startTarget, options) - ); - }, -}); - -//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: - -var registry = publicWidget.registry; - -registry.slider = publicWidget.Widget.extend({ - selector: '.carousel', - disabledInEditableMode: false, - edit_events: { - 'content_changed': '_onContentChanged', - }, - - /** - * @override - */ - start: function () { - this.$('img').on('load.slider', () => this._computeHeights()); - this._computeHeights(); - // Initialize carousel and pause if in edit mode. - this.$target.carousel(this.editableMode ? 'pause' : undefined); - return this._super.apply(this, arguments); - }, - /** - * @override - */ - destroy: function () { - this._super.apply(this, arguments); - this.$('img').off('.slider'); - this.$target.carousel('pause'); - this.$target.removeData('bs.carousel'); - _.each(this.$('.carousel-item'), function (el) { - $(el).css('min-height', ''); - }); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - */ - _computeHeights: function () { - var maxHeight = 0; - var $items = this.$('.carousel-item'); - $items.css('min-height', ''); - _.each($items, function (el) { - var $item = $(el); - var isActive = $item.hasClass('active'); - $item.addClass('active'); - var height = $item.outerHeight(); - if (height > maxHeight) { - maxHeight = height; - } - $item.toggleClass('active', isActive); - }); - $items.css('min-height', maxHeight); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onContentChanged: function (ev) { - this._computeHeights(); - }, -}); - -registry.parallax = Animation.extend({ - selector: '.parallax', - disabledInEditableMode: false, - effects: [{ - startEvents: 'scroll', - update: '_onWindowScroll', - }], - - /** - * @override - */ - start: function () { - this._rebuild(); - $(window).on('resize.animation_parallax', _.debounce(this._rebuild.bind(this), 500)); - return this._super.apply(this, arguments); - }, - /** - * @override - */ - destroy: function () { - this._super.apply(this, arguments); - $(window).off('.animation_parallax'); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Prepares the background element which will scroll at a different speed - * according to the viewport dimensions and other snippet parameters. - * - * @private - */ - _rebuild: function () { - // Add/find bg DOM element to hold the parallax bg (support old v10.0 parallax) - if (!this.$bg || !this.$bg.length) { - this.$bg = this.$('> .s_parallax_bg'); - if (!this.$bg.length) { - this.$bg = $('<span/>', { - class: 's_parallax_bg' + (this.$target.hasClass('oe_custom_bg') ? ' oe_custom_bg' : ''), - }).prependTo(this.$target); - } - } - var urlTarget = this.$target.css('background-image'); - if (urlTarget !== 'none') { - this.$bg.css('background-image', urlTarget); - } - this.$target.css('background-image', 'none'); - - // Get parallax speed - this.speed = parseFloat(this.$target.attr('data-scroll-background-ratio') || 0); - - // Reset offset if parallax effect will not be performed and leave - this.$target.toggleClass('s_parallax_is_fixed', this.speed === 1); - var noParallaxSpeed = (this.speed === 0 || this.speed === 1); - this.$target.toggleClass('s_parallax_no_overflow_hidden', noParallaxSpeed); - if (noParallaxSpeed) { - this.$bg.css({ - transform: '', - top: '', - bottom: '', - }); - return; - } - - // Initialize parallax data according to snippet and viewport dimensions - this.viewport = document.body.clientHeight - $('#wrapwrap').position().top; - this.visibleArea = [this.$target.offset().top]; - this.visibleArea.push(this.visibleArea[0] + this.$target.innerHeight() + this.viewport); - this.ratio = this.speed * (this.viewport / 10); - - // Provide a "safe-area" to limit parallax - this.$bg.css({ - top: -this.ratio, - bottom: -this.ratio, - }); - }, - - //-------------------------------------------------------------------------- - // Effects - //-------------------------------------------------------------------------- - - /** - * Describes how to update the snippet when the window scrolls. - * - * @private - * @param {integer} scrollOffset - */ - _onWindowScroll: function (scrollOffset) { - // Speed == 0 is no effect and speed == 1 is handled by CSS only - if (this.speed === 0 || this.speed === 1) { - return; - } - - // Perform translation if the element is visible only - var vpEndOffset = scrollOffset + this.viewport; - if (vpEndOffset >= this.visibleArea[0] - && vpEndOffset <= this.visibleArea[1]) { - this.$bg.css('transform', 'translateY(' + _getNormalizedPosition.call(this, vpEndOffset) + 'px)'); - } - - function _getNormalizedPosition(pos) { - // Normalize scroll in a 1 to 0 range - var r = (pos - this.visibleArea[1]) / (this.visibleArea[0] - this.visibleArea[1]); - // Normalize accordingly to current options - return Math.round(this.ratio * (2 * r - 1)); - } - }, -}); - -registry.mediaVideo = publicWidget.Widget.extend({ - selector: '.media_iframe_video', - - /** - * @override - */ - start: function () { - // TODO: this code should be refactored to make more sense and be better - // integrated with Odoo (this refactoring should be done in master). - - var def = this._super.apply(this, arguments); - if (this.$target.children('iframe').length) { - // There already is an <iframe/>, do nothing - return def; - } - - // Bug fix / compatibility: empty the <div/> element as all information - // to rebuild the iframe should have been saved on the <div/> element - this.$target.empty(); - - // Add extra content for size / edition - this.$target.append( - '<div class="css_editable_mode_display"> </div>' + - '<div class="media_iframe_video_size"> </div>' - ); - - // Rebuild the iframe. Depending on version / compatibility / instance, - // the src is saved in the 'data-src' attribute or the - // 'data-oe-expression' one (the latter is used as a workaround in 10.0 - // system but should obviously be reviewed in master). - this.$target.append($('<iframe/>', { - src: _.escape(this.$target.data('oe-expression') || this.$target.data('src')), - frameborder: '0', - allowfullscreen: 'allowfullscreen', - sandbox: 'allow-scripts allow-same-origin', // https://www.html5rocks.com/en/tutorials/security/sandboxed-iframes/ - })); - - return def; - }, -}); - -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(); - })); - $('<script/>', { - src: 'https://www.youtube.com/iframe_api', - }).appendTo('head'); - } - } - - var throttledUpdate = _.throttle(() => this._adjustIframe(), 50); - - var $dropdownMenu = this.$el.closest('.dropdown-menu'); - if ($dropdownMenu.length) { - this.$dropdownParent = $dropdownMenu.parent(); - this.$dropdownParent.on('shown.bs.dropdown.backgroundVideo', throttledUpdate); - } - - $(window).on('resize.' + this.iframeID, throttledUpdate); - - return Promise.all(proms).then(() => this._appendBgVideo()); - }, - /** - * @override - */ - destroy: function () { - this._super.apply(this, arguments); - - if (this.$dropdownParent) { - this.$dropdownParent.off('.backgroundVideo'); - } - - $(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 () { - this.$iframe.removeClass('show'); - - // 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); - - void this.$iframe[0].offsetWidth; // Force style addition - this.$iframe.addClass('show'); - }, - /** - * 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: { - 'click .o_ul_toggle_next': '_onToggleNextClick', - 'click .o_ul_toggle_self': '_onToggleSelfClick', - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Called when a "toggle next" ul is clicked. - * - * @private - */ - _onToggleNextClick: function (ev) { - ev.preventDefault(); - var $target = $(ev.currentTarget); - $target.toggleClass('o_open'); - $target.closest('li').next().toggleClass('o_close'); - }, - /** - * Called when a "toggle self" ul is clicked. - * - * @private - */ - _onToggleSelfClick: function (ev) { - ev.preventDefault(); - var $target = $(ev.currentTarget); - $target.toggleClass('o_open'); - $target.closest('li').find('ul,ol').toggleClass('o_close'); - }, -}); - -registry.gallery = publicWidget.Widget.extend({ - selector: '.o_gallery:not(.o_slideshow)', - xmlDependencies: ['/website/static/src/xml/website.gallery.xml'], - events: { - 'click img': '_onClickImg', - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Called when an image is clicked. Opens a dialog to browse all the images - * with a bigger size. - * - * @private - * @param {Event} ev - */ - _onClickImg: function (ev) { - var self = this; - var $cur = $(ev.currentTarget); - - var urls = []; - var idx = undefined; - var milliseconds = undefined; - var params = undefined; - var $images = $cur.closest('.o_gallery').find('img'); - var size = 0.8; - var dimensions = { - min_width: Math.round(window.innerWidth * size * 0.9), - min_height: Math.round(window.innerHeight * size), - max_width: Math.round(window.innerWidth * size * 0.9), - max_height: Math.round(window.innerHeight * size), - width: Math.round(window.innerWidth * size * 0.9), - height: Math.round(window.innerHeight * size) - }; - - $images.each(function () { - urls.push($(this).attr('src')); - }); - var $img = ($cur.is('img') === true) ? $cur : $cur.closest('img'); - idx = urls.indexOf($img.attr('src')); - - milliseconds = $cur.closest('.o_gallery').data('interval') || false; - var $modal = $(qweb.render('website.gallery.slideshow.lightbox', { - srcs: urls, - index: idx, - dim: dimensions, - interval: milliseconds || 0, - id: _.uniqueId('slideshow_'), - })); - $modal.modal({ - keyboard: true, - backdrop: true, - }); - $modal.on('hidden.bs.modal', function () { - $(this).hide(); - $(this).siblings().filter('.modal-backdrop').remove(); // bootstrap leaves a modal-backdrop - $(this).remove(); - }); - $modal.find('.modal-content, .modal-body.o_slideshow').css('height', '100%'); - $modal.appendTo(document.body); - - $modal.one('shown.bs.modal', function () { - self.trigger_up('widgets_start_request', { - editableMode: false, - $target: $modal.find('.modal-body.o_slideshow'), - }); - }); - }, -}); - -registry.gallerySlider = publicWidget.Widget.extend({ - selector: '.o_slideshow', - xmlDependencies: ['/website/static/src/xml/website.gallery.xml'], - disabledInEditableMode: false, - - /** - * @override - */ - start: function () { - var self = this; - this.$carousel = this.$target.is('.carousel') ? this.$target : this.$target.find('.carousel'); - this.$indicator = this.$carousel.find('.carousel-indicators'); - this.$prev = this.$indicator.find('li.o_indicators_left').css('visibility', ''); // force visibility as some databases have it hidden - this.$next = this.$indicator.find('li.o_indicators_right').css('visibility', ''); - var $lis = this.$indicator.find('li[data-slide-to]'); - var nbPerPage = Math.floor(this.$indicator.width() / $lis.first().outerWidth(true)) - 3; // - navigator - 1 to leave some space - var realNbPerPage = nbPerPage || 1; - var nbPages = Math.ceil($lis.length / realNbPerPage); - - var index; - var page; - update(); - - function hide() { - $lis.each(function (i) { - $(this).toggleClass('d-none', i < page * nbPerPage || i >= (page + 1) * nbPerPage); - }); - if (self.editableMode) { // do not remove DOM in edit mode - return; - } - if (page <= 0) { - self.$prev.detach(); - } else { - self.$prev.prependTo(self.$indicator); - } - if (page >= nbPages - 1) { - self.$next.detach(); - } else { - self.$next.appendTo(self.$indicator); - } - } - - function update() { - index = $lis.index($lis.filter('.active')) || 0; - page = Math.floor(index / realNbPerPage); - hide(); - } - - this.$carousel.on('slide.bs.carousel.gallery_slider', function () { - setTimeout(function () { - var $item = self.$carousel.find('.carousel-inner .carousel-item-prev, .carousel-inner .carousel-item-next'); - var index = $item.index(); - $lis.removeClass('active') - .filter('[data-slide-to="' + index + '"]') - .addClass('active'); - }, 0); - }); - this.$indicator.on('click.gallery_slider', '> li:not([data-slide-to])', function () { - page += ($(this).hasClass('o_indicators_left') ? -1 : 1); - page = Math.max(0, Math.min(nbPages - 1, page)); // should not be necessary - self.$carousel.carousel(page * realNbPerPage); - hide(); - }); - this.$carousel.on('slid.bs.carousel.gallery_slider', update); - - return this._super.apply(this, arguments); - }, - /** - * @override - */ - destroy: function () { - this._super.apply(this, arguments); - - if (!this.$indicator) { - return; - } - - this.$prev.prependTo(this.$indicator); - this.$next.appendTo(this.$indicator); - this.$carousel.off('.gallery_slider'); - this.$indicator.off('.gallery_slider'); - }, -}); - -registry.socialShare = publicWidget.Widget.extend({ - selector: '.oe_social_share', - xmlDependencies: ['/website/static/src/xml/website.share.xml'], - events: { - 'mouseenter': '_onMouseEnter', - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - */ - _bindSocialEvent: function () { - this.$('.oe_social_facebook').click($.proxy(this._renderSocial, this, 'facebook')); - this.$('.oe_social_twitter').click($.proxy(this._renderSocial, this, 'twitter')); - this.$('.oe_social_linkedin').click($.proxy(this._renderSocial, this, 'linkedin')); - }, - /** - * @private - */ - _render: function () { - this.$el.popover({ - content: qweb.render('website.social_hover', {medias: this.socialList}), - placement: 'bottom', - container: this.$el, - html: true, - trigger: 'manual', - animation: false, - }).popover("show"); - - this.$el.off('mouseleave.socialShare').on('mouseleave.socialShare', function () { - var self = this; - setTimeout(function () { - if (!$(".popover:hover").length) { - $(self).popover('dispose'); - } - }, 200); - }); - }, - /** - * @private - */ - _renderSocial: function (social) { - var url = this.$el.data('urlshare') || document.URL.split(/[?#]/)[0]; - url = encodeURIComponent(url); - var title = document.title.split(" | ")[0]; // get the page title without the company name - var hashtags = ' #' + document.title.split(" | ")[1].replace(' ', '') + ' ' + this.hashtags; // company name without spaces (for hashtag) - var socialNetworks = { - 'facebook': 'https://www.facebook.com/sharer/sharer.php?u=' + url, - 'twitter': 'https://twitter.com/intent/tweet?original_referer=' + url + '&text=' + encodeURIComponent(title + hashtags + ' - ') + url, - 'linkedin': 'https://www.linkedin.com/shareArticle?mini=true&url=' + url + '&title=' + encodeURIComponent(title), - }; - if (!_.contains(_.keys(socialNetworks), social)) { - return; - } - var wHeight = 500; - var wWidth = 500; - window.open(socialNetworks[social], '', 'menubar=no, toolbar=no, resizable=yes, scrollbar=yes, height=' + wHeight + ',width=' + wWidth); - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * Called when the user hovers the animation element -> open the social - * links popover. - * - * @private - */ - _onMouseEnter: function () { - var social = this.$el.data('social'); - this.socialList = social ? social.split(',') : ['facebook', 'twitter', 'linkedin']; - this.hashtags = this.$el.data('hashtags') || ''; - - this._render(); - this._bindSocialEvent(); - }, -}); - -registry.facebookPage = publicWidget.Widget.extend({ - selector: '.o_facebook_page', - disabledInEditableMode: false, - - /** - * @override - */ - start: function () { - var def = this._super.apply(this, arguments); - - var params = _.pick(this.$el.data(), 'href', 'height', 'tabs', 'small_header', 'hide_cover', 'show_facepile'); - if (!params.href) { - return def; - } - params.width = utils.confine(Math.floor(this.$el.width()), 180, 500); - - var src = $.param.querystring('https://www.facebook.com/plugins/page.php', params); - this.$iframe = $('<iframe/>', { - src: src, - width: params.width, - height: params.height, - css: { - border: 'none', - overflow: 'hidden', - }, - scrolling: 'no', - frameborder: '0', - allowTransparency: 'true', - }); - this.$el.append(this.$iframe); - - return def; - }, - /** - * @override - */ - destroy: function () { - this._super.apply(this, arguments); - - if (this.$iframe) { - this.$iframe.remove(); - } - }, -}); - -registry.anchorSlide = publicWidget.Widget.extend({ - selector: 'a[href^="/"][href*="#"], a[href^="#"]', - events: { - 'click': '_onAnimateClick', - }, - - //-------------------------------------------------------------------------- - // Handlers - //-------------------------------------------------------------------------- - - /** - * @private - */ - _onAnimateClick: function (ev) { - if (this.$target[0].pathname !== window.location.pathname) { - return; - } - var hash = this.$target[0].hash; - if (!utils.isValidAnchor(hash)) { - return; - } - var $anchor = $(hash); - if (!$anchor.length || !$anchor.attr('data-anchor')) { - return; - } - ev.preventDefault(); - $('html, body').animate({ - scrollTop: $anchor.offset().top, - }, 500); - }, -}); - -registry.countdown = publicWidget.Widget.extend({ - selector: '.s_countdown', - xmlDependencies: ['/website/static/src/xml/website.s_countdown.xml'], - disabledInEditableMode: false, - - /** - * @override - */ - start: function () { - this.$wrapper = this.$('.s_countdown_canvas_wrapper'); - this.hereBeforeTimerEnds = false; - this.endAction = this.el.dataset.endAction; - this.endTime = parseInt(this.el.dataset.endTime); - this.size = parseInt(this.el.dataset.size); - this.display = this.el.dataset.display; - - this.layout = this.el.dataset.layout; - this.layoutBackground = this.el.dataset.layoutBackground; - this.progressBarStyle = this.el.dataset.progressBarStyle; - this.progressBarWeight = this.el.dataset.progressBarWeight; - - this.textColor = this._ensureCssColor(this.el.dataset.textColor); - this.layoutBackgroundColor = this._ensureCssColor(this.el.dataset.layoutBackgroundColor); - this.progressBarColor = this._ensureCssColor(this.el.dataset.progressBarColor); - - this.onlyOneUnit = this.display === 'd'; - this.width = parseInt(this.size); - if (this.layout === 'boxes') { - this.width /= 1.75; - } - this._initTimeDiff(); - - this._render(); - - this.setInterval = setInterval(this._render.bind(this), 1000); - return this._super(...arguments); - }, - /** - * @override - */ - destroy: function () { - this.$('.s_countdown_end_redirect_message').remove(); - this.$('canvas').remove(); - this.$('.s_countdown_end_message').addClass('d-none'); - this.$('.s_countdown_text_wrapper').remove(); - this.$('.s_countdown_canvas_wrapper').removeClass('d-none'); - - clearInterval(this.setInterval); - this._super(...arguments); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * Ensures the color is an actual css color. In case of a color variable, - * the color will be mapped to hexa. - * - * @private - * @param {string} color - * @returns {string} - */ - _ensureCssColor: function (color) { - if (ColorpickerDialog.isCSSColor(color)) { - return color; - } - const style = window.getComputedStyle(document.documentElement); - return style.getPropertyValue('--' + color).trim(); - }, - /** - * Gets the time difference in seconds between now and countdown due date. - * - * @private - */ - _getDelta: function () { - const currentTimestamp = Date.now() / 1000; - return this.endTime - currentTimestamp; - }, - /** - * Handles the action that should be executed once the countdown ends. - * - * @private - */ - _handleEndCountdownAction: function () { - if (this.endAction === 'redirect') { - const redirectUrl = this.el.dataset.redirectUrl; - if (this.hereBeforeTimerEnds) { - // Wait a bit, if the landing page has the same publish date - setTimeout(() => window.location = redirectUrl, 500); - } else { - // Show (non editable) msg when user lands on already finished countdown - if (!this.$('.s_countdown_end_redirect_message').length) { - this.$target.find('.container').append( - $(qweb.render('website.s_countdown.end_redirect_message', { - redirectUrl: redirectUrl, - })) - ); - } - } - } else if (this.endAction === 'message') { - this.$('.s_countdown_end_message').removeClass('d-none'); - } - }, - /** - * Initializes the `diff` object. It will contains every visible time unit - * which will each contain its related canvas, total step, label.. - * - * @private - */ - _initTimeDiff: function () { - const delta = this._getDelta(); - this.diff = []; - if (this._isUnitVisible('d') && !(this.onlyOneUnit && delta < 86400)) { - this.diff.push({ - canvas: $('<canvas/>').appendTo(this.$wrapper)[0], - // There is no logical number of unit (total) on which day units - // can be compared against, so we use an arbitrary number. - total: 15, - label: _t("Days"), - nbSeconds: 86400, - }); - } - if (this._isUnitVisible('h') || (this.onlyOneUnit && delta < 86400 && delta > 3600)) { - this.diff.push({ - canvas: $('<canvas/>').appendTo(this.$wrapper)[0], - total: 24, - label: _t("Hours"), - nbSeconds: 3600, - }); - } - if (this._isUnitVisible('m') || (this.onlyOneUnit && delta < 3600 && delta > 60)) { - this.diff.push({ - canvas: $('<canvas/>').appendTo(this.$wrapper)[0], - total: 60, - label: _t("Minutes"), - nbSeconds: 60, - }); - } - if (this._isUnitVisible('s') || (this.onlyOneUnit && delta < 60)) { - this.diff.push({ - canvas: $('<canvas/>').appendTo(this.$wrapper)[0], - total: 60, - label: _t("Seconds"), - nbSeconds: 1, - }); - } - }, - /** - * Returns weither or not the countdown should be displayed for the given - * unit (days, sec..). - * - * @private - * @param {string} unit - either 'd', 'm', 'h', or 's' - * @returns {boolean} - */ - _isUnitVisible: function (unit) { - return this.display.includes(unit); - }, - /** - * Draws the whole countdown, including one countdown for each time unit. - * - * @private - */ - _render: function () { - // If only one unit mode, restart widget on unit change to populate diff - if (this.onlyOneUnit && this._getDelta() < this.diff[0].nbSeconds) { - this.$('canvas').remove(); - this._initTimeDiff(); - } - this._updateTimeDiff(); - - const hideCountdown = this.isFinished && !this.editableMode && this.$el.hasClass('hide-countdown'); - if (this.layout === 'text') { - this.$('canvas').addClass('d-none'); - if (!this.$textWrapper) { - this.$textWrapper = $('<span/>').attr({ - class: 's_countdown_text_wrapper d-none', - }); - this.$textWrapper.text(_t("Countdown ends in")); - this.$textWrapper.append($('<span/>').attr({ - class: 's_countdown_text ml-1', - })); - this.$textWrapper.appendTo(this.$wrapper); - } - - this.$textWrapper.toggleClass('d-none', hideCountdown); - - const countdownText = this.diff.map(e => e.nb + ' ' + e.label).join(', '); - this.$('.s_countdown_text').text(countdownText.toLowerCase()); - } else { - for (const val of this.diff) { - const canvas = val.canvas; - const ctx = canvas.getContext("2d"); - ctx.canvas.width = this.width; - ctx.canvas.height = this.size; - this._clearCanvas(ctx); - - $(canvas).toggleClass('d-none', hideCountdown); - if (hideCountdown) { - return; - } - - // Draw canvas elements - if (this.layoutBackground !== 'none') { - this._drawBgShape(ctx, this.layoutBackground === 'plain'); - } - this._drawText(canvas, val.nb, val.label, this.layoutBackground === 'plain'); - if (this.progressBarStyle === 'surrounded') { - this._drawProgressBarBg(ctx, this.progressBarWeight === 'thin'); - } - if (this.progressBarStyle !== 'none') { - this._drawProgressBar(ctx, val.nb, val.total, this.progressBarWeight === 'thin'); - } - $(canvas).toggleClass('mx-2', this.layout === 'boxes'); - } - } - - if (this.isFinished) { - clearInterval(this.setInterval); - if (!this.editableMode) { - this._handleEndCountdownAction(); - } - } - }, - /** - * Updates the remaining units into the `diff` object. - * - * @private - */ - _updateTimeDiff: function () { - let delta = this._getDelta(); - this.isFinished = delta < 0; - if (this.isFinished) { - for (const unitData of this.diff) { - unitData.nb = 0; - } - return; - } - - this.hereBeforeTimerEnds = true; - for (const unitData of this.diff) { - unitData.nb = Math.floor(delta / unitData.nbSeconds); - delta -= unitData.nb * unitData.nbSeconds; - } - }, - - //-------------------------------------------------------------------------- - // Canvas drawing methods - //-------------------------------------------------------------------------- - - /** - * Erases the canvas. - * - * @private - * @param {RenderingContext} ctx - Context of the canvas - */ - _clearCanvas: function (ctx) { - ctx.clearRect(0, 0, this.size, this.size); - }, - /** - * Draws a text into the canvas. - * - * @private - * @param {HTMLCanvasElement} canvas - * @param {string} textNb - text to display in the center of the canvas, in big - * @param {string} textUnit - text to display bellow `textNb` in small - * @param {boolean} full - if true, the shape will be drawn up to the progressbar - */ - _drawText: function (canvas, textNb, textUnit, full = false) { - const ctx = canvas.getContext("2d"); - const nbSize = this.size / 4; - ctx.font = `${nbSize}px Arial`; - ctx.fillStyle = this.textColor; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(textNb, canvas.width / 2, canvas.height / 2); - - const unitSize = this.size / 12; - ctx.font = `${unitSize}px Arial`; - ctx.fillText(textUnit, canvas.width / 2, canvas.height / 2 + nbSize / 1.5, this.width); - - if (this.layout === 'boxes' && this.layoutBackground !== 'none' && this.progressBarStyle === 'none') { - let barWidth = this.size / (this.progressBarWeight === 'thin' ? 31 : 10); - if (full) { - barWidth = 0; - } - ctx.beginPath(); - ctx.moveTo(barWidth, this.size / 2); - ctx.lineTo(this.width - barWidth, this.size / 2); - ctx.stroke(); - } - }, - /** - * Draws a plain shape into the canvas. - * - * @private - * @param {RenderingContext} ctx - Context of the canvas - * @param {boolean} full - if true, the shape will be drawn up to the progressbar - */ - _drawBgShape: function (ctx, full = false) { - ctx.fillStyle = this.layoutBackgroundColor; - ctx.beginPath(); - if (this.layout === 'circle') { - let rayon = this.size / 2; - if (this.progressBarWeight === 'thin') { - rayon -= full ? this.size / 29 : this.size / 15; - } else { - rayon -= full ? 0 : this.size / 10; - } - ctx.arc(this.size / 2, this.size / 2, rayon, 0, Math.PI * 2); - ctx.fill(); - } else if (this.layout === 'boxes') { - let barWidth = this.size / (this.progressBarWeight === 'thin' ? 31 : 10); - if (full) { - barWidth = 0; - } - - ctx.fillStyle = this.layoutBackgroundColor; - ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2); - ctx.fill(); - - const gradient = ctx.createLinearGradient(0, this.width, 0, 0); - gradient.addColorStop(0, '#ffffff24'); - gradient.addColorStop(1, this.layoutBackgroundColor); - ctx.fillStyle = gradient; - ctx.rect(barWidth, barWidth, this.width - barWidth * 2, this.size - barWidth * 2); - ctx.fill(); - $(ctx.canvas).css({'border-radius': '8px'}); - } - }, - /** - * Draws a progress bar around the countdown shape. - * - * @private - * @param {RenderingContext} ctx - Context of the canvas - * @param {string} nbUnit - how many unit should fill progress bar - * @param {string} totalUnit - number of unit to do a complete progress bar - * @param {boolean} thinLine - if true, the progress bar will be thiner - */ - _drawProgressBar: function (ctx, nbUnit, totalUnit, thinLine) { - ctx.strokeStyle = this.progressBarColor; - ctx.lineWidth = thinLine ? this.size / 35 : this.size / 10; - if (this.layout === 'circle') { - ctx.beginPath(); - ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.size / 20, Math.PI / -2, (Math.PI * 2) * (nbUnit / totalUnit) + (Math.PI / -2)); - ctx.stroke(); - } else if (this.layout === 'boxes') { - ctx.lineWidth *= 2; - let pc = nbUnit / totalUnit * 100; - - // Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2) - const linesCoordFuncs = [ - (linePc) => [0 + ctx.lineWidth / 2, 0, (this.width - ctx.lineWidth / 2) * linePc / 25 + ctx.lineWidth / 2, 0], - (linePc) => [this.width, 0 + ctx.lineWidth / 2, this.width, (this.size - ctx.lineWidth / 2) * linePc / 25 + ctx.lineWidth / 2], - (linePc) => [this.width - ((this.width - ctx.lineWidth / 2) * linePc / 25) - ctx.lineWidth / 2, this.size, this.width - ctx.lineWidth / 2, this.size], - (linePc) => [0, this.size - ((this.size - ctx.lineWidth / 2) * linePc / 25) - ctx.lineWidth / 2, 0, this.size - ctx.lineWidth / 2], - ]; - while (pc > 0 && linesCoordFuncs.length) { - const linePc = Math.min(pc, 25); - const lineCoord = (linesCoordFuncs.shift())(linePc); - ctx.beginPath(); - ctx.moveTo(lineCoord[0], lineCoord[1]); - ctx.lineTo(lineCoord[2], lineCoord[3]); - ctx.stroke(); - pc -= linePc; - } - } - }, - /** - * Draws a full lighter background progressbar around the shape. - * - * @private - * @param {RenderingContext} ctx - Context of the canvas - * @param {boolean} thinLine - if true, the progress bar will be thiner - */ - _drawProgressBarBg: function (ctx, thinLine) { - ctx.strokeStyle = this.progressBarColor; - ctx.globalAlpha = 0.2; - ctx.lineWidth = thinLine ? this.size / 35 : this.size / 10; - if (this.layout === 'circle') { - ctx.beginPath(); - ctx.arc(this.size / 2, this.size / 2, this.size / 2 - this.size / 20, 0, Math.PI * 2); - ctx.stroke(); - } else if (this.layout === 'boxes') { - ctx.lineWidth *= 2; - - // Lines: Top(x1,y1,x2,y2) Right(x1,y1,x2,y2) Bottom(x1,y1,x2,y2) Left(x1,y1,x2,y2) - const points = [ - [0 + ctx.lineWidth / 2, 0, this.width, 0], - [this.width, 0 + ctx.lineWidth / 2, this.width, this.size], - [0, this.size, this.width - ctx.lineWidth / 2, this.size], - [0, 0, 0, this.size - ctx.lineWidth / 2], - ]; - while (points.length) { - const point = points.shift(); - ctx.beginPath(); - ctx.moveTo(point[0], point[1]); - ctx.lineTo(point[2], point[3]); - ctx.stroke(); - } - } - ctx.globalAlpha = 1; - }, -}); - -registry.chart = publicWidget.Widget.extend({ - selector: '.s_chart', - disabledInEditableMode: false, - jsLibs: [ - '/web/static/lib/Chart/Chart.js', - ], - - /** - * @override - * @param {Object} parent - * @param {Object} options The default value of the chartbar. - */ - init: function (parent, options) { - this._super.apply(this, arguments); - this.style = window.getComputedStyle(document.documentElement); - }, - /** - * @override - */ - start: function () { - // Convert Theme colors to css color - const data = JSON.parse(this.el.dataset.data); - data.datasets.forEach(el => { - if (Array.isArray(el.backgroundColor)) { - el.backgroundColor = el.backgroundColor.map(el => this._convertToCssColor(el)); - el.borderColor = el.borderColor.map(el => this._convertToCssColor(el)); - } else { - el.backgroundColor = this._convertToCssColor(el.backgroundColor); - el.borderColor = this._convertToCssColor(el.borderColor); - } - el.borderWidth = this.el.dataset.borderWidth; - }); - - // Make chart data - const chartData = { - type: this.el.dataset.type, - data: data, - options: { - legend: { - display: this.el.dataset.legendPosition !== 'none', - position: this.el.dataset.legendPosition, - }, - tooltips: { - enabled: this.el.dataset.tooltipDisplay === 'true', - }, - title: { - display: !!this.el.dataset.title, - text: this.el.dataset.title, - }, - }, - }; - - // Add type specific options - if (this.el.dataset.type === 'radar') { - chartData.options.scale = { - ticks: { - beginAtZero: true, - } - }; - } else if (['pie', 'doughnut'].includes(this.el.dataset.type)) { - chartData.options.tooltips.callbacks = { - label: (tooltipItem, data) => { - const label = data.datasets[tooltipItem.datasetIndex].label; - const secondLabel = data.labels[tooltipItem.index]; - let final = label; - if (label) { - if (secondLabel) { - final = label + ' - ' + secondLabel; - } - } else if (secondLabel) { - final = secondLabel; - } - return final + ':' + data.datasets[tooltipItem.datasetIndex].data[tooltipItem.index]; - }, - }; - } else { - chartData.options.scales = { - xAxes: [{ - stacked: this.el.dataset.stacked === 'true', - ticks: { - beginAtZero: true - }, - }], - yAxes: [{ - stacked: this.el.dataset.stacked === 'true', - ticks: { - beginAtZero: true - }, - }], - }; - } - - // Disable animation in edit mode - if (this.editableMode) { - chartData.options.animation = { - duration: 0, - }; - } - - const canvas = this.el.querySelector('canvas'); - this.chart = new window.Chart(canvas, chartData); - return this._super.apply(this, arguments); - }, - /** - * @override - * Discard all library changes to reset the state of the Html. - */ - destroy: function () { - if (this.chart) { // The widget can be destroyed before start has completed - this.chart.destroy(); - this.el.querySelectorAll('.chartjs-size-monitor').forEach(el => el.remove()); - } - this._super.apply(this, arguments); - }, - - //-------------------------------------------------------------------------- - // Private - //-------------------------------------------------------------------------- - - /** - * @private - * @param {string} color A css color or theme color string - * @returns {string} Css color - */ - _convertToCssColor: function (color) { - if (!color) { - return 'transparent'; - } - return this.style.getPropertyValue(`--${color}`).trim() || color; - }, -}); - -return { - Widget: publicWidget.Widget, - Animation: Animation, - registry: registry, - - Class: Animation, // Deprecated -}; -}); |
