From 5f988c3d7b2504a63117c97065a957e7dcfff32b Mon Sep 17 00:00:00 2001 From: pratu16x7 Date: Wed, 9 Aug 2017 10:21:48 +0530 Subject: [PATCH 01/20] [user-progress] first cut --- frappe/desk/notifications.py | 3 +- frappe/desk/page/setup_wizard/setup_wizard.js | 2 +- frappe/desk/user_progress.py | 17 ++ frappe/public/build.json | 6 +- frappe/public/css/desk.css | 41 ++- .../js/frappe/form/user_progress_dialog.js | 253 ++++++++++++++++++ .../js/frappe/form/user_progress_slide.html | 44 +++ frappe/public/js/frappe/ui/toolbar/about.js | 4 +- .../public/js/frappe/ui/toolbar/navbar.html | 7 + frappe/public/js/frappe/ui/toolbar/toolbar.js | 26 +- frappe/public/less/desk.less | 52 +++- 11 files changed, 447 insertions(+), 8 deletions(-) create mode 100644 frappe/desk/user_progress.py create mode 100644 frappe/public/js/frappe/form/user_progress_dialog.js create mode 100644 frappe/public/js/frappe/form/user_progress_slide.html diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index d0ee87a209..280bfe5d0a 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -30,6 +30,7 @@ def get_notifications(): "open_count_module": get_notifications_for_modules(config, notification_count), "open_count_other": get_notifications_for_other(config, notification_count), "targets": get_notifications_for_targets(config, notification_percent), + "user_progress": config.get("user_progress"), "new_messages": get_new_messages() } @@ -209,7 +210,7 @@ def get_notification_config(): config = frappe._dict() for notification_config in frappe.get_hooks().notification_config: nc = frappe.get_attr(notification_config)() - for key in ("for_doctype", "for_module", "for_other", "targets"): + for key in ("for_doctype", "for_module", "for_other", "targets", "user_progress"): config.setdefault(key, {}) config[key].update(nc.get(key, {})) return config diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 5ea6412977..a6fefab6ac 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -1,4 +1,4 @@ -frappe.provide("frappe.wiz"); +frappe.provide("frappe.setup"); frappe.provide("frappe.setup.events"); frappe.setup = { diff --git a/frappe/desk/user_progress.py b/frappe/desk/user_progress.py new file mode 100644 index 0000000000..f22adf8890 --- /dev/null +++ b/frappe/desk/user_progress.py @@ -0,0 +1,17 @@ +# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals + +import frappe + +@frappe.whitelist() +def get_user_progress_slides(): + ''' + Return user progress slides for the desktop (called via `get_user_progress_slides` hook) + ''' + slides = [] + for fn in frappe.get_hooks('get_user_progress_slides'): + slides += frappe.get_attr(fn)() + + return slides diff --git a/frappe/public/build.json b/frappe/public/build.json index 5035a1fec0..3bb9b26b66 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -89,7 +89,9 @@ "public/js/frappe/form/controls/read_only.js", "public/js/frappe/form/controls/button.js", "public/js/frappe/form/controls/html.js", - "public/js/frappe/form/controls/heading.js" + "public/js/frappe/form/controls/heading.js", + "public/js/frappe/form/user_progress_dialog.js", + "public/js/frappe/form/user_progress_slide.html" ], "css/desk.min.css": [ "public/js/lib/datepicker/datepicker.min.css", @@ -169,6 +171,8 @@ "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/user_progress_dialog.js", + "public/js/frappe/form/user_progress_slide.html", "public/js/frappe/ui/app_icon.js", "public/js/frappe/model/model.js", diff --git a/frappe/public/css/desk.css b/frappe/public/css/desk.css index 22ecdb993b..dabcdf6172 100644 --- a/frappe/public/css/desk.css +++ b/frappe/public/css/desk.css @@ -510,7 +510,7 @@ fieldset[disabled] .form-control { margin-right: 10px; } a.progress-small .progress-chart { - width: 60px; + width: 50px; margin-top: 4px; float: right; } @@ -520,6 +520,18 @@ a.progress-small .progress { a.progress-small .progress-bar { 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 { + background-color: #98d85b; +} /* on small screens, show only icons on top */ @media (max-width: 767px) { .module-view-layout .nav-stacked > li { @@ -1070,3 +1082,30 @@ input[type="checkbox"]:checked:before { margin: -2px 0 0 3px; border: 1px solid rgba(0, 0, 0, 0.25); } +.dialog-slide .fa-circle.active { + color: #5e64ff; +} +.dialog-slide .fa-check-circle.active { + color: #98d85b; +} +.dialog-slide .form { + margin-top: 15px; +} +.dialog-slide .form .form-section { + padding: 0px 7px; + border: none; +} +.dialog-slide .lead { + margin-top: 20px; +} +.dialog-slide .success-state { + margin-bottom: 20px; +} +.dialog-slide .footer { + margin-top: 20px; +} +.dialog-slide .footer a.make-btn.disabled { + background-color: #b1bdca; + color: #fff; + border-color: #b1bdca; +} diff --git a/frappe/public/js/frappe/form/user_progress_dialog.js b/frappe/public/js/frappe/form/user_progress_dialog.js new file mode 100644 index 0000000000..aeaa7444e3 --- /dev/null +++ b/frappe/public/js/frappe/form/user_progress_dialog.js @@ -0,0 +1,253 @@ +frappe.provide("frappe.setup"); + +frappe.setup.ProgressSlide = Class.extend({ + init: function(opts) { + $.extend(this, opts); + this.$wrapper = $('') + .appendTo(this.dialog.d.body) + .attr("data-slide-id", this.id); + }, + make: function() { + var me = this; + if(this.$body) this.$body.remove(); + + if(this.before_load) { + this.before_load(this); + } + this.$body = $(frappe.render_template("user_progress_slide", { + help: __(this.help), + title:__(this.title), + image_src: __(this.image_src), + main_title:__(this.dialog.title), + step: this.id + 1, + name: this.name, + slides_count: this.dialog.slides.length, + success_states: this.dialog.slides.map((slide, i) => { + if(this.dialog.slide_dict[i]) { + return this.dialog.slide_dict[i].done || 0; + } else { + return slide.done || 0; + } + }) + })).appendTo(this.$wrapper); + + this.make_prev_next_buttons(); + + this.content = this.$body.find(".form")[0]; + if(!this.done) { + this.$make = this.$body.find('.make-btn') + .removeClass("hide") + .attr('tabIndex', 0) + .click(this.make_records.bind(this)); + this.setup_form(); + } else { + this.setup_success_state(); + } + }, + setup_success_state: function() { + console.log(this); + this.$success_state = this.$body.find(".success-state").removeClass("hide"); + if(this.doctype) { + this.$body.find('.doctype-actions').removeClass("hide"); + this.$list = this.$body.find('.list-btn').on('click', () => { + frappe.set_route("List", this.doctype); + }); + } else if (this.route) { + this.$body.find('.doc-actions').removeClass("hide"); + this.$doc = this.$body.find('.doc-btn').on('click', () => { + frappe.set_route(this.route); + }); + } + }, + setup_form: function() { + 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; + }); + } + if(this.fields) { + this.form = new frappe.ui.FieldGroup({ + fields: fields, + body: this.content, + no_submit_on_enter: true + }); + this.form.make(); + } else { + $(this.content).html(this.html); + } + + this.set_reqd_fields(); + + if(this.add_more) this.bind_more_button(); + var $primary_btn = this.$make; + this.bind_fields_to_make($primary_btn); + + if(this.onload) { + this.onload(this); + } + this.set_reqd_fields(); + this.bind_fields_to_make($primary_btn); + this.reset_make($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_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'); + } + }); + }, + make_prev_next_buttons: function() { + if(this.id > 0) { + this.$prev = this.$body.find('.prev-btn') + .removeClass("hide") + .attr('tabIndex', 0) + .click(() => { this.prev(); }) + .css({"margin-right": "10px"}); + } + if(this.id+1 < this.dialog.slides.length) { + this.$next = this.$body.find('.next-btn') + .removeClass("hide") + .attr('tabIndex', 0) + .click(this.next.bind(this)); + } + }, + bind_fields_to_make: function($primary_btn) { + var me = this; + this.reqd_fields.map((field) => { + field.$wrapper.on('change input', () => { + me.reset_make($primary_btn); + }); + }); + }, + reset_make: 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); + }, + make_records: function() { + var me = this; + if(this.set_values()) { + frappe.call({ + method: me.method, + args: {args_data: me.form.get_values()}, + callback: function(r) { + me.done = 1; + me.make(); + let completed = 0; + me.dialog.slides.map((slide, i) => { + if(me.dialog.slide_dict[i]) { + if(me.dialog.slide_dict[i].done) completed++; + } else { + if(slide.done) completed++; + } + }) + let percent = completed * 100 / me.dialog.slides.length; + $('.user-progress .progress-bar').css({'width': percent + '%'}); + }, + freeze: true + }); + } + }, + next: function() { + this.dialog.show_slide(this.id + 1); + }, + prev: function() { + this.dialog.show_slide(this.id - 1); + } +}); + +frappe.setup.UserProgressDialog = class UserProgressDialog { + constructor({ + slides = [] + }) { + this.slides = slides; + this.setup(); + } + + setup() { + this.d = new frappe.ui.Dialog({title: __("Setup Tour")}); + this.slide_dict = {}; + this.slides.map((slide, id) => { + if(!this.slide_dict[id]) { + this.slide_dict[id] = new frappe.setup.ProgressSlide( + $.extend(this.slides[id], {dialog: this, id:id}) + ); + this.slide_dict[id].make(); + } + }); + this.show_slide(0); + } + + show() { + // Calculate the progress number and render bar + // render slide states + this.d.show(); + } + + show_slide(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() { + if(this.current_slide) { + this.current_slide.$wrapper.addClass("hidden"); + this.current_slide = null; + } + } + +}; \ No newline at end of file diff --git a/frappe/public/js/frappe/form/user_progress_slide.html b/frappe/public/js/frappe/form/user_progress_slide.html new file mode 100644 index 0000000000..6c0b47b689 --- /dev/null +++ b/frappe/public/js/frappe/form/user_progress_slide.html @@ -0,0 +1,44 @@ +
+
+ {% for (var i=0; i < slides_count; i++) { %} + + {% } %} +
+
+
+
+

{%= title %}

+

{%= help %}

+ +
+

+

{%= __("Completed!") %}

+
+ + +
+ +
+
+ {%= __("Add More") %} +
+
+
+ +
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 @@