From d95a98f97b25e63d51d601292345ede5b1e78fd8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 10 Dec 2020 10:40:23 +0530 Subject: [PATCH 01/11] refactor: Make dashboard collapsible --- .../data_import_legacy/data_import_legacy.js | 2 +- frappe/public/js/frappe/form/dashboard.js | 497 ++++++++++++------ frappe/public/js/frappe/form/form.js | 1 + frappe/public/js/frappe/form/layout.js | 24 +- frappe/public/less/form.less | 29 - frappe/public/scss/desk/form.scss | 35 +- 6 files changed, 353 insertions(+), 235 deletions(-) diff --git a/frappe/core/doctype/data_import_legacy/data_import_legacy.js b/frappe/core/doctype/data_import_legacy/data_import_legacy.js index 9a301af76e..8e4f397171 100644 --- a/frappe/core/doctype/data_import_legacy/data_import_legacy.js +++ b/frappe/core/doctype/data_import_legacy/data_import_legacy.js @@ -32,7 +32,7 @@ frappe.ui.form.on('Data Import Legacy', { frm.reload_doc(); } if (data.progress) { - let progress_bar = $(frm.dashboard.progress_area).find(".progress-bar"); + let progress_bar = $(frm.dashboard.progress_area.body).find(".progress-bar"); if (progress_bar) { $(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped"); $(progress_bar).css("width", data.progress + "%"); diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index c9b5ab542c..635629130f 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -1,92 +1,100 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt -frappe.ui.form.Dashboard = Class.extend({ - init: function(opts) { +frappe.ui.form.Dashboard = class FormDashboard { + constructor(opts) { $.extend(this, opts); - this.section = this.frm.fields_dict._form_dashboard.wrapper; - this.parent = this.section.find('.section-body'); - this.wrapper = $(frappe.render_template('form_dashboard', - {frm: this.frm})).appendTo(this.parent); + this.setup_dashboard_sections(); + } - this.progress_area = this.wrapper.find(".progress-area"); - this.heatmap_area = this.wrapper.find('.form-heatmap'); - this.chart_area = this.wrapper.find('.form-graph'); - this.stats_area = this.wrapper.find('.form-stats'); - this.stats_area_row = this.stats_area.find('.row'); - this.links_area = this.wrapper.find('.form-links'); - this.transactions_area = this.links_area.find('.transactions'); + setup_dashboard_sections() { + this.progress_area = new Section(this.parent, { + css_class: 'progress-area', + hidden: 1, + collapsible: 1 + }); - }, - reset: function() { + this.heatmap_area = new Section(this.parent, { + title: __("Overview"), + css_class: 'form-heatmap', + hidden: 1, + collapsible: 1, + body_html: ` +
+ + ` + }); + + this.chart_area = new Section(this.parent, { + title: __("Graph"), + css_class: 'form-graph', + hidden: 1, + collapsible: 1 + }); + + this.stats_area_row = $(`
`); + this.stats_area = new Section(this.parent, { + title: __("Stats"), + css_class: 'form-stats', + hidden: 1, + collapsible: 1, + body_html: this.stats_area_row + }); + + this.links_area = new Section(this.parent, { + title: __("Documents Links"), + css_class: 'form-links', + hidden: 1, + collapsible: 1, + }); + + this.transactions_area = $(`
${text}
`, color); - } else { - this.clear_headline(); - } - }, - - add_section: function(html, section_head=null) { - let section = $(`
`); - if (section_head) { - section.append(`
${section_head}
`); - } - section.append(html); - section.appendTo(this.wrapper); - return section; - }, - - add_progress: function(title, percent, message) { - var progress_chart = this.make_progress_chart(title); - - if(!$.isArray(percent)) { + if (!$.isArray(percent)) { percent = this.format_percent(title, percent); } - var progress = $('
').appendTo(progress_chart); + let progress = $('
').appendTo(progress_chart); + $.each(percent, function(i, opts) { - $(repl('
', opts)).appendTo(progress); + $(`
`).appendTo(progress); }); if (!message) message = ''; @@ -95,9 +103,9 @@ frappe.ui.form.Dashboard = Class.extend({ this.show(); return progress_chart; - }, + } - show_progress: function(title, percent, message) { + show_progress(title, percent, message) { this._progress_map = this._progress_map || {}; let progress_chart = this._progress_map[title]; // create a new progress chart if it doesnt exist @@ -119,19 +127,19 @@ frappe.ui.form.Dashboard = Class.extend({ if (!message) message = ''; progress_chart.find('.progress-message').text(message); - }, + } - hide_progress: function(title) { - if (title){ + hide_progress(title) { + if (title) { this._progress_map[title].remove(); delete this._progress_map[title]; } else { this._progress_map = {}; - this.progress_area.empty(); + this.progress_area.hide(); } - }, + } - format_percent: function(title, percent) { + format_percent(title, percent) { const percentage = cint(percent); const width = percentage < 0 ? 100 : percentage; const progress_class = percentage < 0 ? "progress-bar-danger" : "progress-bar-success"; @@ -141,28 +149,30 @@ frappe.ui.form.Dashboard = Class.extend({ width: width + '%', progress_class: progress_class }]; - }, - make_progress_chart: function(title) { - var progress_chart = $('
') - .appendTo(this.progress_area.removeClass('hidden')); - return progress_chart; - }, + } - refresh: function() { + make_progress_chart(title) { + this.progress_area.show(); + var progress_chart = $('
') + .appendTo(this.progress_area.body); + return progress_chart; + } + + refresh() { this.reset(); - if(this.frm.doc.__islocal) { + if (this.frm.doc.__islocal) { return; } - if(!this.data) { + if (!this.data) { this.init_data(); } var show = false; - if(this.data && ((this.data.transactions || []).length + if (this.data && ((this.data.transactions || []).length || (this.data.reports || []).length)) { - if(this.data.docstatus && this.frm.doc.docstatus !== this.data.docstatus) { + if (this.data.docstatus && this.frm.doc.docstatus !== this.data.docstatus) { // limited docstatus return; } @@ -171,53 +181,53 @@ frappe.ui.form.Dashboard = Class.extend({ show = true; } - if(this.data.heatmap) { + if (this.data.heatmap) { this.render_heatmap(); show = true; } - if(this.data.graph) { + if (this.data.graph) { this.setup_graph(); // show = true; } - if(show) { + if (show) { this.show(); } - }, + } - after_refresh: function() { + after_refresh() { var me = this; // show / hide new buttons (if allowed) - this.links_area.find('.btn-new').each(function() { - if(me.frm.can_create($(this).attr('data-doctype'))) { + this.links_area.body.find('.btn-new').each(function() { + if (me.frm.can_create($(this).attr('data-doctype'))) { $(this).removeClass('hidden'); } }); - }, + } - init_data: function() { + init_data() { this.data = this.frm.meta.__dashboard || {}; - if(!this.data.transactions) this.data.transactions = []; - if(!this.data.internal_links) this.data.internal_links = {}; + if (!this.data.transactions) this.data.transactions = []; + if (!this.data.internal_links) this.data.internal_links = {}; this.filter_permissions(); - }, + } - add_transactions: function(opts) { + add_transactions(opts) { // add additional data on dashboard let group_added = []; - if(!Array.isArray(opts)) opts=[opts]; + if (!Array.isArray(opts)) opts=[opts]; - if(!this.data) { + if (!this.data) { this.init_data(); } - if(this.data && (this.data.transactions || []).length) { + if (this.data && (this.data.transactions || []).length) { // check if label already exists, add items to it this.data.transactions.map(group => { opts.map(d => { - if(d.label == group.label) { + if (d.label == group.label) { group_added.push(d.label); group.items.push(...d.items); } @@ -226,80 +236,81 @@ frappe.ui.form.Dashboard = Class.extend({ // if label not already present, add new label and items under it opts.map(d => { - if(!group_added.includes(d.label)) { + if (!group_added.includes(d.label)) { this.data.transactions.push(d); } }); this.filter_permissions(); } - }, + } - filter_permissions: function() { + filter_permissions() { // filter out transactions for which the user // does not have permission - var transactions = []; + let transactions = []; (this.data.transactions || []).forEach(function(group) { - var items = []; + let items = []; group.items.forEach(function(doctype) { - if(frappe.model.can_read(doctype)) { + if (frappe.model.can_read(doctype)) { items.push(doctype); } }); - // only add thie group, if there is atleast + // only add this group, if there is at-least // one item with permission - if(items.length) { + if (items.length) { group.items = items; transactions.push(group); } }); this.data.transactions = transactions; - }, - render_links: function() { + } + + render_links() { var me = this; - this.links_area.removeClass('hidden'); - this.links_area.find('.btn-new').addClass('hidden'); - if(this.data_rendered) { + this.links_area.show(); + this.links_area.body.find('.btn-new').addClass('hidden'); + if (this.data_rendered) { return; } - //this.transactions_area.empty(); - this.data.frm = this.frm; + let transactions_area_body = this.transactions_area; + $(frappe.render_template('form_links', this.data)) - .appendTo(this.transactions_area) + .appendTo(transactions_area_body); if (this.data.reports && this.data.reports.length) { $(frappe.render_template('report_links', this.data)) - .appendTo(this.transactions_area) + .appendTo(transactions_area_body); } // bind links - this.transactions_area.find(".badge-link").on('click', function() { + transactions_area_body.find(".badge-link").on('click', function() { me.open_document_list($(this).parent()); }); // bind reports - this.transactions_area.find(".report-link").on('click', function() { + transactions_area_body.find(".report-link").on('click', function() { me.open_report($(this).parent()); }); // bind open notifications - this.transactions_area.find('.open-notification').on('click', function() { + transactions_area_body.find('.open-notification').on('click', function() { me.open_document_list($(this).parent(), true); }); // bind new - this.transactions_area.find('.btn-new').on('click', function() { + transactions_area_body.find('.btn-new').on('click', function() { me.frm.make_new($(this).attr('data-doctype')); }); this.data_rendered = true; - }, - open_report: function($link) { + } + open_report($link) { let report = $link.attr('data-report'); let fieldname = this.data.non_standard_fieldnames @@ -308,28 +319,30 @@ frappe.ui.form.Dashboard = Class.extend({ frappe.route_options[fieldname] = this.frm.doc.name; frappe.set_route("query-report", report); - }, - open_document_list: function($link, show_open) { + } + + open_document_list($link, show_open) { // show document list with filters var doctype = $link.attr('data-doctype'), names = $link.attr('data-names') || []; - if(this.data.internal_links[doctype]) { - if(names.length) { + if (this.data.internal_links[doctype]) { + if (names.length) { frappe.route_options = {'name': ['in', names]}; } else { return false; } - } else if(this.data.fieldname) { + } else if (this.data.fieldname) { frappe.route_options = this.get_document_filter(doctype); - if(show_open) { + if (show_open) { frappe.ui.notifications.show_open_count_list(doctype); } } frappe.set_route("List", doctype, "List"); - }, - get_document_filter: function(doctype) { + } + + get_document_filter(doctype) { // return the default filter for the given document // like {"customer": frm.doc.name} var filter = {}; @@ -344,9 +357,10 @@ frappe.ui.form.Dashboard = Class.extend({ filter[fieldname] = this.frm.doc.name; return filter; - }, - set_open_count: function() { - if(!this.data.transactions || !this.data.fieldname) { + } + + set_open_count() { + if (!this.data.transactions || !this.data.fieldname) { return; } @@ -355,7 +369,9 @@ frappe.ui.form.Dashboard = Class.extend({ me = this; this.data.transactions.forEach(function(group) { - group.items.forEach(function(item) { items.push(item); }); + group.items.forEach(function(item) { + items.push(item); + }); }); var method = this.data.method || 'frappe.desk.notifications.get_open_count'; @@ -368,7 +384,7 @@ frappe.ui.form.Dashboard = Class.extend({ items: items }, callback: function(r) { - if(r.message.timeline_data) { + if (r.message.timeline_data) { me.update_heatmap(r.message.timeline_data); } @@ -404,12 +420,13 @@ frappe.ui.form.Dashboard = Class.extend({ } }); - }, - set_badge_count: function(doctype, open_count, count, names) { + } + + set_badge_count(doctype, open_count, count, names) { var $link = $(this.transactions_area) .find('.document-link[data-doctype="'+doctype+'"]'); - if(open_count) { + if (open_count) { $link.find('.open-notification') .removeClass('hidden') .html((open_count > 99) ? '99+' : open_count); @@ -421,24 +438,24 @@ frappe.ui.form.Dashboard = Class.extend({ .text((count > 99) ? '99+' : count); } - if(this.data.internal_links[doctype]) { - if(names && names.length) { + if (this.data.internal_links[doctype]) { + if (names && names.length) { $link.attr('data-names', names ? names.join(',') : ''); } else { $link.find('a').attr('disabled', true); } } - }, + } - update_heatmap: function(data) { - if(this.heatmap) { + update_heatmap(data) { + if (this.heatmap) { this.heatmap.update({dataPoints: data}); } - }, + } // heatmap - render_heatmap: function() { - if(!this.heatmap) { + render_heatmap() { + if (!this.heatmap) { this.heatmap = new frappe.Chart("#heatmap-" + frappe.model.scrub(this.frm.doctype), { type: 'heatmap', start: new Date(moment().subtract(1, 'year').toDate()), @@ -449,32 +466,36 @@ frappe.ui.form.Dashboard = Class.extend({ }); // center the heatmap - this.heatmap_area.removeClass('hidden').find('svg').css({'margin': 'auto'}); + this.heatmap_area.show(); + this.heatmap_area.body.find('svg').css({'margin': 'auto'}); // message - var heatmap_message = this.heatmap_area.find('.heatmap-message'); - if(this.data.heatmap_message) { + var heatmap_message = this.heatmap_area.body.find('.heatmap-message'); + if (this.data.heatmap_message) { heatmap_message.removeClass('hidden').html(this.data.heatmap_message); } else { heatmap_message.addClass('hidden'); } } - }, + } - add_indicator: function(label, color) { + add_indicator(label, color) { this.show(); - this.stats_area.removeClass('hidden'); + this.stats_area.show(); // set colspan var indicators = this.stats_area_row.find('.indicator-column'); var n_indicators = indicators.length + 1; var colspan; - if(n_indicators > 4) { colspan = 3 } - else { colspan = 12 / n_indicators; } + if (n_indicators > 4) { + colspan = 3; + } else { + colspan = 12 / n_indicators; + } // reset classes in existing indicators - if(indicators.length) { + if (indicators.length) { indicators.removeClass().addClass('col-sm-'+colspan).addClass('indicator-column'); } @@ -482,10 +503,10 @@ frappe.ui.form.Dashboard = Class.extend({ +label+'').appendTo(this.stats_area_row); return indicator; - }, + } // graphs - setup_graph: function() { + setup_graph() { var me = this; var method = this.data.graph_method; var args = { @@ -500,7 +521,7 @@ frappe.ui.form.Dashboard = Class.extend({ args: args, callback: function(r) { - if(r.message) { + if (r.message) { me.render_graph(r.message); me.show(); } else { @@ -508,11 +529,11 @@ frappe.ui.form.Dashboard = Class.extend({ } } }); - }, + } - render_graph: function(args) { - var me = this; - this.chart_area.empty().removeClass('hidden'); + render_graph(args) { + this.chart_area.show(); + this.chart_area.body.empty(); $.extend(args, { type: 'line', colors: ['green'], @@ -524,21 +545,151 @@ frappe.ui.form.Dashboard = Class.extend({ this.show(); this.chart = new frappe.Chart('.form-graph', args); - if(!this.chart) { + if (!this.chart) { this.hide(); } - }, + } - show: function() { + show() { this.toggle_visibility(true); - }, + } - hide: function() { + hide() { this.toggle_visibility(false); - }, + } toggle_visibility(show) { - this.section.toggleClass('visible-section', show); - this.section.toggleClass('empty-section', !show); + this.parent.toggleClass('visible-section', show); + this.parent.toggleClass('empty-section', !show); } -}); + + // TODO: Review! code related to headline should be the part of layout/form + set_headline(html, color) { + this.frm.layout.show_message(html, color); + } + + clear_headline() { + this.frm.layout.show_message(); + } + + add_comment(text, alert_class, permanent) { + var me = this; + this.set_headline_alert(text, alert_class); + if (!permanent) { + setTimeout(function() { + me.clear_headline(); + }, 10000); + } + } + + clear_comment() { + this.clear_headline(); + } + + set_headline_alert(text, color) { + if (text) { + this.set_headline(`
${text}
`, color); + } else { + this.clear_headline(); + } + } +}; + +class Section { + constructor(parent, options) { + this.parent = parent; + this.df = options || {}; + this.make(); + + if (this.df.title && this.df.collapsible) { + this.collapse(); + } + this.refresh(); + } + + make() { + this.wrapper = $(`
`) + .appendTo(this.parent); + + if (this.df) { + if (this.df.title) { + this.make_head(); + } + if (this.df.description) { + this.description_wrapper = $( + `
+ ${__(this.df.description)} +
` + ); + + this.wrapper.append(this.description_wrapper); + } + if (this.df.css_class) { + this.wrapper.addClass(this.df.css_class); + } + if (this.df.hide_border) { + this.wrapper.toggleClass("hide-border", true); + } + } + + this.body = $('
').appendTo(this.wrapper); + + if (this.body_html) { + this.body.append(this.body_html); + } + } + + make_head() { + this.head = $(` +
+ ${__(this.df.title)} + +
+ `); + + this.head.appendTo(this.wrapper); + this.indicator = this.head.find('.collapse-indicator'); + this.indicator.hide(); + + if (this.df.collapsible) { + // show / hide based on status + this.collapse_link = this.head.on("click", () => { + this.collapse(); + }); + this.indicator.show(); + } + } + + refresh() { + if (!this.df) return; + + // hide if explicitly hidden + let hide = this.df.hidden; + this.wrapper.toggle(!hide); + } + + collapse(hide) { + if (hide === undefined) { + hide = !this.body.hasClass("hide"); + } + + this.body.toggleClass("hide", hide); + this.head && this.head.toggleClass("collapsed", hide); + + let indicator_icon = hide ? 'down' : 'up-line'; + + this.indicator & this.indicator.html(frappe.utils.icon(indicator_icon, 'sm', 'mb-1')); + } + + is_collapsed() { + return this.body.hasClass('hide'); + } + + hide() { + this.wrapper.hide(); + } + + show() { + this.wrapper.show(); + } +} \ No newline at end of file diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index efe86ee0f3..77be2f2816 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -175,6 +175,7 @@ frappe.ui.form.Form = class FrappeForm { this.dashboard = new frappe.ui.form.Dashboard({ frm: this, + parent: $('
').insertAfter(this.layout.wrapper.find('.form-message')) }); // workflow state diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index ce149e4371..1d0f1f8ffd 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -127,14 +127,6 @@ frappe.ui.form.Layout = Class.extend({ if (this.no_opening_section()) { this.fields.unshift({fieldtype: 'Section Break'}); } - - this.fields.unshift({ - fieldtype: 'Section Break', - fieldname: '_form_dashboard', - cssClass: 'form-dashboard', - collapsible: 1, - // hidden: 1 - }); }, replace_field: function(fieldname, df, render) { @@ -312,10 +304,6 @@ frappe.ui.form.Layout = Class.extend({ collapse = false; } - if (df.fieldname === '_form_dashboard') { - collapse = localStorage.getItem('collapseFormDashboard')==='yes' ? true : false; - } - section.collapse(collapse); } } @@ -587,17 +575,13 @@ frappe.ui.form.Section = Class.extend({ wrapper: this.wrapper }; - if (this.df.collapsible && this.df.fieldname !== '_form_dashboard') { - this.collapse(true); - } - this.refresh(); }, make: function() { if (!this.layout.page) { this.layout.page = $('
').appendTo(this.layout.wrapper); } - let make_card = this.layout.card_layout && this.df.fieldname !== '_form_dashboard'; + let make_card = this.layout.card_layout; this.wrapper = $(`
`) .appendTo(this.layout.page); this.layout.sections.push(this); @@ -664,18 +648,12 @@ frappe.ui.form.Section = Class.extend({ hide = !this.body.hasClass("hide"); } - if (this.df.fieldname==='_form_dashboard') { - localStorage.setItem('collapseFormDashboard', hide ? 'yes' : 'no'); - } - this.body.toggleClass("hide", hide); this.head.toggleClass("collapsed", hide); let indicator_icon = hide ? 'down' : 'up-line'; this.indicator & this.indicator.html(frappe.utils.icon(indicator_icon, 'sm', 'mb-1')); - // this.indicator && this.indicator.toggleClass("octicon-chevron-down", hide); - // this.indicator && this.indicator.toggleClass("octicon-chevron-up", !hide); // refresh signature fields this.fields_list.forEach((f) => { diff --git a/frappe/public/less/form.less b/frappe/public/less/form.less index 5aa23d332e..37dae6ea4f 100644 --- a/frappe/public/less/form.less +++ b/frappe/public/less/form.less @@ -123,24 +123,6 @@ } } -.progress-area { - padding-top: 15px; - padding-bottom: 15px; - - .progress-chart { - padding-top: 15px; - } - - .progress { - margin-bottom: 5px; - } - - .progress-message { - font-feature-settings: "tnum" 1; - margin-top: 0px; - } -} - .form-section { margin: 0px; // padding: 15px; @@ -173,21 +155,10 @@ } } -.hide-border { - border-top: none !important; - padding-top: 0px; -} - .empty-section { display: none !important; } -.modal { - .hide-border { - padding-top: 0; - } -} - .help ol { padding-left: 19px; } diff --git a/frappe/public/scss/desk/form.scss b/frappe/public/scss/desk/form.scss index b9d37d948d..e0895b9073 100644 --- a/frappe/public/scss/desk/form.scss +++ b/frappe/public/scss/desk/form.scss @@ -3,11 +3,13 @@ font-size: var(--text-md); } -.form-section { +.form-section, .form-dashboard-section { margin: 0px; .form-section-description { margin-bottom: 10px; + font-size: var(--text-xs); + color: var(--text-muted); } .section-head { @@ -59,14 +61,11 @@ .section-body:first-child { margin-top: 0; } - .section-head { - padding-left: 0; - padding-right: 0; - } - .form-dashboard-section { - padding-left: calc(var(--padding-lg) + var(--padding-xs)); - padding-right: calc(var(--padding-lg) + var(--padding-xs)); - padding-bottom: var(--padding-lg); + .form-dashboard-section .section-body { + display: block; + padding-left: var(--padding-md); + padding-right: var(--padding-md); + padding-bottom: var(--padding-md); } } @@ -271,3 +270,21 @@ right: 0; } } + +.progress-area { + padding-top: var(--padding-md); + padding-bottom: var(--padding-md); + + .progress-chart { + padding-top: var(--padding-lg); + } + + .progress { + margin-bottom: var(--margin-xs); + } + + .progress-message { + font-feature-settings: "tnum" 1; + margin-top: 0px; + } +} \ No newline at end of file From e2246754bd997b39998d9c12615011335d319b80 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 10 Dec 2020 10:56:03 +0530 Subject: [PATCH 02/11] fix: Dashboard heamap render issue --- frappe/public/js/frappe/form/dashboard.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index 635629130f..b6a0f931e1 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -41,14 +41,15 @@ frappe.ui.form.Dashboard = class FormDashboard { body_html: this.stats_area_row }); + this.transactions_area = $(`
').appendTo(this.wrapper); - if (this.body_html) { - this.body.append(this.body_html); + if (this.df.body_html) { + this.body.append(this.df.body_html); } } From 30a041646c685c6f13d0469689f88c3016c3629c Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Fri, 4 Dec 2020 09:33:13 +0530 Subject: [PATCH 03/11] feat(guest-access): Allow Guest to view, edit allowed forms, making way remove web forms --- .../doctype/navbar_settings/navbar_settings.py | 2 +- frappe/core/doctype/role/role.json | 16 ++++++++-------- frappe/core/doctype/role/role.py | 2 +- .../doctype/user_permission/user_permission.py | 4 ++-- frappe/desk/form/load.py | 4 ++-- frappe/desk/form/meta.py | 16 ++++++++++------ frappe/desk/listview.py | 2 +- frappe/desk/reportview.py | 2 +- frappe/public/js/frappe/chat.js | 14 -------------- frappe/public/js/frappe/desk.js | 7 +------ frappe/public/js/frappe/form/footer/footer.js | 2 +- frappe/public/js/frappe/form/form.js | 14 ++++++++------ frappe/public/js/frappe/form/toolbar.js | 3 +++ frappe/public/js/frappe/list/base_list.js | 2 +- frappe/public/js/frappe/list/list_filter.js | 1 + frappe/public/js/frappe/model/user_settings.js | 3 +++ frappe/public/js/frappe/request.js | 5 +++-- frappe/public/js/frappe/router.js | 1 + frappe/public/js/frappe/router_history.js | 5 ++++- .../js/frappe/ui/notifications/notifications.js | 3 +-- .../public/js/frappe/ui/toolbar/awesome_bar.js | 2 ++ frappe/public/js/frappe/ui/toolbar/navbar.html | 6 +++--- frappe/public/js/frappe/ui/toolbar/toolbar.js | 11 +++++++---- frappe/sessions.py | 2 +- frappe/www/app.py | 4 ++-- 25 files changed, 68 insertions(+), 65 deletions(-) diff --git a/frappe/core/doctype/navbar_settings/navbar_settings.py b/frappe/core/doctype/navbar_settings/navbar_settings.py index f7c437bf00..db510981a4 100644 --- a/frappe/core/doctype/navbar_settings/navbar_settings.py +++ b/frappe/core/doctype/navbar_settings/navbar_settings.py @@ -23,7 +23,7 @@ class NavbarSettings(Document): if not frappe.flags.in_patch and (len(before_save_items) > len(after_save_items)): frappe.throw(_("Please hide the standard navbar items instead of deleting them")) -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def get_app_logo(): app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo') if not app_logo: diff --git a/frappe/core/doctype/role/role.json b/frappe/core/doctype/role/role.json index 1e2366c041..e47dc7194b 100644 --- a/frappe/core/doctype/role/role.json +++ b/frappe/core/doctype/role/role.json @@ -16,7 +16,7 @@ "two_factor_auth", "navigation_settings_section", "search_bar", - "notification", + "notifications", "chat", "list_settings_section", "list_sidebar", @@ -84,12 +84,6 @@ "fieldtype": "Check", "label": "Search Bar" }, - { - "default": "1", - "fieldname": "notification", - "fieldtype": "Check", - "label": "Notification" - }, { "default": "1", "fieldname": "chat", @@ -141,13 +135,19 @@ "fieldname": "view_switcher", "fieldtype": "Check", "label": "View Switcher" + }, + { + "default": "1", + "fieldname": "notifications", + "fieldtype": "Check", + "label": "Notifications" } ], "icon": "fa fa-bookmark", "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-11-11 17:29:13.149522", + "modified": "2020-12-03 14:08:38.181035", "modified_by": "Administrator", "module": "Core", "name": "Role", diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py index f9fbd9cbe6..bac68e30ab 100644 --- a/frappe/core/doctype/role/role.py +++ b/frappe/core/doctype/role/role.py @@ -6,7 +6,7 @@ import frappe from frappe.model.document import Document -desk_properties = ("search_bar", "notification", "chat", "list_sidebar", +desk_properties = ("search_bar", "notifications", "chat", "list_sidebar", "bulk_actions", "view_switcher", "form_sidebar", "timeline", "dashboard") class Role(Document): diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index ba14583c2f..de14651d50 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -55,7 +55,7 @@ class UserPermission(Document): ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name) frappe.throw(_("{0} has already assigned default value for {1}.").format(ref_link, self.allow)) -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def get_user_permissions(user=None): '''Get all users permissions for the user as a dict of doctype''' # if this is called from client-side, @@ -66,7 +66,7 @@ def get_user_permissions(user=None): if not user: user = frappe.session.user - if not user or user == "Administrator": + if not user or user in ("Administrator", "Guest"): return {} cached_user_permissions = frappe.cache().hget("user_permissions", user) diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index 2d9223edf8..1f5c437330 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -13,7 +13,7 @@ from frappe.desk.form.document_follow import is_document_followed from frappe import _ from six.moves.urllib.parse import quote -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def getdoc(doctype, name, user=None): """ Loads a doclist for a given document. This method is called directly from the client. @@ -52,7 +52,7 @@ def getdoc(doctype, name, user=None): frappe.response.docs.append(doc) -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def getdoctype(doctype, with_parent=False, cached_timestamp=None): """load doctype""" diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index c28a40657f..d5428b1da2 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -202,13 +202,17 @@ class FormMeta(Meta): self.load_kanban_column_fields() def load_kanban_column_fields(self): - values = frappe.get_list( - 'Kanban Board', fields=['field_name'], - filters={'reference_doctype': self.name}) + try: + values = frappe.get_list( + 'Kanban Board', fields=['field_name'], + filters={'reference_doctype': self.name}) - fields = [x['field_name'] for x in values] - fields = list(set(fields)) - self.set("__kanban_column_fields", fields, as_value=True) + fields = [x['field_name'] for x in values] + fields = list(set(fields)) + self.set("__kanban_column_fields", fields, as_value=True) + except frappe.PermissionError: + # no access to kanban board + pass def get_code_files_via_hooks(hook, name): code_files = [] diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py index 1d10a13930..91dc0f3ba9 100644 --- a/frappe/desk/listview.py +++ b/frappe/desk/listview.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def get_list_settings(doctype): try: return frappe.get_cached_doc("List View Settings", doctype) diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 9f5a5d84c8..6cd1d24626 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -14,7 +14,7 @@ from frappe.core.doctype.access_log.access_log import make_access_log from frappe.utils import cstr, format_duration -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) @frappe.read_only() def get(): args = get_form_params() diff --git a/frappe/public/js/frappe/chat.js b/frappe/public/js/frappe/chat.js index e17a7ebb4d..61fa89deab 100644 --- a/frappe/public/js/frappe/chat.js +++ b/frappe/public/js/frappe/chat.js @@ -2670,20 +2670,6 @@ frappe.chat.render = (render = true, force = false) => // With the assumption, that there's only one navbar. const $placeholder = $('.navbar .frappe-chat-dropdown') - // Render if frappe-chat-toggle doesn't exist. - if ( frappe.utils.is_empty($placeholder.has('.frappe-chat-toggle')) ) { - const $template = $(` - -
- -
-
- `) - - $placeholder.addClass('dropdown hidden') - $placeholder.html($template) - } - if ( render ) { $placeholder.removeClass('hidden') } else { diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 5a6ea54a3c..c1bbe78781 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -160,8 +160,6 @@ frappe.Application = Class.extend({ }, 600000); // check every 10 minutes } } - - this.fetch_tags(); }, set_route() { @@ -294,6 +292,7 @@ frappe.Application = Class.extend({ set_globals: function() { frappe.session.user = frappe.boot.user.name; + frappe.session.logged_in_user = frappe.boot.user.name; frappe.session.user_email = frappe.boot.user.email; frappe.session.user_fullname = frappe.user_info().fullname; @@ -599,10 +598,6 @@ frappe.Application = Class.extend({ frappe.show_alert(message); }); }, - - fetch_tags() { - frappe.tags.utils.fetch_tags(); - } }); frappe.get_module = function(m, default_module) { diff --git a/frappe/public/js/frappe/form/footer/footer.js b/frappe/public/js/frappe/form/footer/footer.js index 827eb861dc..074646c67b 100644 --- a/frappe/public/js/frappe/form/footer/footer.js +++ b/frappe/public/js/frappe/form/footer/footer.js @@ -48,7 +48,7 @@ frappe.ui.form.Footer = Class.extend({ }); }, get_names_for_mentions() { - let names_for_mentions = Object.keys(frappe.boot.user_info) + let names_for_mentions = Object.keys(frappe.boot.user_info || []) .filter(user => { return !["Administrator", "Guest"].includes(user) && frappe.boot.user_info[user].allowed_in_mentions; diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 77be2f2816..b819b6ad97 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -107,7 +107,7 @@ frappe.ui.form.Form = class FrappeForm { this.script_manager.setup(); this.watch_model_updates(); - if(!this.meta.hide_toolbar) { + if(!this.meta.hide_toolbar && frappe.boot.desk_settings.timeline) { this.footer = new frappe.ui.form.Footer({ frm: this, parent: $('
').appendTo(this.page.main.parent()) @@ -446,11 +446,13 @@ frappe.ui.form.Form = class FrappeForm { this.layout.doc = this.doc; this.layout.attach_doc_and_docfields(); - this.sidebar = new frappe.ui.form.Sidebar({ - frm: this, - page: this.page - }); - this.sidebar.make(); + if (frappe.boot.desk_settings.form_sidebar) { + this.sidebar = new frappe.ui.form.Sidebar({ + frm: this, + page: this.page + }); + this.sidebar.make(); + } // clear layout message this.layout.show_message(); diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 28cc9bb28b..236e4b3fba 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -198,6 +198,9 @@ frappe.ui.form.Toolbar = Class.extend({ make_menu: function() { this.page.clear_icons(); this.page.clear_menu(); + + if (frappe.session.user === 'Guest') return; + var me = this; var p = this.frm.perm[0]; var docstatus = cint(this.frm.doc.docstatus); diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index c67de6afc9..b9a352bd06 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -236,7 +236,7 @@ frappe.views.BaseList = class BaseList { } setup_side_bar() { - if (this.hide_sidebar) return; + if (this.hide_sidebar || !frappe.boot.desk_settings.list_sidebar) return; this.list_sidebar = new frappe.views.ListSidebar({ doctype: this.doctype, stats: this.stats, diff --git a/frappe/public/js/frappe/list/list_filter.js b/frappe/public/js/frappe/list/list_filter.js index fbbf5e583f..c02755d50c 100644 --- a/frappe/public/js/frappe/list/list_filter.js +++ b/frappe/public/js/frappe/list/list_filter.js @@ -167,6 +167,7 @@ export default class ListFilter { } get_list_filters() { + if (frappe.session.user === 'Guest') return Promise.resolve(); return frappe.db .get_list('List Filter', { fields: ['name', 'filter_name', 'for_user', 'filters'], diff --git a/frappe/public/js/frappe/model/user_settings.js b/frappe/public/js/frappe/model/user_settings.js index 30bd98008d..4d11429ce4 100644 --- a/frappe/public/js/frappe/model/user_settings.js +++ b/frappe/public/js/frappe/model/user_settings.js @@ -6,6 +6,8 @@ $.extend(frappe.model.user_settings, { .then(r => JSON.parse(r.message || '{}')); }, save: function(doctype, key, value) { + if (frappe.session.user === 'Guest') return Promise.resolve(); + const old_user_settings = frappe.model.user_settings[doctype] || {}; const new_user_settings = $.extend(true, {}, old_user_settings); // deep copy @@ -31,6 +33,7 @@ $.extend(frappe.model.user_settings, { return this.update(doctype, user_settings); }, update: function(doctype, user_settings) { + if (frappe.session.user === 'Guest') return Promise.resolve(); return frappe.call({ method: 'frappe.model.utils.user_settings.save', args: { diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index a49b916f6e..93bec14237 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -127,7 +127,7 @@ frappe.request.call = function(opts) { message: __('The resource you are looking for is not available')}); }, 403: function(xhr) { - if (frappe.session.user === 'Guest') { + if (frappe.session.logged_in_user !== 'Guest') { // session expired frappe.app.handle_session_expired(); } @@ -322,7 +322,8 @@ frappe.request.cleanup = function(opts, r) { if(r) { // session expired? - Guest has no business here! - if (r.session_expired || frappe.session.user === "Guest") { + if (r.session_expired || + (frappe.session.user === 'Guest' && frappe.session.logged_in_user !== "Guest")) { frappe.app.handle_session_expired(); return; } diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index d3fe28dd10..445478b200 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -20,6 +20,7 @@ $(window).on('hashchange', function() { let sub_path = frappe.router.get_sub_path(window.location.hash); window.location.hash = ''; frappe.router.push_state(sub_path); + return false; } }); diff --git a/frappe/public/js/frappe/router_history.js b/frappe/public/js/frappe/router_history.js index 61fc4d6b13..41feaf2728 100644 --- a/frappe/public/js/frappe/router_history.js +++ b/frappe/public/js/frappe/router_history.js @@ -3,14 +3,17 @@ frappe.route_history_queue = []; const routes_to_skip = ['Form', 'social', 'setup-wizard', 'recorder']; const save_routes = frappe.utils.debounce(() => { + if (frappe.session.user === 'Guest') return; const routes = frappe.route_history_queue; frappe.route_history_queue = []; + frappe.xcall('frappe.deferred_insert.deferred_insert', { 'doctype': 'Route History', 'records': routes }).catch(() => { frappe.route_history_queue.concat(routes); - }); + }); + }, 10000); frappe.route.on('change', () => { diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index afdb40118a..998ddbedc9 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -12,8 +12,7 @@ frappe.ui.Notifications = class Notifications { } make() { - this.dropdown = $('.navbar').find('.dropdown-notifications'); - this.dropdown.removeClass("hidden") + this.dropdown = $('.navbar').find('.dropdown-notifications').removeClass('hidden'); this.dropdown_list = this.dropdown.find('.notifications-list'); this.header_items = this.dropdown_list.find('.header-items'); this.header_actions = this.dropdown_list.find('.header-actions'); diff --git a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js index 577b3d3f14..f9ec4c789d 100644 --- a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js +++ b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js @@ -7,6 +7,7 @@ frappe.search.AwesomeBar = Class.extend({ setup: function(element) { var me = this; + $('.search-bar').removeClass('hidden'); var $input = $(element); var input = $input.get(0); @@ -122,6 +123,7 @@ frappe.search.AwesomeBar = Class.extend({ $input.blur(); }); frappe.search.utils.setup_recent(); + frappe.tags.utils.fetch_tags(); }, add_help: function() { diff --git a/frappe/public/js/frappe/ui/toolbar/navbar.html b/frappe/public/js/frappe/ui/toolbar/navbar.html index 5ddb5ffc83..2a48165d81 100644 --- a/frappe/public/js/frappe/ui/toolbar/navbar.html +++ b/frappe/public/js/frappe/ui/toolbar/navbar.html @@ -6,7 +6,7 @@