From 58b90e72f35a6b004230b3197ca624ea70c5a5dd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 13:20:27 +0530 Subject: [PATCH 01/31] feat: duration control --- frappe/core/doctype/docfield/docfield.json | 4 +- .../doctype/custom_field/custom_field.json | 4 +- .../customize_form_field.json | 4 +- frappe/database/mariadb/database.py | 3 +- frappe/database/postgres/database.py | 3 +- frappe/model/__init__.py | 3 +- frappe/public/build.json | 4 +- .../public/js/frappe/form/controls/control.js | 1 + .../js/frappe/form/controls/duration.js | 159 ++++++++++++++++++ frappe/public/less/controls.less | 63 +++++++ .../website_theme/standard/standard.json | 2 +- 11 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 frappe/public/js/frappe/form/controls/duration.js diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 6d8ee41a5a..04f31375d6 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -86,7 +86,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\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", "reqd": 1, "search_index": 1 }, @@ -453,7 +453,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-19 21:54:13.783908", + "modified": "2020-04-30 09:00:38.012601", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 394f38b56c..6534bfd90e 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -116,7 +116,7 @@ "label": "Field Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\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", "reqd": 1 }, { @@ -383,7 +383,7 @@ "icon": "fa fa-glass", "idx": 1, "links": [], - "modified": "2020-04-10 11:57:10.392218", + "modified": "2020-04-30 09:15:24.394782", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index d7887cf8bd..d1510e0858 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -78,7 +78,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime", + "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\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime", "reqd": 1, "search_index": 1 }, @@ -393,7 +393,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-10 11:58:44.573537", + "modified": "2020-04-30 09:15:51.094586", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index cd053569f0..7350e0aaef 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -55,7 +55,8 @@ class MariaDBDatabase(Database): 'Signature': ('longtext', ''), 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('longtext', ''), - 'Geolocation': ('longtext', '') + 'Geolocation': ('longtext', ''), + 'Duration': ('bigint', '20') } def get_connection(self): diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index e30ef3293f..78dc6d42ec 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -60,7 +60,8 @@ class PostgresDatabase(Database): 'Signature': ('text', ''), 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('text', ''), - 'Geolocation': ('text', '') + 'Geolocation': ('text', ''), + 'Duration': ('bigint', '20') } def get_connection(self): diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 93ef78df7b..3c5d996439 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -34,7 +34,8 @@ data_fieldtypes = ( 'Signature', 'Color', 'Barcode', - 'Geolocation' + 'Geolocation', + 'Duration' ) no_value_fields = ('Section Break', 'Column Break', 'HTML', 'Table', 'Table MultiSelect', 'Button', 'Image', diff --git a/frappe/public/build.json b/frappe/public/build.json index 7f55924a6b..fe89e3bcc9 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -69,7 +69,8 @@ "node_modules/air-datepicker/dist/js/i18n/datepicker.sk.js", "node_modules/air-datepicker/dist/js/i18n/datepicker.zh.js", "public/js/frappe/ui/capture.js", - "public/js/frappe/form/controls/control.js" + "public/js/frappe/form/controls/control.js", + "public/js/frappe/form/controls/duration.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", @@ -259,6 +260,7 @@ "public/js/frappe/form/templates/timeline.html", "public/js/frappe/form/templates/timeline_item.html", "public/js/frappe/form/controls/control.js", + "public/js/frappe/form/controls/duration.js", "public/js/frappe/views/formview.js", "public/js/frappe/form/form.js", "public/js/frappe/meta_tag.js" diff --git a/frappe/public/js/frappe/form/controls/control.js b/frappe/public/js/frappe/form/controls/control.js index 2bf6292abc..168da2717c 100644 --- a/frappe/public/js/frappe/form/controls/control.js +++ b/frappe/public/js/frappe/form/controls/control.js @@ -38,6 +38,7 @@ import './table_multiselect'; import './multiselect_pills'; import './multiselect_list'; import './rating'; +import './duration'; frappe.ui.form.make_control = function (opts) { var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, ""); diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js new file mode 100644 index 0000000000..8c9aa68b5b --- /dev/null +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -0,0 +1,159 @@ +frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ + make_input: function() { + this._super(); + this.make_picker(); + }, + + make_picker: function() { + this.inputs = []; + this.$picker = $( + `
+
+
` + ); + this.$wrapper.append(this.$picker); + this.build_numeric_input('days', false); + this.build_numeric_input('hrs', false, 23); + this.build_numeric_input('mins', false, 59); + this.build_numeric_input('secs', false, 59); + this.set_duration_picker(); + this.$picker.hide(); + this.bind_events(); + this.refresh(); + }, + + build_numeric_input: function(label, hidden, max) { + let $duration_input = $(` + + `) + + let $input = $(`
`).prepend($duration_input); + + if (max) { + $duration_input.attr('max', max); + } + + this.inputs[label] = $duration_input; + + let $control = $(` +
+
${label}
+
` + ) + + if (hidden) { + $control.addClass('hidden'); + } + $control.prepend($input); + $control.appendTo($('.picker-row')); + }, + + set_duration_picker() { + let total_duration = this.seconds_to_duration(this.value); + if (total_duration.days()) { + this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days()); + } + if (total_duration.hours()) { + this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours()); + } + if (total_duration.minutes()) { + this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes()); + } + if (total_duration.seconds()) { + this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds()); + } + }, + + bind_events: function() { + let me = this; + let clicked = false; + + this.$picker.on('change', '.duration-input', () => { + clicked = false; + me.set_value(me.duration_to_seconds()); + me.set_focus(); + }); + + this.$wrapper.find(".duration-input").mousedown(() => { + clicked = true; + }); + + this.$input.on("focus", () => { + this.$picker.show(); + }); + + this.$input.on("blur", () => { + if (clicked) { + clicked = false; + } else { + this.$picker.hide(); + } + }); + }, + + refresh_input: function() { + this._super(); + this.set_duration_picker(); + }, + + format_for_input: function(value) { + let input_string = ''; + if (value) { + let total_duration = this.seconds_to_duration(value); + + if (total_duration.days()) { + input_string += total_duration.days() + 'd'; + } + if (total_duration.hours()) { + input_string += (input_string.length ? " " : ""); + input_string += total_duration.hours() + 'h'; + } + if (total_duration.minutes()) { + input_string += (input_string.length ? " " : ""); + input_string += total_duration.minutes() + 'm'; + } + if (total_duration.seconds()) { + input_string += (input_string.length ? " " : ""); + input_string += total_duration.seconds() + 's'; + } + } + return input_string; + }, + + seconds_to_duration(value) { + let secs = value; + let total_duration = moment.duration({ + days: Math.floor(secs / (3600 * 24)), + hours: Math.floor(secs % (3600 * 24) / 3600), + minutes: Math.floor(secs % 3600 / 60), + seconds : Math.floor(secs % 60) + }); + return total_duration; + }, + + duration_to_seconds() { + let value = 0; + if (this.inputs) { + let total_duration = moment.duration({ + seconds : parseInt(this.inputs.secs.val()), + minutes : parseInt(this.inputs.mins.val()), + hours : parseInt(this.inputs.hrs.val()), + days : parseInt(this.inputs.days.val()) + }); + + if (total_duration.days()) { + value += total_duration.days() * 24 * 60 * 60; + } + if (total_duration.hours()) { + value += total_duration.hours() * 60 * 60; + } + if (total_duration.minutes()) { + value += total_duration.minutes() * 60; + } + if (total_duration.seconds()) { + value += total_duration.seconds(); + } + } + return value; + } +}); \ No newline at end of file diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index d88e6adaec..9b585a58f3 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -165,3 +165,66 @@ top: 8px; } } + +/* duration control */ + +.duration-picker { + position: relative; + z-index: 999; + + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0,0,0,.15); + background: #fff; + border: 1px solid @border-color; + padding-top: 10px; + padding-left: 5px; + position: absolute; + + &:after, + &:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + pointer-events: none; + position: absolute; + bottom: 100%; + left: 30px; + } + &:after { + border-color: rgba(255, 255, 255, 0); + border-bottom-color: #fff; + border-width: 8px; + margin-left: -8px; + } + &:before { + border-color: rgba(221, 221, 221, 0); + border-bottom-color: @border-color; + border-width: 9px; + margin-left: -9px; + } + + .duration-row { + margin: 7px; + display: flex; + } + + .duration-col { + margin-left: 2px; + } + + .duration-input { + width: 60px; + } + + .duration-label { + justify-content: center; + } + + .picker-row { + display: flex; + width: 335px; + margin-left: 5px; + margin-bottom: 3px; + } +} \ No newline at end of file diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json index a799f25425..799eee72ab 100644 --- a/frappe/website/website_theme/standard/standard.json +++ b/frappe/website/website_theme/standard/standard.json @@ -9,7 +9,7 @@ "font_properties": "300,600", "font_size": "", "idx": 27, - "modified": "2020-04-21 02:10:31.761219", + "modified": "2020-04-24 10:02:44.993836", "modified_by": "Administrator", "module": "Website", "name": "Standard", From 2cd39a1bc4aa4eff44b482b9d786b224b7ac0923 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 14:36:32 +0530 Subject: [PATCH 02/31] feat: add duration options --- .../js/frappe/form/controls/duration.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 8c9aa68b5b..c615dd6eb8 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -6,16 +6,17 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ make_picker: function() { this.inputs = []; + this.set_duration_options(); this.$picker = $( `
` ); this.$wrapper.append(this.$picker); - this.build_numeric_input('days', false); - this.build_numeric_input('hrs', false, 23); - this.build_numeric_input('mins', false, 59); - this.build_numeric_input('secs', false, 59); + this.build_numeric_input('days', !this.duration_options.showDays); + this.build_numeric_input('hrs', false); + this.build_numeric_input('mins', false); + this.build_numeric_input('secs', !this.duration_options.showSeconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -48,6 +49,19 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ $control.appendTo($('.picker-row')); }, + set_duration_options() { + let lang = 'en'; + frappe.boot.user && (lang = frappe.boot.user.language); + + this.duration_options = { + lang: lang, + max: 59, + checkRanges: false, + showSeconds: true, + showDays: true, + }; + }, + set_duration_picker() { let total_duration = this.seconds_to_duration(this.value); if (total_duration.days()) { From 94bfe471a56ea51a11224a68e5f87d0e85c81c15 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 19:12:19 +0530 Subject: [PATCH 03/31] fix: wrong days setting --- .../js/frappe/form/controls/duration.js | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c615dd6eb8..ade49e2293 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -64,17 +64,17 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ set_duration_picker() { let total_duration = this.seconds_to_duration(this.value); - if (total_duration.days()) { - this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days()); + if (total_duration.days) { + this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); } - if (total_duration.hours()) { - this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours()); + if (total_duration.hours) { + this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours); } - if (total_duration.minutes()) { - this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes()); + if (total_duration.minutes) { + this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes); } - if (total_duration.seconds()) { - this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds()); + if (total_duration.seconds) { + this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds); } }, @@ -115,20 +115,20 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ if (value) { let total_duration = this.seconds_to_duration(value); - if (total_duration.days()) { - input_string += total_duration.days() + 'd'; + if (total_duration.days) { + input_string += total_duration.days + 'd'; } - if (total_duration.hours()) { + if (total_duration.hours) { input_string += (input_string.length ? " " : ""); - input_string += total_duration.hours() + 'h'; + input_string += total_duration.hours + 'h'; } - if (total_duration.minutes()) { + if (total_duration.minutes) { input_string += (input_string.length ? " " : ""); - input_string += total_duration.minutes() + 'm'; + input_string += total_duration.minutes + 'm'; } - if (total_duration.seconds()) { + if (total_duration.seconds) { input_string += (input_string.length ? " " : ""); - input_string += total_duration.seconds() + 's'; + input_string += total_duration.seconds + 's'; } } return input_string; @@ -136,36 +136,36 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ seconds_to_duration(value) { let secs = value; - let total_duration = moment.duration({ + let total_duration = { days: Math.floor(secs / (3600 * 24)), hours: Math.floor(secs % (3600 * 24) / 3600), minutes: Math.floor(secs % 3600 / 60), seconds : Math.floor(secs % 60) - }); + }; return total_duration; }, duration_to_seconds() { let value = 0; if (this.inputs) { - let total_duration = moment.duration({ + let total_duration = { seconds : parseInt(this.inputs.secs.val()), minutes : parseInt(this.inputs.mins.val()), hours : parseInt(this.inputs.hrs.val()), days : parseInt(this.inputs.days.val()) - }); + }; - if (total_duration.days()) { - value += total_duration.days() * 24 * 60 * 60; + if (total_duration.days) { + value += total_duration.days * 24 * 60 * 60; } - if (total_duration.hours()) { - value += total_duration.hours() * 60 * 60; + if (total_duration.hours) { + value += total_duration.hours * 60 * 60; } - if (total_duration.minutes()) { - value += total_duration.minutes() * 60; + if (total_duration.minutes) { + value += total_duration.minutes * 60; } - if (total_duration.seconds()) { - value += total_duration.seconds(); + if (total_duration.seconds) { + value += total_duration.seconds; } } return value; From dc555864dd544c168d38b77c1a814991772bb572 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 21:40:35 +0530 Subject: [PATCH 04/31] fix: add formatter and util methods for duration fieldtype --- .../js/frappe/form/controls/duration.js | 35 ++----------------- frappe/public/js/frappe/form/formatters.js | 7 ++++ frappe/public/js/frappe/utils/utils.js | 34 ++++++++++++++++++ 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index ade49e2293..c0ca37dea3 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -63,7 +63,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_picker() { - let total_duration = this.seconds_to_duration(this.value); + let total_duration = frappe.utils.seconds_to_duration(this.value); if (total_duration.days) { this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); } @@ -111,38 +111,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, format_for_input: function(value) { - let input_string = ''; - if (value) { - let total_duration = this.seconds_to_duration(value); - - if (total_duration.days) { - input_string += total_duration.days + 'd'; - } - if (total_duration.hours) { - input_string += (input_string.length ? " " : ""); - input_string += total_duration.hours + 'h'; - } - if (total_duration.minutes) { - input_string += (input_string.length ? " " : ""); - input_string += total_duration.minutes + 'm'; - } - if (total_duration.seconds) { - input_string += (input_string.length ? " " : ""); - input_string += total_duration.seconds + 's'; - } - } - return input_string; - }, - - seconds_to_duration(value) { - let secs = value; - let total_duration = { - days: Math.floor(secs / (3600 * 24)), - hours: Math.floor(secs % (3600 * 24) / 3600), - minutes: Math.floor(secs % 3600 / 60), - seconds : Math.floor(secs % 60) - }; - return total_duration; + return frappe.utils.get_formatted_duration(value); }, duration_to_seconds() { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index d178c59100..cc24c48a1f 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -188,6 +188,13 @@ frappe.form.formatters = { return value || ""; }, + Duration: function(value) { + if (value) { + value = frappe.utils.get_formatted_duration(value); + } + + return value || ""; + }, LikedBy: function(value) { var html = ""; $.each(JSON.parse(value || "[]"), function(i, v) { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 1afdbfd81c..2584248711 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -778,6 +778,40 @@ Object.assign(frappe.utils, { version: M[1], }; }, + + get_formatted_duration(value) { + let duration = ''; + if (value) { + let total_duration = frappe.utils.seconds_to_duration(value); + + if (total_duration.days) { + duration += total_duration.days + 'd'; + } + if (total_duration.hours) { + duration += (duration.length ? " " : ""); + duration += total_duration.hours + 'h'; + } + if (total_duration.minutes) { + duration += (duration.length ? " " : ""); + duration += total_duration.minutes + 'm'; + } + if (total_duration.seconds) { + duration += (duration.length ? " " : ""); + duration += total_duration.seconds + 's'; + } + } + return duration; + }, + seconds_to_duration(value) { + let secs = value; + let total_duration = { + days: Math.floor(secs / (3600 * 24)), + hours: Math.floor(secs % (3600 * 24) / 3600), + minutes: Math.floor(secs % 3600 / 60), + seconds : Math.floor(secs % 60) + }; + return total_duration; + }, }); // Array de duplicate From 003225f20c4c776f794cb3935a69c59933675dc1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 21:59:46 +0530 Subject: [PATCH 05/31] fix: filters for duration fieldtype --- frappe/public/js/frappe/form/controls/duration.js | 4 ++++ frappe/public/js/frappe/ui/filters/filters.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c0ca37dea3..791882185c 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -105,6 +105,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }); }, + get_value() { + return cint(this.value); + }, + refresh_input: function() { this._super(); this.set_duration_picker(); diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js index 3646dc6b6e..94e2b59d22 100644 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -200,6 +200,8 @@ frappe.ui.FilterList = Class.extend({ value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value; } else if(field.df.original_type==="Check") { value = {0:"No", 1:"Yes"}[cint(value)]; + } else if (field.df.original_type === "Duration") { + value = frappe.utils.get_formatted_duration(value); } value = frappe.format(value, field.df, {only_value: 1}); From 58e4ed06037b7ea45a88a087db0e941b1196d9a5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 May 2020 00:59:03 +0530 Subject: [PATCH 06/31] fix: control styling in child table --- frappe/public/js/frappe/form/controls/duration.js | 4 ++-- frappe/public/less/controls.less | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 791882185c..86dee3866f 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -25,7 +25,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ build_numeric_input: function(label, hidden, max) { let $duration_input = $(` - + `) let $input = $(`
`).prepend($duration_input); @@ -46,7 +46,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ $control.addClass('hidden'); } $control.prepend($input); - $control.appendTo($('.picker-row')); + $control.appendTo(this.$picker.find('.picker-row')); }, set_duration_options() { diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index 9b585a58f3..ac2936b149 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -204,6 +204,12 @@ margin-left: -9px; } + .row .col { + // for fixing layout in child table + padding-left: 0px !important; + padding-right: 0px !important; + } + .duration-row { margin: 7px; display: flex; @@ -215,6 +221,7 @@ .duration-input { width: 60px; + border: 1px solid rgba(0, 0, 0, 0.25) !important; } .duration-label { From 87f2c66e0f3b13359b94819017c3252033089a4d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 00:09:55 +0530 Subject: [PATCH 07/31] feat: options to hide days and seconds for duration control --- .../js/frappe/form/controls/duration.js | 21 ++++++++++--------- frappe/public/js/frappe/utils/utils.js | 10 ++++++--- frappe/public/less/controls.less | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 86dee3866f..49c1e46530 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -50,20 +50,16 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - let lang = 'en'; - frappe.boot.user && (lang = frappe.boot.user.language); - this.duration_options = { - lang: lang, - max: 59, - checkRanges: false, showSeconds: true, showDays: true, }; }, set_duration_picker() { - let total_duration = frappe.utils.seconds_to_duration(this.value); + let total_duration = frappe.utils.seconds_to_duration(this.value, + this.duration_options.showDays, this.set_duration_options.showSeconds); + if (total_duration.days) { this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); } @@ -115,18 +111,23 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, format_for_input: function(value) { - return frappe.utils.get_formatted_duration(value); + return frappe.utils.get_formatted_duration(value, + this.duration_options.showDays, this.duration_options.showSeconds); }, duration_to_seconds() { let value = 0; if (this.inputs) { let total_duration = { - seconds : parseInt(this.inputs.secs.val()), minutes : parseInt(this.inputs.mins.val()), hours : parseInt(this.inputs.hrs.val()), - days : parseInt(this.inputs.days.val()) }; + if (this.duration_options.showDays) { + total_duration.days = parseInt(this.inputs.days.val()); + } + if (this.duration_options.showSeconds) { + total_duration.seconds = parseInt(this.inputs.secs.val()); + } if (total_duration.days) { value += total_duration.days * 24 * 60 * 60; diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 2584248711..559598de5b 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -779,10 +779,10 @@ Object.assign(frappe.utils, { }; }, - get_formatted_duration(value) { + get_formatted_duration(value, showDays=true, showSeconds=true) { let duration = ''; if (value) { - let total_duration = frappe.utils.seconds_to_duration(value); + let total_duration = frappe.utils.seconds_to_duration(value, showDays, showSeconds); if (total_duration.days) { duration += total_duration.days + 'd'; @@ -802,7 +802,7 @@ Object.assign(frappe.utils, { } return duration; }, - seconds_to_duration(value) { + seconds_to_duration(value, showDays=true, showSeconds=true) { let secs = value; let total_duration = { days: Math.floor(secs / (3600 * 24)), @@ -810,6 +810,10 @@ Object.assign(frappe.utils, { minutes: Math.floor(secs % 3600 / 60), seconds : Math.floor(secs % 60) }; + if (!showDays) { + total_duration.hours = Math.floor(secs / 3600) + total_duration.days = 0 + } return total_duration; }, }); diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index ac2936b149..b6ac3cf359 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -230,8 +230,8 @@ .picker-row { display: flex; - width: 335px; margin-left: 5px; margin-bottom: 3px; + margin-right: 12px; } } \ No newline at end of file From 156a00724fd6adbeb65471e99d4fb0744c6e7c3f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 09:39:57 +0530 Subject: [PATCH 08/31] feat: add duration options to Duration docfield --- frappe/core/doctype/docfield/docfield.json | 18 +++++++++++++++++- .../public/js/frappe/form/controls/duration.js | 11 +++-------- frappe/public/js/frappe/form/formatters.js | 5 +++-- frappe/public/js/frappe/model/meta.js | 8 ++++++++ frappe/public/js/frappe/utils/utils.js | 8 ++++---- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 04f31375d6..8ae2b740b5 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -13,6 +13,8 @@ "fieldname", "precision", "length", + "show_days", + "show_seconds", "reqd", "search_index", "in_list_view", @@ -448,12 +450,26 @@ { "fieldname": "column_break_38", "fieldtype": "Column Break" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_days", + "fieldtype": "Check", + "label": "Show Days" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_seconds", + "fieldtype": "Check", + "label": "Show Seconds" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-30 09:00:38.012601", + "modified": "2020-05-06 09:06:25.224411", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 49c1e46530..de8aa96c95 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -50,15 +50,11 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - this.duration_options = { - showSeconds: true, - showDays: true, - }; + this.duration_options = frappe.meta.get_duration_options(this.df, this.get_doc()); }, set_duration_picker() { - let total_duration = frappe.utils.seconds_to_duration(this.value, - this.duration_options.showDays, this.set_duration_options.showSeconds); + let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); if (total_duration.days) { this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); @@ -111,8 +107,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, format_for_input: function(value) { - return frappe.utils.get_formatted_duration(value, - this.duration_options.showDays, this.duration_options.showSeconds); + return frappe.utils.get_formatted_duration(value, this.duration_options); }, duration_to_seconds() { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index cc24c48a1f..a9abd1c601 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -188,9 +188,10 @@ frappe.form.formatters = { return value || ""; }, - Duration: function(value) { + Duration: function(value, docfield, doc) { if (value) { - value = frappe.utils.get_formatted_duration(value); + let duration_options = frappe.meta.get_duration_options(docfield, doc); + value = frappe.utils.get_formatted_duration(value, duration_options); } return value || ""; diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index b7ad52838c..acde2a367f 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -267,4 +267,12 @@ $.extend(frappe.meta, { } return precision; }, + + get_duration_options: function(df, doc) { + let duration_options = { + showDays: df.show_days, + showSeconds: df.show_seconds + }; + return duration_options; + } }); diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 559598de5b..9281ba6c43 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -779,10 +779,10 @@ Object.assign(frappe.utils, { }; }, - get_formatted_duration(value, showDays=true, showSeconds=true) { + get_formatted_duration(value, duration_options) { let duration = ''; if (value) { - let total_duration = frappe.utils.seconds_to_duration(value, showDays, showSeconds); + let total_duration = frappe.utils.seconds_to_duration(value, duration_options); if (total_duration.days) { duration += total_duration.days + 'd'; @@ -802,7 +802,7 @@ Object.assign(frappe.utils, { } return duration; }, - seconds_to_duration(value, showDays=true, showSeconds=true) { + seconds_to_duration(value, duration_options) { let secs = value; let total_duration = { days: Math.floor(secs / (3600 * 24)), @@ -810,7 +810,7 @@ Object.assign(frappe.utils, { minutes: Math.floor(secs % 3600 / 60), seconds : Math.floor(secs % 60) }; - if (!showDays) { + if (!duration_options.showDays) { total_duration.hours = Math.floor(secs / 3600) total_duration.days = 0 } From 94116d322784098f2a215ceb134efd1a24e73589 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 09:51:16 +0530 Subject: [PATCH 09/31] fix: duration options for filters --- frappe/public/js/frappe/ui/filters/filters.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js index 94e2b59d22..9a394eade3 100644 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -201,7 +201,11 @@ frappe.ui.FilterList = Class.extend({ } else if(field.df.original_type==="Check") { value = {0:"No", 1:"Yes"}[cint(value)]; } else if (field.df.original_type === "Duration") { - value = frappe.utils.get_formatted_duration(value); + let duration_options = { + showDays: field.df.show_days, + showSeconds: field.df.show_seconds + }; + value = frappe.utils.get_formatted_duration(value, duration_options); } value = frappe.format(value, field.df, {only_value: 1}); From b696306edc65bb96a4ed450451c26ca61937b6c9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 10:15:11 +0530 Subject: [PATCH 10/31] fix: set duration options in refresh_input --- .../js/frappe/form/controls/duration.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index de8aa96c95..c177ae6f17 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -13,10 +13,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ ` ); this.$wrapper.append(this.$picker); - this.build_numeric_input('days', !this.duration_options.showDays); - this.build_numeric_input('hrs', false); - this.build_numeric_input('mins', false); - this.build_numeric_input('secs', !this.duration_options.showSeconds); + this.build_numeric_input("days", !this.duration_options.showDays); + this.build_numeric_input("hrs", false); + this.build_numeric_input("mins", false); + this.build_numeric_input("secs", !this.duration_options.showSeconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -31,7 +31,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let $input = $(`
`).prepend($duration_input); if (max) { - $duration_input.attr('max', max); + $duration_input.attr("max", max); } this.inputs[label] = $duration_input; @@ -43,10 +43,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ ) if (hidden) { - $control.addClass('hidden'); + $control.addClass("hidden"); } $control.prepend($input); - $control.appendTo(this.$picker.find('.picker-row')); + $control.appendTo(this.$picker.find(".picker-row")); }, set_duration_options() { @@ -57,16 +57,16 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); if (total_duration.days) { - this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); + this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); } if (total_duration.hours) { - this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours); + this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); } if (total_duration.minutes) { - this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes); + this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); } if (total_duration.seconds) { - this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds); + this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); } }, @@ -74,7 +74,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let me = this; let clicked = false; - this.$picker.on('change', '.duration-input', () => { + this.$picker.on("change", ".duration-input", () => { clicked = false; me.set_value(me.duration_to_seconds()); me.set_focus(); @@ -103,6 +103,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ refresh_input: function() { this._super(); + this.set_duration_options(); this.set_duration_picker(); }, From 972833d13fb20deebe95acb47d0495baed655831 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 12:36:22 +0530 Subject: [PATCH 11/31] fix: change duration fieldtype to decimal --- frappe/database/mariadb/database.py | 2 +- frappe/database/postgres/database.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 7350e0aaef..4ec89c126d 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -56,7 +56,7 @@ class MariaDBDatabase(Database): 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('longtext', ''), 'Geolocation': ('longtext', ''), - 'Duration': ('bigint', '20') + 'Duration': ('decimal', '18,6') } def get_connection(self): diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 78dc6d42ec..e348916705 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -61,7 +61,7 @@ class PostgresDatabase(Database): 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('text', ''), 'Geolocation': ('text', ''), - 'Duration': ('bigint', '20') + 'Duration': ('decimal', '18,6') } def get_connection(self): From 18ce053a7475759f378038bb7017099c6db989a1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 13:39:40 +0530 Subject: [PATCH 12/31] fix: refresh input --- .../js/frappe/form/controls/duration.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c177ae6f17..c764c9b041 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -56,17 +56,19 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ set_duration_picker() { let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); - if (total_duration.days) { - this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); - } - if (total_duration.hours) { - this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); - } - if (total_duration.minutes) { - this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); - } - if (total_duration.seconds) { - this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); + if (this.$picker) { + if (total_duration.days) { + this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); + } + if (total_duration.hours) { + this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); + } + if (total_duration.minutes) { + this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); + } + if (total_duration.seconds) { + this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); + } } }, From 74eba09e526e521e503ac10b0597dfe6677cbd56 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 14:06:34 +0530 Subject: [PATCH 13/31] fix(style): remove borders on focus for individual inputs --- frappe/public/less/controls.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index b6ac3cf359..161293b558 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -224,6 +224,10 @@ border: 1px solid rgba(0, 0, 0, 0.25) !important; } + .duration-input:focus { + outline: None; + } + .duration-label { justify-content: center; } From 8d79230be2a4e570b311ee1808a9b6c4518d98a7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 13:33:45 +0530 Subject: [PATCH 14/31] fix: code clean-up --- frappe/public/build.json | 4 +-- .../js/frappe/form/controls/duration.js | 34 +++++++------------ 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/frappe/public/build.json b/frappe/public/build.json index 86f3d23c99..d56907b558 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -72,8 +72,7 @@ "node_modules/air-datepicker/dist/js/i18n/datepicker.sk.js", "node_modules/air-datepicker/dist/js/i18n/datepicker.zh.js", "public/js/frappe/ui/capture.js", - "public/js/frappe/form/controls/control.js", - "public/js/frappe/form/controls/duration.js" + "public/js/frappe/form/controls/control.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", @@ -264,7 +263,6 @@ "public/js/frappe/form/templates/timeline.html", "public/js/frappe/form/templates/timeline_item.html", "public/js/frappe/form/controls/control.js", - "public/js/frappe/form/controls/duration.js", "public/js/frappe/views/formview.js", "public/js/frappe/form/form.js", "public/js/frappe/meta_tag.js" diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c764c9b041..01e18de731 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -14,9 +14,9 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ ); this.$wrapper.append(this.$picker); this.build_numeric_input("days", !this.duration_options.showDays); - this.build_numeric_input("hrs", false); - this.build_numeric_input("mins", false); - this.build_numeric_input("secs", !this.duration_options.showSeconds); + this.build_numeric_input("hours", false); + this.build_numeric_input("minutes", false); + this.build_numeric_input("seconds", !this.duration_options.showSeconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -38,7 +38,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let $control = $(`
-
${label}
+
${__(label)}
` ) @@ -57,29 +57,19 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); if (this.$picker) { - if (total_duration.days) { - this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); - } - if (total_duration.hours) { - this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); - } - if (total_duration.minutes) { - this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); - } - if (total_duration.seconds) { - this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); - } + Object.keys(total_duration).forEach(duration => { + this.inputs[duration].prop("value", total_duration[duration]); + }); } }, bind_events: function() { - let me = this; let clicked = false; this.$picker.on("change", ".duration-input", () => { clicked = false; - me.set_value(me.duration_to_seconds()); - me.set_focus(); + this.set_value(this.duration_to_seconds()); + this.set_focus(); }); this.$wrapper.find(".duration-input").mousedown(() => { @@ -117,14 +107,14 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let value = 0; if (this.inputs) { let total_duration = { - minutes : parseInt(this.inputs.mins.val()), - hours : parseInt(this.inputs.hrs.val()), + minutes : parseInt(this.inputs.minutes.val()), + hours : parseInt(this.inputs.hours.val()), }; if (this.duration_options.showDays) { total_duration.days = parseInt(this.inputs.days.val()); } if (this.duration_options.showSeconds) { - total_duration.seconds = parseInt(this.inputs.secs.val()); + total_duration.seconds = parseInt(this.inputs.seconds.val()); } if (total_duration.days) { From 776da4cc907e9b3a332d82ea7d1cda2898b2f02f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 14:00:10 +0530 Subject: [PATCH 15/31] feat: add duration field in web form --- frappe/website/doctype/web_form_field/web_form_field.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/web_form_field/web_form_field.json b/frappe/website/doctype/web_form_field/web_form_field.json index 90f9b24a16..ef7b1c4ddf 100644 --- a/frappe/website/doctype/web_form_field/web_form_field.json +++ b/frappe/website/doctype/web_form_field/web_form_field.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2014-09-01 14:14:14.292173", "doctype": "DocType", "editable_grid": 1, @@ -34,7 +35,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Fieldtype", - "options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nInt\nLink\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break" + "options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break" }, { "fieldname": "label", @@ -119,7 +120,8 @@ } ], "istable": 1, - "modified": "2019-06-07 12:17:10.547133", + "links": [], + "modified": "2020-05-13 13:35:08.454427", "modified_by": "Administrator", "module": "Website", "name": "Web Form Field", From 3174d16ab33f0965e91f55cc38944b9f8ce6d6b2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 18:52:05 +0530 Subject: [PATCH 16/31] fix: sider issues, add comments --- .../js/frappe/form/controls/duration.js | 23 +++++++++++-------- frappe/public/js/frappe/form/formatters.js | 2 +- frappe/public/js/frappe/model/meta.js | 2 +- frappe/public/js/frappe/utils/utils.js | 6 ++--- frappe/public/less/controls.less | 3 +-- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 01e18de731..d6d512da9c 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -26,7 +26,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ build_numeric_input: function(label, hidden, max) { let $duration_input = $(` - `) + `); let $input = $(`
`).prepend($duration_input); @@ -40,7 +40,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({
${__(label)}
` - ) + ); if (hidden) { $control.addClass("hidden"); @@ -50,7 +50,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - this.duration_options = frappe.meta.get_duration_options(this.df, this.get_doc()); + this.duration_options = frappe.meta.get_duration_options(this.df); }, set_duration_picker() { @@ -64,26 +64,31 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, bind_events: function() { + // flag to handle the display property of the picker let clicked = false; + this.$wrapper.find(".duration-input").mousedown(() => { + // input in individual duration boxes + clicked = true; + }); + this.$picker.on("change", ".duration-input", () => { + // duration changed in individual boxes clicked = false; this.set_value(this.duration_to_seconds()); this.set_focus(); }); - this.$wrapper.find(".duration-input").mousedown(() => { - clicked = true; - }); - this.$input.on("focus", () => { this.$picker.show(); }); this.$input.on("blur", () => { + // input in duration boxes, don't close the picker if (clicked) { clicked = false; } else { + // blur event was not due to duration inputs this.$picker.hide(); } }); @@ -107,8 +112,8 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let value = 0; if (this.inputs) { let total_duration = { - minutes : parseInt(this.inputs.minutes.val()), - hours : parseInt(this.inputs.hours.val()), + minutes: parseInt(this.inputs.minutes.val()), + hours: parseInt(this.inputs.hours.val()), }; if (this.duration_options.showDays) { total_duration.days = parseInt(this.inputs.days.val()); diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index a9abd1c601..23c0ad5d5d 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -190,7 +190,7 @@ frappe.form.formatters = { }, Duration: function(value, docfield, doc) { if (value) { - let duration_options = frappe.meta.get_duration_options(docfield, doc); + let duration_options = frappe.meta.get_duration_options(docfield); value = frappe.utils.get_formatted_duration(value, duration_options); } diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index acde2a367f..c6b5ca0b4a 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -268,7 +268,7 @@ $.extend(frappe.meta, { return precision; }, - get_duration_options: function(df, doc) { + get_duration_options: function(df) { let duration_options = { showDays: df.show_days, showSeconds: df.show_seconds diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 3ddfa0f4dd..68628276a8 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -833,11 +833,11 @@ Object.assign(frappe.utils, { days: Math.floor(secs / (3600 * 24)), hours: Math.floor(secs % (3600 * 24) / 3600), minutes: Math.floor(secs % 3600 / 60), - seconds : Math.floor(secs % 60) + seconds: Math.floor(secs % 60) }; if (!duration_options.showDays) { - total_duration.hours = Math.floor(secs / 3600) - total_duration.days = 0 + total_duration.hours = Math.floor(secs / 3600); + total_duration.days = 0; } return total_duration; }, diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index 33d8594535..2b03b93f56 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -169,7 +169,7 @@ /* duration control */ .duration-picker { - position: relative; + position: absolute; z-index: 999; border-radius: 4px; @@ -178,7 +178,6 @@ border: 1px solid @border-color; padding-top: 10px; padding-left: 5px; - position: absolute; &:after, &:before { From 8933e71d906c6e16daa002ab478d123afd53842f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 18:52:34 +0530 Subject: [PATCH 17/31] test(ui): duration control --- cypress/integration/control_duration.js | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 cypress/integration/control_duration.js diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js new file mode 100644 index 0000000000..20634acb80 --- /dev/null +++ b/cypress/integration/control_duration.js @@ -0,0 +1,45 @@ +context('Control Duration', () => { + before(() => { + cy.login(); + cy.visit('/desk#workspace/Website'); + }); + + function get_dialog_with_duration(show_days=1, show_seconds=1) { + return cy.dialog({ + title: 'Duration', + fields: [{ + 'fieldname': 'duration', + 'fieldtype': 'Duration', + 'show_seconds': show_days, + 'show_days': show_seconds + }] + }); + } + + it('should set duration', () => { + get_dialog_with_duration().as('dialog') + cy.get('.frappe-control[data-fieldname=duration] input') + .first() + .click(); + cy.get('.duration-input[data-duration=days]') + .type(45) + .blur(); + cy.get('.duration-input[data-duration=minutes]') + .type(30) + .blur(); + cy.get('.frappe-control[data-fieldname=duration] input').should('have.value', '45d 30m'); + cy.get('.frappe-control[data-fieldname=duration] input').first().blur(); + cy.get('.duration-picker').should('not.be.visible'); + cy.get('@dialog').then(dialog => { + let value = dialog.get_value('duration'); + expect(value).to.equal(3889800); + }); + }); + + it('should hide days or seconds according to duration options', () => { + get_dialog_with_duration(0, 0).as('dialog'); + cy.get('.frappe-control[data-fieldname=duration] input').first().click(); + cy.get('.duration-input[data-duration=days]').should('not.be.visible'); + cy.get('.duration-input[data-duration=seconds]').should('not.be.visible'); + }); +}); \ No newline at end of file From 5a47f2da7fa2417f2c1624c7b6933d6dedce31e2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 19:17:51 +0530 Subject: [PATCH 18/31] fix: sider and translations issues --- cypress/integration/control_duration.js | 2 +- frappe/public/js/frappe/form/formatters.js | 7 ++----- frappe/public/js/frappe/model/meta.js | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 20634acb80..8ff6b8668d 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -17,7 +17,7 @@ context('Control Duration', () => { } it('should set duration', () => { - get_dialog_with_duration().as('dialog') + get_dialog_with_duration().as('dialog'); cy.get('.frappe-control[data-fieldname=duration] input') .first() .click(); diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 23c0ad5d5d..37c4db7428 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -142,10 +142,7 @@ frappe.form.formatters = { }, DateRange: function(value) { if($.isArray(value)) { - return __("{0} to {1}", [ - frappe.datetime.str_to_user(value[0]), - frappe.datetime.str_to_user(value[1]) - ]); + return __("{0} to {1}", [frappe.datetime.str_to_user(value[0]), frappe.datetime.str_to_user(value[1])]); } else { return value || ""; } @@ -188,7 +185,7 @@ frappe.form.formatters = { return value || ""; }, - Duration: function(value, docfield, doc) { + Duration: function(value, docfield) { if (value) { let duration_options = frappe.meta.get_duration_options(docfield); value = frappe.utils.get_formatted_duration(value, duration_options); diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index c6b5ca0b4a..f71689e409 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -161,8 +161,7 @@ $.extend(frappe.meta, { if(!out) { // eslint-disable-next-line - console.log(__('Warning: Unable to find {0} in any table related to {1}', [ - key, __(doctype)])); + console.log(__('Warning: Unable to find {0} in any table related to {1}', [key, __(doctype)])); } } return out; From e1fd1c0144a471029eae4834577b6d93855fd085 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 14 May 2020 11:32:32 +0530 Subject: [PATCH 19/31] fix: test --- cypress/integration/control_duration.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 8ff6b8668d..195200e824 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -23,11 +23,11 @@ context('Control Duration', () => { .click(); cy.get('.duration-input[data-duration=days]') .type(45) - .blur(); + .blur({force: true}); cy.get('.duration-input[data-duration=minutes]') .type(30) - .blur(); - cy.get('.frappe-control[data-fieldname=duration] input').should('have.value', '45d 30m'); + .blur({force: true}); + cy.get('.frappe-control[data-fieldname=duration] input').first().should('have.value', '45d 30m'); cy.get('.frappe-control[data-fieldname=duration] input').first().blur(); cy.get('.duration-picker').should('not.be.visible'); cy.get('@dialog').then(dialog => { From cfd7d6e09ffea9dbcfbfb30668fa896e408a3f35 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 14 May 2020 12:21:21 +0530 Subject: [PATCH 20/31] fix: test --- cypress/integration/control_duration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 195200e824..f304abd3d9 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -22,7 +22,7 @@ context('Control Duration', () => { .first() .click(); cy.get('.duration-input[data-duration=days]') - .type(45) + .type(45, {force: true}) .blur({force: true}); cy.get('.duration-input[data-duration=minutes]') .type(30) From 080a8b9ce0b51ea5adbd33a9244b48c16b39b163 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 14 May 2020 23:48:17 +0530 Subject: [PATCH 21/31] fix: add duration field options in custom field and customize form field --- .../doctype/custom_field/custom_field.json | 22 ++++++++++++++++++- .../customize_form_field.json | 22 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 6534bfd90e..d220e448df 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -16,6 +16,8 @@ "column_break_6", "fieldtype", "precision", + "show_seconds", + "show_days", "options", "fetch_from", "fetch_if_empty", @@ -378,12 +380,30 @@ "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_seconds", + "fieldtype": "Check", + "label": "Show Seconds", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_days", + "fieldtype": "Check", + "label": "Show Days", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-glass", "idx": 1, "links": [], - "modified": "2020-04-30 09:15:24.394782", + "modified": "2020-05-14 23:43:00.123572", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index d1510e0858..5876666485 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -11,6 +11,8 @@ "label", "fieldtype", "fieldname", + "show_seconds", + "show_days", "reqd", "unique", "in_list_view", @@ -388,12 +390,30 @@ "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_seconds", + "fieldtype": "Check", + "label": "Show Seconds", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_days", + "fieldtype": "Check", + "label": "Show Days", + "show_days": 1, + "show_seconds": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-30 09:15:51.094586", + "modified": "2020-05-14 23:45:46.810869", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", From 068d55c68278e5409ed5273f2bd85f93c7ec1d70 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 20 May 2020 10:14:48 +0530 Subject: [PATCH 22/31] refactor: change duration options to de-facto snake case --- frappe/public/js/frappe/form/controls/duration.js | 8 ++++---- frappe/public/js/frappe/model/meta.js | 4 ++-- frappe/public/js/frappe/ui/filters/filters.js | 4 ++-- frappe/public/js/frappe/utils/utils.js | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index d6d512da9c..2014dbdabd 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -13,10 +13,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ ` ); this.$wrapper.append(this.$picker); - this.build_numeric_input("days", !this.duration_options.showDays); + this.build_numeric_input("days", !this.duration_options.show_days); this.build_numeric_input("hours", false); this.build_numeric_input("minutes", false); - this.build_numeric_input("seconds", !this.duration_options.showSeconds); + this.build_numeric_input("seconds", !this.duration_options.show_seconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -115,10 +115,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ minutes: parseInt(this.inputs.minutes.val()), hours: parseInt(this.inputs.hours.val()), }; - if (this.duration_options.showDays) { + if (this.duration_options.show_days) { total_duration.days = parseInt(this.inputs.days.val()); } - if (this.duration_options.showSeconds) { + if (this.duration_options.show_seconds) { total_duration.seconds = parseInt(this.inputs.seconds.val()); } diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index f71689e409..5e3d709b4b 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -269,8 +269,8 @@ $.extend(frappe.meta, { get_duration_options: function(df) { let duration_options = { - showDays: df.show_days, - showSeconds: df.show_seconds + show_days: df.show_days, + show_seconds: df.show_seconds }; return duration_options; } diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js index 9a394eade3..f8f0535b83 100644 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -202,8 +202,8 @@ frappe.ui.FilterList = Class.extend({ value = {0:"No", 1:"Yes"}[cint(value)]; } else if (field.df.original_type === "Duration") { let duration_options = { - showDays: field.df.show_days, - showSeconds: field.df.show_seconds + show_days: field.df.show_days, + show_seconds: field.df.show_seconds }; value = frappe.utils.get_formatted_duration(value, duration_options); } diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 3cbbecea83..6b0ac3d437 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -836,7 +836,7 @@ Object.assign(frappe.utils, { minutes: Math.floor(secs % 3600 / 60), seconds: Math.floor(secs % 60) }; - if (!duration_options.showDays) { + if (!duration_options.show_days) { total_duration.hours = Math.floor(secs / 3600); total_duration.days = 0; } From 0d071217a7bc80e13febef83500762bb9b097337 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 May 2020 20:47:09 +0530 Subject: [PATCH 23/31] fix: edit duration from report view --- .../js/frappe/form/controls/duration.js | 25 +++++++++++++++---- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 2014dbdabd..1041cb8c1a 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -17,7 +17,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.build_numeric_input("hours", false); this.build_numeric_input("minutes", false); this.build_numeric_input("seconds", !this.duration_options.show_seconds); - this.set_duration_picker(); + this.set_duration_picker_value(this.value); this.$picker.hide(); this.bind_events(); this.refresh(); @@ -53,8 +53,8 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.duration_options = frappe.meta.get_duration_options(this.df); }, - set_duration_picker() { - let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); + set_duration_picker_value: function(value) { + let total_duration = frappe.utils.seconds_to_duration(value, this.duration_options); if (this.$picker) { Object.keys(total_duration).forEach(duration => { @@ -75,12 +75,17 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.$picker.on("change", ".duration-input", () => { // duration changed in individual boxes clicked = false; - this.set_value(this.duration_to_seconds()); + let value = this.duration_to_seconds(); + this.set_value(value); this.set_focus(); }); this.$input.on("focus", () => { this.$picker.show(); + let is_picker_set = this.is_duration_picker_set(this.inputs); + if (!is_picker_set) { + this.set_duration_picker_value(this.value); + } }); this.$input.on("blur", () => { @@ -101,7 +106,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ refresh_input: function() { this._super(); this.set_duration_options(); - this.set_duration_picker(); + this.set_duration_picker_value(this.value); }, format_for_input: function(value) { @@ -136,5 +141,15 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ } } return value; + }, + + is_duration_picker_set(inputs) { + let is_set = false; + Object.values(inputs).forEach(duration => { + if (duration.prop("value") != 0) { + is_set = true; + } + }); + return is_set; } }); \ No newline at end of file From 1078b0e2da2108656a05231d94cf4b022b1d629c Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 May 2020 21:48:47 +0530 Subject: [PATCH 24/31] fix(print preview): add formatter for duration fieldtype --- frappe/public/js/frappe/utils/utils.js | 1 + frappe/utils/data.py | 28 ++++++++++++++++++++++++++ frappe/utils/formatters.py | 6 +++++- 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 6b0ac3d437..81034cb243 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -828,6 +828,7 @@ Object.assign(frappe.utils, { } return duration; }, + seconds_to_duration(value, duration_options) { let secs = value; let total_duration = { diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 9796aa3c4a..250917cc27 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -319,6 +319,34 @@ def format_datetime(datetime_string, format_string=None): formatted_datetime = datetime.strftime('%Y-%m-%d %H:%M:%S') return formatted_datetime +def format_duration(seconds, show_days=True): + total_duration = { + 'days': math.floor(seconds / (3600 * 24)), + 'hours': math.floor(seconds % (3600 * 24) / 3600), + 'minutes': math.floor(seconds % 3600 / 60), + 'seconds': math.floor(seconds % 60) + } + + if not show_days: + total_duration['hours'] = math.floor(seconds / 3600) + total_duration['days'] = 0 + + duration = '' + if total_duration: + if total_duration.get('days'): + duration += str(total_duration.get('days')) + 'd' + if total_duration.get('hours'): + duration += ' ' if len(duration) else '' + duration += str(total_duration.get('hours')) + 'h' + if total_duration.get('minutes'): + duration += ' ' if len(duration) else '' + duration += str(total_duration.get('minutes')) + 'm' + if total_duration.get('seconds'): + duration += ' ' if len(duration) else '' + duration += str(total_duration.get('seconds')) + 's' + + return duration + def get_weekdays(): return ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'] diff --git a/frappe/utils/formatters.py b/frappe/utils/formatters.py index 46e3694e6f..d7646eeb71 100644 --- a/frappe/utils/formatters.py +++ b/frappe/utils/formatters.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe import datetime -from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time +from frappe.utils import formatdate, fmt_money, flt, cstr, cint, format_datetime, format_time, format_duration from frappe.model.meta import get_field_currency, get_field_precision import re from six import string_types @@ -90,4 +90,8 @@ def format_value(value, df=None, doc=None, currency=None, translated=False): values = [v.get(link_field.fieldname, 'asdf') for v in value] return ', '.join(values) + elif df.get("fieldtype") == "Duration": + show_days = df.show_days + return format_duration(value, show_days) + return value From d3a4a02259d58f547db6322d94bebf8dd865a7c6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 May 2020 22:33:58 +0530 Subject: [PATCH 25/31] fix: move duration options function from meta to utils.js --- frappe/public/js/frappe/form/controls/duration.js | 2 +- frappe/public/js/frappe/form/formatters.js | 2 +- frappe/public/js/frappe/model/meta.js | 8 -------- frappe/public/js/frappe/utils/utils.js | 8 ++++++++ 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 1041cb8c1a..17dfc34827 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -50,7 +50,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - this.duration_options = frappe.meta.get_duration_options(this.df); + this.duration_options = frappe.utils.get_duration_options(this.df); }, set_duration_picker_value: function(value) { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 37c4db7428..9f4a2a61d6 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -187,7 +187,7 @@ frappe.form.formatters = { }, Duration: function(value, docfield) { if (value) { - let duration_options = frappe.meta.get_duration_options(docfield); + let duration_options = frappe.utils.get_duration_options(docfield); value = frappe.utils.get_formatted_duration(value, duration_options); } diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 5e3d709b4b..c2fd6b1ae6 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -265,13 +265,5 @@ $.extend(frappe.meta, { precision = cint(frappe.defaults.get_default("float_precision")) || 3; } return precision; - }, - - get_duration_options: function(df) { - let duration_options = { - show_days: df.show_days, - show_seconds: df.show_seconds - }; - return duration_options; } }); diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 81034cb243..b5ff436383 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -843,6 +843,14 @@ Object.assign(frappe.utils, { } return total_duration; }, + + get_duration_options: function(docfield) { + let duration_options = { + show_days: docfield.show_days, + show_seconds: docfield.show_seconds + }; + return duration_options; + } }); // Array de duplicate From d7800143970ca7a10429ba294db80f09d715c062 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Sat, 23 May 2020 23:35:52 +0530 Subject: [PATCH 26/31] fix: export duration to seconds function to utils.js --- .../js/frappe/form/controls/duration.js | 39 +++++++++---------- frappe/public/js/frappe/utils/utils.js | 17 ++++++++ 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 17dfc34827..58df8e15e6 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -75,7 +75,13 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ this.$picker.on("change", ".duration-input", () => { // duration changed in individual boxes clicked = false; - let value = this.duration_to_seconds(); + let duration = this.get_duration(); + let value = frappe.utils.duration_to_seconds( + duration.days, + duration.hours, + duration.minutes, + duration.seconds + ); this.set_value(value); this.set_focus(); }); @@ -113,34 +119,25 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ return frappe.utils.get_formatted_duration(value, this.duration_options); }, - duration_to_seconds() { - let value = 0; + get_duration() { + // returns an object of days, hours, minutes and seconds from the inputs array + let total_duration = { + minutes: 0, + hours: 0, + days: 0, + seconds: 0 + }; if (this.inputs) { - let total_duration = { - minutes: parseInt(this.inputs.minutes.val()), - hours: parseInt(this.inputs.hours.val()), - }; + total_duration.minutes = parseInt(this.inputs.minutes.val()); + total_duration.hours = parseInt(this.inputs.hours.val()); if (this.duration_options.show_days) { total_duration.days = parseInt(this.inputs.days.val()); } if (this.duration_options.show_seconds) { total_duration.seconds = parseInt(this.inputs.seconds.val()); } - - if (total_duration.days) { - value += total_duration.days * 24 * 60 * 60; - } - if (total_duration.hours) { - value += total_duration.hours * 60 * 60; - } - if (total_duration.minutes) { - value += total_duration.minutes * 60; - } - if (total_duration.seconds) { - value += total_duration.seconds; - } } - return value; + return total_duration; }, is_duration_picker_set(inputs) { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index b5ff436383..f985103803 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -844,6 +844,23 @@ Object.assign(frappe.utils, { return total_duration; }, + duration_to_seconds(days=0, hours=0, minutes=0, seconds=0) { + let value = 0; + if (days) { + value += days * 24 * 60 * 60; + } + if (hours) { + value += hours * 60 * 60; + } + if (minutes) { + value += minutes * 60; + } + if (seconds) { + value += seconds; + } + return value; + }, + get_duration_options: function(docfield) { let duration_options = { show_days: docfield.show_days, From dcf83b864550dd12b524995abacbc1456ac26e68 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 26 May 2020 23:03:59 +0530 Subject: [PATCH 27/31] fix: Update translation_map with user translation - Even while returning from cache --- frappe/translate.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/frappe/translate.py b/frappe/translate.py index 91562382e6..2fc2d3f328 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -119,23 +119,18 @@ def get_dict(fortype, name=None): messages += frappe.db.sql("select 'Role:', name from tabRole") messages += frappe.db.sql("select 'Module:', name from `tabModule Def`") - message_dict = make_dict_from_messages(messages) message_dict.update(get_dict_from_hooks(fortype, name)) - # remove untranslated message_dict = {k:v for k, v in iteritems(message_dict) if k!=v} - translation_assets[asset_key] = message_dict - cache.hset("translation_assets", frappe.local.lang, translation_assets, shared=True) - if fortype=="boot": - message_dict.update(get_user_translations(frappe.local.lang)) + translation_map = translation_assets[asset_key] + if fortype == "boot": + translation_map.update(get_user_translations(frappe.local.lang)) - return message_dict - - return translation_assets[asset_key] + return translation_map def get_dict_from_hooks(fortype, name): From bba7336b6ce55a23edca7fc9805100902500afd3 Mon Sep 17 00:00:00 2001 From: karthikeyan5 Date: Wed, 27 May 2020 08:39:50 +0530 Subject: [PATCH 28/31] fix(dropbox): dropbox timeout for large files --- .../integrations/doctype/dropbox_settings/dropbox_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index 2a036f4838..4b595b1abf 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -90,7 +90,7 @@ def backup_to_dropbox(upload_db_backup=True): dropbox_settings['access_token'] = access_token['oauth2_token'] set_dropbox_access_token(access_token['oauth2_token']) - dropbox_client = dropbox.Dropbox(dropbox_settings['access_token']) + dropbox_client = dropbox.Dropbox(dropbox_settings['access_token'], timeout=None) if upload_db_backup: if frappe.flags.create_new_backup: From 30a8472140c7b6e29dcb27314770825d94ed48ca Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 27 May 2020 12:03:47 +0530 Subject: [PATCH 29/31] feat: set scrollbar to thin --- frappe/public/less/frappe-datatable.less | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/less/frappe-datatable.less b/frappe/public/less/frappe-datatable.less index 39ed2eebf3..54eecf2b3d 100644 --- a/frappe/public/less/frappe-datatable.less +++ b/frappe/public/less/frappe-datatable.less @@ -36,6 +36,7 @@ .dt-scrollable { max-height: calc(100vh - 250px); min-height: 100px; + scrollbar-width: thin; } table td.dt-cell { From 07df8b1083ccb58730a52e410d57f00ca9d7431b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 27 May 2020 21:13:01 +0530 Subject: [PATCH 30/31] fix: trigger after submit route hook after on_submit --- frappe/public/js/frappe/form/form.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index bad7c877fc..aa0fc6e2ac 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -650,13 +650,14 @@ frappe.ui.form.Form = class FrappeForm { frappe.utils.play_sound("submit"); callback && callback(); me.script_manager.trigger("on_submit") - .then(() => resolve(me)); - if (frappe.route_hooks.after_submit) { - let route_callback = frappe.route_hooks.after_submit; - delete frappe.route_hooks.after_submit; - - route_callback(me); - } + .then(() => resolve(me)) + .then(() => { + if (frappe.route_hooks.after_submit) { + let route_callback = frappe.route_hooks.after_submit; + delete frappe.route_hooks.after_submit; + route_callback(me); + } + }); } }, btn, () => me.handle_save_fail(btn, on_error), resolve); }); From a4a49664a4bdf3694612b0007c2a192703c2fdd1 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 28 May 2020 10:08:59 +0530 Subject: [PATCH 31/31] fix: driver find element by fieldname (bp #10494) (#10500) (cherry picked from commit e3e7d678447ac1c337b298376d557f09df859395) Co-authored-by: Shivam Mishra --- frappe/public/js/frappe/form/form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index aa0fc6e2ac..369e4a56d4 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -1587,7 +1587,7 @@ frappe.ui.form.Form = class FrappeForm { let steps = frappe.tour[this.doctype].map(step => { let field = this.get_docfield(step.fieldname); return { - element: `.frappe-control[title='${step.fieldname}']`, + element: `.frappe-control[data-fieldname='${step.fieldname}']`, popover: { title: step.title || field.label, description: step.description