From 60454fafc84ef126f972cff41960f9a592bc8a1e Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 29 Apr 2021 14:57:02 +0530 Subject: [PATCH 001/204] feat: Tab Break fieldtype --- frappe/core/doctype/docfield/docfield.json | 4 +- frappe/public/build.json | 3 + frappe/public/js/frappe/form/column.js | 41 +++ frappe/public/js/frappe/form/dashboard.js | 231 +++++-------- frappe/public/js/frappe/form/form.js | 21 +- frappe/public/js/frappe/form/layout.js | 361 ++++++++------------- frappe/public/js/frappe/form/section.js | 158 +++++++++ frappe/public/js/frappe/form/tab.js | 76 +++++ frappe/public/js/frappe/ui/field_group.js | 16 +- frappe/public/scss/desk/form.scss | 22 +- 10 files changed, 540 insertions(+), 393 deletions(-) create mode 100644 frappe/public/js/frappe/form/column.js create mode 100644 frappe/public/js/frappe/form/section.js create mode 100644 frappe/public/js/frappe/form/tab.js diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index ca134665b8..4ce553b44a 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -90,7 +90,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", "reqd": 1, "search_index": 1 }, @@ -487,7 +487,7 @@ "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-10-29 06:09:26.454990", + "modified": "2021-04-15 12:59:35.484572", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/public/build.json b/frappe/public/build.json index 942871ee9b..192deab9b1 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -140,6 +140,9 @@ "public/js/frappe/ui/find.js", "public/js/frappe/ui/iconbar.js", "public/js/frappe/form/layout.js", + "public/js/frappe/form/section.js", + "public/js/frappe/form/tab.js", + "public/js/frappe/form/column.js", "public/js/frappe/ui/field_group.js", "public/js/frappe/form/link_selector.js", "public/js/frappe/form/multi_select_dialog.js", diff --git a/frappe/public/js/frappe/form/column.js b/frappe/public/js/frappe/form/column.js new file mode 100644 index 0000000000..8ceb2d029e --- /dev/null +++ b/frappe/public/js/frappe/form/column.js @@ -0,0 +1,41 @@ +frappe.ui.form.Column = class Column { + constructor(section, df) { + if (!df) df = {}; + + this.df = df; + this.section = section; + this.make(); + this.resize_all_columns(); + } + + make() { + this.wrapper = $(`
+
+
+
`).appendTo(this.section.body) + .find("form") + .on("submit", function () { + return false; + }); + + if (this.df.label) { + $(``).appendTo(this.wrapper); + } + } + + resize_all_columns() { + // distribute all columns equally + let colspan = cint(12 / this.section.wrapper.find(".form-column").length); + + this.section.wrapper.find(".form-column").removeClass() + .addClass("form-column") + .addClass("col-sm-" + colspan); + + } + + refresh() { + this.section.refresh(); + } +} \ No newline at end of file diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index c1c95d94cf..c983f612e0 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -4,57 +4,79 @@ frappe.ui.form.Dashboard = class FormDashboard { constructor(opts) { $.extend(this, opts); + this.setup_dashboard_tabs(); this.setup_dashboard_sections(); } + setup_dashboard_tabs() { + this.overview_tab = new frappe.ui.form.Tab(this.frm.layout, { + label: __("Overview"), + hidden: 1, + fieldname: 'dashboard-overview' + }); + + this.connections_tab = new frappe.ui.form.Tab(this.frm.layout, { + label: __("Connections"), + hidden: 1, + fieldname: 'dashboard-connection' + }); + } + setup_dashboard_sections() { - this.progress_area = new Section(this.parent, { + this.progress_area = this.make_section({ css_class: 'progress-area', hidden: 1, - collapsible: 1 - }); + is_dashboard_section: 1, + }, this.overview_tab); - this.heatmap_area = new Section(this.parent, { - title: __("Overview"), + this.heatmap_area = this.make_section({ + label: __("Overview"), css_class: 'form-heatmap', hidden: 1, - collapsible: 1, + is_dashboard_section: 1, body_html: `
` - }); + }, this.overview_tab); - this.chart_area = new Section(this.parent, { - title: __("Graph"), + this.chart_area = this.make_section({ + label: __("Graph"), css_class: 'form-graph', hidden: 1, - collapsible: 1 - }); + is_dashboard_section: 1 + }, this.overview_tab); this.stats_area_row = $(`
`); - this.stats_area = new Section(this.parent, { - title: __("Stats"), + this.stats_area = this.make_section({ + label: __("Stats"), css_class: 'form-stats', hidden: 1, - collapsible: 1, + is_dashboard_section: 1, body_html: this.stats_area_row - }); + }, this.overview_tab); this.transactions_area = $(`
') + let progress_chart = $('
') .appendTo(this.progress_area.body); return progress_chart; } @@ -169,7 +196,7 @@ frappe.ui.form.Dashboard = class FormDashboard { this.init_data(); } - var show = false; + let show = false; if (this.data && ((this.data.transactions || []).length || (this.data.reports || []).length)) { @@ -198,11 +225,10 @@ frappe.ui.form.Dashboard = class FormDashboard { } after_refresh() { - var me = this; // show / hide new buttons (if allowed) - this.links_area.body.find('.btn-new').each(function() { - if (me.frm.can_create($(this).attr('data-doctype'))) { - $(this).removeClass('hidden'); + this.links_area.body.find('.btn-new').each((i, el) => { + if (this.frm.can_create($(this).attr('data-doctype'))) { + $(el).removeClass('hidden'); } }); } @@ -269,7 +295,7 @@ frappe.ui.form.Dashboard = class FormDashboard { } render_links() { - var me = this; + let me = this; this.links_area.show(); this.links_area.body.find('.btn-new').addClass('hidden'); if (this.data_rendered) { @@ -324,7 +350,7 @@ frappe.ui.form.Dashboard = class FormDashboard { open_document_list($link, show_open) { // show document list with filters - var doctype = $link.attr('data-doctype'), + let doctype = $link.attr('data-doctype'), names = $link.attr('data-names') || []; if (this.data.internal_links[doctype]) { @@ -346,8 +372,8 @@ frappe.ui.form.Dashboard = class FormDashboard { get_document_filter(doctype) { // return the default filter for the given document // like {"customer": frm.doc.name} - var filter = {}; - var fieldname = this.data.non_standard_fieldnames + let filter = {}; + let fieldname = this.data.non_standard_fieldnames ? (this.data.non_standard_fieldnames[doctype] || this.data.fieldname) : this.data.fieldname; @@ -366,7 +392,7 @@ frappe.ui.form.Dashboard = class FormDashboard { } // list all items from the transaction list - var items = [], + let items = [], me = this; this.data.transactions.forEach(function(group) { @@ -375,7 +401,7 @@ frappe.ui.form.Dashboard = class FormDashboard { }); }); - var method = this.data.method || 'frappe.desk.notifications.get_open_count'; + let method = this.data.method || 'frappe.desk.notifications.get_open_count'; frappe.call({ type: "GET", method: method, @@ -424,7 +450,7 @@ frappe.ui.form.Dashboard = class FormDashboard { } set_badge_count(doctype, open_count, count, names) { - var $link = $(this.transactions_area) + let $link = $(this.transactions_area) .find('.document-link[data-doctype="'+doctype+'"]'); if (open_count) { @@ -471,7 +497,7 @@ frappe.ui.form.Dashboard = class FormDashboard { this.heatmap_area.body.find('svg').css({'margin': 'auto'}); // message - var heatmap_message = this.heatmap_area.body.find('.heatmap-message'); + let heatmap_message = this.heatmap_area.body.find('.heatmap-message'); if (this.data.heatmap_message) { heatmap_message.removeClass('hidden').html(this.data.heatmap_message); } else { @@ -486,9 +512,9 @@ frappe.ui.form.Dashboard = class FormDashboard { // set colspan - var indicators = this.stats_area_row.find('.indicator-column'); - var n_indicators = indicators.length + 1; - var colspan; + let indicators = this.stats_area_row.find('.indicator-column'); + let n_indicators = indicators.length + 1; + let colspan; if (n_indicators > 4) { colspan = 3; } else { @@ -500,7 +526,7 @@ frappe.ui.form.Dashboard = class FormDashboard { indicators.removeClass().addClass('col-sm-'+colspan).addClass('indicator-column'); } - var indicator = $('
' + let indicator = $('
' +label+'
').appendTo(this.stats_area_row); return indicator; @@ -508,9 +534,9 @@ frappe.ui.form.Dashboard = class FormDashboard { // graphs setup_graph() { - var me = this; - var method = this.data.graph_method; - var args = { + let me = this; + let method = this.data.graph_method; + let args = { doctype: this.frm.doctype, docname: this.frm.doc.name, }; @@ -574,11 +600,10 @@ frappe.ui.form.Dashboard = class FormDashboard { } add_comment(text, alert_class, permanent) { - var me = this; this.set_headline_alert(text, alert_class); if (!permanent) { - setTimeout(function() { - me.clear_headline(); + setTimeout(() => { + this.clear_headline(); }, 10000); } } @@ -595,109 +620,3 @@ frappe.ui.form.Dashboard = class FormDashboard { } } }; - -class Section { - constructor(parent, options) { - this.parent = parent; - this.df = options || {}; - this.make(); - - if (this.df.title && this.df.collapsible && localStorage.getItem(options.css_class + '-closed')) { - 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.df.body_html) { - this.body.append(this.df.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.set_icon(); - 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); - - this.set_icon(hide); - - // save state for next reload ('' is falsy) - localStorage.setItem(this.df.css_class + '-closed', hide ? '1' : ''); - } - - set_icon(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(); - } -} diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index a188c42950..83d38b323b 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -93,6 +93,11 @@ frappe.ui.form.Form = class FrappeForm { this.watch_model_updates(); if (!this.meta.hide_toolbar && frappe.boot.desk_settings.timeline) { + // this.footer_tab = new frappe.ui.form.Tab(this.layout, { + // label: __("Activity"), + // fieldname: 'timeline' + // }); + this.footer = new frappe.ui.form.Footer({ frm: this, parent: $('
').appendTo(this.page.main.parent()) @@ -141,6 +146,7 @@ frappe.ui.form.Form = class FrappeForm { frm: this, with_dashboard: true, card_layout: true, + tabbed_layout: true, }); this.layout.make(); @@ -149,7 +155,7 @@ frappe.ui.form.Form = class FrappeForm { this.dashboard = new frappe.ui.form.Dashboard({ frm: this, - parent: $('
').insertAfter(this.layout.wrapper.find('.form-message')) + parent: this.layout.wrapper, }); // workflow state @@ -453,7 +459,7 @@ frappe.ui.form.Form = class FrappeForm { }, () => this.cscript.is_onload && this.is_new() && this.focus_on_first_input(), () => this.run_after_load_hook(), - () => this.dashboard.after_refresh() + () => this.dashboard.after_refresh(), ]); } else { @@ -462,6 +468,7 @@ frappe.ui.form.Form = class FrappeForm { this.$wrapper.trigger('render_complete'); + this.cscript.is_onload && this.set_first_tab_as_active(); if(!this.hidden) { this.layout.show_empty_form_message(); } @@ -469,6 +476,11 @@ frappe.ui.form.Form = class FrappeForm { this.scroll_to_element(); } + set_first_tab_as_active() { + this.layout.tabs[0] + && this.layout.tabs[0].set_active(); + } + focus_on_first_input() { let first = this.form_wrapper.find('.form-layout :input:visible:first'); if (!in_list(["Date", "Datetime"], first.attr("data-fieldtype"))) { @@ -1580,6 +1592,11 @@ frappe.ui.form.Form = class FrappeForm { let $el = field.$wrapper; + // set tab as active + if (field.tab && !field.tab.is_active()) { + field.tab.set_active(); + } + // uncollapse section if (field.section.is_collapsed()) { field.section.collapse(false); diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 282655b589..2ffba7b15a 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -1,27 +1,38 @@ -import '../class'; - frappe.ui.form.Layout = class Layout { constructor (opts) { this.views = {}; this.pages = []; + this.tabs = []; this.sections = []; this.fields_list = []; this.fields_dict = {}; $.extend(this, opts); } + make() { if (!this.parent && this.body) { this.parent = this.body; } this.wrapper = $('
').appendTo(this.parent); this.message = $('').appendTo(this.wrapper); + + this.page = $('
').appendTo(this.wrapper); + $(`
+ +
`).appendTo(this.page); + this.tabs_list = this.page.find('.form-tabs'); + this.tabs_content = $(`
`).appendTo(this.page); + if (!this.fields) { this.fields = this.get_doctype_fields(); } + + this.setup_events(); this.setup_tabbing(); this.render(); } + show_empty_form_message() { if (!(this.wrapper.find(".frappe-control:visible").length || this.wrapper.find(".section-head.collapsed").length)) { this.show_message(__("This form does not have any input")); @@ -87,9 +98,9 @@ frappe.ui.form.Layout = class Layout { this.message.empty().addClass('hidden'); } } - render (new_fields) { - var me = this; - var fields = new_fields || this.fields; + + render(new_fields) { + let fields = new_fields || this.fields; this.section = null; this.column = null; @@ -98,38 +109,45 @@ frappe.ui.form.Layout = class Layout { this.setup_dashboard_section(); } + if (this.tabbed_layout) { + this.first_tab = this.make_tab({label: __('Details'), fieldname: 'details'}) + } + if (this.no_opening_section()) { this.make_section(); } - $.each(fields, function (i, df) { + + fields.forEach(df => { switch (df.fieldtype) { case "Fold": - me.make_page(df); + this.make_page(df); break; case "Section Break": - me.make_section(df); + this.make_section(df); break; case "Column Break": - me.make_column(df); + this.make_column(df); + break; + case "Tab Break": + this.make_tab(df); break; default: - me.make_field(df); + this.make_field(df); } }); - } - no_opening_section () { + no_opening_section() { return (this.fields[0] && this.fields[0].fieldtype != "Section Break") || !this.fields.length; } - setup_dashboard_section () { + setup_dashboard_section() { if (this.no_opening_section()) { this.fields.unshift({fieldtype: 'Section Break'}); } } - replace_field (fieldname, df, render) { + replace_field(fieldname, df, render) { df.fieldname = fieldname; // change of fieldname is avoided if (this.fields_dict[fieldname] && this.fields_dict[fieldname].df) { const fieldobj = this.init_field(df, render); @@ -145,7 +163,7 @@ frappe.ui.form.Layout = class Layout { } } - make_field (df, colspan, render) { + make_field(df, colspan, render) { !this.section && this.make_section(); !this.column && this.make_column(); @@ -159,9 +177,10 @@ frappe.ui.form.Layout = class Layout { this.section.fields_list.push(fieldobj); this.section.fields_dict[df.fieldname] = fieldobj; fieldobj.section = this.section; + fieldobj.tab = this.tab; } - init_field (df, render = false) { + init_field(df, render=false) { const fieldobj = frappe.ui.form.make_control({ df: df, doctype: this.doctype, @@ -176,8 +195,8 @@ frappe.ui.form.Layout = class Layout { return fieldobj; } - make_page (df) { // eslint-disable-line no-unused-vars - var me = this, + make_page(df) { // eslint-disable-line no-unused-vars + let me = this, head = $('').appendTo(this.wrapper); @@ -185,7 +204,7 @@ frappe.ui.form.Layout = class Layout { this.page = $('
').appendTo(this.wrapper); this.fold_btn = head.find(".btn-fold").on("click", function () { - var page = $(this).parent().next(); + let page = $(this).parent().next(); if (page.hasClass("hide")) { $(this).removeClass("btn-fold").html(__("Hide details")); page.removeClass("hide"); @@ -202,12 +221,12 @@ frappe.ui.form.Layout = class Layout { this.folded = true; } - unfold () { + unfold() { this.fold_btn.trigger('click'); } - make_section (df) { - this.section = new frappe.ui.form.Section(this, df); + make_section(df) { + this.section = new frappe.ui.form.Section(this, df, this.tab || null); // append to layout fields if (df) { @@ -218,15 +237,25 @@ frappe.ui.form.Layout = class Layout { this.column = null; } - make_column (df) { + make_column(df) { this.column = new frappe.ui.form.Column(this.section, df); if (df && df.fieldname) { this.fields_list.push(this.column); } } - refresh (doc) { - var me = this; + make_tab(df) { + this.tab = new frappe.ui.form.Tab(this, df); + + if (df) { + this.fields_dict[df.fieldname] = this.tab; + this.fields_list.push(this.tab); + } + + return this.tab; + } + + refresh(doc) { if (doc) this.doc = doc; if (this.frm) { @@ -234,7 +263,7 @@ frappe.ui.form.Layout = class Layout { } // NOTE this might seem redundant at first, but it needs to be executed when frm.refresh_fields is called - me.attach_doc_and_docfields(true); + this.attach_doc_and_docfields(true); if (this.frm && this.frm.wrapper) { $(this.frm.wrapper).trigger("refresh-fields"); @@ -246,6 +275,9 @@ frappe.ui.form.Layout = class Layout { // refresh sections this.refresh_sections(); + // refresh tabs + this.refresh_tabs(); + if (this.frm) { // collapse sections this.refresh_section_collapse(); @@ -265,10 +297,26 @@ frappe.ui.form.Layout = class Layout { }); this.frm && this.frm.dashboard.refresh(); + } + refresh_tabs() { + this.tabs.forEach(tab => { + if (!tab.wrapper.hasClass('hide') && !tab.parent.hasClass('hide')) { + tab.parent.removeClass('show hide'); + tab.wrapper.removeClass('show hide'); + if (tab.wrapper.find( + ".form-section:not(.hide-control, .empty-section), .form-dashboard-section:not(.hide-control, .empty-section)" + ).length + ) { + tab.toggle(true) + } else { + tab.toggle(false) + } + } + }); } - refresh_fields (fields) { + refresh_fields(fields) { let fieldnames = fields.map((field) => { if (field.fieldname) return field.fieldname; }); @@ -283,7 +331,7 @@ frappe.ui.form.Layout = class Layout { }); } - add_fields (fields) { + add_fields(fields) { this.render(fields); this.refresh_fields(fields); } @@ -291,11 +339,11 @@ frappe.ui.form.Layout = class Layout { refresh_section_collapse () { if (!(this.sections && this.sections.length)) return; - for (var i = 0; i < this.sections.length; i++) { - var section = this.sections[i]; - var df = section.df; + for (let i = 0; i < this.sections.length; i++) { + let section = this.sections[i]; + let df = section.df; if (df && df.collapsible) { - var collapse = true; + let collapse = true; if (df.collapsible_depends_on) { collapse = !this.evaluate_depends_on_value(df.collapsible_depends_on); @@ -310,10 +358,10 @@ frappe.ui.form.Layout = class Layout { } } - attach_doc_and_docfields (refresh) { - var me = this; - for (var i = 0, l = this.fields_list.length; i < l; i++) { - var fieldobj = this.fields_list[i]; + attach_doc_and_docfields(refresh) { + let me = this; + for (let i = 0, l = this.fields_list.length; i < l; i++) { + let fieldobj = this.fields_list[i]; if (me.doc) { fieldobj.doc = me.doc; fieldobj.doctype = me.doc.doctype; @@ -330,41 +378,49 @@ frappe.ui.form.Layout = class Layout { } } - refresh_section_count () { + refresh_section_count() { this.wrapper.find(".section-count-label:visible").each(function (i) { $(this).html(i + 1); }); } - setup_tabbing () { - var me = this; - this.wrapper.on("keydown", function (ev) { + + setup_events() { + this.tabs_list.off('click').on('click', '.nav-link', (e) => { + e.preventDefault() + e.stopImmediatePropagation(); + $(e.currentTarget).tab('show'); + // this.$current_tab = $(e.currentTarget); + }); + } + + setup_tabbing() { + this.wrapper.on("keydown", (ev) => { if (ev.which == 9) { - var current = $(ev.target), + let current = $(ev.target), doctype = current.attr("data-doctype"), fieldname = current.attr("data-fieldname"); if (doctype) - return me.handle_tab(doctype, fieldname, ev.shiftKey); + return this.handle_tab(doctype, fieldname, ev.shiftKey); } }); } - handle_tab (doctype, fieldname, shift) { - var me = this, - grid_row = null, + + handle_tab(doctype, fieldname, shift) { + let grid_row = null, prev = null, - fields = me.fields_list, - in_grid = false, + fields = this.fields_list, focused = false; // in grid - if (doctype != me.doctype) { - grid_row = me.get_open_grid_row(); + if (doctype != this.doctype) { + grid_row = this.get_open_grid_row(); if (!grid_row || !grid_row.layout) { return; } fields = grid_row.layout.fields_list; } - for (var i = 0, len = fields.length; i < len; i++) { + for (let i = 0, len = fields.length; i < len; i++) { if (fields[i].df.fieldname == fieldname) { if (shift) { if (prev) { @@ -375,7 +431,7 @@ frappe.ui.form.Layout = class Layout { break; } if (i < len - 1) { - focused = me.focus_on_next_field(i, fields); + focused = this.focus_on_next_field(i, fields); } if (focused) { @@ -406,10 +462,11 @@ frappe.ui.form.Layout = class Layout { return false; } - focus_on_next_field (start_idx, fields) { + + focus_on_next_field(start_idx, fields) { // loop to find next eligible fields - for (var i = start_idx + 1, len = fields.length; i < len; i++) { - var field = fields[i]; + for (let i = start_idx + 1, len = fields.length; i < len; i++) { + let field = fields[i]; if (this.is_visible(field)) { if (field.df.fieldtype === "Table") { // open table grid @@ -428,10 +485,12 @@ frappe.ui.form.Layout = class Layout { } } } - is_visible (field) { + + is_visible(field) { return field.disp_status === "Write" && (field.$wrapper && field.$wrapper.is(":visible")); } - set_focus (field) { + + set_focus(field) { // next is table, show the table if (field.df.fieldtype=="Table") { if (!field.grid.grid_rows.length) { @@ -445,18 +504,19 @@ frappe.ui.form.Layout = class Layout { field.$input.focus(); } } - get_open_grid_row () { + + get_open_grid_row() { return $(".grid-row-open").data("grid_row"); } - refresh_dependency () { + + refresh_dependency() { // Resolve "depends_on" and show / hide accordingly - var me = this; // build dependants' dictionary - var has_dep = false; + let has_dep = false; - for (var fkey in this.fields_list) { - var f = this.fields_list[fkey]; + for (let fkey in this.fields_list) { + let f = this.fields_list[fkey]; f.dependencies_clear = true; if (f.df.depends_on || f.df.mandatory_depends_on || f.df.read_only_depends_on) { has_dep = true; @@ -466,8 +526,8 @@ frappe.ui.form.Layout = class Layout { if (!has_dep) return; // show / hide based on values - for (var i = me.fields_list.length - 1; i >= 0; i--) { - var f = me.fields_list[i]; + for (let i = this.fields_list.length - 1; i >= 0; i--) { + let f = this.fields_list[i]; f.guardian_has_value = true; if (f.df.depends_on) { // evaluate guardian @@ -499,7 +559,8 @@ frappe.ui.form.Layout = class Layout { this.refresh_section_count(); } - set_dependant_property (condition, fieldname, property) { + + set_dependant_property(condition, fieldname, property) { let set_property = this.evaluate_depends_on_value(condition); let value = set_property ? 1 : 0; let form_obj; @@ -521,19 +582,20 @@ frappe.ui.form.Layout = class Layout { } } } - evaluate_depends_on_value (expression) { - var out = null; - var doc = this.doc; + + evaluate_depends_on_value(expression) { + let out = null; + let doc = this.doc; if (!doc && this.get_values) { - var doc = this.get_values(true); + let doc = this.get_values(true); } if (!doc) { return; } - var parent = this.frm ? this.frm.doc : this.doc || null; + let parent = this.frm ? this.frm.doc : this.doc || null; if (typeof (expression) === 'boolean') { out = expression; @@ -564,161 +626,4 @@ frappe.ui.form.Layout = class Layout { return out; } -}; - -frappe.ui.form.Section = class FormSection { - constructor(layout, df) { - this.layout = layout; - this.df = df || {}; - this.fields_list = []; - this.fields_dict = {}; - - this.make(); - // if (this.frm) - // this.section.body.css({"padding":"0px 3%"}) - this.row = { - wrapper: this.wrapper - }; - - this.refresh(); - } - make() { - if (!this.layout.page) { - this.layout.page = $('
').appendTo(this.layout.wrapper); - } - let make_card = this.layout.card_layout; - this.wrapper = $(`
`) - .appendTo(this.layout.page); - this.layout.sections.push(this); - - if (this.df) { - if (this.df.label) { - this.make_head(); - } - if (this.df.description) { - $('
' + __(this.df.description) + '
') - .appendTo(this.wrapper); - } - if (this.df.cssClass) { - this.wrapper.addClass(this.df.cssClass); - } - if (this.df.hide_border) { - this.wrapper.toggleClass("hide-border", true); - } - } - - // for bc - this.body = $('
').appendTo(this.wrapper); - } - - make_head () { - this.head = $(`
- ${__(this.df.label)} - - -
`); - 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 explictly hidden - var hide = this.df.hidden || this.df.hidden_due_to_dependency; - - // hide if no perm - if (!hide && this.layout && this.layout.frm && !this.layout.frm.get_perm(this.df.permlevel || 0, "read")) { - hide = true; - } - - this.wrapper.toggleClass("hide-control", !!hide); - } - collapse (hide) { - // unknown edge case - if (!(this.head && this.body)) { - return; - } - - if (hide===undefined) { - hide = !this.body.hasClass("hide"); - } - - 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')); - - // refresh signature fields - this.fields_list.forEach((f) => { - if (f.df.fieldtype == 'Signature') { - f.refresh(); - } - }); - } - - is_collapsed() { - return this.body.hasClass('hide'); - } - - has_missing_mandatory () { - var missing_mandatory = false; - for (var j = 0, l = this.fields_list.length; j < l; j++) { - var section_df = this.fields_list[j].df; - if (section_df.reqd && this.layout.doc[section_df.fieldname] == null) { - missing_mandatory = true; - break; - } - } - return missing_mandatory; - } -}; - -frappe.ui.form.Column = class FormColumn { - constructor(section, df) { - if (!df) df = {}; - - this.df = df; - this.section = section; - this.make(); - this.resize_all_columns(); - } - make () { - this.wrapper = $('
\ -
\ -
\ -
').appendTo(this.section.body) - .find("form") - .on("submit", function () { - return false; - }); - - if (this.df.label) { - $('').appendTo(this.wrapper); - } - } - resize_all_columns () { - // distribute all columns equally - var colspan = cint(12 / this.section.wrapper.find(".form-column").length); - - this.section.wrapper.find(".form-column").removeClass() - .addClass("form-column") - .addClass("col-sm-" + colspan); - - } - refresh () { - this.section.refresh(); - } -}; +} diff --git a/frappe/public/js/frappe/form/section.js b/frappe/public/js/frappe/form/section.js new file mode 100644 index 0000000000..f84dd6a58b --- /dev/null +++ b/frappe/public/js/frappe/form/section.js @@ -0,0 +1,158 @@ +// import '../class'; + +frappe.ui.form.Section = class Section { + constructor(layout, df, tab) { + this.layout = layout; + this.tab = tab; + this.parent = this.tab && this.tab.wrapper || null; + this.df = df || {}; + this.fields_list = []; + this.fields_dict = {}; + + this.make(); + + if (this.df.label && this.df.collapsible && localStorage.getItem(df.css_class + '-closed')) { + this.collapse(); + } + + this.row = { + wrapper: this.wrapper + }; + + this.refresh(); + } + + make() { + if (!this.layout.page) { + this.layout.page = $('
').appendTo(this.layout.wrapper); + } + + let make_card = this.layout.card_layout; + + this.wrapper = $(`
+ `).appendTo(this.parent || this.layout.page); + + this.layout.sections.push(this); + + if (this.df) { + if (this.df.label) { + 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.df.body_html) { + this.body.append(this.df.body_html); + } + } + + make_head() { + this.head = $(` +
+ ${__(this.df.label)} + +
+ `); + + 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.set_icon(); + this.indicator.show(); + } + } + + refresh() { + if (!this.df) return; + + // hide if explicitly hidden + let hide = this.df.hidden || this.df.hidden_due_to_dependency; + if (!hide && this.layout && this.layout.frm && !this.layout.frm.get_perm(this.df.permlevel || 0, "read")) { + hide = true; + } + + this.wrapper.toggleClass("hide-control", !!hide); + // this.tab && this.tab.refresh(); + } + + collapse(hide) { + // unknown edge case + if (!(this.head && this.body)) { + return; + } + + if (hide === undefined) { + hide = !this.body.hasClass("hide"); + } + + this.body.toggleClass("hide", hide); + this.head && this.head.toggleClass("collapsed", hide); + + this.set_icon(hide); + + // refresh signature fields + this.fields_list.forEach((f) => { + if (f.df.fieldtype == 'Signature') { + f.refresh(); + } + }); + + // save state for next reload ('' is falsy) + if (this.df.css_class) + localStorage.setItem(this.df.css_class + '-closed', hide ? '1' : ''); + } + + set_icon(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'); + } + + has_missing_mandatory () { + var missing_mandatory = false; + for (var j = 0, l = this.fields_list.length; j < l; j++) { + var section_df = this.fields_list[j].df; + if (section_df.reqd && this.layout.doc[section_df.fieldname] == null) { + missing_mandatory = true; + break; + } + } + return missing_mandatory; + } + + hide() { + this.wrapper.toggleClass("hide-control", true); + } + + show() { + this.wrapper.toggleClass("hide-control", false); + this.tab && this.tab.toggle(true); + } +} \ No newline at end of file diff --git a/frappe/public/js/frappe/form/tab.js b/frappe/public/js/frappe/form/tab.js new file mode 100644 index 0000000000..bcd532061f --- /dev/null +++ b/frappe/public/js/frappe/form/tab.js @@ -0,0 +1,76 @@ +frappe.ui.form.Tab = class Tab { + constructor(layout, df) { + this.layout = layout; + this.df = df || {}; + this.label = this.df && this.df.label || 'Details'; + this.fields_list = []; + this.fields_dict = {}; + this.make(); + this.refresh(); + } + + make() { + if (!this.layout.page) { + this.layout.page = $('
').appendTo(this.layout.wrapper); + } + + const id = `${frappe.scrub(this.layout.doctype, '-')}-${this.df.fieldname}`; + this.parent = $(``).appendTo(this.layout.tabs_list); + + this.wrapper = $(`
+ `).appendTo(this.layout.tabs_content); + + this.layout.tabs.push(this); + } + + set_content() { + + } + + refresh() { + if (!this.df) return; + + // hide if explicitly hidden + let hide = this.df.hidden || this.df.hidden_due_to_dependency; + if (!hide && this.layout && this.layout.frm && !this.layout.frm.get_perm(this.df.permlevel || 0, "read")) { + hide = true; + } + this.toggle(!hide); + } + + toggle(show) { + this.parent.toggleClass('hide', !show); + this.wrapper.toggleClass('hide', !show); + this.parent.toggleClass('show', show); + this.wrapper.toggleClass('show', show); + } + + show() { + this.parent.show(); + } + + hide() { + this.parent.hide(); + } + + set_active() { + this.parent.find('.nav-link').tab('show'); + this.wrapper.addClass('show'); + } + + is_active() { + return this.wrapper.hasClass('active'); + } + + is_hidden() { + this.wrapper.hasClass('hidden') + && this.parent.hasClass('hidden'); + } +} diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index b8b908eb95..9fc357e9c4 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -5,7 +5,6 @@ frappe.provide('frappe.ui'); frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { constructor(opts) { super(opts); - this.first_button = false; this.dirty = false; $.each(this.fields || [], function(i, f) { if(!f.fieldname && f.label) { @@ -16,6 +15,7 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { this.set_values(this.values); } } + make() { var me = this; if(this.fields) { @@ -61,6 +61,7 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { } }); } + catch_enter_as_submit() { var me = this; $(this.body).find('input[type="text"], input[type="password"], select').keypress(function(e) { @@ -72,13 +73,16 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { } }); } + get_input(fieldname) { var field = this.fields_dict[fieldname]; return $(field.txt ? field.txt : field.input); } + get_field(fieldname) { return this.fields_dict[fieldname]; } + get_values(ignore_errors) { var ret = {}; var errors = []; @@ -111,10 +115,12 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { } return ret; } + get_value(key) { var f = this.fields_dict[key]; return f && (f.get_value ? f.get_value() : null); } + set_value(key, val){ return new Promise(resolve => { var f = this.fields_dict[key]; @@ -129,9 +135,11 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { } }); } + set_input(key, val) { return this.set_value(key, val); } + set_values(dict) { let promises = []; for(var key in dict) { @@ -142,6 +150,7 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { return Promise.all(promises); } + clear() { for(var key in this.fields_dict) { var f = this.fields_dict[key]; @@ -150,9 +159,10 @@ frappe.ui.FieldGroup = class FieldGroup extends frappe.ui.form.Layout { } } } + set_df_property (fieldname, prop, value) { - const field = this.get_field(fieldname); + const field = this.get_field(fieldname); field.df[prop] = value; field.refresh(); } -}; +} diff --git a/frappe/public/scss/desk/form.scss b/frappe/public/scss/desk/form.scss index 0bb686f045..a7f9fd5ab6 100644 --- a/frappe/public/scss/desk/form.scss +++ b/frappe/public/scss/desk/form.scss @@ -50,11 +50,11 @@ @extend .frappe-card; } -.form-dashboard { +.form-dashboard-section { .section-body:first-child { margin-top: 0; } - .form-dashboard-section .section-body { + .section-body { display: block; padding-left: var(--padding-md); padding-right: var(--padding-md); @@ -302,6 +302,24 @@ } } +.form-tabs { + .nav-item { + .nav-link { + padding-bottom: 15px; + color: var(--gray-700); + padding-left: 0; + padding-right: 0; + margin-right: 30px; + + &.active { + font-weight: 500; + border-bottom: 1px solid var(--primary); + color: var(--text-color); + } + } + } +} + .progress-area { padding-top: var(--padding-md); padding-bottom: var(--padding-md); From 6e9555ca815ea3b0670834a0135911b5b654083d Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 29 Apr 2021 15:02:47 +0530 Subject: [PATCH 002/204] feat: add Tab Break to Custom Field fieldtype --- frappe/custom/doctype/custom_field/custom_field.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 2f0819ab68..4f987e3d14 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -120,7 +120,7 @@ "label": "Field Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature\nTab Break", "reqd": 1 }, { @@ -417,7 +417,7 @@ "idx": 1, "index_web_pages_for_search": 1, "links": [], - "modified": "2020-10-29 06:14:43.073329", + "modified": "2021-04-29 15:02:06.442612", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", From 4be537d287125aade9c357efc393d245fdb3ec5c Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 30 Apr 2021 12:58:47 +0530 Subject: [PATCH 003/204] fix: add bottom margin to tabs --- frappe/public/scss/desk/form.scss | 33 ++++++++++++++++--------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/frappe/public/scss/desk/form.scss b/frappe/public/scss/desk/form.scss index a7f9fd5ab6..c2a7c00e40 100644 --- a/frappe/public/scss/desk/form.scss +++ b/frappe/public/scss/desk/form.scss @@ -302,19 +302,23 @@ } } -.form-tabs { - .nav-item { - .nav-link { - padding-bottom: 15px; - color: var(--gray-700); - padding-left: 0; - padding-right: 0; - margin-right: 30px; - - &.active { - font-weight: 500; - border-bottom: 1px solid var(--primary); - color: var(--text-color); +.form-tabs-list { + margin-bottom: var(--margin-lg); + + .form-tabs { + .nav-item { + .nav-link { + padding-bottom: var(--padding-md); + color: var(--gray-700); + padding-left: 0; + padding-right: 0; + margin-right: var(--margin-xl); + + &.active { + font-weight: 500; + border-bottom: 1px solid var(--primary); + color: var(--text-color); + } } } } @@ -373,7 +377,4 @@ .form-column:not(:first-child) { padding-top: var(--padding-md); } - - - } From 022f7e1c06c8a076fe1c36e2a60059242b077015 Mon Sep 17 00:00:00 2001 From: prssanna Date: Sat, 8 May 2021 17:59:25 +0530 Subject: [PATCH 004/204] fix: label for Tab Break fields --- frappe/core/doctype/doctype/doctype.py | 2 ++ frappe/custom/doctype/custom_field/custom_field.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 84673f990a..3cd336377c 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -277,6 +277,8 @@ class DocType(Document): d.fieldname = d.fieldname + '_section' elif d.fieldtype=='Column Break': d.fieldname = d.fieldname + '_column' + elif d.fieldtype=='Tab Break': + d.fieldname = d.fieldname + '_tab' else: d.fieldname = d.fieldtype.lower().replace(" ","_") + "_" + str(d.idx) else: diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 39aff8b4a7..dc09173595 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -19,7 +19,7 @@ class CustomField(Document): if not self.fieldname: label = self.label if not label: - if self.fieldtype in ["Section Break", "Column Break"]: + if self.fieldtype in ["Section Break", "Column Break", "Tab Break"]: label = self.fieldtype + "_" + str(self.idx) else: frappe.throw(_("Label is mandatory")) From ea3ca571ad768e0d6e1b368b6ffdfe21d76bb75a Mon Sep 17 00:00:00 2001 From: prssanna Date: Sat, 8 May 2021 18:00:30 +0530 Subject: [PATCH 005/204] fix: add Tab Break to no value fields --- frappe/custom/doctype/property_setter/property_setter.py | 2 +- frappe/model/__init__.py | 2 ++ .../page/print_format_builder/print_format_builder.js | 6 +++--- .../print_format_builder/print_format_builder_sidebar.html | 2 +- frappe/public/js/frappe/form/toolbar.js | 2 +- frappe/public/js/frappe/model/model.js | 4 ++-- 6 files changed, 10 insertions(+), 8 deletions(-) diff --git a/frappe/custom/doctype/property_setter/property_setter.py b/frappe/custom/doctype/property_setter/property_setter.py index 56e5829271..f7e8650489 100644 --- a/frappe/custom/doctype/property_setter/property_setter.py +++ b/frappe/custom/doctype/property_setter/property_setter.py @@ -35,7 +35,7 @@ class PropertySetter(Document): fields=['fieldname', 'label', 'fieldtype'], filters={ 'parent': dt, - 'fieldtype': ['not in', ('Section Break', 'Column Break', 'HTML', 'Read Only', 'Fold') + frappe.model.table_fields], + 'fieldtype': ['not in', ('Section Break', 'Column Break', 'Tab Break', 'HTML', 'Read Only', 'Fold') + frappe.model.table_fields], 'fieldname': ['!=', ''] }, order_by='label asc', diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 205b451336..356275e7b8 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -41,6 +41,7 @@ data_fieldtypes = ( no_value_fields = ( 'Section Break', 'Column Break', + 'Tab Break', 'HTML', 'Table', 'Table MultiSelect', @@ -53,6 +54,7 @@ no_value_fields = ( display_fieldtypes = ( 'Section Break', 'Column Break', + 'Tab Break', 'HTML', 'Button', 'Image', diff --git a/frappe/printing/page/print_format_builder/print_format_builder.js b/frappe/printing/page/print_format_builder/print_format_builder.js index ca2a8bc378..51cac66026 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.js +++ b/frappe/printing/page/print_format_builder/print_format_builder.js @@ -261,7 +261,7 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder { } else if(f.fieldtype==="Column Break") { set_column(); - } else if(!in_list(["Section Break", "Column Break", "Fold"], f.fieldtype) + } else if(!in_list(["Section Break", "Column Break", "Tab Break", "Fold"], f.fieldtype) && f.label) { if(!column) set_column(); @@ -298,7 +298,7 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder { init_visible_columns(f) { f.visible_columns = [] $.each(frappe.get_meta(f.options).fields, function(i, _f) { - if(!in_list(["Section Break", "Column Break"], _f.fieldtype) && + if(!in_list(["Section Break", "Column Break", "Tab Break"], _f.fieldtype) && !_f.print_hide && f.label) { // column names set as fieldname|width @@ -606,7 +606,7 @@ frappe.PrintFormatBuilder = class PrintFormatBuilder { // add remaining fields $.each(doc_fields, function(j, f) { if (f && !in_list(column_names, f.fieldname) - && !in_list(["Section Break", "Column Break"], f.fieldtype) && f.label) { + && !in_list(["Section Break", "Column Break", "Tab Break"], f.fieldtype) && f.label) { fields.push(f); } }) diff --git a/frappe/printing/page/print_format_builder/print_format_builder_sidebar.html b/frappe/printing/page/print_format_builder/print_format_builder_sidebar.html index 1ebb87ac31..c608eecbbd 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder_sidebar.html +++ b/frappe/printing/page/print_format_builder/print_format_builder_sidebar.html @@ -4,7 +4,7 @@