From ac094aeba15f5e2ec9d1e5c7f75d01fc61fb8cb1 Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Tue, 21 May 2024 09:14:46 +0200 Subject: [PATCH 1/5] Create sandboxes for the ExplorationMap and MapBlock, including some re-factoring --- .../components/common/blocks/block-map.tsx | 193 ++++++++++-------- .../common/blocks/scrollytelling/index.tsx | 4 +- .../common/{mapbox => map}/layer-legend.tsx | 24 +-- .../common/{mapbox => map}/map-coords.tsx | 0 .../common/{mapbox => map}/map-message.tsx | 8 +- .../components/common/mapbox/index.tsx | 10 +- app/scripts/components/common/mapbox/map.tsx | 6 +- .../components/datasets/data-layer-card.tsx | 59 +++--- .../exploration/components/map/index.tsx | 125 +++--------- app/scripts/components/exploration/index.tsx | 115 ++++++++++- .../sandbox/exploration-map/index.js | 129 ++++++++++++ app/scripts/components/sandbox/index.js | 12 ++ .../components/sandbox/map-block/index.js | 37 ++++ 13 files changed, 477 insertions(+), 245 deletions(-) rename app/scripts/components/common/{mapbox => map}/layer-legend.tsx (95%) rename app/scripts/components/common/{mapbox => map}/map-coords.tsx (100%) rename app/scripts/components/common/{mapbox => map}/map-message.tsx (89%) create mode 100644 app/scripts/components/sandbox/exploration-map/index.js create mode 100644 app/scripts/components/sandbox/map-block/index.js diff --git a/app/scripts/components/common/blocks/block-map.tsx b/app/scripts/components/common/blocks/block-map.tsx index 94f572bbb..68752d0a6 100644 --- a/app/scripts/components/common/blocks/block-map.tsx +++ b/app/scripts/components/common/blocks/block-map.tsx @@ -9,11 +9,18 @@ import { validateProjectionBlockProps } from '../map/controls/map-options/projections'; import { Basemap } from '../map/style-generators/basemap'; -import { LayerLegend, LayerLegendContainer } from '../mapbox/layer-legend'; +import { LayerLegend, LayerLegendContainer } from '../map/layer-legend'; import MapCoordsControl from '../map/controls/coords'; -import MapMessage from '../mapbox/map-message'; -import { formatCompareDate, formatSingleDate, resolveConfigFunctions } from '../map/utils'; -import { BasemapId, DEFAULT_MAP_STYLE_URL } from '../map/controls/map-options/basemap'; +import MapMessage from '../map/map-message'; +import { + formatCompareDate, + formatSingleDate, + resolveConfigFunctions +} from '../map/utils'; +import { + BasemapId, + DEFAULT_MAP_STYLE_URL +} from '../map/controls/map-options/basemap'; import { utcString2userTzDate } from '$utils/date'; import Map, { Compare, MapControls } from '$components/common/map'; import { validateRangeNum } from '$utils/utils'; @@ -23,10 +30,11 @@ import { ScaleControl } from '$components/common/map/controls'; import { Layer } from '$components/exploration/components/map/layer'; +import { S_SUCCEEDED } from '$utils/status'; import { - S_SUCCEEDED -} from '$utils/status'; -import { TimelineDataset, TimelineDatasetSuccess } from '$components/exploration/types.d.ts'; + TimelineDataset, + TimelineDatasetSuccess +} from '$components/exploration/types.d.ts'; import { reconcileDatasets } from '$components/exploration/data-utils'; import { datasetLayers } from '$components/exploration/data-utils'; @@ -134,24 +142,30 @@ function MapBlock(props: MapBlockProps) { projectionParallels, basemapId } = props; - + const errors = validateBlockProps(props); if (errors.length) { throw new HintedError('Malformed Map Block', errors); } - const [ baseLayers, setBaseLayers] = useState(); - const [ compareLayers, setCompareLayers] = useState(); + const [baseLayers, setBaseLayers] = useState(); + const [compareLayers, setCompareLayers] = useState< + TimelineDataset[] | undefined + >(); - const [ baseMapStaticData ] = reconcileDatasets([layerId], datasetLayers, []); + const [baseMapStaticData] = reconcileDatasets([layerId], datasetLayers, []); const baseMapStaticCompareData = baseMapStaticData.data.compare; let compareLayerId: undefined | string; - if (baseMapStaticCompareData && ('layerId' in baseMapStaticCompareData)) { + if (baseMapStaticCompareData && 'layerId' in baseMapStaticCompareData) { compareLayerId = baseMapStaticCompareData.layerId; } - const [ compareMapStaticData ] = reconcileDatasets(compareLayerId ? [compareLayerId] : [], datasetLayers, []); + const [compareMapStaticData] = reconcileDatasets( + compareLayerId ? [compareLayerId] : [], + datasetLayers, + [] + ); useReconcileWithStacMetadata([baseMapStaticData], setBaseLayers); useReconcileWithStacMetadata([compareMapStaticData], setCompareLayers); @@ -162,7 +176,7 @@ function MapBlock(props: MapBlockProps) { const selectedCompareDatetime = compareDateTime ? utcString2userTzDate(compareDateTime) : undefined; - + const projectionStart = useMemo(() => { if (projectionId) { // Ensure that the default center and parallels are used if none are @@ -184,9 +198,13 @@ function MapBlock(props: MapBlockProps) { const [, setProjection] = useState(projectionStart); const dataset = datasetId ? datasets[datasetId] : null; - + const resolverBag = useMemo( - () => ({ datetime: selectedDatetime, compareDatetime: selectedCompareDatetime, dateFns }), + () => ({ + datetime: selectedDatetime, + compareDatetime: selectedCompareDatetime, + dateFns + }), [selectedDatetime, selectedCompareDatetime] ); @@ -194,16 +212,18 @@ function MapBlock(props: MapBlockProps) { if (!baseLayers || baseLayers.length !== 1) return [null, null]; const baseLayer = baseLayers[0]; - if (baseLayer.status !== S_SUCCEEDED ) return [null, null]; + if (baseLayer.status !== S_SUCCEEDED) return [null, null]; const bag = { ...resolverBag, raw: baseLayer.data }; const data = resolveConfigFunctions(baseLayer.data, bag); return [data]; }, [baseLayers, resolverBag]); - const baseDataLayer: TimelineDataset | null = ({data: baseLayerResolvedData} as unknown) as TimelineDataset; + const baseDataLayer: TimelineDataset | null = { + data: baseLayerResolvedData + } as unknown as TimelineDataset; const baseTimeDensity = baseLayerResolvedData?.timeDensity; - + // Resolve data needed for the compare layer once it is loaded. const [compareLayerResolvedData] = useMemo(() => { if (!compareLayers || compareLayers.length !== 1) return [null, null]; @@ -216,7 +236,9 @@ function MapBlock(props: MapBlockProps) { return [data]; }, [compareLayers, resolverBag]); - const compareDataLayer: TimelineDataset | null = ({data: compareLayerResolvedData} as unknown) as TimelineDataset; + const compareDataLayer: TimelineDataset | null = { + data: compareLayerResolvedData + } as unknown as TimelineDataset; const compareTimeDensity = compareLayerResolvedData?.timeDensity; const mapOptions: Partial = { @@ -227,17 +249,17 @@ function MapBlock(props: MapBlockProps) { dragRotate: false, zoom: 1 }; - + const getMapPositionOptions = (position) => { const opts = {} as Pick; if (position?.lng !== undefined && position?.lat !== undefined) { opts.center = [position.lng, position.lat]; } - + if (position?.zoom) { opts.zoom = position.zoom; } - + return opts; }; @@ -285,27 +307,28 @@ function MapBlock(props: MapBlockProps) { compareTimeDensity ]); - const initialPosition = useMemo(() => center ? { lng: center[0], lat: center[1], zoom } : undefined , [center, zoom]); + const initialPosition = useMemo( + () => (center ? { lng: center[0], lat: center[1], zoom } : undefined), + [center, zoom] + ); return ( - - - { - dataset && - selectedDatetime && - layerId && - baseLayerResolvedData && - ( - - ) - } + + + {dataset && selectedDatetime && layerId && baseLayerResolvedData && ( + + )} {baseLayerResolvedData?.legend && ( // Map overlay element // Layer legend for the active layer. @@ -330,60 +353,50 @@ function MapBlock(props: MapBlockProps) { )} - { - (selectedDatetime && selectedCompareDatetime) ? - ( - - {computedCompareLabel} - - ) : - ( - - {selectedDatetime && formatSingleDate(selectedDatetime, baseLayerResolvedData?.timeDensity)} - - ) - } + {selectedDatetime && selectedCompareDatetime ? ( + + {computedCompareLabel} + + ) : ( + + {selectedDatetime && + formatSingleDate( + selectedDatetime, + baseLayerResolvedData?.timeDensity + )} + + )} - { - selectedCompareDatetime && ( - - - { - dataset && - selectedCompareDatetime && - layerId && - compareLayerResolvedData && - ( - - ) - } - - ) - } + {selectedCompareDatetime && ( + + + {dataset && + selectedCompareDatetime && + layerId && + compareLayerResolvedData && ( + + )} + + )} ); } -export default MapBlock; \ No newline at end of file +export default MapBlock; diff --git a/app/scripts/components/common/blocks/scrollytelling/index.tsx b/app/scripts/components/common/blocks/scrollytelling/index.tsx index 795758656..73953be7c 100644 --- a/app/scripts/components/common/blocks/scrollytelling/index.tsx +++ b/app/scripts/components/common/blocks/scrollytelling/index.tsx @@ -37,8 +37,8 @@ import Hug from '$styles/hug'; import { LayerLegendContainer, LayerLegend -} from '$components/common/mapbox/layer-legend'; -import MapMessage from '$components/common/mapbox/map-message'; +} from '$components/common/map/layer-legend'; +import MapMessage from '$components/common/map/map-message'; import { MapLoading } from '$components/common/loading-skeleton'; import { HintedError } from '$utils/hinted-error'; import { formatSingleDate } from '$components/common/mapbox/utils'; diff --git a/app/scripts/components/common/mapbox/layer-legend.tsx b/app/scripts/components/common/map/layer-legend.tsx similarity index 95% rename from app/scripts/components/common/mapbox/layer-legend.tsx rename to app/scripts/components/common/map/layer-legend.tsx index 2d68c4951..1cbcec0d8 100644 --- a/app/scripts/components/common/mapbox/layer-legend.tsx +++ b/app/scripts/components/common/map/layer-legend.tsx @@ -27,8 +27,6 @@ import { WidgetItemHGroup } from '$styles/panel'; -// @NOTE: File should be moved under "/common/map" as we are working to deprecate "/common/mapbox" dir - interface LayerLegendCommonProps { id: string; title: string; @@ -167,13 +165,13 @@ const LegendList = styled.dl` } .unit { - grid-row: 3; - width: 100%; - text-align: center; - font-size: 0.75rem; - line-height: 1rem; - justify-content: center; - } + grid-row: 3; + width: 100%; + text-align: center; + font-size: 0.75rem; + line-height: 1rem; + justify-content: center; + } `; const LegendSwatch = styled.span` @@ -255,11 +253,9 @@ export function LayerLegend( )} renderBody={() => ( - -
- {description ||

No info available for this layer.

} -
- +
+ {description ||

No info available for this layer.

} +
)} /> diff --git a/app/scripts/components/common/mapbox/map-coords.tsx b/app/scripts/components/common/map/map-coords.tsx similarity index 100% rename from app/scripts/components/common/mapbox/map-coords.tsx rename to app/scripts/components/common/map/map-coords.tsx diff --git a/app/scripts/components/common/mapbox/map-message.tsx b/app/scripts/components/common/map/map-message.tsx similarity index 89% rename from app/scripts/components/common/mapbox/map-message.tsx rename to app/scripts/components/common/map/map-message.tsx index 77fae7397..0b12321b9 100644 --- a/app/scripts/components/common/mapbox/map-message.tsx +++ b/app/scripts/components/common/map/map-message.tsx @@ -5,9 +5,7 @@ import { Transition, TransitionGroup } from 'react-transition-group'; import { glsp, themeVal } from '@devseed-ui/theme-provider'; import { variableGlsp } from '$styles/variable-utils'; -// @NOTE: File should be moved under "/common/map" as we are working to deprecate "/common/mapbox" dir - -const fadeDuration = 240; +const FADE_DURATION = 240; interface MessageProps { show: boolean; @@ -45,7 +43,7 @@ const Message = styled.div` background: #fff; `} - transition: all ${fadeDuration}ms ease-in-out; + transition: all ${FADE_DURATION}ms ease-in-out; ${({ show }) => show ? css` @@ -72,7 +70,7 @@ export default function MapMessage(props: MapMessageProps) { return ( {active && ( - + {(state) => { return ( { geocoderControl.clear(); geocoderControl._inputEl.blur(); - const zoom = result.bbox? getZoomFromBbox(result.bbox): 14; + const zoom = result.bbox ? getZoomFromBbox(result.bbox) : 14; mapRef.current?.flyTo({ center: result.center, zoom diff --git a/app/scripts/components/exploration/components/datasets/data-layer-card.tsx b/app/scripts/components/exploration/components/datasets/data-layer-card.tsx index 032e5506f..a69bb8da2 100644 --- a/app/scripts/components/exploration/components/datasets/data-layer-card.tsx +++ b/app/scripts/components/exploration/components/datasets/data-layer-card.tsx @@ -6,7 +6,7 @@ import { LayerLegendCategorical, LayerLegendGradient } from 'veda'; import { CollecticonCircleInformation, CollecticonEyeDisabled, - CollecticonEye, + CollecticonEye } from '@devseed-ui/collecticons'; import { Toolbar } from '@devseed-ui/toolbar'; import { Heading } from '@devseed-ui/typography'; @@ -17,7 +17,7 @@ import { TipButton } from '$components/common/tip-button'; import { LayerCategoricalGraphic, LayerGradientGraphic -} from '$components/common/mapbox/layer-legend'; +} from '$components/common/map/layer-legend'; import { TimelineDataset } from '$components/exploration/types.d.ts'; import { CollecticonDatasetLayers } from '$components/common/icons/dataset-layers'; @@ -42,7 +42,6 @@ const DatasetCardInfo = styled.div` gap: 0rem; `; - const DatasetInfo = styled.div` width: 100%; display: flex; @@ -87,11 +86,18 @@ const DatasetTitle = styled(Heading)` const DatasetMetricInfo = styled.div` font-size: 0.75rem; - color: ${themeVal('color.base-500')} + color: ${themeVal('color.base-500')}; `; export default function DataLayerCard(props: CardProps) { - const { dataset, datasetAtom, isVisible, setVisible, datasetLegend, onClickLayerInfo } = props; + const { + dataset, + datasetAtom, + isVisible, + setVisible, + datasetLegend, + onClickLayerInfo + } = props; const layerInfo = dataset.data.info; return ( @@ -99,9 +105,10 @@ export default function DataLayerCard(props: CardProps) {
- -

{dataset.data.parentDataset.name}

-
+ + {' '} +

{dataset.data.parentDataset.name}

+
@@ -114,7 +121,7 @@ export default function DataLayerCard(props: CardProps) { // latter doesn't support the `forwardedAs` prop. size='small' fitting='skinny' - onPointerDownCapture={e => e.stopPropagation()} + onPointerDownCapture={(e) => e.stopPropagation()} onClick={onClickLayerInfo} > e.stopPropagation()} + onPointerDownCapture={(e) => e.stopPropagation()} onClick={() => setVisible((v) => !v)} > - {isVisible ? ( - - ) : ( - - )} + {isVisible ? ( + + ) : ( + + )} - + - { - layerInfo && ( - - ) - } + {layerInfo && }
{datasetLegend?.type === 'categorical' && ( @@ -172,4 +175,4 @@ export default function DataLayerCard(props: CardProps) {
); -} \ No newline at end of file +} diff --git a/app/scripts/components/exploration/components/map/index.tsx b/app/scripts/components/exploration/components/map/index.tsx index c53522383..46af8bca4 100644 --- a/app/scripts/components/exploration/components/map/index.tsx +++ b/app/scripts/components/exploration/components/map/index.tsx @@ -1,10 +1,7 @@ -import React, { useCallback, useEffect, useState } from 'react'; -import { useAtom, useAtomValue } from 'jotai'; - -import { useReconcileWithStacMetadata } from '../../hooks/use-stac-metadata-datasets'; -import { selectedCompareDateAtom, selectedDateAtom } from '../../atoms/dates'; -import { timelineDatasetsAtom } from '../../atoms/datasets'; +import React from 'react'; +import { ProjectionOptions } from 'veda'; import { + TimelineDataset, TimelineDatasetStatus, TimelineDatasetSuccess } from '../../types.d.ts'; @@ -21,60 +18,37 @@ import { } from '$components/common/map/controls'; import MapCoordsControl from '$components/common/map/controls/coords'; import MapOptionsControl from '$components/common/map/controls/map-options'; -import { projectionDefault } from '$components/common/map/controls/map-options/projections'; -import { useBasemap } from '$components/common/map/controls/hooks/use-basemap'; import DrawControl from '$components/common/map/controls/aoi'; import CustomAoIControl from '$components/common/map/controls/aoi/custom-aoi-control'; -import { usePreviousValue } from '$utils/use-effect-previous'; - -export function ExplorationMap() { - const [projection, setProjection] = useState(projectionDefault); - - const { - mapBasemapId, - setBasemapId, - labelsOption, - boundariesOption, - onOptionChange - } = useBasemap(); - - const [datasets, setDatasets] = useAtom(timelineDatasetsAtom); - - useReconcileWithStacMetadata(datasets, setDatasets); - - const selectedDay = useAtomValue(selectedDateAtom); - const selectedCompareDay = useAtomValue(selectedCompareDateAtom); - - // Different datasets may have a different default projection. - // When datasets are selected the first time, we set the map projection to the - // first dataset's projection. - const prevDatasetCount = usePreviousValue(datasets.length); - useEffect(() => { - if (!prevDatasetCount && datasets.length > 0) { - setProjection(datasets[0].data.projection ?? projectionDefault); - } - }, [datasets, prevDatasetCount]); - - // If all the datasets are changed through the modal, we also want to update - // the map projection, since it is as if the datasets were selected for the - // first time. - // The only case where we don't want to update the projection is when not all - // datasets are changed, because it is not possible to know which projection - // to use. - const prevDatasetsIds = usePreviousValue(datasets.map((d) => d.data.id)); - useEffect(() => { - if (!prevDatasetsIds) return; - - const newDatasetsIds = datasets.map((d) => d.data.id); - const hasSameId = newDatasetsIds.some((id) => prevDatasetsIds.includes(id)); - - if (!hasSameId && datasets.length > 0) { - setProjection(datasets[0].data.projection ?? projectionDefault); - } - }, [prevDatasetsIds, datasets]); - - const comparing = !!selectedCompareDay; +import { BasemapId } from '$components/common/map/controls/map-options/basemap.js'; + +interface ExplorationMapProps { + datasets: TimelineDataset[]; + selectedDay: Date | null; + selectedCompareDay: Date | null; + mapBasemapId: BasemapId; + setBasemapId: (id: BasemapId) => void; + labelsOption: boolean; + boundariesOption: boolean; + onOptionChange: (option: string, value: boolean) => void; + onStyleUpdate: (style: any) => void; + projection: ProjectionOptions; + setProjection: (projection: ProjectionOptions) => void; +} +export function ExplorationMap({ + datasets, + selectedDay, + selectedCompareDay, + mapBasemapId, + setBasemapId, + labelsOption, + boundariesOption, + onOptionChange, + onStyleUpdate, + projection, + setProjection +}: ExplorationMapProps) { // Reverse the datasets order to have the "top" layer, list-wise, at the "top" layer, z-order wise // Disabled eslint rule as slice() creates a shallow copy // eslint-disable-next-line fp/no-mutating-methods @@ -86,42 +60,10 @@ export function ExplorationMap() { .slice() .reverse(); - const onStyleUpdate = useCallback( - (style) => { - const updatedDatasets = datasets.map((dataset) => { - // Skip non loaded datasets - if (dataset.status !== TimelineDatasetStatus.SUCCESS) return dataset; - - // Check if there's layer information for this dataset. - const layerMetadata = style.layers.find( - (l) => l.metadata?.id === dataset.data.id - ); - - // Skip if no metadata. - if (!layerMetadata) return dataset; - - const currentMeta = dataset.meta ?? {}; - - return { - ...dataset, - meta: { - ...currentMeta, - tileUrls: { - wmtsTileUrl: layerMetadata.metadata.wmtsTileUrl, - xyzTileUrl: layerMetadata.metadata.xyzTileUrl - } - } - }; - }); - - setDatasets(updatedDatasets); - }, - [datasets, setDatasets] - ); + const comparing = !!selectedCompareDay; return ( - {/* Map layers */} ))} - {/* Map controls */} - {comparing && ( - // Compare map layers { + if (!prevDatasetCount && datasets.length > 0) { + setProjection(datasets[0].data.projection ?? projectionDefault); + } + }, [datasets, prevDatasetCount]); + + // If all the datasets are changed through the modal, we also want to update + // the map projection, since it is as if the datasets were selected for the + // first time. + // The only case where we don't want to update the projection is when not all + // datasets are changed, because it is not possible to know which projection + // to use. + const prevDatasetsIds = usePreviousValue(datasets.map((d) => d.data.id)); + + useEffect(() => { + if (!prevDatasetsIds) return; + + const newDatasetsIds = datasets.map((d) => d.data.id); + const hasSameId = newDatasetsIds.some((id) => prevDatasetsIds.includes(id)); + + if (!hasSameId && datasets.length > 0) { + setProjection(datasets[0].data.projection ?? projectionDefault); + } + }, [prevDatasetsIds, datasets]); + + const onStyleUpdate = useCallback( + (style) => { + const updatedDatasets = datasets.map((dataset) => { + // Skip non loaded datasets + if (dataset.status !== TimelineDatasetStatus.SUCCESS) return dataset; + + // Check if there's layer information for this dataset. + const layerMetadata = style.layers.find( + (l) => l.metadata?.id === dataset.data.id + ); + + // Skip if no metadata. + if (!layerMetadata) return dataset; + + const currentMeta = dataset.meta ?? {}; + + return { + ...dataset, + meta: { + ...currentMeta, + tileUrls: { + wmtsTileUrl: layerMetadata.metadata.wmtsTileUrl, + xyzTileUrl: layerMetadata.metadata.xyzTileUrl + } + } + }; + }); + + setDatasets(updatedDatasets); + }, + [datasets, setDatasets] + ); + const [datasetModalRevealed, setDatasetModalRevealed] = useState( !datasets.length ); - // @TECH-DEBT: panelHeight needs to be passed to work around Safari CSS + // @TECH-DEBT: panelHeight needs to be passed to work around Safari CSS const [panelHeight, setPanelHeight] = useState(0); const openModal = useCallback(() => setDatasetModalRevealed(true), []); @@ -107,15 +193,32 @@ function Exploration() { {setPanelHeight(size);}} + maxSize={75} + className='panel' + onResize={(size: number) => { + setPanelHeight(size); + }} > - + - + diff --git a/app/scripts/components/sandbox/exploration-map/index.js b/app/scripts/components/sandbox/exploration-map/index.js new file mode 100644 index 000000000..c0eb23c7a --- /dev/null +++ b/app/scripts/components/sandbox/exploration-map/index.js @@ -0,0 +1,129 @@ +import React from 'react'; +import styled from 'styled-components'; +import Constrainer from '$styles/constrainer'; +import { PageMainContent } from '$styles/page'; +import { ExplorationMap } from '$components/exploration/components/map'; + +const DemoMap = styled(ExplorationMap)` + height: 40rem; +`; + +const Wrapper = styled.div` + position: relative; + grid-column: 1 / -1; + height: 42rem; +`; + +const mockDatasets = [ + { + status: 'loading', + data: { + id: 'casa-gfed-co2-flux-hr', + stacApiEndpoint: 'https://ghg.center/api/stac', + tileApiEndpoint: 'https://ghg.center/api/raster', + stacCol: 'casagfed-carbonflux-monthgrid-v3', + name: 'Heterotrophic Respiration (Rh)', + type: 'raster', + description: + 'Model-estimated heterotrophic respiration (Rh), which is the flux of carbon from the soil to the atmosphere', + initialDatetime: 'newest', + projection: { id: 'equirectangular' }, + basemapId: 'light', + zoomExtent: [0, 20], + sourceParams: { + assets: 'rh', + colormap_name: 'purd', + rescale: [0, 0.3] + }, + compare: { + datasetId: 'casagfed-carbonflux-monthgrid-v3', + layerId: 'casa-gfed-co2-flux-hr' + }, + legend: { + unit: { label: 'kg Carbon/m²/mon' }, + type: 'gradient', + min: 0, + max: 0.3, + stops: [ + '#F7F4F9', + '#E9E3F0', + '#D9C3DF', + '#CDA0CD', + '#D57ABA', + '#E34A9F', + '#DF2179', + '#C10E51', + '#92003F', + '#67001F' + ] + }, + parentDataset: { + id: 'casagfed-carbonflux-monthgrid-v3', + name: 'CASA-GFED3 Land Carbon Flux' + } + }, + error: null, + settings: { + isVisible: true, + opacity: 100, + analysisMetrics: [ + { + id: 'mean', + label: 'Average', + chartLabel: 'Average', + themeColor: 'infographicB' + }, + { + id: 'std', + label: 'St Deviation', + chartLabel: 'St Deviation', + themeColor: 'infographicD' + } + ] + }, + analysis: { + status: 'idle', + data: null, + error: null, + meta: {} + } + } +]; + +const mockSelectedDay = new Date('2017-11-30T23:00:00.000Z'); +const mockSelectedCompareDay = null; +const mockMapBasemapId = 'satellite'; +const mockLabelsOption = true; +const mockBoundariesOption = true; +const mockProjection = { id: 'equirectangular' }; + +const mockOnOptionChange = () => {}; +const mockOnStyleUpdate = () => {}; +const mockSetProjection = () => {}; +const mockSetBasemapId = () => {}; + +function SandboxExplorationMap() { + return ( + + + + + + + + ); +} + +export default SandboxExplorationMap; diff --git a/app/scripts/components/sandbox/index.js b/app/scripts/components/sandbox/index.js index 50e41d832..8fd7a121e 100644 --- a/app/scripts/components/sandbox/index.js +++ b/app/scripts/components/sandbox/index.js @@ -4,6 +4,8 @@ import { Route, Routes, useParams } from 'react-router-dom'; import SandboxTypography from './typography'; import SandboxHug from './hug'; import SandboxMap from './map'; +import SandboxExplorationMap from './exploration-map'; +import SandboxMapBlock from './map-block'; import SandboxContentBlocks from './content-blocks'; import SandboxCards from './cards'; import SandboxMDXPage from './mdx-page'; @@ -42,6 +44,16 @@ const pages = [ name: 'Mapbox map', component: SandboxMap }, + { + id: 'exploration-map', + name: 'Exploration map', + component: SandboxExplorationMap + }, + { + id: 'map-block', + name: 'Map block', + component: SandboxMapBlock + }, { id: 'content-blocks', name: 'Content Blocks', diff --git a/app/scripts/components/sandbox/map-block/index.js b/app/scripts/components/sandbox/map-block/index.js new file mode 100644 index 000000000..dab81d49a --- /dev/null +++ b/app/scripts/components/sandbox/map-block/index.js @@ -0,0 +1,37 @@ +import React from 'react'; +import styled from 'styled-components'; +import Constrainer from '$styles/constrainer'; +import { PageMainContent } from '$styles/page'; +import MapBlock from '$components/common/blocks/block-map'; + +const DemoMap = styled(MapBlock)` + height: 40rem; +`; + +const Wrapper = styled.div` + position: relative; + grid-column: 1 / -1; + height: 42rem; +`; + +function SandboxMapBlock() { + return ( + + + + + + + + ); +} + +export default SandboxMapBlock; From 10a9e55e71a2f7e1aa327358ccf9869851b3f1cc Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Thu, 23 May 2024 08:54:16 +0200 Subject: [PATCH 2/5] Clean up and address review comments --- .../exploration/components/map/index.tsx | 170 +++++++++++---- .../components/timeline/timeline.tsx | 109 ++++++---- .../components/exploration/container.tsx | 49 +++++ app/scripts/components/exploration/index.tsx | 200 +++++------------- .../sandbox/exploration-map/index.js | 25 +-- app/scripts/main.tsx | 9 +- 6 files changed, 297 insertions(+), 265 deletions(-) create mode 100644 app/scripts/components/exploration/container.tsx diff --git a/app/scripts/components/exploration/components/map/index.tsx b/app/scripts/components/exploration/components/map/index.tsx index 46af8bca4..c9aa21300 100644 --- a/app/scripts/components/exploration/components/map/index.tsx +++ b/app/scripts/components/exploration/components/map/index.tsx @@ -1,5 +1,7 @@ -import React from 'react'; +import React, { useCallback, useEffect, useState } from 'react'; + import { ProjectionOptions } from 'veda'; +import { useReconcileWithStacMetadata } from '../../hooks/use-stac-metadata-datasets'; import { TimelineDataset, TimelineDatasetStatus, @@ -18,37 +20,66 @@ import { } from '$components/common/map/controls'; import MapCoordsControl from '$components/common/map/controls/coords'; import MapOptionsControl from '$components/common/map/controls/map-options'; +import { projectionDefault } from '$components/common/map/controls/map-options/projections'; +import { useBasemap } from '$components/common/map/controls/hooks/use-basemap'; import DrawControl from '$components/common/map/controls/aoi'; import CustomAoIControl from '$components/common/map/controls/aoi/custom-aoi-control'; -import { BasemapId } from '$components/common/map/controls/map-options/basemap.js'; +import { usePreviousValue } from '$utils/use-effect-previous'; +import { ExtendedStyle } from '$components/common/map/styles'; interface ExplorationMapProps { datasets: TimelineDataset[]; + setDatasets: (datasets: TimelineDataset[]) => void; selectedDay: Date | null; selectedCompareDay: Date | null; - mapBasemapId: BasemapId; - setBasemapId: (id: BasemapId) => void; - labelsOption: boolean; - boundariesOption: boolean; - onOptionChange: (option: string, value: boolean) => void; - onStyleUpdate: (style: any) => void; - projection: ProjectionOptions; - setProjection: (projection: ProjectionOptions) => void; } -export function ExplorationMap({ - datasets, - selectedDay, - selectedCompareDay, - mapBasemapId, - setBasemapId, - labelsOption, - boundariesOption, - onOptionChange, - onStyleUpdate, - projection, - setProjection -}: ExplorationMapProps) { +export function ExplorationMap(props: ExplorationMapProps) { + const { datasets, setDatasets, selectedDay, selectedCompareDay } = props; + + const [projection, setProjection] = + useState(projectionDefault); + + const { + mapBasemapId, + setBasemapId, + labelsOption, + boundariesOption, + onOptionChange + } = useBasemap(); + + useReconcileWithStacMetadata(datasets, setDatasets); + + // Different datasets may have a different default projection. + // When datasets are selected the first time, we set the map projection to the + // first dataset's projection. + const prevDatasetCount = usePreviousValue(datasets.length); + useEffect(() => { + if (!prevDatasetCount && datasets.length > 0) { + setProjection(datasets[0].data.projection ?? projectionDefault); + } + }, [datasets, prevDatasetCount]); + + // If all the datasets are changed through the modal, we also want to update + // the map projection, since it is as if the datasets were selected for the + // first time. + // The only case where we don't want to update the projection is when not all + // datasets are changed, because it is not possible to know which projection + // to use. + const prevDatasetsIds = usePreviousValue(datasets.map((d) => d.data.id)); + useEffect(() => { + if (!prevDatasetsIds) return; + + const newDatasetsIds = datasets.map((d) => d.data.id); + const hasSameId = newDatasetsIds.some((id) => prevDatasetsIds.includes(id)); + + if (!hasSameId && datasets.length > 0) { + setProjection(datasets[0].data.projection ?? projectionDefault); + } + }, [prevDatasetsIds, datasets]); + + const comparing = !!selectedCompareDay; + // Reverse the datasets order to have the "top" layer, list-wise, at the "top" layer, z-order wise // Disabled eslint rule as slice() creates a shallow copy // eslint-disable-next-line fp/no-mutating-methods @@ -60,25 +91,54 @@ export function ExplorationMap({ .slice() .reverse(); - const comparing = !!selectedCompareDay; + const onStyleUpdate = useCallback( + (style: ExtendedStyle) => { + const updatedDatasets = datasets.map((dataset) => { + // Skip non loaded datasets + if (dataset.status !== TimelineDatasetStatus.SUCCESS) return dataset; + + // Check if there's layer information for this dataset. + const layerMetadata = style.layers.find( + (l) => l.metadata?.id === dataset.data.id + ); + + // Skip if no metadata. + if (!layerMetadata) return dataset; + + const currentMeta = dataset.meta ?? {}; + + return { + ...dataset, + meta: { + ...currentMeta, + tileUrls: { + wmtsTileUrl: layerMetadata.metadata.wmtsTileUrl, + xyzTileUrl: layerMetadata.metadata.xyzTileUrl + } + } + }; + }); + + setDatasets(updatedDatasets); + }, + [datasets, setDatasets] + ); return ( + {/* Map layers */} - {selectedDay && - loadedDatasets.map((dataset, idx) => ( - - ))} + {selectedDay && ( + + )} + {/* Map controls */} {comparing && ( + // Compare map layers - {selectedDay && - loadedDatasets.map((dataset, idx) => ( - - ))} + {selectedDay && ( + + )} )} ); } + +interface ExplorationMapsLayerProps { + datasets: TimelineDatasetSuccess[]; + selectedDay: Date; + idSuffix?: string; +} + +export function ExplorationMapLayers(props: ExplorationMapsLayerProps) { + const { datasets, selectedDay, idSuffix = '' } = props; + + return ( + <> + {datasets.map((dataset, idx) => ( + + ))} + + ); +} diff --git a/app/scripts/components/exploration/components/timeline/timeline.tsx b/app/scripts/components/exploration/components/timeline/timeline.tsx index ee6e9cb50..7714ed332 100644 --- a/app/scripts/components/exploration/components/timeline/timeline.tsx +++ b/app/scripts/components/exploration/components/timeline/timeline.tsx @@ -1,5 +1,8 @@ import { Button } from '@devseed-ui/button'; -import { CollecticonIsoStack, CollecticonPlusSmall } from '@devseed-ui/collecticons'; +import { + CollecticonIsoStack, + CollecticonPlusSmall +} from '@devseed-ui/collecticons'; import { glsp, themeVal } from '@devseed-ui/theme-provider'; import { Heading } from '@devseed-ui/typography'; import { select, zoom } from 'd3'; @@ -26,7 +29,11 @@ import styled from 'styled-components'; import { DatasetList } from '../datasets/dataset-list'; import { applyTransform, isEqualTransform, rescaleX } from './timeline-utils'; -import { TimelineControls, getInitialScale, TimelineDateAxis } from './timeline-controls'; +import { + TimelineControls, + getInitialScale, + TimelineDateAxis +} from './timeline-controls'; import { TimelineHeadIn, TimelineHeadPoint, @@ -35,12 +42,7 @@ import { } from './timeline-head'; import { DateGrid } from './date-axis'; -import { - selectedCompareDateAtom, - selectedDateAtom, - selectedIntervalAtom -} from '$components/exploration/atoms/dates'; -import { timelineDatasetsAtom } from '$components/exploration/atoms/datasets'; +import { selectedIntervalAtom } from '$components/exploration/atoms/dates'; import { timelineSizesAtom, timelineWidthAtom, @@ -58,6 +60,7 @@ import { } from '$components/exploration/hooks/scales-hooks'; import { useOnTOIZoom } from '$components/exploration/hooks/use-toi-zoom'; import { + TimelineDataset, TimelineDatasetStatus, TimelineDatasetSuccess, ZoomTransformPlain @@ -130,12 +133,14 @@ const EmptyTimelineContentInner = styled.div` position: relative; `; -const TimelineContentInner = styled(EmptyTimelineContentInner)<{panelHeight: number}>` +const TimelineContentInner = styled(EmptyTimelineContentInner)<{ + panelHeight: number; +}>` overflow-y: scroll; /* @TECH-DEBT: A hack to target only Safari Safari needs a specific height to make the contents scrollable */ @supports (font: -apple-system-body) and (-webkit-appearance: none) { - height: calc(${(props)=> 100 - props.panelHeight}vh - 130px); + height: calc(${(props) => 100 - props.panelHeight}vh - 130px); } `; @@ -150,14 +155,19 @@ const LayerActionBox = styled.div` align-items: center; } svg { - fill: ${themeVal('color.base-300')} + fill: ${themeVal('color.base-300')}; } `; const TimelineHeading = styled(Heading)` - font-size: 0.875rem; + font-size: 0.875rem; `; interface TimelineProps { + datasets: TimelineDataset[]; + selectedDay: Date | null; + setSelectedDay: (d: Date | null) => void; + selectedCompareDay: Date | null; + setSelectedCompareDay: (d: Date | null) => void; onDatasetAddClick: () => void; panelHeight: number; } @@ -175,7 +185,15 @@ const getIntervalFromDate = (selectedDay: Date, dataDomain: [Date, Date]) => { }; export default function Timeline(props: TimelineProps) { - const { onDatasetAddClick, panelHeight } = props; + const { + datasets, + selectedDay, + setSelectedDay, + selectedCompareDay, + setSelectedCompareDay, + onDatasetAddClick, + panelHeight + } = props; // Refs for non react based interactions. // The interaction rect is used to capture the different d3 events for the @@ -185,8 +203,6 @@ export default function Timeline(props: TimelineProps) { // container to propagate the needed events to it, like scroll. const datasetsContainerRef = useRef(null); - const datasets = useAtomValue(timelineDatasetsAtom); - const dataDomain = useTimelineDatasetsDomain(); // Observe the width of the timeline wrapper and store it. @@ -200,10 +216,6 @@ export default function Timeline(props: TimelineProps) { const { contentWidth: width } = useAtomValue(timelineSizesAtom); - const [selectedDay, setSelectedDay] = useAtom(selectedDateAtom); - const [selectedCompareDay, setSelectedCompareDay] = useAtom( - selectedCompareDateAtom - ); const [selectedInterval, setSelectedInterval] = useAtom(selectedIntervalAtom); const { setObsolete, runAnalysis, isAnalyzing } = useAnalysisController(); @@ -386,7 +398,7 @@ export default function Timeline(props: TimelineProps) { } }, [initializeTOIZoom, zoomBehavior, interactionRef]); - const onControlsZoom = useCallback( + const onControlsZoom = useCallback( (zoomV) => { if (!interactionRef.current || !xMain || !xScaled || !selectedDay) return; @@ -529,9 +541,7 @@ export default function Timeline(props: TimelineProps) { const CommonTimelineHeadline = () => { return ( - - Data layers - + Data layers - ); + + ); }; // Stub scale for when there is no layers - const initialScale = useMemo(() => getInitialScale(width) ,[width]); + const initialScale = useMemo(() => getInitialScale(width), [width]); // Some of these values depend on each other, but we check all of them so // typescript doesn't complain. if (datasets.length === 0) { return ( - - - {CommonTimelineHeadline()} - - - + + {CommonTimelineHeadline()} + + - - - - -
- -

No data layer added to the map!

- -
-
-
+ + + + +
+ +

No data layer added to the map!

+ +
+
+
); @@ -584,7 +596,7 @@ export default function Timeline(props: TimelineProps) { /> - {CommonTimelineHeadline()} + {CommonTimelineHeadline()} )} - + diff --git a/app/scripts/components/exploration/container.tsx b/app/scripts/components/exploration/container.tsx new file mode 100644 index 000000000..0c73a0ab4 --- /dev/null +++ b/app/scripts/components/exploration/container.tsx @@ -0,0 +1,49 @@ +import React from 'react'; +import { TourProvider } from '@reactour/tour'; +import { DevTools } from 'jotai-devtools'; +import { useAtom } from 'jotai'; +import { PopoverTourComponent, TourManager } from './tour-manager'; +import { timelineDatasetsAtom } from './atoms/datasets'; +import ExplorationAndAnalysis from '.'; +import { PageMainContent } from '$styles/page'; +import { LayoutProps } from '$components/common/layout-root'; +import PageHero from '$components/common/page-hero'; + +/** + * @VEDA2-REFACTOR-WORK + * + * @NOTE: This container component serves as a wrapper for the purpose of data management, this is ONLY to support current instances. + * veda2 instances can just use the direct component, 'Exploration', and manage data directly in their page views + */ + +const tourProviderStyles = { + popover: (base) => ({ + ...base, + padding: '0', + background: 'none' + }) +}; + +export default function ExplorationAndAnalysisContainer() { + const [datasets, setDatasets] = useAtom(timelineDatasetsAtom); + + return ( + + + + + + + + + + ); +} diff --git a/app/scripts/components/exploration/index.tsx b/app/scripts/components/exploration/index.tsx index e672d4ff5..d50b55512 100644 --- a/app/scripts/components/exploration/index.tsx +++ b/app/scripts/components/exploration/index.tsx @@ -1,29 +1,17 @@ import React, { useCallback, useEffect, useState } from 'react'; import { Panel, PanelGroup, PanelResizeHandle } from 'react-resizable-panels'; import styled from 'styled-components'; -import { useAtomValue, useSetAtom } from 'jotai'; -import { TourProvider } from '@reactour/tour'; import { themeVal } from '@devseed-ui/theme-provider'; -import { DevTools } from 'jotai-devtools'; -import { TimelineDatasetStatus } from './types.d.ts'; +import { useAtom, useSetAtom } from 'jotai'; import Timeline from './components/timeline/timeline'; import { ExplorationMap } from './components/map'; import { DatasetSelectorModal } from './components/dataset-selector-modal'; -import { timelineDatasetsAtom } from './atoms/datasets'; -import { PopoverTourComponent, TourManager } from './tour-manager'; import { useAnalysisController } from './hooks/use-analysis-data-request'; -import { useReconcileWithStacMetadata } from './hooks/use-stac-metadata-datasets'; +import { TimelineDataset } from './types.d.ts'; import { selectedCompareDateAtom, selectedDateAtom } from './atoms/dates'; import { CLEAR_LOCATION, urlAtom } from '$utils/params-location-atom/url'; -import { LayoutProps } from '$components/common/layout-root'; -import PageHero from '$components/common/page-hero'; -import { PageMainContent } from '$styles/page'; -import { usePreviousValue } from '$utils/use-effect-previous'; -import { projectionDefault } from '$components/common/map/controls/map-options/projections'; -import { useBasemap } from '$components/common/map/controls/hooks/use-basemap'; - const Container = styled.div` display: flex; flex-flow: column; @@ -66,99 +54,24 @@ const Container = styled.div` } `; -const tourProviderStyles = { - popover: (base) => ({ - ...base, - padding: '0', - background: 'none' - }) -}; - -function Exploration() { - const [projection, setProjection] = useState(projectionDefault); - - const { - mapBasemapId, - setBasemapId, - labelsOption, - boundariesOption, - onOptionChange - } = useBasemap(); - - const datasets = useAtomValue(timelineDatasetsAtom); - const setDatasets = useSetAtom(timelineDatasetsAtom); - - useReconcileWithStacMetadata(datasets, setDatasets); - - const selectedDay = useAtomValue(selectedDateAtom); - const selectedCompareDay = useAtomValue(selectedCompareDateAtom); - - // Different datasets may have a different default projection. - // When datasets are selected the first time, we set the map projection to the - // first dataset's projection. - const prevDatasetCount = usePreviousValue(datasets.length); - - useEffect(() => { - if (!prevDatasetCount && datasets.length > 0) { - setProjection(datasets[0].data.projection ?? projectionDefault); - } - }, [datasets, prevDatasetCount]); - - // If all the datasets are changed through the modal, we also want to update - // the map projection, since it is as if the datasets were selected for the - // first time. - // The only case where we don't want to update the projection is when not all - // datasets are changed, because it is not possible to know which projection - // to use. - const prevDatasetsIds = usePreviousValue(datasets.map((d) => d.data.id)); +interface ExplorationAndAnalysisProps { + datasets: TimelineDataset[]; + setDatasets: (datasets: TimelineDataset[]) => void; +} - useEffect(() => { - if (!prevDatasetsIds) return; +function ExplorationAndAnalysis(props: ExplorationAndAnalysisProps) { + const { datasets, setDatasets } = props; - const newDatasetsIds = datasets.map((d) => d.data.id); - const hasSameId = newDatasetsIds.some((id) => prevDatasetsIds.includes(id)); + const [selectedDay, setSelectedDay] = useAtom(selectedDateAtom); - if (!hasSameId && datasets.length > 0) { - setProjection(datasets[0].data.projection ?? projectionDefault); - } - }, [prevDatasetsIds, datasets]); - - const onStyleUpdate = useCallback( - (style) => { - const updatedDatasets = datasets.map((dataset) => { - // Skip non loaded datasets - if (dataset.status !== TimelineDatasetStatus.SUCCESS) return dataset; - - // Check if there's layer information for this dataset. - const layerMetadata = style.layers.find( - (l) => l.metadata?.id === dataset.data.id - ); - - // Skip if no metadata. - if (!layerMetadata) return dataset; - - const currentMeta = dataset.meta ?? {}; - - return { - ...dataset, - meta: { - ...currentMeta, - tileUrls: { - wmtsTileUrl: layerMetadata.metadata.wmtsTileUrl, - xyzTileUrl: layerMetadata.metadata.xyzTileUrl - } - } - }; - }); - - setDatasets(updatedDatasets); - }, - [datasets, setDatasets] + const [selectedCompareDay, setSelectedCompareDay] = useAtom( + selectedCompareDateAtom ); const [datasetModalRevealed, setDatasetModalRevealed] = useState( !datasets.length ); + // @TECH-DEBT: panelHeight needs to be passed to work around Safari CSS const [panelHeight, setPanelHeight] = useState(0); @@ -167,67 +80,50 @@ function Exploration() { const setUrl = useSetAtom(urlAtom); const { reset: resetAnalysisController } = useAnalysisController(); + // Reset atoms when leaving the page. useEffect(() => { return () => { resetAnalysisController(); setUrl(CLEAR_LOCATION); }; - }, []); + }, [resetAnalysisController, setUrl]); return ( - - - + + { + setPanelHeight(size); + }} + > + + + + + + + + - - - - - - { - setPanelHeight(size); - }} - > - - - - - - - - - - - + ); } -export default Exploration; +export default ExplorationAndAnalysis; diff --git a/app/scripts/components/sandbox/exploration-map/index.js b/app/scripts/components/sandbox/exploration-map/index.js index c0eb23c7a..4b58b988e 100644 --- a/app/scripts/components/sandbox/exploration-map/index.js +++ b/app/scripts/components/sandbox/exploration-map/index.js @@ -4,7 +4,7 @@ import Constrainer from '$styles/constrainer'; import { PageMainContent } from '$styles/page'; import { ExplorationMap } from '$components/exploration/components/map'; -const DemoMap = styled(ExplorationMap)` +const DemoExplorationMap = styled(ExplorationMap)` height: 40rem; `; @@ -90,35 +90,20 @@ const mockDatasets = [ } ]; -const mockSelectedDay = new Date('2017-11-30T23:00:00.000Z'); +const mockSelectedDay = new Date('2024-05-20T13:00:00.000Z'); const mockSelectedCompareDay = null; -const mockMapBasemapId = 'satellite'; -const mockLabelsOption = true; -const mockBoundariesOption = true; -const mockProjection = { id: 'equirectangular' }; - -const mockOnOptionChange = () => {}; -const mockOnStyleUpdate = () => {}; -const mockSetProjection = () => {}; -const mockSetBasemapId = () => {}; +const mockSetDatasets = () => {}; function SandboxExplorationMap() { return ( - diff --git a/app/scripts/main.tsx b/app/scripts/main.tsx index 23ac06bb9..c38c6dd78 100644 --- a/app/scripts/main.tsx +++ b/app/scripts/main.tsx @@ -34,7 +34,9 @@ const DatasetsOverview = lazy(() => import('$components/datasets/s-overview')); const Analysis = lazy(() => import('$components/analysis/define')); const AnalysisResults = lazy(() => import('$components/analysis/results')); -const Exploration = lazy(() => import('$components/exploration')); +const ExplorationAndAnalysis = lazy( + () => import('$components/exploration/container') +); const Sandbox = lazy(() => import('$components/sandbox')); @@ -120,7 +122,10 @@ function Root() { } /> {/* {useNewExploration && ( */} - } /> + } + /> {/* )} */} {process.env.NODE_ENV !== 'production' && ( From a8ce39f3b476187dfa122ccf13b6672d52f53fdf Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Thu, 23 May 2024 09:13:26 +0200 Subject: [PATCH 3/5] Update comment --- app/scripts/components/exploration/container.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/scripts/components/exploration/container.tsx b/app/scripts/components/exploration/container.tsx index 0c73a0ab4..ad4ad1fd8 100644 --- a/app/scripts/components/exploration/container.tsx +++ b/app/scripts/components/exploration/container.tsx @@ -13,7 +13,7 @@ import PageHero from '$components/common/page-hero'; * @VEDA2-REFACTOR-WORK * * @NOTE: This container component serves as a wrapper for the purpose of data management, this is ONLY to support current instances. - * veda2 instances can just use the direct component, 'Exploration', and manage data directly in their page views + * veda2 instances can just use the direct component, 'ExplorationAndAnalysis', and manage data directly in their page views */ const tourProviderStyles = { From 53df7813b1f9495861a7e6a6e550c8e0d8b52d07 Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Thu, 23 May 2024 11:03:23 +0200 Subject: [PATCH 4/5] Fix naming typo --- app/scripts/components/exploration/components/map/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/scripts/components/exploration/components/map/index.tsx b/app/scripts/components/exploration/components/map/index.tsx index c9aa21300..a5a38aa13 100644 --- a/app/scripts/components/exploration/components/map/index.tsx +++ b/app/scripts/components/exploration/components/map/index.tsx @@ -183,13 +183,13 @@ export function ExplorationMap(props: ExplorationMapProps) { ); } -interface ExplorationMapsLayerProps { +interface ExplorationMapLayersProps { datasets: TimelineDatasetSuccess[]; selectedDay: Date; idSuffix?: string; } -export function ExplorationMapLayers(props: ExplorationMapsLayerProps) { +export function ExplorationMapLayers(props: ExplorationMapLayersProps) { const { datasets, selectedDay, idSuffix = '' } = props; return ( From 52ee6f72d9cace383c814055f3de2ae1ba7576fb Mon Sep 17 00:00:00 2001 From: Gjore Milevski Date: Fri, 24 May 2024 15:00:40 +0200 Subject: [PATCH 5/5] Fill mocked sandbox dataset --- .../sandbox/exploration-map/index.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/app/scripts/components/sandbox/exploration-map/index.js b/app/scripts/components/sandbox/exploration-map/index.js index 4b58b988e..49e3c57eb 100644 --- a/app/scripts/components/sandbox/exploration-map/index.js +++ b/app/scripts/components/sandbox/exploration-map/index.js @@ -16,7 +16,7 @@ const Wrapper = styled.div` const mockDatasets = [ { - status: 'loading', + status: 'success', data: { id: 'casa-gfed-co2-flux-hr', stacApiEndpoint: 'https://ghg.center/api/stac', @@ -27,7 +27,9 @@ const mockDatasets = [ description: 'Model-estimated heterotrophic respiration (Rh), which is the flux of carbon from the soil to the atmosphere', initialDatetime: 'newest', - projection: { id: 'equirectangular' }, + projection: { + id: 'equirectangular' + }, basemapId: 'light', zoomExtent: [0, 20], sourceParams: { @@ -40,7 +42,9 @@ const mockDatasets = [ layerId: 'casa-gfed-co2-flux-hr' }, legend: { - unit: { label: 'kg Carbon/m²/mon' }, + unit: { + label: 'kg Carbon/m²/mon' + }, type: 'gradient', min: 0, max: 0.3, @@ -60,7 +64,14 @@ const mockDatasets = [ parentDataset: { id: 'casagfed-carbonflux-monthgrid-v3', name: 'CASA-GFED3 Land Carbon Flux' - } + }, + isPeriodic: true, + timeDensity: 'month', + domain: [ + '2017-09-30T22:00:00.000Z', + '2017-10-31T23:00:00.000Z', + '2017-11-30T23:00:00.000Z' + ] }, error: null, settings: { @@ -83,14 +94,14 @@ const mockDatasets = [ }, analysis: { status: 'idle', - data: null, error: null, + data: null, meta: {} } } ]; -const mockSelectedDay = new Date('2024-05-20T13:00:00.000Z'); +const mockSelectedDay = new Date('2017-12-01T00:00:00.000Z'); const mockSelectedCompareDay = null; const mockSetDatasets = () => {};