Mark as done and progress updates

This commit is contained in:
pratu16x7 2017-08-28 09:49:30 +05:30
parent 2a0901af02
commit 81d2edf0df
8 changed files with 257 additions and 89 deletions

View file

@ -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

View file

@ -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

View file

@ -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;
}

View file

@ -26,7 +26,7 @@ frappe.help.show_video = function(youtube_id, title) {
frameborder="0" allowfullscreen></iframe>' + (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() {

View file

@ -25,7 +25,7 @@ frappe.ui.Slide = class Slide {
</div>
<div class="form-wrapper">
<div class="form"></div>
<div class="text-center" style="margin-top: 5px;">
<div class="text-center" style="margin-top: 5px;margin-bottom: 30px;">
<a class="form-more-btn hide btn btn-default btn-xs">${__("Add More")}</a>
</div>
</div>
@ -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($(`<p class="slide-help">${this.help}</p>`));
if(this.image_src) this.$content.append(

View file

@ -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

View file

@ -18,35 +18,34 @@ frappe.ui.ActionCard = class {
<div class="img-container">
<img src="" class="clip">
<div class="image-overlay hide"></div>
<i class="play fa fa-fw fa-play-circle hide"></i>
</div>
<div class="content-container">
<h5 class="title"></h5>
<div class="content"></div>
<div class="action-area">
<div class="actions"></div>
<div class="help-links"></div>
</div>
<i class="check pull-right fa fa-fw fa-check-circle text-success"
style="font-size: 24px;"></i>
<div class="action-area"></div>
</div>
<i class="check pull-right fa fa-fw fa-check-circle text-success"></i>
</div>`);
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 = $(`<button class="btn btn-default btn-sm">${action.label}</button>`);
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 = $(`<a target="_blank" href="${link.url}">${link.label}</a>`);
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 = $(`<div class="progress-chart">
<div class="progress"><div class="progress-bar"></div></div>
</div>`);
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(
$(`<a class="make-btn btn btn-primary btn-sm primary">
${__("Create")}</a>`));
$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">
${__("Create")}</a>`));
},
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');
}

View file

@ -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;
}
}