diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index 583878a26e..07725423f4 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -30,7 +30,6 @@ 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": get_user_progress_status(config), "new_messages": get_new_messages() } @@ -157,19 +156,6 @@ def get_notifications_for_targets(config, notification_percent): return doc_target_percents -def get_user_progress_status(config): - "User Progress status based on predefined setup slides" - user_progress_status = {} - for key, val in config.user_progress.iteritems(): - if "default" in val and val["default"] in frappe.defaults.get_defaults(): - doc_name = frappe.defaults.get_defaults()[val["default"]] - field_value = frappe.db.get_value(val["doctype"], doc_name, val["field"]) - user_progress_status[key] = int(field_value > val["min_value"]) - elif "min_count" in val: - user_progress_status[key] = int(frappe.db.count(val["doctype"]) > val["min_count"]) - - return user_progress_status - def clear_notifications(user=None): if frappe.flags.in_install: return @@ -222,7 +208,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", "user_progress"): + for key in ("for_doctype", "for_module", "for_other", "targets"): config.setdefault(key, {}) config[key].update(nc.get(key, {})) return config diff --git a/frappe/desk/user_progress.py b/frappe/desk/user_progress.py index f22adf8890..15877dc767 100644 --- a/frappe/desk/user_progress.py +++ b/frappe/desk/user_progress.py @@ -15,3 +15,14 @@ def 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/css/desk.css b/frappe/public/css/desk.css index 2049e0a170..f8893f1f66 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; } } @@ -1093,7 +1093,7 @@ input[type="checkbox"]:checked:before { cursor: pointer; } .slides-wrapper .form { - margin-top: 15px; + margin-top: 30px; } .slides-wrapper .form .form-layout { margin-top: 0px; @@ -1121,6 +1121,12 @@ input[type="checkbox"]:checked:before { 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; @@ -1132,6 +1138,7 @@ input[type="checkbox"]:checked:before { border: 1px solid #e5e5e5; border-radius: 3px; display: flex; + position: relative; } .cards-container .card-container.done { background-color: #fafbfc; @@ -1142,12 +1149,35 @@ input[type="checkbox"]:checked:before { .cards-container .card-container.single_action { cursor: pointer; } +.cards-container .card-container.single_action .image-overlay { + opacity: 0.1; +} .cards-container .title { margin-top: 0px; } .cards-container .content { font-size: 12px; } +.cards-container .img-container { + position: relative; +} +.cards-container .img-container .image-overlay { + position: absolute; + top: 0; + background-color: #fff; + width: 100%; + height: 100%; + opacity: 0; +} +.cards-container .img-container .fa-play-circle { + position: absolute; + font-size: 42px; + left: calc(33%); + top: 25%; + color: #fff; + opacity: 0.8; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.35); +} .cards-container img { width: 195px; } @@ -1157,11 +1187,34 @@ input[type="checkbox"]:checked:before { } .cards-container .content-container { padding: 10px; - width: 516px; + width: 92%; + position: relative; } -.cards-container .actions { +.cards-container .action-area { margin-top: 15px; + font-size: 12px; } -.cards-container .actions button:not(:first-child) { +.cards-container .action-area button:not(:first-child), +.cards-container .action-area a:not(:first-child) { margin-left: 15px; } +.cards-container .check { + position: absolute; + font-size: 24px; + right: 10px; + top: calc(40%); +} +.user-progress-dialog .modal-title { + display: inline-block; +} +.user-progress-dialog .progress-chart { + display: inline-block; + margin-left: 15px; + width: 60px; +} +.user-progress-dialog .progress { + margin-bottom: 0px; +} +.user-progress-dialog .progress-bar { + background-color: #98d85b; +} diff --git a/frappe/public/js/frappe/misc/help.js b/frappe/public/js/frappe/misc/help.js index 754bc25185..1d3402c216 100644 --- a/frappe/public/js/frappe/misc/help.js +++ b/frappe/public/js/frappe/misc/help.js @@ -26,7 +26,7 @@ frappe.help.show_video = function(youtube_id, title) { frameborder="0" allowfullscreen>' + (frappe.help_feedback_link || ""), 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/slides.js b/frappe/public/js/frappe/ui/slides.js index a14702c111..71cbdf0d7b 100644 --- a/frappe/public/js/frappe/ui/slides.js +++ b/frappe/public/js/frappe/ui/slides.js @@ -25,7 +25,7 @@ frappe.ui.Slide = class Slide {
-
+
@@ -33,7 +33,7 @@ frappe.ui.Slide = class Slide { this.$content = this.$body.find(".content"); this.$form = this.$body.find(".form"); - this.$primary_btn = this.slides_footer.find('.btn-primary').addClass('primary'); + this.$primary_btn = this.slides_footer.find('.primary'); if(this.help) this.$content.append($(`

${this.help}

`)); if(this.image_src) this.$content.append( diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js index c1d6e8ac6b..315a675a76 100644 --- a/frappe/public/js/frappe/ui/toolbar/toolbar.js +++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js @@ -200,18 +200,6 @@ frappe.ui.toolbar.Toolbar = Class.extend({ callback: function(r) { if(r.message) { let slides = r.message; - let boot_info = frappe.boot.notification_info.user_progress; - let completed = 0; - slides.map(slide => { - let key = slide.name; - if(Object.keys(boot_info).includes(key) && boot_info[key]) { - completed++; - slide.done = 1; - } - }); - let percent = completed * 100 / slides.length; - $('.user-progress .progress-bar').css({'width': percent + '%'}); - frappe.require("assets/frappe/js/frappe/ui/toolbar/user_progress_dialog.js", function() { me.progress_dialog = new frappe.setup.UserProgressDialog({ slides: slides diff --git a/frappe/public/js/frappe/ui/toolbar/user_progress_dialog.js b/frappe/public/js/frappe/ui/toolbar/user_progress_dialog.js index dad21e477a..192ee51c3c 100644 --- a/frappe/public/js/frappe/ui/toolbar/user_progress_dialog.js +++ b/frappe/public/js/frappe/ui/toolbar/user_progress_dialog.js @@ -18,35 +18,34 @@ frappe.ui.ActionCard = class {
+
-
-
- -
- +
+
`); this.property_components = [ - { card_property: 'content', component_name: '$content', class_name: 'content' }, - { card_property: 'image', component_name: '$img_container', class_name: 'img-container'}, - { card_property: 'done', component_name: '$check', class_name: 'check' }, - { card_property: 'actions', component_name: '$actions', class_name: 'actions' }, - { card_property: 'help_links', component_name: '$help_links', class_name: 'help-links' }, + { card_properties: ['content'], component_name: '$content', class_name: 'content' }, + { card_properties: ['image'], component_name: '$img_container', class_name: 'img-container'}, + { card_properties: ['done'], component_name: '$check', class_name: 'check' }, + { card_properties: ['actions', 'help_links'], component_name: '$action_area', class_name: 'action-area' } ]; } setup() { this.property_components.map(d => { - this[d.component_name] = this.container.find('.' + d.class_name); + if(!this[d.component_name]) { + this[d.component_name] = this.container.find('.' + d.class_name); + } }); if(this.data.video_id) { - this.data.image = `http://img.youtube.com/vi/${this.data.video_id}/1.jpg`; + this.data.image = `http://img.youtube.com/vi/${this.data.video_id}/0.jpg`; this.$img_container.find('.image-overlay').removeClass('hide'); + this.$img_container.find('.fa-play-circle').removeClass('hide'); } this.refresh(); @@ -59,9 +58,13 @@ frappe.ui.ActionCard = class { refresh() { // render according to props - this.property_components.map(d => { - if(!this.data[d.card_property]) { - this[d.component_name].hide(); + this.property_components.map(comp => { + let visible = 0; + comp.card_properties.map(d => { + if(this.data[d]) visible = 1; + }); + if(!visible) { + this[comp.component_name].hide(); } }); @@ -82,7 +85,7 @@ frappe.ui.ActionCard = class { if(this.data.actions) { this.data.actions.map(action => { let $btn = $(``); - this.$actions.append($btn); + this.$action_area.append($btn); if(action.route) { $btn.on('click', () => { frappe.set_route(action.route); @@ -97,7 +100,7 @@ frappe.ui.ActionCard = class { if(this.data.help_links) { this.data.help_links.map(link => { let $link = $(`${link.label}`); - this.$help_links.append($link); + this.$action_area.append($link); }); } } @@ -127,8 +130,11 @@ frappe.setup.UserProgressSlide = class UserProgressSlide extends frappe.ui.Slide } setup_done_state() { - this.$body.find(".form-wrapper").hide(); 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_action_cards(); } @@ -154,8 +160,10 @@ frappe.setup.UserProgressSlide = class UserProgressSlide extends frappe.ui.Slide 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(); } } @@ -163,23 +171,16 @@ frappe.setup.UserProgressSlide = class UserProgressSlide extends frappe.ui.Slide var me = this; if(this.set_values()) { frappe.call({ - method: me.method, + method: me.submit_method, args: {args_data: me.values}, callback: function() { me.done = 1; - // hide Create button immediately, or show_slide again - me.slides_footer.find('.next-btn').addClass('btn-primary'); - me.$primary_btn.hide(); me.refresh(); }, freeze: true }); } } - - mark_as_done() { - // most hard - } }; frappe.setup.UserProgressDialog = class UserProgressDialog { @@ -187,47 +188,120 @@ frappe.setup.UserProgressDialog = class UserProgressDialog { slides = [] }) { this.slides = slides; - // Add a progress bar - // show the last visited slide - // Add a mark as done button - // this.progress_state_dict = this.slides.map(); - + 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.$progress = $(`
+
+
`); + this.dialog.header.find('.col-xs-7').append(this.$progress); 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( - $(` - ${__("Create")}`)); + $footer.find('.text-right') + .prepend($(` + ${__("Mark as Done")}`)) + .prepend($(` + ${__("Create")}`)); }, on_update: (completed, total) => { let percent = completed * 100 / total; + this.$wrapper.find('.progress-bar').css({'width': percent + '%'}); $('.user-progress .progress-bar').css({'width': percent + '%'}); if(percent === 100) { $(document).trigger("user-initial-setup-complete"); } } }); + + this.$wrapper.find('.done-btn').on('click', () => { + this.mark_as_done(); + }); + this.make_dismiss_button(); + this.get_and_update_progress_state(); + this.check_for_updates(); } - listen_for_updates() { - // on every notif 30 sec event - this.update_progress_state(); + 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 + }); } - update_progress_state() { - // update states of slides and cards and refresh them - // Update the progress bar in both the toolbar and the dialog + check_for_updates() { + this.updater = setInterval(() => { + this.get_and_update_progress_state(); + }, 60000); + } - // remove on_update from original slides container + 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() { + this.update_progress_bars(); + } + + update_progress_bars() { + this.$wrapper.find('.progress-bar').css({'width': this.progress_percent + '%'}); + $('.user-progress .progress-bar').css({'width': this.progress_percent + '%'}); + if(this.progress_percent === 100) { + $(document).trigger("user-initial-setup-complete"); + } } make_dismiss_button() { @@ -245,6 +319,7 @@ frappe.setup.UserProgressDialog = class UserProgressDialog { } add_finish_slide_and_make_dismissable() { + clearInterval(this.updater); this.$dismiss_button.removeClass('hide'); } diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index 1ed397a3bf..08e223915a 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -246,7 +246,7 @@ textarea.form-control { } @media (min-width: 768px) { - .video-modal { + .video-modal .modal-dialog { width: 700px; } } @@ -1036,7 +1036,7 @@ input[type="checkbox"] { } } .form { - margin-top: 15px; + margin-top: 30px; .form-layout { margin-top: 0px; margin-bottom: 0px; @@ -1067,6 +1067,14 @@ input[type="checkbox"] { 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; @@ -1083,6 +1091,7 @@ input[type="checkbox"] { border: 1px solid #e5e5e5; border-radius: 3px; display: flex; + position: relative; &.done { background-color: #fafbfc; @@ -1094,12 +1103,12 @@ input[type="checkbox"] { &.single_action { cursor: pointer; - - // .title { - // color: blue; + .image-overlay { + opacity: 0.1; + } + // .fa-play-circle{ + // opacity: 1; // } - - // emphasize image overlay } } @@ -1110,9 +1119,28 @@ input[type="checkbox"] { .content { font-size: 12px; } - // .img-container { + .img-container { + position: relative; - // } + .image-overlay { + position: absolute; + top: 0; + background-color: #fff; + width: 100%; + height: 100%; + opacity: 0; + } + + .fa-play-circle { + position: absolute; + font-size: 42px; + left: calc(50% - 17px); + top: 25%; + color: #fff; + opacity: 0.8; + text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.35); + } + } img { width: 195px; @@ -1125,14 +1153,41 @@ input[type="checkbox"] { .content-container { padding: 10px; - width: 516px; + width: 92%; + position: relative; } - .actions { + .action-area { margin-top: 15px; + font-size: 12px; - button:not(:first-child) { + button:not(:first-child), a:not(:first-child) { margin-left: 15px; } } + + .check { + position: absolute; + font-size: 24px; + right: 10px; + top: calc(50% - 10px); + } } + +// User Progress Dialog +.user-progress-dialog { + .modal-title { + display: inline-block; + } + .progress-chart { + display: inline-block; + margin-left: 15px; + width: 60px; + } + .progress { + margin-bottom: 0px; + } + .progress-bar { + background-color: #98d85b; + } +} \ No newline at end of file