Skip to content

Commit

Permalink
[1156] Expose a Veda UI EnvConfigProvider for providing env variables (
Browse files Browse the repository at this point in the history
…#1253)

**Related Ticket:** #1166

### Problem

We need a way for future Next.js instances using veda-ui components to
pass their environment variables to the library

[The previous PR](#1252)
tried to pass env variables as props to our components. However, this
might be difficult to maintain and to scale as new environment variables
will be added.

This PR introduces a centralized config place through a new
`VedauiConfigProvider` context to allow devs to configure all
environment variables in one place via a config object.

### Description of Changes

**Veda-UI changes**

- Created `VedauiConfigProvider` context provider
- Currently supports these configuration options:
  - `mapboxToken` ( `MAPBOX_TOKEN`)
  - `apiRasterEndpoint` (`API_RASTER_ENDPOINT`)
  - `apiStacEndpoint` (from `API_STAC_ENDPOINT`)
- Refactored internal components to consume the environment variables
from this context

**Next.js ([PR
here](developmentseed/next-veda-ui#13

- Imported and used the new provider
- ℹ️ In Next.js, environment variables should be named according to the
Next.js naming standards so that they can be applied e.g `MAPBOX_TOKEN`
should be `NEXT_PUBLIC_MAPBOX_TOKEN` etc.

### Notes & Questions About Changes
_{Add additonal notes and outstanding questions here related to changes
in this pull request}_

### Validation / Testing

Test that the correct env variables are applied when using the library
in Next.js
  • Loading branch information
dzole0311 authored Nov 19, 2024
2 parents a8311c2 + 0b13f55 commit ad47c20
Show file tree
Hide file tree
Showing 20 changed files with 322 additions and 163 deletions.
65 changes: 42 additions & 23 deletions app/scripts/components/common/blocks/block-map.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useMemo, useState, useEffect } from 'react';
import React, { useMemo, useState, useEffect, useContext } from 'react';
import styled from 'styled-components';
import { MapboxOptions } from 'mapbox-gl';
import * as dateFns from 'date-fns';
Expand All @@ -11,10 +11,7 @@ import { Basemap } from '../map/style-generators/basemap';
import { LayerLegend, LayerLegendContainer } from '../map/layer-legend';
import MapCoordsControl from '../map/controls/coords';
import MapMessage from '../map/map-message';
import {
formatCompareDate,
formatSingleDate,
} from '../map/utils';
import { formatCompareDate, formatSingleDate } from '../map/utils';
import {
BasemapId,
DEFAULT_MAP_STYLE_URL
Expand All @@ -34,9 +31,13 @@ import {
DatasetStatus
} from '$components/exploration/types.d.ts';

import { reconcileDatasets, getDatasetLayers } from '$components/exploration/data-utils-no-faux-module';
import {
reconcileDatasets,
getDatasetLayers
} from '$components/exploration/data-utils-no-faux-module';
import { useReconcileWithStacMetadata } from '$components/exploration/hooks/use-stac-metadata-datasets';
import { ProjectionOptions, VedaDatum, DatasetData } from '$types/veda';
import { EnvConfigContext } from '$context/env-config';

export const mapHeight = '32rem';
const Carto = styled.div`
Expand Down Expand Up @@ -125,7 +126,10 @@ interface MapBlockProps {
layerId: string;
}

const getDataLayer = (layerIndex: number, layers: VizDataset[] | undefined): (VizDatasetSuccess | null) => {
const getDataLayer = (
layerIndex: number,
layers: VizDataset[] | undefined
): VizDatasetSuccess | null => {
if (!layers || layers.length <= layerIndex) return null;
const layer = layers[layerIndex];
// @NOTE: What to do when data returns ERROR
Expand Down Expand Up @@ -177,16 +181,18 @@ function MapBlock(props: MapBlockProps) {
totalLayers = [...totalLayers, compareMapStaticData];
}
return totalLayers;
},[layerId]);
}, [layerId]);

const [layers, setLayers] = useState<VizDataset[]>(layersToFetch);

useReconcileWithStacMetadata(layers, setLayers);
const { envApiStacEndpoint } = useContext(EnvConfigContext);

useReconcileWithStacMetadata(layers, setLayers, envApiStacEndpoint);

const selectedDatetime: (Date | undefined) = dateTime
const selectedDatetime: Date | undefined = dateTime
? utcString2userTzDate(dateTime)
: undefined;
const selectedCompareDatetime: (Date | undefined) = compareDateTime
const selectedCompareDatetime: Date | undefined = compareDateTime
? utcString2userTzDate(compareDateTime)
: undefined;

Expand All @@ -210,8 +216,14 @@ function MapBlock(props: MapBlockProps) {

const [, setProjection] = useState(projectionStart);

const baseDataLayer: (VizDatasetSuccess | null) = useMemo(() => getDataLayer(0, layers), [layers]);
const compareDataLayer: (VizDatasetSuccess | null) = useMemo(() => getDataLayer(1, layers), [layers]);
const baseDataLayer: VizDatasetSuccess | null = useMemo(
() => getDataLayer(0, layers),
[layers]
);
const compareDataLayer: VizDatasetSuccess | null = useMemo(
() => getDataLayer(1, layers),
[layers]
);

const baseTimeDensity = baseDataLayer?.data.timeDensity;
const compareTimeDensity = compareDataLayer?.data.timeDensity;
Expand Down Expand Up @@ -262,9 +274,16 @@ function MapBlock(props: MapBlockProps) {
if (compareLabel) return compareLabel as string;
// Use label function from originalData.Compare
else if (baseDataLayer?.data.compare?.mapLabel) {
if (typeof baseDataLayer.data.compare.mapLabel === 'string') return baseDataLayer.data.compare.mapLabel;
const labelFn = baseDataLayer.data.compare.mapLabel as (unknown) => string;
return labelFn({dateFns, datetime: selectedDatetime, compareDatetime: compareToDate });
if (typeof baseDataLayer.data.compare.mapLabel === 'string')
return baseDataLayer.data.compare.mapLabel;
const labelFn = baseDataLayer.data.compare.mapLabel as (
unknown
) => string;
return labelFn({
dateFns,
datetime: selectedDatetime,
compareDatetime: compareToDate
});
}

// Default to date comparison.
Expand Down Expand Up @@ -356,13 +375,13 @@ function MapBlock(props: MapBlockProps) {
<Compare>
<Basemap basemapStyleId={mapBasemapId} />
{compareDataLayer && (
<Layer
key={compareDataLayer.data.id}
id={`compare-${compareDataLayer.data.id}`}
dataset={compareDataLayer}
selectedDay={selectedCompareDatetime}
/>
)}
<Layer
key={compareDataLayer.data.id}
id={`compare-${compareDataLayer.data.id}`}
dataset={compareDataLayer}
selectedDay={selectedCompareDatetime}
/>
)}
</Compare>
)}
</Map>
Expand Down
89 changes: 58 additions & 31 deletions app/scripts/components/common/blocks/scrollytelling/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import React, {
Children, ReactElement,
Children,
ReactElement,
useCallback,
useContext,
useEffect,
useMemo,
useRef,
Expand Down Expand Up @@ -33,12 +35,24 @@ import { useSlidingStickyHeaderProps } from '$components/common/layout-root/useS
import { HEADER_TRANSITION_DURATION } from '$utils/use-sliding-sticky-header';
import { Basemap } from '$components/common/map/style-generators/basemap';
import Map from '$components/common/map';
import { LayerLegend, LayerLegendContainer } from '$components/common/map/layer-legend';
import {
LayerLegend,
LayerLegendContainer
} from '$components/common/map/layer-legend';
import { Layer } from '$components/exploration/components/map/layer';
import { MapLoading } from '$components/common/loading-skeleton';
import { DatasetData, DatasetStatus, VizDataset, VizDatasetSuccess } from '$components/exploration/types.d.ts';
import {
DatasetData,
DatasetStatus,
VizDataset,
VizDatasetSuccess
} from '$components/exploration/types.d.ts';
import { useReconcileWithStacMetadata } from '$components/exploration/hooks/use-stac-metadata-datasets';
import { formatSingleDate, reconcileVizDataset } from '$components/common/map/utils';
import {
formatSingleDate,
reconcileVizDataset
} from '$components/common/map/utils';
import { EnvConfigContext } from '$context/env-config';

type ResolvedScrollyMapLayer = {
vizDataset: VizDatasetSuccess;
Expand Down Expand Up @@ -125,10 +139,10 @@ function getChapterLayerKey(ch: ScrollyChapter) {
*
* @param {array} chList List of chapters with related layers.
*/
function useMapLayersFromChapters(chList: ScrollyChapter[]): [
ResolvedScrollyMapLayer[],
string[]
] {
function useMapLayersFromChapters(
chList: ScrollyChapter[],
envApiStacEndpoint: string
): [ResolvedScrollyMapLayer[], string[]] {
// The layers are unique based on the dataset, layer id and datetime.
// First we filter out any scrollytelling block that doesn't have layer.
const uniqueChapterLayers = useMemo(() => {
Expand All @@ -141,39 +155,44 @@ function useMapLayersFromChapters(chList: ScrollyChapter[]): [
}, {});

return Object.values(unique);

}, [chList]);

// Create an array of datasetId & layerId pairs which we can easily validate when creating
// the layers array.
const uniqueLayerRefs = useMemo(() => {
return uniqueChapterLayers.map(({ datasetId, layerId }) => ({
datasetId,
layerId,
layerId
}));
}, [uniqueChapterLayers]);

// Validate that all layers are defined in the configuration.
// They must be defined in the configuration otherwise it is not possible to load them.
const reconciledVizDatasets = uniqueLayerRefs.map(({ datasetId, layerId }) => {
const layers = datasets[datasetId]?.data.layers;
const reconciledVizDatasets = uniqueLayerRefs.map(
({ datasetId, layerId }) => {
const layers = datasets[datasetId]?.data.layers;

const layer = layers?.find(
(l) => l.id === layerId
) as DatasetData | null;
const layer = layers?.find((l) => l.id === layerId) as DatasetData | null;

if (!layer) {
throw new Error(
`Layer [${layerId}] not found in dataset [${datasetId}]`
);
}
if (!layer) {
throw new Error(
`Layer [${layerId}] not found in dataset [${datasetId}]`
);
}

return reconcileVizDataset(layer);
});
return reconcileVizDataset(layer);
}
);

const [resolvedDatasetsWithStac, setResolvedDatasetsWithStac] = useState<VizDataset[]>([]);
const [resolvedDatasetsWithStac, setResolvedDatasetsWithStac] = useState<
VizDataset[]
>([]);

useReconcileWithStacMetadata(reconciledVizDatasets, setResolvedDatasetsWithStac);
useReconcileWithStacMetadata(
reconciledVizDatasets,
setResolvedDatasetsWithStac,
envApiStacEndpoint
);

// Each resolved layer will be an object with:
// layer: The resolved layerData
Expand Down Expand Up @@ -241,6 +260,8 @@ const MAP_OPTIONS = {
function Scrollytelling(props) {
const { children } = props;

const { envApiStacEndpoint } = useContext(EnvConfigContext);

const { isHeaderHidden, headerHeight, wrapperHeight } =
useSlidingStickyHeaderProps();

Expand All @@ -250,13 +271,16 @@ function Scrollytelling(props) {
// Extract the props from the chapters.
const chapterProps = useChapterPropsFromChildren(children);

const [resolvedLayers, resolvedStatus] =
useMapLayersFromChapters(chapterProps);
const [resolvedLayers, resolvedStatus] = useMapLayersFromChapters(
chapterProps,
envApiStacEndpoint
);

const [activeChapter, setActiveChapter] = useState<ScrollyChapter | null>(
null
);
const [projection, setProjection] = useState<ProjectionOptions>(projectionDefault);
const [projection, setProjection] =
useState<ProjectionOptions>(projectionDefault);

// All layers must be loaded, resolved, and added to the map before we
// initialize scrollama. This is needed because in a scrollytelling map we
Expand Down Expand Up @@ -334,9 +358,12 @@ function Scrollytelling(props) {
: // Otherwise it's the full header height.
wrapperHeight;

const activeChapterLayerData = activeChapterLayer ? activeChapterLayer.vizDataset.data : null;
const activeChapterLayerData = activeChapterLayer
? activeChapterLayer.vizDataset.data
: null;

const { description, id, name, legend, timeDensity } = activeChapterLayerData ?? {};
const { description, id, name, legend, timeDensity } =
activeChapterLayerData ?? {};

return (
<>
Expand Down Expand Up @@ -438,7 +465,7 @@ function Scrollytelling(props) {
...vizDataset,
settings: {
opacity: 100,
isVisible: !isHidden,
isVisible: !isHidden
}
}}
selectedDay={runtimeData.datetime ?? new Date()}
Expand All @@ -461,4 +488,4 @@ Scrollytelling.propTypes = {

export function ScrollytellingBlock(props) {
return <BlockErrorBoundary {...props} childToRender={Scrollytelling} />;
}
}
33 changes: 20 additions & 13 deletions app/scripts/components/common/map/controls/geocoder.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,33 @@
import { useCallback } from 'react';
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';


import { useControl } from 'react-map-gl';
import { getZoomFromBbox } from '$components/common/map/utils';

export default function GeocoderControl() {
const handleGeocoderResult = useCallback((map, geocoder) => ({ result }) => {
geocoder.clear();
geocoder._inputEl.blur();
// Pass arbiturary number for zoom if there is no bbox
const zoom = result.bbox? getZoomFromBbox(result.bbox): 14;
map.flyTo({
center: result.center,
zoom
});
}, []);
export default function GeocoderControl({
envMapboxToken
}: {
envMapboxToken: string;
}) {
const handleGeocoderResult = useCallback(
(map, geocoder) =>
({ result }) => {
geocoder.clear();
geocoder._inputEl.blur();
// Pass arbiturary number for zoom if there is no bbox
const zoom = result.bbox ? getZoomFromBbox(result.bbox) : 14;
map.flyTo({
center: result.center,
zoom
});
},
[]
);

useControl(
({ map }) => {
const geocoder = new MapboxGeocoder({
accessToken: process.env.MAPBOX_TOKEN,
accessToken: envMapboxToken,
marker: false,
collapsed: true,
// Because of Mapbox issue: https://github.com/mapbox/mapbox-gl-js/issues/12565
Expand Down
Loading

0 comments on commit ad47c20

Please sign in to comment.