From 4dbcc1247033d64160da8bc2b17ef4b21be384e5 Mon Sep 17 00:00:00 2001 From: Corentin Forler Date: Mon, 5 Aug 2024 12:57:34 +0200 Subject: [PATCH] feat(DurationControl): Parse duration string input --- .../js/frappe/form/controls/duration.js | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 66f73060b2..2808200b69 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -60,7 +60,7 @@ frappe.ui.form.ControlDuration = class ControlDuration extends frappe.ui.form.Co } set_duration_picker_value(value) { - let total_duration = frappe.utils.seconds_to_duration(value, this.duration_options); + let total_duration = frappe.utils.seconds_to_duration(value || 0, this.duration_options); if (this.$picker) { Object.keys(total_duration).forEach((duration) => { @@ -108,6 +108,7 @@ frappe.ui.form.ControlDuration = class ControlDuration extends frappe.ui.form.Co // blur event was not due to duration inputs this.$picker.hide(); } + this.set_formatted_input(this.value); }); } @@ -116,7 +117,29 @@ frappe.ui.form.ControlDuration = class ControlDuration extends frappe.ui.form.Co } parse(value) { - return !value ? "" : value; + if (!value) { + return ""; + } else if (/^\s*\d+\s*$/.test(value)) { + return parseInt(value); + } + + this.DURATION_PARSE_REGEX ??= makeDurationParseRegex(); + const match = String(value).trim().match(this.DURATION_PARSE_REGEX); + + if (!match?.groups || !Object.values(match.groups).some((g) => !!g)) { + return null; // At least one group is required + } + + let duration_in_seconds = 0; + for (const [key, multiplier] of Object.entries(DURATION_MULTIPLIERS)) { + duration_in_seconds += parseInt(match.groups?.[key] || 0) * multiplier; + } + return duration_in_seconds; + } + + set_formatted_input(value) { + super.set_formatted_input(value); + this.set_duration_picker_value(value); } refresh_input() { @@ -160,3 +183,34 @@ frappe.ui.form.ControlDuration = class ControlDuration extends frappe.ui.form.Co return is_set; } }; + +const DURATION_MULTIPLIERS = { + weeks: 7 * 24 * 60 * 60, + days: 24 * 60 * 60, + hours: 60 * 60, + minutes: 60, + seconds: 1, +}; + +function makeDurationParseRegex() { + // Wrap in a function for translations to work + // Matches strings like: 1d 2h 3m 4s, with optional parts + + const _part = (key, seps) => { + // Use named capture group for the key, then match the separator (h, m, s) + const rCapture = `(?<${key}>\\d+)`; + const rSep = "(?:" + seps.join("|") + ")"; + return "\\s*(?:" + rCapture + "\\s*" + rSep + ")?\\s*"; + }; + + return new RegExp( + [ + _part("days", [__("d", null, "Days (Field: Duration)")]), + _part("hours", [__("h", null, "Hours (Field: Duration)")]), + _part("minutes", [__("m", null, "Minutes (Field: Duration)")]), + _part("seconds", [__("s", null, "Seconds (Field: Duration)")]), + ".*", // Fallback to ignore unknown parts + ].join(""), + "i" + ); +}