Skip to content

Commit

Permalink
Added coords and a generic hook to render a custom control
Browse files Browse the repository at this point in the history
  • Loading branch information
nerik committed Sep 11, 2023
1 parent 9130bc9 commit d2ec945
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 20 deletions.
77 changes: 77 additions & 0 deletions app/scripts/components/common/map/controls/coords.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useEffect, useState } from 'react';
import { MapRef } from 'react-map-gl';
import styled from 'styled-components';
import { Button } from '@devseed-ui/button';
import { themeVal } from '@devseed-ui/theme-provider';
import useMaps from '../hooks/use-maps';
import useThemedControl from './hooks/use-themed-control';
import { round } from '$utils/format';
import { CopyField } from '$components/common/copy-field';

const MapCoordsWrapper = styled.div`
/* Large width so parent will wrap */
width: 100vw;
${Button} {
background: ${themeVal('color.base-400a')};
font-weight: ${themeVal('type.base.regular')};
font-size: 0.75rem;
}
&& ${Button /* sc-selector */}:hover {
background: ${themeVal('color.base-500')};
}
`;

const getCoords = (mapInstance?: MapRef) => {
if (!mapInstance) return { lng: 0, lat: 0 };
const mapCenter = mapInstance.getCenter();
return {
lng: round(mapCenter.lng, 4),
lat: round(mapCenter.lat, 4)
};
};

export default function MapCoords() {
const { main } = useMaps();

const [position, setPosition] = useState(getCoords(main));

useEffect(() => {
const posListener = (e) => {
setPosition(getCoords(e.target));
};

if (main) main.on('moveend', posListener);

return () => {
if (main) main.off('moveend', posListener);
};
}, [main]);

const { lng, lat } = position;
const value = `W ${lng}, N ${lat}`;

useThemedControl(
() => (
<MapCoordsWrapper>
<CopyField value={value}>
{({ ref, showCopiedMsg }) => (
<Button
ref={ref}
// @ts-expect-error achromic-text exists but ui-library types are
// not up to date
variation='achromic-text'
size='small'
>
{showCopiedMsg ? 'Copied!' : value}
</Button>
)}
</CopyField>
</MapCoordsWrapper>
),
{ position: 'bottom-left' }
);

return null;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { IControl } from 'mapbox-gl';
import React, { ReactNode, useEffect, useRef } from 'react';
import { createRoot } from 'react-dom/client';
import { useControl } from 'react-map-gl';
import { useTheme, ThemeProvider } from 'styled-components';

export default function useThemedControl(
renderFn: () => ReactNode,
opts?: any
) {
const theme = useTheme();
const elementRef = useRef<HTMLDivElement | null>(null);
const rootRef = useRef<ReturnType<typeof createRoot> | null>(null);

// Define the control methods and its lifecycle
class ThemedControl implements IControl {
onAdd() {
const el = document.createElement('div');
el.className = 'mapboxgl-ctrl';
elementRef.current = el;

// Create a root and render the component
rootRef.current = createRoot(el);

rootRef.current.render(
<ThemeProvider theme={theme}>{renderFn() as any}</ThemeProvider>
);

return el;
}

onRemove() {
// Cleanup if necessary
if (elementRef.current) {
rootRef.current?.unmount();
}
}
}

// Listen for changes in dependencies and re-render if necessary
useEffect(() => {
if (rootRef.current) {
rootRef.current.render(
<ThemeProvider theme={theme}>{renderFn() as any}</ThemeProvider>
);
}
}, [renderFn, theme]);
useControl(() => new ThemedControl(), opts);
return null;
}
30 changes: 13 additions & 17 deletions app/scripts/components/common/map/hooks/use-map-compare.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,25 @@
import { useContext, useEffect } from "react";
import { useContext, useEffect } from 'react';
import MapboxCompare from 'mapbox-gl-compare';
import { MapsContext } from "../maps";
import { useMaps } from "./use-maps";
import { MapsContext } from '../maps';
import useMaps from './use-maps';

export default function useMapCompare() {
const maps = useMaps();
const { main, compared } = useMaps();
const { containerId } = useContext(MapsContext);
const hasMapCompare = !!maps.compared;
const hasMapCompare = !!compared;
useEffect(() => {
if (!maps.main) return;
if (!main) return;

if (maps.compared) {
const compare = new MapboxCompare(
maps.main,
maps.compared,
`#${containerId}`,
{
mousemove: false,
orientation: 'vertical'
}
);
if (compared) {
const compare = new MapboxCompare(main, compared, `#${containerId}`, {
mousemove: false,
orientation: 'vertical'
});

return () => {
compare.remove();
};
}
// main should be stable, while we are only interested here in the absence or presence of compared
}, [containerId, hasMapCompare]);
}
}
2 changes: 1 addition & 1 deletion app/scripts/components/common/map/hooks/use-maps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { useContext } from 'react';
import { useMap } from 'react-map-gl';
import { MapsContext } from '../maps';

export function useMaps() {
export default function useMaps() {
const { mainId, comparedId } = useContext(MapsContext);
const maps = useMap();

Expand Down
2 changes: 1 addition & 1 deletion app/scripts/components/common/map/maps.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import MapboxStyleOverride from './mapbox-style-override';
import { Styles } from './styles';
import useMapCompare from './hooks/use-map-compare';
import MapComponent from './map-component';
import { useMaps } from './hooks/use-maps';
import useMaps from './hooks/use-maps';

const chevronRightURI = () =>
iconDataURI(CollecticonChevronRightSmall, {
Expand Down
4 changes: 3 additions & 1 deletion app/scripts/components/exploration/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Map, { Compare } from '$components/common/map';
import { Basemap } from '$components/common/map/style-generators/basemap';
import GeocoderControl from '$components/common/map/controls/geocoder';
import { NavigationControl, ScaleControl } from '$components/common/map/controls';
import Coords from '$components/common/map/controls/coords';

const Container = styled.div`
display: flex;
Expand Down Expand Up @@ -58,7 +59,7 @@ const Container = styled.div`
`;

function Exploration() {
const [compare, setCompare] = useState(false);
const [compare, setCompare] = useState(true);
const [datasetModalRevealed, setDatasetModalRevealed] = useState(true);

const openModal = useCallback(() => setDatasetModalRevealed(true), []);
Expand All @@ -82,6 +83,7 @@ function Exploration() {
<GeocoderControl />
<NavigationControl />
<ScaleControl />
<Coords />
{compare && (
<Compare>
<Basemap basemapStyleId='dark' />
Expand Down

0 comments on commit d2ec945

Please sign in to comment.