diff --git a/app/scripts/components/common/map/controls/coords.tsx b/app/scripts/components/common/map/controls/coords.tsx
new file mode 100644
index 000000000..0e5bc2f39
--- /dev/null
+++ b/app/scripts/components/common/map/controls/coords.tsx
@@ -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(
+ () => (
+
+
+ {({ ref, showCopiedMsg }) => (
+
+ )}
+
+
+ ),
+ { position: 'bottom-left' }
+ );
+
+ return null;
+}
diff --git a/app/scripts/components/common/map/controls/hooks/use-themed-control.tsx b/app/scripts/components/common/map/controls/hooks/use-themed-control.tsx
new file mode 100644
index 000000000..f2bf5a90c
--- /dev/null
+++ b/app/scripts/components/common/map/controls/hooks/use-themed-control.tsx
@@ -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(null);
+ const rootRef = useRef | 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(
+ {renderFn() as any}
+ );
+
+ 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(
+ {renderFn() as any}
+ );
+ }
+ }, [renderFn, theme]);
+ useControl(() => new ThemedControl(), opts);
+ return null;
+}
diff --git a/app/scripts/components/common/map/hooks/use-map-compare.ts b/app/scripts/components/common/map/hooks/use-map-compare.ts
index 7cb4d238f..b5d17b732 100644
--- a/app/scripts/components/common/map/hooks/use-map-compare.ts
+++ b/app/scripts/components/common/map/hooks/use-map-compare.ts
@@ -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]);
-}
\ No newline at end of file
+}
diff --git a/app/scripts/components/common/map/hooks/use-maps.ts b/app/scripts/components/common/map/hooks/use-maps.ts
index add233002..56f877e2b 100644
--- a/app/scripts/components/common/map/hooks/use-maps.ts
+++ b/app/scripts/components/common/map/hooks/use-maps.ts
@@ -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();
diff --git a/app/scripts/components/common/map/maps.tsx b/app/scripts/components/common/map/maps.tsx
index 32070ea57..6872c3214 100644
--- a/app/scripts/components/common/map/maps.tsx
+++ b/app/scripts/components/common/map/maps.tsx
@@ -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, {
diff --git a/app/scripts/components/exploration/index.tsx b/app/scripts/components/exploration/index.tsx
index 0ae490aa9..dd990d943 100644
--- a/app/scripts/components/exploration/index.tsx
+++ b/app/scripts/components/exploration/index.tsx
@@ -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;
@@ -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), []);
@@ -82,6 +83,7 @@ function Exploration() {
+
{compare && (