From d5f06bae7d23ce9fb09d7902068fdf6177fd4ea0 Mon Sep 17 00:00:00 2001
From: Ramesh Kumar <ramesh.p@matriotsolutions.com>
Date: Wed, 31 Mar 2021 20:35:16 +0530
Subject: [PATCH] TMSS-577: Angle fields allowed to enter dms,degrees, hms,
 hours & radians.

---
 .../src/components/JSONEditor/JEditor.js      |  76 ++----------
 .../components/Spreadsheet/DegreeInputmask.js |   7 +-
 .../components/Spreadsheet/TimeInputmask.js   |   7 +-
 .../Scheduling/excelview.schedulingset.js     |   8 +-
 .../tmss_webapp/src/utils/unit.converter.js   | 113 ++++++++++++++++--
 .../tmss_webapp/src/utils/validator.js        |  38 ++----
 6 files changed, 138 insertions(+), 111 deletions(-)

diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js b/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js
index 7581c8e1765..5af6f357d38 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/components/JSONEditor/JEditor.js
@@ -5,7 +5,8 @@
 /* eslint-disable react-hooks/exhaustive-deps */
 import React, {useEffect, useRef} from 'react';
 import _ from 'lodash';
-import UnitConverter from '../../utils/unit.converter'
+import UnitConverter from '../../utils/unit.converter';
+import Validator from '../../utils/validator';
 import $RefParser from "@apidevtools/json-schema-ref-parser";
 import "@fortawesome/fontawesome-free/css/all.css";
 import flatpickr from 'flatpickr';
@@ -137,8 +138,8 @@ function Jeditor(props) {
         getCustomProperties(schema.definitions);
         schema.title = props.title;
         const subbandValidator = validateSubbandOutput;
-        const timeValidator = validateTime;
-        const angleValidator = validateAngle;
+        const timeValidator = Validator.validateTime;
+        const angleValidator = Validator.validateAngle;
         JSONEditor.defaults.custom_validators.push((schema, value, path) => {
             const errors = [];
             if (schema.validationType === "subband_list") {
@@ -154,7 +155,7 @@ function Jeditor(props) {
                     errors.push({
                         path: path,
                         property: 'validationType',
-                        message: 'Not a valid input. Mimimum: 00:00:00.0000, Maximum:23:59:59.9999'
+                        message: 'Not a valid input. Mimimum: 00:00:00.0000hours or 0, Maximum:23:59:59.9999hours or 6.2831'
                     });
                 }
             }   else if (schema.validationType === "angle") {
@@ -162,7 +163,7 @@ function Jeditor(props) {
                     errors.push({
                         path: path,
                         property: 'validationType',
-                        message: 'Not a valid input. Mimimum: 00:00:00.0000, Maximum:90:00:00.0000'
+                        message: 'Not a valid input. Mimimum: 00:00:00.0000degrees 0r 0, Maximum:90:00:00.0000degrees or 1.5708'
                     });
                 }
             } else if (schema.validationType === "distanceOnSky") {
@@ -265,19 +266,15 @@ function Jeditor(props) {
         let newProperty = {
             type: "string",
             title: defProperty.title,
-            description: (defProperty.description + (isDegree?'(Degrees:Minutes:Seconds.MilliSeconds)':'(Hours:Minutes:Seconds.MilliSeconds)')),
-            default: "00:00:00.0000",
+            description: (defProperty.description + (isDegree?
+                            "(Supported Formats: '10d15m10.1234s', '10:15:10.1234degrees', '10.2528degrees', '0.1789')":
+                            "(Supported Formats: '10h15m10.1234s', '10:15:10.1234hours', '10.4187hours', '2.7276')")),
+            default: "0",
             validationType: isDegree?'angle':'time',
             options: {
                 "grid_columns": 4,
                 "inputAttributes": {
-                    "placeholder": isDegree?"DD:mm:ss.ssss":"HH:mm:ss.ssss"
-                },
-                "cleave": {
-                    numericOnly: true,
-                    blocks: [2, 2, 2, 4],
-                    delimiters: isDegree ? [':', ':','.'] : [':', ':', '.'],
-                    delimiterLazyShow: true
+                    "placeholder": isDegree?"Degrees or Radian":"Hours or Radian"
                 }
             }
         }
@@ -391,8 +388,8 @@ function Jeditor(props) {
             let outputValue = editorOutput[outputKey];
             if (outputValue instanceof Object) {
                 if (_.indexOf(pointingProps, outputKey) >= 0) {
-                    outputValue.angle1 = UnitConverter.getAngleOutput(outputValue.angle1, false);
-                    outputValue.angle2 = UnitConverter.getAngleOutput(outputValue.angle2, true);
+                    outputValue.angle1 = UnitConverter.parseAngle(outputValue.angle1);
+                    outputValue.angle2 = UnitConverter.parseAngle(outputValue.angle2);
                 } else {
                     updateOutput(outputValue);
                 }
@@ -443,53 +440,6 @@ function Jeditor(props) {
         return (hh<10?`0${hh}`:`${hh}`) + ':' + (mm<10?`0${mm}`:`${mm}`) + ':' + (ss<10?`0${ss}`:`${ss}`);
     }
 
-    /**
-     * Validate time entered as string in HH:mm:ss format
-     * @param {String} prpOutput 
-     */
-    function validateTime(prpOutput) {
-        const splitOutput = prpOutput.split(':');
-        const seconds = splitOutput[2]?splitOutput[2].split('.')[0].split('.')[0]:splitOutput[2];
-        let milliSeconds = prpOutput.split('.')[1] || '0000';
-        milliSeconds = milliSeconds.padEnd(4,0);
-        if (splitOutput.length < 3) {
-            return false;
-        }   else {
-            if (parseInt(splitOutput[0]) > 23 || parseInt(splitOutput[1])>59 || parseInt(splitOutput[2])>59 )
-             {
-                return false;
-            }
-            const timeValue = parseInt(splitOutput[0]*60*60) + parseInt(splitOutput[1]*60) + parseInt(seconds) + milliSeconds/10000;
-            if (timeValue >= 86400) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    /**
-     * Validate angle input to not exceed 90 degrees
-     * @param {String} prpOutput 
-     */
-    function validateAngle(prpOutput) {
-        const splitOutput = prpOutput.split(':');
-        const seconds = splitOutput[2]?splitOutput[2].split('.')[0].split('.')[0]:splitOutput[2];
-        let milliSeconds = prpOutput.split('.')[1] || '0000';
-        milliSeconds = milliSeconds.padEnd(4,0);
-        if (splitOutput.length < 3) {
-            return false;
-        }   else {
-            if (parseInt(splitOutput[0]) > 90 || parseInt(splitOutput[1])>59 || parseInt(seconds)>59) {
-                return false;
-            }
-            const timeValue = parseInt(splitOutput[0]*60*60) + parseInt(splitOutput[1]*60) + parseInt(seconds) + milliSeconds/10000;
-            if (timeValue > 324000) {
-                return false;
-            }
-        }
-        return true;
-    }
-
     /**
      * Validates if the subband list custom field
      * @param {String} prpOutput 
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/DegreeInputmask.js b/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/DegreeInputmask.js
index 16e5057bdf3..4fd0b705175 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/DegreeInputmask.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/DegreeInputmask.js
@@ -1,5 +1,4 @@
 import React, { Component } from 'react';
-import { InputMask } from 'primereact/inputmask';
 import Validator from  '../../utils/validator';
 import Cleave from 'cleave.js/react';
 
@@ -35,10 +34,8 @@ export default class DegreeInputMask extends Component {
 
   render() {
     return (
-      <Cleave placeholder="DD:mm:ss.ssss" value={this.props.value}
-          options={{numericOnly: true, blocks: [2, 2, 2, 4],
-                    delimiters: [':', ':', '.'],
-                    delimiterLazyShow: false}}
+      <Cleave placeholder="Degree/Radian" value={this.props.value}
+          title="Enter in dms or degrees or radians"
           className="inputmask" 
           htmlRef={(ref) => this.input = ref }
           onChange={this.callbackUpdateAngle} />
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/TimeInputmask.js b/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/TimeInputmask.js
index 540f276baf8..3a7fe62f3a1 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/TimeInputmask.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/components/Spreadsheet/TimeInputmask.js
@@ -1,5 +1,4 @@
 import React, { Component } from 'react';
-import { InputMask } from 'primereact/inputmask';
 import Validator from  '../../utils/validator';
 import Cleave from 'cleave.js/react';
 
@@ -33,10 +32,8 @@ export default class TimeInputMask extends Component {
 
   render() {
     return (
-      <Cleave placeholder="HH:mm:ss.ssss" value={this.props.value}
-          options={{numericOnly: true, blocks: [2, 2, 2, 4],
-                    delimiters: [':', ':', '.'],
-                    delimiterLazyShow: false}}
+      <Cleave placeholder="Hour/Radian" value={this.props.value}
+          title="Enter in hms or hours or radians"
           className="inputmask" 
           htmlRef={(ref) => this.input = ref }
           onChange={this.callbackUpdateAngle} />
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js
index 6e8e565a824..01fee2c2d75 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/routes/Scheduling/excelview.schedulingset.js
@@ -1337,7 +1337,7 @@ export class SchedulingSetCreate extends Component {
             row = this.state.commonRowData[0];
             row[field] = value;
             row['isValid'] = isValid;
-            row[field+'value'] = UnitConverter.getAngleOutput(value,isDegree);
+            row[field+'value'] = UnitConverter.parseAngle(value);
             tmpRowData = this.state.commonRowData;
             tmpRowData[0] = row;
             await this.setState({
@@ -1348,7 +1348,7 @@ export class SchedulingSetCreate extends Component {
             row = this.state.rowData[rowIndex];
             row[field] = value;
             row['isValid'] = isValid;
-            row[field+'value'] = UnitConverter.getAngleOutput(value,isDegree);
+            row[field+'value'] = UnitConverter.parseAngle(value);
             tmpRowData = this.state.rowData;
             tmpRowData[rowIndex] = row;
             await this.setState({
@@ -1752,13 +1752,13 @@ export class SchedulingSetCreate extends Component {
                                 validRow = false;
                                 return;
                             }
-                            paramOutput[key] = UnitConverter.getAngleOutput(suRow[result[key]],false);
+                            paramOutput[key] = UnitConverter.parseAngle(suRow[result[key]]);
                         } else if (key === 'angle2'){
                             if  (!Validator.validateAngle(suRow[result[key]])){
                                 validRow = false;
                                 return;
                             }
-                            paramOutput[key] = UnitConverter.getAngleOutput(suRow[result[key]],true);
+                            paramOutput[key] = UnitConverter.parseAngle(suRow[result[key]]);
                         }  else if (key === 'angle3'){
                             paramOutput[key] = Number(suRow[result[key]]);
 
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js
index 471818ef7f1..17569078541 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/unit.converter.js
@@ -69,17 +69,13 @@ const UnitConverter = {
             if (isDegree) {
                 const dd = Math.floor(prpInput * 180 / Math.PI);
                 const mm = Math.floor((degrees-dd) * 60);
-                const ss = Math.floor((degrees-dd-(mm/60)) * 3600);
-                const ssss = round(((degrees - dd - (mm/60) - (ss/3600)) * 36000000), 4);
-                const milliSeconds = String(ssss).padStart(4,0);
-                return (dd<10?`0${dd}`:`${dd}`) + ':' + (mm<10?`0${mm}`:`${mm}`) + ':' + (ss<10?`0${ss}`:`${ss}`) + '.' + milliSeconds;
+                const ss = round((degrees-dd-(mm/60)) * 3600,4);
+                return (dd<10?`0${dd}`:`${dd}`) + 'd' + (mm<10?`0${mm}`:`${mm}`) + 'm' + (ss<10?`0${ss}`:`${ss}`) + 's';
             }   else {
                 const hh = Math.floor(degrees/15);
                 const mm = Math.floor((degrees - (hh*15))/15 * 60 );
-                const ss = Math.floor((degrees -(hh*15)-(mm*15/60))/15 * 3600);
-                const ssss = round(((degrees - (hh*15) - (mm/4) - (ss/240)) *2400000),4);
-                const milliSeconds = String(ssss).padStart(4,0);
-                return (hh<10?`0${hh}`:`${hh}`) + ':' + (mm<10?`0${mm}`:`${mm}`) + ':' + (ss<10?`0${ss}`:`${ss}`) + '.' + milliSeconds;
+                const ss = round((degrees -(hh*15)-(mm*15/60))/15 * 3600, 4);
+                return (hh<10?`0${hh}`:`${hh}`) + 'h' + (mm<10?`0${mm}`:`${mm}`) + 'm' + (ss<10?`0${ss}`:`${ss}`) + 's';
             }
         } else {
             return "00:00:00";
@@ -103,6 +99,107 @@ const UnitConverter = {
         }else{
             return "00:00:00.0000";
         }
+    },
+    /**
+     * Function to check the input type/format based on the matching predeifined regular expression. It can be any of the supported format
+     * like dms, hms, degrees, hours, radians. Example values are 10h10m10s, 10h10m10.1234s, 10:10:10 hour, 10:10:10.1234 hours,
+     * 10.1234 hours, 15d15m15s, 15d15m15.1515s, 15:15:15 degree, 15:15:15.1515 degrees, 15.1515 degrees. If only number is entered, it will
+     * be considered as radians.
+     * @param {String} input - value entered in the angle field. 
+     * @returns String - the format of the input identified. If no format is identified, returns null. 
+     */
+    getAngleInputType(input) {
+        if (input.match(/^\-?((\d0?d(0?0m)(0?0(\.\d{1,4})?s))|(([0-8]?\d)d(([0-5]?\d)m)(([0-5]?\d)(\.\d{1,4})?s)))$/)) {
+            return 'dms';
+        }   else if (input.match(/^([0-1]?\d|2[0-3])h([0-5]?\d)m([0-5]?\d)(\.\d{1,4})?s$/)) {
+            return 'hms';
+        }   else if (input.match(/^-?((\d0(.0{1,4})?)|([0-8]?\d)(.\d{1,4})?) ?d(egree)?s?$/)) {
+            return 'degrees';
+        }   else if (input.match(/^([0-1]?\d|2[0-3])(.\d{1,4})? ?h(our)?s?$/)) {
+            return 'hours';
+        }   else if (input.match(/^\-?((\d0?:(00:)(00))|(([0-8]\d):(([0-5]\d):)(([0-5]\d)(\.\d{1,4})?))) ?d(egree)?s?$/)) {
+            return 'deg_format';
+        }   else if (input.match(/^([0-1]?\d|2[0-3]):([0-5]?\d):([0-5]?\d)(\.\d{1,4})? ?h(our)?s?$/)) {
+            return 'hour_format';
+        }   else if (input.match(/^[0-6](.\d+)?$/)) {
+            return 'radians';
+        }   else {
+            return null;
+        }
+    },
+    /**
+     * Function to validate an angle input value based on  the format entered and converrt to radians
+     * @param {String} angle - value to be parsed to radians.
+     * @returns number - radian value.
+     */
+    parseAngle(angle) {
+        let radians = 0;
+        const angleType = this.getAngleInputType(angle);
+        switch(angleType) {
+            case 'dms' : {
+                radians = this.convertAngleToRadian(angle);
+                break;
+            }
+            case 'hms' : {
+                radians = this.convertAngleToRadian(angle);
+                break;
+            }
+            case 'degrees' : {
+                radians = this.convertToRadians(angle.replace('d','').replace('egree','').replace('s','').replace(' ',''));
+                break;
+            }
+            case 'hours' : {
+                radians = this.convertToRadians(angle.replace('h','').replace('our','').replace('s','').replace(' ','') * 15);
+                break;
+            }
+            case 'deg_format' : {
+                radians  = this.getAngleOutput(angle.replace('d','').replace('egree','').replace('s','').replace(' ',''), true);
+                break;
+            }
+            case 'hour_format' : {
+                radians = this.getAngleOutput(angle.replace('h','').replace('our','').replace('s','').replace(' ',''), false);
+                break;
+            }
+            case 'radians': {
+                radians = parseFloat(angle);
+                break;
+            }
+            default: {
+                break;
+            }
+        }
+        return radians;
+    },
+    /**
+     * Convert a degree value to radian
+     * @param {*} angle 
+     * @returns 
+     */
+    convertToRadians(angle) {
+        return angle * Math.PI /180;
+    },
+    /**
+     * Converts a formatted string to a radian value
+     * @param {String} angle 
+     * @returns 
+     */
+    convertAngleToRadian(angle) {
+        let radian = 0;
+        const isDegree = angle.indexOf('d') > 0;
+        const degreeHourSplit = isDegree?angle.split("d"):angle.split("h");
+        let degreeHour = degreeHourSplit[0];
+        const isNegativeAngle = parseInt(degreeHour)<0;
+        degreeHour = isNegativeAngle?degreeHour*-1:degreeHour;
+        const minuteSplit = degreeHourSplit[1].split('m');
+        const minute = minuteSplit[0];
+        const second = minuteSplit[1].replace('s','');
+        if (isDegree) {
+            radian = this.convertToRadians((degreeHour*1 + minute/60 + second/3600));
+            radian = isNegativeAngle?radian*-1:radian;
+        }   else {
+            radian = this.convertToRadians((degreeHour*15 + minute/4 + second/240));
+        }
+        return radian;
     }
 };
 
diff --git a/SAS/TMSS/frontend/tmss_webapp/src/utils/validator.js b/SAS/TMSS/frontend/tmss_webapp/src/utils/validator.js
index c3101bd69b6..876f4574c47 100644
--- a/SAS/TMSS/frontend/tmss_webapp/src/utils/validator.js
+++ b/SAS/TMSS/frontend/tmss_webapp/src/utils/validator.js
@@ -1,39 +1,25 @@
+import UnitConverter from "./unit.converter";
+
 const Validator = {
     validateTime(value) {
-        const splitOutput = value.split(':');
-        const seconds = splitOutput[2]?splitOutput[2].split('.')[0]:splitOutput[2];
-        let milliSeconds = value.split('.')[1] || '0000';
-        milliSeconds = milliSeconds.padEnd(4,0);
-        if (splitOutput.length < 3) {
-            return false;
-        }   else {
-            if (parseInt(splitOutput[0]) > 23 || parseInt(splitOutput[1])>59 || parseInt(splitOutput[2])>59) {
-                return false;
-            }
-            const timeValue = parseInt(splitOutput[0]*60*60) + parseInt(splitOutput[1]*60) + parseInt(seconds) + milliSeconds/10000;
-            if (timeValue >= 86400) {
+        const angleType = UnitConverter.getAngleInputType(value);
+        if (angleType && ['hms', 'hours', 'hour_format', 'radians'].indexOf(angleType)>=0) {
+            if (angleType === 'radians' && parseFloat(value) > 6.2830) {
                 return false;
             }
+            return true;
         }
-        return true;
+        return false;
     },
     validateAngle(value) {
-        const splitOutput = value.split(':');
-        const seconds = splitOutput[2]?splitOutput[2].split('.')[0]:splitOutput[2];
-        let milliSeconds = value.split('.')[1] || '0000';
-        milliSeconds = milliSeconds.padEnd(4,0);
-        if (splitOutput.length < 3) {
-            return false;
-        }   else {
-            if (parseInt(splitOutput[0]) > 90 || parseInt(splitOutput[1])>59 || parseInt(splitOutput[2])>59) {
-                return false;
-            }
-            const timeValue = parseInt(splitOutput[0]*60*60) + parseInt(splitOutput[1]*60) + parseInt(seconds) + milliSeconds/10000;
-            if (timeValue > 324000) {
+        const angleType = UnitConverter.getAngleInputType(value);
+        if (angleType && ['dms', 'degrees', 'deg_format', 'radians'].indexOf(angleType)>=0) {
+            if (angleType === 'radians' && parseFloat(value) > 1.5707) {
                 return false;
             }
+            return true;
         }
-        return true;
+        return false;
     },
     /**
      * Validates whether any of the given property values is modified comparing the old and new object.
-- 
GitLab