674 lines
18 KiB
JavaScript
674 lines
18 KiB
JavaScript
frappe.provide("frappe.setup");
|
|
frappe.provide("frappe.setup.events");
|
|
frappe.provide("frappe.ui");
|
|
|
|
frappe.setup = {
|
|
slides: [],
|
|
events: {},
|
|
data: {},
|
|
utils: {},
|
|
domains: [],
|
|
|
|
on: function (event, fn) {
|
|
if (!frappe.setup.events[event]) {
|
|
frappe.setup.events[event] = [];
|
|
}
|
|
frappe.setup.events[event].push(fn);
|
|
},
|
|
add_slide: function (slide) {
|
|
frappe.setup.slides.push(slide);
|
|
},
|
|
|
|
remove_slide: function (slide_name) {
|
|
frappe.setup.slides = frappe.setup.slides.filter((slide) => slide.name !== slide_name);
|
|
},
|
|
|
|
run_event: function (event) {
|
|
$.each(frappe.setup.events[event] || [], function (i, fn) {
|
|
fn();
|
|
});
|
|
},
|
|
};
|
|
|
|
frappe.pages["setup-wizard"].on_page_load = function (wrapper) {
|
|
if (frappe.boot.setup_complete) {
|
|
window.location.href = "/app";
|
|
}
|
|
let requires = frappe.boot.setup_wizard_requires || [];
|
|
frappe.require(requires, function () {
|
|
frappe.call({
|
|
method: "frappe.desk.page.setup_wizard.setup_wizard.load_languages",
|
|
freeze: true,
|
|
callback: function (r) {
|
|
frappe.setup.data.lang = r.message;
|
|
|
|
frappe.setup.run_event("before_load");
|
|
var wizard_settings = {
|
|
parent: wrapper,
|
|
slides: frappe.setup.slides,
|
|
slide_class: frappe.setup.SetupWizardSlide,
|
|
unidirectional: 1,
|
|
done_state: 1,
|
|
};
|
|
frappe.wizard = new frappe.setup.SetupWizard(wizard_settings);
|
|
frappe.setup.run_event("after_load");
|
|
frappe.wizard.show_slide(cint(frappe.get_route()[1]));
|
|
},
|
|
});
|
|
});
|
|
};
|
|
|
|
frappe.pages["setup-wizard"].on_page_show = function () {
|
|
frappe.wizard && frappe.wizard.show_slide(cint(frappe.get_route()[1]));
|
|
};
|
|
|
|
frappe.setup.on("before_load", function () {
|
|
// load slides
|
|
frappe.setup.slides_settings.forEach((s) => {
|
|
if (!(s.name === "user" && frappe.boot.developer_mode)) {
|
|
// if not user slide with developer mode
|
|
frappe.setup.add_slide(s);
|
|
}
|
|
});
|
|
});
|
|
|
|
frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
|
|
constructor(args = {}) {
|
|
super(args);
|
|
$.extend(this, args);
|
|
|
|
this.page_name = "setup-wizard";
|
|
this.welcomed = true;
|
|
frappe.set_route("setup-wizard/0");
|
|
}
|
|
|
|
make() {
|
|
super.make();
|
|
this.container.addClass("container setup-wizard-slide with-form");
|
|
this.$next_btn.addClass("action");
|
|
this.$complete_btn.addClass("action");
|
|
this.setup_keyboard_nav();
|
|
}
|
|
|
|
setup_keyboard_nav() {
|
|
$("body").on("keydown", this.handle_enter_press.bind(this));
|
|
}
|
|
|
|
disable_keyboard_nav() {
|
|
$("body").off("keydown", this.handle_enter_press.bind(this));
|
|
}
|
|
|
|
handle_enter_press(e) {
|
|
if (e.which === frappe.ui.keyCode.ENTER) {
|
|
let $target = $(e.target);
|
|
if ($target.hasClass("prev-btn") || $target.hasClass("next-btn")) {
|
|
$target.trigger("click");
|
|
} else {
|
|
// hitting enter on autocomplete field shouldn't trigger next slide.
|
|
if ($target.data().fieldtype == "Autocomplete") return;
|
|
|
|
this.container.find(".next-btn").trigger("click");
|
|
e.preventDefault();
|
|
}
|
|
}
|
|
}
|
|
|
|
before_show_slide() {
|
|
if (!this.welcomed) {
|
|
frappe.set_route(this.page_name);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
show_slide(id) {
|
|
if (id === this.slides.length) {
|
|
return;
|
|
}
|
|
super.show_slide(id);
|
|
frappe.set_route(this.page_name, cstr(id));
|
|
}
|
|
|
|
show_hide_prev_next(id) {
|
|
super.show_hide_prev_next(id);
|
|
if (id + 1 === this.slides.length) {
|
|
this.$next_btn.removeClass("btn-primary").hide();
|
|
this.$complete_btn
|
|
.addClass("btn-primary")
|
|
.show()
|
|
.on("click", () => this.action_on_complete());
|
|
} else {
|
|
this.$next_btn.addClass("btn-primary").show();
|
|
this.$complete_btn.removeClass("btn-primary").hide();
|
|
}
|
|
}
|
|
|
|
refresh_slides() {
|
|
// For Translations, etc.
|
|
if (this.in_refresh_slides || !this.current_slide.set_values(true)) {
|
|
return;
|
|
}
|
|
this.in_refresh_slides = true;
|
|
|
|
this.update_values();
|
|
frappe.setup.slides = [];
|
|
frappe.setup.run_event("before_load");
|
|
|
|
frappe.setup.slides = this.get_setup_slides_filtered_by_domain();
|
|
|
|
this.slides = frappe.setup.slides;
|
|
frappe.setup.run_event("after_load");
|
|
|
|
// re-render all slide, only remake made slides
|
|
$.each(this.slide_dict, (id, slide) => {
|
|
if (slide.made) {
|
|
this.made_slide_ids.push(id);
|
|
}
|
|
});
|
|
this.made_slide_ids.push(this.current_id);
|
|
this.setup();
|
|
|
|
this.show_slide(this.current_id);
|
|
this.refresh(this.current_id);
|
|
setTimeout(() => {
|
|
this.container.find(".form-control").first().focus();
|
|
}, 200);
|
|
this.in_refresh_slides = false;
|
|
}
|
|
|
|
action_on_complete() {
|
|
frappe.telemetry.capture("initated_client_side", "setup");
|
|
if (!this.current_slide.set_values()) return;
|
|
this.update_values();
|
|
this.show_working_state();
|
|
this.disable_keyboard_nav();
|
|
this.listen_for_setup_stages();
|
|
|
|
return frappe.call({
|
|
method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete",
|
|
args: { args: this.values },
|
|
callback: (r) => {
|
|
if (r.message.status === "ok") {
|
|
this.post_setup_success();
|
|
} else if (r.message.status === "registered") {
|
|
this.update_setup_message(__("starting the setup..."));
|
|
} else if (r.message.fail !== undefined) {
|
|
this.abort_setup(r.message.fail);
|
|
}
|
|
},
|
|
error: () => this.abort_setup(),
|
|
});
|
|
}
|
|
|
|
post_setup_success() {
|
|
this.set_setup_complete_message(__("Setup Complete"), __("Refreshing..."));
|
|
if (frappe.setup.welcome_page) {
|
|
localStorage.setItem("session_last_route", frappe.setup.welcome_page);
|
|
}
|
|
setTimeout(function () {
|
|
// Reload
|
|
window.location.href = "/app";
|
|
}, 2000);
|
|
}
|
|
|
|
abort_setup(fail_msg) {
|
|
this.$working_state.find(".state-icon-container").html("");
|
|
fail_msg = fail_msg
|
|
? fail_msg
|
|
: frappe.last_response.setup_wizard_failure_message
|
|
? frappe.last_response.setup_wizard_failure_message
|
|
: __("Failed to complete setup");
|
|
|
|
this.update_setup_message("Could not start up: " + fail_msg);
|
|
|
|
this.$working_state.find(".title").html("Setup failed");
|
|
|
|
this.$abort_btn.show();
|
|
}
|
|
|
|
listen_for_setup_stages() {
|
|
frappe.realtime.on("setup_task", (data) => {
|
|
// console.log('data', data);
|
|
if (data.stage_status) {
|
|
// .html('Process '+ data.progress[0] + ' of ' + data.progress[1] + ': ' + data.stage_status);
|
|
this.update_setup_message(data.stage_status);
|
|
this.set_setup_load_percent(((data.progress[0] + 1) / data.progress[1]) * 100);
|
|
}
|
|
if (data.fail_msg) {
|
|
this.abort_setup(data.fail_msg);
|
|
}
|
|
if (data.status === "ok") {
|
|
this.post_setup_success();
|
|
}
|
|
});
|
|
}
|
|
|
|
update_setup_message(message) {
|
|
this.$working_state.find(".setup-message").html(message);
|
|
}
|
|
|
|
get_setup_slides_filtered_by_domain() {
|
|
let filtered_slides = [];
|
|
frappe.setup.slides.forEach(function (slide) {
|
|
if (frappe.setup.domains) {
|
|
let active_domains = frappe.setup.domains;
|
|
if (
|
|
!slide.domains ||
|
|
slide.domains.filter((d) => active_domains.includes(d)).length > 0
|
|
) {
|
|
filtered_slides.push(slide);
|
|
}
|
|
} else {
|
|
filtered_slides.push(slide);
|
|
}
|
|
});
|
|
return filtered_slides;
|
|
}
|
|
|
|
show_working_state() {
|
|
this.container.hide();
|
|
frappe.set_route(this.page_name);
|
|
|
|
this.$working_state = this.get_message(
|
|
__("Setting up your system"),
|
|
__("Starting Frappe ...")
|
|
).appendTo(this.parent);
|
|
|
|
this.attach_abort_button();
|
|
|
|
this.current_id = this.slides.length;
|
|
this.current_slide = null;
|
|
}
|
|
|
|
attach_abort_button() {
|
|
this.$abort_btn = $(
|
|
`<button class='btn btn-secondary btn-xs btn-abort text-muted'>${__("Retry")}</button>`
|
|
);
|
|
this.$working_state.find(".content").append(this.$abort_btn);
|
|
|
|
this.$abort_btn.on("click", () => {
|
|
$(this.parent).find(".setup-in-progress").remove();
|
|
this.container.show();
|
|
frappe.set_route(this.page_name, this.slides.length - 1);
|
|
});
|
|
|
|
this.$abort_btn.hide();
|
|
}
|
|
|
|
get_message(title, message = "") {
|
|
const loading_html = `<div class="progress-chart">
|
|
<div class="progress">
|
|
<div class="progress-bar"></div>
|
|
</div>
|
|
</div>`;
|
|
|
|
return $(`<div class="slides-wrapper container setup-wizard-slide setup-in-progress">
|
|
<div class="content text-center">
|
|
<h1 class="slide-title title">${title}</h1>
|
|
<div class="state-icon-container">${loading_html}</div>
|
|
<p class="setup-message text-muted">${message}</p>
|
|
</div>
|
|
</div>`);
|
|
}
|
|
|
|
set_setup_complete_message(title, message) {
|
|
this.$working_state.find(".title").html(title);
|
|
this.$working_state.find(".setup-message").html(message);
|
|
}
|
|
|
|
set_setup_load_percent(percent) {
|
|
this.$working_state.find(".progress-bar").css({ width: percent + "%" });
|
|
}
|
|
};
|
|
|
|
frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide {
|
|
constructor(slide = null) {
|
|
super(slide);
|
|
}
|
|
|
|
make() {
|
|
super.make();
|
|
this.set_init_values();
|
|
this.setup_telemetry_events();
|
|
this.reset_action_button_state();
|
|
}
|
|
|
|
set_init_values() {
|
|
let me = this;
|
|
// set values from frappe.setup.values
|
|
if (frappe.wizard.values && this.fields) {
|
|
this.fields.forEach(function (f) {
|
|
var value = frappe.wizard.values[f.fieldname];
|
|
if (value) {
|
|
me.get_field(f.fieldname).set_input(value);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
setup_telemetry_events() {
|
|
let me = this;
|
|
this.fields.filter(frappe.model.is_value_type).forEach((field) => {
|
|
field.fieldname &&
|
|
me.get_input(field.fieldname)?.on?.("change", function () {
|
|
frappe.telemetry.capture(`${field.fieldname}_set`, "setup");
|
|
if (
|
|
field.fieldname == "enable_telemetry" &&
|
|
!me.get_value("enable_telemetry")
|
|
) {
|
|
frappe.telemetry.disable();
|
|
}
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
// Frappe slides settings
|
|
// ======================================================
|
|
frappe.setup.slides_settings = [
|
|
{
|
|
// Welcome (language) slide
|
|
name: "welcome",
|
|
title: __("Welcome"),
|
|
|
|
fields: [
|
|
{
|
|
fieldname: "language",
|
|
label: __("Your Language"),
|
|
fieldtype: "Autocomplete",
|
|
placeholder: __("Select Language"),
|
|
default: "English",
|
|
reqd: 1,
|
|
},
|
|
{
|
|
fieldname: "country",
|
|
label: __("Your Country"),
|
|
fieldtype: "Autocomplete",
|
|
placeholder: __("Select Country"),
|
|
reqd: 1,
|
|
},
|
|
{
|
|
fieldtype: "Section Break",
|
|
},
|
|
{
|
|
fieldname: "timezone",
|
|
label: __("Time Zone"),
|
|
placeholder: __("Select Time Zone"),
|
|
fieldtype: "Select",
|
|
reqd: 1,
|
|
},
|
|
{ fieldtype: "Column Break" },
|
|
{
|
|
fieldname: "currency",
|
|
label: __("Currency"),
|
|
placeholder: __("Select Currency"),
|
|
fieldtype: "Select",
|
|
reqd: 1,
|
|
},
|
|
{
|
|
fieldtype: "Section Break",
|
|
},
|
|
{
|
|
fieldname: "enable_telemetry",
|
|
label: __("Allow sending usage data for improving applications"),
|
|
fieldtype: "Check",
|
|
default: cint(frappe.telemetry.can_enable()),
|
|
depends_on: "eval:frappe.telemetry.can_enable()",
|
|
},
|
|
{
|
|
fieldname: "allow_recording_first_session",
|
|
label: __("Allow recording my first session to improve user experience"),
|
|
fieldtype: "Check",
|
|
default: 0,
|
|
depends_on: "eval:frappe.telemetry.can_enable()",
|
|
},
|
|
],
|
|
|
|
onload: function (slide) {
|
|
if (frappe.setup.data.regional_data) {
|
|
this.setup_fields(slide);
|
|
} else {
|
|
frappe.setup.utils.load_regional_data(slide, this.setup_fields);
|
|
}
|
|
if (!slide.get_value("language")) {
|
|
let session_language =
|
|
frappe.setup.utils.get_language_name_from_code(
|
|
frappe.boot.lang || navigator.language
|
|
) || "English";
|
|
let language_field = slide.get_field("language");
|
|
|
|
language_field.set_input(session_language);
|
|
if (!frappe.setup._from_load_messages) {
|
|
language_field.$input.trigger("change");
|
|
}
|
|
delete frappe.setup._from_load_messages;
|
|
moment.locale("en");
|
|
}
|
|
frappe.setup.utils.bind_region_events(slide);
|
|
frappe.setup.utils.bind_language_events(slide);
|
|
},
|
|
|
|
setup_fields: function (slide) {
|
|
frappe.setup.utils.setup_region_fields(slide);
|
|
frappe.setup.utils.setup_language_field(slide);
|
|
},
|
|
},
|
|
{
|
|
// Profile slide
|
|
name: "user",
|
|
title: __("Let's set up your account"),
|
|
icon: "fa fa-user",
|
|
fields: [
|
|
{
|
|
fieldname: "full_name",
|
|
label: __("Full Name"),
|
|
fieldtype: "Data",
|
|
reqd: 1,
|
|
},
|
|
{
|
|
fieldname: "email",
|
|
label: __("Email Address") + " (" + __("Will be your login ID") + ")",
|
|
fieldtype: "Data",
|
|
options: "Email",
|
|
},
|
|
{ fieldname: "password", label: __("Password"), fieldtype: "Password", length: 512 },
|
|
],
|
|
|
|
onload: function (slide) {
|
|
if (frappe.session.user !== "Administrator") {
|
|
const { first_name, last_name, email } = frappe.boot.user;
|
|
if (first_name || last_name) {
|
|
slide.form.fields_dict.full_name.set_input(
|
|
[first_name, last_name].join(" ").trim()
|
|
);
|
|
}
|
|
slide.form.fields_dict.email.set_input(email);
|
|
slide.form.fields_dict.email.df.read_only = 1;
|
|
slide.form.fields_dict.email.refresh();
|
|
} else {
|
|
slide.form.fields_dict.email.df.reqd = 1;
|
|
slide.form.fields_dict.email.refresh();
|
|
slide.form.fields_dict.password.df.reqd = 1;
|
|
slide.form.fields_dict.password.refresh();
|
|
|
|
frappe.setup.utils.load_user_details(slide, this.setup_fields);
|
|
}
|
|
},
|
|
|
|
setup_fields: function (slide) {
|
|
if (frappe.setup.data.full_name) {
|
|
slide.form.fields_dict.full_name.set_input(frappe.setup.data.full_name);
|
|
}
|
|
if (frappe.setup.data.email) {
|
|
let email = frappe.setup.data.email;
|
|
slide.form.fields_dict.email.set_input(email);
|
|
}
|
|
},
|
|
},
|
|
];
|
|
|
|
frappe.setup.utils = {
|
|
load_regional_data: function (slide, callback) {
|
|
frappe.call({
|
|
method: "frappe.geo.country_info.get_country_timezone_info",
|
|
callback: function (data) {
|
|
frappe.setup.data.regional_data = data.message;
|
|
callback(slide);
|
|
},
|
|
});
|
|
},
|
|
|
|
load_user_details: function (slide, callback) {
|
|
frappe.call({
|
|
method: "frappe.desk.page.setup_wizard.setup_wizard.load_user_details",
|
|
freeze: true,
|
|
callback: function (r) {
|
|
frappe.setup.data.full_name = r.message.full_name;
|
|
frappe.setup.data.email = r.message.email;
|
|
callback(slide);
|
|
},
|
|
});
|
|
},
|
|
|
|
setup_language_field: function (slide) {
|
|
var language_field = slide.get_field("language");
|
|
language_field.df.options = frappe.setup.data.lang.languages;
|
|
language_field.set_options();
|
|
},
|
|
|
|
setup_region_fields: function (slide) {
|
|
/*
|
|
Set a slide's country, timezone and currency fields
|
|
*/
|
|
let data = frappe.setup.data.regional_data;
|
|
let country_field = slide.get_field("country");
|
|
let translated_countries = [];
|
|
|
|
Object.keys(data.country_info)
|
|
.sort()
|
|
.forEach((country) => {
|
|
translated_countries.push({
|
|
label: __(country),
|
|
value: country,
|
|
});
|
|
});
|
|
|
|
country_field.set_data(translated_countries);
|
|
|
|
slide
|
|
.get_input("currency")
|
|
.empty()
|
|
.add_options(
|
|
frappe.utils.unique($.map(data.country_info, (opts) => opts.currency).sort())
|
|
);
|
|
|
|
slide.get_input("timezone").empty().add_options(data.all_timezones);
|
|
|
|
slide.get_field("currency").set_input(frappe.wizard.values.currency);
|
|
slide.get_field("timezone").set_input(frappe.wizard.values.timezone);
|
|
|
|
// set values if present
|
|
let country =
|
|
frappe.wizard.values.country ||
|
|
data.default_country ||
|
|
guess_country(frappe.setup.data.regional_data.country_info);
|
|
|
|
if (country) {
|
|
country_field.set_input(country);
|
|
$(country_field.input).change();
|
|
}
|
|
},
|
|
|
|
bind_language_events: function (slide) {
|
|
slide
|
|
.get_input("language")
|
|
.unbind("change")
|
|
.on("change", function () {
|
|
clearTimeout(slide.language_call_timeout);
|
|
slide.language_call_timeout = setTimeout(() => {
|
|
let lang = $(this).val() || "English";
|
|
frappe._messages = {};
|
|
frappe.call({
|
|
method: "frappe.desk.page.setup_wizard.setup_wizard.load_messages",
|
|
freeze: true,
|
|
args: {
|
|
language: lang,
|
|
},
|
|
callback: function () {
|
|
frappe.setup._from_load_messages = true;
|
|
frappe.wizard.refresh_slides();
|
|
},
|
|
});
|
|
}, 500);
|
|
});
|
|
},
|
|
|
|
get_language_name_from_code: function (language_code) {
|
|
return frappe.setup.data.lang.codes_to_names[language_code] || "English";
|
|
},
|
|
|
|
bind_region_events: function (slide) {
|
|
/*
|
|
Bind a slide's country, timezone and currency fields
|
|
*/
|
|
slide.get_input("country").on("change", function () {
|
|
let country = slide.get_input("country").val();
|
|
let $timezone = slide.get_input("timezone");
|
|
let data = frappe.setup.data.regional_data;
|
|
|
|
$timezone.empty();
|
|
|
|
if (!country) return;
|
|
// add country specific timezones first
|
|
const timezone_list = data.country_info[country].timezones || [];
|
|
$timezone.add_options(timezone_list.sort());
|
|
slide.get_field("currency").set_input(data.country_info[country].currency);
|
|
slide.get_field("currency").$input.trigger("change");
|
|
|
|
// add all timezones at the end, so that user has the option to change it to any timezone
|
|
$timezone.add_options(data.all_timezones);
|
|
slide.get_field("timezone").set_input($timezone.val());
|
|
|
|
// temporarily set date format
|
|
frappe.boot.sysdefaults.date_format =
|
|
data.country_info[country].date_format || "dd-mm-yyyy";
|
|
});
|
|
|
|
slide.get_input("currency").on("change", function () {
|
|
let currency = slide.get_input("currency").val();
|
|
if (!currency) return;
|
|
frappe.model.with_doc("Currency", currency, function () {
|
|
frappe.provide("locals.:Currency." + currency);
|
|
let currency_doc = frappe.model.get_doc("Currency", currency);
|
|
let number_format = currency_doc.number_format;
|
|
if (number_format === "#.###") {
|
|
number_format = "#.###,##";
|
|
} else if (number_format === "#,###") {
|
|
number_format = "#,###.##";
|
|
}
|
|
|
|
frappe.boot.sysdefaults.number_format = number_format;
|
|
locals[":Currency"][currency] = $.extend({}, currency_doc);
|
|
});
|
|
});
|
|
},
|
|
};
|
|
|
|
// https://github.com/eggert/tz/blob/main/backward add more if required.
|
|
const TZ_BACKWARD_COMPATBILITY_MAP = {
|
|
"Asia/Calcutta": "Asia/Kolkata",
|
|
};
|
|
|
|
function guess_country(country_info) {
|
|
try {
|
|
let system_timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
system_timezone = TZ_BACKWARD_COMPATBILITY_MAP[system_timezone] || system_timezone;
|
|
|
|
for (let [country, info] of Object.entries(country_info)) {
|
|
let possible_timezones = (info.timezones || []).filter((t) => t == system_timezone);
|
|
if (possible_timezones.length) return country;
|
|
}
|
|
} catch (e) {
|
|
console.log("Could not guess country", e);
|
|
}
|
|
}
|