diff options
| author | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
|---|---|---|
| committer | stephanchrst <stephanchrst@gmail.com> | 2022-05-10 21:51:50 +0700 |
| commit | 3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch) | |
| tree | a44932296ef4a9b71d5f010906253d8c53727726 /addons/web_tour/static/src/js/tour_service.js | |
| parent | 0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (diff) | |
initial commit 2
Diffstat (limited to 'addons/web_tour/static/src/js/tour_service.js')
| -rw-r--r-- | addons/web_tour/static/src/js/tour_service.js | 189 |
1 files changed, 189 insertions, 0 deletions
diff --git a/addons/web_tour/static/src/js/tour_service.js b/addons/web_tour/static/src/js/tour_service.js new file mode 100644 index 00000000..825b275c --- /dev/null +++ b/addons/web_tour/static/src/js/tour_service.js @@ -0,0 +1,189 @@ +odoo.define('web_tour.tour', function (require) { +"use strict"; + +var rootWidget = require('root.widget'); +var rpc = require('web.rpc'); +var session = require('web.session'); +var TourManager = require('web_tour.TourManager'); + +const untrackedClassnames = ["o_tooltip", "o_tooltip_content", "o_tooltip_overlay"]; + +/** + * @namespace + * @property {Object} active_tooltips + * @property {Object} tours + * @property {Array} consumed_tours + * @property {String} running_tour + * @property {Number} running_step_delay + * @property {'community' | 'enterprise'} edition + * @property {Array} _log + */ +return session.is_bound.then(function () { + var defs = []; + // Load the list of consumed tours and the tip template only if we are admin, in the frontend, + // tours being only available for the admin. For the backend, the list of consumed is directly + // in the page source. + if (session.is_frontend && session.is_admin) { + var def = rpc.query({ + model: 'web_tour.tour', + method: 'get_consumed_tours', + }); + defs.push(def); + } + return Promise.all(defs).then(function (results) { + var consumed_tours = session.is_frontend ? results[0] : session.web_tours; + var tour_manager = new TourManager(rootWidget, consumed_tours); + + function _isTrackedNode(node) { + if (node.classList) { + return !untrackedClassnames + .some(className => node.classList.contains(className)); + } + return true; + } + + const classSplitRegex = /\s+/g; + const tooltipParentRegex = /\bo_tooltip_parent\b/; + let currentMutations = []; + function _processMutations() { + const hasTrackedMutation = currentMutations.some(mutation => { + // First check if the mutation applied on an element we do not + // track (like the tour tips themself). + if (!_isTrackedNode(mutation.target)) { + return false; + } + + if (mutation.type === 'characterData') { + return true; + } + + if (mutation.type === 'childList') { + // If it is a modification to the DOM hierarchy, only + // consider the addition/removal of tracked nodes. + for (const nodes of [mutation.addedNodes, mutation.removedNodes]) { + for (const node of nodes) { + if (_isTrackedNode(node)) { + return true; + } + } + } + return false; + } else if (mutation.type === 'attributes') { + // Get old and new value of the attribute. Note: as we + // compute the new value after a setTimeout, this might not + // actually be the new value for that particular mutation + // record but this is the one after all mutations. This is + // normally not an issue: e.g. "a" -> "a b" -> "a" will be + // seen as "a" -> "a" (not "a b") + "a b" -> "a" but we + // only need to detect *one* tracked mutation to know we + // have to update tips anyway. + const oldV = mutation.oldValue ? mutation.oldValue.trim() : ''; + const newV = (mutation.target.getAttribute(mutation.attributeName) || '').trim(); + + // Not sure why but this occurs, especially on ID change + // (probably some strange jQuery behavior, see below). + // Also sometimes, a class is just considered changed while + // it just loses the spaces around the class names. + if (oldV === newV) { + return false; + } + + if (mutation.attributeName === 'id') { + // Check if this is not an ID change done by jQuery for + // performance reasons. + return !(oldV.includes('sizzle') || newV.includes('sizzle')); + } else if (mutation.attributeName === 'class') { + // Check if the change is *only* about receiving or + // losing the 'o_tooltip_parent' class, which is linked + // to the tour service system. We have to check the + // potential addition of another class as we compute + // the new value after a setTimeout. So this case: + // 'a' -> 'a b' -> 'a b o_tooltip_parent' produces 2 + // mutation records but will be seen here as + // 1) 'a' -> 'a b o_tooltip_parent' + // 2) 'a b' -> 'a b o_tooltip_parent' + const hadClass = tooltipParentRegex.test(oldV); + const newClasses = mutation.target.classList; + const hasClass = newClasses.contains('o_tooltip_parent'); + return !(hadClass !== hasClass + && Math.abs(oldV.split(classSplitRegex).length - newClasses.length) === 1); + } + } + + return true; + }); + + // Either all the mutations have been ignored or one was detected as + // tracked and will trigger a tour manager update. + currentMutations = []; + + // Update the tour manager if required. + if (hasTrackedMutation) { + tour_manager.update(); + } + } + + // Use a MutationObserver to detect DOM changes. When a mutation occurs, + // only add it to the list of mutations to process and delay the + // mutation processing. We have to record them all and not in a + // debounced way otherwise we may ignore tracked ones in a serie of + // 10 tracked mutations followed by an untracked one. Most of them + // will trigger a tip check anyway so, most of the time, processing the + // first ones will be enough to ensure that a tip update has to be done. + let mutationTimer; + const observer = new MutationObserver(mutations => { + clearTimeout(mutationTimer); + currentMutations = currentMutations.concat(mutations); + mutationTimer = setTimeout(() => _processMutations(), 750); + }); + + // Now that the observer is configured, we have to start it when needed. + var start_service = (function () { + return function (observe) { + return new Promise(function (resolve, reject) { + tour_manager._register_all(observe).then(function () { + if (observe) { + observer.observe(document.body, { + attributes: true, + childList: true, + subtree: true, + attributeOldValue: true, + characterData: true, + }); + } + resolve(); + }); + }); + }; + })(); + + // Enable the MutationObserver for the admin or if a tour is running, when the DOM is ready + start_service(session.is_admin || tour_manager.running_tour); + + // Override the TourManager so that it enables/disables the observer when necessary + if (!session.is_admin) { + var run = tour_manager.run; + tour_manager.run = function () { + var self = this; + var args = arguments; + + start_service(true).then(function () { + run.apply(self, args); + if (!self.running_tour) { + observer.disconnect(); + } + }); + }; + var _consume_tour = tour_manager._consume_tour; + tour_manager._consume_tour = function () { + _consume_tour.apply(this, arguments); + observer.disconnect(); + }; + } + // helper to start a tour manually (or from a python test with its counterpart start_tour function) + odoo.startTour = tour_manager.run.bind(tour_manager); + return tour_manager; + }); +}); + +}); |
