Skip to content

Commit

Permalink
Include center coordinates control (#620)
Browse files Browse the repository at this point in the history
  • Loading branch information
danielfdsilva authored Aug 16, 2023
2 parents 2926bcd + 842bc08 commit 4dd2aae
Show file tree
Hide file tree
Showing 7 changed files with 199 additions and 23 deletions.
54 changes: 54 additions & 0 deletions app/scripts/components/common/copy-field.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useEffect, useRef, useState } from 'react';
import Clipboard from 'clipboard';

interface CopyFieldProps {
value: string;
children: (props: {
value: string;
ref: React.MutableRefObject<any>;
originalValue: string;
showCopiedMsg: boolean;
}) => JSX.Element;
}

export function CopyField(props: CopyFieldProps) {
const { value, children } = props;

const [showCopiedMsg, setShowCopiedMsg] = useState(false);
const triggerElement = useRef<any>();

const copyValue = useRef<string>(value);
copyValue.current = value;

useEffect(() => {
if (!triggerElement.current) throw new Error("ref for trigger element is not set");

let copiedMsgTimeout: NodeJS.Timeout | undefined;
const clipboard = new Clipboard(triggerElement.current, {
text: () => copyValue.current
});

clipboard.on('success', () => {
setShowCopiedMsg(true);
copiedMsgTimeout = setTimeout(() => {
setShowCopiedMsg(false);
}, 2000);
});

return () => {
clipboard.destroy();
if (copiedMsgTimeout) {
clearTimeout(copiedMsgTimeout);
}
};
}, []);

const val = showCopiedMsg ? 'Copied!' : value;

return children({
value: val,
ref: triggerElement,
originalValue: value,
showCopiedMsg
});
}
75 changes: 75 additions & 0 deletions app/scripts/components/common/mapbox/map-coords.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import React, { useEffect, useState } from 'react';
import styled from 'styled-components';
import { Map } from 'mapbox-gl';

import { themeVal } from '@devseed-ui/theme-provider';
import { Button } from '@devseed-ui/button';

import { CopyField } from '../copy-field';
import { round } from '$utils/format';

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')};
}
`;

interface MapCoordsProps {
mapInstance: Map;
}

const getCoords = (mapInstance: Map) => {
const mapCenter = mapInstance.getCenter();
return {
lng: round(mapCenter.lng, 4),
lat: round(mapCenter.lat, 4)
};
};

export default function MapCoords(props: MapCoordsProps) {
const { mapInstance } = props;

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

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

mapInstance.on('moveend', posListener);

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

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

return (
<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>
);
}
9 changes: 9 additions & 0 deletions app/scripts/components/common/mapbox/map.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { aoiCursorStyles, useMbDraw } from './aoi/mb-aoi-draw';
import MapOptions from './map-options';
import { useMapboxControl } from './use-mapbox-control';
import { convertProjectionToMapbox } from './map-options/utils';
import MapCoords from './map-coords';

import { useMapStyle } from './layers/styles';
import { BasemapId, Option } from './map-options/basemaps';
Expand Down Expand Up @@ -116,6 +117,12 @@ export function SimpleMap(props: SimpleMapProps): ReactElement {
onOptionChange
]);

const mapCoordsControl = useMapboxControl(() => {
if (!mapRef.current) return null;

return <MapCoords mapInstance={mapRef.current} />;
}, []);

const { style } = useMapStyle();

useEffect(() => {
Expand All @@ -130,6 +137,8 @@ export function SimpleMap(props: SimpleMapProps): ReactElement {

mapRef.current = mbMap;

mapRef.current.addControl(mapCoordsControl, 'bottom-left');

if (onProjectionChange && projection) {
mapRef.current.addControl(mapOptionsControl, 'top-left');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ const MapboxStyleOverride = css`
/* stylelint-enable no-descending-specificity */
.mapboxgl-ctrl-bottom-left {
flex-direction: row;
flex-flow: row wrap;
align-items: flex-end;
align-items: center;
}
Expand Down
9 changes: 7 additions & 2 deletions app/scripts/components/common/mapbox/use-mapbox-control.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,20 @@ import { ThemeProvider, useTheme } from 'styled-components';
* // Add the control to mapbox
* }
*/
export function useMapboxControl(renderFn, deps: any[] = []) {
export function useMapboxControl(
renderFn: (el: HTMLDivElement) => React.ReactNode,
deps: any[] = []
) {
const rootRef = useRef<Root>();
const elementRef = useRef<HTMLDivElement>();
const renderFnRef = useRef<() => void>(() => ({}));
const theme = useTheme();

// Use a ref so that we don't need to receive a memoized renderFn
renderFnRef.current = () => {
if (!rootRef.current) return;
rootRef.current.render(
<ThemeProvider theme={theme}>{renderFn()}</ThemeProvider>
<ThemeProvider theme={theme}>{renderFn(elementRef.current!)}</ThemeProvider>
);
};

Expand All @@ -43,6 +47,7 @@ export function useMapboxControl(renderFn, deps: any[] = []) {
onAdd() {
const el = document.createElement('div');
el.className = 'mapboxgl-ctrl';
elementRef.current = el;

rootRef.current = createRoot(el);
renderFnRef.current();
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@
"@types/react-dom": "^18.2.5",
"@types/styled-components": "^5.1.26",
"axios": "^0.25.0",
"clipboard": "^2.0.11",
"codemirror": "^6.0.1",
"d3": "^7.6.1",
"d3-scale-chromatic": "^3.0.0",
Expand All @@ -139,7 +140,7 @@
"jest-environment-jsdom": "^28.1.3",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"mapbox-gl": "^2.11.0",
"mapbox-gl": "^2.15.0",
"mapbox-gl-compare": "^0.4.0",
"mapbox-gl-draw-rectangle-mode": "^1.0.4",
"papaparse": "^5.3.2",
Expand Down
70 changes: 51 additions & 19 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1979,10 +1979,10 @@
dependencies:
meow "^6.1.1"

"@mapbox/tiny-sdf@^2.0.5":
version "2.0.5"
resolved "http://verdaccio.ds.io:4873/@mapbox%2ftiny-sdf/-/tiny-sdf-2.0.5.tgz#cdba698d3d65087643130f9af43a2b622ce0b372"
integrity sha512-OhXt2lS//WpLdkqrzo/KwB7SRD8AiNTFFzuo9n14IBupzIMa67yGItcK7I2W9D8Ghpa4T04Sw9FWsKCJG50Bxw==
"@mapbox/tiny-sdf@^2.0.6":
version "2.0.6"
resolved "http://verdaccio.ds.io:4873/@mapbox%2ftiny-sdf/-/tiny-sdf-2.0.6.tgz#9a1d33e5018093e88f6a4df2343e886056287282"
integrity sha512-qMqa27TLw+ZQz5Jk+RcwZGH7BQf5G/TrutJhspsca/3SHwmgKQ1iq+d3Jxz5oysPVYTGP6aXxCo5Lk9Er6YBAA==

"@mapbox/unitbezier@^0.0.1":
version "0.0.1"
Expand Down Expand Up @@ -4651,6 +4651,15 @@ clean-stack@^2.0.0:
resolved "http://verdaccio.ds.io:4873/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==

clipboard@^2.0.11:
version "2.0.11"
resolved "http://verdaccio.ds.io:4873/clipboard/-/clipboard-2.0.11.tgz#62180360b97dd668b6b3a84ec226975762a70be5"
integrity sha512-C+0bbOqkezLIsmWSvlsXS0Q0bmkugu7jcfMIACB+RDEntIzQIkdr148we28AfSloQLRdZlYL/QYyrq05j/3Faw==
dependencies:
good-listener "^1.2.2"
select "^1.1.2"
tiny-emitter "^2.0.0"

cliui@^3.2.0:
version "3.2.0"
resolved "http://verdaccio.ds.io:4873/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d"
Expand Down Expand Up @@ -5505,6 +5514,11 @@ delayed-stream@~1.0.0:
resolved "http://verdaccio.ds.io:4873/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=

delegate@^3.1.2:
version "3.2.0"
resolved "http://verdaccio.ds.io:4873/delegate/-/delegate-3.2.0.tgz#b66b71c3158522e8ab5744f720d8ca0c2af59166"
integrity sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==

dequal@1.0.0:
version "1.0.0"
resolved "http://verdaccio.ds.io:4873/dequal/-/dequal-1.0.0.tgz#41c6065e70de738541c82cdbedea5292277a017e"
Expand Down Expand Up @@ -6769,6 +6783,13 @@ gonzales-pe@^4.3.0:
dependencies:
minimist "^1.2.5"

good-listener@^1.2.2:
version "1.2.2"
resolved "http://verdaccio.ds.io:4873/good-listener/-/good-listener-1.2.2.tgz#d53b30cdf9313dffb7dc9a0d477096aa6d145c50"
integrity sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=
dependencies:
delegate "^3.1.2"

google-polyline@^1.0.3:
version "1.0.3"
resolved "http://verdaccio.ds.io:4873/google-polyline/-/google-polyline-1.0.3.tgz#b1fafd8841059f7049d4ba6767c386be979510dc"
Expand Down Expand Up @@ -8244,10 +8265,10 @@ just-debounce@^1.0.0:
resolved "http://verdaccio.ds.io:4873/just-debounce/-/just-debounce-1.1.0.tgz#2f81a3ad4121a76bc7cb45dbf704c0d76a8e5ddf"
integrity sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==

kdbush@^3.0.0:
version "3.0.0"
resolved "http://verdaccio.ds.io:4873/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0"
integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==
kdbush@^4.0.1, kdbush@^4.0.2:
version "4.0.2"
resolved "http://verdaccio.ds.io:4873/kdbush/-/kdbush-4.0.2.tgz#2f7b7246328b4657dd122b6c7f025fbc2c868e39"
integrity sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==

keyv@^4.0.0:
version "4.3.2"
Expand Down Expand Up @@ -8626,16 +8647,16 @@ mapbox-gl-draw-rectangle-mode@^1.0.4:
resolved "http://verdaccio.ds.io:4873/mapbox-gl-draw-rectangle-mode/-/mapbox-gl-draw-rectangle-mode-1.0.4.tgz#42987d68872a5fb5cc5d76d3375ee20cd8bab8f7"
integrity sha512-BdF6nwEK2p8n9LQoMPzBO8LhddW1fe+d5vK8HQIei+4VcRnUbKNsEj7Z15FsJxCHzsc2BQKXbESx5GaE8x0imQ==

mapbox-gl@^2.11.0:
version "2.11.0"
resolved "http://verdaccio.ds.io:4873/mapbox-gl/-/mapbox-gl-2.11.0.tgz#fa4839e81bdd6f6d9b9615ceee84bb88adb89b4c"
integrity sha512-zXbqHfEQMsta4iKO8bzXapDz1DihvZg24gmPJoQgUYAJxjc0NVVa5G/1QF3TIlpAJDD7WPzQSAogItab5oOF3A==
mapbox-gl@^2.15.0:
version "2.15.0"
resolved "http://verdaccio.ds.io:4873/mapbox-gl/-/mapbox-gl-2.15.0.tgz#9439828d0bae1e7b464ae08b30cb2e65a7e2256d"
integrity sha512-fjv+aYrd5TIHiL7wRa+W7KjtUqKWziJMZUkK5hm8TvJ3OLeNPx4NmW/DgfYhd/jHej8wWL+QJBDbdMMAKvNC0A==
dependencies:
"@mapbox/geojson-rewind" "^0.5.2"
"@mapbox/jsonlint-lines-primitives" "^2.0.2"
"@mapbox/mapbox-gl-supported" "^2.0.1"
"@mapbox/point-geometry" "^0.1.0"
"@mapbox/tiny-sdf" "^2.0.5"
"@mapbox/tiny-sdf" "^2.0.6"
"@mapbox/unitbezier" "^0.0.1"
"@mapbox/vector-tile" "^1.3.1"
"@mapbox/whoots-js" "^3.1.0"
Expand All @@ -8644,12 +8665,13 @@ mapbox-gl@^2.11.0:
geojson-vt "^3.2.1"
gl-matrix "^3.4.3"
grid-index "^1.1.0"
kdbush "^4.0.1"
murmurhash-js "^1.0.0"
pbf "^3.2.1"
potpack "^2.0.0"
quickselect "^2.0.0"
rw "^1.3.3"
supercluster "^7.1.5"
supercluster "^8.0.0"
tinyqueue "^2.0.3"
vt-pbf "^3.1.3"

Expand Down Expand Up @@ -11222,6 +11244,11 @@ section-matter@^1.0.0:
extend-shallow "^2.0.1"
kind-of "^6.0.0"

select@^1.1.2:
version "1.1.2"
resolved "http://verdaccio.ds.io:4873/select/-/select-1.1.2.tgz#0e7350acdec80b1108528786ec1d4418d11b396d"
integrity sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=

semver-greatest-satisfied-range@^1.1.0:
version "1.1.0"
resolved "http://verdaccio.ds.io:4873/semver-greatest-satisfied-range/-/semver-greatest-satisfied-range-1.1.0.tgz#13e8c2658ab9691cb0cd71093240280d36f77a5b"
Expand Down Expand Up @@ -11795,12 +11822,12 @@ suggestions@^1.6.0:
fuzzy "^0.1.1"
xtend "^4.0.0"

supercluster@^7.1.5:
version "7.1.5"
resolved "http://verdaccio.ds.io:4873/supercluster/-/supercluster-7.1.5.tgz#65a6ce4a037a972767740614c19051b64b8be5a3"
integrity sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==
supercluster@^8.0.0:
version "8.0.1"
resolved "http://verdaccio.ds.io:4873/supercluster/-/supercluster-8.0.1.tgz#9946ba123538e9e9ab15de472531f604e7372df5"
integrity sha512-IiOea5kJ9iqzD2t7QJq/cREyLHTtSmUT6gQsweojg9WH2sYJqZK9SswTu6jrscO6D1G5v5vYZ9ru/eq85lXeZQ==
dependencies:
kdbush "^3.0.0"
kdbush "^4.0.2"

supports-color@^5.3.0, supports-color@^5.5.0:
version "5.5.0"
Expand Down Expand Up @@ -11951,6 +11978,11 @@ timsort@^0.3.0:
resolved "http://verdaccio.ds.io:4873/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4"
integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q=

tiny-emitter@^2.0.0:
version "2.1.0"
resolved "http://verdaccio.ds.io:4873/tiny-emitter/-/tiny-emitter-2.1.0.tgz#1d1a56edfc51c43e863cbb5382a72330e3555423"
integrity sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==

tinyqueue@^2.0.3:
version "2.0.3"
resolved "http://verdaccio.ds.io:4873/tinyqueue/-/tinyqueue-2.0.3.tgz#64d8492ebf39e7801d7bd34062e29b45b2035f08"
Expand Down

0 comments on commit 4dd2aae

Please sign in to comment.