diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index fdebc17e76..7c18bdbc78 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -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, diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 6405a275bf..42f873509a 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -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": "", diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index d0ee87a209..07725423f4 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -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 diff --git a/frappe/desk/page/setup_wizard/setup_wizard.css b/frappe/desk/page/setup_wizard/setup_wizard.css deleted file mode 100644 index f61ea87863..0000000000 --- a/frappe/desk/page/setup_wizard/setup_wizard.css +++ /dev/null @@ -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; -} diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 5ea6412977..ba481f9f18 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -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( + $(` + ${__("Complete Setup")}`)); + + } } - - 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 = $('
').appendTo(this.parent); - }, - get_message: function(html) { - return $(repl('
\ -
%(html)s
\ -
', {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 = $('') - .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 $(`
+
+
+ + ${title} +
+

${message}

+
+ ${loading + ? '
' + : `
+
` } - if(i === 1 && this.mandatory_entry) { - field.reqd = 1; - } - if(!field.static) { - if(field.label) field.label += ' 1'; - } - return field; - }); - } +
+
+
`); + } +}; - 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); -}); +}; diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index 87a574cba2..db9f29fc80 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -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(): diff --git a/frappe/desk/page/setup_wizard/setup_wizard_message.html b/frappe/desk/page/setup_wizard/setup_wizard_message.html deleted file mode 100644 index e2f6bbc51d..0000000000 --- a/frappe/desk/page/setup_wizard/setup_wizard_message.html +++ /dev/null @@ -1,7 +0,0 @@ -
- - -

{%= title %}

- -

{%= message %}

-
diff --git a/frappe/desk/page/setup_wizard/setup_wizard_page.html b/frappe/desk/page/setup_wizard/setup_wizard_page.html deleted file mode 100644 index 565dab563a..0000000000 --- a/frappe/desk/page/setup_wizard/setup_wizard_page.html +++ /dev/null @@ -1,25 +0,0 @@ -
-
- {% for (var i=0; i < slides_count; i++) { %} - - - - - {% } %} -
-

{%= title %}

-
- -
- -
diff --git a/frappe/desk/user_progress.py b/frappe/desk/user_progress.py new file mode 100644 index 0000000000..f62bb2a29d --- /dev/null +++ b/frappe/desk/user_progress.py @@ -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 diff --git a/frappe/public/build.json b/frappe/public/build.json index 5035a1fec0..15dc9e313b 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -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", diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index 22ecdb993b..f5193dac9d 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -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; +} diff --git a/frappe/public/css/page.css b/frappe/public/css/page.css index 66a7bbd836..1f54563454 100644 --- a/frappe/public/css/page.css +++ b/frappe/public/css/page.css @@ -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); +} diff --git a/frappe/public/js/frappe/misc/help.js b/frappe/public/js/frappe/misc/help.js index f67628142b..1d3402c216 100644 --- a/frappe/public/js/frappe/misc/help.js +++ b/frappe/public/js/frappe/misc/help.js @@ -24,9 +24,9 @@ frappe.help.show_video = function(youtube_id, title) { var dialog = frappe.msgprint('' + (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() { diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 8ab1019a54..8163cf5ec8 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -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"); }, diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index 5df46c55ef..c2a21825b9 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -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) { diff --git a/frappe/public/js/frappe/ui/slides.js b/frappe/public/js/frappe/ui/slides.js new file mode 100644 index 0000000000..7d2fc295ac --- /dev/null +++ b/frappe/public/js/frappe/ui/slides.js @@ -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 = $('') + .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 = $(`
+
+

${this.title}

+
+
+
+ +
+
`).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($(`

${this.help}

`)); + if(this.image_src) this.$content.append( + $(``)); + + 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 = $('
').addClass("slides-wrapper") + .appendTo(this.parent); + this.$slide_progress = $(`
`).addClass(`slides-progress text-center text-extra-muted`) + .appendTo(this.container); + this.$body = $(`
`).addClass(`slide-container`) + .appendTo(this.container); + this.$footer = $(`
`).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 = $(` `) + .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() { + $(``).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()); + } +}; \ No newline at end of file diff --git a/frappe/public/js/frappe/ui/toolbar/about.js b/frappe/public/js/frappe/ui/toolbar/about.js index 3ef036ad5f..234c5ee5bf 100644 --- a/frappe/public/js/frappe/ui/toolbar/about.js +++ b/frappe/public/js/frappe/ui/toolbar/about.js @@ -35,10 +35,10 @@ frappe.ui.misc.about = function() { var v = versions[key]; if(v.branch) { var text = $.format('

{0}: v{1} ({2})

', - [v.title, v.branch_version || v.version, v.branch]) + [v.title, v.branch_version || v.version, v.branch]) } else { var text = $.format('

{0}: v{1}

', - [v.title, v.version]) + [v.title, v.version]) } $(text).appendTo($wrap); }); diff --git a/frappe/public/js/frappe/ui/toolbar/navbar.html b/frappe/public/js/frappe/ui/toolbar/navbar.html index fb49d558b3..4e92bc9ac5 100644 --- a/frappe/public/js/frappe/ui/toolbar/navbar.html +++ b/frappe/public/js/frappe/ui/toolbar/navbar.html @@ -11,6 +11,13 @@