601 lines
15 KiB
JavaScript
601 lines
15 KiB
JavaScript
/* eslint-disable no-underscore-dangle,no-useless-escape */
|
|
/**
|
|
* OnBoarding main class.
|
|
*/
|
|
class OnBoarding {
|
|
/**
|
|
* Constructor.
|
|
*
|
|
* @param {int} currentStep Current step ID
|
|
* @param {object} steps All steps configuration
|
|
* @param {boolean} isShutDown Did the OnBoarding is shut down ?
|
|
* @param {string} apiLocation OnBoarding API location
|
|
* @param {string} baseAdminDir Base PrestaShop admin directory
|
|
*/
|
|
constructor(currentStep, steps, isShutDown, apiLocation, baseAdminDir) {
|
|
this.currentStep = currentStep;
|
|
this.steps = steps;
|
|
this.isShutDown = isShutDown;
|
|
this.apiLocation = apiLocation;
|
|
this.baseAdminDir = baseAdminDir;
|
|
|
|
this.templates = [];
|
|
}
|
|
|
|
/**
|
|
* Add a template used by the steps.
|
|
*
|
|
* @param {string} name Name of the template
|
|
* @param {string} content Content of the template
|
|
*/
|
|
addTemplate(name, content) {
|
|
this.templates[name] = content;
|
|
}
|
|
|
|
/**
|
|
* Display the needed elements for the current step.
|
|
*/
|
|
showCurrentStep() {
|
|
$('.onboarding-navbar').toggleClass('displayed', !!this.isShutDown);
|
|
$('.onboarding-advancement').toggle(this.isShutDown === false);
|
|
$('.onboarding-popup').remove();
|
|
$('.onboarding-tooltip').remove();
|
|
|
|
const onBoardingHeight = $('.onboarding-navbar.displayed').innerHeight();
|
|
|
|
// Fix the menu scroll
|
|
if ($('#nav-sidebar').length) {
|
|
$('#nav-sidebar').css('padding-bottom', `${onBoardingHeight + 50}px`);
|
|
} else {
|
|
$('nav.nav-bar ul.main-menu').css(
|
|
'margin-bottom',
|
|
`${onBoardingHeight}px`,
|
|
);
|
|
}
|
|
|
|
if (!this.isShutDown) {
|
|
const step = this.getStep(this.currentStep);
|
|
|
|
if (OnBoarding.isCurrentPage(step.page)) {
|
|
this.prependTemplate(step.type, step.text);
|
|
|
|
if (step.type === 'tooltip') {
|
|
this.placeToolTip(step);
|
|
}
|
|
|
|
$('.onboarding-advancement').toggle(
|
|
$.inArray('hideFooter', step.options) === -1,
|
|
);
|
|
this.updateAdvancement();
|
|
} else {
|
|
$('.onboarding-advancement').toggle(false);
|
|
this.setShutDown(true);
|
|
// this.prependTemplate('lost');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Prepend a template to a body and add the content to its '.content' element.
|
|
*
|
|
* @param {string} templateName Template name
|
|
* @param {string} content Content to add
|
|
*/
|
|
prependTemplate(templateName, content = '') {
|
|
const newContent = $(this.templates[templateName]);
|
|
|
|
if (content !== '') {
|
|
newContent.find('.content').html(content);
|
|
}
|
|
|
|
$('body').prepend(newContent);
|
|
}
|
|
|
|
/**
|
|
* Move to the next step.
|
|
*/
|
|
gotoNextStep() {
|
|
this.gotoStep(this.currentStep + 1);
|
|
}
|
|
|
|
/**
|
|
* Go to a step defined by its index.
|
|
*
|
|
* @param {int} stepIndex Step index
|
|
*/
|
|
gotoStep(stepIndex) {
|
|
this.save({action: 'setCurrentStep', value: stepIndex}, (error) => {
|
|
if (!error) {
|
|
const currentStep = this.getStep(this.currentStep);
|
|
const nextStep = this.getStep(stepIndex);
|
|
|
|
if (!nextStep) {
|
|
$('.onboarding-popup').remove();
|
|
$('.onboarding-navbar').remove();
|
|
$('.onboarding-tooltip').remove();
|
|
return;
|
|
}
|
|
|
|
if (currentStep.action) {
|
|
$(currentStep.action.selector)[currentStep.action.action]();
|
|
} else {
|
|
this.currentStep += 1;
|
|
if (!OnBoarding.isCurrentPage(nextStep.page)) {
|
|
window.location.href = this.getRedirectUrl(nextStep);
|
|
} else {
|
|
this.showCurrentStep();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
getTokenAsString(redirectUrl) {
|
|
let separator;
|
|
|
|
if (redirectUrl.indexOf('?') !== -1) {
|
|
separator = '&';
|
|
} else {
|
|
separator = '?';
|
|
}
|
|
|
|
const queryString = window.location.search.substr(1);
|
|
const tokens = OnBoarding.getSecurityTokens(queryString);
|
|
|
|
let tokenAsString = separator;
|
|
|
|
if (tokens._token !== undefined) {
|
|
tokenAsString = `${tokenAsString}&_token=${tokens._token}`;
|
|
}
|
|
|
|
return tokenAsString;
|
|
}
|
|
|
|
getRedirectUrl(nextStep) {
|
|
let redirectUrl;
|
|
|
|
if (Array.isArray(nextStep.page)) {
|
|
[redirectUrl] = nextStep.page;
|
|
} else {
|
|
redirectUrl = nextStep.page;
|
|
}
|
|
|
|
return redirectUrl + this.getTokenAsString(redirectUrl);
|
|
}
|
|
|
|
static parseQueryString(queryString) {
|
|
const queryStringParts = queryString.split('&');
|
|
const queryParams = {};
|
|
let parts;
|
|
let i;
|
|
let key;
|
|
let value;
|
|
|
|
for (i = 0; i < queryStringParts.length; i += 1) {
|
|
parts = queryStringParts[i].split('=');
|
|
[key, value] = parts;
|
|
queryParams[key] = value;
|
|
}
|
|
|
|
return queryParams;
|
|
}
|
|
|
|
/**
|
|
* Get security tokens from URL and navigation menu
|
|
*
|
|
* @param queryString
|
|
* @returns {{}}
|
|
*/
|
|
static getSecurityTokens(queryString) {
|
|
const queryParams = OnBoarding.parseQueryString(queryString);
|
|
const tokens = {};
|
|
|
|
if (typeof queryParams._token !== 'undefined') {
|
|
tokens._token = queryParams._token;
|
|
}
|
|
|
|
return tokens;
|
|
}
|
|
|
|
/**
|
|
* Stop the OnBoarding
|
|
*/
|
|
stop() {
|
|
this.save(
|
|
{action: 'setCurrentStep', value: this.getTotalSteps()},
|
|
(error) => {
|
|
if (!error) {
|
|
$('.onboarding-advancement').remove();
|
|
$('.onboarding-navbar').remove();
|
|
$('.onboarding-popup').remove();
|
|
$('.onboarding-tooltip').remove();
|
|
}
|
|
},
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Goto the last save point step.
|
|
*/
|
|
gotoLastSavePoint() {
|
|
let lastSavePointStep = 0;
|
|
let stepCount = 0;
|
|
|
|
this.steps.groups.forEach((group) => {
|
|
group.steps.forEach((step) => {
|
|
if (
|
|
stepCount <= this.currentStep
|
|
&& $.inArray('savepoint', step.options) !== -1
|
|
) {
|
|
lastSavePointStep = stepCount;
|
|
}
|
|
stepCount += 1;
|
|
});
|
|
});
|
|
|
|
this.gotoStep(lastSavePointStep);
|
|
}
|
|
|
|
/**
|
|
* Return a group configuration for a step ID.
|
|
*
|
|
* @param {int} stepID Step ID
|
|
*
|
|
* @return {object} Group configuration
|
|
*/
|
|
getGroupForStep(stepID) {
|
|
return this.getElementForStep(stepID, 'group');
|
|
}
|
|
|
|
/**
|
|
* Return the current group ID.
|
|
*
|
|
* @return {int} Current group
|
|
*/
|
|
getCurrentGroupID() {
|
|
let currentGroupID = 0;
|
|
let currentStepID = 0;
|
|
let returnValue = 0;
|
|
|
|
this.steps.groups.forEach((group) => {
|
|
group.steps.forEach(() => {
|
|
if (currentStepID === this.currentStep) {
|
|
returnValue = currentGroupID;
|
|
}
|
|
currentStepID += 1;
|
|
});
|
|
currentGroupID += 1;
|
|
});
|
|
|
|
return returnValue;
|
|
}
|
|
|
|
/**
|
|
* Get current step ID on the group.
|
|
*
|
|
* @return {int} Step ID
|
|
*/
|
|
getCurrentStepIDOnGroup() {
|
|
let currentStepID = 0;
|
|
let stepID = 0;
|
|
let stepIDOnGroup = 0;
|
|
|
|
this.steps.groups.forEach((group) => {
|
|
stepIDOnGroup = 0;
|
|
group.steps.forEach(() => {
|
|
if (currentStepID === this.currentStep) {
|
|
stepID = stepIDOnGroup;
|
|
}
|
|
stepIDOnGroup += 1;
|
|
currentStepID += 1;
|
|
});
|
|
});
|
|
|
|
return stepID;
|
|
}
|
|
|
|
/**
|
|
* Get the step configuration for a step ID.
|
|
*
|
|
* @param {int} stepID Step ID
|
|
*
|
|
* @return {object} Step configuration
|
|
*/
|
|
getStep(stepID) {
|
|
return this.getElementForStep(stepID, 'step');
|
|
}
|
|
|
|
/**
|
|
* Return the element configuration fot a step or a group.
|
|
*
|
|
* @param {int} stepID Step ID for the element to get
|
|
* @param {string} elementType Element type (step or group)
|
|
*
|
|
* @returns {(object|null)} Element configuration if it exists
|
|
*/
|
|
getElementForStep(stepID, elementType) {
|
|
let currentStepID = 0;
|
|
let element = null;
|
|
|
|
this.steps.groups.forEach((group) => {
|
|
group.steps.forEach((step) => {
|
|
if (currentStepID === stepID) {
|
|
if (elementType === 'step') {
|
|
element = step;
|
|
} else if (elementType === 'group') {
|
|
element = group;
|
|
}
|
|
}
|
|
currentStepID += 1;
|
|
});
|
|
});
|
|
|
|
return element;
|
|
}
|
|
|
|
/**
|
|
* Call the save ajax api of the module.
|
|
*
|
|
* @param {object} settings Settings to save via POST
|
|
* @param {function} callback Callback function called after the execution
|
|
*/
|
|
save(settings, callback) {
|
|
$.ajax({
|
|
method: 'POST',
|
|
url: this.apiLocation,
|
|
data: settings,
|
|
})
|
|
.done((result) => {
|
|
callback(result !== '0');
|
|
})
|
|
.fail(() => {
|
|
callback(true);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Update the advancement footer.
|
|
*/
|
|
updateAdvancement() {
|
|
const advancementFooter = $('.onboarding-advancement');
|
|
const advancementNav = $('.onboarding-navbar');
|
|
let totalSteps = 0;
|
|
|
|
this.steps.groups.forEach((group, index) => {
|
|
const positionOnChunk = Math.min(
|
|
this.currentStep + 1 - totalSteps,
|
|
group.steps.length,
|
|
);
|
|
advancementFooter
|
|
.find(`.group-${index} .advancement`)
|
|
.css('width', `${(positionOnChunk / group.steps.length) * 100}%`);
|
|
totalSteps += group.steps.length;
|
|
if (positionOnChunk === group.steps.length) {
|
|
const id = advancementFooter.find(`.group-${index} .id`);
|
|
|
|
if (!id.hasClass('-done')) {
|
|
id.addClass('-done');
|
|
}
|
|
}
|
|
});
|
|
|
|
advancementFooter
|
|
.find('.group-title')
|
|
.html(
|
|
`${this.getCurrentGroupID() + 1}/${this.getTotalGroups()} - ${
|
|
this.getGroupForStep(this.currentStep).title
|
|
}`,
|
|
);
|
|
|
|
if (this.getGroupForStep(this.currentStep).subtitle) {
|
|
if (this.getGroupForStep(this.currentStep).subtitle[1]) {
|
|
advancementFooter
|
|
.find('.step-title-1')
|
|
.html(
|
|
`<i class="material-icons">check</i> ${
|
|
this.getGroupForStep(this.currentStep).subtitle[1]
|
|
}`,
|
|
);
|
|
}
|
|
if (this.getGroupForStep(this.currentStep).subtitle[2]) {
|
|
advancementFooter
|
|
.find('.step-title-2')
|
|
.html(
|
|
`<i class="material-icons">check</i> ${
|
|
this.getGroupForStep(this.currentStep).subtitle[2]
|
|
}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
const totalAdvancement = this.currentStep / this.getTotalSteps();
|
|
advancementNav
|
|
.find('.text')
|
|
.find('.text-right')
|
|
.html(`${Math.floor(totalAdvancement * 100)}%`);
|
|
advancementNav.find('.progress-bar').width(`${totalAdvancement * 100}%`);
|
|
}
|
|
|
|
/**
|
|
* Return the total steps count.
|
|
*
|
|
* @return {int} Total steps.
|
|
*/
|
|
getTotalSteps() {
|
|
let total = 0;
|
|
this.steps.groups.forEach((group) => {
|
|
total += group.steps.length;
|
|
});
|
|
return total;
|
|
}
|
|
|
|
/**
|
|
* Return the total groups count.
|
|
*
|
|
* @return {int} Total groups.
|
|
*/
|
|
getTotalGroups() {
|
|
return this.steps.groups.length;
|
|
}
|
|
|
|
/**
|
|
* Shut down or reactivate the onBoarding.
|
|
*
|
|
* @param {boolean} value True to shut down, false to activate.
|
|
*/
|
|
setShutDown(value) {
|
|
this.isShutDown = value ? 1 : 0;
|
|
|
|
if (this.isShutDown === 1) {
|
|
$('.onboarding-advancement').toggle(false);
|
|
$('.onboarding-navbar').toggleClass('displayed', true);
|
|
$('.onboarding-popup').remove();
|
|
$('.onboarding-tooltip').remove();
|
|
}
|
|
|
|
this.save({action: 'setShutDown', value: this.isShutDown}, (error) => {
|
|
if (!error) {
|
|
if (this.isShutDown === 0) {
|
|
if (OnBoarding.isCurrentPage(this.getStep(this.currentStep).page)) {
|
|
this.showCurrentStep();
|
|
} else {
|
|
this.gotoLastSavePoint();
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Return true if the url correspond to the current page.
|
|
*
|
|
* @param {(string|Array)} url Url to test
|
|
*
|
|
* @return {boolean} True if the url correspond to the current page
|
|
*/
|
|
static isCurrentPage(url) {
|
|
const currentPage = window.location.href;
|
|
|
|
let urls;
|
|
|
|
if (!$.isArray(url)) {
|
|
urls = [String(url)];
|
|
} else {
|
|
urls = url;
|
|
}
|
|
|
|
let isCurrentUrl = false;
|
|
urls.forEach((currentUrl) => {
|
|
// replace special chars for correct regexp testing
|
|
const currentUrlFixed = currentUrl.replace(/[\?\$]/g, '\\$&');
|
|
const urlRegexp = new RegExp(currentUrlFixed, 'i');
|
|
|
|
if (urlRegexp.test(currentPage)) {
|
|
isCurrentUrl = true;
|
|
}
|
|
});
|
|
|
|
return isCurrentUrl;
|
|
}
|
|
|
|
/**
|
|
* Show a tooltip for a step.
|
|
*
|
|
* @param {object} step Step configuration
|
|
*/
|
|
placeToolTip(step) {
|
|
this.tooltipElement = $(step.selector);
|
|
this.tooltip = $('.onboarding-tooltip');
|
|
|
|
this.tooltip.hide();
|
|
|
|
if (!this.tooltipElement.is(':visible')) {
|
|
setTimeout(() => {
|
|
this.placeToolTip(step);
|
|
}, 100);
|
|
if (this.tooltipPlacementInterval !== undefined) {
|
|
clearInterval(this.tooltipPlacementInterval);
|
|
}
|
|
return;
|
|
}
|
|
this.tooltipPlacementInterval = setInterval(() => {
|
|
this.updateToolTipPosition(step);
|
|
}, 100);
|
|
|
|
this.tooltip.show();
|
|
|
|
this.tooltip.addClass(`-${step.position}`);
|
|
this.tooltip.data('position', step.position);
|
|
|
|
|
|
const currentStepIDOnGroup = this.getCurrentStepIDOnGroup();
|
|
const groupStepsCount = this.getGroupForStep(this.currentStep).steps.length;
|
|
|
|
this.tooltip
|
|
.find('.count')
|
|
.html(`${currentStepIDOnGroup + 1}/${groupStepsCount}`);
|
|
|
|
const bullsContainer = this.tooltip.find('.bulls');
|
|
|
|
for (let idStep = 0; idStep < groupStepsCount; idStep += 1) {
|
|
const newElement = $('<div></div>').addClass('bull');
|
|
|
|
if (idStep < currentStepIDOnGroup) {
|
|
newElement.addClass('-done');
|
|
}
|
|
if (idStep === currentStepIDOnGroup) {
|
|
newElement.addClass('-current');
|
|
}
|
|
bullsContainer.append(newElement);
|
|
}
|
|
|
|
setTimeout(() => {
|
|
if (this.tooltipElement.offset().top > window.screen.height / 2 - 200) {
|
|
window.scrollTo(
|
|
0,
|
|
this.tooltipElement.offset().top - (window.screen.height / 2 - 200),
|
|
);
|
|
}
|
|
}, 200);
|
|
|
|
this.updateToolTipPosition();
|
|
}
|
|
|
|
/**
|
|
* Update the position of the tooltip.
|
|
*/
|
|
updateToolTipPosition() {
|
|
/* eslint-disable */
|
|
const middleX =
|
|
this.tooltipElement.offset().top -
|
|
this.tooltipElement.outerHeight() / 2 -
|
|
this.tooltip.outerHeight() / 2;
|
|
const middleY =
|
|
this.tooltipElement.offset().top +
|
|
this.tooltipElement.outerHeight() / 2 -
|
|
this.tooltip.outerHeight() / 2;
|
|
const topY =
|
|
this.tooltipElement.offset().top +
|
|
this.tooltipElement.outerHeight() / 2 -
|
|
this.tooltip.outerHeight() / 2;
|
|
/* eslint-enable */
|
|
const leftX = this.tooltipElement.offset().left - this.tooltip.outerWidth();
|
|
const rightX = this.tooltipElement.offset().left + this.tooltipElement.outerWidth();
|
|
|
|
switch (this.tooltip.data('position')) {
|
|
case 'right':
|
|
this.tooltip.css({left: rightX, top: middleY});
|
|
break;
|
|
case 'left':
|
|
this.tooltip.css({left: leftX, top: middleY});
|
|
break;
|
|
case 'top':
|
|
this.tooltip.css({left: middleX, top: topY});
|
|
break;
|
|
default:
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = OnBoarding;
|