From ee77717bdaef6f5e3b4b97a945c6bba15e14d077 Mon Sep 17 00:00:00 2001 From: eireland Date: Fri, 26 Jan 2024 11:54:42 -0800 Subject: [PATCH] Adds ability to select a weather station from CODAP map and plugin updates its selected station --- src/components/App.tsx | 10 +++++----- src/components/location-picker.tsx | 29 +++++++++++++++++++---------- src/hooks/use-codap-api.tsx | 12 +++++++++++- src/types.ts | 4 ++++ src/utils/codapHelpers.ts | 2 +- src/utils/noaaApiHelper.ts | 5 +++-- 6 files changed, 43 insertions(+), 19 deletions(-) diff --git a/src/components/App.tsx b/src/components/App.tsx index 4a2f27a..0f28af8 100755 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -7,7 +7,7 @@ import { AttributeFilter } from "./attribute-filter"; import { InfoModal } from "./info-modal"; import { useStateContext } from "../hooks/use-state"; import { adjustStationDataset, getWeatherStations } from "../utils/getWeatherStations"; -import { createStationsDataset } from "../utils/codapHelpers"; +import { addNotificationHandler, createStationsDataset } from "../utils/codapHelpers"; import InfoIcon from "../assets/images/icon-info.svg"; import { useCODAPApi } from "../hooks/use-codap-api"; import { dataTypeStore } from "../utils/noaaDataTypes"; @@ -31,7 +31,6 @@ export const App = () => { const { filterItems, createNOAAItems } = useCODAPApi(); const [statusMessage, setStatusMessage] = useState(""); const [isFetching, setIsFetching] = useState(false); - // const [listenerNotification, setListenerNotification] = useState(); const { showModal } = state; const weatherStations = getWeatherStations(); @@ -57,7 +56,7 @@ export const App = () => { }; addNotificationHandler("notify", - `dataContextChangeNotice[${StationDSName}]`, async (req) => { + `dataContextChangeNotice[${StationDSName}]`, async (req: any) => { stationSelectionHandler(req); }); // eslint-disable-next-line react-hooks/exhaustive-deps @@ -138,7 +137,7 @@ export const App = () => { const handleGetData = async () => { const { location, startDate, endDate, weatherStation, frequencies, - selectedFrequency, timezone } = state; + selectedFrequency, timezone, units } = state; const attributes = frequencies[selectedFrequency].attrs.map(attr => attr.name); const allDefined = (startDate && endDate && location && weatherStation && timezone); @@ -152,7 +151,8 @@ export const App = () => { frequency: selectedFrequency, weatherStation, attributes, - gmtOffset: timezone.gmtOffset + gmtOffset: timezone.gmtOffset, + units }); try { const tRequest = new Request(tURL); diff --git a/src/components/location-picker.tsx b/src/components/location-picker.tsx index 9a86307..c944da0 100644 --- a/src/components/location-picker.tsx +++ b/src/components/location-picker.tsx @@ -1,8 +1,7 @@ import React, { useEffect, useRef, useState } from "react"; import classnames from "classnames"; -import { createMap, selectStations } from "../utils/codapHelpers"; import { autoComplete, geoLocSearch } from "../utils/geonameSearch"; -import { kStationsCollectionName, geonamesUser, kOffsetMap, timezoneServiceURL } from "../constants"; +import { geonamesUser, kOffsetMap, timezoneServiceURL } from "../constants"; import { useStateContext } from "../hooks/use-state"; import { IPlace, IStation } from "../types"; import { convertDistanceToStandard, findNearestActiveStations } from "../utils/getWeatherStations"; @@ -31,9 +30,10 @@ export const LocationPicker = () => { const stationSelectionListElRef = useRef(null); const firstStationListedRef = useRef(null); const unitDistanceText = units === "standard" ? "mi" : "km"; - const stationDistance = weatherStationDistance && units === "standard" ? convertDistanceToStandard(weatherStationDistance) : weatherStationDistance; - - + const stationDistance = + weatherStationDistance === undefined + ? 0 : units === "standard" ? convertDistanceToStandard(weatherStationDistance) + : weatherStationDistance; useEffect(() => { function handleClickOutside(event: MouseEvent) { if (event.target) { @@ -212,6 +212,7 @@ export const LocationPicker = () => { placeNameSelected(locationPossibilities[selectedLocIdx]); setState(draft=>{ draft.location = locationPossibilities[selectedLocIdx]; + draft.zoomMap = true; }); } } @@ -220,7 +221,9 @@ export const LocationPicker = () => { const handlePlaceNameSelectionKeyDown = (e: React.KeyboardEvent, index: number) => { if (e.key === "Enter") { placeNameSelected(locationPossibilities[index-1]); - + setState(draft => { + draft.zoomMap = true; + }); } }; @@ -237,6 +240,9 @@ export const LocationPicker = () => { }); } setShowStationSelectionList(false); + setState(draft => { + draft.zoomMap = false; + }); } }; @@ -249,6 +255,9 @@ export const LocationPicker = () => { draft.weatherStationDistance = stationPossibilities[index].distance; }); setShowStationSelectionList(false); + setState(draft => { + draft.zoomMap = false; + }); } }; @@ -277,10 +286,10 @@ export const LocationPicker = () => { const handleOpenMap = () => { if (weatherStation) { - createMap(kStationsCollectionName, {width: 500, height: 350}, [weatherStation.latitude, weatherStation.longitude], 7); - selectStations([weatherStation.name]); - } else if (location) { - createMap(kStationsCollectionName, {width: 500, height: 350}, [location.latitude, location.longitude], 7); + setState((draft) => { + draft.isMapOpen = true; + draft.zoomMap = true; + }); } }; diff --git a/src/hooks/use-codap-api.tsx b/src/hooks/use-codap-api.tsx index 3cc3073..2d451a7 100644 --- a/src/hooks/use-codap-api.tsx +++ b/src/hooks/use-codap-api.tsx @@ -1,11 +1,21 @@ import { Attribute, Collection, DataContext, IDataType, IItem } from "../types"; import { IResult, codapInterface, createItems, getDataContext } from "@concord-consortium/codap-plugin-api"; -import { DSCollection1, DSCollection2, DSName } from "../constants"; +import { DSCollection1, DSCollection2, DSName, kStationsCollectionName } from "../constants"; import { useStateContext } from "./use-state"; +import { useEffect } from "react"; +import { createMap, selectStations } from "../utils/codapHelpers"; export const useCODAPApi = () => { const {state} = useStateContext(); + useEffect(() => { + if (state.weatherStation && state.isMapOpen) { + const zoom = state.zoomMap ? 7 : null; + createMap(kStationsCollectionName, {width: 500, height: 350}, [state.weatherStation.latitude, state.weatherStation.longitude], zoom); + selectStations([state.weatherStation.name]); + } + }, [state.isMapOpen, state.weatherStation, state.zoomMap]); + const getNoaaDataContextSetupObject = () => { return { name: DSName, diff --git a/src/types.ts b/src/types.ts index 59c5162..4690e1e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -119,6 +119,8 @@ export interface IState { showModal?: "info" | "data-return-warning"; timezone?: ITimeZone; didUserSelectDate: boolean; + isMapOpen: boolean; + zoomMap: boolean; } export const unitMap: UnitMap = { @@ -157,6 +159,8 @@ export const DefaultState: IState = { monthly: {attrs: dailyMonthlyAttrMap, filters: []}}, units: "standard", didUserSelectDate: false, + isMapOpen: false, + zoomMap: false, }; interface IDataTypeUnits { diff --git a/src/utils/codapHelpers.ts b/src/utils/codapHelpers.ts index e61ba27..2fde76e 100644 --- a/src/utils/codapHelpers.ts +++ b/src/utils/codapHelpers.ts @@ -20,7 +20,7 @@ const hasDataset = async (name: string) => { return result.success === true; }; -const createMap = async (name: string, dimensions: IDimensions, center: ILatLong, zoom: number) => { +const createMap = async (name: string, dimensions: IDimensions, center: ILatLong, zoom: number | null) => { let map; let componentListResult = await codapInterface.sendRequest({ diff --git a/src/utils/noaaApiHelper.ts b/src/utils/noaaApiHelper.ts index 1d5aa59..31a1cbf 100644 --- a/src/utils/noaaApiHelper.ts +++ b/src/utils/noaaApiHelper.ts @@ -84,10 +84,11 @@ interface IComposeURL { attributes: string[]; weatherStation: IWeatherStation; gmtOffset: string; + units: IUnits; } export const composeURL = (props: IComposeURL) => { - const { startDate, endDate, frequency, attributes, weatherStation, gmtOffset } = props; + const { startDate, endDate, frequency, attributes, weatherStation, gmtOffset, units } = props; const database = frequencyToReportTypeMap[frequency]; const format = "YYYY-MM-DDThh:mm:ss"; let sDate = dayjs(startDate); @@ -108,7 +109,7 @@ export const composeURL = (props: IComposeURL) => { const tDataTypeIDClause = `dataTypes=${dataTypes.join()}`; const tStartDateClause = `startDate=${startDateString}`; const tEndDateClause = `endDate=${endDateString}`; - const tUnitClause = `units=metric`; + const tUnitClause = `units=${units}`; const tFormatClause = "format=json"; let tURL = [nceiBaseURL, [tDatasetIDClause, tStationIDClause, tStartDateClause, tEndDateClause, tFormatClause, tDataTypeIDClause, tUnitClause].join(