Merge pull request #3882 from pratu16x7/user-progress

User Progress dialog
This commit is contained in:
Rushabh Mehta 2017-09-04 11:13:35 +05:30 committed by GitHub
commit a8a10a9d72
26 changed files with 1595 additions and 582 deletions

View file

@ -344,6 +344,26 @@ def run_ui_tests(context, app=None, test=False, profile=False):
if os.environ.get('CI'):
sys.exit(ret)
@click.command('run-setup-wizard-ui-test')
@click.option('--app', help="App to run tests on, leave blank for all apps")
@click.option('--profile', is_flag=True, default=False)
@pass_context
def run_setup_wizard_ui_test(context, app=None, profile=False):
"Run setup wizard UI test"
import frappe.test_runner
site = get_site(context)
frappe.init(site=site)
frappe.connect()
ret = frappe.test_runner.run_setup_wizard_ui_test(app=app, verbose=context.verbose,
profile=profile)
if len(ret.failures) == 0 and len(ret.errors) == 0:
ret = 0
if os.environ.get('CI'):
sys.exit(ret)
@click.command('serve')
@click.option('--port', default=8000)
@click.option('--profile', is_flag=True, default=False)
@ -485,6 +505,7 @@ commands = [
reset_perms,
run_tests,
run_ui_tests,
run_setup_wizard_ui_test,
serve,
set_config,
watch,

View file

@ -159,6 +159,36 @@
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fieldname": "is_first_startup",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Is First Startup",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_on_submit": 0,
@ -1186,8 +1216,8 @@
"issingle": 1,
"istable": 0,
"max_attachments": 0,
"modified": "2017-08-07 23:29:18.858797",
"modified_by": "Administrator",
"modified": "2017-08-31 14:53:31.065925",
"modified_by": "ewfds@wfe.ef",
"module": "Core",
"name": "System Settings",
"name_case": "",

View file

@ -156,7 +156,6 @@ def get_notifications_for_targets(config, notification_percent):
return doc_target_percents
def clear_notifications(user=None):
if frappe.flags.in_install:
return

View file

@ -1,146 +0,0 @@
#page-setup-wizard {
margin-top: 30px;
}
.setup-wizard-slide {
padding-left: 0px;
padding-right: 0px;
}
@media (min-width: 768px) {
.setup-wizard-slide {
max-width: 500px;
}
}
.setup-wizard-slide .lead {
margin: 30px;
color: #777777;
text-align: center;
font-size: 24px;
}
.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 {
font-weight: 500;
}
.setup-wizard-slide .form-control.bold {
background-color: #fff;
}
.setup-wizard-slide.with-form {
margin: 60px 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 0px;
}
.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;
}
.setup-wizard-slide .fa-fw {
vertical-align: middle;
font-size: 10px;
}
.setup-wizard-slide .fa-fw.active {
color: #5e64ff;
}
.setup-wizard-slide .icon-circle-blank {
font-size: 7px;
}
.setup-wizard-slide .icon-circle {
font-size: 10px;
}
.setup-wizard-slide .frappe-control[data-fieldtype="Attach Image"] {
width: 140px;
height: 180px; /*depends on presence of heading*/
margin-top: 20px;
}
.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;
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 {
position: relative;
top: 50%;
transform: translate(0px, -50%);
-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;
}

View file

@ -1,5 +1,6 @@
frappe.provide("frappe.wiz");
frappe.provide("frappe.setup");
frappe.provide("frappe.setup.events");
frappe.provide("frappe.ui");
frappe.setup = {
slides: [],
@ -7,7 +8,6 @@ frappe.setup = {
data: {},
utils: {},
remove_app_slides: [],
on: function(event, fn) {
if(!frappe.setup.events[event]) {
frappe.setup.events[event] = [];
@ -29,277 +29,226 @@ frappe.pages['setup-wizard'].on_page_load = function(wrapper) {
// setup page ui
$(".navbar:first").toggle(false);
var requires = ["/assets/frappe/css/animate.min.css"].concat(frappe.boot.setup_wizard_requires || []);
var requires = ["/assets/frappe/css/animate.min.css"].concat(
frappe.boot.setup_wizard_requires || []);
frappe.require(requires, function() {
frappe.setup.run_event("before_load");
var wizard_settings = {
page_name: "setup-wizard",
parent: wrapper,
slides: frappe.setup.slides,
title: __("Welcome")
slide_class: frappe.setup.SetupWizardSlide,
unidirectional: 1,
before_load: ($footer) => {
$footer.find('.next-btn').removeClass('btn-default')
.addClass('btn-primary');
$footer.find('.text-right').prepend(
$(`<a class="complete-btn btn btn-sm primary">
${__("Complete Setup")}</a>`));
}
}
frappe.wizard = new frappe.setup.Wizard(wizard_settings);
frappe.wizard = new frappe.setup.SetupWizard(wizard_settings);
frappe.setup.run_event("after_load");
// frappe.wizard.values = test_values_edu;
var route = frappe.get_route();
let route = frappe.get_route();
if(route) {
frappe.wizard.show(route[1]);
frappe.wizard.show_slide(route[1]);
}
});
}
};
frappe.pages['setup-wizard'].on_page_show = function(wrapper) {
if(frappe.get_route()[1]) {
frappe.wizard && frappe.wizard.show(frappe.get_route()[1]);
frappe.wizard && frappe.wizard.show_slide(frappe.get_route()[1]);
}
}
};
frappe.setup.on("before_load", function() {
// load slides
frappe.setup.slides_settings.map(frappe.setup.add_slide);
});
frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides {
constructor(args = {}) {
super(args);
$.extend(this, args);
frappe.setup.Wizard = Class.extend({
init: function(opts) {
$.extend(this, opts);
this.make();
this.slides;
this.slide_dict = {};
this.values = {};
this.welcomed = true;
this.page_name = "setup-wizard";
frappe.set_route("setup-wizard/0");
},
make: function() {
this.parent = $('<div class="setup-wizard-wrapper">').appendTo(this.parent);
},
get_message: function(html) {
return $(repl('<div data-state="setup-complete">\
<div style="padding: 40px;" class="text-center">%(html)s</div>\
</div>', {html:html}))
},
show_working: function() {
this.hide_current_slide();
frappe.set_route(this.page_name);
this.current_slide = {"$wrapper": this.get_message(this.working_html()).appendTo(this.parent)};
},
show_complete: function() {
this.hide_current_slide();
this.current_slide = {"$wrapper": this.get_message(this.complete_html()).appendTo(this.parent)};
},
show: function(id) {
}
make() {
super.make();
this.container.addClass("container setup-wizard-slide with-form");
this.$next_btn.addClass('action');
this.$complete_btn = this.$footer.find('.complete-btn').addClass('action');
}
before_show_slide() {
if(!this.welcomed) {
frappe.set_route(this.page_name);
return;
return false;
}
id = cint(id);
if(this.current_slide && this.current_slide.id===id) {
return;
return true;
}
show_slide(id) {
super.show_slide(id);
frappe.set_route(this.page_name, 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.bind(this));
} else {
this.$next_btn.addClass("btn-primary").show();
this.$complete_btn.removeClass("btn-primary").hide();
}
}
this.update_values();
if(!this.slide_dict[id]) {
this.slide_dict[id] = new frappe.setup.WizardSlide($.extend(this.slides[id], {wiz:this, id:id}));
this.slide_dict[id].make();
}
this.hide_current_slide();
this.current_slide = this.slide_dict[id];
this.current_slide.$wrapper.removeClass("hidden");
},
hide_current_slide: function() {
if(this.current_slide) {
this.current_slide.$wrapper.addClass("hidden");
this.current_slide = null;
}
},
get_values: function() {
var values = {};
$.each(this.slide_dict, function(id, slide) {
if(slide.values) {
$.extend(values, slide.values);
}
});
return values;
},
working_html: function() {
var msg = $(frappe.render_template("setup_wizard_message", {
image: "/assets/frappe/images/ui/bubble-tea-smile.svg",
title: __("Setting Up"),
message: __('Sit tight while your system is being setup. This may take a few moments.')
}));
msg.find(".setup-wizard-message-image").addClass("animated infinite bounce");
return msg.html();
},
complete_html: function() {
return frappe.render_template("setup_wizard_message", {
image: "/assets/frappe/images/ui/bubble-tea-happy.svg",
title: __('Setup Complete'),
message: ""
});
},
on_complete: function() {
var me = this;
this.update_values();
this.show_working();
return frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete",
args: {args: this.values},
callback: function(r) {
me.show_complete();
if(frappe.setup.welcome_page) {
localStorage.setItem("session_last_route", frappe.setup.welcome_page);
}
setTimeout(function() {
window.location = "/desk";
}, 2000);
},
error: function(r) {
var d = frappe.msgprint(__("There were errors."));
d.custom_onhide = function() {
frappe.set_route(me.page_name, me.slides.length - 1);
};
}
});
},
update_values: function() {
this.values = $.extend(this.values, this.get_values());
},
refresh_slides: function() {
// reset all slides so that labels are translated
var me = this;
if(this.in_refresh_slides) {
refresh_slides() {
// For Translations, etc.
if(this.in_refresh_slides || !this.current_slide.set_values()) {
return;
}
this.in_refresh_slides = true;
if(!this.current_slide.set_values()) {
return;
}
this.update_values();
frappe.setup.slides = [];
frappe.setup.run_event("before_load");
// remove slides listed in remove_app_slides
var new_slides = [];
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.in_refresh_slides = false;
}
action_on_complete() {
var me = this;
if (!this.current_slide.set_values()) return;
this.update_values();
this.show_working_state();
return frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete",
args: {args: this.values},
callback: function() {
me.show_setup_complete_state();
if(frappe.setup.welcome_page) {
localStorage.setItem("session_last_route", frappe.setup.welcome_page);
}
window.location = "/desk";
window.location.reload();
setTimeout(function() {
// frappe.ui.toolbar.clear_cache();
window.location = "/desk";
}, 2000);
setTimeout(()=> {
$('body').removeClass('setup-state');
}, 20000);
},
error: function() {
var d = frappe.msgprint(__("There were errors."));
d.custom_onhide = function() {
$(me.parent).find('.setup-state').remove();
$('body').removeClass('setup-state');
me.container.show();
frappe.set_route(me.page_name, me.slides.length - 1);
};
}
});
}
get_setup_slides_filtered_by_domain() {
var filtered_slides = [];
frappe.setup.slides.forEach(function(slide) {
if(frappe.setup.domain) {
var domains = slide.domains;
if (domains.indexOf('all') !== -1 ||
domains.indexOf(frappe.setup.domain.toLowerCase()) !== -1) {
new_slides.push(slide);
filtered_slides.push(slide);
}
} else {
new_slides.push(slide);
filtered_slides.push(slide);
}
})
frappe.setup.slides = new_slides;
this.slides = frappe.setup.slides;
frappe.setup.run_event("after_load");
// re-render all slides
this.slide_dict = {};
var current_id = this.current_slide.id;
this.current_slide.destroy();
this.show(current_id);
this.in_refresh_slides = false;
return filtered_slides;
}
});
frappe.setup.WizardSlide = Class.extend({
init: function(opts) {
$.extend(this, opts);
this.$wrapper = $('<div class="slide-wrapper hidden"></div>')
.appendTo(this.wiz.parent)
.attr("data-slide-id", this.id);
},
make: function() {
var me = this;
if(this.$body) this.$body.remove();
show_working_state() {
this.container.hide();
$('body').addClass('setup-state');
frappe.set_route(this.page_name);
var fields = JSON.parse(JSON.stringify(this.fields));
this.working_state_message = this.get_message(
__("Setting Up"),
__("Sit tight while your system is being setup. This may take a few moments."),
true
).appendTo(this.parent);
if(this.add_more) {
this.count = 1;
fields = fields.map((field, i) => {
if(field.fieldname) {
field.fieldname += '_1';
this.current_id = this.slides.length;
this.current_slide = null;
this.completed_state_message = this.get_message(
__("Setup Complete"),
__("You're all set!")
);
}
show_setup_complete_state() {
this.working_state_message.hide();
this.completed_state_message.appendTo(this.parent);
}
get_message(title, message="", loading=false) {
return $(`<div data-state="setup">
<div class="page-card">
<div class="page-card-head">
<span class="indicator blue">
${title}</span>
</div>
<p>${message}</p>
<div class="state-icon-container">
${loading
? '<div style="width:100%;height:100%" class="lds-rolling state-icon"><div></div></div>'
: `<div style="width:100%;height:100%" class="state-icon"><i class="fa fa-check-circle text-extra-muted"
style="font-size: 64px; margin-top: -8px;">
</i></div>`
}
if(i === 1 && this.mandatory_entry) {
field.reqd = 1;
}
if(!field.static) {
if(field.label) field.label += ' 1';
}
return field;
});
}
</div>
</div>
</div>`);
}
};
if(this.before_load) {
this.before_load(this);
}
frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide {
constructor(slide = null) {
super(slide);
}
this.$body = $(frappe.render_template("setup_wizard_page", {
help: __(this.help),
title:__(this.title),
main_title:__(this.wiz.title),
step: this.id + 1,
name: this.name,
slides_count: this.wiz.slides.length
})).appendTo(this.$wrapper);
this.body = this.$body.find(".form")[0];
if(this.fields) {
this.form = new frappe.ui.FieldGroup({
fields: fields,
body: this.body,
no_submit_on_enter: true
});
this.form.make();
} else {
$(this.body).html(this.html);
}
this.set_reqd_fields();
make() {
super.make();
this.set_init_values();
this.make_prev_next_buttons();
if(this.add_more) this.bind_more_button();
this.reset_action_button_state();
}
var $primary_btn = this.$next ? this.$next : this.$complete;
this.bind_fields_to_next($primary_btn);
if(this.onload) {
this.onload(this);
}
this.set_reqd_fields();
this.bind_fields_to_next($primary_btn);
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() {
set_init_values () {
var me = this;
// set values from frappe.setup.values
if(frappe.wizard.values && this.fields) {
@ -310,141 +259,21 @@ frappe.setup.WizardSlide = Class.extend({
}
});
}
},
}
set_values: function() {
this.values = this.form.get_values();
if(this.values===null) {
return false;
}
if(this.validate && !this.validate()) {
return false;
}
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.static) {
if(field.label) field.label += ' ' + this.count;
}
return field;
}));
if(this.count === this.max_count) {
this.$more.addClass('hide');
}
});
},
// Frappe slides settings
// ======================================================
make_prev_next_buttons: function() {
var me = this;
// prev
if(this.id > 0) {
this.$prev = this.$body.find('.prev-btn')
.removeClass("hide")
.attr('tabIndex', 0)
.click(function() {
me.prev();
})
.css({"margin-right": "10px"});
}
// next or complete
if(this.id+1 < this.wiz.slides.length) {
this.$next = this.$body.find('.next-btn')
.removeClass("hide")
.attr('tabIndex', 0)
.click(this.next_or_complete.bind(this));
} else {
this.$complete = this.$body.find('.complete-btn')
.removeClass("hide")
.attr('tabIndex', 0)
.click(this.next_or_complete.bind(this));
}
// setup mousefree navigation
this.$body.on('keypress', function(e) {
if(e.which === 13) {
var $target = $(e.target);
if($target.hasClass('prev-btn')) {
me.prev();
} else if($target.hasClass('btn-attach')) {
//do nothing
} else {
me.next_or_complete();
e.preventDefault();
}
}
});
},
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) {
this.next();
} else {
this.wiz.on_complete(this.wiz);
}
}
},
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();
}.bind(this), 0);
},
next: function() {
frappe.set_route(this.wiz.page_name, this.id+1 + "");
},
prev: function() {
frappe.set_route(this.wiz.page_name, this.id-1 + "");
},
get_input: function(fn) {
return this.form.get_input(fn);
},
get_field: function(fn) {
return this.form.get_field(fn);
},
destroy: function() {
this.$body.remove();
if(frappe.wizard.current_slide===this) {
frappe.wizard.current_slide = null;
}
},
});
var frappe_slides = [
frappe.setup.slides_settings = [
{
// Welcome (language) slide
name: "welcome",
domains: ["all"],
title: __("Hello!"),
icon: "fa fa-world",
help: __("Let's prepare the system for first use."),
// help: __("Let's prepare the system for first use."),
fields: [
{ fieldname: "language", label: __("Your Language"),
@ -455,13 +284,13 @@ var frappe_slides = [
if (frappe.setup.data.lang) {
this.setup_fields(slide);
} else {
utils.load_languages(slide, this.setup_fields);
frappe.setup.utils.load_languages(slide, this.setup_fields);
}
},
setup_fields: function(slide) {
utils.setup_language_field(slide);
utils.bind_language_events(slide);
frappe.setup.utils.setup_language_field(slide);
frappe.setup.utils.bind_language_events(slide);
},
},
@ -471,7 +300,7 @@ var frappe_slides = [
domains: ["all"],
title: __("Select Your Region"),
icon: "fa fa-flag",
help: __("Select your Country, Time Zone and Currency"),
// help: __("Select your Country, Time Zone and Currency"),
fields: [
{ fieldname: "country", label: __("Your Country"), reqd:1,
fieldtype: "Select" },
@ -487,13 +316,13 @@ var frappe_slides = [
if(frappe.setup.data.regional_data) {
this.setup_fields(slide);
} else {
utils.load_regional_data(slide, this.setup_fields);
frappe.setup.utils.load_regional_data(slide, this.setup_fields);
}
},
setup_fields: function(slide) {
utils.setup_region_fields(slide);
utils.bind_region_events(slide);
frappe.setup.utils.setup_region_fields(slide);
frappe.setup.utils.bind_region_events(slide);
}
},
@ -512,7 +341,7 @@ var frappe_slides = [
"fieldtype": "Data", "options":"Email"},
{ "fieldname": "password", "label": __("Password"), "fieldtype": "Password" }
],
help: __('The first user will become the System Manager (you can change this later).'),
// 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.email.$wrapper.toggle(false);
@ -542,7 +371,7 @@ var frappe_slides = [
slide.form.fields_dict.password.df.reqd = 1;
slide.form.fields_dict.password.refresh();
utils.load_user_details(slide, this.setup_fields);
frappe.setup.utils.load_user_details(slide, this.setup_fields);
}
},
@ -564,7 +393,7 @@ var frappe_slides = [
}
];
var utils = {
frappe.setup.utils = {
load_languages: function(slide, callback) {
frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.load_languages",
@ -714,10 +543,4 @@ var utils = {
});
});
},
}
frappe.setup.on("before_load", function() {
// load slides
frappe_slides.map(frappe.setup.add_slide);
});
};

View file

@ -151,6 +151,7 @@ def add_all_roles_to(name):
def disable_future_access():
frappe.db.set_default('desktop:home_page', 'desktop')
frappe.db.set_value('System Settings', 'System Settings', 'setup_complete', 1)
frappe.db.set_value('System Settings', 'System Settings', 'is_first_startup', 1)
if not frappe.flags.in_test:
# remove all roles and add 'Administrator' to prevent future access
@ -202,6 +203,10 @@ def load_user_details():
"email": frappe.cache().hget("email", "signup")
}
@frappe.whitelist()
def reset_is_first_startup():
frappe.db.set_value('System Settings', 'System Settings', 'is_first_startup', 0)
def prettify_args(args):
# remove attachments
for key, val in args.items():

View file

@ -1,7 +0,0 @@
<div class="container setup-wizard-slide">
<img class="img-responsive setup-wizard-message-image" src="{%= image %}">
<p class="text-center lead">{%= title %}</p>
<p class="text-center">{%= message %}</p>
</div>

View file

@ -1,25 +0,0 @@
<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++) { %}
<!--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="lead">{%= title %}</p>
<div class="row">
<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">
<div>
<a class="prev-btn hide grey small">{%= __("Previous") %}</a>
<a class="next-btn hide btn btn-primary btn-sm">{%= __("Next") %}</a>
<a class="complete-btn hide btn btn-primary btn-sm"><b>{%= __("Complete Setup") %}</b></a>
</div>
</div>
</div>

View file

@ -0,0 +1,30 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# MIT License. See license.txt
from __future__ import unicode_literals
import frappe
from frappe.utils import cint
@frappe.whitelist()
def get_user_progress_slides():
'''
Return user progress slides for the desktop (called via `get_user_progress_slides` hook)
'''
slides = []
if cint(frappe.db.get_single_value('System Settings', 'setup_complete')):
for fn in frappe.get_hooks('get_user_progress_slides'):
slides += frappe.get_attr(fn)()
return slides
@frappe.whitelist()
def update_and_get_user_progress():
'''
Return setup progress action states (called via `update_and_get_user_progress` hook)
'''
states = {}
for fn in frappe.get_hooks('update_and_get_user_progress'):
states.update(frappe.get_attr(fn)())
return states

View file

@ -61,7 +61,6 @@
"public/js/frappe/form/link_selector.js",
"public/js/frappe/form/multi_select_dialog.js",
"public/js/frappe/ui/dialog.js",
"public/js/frappe/form/controls/base_control.js",
"public/js/frappe/form/controls/base_input.js",
"public/js/frappe/form/controls/data.js",
@ -162,6 +161,7 @@
"public/js/frappe/ui/page.html",
"public/js/frappe/ui/page.js",
"public/js/frappe/ui/slides.js",
"public/js/frappe/ui/find.js",
"public/js/frappe/ui/iconbar.js",
"public/js/frappe/form/layout.js",

View file

@ -441,7 +441,7 @@ fieldset[disabled] .form-control {
}
}
@media (min-width: 768px) {
.video-modal {
.video-modal .modal-dialog {
width: 700px;
}
}
@ -510,7 +510,7 @@ fieldset[disabled] .form-control {
margin-right: 10px;
}
a.progress-small .progress-chart {
width: 60px;
width: 40px;
margin-top: 4px;
float: right;
}
@ -518,6 +518,20 @@ a.progress-small .progress {
margin-bottom: 0;
}
a.progress-small .progress-bar {
transition: unset;
background-color: #98d85b;
}
li.user-progress .progress-chart {
width: 60px;
margin-top: 8px;
}
li.user-progress .progress {
margin-bottom: 0;
background-color: #fff;
border: 1px solid #e5e7e9;
}
li.user-progress .progress-bar {
transition: unset;
background-color: #98d85b;
}
/* on small screens, show only icons on top */
@ -1070,3 +1084,69 @@ input[type="checkbox"]:checked:before {
margin: -2px 0 0 3px;
border: 1px solid rgba(0, 0, 0, 0.25);
}
.slides-wrapper .fa-circle {
font-size: 10px;
margin: 0px 2px;
}
.slides-wrapper .fa-circle.active {
color: #5e64ff;
}
.slides-wrapper .fa-circle.link {
cursor: pointer;
}
.slides-wrapper .form {
margin-top: 30px;
}
.slides-wrapper .form .form-layout {
margin-top: 0px;
margin-bottom: 0px;
}
.slides-wrapper .form .form-section {
padding: 0px 7px;
border: none;
}
.slides-wrapper .add-more {
margin-bottom: 30px;
}
.slides-wrapper .lead {
margin-top: 20px;
}
.slides-wrapper .success-state {
margin-bottom: 20px;
}
.slides-wrapper .next-steps-links .title {
text-transform: uppercase;
color: #8D99A6;
font-size: 11px;
}
.slides-wrapper .btn-primary {
font-weight: bold;
}
.slides-wrapper .footer {
margin-top: 15px;
padding: 0px 7px;
}
.slides-wrapper .footer .btn:not(:last-child) {
margin-right: 3px;
}
.slides-wrapper .footer a.btn.make-btn {
margin-right: 7px;
}
.slides-wrapper .footer a.make-btn.disabled {
background-color: #b1bdca;
color: #fff;
border-color: #b1bdca;
}
.user-progress-dialog .slides-progress {
margin-top: 15px;
}
.user-progress-dialog .done-state .check-container {
font-size: 64px;
margin: 40px;
}
.user-progress-dialog .done-state .title {
font-weight: normal;
}
.user-progress-dialog .done-state .help-links a {
margin: 0px 10px;
}

View file

@ -153,3 +153,200 @@ select.input-sm {
font-size: 18px;
}
}
#page-setup-wizard {
margin-top: 30px;
}
.setup-wizard-slide {
padding-left: 0px;
padding-right: 0px;
}
@media (min-width: 768px) {
.setup-wizard-slide {
max-width: 500px;
}
}
.setup-wizard-slide .slides-progress {
margin-top: 20px;
}
.setup-wizard-slide .lead {
margin: 30px;
color: #777777;
text-align: center;
font-size: 24px;
}
.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 {
font-weight: 500;
}
.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 .add-more {
margin: 0px;
}
.setup-wizard-slide .footer {
padding: 30px 7px;
}
.setup-wizard-slide a.next-btn.disabled,
.setup-wizard-slide a.complete-btn.disabled {
background-color: #b1bdca;
color: #fff;
border-color: #b1bdca;
}
.setup-wizard-slide .fa-fw {
vertical-align: middle;
font-size: 10px;
}
.setup-wizard-slide .fa-fw.active {
color: #5e64ff;
}
.setup-wizard-slide .icon-circle-blank {
font-size: 7px;
}
.setup-wizard-slide .icon-circle {
font-size: 10px;
}
.setup-wizard-slide .frappe-control[data-fieldtype="Attach Image"] {
width: 140px;
height: 180px;
/*depends on presence of heading*/
margin-top: 20px;
}
.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;
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, 0.075);
}
.setup-wizard-slide .missing-image .octicon {
position: relative;
top: 50%;
transform: translate(0px, -50%);
-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, 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-state {
background-color: #f5f7fa;
}
.page-card {
max-width: 360px;
padding: 15px;
margin: 70px auto;
border: 1px solid #d1d8dd;
border-radius: 4px;
background-color: #fff;
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1);
}
.page-card .page-card-head {
padding: 10px 15px;
margin: -15px;
margin-bottom: 15px;
border-bottom: 1px solid #d1d8dd;
}
.state-icon-container {
display: flex;
justify-content: center;
}
.state-icon {
position: relative;
width: 100px !important;
height: 100px !important;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes lds-rolling {
0% {
-webkit-transform: translate(-50%, -50%) rotate(0deg);
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
-webkit-transform: translate(-50%, -50%) rotate(360deg);
transform: translate(-50%, -50%) rotate(360deg);
}
}
@-webkit-keyframes lds-rolling {
0% {
-webkit-transform: translate(-50%, -50%) rotate(0deg);
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
-webkit-transform: translate(-50%, -50%) rotate(360deg);
transform: translate(-50%, -50%) rotate(360deg);
}
}
.lds-rolling {
-webkit-transform: translate(-100px, -100px) scale(1) translate(100px, 100px);
transform: translate(-100px, -100px) scale(1) translate(100px, 100px);
}
.lds-rolling div {
position: absolute;
width: 60px;
height: 60px;
border: 3px solid #d1d8dd;
border-top-color: transparent;
border-radius: 50%;
-webkit-animation: lds-rolling 1s linear infinite;
animation: lds-rolling 1s linear infinite;
top: 50px;
left: 50px;
}
.lds-rolling div:after {
position: absolute;
width: 60px;
height: 60px;
border: 3px solid #d1d8dd;
border-top-color: transparent;
border-radius: 50%;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}

View file

@ -24,9 +24,9 @@ frappe.help.show_video = function(youtube_id, title) {
var dialog = frappe.msgprint('<iframe width="'+size[0]+'" height="'+size[1]+'" \
src="https://www.youtube.com/embed/'+ youtube_id +'" \
frameborder="0" allowfullscreen></iframe>' + (frappe.help_feedback_link || ""),
title || __("Help"));
title || __("Help"));
dialog.$wrapper.find(".modal-content").addClass("video-modal");
dialog.$wrapper.addClass("video-modal");
}
$("body").on("click", "a.help-link", function() {

View file

@ -67,15 +67,6 @@ frappe.ui.Dialog = frappe.ui.FieldGroup.extend({
});
},
focus_on_first_input: function() {
if(this.no_focus) return;
$.each(this.fields_list, function(i, f) {
if(!in_list(['Date', 'Datetime', 'Time'], f.df.fieldtype) && f.set_focus) {
f.set_focus();
return false;
}
});
},
get_primary_btn: function() {
return this.$wrapper.find(".modal-header .btn-primary");
},

View file

@ -60,6 +60,15 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({
});
},
first_button: false,
focus_on_first_input: function() {
if(this.no_focus) return;
$.each(this.fields_list, function(i, f) {
if(!in_list(['Date', 'Datetime', 'Time'], f.df.fieldtype) && f.set_focus) {
f.set_focus();
return false;
}
});
},
catch_enter_as_submit: function() {
var me = this;
$(this.body).find('input[type="text"], input[type="password"]').keypress(function(e) {

View file

@ -0,0 +1,393 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.provide("frappe.ui");
frappe.ui.Slide = class Slide {
constructor(slide = null) {
$.extend(this, slide);
this.setup();
}
setup() {
this.$wrapper = $('<div class="slide-wrapper hidden"></div>')
.attr({"data-slide-id": this.id, "data-slide-name": this.name})
.appendTo(this.parent);
}
// Make has to be called manually, to account for on-demand use cases
make() {
if(this.before_load) { this.before_load(this); }
this.$body = $(`<div class="slide-body">
<div class="content text-center">
<p class="title lead">${this.title}</p>
</div>
<div class="form-wrapper">
<div class="form"></div>
<div class="add-more text-center" style="margin-top: 5px;">
<a class="form-more-btn hide btn btn-default btn-xs">${__("Add More")}</a>
</div>
</div>
</div>`).appendTo(this.$wrapper);
this.$content = this.$body.find(".content");
this.$form = this.$body.find(".form");
this.$primary_btn = this.slides_footer.find('.primary');
if(this.help) this.$content.append($(`<p class="slide-help">${this.help}</p>`));
if(this.image_src) this.$content.append(
$(`<img src="${this.image_src}" style="margin: 20px;">`));
this.reqd_fields = [];
this.refresh();
this.made = true;
}
refresh() {
this.render_parent_dots();
if(!this.done) {
this.setup_form();
} else {
this.setup_done_state();
}
}
setup_form() {
this.form = new frappe.ui.FieldGroup({
fields: this.get_atomic_fields(),
body: this.$form[0],
no_submit_on_enter: true
});
this.form.make();
if(this.add_more) this.bind_more_button();
this.set_reqd_fields();
if(this.onload) { this.onload(this); }
this.set_reqd_fields();
}
// Form methods
get_atomic_fields() {
var fields = JSON.parse(JSON.stringify(this.fields));
if(this.add_more) {
this.count = 1;
fields = fields.map((field, i) => {
if(field.fieldname) {
field.fieldname += '_1';
}
if(i === 1 && this.mandatory_entry) {
field.reqd = 1;
}
if(!field.static) {
if(field.label) field.label += ' 1';
}
return field;
});
}
return fields;
}
set_reqd_fields() {
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_values() {
this.values = this.form.get_values();
if(this.values===null) {
return false;
}
if(this.validate && !this.validate()) {
return false;
}
return true;
}
bind_more_button() {
this.$more = this.$body.find('.form-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.static) {
if(field.label) field.label += ' ' + this.count;
}
return field;
}));
if(this.count === this.max_count) {
this.$more.addClass('hide');
}
});
}
// Primary button (outside of slide)
resetup_primary_button() {
this.unbind_primary_action();
this.bind_fields_to_action_btn();
this.reset_action_button_state();
this.bind_primary_action();
}
bind_fields_to_action_btn() {
var me = this;
this.reqd_fields.map((field) => {
field.$wrapper.on('change input', () => {
me.reset_action_button_state();
});
});
}
reset_action_button_state() {
var empty_fields = this.reqd_fields.filter((field) => {
return !field.get_value();
});
if(empty_fields.length) {
this.slides_footer.find('.action').addClass('disabled');
} else {
this.slides_footer.find('.action').removeClass('disabled');
}
}
unbind_primary_action() {
this.slides_footer.find(".primary").off();
}
bind_primary_action() {
this.slides_footer.find(".primary").on('click', () => {
this.primary_action();
});
}
before_show() { }
show_slide() {
this.$wrapper.removeClass("hidden");
this.before_show();
this.resetup_primary_button();
if(!this.done) {
this.$body.find('.form-control').first().focus();
this.$primary_btn.show();
} else {
this.$primary_btn.hide();
}
}
hide_slide() {
this.$wrapper.addClass("hidden");
}
get_input(fieldname) {
return this.form.get_input(fieldname);
}
get_field(fieldname) {
return this.form.get_field(fieldname);
}
destroy() {
this.$body.remove();
}
primary_action() { }
};
frappe.ui.Slides = class Slides {
constructor({
parent = null,
slides = [],
slide_class = null,
unidirectional = 0,
done_state = 0,
before_load = null,
on_update = null
}) {
this.parent = parent;
this.slides = slides;
this.slide_class = slide_class;
this.unidirectional = unidirectional;
this.done_state = done_state;
this.before_load = before_load;
this.on_update = on_update;
this.slide_dict = {};
//In case of refreshing
this.made_slide_ids = [];
this.values = {};
this.make();
}
make() {
this.container = $('<div>').addClass("slides-wrapper")
.appendTo(this.parent);
this.$slide_progress = $(`<div>`).addClass(`slides-progress text-center text-extra-muted`)
.appendTo(this.container);
this.$body = $(`<div>`).addClass(`slide-container`)
.appendTo(this.container);
this.$footer = $(`<div>`).addClass(`footer`)
.appendTo(this.container);
this.render_progress_dots();
this.make_prev_next_buttons();
if(this.before_load) { this.before_load(this.$footer); }
// can be on demand
this.setup();
// can be on demand
this.show_slide(0);
}
setup() {
this.slides.map((slide, id) => {
if(!this.slide_dict[id]) {
this.slide_dict[id] = new (this.slide_class)(
$.extend(this.slides[id], {
parent: this.$body,
slides_footer: this.$footer,
render_parent_dots: this.render_progress_dots.bind(this),
id: id,
})
);
if(!this.unidirectional) {
this.slide_dict[id].make();
}
} else {
if(this.made_slide_ids.includes(id+"")) {
this.slide_dict[id].destroy();
this.slide_dict[id].make();
}
}
});
}
refresh(id) {
this.render_progress_dots();
this.show_hide_prev_next(id);
this.$body.find('.form-control').first().focus();
}
render_progress_dots() {
// Depends on this.unidirectional and this.done_state
// Can be called by a slide to update states
this.$slide_progress.empty();
this.slides.map((slide, id) => {
let $dot = $(`<i class="fa fa-fw fa-circle"> </i> `)
.attr({'data-step-id': id});
if(this.done_state && (this.slide_dict[id] &&
this.slide_dict[id].done || slide.done)) {
$dot.addClass('text-success');
}
if((this.unidirectional && id <= this.current_id) ||
id === this.current_id) {
$dot.addClass('active');
}
// Add pointer event for non-unidirectional
this.$slide_progress.append($dot);
});
this.completed = 0;
this.slides.map((slide, i) => {
if(this.slide_dict[i]) {
if(this.slide_dict[i].done) this.completed++;
} else {
if(slide.done) this.completed++;
}
});
if(this.on_update) {this.on_update(this.completed, this.slides.length);}
if(!this.unidirectional) this.bind_progress_dots();
}
make_prev_next_buttons() {
$(`<div class="row">
<div class="col-sm-6">
<a class="prev-btn btn btn-default btn-sm" tabindex="0">${__("Previous")}</a>
</div>
<div class="col-sm-6 text-right">
<a class="next-btn btn btn-default btn-sm" tabindex="0">${__("Next")}</a>
</div>
</div>`).appendTo(this.$footer);
this.$prev_btn = this.$footer.find('.prev-btn').attr('tabIndex', 0)
.on('click', () => { this.show_slide(this.current_id - 1); });
this.$next_btn = this.$footer.find('.next-btn').attr('tabIndex', 0)
.on('click', () => {
if (!this.unidirectional || (this.unidirectional && this.current_slide.set_values())) {
this.show_slide(this.current_id + 1);
}
});
}
bind_progress_dots() {
var me = this;
this.$slide_progress.find('.fa-circle').addClass('link').on('click', function() {
let id = $(this).attr('data-step-id');
me.show_slide(id);
});
}
before_show_slide() {
return true;
}
show_slide(id) {
id = cint(id);
if(!this.before_show_slide() ||
(this.current_slide && this.current_id===id)) {
return;
}
this.update_values();
if(this.current_slide) this.current_slide.hide_slide();
if(this.unidirectional && !this.slide_dict[id].made) {
this.slide_dict[id].make();
}
this.current_id = id;
this.current_slide = this.slide_dict[id];
this.current_slide.show_slide();
this.refresh(id);
}
destroy_slide(id) {
if(this.slide_dict[id]) this.slide_dict[id].destroy();
this.slide_dict[id] = null;
}
on_update(completed, total) {}
show_hide_prev_next(id) {
(id === 0) ?
this.$prev_btn.hide() : this.$prev_btn.show();
(id + 1 === this.slides.length) ?
this.$next_btn.hide() : this.$next_btn.show();
}
get_values() {
var values = {};
$.each(this.slide_dict, function(id, slide) {
if(slide.values) {
$.extend(values, slide.values);
}
});
return values;
}
update_values() {
this.values = $.extend(this.values, this.get_values());
}
};

View file

@ -35,10 +35,10 @@ frappe.ui.misc.about = function() {
var v = versions[key];
if(v.branch) {
var text = $.format('<p><b>{0}:</b> v{1} ({2})<br></p>',
[v.title, v.branch_version || v.version, v.branch])
[v.title, v.branch_version || v.version, v.branch])
} else {
var text = $.format('<p><b>{0}:</b> v{1}<br></p>',
[v.title, v.version])
[v.title, v.version])
}
$(text).appendTo($wrap);
});

View file

@ -11,6 +11,13 @@
<div class="navbar-center ellipsis" style="display: none;"></div>
<ul class="nav navbar-nav navbar-right">
<li class="user-progress">
<a class="dropdown-toggle" data-toggle="dropdown" href="#" onclick="return false;" style="height: 40px;">
<div class="progress-chart" style"width: 50px; margin-top: 8px;"><div class="progress">
<div class="progress-bar"></div>
</div></div>
</a>
</li>
<li class="visible-xs">
<a class="navbar-search-button" href="#" data-toggle="modal" data-target="#search-modal"><i class="octicon octicon-search"></i></a>
</li>

View file

@ -6,26 +6,32 @@ frappe.provide('frappe.search');
frappe.ui.toolbar.Toolbar = Class.extend({
init: function() {
var header = $('header').append(frappe.render_template("navbar", {
$('header').append(frappe.render_template("navbar", {
avatar: frappe.avatar(frappe.session.user)
}));
$('.dropdown-toggle').dropdown();
this.setup_sidebar();
var awesome_bar = new frappe.search.AwesomeBar();
let awesome_bar = new frappe.search.AwesomeBar();
awesome_bar.setup("#navbar-search");
awesome_bar.setup("#modal-search");
this.setup_help();
this.make();
},
make: function() {
this.setup_sidebar();
this.setup_help();
this.setup_progress_dialog();
this.bind_events();
$(document).trigger('toolbar_setup');
},
bind_events: function() {
$(document).on("notification-update", function() {
frappe.ui.notifications.update_notifications();
});
$('.dropdown-toggle').dropdown();
$(document).trigger('toolbar_setup');
// clear all custom menus on page change
$(document).on("page-change", function() {
$("header .navbar .custom-menu").remove();
@ -41,7 +47,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({
},
setup_sidebar: function () {
var header = $('header');
header.find(".toggle-sidebar").on("click", function () {
var layout_side_section = $('.layout-side-section');
@ -186,10 +191,44 @@ frappe.ui.toolbar.Toolbar = Class.extend({
});
}
}
},
setup_progress_dialog: function() {
var me = this;
$('.user-progress').hide();
frappe.call({
method: "frappe.desk.user_progress.get_user_progress_slides",
callback: function(r) {
if(r.message) {
let slides = r.message;
if(slides.length) {
frappe.require("assets/frappe/js/frappe/ui/toolbar/user_progress_dialog.js", function() {
me.progress_dialog = new frappe.setup.UserProgressDialog({
slides: slides
});
$('.user-progress').show();
$('.user-progress .dropdown-toggle').on('click', () => {
me.progress_dialog.show();
});
if (frappe.boot.is_first_startup) {
me.progress_dialog.show();
frappe.call({
method: "frappe.desk.page.setup_wizard.setup_wizard.reset_is_first_startup",
args: {},
callback: () => {}
});
}
});
}
}
},
freeze: false
});
}
});
$.extend(frappe.ui.toolbar, {
add_dropdown_button: function(parent, label, click, icon) {
var menu = frappe.ui.toolbar.get_menu(parent);

View file

@ -0,0 +1,210 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// License: GNU General Public License v3. See license.txt
frappe.provide("frappe.setup");
frappe.provide("frappe.ui");
frappe.setup.UserProgressSlide = class UserProgressSlide extends frappe.ui.Slide {
constructor(slide = null) {
super(slide);
}
make() {
super.make();
}
setup_done_state() {
this.$body.find(".slide-help").hide();
this.$body.find(".form-wrapper").hide();
this.slides_footer.find('.next-btn').addClass('btn-primary');
this.slides_footer.find('.done-btn').hide();
this.$primary_btn.hide();
this.make_done_state();
}
make_done_state() {
this.$done_state = $(`<div class="done-state text-center">
<div class="check-container"><i class="check fa fa-fw fa-check-circle text-success"></i></div>
<h4 class="title"><a></a></h4>
<div class="help-links"></div>
</div>`).appendTo(this.$body);
this.$done_state_title = this.$done_state.find('.title');
this.$check = this.$done_state.find('.check');
this.$help_links = this.$done_state.find('.help-links');
if(this.done_state_title) {
$("<a>" + this.done_state_title + "</a>").appendTo(this.$done_state_title);
this.$done_state_title.on('click', () => {
frappe.set_route(this.done_state_title_route);
});
}
if(this.help_links) {
this.help_links.map(link => {
let $link = $(`<a target="_blank" class="small text-muted">${link.label}</a>`);
if(link.url) {
$link.attr({"href": link.url});
} else if(link.video_id) {
$link.on('click', () => {
frappe.help.show_video(link.video_id, link.label);
})
}
this.$help_links.append($link);
});
}
}
before_show() {
if(this.done) {
this.slides_footer.find('.next-btn').addClass('btn-primary');
this.slides_footer.find('.done-btn').hide();
} else {
this.slides_footer.find('.next-btn').removeClass('btn-primary');
this.slides_footer.find('.done-btn').show();
}
if(this.dialog_dismissed) {
this.slides_footer.find('.next-btn').removeClass('btn-primary');
}
}
primary_action() {
var me = this;
if(this.set_values()) {
this.slides_footer.find('.make-btn').addClass('disabled');
frappe.call({
method: me.submit_method,
args: {args_data: me.values},
callback: function() {
me.done = 1;
me.refresh();
},
onerror: function() {
me.slides_footer.find('.make-btn').removeClass('disabled');
},
freeze: true
});
}
}
};
frappe.setup.UserProgressDialog = class UserProgressDialog {
constructor({
slides = []
}) {
this.slides = slides;
this.progress_state_dict = {};
this.slides.map(slide => {
this.progress_state_dict[slide.action_name] = slide.done || 0;
});
this.progress_percent = 0;
this.setup();
}
setup() {
this.dialog = new frappe.ui.Dialog({title: __("Complete Setup")});
this.$wrapper = $(this.dialog.$wrapper).addClass('user-progress-dialog');
this.slide_container = new frappe.ui.Slides({
parent: this.dialog.body,
slides: this.slides,
slide_class: frappe.setup.UserProgressSlide,
done_state: 1,
before_load: ($footer) => {
$footer.find('.text-right')
.prepend($(`<a class="done-btn btn btn-default btn-sm">
${__("Mark as Done")}</a>`))
.prepend($(`<a class="make-btn btn btn-primary btn-sm primary action">
${__("Create")}</a>`));
},
on_update: (completed, total) => {
let percent = completed * 100 / total;
$('.user-progress .progress-bar').css({'width': percent + '%'});
if(percent === 100) {
this.dismiss_progress();
}
}
});
this.$wrapper.find('.done-btn').on('click', () => {
this.mark_as_done();
});
this.get_and_update_progress_state();
this.check_for_updates();
}
mark_as_done() {
let me = this;
let current_slide = this.slide_container.current_slide;
frappe.call({
method: current_slide.mark_as_done_method,
args: {action_name: current_slide.action_name},
callback: function() {
current_slide.done = 1;
current_slide.refresh();
},
freeze: true
});
}
check_for_updates() {
this.updater = setInterval(() => {
this.get_and_update_progress_state();
}, 60000);
}
get_and_update_progress_state() {
var me = this;
frappe.call({
method: "frappe.desk.user_progress.update_and_get_user_progress",
callback: function(r) {
// console.log("states", r.message);
let states = r.message;
let changed = 0;
let completed = 0;
Object.keys(states).map(action_name => {
if(states[action_name]) {
completed ++;
}
if(me.progress_state_dict[action_name] != states[action_name]) {
changed = 1;
me.progress_state_dict[action_name] = states[action_name];
}
});
if(changed) {
Object.keys(me.slide_container.slide_dict).map((id) => {
let slide = me.slide_container.slide_dict[id];
if(me.progress_state_dict[slide.action_name]) {
if(!slide.done) {
slide.done = 1;
slide.refresh();
}
}
});
}
me.progress_percent = completed / Object.keys(states).length * 100;
me.update_progress();
},
freeze: false
});
}
update_progress() {
$('.user-progress .progress-bar').css({'width': this.progress_percent + '%'});
if(this.progress_percent === 100) {
this.dismiss_progress();
}
}
dismiss_progress() {
$('.user-progress').addClass('hide');
clearInterval(this.updater);
}
show() {
this.dialog.show();
}
};

View file

@ -246,7 +246,7 @@ textarea.form-control {
}
@media (min-width: 768px) {
.video-modal {
.video-modal .modal-dialog {
width: 700px;
}
}
@ -333,7 +333,7 @@ textarea.form-control {
a.progress-small {
.progress-chart {
width: 60px;
width: 40px;
margin-top: 4px;
float: right;
}
@ -343,6 +343,25 @@ a.progress-small {
}
.progress-bar {
transition: unset;
background-color: #98d85b;
}
}
li.user-progress {
.progress-chart {
width: 60px;
margin-top: 8px;
}
.progress {
margin-bottom: 0;
background-color: #fff;
border: 1px solid #e5e7e9;
}
.progress-bar {
transition: unset;
background-color: #98d85b;
}
}
@ -1005,3 +1024,92 @@ input[type="checkbox"] {
}
}
// Slides
.slides-wrapper {
.fa-circle {
font-size: 10px;
margin: 0px 2px;
&.active {
color: #5e64ff;
}
&.link {
cursor: pointer;
}
}
.form {
margin-top: 30px;
.form-layout {
margin-top: 0px;
margin-bottom: 0px;
}
.form-section {
padding: 0px 7px;
border: none;
}
}
.add-more {
margin-bottom: 30px;
}
.lead {
margin-top: 20px;
}
.success-state {
margin-bottom: 20px;
}
.next-steps-links {
.title {
text-transform: uppercase;
color: #8D99A6;
font-size: 11px;
}
}
.btn-primary {
font-weight: bold;
}
.footer {
margin-top: 15px;
padding: 0px 7px;
.btn:not(:last-child) {
margin-right: 3px;
}
a.btn.make-btn {
margin-right: 7px;
}
a.make-btn.disabled {
background-color: #b1bdca;
color: #fff;
border-color: #b1bdca;
}
}
}
// User Progress Dialog
.user-progress-dialog {
.slides-progress {
margin-top: 15px;
}
.done-state {
.check-container {
font-size: 64px;
margin: 40px;
}
.title {
font-weight: normal;
}
.help-links {
a {
margin: 0px 10px;
}
}
}
}

View file

@ -187,3 +187,226 @@ select.input-sm {
font-size: 18px;
}
}
#page-setup-wizard {
margin-top: 30px;
}
.setup-wizard-slide {
padding-left: 0px;
padding-right: 0px;
.slides-progress {
margin-top: 20px;
}
.lead {
margin: 30px;
color: #777777;
text-align: center;
font-size: 24px;
}
.col-sm-12 {
padding: 0px;
}
.section-body .col-sm-6 {
&:first-child {
padding-left: 0px;
}
&:last-child {
padding-right: 0px;
}
}
.form-control {
font-weight: 500;
}
.form-control.bold {
background-color: #fff;
}
.add-more {
margin: 0px;
}
.footer {
padding: 30px 7px;
}
a.next-btn.disabled {
background-color: #b1bdca;
color: #fff;
border-color: #b1bdca;
}
a.complete-btn.disabled {
background-color: #b1bdca;
color: #fff;
border-color: #b1bdca;
}
.fa-fw {
vertical-align: middle;
font-size: 10px;
}
.fa-fw.active {
color: #5e64ff;
}
.icon-circle-blank {
font-size: 7px;
}
.icon-circle {
font-size: 10px;
}
.frappe-control[data-fieldtype="Attach Image"] {
width: 140px;
height: 180px;
margin-top: 20px;
.form-group {
display: none;
}
.clearfix {
display: none;
}
}
}
.missing-image {
display: block;
position: relative;
border-radius: 4px;
border: 1px solid #d1d8dd;
border-radius: 6px;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);
.octicon {
position: relative;
top: 50%;
transform: translate(0px, -50%);
-webkit-transform: translate(0px, -50%);
}
}
.attach-image-display {
display: block;
position: relative;
border-radius: 4px;
}
.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);
}
.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;
&:hover {
opacity: 1;
cursor: pointer;
}
}
}
.setup-state {
background-color: #f5f7fa;
}
.page-card {
max-width: 360px;
padding: 15px;
margin: 70px auto;
border: 1px solid #d1d8dd;
border-radius: 4px;
background-color: #fff;
box-shadow: 0px 3px 6px rgba(0, 0, 0, 0.1);
.page-card-head {
padding: 10px 15px;
margin: -15px;
margin-bottom: 15px;
border-bottom: 1px solid #d1d8dd;
}
}
.state-icon-container {
display: flex;
justify-content: center;
}
.state-icon {
position: relative;
width: 100px !important;
height: 100px !important;
display: flex;
justify-content: center;
align-items: center;
}
@keyframes lds-rolling {
0% {
-webkit-transform: translate(-50%, -50%) rotate(0deg);
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
-webkit-transform: translate(-50%, -50%) rotate(360deg);
transform: translate(-50%, -50%) rotate(360deg);
}
}
@-webkit-keyframes lds-rolling {
0% {
-webkit-transform: translate(-50%, -50%) rotate(0deg);
transform: translate(-50%, -50%) rotate(0deg);
}
100% {
-webkit-transform: translate(-50%, -50%) rotate(360deg);
transform: translate(-50%, -50%) rotate(360deg);
}
}
.lds-rolling {
-webkit-transform: translate(-100px, -100px) scale(1) translate(100px, 100px);
transform: translate(-100px, -100px) scale(1) translate(100px, 100px);
div {
position: absolute;
width: 60px;
height: 60px;
border: 3px solid #d1d8dd;
border-top-color: transparent;
border-radius: 50%;
-webkit-animation: lds-rolling 1s linear infinite;
animation: lds-rolling 1s linear infinite;
top: 50px;
left: 50px;
&:after {
position: absolute;
width: 60px;
height: 60px;
border: 3px solid #d1d8dd;
border-top-color: transparent;
border-radius: 50%;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
}
}

View file

@ -163,6 +163,7 @@ def get():
# check only when clear cache is done, and don't cache this
if frappe.local.request:
bootinfo["change_log"] = get_change_log()
bootinfo["is_first_startup"] = cint(frappe.db.get_single_value('System Settings', 'is_first_startup'))
bootinfo["metadata_version"] = frappe.cache().get_value("metadata_version")
if not bootinfo["metadata_version"]:

View file

@ -139,6 +139,11 @@ def run_tests_for_module(module, verbose=False, tests=(), profile=False):
return _run_unittest(module=module, verbose=verbose, tests=tests, profile=profile)
def run_setup_wizard_ui_test(app=None, verbose=False, profile=False):
'''Run setup wizard UI test using test_test_runner'''
frappe.flags.run_setup_wizard_ui_test = 1
return run_ui_tests(app, None, verbose, profile)
def run_ui_tests(app=None, test=None, verbose=False, profile=False):
'''Run a single unit test for UI using test_test_runner'''
module = importlib.import_module('frappe.tests.ui.test_test_runner')

View file

@ -4,9 +4,15 @@ import unittest, os, frappe, time
class TestTestRunner(unittest.TestCase):
def test_test_runner(self):
if frappe.flags.run_setup_wizard_ui_test:
for setup_wizard_test in frappe.get_hooks("setup_wizard_test"):
passed = frappe.get_attr(setup_wizard_test)()
self.assertTrue(passed)
return
driver = TestDriver()
driver.login()
frappe.db.set_default('in_selenium', '1')
driver.login()
for test in get_tests():
if test.startswith('#'):
continue
@ -26,6 +32,7 @@ class TestTestRunner(unittest.TestCase):
driver.set_route('Form', 'Test Runner')
driver.click_primary_action()
driver.wait_for('#frappe-qunit-done', timeout=timeout)
console = driver.get_console()
passed = 'Tests Passed' in console
if frappe.flags.tests_verbose or not passed:
@ -62,4 +69,3 @@ def get_tests_for(app):
with open(tests_path, 'r') as fileobj:
tests = fileobj.read().strip().splitlines()
return tests

View file

@ -87,6 +87,10 @@ class TestDriver(object):
elem = self.find(xpath='//input[@data-fieldname="{0}"]'.format(fieldname))
elem[0].send_keys(text)
def set_select(self, fieldname, text):
elem = self.find(xpath='//select[@data-fieldname="{0}"]'.format(fieldname))
elem[0].send_keys(text)
def set_text_editor(self, fieldname, text):
elem = self.find(xpath='//div[@data-fieldname="{0}"]//div[@contenteditable="true"]'.format(fieldname))
elem[0].send_keys(text)
@ -99,7 +103,7 @@ class TestDriver(object):
selector = self.cur_route + " " + selector
return self.driver.find_elements_by_css_selector(selector)
def wait_for(self, selector=None, everywhere=False, timeout=20, xpath=None):
def wait_for(self, selector=None, everywhere=False, timeout=20, xpath=None, for_invisible=False):
if self.cur_route and not everywhere:
selector = self.cur_route + " " + selector
@ -112,8 +116,12 @@ class TestDriver(object):
selector = xpath
try:
elem = self.get_wait(timeout).until(
EC.presence_of_element_located((_by, selector)))
if not for_invisible:
elem = self.get_wait(timeout).until(
EC.presence_of_element_located((_by, selector)))
else:
elem = self.get_wait(timeout).until(
EC.invisibility_of_element_located((_by, selector)))
return elem
except Exception as e:
# body = self.driver.find_element_by_id('body_div')
@ -121,6 +129,9 @@ class TestDriver(object):
self.print_console()
raise e
def wait_for_invisible(self, selector=None, everywhere=False, timeout=20, xpath=None):
self.wait_for(selector, everywhere, timeout, xpath, True)
def get_console(self):
out = []
for entry in self.driver.get_log('browser'):
@ -193,8 +204,11 @@ class TestDriver(object):
def execute_script(self, js):
self.driver.execute_script(js)
def wait_for_ajax(self):
def wait_for_ajax(self, freeze = False):
self.wait_for('body[data-ajax-state="complete"]', True)
if freeze:
self.wait_for_invisible(".freeze-message-container")
# def go_to_module(module_name, item=None):
# global cur_route