summaryrefslogtreecommitdiff
path: root/addons/web_tour/static/src/js/tour_service.js
diff options
context:
space:
mode:
authorstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
committerstephanchrst <stephanchrst@gmail.com>2022-05-10 21:51:50 +0700
commit3751379f1e9a4c215fb6eb898b4ccc67659b9ace (patch)
treea44932296ef4a9b71d5f010906253d8c53727726 /addons/web_tour/static/src/js/tour_service.js
parent0a15094050bfde69a06d6eff798e9a8ddf2b8c21 (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.js189
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;
+ });
+});
+
+});