瀏覽代碼

Setup wizard refactor (#3548)

* [wizard] refactor and UI cleanups

* [wip] attach image control cleanup

* try and detect lang, country, gravatar

* password control

* frappe.wiz to frappe.setup

* cleaned up slides, yet to decide on master data

* Add function to add dynamic fields in layout

* [fix] independent slide fields numbering by deep-cloning initial fields

* refresh only appended fields, autofill user_details

* [wizard] UI test

* [wizard] frappe icon, reuse get_geo_ip_country, cleanups
version-14
Prateeksha Singh 8 年之前
committed by Makarand Bauskar
父節點
當前提交
afed0c9374
共有 12 個檔案被更改,包括 664 行新增235 行删除
  1. +1
    -0
      frappe/build.js
  2. +114
    -6
      frappe/desk/page/setup_wizard/setup_wizard.css
  3. +333
    -198
      frappe/desk/page/setup_wizard/setup_wizard.js
  4. +17
    -1
      frappe/desk/page/setup_wizard/setup_wizard.py
  5. +8
    -4
      frappe/desk/page/setup_wizard/setup_wizard_page.html
  6. +12
    -0
      frappe/public/css/form.css
  7. +81
    -9
      frappe/public/js/frappe/form/control.js
  8. +31
    -14
      frappe/public/js/frappe/form/layout.js
  9. +4
    -2
      frappe/public/js/frappe/misc/common.js
  10. +5
    -1
      frappe/public/js/frappe/ui/field_group.js
  11. +15
    -0
      frappe/public/less/form.less
  12. +43
    -0
      frappe/tests/ui/setup_wizard.js

+ 1
- 0
frappe/build.js 查看文件

@@ -272,6 +272,7 @@ function watch_js(ondirty) {
if (sources.includes(filename)) {
pack(target, sources);
ondirty && ondirty(target);
// break;
}
}
});


+ 114
- 6
frappe/desk/page/setup_wizard/setup_wizard.css 查看文件

@@ -1,3 +1,22 @@
.setup-wizard-brand {
margin: 40px;
text-align: center;
display: flex;
justify-content: center;
align-items: center
}

.setup-wizard-brand .brand-icon {
width: 36px;
height: 36px;
}

.setup-wizard-brand .brand-name {
font-size: 20px;
margin-left: 8px;
color: #36414C;
}

.setup-wizard-slide {
padding-left: 0px;
padding-right: 0px;
@@ -14,22 +33,67 @@
}

.setup-wizard-slide .lead {
margin-bottom: 10px;
margin: 40px;
color: #777777;
text-align: center;
font-size: 30px;
}

.setup-wizard-slide .col-sm-12 {
padding: 0px;
}

.setup-wizard-slide .section-body .col-sm-6:first-child {
padding-left: 0px;
}

.setup-wizard-slide .section-body .col-sm-6:last-child {
padding-right: 0px;
}

.setup-wizard-slide .form-control {
height: 35px;
font-weight: 500;
}

.setup-wizard-slide .has-error .control-label {
color: #ffa00a;
}

.setup-wizard-slide .has-error .form-control{
border-color: #ffa00a;
}

.setup-wizard-slide .form-control.bold {
background-color: #fff;
}

.setup-wizard-slide.with-form {
margin: 40px auto;
padding: 10px 50px;
border: 1px solid #d1d8dd;
box-shadow: 0px 3px 5px rgba(0, 0, 0, 0.1);
}

.setup-wizard-slide .footer {
padding: 30px;
padding: 30px 0px;
}

.setup-wizard-slide a.next-btn,
.setup-wizard-slide a.complete-btn {
font-size: 14px;
padding: 7px 25px;
}

.setup-wizard-slide a.next-btn.disabled,
.setup-wizard-slide a.complete-btn.disabled {
background-color: #b1bdca;
color: #fff;
border-color: #b1bdca;
}

.setup-wizard-progress {
padding: 15px;
padding: 15px;
}

.setup-wizard-slide .fa-fw {
@@ -50,16 +114,28 @@
}

.setup-wizard-slide .frappe-control[data-fieldtype="Attach Image"] {
width: 140px;
height: 180px; /*depends on presence of heading*/
text-align: center;
margin-left: 33%;
}

.setup-wizard-slide .frappe-control[data-fieldtype="Attach Image"] .form-group,
.setup-wizard-slide .frappe-control[data-fieldtype="Attach Image"] .clearfix {
display: none;
}

.setup-wizard-slide .missing-image,
.setup-wizard-slide .attach-image-display {
display: block;
position: relative;
left: 50%;
transform: translate(-50%, 0);
-webkit-transform: translate(-50%, 0);
border-radius: 4px;
}

.setup-wizard-slide .missing-image {
border: 1px solid #d1d8dd;
border-radius: 6px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
}

.setup-wizard-slide .missing-image .octicon {
@@ -69,6 +145,38 @@
-webkit-transform: translate(0px, -50%);
}


.setup-wizard-slide .img-container {
height: 100%;
width: 100%;
padding: 2px;
display: flex;
align-items: center;
justify-content: center;
position: relative;
border: 1px solid #d1d8dd;
border-radius: 6px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
}

.setup-wizard-slide .img-overlay {
display: flex;
align-items: center;
justify-content: center;
position: absolute;
width: 100%;
height: 100%;
color: #777777;
background-color: rgba(255, 255, 255, 0.7);
opacity: 0;
}

.setup-wizard-slide .img-overlay:hover {
opacity: 1;
cursor: pointer;
}


.setup-wizard-message-image {
margin: 15px auto;
}

+ 333
- 198
frappe/desk/page/setup_wizard/setup_wizard.js 查看文件

@@ -1,22 +1,25 @@
frappe.provide("frappe.wiz");
frappe.provide("frappe.wiz.events");
frappe.provide("frappe.setup.events");

frappe.wiz = {
frappe.setup = {
slides: [],
events: {},
data: {},
utils: {},

remove_app_slides: [],
on: function(event, fn) {
if(!frappe.wiz.events[event]) {
frappe.wiz.events[event] = [];
if(!frappe.setup.events[event]) {
frappe.setup.events[event] = [];
}
frappe.wiz.events[event].push(fn);
frappe.setup.events[event].push(fn);
},
add_slide: function(slide) {
frappe.wiz.slides.push(slide);
frappe.setup.slides.push(slide);
},

run_event: function(event) {
$.each(frappe.wiz.events[event] || [], function(i, fn) {
$.each(frappe.setup.events[event] || [], function(i, fn) {
fn();
});
}
@@ -25,21 +28,21 @@ frappe.wiz = {
frappe.pages['setup-wizard'].on_page_load = function(wrapper) {
// setup page ui
$(".navbar:first").toggle(false);
$("body").css({"padding-top":"30px"});

var requires = ["/assets/frappe/css/animate.min.css"].concat(frappe.boot.setup_wizard_requires || []);

frappe.require(requires, function() {
frappe.wiz.run_event("before_load");
frappe.setup.run_event("before_load");

var wizard_settings = {
page_name: "setup-wizard",
parent: wrapper,
slides: frappe.wiz.slides,
slides: frappe.setup.slides,
title: __("Welcome")
}

frappe.wizard = new frappe.wiz.Wizard(wizard_settings);
frappe.wiz.run_event("after_load");
frappe.wizard = new frappe.setup.Wizard(wizard_settings);
frappe.setup.run_event("after_load");

// frappe.wizard.values = test_values_edu;

@@ -56,7 +59,7 @@ frappe.pages['setup-wizard'].on_page_show = function(wrapper) {
}
}

frappe.wiz.Wizard = Class.extend({
frappe.setup.Wizard = Class.extend({
init: function(opts) {
$.extend(this, opts);
this.make();
@@ -75,6 +78,7 @@ frappe.wiz.Wizard = Class.extend({
</div>', {html:html}))
},
show_working: function() {
$('header').find('.setup-wizard-brand').hide();
this.hide_current_slide();
frappe.set_route(this.page_name);
this.current_slide = {"$wrapper": this.get_message(this.working_html()).appendTo(this.parent)};
@@ -96,7 +100,7 @@ frappe.wiz.Wizard = Class.extend({
this.update_values();

if(!this.slide_dict[id]) {
this.slide_dict[id] = new frappe.wiz.WizardSlide($.extend(this.slides[id], {wiz:this, id:id}));
this.slide_dict[id] = new frappe.setup.WizardSlide($.extend(this.slides[id], {wiz:this, id:id}));
this.slide_dict[id].make();
}

@@ -147,8 +151,8 @@ frappe.wiz.Wizard = Class.extend({
args: {args: this.values},
callback: function(r) {
me.show_complete();
if(frappe.wiz.welcome_page) {
localStorage.setItem("session_last_route", frappe.wiz.welcome_page);
if(frappe.setup.welcome_page) {
localStorage.setItem("session_last_route", frappe.setup.welcome_page);
}
setTimeout(function() {
window.location = "/desk";
@@ -181,26 +185,27 @@ frappe.wiz.Wizard = Class.extend({

this.update_values();

frappe.wiz.slides = [];
frappe.wiz.run_event("before_load");
frappe.setup.slides = [];
frappe.setup.run_event("before_load");

// remove slides listed in remove_app_slides
var new_slides = [];
frappe.wiz.slides.forEach(function(slide) {
if(frappe.wiz.domain) {
frappe.setup.slides.forEach(function(slide) {
if(frappe.setup.domain) {
var domains = slide.domains;
if (domains.indexOf('all') !== -1 ||
domains.indexOf(frappe.wiz.domain.toLowerCase()) !== -1) {
domains.indexOf(frappe.setup.domain.toLowerCase()) !== -1) {
new_slides.push(slide);
}
} else {
new_slides.push(slide);
}
})
frappe.wiz.slides = new_slides;

this.slides = frappe.wiz.slides;
frappe.wiz.run_event("after_load");
frappe.setup.slides = new_slides;

this.slides = frappe.setup.slides;
frappe.setup.run_event("after_load");

// re-render all slides
this.slide_dict = {};
@@ -213,7 +218,7 @@ frappe.wiz.Wizard = Class.extend({
}
});

frappe.wiz.WizardSlide = Class.extend({
frappe.setup.WizardSlide = Class.extend({
init: function(opts) {
$.extend(this, opts);
this.$wrapper = $('<div class="slide-wrapper hidden"></div>')
@@ -224,6 +229,17 @@ frappe.wiz.WizardSlide = Class.extend({
var me = this;
if(this.$body) this.$body.remove();

var fields = JSON.parse(JSON.stringify(this.fields));

if(this.add_more) {
this.count = 1;
fields = fields.map(field => {
if(field.fieldname) field.fieldname += '_1';
if(field.label) field.label += ' 1';
return field;
});
}

if(this.before_load) {
this.before_load(this);
}
@@ -234,7 +250,6 @@ frappe.wiz.WizardSlide = Class.extend({
main_title:__(this.wiz.title),
step: this.id + 1,
name: this.name,
css_class: this.css_class || "",
slides_count: this.wiz.slides.length
})).appendTo(this.$wrapper);

@@ -242,7 +257,7 @@ frappe.wiz.WizardSlide = Class.extend({

if(this.fields) {
this.form = new frappe.ui.FieldGroup({
fields: this.fields,
fields: fields,
body: this.body,
no_submit_on_enter: true
});
@@ -251,18 +266,33 @@ frappe.wiz.WizardSlide = Class.extend({
$(this.body).html(this.html);
}

this.set_reqd_fields();
this.set_init_values();
this.make_prev_next_buttons();
if(this.add_more) this.bind_more_button();

var $primary_btn = this.$next ? this.$next : this.$complete;

this.bind_fields_to_next($primary_btn);

if(this.onload) {
this.onload(this);
}
this.reset_next($primary_btn);
this.focus_first_input();

},
set_reqd_fields: function() {
var dict = this.form.fields_dict;
this.reqd_fields = [];
Object.keys(dict).map(key => {
if(dict[key].df.reqd) {
this.reqd_fields.push(dict[key]);
}
});
},
set_init_values: function() {
var me = this;
// set values from frappe.wiz.values
// set values from frappe.setup.values
if(frappe.wizard.values && this.fields) {
this.fields.forEach(function(f) {
var value = frappe.wizard.values[f.fieldname];
@@ -284,6 +314,23 @@ frappe.wiz.WizardSlide = Class.extend({
return true;
},

bind_more_button: function() {
this.$more = this.$body.find('.more-btn');
this.$more.removeClass('hide')
.on('click', () => {
this.count++;
var fields = JSON.parse(JSON.stringify(this.fields));
this.form.add_fields(fields.map(field => {
if(field.fieldname) field.fieldname += '_' + this.count;
if(field.label) field.label += ' ' + this.count;
return field;
}));
if(this.count === this.max_count) {
this.$more.addClass('hide');
}
});
},

make_prev_next_buttons: function() {
var me = this;

@@ -311,7 +358,7 @@ frappe.wiz.WizardSlide = Class.extend({
.click(this.next_or_complete.bind(this));
}

//setup mousefree navigation
// setup mousefree navigation
this.$body.on('keypress', function(e) {
if(e.which === 13) {
var $target = $(e.target);
@@ -326,6 +373,14 @@ frappe.wiz.WizardSlide = Class.extend({
}
});
},
bind_fields_to_next: function($primary_btn) {
var me = this;
this.reqd_fields.map((field) => {
field.$wrapper.on('change input', () => {
me.reset_next($primary_btn);
});
});
},
next_or_complete: function() {
if(this.set_values()) {
if(this.id+1 < this.wiz.slides.length) {
@@ -335,6 +390,17 @@ frappe.wiz.WizardSlide = Class.extend({
}
}
},
reset_next: function($primary_btn) {
var empty_fields = this.reqd_fields.filter((field) => {
return !field.get_value();
})

if(empty_fields.length) {
$primary_btn.addClass('disabled');
} else {
$primary_btn.removeClass('disabled');
}
},
focus_first_input: function() {
setTimeout(function() {
this.$body.find('.form-control').first().focus();
@@ -360,233 +426,302 @@ frappe.wiz.WizardSlide = Class.extend({
},
});

function load_frappe_slides() {
// language selection
frappe.wiz.welcome = {
var frappe_slides = [
{
// Welcome (language) slide
name: "welcome",
domains: ["all"],
title: __("Welcome"),
title: __("Hello!"),
icon: "fa fa-world",
help: __("Let's prepare the system for first use."),

fields: [
{ fieldname: "language", label: __("Select Your Language"), reqd:1,
fieldtype: "Select", "default": "english" },
{ fieldname: "language", label: __("Your Language"),
fieldtype: "Select", "default": "English" }
],

onload: function(slide) {
if (!frappe.wiz.welcome.data) {
frappe.wiz.welcome.load_languages(slide);
if (frappe.setup.data.lang) {
this.setup_fields(slide);
} else {
frappe.wiz.welcome.setup_fields(slide);
utils.load_languages(slide, this.setup_fields);
}
},

css_class: "single-column",
load_languages: function(slide) {
frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.load_languages",
freeze: true,
callback: function(r) {
frappe.wiz.welcome.data = r.message;
frappe.wiz.welcome.setup_fields(slide);

var language_field = slide.get_field("language");
language_field.set_input(frappe.wiz.welcome.data.default_language || "english");

if (!frappe.wiz._from_load_messages) {
language_field.$input.trigger("change");
}

delete frappe.wiz._from_load_messages;

moment.locale("en");
}
});
},

setup_fields: function(slide) {
var select = slide.get_field("language");
select.df.options = frappe.wiz.welcome.data.languages;
select.refresh();
frappe.wiz.welcome.bind_events(slide);
utils.setup_language_field(slide);
utils.bind_language_events(slide);
},

bind_events: function(slide) {
slide.get_input("language").unbind("change").on("change", function() {
var 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(r) {
frappe.wiz._from_load_messages = true;
frappe.wizard.refresh_slides();
}
});
});
}
},

// region selection
frappe.wiz.region = {
{
// Region slide
name: 'region',
domains: ["all"],
title: __("Region"),
title: __("Select Your Region"),
icon: "fa fa-flag",
help: __("Select your Country, Time Zone and Currency"),
fields: [
{ fieldname: "country", label: __("Country"), reqd:1,
{ fieldname: "country", label: __("Your Country"), reqd:1,
fieldtype: "Select" },
{ fieldtype: "Section Break" },
{ fieldname: "timezone", label: __("Time Zone"), reqd:1,
fieldtype: "Select" },
{ fieldtype: "Column Break" },
{ fieldname: "currency", label: __("Currency"), reqd:1,
fieldtype: "Select" },
fieldtype: "Select" }
],

onload: function(slide) {
var _setup = function() {
frappe.wiz.region.setup_fields(slide);
frappe.wiz.region.bind_events(slide);
};

if(frappe.wiz.regional_data) {
_setup();
if(frappe.setup.data.regional_data) {
this.setup_fields(slide);
} else {
frappe.call({
method:"frappe.geo.country_info.get_country_timezone_info",
callback: function(data) {
frappe.wiz.regional_data = data.message;
_setup();
}
});
utils.load_regional_data(slide, this.setup_fields);
}
},
css_class: "single-column",

setup_fields: function(slide) {
var data = frappe.wiz.regional_data;
utils.setup_region_fields(slide);
utils.bind_region_events(slide);
}
},

slide.get_input("country").empty()
.add_options([""].concat(Object.keys(data.country_info).sort()));
{
// Profile slide
name: 'user',
domains: ["all"],
title: __("The First User: You"),
icon: "fa fa-user",
fields: [
{ "fieldtype":"Attach Image", "fieldname":"attach_user_image",
label: __("Attach Your Picture"), is_private: 0},
{ "fieldname": "full_name", "label": __("Full Name"), "fieldtype": "Data",
reqd:1},
{ "fieldname": "email", "label": __("Email Address") + ' <i>(' + __("Will be your login ID") + ')</i>',
"fieldtype": "Data", reqd:1, "options":"Email"},
{ "fieldname": "password", "label": __("Password"), "fieldtype": "Password", reqd:1 }
],
help: __('The first user will become the System Manager (you can change this later).'),
onload: function(slide) {
if(frappe.session.user!=="Administrator") {
// remove password field
delete slide.form.fields_dict.password;

slide.form.fields_dict.email.$wrapper.toggle(false);
if(frappe.boot.user.first_name || frappe.boot.user.last_name) {
slide.form.fields_dict.full_name.set_input(
[frappe.boot.user.first_name, frappe.boot.user.last_name].join(' ').trim());
}

slide.get_input("currency").empty()
.add_options(frappe.utils.unique([""].concat($.map(data.country_info,
function(opts, country) { return opts.currency; }))).sort());
var user_image = frappe.get_cookie("user_image");
var $attach_user_image = slide.form.fields_dict.attach_user_image.$wrapper;

slide.get_input("timezone").empty()
.add_options([""].concat(data.all_timezones));
if(user_image) {
$attach_user_image.find(".missing-image").toggle(false);
$attach_user_image.find("img").attr("src", decodeURIComponent(user_image)).toggle(true);
}
delete slide.form.fields_dict.email;

// set values if present
if(frappe.wizard.values.country) {
slide.get_field("country").set_input(frappe.wizard.values.country);
} else if (data.default_country) {
slide.get_field("country").set_input(data.default_country);
} else {
utils.load_user_details(slide, this.setup_fields);
}
},

if(frappe.wizard.values.currency) {
slide.get_field("currency").set_input(frappe.wizard.values.currency);
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.wizard.values.timezone) {
slide.get_field("timezone").set_input(frappe.wizard.values.timezone);
if(frappe.setup.data.email) {
let email = frappe.setup.data.email;
slide.form.fields_dict.email.set_input(email);
if (frappe.get_gravatar(email, 200)) {
var $attach_user_image = slide.form.fields_dict.attach_user_image.$wrapper;
$attach_user_image.find(".missing-image").toggle(false);
$attach_user_image.find("img").attr("src", frappe.get_gravatar(email, 200));
$attach_user_image.find(".img-container").toggle(true);
}
}

},
},
];

bind_events: function(slide) {
slide.get_input("country").on("change", function() {
var country = slide.get_input("country").val();
var $timezone = slide.get_input("timezone");
var data = frappe.wiz.regional_data;
var utils = {
load_languages: function(slide, callback) {
frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.load_languages",
freeze: true,
callback: function(r) {
frappe.setup.data.lang = r.message;
callback(slide);

$timezone.empty();
var language_field = slide.get_field("language");

// add country specific timezones first
if(country) {
var 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");
language_field.set_input(frappe.setup.data.default_language || "English");

if (!frappe.setup._from_load_messages) {
language_field.$input.trigger("change");
}
delete frappe.setup._from_load_messages;
moment.locale("en");
}
});
},

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);
}
});
},

// add all timezones at the end, so that user has the option to change it to any timezone
$timezone.add_options([""].concat(data.all_timezones));
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);
}
})
},

slide.get_field("timezone").set_input($timezone.val());
setup_language_field: function(slide) {
var language_field = slide.get_field("language");
language_field.df.options = frappe.setup.data.lang.languages;
language_field.refresh();
},

// temporarily set date format
frappe.boot.sysdefaults.date_format = (data.country_info[country].date_format
|| "dd-mm-yyyy");
});
setup_region_fields: function(slide) {
/*
Set a slide's country, timezone and currency fields
*/
var data = frappe.setup.data.regional_data;

slide.get_input("currency").on("change", function() {
var currency = slide.get_input("currency").val();
if (!currency) return;
frappe.model.with_doc("Currency", currency, function() {
frappe.provide("locals.:Currency." + currency);
var currency_doc = frappe.model.get_doc("Currency", currency);
var 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);
});
});
var country_field = slide.get_field('country');
slide.get_input("country").empty()
.add_options([""].concat(Object.keys(data.country_info).sort()));
slide.get_input("currency").empty()
.add_options(frappe.utils.unique([""].concat($.map(data.country_info,
function(opts, country) { return opts.currency; }))).sort());
slide.get_input("timezone").empty()
.add_options([""].concat(data.all_timezones));
// set values if present
if(frappe.wizard.values.country) {
country_field.set_input(frappe.wizard.values.country);
} else if (data.default_country) {
country_field.set_input(data.default_country);
}
},

if(frappe.wizard.values.currency) {
slide.get_field("currency").set_input(frappe.wizard.values.currency);
}

frappe.wiz.user = {
domains: ["all"],
title: __("The First User: You"),
icon: "fa fa-user",
fields: [
{"fieldname": "full_name", "label": __("Full Name"), "fieldtype": "Data",
reqd:1},
{"fieldname": "email", "label": __("Email Address"), "fieldtype": "Data",
reqd:1, "description": __("Login id"), "options":"Email"},
{"fieldname": "password", "label": __("Password"), "fieldtype": "Password",
reqd:1},
{fieldtype:"Attach Image", fieldname:"attach_user",
label: __("Attach Your Picture"), is_private: 0},
],
help: __('The first user will become the System Manager (you can change this later).'),
onload: function(slide) {
if(frappe.session.user!=="Administrator") {
slide.form.fields_dict.password.$wrapper.toggle(false);
slide.form.fields_dict.email.$wrapper.toggle(false);
if(frappe.boot.user.first_name || frappe.boot.user.last_name) {
slide.form.fields_dict.full_name.set_input(
[frappe.boot.user.first_name, frappe.boot.user.last_name].join(' ').trim());
if(frappe.wizard.values.timezone) {
slide.get_field("timezone").set_input(frappe.wizard.values.timezone);
}

country_field.df.description = 'fetching country...';
country_field.set_description();

// get location from IP (unreliable)
frappe.call({
method:"frappe.desk.page.setup_wizard.setup_wizard.load_country",
callback: function(r) {
if(r.message) {
slide.get_field("country").set_input(r.message);
slide.get_input("country").trigger('change');
}
country_field.df.description = '';
country_field.set_description();
}
});
},

var user_image = frappe.get_cookie("user_image");
if(user_image) {
var $attach_user = slide.form.fields_dict.attach_user.$wrapper;
$attach_user.find(".missing-image").toggle(false);
$attach_user.find("img").attr("src", decodeURIComponent(user_image)).toggle(true);
bind_language_events: function(slide) {
slide.get_input("language").unbind("change").on("change", function() {
var 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(r) {
frappe.setup._from_load_messages = true;
frappe.wizard.refresh_slides();
}
});
});
},

delete slide.form.fields_dict.email;
delete slide.form.fields_dict.password;
bind_region_events: function(slide) {
/*
Bind a slide's country, timezone and currency fields
*/
slide.get_input("country").on("change", function() {
var country = slide.get_input("country").val();
var $timezone = slide.get_input("timezone");
var data = frappe.setup.data.regional_data;

$timezone.empty();

// add country specific timezones first
if(country) {
var 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");
}
},
css_class: "single-column"
};

// add all timezones at the end, so that user has the option to change it to any timezone
$timezone.add_options([""].concat(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() {
var currency = slide.get_input("currency").val();
if (!currency) return;
frappe.model.with_doc("Currency", currency, function() {
frappe.provide("locals.:Currency." + currency);
var currency_doc = frappe.model.get_doc("Currency", currency);
var 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);
});
});
},

}

frappe.wiz.on("before_load", function() {
load_frappe_slides();
frappe.setup.on("before_load", function() {
// load slides
frappe_slides.map(frappe.setup.add_slide);

// add welcome slide
frappe.wiz.add_slide(frappe.wiz.welcome);
frappe.wiz.add_slide(frappe.wiz.region);
frappe.wiz.add_slide(frappe.wiz.user);
// set header image
let $icon = $('header .setup-wizard-brand');
if($icon.length === 0) {
$('header').append(`<div class="setup-wizard-brand"">
<img src="/assets/frappe/images/frappe-bird-grey.svg"
class="brand-icon frappe-icon" style="width:36px;"></div>`);
}
});

+ 17
- 1
frappe/desk/page/setup_wizard/setup_wizard.py 查看文件

@@ -179,11 +179,27 @@ def load_messages(language):

@frappe.whitelist()
def load_languages():
language_codes = frappe.db.sql('select language_code, language_name from tabLanguage order by name', as_dict=True)
codes_to_names = {}
for d in language_codes:
codes_to_names[d.language_code] = d.language_name
return {
"default_language": frappe.db.get_value('Language', frappe.local.lang, 'language_name') or frappe.local.lang,
"languages": sorted(frappe.db.sql_list('select language_name from tabLanguage order by name'))
"languages": sorted(frappe.db.sql_list('select language_name from tabLanguage order by name')),
"codes_to_names": codes_to_names
}

@frappe.whitelist()
def load_country():
from frappe.sessions import get_geo_ip_country
return get_geo_ip_country(frappe.local.request_ip) if frappe.local.request_ip else None

@frappe.whitelist()
def load_user_details():
return {
"full_name": frappe.cache().hget("full_name", "signup"),
"email": frappe.cache().hget("email", "signup")
}

def prettify_args(args):
# remove attachments


+ 8
- 4
frappe/desk/page/setup_wizard/setup_wizard_page.html 查看文件

@@ -1,14 +1,18 @@
<div class="container setup-wizard-slide {%= css_class %} with-form" data-slide-name="{%= name %}">
<div class="container setup-wizard-slide single-column with-form" data-slide-name="{%= name %}">
<div class="text-center setup-wizard-progress text-extra-muted">
{% for (var i=0; i < slides_count; i++) { %}
<i class="fa fa-fw fa-circle{% if (i+1<=step) { %} active {% } %}"></i>
<!--dev_mode: link progress bubbles-->
<!--<a href="http://erpnext.domainify:8000/desk#setup-wizard/{%= i %}">-->
<i class="fa fa-fw fa-circle{% if (i+1<=step) { %} active {% } %}"></i>
<!--</a>-->
{% } %}
</div>
<p class="text-center lead">{%= title %}</p>
<p class="lead">{%= title %}</p>
<div class="row">
<div class="col-sm-12">
<div class="setup-wizard-body col-sm-12">
<!-- {% if (help) { %} <p class="text-center">{%= help %}</p> {% } %} -->
<div class="form"></div>
<a class="more-btn hide btn btn-default btn-sm" style="margin-left: 41%;">{%= __("Add More") %}</a>
</div>
</div>
<div class="footer text-right">


+ 12
- 0
frappe/public/css/form.css 查看文件

@@ -530,6 +530,18 @@ select.form-control {
font-weight: bold;
background-color: #fffdf4;
}
.form-control[data-fieldtype="Password"] {
position: inherit;
}
.password-strength-indicator {
float: right;
padding: 15px;
margin-top: -41px;
margin-right: -7px;
}
.password-strength-message {
margin-top: -10px;
}
.form-headline {
padding: 0px 15px;
margin: 0px;


+ 81
- 9
frappe/public/js/frappe/form/control.js 查看文件

@@ -535,9 +535,57 @@ frappe.ui.form.ControlReadOnly = frappe.ui.form.ControlData.extend({
},
});


frappe.ui.form.ControlPassword = frappe.ui.form.ControlData.extend({
input_type: "password"
input_type: "password",
make: function() {
this._super();
},
make_input: function() {
var me = this;
this._super();
this.$input.parent().append($('<span class="password-strength-indicator indicator"></span>'));
this.$wrapper.find('.control-input-wrapper').append($('<p class="password-strength-message text-muted small hidden"></p>'));

this.indicator = this.$wrapper.find('.password-strength-indicator');
this.message = this.$wrapper.find('.help-box');

this.$input.on('input', () => {
var $this = $(this);
clearTimeout($this.data('timeout'));
$this.data('timeout', setTimeout(() => {
var txt = me.$input.val();
me.get_password_strength(txt);
}), 300);
});
},
get_password_strength: function(value) {
var me = this;
frappe.call({
type: 'GET',
method: 'frappe.core.doctype.user.user.test_password_strength',
args: {
new_password: value || ''
},
callback: function(r) {
if (r.message && r.message.entropy) {
var score = r.message.score,
feedback = r.message.feedback;

feedback.crack_time_display = r.message.crack_time_display;

var indicators = ['grey', 'red', 'orange', 'yellow', 'green'];
me.set_strength_indicator(indicators[score]);

}
}

});
},
set_strength_indicator: function(color) {
var message = __("Include symbols, numbers and capital letters in the password");
this.indicator.removeClass().addClass('password-strength-indicator indicator ' + color);
this.message.html(message).removeClass('hidden');
}
});

frappe.ui.form.ControlInt = frappe.ui.form.ControlData.extend({
@@ -1112,31 +1160,55 @@ frappe.ui.form.ControlAttachImage = frappe.ui.form.ControlAttach.extend({
make: function() {
var me = this;
this._super();
this.img_wrapper = $('<div style="margin: 7px 0px;">\
<div class="missing-image attach-missing-image"><i class="octicon octicon-circle-slash"></i></div></div>')
this.img_wrapper = $('<div style="width: 100%; height: calc(100% - 40px); position: relative;">\
<div class="missing-image attach-missing-image"><i class="octicon octicon-device-camera"></i></div></div>')
.appendTo(this.wrapper);
this.img = $("<img class='img-responsive attach-image-display'>")
.appendTo(this.img_wrapper).toggle(false);

this.img_container = $(`<div class='img-container'></div>`);
this.img = $(`<img class='img-responsive attach-image-display'>`)
.appendTo(this.img_container);

this.img_overlay = $(`<div class='img-overlay'>
<span class="overlay-text">Change</span>
</div>`).appendTo(this.img_container);

this.remove_image_link = $('<a style="font-size: 12px;color: #8D99A6;">Remove</a>');

this.img_wrapper.append(this.img_container).append(this.remove_image_link);
// this.img.toggle(false);
// this.img_overlay.toggle(false);
this.img_container.toggle(false);
this.remove_image_link.toggle(false);

// propagate click to Attach button
this.img_wrapper.find(".missing-image").on("click", function() { me.$input.click(); });
this.img.on("click", function() { me.$input.click(); });
this.img_container.on("click", function() { me.$input.click(); });
this.remove_image_link.on("click", function() { me.$value.find(".close").click(); });

this.$wrapper.on("refresh", function() {
$(me.wrapper).find('.btn-attach').addClass('hidden');
me.set_image();
if(me.get_status()=="Read") {
$(me.disp_area).toggle(false);
}
});

this.set_image();
},
set_image: function() {
if(this.get_value()) {
$(this.img_wrapper).find(".missing-image").toggle(false);
this.img.attr("src", this.dataurl ? this.dataurl : this.value).toggle(true);
// this.img.attr("src", this.dataurl ? this.dataurl : this.value).toggle(true);
// this.img_overlay.toggle(true);
this.img.attr("src", this.dataurl ? this.dataurl : this.value);
this.img_container.toggle(true);
this.remove_image_link.toggle(true);
} else {
$(this.img_wrapper).find(".missing-image").toggle(true);
this.img.toggle(false);
// this.img.toggle(false);
// this.img_overlay.toggle(false);
this.img_container.toggle(false);
this.remove_image_link.toggle(false);
}
}
});


+ 31
- 14
frappe/public/js/frappe/form/layout.js 查看文件

@@ -44,29 +44,33 @@ frappe.ui.form.Layout = Class.extend({
this.message.empty().addClass('hidden');
}
},
render: function() {
render: function(new_fields) {
var me = this;

var fields = new_fields || this.fields;
this.section = null;
this.column = null;
if((this.fields[0] && this.fields[0].fieldtype!="Section Break") || !this.fields.length) {
if((fields[0] && fields[0].fieldtype!="Section Break") || !fields.length) {
this.make_section();
}
$.each(this.fields, function(i, df) {
if(df.fieldtype === "Fold") {
me.make_page(df);
} else if (df.fieldtype === "Section Break") {
me.make_section(df);
} else if (df.fieldtype === "Column Break") {
me.make_column(df);
} else {
me.make_field(df);
$.each(fields, function(i, df) {
switch(df.fieldtype) {
case "Fold":
me.make_page(df);
break;
case "Section Break":
me.make_section(df);
break;
case "Column Break":
me.make_column(df);
break;
default:
me.make_field(df);
}
});

},
make_field: function(df, colspan) {
make_field: function(df, colspan, render = false) {
!this.section && this.make_section();
!this.column && this.make_column();

@@ -74,7 +78,8 @@ frappe.ui.form.Layout = Class.extend({
df: df,
doctype: this.doctype,
parent: this.column.wrapper.get(0),
frm: this.frm
frm: this.frm,
render_input: render
});

fieldobj.layout = this;
@@ -226,6 +231,18 @@ frappe.ui.form.Layout = Class.extend({
}
},

refresh_fields: function(fields) {
let fieldnames = fields.map((field) => {
if(field.label) return field.label;
});

this.fields_list.map(fieldobj => {
if(fieldnames.includes(fieldobj._label)) {
fieldobj.refresh();
}
});
},

refresh_section_count: function() {
this.wrapper.find(".section-count-label:visible").each(function(i) {
$(this).html(i+1);


+ 4
- 2
frappe/public/js/frappe/misc/common.js 查看文件

@@ -81,9 +81,11 @@ frappe.get_abbr = function(txt, max_length) {
}

frappe.gravatars = {};
frappe.get_gravatar = function(email_id) {
frappe.get_gravatar = function(email_id, size = 0) {
var param = size ? ('s=' + size) : 'd=retro';
if(!frappe.gravatars[email_id]) {
frappe.gravatars[email_id] = "https://secure.gravatar.com/avatar/" + md5(email_id) + "?d=retro";
// TODO: check if gravatar exists
frappe.gravatars[email_id] = "https://secure.gravatar.com/avatar/" + md5(email_id) + "?" + param;
}
return frappe.gravatars[email_id];
}


+ 5
- 1
frappe/public/js/frappe/ui/field_group.js 查看文件

@@ -41,6 +41,10 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({
})
}
},
add_fields: function(fields) {
this.render(fields);
this.refresh_fields(fields);
},
first_button: false,
catch_enter_as_submit: function() {
var me = this;
@@ -119,5 +123,5 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({
f.set_input(f.df['default'] || '');
}
}
}
},
});

+ 15
- 0
frappe/public/less/form.less 查看文件

@@ -666,6 +666,21 @@ select.form-control {
background-color: @extra-light-yellow;
}

.form-control[data-fieldtype="Password"] {
position: inherit;
}

.password-strength-indicator {
float: right;
padding: 15px;
margin-top: -41px;
margin-right: -7px;
}

.password-strength-message {
margin-top: -10px;
}

.form-headline {
padding: 0px 15px;
margin: 0px;


+ 43
- 0
frappe/tests/ui/setup_wizard.js 查看文件

@@ -0,0 +1,43 @@
var login = require("./login.js")['Login'];

module.exports = {
before: browser => {
browser
.url(browser.launch_url + '/login')
.waitForElementVisible('body', 5000);
},
'Login': login,
'Welcome': browser => {
let slide_selector = '[data-slide-name="welcome"]';
browser
.assert.title('Frappe Desk')
.pause(5000)
.assert.visible(slide_selector, 'Check if welcome slide is visible')
.assert.value('select[data-fieldname="language"]', 'English')
.click(slide_selector + ' .next-btn');
},
'Region': browser => {
let slide_selector = '[data-slide-name="region"]';
browser
.waitForElementVisible(slide_selector , 2000)
.pause(6000)
.setValue('select[data-fieldname="language"]', "India")
.pause(4000)
.assert.containsText('div[data-fieldname="timezone"]', 'India Time - Asia/Kolkata')
.click(slide_selector + ' .next-btn');
},
'User': browser => {
let slide_selector = '[data-slide-name="user"]';
browser
.waitForElementVisible(slide_selector, 2000)
.pause(3000)
.setValue('input[data-fieldname="full_name"]', "John Doe")
.setValue('input[data-fieldname="email"]', "john@example.com")
.setValue('input[data-fieldname="password"]', "vbjwearghu")
.click(slide_selector + ' .next-btn');
},

after: browser => {
browser.end();
},
};

Loading…
取消
儲存