From 899459cc02f63167f81c8491e7e115d88b7dc76c Mon Sep 17 00:00:00 2001 From: Hanbyul Jo Date: Fri, 31 May 2024 16:03:12 -0500 Subject: [PATCH] Simplify BlockMap, Use new datatype --- .../components/common/blocks/block-map.tsx | 207 +++++++----------- .../exploration/components/map/layer.tsx | 32 +-- .../components/exploration/data-utils.ts | 16 +- .../hooks/use-stac-metadata-datasets.ts | 42 ++-- .../components/exploration/types.d.ts.ts | 26 ++- parcel-resolver-veda/index.d.ts | 2 + 6 files changed, 150 insertions(+), 175 deletions(-) diff --git a/app/scripts/components/common/blocks/block-map.tsx b/app/scripts/components/common/blocks/block-map.tsx index 68752d0a6..3047dc8fd 100644 --- a/app/scripts/components/common/blocks/block-map.tsx +++ b/app/scripts/components/common/blocks/block-map.tsx @@ -1,11 +1,9 @@ import React, { useMemo, useState, useEffect } from 'react'; import styled from 'styled-components'; -import { DatasetDatumFnResolverBag, ProjectionOptions, datasets } from 'veda'; +import { ProjectionOptions } from 'veda'; import { MapboxOptions } from 'mapbox-gl'; -import * as dateFns from 'date-fns'; +import { DatasetLayerCompareInternal } from 'veda'; import { - convertProjectionToMapbox, - projectionDefault, validateProjectionBlockProps } from '../map/controls/map-options/projections'; import { Basemap } from '../map/style-generators/basemap'; @@ -14,14 +12,14 @@ import MapCoordsControl from '../map/controls/coords'; import MapMessage from '../map/map-message'; import { formatCompareDate, - formatSingleDate, - resolveConfigFunctions + formatSingleDate } from '../map/utils'; import { BasemapId, DEFAULT_MAP_STYLE_URL } from '../map/controls/map-options/basemap'; import { utcString2userTzDate } from '$utils/date'; +import { S_SUCCEEDED } from '$utils/status'; import Map, { Compare, MapControls } from '$components/common/map'; import { validateRangeNum } from '$utils/utils'; import { HintedError } from '$utils/hinted-error'; @@ -30,13 +28,12 @@ import { ScaleControl } from '$components/common/map/controls'; import { Layer } from '$components/exploration/components/map/layer'; -import { S_SUCCEEDED } from '$utils/status'; import { - TimelineDataset, - TimelineDatasetSuccess + VizDataset, + VizDatasetSUCCESS } from '$components/exploration/types.d.ts'; -import { reconcileDatasets } from '$components/exploration/data-utils'; +import { reconcileVizDataset } from '$components/exploration/data-utils'; import { datasetLayers } from '$components/exploration/data-utils'; import { useReconcileWithStacMetadata } from '$components/exploration/hooks/use-stac-metadata-datasets'; @@ -130,16 +127,12 @@ function MapBlock(props: MapBlockProps) { const generatedId = useMemo(() => `map-${++mapInstanceId}`, []); const { - datasetId, layerId, dateTime, compareDateTime, compareLabel, center, zoom, - projectionId, - projectionCenter, - projectionParallels, basemapId } = props; @@ -148,98 +141,62 @@ function MapBlock(props: MapBlockProps) { throw new HintedError('Malformed Map Block', errors); } - const [baseLayers, setBaseLayers] = useState(); - const [compareLayers, setCompareLayers] = useState< - TimelineDataset[] | undefined - >(); - - const [baseMapStaticData] = reconcileDatasets([layerId], datasetLayers, []); - const baseMapStaticCompareData = baseMapStaticData.data.compare; - - let compareLayerId: undefined | string; - if (baseMapStaticCompareData && 'layerId' in baseMapStaticCompareData) { - compareLayerId = baseMapStaticCompareData.layerId; - } - - const [compareMapStaticData] = reconcileDatasets( - compareLayerId ? [compareLayerId] : [], - datasetLayers, - [] - ); - - useReconcileWithStacMetadata([baseMapStaticData], setBaseLayers); - useReconcileWithStacMetadata([compareMapStaticData], setCompareLayers); + const renderCompare = !!compareDateTime; + const [ layers, setLayers ] = useState(); const selectedDatetime = dateTime ? utcString2userTzDate(dateTime) : undefined; + const selectedCompareDatetime = compareDateTime ? utcString2userTzDate(compareDateTime) : undefined; - const projectionStart = useMemo(() => { - if (projectionId) { - // Ensure that the default center and parallels are used if none are - // provided. - const projection = convertProjectionToMapbox({ - id: projectionId, - center: projectionCenter, - parallels: projectionParallels - }); - return { - ...projection, - id: projectionId - }; - } else { - return projectionDefault; - } - }, [projectionId, projectionCenter, projectionParallels]); - - const [, setProjection] = useState(projectionStart); - - const dataset = datasetId ? datasets[datasetId] : null; - - const resolverBag = useMemo( - () => ({ - datetime: selectedDatetime, - compareDatetime: selectedCompareDatetime, - dateFns - }), - [selectedDatetime, selectedCompareDatetime] - ); - - const [baseLayerResolvedData] = useMemo(() => { - if (!baseLayers || baseLayers.length !== 1) return [null, null]; - const baseLayer = baseLayers[0]; - - if (baseLayer.status !== S_SUCCEEDED) return [null, null]; + const baseLayerMDXData = datasetLayers.find(e => e.id === layerId); + + // @ REVIEW - Is there any risk not using useMemo for this line? + // const baseLayerMDXWConfig = resolveConfigFunctions(baseLayerMDXData, resolverBag); + + const baseMapStaticData = reconcileVizDataset(baseLayerMDXData); + let dataToReconcileWithStac = [baseMapStaticData]; + + const baseMapStaticCompareData = baseMapStaticData.data.compare as DatasetLayerCompareInternal | null; + + // compareData can be null + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (baseMapStaticCompareData?.layerId) { + const compareLayerId = baseMapStaticCompareData.layerId; + const compareLayerMDXData = datasetLayers.find(e => e.id === compareLayerId); + const compareMapStaticData = reconcileVizDataset(compareLayerMDXData); + dataToReconcileWithStac = [...dataToReconcileWithStac, compareMapStaticData]; + } else { + if (renderCompare) throw Error('There is no compare layer defined.'); + } - const bag = { ...resolverBag, raw: baseLayer.data }; - const data = resolveConfigFunctions(baseLayer.data, bag); - return [data]; - }, [baseLayers, resolverBag]); + useReconcileWithStacMetadata(dataToReconcileWithStac as VizDataset[], setLayers); - const baseDataLayer: TimelineDataset | null = { - data: baseLayerResolvedData - } as unknown as TimelineDataset; - const baseTimeDensity = baseLayerResolvedData?.timeDensity; + const baseLayer = layers?.[0]; + const baseTimeDensity = baseLayer?.data.timeDensity; + + const compareLayer = layers?.[1]; + const compareTimeDensity = compareLayer?.data.timeDensity; // Resolve data needed for the compare layer once it is loaded. - const [compareLayerResolvedData] = useMemo(() => { - if (!compareLayers || compareLayers.length !== 1) return [null, null]; - const compareLayer = compareLayers[0]; - - if (compareLayer.status !== S_SUCCEEDED) return [null, null]; - // Include access to raw data. - const bag = { ...resolverBag, raw: compareLayer.data }; - const data = resolveConfigFunctions(compareLayer.data, bag); - return [data]; - }, [compareLayers, resolverBag]); - - const compareDataLayer: TimelineDataset | null = { - data: compareLayerResolvedData - } as unknown as TimelineDataset; - const compareTimeDensity = compareLayerResolvedData?.timeDensity; + // const [compareLayerResolvedData] = useMemo(() => { + // if (!compareLayers || compareLayers.length !== 1) return [null, null]; + // const compareLayer = compareLayers[0]; + + // if (compareLayer.status !== S_SUCCEEDED) return [null, null]; + // // Include access to raw data. + // const bag = { ...resolverBag, raw: compareLayer.data }; + // const data = resolveConfigFunctions(compareLayer.data, bag); + // return [data]; + // }, [compareLayers, resolverBag]); + + // const compareDataLayer: TimelineDataset | null = { + // data: compareLayerResolvedData + // } as unknown as TimelineDataset; + // const compareTimeDensity = compareLayerResolvedData?.time_density; const mapOptions: Partial = { style: DEFAULT_MAP_STYLE_URL, @@ -263,15 +220,10 @@ function MapBlock(props: MapBlockProps) { return opts; }; - useEffect(() => { - setProjection(projectionStart); - }, [projectionStart]); - const [mapBasemapId, setMapBasemapId] = useState(basemapId); useEffect(() => { if (!basemapId) return; - setMapBasemapId(basemapId); }, [basemapId]); @@ -284,8 +236,8 @@ function MapBlock(props: MapBlockProps) { const computedCompareLabel = useMemo(() => { // Use a provided label if it exist. - if (compareLabel && compareLayerResolvedData) { - const providedLabel = compareLayerResolvedData?.mapLabel as string; + if (compareLabel && compareLayer) { + const providedLabel = compareLayer.data.mapLabel as string; return providedLabel; } @@ -300,7 +252,7 @@ function MapBlock(props: MapBlockProps) { : null; }, [ compareLabel, - compareLayerResolvedData, + compareLayer, selectedDatetime, compareToDate, baseTimeDensity, @@ -321,33 +273,33 @@ function MapBlock(props: MapBlockProps) { }} > - {dataset && selectedDatetime && layerId && baseLayerResolvedData && ( + {selectedDatetime && (baseLayer?.status === S_SUCCEEDED) && ( )} - {baseLayerResolvedData?.legend && ( + {baseLayer?.data.legend && ( // Map overlay element // Layer legend for the active layer. // @NOTE: LayerLegendContainer is in old mapbox directory, may want to move this over to /map directory once old directory is deprecated - {compareLayerResolvedData?.legend && + {compareLayer?.data.legend && !!selectedCompareDatetime && - baseLayerResolvedData.id !== compareLayerResolvedData.id && ( + baseLayer.data.id !== compareLayer.data.id && ( )} @@ -356,19 +308,19 @@ function MapBlock(props: MapBlockProps) { {selectedDatetime && selectedCompareDatetime ? ( {computedCompareLabel} ) : ( {selectedDatetime && formatSingleDate( selectedDatetime, - baseLayerResolvedData?.timeDensity + baseLayer?.data.time_density )} )} @@ -376,19 +328,16 @@ function MapBlock(props: MapBlockProps) { - {selectedCompareDatetime && ( + {selectedCompareDatetime && (compareLayer?.status === S_SUCCEEDED) && ( - {dataset && - selectedCompareDatetime && + {selectedCompareDatetime && layerId && - compareLayerResolvedData && ( + compareLayer && ( )} diff --git a/app/scripts/components/exploration/components/map/layer.tsx b/app/scripts/components/exploration/components/map/layer.tsx index d2727abda..d676736bf 100644 --- a/app/scripts/components/exploration/components/map/layer.tsx +++ b/app/scripts/components/exploration/components/map/layer.tsx @@ -2,12 +2,8 @@ import React, { useMemo } from 'react'; // Avoid error: node_modules/date-fns/esm/index.js does not export 'default' import * as dateFns from 'date-fns'; -import { TimelineDatasetSuccess } from '../../types.d.ts'; +import { TimelineDatasetSuccess, VizDatasetSUCCESS } from '../../types.d.ts'; import { getTimeDensityStartDate } from '../../data-utils'; -import { - useTimelineDatasetAtom, - useTimelineDatasetSettings -} from '../../atoms/hooks'; import { resolveConfigFunctions } from '$components/common/map/utils'; import { RasterTimeseries } from '$components/common/map/style-generators/raster-timeseries'; @@ -17,36 +13,20 @@ import { CMRTimeseries } from '$components/common/map/style-generators/cmr-times interface LayerProps { id: string; - dataset: TimelineDatasetSuccess; + dataset: TimelineDatasetSuccess | VizDatasetSUCCESS; order?: number; selectedDay: Date; } export function Layer(props: LayerProps) { const { id: layerId, dataset, order, selectedDay } = props; - - let isVisible: boolean | undefined; - let opacity: number | undefined; - - const datasetAtom = useTimelineDatasetAtom(dataset.data.id); - - try { - // @TECH-DEBT: Wrapping this logic with a try/catch because jotai errors because it is unable to find - // 'settings' on undefined value even when dataset has 'settings' key. This is a workaround for now but - // should be revisited. Ideally type should be fine with 'Partial' - const [getSettings] = useTimelineDatasetSettings(datasetAtom); - - isVisible = getSettings('isVisible'); - opacity = getSettings('opacity'); - } catch { - isVisible = true; - opacity = undefined; - } + const { isVisible, opacity } = dataset.settings; // The date needs to match the dataset's time density. const relevantDate = useMemo( - () => getTimeDensityStartDate(selectedDay, dataset.data?.timeDensity), - [selectedDay, dataset.data?.timeDensity] + () => { + return getTimeDensityStartDate(selectedDay, dataset.data.timeDensity); + }, [selectedDay, dataset.data.timeDensity] ); // Resolve config functions. diff --git a/app/scripts/components/exploration/data-utils.ts b/app/scripts/components/exploration/data-utils.ts index c1f4380ed..e278ca43d 100644 --- a/app/scripts/components/exploration/data-utils.ts +++ b/app/scripts/components/exploration/data-utils.ts @@ -12,7 +12,8 @@ import { StacDatasetData, TimeDensity, TimelineDataset, - TimelineDatasetStatus + TimelineDatasetStatus, + VizDataset } from './types.d.ts'; import { DataMetric, @@ -100,6 +101,19 @@ function getInitialMetrics(data: DatasetLayer): DataMetric[] { return foundMetrics; } + +export function reconcileVizDataset(dataset): VizDataset { + return { + status: TimelineDatasetStatus.IDLE, + data: dataset, + error: null, + settings: { + isVisible: true, + opacity: 100 + } + }; +} + /** * Converts the datasets to a format that can be used by the timeline, skipping * the ones that have already been reconciled. diff --git a/app/scripts/components/exploration/hooks/use-stac-metadata-datasets.ts b/app/scripts/components/exploration/hooks/use-stac-metadata-datasets.ts index 57632fe28..0c32d2bea 100644 --- a/app/scripts/components/exploration/hooks/use-stac-metadata-datasets.ts +++ b/app/scripts/components/exploration/hooks/use-stac-metadata-datasets.ts @@ -9,7 +9,9 @@ import { StacDatasetData, TimeDensity, TimelineDataset, - TimelineDatasetStatus + TimelineDatasetStatus, + VizDataset, + VizDatasetSUCCESS } from '../types.d.ts'; import { resolveLayerTemporalExtent } from '../data-utils'; @@ -34,8 +36,8 @@ function didDataChange(curr: UseQueryResult, prev?: UseQueryResult) { */ function reconcileQueryDataWithDataset( queryData: UseQueryResult, - dataset: TimelineDataset -): TimelineDataset { + dataset: TimelineDataset | VizDataset +): TimelineDataset | VizDataset { try { let base = { ...dataset, @@ -70,7 +72,7 @@ function reconcileQueryDataWithDataset( } async function fetchStacDatasetById( - dataset: TimelineDataset + dataset: TimelineDataset | VizDataset ): Promise { const { type, stacCol, stacApiEndpoint, time_density } = dataset.data; @@ -127,10 +129,10 @@ async function fetchStacDatasetById( // Create a query object for react query. function makeQueryObject( - dataset: TimelineDataset + dataset: TimelineDataset | VizDataset ): UseQueryOptions { return { - queryKey: ['dataset', dataset?.data?.id], + queryKey: ['dataset', dataset.data.id], queryFn: () => fetchStacDatasetById(dataset), // This data will not be updated in the context of a browser session, so it is // safe to set the staleTime to Infinity. As specified by react-query's @@ -151,27 +153,37 @@ function makeQueryObject( * Whenever a dataset is added to the timeline, this hook will fetch the STAC * metadata for that dataset and add it to the dataset state atom. */ + +// Overload signatures +export function useReconcileWithStacMetadata( + datasets: VizDataset[], + handleUpdate: React.Dispatch> +): void; export function useReconcileWithStacMetadata( datasets: TimelineDataset[], - handleUpdate: SetAtom<[updates: SetStateAction], void> | React.Dispatch> -) { - const noDatasetsToQuery: boolean = !datasets || (datasets.length === 1 && datasets[0] === undefined); - + handleUpdate: SetAtom<[updates: SetStateAction], void> +): void; + +export function useReconcileWithStacMetadata( + datasets: T[], + handleUpdate: SetAtom<[updates: SetStateAction], void> | React.Dispatch> +):void { + const noDatasetsToQuery: boolean = (datasets.length === 1 && datasets[0] === undefined); const datasetsQueryData = useQueries({ queries: noDatasetsToQuery ? [] : datasets.filter((d) => !(d as any)?.mocked).map((dataset) => makeQueryObject(dataset)) }); - useEffectPrevious<[typeof datasetsQueryData, TimelineDataset[]]>( + useEffectPrevious<[typeof datasetsQueryData, T[]]>( (prev) => { if (noDatasetsToQuery) return; const prevQueryData = prev[0]; const hasPrev = !!prevQueryData; const { updated, data: updatedDatasets } = datasets - .filter((d) => !(d as any)?.mocked) .reduce<{ updated: boolean; - data: TimelineDataset[]; + // @TODO: type this properly + data; }>( (acc, dataset, idx) => { const curr = datasetsQueryData[idx]; @@ -197,11 +209,7 @@ export function useReconcileWithStacMetadata( if (updated) { handleUpdate(updatedDatasets); } - }, [datasetsQueryData, datasets] ); } - - - diff --git a/app/scripts/components/exploration/types.d.ts.ts b/app/scripts/components/exploration/types.d.ts.ts index 1898ec135..4d0600c2b 100644 --- a/app/scripts/components/exploration/types.d.ts.ts +++ b/app/scripts/components/exploration/types.d.ts.ts @@ -47,13 +47,15 @@ interface AnalysisMeta { // TimelineDatasetAnalysis type discriminants export interface TimelineDatasetAnalysisIdle { - status: TimelineDatasetStatus.IDLE; + // @TODO : make status right + status: string; data: null; error: null; meta: Record; } export interface TimelineDatasetAnalysisLoading { - status: TimelineDatasetStatus.LOADING; + // @TO DO : make status right + status: string; data: null; error: null; meta: Partial @@ -107,6 +109,26 @@ export interface TimelineDatasetSettings { // Tile endpoints for the layer given the current map view. type TimelineDatasetMeta = Record; +// Holds only dataset needed for visualization (Subset of timeline dataset) +// @ TODO: Rename Timeline specific variable names +export interface VizDatasetIDLE { + status: TimelineDatasetStatus.IDLE | TimelineDatasetStatus.LOADING; + data: EnhancedDatasetLayer; + error: null; + settings: TimelineDatasetSettings; + meta?: TimelineDatasetMeta; +} + +export interface VizDatasetSUCCESS { + status: TimelineDatasetStatus.SUCCESS; + data: TimelineDatasetData; + error: null; + settings: TimelineDatasetSettings; + meta?: TimelineDatasetMeta; +} + +export type VizDataset = VizDatasetIDLE | VizDatasetSUCCESS; + // TimelineDataset type discriminants export interface TimelineDatasetIdle { status: TimelineDatasetStatus.IDLE; diff --git a/parcel-resolver-veda/index.d.ts b/parcel-resolver-veda/index.d.ts index 893801aac..288b45f18 100644 --- a/parcel-resolver-veda/index.d.ts +++ b/parcel-resolver-veda/index.d.ts @@ -126,6 +126,8 @@ export interface LayerInfo { /* functions from date-fns package */ dateFns: typeof dateFns; + + raw?: any; } interface LayerLegendUnit {