(null);
const channelIDs = Array.from(new Set((props.ChannelInfo ?? []).map(info => info?.Channel?.ChannelID).filter(isChannelID))).sort((left, right) => left - right);
const channelKey = channelIDs.join(',');
const rows = React.useMemo(() => buildStatisticsRows(points, props.ChannelInfo, props.PlotFilter), [points, props.ChannelInfo, props.PlotFilter]);
@@ -100,6 +102,10 @@ const Statistics = React.memo((props: ITrendWidgetProps) => {
return () => handle.abort();
}, [channelKey, props.TimeFilter]);
+ const exportCsv = React.useCallback(() => {
+ downloadCsv(buildStatisticsCsvRows(sortedRows), props.Title);
+ }, [props.Title, sortedRows]);
+
if (status === 'error')
return {props.Controls};
@@ -111,17 +117,35 @@ const Statistics = React.memo((props: ITrendWidgetProps) => {
{props.Title ?? ''}
{hasMissingData ?
- setHover(true)} onMouseLeave={() => setHover(false)}>
+ setTooltip('warning')} onMouseLeave={() => setTooltip(null)}>
: null}
+ {status === 'idle' && sortedRows.length > 0 ?
+
+ : null}
{React.Children.map(props.Controls, element => {
if (!React.isValidElement(element) || (element as React.ReactElement).type !== Button) return null;
return (
-
- Some selected Statistics have no finite data for the selected Time Window.
+
+ Some selected Statistics have no finite data for the selected Time Window.
+
+
+ Export statistics as CSV.
+
+
+ Remove plot.
+
);
});
@@ -178,4 +210,10 @@ const formatValue = (value: number | null): string => value == null ? '—' : va
const sortRows = (rows: IStatisticsRow[], sortKey: StatisticsSortKey, ascending: boolean): IStatisticsRow[] =>
orderBy(rows, [row => row[sortKey] == null, sortKey], ['asc', ascending ? 'asc' : 'desc']);
+/** Builds CSV rows in the same column order and sort order as the displayed statistics table. */
+export const buildStatisticsCsvRows = (rows: IStatisticsRow[]): CsvRow[] => [
+ ['Statistic', ...numericColumns.map(column => column.Label)],
+ ...rows.map(row => [row.Statistic, ...numericColumns.map(column => formatValue(row[column.Field]))])
+];
+
export { Statistics };
diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/Statistics/StatisticsData.ts b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/Statistics/StatisticsData.ts
index 30b16ef5..976a3361 100644
--- a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/Statistics/StatisticsData.ts
+++ b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/Statistics/StatisticsData.ts
@@ -20,6 +20,7 @@
// Generated original version of source code.
//
//******************************************************************************************************
+import { sortBy } from 'lodash';
import type { IMultiCheckboxOption, TrendSearch } from '../../../../global';
export const statisticSeriesTypes = ['Minimum', 'Average', 'Maximum'] as const;
@@ -47,7 +48,7 @@ type StatisticsValues = Omit;
/** Calculates summary statistics from finite values only. */
export const calculateStatistics = (values: number[]): StatisticsValues => {
- const sortedValues = values.filter(Number.isFinite).sort((left, right) => left - right);
+ const sortedValues = sortBy(values.filter(Number.isFinite));
const count = sortedValues.length;
if (count === 0)
return emptyStatistics;
diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendCsv.ts b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendCsv.ts
new file mode 100644
index 00000000..80f20d73
--- /dev/null
+++ b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendCsv.ts
@@ -0,0 +1,53 @@
+//******************************************************************************************************
+// TrendCsv.ts - Gbtc
+//
+// Copyright © 2026, Grid Protection Alliance. All Rights Reserved.
+//
+// Licensed to the Grid Protection Alliance (GPA) under one or more contributor license agreements. See
+// the NOTICE file distributed with this work for additional information regarding copyright ownership.
+// The GPA licenses this file to you under the MIT License (MIT), the "License"; you may not use this
+// file except in compliance with the License. You may obtain a copy of the License at:
+//
+// http://opensource.org/licenses/MIT
+//
+// Unless agreed to in writing, the subject software distributed under the License is distributed on an
+// "AS-IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. Refer to the
+// License for the specific language governing permissions and limitations.
+//
+//******************************************************************************************************
+
+export type CsvValue = string | number | null | undefined;
+export type CsvRow = CsvValue[];
+
+/** Serializes rows as an Excel-compatible UTF-8 CSV document. */
+export const serializeCsv = (rows: CsvRow[]): string =>
+ rows
+ .map(row => row.map(value => escapeCsvField(value?.toString() ?? '')).join(','))
+ .join('\r\n');
+
+/** Downloads CSV rows using a filename derived from the widget title. */
+export const downloadCsv = (rows: CsvRow[], title?: string): void => {
+ const url = URL.createObjectURL(new Blob([serializeCsv(rows)], { type: 'text/csv;charset=utf-8' }));
+ const link = document.createElement('a');
+ link.href = url;
+ link.download = getCsvFileName(title);
+ document.body.appendChild(link);
+ link.click();
+ link.remove();
+ URL.revokeObjectURL(url);
+};
+
+/** Escapes a single CSV field when it contains delimiters, quotes, or line breaks. */
+const escapeCsvField = (value: string): string =>
+ /[",\r\n]/.test(value) ? `"${value.replace(/"/g, '""')}"` : value;
+
+/** Returns a Windows-safe CSV filename while preserving valid custom title characters. */
+const getCsvFileName = (title?: string): string => {
+ const invalidCharacters = '<>:"/\\|?*';
+ const safeTitle = Array.from(title ?? '').map(character =>
+ character.charCodeAt(0) < 32 || invalidCharacters.includes(character) ? '_' : character
+ ).join('').trim().replace(/[. ]+$/g, '');
+ const isReserved = /^(con|prn|aux|nul|com[1-9]|lpt[1-9])(?:\..*)?$/i.test(safeTitle);
+ const fileName = safeTitle.length === 0 ? 'trend-data' : isReserved ? `_${safeTitle}` : safeTitle;
+ return `${fileName}.csv`;
+};
diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendWidgetRegistry.ts b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendWidgetRegistry.ts
index 49e77ef7..d2cc6639 100644
--- a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendWidgetRegistry.ts
+++ b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendWidgetRegistry.ts
@@ -45,7 +45,7 @@ export interface ITrendWidgetProps {
/** Selected channels and their per-series display settings and data-availability state. */
ChannelInfo: TrendSearch.ISeriesSettings[],
/** Updates channel series settings, including whether each series contains data. */
- SetChannelInfo: (newSettings: TrendSearch.ISeriesSettings[]) => void,
+ SetChannelInfo: React.Dispatch>,
/** Controls which data series, such as minimum, average, and maximum, are plotted. */
PlotFilter: IMultiCheckboxOption[],
/** Available widget height in pixels. */
diff --git a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendWidgetWrapper.tsx b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendWidgetWrapper.tsx
index bd67dd7c..f52abb43 100644
--- a/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendWidgetWrapper.tsx
+++ b/SEBrowser/Scripts/TSX/Components/TrendData/TrendPlot/TrendWidgetWrapper.tsx
@@ -259,7 +259,8 @@ const TrendPlot = (props: IContainerProps) => {
Type: props.LineDefaults.Average.Default.Type,
Axis: 'left',
Label: 'avg',
- HasData: false
+ HasData: false,
+ Enabled: true
}
}
};
@@ -276,14 +277,16 @@ const TrendPlot = (props: IContainerProps) => {
Type: defaultsUsed.Default.Type,
Axis: 'left',
Label: label,
- HasData: false
+ HasData: false,
+ Enabled: true
};
if (props.Plot.Type === 'Histogram')
(settings.Settings as TrendSearch.IHistogramSeriesSettings)[series.TypeName] = {
...lineSettings,
ShowCumulativeProbability: true,
CumulativeProbabilityColor: color?.[series.TypeName] ?? color.Average,
- CumulativeProbabilityLabel: `${label} Cumulative Probability`
+ CumulativeProbabilityLabel: `${label} Cumulative Probability`,
+ CumulativeProbabilityEnabled: true
};
else
(settings.Settings as TrendSearch.ILineSeriesSettings)[series.TypeName] = lineSettings;
@@ -316,11 +319,13 @@ const TrendPlot = (props: IContainerProps) => {
(props.Plot.Type === 'Statistics' ? { ID: 0, ChannelID: channel.ChannelID, TypeName: key, TypeDescription: key } : undefined);
const label = constructLabel(channel, series);
lineSettings[key].Label = label;
+ lineSettings[key].Enabled ??= true;
if (props.Plot.Type === 'Histogram') {
const histogramSettings = lineSettings[key] as TrendSearch.IHistogramSettings;
histogramSettings.ShowCumulativeProbability ??= true;
histogramSettings.CumulativeProbabilityColor ??= histogramSettings.Color;
histogramSettings.CumulativeProbabilityLabel ??= `${label} Cumulative Probability`;
+ histogramSettings.CumulativeProbabilityEnabled ??= true;
}
}
);
diff --git a/SEBrowser/Scripts/TSX/global.d.ts b/SEBrowser/Scripts/TSX/global.d.ts
index 65cb029c..9a890930 100644
--- a/SEBrowser/Scripts/TSX/global.d.ts
+++ b/SEBrowser/Scripts/TSX/global.d.ts
@@ -218,7 +218,8 @@ export namespace TrendSearch {
Color: string,
Axis: 'right' | 'left',
Label: string,
- HasData: boolean
+ HasData: boolean,
+ Enabled?: boolean
}
// Signifies Settings for a particular type of graph series
@@ -230,7 +231,8 @@ export namespace TrendSearch {
interface IHistogramSettings extends ILineSettings {
ShowCumulativeProbability: boolean,
CumulativeProbabilityColor: string,
- CumulativeProbabilityLabel: string
+ CumulativeProbabilityLabel: string,
+ CumulativeProbabilityEnabled?: boolean
}
interface IHistogramSeriesSettings extends ILineSeriesSettings {
diff --git a/SEBrowser/package-lock.json b/SEBrowser/package-lock.json
index 7efe5521..8350d610 100644
--- a/SEBrowser/package-lock.json
+++ b/SEBrowser/package-lock.json
@@ -9,13 +9,13 @@
"version": "3.1.0",
"dependencies": {
"@gpa-gemstone/application-typings": "0.0.99",
- "@gpa-gemstone/common-pages": "0.0.184",
+ "@gpa-gemstone/common-pages": "0.0.187",
"@gpa-gemstone/gpa-symbols": "0.0.64",
"@gpa-gemstone/helper-functions": "0.0.61",
- "@gpa-gemstone/react-forms": "1.1.125",
- "@gpa-gemstone/react-graph": "1.0.112",
- "@gpa-gemstone/react-interactive": "1.0.193",
- "@gpa-gemstone/react-table": "1.2.117",
+ "@gpa-gemstone/react-forms": "1.1.127",
+ "@gpa-gemstone/react-graph": "1.0.115",
+ "@gpa-gemstone/react-interactive": "1.0.195",
+ "@gpa-gemstone/react-table": "1.2.119",
"@reduxjs/toolkit": "1.8.3",
"d3": "7.3.0",
"esri-leaflet": "3.0.4",
@@ -834,17 +834,17 @@
}
},
"node_modules/@gpa-gemstone/common-pages": {
- "version": "0.0.184",
- "resolved": "https://registry.npmjs.org/@gpa-gemstone/common-pages/-/common-pages-0.0.184.tgz",
- "integrity": "sha512-RXVBxjQBADZQfPrfGr43JdPHak8icscs/txKz2jdS8v27+zrGamHbnuK64AhOzK93KZtqV21NZDQsqsGT/8UOg==",
+ "version": "0.0.187",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/common-pages/-/common-pages-0.0.187.tgz",
+ "integrity": "sha512-jmnYvuFSbaJ+OQ2erVbNpS7RI0PcdTbOHHO3f2wPdWeN0LWckngbDTWZNLxsEZ2cOJBA/xHJ0pG4CDDGjRidFQ==",
"license": "MIT",
"dependencies": {
"@gpa-gemstone/application-typings": "0.0.99",
- "@gpa-gemstone/gpa-symbols": "0.0.64",
+ "@gpa-gemstone/gpa-symbols": "0.0.65",
"@gpa-gemstone/helper-functions": "0.0.61",
- "@gpa-gemstone/react-forms": "1.1.125",
- "@gpa-gemstone/react-interactive": "1.0.193",
- "@gpa-gemstone/react-table": "1.2.117",
+ "@gpa-gemstone/react-forms": "1.1.127",
+ "@gpa-gemstone/react-interactive": "1.0.195",
+ "@gpa-gemstone/react-table": "1.2.119",
"@reduxjs/toolkit": "1.8.3",
"crypto-js": "^4.2.0",
"moment": "^2.29.4",
@@ -857,6 +857,152 @@
"react-dom": "18.2.0"
}
},
+ "node_modules/@gpa-gemstone/common-pages/node_modules/@discoveryjs/json-ext": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/common-pages/node_modules/@gpa-gemstone/gpa-symbols": {
+ "version": "0.0.65",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.65.tgz",
+ "integrity": "sha512-Crr8+/fK6ned0yKqGgde9cJwgbsoH4/4Rq5KUOOSrE56iIeozbBMeiA4aMPEy/IkhYllI7FyJxS8UVq7tqeflA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/preset-typescript": "^7.14.5",
+ "babel-jest": "^29.0.0",
+ "babel-loader": "^8.2.2",
+ "jest-cli": "^29.0.0",
+ "webpack": "^5.47.0",
+ "webpack-cli": "^4.7.2",
+ "yarn": "^1.22.11"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/common-pages/node_modules/@webpack-cli/configtest": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
+ "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x",
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@gpa-gemstone/common-pages/node_modules/@webpack-cli/info": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz",
+ "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "envinfo": "^7.7.3"
+ },
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@gpa-gemstone/common-pages/node_modules/@webpack-cli/serve": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
+ "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ },
+ "peerDependenciesMeta": {
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/common-pages/node_modules/interpret": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
+ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/common-pages/node_modules/rechoir": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
+ "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
+ "license": "MIT",
+ "dependencies": {
+ "resolve": "^1.9.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/common-pages/node_modules/webpack-cli": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
+ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@discoveryjs/json-ext": "^0.5.0",
+ "@webpack-cli/configtest": "^1.2.0",
+ "@webpack-cli/info": "^1.5.0",
+ "@webpack-cli/serve": "^1.7.0",
+ "colorette": "^2.0.14",
+ "commander": "^7.0.0",
+ "cross-spawn": "^7.0.3",
+ "fastest-levenshtein": "^1.0.12",
+ "import-local": "^3.0.2",
+ "interpret": "^2.2.0",
+ "rechoir": "^0.7.0",
+ "webpack-merge": "^5.7.3"
+ },
+ "bin": {
+ "webpack-cli": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x"
+ },
+ "peerDependenciesMeta": {
+ "@webpack-cli/generators": {
+ "optional": true
+ },
+ "@webpack-cli/migrate": {
+ "optional": true
+ },
+ "webpack-bundle-analyzer": {
+ "optional": true
+ },
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/common-pages/node_modules/webpack-merge": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
+ "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==",
+ "license": "MIT",
+ "dependencies": {
+ "clone-deep": "^4.0.1",
+ "flat": "^5.0.2",
+ "wildcard": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
"node_modules/@gpa-gemstone/gpa-symbols": {
"version": "0.0.64",
"resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.64.tgz",
@@ -1017,13 +1163,13 @@
}
},
"node_modules/@gpa-gemstone/react-forms": {
- "version": "1.1.125",
- "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-forms/-/react-forms-1.1.125.tgz",
- "integrity": "sha512-bnAI5x8v++LnO7Ba+alvMponnMu+d7FoeLElmnOfZ5Scl6//grDlU24KyX0RL3fFAFJKVUFL6vHk4kfin8Au8A==",
+ "version": "1.1.127",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-forms/-/react-forms-1.1.127.tgz",
+ "integrity": "sha512-Cb0j3ZBN0LVBZVgomiYWB1rrVFeFAuZVeOrxasNv3rGsB/uWAH4pUg+o8QWvjTvhZD83IHzVS7gudZC/W3GbMw==",
"license": "MIT",
"dependencies": {
"@gpa-gemstone/application-typings": "0.0.99",
- "@gpa-gemstone/gpa-symbols": "0.0.64",
+ "@gpa-gemstone/gpa-symbols": "0.0.65",
"@gpa-gemstone/helper-functions": "0.0.61",
"lodash": "^4.17.21",
"moment": "2.29.4",
@@ -1035,63 +1181,647 @@
"react": "18.2.0"
}
},
- "node_modules/@gpa-gemstone/react-graph": {
- "version": "1.0.112",
- "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-graph/-/react-graph-1.0.112.tgz",
- "integrity": "sha512-WxlYOEE5uap6qRbk9jXi8DyAtRBIwtAMSX1ACDMjURcTGfIHRar9DHxjvKVOlyJuxlP/FUmSFLFOzw3pM+I9cA==",
+ "node_modules/@gpa-gemstone/react-forms/node_modules/@discoveryjs/json-ext": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-forms/node_modules/@gpa-gemstone/gpa-symbols": {
+ "version": "0.0.65",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.65.tgz",
+ "integrity": "sha512-Crr8+/fK6ned0yKqGgde9cJwgbsoH4/4Rq5KUOOSrE56iIeozbBMeiA4aMPEy/IkhYllI7FyJxS8UVq7tqeflA==",
"license": "MIT",
"dependencies": {
- "@gpa-gemstone/gpa-symbols": "0.0.64",
- "@gpa-gemstone/helper-functions": "0.0.61",
- "@gpa-gemstone/react-forms": "1.1.125",
- "html2canvas": "^1.4.1",
- "lodash": "^4.17.21",
- "moment": "2.29.4"
+ "@babel/preset-typescript": "^7.14.5",
+ "babel-jest": "^29.0.0",
+ "babel-loader": "^8.2.2",
+ "jest-cli": "^29.0.0",
+ "webpack": "^5.47.0",
+ "webpack-cli": "^4.7.2",
+ "yarn": "^1.22.11"
},
"peerDependencies": {
- "react": "18.2.0",
- "react-dom": "18.2.0"
+ "react": "18.2.0"
}
},
- "node_modules/@gpa-gemstone/react-interactive": {
- "version": "1.0.193",
- "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-interactive/-/react-interactive-1.0.193.tgz",
- "integrity": "sha512-qLS5BA1x6+Qp741zSImYu/pY3FLeKpI7VZ+c5Z9xHvKaCrj5m2KaSU+aixUHtvRA0kyfv5dYNF+XCkp5FoHGaA==",
+ "node_modules/@gpa-gemstone/react-forms/node_modules/@webpack-cli/configtest": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
+ "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x",
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-forms/node_modules/@webpack-cli/info": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz",
+ "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
"license": "MIT",
"dependencies": {
- "@gpa-gemstone/application-typings": "0.0.99",
- "@gpa-gemstone/gpa-symbols": "0.0.64",
- "@gpa-gemstone/helper-functions": "0.0.61",
- "@gpa-gemstone/react-forms": "1.1.125",
- "@reduxjs/toolkit": "1.8.3",
- "jquery": "^3.6.0",
- "lodash": "^4.17.21",
- "react-portal": "4.2.2",
- "react-redux": "8.0.2",
- "react-router-dom": "6.30.03",
- "styled-components": "5.3.3"
+ "envinfo": "^7.7.3"
},
"peerDependencies": {
- "leaflet": "1.9.4",
- "react": "18.2.0",
- "react-dom": "18.2.0"
+ "webpack-cli": "4.x.x"
}
},
- "node_modules/@gpa-gemstone/react-table": {
- "version": "1.2.117",
- "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-table/-/react-table-1.2.117.tgz",
- "integrity": "sha512-xsX0F1UG1tdXc1NB1LT+Xagw8IahVVmGvE5o+3W3DEj95ylFEwGfzM0rMG5bsfSO4dNuWFzbDZ64NkDpBgt+Uw==",
+ "node_modules/@gpa-gemstone/react-forms/node_modules/@webpack-cli/serve": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
+ "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ },
+ "peerDependenciesMeta": {
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/react-forms/node_modules/interpret": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
+ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-forms/node_modules/rechoir": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
+ "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
"license": "MIT",
"dependencies": {
- "@gpa-gemstone/gpa-symbols": "0.0.64",
- "@gpa-gemstone/helper-functions": "0.0.61",
- "@gpa-gemstone/react-interactive": "1.0.193",
- "lodash": "^4.17.21",
- "react-portal": "4.2.2"
+ "resolve": "^1.9.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-forms/node_modules/webpack-cli": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
+ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@discoveryjs/json-ext": "^0.5.0",
+ "@webpack-cli/configtest": "^1.2.0",
+ "@webpack-cli/info": "^1.5.0",
+ "@webpack-cli/serve": "^1.7.0",
+ "colorette": "^2.0.14",
+ "commander": "^7.0.0",
+ "cross-spawn": "^7.0.3",
+ "fastest-levenshtein": "^1.0.12",
+ "import-local": "^3.0.2",
+ "interpret": "^2.2.0",
+ "rechoir": "^0.7.0",
+ "webpack-merge": "^5.7.3"
+ },
+ "bin": {
+ "webpack-cli": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
},
"peerDependencies": {
- "react": "18.2.0",
- "react-dom": "18.2.0"
+ "webpack": "4.x.x || 5.x.x"
+ },
+ "peerDependenciesMeta": {
+ "@webpack-cli/generators": {
+ "optional": true
+ },
+ "@webpack-cli/migrate": {
+ "optional": true
+ },
+ "webpack-bundle-analyzer": {
+ "optional": true
+ },
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/react-forms/node_modules/webpack-merge": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
+ "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==",
+ "license": "MIT",
+ "dependencies": {
+ "clone-deep": "^4.0.1",
+ "flat": "^5.0.2",
+ "wildcard": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph": {
+ "version": "1.0.115",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-graph/-/react-graph-1.0.115.tgz",
+ "integrity": "sha512-7JZqqEyvk+IxKqbQ6cALV9EuD/Pe7jed1WscpdgMR37wJZXB01Z7jMktr7zYsB4WbR7HkonUSBwFBBgYMaa7xw==",
+ "license": "MIT",
+ "dependencies": {
+ "@gpa-gemstone/gpa-symbols": "0.0.65",
+ "@gpa-gemstone/helper-functions": "0.0.61",
+ "@gpa-gemstone/react-forms": "1.1.127",
+ "html2canvas": "^1.4.1",
+ "lodash": "^4.17.21",
+ "moment": "2.29.4"
+ },
+ "peerDependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph/node_modules/@discoveryjs/json-ext": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph/node_modules/@gpa-gemstone/gpa-symbols": {
+ "version": "0.0.65",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.65.tgz",
+ "integrity": "sha512-Crr8+/fK6ned0yKqGgde9cJwgbsoH4/4Rq5KUOOSrE56iIeozbBMeiA4aMPEy/IkhYllI7FyJxS8UVq7tqeflA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/preset-typescript": "^7.14.5",
+ "babel-jest": "^29.0.0",
+ "babel-loader": "^8.2.2",
+ "jest-cli": "^29.0.0",
+ "webpack": "^5.47.0",
+ "webpack-cli": "^4.7.2",
+ "yarn": "^1.22.11"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph/node_modules/@webpack-cli/configtest": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
+ "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x",
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph/node_modules/@webpack-cli/info": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz",
+ "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "envinfo": "^7.7.3"
+ },
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph/node_modules/@webpack-cli/serve": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
+ "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ },
+ "peerDependenciesMeta": {
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph/node_modules/interpret": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
+ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph/node_modules/rechoir": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
+ "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
+ "license": "MIT",
+ "dependencies": {
+ "resolve": "^1.9.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph/node_modules/webpack-cli": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
+ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@discoveryjs/json-ext": "^0.5.0",
+ "@webpack-cli/configtest": "^1.2.0",
+ "@webpack-cli/info": "^1.5.0",
+ "@webpack-cli/serve": "^1.7.0",
+ "colorette": "^2.0.14",
+ "commander": "^7.0.0",
+ "cross-spawn": "^7.0.3",
+ "fastest-levenshtein": "^1.0.12",
+ "import-local": "^3.0.2",
+ "interpret": "^2.2.0",
+ "rechoir": "^0.7.0",
+ "webpack-merge": "^5.7.3"
+ },
+ "bin": {
+ "webpack-cli": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x"
+ },
+ "peerDependenciesMeta": {
+ "@webpack-cli/generators": {
+ "optional": true
+ },
+ "@webpack-cli/migrate": {
+ "optional": true
+ },
+ "webpack-bundle-analyzer": {
+ "optional": true
+ },
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/react-graph/node_modules/webpack-merge": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
+ "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==",
+ "license": "MIT",
+ "dependencies": {
+ "clone-deep": "^4.0.1",
+ "flat": "^5.0.2",
+ "wildcard": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive": {
+ "version": "1.0.195",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-interactive/-/react-interactive-1.0.195.tgz",
+ "integrity": "sha512-QMW0EF6OjqMtXFsYZnTimQSAIWMjM5u5OXsuWOFgxQBTl0UkDezdxuDouTOONCqKcx8bwga5FCLP0LCnsjx1Aw==",
+ "license": "MIT",
+ "dependencies": {
+ "@gpa-gemstone/application-typings": "0.0.99",
+ "@gpa-gemstone/gpa-symbols": "0.0.65",
+ "@gpa-gemstone/helper-functions": "0.0.61",
+ "@gpa-gemstone/react-forms": "1.1.127",
+ "@reduxjs/toolkit": "1.8.3",
+ "jquery": "^3.6.0",
+ "lodash": "^4.17.21",
+ "react-portal": "4.2.2",
+ "react-redux": "8.0.2",
+ "react-router-dom": "6.30.03",
+ "styled-components": "5.3.3"
+ },
+ "peerDependencies": {
+ "leaflet": "1.9.4",
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive/node_modules/@discoveryjs/json-ext": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive/node_modules/@gpa-gemstone/gpa-symbols": {
+ "version": "0.0.65",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.65.tgz",
+ "integrity": "sha512-Crr8+/fK6ned0yKqGgde9cJwgbsoH4/4Rq5KUOOSrE56iIeozbBMeiA4aMPEy/IkhYllI7FyJxS8UVq7tqeflA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/preset-typescript": "^7.14.5",
+ "babel-jest": "^29.0.0",
+ "babel-loader": "^8.2.2",
+ "jest-cli": "^29.0.0",
+ "webpack": "^5.47.0",
+ "webpack-cli": "^4.7.2",
+ "yarn": "^1.22.11"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive/node_modules/@webpack-cli/configtest": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
+ "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x",
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive/node_modules/@webpack-cli/info": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz",
+ "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "envinfo": "^7.7.3"
+ },
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive/node_modules/@webpack-cli/serve": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
+ "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ },
+ "peerDependenciesMeta": {
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive/node_modules/interpret": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
+ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive/node_modules/rechoir": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
+ "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
+ "license": "MIT",
+ "dependencies": {
+ "resolve": "^1.9.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive/node_modules/webpack-cli": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
+ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@discoveryjs/json-ext": "^0.5.0",
+ "@webpack-cli/configtest": "^1.2.0",
+ "@webpack-cli/info": "^1.5.0",
+ "@webpack-cli/serve": "^1.7.0",
+ "colorette": "^2.0.14",
+ "commander": "^7.0.0",
+ "cross-spawn": "^7.0.3",
+ "fastest-levenshtein": "^1.0.12",
+ "import-local": "^3.0.2",
+ "interpret": "^2.2.0",
+ "rechoir": "^0.7.0",
+ "webpack-merge": "^5.7.3"
+ },
+ "bin": {
+ "webpack-cli": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x"
+ },
+ "peerDependenciesMeta": {
+ "@webpack-cli/generators": {
+ "optional": true
+ },
+ "@webpack-cli/migrate": {
+ "optional": true
+ },
+ "webpack-bundle-analyzer": {
+ "optional": true
+ },
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/react-interactive/node_modules/webpack-merge": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
+ "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==",
+ "license": "MIT",
+ "dependencies": {
+ "clone-deep": "^4.0.1",
+ "flat": "^5.0.2",
+ "wildcard": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table": {
+ "version": "1.2.119",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/react-table/-/react-table-1.2.119.tgz",
+ "integrity": "sha512-S2ZpVDiggcYWp2ADZEGz/wWXfXpWkMJRfCAscwGC2cI8+QnSsxNnyzlp+heEvhQFP7L086/vpL1noe3CgyGPsw==",
+ "license": "MIT",
+ "dependencies": {
+ "@gpa-gemstone/gpa-symbols": "0.0.65",
+ "@gpa-gemstone/helper-functions": "0.0.61",
+ "@gpa-gemstone/react-interactive": "1.0.195",
+ "lodash": "^4.17.21",
+ "react-portal": "4.2.2"
+ },
+ "peerDependencies": {
+ "react": "18.2.0",
+ "react-dom": "18.2.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table/node_modules/@discoveryjs/json-ext": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz",
+ "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table/node_modules/@gpa-gemstone/gpa-symbols": {
+ "version": "0.0.65",
+ "resolved": "https://registry.npmjs.org/@gpa-gemstone/gpa-symbols/-/gpa-symbols-0.0.65.tgz",
+ "integrity": "sha512-Crr8+/fK6ned0yKqGgde9cJwgbsoH4/4Rq5KUOOSrE56iIeozbBMeiA4aMPEy/IkhYllI7FyJxS8UVq7tqeflA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/preset-typescript": "^7.14.5",
+ "babel-jest": "^29.0.0",
+ "babel-loader": "^8.2.2",
+ "jest-cli": "^29.0.0",
+ "webpack": "^5.47.0",
+ "webpack-cli": "^4.7.2",
+ "yarn": "^1.22.11"
+ },
+ "peerDependencies": {
+ "react": "18.2.0"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table/node_modules/@webpack-cli/configtest": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.2.0.tgz",
+ "integrity": "sha512-4FB8Tj6xyVkyqjj1OaTqCjXYULB9FMkqQ8yGrZjRDrYh0nOE+7Lhs45WioWQQMV+ceFlE368Ukhe6xdvJM9Egg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x",
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table/node_modules/@webpack-cli/info": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.5.0.tgz",
+ "integrity": "sha512-e8tSXZpw2hPl2uMJY6fsMswaok5FdlGNRTktvFk2sD8RjH0hE2+XistawJx1vmKteh4NmGmNUrp+Tb2w+udPcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "envinfo": "^7.7.3"
+ },
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table/node_modules/@webpack-cli/serve": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.7.0.tgz",
+ "integrity": "sha512-oxnCNGj88fL+xzV+dacXs44HcDwf1ovs3AuEzvP7mqXw7fQntqIhQ1BRmynh4qEKQSSSRSWVyXRjmTbZIX9V2Q==",
+ "license": "MIT",
+ "peerDependencies": {
+ "webpack-cli": "4.x.x"
+ },
+ "peerDependenciesMeta": {
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table/node_modules/interpret": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz",
+ "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table/node_modules/rechoir": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.1.tgz",
+ "integrity": "sha512-/njmZ8s1wVeR6pjTZ+0nCnv8SpZNRMT2D1RLOJQESlYFDBvwpTA4KWJpZ+sBJ4+vhjILRcK7JIFdGCdxEAAitg==",
+ "license": "MIT",
+ "dependencies": {
+ "resolve": "^1.9.0"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table/node_modules/webpack-cli": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.10.0.tgz",
+ "integrity": "sha512-NLhDfH/h4O6UOy+0LSso42xvYypClINuMNBVVzX4vX98TmTaTUxwRbXdhucbFMd2qLaCTcLq/PdYrvi8onw90w==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@discoveryjs/json-ext": "^0.5.0",
+ "@webpack-cli/configtest": "^1.2.0",
+ "@webpack-cli/info": "^1.5.0",
+ "@webpack-cli/serve": "^1.7.0",
+ "colorette": "^2.0.14",
+ "commander": "^7.0.0",
+ "cross-spawn": "^7.0.3",
+ "fastest-levenshtein": "^1.0.12",
+ "import-local": "^3.0.2",
+ "interpret": "^2.2.0",
+ "rechoir": "^0.7.0",
+ "webpack-merge": "^5.7.3"
+ },
+ "bin": {
+ "webpack-cli": "bin/cli.js"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ },
+ "peerDependencies": {
+ "webpack": "4.x.x || 5.x.x"
+ },
+ "peerDependenciesMeta": {
+ "@webpack-cli/generators": {
+ "optional": true
+ },
+ "@webpack-cli/migrate": {
+ "optional": true
+ },
+ "webpack-bundle-analyzer": {
+ "optional": true
+ },
+ "webpack-dev-server": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@gpa-gemstone/react-table/node_modules/webpack-merge": {
+ "version": "5.10.0",
+ "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.10.0.tgz",
+ "integrity": "sha512-+4zXKdx7UnO+1jaN4l2lHVD+mFvnlZQP/6ljaJVb4SZiwIKeUnrT5l0gkT8z+n4hKpC+jpOv6O9R+gLtag7pSA==",
+ "license": "MIT",
+ "dependencies": {
+ "clone-deep": "^4.0.1",
+ "flat": "^5.0.2",
+ "wildcard": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
diff --git a/SEBrowser/package.json b/SEBrowser/package.json
index e1b32291..c5590702 100644
--- a/SEBrowser/package.json
+++ b/SEBrowser/package.json
@@ -31,13 +31,13 @@
},
"dependencies": {
"@gpa-gemstone/application-typings": "0.0.99",
- "@gpa-gemstone/common-pages": "0.0.184",
+ "@gpa-gemstone/common-pages": "0.0.187",
"@gpa-gemstone/gpa-symbols": "0.0.64",
"@gpa-gemstone/helper-functions": "0.0.61",
- "@gpa-gemstone/react-forms": "1.1.125",
- "@gpa-gemstone/react-graph": "1.0.112",
- "@gpa-gemstone/react-interactive": "1.0.193",
- "@gpa-gemstone/react-table": "1.2.117",
+ "@gpa-gemstone/react-forms": "1.1.127",
+ "@gpa-gemstone/react-graph": "1.0.115",
+ "@gpa-gemstone/react-interactive": "1.0.195",
+ "@gpa-gemstone/react-table": "1.2.119",
"@reduxjs/toolkit": "1.8.3",
"d3": "7.3.0",
"esri-leaflet": "3.0.4",
diff --git a/docs/conventions/tsx-conventions.md b/docs/conventions/tsx-conventions.md
new file mode 100644
index 00000000..12945405
--- /dev/null
+++ b/docs/conventions/tsx-conventions.md
@@ -0,0 +1,257 @@
+# React/TSX Component Conventions
+
+These conventions define the shape of a well-formed component file. Follow them
+when writing new components; check against them when reviewing or refactoring.
+The goal is that any developer can open any component and find things in the
+same place every time: constants at top, hooks grouped by kind, JSX in the
+middle, pure helpers at the bottom.
+
+When editing an existing file, match what's already there for anything not
+covered here — never reformat unrelated code just to satisfy a rule.
+
+## File anatomy
+
+Every component file follows this top-to-bottom order:
+
+```
+1. License/file header comment (if project uses one — copy an existing file's)
+2. Imports (React first, then third-party, then project imports)
+3. Props interface(s)
+5. Module-level constants and default objects
+6. The component
+7. Pure helper functions
+8. export default ComponentName;
+```
+
+### One component per file
+
+Each exported component gets its own file, named after the component
+(`AdapterStateBadge.tsx` exports `AdapterStateBadge`). Small private
+sub-components used only by this component (a `HeaderContent`, a table row)
+may live in the same file below the main component — they still get their own
+props interface.
+
+### Constants at the top
+
+Anything that doesn't depend on props or state is module-level, defined above
+the component: option lists, default records, page sizes, format strings.
+
+```ts
+const PAGE_SIZE = 25;
+const format = 'MM/DD/YYYY HH:mm:ss.SSS';
+const limitClauseOptions = [
+ { Label: "TOP", Value: "TOP" },
+ { Label: "ALL", Value: "ALL" },
+]
+```
+
+If it's inside the component but never uses props/state/hooks, move it out.
+
+### Pure functions at the bottom
+
+Any function that doesn't touch component state belongs outside the component
+body, placed **before** the `export default` line at the bottom of the file.
+Export them (named) only if another file reuses them. This keeps the component
+body focused on state and rendering, and keeps helpers from being re-created
+per render.
+
+```ts
+
+const getClassName = (status: ServiceStatus) => {
+ switch (status) {
+ case ServiceStatus.Normal: return 'badge-success';
+ ...
+ }
+}
+
+export default AdapterStateBadge;
+```
+
+## Props
+
+### Document every prop
+
+Add a JSDoc comment immediately above every property in a props interface.
+Describe what the prop provides or controls; for callbacks, describe when the
+component invokes it.
+
+```ts
+interface IProps {
+ /** The adapter record displayed by the component. */
+ Adapter: Common.IAdapter;
+ /** Called after the user saves a valid adapter change. */
+ OnSave: (adapter: Common.IAdapter) => void;
+}
+```
+
+### Every component takes an interface
+
+No inline prop type literals. Declare an interface — `IProps` for the file's
+main component, `IProps` for secondary components in the same file
+(`IHeaderContentProps`). Interface names are prefixed with `I`. Generic
+components parameterize the interface: `IProps`.
+
+### Props are PascalCase
+
+`HeaderText`, `SetIsCardOpen`, `IsOpenByDefault` — not `headerText`. Callback
+props are verbs in PascalCase (`SetRecord`, `HandleSave`, `OnClick`); boolean
+props read as assertions (`IsCardEnabled`, `Disabled`, `AllowSort`).
+
+### Accessing props
+
+Either destructure in the signature or use `props.X` — but pick one per
+component. Prefer destructuring when the component takes function(s) as props, with one
+prop per line:
+
+```ts
+const AdapterStateBadge = ({
+ AdapterAcronym,
+ AdapterType,
+ RefreshIntervalMs
+}: IProps) => {
+```
+
+## Component body ordering
+
+Inside the component, group by kind, in this order:
+
+```
+1. Context / store / ref hooks (usePageContext, useAppSelector, useRef)
+2. State (all useState together)
+3. Derived values (all useMemo together)
+4. Effects (all useEffect together)
+5. Callbacks / handlers (all useCallback / handler fns together)
+6. Early returns (null guards, loading states)
+7. return ( ...JSX... )
+```
+
+Additional rules within this structure:
+
+- **Type state explicitly**: `React.useState(false)`,
+ `React.useState('uninitiated')`.
+- **Comment each effect** with one or two lines immediately above it explaining
+ the purpose of the effect and, when useful, what causes it to run. Describe
+ the behavior specifically rather than restating that it is an effect.
+
+ ```ts
+ // Load the adapter commands whenever the selected adapter changes.
+ React.useEffect(() => {
+ ...
+ }, [SelectedAdapter]);
+ ```
+- **Effects that fetch must clean up**: abort the request handle in the
+ cleanup function.
+
+ ```ts
+ return () => {
+ if (handle?.abort != null) handle.abort();
+ }
+ ```
+
+- **Track async status** with the Application.Types.Status rather than ad-hoc booleans.
+- **Local handlers** are camelCase `handleX` (`handleSetMax`, `handleToggle`);
+ memoized callbacks passed down as props may be PascalCase to match the prop
+ they fill (`GetAdapterCommands`).
+- **Early returns** come after all hooks, before the JSX:
+ `if (SelectedRecord == null) return <>>`.
+
+## JSX formatting
+
+### Multiline props
+
+When a component invocation takes more than two props, put each prop on its
+own line with the closing `>` aligned:
+
+```tsx
+