From 598e3e6710e2fe162ee2fa2176c87f8a5520611d Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 19 Nov 2024 16:32:29 -0500 Subject: [PATCH 01/19] Moving setLayout functions to utils --- src/client/app/components/ThreeDComponent.tsx | 116 +++++------------- src/client/app/utils/setLayout.ts | 94 ++++++++++++++ 2 files changed, 122 insertions(+), 88 deletions(-) create mode 100644 src/client/app/utils/setLayout.ts diff --git a/src/client/app/components/ThreeDComponent.tsx b/src/client/app/components/ThreeDComponent.tsx index 658df7415..afaa6aafd 100644 --- a/src/client/app/components/ThreeDComponent.tsx +++ b/src/client/app/components/ThreeDComponent.tsx @@ -26,6 +26,8 @@ import ThreeDPillComponent from './ThreeDPillComponent'; import Plot from 'react-plotly.js'; import { selectSelectedLanguage } from '../redux/slices/appStateSlice'; import Locales from '../types/locales'; +import {setHelpLayout} from '../utils/setLayout'; +import {setThreeDLayout} from '../utils/setLayout'; /** * Component used to render 3D graphics @@ -194,93 +196,31 @@ function formatThreeDData( return [formattedData, layout]; } -/** - * Utility to get/ set help text plotlyLayout - * @param helpText 3D data to be formatted - * @param fontSize current application state - * @returns plotly layout object. - */ -function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: number = 28) { - return { - 'xaxis': { - 'visible': false - }, - 'yaxis': { - 'visible': false - }, - 'annotations': [ - { - 'text': helpText, - 'xref': 'paper', - 'yref': 'paper', - 'showarrow': false, - 'font': { 'size': fontSize } - } - ] - }; -} +// /** +// * Utility to get/ set help text plotlyLayout +// * @param helpText 3D data to be formatted +// * @param fontSize current application state +// * @returns plotly layout object. +// */ +// function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: number = 28) { +// return { +// 'xaxis': { +// 'visible': false +// }, +// 'yaxis': { +// 'visible': false +// }, +// 'annotations': [ +// { +// 'text': helpText, +// 'xref': 'paper', +// 'yref': 'paper', +// 'showarrow': false, +// 'font': { 'size': fontSize } +// } +// ] +// }; +//} +// // Move this to utils directory and do import here and radarchart -/** - * Utility to get / set 3D graphic plotlyLayout - * @param zLabelText 3D data to be formatted - * @param yDataToRender Data range for yaxis - * @returns plotly layout object. - */ -function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { - // Convert date strings to JavaScript Date objects and then get dataRange - const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); - const dataMin = Math.min(...dateObjects.map(date => date.getTime())); - const dataMax = Math.max(...dateObjects.map(date => date.getTime())); - const dataRange = dataMax - dataMin; - - //Calculate nTicks for small num of days on y-axis; possibly a better way - let nTicks, dTick = 'd1'; - if (dataRange <= 864000000) { // 1 Day (need 2 ticks) - nTicks = 2; - } else if (dataRange <= 172800000) { // 2 days - nTicks = 3; - } else if (dataRange <= 259200000) { // 3 Days - nTicks = 4; - } else if (dataRange <= 345600000) { // 4 Days - nTicks = 5; - } else { // Anything else; use default nTicks/dTick - nTicks = 0; - dTick = ''; - } - // responsible for setting Labels - return { - // Eliminate margin - margin: { t: 0, b: 0, l: 0, r: 0 }, - // Leaves gaps / voids in graph for missing, undefined, NaN, or null values - connectgaps: false, - scene: { - xaxis: { - title: { text: translate('threeD.x.axis.label') } - }, - yaxis: { - nticks: nTicks, - dtick: dTick, - title: { text: translate('threeD.y.axis.label') }, - tickangle: 0 // This lets y-axis dates appear horizontally rather overlapping ticks - }, - zaxis: { - title: { text: zLabelText } - }, - // Somewhat suitable aspect ratio values for 3D Graphs - aspectratio: { - x: 1, - y: 2.75, - z: 1 - }, - // Somewhat suitable camera eye values for data of zResource[day][interval] - camera: { - eye: { - x: 2.5, - y: -1.6, - z: 0.8 - } - } - } - } as Partial; -} diff --git a/src/client/app/utils/setLayout.ts b/src/client/app/utils/setLayout.ts new file mode 100644 index 000000000..e22be1d93 --- /dev/null +++ b/src/client/app/utils/setLayout.ts @@ -0,0 +1,94 @@ +import translate from '../utils/translate'; + + + + +/** + * Utility to get/ set help text plotlyLayout + * @param helpText 3D data to be formatted + * @param fontSize current application state + * @returns plotly layout object. + */ +export function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: number = 28) { + return { + 'xaxis': { + 'visible': false + }, + 'yaxis': { + 'visible': false + }, + 'annotations': [ + { + 'text': helpText, + 'xref': 'paper', + 'yref': 'paper', + 'showarrow': false, + 'font': { 'size': fontSize } + } + ] + }; +} + +/** + * Utility to get / set 3D graphic plotlyLayout + * @param zLabelText 3D data to be formatted + * @param yDataToRender Data range for yaxis + * @returns plotly layout object. + */ +export function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { + // Convert date strings to JavaScript Date objects and then get dataRange + const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); + const dataMin = Math.min(...dateObjects.map(date => date.getTime())); + const dataMax = Math.max(...dateObjects.map(date => date.getTime())); + const dataRange = dataMax - dataMin; + + //Calculate nTicks for small num of days on y-axis; possibly a better way + let nTicks, dTick = 'd1'; + if (dataRange <= 864000000) { // 1 Day (need 2 ticks) + nTicks = 2; + } else if (dataRange <= 172800000) { // 2 days + nTicks = 3; + } else if (dataRange <= 259200000) { // 3 Days + nTicks = 4; + } else if (dataRange <= 345600000) { // 4 Days + nTicks = 5; + } else { // Anything else; use default nTicks/dTick + nTicks = 0; + dTick = ''; + } + // responsible for setting Labels + return { + // Eliminate margin + margin: { t: 0, b: 0, l: 0, r: 0 }, + // Leaves gaps / voids in graph for missing, undefined, NaN, or null values + connectgaps: false, + scene: { + xaxis: { + title: { text: translate('threeD.x.axis.label') } + }, + yaxis: { + nticks: nTicks, + dtick: dTick, + title: { text: translate('threeD.y.axis.label') }, + tickangle: 0 // This lets y-axis dates appear horizontally rather overlapping ticks + }, + zaxis: { + title: { text: zLabelText } + }, + // Somewhat suitable aspect ratio values for 3D Graphs + aspectratio: { + x: 1, + y: 2.75, + z: 1 + }, + // Somewhat suitable camera eye values for data of zResource[day][interval] + camera: { + eye: { + x: 2.5, + y: -1.6, + z: 0.8 + } + } + } + } as Partial; +} From 372f0d53dd17277dfc623219bf424029bedd516a Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 19 Nov 2024 17:09:27 -0500 Subject: [PATCH 02/19] Changed Radar Chart Components to use setHelpLayout --- .../app/components/RadarChartComponent.tsx | 67 +++---------------- 1 file changed, 8 insertions(+), 59 deletions(-) diff --git a/src/client/app/components/RadarChartComponent.tsx b/src/client/app/components/RadarChartComponent.tsx index 37ce9a0b7..52426d7a0 100644 --- a/src/client/app/components/RadarChartComponent.tsx +++ b/src/client/app/components/RadarChartComponent.tsx @@ -4,7 +4,6 @@ import { values } from 'lodash'; import * as moment from 'moment'; -import { Layout } from 'plotly.js'; import * as React from 'react'; import Plot from 'react-plotly.js'; import { selectGroupDataById } from '../redux/api/groupsApi'; @@ -24,6 +23,8 @@ import getGraphColor from '../utils/getGraphColor'; import { lineUnitLabel } from '../utils/graphics'; import translate from '../utils/translate'; import SpinnerComponent from './SpinnerComponent'; +import {setHelpLayout} from '../utils/setLayout'; +//import {setThreeDLayout} from '../utils/setLayout'; /** * @returns radar plotly component @@ -186,31 +187,13 @@ export default function RadarChartComponent() { } } - let layout: Partial; + let layout = {}; // TODO See 3D code for functions that can be used for layout and notices. if (datasets.length === 0) { // There are no meters so tell user. // Customize the layout of the plot // See https://community.plotly.com/t/replacing-an-empty-graph-with-a-message/31497 for showing text not plot. - layout = { - 'xaxis': { - 'visible': false - }, - 'yaxis': { - 'visible': false - }, - 'annotations': [ - { - 'text': `${translate('select.meter.group')}`, - 'xref': 'paper', - 'yref': 'paper', - 'showarrow': false, - 'font': { - 'size': 28 - } - } - ] - }; + layout = setHelpLayout(translate('select.meter.group'), 28); } else { // Plotly scatterpolar plots have the unfortunate attribute that if a smaller number of plotting // points is done first then that impacts the labeling of the polar coordinate where you can get @@ -227,25 +210,7 @@ export default function RadarChartComponent() { // There is no data so tell user - likely due to date range outside where readings. // Remove plotting data even though none there is an empty r & theta that gives empty graphic. datasets.splice(0, datasets.length); - layout = { - 'xaxis': { - 'visible': false - }, - 'yaxis': { - 'visible': false - }, - 'annotations': [ - { - 'text': `${translate('radar.no.data')}`, - 'xref': 'paper', - 'yref': 'paper', - 'showarrow': false, - 'font': { - 'size': 28 - } - } - ] - }; + layout = setHelpLayout(translate('radar.no.data'),28); } else { // Check if all the values for the dates are compatible. Plotly does not like having different dates in different // scatterpolar lines. Lots of attempts to get this to work failed so not going to allow since not that common. @@ -262,25 +227,9 @@ export default function RadarChartComponent() { // Remove plotting data. datasets.splice(0, datasets.length); // The lines are not compatible so tell user. - layout = { - 'xaxis': { - 'visible': false - }, - 'yaxis': { - 'visible': false - }, - 'annotations': [ - { - 'text': `${translate('radar.lines.incompatible')}`, - 'xref': 'paper', - 'yref': 'paper', - 'showarrow': false, - 'font': { - 'size': 28 - } - } - ] - }; + //Change layout to use function from utils + //Then look at other chart components and do it similar way + layout = setHelpLayout(translate('radar.lines.incompatible'),28); } else { // Data available and okay so plot. // Maximum number of ticks, represents 12 months. Too many is cluttered so this seems good value. From a95dbd582da114112a5e6e8ef0d1102790abe366 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 19 Nov 2024 17:24:59 -0500 Subject: [PATCH 03/19] Formatting +keeping setThreeDLayout as a unique function of ThreeDComponent util file --- .../app/components/RadarChartComponent.tsx | 3 +- src/client/app/components/ThreeDComponent.tsx | 92 +++++++++++++------ src/client/app/utils/setLayout.ts | 69 +------------- 3 files changed, 68 insertions(+), 96 deletions(-) diff --git a/src/client/app/components/RadarChartComponent.tsx b/src/client/app/components/RadarChartComponent.tsx index 52426d7a0..822d57478 100644 --- a/src/client/app/components/RadarChartComponent.tsx +++ b/src/client/app/components/RadarChartComponent.tsx @@ -24,7 +24,6 @@ import { lineUnitLabel } from '../utils/graphics'; import translate from '../utils/translate'; import SpinnerComponent from './SpinnerComponent'; import {setHelpLayout} from '../utils/setLayout'; -//import {setThreeDLayout} from '../utils/setLayout'; /** * @returns radar plotly component @@ -253,7 +252,7 @@ export default function RadarChartComponent() { angularaxis: { // TODO Attempts to format the dates to remove the time did not work with plotly // choosing the tick values which is desirable. Also want time if limited time range. - direction: 'clockwise', + direction: 'counterclockwise', showgrid: true, gridcolor: '#ddd', nticks: maxTicks diff --git a/src/client/app/components/ThreeDComponent.tsx b/src/client/app/components/ThreeDComponent.tsx index afaa6aafd..905f3b00d 100644 --- a/src/client/app/components/ThreeDComponent.tsx +++ b/src/client/app/components/ThreeDComponent.tsx @@ -27,7 +27,6 @@ import Plot from 'react-plotly.js'; import { selectSelectedLanguage } from '../redux/slices/appStateSlice'; import Locales from '../types/locales'; import {setHelpLayout} from '../utils/setLayout'; -import {setThreeDLayout} from '../utils/setLayout'; /** * Component used to render 3D graphics @@ -181,7 +180,6 @@ function formatThreeDData( const readingValue = readings === null ? null : readings.toPrecision(6); return `${translate('threeD.date')}: ${date}
${translate('threeD.time')}: ${time}
${unitLabel}: ${readingValue}`; })); - const formattedData = [{ type: 'surface', showlegend: false, @@ -196,31 +194,69 @@ function formatThreeDData( return [formattedData, layout]; } -// /** -// * Utility to get/ set help text plotlyLayout -// * @param helpText 3D data to be formatted -// * @param fontSize current application state -// * @returns plotly layout object. -// */ -// function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: number = 28) { -// return { -// 'xaxis': { -// 'visible': false -// }, -// 'yaxis': { -// 'visible': false -// }, -// 'annotations': [ -// { -// 'text': helpText, -// 'xref': 'paper', -// 'yref': 'paper', -// 'showarrow': false, -// 'font': { 'size': fontSize } -// } -// ] -// }; -//} -// // Move this to utils directory and do import here and radarchart +/** + * Utility to get / set 3D graphic plotlyLayout + * @param zLabelText 3D data to be formatted + * @param yDataToRender Data range for yaxis + * @returns plotly layout object. + */ +export function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { + // Convert date strings to JavaScript Date objects and then get dataRange + const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); + const dataMin = Math.min(...dateObjects.map(date => date.getTime())); + const dataMax = Math.max(...dateObjects.map(date => date.getTime())); + const dataRange = dataMax - dataMin; + + //Calculate nTicks for small num of days on y-axis; possibly a better way + let nTicks, dTick = 'd1'; + if (dataRange <= 864000000) { // 1 Day (need 2 ticks) + nTicks = 2; + } else if (dataRange <= 172800000) { // 2 days + nTicks = 3; + } else if (dataRange <= 259200000) { // 3 Days + nTicks = 4; + } else if (dataRange <= 345600000) { // 4 Days + nTicks = 5; + } else { // Anything else; use default nTicks/dTick + nTicks = 0; + dTick = ''; + } + // responsible for setting Labels + return { + // Eliminate margin + margin: { t: 0, b: 0, l: 0, r: 0 }, + // Leaves gaps / voids in graph for missing, undefined, NaN, or null values + connectgaps: false, + scene: { + xaxis: { + title: { text: translate('threeD.x.axis.label') } + }, + yaxis: { + nticks: nTicks, + dtick: dTick, + title: { text: translate('threeD.y.axis.label') }, + tickangle: 0 // This lets y-axis dates appear horizontally rather overlapping ticks + }, + zaxis: { + title: { text: zLabelText } + }, + // Somewhat suitable aspect ratio values for 3D Graphs + aspectratio: { + x: 1, + y: 2.75, + z: 1 + }, + // Somewhat suitable camera eye values for data of zResource[day][interval] + camera: { + eye: { + x: 2.5, + y: -1.6, + z: 0.8 + } + } + } + } as Partial; +} + diff --git a/src/client/app/utils/setLayout.ts b/src/client/app/utils/setLayout.ts index e22be1d93..349cc1934 100644 --- a/src/client/app/utils/setLayout.ts +++ b/src/client/app/utils/setLayout.ts @@ -1,6 +1,6 @@ -import translate from '../utils/translate'; - - +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @@ -29,66 +29,3 @@ export function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize }; } -/** - * Utility to get / set 3D graphic plotlyLayout - * @param zLabelText 3D data to be formatted - * @param yDataToRender Data range for yaxis - * @returns plotly layout object. - */ -export function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { - // Convert date strings to JavaScript Date objects and then get dataRange - const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); - const dataMin = Math.min(...dateObjects.map(date => date.getTime())); - const dataMax = Math.max(...dateObjects.map(date => date.getTime())); - const dataRange = dataMax - dataMin; - - //Calculate nTicks for small num of days on y-axis; possibly a better way - let nTicks, dTick = 'd1'; - if (dataRange <= 864000000) { // 1 Day (need 2 ticks) - nTicks = 2; - } else if (dataRange <= 172800000) { // 2 days - nTicks = 3; - } else if (dataRange <= 259200000) { // 3 Days - nTicks = 4; - } else if (dataRange <= 345600000) { // 4 Days - nTicks = 5; - } else { // Anything else; use default nTicks/dTick - nTicks = 0; - dTick = ''; - } - // responsible for setting Labels - return { - // Eliminate margin - margin: { t: 0, b: 0, l: 0, r: 0 }, - // Leaves gaps / voids in graph for missing, undefined, NaN, or null values - connectgaps: false, - scene: { - xaxis: { - title: { text: translate('threeD.x.axis.label') } - }, - yaxis: { - nticks: nTicks, - dtick: dTick, - title: { text: translate('threeD.y.axis.label') }, - tickangle: 0 // This lets y-axis dates appear horizontally rather overlapping ticks - }, - zaxis: { - title: { text: zLabelText } - }, - // Somewhat suitable aspect ratio values for 3D Graphs - aspectratio: { - x: 1, - y: 2.75, - z: 1 - }, - // Somewhat suitable camera eye values for data of zResource[day][interval] - camera: { - eye: { - x: 2.5, - y: -1.6, - z: 0.8 - } - } - } - } as Partial; -} From fdd24afaa5b567b505ce130ce3cc9b733a9dccb8 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 22 Nov 2024 09:27:04 -0500 Subject: [PATCH 04/19] Fixing changes made during testing --- src/client/app/components/RadarChartComponent.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/app/components/RadarChartComponent.tsx b/src/client/app/components/RadarChartComponent.tsx index 822d57478..63f5463b3 100644 --- a/src/client/app/components/RadarChartComponent.tsx +++ b/src/client/app/components/RadarChartComponent.tsx @@ -252,7 +252,7 @@ export default function RadarChartComponent() { angularaxis: { // TODO Attempts to format the dates to remove the time did not work with plotly // choosing the tick values which is desirable. Also want time if limited time range. - direction: 'counterclockwise', + direction: 'clockwise', showgrid: true, gridcolor: '#ddd', nticks: maxTicks From 50356f282c04eed9d832847280c37ad090ccf535 Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 22 Nov 2024 17:16:32 -0500 Subject: [PATCH 05/19] Created isValidGPSInputNew that returns boolean and a message of the error --- src/client/app/utils/calibration.ts | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index 7ec057a4b..22f843b55 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -127,6 +127,38 @@ export function isValidGPSInput(input: string): boolean { return result; } +/** + * Checks if the string is a valid GPS representation. This requires it to be two numbers + * separated by a comma and the GPS values to be within allowed values. + * Note it causes a popup if the GPS values are not valid. + * @param input The string to check for GPS values + * @returns true if string is GPS and false otherwise. + */ +export function isValidGPSInputNew(input: string){ + let message = ''; + let validGps = true; + if (input.indexOf(',') === -1) { // if there is no comma + // TODO It would be nice to tell user that comma is missing but need to check all uses to be sure don't get ''. + message = 'Input is missing a comma'; //Translate later + validGps = false; + } else if (input.indexOf(',') !== input.lastIndexOf(',')) { // if there are multiple commas + message = 'Input has too many commas'; //Translate later + validGps = false; + } + // Works if value is not a number since parseFloat returns a NaN so treated as invalid later. + const array = input.split(',').map((value: string) => parseFloat(value)); + const latitudeIndex = 0; + const longitudeIndex = 1; + const latitudeConstraint = array[latitudeIndex] >= -90 && array[latitudeIndex] <= 90; + const longitudeConstraint = array[longitudeIndex] >= -180 && array[longitudeIndex] <= 180; + const result = latitudeConstraint && longitudeConstraint; + if (!result) { + // TODO It would be nice to return the error and then notify as desired. + showErrorNotification(translate('input.gps.range') + input); + } + return {validGps,message}; +} + /** * Calculates the GPS unit per coordinate unit. * @param origin The GPS value for the origin that was computed during calibration. From 2ece75f4fea540e03a7a0d6e637d9914c62f06fa Mon Sep 17 00:00:00 2001 From: Andrew Date: Fri, 22 Nov 2024 17:17:06 -0500 Subject: [PATCH 06/19] adapting and testing CreateGroupModalComponent to use the new isValidGPSInput --- .../app/components/groups/CreateGroupModalComponent.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index 21be8a6a7..5412fb748 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -19,7 +19,7 @@ import '../../styles/modal.css'; import { tooltipBaseStyle } from '../../styles/modalStyle'; import { SelectOption, TrueFalseType } from '../../types/items'; import { UnitData } from '../../types/redux/units'; -import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; +import { GPSPoint, isValidGPSInputNew } from '../../utils/calibration'; import { getGroupMenuOptionsForGroup, getMeterMenuOptionsForGroup, @@ -195,7 +195,8 @@ export default function CreateGroupModalComponent() { // If the user input a value then gpsInput should be a string. // null came from the DB and it is okay to just leave it - Not a string. if (typeof gpsInput === 'string') { - if (isValidGPSInput(gpsInput)) { + const {validGps,message} = isValidGPSInputNew(gpsInput); + if (validGps) { // Clearly gpsInput is a string but TS complains about the split so cast. const gpsValues = (gpsInput as string).split(',').map((value: string) => parseFloat(value)); // It is valid and needs to be in this format for routing. @@ -209,6 +210,7 @@ export default function CreateGroupModalComponent() { // TODO isValidGPSInput currently pops up an alert so not doing it here, may change // so leaving code commented out. // showErrorNotification(translate('input.gps.range') + state.gps + '.'); + showErrorNotification(message); inputOk = false; } } From efc219ab88a9f35a584232834acab392edb92670 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 30 Nov 2024 16:18:37 -0500 Subject: [PATCH 07/19] Formatting +keeping setThreeDLayout as a unique function of ThreeDComponent util file --- .../app/components/RadarChartComponent.tsx | 1 - src/client/app/components/ThreeDComponent.tsx | 92 +++++++++++++------ 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src/client/app/components/RadarChartComponent.tsx b/src/client/app/components/RadarChartComponent.tsx index 52426d7a0..63f5463b3 100644 --- a/src/client/app/components/RadarChartComponent.tsx +++ b/src/client/app/components/RadarChartComponent.tsx @@ -24,7 +24,6 @@ import { lineUnitLabel } from '../utils/graphics'; import translate from '../utils/translate'; import SpinnerComponent from './SpinnerComponent'; import {setHelpLayout} from '../utils/setLayout'; -//import {setThreeDLayout} from '../utils/setLayout'; /** * @returns radar plotly component diff --git a/src/client/app/components/ThreeDComponent.tsx b/src/client/app/components/ThreeDComponent.tsx index afaa6aafd..905f3b00d 100644 --- a/src/client/app/components/ThreeDComponent.tsx +++ b/src/client/app/components/ThreeDComponent.tsx @@ -27,7 +27,6 @@ import Plot from 'react-plotly.js'; import { selectSelectedLanguage } from '../redux/slices/appStateSlice'; import Locales from '../types/locales'; import {setHelpLayout} from '../utils/setLayout'; -import {setThreeDLayout} from '../utils/setLayout'; /** * Component used to render 3D graphics @@ -181,7 +180,6 @@ function formatThreeDData( const readingValue = readings === null ? null : readings.toPrecision(6); return `${translate('threeD.date')}: ${date}
${translate('threeD.time')}: ${time}
${unitLabel}: ${readingValue}`; })); - const formattedData = [{ type: 'surface', showlegend: false, @@ -196,31 +194,69 @@ function formatThreeDData( return [formattedData, layout]; } -// /** -// * Utility to get/ set help text plotlyLayout -// * @param helpText 3D data to be formatted -// * @param fontSize current application state -// * @returns plotly layout object. -// */ -// function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize: number = 28) { -// return { -// 'xaxis': { -// 'visible': false -// }, -// 'yaxis': { -// 'visible': false -// }, -// 'annotations': [ -// { -// 'text': helpText, -// 'xref': 'paper', -// 'yref': 'paper', -// 'showarrow': false, -// 'font': { 'size': fontSize } -// } -// ] -// }; -//} -// // Move this to utils directory and do import here and radarchart +/** + * Utility to get / set 3D graphic plotlyLayout + * @param zLabelText 3D data to be formatted + * @param yDataToRender Data range for yaxis + * @returns plotly layout object. + */ +export function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { + // Convert date strings to JavaScript Date objects and then get dataRange + const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); + const dataMin = Math.min(...dateObjects.map(date => date.getTime())); + const dataMax = Math.max(...dateObjects.map(date => date.getTime())); + const dataRange = dataMax - dataMin; + + //Calculate nTicks for small num of days on y-axis; possibly a better way + let nTicks, dTick = 'd1'; + if (dataRange <= 864000000) { // 1 Day (need 2 ticks) + nTicks = 2; + } else if (dataRange <= 172800000) { // 2 days + nTicks = 3; + } else if (dataRange <= 259200000) { // 3 Days + nTicks = 4; + } else if (dataRange <= 345600000) { // 4 Days + nTicks = 5; + } else { // Anything else; use default nTicks/dTick + nTicks = 0; + dTick = ''; + } + // responsible for setting Labels + return { + // Eliminate margin + margin: { t: 0, b: 0, l: 0, r: 0 }, + // Leaves gaps / voids in graph for missing, undefined, NaN, or null values + connectgaps: false, + scene: { + xaxis: { + title: { text: translate('threeD.x.axis.label') } + }, + yaxis: { + nticks: nTicks, + dtick: dTick, + title: { text: translate('threeD.y.axis.label') }, + tickangle: 0 // This lets y-axis dates appear horizontally rather overlapping ticks + }, + zaxis: { + title: { text: zLabelText } + }, + // Somewhat suitable aspect ratio values for 3D Graphs + aspectratio: { + x: 1, + y: 2.75, + z: 1 + }, + // Somewhat suitable camera eye values for data of zResource[day][interval] + camera: { + eye: { + x: 2.5, + y: -1.6, + z: 0.8 + } + } + } + } as Partial; +} + From 2eb4665c17793e1012378361c42dc3b37ff5b4e3 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 30 Nov 2024 16:23:21 -0500 Subject: [PATCH 08/19] Creating new setLayout file in utils for import --- src/client/app/utils/setLayout.ts | 69 ++----------------------------- 1 file changed, 3 insertions(+), 66 deletions(-) diff --git a/src/client/app/utils/setLayout.ts b/src/client/app/utils/setLayout.ts index e22be1d93..349cc1934 100644 --- a/src/client/app/utils/setLayout.ts +++ b/src/client/app/utils/setLayout.ts @@ -1,6 +1,6 @@ -import translate from '../utils/translate'; - - +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ /** @@ -29,66 +29,3 @@ export function setHelpLayout(helpText: string = 'Help Text Goes Here', fontSize }; } -/** - * Utility to get / set 3D graphic plotlyLayout - * @param zLabelText 3D data to be formatted - * @param yDataToRender Data range for yaxis - * @returns plotly layout object. - */ -export function setThreeDLayout(zLabelText: string = 'Resource Usage', yDataToRender: string[]) { - // Convert date strings to JavaScript Date objects and then get dataRange - const dateObjects = yDataToRender.map(dateStr => new Date(dateStr)); - const dataMin = Math.min(...dateObjects.map(date => date.getTime())); - const dataMax = Math.max(...dateObjects.map(date => date.getTime())); - const dataRange = dataMax - dataMin; - - //Calculate nTicks for small num of days on y-axis; possibly a better way - let nTicks, dTick = 'd1'; - if (dataRange <= 864000000) { // 1 Day (need 2 ticks) - nTicks = 2; - } else if (dataRange <= 172800000) { // 2 days - nTicks = 3; - } else if (dataRange <= 259200000) { // 3 Days - nTicks = 4; - } else if (dataRange <= 345600000) { // 4 Days - nTicks = 5; - } else { // Anything else; use default nTicks/dTick - nTicks = 0; - dTick = ''; - } - // responsible for setting Labels - return { - // Eliminate margin - margin: { t: 0, b: 0, l: 0, r: 0 }, - // Leaves gaps / voids in graph for missing, undefined, NaN, or null values - connectgaps: false, - scene: { - xaxis: { - title: { text: translate('threeD.x.axis.label') } - }, - yaxis: { - nticks: nTicks, - dtick: dTick, - title: { text: translate('threeD.y.axis.label') }, - tickangle: 0 // This lets y-axis dates appear horizontally rather overlapping ticks - }, - zaxis: { - title: { text: zLabelText } - }, - // Somewhat suitable aspect ratio values for 3D Graphs - aspectratio: { - x: 1, - y: 2.75, - z: 1 - }, - // Somewhat suitable camera eye values for data of zResource[day][interval] - camera: { - eye: { - x: 2.5, - y: -1.6, - z: 0.8 - } - } - } - } as Partial; -} From 7b5e7da771d9c60f951efb89a66e11c96172d569 Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 30 Nov 2024 16:42:29 -0500 Subject: [PATCH 09/19] Adapted CreateGroupModalComponent to use IsValidGPSInputNew --- .../app/components/groups/CreateGroupModalComponent.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index 21be8a6a7..d9cb92071 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -19,7 +19,7 @@ import '../../styles/modal.css'; import { tooltipBaseStyle } from '../../styles/modalStyle'; import { SelectOption, TrueFalseType } from '../../types/items'; import { UnitData } from '../../types/redux/units'; -import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; +import { GPSPoint, isValidGPSInputNew } from '../../utils/calibration'; import { getGroupMenuOptionsForGroup, getMeterMenuOptionsForGroup, @@ -195,7 +195,8 @@ export default function CreateGroupModalComponent() { // If the user input a value then gpsInput should be a string. // null came from the DB and it is okay to just leave it - Not a string. if (typeof gpsInput === 'string') { - if (isValidGPSInput(gpsInput)) { + const {validGps,message} = isValidGPSInputNew(gpsInput); + if (validGps) { // Clearly gpsInput is a string but TS complains about the split so cast. const gpsValues = (gpsInput as string).split(',').map((value: string) => parseFloat(value)); // It is valid and needs to be in this format for routing. @@ -209,6 +210,7 @@ export default function CreateGroupModalComponent() { // TODO isValidGPSInput currently pops up an alert so not doing it here, may change // so leaving code commented out. // showErrorNotification(translate('input.gps.range') + state.gps + '.'); + showErrorNotification(translate(message)); inputOk = false; } } From 2ebf879f271904153b1356097c50700affb666fc Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 30 Nov 2024 16:42:49 -0500 Subject: [PATCH 10/19] Adapted EditGroupModalComponent to use isValidGPSInputNew --- .../app/components/groups/EditGroupModalComponent.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index d72d0b42e..f12ab21b2 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -24,7 +24,7 @@ import { DataType } from '../../types/Datasources'; import { SelectOption, TrueFalseType } from '../../types/items'; import { GroupData } from '../../types/redux/groups'; import { UnitData } from '../../types/redux/units'; -import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; +import { GPSPoint, isValidGPSInputNew } from '../../utils/calibration'; import { GroupCase, getCompatibilityChangeCase, @@ -285,7 +285,8 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr // If the user input a value then gpsInput should be a string // null came from DB and it is okay to just leave it - Not a String. if (typeof gpsInput === 'string') { - if (isValidGPSInput(gpsInput)) { + const {validGps,message} = isValidGPSInputNew(gpsInput); + if (validGps) { // Clearly gpsInput is a string but TS complains about the split so cast. const gpsValues = (gpsInput as string).split(',').map((value: string) => parseFloat(value)); // It is valid and needs to be in this format for routing @@ -298,6 +299,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr // TODO isValidGPSInput currently pops up an alert so not doing it here, may change // so leaving code commented out. // showErrorNotification(translate('input.gps.range') + groupState.gps + '.'); + showErrorNotification(message); inputOk = false; } } From d2973a45fab3210fbf2ea3b666e83f9cafb14abe Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 30 Nov 2024 16:43:54 -0500 Subject: [PATCH 11/19] Implemented isValidGPSInputNew as a temporary solution and will adapt it back to isValidGPSInput, created to return a message of the error as well as a boolean of whether the GPS Coordinate is valid --- src/client/app/utils/calibration.ts | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index 7ec057a4b..22f843b55 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -127,6 +127,38 @@ export function isValidGPSInput(input: string): boolean { return result; } +/** + * Checks if the string is a valid GPS representation. This requires it to be two numbers + * separated by a comma and the GPS values to be within allowed values. + * Note it causes a popup if the GPS values are not valid. + * @param input The string to check for GPS values + * @returns true if string is GPS and false otherwise. + */ +export function isValidGPSInputNew(input: string){ + let message = ''; + let validGps = true; + if (input.indexOf(',') === -1) { // if there is no comma + // TODO It would be nice to tell user that comma is missing but need to check all uses to be sure don't get ''. + message = 'Input is missing a comma'; //Translate later + validGps = false; + } else if (input.indexOf(',') !== input.lastIndexOf(',')) { // if there are multiple commas + message = 'Input has too many commas'; //Translate later + validGps = false; + } + // Works if value is not a number since parseFloat returns a NaN so treated as invalid later. + const array = input.split(',').map((value: string) => parseFloat(value)); + const latitudeIndex = 0; + const longitudeIndex = 1; + const latitudeConstraint = array[latitudeIndex] >= -90 && array[latitudeIndex] <= 90; + const longitudeConstraint = array[longitudeIndex] >= -180 && array[longitudeIndex] <= 180; + const result = latitudeConstraint && longitudeConstraint; + if (!result) { + // TODO It would be nice to return the error and then notify as desired. + showErrorNotification(translate('input.gps.range') + input); + } + return {validGps,message}; +} + /** * Calculates the GPS unit per coordinate unit. * @param origin The GPS value for the origin that was computed during calibration. From 0ef59f6b1eed0d4dc45bcda5e6e1b0be86cee18f Mon Sep 17 00:00:00 2001 From: Andrew Date: Sat, 30 Nov 2024 16:44:09 -0500 Subject: [PATCH 12/19] Adapted CreateMeterModalComponent to use isValidGPSInputNew --- .../app/components/meters/CreateMeterModalComponent.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/meters/CreateMeterModalComponent.tsx b/src/client/app/components/meters/CreateMeterModalComponent.tsx index 3a53cb3e4..4a876dce0 100644 --- a/src/client/app/components/meters/CreateMeterModalComponent.tsx +++ b/src/client/app/components/meters/CreateMeterModalComponent.tsx @@ -22,7 +22,7 @@ import '../../styles/modal.css'; import { tooltipBaseStyle } from '../../styles/modalStyle'; import { TrueFalseType } from '../../types/items'; import { MeterData, MeterTimeSortType, MeterType } from '../../types/redux/meters'; -import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; +import { GPSPoint, isValidGPSInputNew } from '../../utils/calibration'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { showErrorNotification, showSuccessNotification } from '../../utils/notifications'; import translate from '../../utils/translate'; @@ -123,7 +123,8 @@ export default function CreateMeterModalComponent(props: CreateMeterModalProps): // If the user input a value then gpsInput should be a string. // null came from the DB and it is okay to just leave it - Not a string. if (typeof gpsInput === 'string') { - if (isValidGPSInput(gpsInput)) { + const {validGps, message} = isValidGPSInputNew(gpsInput); + if (validGps) { const gpsValues = gpsInput.split(',').map(value => parseFloat(value)); // It is valid and needs to be in this format for routing. gps = { @@ -135,6 +136,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalProps): // TODO isValidGPSInput currently pops up an alert so not doing it here, may change // so leaving code commented out. // showErrorNotification(translate('input.gps.range') + state.gps + '.'); + showErrorNotification(message); inputOk = false; } } From 978457903125fe152f1486034a05ea802f5e67b2 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 2 Dec 2024 12:20:07 -0500 Subject: [PATCH 13/19] Changed return message from isValidGPSInputNew for Component files to use instead of creating error message within the component --- .../app/components/groups/CreateGroupModalComponent.tsx | 2 +- src/client/app/utils/calibration.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index d9cb92071..5412fb748 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -210,7 +210,7 @@ export default function CreateGroupModalComponent() { // TODO isValidGPSInput currently pops up an alert so not doing it here, may change // so leaving code commented out. // showErrorNotification(translate('input.gps.range') + state.gps + '.'); - showErrorNotification(translate(message)); + showErrorNotification(message); inputOk = false; } } diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index 22f843b55..ddbec5cd0 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -154,7 +154,9 @@ export function isValidGPSInputNew(input: string){ const result = latitudeConstraint && longitudeConstraint; if (!result) { // TODO It would be nice to return the error and then notify as desired. - showErrorNotification(translate('input.gps.range') + input); + //showErrorNotification(translate('input.gps.range') + input); + validGps = false; + message = translate('input.gps.range') + input; } return {validGps,message}; } From a3e8c09c17162c494179e563b0f0fa7b5dca7ad9 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 3 Dec 2024 11:12:14 -0500 Subject: [PATCH 14/19] Fixing isValidGpsInputNew Logic to fix error notification for invalid Meters, and adapting MapCalibrationInfoDisplayComponents --- .../MapCalibrationInfoDisplayComponent.tsx | 5 ++-- src/client/app/utils/calibration.ts | 30 ++++++++++--------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx index 7e27aeede..d42f1bd4e 100644 --- a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx +++ b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as React from 'react'; -import {GPSPoint, isValidGPSInput} from '../../utils/calibration'; +import {GPSPoint, isValidGPSInputNew} from '../../utils/calibration'; import {ChangeEvent, FormEvent} from 'react'; import {FormattedMessage} from 'react-intl'; @@ -88,7 +88,8 @@ export default class MapCalibrationInfoDisplayComponent extends React.Component< const longitudeIndex = 1; if (this.props.currentCartesianDisplay === 'x: undefined, y: undefined') { return; } const input = this.state.value; - if (isValidGPSInput(input)) { + const {validGps} = isValidGPSInputNew(input); + if (validGps) { const array = input.split(',').map((value: string) => parseFloat(value)); const gps: GPSPoint = { longitude: array[longitudeIndex], diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index ddbec5cd0..6cba8579c 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -139,24 +139,26 @@ export function isValidGPSInputNew(input: string){ let validGps = true; if (input.indexOf(',') === -1) { // if there is no comma // TODO It would be nice to tell user that comma is missing but need to check all uses to be sure don't get ''. - message = 'Input is missing a comma'; //Translate later + message = 'GPS Input is missing a comma'; //Translate later validGps = false; } else if (input.indexOf(',') !== input.lastIndexOf(',')) { // if there are multiple commas - message = 'Input has too many commas'; //Translate later + message = 'GPS Input has too many commas'; //Translate later validGps = false; } - // Works if value is not a number since parseFloat returns a NaN so treated as invalid later. - const array = input.split(',').map((value: string) => parseFloat(value)); - const latitudeIndex = 0; - const longitudeIndex = 1; - const latitudeConstraint = array[latitudeIndex] >= -90 && array[latitudeIndex] <= 90; - const longitudeConstraint = array[longitudeIndex] >= -180 && array[longitudeIndex] <= 180; - const result = latitudeConstraint && longitudeConstraint; - if (!result) { - // TODO It would be nice to return the error and then notify as desired. - //showErrorNotification(translate('input.gps.range') + input); - validGps = false; - message = translate('input.gps.range') + input; + if(validGps){ + // Works if value is not a number since parseFloat returns a NaN so treated as invalid later. + const array = input.split(',').map((value: string) => parseFloat(value)); + const latitudeIndex = 0; + const longitudeIndex = 1; + const latitudeConstraint = array[latitudeIndex] >= -90 && array[latitudeIndex] <= 90; + const longitudeConstraint = array[longitudeIndex] >= -180 && array[longitudeIndex] <= 180; + const result = latitudeConstraint && longitudeConstraint; + if (!result) { + // TODO It would be nice to return the error and then notify as desired. + //showErrorNotification(translate('input.gps.range') + input); + validGps = false; + message = translate('input.gps.range') + input; + } } return {validGps,message}; } From eb66706736e3557d78ffa12025c9d863f0023e95 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 3 Dec 2024 14:00:25 -0500 Subject: [PATCH 15/19] Changing to use isValidGPSInputNew --- .../app/components/groups/EditGroupModalComponent.tsx | 4 ---- .../app/components/meters/CreateMeterModalComponent.tsx | 3 --- .../app/components/meters/EditMeterModalComponent.tsx | 9 ++++----- src/client/app/utils/calibration.ts | 3 ++- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index f4acd338c..a7e17b52e 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -298,10 +298,6 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr latitude: gpsValues[latitudeIndex] }; } else if ((gpsInput as string).length !== 0) { - // GPS not okay and there since non-zero length value. - // TODO isValidGPSInput currently pops up an alert so not doing it here, may change - // so leaving code commented out. - // showErrorNotification(translate('input.gps.range') + groupState.gps + '.'); showErrorNotification(message); inputOk = false; } diff --git a/src/client/app/components/meters/CreateMeterModalComponent.tsx b/src/client/app/components/meters/CreateMeterModalComponent.tsx index c75d97941..4e57a3dc2 100644 --- a/src/client/app/components/meters/CreateMeterModalComponent.tsx +++ b/src/client/app/components/meters/CreateMeterModalComponent.tsx @@ -134,9 +134,6 @@ export default function CreateMeterModalComponent(props: CreateMeterModalProps): }; } else if (gpsInput.length !== 0) { // GPS not okay. Only true if some input. - // TODO isValidGPSInput currently pops up an alert so not doing it here, may change - // so leaving code commented out. - // showErrorNotification(translate('input.gps.range') + state.gps + '.'); showErrorNotification(message); inputOk = false; } diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index 59afc37a7..eec06fbf1 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -22,7 +22,7 @@ import { tooltipBaseStyle } from '../../styles/modalStyle'; import { TrueFalseType } from '../../types/items'; import { MeterData, MeterTimeSortType, MeterType } from '../../types/redux/meters'; import { UnitRepresentType } from '../../types/redux/units'; -import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; +import { GPSPoint, isValidGPSInputNew } from '../../utils/calibration'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { getGPSString, nullToEmptyString } from '../../utils/input'; import { showErrorNotification } from '../../utils/notifications'; @@ -105,7 +105,8 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr // If the user input a value then gpsInput should be a string. // null came from the DB and it is okay to just leave it - Not a string. if (typeof gpsInput === 'string') { - if (isValidGPSInput(gpsInput)) { + const {validGps, message} = isValidGPSInputNew(gpsInput); + if (validGps) { // Clearly gpsInput is a string but TS complains about the split so cast. const gpsValues = (gpsInput as string).split(',').map((value: string) => parseFloat(value)); // It is valid and needs to be in this format for routing. @@ -116,9 +117,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr // gpsInput must be of type string but TS does not think so so cast. } else if ((gpsInput as string).length !== 0) { // GPS not okay. - // TODO isValidGPSInput currently tops up an alert so not doing it here, may change - // so leaving code commented out. - // showErrorNotification(translate('input.gps.range') + state.gps + '.'); + showErrorNotification(message); inputOk = false; } } diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index 6cba8579c..08b951e85 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -78,7 +78,8 @@ export interface Dimensions { export function itemMapInfoOk(itemID: number, type: DataType, map: MapMetadata, gps?: GPSPoint): boolean { if (map === undefined) { return false; } if ((gps === null || gps === undefined) || map.origin === undefined || map.opposite === undefined) { return false; } - if (!isValidGPSInput(`${gps.latitude},${gps.longitude}`)) { + const {validGps} = isValidGPSInputNew(`${gps.latitude},${gps.longitude}`); + if (!validGps) { logToServer('error', `Found invalid ${type === DataType.Meter ? 'meter' : 'group'} gps stored in database, id = ${itemID}`)(); return false; } From 5d994ab37a77fc141aac9c9fb031ccf9a82ff8d1 Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 3 Dec 2024 14:06:03 -0500 Subject: [PATCH 16/19] Changing all adapted files back to isValidGPSInput and deleting temporary test function --- .../groups/CreateGroupModalComponent.tsx | 4 +-- .../groups/EditGroupModalComponent.tsx | 4 +-- .../MapCalibrationInfoDisplayComponent.tsx | 4 +-- .../meters/CreateMeterModalComponent.tsx | 4 +-- .../meters/EditMeterModalComponent.tsx | 4 +-- src/client/app/utils/calibration.ts | 33 ++----------------- 6 files changed, 12 insertions(+), 41 deletions(-) diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index 1201fc2af..3ab3a5cbc 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -19,7 +19,7 @@ import '../../styles/modal.css'; import { tooltipBaseStyle } from '../../styles/modalStyle'; import { SelectOption, TrueFalseType } from '../../types/items'; import { UnitData } from '../../types/redux/units'; -import { GPSPoint, isValidGPSInputNew } from '../../utils/calibration'; +import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; import { getGroupMenuOptionsForGroup, getMeterMenuOptionsForGroup, @@ -197,7 +197,7 @@ export default function CreateGroupModalComponent() { // If the user input a value then gpsInput should be a string. // null came from the DB and it is okay to just leave it - Not a string. if (typeof gpsInput === 'string') { - const {validGps,message} = isValidGPSInputNew(gpsInput); + const {validGps,message} = isValidGPSInput(gpsInput); if (validGps) { // Clearly gpsInput is a string but TS complains about the split so cast. const gpsValues = (gpsInput as string).split(',').map((value: string) => parseFloat(value)); diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index a7e17b52e..569494704 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -25,7 +25,7 @@ import { DataType } from '../../types/Datasources'; import { SelectOption, TrueFalseType } from '../../types/items'; import { GroupData } from '../../types/redux/groups'; import { UnitData } from '../../types/redux/units'; -import { GPSPoint, isValidGPSInputNew } from '../../utils/calibration'; +import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; import { GroupCase, getCompatibilityChangeCase, @@ -288,7 +288,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr // If the user input a value then gpsInput should be a string // null came from DB and it is okay to just leave it - Not a String. if (typeof gpsInput === 'string') { - const {validGps,message} = isValidGPSInputNew(gpsInput); + const {validGps,message} = isValidGPSInput(gpsInput); if (validGps) { // Clearly gpsInput is a string but TS complains about the split so cast. const gpsValues = (gpsInput as string).split(',').map((value: string) => parseFloat(value)); diff --git a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx index d42f1bd4e..89bf1dcf4 100644 --- a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx +++ b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx @@ -3,7 +3,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as React from 'react'; -import {GPSPoint, isValidGPSInputNew} from '../../utils/calibration'; +import {GPSPoint, isValidGPSInput} from '../../utils/calibration'; import {ChangeEvent, FormEvent} from 'react'; import {FormattedMessage} from 'react-intl'; @@ -88,7 +88,7 @@ export default class MapCalibrationInfoDisplayComponent extends React.Component< const longitudeIndex = 1; if (this.props.currentCartesianDisplay === 'x: undefined, y: undefined') { return; } const input = this.state.value; - const {validGps} = isValidGPSInputNew(input); + const {validGps} = isValidGPSInput(input); if (validGps) { const array = input.split(',').map((value: string) => parseFloat(value)); const gps: GPSPoint = { diff --git a/src/client/app/components/meters/CreateMeterModalComponent.tsx b/src/client/app/components/meters/CreateMeterModalComponent.tsx index 4e57a3dc2..13534f067 100644 --- a/src/client/app/components/meters/CreateMeterModalComponent.tsx +++ b/src/client/app/components/meters/CreateMeterModalComponent.tsx @@ -22,7 +22,7 @@ import '../../styles/modal.css'; import { tooltipBaseStyle } from '../../styles/modalStyle'; import { TrueFalseType } from '../../types/items'; import { MeterData, MeterTimeSortType, MeterType } from '../../types/redux/meters'; -import { GPSPoint, isValidGPSInputNew } from '../../utils/calibration'; +import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { showErrorNotification, showSuccessNotification } from '../../utils/notifications'; import { useTranslate } from '../../redux/componentHooks'; @@ -124,7 +124,7 @@ export default function CreateMeterModalComponent(props: CreateMeterModalProps): // If the user input a value then gpsInput should be a string. // null came from the DB and it is okay to just leave it - Not a string. if (typeof gpsInput === 'string') { - const {validGps, message} = isValidGPSInputNew(gpsInput); + const {validGps, message} = isValidGPSInput(gpsInput); if (validGps) { const gpsValues = gpsInput.split(',').map(value => parseFloat(value)); // It is valid and needs to be in this format for routing. diff --git a/src/client/app/components/meters/EditMeterModalComponent.tsx b/src/client/app/components/meters/EditMeterModalComponent.tsx index eec06fbf1..ca93e51a2 100644 --- a/src/client/app/components/meters/EditMeterModalComponent.tsx +++ b/src/client/app/components/meters/EditMeterModalComponent.tsx @@ -22,7 +22,7 @@ import { tooltipBaseStyle } from '../../styles/modalStyle'; import { TrueFalseType } from '../../types/items'; import { MeterData, MeterTimeSortType, MeterType } from '../../types/redux/meters'; import { UnitRepresentType } from '../../types/redux/units'; -import { GPSPoint, isValidGPSInputNew } from '../../utils/calibration'; +import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; import { AreaUnitType } from '../../utils/getAreaUnitConversion'; import { getGPSString, nullToEmptyString } from '../../utils/input'; import { showErrorNotification } from '../../utils/notifications'; @@ -105,7 +105,7 @@ export default function EditMeterModalComponent(props: EditMeterModalComponentPr // If the user input a value then gpsInput should be a string. // null came from the DB and it is okay to just leave it - Not a string. if (typeof gpsInput === 'string') { - const {validGps, message} = isValidGPSInputNew(gpsInput); + const {validGps, message} = isValidGPSInput(gpsInput); if (validGps) { // Clearly gpsInput is a string but TS complains about the split so cast. const gpsValues = (gpsInput as string).split(',').map((value: string) => parseFloat(value)); diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index 08b951e85..84368103b 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -2,7 +2,6 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { showErrorNotification } from './notifications'; import { logToServer } from '../redux/actions/logs'; import { DataType } from '../types/Datasources'; import { MapMetadata } from '../types/redux/map'; @@ -78,7 +77,7 @@ export interface Dimensions { export function itemMapInfoOk(itemID: number, type: DataType, map: MapMetadata, gps?: GPSPoint): boolean { if (map === undefined) { return false; } if ((gps === null || gps === undefined) || map.origin === undefined || map.opposite === undefined) { return false; } - const {validGps} = isValidGPSInputNew(`${gps.latitude},${gps.longitude}`); + const {validGps} = isValidGPSInput(`${gps.latitude},${gps.longitude}`); if (!validGps) { logToServer('error', `Found invalid ${type === DataType.Meter ? 'meter' : 'group'} gps stored in database, id = ${itemID}`)(); return false; @@ -107,35 +106,7 @@ export function itemDisplayableOnMap(size: Dimensions, point: CartesianPoint): b * @param input The string to check for GPS values * @returns true if string is GPS and false otherwise. */ -export function isValidGPSInput(input: string): boolean { - if (input.indexOf(',') === -1) { // if there is no comma - // TODO It would be nice to tell user that comma is missing but need to check all uses to be sure don't get ''. - return false; - } else if (input.indexOf(',') !== input.lastIndexOf(',')) { // if there are multiple commas - return false; - } - // Works if value is not a number since parseFloat returns a NaN so treated as invalid later. - const array = input.split(',').map((value: string) => parseFloat(value)); - const latitudeIndex = 0; - const longitudeIndex = 1; - const latitudeConstraint = array[latitudeIndex] >= -90 && array[latitudeIndex] <= 90; - const longitudeConstraint = array[longitudeIndex] >= -180 && array[longitudeIndex] <= 180; - const result = latitudeConstraint && longitudeConstraint; - if (!result) { - // TODO It would be nice to return the error and then notify as desired. - showErrorNotification(translate('input.gps.range') + input); - } - return result; -} - -/** - * Checks if the string is a valid GPS representation. This requires it to be two numbers - * separated by a comma and the GPS values to be within allowed values. - * Note it causes a popup if the GPS values are not valid. - * @param input The string to check for GPS values - * @returns true if string is GPS and false otherwise. - */ -export function isValidGPSInputNew(input: string){ +export function isValidGPSInput(input: string){ let message = ''; let validGps = true; if (input.indexOf(',') === -1) { // if there is no comma From db24b73783e4e5c10745c22d2d7e27d919176a2a Mon Sep 17 00:00:00 2001 From: Andrew Date: Tue, 3 Dec 2024 14:25:41 -0500 Subject: [PATCH 17/19] Formatting + adding translations for error messages --- .../app/components/groups/CreateGroupModalComponent.tsx | 4 ---- src/client/app/translations/data.ts | 6 ++++++ src/client/app/utils/calibration.ts | 7 ++++--- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index 3ab3a5cbc..b2a74a9a1 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -208,10 +208,6 @@ export default function CreateGroupModalComponent() { }; // gpsInput must be of type string but TS does not think so so cast. } else if ((gpsInput as string).length !== 0) { - // GPS not okay. Only true if some input. - // TODO isValidGPSInput currently pops up an alert so not doing it here, may change - // so leaving code commented out. - // showErrorNotification(translate('input.gps.range') + state.gps + '.'); showErrorNotification(message); inputOk = false; } diff --git a/src/client/app/translations/data.ts b/src/client/app/translations/data.ts index 91795e891..b8fde5bfc 100644 --- a/src/client/app/translations/data.ts +++ b/src/client/app/translations/data.ts @@ -176,6 +176,8 @@ const LocaleTranslationData = { "failed.to.submit.changes": "Failed to submit changes", "false": "False", "gps": "GPS: latitude, longitude", + "gps.missing.comma": "GPS Input is missing a comma", + "gps.many.comma": "GPS Input has too many commas", "graph": "Graph", "graph.settings": "Graph Settings", "graph.type": "Graph Type", @@ -698,6 +700,8 @@ const LocaleTranslationData = { "failed.to.submit.changes": "Échec de l'envoi des modifications", "false": "Faux", "gps": "GPS: latitude, longitude\u{26A1}", + "gps.missing.comma": "GPS Input is missing a comma\u{26A1}", + "gps.many.comma": "GPS Input has too many commas\u{26A1}", "graph": "Graphique", "graph.settings": "Graph Settings\u{26A1}", "graph.type": "Type du Diagramme", @@ -1221,6 +1225,8 @@ const LocaleTranslationData = { "failed.to.submit.changes": "No se pudo entregar los cambios", "false": "Falso", "gps": "GPS: latitud, longitud", + "gps.missing.comma": "GPS Input is missing a comma\u{26A1}", + "gps.many.comma": "GPS Input has too many commas\u{26A1}", "graph": "Gráfico", "graph.settings": "Graph Settings\u{26A1}", "graph.type": "Tipo de gráfico", diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index 84368103b..d0553f587 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -67,7 +67,8 @@ export interface Dimensions { } /** - * Returns true if item (meter or group) and map and reasonably defined and false otherwise. + * Returns true if item (meter or group) and map and reasonably defined + * Returns false and a message about the reason the input is invalid otherwise * @param itemID ID to be used for logging errors * @param type DataType to distinguish between meter and group * @param map map info to check @@ -111,10 +112,10 @@ export function isValidGPSInput(input: string){ let validGps = true; if (input.indexOf(',') === -1) { // if there is no comma // TODO It would be nice to tell user that comma is missing but need to check all uses to be sure don't get ''. - message = 'GPS Input is missing a comma'; //Translate later + message = translate('gps.missing.comma'); validGps = false; } else if (input.indexOf(',') !== input.lastIndexOf(',')) { // if there are multiple commas - message = 'GPS Input has too many commas'; //Translate later + message = translate('gps.many.comma'); validGps = false; } if(validGps){ From b12513d6ea74214588f60b36e817cec7fb75fd2b Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 5 Dec 2024 14:33:07 -0500 Subject: [PATCH 18/19] Remove TODO Comments --- src/client/app/utils/calibration.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/client/app/utils/calibration.ts b/src/client/app/utils/calibration.ts index d0553f587..50e12e50a 100644 --- a/src/client/app/utils/calibration.ts +++ b/src/client/app/utils/calibration.ts @@ -111,7 +111,6 @@ export function isValidGPSInput(input: string){ let message = ''; let validGps = true; if (input.indexOf(',') === -1) { // if there is no comma - // TODO It would be nice to tell user that comma is missing but need to check all uses to be sure don't get ''. message = translate('gps.missing.comma'); validGps = false; } else if (input.indexOf(',') !== input.lastIndexOf(',')) { // if there are multiple commas @@ -127,8 +126,6 @@ export function isValidGPSInput(input: string){ const longitudeConstraint = array[longitudeIndex] >= -180 && array[longitudeIndex] <= 180; const result = latitudeConstraint && longitudeConstraint; if (!result) { - // TODO It would be nice to return the error and then notify as desired. - //showErrorNotification(translate('input.gps.range') + input); validGps = false; message = translate('input.gps.range') + input; } From 87b288515cf00307d7211da6990b12ca2ec6516f Mon Sep 17 00:00:00 2001 From: Steven Huss-Lederman Date: Thu, 5 Dec 2024 18:16:02 -0600 Subject: [PATCH 19/19] formatting, much outside this PR I went on a formatting rampage on all file in PR. --- .../groups/CreateGroupModalComponent.tsx | 6 ++--- .../groups/EditGroupModalComponent.tsx | 12 +++++----- .../MapCalibrationInfoDisplayComponent.tsx | 22 +++++++++---------- .../meters/CreateMeterModalComponent.tsx | 4 ++-- src/client/app/utils/calibration.ts | 8 +++---- src/client/app/utils/setLayout.ts | 1 - 6 files changed, 26 insertions(+), 27 deletions(-) diff --git a/src/client/app/components/groups/CreateGroupModalComponent.tsx b/src/client/app/components/groups/CreateGroupModalComponent.tsx index b2a74a9a1..fdbaf121a 100644 --- a/src/client/app/components/groups/CreateGroupModalComponent.tsx +++ b/src/client/app/components/groups/CreateGroupModalComponent.tsx @@ -197,7 +197,7 @@ export default function CreateGroupModalComponent() { // If the user input a value then gpsInput should be a string. // null came from the DB and it is okay to just leave it - Not a string. if (typeof gpsInput === 'string') { - const {validGps,message} = isValidGPSInput(gpsInput); + const { validGps, message } = isValidGPSInput(gpsInput); if (validGps) { // Clearly gpsInput is a string but TS complains about the split so cast. const gpsValues = (gpsInput as string).split(',').map((value: string) => parseFloat(value)); @@ -523,7 +523,7 @@ export default function CreateGroupModalComponent() { }); // Want chosen in sorted order. return selectedMetersUnsorted.sort((meterA, meterB) => meterA.label.toLowerCase()?. - localeCompare(meterB.label.toLowerCase(), String(locale), { sensitivity: 'accent'})); + localeCompare(meterB.label.toLowerCase(), String(locale), { sensitivity: 'accent' })); } /** @@ -543,7 +543,7 @@ export default function CreateGroupModalComponent() { }); // Want chosen in sorted order. return selectedGroupsUnsorted.sort((groupA, groupB) => groupA.label.toLowerCase()?. - localeCompare(groupB.label.toLowerCase(), String(locale), { sensitivity: 'accent'})); + localeCompare(groupB.label.toLowerCase(), String(locale), { sensitivity: 'accent' })); } /** diff --git a/src/client/app/components/groups/EditGroupModalComponent.tsx b/src/client/app/components/groups/EditGroupModalComponent.tsx index 569494704..1a1568892 100644 --- a/src/client/app/components/groups/EditGroupModalComponent.tsx +++ b/src/client/app/components/groups/EditGroupModalComponent.tsx @@ -288,7 +288,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr // If the user input a value then gpsInput should be a string // null came from DB and it is okay to just leave it - Not a String. if (typeof gpsInput === 'string') { - const {validGps,message} = isValidGPSInput(gpsInput); + const { validGps, message } = isValidGPSInput(gpsInput); if (validGps) { // Clearly gpsInput is a string but TS complains about the split so cast. const gpsValues = (gpsInput as string).split(',').map((value: string) => parseFloat(value)); @@ -929,7 +929,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr }); // Want chosen in sorted order. return selectedMetersUnsorted.sort((meterA, meterB) => meterA.label.toLowerCase()?. - localeCompare(meterB.label.toLowerCase(), String(locale), { sensitivity: 'accent'})); + localeCompare(meterB.label.toLowerCase(), String(locale), { sensitivity: 'accent' })); } /** @@ -951,7 +951,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr }); // Want chosen in sorted order. return selectedGroupsUnsorted.sort((groupA, groupB) => groupA.label.toLowerCase()?. - localeCompare(groupB.label.toLowerCase(), String(locale), { sensitivity: 'accent'})); + localeCompare(groupB.label.toLowerCase(), String(locale), { sensitivity: 'accent' })); } /** @@ -975,7 +975,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr } }); // Sort for display. Before were sorted by id so not okay here. - listedMeters.sort((meterA, meterB) => meterA.toLowerCase().localeCompare(meterB.toLowerCase(), locale, { sensitivity : 'accent' })); + listedMeters.sort((meterA, meterB) => meterA.toLowerCase().localeCompare(meterB.toLowerCase(), locale, { sensitivity: 'accent' })); if (hasHidden) { // There are hidden meters so note at bottom of list. listedMeters.push(translate('meter.hidden')); @@ -1007,7 +1007,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr }); // Sort for display. Before were sorted by id so not okay here. listedGroups.sort((groupA, groupB) => groupA.toLowerCase().localeCompare( - groupB.toLowerCase(), locale, { sensitivity : 'accent' })); + groupB.toLowerCase(), locale, { sensitivity: 'accent' })); if (hasHidden) { // There are hidden groups so note at bottom of list. listedGroups.push(translate('group.hidden')); @@ -1036,7 +1036,7 @@ export default function EditGroupModalComponent(props: EditGroupModalComponentPr }); // Sort for display. listedDeepMeters.sort((deepMeterA, deepMeterB) => deepMeterA.toLowerCase().localeCompare( - deepMeterB.toLowerCase(), locale, { sensitivity : 'accent' })); + deepMeterB.toLowerCase(), locale, { sensitivity: 'accent' })); if (hasHidden) { // There are hidden meters so note at bottom of list. // This should never happen to an admin. diff --git a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx index 89bf1dcf4..6f57185b5 100644 --- a/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx +++ b/src/client/app/components/maps/MapCalibrationInfoDisplayComponent.tsx @@ -3,9 +3,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ import * as React from 'react'; -import {GPSPoint, isValidGPSInput} from '../../utils/calibration'; -import {ChangeEvent, FormEvent} from 'react'; -import {FormattedMessage} from 'react-intl'; +import { GPSPoint, isValidGPSInput } from '../../utils/calibration'; +import { ChangeEvent, FormEvent } from 'react'; +import { FormattedMessage } from 'react-intl'; interface InfoDisplayProps { showGrid: boolean; @@ -47,15 +47,15 @@ export default class MapCalibrationInfoDisplayComponent extends React.Component<