Merge pull request #3882 from pratu16x7/user-progress
User Progress dialog
This commit is contained in:
commit
a8a10a9d72
26 changed files with 1595 additions and 582 deletions
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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": "",
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
30
frappe/desk/user_progress.py
Normal file
30
frappe/desk/user_progress.py
Normal 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
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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() {
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
393
frappe/public/js/frappe/ui/slides.js
Normal file
393
frappe/public/js/frappe/ui/slides.js
Normal 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());
|
||||
}
|
||||
};
|
||||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
210
frappe/public/js/frappe/ui/toolbar/user_progress_dialog.js
Normal file
210
frappe/public/js/frappe/ui/toolbar/user_progress_dialog.js
Normal 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();
|
||||
}
|
||||
};
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"]:
|
||||
|
|
|
|||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue