From 4058b8c536a481c7d51c91f2492b79eb049b6ccb Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Fri, 3 May 2024 16:30:27 -0400 Subject: [PATCH 01/24] generalizing its usage better by passing the data key and state data/setter references --- src/utils/base-floating-dialog.js | 131 ++++++++++++++---------------- 1 file changed, 62 insertions(+), 69 deletions(-) diff --git a/src/utils/base-floating-dialog.js b/src/utils/base-floating-dialog.js index 8c598e82..91d79b56 100644 --- a/src/utils/base-floating-dialog.js +++ b/src/utils/base-floating-dialog.js @@ -7,98 +7,91 @@ import CssBaseline from '@mui/material/CssBaseline'; import Dialog from '@mui/material/Dialog'; import DialogActions from '@mui/material/DialogActions'; import DialogContent from '@mui/material/DialogActions'; -import DialogContentText from '@mui/material/DialogContentText'; import DialogTitle from '@mui/material/DialogTitle'; import Paper from '@mui/material/Paper'; import Slide from '@mui/material/Slide'; -import { useLayers } from '@context'; - // define the properties of this component BaseFloatingDialog.propTypes = { - title: PropTypes.string, - description: PropTypes.string, - openDialogImmediately: PropTypes.bool, - dialogObject: PropTypes.any + title: PropTypes.string, + dialogObject: PropTypes.any, + dataKey: PropTypes.any, + dataList: PropTypes.any, + setDataList: PropTypes.func }; /** - * This is a component that displays a floating dialog with the content passed + * This is a component that displays a floating dialog with the content passed. + * Note: this component * * @param title: string - * @param description: string - * @param openDialogImmediately: boolean * @param dialogObject: {JSX.Element} - * @returns {JSX.Element} + * @param dataKey: + * @param dataList: + * @param setDataList: */ -export default function BaseFloatingDialog({ title, description, openDialogImmediately, dialogObject} ) { - // define the dialog open/close session state - const [open, setOpen] = React.useState(openDialogImmediately); - - const { - selectedObservations, - setSelectedObservations - } = useLayers(); - - /** - * the close dialog handler - */ - const handleClose = () => { - // close the dialog - setOpen(false); - - // remove this item from the selected observations list - setSelectedObservations(selectedObservations.filter(item => item.station_name !== title)); - }; +export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataList, setDataList} ) { + // define the dialog open/close session state + //const [open, setOpen] = React.useState(true); - /** - * configure and render the floating dialog - */ - return ( - - - - { "Station: " + title } + /** + * the close dialog handler + */ + const handleClose = () => { + // if there was a data key defined, use it + if (dataKey !== undefined) { + // remove this item from the data list + // TODO: change location_name to an id element added to the data + setDataList(dataList.filter(item => item.location_name !== dataKey)); + } + }; - { "Location: " + description } + /** + * configure and render the floating dialog + */ + return ( + + + + { title } - { dialogObject } + { dialogObject } - Close - - - ); + Close + + + ); }; /** - * This creates a 3D dialog. - * - * @param props - * @returns {JSX.Element} - * @constructor - */ +* This creates a 3D dialog. +* +* @param props +* @returns {JSX.Element} +* @constructor +*/ function PaperComponent(props) { - return ( - - - - ); + return ( + + + + ); } /** - * This creates an animated transition for the dialog that pops up - * @type {React.ForwardRefExoticComponent & React.RefAttributes>} - */ +* This creates an animated transition for the dialog that pops up +* @type {React.ForwardRefExoticComponent & React.RefAttributes>} +*/ const Transition = React.forwardRef(function Transition(props, ref) { - return ; + return ; }); \ No newline at end of file From 6d5dfeb592777e3285426f0512163eb91a1d2737 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Fri, 3 May 2024 16:31:27 -0400 Subject: [PATCH 02/24] now calling an object to render the graph and the generalized params for the floating dialog --- src/components/map/observation-dialog.js | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/components/map/observation-dialog.js b/src/components/map/observation-dialog.js index 4d0ab9d6..b9f5b0e3 100644 --- a/src/components/map/observation-dialog.js +++ b/src/components/map/observation-dialog.js @@ -1,6 +1,8 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; import BaseFloatingDialog from "@utils/base-floating-dialog"; +import {useLayers} from "@context"; +import ObservationChart from "@utils/observation-chart"; // define the properties of this component ObservationDialog.propTypes = { @@ -8,20 +10,26 @@ ObservationDialog.propTypes = { }; export default function ObservationDialog(obs_data) { - // TODO: the url is put in here but it will eventually - // return a graph using data from this url + + // get references to the observation data/list + const { + selectedObservations, + setSelectedObservations + } = useLayers(); + + // create a graph using data from this url const graphObj = (url) => { + const args = {dataUrl: url}; + return ( - - {url} - + ); }; // create an object for the base dialog - const floaterArgs = {title: obs_data.obs.station_name, description: obs_data.obs.location_name, openDialogImmediately:true, "dialogObject": {...graphObj(obs_data.obs.csvurl)}}; + const floaterArgs = {title: obs_data.obs.location_name, dialogObject: {...graphObj(obs_data.obs.csvurl)}, dataKey: obs_data.obs.location_name, dataList: selectedObservations, setDataList: setSelectedObservations}; // render the dialog. // the key here will be used to remove the dialog from the selected observation list when the dialog is closed From 8ad109e6f4717f17430b57b4afa5f10747ec5f53 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Fri, 3 May 2024 16:55:53 -0400 Subject: [PATCH 03/24] adding an id to the feature props to remove it from the selected list in BaseFloatingDialog --- src/components/map/default-layers.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/map/default-layers.js b/src/components/map/default-layers.js index 0b3b9f0d..53d987c1 100644 --- a/src/components/map/default-layers.js +++ b/src/components/map/default-layers.js @@ -80,9 +80,13 @@ export const DefaultLayers = () => { layer.on("click", function (e) { // Do stuff here for retrieving time series data, in csv fomat, // from the feature.properties.csv_url and create a fancy plot - console.log("Observation Station '" + feature.properties.location_name + "' clicked"); + //console.log("Observation Station '" + feature.properties.location_name + "' clicked"); markClicked(map, e); + // add in a record id. + // this is used to remove the selected observation from the selectedObservations list when the dialog is closed + feature.properties.id = feature.properties.station_name; + // populate selectedObservations list with the newly selected observation point setSelectedObservations(previous => [...previous, feature.properties]); }); From e1daa6d297357c67465bb941aad50b13dce39bd5 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Fri, 3 May 2024 16:56:37 -0400 Subject: [PATCH 04/24] adjusting the params sent to BaseFloatingDialog --- src/components/map/observation-dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/map/observation-dialog.js b/src/components/map/observation-dialog.js index b9f5b0e3..eae385f5 100644 --- a/src/components/map/observation-dialog.js +++ b/src/components/map/observation-dialog.js @@ -29,7 +29,7 @@ export default function ObservationDialog(obs_data) { }; // create an object for the base dialog - const floaterArgs = {title: obs_data.obs.location_name, dialogObject: {...graphObj(obs_data.obs.csvurl)}, dataKey: obs_data.obs.location_name, dataList: selectedObservations, setDataList: setSelectedObservations}; + const floaterArgs = {title: obs_data.obs.location_name, dialogObject: {...graphObj(obs_data.obs.csvurl)}, dataKey: obs_data.obs.station_name, dataList: selectedObservations, setDataList: setSelectedObservations}; // render the dialog. // the key here will be used to remove the dialog from the selected observation list when the dialog is closed From f2a5ce9b3c63e47087beb07cdbfcc8b450f55227 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Fri, 3 May 2024 16:57:35 -0400 Subject: [PATCH 05/24] generalizing the id used for removing data list items from state --- src/utils/base-floating-dialog.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/base-floating-dialog.js b/src/utils/base-floating-dialog.js index 91d79b56..41e804b9 100644 --- a/src/utils/base-floating-dialog.js +++ b/src/utils/base-floating-dialog.js @@ -42,7 +42,7 @@ export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataL if (dataKey !== undefined) { // remove this item from the data list // TODO: change location_name to an id element added to the data - setDataList(dataList.filter(item => item.location_name !== dataKey)); + setDataList(dataList.filter(item => item.id !== dataKey)); } }; From a317555057e81e5fb53489401cf653f46b67d46c Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Fri, 3 May 2024 17:04:29 -0400 Subject: [PATCH 06/24] new component to render the observation data as a chart --- src/utils/observation-chart.js | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 src/utils/observation-chart.js diff --git a/src/utils/observation-chart.js b/src/utils/observation-chart.js new file mode 100644 index 00000000..7e2f3870 --- /dev/null +++ b/src/utils/observation-chart.js @@ -0,0 +1,86 @@ +import React, {Fragment, useState, useEffect} from 'react'; +import PropTypes from 'prop-types'; + +// define the properties of this component +ObservationChart.propTypes = { + dataUrl: PropTypes.string +}; + +/** + * converts CSV data to json format + * + * @param csvData + * @returns {string} + */ +function csvToJSON(csvData) { + // ensure that there is csv data to convert + if (csvData !== "") { + // split on carriage returns + const lines = csvData.split("\n"); + + // init the result + const ret_val = []; + + // get the first line (data header) + const dataHeader = lines[0].split(","); + + // loop through the rest of the data + for (let i = 1; i < lines.length; i++) { + // split the line on commas + const currentLine = lines[i].split(","); + + // init the converted data + const jsonObj = {}; + + // loop through the data and get name/vale pairs in json format + for (let j = 0; j < dataHeader.length; j++) { + // save the data + jsonObj[dataHeader[j]] = currentLine[j]; + } + + // add the data to the return + ret_val.push(jsonObj); + } + + // TODO: return the data as a json string (for now) + return JSON.stringify(ret_val); // Returns JSON string + } +} + +/** + * renders the observations as a chart + * + * @param dataUrl + * @returns {JSX.Element} + * @constructor + */ +export default function ObservationChart(dataUrl) { + // store the items in state + const [stationObs, setStationObs] = useState(""); + + // get the data + useEffect( () => { + const fetchData = () => { + return fetch(dataUrl.dataUrl) + .then(res => { + return res.text(); + }) + .then(data => { + setStationObs(csvToJSON(data)); + console.log(JSON.stringify(data)); + }) + .catch(err => { + console.log(err); + }); + }; + + fetchData().then(); + }, [dataUrl]); + + // render the chart. + return ( + + {stationObs} + + ); +}; \ No newline at end of file From d96611e73c35754e9a2c459da57740ce8cafbd30 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Mon, 6 May 2024 09:28:52 -0400 Subject: [PATCH 07/24] checkpointing current state of branch --- package-lock.json | 151 ++++++++++++++++++++++- package.json | 3 +- src/components/map/observation-dialog.js | 62 +++++++++- src/utils/observation-chart.js | 67 +++++++++- 4 files changed, 273 insertions(+), 10 deletions(-) diff --git a/package-lock.json b/package-lock.json index eebaf534..03572a38 100644 --- a/package-lock.json +++ b/package-lock.json @@ -25,7 +25,8 @@ "react-leaflet": "^4.2.1", "react-map-gl": "^7.1.7", "react-query": "^3.39.3", - "react-timeago": "^7.2.0" + "react-timeago": "^7.2.0", + "recharts": "^2.12.6" }, "devDependencies": { "@babel/core": "^7.24.0", @@ -4932,6 +4933,60 @@ "@types/node": "*" } }, + "node_modules/@types/d3-array": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.1.tgz", + "integrity": "sha512-Y2Jn2idRrLzUfAKV2LyRImR+y4oa2AntrgID95SHJxuMUrkNXmanDSed71sRNZysveJVt1hLLemQZIady0FpEg==" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-P2dlU/q51fkOc/Gfl3Ul9kicV7l+ra934qBFXCFhrZMOL6du1TM0pm1ThYvENukyOn5h9v+yMJ9Fn5JK4QozrQ==" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.8.tgz", + "integrity": "sha512-gkK1VVTr5iNiYJ7vWDI+yUFFlszhNMtVeneJ6lUTKPjprsvLLI9/tgEGiXJOnlINJA8FyA88gfnQsHbybVZrYQ==", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.6.tgz", + "integrity": "sha512-5KKk5aKGu2I+O6SONMYSNflgiP0WfZIQvVUMan50wHsLG1G94JlxEVnCpQARfTtzytuY0p/9PXXZb3I7giofIA==", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.3.tgz", + "integrity": "sha512-2p6olUZ4w3s+07q3Tm2dbiMZy5pCDfYwtLXXHUnVzXgQlZ/OyPtUz6OL382BkOuGlLXqfT+wqv8Fw2v8/0geBw==" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==" + }, "node_modules/@types/eslint": { "version": "8.56.10", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.56.10.tgz", @@ -8088,6 +8143,11 @@ "node": ">=0.10.0" } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==" + }, "node_modules/dedent": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/dedent/-/dedent-1.5.3.tgz", @@ -9270,8 +9330,7 @@ "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" }, "node_modules/events": { "version": "3.3.0", @@ -9404,6 +9463,14 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "node_modules/fast-equals": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.0.1.tgz", + "integrity": "sha512-WF1Wi8PwwSY7/6Kx0vKXtw8RwuSGoM1bvDaJbu7MxDlR1vovZjIAKrnzyrThgAjm6JDTu0fVgWXDlMGspodfoQ==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -13403,8 +13470,7 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -15768,6 +15834,20 @@ "node": ">=0.10.0" } }, + "node_modules/react-smooth": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.1.tgz", + "integrity": "sha512-OE4hm7XqR0jNOq3Qmk9mFLyd6p2+j6bvbPJ7qlB7+oo0eNcL2l7WQzG6MBnT3EXY6xzkLMUBec3AfewJdA0J8w==", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-timeago": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/react-timeago/-/react-timeago-7.2.0.tgz", @@ -15833,6 +15913,41 @@ "node": ">= 0.8" } }, + "node_modules/recharts": { + "version": "2.12.6", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.12.6.tgz", + "integrity": "sha512-D+7j9WI+D0NHauah3fKHuNNcRK8bOypPW7os1DERinogGBGaHI7i6tQKJ0aUF3JXyBZ63dyfKIW2WTOPJDxJ8w==", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^16.10.2", + "react-smooth": "^4.0.0", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, + "node_modules/recharts/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -17363,6 +17478,11 @@ "integrity": "sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==", "dev": true }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==" + }, "node_modules/tiny-lru": { "version": "11.2.6", "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-11.2.6.tgz", @@ -17825,6 +17945,27 @@ "node": ">= 0.8" } }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vt-pbf": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/vt-pbf/-/vt-pbf-3.1.3.tgz", diff --git a/package.json b/package.json index 5abcda2f..477e6012 100644 --- a/package.json +++ b/package.json @@ -63,7 +63,8 @@ "react-leaflet": "^4.2.1", "react-map-gl": "^7.1.7", "react-query": "^3.39.3", - "react-timeago": "^7.2.0" + "react-timeago": "^7.2.0", + "recharts": "^2.12.6" }, "overrides": { "json5": "^2.2.2", diff --git a/src/components/map/observation-dialog.js b/src/components/map/observation-dialog.js index eae385f5..6ab6e1dc 100644 --- a/src/components/map/observation-dialog.js +++ b/src/components/map/observation-dialog.js @@ -3,12 +3,58 @@ import PropTypes from 'prop-types'; import BaseFloatingDialog from "@utils/base-floating-dialog"; import {useLayers} from "@context"; import ObservationChart from "@utils/observation-chart"; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; // define the properties of this component ObservationDialog.propTypes = { obs_data: PropTypes.object }; +const data = [ + { + name: 'Page A', + uv: 4000, + pv: 2400, + amt: 2400, + }, + { + name: 'Page B', + uv: 3000, + pv: 1398, + amt: 2210, + }, + { + name: 'Page C', + uv: 2000, + pv: 9800, + amt: 2290, + }, + { + name: 'Page D', + uv: 2780, + pv: 3908, + amt: 2000, + }, + { + name: 'Page E', + uv: 1890, + pv: 4800, + amt: 2181, + }, + { + name: 'Page F', + uv: 2390, + pv: 3800, + amt: 2500, + }, + { + name: 'Page G', + uv: 3490, + pv: 4300, + amt: 2100, + }, +]; + export default function ObservationDialog(obs_data) { // get references to the observation data/list @@ -23,7 +69,21 @@ export default function ObservationDialog(obs_data) { return ( - + + + + + {/**/} ); }; diff --git a/src/utils/observation-chart.js b/src/utils/observation-chart.js index 7e2f3870..14f6fe6f 100644 --- a/src/utils/observation-chart.js +++ b/src/utils/observation-chart.js @@ -1,4 +1,6 @@ import React, {Fragment, useState, useEffect} from 'react'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; + import PropTypes from 'prop-types'; // define the properties of this component @@ -47,6 +49,51 @@ function csvToJSON(csvData) { } } +const data = [ + { + name: 'Page A', + uv: 4000, + pv: 2400, + amt: 2400, + }, + { + name: 'Page B', + uv: 3000, + pv: 1398, + amt: 2210, + }, + { + name: 'Page C', + uv: 2000, + pv: 9800, + amt: 2290, + }, + { + name: 'Page D', + uv: 2780, + pv: 3908, + amt: 2000, + }, + { + name: 'Page E', + uv: 1890, + pv: 4800, + amt: 2181, + }, + { + name: 'Page F', + uv: 2390, + pv: 3800, + amt: 2500, + }, + { + name: 'Page G', + uv: 3490, + pv: 4300, + amt: 2100, + }, +]; + /** * renders the observations as a chart * @@ -55,7 +102,7 @@ function csvToJSON(csvData) { * @constructor */ export default function ObservationChart(dataUrl) { - // store the items in state + // store the station observation data in state const [stationObs, setStationObs] = useState(""); // get the data @@ -67,7 +114,7 @@ export default function ObservationChart(dataUrl) { }) .then(data => { setStationObs(csvToJSON(data)); - console.log(JSON.stringify(data)); + // console.log(JSON.stringify(data)); }) .catch(err => { console.log(err); @@ -80,7 +127,21 @@ export default function ObservationChart(dataUrl) { // render the chart. return ( - {stationObs} + {/*{stationObs}*/} + {/**/} + {/* */} + {/* */} + {/**/} ); }; \ No newline at end of file From 6760c130164b99580d7a6c2c795da54e16c76da4 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Mon, 6 May 2024 09:49:30 -0400 Subject: [PATCH 08/24] removing TODO as it is done --- src/utils/base-floating-dialog.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/utils/base-floating-dialog.js b/src/utils/base-floating-dialog.js index 41e804b9..88afb08d 100644 --- a/src/utils/base-floating-dialog.js +++ b/src/utils/base-floating-dialog.js @@ -41,7 +41,6 @@ export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataL // if there was a data key defined, use it if (dataKey !== undefined) { // remove this item from the data list - // TODO: change location_name to an id element added to the data setDataList(dataList.filter(item => item.id !== dataKey)); } }; From 294a3b3204c05dbd67c1fc6ea84acfe57212283a Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Mon, 6 May 2024 12:15:58 -0400 Subject: [PATCH 09/24] checkpointing current state of code --- src/components/map/observation-dialog.js | 62 +---------------- src/utils/base-floating-dialog.js | 6 +- src/utils/observation-chart.js | 84 ++++++------------------ 3 files changed, 25 insertions(+), 127 deletions(-) diff --git a/src/components/map/observation-dialog.js b/src/components/map/observation-dialog.js index 6ab6e1dc..4c6d16ac 100644 --- a/src/components/map/observation-dialog.js +++ b/src/components/map/observation-dialog.js @@ -3,58 +3,12 @@ import PropTypes from 'prop-types'; import BaseFloatingDialog from "@utils/base-floating-dialog"; import {useLayers} from "@context"; import ObservationChart from "@utils/observation-chart"; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; // define the properties of this component ObservationDialog.propTypes = { obs_data: PropTypes.object }; -const data = [ - { - name: 'Page A', - uv: 4000, - pv: 2400, - amt: 2400, - }, - { - name: 'Page B', - uv: 3000, - pv: 1398, - amt: 2210, - }, - { - name: 'Page C', - uv: 2000, - pv: 9800, - amt: 2290, - }, - { - name: 'Page D', - uv: 2780, - pv: 3908, - amt: 2000, - }, - { - name: 'Page E', - uv: 1890, - pv: 4800, - amt: 2181, - }, - { - name: 'Page F', - uv: 2390, - pv: 3800, - amt: 2500, - }, - { - name: 'Page G', - uv: 3490, - pv: 4300, - amt: 2100, - }, -]; - export default function ObservationDialog(obs_data) { // get references to the observation data/list @@ -69,21 +23,7 @@ export default function ObservationDialog(obs_data) { return ( - - - - - {/**/} + ); }; diff --git a/src/utils/base-floating-dialog.js b/src/utils/base-floating-dialog.js index 88afb08d..013a2291 100644 --- a/src/utils/base-floating-dialog.js +++ b/src/utils/base-floating-dialog.js @@ -58,13 +58,13 @@ export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataL PaperComponent={PaperComponent} TransitionComponent={Transition} disableEnforceFocus - style={{ pointerEvents: 'none'}} + style={{ pointerEvents: 'none' }} PaperProps={{ style: { pointerEvents: 'auto'} }} sx={{ '.MuiBackdrop-root': { backgroundColor: 'transparent' }}} > - { title } + { title } - { dialogObject } + { dialogObject } Close diff --git a/src/utils/observation-chart.js b/src/utils/observation-chart.js index 14f6fe6f..d0741ba5 100644 --- a/src/utils/observation-chart.js +++ b/src/utils/observation-chart.js @@ -1,5 +1,5 @@ import React, {Fragment, useState, useEffect} from 'react'; -import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; +import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend } from 'recharts'; import PropTypes from 'prop-types'; @@ -45,55 +45,10 @@ function csvToJSON(csvData) { } // TODO: return the data as a json string (for now) - return JSON.stringify(ret_val); // Returns JSON string + return ret_val; } } -const data = [ - { - name: 'Page A', - uv: 4000, - pv: 2400, - amt: 2400, - }, - { - name: 'Page B', - uv: 3000, - pv: 1398, - amt: 2210, - }, - { - name: 'Page C', - uv: 2000, - pv: 9800, - amt: 2290, - }, - { - name: 'Page D', - uv: 2780, - pv: 3908, - amt: 2000, - }, - { - name: 'Page E', - uv: 1890, - pv: 4800, - amt: 2181, - }, - { - name: 'Page F', - uv: 2390, - pv: 3800, - amt: 2500, - }, - { - name: 'Page G', - uv: 3490, - pv: 4300, - amt: 2100, - }, -]; - /** * renders the observations as a chart * @@ -127,21 +82,24 @@ export default function ObservationChart(dataUrl) { // render the chart. return ( - {/*{stationObs}*/} - {/**/} - {/* */} - {/* */} - {/**/} - + + + + + + + + + ); }; \ No newline at end of file From 89fa5918c6e0fbb17de38beb9b85355a08192bac Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Mon, 6 May 2024 14:23:04 -0400 Subject: [PATCH 10/24] tidying up --- src/utils/base-floating-dialog.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/utils/base-floating-dialog.js b/src/utils/base-floating-dialog.js index 013a2291..01f0c391 100644 --- a/src/utils/base-floating-dialog.js +++ b/src/utils/base-floating-dialog.js @@ -31,9 +31,6 @@ BaseFloatingDialog.propTypes = { * @param setDataList: */ export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataList, setDataList} ) { - // define the dialog open/close session state - //const [open, setOpen] = React.useState(true); - /** * the close dialog handler */ From e561cb9c596bbe6b07c79ae02b1b451b3d7cc0ef Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Mon, 6 May 2024 14:23:34 -0400 Subject: [PATCH 11/24] tidying up --- src/utils/observation-chart.js | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/src/utils/observation-chart.js b/src/utils/observation-chart.js index d0741ba5..ec05fb39 100644 --- a/src/utils/observation-chart.js +++ b/src/utils/observation-chart.js @@ -9,10 +9,10 @@ ObservationChart.propTypes = { }; /** - * converts CSV data to json format + * converts CSV data into json format * * @param csvData - * @returns {string} + * @returns {*[]} */ function csvToJSON(csvData) { // ensure that there is csv data to convert @@ -44,7 +44,7 @@ function csvToJSON(csvData) { ret_val.push(jsonObj); } - // TODO: return the data as a json string (for now) + // return the json data representation return ret_val; } } @@ -76,23 +76,14 @@ export default function ObservationChart(dataUrl) { }); }; + // finish off the data retrieval fetchData().then(); }, [dataUrl]); // render the chart. return ( - + From ae04250464dee14324b368a43a9d6609f4390691 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Mon, 6 May 2024 14:38:53 -0400 Subject: [PATCH 12/24] definition for the remove all observations sidebar --- src/components/trays/observations/index.js | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/components/trays/observations/index.js diff --git a/src/components/trays/observations/index.js b/src/components/trays/observations/index.js new file mode 100644 index 00000000..6ce54ee1 --- /dev/null +++ b/src/components/trays/observations/index.js @@ -0,0 +1,23 @@ +import React from 'react'; +import { Stack } from '@mui/joy'; +import { ClearAll as RemoveObservationsIcon} from '@mui/icons-material'; + +// import the component that will do the observation removal from state +import { RemoveObservations } from "./removeObservations"; + +// get an icon for the tray +export const icon = ; + +// create a title for this tray element +export const title = 'Remove observations'; + +/** + * render the removal component + * + * @returns {JSX.Element} + */ +export const trayContents = () => ( + + + + ); From 109e730a4ff06507515eb2b606dd48f4405070a6 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Mon, 6 May 2024 14:40:06 -0400 Subject: [PATCH 13/24] adding a component that will be rendered on the sidebar to remove all observation selection dialogs --- .../trays/observations/removeObservations.js | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/components/trays/observations/removeObservations.js diff --git a/src/components/trays/observations/removeObservations.js b/src/components/trays/observations/removeObservations.js new file mode 100644 index 00000000..76f2aa96 --- /dev/null +++ b/src/components/trays/observations/removeObservations.js @@ -0,0 +1,31 @@ +import React, { Fragment } from 'react'; +import { Button } from '@mui/joy'; + +import {useLayers} from "@context"; + +/** + * component that handles the removal of all observation selections from the map. + * + * @returns {JSX.Element} + * @constructor + */ +export const RemoveObservations = () => { + // get references to the observation data/list + const { + selectedObservations, + setSelectedObservations + } = useLayers(); + + // remove the observation selections from state + function removeAllObservations() { + // remove all items from the data list + setSelectedObservations(selectedObservations.filter(item => item === undefined)); + } + + // render the button + return ( + + removeAllObservations()}>Remove all observation selections + + ); +}; From 8a572f593f0c5b011b5df89d72c4057d97b9f62b Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Mon, 6 May 2024 14:40:37 -0400 Subject: [PATCH 14/24] adding a component to the sibebar --- src/components/trays/index.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/trays/index.js b/src/components/trays/index.js index debfb1f8..9d73f1ca 100644 --- a/src/components/trays/index.js +++ b/src/components/trays/index.js @@ -1,11 +1,13 @@ import * as layers from './layers'; import * as hurricanes from './hurricanes'; +import * as removeObservationSelections from './observations'; import * as settings from './settings'; export default { layers, hurricanes, settings, + removeObservationSelections }; /* From fb03178891c7f0a027b449f1f048a66a1983e4e3 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Mon, 6 May 2024 14:41:41 -0400 Subject: [PATCH 15/24] tidying up, adding comments --- src/components/map/observation-dialog.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/components/map/observation-dialog.js b/src/components/map/observation-dialog.js index 4c6d16ac..648de8c2 100644 --- a/src/components/map/observation-dialog.js +++ b/src/components/map/observation-dialog.js @@ -9,8 +9,14 @@ ObservationDialog.propTypes = { obs_data: PropTypes.object }; +/** + * This component renders the observation dialog, including the chart + * + * @param obs_data + * @returns {JSX.Element} + * @constructor + */ export default function ObservationDialog(obs_data) { - // get references to the observation data/list const { selectedObservations, @@ -19,20 +25,21 @@ export default function ObservationDialog(obs_data) { // create a graph using data from this url const graphObj = (url) => { - const args = {dataUrl: url}; + // create the data object + const args = { dataUrl: url }; + // create the chart return ( - + ); }; - // create an object for the base dialog + // create an object for the base dialog arguments const floaterArgs = {title: obs_data.obs.location_name, dialogObject: {...graphObj(obs_data.obs.csvurl)}, dataKey: obs_data.obs.station_name, dataList: selectedObservations, setDataList: setSelectedObservations}; - // render the dialog. - // the key here will be used to remove the dialog from the selected observation list when the dialog is closed + // render the dialog return ( From 02b2ccfc03564a6202dee57eb405fef83e85a68d Mon Sep 17 00:00:00 2001 From: Lisa Stillwell Date: Mon, 6 May 2024 18:59:18 -0400 Subject: [PATCH 16/24] updates for addition and removal of target icon for observations selected --- .gitignore | 2 ++ src/app.js | 2 +- src/components/map/default-layers.js | 9 ++++---- src/components/map/map.js | 2 +- src/components/map/observation-dialog.js | 10 +++++---- src/utils/base-floating-dialog.js | 8 ++++--- src/utils/map-utils.js | 27 +++++++++++++++++++----- 7 files changed, 41 insertions(+), 19 deletions(-) diff --git a/.gitignore b/.gitignore index b42ec396..21d3b519 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ dist node_modules .env* +.DS_Store +src/.DS_Store diff --git a/src/app.js b/src/app.js index fb5a61dd..0d0c9c20 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,6 @@ import React, { Fragment } from 'react'; import { Map } from '@components/map'; -import ObservationDialog from "@components/map/observation-dialog"; +import { ObservationDialog } from "@components/map/observation-dialog"; import { useLayers } from '@context'; import { Sidebar } from '@components/sidebar'; diff --git a/src/components/map/default-layers.js b/src/components/map/default-layers.js index 53d987c1..99f46576 100644 --- a/src/components/map/default-layers.js +++ b/src/components/map/default-layers.js @@ -2,7 +2,7 @@ import React, { Fragment, useEffect, useState } from 'react'; import { WMSTileLayer, GeoJSON, useMap } from 'react-leaflet'; import { CircleMarker } from 'leaflet'; import { useLayers } from '@context'; -import { markClicked } from '@utils/map-utils'; +import { markClicked, markUnclicked } from '@utils/map-utils'; const newLayerDefaultState = layer => { const { product_type } = layer.properties; @@ -78,15 +78,14 @@ export const DefaultLayers = () => { this.closePopup(); }); layer.on("click", function (e) { - // Do stuff here for retrieving time series data, in csv fomat, - // from the feature.properties.csv_url and create a fancy plot - //console.log("Observation Station '" + feature.properties.location_name + "' clicked"); - markClicked(map, e); // add in a record id. // this is used to remove the selected observation from the selectedObservations list when the dialog is closed feature.properties.id = feature.properties.station_name; + // create a marker target icon around the observation clicked + markClicked(map, e, feature.properties.id); + // populate selectedObservations list with the newly selected observation point setSelectedObservations(previous => [...previous, feature.properties]); }); diff --git a/src/components/map/map.js b/src/components/map/map.js index d54bf614..7fb63715 100644 --- a/src/components/map/map.js +++ b/src/components/map/map.js @@ -17,7 +17,7 @@ export const Map = () => { zoom={5} zoomControl={false} scrollWheelZoom={true} - whenCreated={setMap} + ref={setMap} style={{ height: '100vh', width:'100wh' }}> { // get references to the observation data/list const { + map, selectedObservations, setSelectedObservations } = useLayers(); @@ -29,7 +31,7 @@ export default function ObservationDialog(obs_data) { }; // create an object for the base dialog - const floaterArgs = {title: obs_data.obs.location_name, dialogObject: {...graphObj(obs_data.obs.csvurl)}, dataKey: obs_data.obs.station_name, dataList: selectedObservations, setDataList: setSelectedObservations}; + const floaterArgs = {title: obs_data.obs.location_name, dialogObject: {...graphObj(obs_data.obs.csvurl)}, dataKey: obs_data.obs.station_name, dataList: selectedObservations, setDataList: setSelectedObservations, map: map}; // render the dialog. // the key here will be used to remove the dialog from the selected observation list when the dialog is closed diff --git a/src/utils/base-floating-dialog.js b/src/utils/base-floating-dialog.js index 013a2291..50aaf37e 100644 --- a/src/utils/base-floating-dialog.js +++ b/src/utils/base-floating-dialog.js @@ -10,6 +10,7 @@ import DialogContent from '@mui/material/DialogActions'; import DialogTitle from '@mui/material/DialogTitle'; import Paper from '@mui/material/Paper'; import Slide from '@mui/material/Slide'; +import { markUnclicked } from '@utils/map-utils'; // define the properties of this component BaseFloatingDialog.propTypes = { @@ -17,7 +18,8 @@ BaseFloatingDialog.propTypes = { dialogObject: PropTypes.any, dataKey: PropTypes.any, dataList: PropTypes.any, - setDataList: PropTypes.func + setDataList: PropTypes.func, + map: PropTypes.any, }; /** @@ -30,16 +32,16 @@ BaseFloatingDialog.propTypes = { * @param dataList: * @param setDataList: */ -export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataList, setDataList} ) { +export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataList, setDataList, map} ) { // define the dialog open/close session state //const [open, setOpen] = React.useState(true); - /** * the close dialog handler */ const handleClose = () => { // if there was a data key defined, use it if (dataKey !== undefined) { + markUnclicked(map, dataKey); // remove this item from the data list setDataList(dataList.filter(item => item.id !== dataKey)); } diff --git a/src/utils/map-utils.js b/src/utils/map-utils.js index bb46b5e6..fe91304e 100644 --- a/src/utils/map-utils.js +++ b/src/utils/map-utils.js @@ -12,16 +12,33 @@ import locationIcon from '@images/location_searching_FILL0_wght400_GRAD0_opsz24. // function to add a location marker where ever and obs mod layer // feature is clicked icon downloaded as png from here: // https://fonts.google.com/icons?selected=Material+Symbols+Outlined:location_searching:FILL@0;wght@400;GRAD@0;opsz@24&icon.query=location -export const markClicked = (map, event) => { +export const markClicked = (map, event, id) => { const L = window.L; + const iconSize = 38; + const iconAnchor = iconSize/2; const targetIcon = L.icon({ iconUrl: locationIcon, - iconSize: [38, 38], - iconAnchor: [19, 19], + iconSize: [iconSize, iconSize], + iconAnchor: [iconAnchor, iconAnchor], popupAnchor: [0, 0], }); - L.marker([event.latlng.lat, event.latlng.lng], {icon: targetIcon}).addTo(map); -}; \ No newline at end of file + const marker = L.marker([event.latlng.lat, event.latlng.lng], {icon: targetIcon}); + marker._id = id; + + marker.addTo(map); +}; + + +export const markUnclicked = (map, id) => { + + map.eachLayer((layer) => { + if (layer.options && layer.options.pane === "markerPane") { + if (layer._id === id) { + map.removeLayer(layer); + } + } + }); +} \ No newline at end of file From 88583f522117ea1c54953b0f363a1fd4c452d9cd Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Tue, 7 May 2024 08:04:15 -0400 Subject: [PATCH 17/24] moving files around, working on line chart --- src/app.js | 2 +- .../dialog}/base-floating-dialog.js | 8 +++---- .../dialog}/observation-chart.js | 23 +++++++++++-------- .../{map => dialog}/observation-dialog.js | 4 ++-- webpack.config.js | 1 + 5 files changed, 21 insertions(+), 17 deletions(-) rename src/{utils => components/dialog}/base-floating-dialog.js (87%) rename src/{utils => components/dialog}/observation-chart.js (71%) rename src/components/{map => dialog}/observation-dialog.js (91%) diff --git a/src/app.js b/src/app.js index fb5a61dd..2fd846ff 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,6 @@ import React, { Fragment } from 'react'; import { Map } from '@components/map'; -import ObservationDialog from "@components/map/observation-dialog"; +import ObservationDialog from "@components/dialog/observation-dialog"; import { useLayers } from '@context'; import { Sidebar } from '@components/sidebar'; diff --git a/src/utils/base-floating-dialog.js b/src/components/dialog/base-floating-dialog.js similarity index 87% rename from src/utils/base-floating-dialog.js rename to src/components/dialog/base-floating-dialog.js index 01f0c391..e17843d2 100644 --- a/src/utils/base-floating-dialog.js +++ b/src/components/dialog/base-floating-dialog.js @@ -56,14 +56,14 @@ export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataL TransitionComponent={Transition} disableEnforceFocus style={{ pointerEvents: 'none' }} - PaperProps={{ style: { pointerEvents: 'auto'} }} - sx={{ '.MuiBackdrop-root': { backgroundColor: 'transparent' }}} + PaperProps={{ sx: { width: 750, height: 510, pointerEvents: 'auto'} }} + sx={{ width: 750, height: 510, '.MuiBackdrop-root': { backgroundColor: 'transparent' }}} > { title } - { dialogObject } + { dialogObject } - Close + Close ); diff --git a/src/utils/observation-chart.js b/src/components/dialog/observation-chart.js similarity index 71% rename from src/utils/observation-chart.js rename to src/components/dialog/observation-chart.js index ec05fb39..313989aa 100644 --- a/src/utils/observation-chart.js +++ b/src/components/dialog/observation-chart.js @@ -83,14 +83,17 @@ export default function ObservationChart(dataUrl) { // render the chart. return ( - - - - - - - - - + + + + + + + + + + + + ); -}; \ No newline at end of file +}; diff --git a/src/components/map/observation-dialog.js b/src/components/dialog/observation-dialog.js similarity index 91% rename from src/components/map/observation-dialog.js rename to src/components/dialog/observation-dialog.js index 648de8c2..a5db69f5 100644 --- a/src/components/map/observation-dialog.js +++ b/src/components/dialog/observation-dialog.js @@ -1,8 +1,8 @@ import React, {Fragment} from 'react'; import PropTypes from 'prop-types'; -import BaseFloatingDialog from "@utils/base-floating-dialog"; +import BaseFloatingDialog from "@dialog/base-floating-dialog"; import {useLayers} from "@context"; -import ObservationChart from "@utils/observation-chart"; +import ObservationChart from "@dialog/observation-chart"; // define the properties of this component ObservationDialog.propTypes = { diff --git a/webpack.config.js b/webpack.config.js index 5cddb928..89317a17 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -102,6 +102,7 @@ module.exports = { '@hooks': path.resolve(__dirname, 'src/hooks/'), '@images': path.resolve(__dirname, 'src/images/'), '@utils': path.resolve(__dirname, 'src/utils/'), + '@dialog': path.resolve(__dirname, 'src/components/dialog') } }, From d784a2d6ff2c33fefa579653af3605c8be376eeb Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Tue, 7 May 2024 08:37:17 -0400 Subject: [PATCH 18/24] changing imports to reference a new location for dialog components --- src/app.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app.js b/src/app.js index 2fd846ff..d1a76a50 100644 --- a/src/app.js +++ b/src/app.js @@ -1,6 +1,6 @@ import React, { Fragment } from 'react'; import { Map } from '@components/map'; -import ObservationDialog from "@components/dialog/observation-dialog"; +import { ObservationDialog } from "@components/dialog/observation-dialog"; import { useLayers } from '@context'; import { Sidebar } from '@components/sidebar'; @@ -11,7 +11,7 @@ export const App = () => { } = useLayers(); return ( - + { // for each observation selected selectedObservations.map (function (obs) { @@ -19,8 +19,8 @@ export const App = () => { return ; }) } - - + + ); }; From 2b68fd1a3257d1f66d8712cc907cac312fab8958 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Tue, 7 May 2024 08:37:39 -0400 Subject: [PATCH 19/24] fixing linting issue --- src/utils/map-utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/map-utils.js b/src/utils/map-utils.js index fe91304e..6ad857d3 100644 --- a/src/utils/map-utils.js +++ b/src/utils/map-utils.js @@ -41,4 +41,4 @@ export const markUnclicked = (map, id) => { } } }); -} \ No newline at end of file +}; \ No newline at end of file From f1c3c5bdb0452bf9a1176ba04d7d83f65611b6bc Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Tue, 7 May 2024 08:38:19 -0400 Subject: [PATCH 20/24] fixing linting issue --- src/components/map/default-layers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/map/default-layers.js b/src/components/map/default-layers.js index 99f46576..5326c507 100644 --- a/src/components/map/default-layers.js +++ b/src/components/map/default-layers.js @@ -2,7 +2,7 @@ import React, { Fragment, useEffect, useState } from 'react'; import { WMSTileLayer, GeoJSON, useMap } from 'react-leaflet'; import { CircleMarker } from 'leaflet'; import { useLayers } from '@context'; -import { markClicked, markUnclicked } from '@utils/map-utils'; +import { markClicked } from '@utils/map-utils'; const newLayerDefaultState = layer => { const { product_type } = layer.properties; From ce6106727efa2e0c07de0931be4647573e8d0746 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Tue, 7 May 2024 08:39:12 -0400 Subject: [PATCH 21/24] adding new map bullseye references --- src/components/dialog/base-floating-dialog.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/components/dialog/base-floating-dialog.js b/src/components/dialog/base-floating-dialog.js index f8ccd27a..bf85c39b 100644 --- a/src/components/dialog/base-floating-dialog.js +++ b/src/components/dialog/base-floating-dialog.js @@ -10,6 +10,7 @@ import DialogContent from '@mui/material/DialogActions'; import DialogTitle from '@mui/material/DialogTitle'; import Paper from '@mui/material/Paper'; import Slide from '@mui/material/Slide'; +import { markUnclicked } from '@utils/map-utils'; // define the properties of this component BaseFloatingDialog.propTypes = { @@ -17,7 +18,8 @@ BaseFloatingDialog.propTypes = { dialogObject: PropTypes.any, dataKey: PropTypes.any, dataList: PropTypes.any, - setDataList: PropTypes.func + setDataList: PropTypes.func, + map: PropTypes.any }; /** @@ -39,7 +41,9 @@ export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataL const handleClose = () => { // if there was a data key defined, use it if (dataKey !== undefined) { + // remove the bullseye markUnclicked(map, dataKey); + // remove this item from the data list setDataList(dataList.filter(item => item.id !== dataKey)); } From 2ed09ec96ce488c5f2ed1d37c9d647f13e68d277 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Tue, 7 May 2024 08:39:37 -0400 Subject: [PATCH 22/24] correcting merge mishap --- src/components/dialog/observation-dialog.js | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/src/components/dialog/observation-dialog.js b/src/components/dialog/observation-dialog.js index e40ede00..b03b3802 100644 --- a/src/components/dialog/observation-dialog.js +++ b/src/components/dialog/observation-dialog.js @@ -1,17 +1,8 @@ import React, {Fragment} from 'react'; -import PropTypes from 'prop-types'; import BaseFloatingDialog from "@dialog/base-floating-dialog"; import {useLayers} from "@context"; import ObservationChart from "@dialog/observation-chart"; -// define the properties of this component -ObservationDialog.propTypes = { - obs_data: PropTypes.object -}; - -//export default function ObservationDialog(obs_data) { -export const ObservationDialog = (obs_data) => { - /** * This component renders the observation dialog, including the chart * @@ -19,9 +10,10 @@ export const ObservationDialog = (obs_data) => { * @returns {JSX.Element} * @constructor */ -export default function ObservationDialog(obs_data) { +export const ObservationDialog = (obs_data) => { // get references to the observation data/list const { + map, selectedObservations, setSelectedObservations } = useLayers(); From 1b195b25649147db8e8396926789778513e8cda3 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Tue, 7 May 2024 10:13:27 -0400 Subject: [PATCH 23/24] adjusting sizing --- src/components/dialog/base-floating-dialog.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/dialog/base-floating-dialog.js b/src/components/dialog/base-floating-dialog.js index 6652ce2c..6f89682c 100644 --- a/src/components/dialog/base-floating-dialog.js +++ b/src/components/dialog/base-floating-dialog.js @@ -63,8 +63,8 @@ export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataL TransitionComponent={Transition} disableEnforceFocus style={{ pointerEvents: 'none' }} - PaperProps={{ sx: { width: 1400, height: 510, pointerEvents: 'auto'} }} - sx={{ width: 1500, height: 510, '.MuiBackdrop-root': { backgroundColor: 'transparent' }}} + PaperProps={{ sx: { width: 625, height: 510, pointerEvents: 'auto'} }} + sx={{ width: 625, height: 510, '.MuiBackdrop-root': { backgroundColor: 'transparent' }}} > { title } From bd530232007a92f3d1144c989b3dfb62c25a2385 Mon Sep 17 00:00:00 2001 From: Phil Owen <19691521+PhillipsOwen@users.noreply.github.com> Date: Tue, 7 May 2024 11:25:14 -0400 Subject: [PATCH 24/24] trying to resync with main --- src/components/buttons/action-button-menu.js | 25 +++ src/components/buttons/action-button.js | 23 +++ src/components/buttons/index.js | 2 + src/components/dialog/base-floating-dialog.js | 6 +- src/components/map/map.js | 16 +- src/components/sidebar/sidebar.js | 10 +- src/components/sidebar/tray.js | 4 +- .../trays/layers/layer-card-actions.js | 46 +++++ src/components/trays/layers/layer-card.js | 157 ++++++++++++++++++ src/components/trays/settings/dark-mode.js | 49 ++++++ src/components/trays/settings/index.js | 4 +- src/context/map-context.js | 70 +++++++- src/context/settings.js | 21 ++- src/index.js | 17 +- src/theme.js | 153 +++++++++++++++++ 15 files changed, 578 insertions(+), 25 deletions(-) create mode 100644 src/components/buttons/action-button-menu.js create mode 100644 src/components/buttons/action-button.js create mode 100644 src/components/buttons/index.js create mode 100644 src/components/trays/layers/layer-card-actions.js create mode 100644 src/components/trays/layers/layer-card.js create mode 100644 src/components/trays/settings/dark-mode.js create mode 100644 src/theme.js diff --git a/src/components/buttons/action-button-menu.js b/src/components/buttons/action-button-menu.js new file mode 100644 index 00000000..a564e330 --- /dev/null +++ b/src/components/buttons/action-button-menu.js @@ -0,0 +1,25 @@ +import React from 'react'; +import { Sheet, Stack } from '@mui/joy'; +import PropTypes from 'prop-types'; + +export const ActionButtonMenu = ({ children }) => { + return ( + + { children } + + ); +}; + +ActionButtonMenu.propTypes = { + children: PropTypes.node.isRequired, +}; diff --git a/src/components/buttons/action-button.js b/src/components/buttons/action-button.js new file mode 100644 index 00000000..cdb4e6db --- /dev/null +++ b/src/components/buttons/action-button.js @@ -0,0 +1,23 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { IconButton } from '@mui/joy'; + +export const ActionButton = ({ + children, + ...props +}) => { + return ( + + { children } + + ); +}; + +ActionButton.propTypes = { + children: PropTypes.node, +}; diff --git a/src/components/buttons/index.js b/src/components/buttons/index.js new file mode 100644 index 00000000..e7c9bed6 --- /dev/null +++ b/src/components/buttons/index.js @@ -0,0 +1,2 @@ +export * from './action-button'; +export * from './action-button-menu'; diff --git a/src/components/dialog/base-floating-dialog.js b/src/components/dialog/base-floating-dialog.js index bf85c39b..6f89682c 100644 --- a/src/components/dialog/base-floating-dialog.js +++ b/src/components/dialog/base-floating-dialog.js @@ -63,12 +63,12 @@ export default function BaseFloatingDialog({ title, dialogObject, dataKey, dataL TransitionComponent={Transition} disableEnforceFocus style={{ pointerEvents: 'none' }} - PaperProps={{ sx: { width: 750, height: 510, pointerEvents: 'auto'} }} - sx={{ width: 750, height: 510, '.MuiBackdrop-root': { backgroundColor: 'transparent' }}} + PaperProps={{ sx: { width: 625, height: 510, pointerEvents: 'auto'} }} + sx={{ width: 625, height: 510, '.MuiBackdrop-root': { backgroundColor: 'transparent' }}} > { title } - { dialogObject } + { dialogObject } Close diff --git a/src/components/map/map.js b/src/components/map/map.js index 7fb63715..8a39e91d 100644 --- a/src/components/map/map.js +++ b/src/components/map/map.js @@ -1,12 +1,16 @@ import React from 'react'; import { MapContainer, TileLayer } from 'react-leaflet'; import { DefaultLayers } from './default-layers'; -import { useLayers } from '@context'; +import { + useLayers, + useSettings, +} from '@context'; import 'leaflet/dist/leaflet.css'; const DEFAULT_CENTER = [30.0, -73.0]; export const Map = () => { + const { darkMode } = useSettings(); const { setMap } = useLayers(); @@ -17,12 +21,12 @@ export const Map = () => { zoom={5} zoomControl={false} scrollWheelZoom={true} - ref={setMap} + whenCreated={setMap} style={{ height: '100vh', width:'100wh' }}> - + { darkMode.enabled + ? + : + } ); diff --git a/src/components/sidebar/sidebar.js b/src/components/sidebar/sidebar.js index 20ecc19e..ebd34f1f 100644 --- a/src/components/sidebar/sidebar.js +++ b/src/components/sidebar/sidebar.js @@ -3,13 +3,15 @@ import { Fragment, useCallback, useState } from 'react'; import { List, Sheet, + useTheme, } from '@mui/joy'; import { Tray } from './tray'; import { MenuItem } from './menu-item'; import SidebarTrays from '../trays'; export const Sidebar = () => { - const [activeIndex, setActiveIndex] = useState(-1); + const theme = useTheme(); + const [activeIndex, setActiveIndex] = useState(0); const handleClickMenuItem = useCallback(newIndex => { // if the incoming new index equals the old index, @@ -34,13 +36,15 @@ export const Sidebar = () => { maxWidth: '68px', overflow: 'hidden', p: 0, - backgroundColor: activeIndex === -1 ? '#f0f4f899' : '#f0f4f8', + backgroundColor: activeIndex === -1 + ? `rgba(${ theme.palette.mainChannel } / 0.2)` + : `rgba(${ theme.palette.mainChannel } / 1.0)`, // a drop shadow looks nice. we'll remove it if a tray is open, // as they should appear on the same plane. filter: activeIndex === -1 ? 'drop-shadow(0 0 8px rgba(0, 0, 0, 0.2))' : 'drop-shadow(1px 0 0 rgba(0, 0, 0, 0.2))', // similarly, here. '&:hover': { - backgroundColor: '#f0f4f8', + backgroundColor: `rgba(${ theme.palette.mainChannel } / 1.0)`, transition: 'max-width 250ms, filter 250ms, background-color 150ms', }, // we'll add a delay to this exit animation to give ample time diff --git a/src/components/sidebar/tray.js b/src/components/sidebar/tray.js index 84916848..1b9a7467 100644 --- a/src/components/sidebar/tray.js +++ b/src/components/sidebar/tray.js @@ -12,6 +12,8 @@ import { KeyboardDoubleArrowLeft as CloseTrayIcon, } from '@mui/icons-material'; +const TRAY_WIDTH = '500px'; + export const Tray = ({ active, Contents, title, closeHandler }) => { return ( { transform: active ? 'translateX(0)' : 'translateX(-120%)', transition: 'transform 250ms', height: '100vh', - width: '450px', + width: TRAY_WIDTH, zIndex: 998, filter: 'drop-shadow(0 0 8px rgba(0, 0, 0, 0.2))', overflowX: 'hidden', diff --git a/src/components/trays/layers/layer-card-actions.js b/src/components/trays/layers/layer-card-actions.js new file mode 100644 index 00000000..f2aab56f --- /dev/null +++ b/src/components/trays/layers/layer-card-actions.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Divider } from '@mui/joy'; +import { + DeleteForever as RemoveIcon, + Opacity as OpacityIcon, + Palette as ColorRampIcon, + DataObject as RawDataIcon, +} from '@mui/icons-material'; +import { useLayers } from '@context'; +import { ActionButton } from '@components/buttons'; +import { ActionButtonMenu } from '@components/buttons'; + +export const LayerActions = ({ layerId = 0 }) => { + const { removeLayer } = useLayers(); + + return ( + + + + + + + + + + + + + + + + removeLayer(layerId) } + > + + + + ); +}; + +LayerActions.propTypes = { + layerId: PropTypes.string.isRequired, +}; diff --git a/src/components/trays/layers/layer-card.js b/src/components/trays/layers/layer-card.js new file mode 100644 index 00000000..0247da06 --- /dev/null +++ b/src/components/trays/layers/layer-card.js @@ -0,0 +1,157 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { + Accordion, + AccordionDetails, + Avatar, + Box, + ButtonGroup, + Stack, + Switch, + Typography, +} from '@mui/joy'; +import { + KeyboardArrowDown as ExpandIcon, + ArrowDropUp as MoveUpArrow, + ArrowDropDown as MoveDownArrow, + Schedule as ClockIcon, +} from '@mui/icons-material'; +import { useLayers } from '@context'; +import { useToggleState } from '@hooks'; +import { LayerActions } from './layer-card-actions'; +import { ActionButton } from '@components/buttons'; + +export const LayerCard = ({ index, layer }) => { + const { + layerTypes, + swapLayers, + toggleLayerVisibility, + } = useLayers(); + const expanded = useToggleState(false); + const isVisible = layer.state.visible; + const LayerIcon = layerTypes[layer.properties.product_type].icon; + + return ( + + {/* + the usual AccordionSummary component results in a button, + but we want some buttons _inside_ the accordion summary, + so we'll build a custom component here. + */} + + + + + + + + {layerTypes[layer.properties.product_type].name} + + toggleLayerVisibility(layer.id) } + className="action-button" + /> + + + + + { new Date(layer.properties.run_date).toLocaleString() } + + + Cycle { layer.properties.cycle } + + + + + + swapLayers(index, index - 1) } + > + swapLayers(index, index + 1) } + > + + + + + + + + + + { JSON.stringify(layer.properties, null, 2) } + + + + + ); +}; + +LayerCard.propTypes = { + index: PropTypes.number.isRequired, + layer: PropTypes.object.isRequired, +}; + diff --git a/src/components/trays/settings/dark-mode.js b/src/components/trays/settings/dark-mode.js new file mode 100644 index 00000000..f611cb42 --- /dev/null +++ b/src/components/trays/settings/dark-mode.js @@ -0,0 +1,49 @@ +import React from 'react'; +import { IconButton, Stack, Typography } from '@mui/joy'; +import { + DarkMode as OnIcon, + LightMode as OffIcon, +} from '@mui/icons-material'; +import { useSettings } from '@context'; + +export const DarkModeToggle = () => { + const { darkMode } = useSettings(); + + return ( + + + + + Dark Mode + + + { darkMode.enabled ? 'Enabled' : 'Disabled' } + + + + ); +}; + +export const Toggler = () => { + const { darkMode } = useSettings(); + + return ( + + { + darkMode.enabled + ? + : + } + + ); +}; diff --git a/src/components/trays/settings/index.js b/src/components/trays/settings/index.js index 464bf421..1cb45672 100644 --- a/src/components/trays/settings/index.js +++ b/src/components/trays/settings/index.js @@ -2,7 +2,7 @@ import React from 'react'; import { Stack } from '@mui/joy'; import { Tune as SettingsIcon } from '@mui/icons-material'; -import { SampleToggle } from './sample'; +import { DarkModeToggle } from './dark-mode'; export const icon = ; @@ -10,6 +10,6 @@ export const title = 'Settings'; export const trayContents = () => ( - + ); diff --git a/src/context/map-context.js b/src/context/map-context.js index c2632def..46b4cd21 100644 --- a/src/context/map-context.js +++ b/src/context/map-context.js @@ -1,9 +1,45 @@ import React, { createContext, useContext, useState } from "react"; import PropTypes from "prop-types"; +import { + Tsunami as WaveHeightIcon, + QueryStats as ObservationIcon, + Air as WindVelocityIcon, + Water as WaterLevelIcon, + BlurOn as WaterSurfaceIcon, +} from '@mui/icons-material'; + export const LayersContext = createContext({}); export const useLayers = () => useContext(LayersContext); +// convert the product type to a readable layer name +const layerTypes = { + obs: { + name: "Observations", + icon: ObservationIcon, + }, + maxwvel63: { + name: "Maximum Wind Velocity", + icon: WindVelocityIcon, + }, + maxele63: { + name: "Maximum Water Level", + icon: WaterLevelIcon, + }, + swan_HS_max63: { + name: "Maximum Wave Height", + icon: WaveHeightIcon, + }, + maxele_level_downscaled_epsg4326: { + name: "Hi-Res Maximum Water Level", + icon: WaterLevelIcon, + }, + hec_ras_water_surface: { + name: "HEC/RAS Water Surface", + icon: WaterSurfaceIcon, + }, +}; + export const LayersProvider = ({ children }) => { const [defaultModelLayers, setDefaultModelLayers] = useState([]); const [filteredModelLayers, setFilteredModelLayers] = useState([]); @@ -17,7 +53,7 @@ export const LayersProvider = ({ children }) => { const newLayers = [...defaultModelLayers]; const index = newLayers.findIndex(l => l.id === id); if (index === -1) { - new Error('Could not locate layer', id); + console.error('Could not locate layer', id); return; } const alteredLayer = newLayers[index]; @@ -29,6 +65,33 @@ export const LayersProvider = ({ children }) => { ]); }; + const swapLayers = (i, j) => { + // ensure our pair has i < j + const [a, b] = [i, j].sort(); + // bail out for select (a, b) pairs. + if ( + a === b || a < 0 || b < 1 + || defaultModelLayers.length - 2 < a + || defaultModelLayers.length - 1 < b + ) { return; } + + const newLayers = [...defaultModelLayers]; + const temp = newLayers[i]; + newLayers[i] = newLayers[j]; + newLayers[j] = temp; + setDefaultModelLayers(newLayers); + }; + + const removeLayer = id => { + const index = defaultModelLayers.findIndex(l => l.id === id); + if (index === -1) { + return; + } + const newLayers = defaultModelLayers.filter(l => l.id !== id); + setDefaultModelLayers(newLayers); + }; + + return ( { setFilteredModelLayers, toggleLayerVisibility, selectedObservations, - setSelectedObservations + setSelectedObservations, + swapLayers, + removeLayer, + layerTypes, }} > {children} diff --git a/src/context/settings.js b/src/context/settings.js index a3bdd0a2..d4018f85 100644 --- a/src/context/settings.js +++ b/src/context/settings.js @@ -1,5 +1,11 @@ -import React, { createContext, useContext } from "react"; +import React, { + createContext, + useCallback, + useContext, + useMemo, +} from "react"; import PropTypes from "prop-types"; +import { useColorScheme } from '@mui/joy/styles'; import { // useToggleLocalStorage, useToggleState, @@ -9,13 +15,24 @@ export const SettingsContext = createContext({}); export const useSettings = () => useContext(SettingsContext); export const SettingsProvider = ({ children }) => { + const { mode, setMode } = useColorScheme(); const booleanValue = useToggleState(); // to persist the value in the device's local // storage, use `useToggleLocalStorage` instead: // const booleanValue = useToggleLocalStorage('boolean-value') + const darkMode = useMemo(() => mode === 'dark', [mode]); + const toggleDarkMode = useCallback(() => { + setMode(darkMode ? 'light' : 'dark'); + }, [mode]); return ( - + { children } ); diff --git a/src/index.js b/src/index.js index d5ea0a3f..42df9d84 100644 --- a/src/index.js +++ b/src/index.js @@ -1,18 +1,23 @@ import React from 'react'; -import { App } from './app'; +import { CssVarsProvider } from '@mui/joy/styles'; import { createRoot } from 'react-dom/client'; +import { App } from './app'; import { LayersProvider, SettingsProvider } from '@context'; import './index.css'; +import '@fontsource/inter'; +import theme from './theme'; const container = document.getElementById('root'); const root = createRoot(container); const ProvisionedApp = () => ( - - - - - + + + + + + + ); root.render(); diff --git a/src/theme.js b/src/theme.js new file mode 100644 index 00000000..ee635cf8 --- /dev/null +++ b/src/theme.js @@ -0,0 +1,153 @@ +import { extendTheme } from '@mui/joy/styles'; + +const theme = extendTheme({ + breakpoints: { + keys: ['xs', 'sm', 'md', 'lg', 'xl'], + values: { xs: 0, sm: 600, md: 900, lg: 1200, xl: 1536 }, + unit: 'px', + }, + defaultMode: 'system', + direction: 'ltr', + palette: { + mode: 'light', + primary: { + 50: '#f0f7ff', + 100: '#c2e0ff', + 200: '#99ccf3', + 300: '#66b2ff', + 400: '#3399ff', + 500: '#007fff', + 600: '#0072e5', + 700: '#0059b2', + 800: '#004c99', + 900: '#003a75', + main: '#007fff', + light: '#66b2ff', + dark: '#0059b2', + contrastText: '#fff' + }, + secondary: { + 50: '#f3f6f9', + 100: '#e5eaf2', + 200: '#dae2ed', + 300: '#c7d0dd', + 400: '#b0b8c4', + 500: '#9da8b7', + 600: '#6b7a90', + 700: '#434d5b', + 800: '#303740', + 900: '#1c2025', + main: '#dae0e7', + contrastText: '#2f3a46', + light: 'rgb(225, 230, 235)', + dark: 'rgb(152, 156, 161)' + }, + divider: '#e5eaf2', + primaryDark: { + 50: '#eaedf1', + 100: '#dae0e7', + 200: '#acbac8', + 300: '#7b91a7', + 400: '#4b5e71', + 500: '#3b4a59', + 600: '#2f3a46', + 700: '#1f262e', + 800: '#141a1f', + 900: '#101418', + main: '#7b91a7' + }, + common: { + black: '#0b0d0e', + white: '#fff' + }, + text: { + primary: '#1c2025', + secondary: '#434d5b', + tertiary: '#6b7a90', + disabled: 'rgba(0, 0, 0, 0.38)' + }, + grey: { + 50: '#f3f6f9', + 100: '#e5eaf2', + 200: '#dae2ed', + 300: '#c7d0dd', + 400: '#b0b8c4', + 500: '#9da8b7', + 600: '#6b7a90', + 700: '#434d5b', + 800: '#303740', + 900: '#1c2025', + main: '#e5eaf2', + contrastText: '#6b7a90', + A100: '#f5f5f5', + A200: '#eeeeee', + A400: '#bdbdbd', + A700: '#616161' + }, + error: { + 50: '#fff0f1', + 100: '#ffdbde', + 200: '#ffbdc2', + 300: '#ff99a2', + 400: '#ff7a86', + 500: '#ff505f', + 600: '#eb0014', + 700: '#c70011', + 800: '#94000d', + 900: '#570007', + main: '#eb0014', + light: '#ff99a2', + dark: '#c70011', + contrastText: '#fff' + }, + success: { + 50: '#e9fbf0', + 100: '#c6f6d9', + 200: '#9aefbc', + 300: '#6ae79c', + 400: '#3ee07f', + 500: '#21cc66', + 600: '#1db45a', + 700: '#1aa251', + 800: '#178d46', + 900: '#0f5c2e', + main: '#1aa251', + light: '#6ae79c', + dark: '#1aa251', + contrastText: '#fff' + }, + warning: { + 50: '#fff9eb', + 100: '#fff3c1', + 200: '#ffeca1', + 300: '#ffdc48', + 400: '#f4c000', + 500: '#dea500', + 600: '#d18e00', + 700: '#ab6800', + 800: '#8c5800', + 900: '#5a3600', + main: '#dea500', + light: '#ffdc48', + dark: '#ab6800', + contrastText: 'rgba(0, 0, 0, 0.87)' + }, + info: { + main: '#0288d1', + light: '#03a9f4', + dark: '#01579b', + contrastText: '#fff' + }, + contrastThreshold: 3, + tonalOffset: 0.2, + background: { + paper: '#fff', + default: '#fff' + }, + }, + shape: { + borderRadius: 12 + }, +}); + +export default theme;