mirror of
https://github.com/bringout/oca-ocb-web.git
synced 2026-04-22 11:32:07 +02:00
294 lines
11 KiB
JavaScript
294 lines
11 KiB
JavaScript
import { Component, markup, whenReady, validate } from "@odoo/owl";
|
|
import { browser } from "@web/core/browser/browser";
|
|
import { DropdownItem } from "@web/core/dropdown/dropdown_item";
|
|
import { registry } from "@web/core/registry";
|
|
import { session } from "@web/session";
|
|
import { loadBundle } from "@web/core/assets";
|
|
import { createPointerState } from "@web_tour/js/tour_pointer/tour_pointer_state";
|
|
import { tourState } from "@web_tour/js/tour_state";
|
|
import { callWithUnloadCheck } from "@web_tour/js/utils/tour_utils";
|
|
import {
|
|
tourRecorderState,
|
|
TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY,
|
|
} from "@web_tour/js/tour_recorder/tour_recorder_state";
|
|
import { redirect } from "@web/core/utils/urls";
|
|
|
|
class OnboardingItem extends Component {
|
|
static components = { DropdownItem };
|
|
static template = "web_tour.OnboardingItem";
|
|
static props = {
|
|
toursEnabled: { type: Boolean },
|
|
toggleItem: { type: Function },
|
|
};
|
|
setup() {}
|
|
}
|
|
|
|
const StepSchema = {
|
|
id: { type: [String], optional: true },
|
|
content: { type: [String, Object], optional: true }, //allow object(_t && markup)
|
|
debugHelp: { type: String, optional: true },
|
|
isActive: { type: Array, element: String, optional: true },
|
|
run: { type: [String, Function, Boolean], optional: true },
|
|
timeout: {
|
|
optional: true,
|
|
validate(value) {
|
|
return value >= 0 && value <= 60000;
|
|
},
|
|
},
|
|
tooltipPosition: {
|
|
optional: true,
|
|
validate(value) {
|
|
return ["top", "bottom", "left", "right"].includes(value);
|
|
},
|
|
},
|
|
trigger: { type: String },
|
|
expectUnloadPage: { type: Boolean, optional: true },
|
|
//ONLY IN DEBUG MODE
|
|
pause: { type: Boolean, optional: true },
|
|
break: { type: Boolean, optional: true },
|
|
};
|
|
|
|
const TourSchema = {
|
|
name: { type: String, optional: true },
|
|
steps: Function,
|
|
url: { type: String, optional: true },
|
|
wait_for: { type: [Function, Object], optional: true },
|
|
};
|
|
|
|
registry.category("web_tour.tours").addValidation(TourSchema);
|
|
const debugMenuRegistry = registry.category("debug").category("default");
|
|
|
|
export const tourService = {
|
|
// localization dependency to make sure translations used by tours are loaded
|
|
dependencies: ["orm", "effect", "overlay", "localization"],
|
|
start: async (env, { orm, effect, overlay }) => {
|
|
await whenReady();
|
|
let toursEnabled = session?.tour_enabled;
|
|
const tourRegistry = registry.category("web_tour.tours");
|
|
const pointer = createPointerState();
|
|
pointer.stop = () => {};
|
|
|
|
debugMenuRegistry.add("onboardingItem", () => ({
|
|
type: "component",
|
|
Component: OnboardingItem,
|
|
props: {
|
|
toursEnabled: toursEnabled || false,
|
|
toggleItem: async () => {
|
|
tourState.clear();
|
|
toursEnabled = await orm.call("res.users", "switch_tour_enabled", [
|
|
!toursEnabled,
|
|
]);
|
|
browser.location.reload();
|
|
},
|
|
},
|
|
sequence: 500,
|
|
section: "testing",
|
|
}));
|
|
|
|
function getTourFromRegistry(tourName) {
|
|
if (!tourRegistry.contains(tourName)) {
|
|
return;
|
|
}
|
|
const tour = tourRegistry.get(tourName);
|
|
return {
|
|
...tour,
|
|
steps: tour.steps(),
|
|
name: tourName,
|
|
wait_for: tour.wait_for || Promise.resolve(),
|
|
};
|
|
}
|
|
|
|
async function getTourFromDB(tourName) {
|
|
const tour = await orm.call("web_tour.tour", "get_tour_json_by_name", [tourName]);
|
|
if (!tour) {
|
|
throw new Error(`Tour '${tourName}' is not found in the database.`);
|
|
}
|
|
|
|
if (!tour.steps.length && tourRegistry.contains(tour.name)) {
|
|
tour.steps = tourRegistry.get(tour.name).steps();
|
|
}
|
|
|
|
return tour;
|
|
}
|
|
|
|
function validateStep(step) {
|
|
try {
|
|
validate(step, StepSchema);
|
|
} catch (error) {
|
|
console.error(
|
|
`Error in schema for TourStep ${JSON.stringify(step, null, 4)}\n${
|
|
error.message
|
|
}`
|
|
);
|
|
}
|
|
}
|
|
|
|
async function startTour(tourName, options = {}) {
|
|
pointer.stop();
|
|
const tourFromRegistry = getTourFromRegistry(tourName);
|
|
|
|
if (!tourFromRegistry && !options.fromDB) {
|
|
// Sometime tours are not loaded depending on the modules.
|
|
// For example, point_of_sale do not load all tours assets.
|
|
return;
|
|
}
|
|
|
|
const tour = options.fromDB ? { name: tourName, url: options.url } : tourFromRegistry;
|
|
if (!session.is_public && !toursEnabled && options.mode === "manual") {
|
|
toursEnabled = await orm.call("res.users", "switch_tour_enabled", [!toursEnabled]);
|
|
}
|
|
|
|
let tourConfig = {
|
|
delayToCheckUndeterminisms: 0,
|
|
stepDelay: 0,
|
|
keepWatchBrowser: false,
|
|
mode: "auto",
|
|
showPointerDuration: 0,
|
|
debug: false,
|
|
redirect: true,
|
|
};
|
|
|
|
tourConfig = Object.assign(tourConfig, options);
|
|
tourState.setCurrentConfig(tourConfig);
|
|
tourState.setCurrentTour(tour.name);
|
|
tourState.setCurrentIndex(0);
|
|
|
|
const willUnload = callWithUnloadCheck(() => {
|
|
if (tour.url && tourConfig.startUrl != tour.url && tourConfig.redirect) {
|
|
redirect(tour.url);
|
|
}
|
|
});
|
|
if (!willUnload) {
|
|
await resumeTour();
|
|
}
|
|
}
|
|
|
|
async function resumeTour() {
|
|
const tourName = tourState.getCurrentTour();
|
|
const tourConfig = tourState.getCurrentConfig();
|
|
|
|
let tour = getTourFromRegistry(tourName);
|
|
if (tourConfig.fromDB) {
|
|
tour = await getTourFromDB(tourName);
|
|
}
|
|
if (!tour) {
|
|
return;
|
|
}
|
|
|
|
tour.steps.forEach((step) => validateStep(step));
|
|
|
|
if (tourConfig.mode === "auto") {
|
|
if (!odoo.loader.modules.get("@web_tour/js/tour_automatic/tour_automatic")) {
|
|
await loadBundle("web_tour.automatic", { css: false });
|
|
}
|
|
const { TourAutomatic } = odoo.loader.modules.get(
|
|
"@web_tour/js/tour_automatic/tour_automatic"
|
|
);
|
|
new TourAutomatic(tour).start();
|
|
} else {
|
|
await loadBundle("web_tour.interactive");
|
|
const { TourPointer } = odoo.loader.modules.get(
|
|
"@web_tour/js/tour_pointer/tour_pointer"
|
|
);
|
|
pointer.stop = overlay.add(
|
|
TourPointer,
|
|
{
|
|
pointerState: pointer.state,
|
|
bounce: !(tourConfig.mode === "auto" && tourConfig.keepWatchBrowser),
|
|
},
|
|
{
|
|
sequence: 1100, // sequence based on bootstrap z-index values.
|
|
}
|
|
);
|
|
const { TourInteractive } = odoo.loader.modules.get(
|
|
"@web_tour/js/tour_interactive/tour_interactive"
|
|
);
|
|
new TourInteractive(tour).start(env, pointer, async () => {
|
|
pointer.stop();
|
|
tourState.clear();
|
|
browser.console.log("tour succeeded");
|
|
let message = tourConfig.rainbowManMessage || tour.rainbowManMessage;
|
|
if (message) {
|
|
message = window.DOMPurify.sanitize(tourConfig.rainbowManMessage);
|
|
effect.add({
|
|
type: "rainbow_man",
|
|
message: markup(message),
|
|
});
|
|
}
|
|
|
|
const nextTour = await orm.call("web_tour.tour", "consume", [tour.name]);
|
|
if (nextTour) {
|
|
startTour(nextTour.name, {
|
|
mode: "manual",
|
|
redirect: false,
|
|
rainbowManMessage: nextTour.rainbowManMessage,
|
|
});
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
async function tourRecorder() {
|
|
await loadBundle("web_tour.recorder");
|
|
const { TourRecorder } = odoo.loader.modules.get(
|
|
"@web_tour/js/tour_recorder/tour_recorder"
|
|
);
|
|
const remove = overlay.add(
|
|
TourRecorder,
|
|
{
|
|
onClose: () => {
|
|
remove();
|
|
browser.localStorage.removeItem(TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY);
|
|
tourRecorderState.clear();
|
|
},
|
|
},
|
|
{ sequence: 99999 }
|
|
);
|
|
}
|
|
|
|
async function startTourRecorder() {
|
|
if (!browser.localStorage.getItem(TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY)) {
|
|
await tourRecorder();
|
|
}
|
|
browser.localStorage.setItem(TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY, "1");
|
|
}
|
|
|
|
if (!window.frameElement) {
|
|
const paramsTourName = new URLSearchParams(browser.location.search).get("tour");
|
|
if (paramsTourName) {
|
|
startTour(paramsTourName, { mode: "manual", fromDB: true });
|
|
}
|
|
|
|
if (tourState.getCurrentTour()) {
|
|
if (tourState.getCurrentConfig().mode === "auto" || toursEnabled) {
|
|
resumeTour();
|
|
} else {
|
|
tourState.clear();
|
|
}
|
|
} else if (session.current_tour) {
|
|
startTour(session.current_tour.name, {
|
|
mode: "manual",
|
|
redirect: false,
|
|
rainbowManMessage: session.current_tour.rainbowManMessage,
|
|
});
|
|
}
|
|
|
|
if (
|
|
browser.localStorage.getItem(TOUR_RECORDER_ACTIVE_LOCAL_STORAGE_KEY) &&
|
|
!session.is_public
|
|
) {
|
|
await tourRecorder();
|
|
}
|
|
}
|
|
|
|
odoo.startTour = startTour;
|
|
odoo.isTourReady = (tourName) => getTourFromRegistry(tourName).wait_for.then(() => true);
|
|
|
|
return {
|
|
startTour,
|
|
startTourRecorder,
|
|
};
|
|
},
|
|
};
|
|
|
|
registry.category("services").add("tour_service", tourService);
|