{"version":3,"file":"usertours.min.js","sources":["../src/usertours.js"],"sourcesContent":["/**\n * User tour control library.\n *\n * @module     tool_usertours/usertours\n * @copyright  2016 Andrew Nicols <andrew@nicols.co.uk>\n */\nimport BootstrapTour from './tour';\nimport Templates from 'core/templates';\nimport log from 'core/log';\nimport notification from 'core/notification';\nimport * as tourRepository from './repository';\nimport Pending from 'core/pending';\nimport {eventTypes} from './events';\n\nlet currentTour = null;\nlet tourId = null;\n\n/**\n * Find the first matching tour.\n *\n * @param {object[]} tourDetails\n * @param {object[]} filters\n * @returns {null|object}\n */\nconst findMatchingTour = (tourDetails, filters) => {\n    return tourDetails.find(tour => filters.some(filter => {\n        if (filter && filter.filterMatches) {\n            return filter.filterMatches(tour);\n        }\n\n        return true;\n    }));\n};\n\n/**\n * Initialise the user tour for the current page.\n *\n * @method  init\n * @param   {Array}    tourDetails      The matching tours for this page.\n * @param   {Array}    filters          The names of all client side filters.\n */\nexport const init = async(tourDetails, filters) => {\n    const requirements = [];\n    filters.forEach(filter => {\n        requirements.push(import(`tool_usertours/filter_${filter}`));\n    });\n\n    const filterPlugins = await Promise.all(requirements);\n\n    const matchingTour = findMatchingTour(tourDetails, filterPlugins);\n    if (!matchingTour) {\n        return;\n    }\n\n    // Only one tour per page is allowed.\n    tourId = matchingTour.tourId;\n\n    let startTour = matchingTour.startTour;\n    if (typeof startTour === 'undefined') {\n        startTour = true;\n    }\n\n    if (startTour) {\n        // Fetch the tour configuration.\n        fetchTour(tourId);\n    }\n\n    addResetLink();\n\n    // Watch for the reset link.\n    document.querySelector('body').addEventListener('click', e => {\n        const resetLink = e.target.closest('#resetpagetour');\n        if (resetLink) {\n            e.preventDefault();\n            resetTourState(tourId);\n        }\n    });\n};\n\n/**\n * Fetch the configuration specified tour, and start the tour when it has been fetched.\n *\n * @method  fetchTour\n * @param   {Number}    tourId      The ID of the tour to start.\n */\nconst fetchTour = async tourId => {\n    const pendingPromise = new Pending(`admin_usertour_fetchTour:${tourId}`);\n\n    try {\n        // If we don't have any tour config (because it doesn't need showing for the current user), return early.\n        const response = await tourRepository.fetchTour(tourId);\n        if (response.hasOwnProperty('tourconfig')) {\n            const {html} = await Templates.renderForPromise('tool_usertours/tourstep', response.tourconfig);\n            startBootstrapTour(tourId, html, response.tourconfig);\n        }\n        pendingPromise.resolve();\n    } catch (error) {\n        pendingPromise.resolve();\n        notification.exception(error);\n    }\n};\n\nconst getPreferredResetLocation = () => {\n    let location = document.querySelector('.tool_usertours-resettourcontainer');\n    if (location) {\n        return location;\n    }\n\n    location = document.querySelector('.logininfo');\n    if (location) {\n        return location;\n    }\n\n    location = document.querySelector('footer');\n    if (location) {\n        return location;\n    }\n\n    return document.body;\n};\n\n/**\n * Add a reset link to the page.\n *\n * @method  addResetLink\n */\nconst addResetLink = () => {\n    const pendingPromise = new Pending('admin_usertour_addResetLink');\n\n    Templates.render('tool_usertours/resettour', {})\n    .then(function(html, js) {\n        // Append the link to the most suitable place on the page with fallback to legacy selectors and finally the body if\n        // there is no better place.\n        Templates.appendNodeContents(getPreferredResetLocation(), html, js);\n\n        return;\n    })\n    .catch()\n    .then(pendingPromise.resolve)\n    .catch();\n};\n\n/**\n * Start the specified tour.\n *\n * @method  startBootstrapTour\n * @param   {Number}    tourId      The ID of the tour to start.\n * @param   {String}    template    The template to use.\n * @param   {Object}    tourConfig  The tour configuration.\n * @return  {Object}\n */\nconst startBootstrapTour = (tourId, template, tourConfig) => {\n    if (currentTour && currentTour.tourRunning) {\n        // End the current tour.\n        currentTour.endTour();\n        currentTour = null;\n    }\n\n    document.addEventListener(eventTypes.tourEnded, markTourComplete);\n    document.addEventListener(eventTypes.stepRenderer, markStepShown);\n\n    // Sort out the tour name.\n    tourConfig.tourName = tourConfig.name;\n    delete tourConfig.name;\n\n    // Add the template to the configuration.\n    // This enables translations of the buttons.\n    tourConfig.template = template;\n\n    tourConfig.steps = tourConfig.steps.map(function(step) {\n        if (typeof step.element !== 'undefined') {\n            step.target = step.element;\n            delete step.element;\n        }\n\n        if (typeof step.reflex !== 'undefined') {\n            step.moveOnClick = !!step.reflex;\n            delete step.reflex;\n        }\n\n        if (typeof step.content !== 'undefined') {\n            step.body = step.content;\n            delete step.content;\n        }\n\n        return step;\n    });\n\n    currentTour = new BootstrapTour(tourConfig);\n    return currentTour.startTour();\n};\n\n/**\n * Mark the specified step as being shownd by the user.\n *\n * @method  markStepShown\n * @param   {Event} e\n */\nconst markStepShown = e => {\n    const tour = e.detail.tour;\n    const stepConfig = tour.getStepConfig(tour.getCurrentStepNumber());\n    tourRepository.markStepShown(\n        stepConfig.stepid,\n        tourId,\n        tour.getCurrentStepNumber()\n    ).catch(log.error);\n};\n\n/**\n * Mark the specified tour as being completed by the user.\n *\n * @method  markTourComplete\n * @param   {Event} e\n * @listens tool_usertours/stepRendered\n */\nconst markTourComplete = e => {\n    document.removeEventListener(eventTypes.tourEnded, markTourComplete);\n    document.removeEventListener(eventTypes.stepRenderer, markStepShown);\n\n    const tour = e.detail.tour;\n    const stepConfig = tour.getStepConfig(tour.getCurrentStepNumber());\n    tourRepository.markTourComplete(\n        stepConfig.stepid,\n        tourId,\n        tour.getCurrentStepNumber()\n    ).catch(log.error);\n};\n\n/**\n * Reset the state, and restart the the tour on the current page.\n *\n * @method  resetTourState\n * @param   {Number}    tourId      The ID of the tour to start.\n * @returns {Promise}\n */\nexport const resetTourState = tourId => tourRepository.resetTourState(tourId)\n.then(response => {\n    if (response.startTour) {\n        fetchTour(response.startTour);\n    }\n    return;\n}).catch(notification.exception);\n"],"names":["currentTour","tourId","async","tourDetails","filters","requirements","forEach","filter","push","matchingTour","find","tour","some","filterMatches","findMatchingTour","Promise","all","startTour","fetchTour","addResetLink","document","querySelector","addEventListener","e","target","closest","preventDefault","resetTourState","pendingPromise","Pending","response","tourRepository","hasOwnProperty","html","Templates","renderForPromise","tourconfig","startBootstrapTour","resolve","error","exception","render","then","js","appendNodeContents","location","body","getPreferredResetLocation","catch","template","tourConfig","tourRunning","endTour","eventTypes","tourEnded","markTourComplete","stepRenderer","markStepShown","tourName","name","steps","map","step","element","reflex","moveOnClick","content","BootstrapTour","detail","stepConfig","getStepConfig","getCurrentStepNumber","stepid","log","removeEventListener","notification"],"mappings":"ssDAcIA,YAAc,KACdC,OAAS,mBA0BOC,MAAMC,YAAaC,iBAC7BC,aAAe,GACrBD,QAAQE,SAAQC,SACZF,aAAaG,qPAAqCD,mUAAAA,mGAAAA,oBAKhDE,aAzBe,EAACN,YAAaC,UAC5BD,YAAYO,MAAKC,MAAQP,QAAQQ,MAAKL,SACrCA,SAAUA,OAAOM,eACVN,OAAOM,cAAcF,UAsBfG,CAAiBX,kBAFVY,QAAQC,IAAIX,mBAGnCI,oBAKLR,OAASQ,aAAaR,WAElBgB,UAAYR,aAAaQ,eACJ,IAAdA,YACPA,WAAY,GAGZA,WAEAC,UAAUjB,QAGdkB,eAGAC,SAASC,cAAc,QAAQC,iBAAiB,SAASC,IACnCA,EAAEC,OAAOC,QAAQ,oBAE/BF,EAAEG,iBACFC,eAAe1B,mBAWrBiB,UAAYhB,MAAAA,eACR0B,eAAiB,IAAIC,oDAAoC5B,mBAIrD6B,eAAiBC,eAAeb,UAAUjB,WAC5C6B,SAASE,eAAe,cAAe,OACjCC,KAACA,YAAcC,mBAAUC,iBAAiB,0BAA2BL,SAASM,YACpFC,mBAAmBpC,OAAQgC,KAAMH,SAASM,YAE9CR,eAAeU,UACjB,MAAOC,OACLX,eAAeU,gCACFE,UAAUD,SA4BzBpB,aAAe,WACXS,eAAiB,IAAIC,iBAAQ,kDAEzBY,OAAO,2BAA4B,IAC5CC,MAAK,SAAST,KAAMU,uBAGPC,mBA/BgB,UAC1BC,SAAWzB,SAASC,cAAc,6CAClCwB,WAIJA,SAAWzB,SAASC,cAAc,cAC9BwB,WAIJA,SAAWzB,SAASC,cAAc,UAC9BwB,UAIGzB,SAAS0B,QAeiBC,GAA6Bd,KAAMU,OAInEK,QACAN,KAAKd,eAAeU,SACpBU,SAYCX,mBAAqB,CAACpC,OAAQgD,SAAUC,cACtClD,aAAeA,YAAYmD,cAE3BnD,YAAYoD,UACZpD,YAAc,MAGlBoB,SAASE,iBAAiB+B,mBAAWC,UAAWC,kBAChDnC,SAASE,iBAAiB+B,mBAAWG,aAAcC,eAGnDP,WAAWQ,SAAWR,WAAWS,YAC1BT,WAAWS,KAIlBT,WAAWD,SAAWA,SAEtBC,WAAWU,MAAQV,WAAWU,MAAMC,KAAI,SAASC,kBACjB,IAAjBA,KAAKC,UACZD,KAAKtC,OAASsC,KAAKC,eACZD,KAAKC,cAGW,IAAhBD,KAAKE,SACZF,KAAKG,cAAgBH,KAAKE,cACnBF,KAAKE,aAGY,IAAjBF,KAAKI,UACZJ,KAAKhB,KAAOgB,KAAKI,eACVJ,KAAKI,SAGTJ,QAGX9D,YAAc,IAAImE,cAAcjB,YACzBlD,YAAYiB,aASjBwC,cAAgBlC,UACZZ,KAAOY,EAAE6C,OAAOzD,KAChB0D,WAAa1D,KAAK2D,cAAc3D,KAAK4D,wBAC3CxC,eAAe0B,cACXY,WAAWG,OACXvE,OACAU,KAAK4D,wBACPvB,MAAMyB,aAAIlC,QAUVgB,iBAAmBhC,IACrBH,SAASsD,oBAAoBrB,mBAAWC,UAAWC,kBACnDnC,SAASsD,oBAAoBrB,mBAAWG,aAAcC,qBAEhD9C,KAAOY,EAAE6C,OAAOzD,KAChB0D,WAAa1D,KAAK2D,cAAc3D,KAAK4D,wBAC3CxC,eAAewB,iBACXc,WAAWG,OACXvE,OACAU,KAAK4D,wBACPvB,MAAMyB,aAAIlC,QAUHZ,eAAiB1B,QAAU8B,eAAeJ,eAAe1B,QACrEyC,MAAKZ,WACEA,SAASb,WACTC,UAAUY,SAASb,cAGxB+B,MAAM2B,sBAAanC"}