mirror of
https://github.com/DOI-DO/j40-cejst-2.git
synced 2025-02-23 01:54:18 -08:00
Zoom button styling does not match spec // General custom controls fix (#357)
This commit is contained in:
parent
cfce0dc826
commit
dbf1ae2ad8
19 changed files with 31006 additions and 294 deletions
1
client/.nvmrc
Normal file
1
client/.nvmrc
Normal file
|
@ -0,0 +1 @@
|
||||||
|
14.17.0
|
|
@ -3,5 +3,10 @@ path = require('path');
|
||||||
exports.onCreateWebpackConfig = ({stage, loaders, actions}) => {
|
exports.onCreateWebpackConfig = ({stage, loaders, actions}) => {
|
||||||
actions.setWebpackConfig({
|
actions.setWebpackConfig({
|
||||||
devtool: 'eval-source-map',
|
devtool: 'eval-source-map',
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
'mapbox-gl': 'maplibre-gl',
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
27329
client/package-lock.json
generated
27329
client/package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -33,6 +33,7 @@
|
||||||
"@testing-library/jest-dom": "^5.12.0",
|
"@testing-library/jest-dom": "^5.12.0",
|
||||||
"@testing-library/react": "^11.2.7",
|
"@testing-library/react": "^11.2.7",
|
||||||
"@types/chroma-js": "^2.1.3",
|
"@types/chroma-js": "^2.1.3",
|
||||||
|
"@types/d3-ease": "^3.0.0",
|
||||||
"@types/jest": "^26.0.23",
|
"@types/jest": "^26.0.23",
|
||||||
"@types/maplibre-gl": "^1.13.1",
|
"@types/maplibre-gl": "^1.13.1",
|
||||||
"@types/node": "^15.3.1",
|
"@types/node": "^15.3.1",
|
||||||
|
@ -69,14 +70,18 @@
|
||||||
"ts-jest": "^27.0.0"
|
"ts-jest": "^27.0.0"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@trussworks/react-uswds": "git+https://www.github.com/nathillardusds/react-uswds#nathillardusds/ssr",
|
"@trussworks/react-uswds": "git+https://git@github.com/nathillardusds/react-uswds#nathillardusds/ssr",
|
||||||
|
"@turf/bbox": "^6.5.0",
|
||||||
"chroma-js": "^2.1.2",
|
"chroma-js": "^2.1.2",
|
||||||
|
"d3-ease": "^3.0.1",
|
||||||
|
"mapbox-gl": "^1.13.0",
|
||||||
"maplibre-gl": ">=1.14.0",
|
"maplibre-gl": ">=1.14.0",
|
||||||
"query-string": "^7.0.0",
|
"query-string": "^7.0.0",
|
||||||
"react": "^17.0.2",
|
"react": "^17.0.2",
|
||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-helmet": "^6.1.0",
|
"react-helmet": "^6.1.0",
|
||||||
"react-intl": "^5.20.4",
|
"react-intl": "^5.20.4",
|
||||||
|
"react-map-gl": "^6.1.16",
|
||||||
"uswds": "^2.10.3"
|
"uswds": "^2.10.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,38 +6,22 @@ $sidebar-color: #ffffff;
|
||||||
}
|
}
|
||||||
|
|
||||||
.j40Popup {
|
.j40Popup {
|
||||||
max-height: 300px;
|
max-height: 50%;
|
||||||
min-width: 36%;
|
|
||||||
max-width: 36%;
|
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
pointer-events: all !important;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.territoryFocusButton {
|
.navigationControl {
|
||||||
width: 40px;
|
left: 1.25em;
|
||||||
height: 40px;
|
top: 2.5em;
|
||||||
border-width: 1px 2px;
|
width: 2.5em;
|
||||||
border-color: #000000;
|
|
||||||
border-style: solid;
|
|
||||||
background-color: #ffffff;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.territoryFocusContainer {
|
.fullscreenControl {
|
||||||
display: flex;
|
right: 1.25em;
|
||||||
flex-direction: column;
|
top: 2.5em;
|
||||||
text-align: center;
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 40px;
|
|
||||||
position: absolute;
|
|
||||||
left: 20px;
|
|
||||||
top: 300px;
|
|
||||||
z-index: 10;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.territoryFocusButton:first-child {
|
.geolocateControl {
|
||||||
border-top-width: 2px;
|
right: 1.25em;
|
||||||
}
|
top: 5em;
|
||||||
|
|
||||||
.territoryFocusButton:last-child {
|
|
||||||
border-bottom-width: 2px;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@ declare namespace J40MapModuleScssNamespace {
|
||||||
j40Popup: string;
|
j40Popup: string;
|
||||||
territoryFocusButton: string;
|
territoryFocusButton: string;
|
||||||
territoryFocusContainer: string;
|
territoryFocusContainer: string;
|
||||||
|
navigationControl: string;
|
||||||
|
fullscreenControl: string;
|
||||||
|
geolocateControl: string;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,131 +1,132 @@
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
import React, {useRef, useEffect, useState} from 'react';
|
import React, {MouseEvent, useRef, useState} from 'react';
|
||||||
import maplibregl, {LngLatBoundsLike,
|
import {Map, MapboxGeoJSONFeature, LngLatBoundsLike} from 'maplibre-gl';
|
||||||
Map,
|
import ReactMapGL, {
|
||||||
|
MapEvent,
|
||||||
|
ViewportProps,
|
||||||
|
WebMercatorViewport,
|
||||||
NavigationControl,
|
NavigationControl,
|
||||||
PopupOptions,
|
GeolocateControl,
|
||||||
Popup,
|
Popup,
|
||||||
LngLatLike,
|
FlyToInterpolator,
|
||||||
MapboxGeoJSONFeature} from 'maplibre-gl';
|
FullscreenControl,
|
||||||
|
MapRef} from 'react-map-gl';
|
||||||
import {makeMapStyle} from '../data/mapStyle';
|
import {makeMapStyle} from '../data/mapStyle';
|
||||||
import PopupContent from './popupContent';
|
import AreaDetail from './areaDetail';
|
||||||
import * as constants from '../data/constants';
|
import bbox from '@turf/bbox';
|
||||||
import ReactDOM from 'react-dom';
|
import * as d3 from 'd3-ease';
|
||||||
import {useFlags} from '../contexts/FlagContext';
|
import {useFlags} from '../contexts/FlagContext';
|
||||||
|
import TerritoryFocusControl from './territoryFocusControl';
|
||||||
|
|
||||||
import 'maplibre-gl/dist/maplibre-gl.css';
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
||||||
|
import * as constants from '../data/constants';
|
||||||
import * as styles from './J40Map.module.scss';
|
import * as styles from './J40Map.module.scss';
|
||||||
|
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
Cypress?: object;
|
Cypress?: object;
|
||||||
underlyingMap: Map;
|
underlyingMap: Map;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
type ClickEvent = maplibregl.MapMouseEvent & maplibregl.EventData;
|
|
||||||
|
|
||||||
|
interface IDetailViewInterface {
|
||||||
|
latitude: number
|
||||||
|
longitude: number
|
||||||
|
zoom: number
|
||||||
|
properties: constants.J40Properties,
|
||||||
|
};
|
||||||
|
|
||||||
const J40Map = () => {
|
const J40Map = () => {
|
||||||
const mapContainer = React.useRef<HTMLDivElement>(null);
|
const [viewport, setViewport] = useState<ViewportProps>({
|
||||||
const mapRef = useRef<Map>() as React.MutableRefObject<Map>;
|
latitude: constants.DEFAULT_CENTER[0],
|
||||||
const selectedFeature = useRef<MapboxGeoJSONFeature>();
|
longitude: constants.DEFAULT_CENTER[1],
|
||||||
const [zoom, setZoom] = useState(constants.GLOBAL_MIN_ZOOM);
|
zoom: constants.GLOBAL_MIN_ZOOM,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [selectedFeature, setSelectedFeature] = useState<MapboxGeoJSONFeature>();
|
||||||
|
const [detailViewData, setDetailViewData] = useState<IDetailViewInterface>();
|
||||||
|
const [transitionInProgress, setTransitionInProgress] = useState<boolean>(false);
|
||||||
|
const [geolocationInProgress, setGeolocationInProgress] = useState<boolean>(false);
|
||||||
|
const mapRef = useRef<MapRef>(null);
|
||||||
const flags = useFlags();
|
const flags = useFlags();
|
||||||
|
|
||||||
useEffect(() => {
|
const onClick = (event: MapEvent) => {
|
||||||
const initialMap = new Map({
|
const feature = event.features && event.features[0];
|
||||||
container: mapContainer.current!,
|
if (feature) {
|
||||||
style: makeMapStyle(flags),
|
const [minLng, minLat, maxLng, maxLat] = bbox(feature);
|
||||||
center: constants.DEFAULT_CENTER as LngLatLike,
|
const newViewPort = new WebMercatorViewport({height: viewport.height!, width: viewport.width!});
|
||||||
zoom: zoom,
|
const {longitude, latitude, zoom} = newViewPort.fitBounds(
|
||||||
minZoom: constants.GLOBAL_MIN_ZOOM,
|
[
|
||||||
maxBounds: constants.GLOBAL_MAX_BOUNDS as LngLatBoundsLike,
|
[minLng, minLat],
|
||||||
hash: true, // Adds hash of zoom/lat/long to the url
|
[maxLng, maxLat],
|
||||||
});
|
],
|
||||||
// disable map rotation using right click + drag
|
{
|
||||||
initialMap.dragRotate.disable();
|
padding: 40,
|
||||||
|
},
|
||||||
// disable map rotation using touch rotation gesture
|
);
|
||||||
initialMap.touchZoomRotate.disableRotation();
|
// If we've selected a new feature, set 'selected' to false
|
||||||
|
if (selectedFeature && feature.id !== selectedFeature.id) {
|
||||||
setZoom(initialMap.getZoom());
|
setMapSelected(selectedFeature, false);
|
||||||
|
|
||||||
initialMap.on('load', () => {
|
|
||||||
if (window.Cypress) {
|
|
||||||
window.underlyingMap = initialMap;
|
|
||||||
}
|
}
|
||||||
});
|
setMapSelected(feature, true);
|
||||||
|
const popupInfo = {
|
||||||
|
longitude: longitude,
|
||||||
|
latitude: latitude,
|
||||||
|
zoom: zoom,
|
||||||
|
properties: feature.properties,
|
||||||
|
};
|
||||||
|
goToPlace([
|
||||||
|
[minLng, minLat],
|
||||||
|
[maxLng, maxLat],
|
||||||
|
]);
|
||||||
|
setDetailViewData(popupInfo);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const onLoad = () => {
|
||||||
|
if (typeof window !== 'undefined' && window.Cypress && mapRef.current) {
|
||||||
|
window.underlyingMap = mapRef.current.getMap();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
const goToPlace = (bounds: LngLatBoundsLike ) => {
|
||||||
|
const {longitude, latitude, zoom} = new WebMercatorViewport({height: viewport.height!, width: viewport.width!})
|
||||||
|
.fitBounds(bounds as [[number, number], [number, number]], {
|
||||||
|
padding: 20,
|
||||||
|
offset: [0, -100],
|
||||||
|
});
|
||||||
|
setViewport({
|
||||||
|
...viewport,
|
||||||
|
longitude,
|
||||||
|
latitude,
|
||||||
|
zoom,
|
||||||
|
transitionDuration: 1000,
|
||||||
|
transitionInterpolator: new FlyToInterpolator(),
|
||||||
|
transitionEasing: d3.easeCubic,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
initialMap.on('click', handleClick);
|
|
||||||
initialMap.addControl(new NavigationControl({showCompass: false}), 'top-left');
|
|
||||||
mapRef.current = initialMap;
|
|
||||||
}, []);
|
|
||||||
const setMapSelected = (feature:MapboxGeoJSONFeature, isSelected:boolean) : void => {
|
const setMapSelected = (feature:MapboxGeoJSONFeature, isSelected:boolean) : void => {
|
||||||
// The below can be confirmed during debug with:
|
// The below can be confirmed during debug with:
|
||||||
// mapRef.current.getFeatureState({"id":feature.id, "source":feature.source, "sourceLayer":feature.sourceLayer})
|
// mapRef.current.getFeatureState({"id":feature.id, "source":feature.source, "sourceLayer":feature.sourceLayer})
|
||||||
mapRef.current.setFeatureState({
|
mapRef.current && mapRef.current.getMap().setFeatureState({
|
||||||
source: feature.source,
|
source: feature.source,
|
||||||
sourceLayer: feature.sourceLayer,
|
sourceLayer: feature.sourceLayer,
|
||||||
id: feature.id,
|
id: feature.id,
|
||||||
}, {[constants.SELECTED_PROPERTY]: isSelected});
|
}, {[constants.SELECTED_PROPERTY]: isSelected});
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
selectedFeature.current = feature;
|
setSelectedFeature(feature);
|
||||||
} else {
|
} else {
|
||||||
selectedFeature.current = undefined;
|
setSelectedFeature(undefined);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleClick = (e: ClickEvent) => {
|
const onClickTerritoryFocusButton = (event: MouseEvent<HTMLButtonElement>) => {
|
||||||
const map = e.target;
|
const buttonID = event.target && (event.target as HTMLElement).id;
|
||||||
const clickedCoord = e.point;
|
|
||||||
const features = map.queryRenderedFeatures(clickedCoord, {
|
|
||||||
layers: [constants.HIGH_SCORE_LAYER_NAME],
|
|
||||||
});
|
|
||||||
const feature = features && features[0];
|
|
||||||
if (feature) {
|
|
||||||
const placeholder = document.createElement('div');
|
|
||||||
// If we've selected a new feature, set 'selected' to false
|
|
||||||
if (selectedFeature.current && feature.id !== selectedFeature.current.id) {
|
|
||||||
setMapSelected(selectedFeature.current, false);
|
|
||||||
}
|
|
||||||
setMapSelected(feature, true);
|
|
||||||
|
|
||||||
ReactDOM.render(<PopupContent properties={feature.properties} />, placeholder);
|
|
||||||
const options : PopupOptions = {
|
|
||||||
offset: [0, 0],
|
|
||||||
className: styles.j40Popup,
|
|
||||||
focusAfterOpen: false,
|
|
||||||
};
|
|
||||||
new Popup(options)
|
|
||||||
.setLngLat(e.lngLat)
|
|
||||||
.setDOMContent(placeholder)
|
|
||||||
.on('close', function() {
|
|
||||||
setMapSelected(feature, false);
|
|
||||||
})
|
|
||||||
.addTo(map);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
mapRef.current.on('move', () => {
|
|
||||||
setZoom(mapRef.current.getZoom());
|
|
||||||
});
|
|
||||||
mapRef.current.on('mouseenter', constants.HIGH_SCORE_LAYER_NAME, () => {
|
|
||||||
mapRef.current.getCanvas().style.cursor = 'pointer';
|
|
||||||
});
|
|
||||||
mapRef.current.on('mouseleave', constants.HIGH_SCORE_LAYER_NAME, () => {
|
|
||||||
mapRef.current.getCanvas().style.cursor = '';
|
|
||||||
});
|
|
||||||
}, [mapRef]);
|
|
||||||
|
|
||||||
const goToPlace = (bounds:number[][]) => {
|
|
||||||
mapRef.current.fitBounds(bounds as LngLatBoundsLike);
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClickTerritoryFocusButton = (event: React.MouseEvent<HTMLButtonElement>) => {
|
|
||||||
// currentTarget always refers to the element to which the event handler
|
|
||||||
// has been attached, as opposed to Event.target, which identifies
|
|
||||||
// the element on which the event occurred and which may be its descendant.
|
|
||||||
const buttonID = event.target && event.currentTarget.id;
|
|
||||||
|
|
||||||
switch (buttonID) {
|
switch (buttonID) {
|
||||||
case '48':
|
case '48':
|
||||||
|
@ -146,40 +147,74 @@ const J40Map = () => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onTransitionStart = () => {
|
||||||
|
setTransitionInProgress(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onTransitionEnd = () => {
|
||||||
|
setTransitionInProgress(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onGeolocate = () => {
|
||||||
|
setGeolocationInProgress(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClickGeolocate = () => {
|
||||||
|
setGeolocationInProgress(true);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<>
|
||||||
<div className={styles.territoryFocusContainer}>
|
<ReactMapGL
|
||||||
<button
|
{...viewport}
|
||||||
id={'48'}
|
className={styles.mapContainer}
|
||||||
onClick={onClickTerritoryFocusButton}
|
mapStyle={makeMapStyle(flags)}
|
||||||
className={styles.territoryFocusButton}
|
minZoom={constants.GLOBAL_MIN_ZOOM}
|
||||||
aria-label="Zoom to Lower 48" >
|
maxZoom={constants.GLOBAL_MAX_ZOOM}
|
||||||
48
|
mapOptions={{hash: true}}
|
||||||
</button>
|
width="100%"
|
||||||
<button
|
height="52vw"
|
||||||
id={'AK'}
|
dragRotate={false}
|
||||||
onClick={onClickTerritoryFocusButton}
|
touchRotate={false}
|
||||||
className={styles.territoryFocusButton}
|
interactiveLayerIds={[constants.HIGH_SCORE_LAYER_NAME]}
|
||||||
aria-label="Zoom to Alaska" >
|
onViewportChange={setViewport}
|
||||||
AK
|
onClick={onClick}
|
||||||
</button>
|
onLoad={onLoad}
|
||||||
<button
|
onTransitionStart={onTransitionStart}
|
||||||
id={'HI'}
|
onTransitionEnd={onTransitionEnd}
|
||||||
onClick={onClickTerritoryFocusButton}
|
ref={mapRef}
|
||||||
className={styles.territoryFocusButton}
|
>
|
||||||
aria-label="Zoom to Hawaii" >
|
{(detailViewData && !transitionInProgress) && (
|
||||||
HI
|
<Popup
|
||||||
</button>
|
className={styles.j40Popup}
|
||||||
<button
|
tipSize={5}
|
||||||
id={'PR'}
|
anchor="top"
|
||||||
onClick={onClickTerritoryFocusButton}
|
longitude={detailViewData.longitude!}
|
||||||
className={styles.territoryFocusButton}
|
latitude={detailViewData.latitude!}
|
||||||
aria-label="Zoom to Puerto Rico" >
|
closeOnClick={true}
|
||||||
PR
|
onClose={setDetailViewData}
|
||||||
</button>
|
captureScroll={true}
|
||||||
</div>
|
>
|
||||||
<div ref={mapContainer} className={styles.mapContainer}/>
|
<AreaDetail properties={detailViewData.properties} />
|
||||||
</div>
|
</Popup>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<NavigationControl
|
||||||
|
showCompass={false}
|
||||||
|
className={styles.navigationControl}
|
||||||
|
/>
|
||||||
|
{'gl' in flags ? <GeolocateControl
|
||||||
|
className={styles.geolocateControl}
|
||||||
|
positionOptions={{enableHighAccuracy: true}}
|
||||||
|
onGeolocate={onGeolocate}
|
||||||
|
// @ts-ignore // Types have not caught up yet, see https://github.com/visgl/react-map-gl/issues/1492
|
||||||
|
onClick={onClickGeolocate}
|
||||||
|
/> : ''}
|
||||||
|
{geolocationInProgress ? <div>Geolocation in progress...</div> : ''}
|
||||||
|
<TerritoryFocusControl onClickTerritoryFocusButton={onClickTerritoryFocusButton}/>
|
||||||
|
{'fs' in flags ? <FullscreenControl className={styles.fullscreenControl}/> :'' }
|
||||||
|
</ReactMapGL>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
23
client/src/components/areaDetail.module.scss
Normal file
23
client/src/components/areaDetail.module.scss
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
.areaDetailTable {
|
||||||
|
max-width: 31.6vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 22px 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.titleIndicatorName {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.areaDetailContainer {
|
||||||
|
max-height: 50vh;
|
||||||
|
overflow: scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
.areaDetailTableContainer {
|
||||||
|
overflow: auto;
|
||||||
|
padding: 22px;
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
declare namespace MapModuleScssNamespace {
|
declare namespace MapModuleScssNamespace {
|
||||||
export interface IMapModuleScss {
|
export interface IMapModuleScss {
|
||||||
popupContainer: string;
|
areaDetailContainer: string;
|
||||||
popupContentTable:string;
|
areaDetailTable:string;
|
||||||
|
areaDetailTableContainer:string;
|
||||||
titleContainer:string;
|
titleContainer:string;
|
||||||
titleIndicatorName:string;
|
titleIndicatorName:string;
|
||||||
}
|
}
|
|
@ -1,13 +1,13 @@
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import * as constants from '../data/constants';
|
import * as constants from '../data/constants';
|
||||||
import * as styles from './popupContent.module.scss';
|
import * as styles from './areaDetail.module.scss';
|
||||||
import {GeoJsonProperties} from 'geojson';
|
|
||||||
|
|
||||||
interface IPopupContentProps {
|
interface IAreaDetailProps {
|
||||||
properties: GeoJsonProperties,
|
properties: constants.J40Properties,
|
||||||
}
|
}
|
||||||
|
|
||||||
const PopupContent = ({properties}:IPopupContentProps) => {
|
|
||||||
|
const AreaDetail = ({properties}:IAreaDetailProps) => {
|
||||||
const readablePercent = (percent: number) => {
|
const readablePercent = (percent: number) => {
|
||||||
return `${(percent * 100).toFixed(2)}`;
|
return `${(percent * 100).toFixed(2)}`;
|
||||||
};
|
};
|
||||||
|
@ -24,7 +24,7 @@ const PopupContent = ({properties}:IPopupContentProps) => {
|
||||||
return categorization;
|
return categorization;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getTitleContent = (properties: constants.J40Properties) => {
|
const getTitleContent = () => {
|
||||||
const blockGroup = properties[constants.GEOID_PROPERTY];
|
const blockGroup = properties[constants.GEOID_PROPERTY];
|
||||||
const score = properties[constants.SCORE_PROPERTY_HIGH] as number;
|
const score = properties[constants.SCORE_PROPERTY_HIGH] as number;
|
||||||
return (
|
return (
|
||||||
|
@ -45,7 +45,7 @@ const PopupContent = ({properties}:IPopupContentProps) => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getBodyContent = (properties: constants.J40Properties) => {
|
const getBodyContent = () => {
|
||||||
const rows = [];
|
const rows = [];
|
||||||
const sortedKeys = Object.entries(properties).sort();
|
const sortedKeys = Object.entries(properties).sort();
|
||||||
for (let [key, value] of sortedKeys) {
|
for (let [key, value] of sortedKeys) {
|
||||||
|
@ -69,23 +69,25 @@ const PopupContent = ({properties}:IPopupContentProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{properties ?
|
{properties ?
|
||||||
<div id='popupContainer'>
|
<div className={styles.areaDetailContainer}>
|
||||||
{getTitleContent(properties)}
|
{getTitleContent()}
|
||||||
<table className={'usa-table usa-table--borderless ' + styles.popupContentTable}>
|
<div className={styles.areaDetailTableContainer}>
|
||||||
<thead>
|
<table className={'usa-table usa-table--borderless ' + styles.areaDetailTable}>
|
||||||
<tr>
|
<thead>
|
||||||
<th scope="col">INDICATOR</th>
|
<tr>
|
||||||
<th scope="col">VALUE</th>
|
<th scope="col">INDICATOR</th>
|
||||||
</tr>
|
<th scope="col">VALUE</th>
|
||||||
</thead>
|
</tr>
|
||||||
<tbody>
|
</thead>
|
||||||
{getBodyContent(properties)}
|
<tbody>
|
||||||
</tbody>
|
{getBodyContent()}
|
||||||
</table>
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
</div> :
|
</div> :
|
||||||
'' }
|
'' }
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default PopupContent;
|
export default AreaDetail;
|
|
@ -1,16 +0,0 @@
|
||||||
.popupContentTable {
|
|
||||||
min-width: 400px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.popupContentTable > thead {
|
|
||||||
background-color: #edeff0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleContainer {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.titleIndicatorName {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
34
client/src/components/territoryFocusControl.module.scss
Normal file
34
client/src/components/territoryFocusControl.module.scss
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
.territoryFocusButton {
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-width: 1px 2px;
|
||||||
|
border-color: #000000;
|
||||||
|
border-style: solid;
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.territoryFocusButton:not(:disabled):hover {
|
||||||
|
background-color: #f4f4f4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.territoryFocusContainer {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 40px;
|
||||||
|
position: absolute;
|
||||||
|
left: 20px;
|
||||||
|
top: 300px;
|
||||||
|
z-index: 10;
|
||||||
|
left: 20px;
|
||||||
|
top: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.territoryFocusButton:first-child {
|
||||||
|
border-top-width: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.territoryFocusButton:last-child {
|
||||||
|
border-bottom-width: 2px;
|
||||||
|
}
|
15
client/src/components/territoryFocusControl.module.scss.d.ts
vendored
Normal file
15
client/src/components/territoryFocusControl.module.scss.d.ts
vendored
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
declare namespace TerritoryFocusControlModuleScssNamespace {
|
||||||
|
export interface ITerritoryFocusControlModuleScss {
|
||||||
|
territoryFocusContainer: string;
|
||||||
|
territoryFocusButton: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare const TerritoryFocusControlModuleScssModule:
|
||||||
|
TerritoryFocusControlModuleScssNamespace.ITerritoryFocusControlModuleScss & {
|
||||||
|
/** WARNING: Only available when `css-loader` is used without `style-loader` or `mini-css-extract-plugin` */
|
||||||
|
locals: TerritoryFocusControlModuleScssNamespace.ITerritoryFocusControlModuleScss;
|
||||||
|
};
|
||||||
|
|
||||||
|
export = TerritoryFocusControlModuleScssModule;
|
||||||
|
|
26
client/src/components/territoryFocusControl.tsx
Normal file
26
client/src/components/territoryFocusControl.tsx
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import React, {MouseEventHandler} from 'react';
|
||||||
|
import {_useMapControl as useMapControl} from 'react-map-gl';
|
||||||
|
import * as styles from './territoryFocusControl.module.scss';
|
||||||
|
|
||||||
|
interface ITerritoryFocusControl {
|
||||||
|
onClickTerritoryFocusButton : MouseEventHandler<HTMLButtonElement>;
|
||||||
|
}
|
||||||
|
|
||||||
|
const TerritoryFocusControl = ({onClickTerritoryFocusButton} : ITerritoryFocusControl) => {
|
||||||
|
const {containerRef} = useMapControl({
|
||||||
|
// @ts-ignore // Types have not caught up yet, see https://github.com/visgl/react-map-gl/issues/1492
|
||||||
|
onClick: onClickTerritoryFocusButton,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div ref={containerRef} className={styles.territoryFocusContainer}>
|
||||||
|
<button id={'48'} onClick={onClickTerritoryFocusButton} className={styles.territoryFocusButton}>48</button>
|
||||||
|
<button id={'AK'} onClick={onClickTerritoryFocusButton} className={styles.territoryFocusButton}>AK</button>
|
||||||
|
<button id={'HI'} onClick={onClickTerritoryFocusButton} className={styles.territoryFocusButton}>HI</button>
|
||||||
|
<button id={'PR'} onClick={onClickTerritoryFocusButton} className={styles.territoryFocusButton}>PR</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default TerritoryFocusControl;
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
|
import {LngLatBoundsLike} from 'maplibre-gl';
|
||||||
|
|
||||||
// URLS
|
// URLS
|
||||||
export const FEATURE_TILE_BASE_URL = 'https://d2zjid6n5ja2pt.cloudfront.net';
|
export const FEATURE_TILE_BASE_URL = 'https://d2zjid6n5ja2pt.cloudfront.net';
|
||||||
const XYZ_SUFFIX = '{z}/{x}/{y}.pbf';
|
const XYZ_SUFFIX = '{z}/{x}/{y}.pbf';
|
||||||
export const featureURLForTilesetName = (tilesetName :string ) : string => {
|
export const featureURLForTilesetName = (tilesetName :string ) : string => {
|
||||||
return `${FEATURE_TILE_BASE_URL}/${tilesetName}/${XYZ_SUFFIX}`;
|
return `${FEATURE_TILE_BASE_URL}/${tilesetName}/${XYZ_SUFFIX}`;
|
||||||
};
|
};
|
||||||
export const FEATURE_TILE_HIGH_ZOOM_URL = featureURLForTilesetName('0629_demo');
|
export const FEATURE_TILE_HIGH_ZOOM_URL = featureURLForTilesetName('0714_high');
|
||||||
export const FEATURE_TILE_LOW_ZOOM_URL = featureURLForTilesetName('tiles_low');
|
export const FEATURE_TILE_LOW_ZOOM_URL = featureURLForTilesetName('tiles_low');
|
||||||
|
|
||||||
|
|
||||||
|
@ -38,42 +40,42 @@ export const GLOBAL_MIN_ZOOM_HIGH = 7;
|
||||||
export const GLOBAL_MAX_ZOOM_HIGH = 11;
|
export const GLOBAL_MAX_ZOOM_HIGH = 11;
|
||||||
|
|
||||||
// Bounds
|
// Bounds
|
||||||
export const GLOBAL_MAX_BOUNDS = [
|
export const GLOBAL_MAX_BOUNDS : LngLatBoundsLike = [
|
||||||
[-180.118306, 5.499550],
|
[-180.118306, 5.499550],
|
||||||
[-65.0, 83.162102],
|
[-65.0, 83.162102],
|
||||||
];
|
];
|
||||||
|
|
||||||
export const LOWER_48_BOUNDS = [
|
export const LOWER_48_BOUNDS : LngLatBoundsLike = [
|
||||||
[-124.7844079, 24.7433195],
|
[-124.7844079, 24.7433195],
|
||||||
[-66.9513812, 49.3457868],
|
[-66.9513812, 49.3457868],
|
||||||
];
|
];
|
||||||
|
|
||||||
export const ALASKA_BOUNDS = [
|
export const ALASKA_BOUNDS : LngLatBoundsLike = [
|
||||||
[-183.856888, 50.875311],
|
[-183.856888, 50.875311],
|
||||||
[-140.932617, 71.958797],
|
[-140.932617, 71.958797],
|
||||||
];
|
];
|
||||||
|
|
||||||
export const HAWAII_BOUNDS = [
|
export const HAWAII_BOUNDS : LngLatBoundsLike = [
|
||||||
[-168.118306, 18.748115],
|
[-168.118306, 18.748115],
|
||||||
[-154.757881, 22.378413],
|
[-154.757881, 22.378413],
|
||||||
];
|
];
|
||||||
|
|
||||||
export const PUERTO_RICO_BOUNDS = [
|
export const PUERTO_RICO_BOUNDS : LngLatBoundsLike = [
|
||||||
[-67.945404, 17.88328],
|
[-67.945404, 17.88328],
|
||||||
[-65.220703, 18.515683],
|
[-65.220703, 18.515683],
|
||||||
];
|
];
|
||||||
|
|
||||||
export const GUAM_BOUNDS = [
|
export const GUAM_BOUNDS : LngLatBoundsLike = [
|
||||||
[-215.389709, 13.225909],
|
[-215.389709, 13.225909],
|
||||||
[-215.040894, 13.663335],
|
[-215.040894, 13.663335],
|
||||||
];
|
];
|
||||||
|
|
||||||
export const MARIANA_ISLAND_BOUNDS = [
|
export const MARIANA_ISLAND_BOUNDS : LngLatBoundsLike = [
|
||||||
[-215.313449, 14.007801],
|
[-215.313449, 14.007801],
|
||||||
[-213.742404, 19.750326],
|
[-213.742404, 19.750326],
|
||||||
];
|
];
|
||||||
|
|
||||||
export const AMERICAN_SAMOA_BOUNDS = [
|
export const AMERICAN_SAMOA_BOUNDS : LngLatBoundsLike = [
|
||||||
[-171.089874, -14.548699],
|
[-171.089874, -14.548699],
|
||||||
[-168.1433, -11.046934],
|
[-168.1433, -11.046934],
|
||||||
];
|
];
|
||||||
|
|
|
@ -206,7 +206,6 @@ export const makeMapStyle = (flagContainer: FlagContainer) : Style => {
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH,
|
'minzoom': constants.GLOBAL_MIN_ZOOM_HIGH,
|
||||||
'maxzoom': constants.GLOBAL_MAX_ZOOM_HIGH,
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// We put labels last to ensure prominence
|
// We put labels last to ensure prominence
|
||||||
|
|
|
@ -24,6 +24,7 @@ $theme-font-role-heading: "sans";
|
||||||
@import "../../node_modules/uswds";
|
@import "../../node_modules/uswds";
|
||||||
|
|
||||||
@import "~@trussworks/react-uswds/lib/index.css";
|
@import "~@trussworks/react-uswds/lib/index.css";
|
||||||
|
@import "../../node_modules/mapbox-gl/dist/mapbox-gl.css";
|
||||||
|
|
||||||
// Custom SASS/CSS goes here
|
// Custom SASS/CSS goes here
|
||||||
$j40-max-width: 80ex;
|
$j40-max-width: 80ex;
|
||||||
|
@ -195,14 +196,29 @@ $j40-max-width: 80ex;
|
||||||
// Maplibre overrides
|
// Maplibre overrides
|
||||||
// Note that these need to be here to properly override defaults
|
// Note that these need to be here to properly override defaults
|
||||||
|
|
||||||
.maplibregl-popup-close-button {
|
.mapboxgl-popup-close-button {
|
||||||
font-size: 3em;
|
font-size: 3em;
|
||||||
margin-right: 12px;
|
margin-right: 12px;
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maplibregl-popup-content {
|
.mapboxgl-popup-content {
|
||||||
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.5) !important;
|
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.5) !important;
|
||||||
border-radius: 8px !important;
|
border-radius: 8px !important;
|
||||||
pointer-events: all !important;
|
pointer-events: all !important;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Because we're using react-map-gl, you need to use
|
||||||
|
// the mapboxgl- class name variables
|
||||||
|
|
||||||
|
.mapboxgl-ctrl-group button {
|
||||||
|
height: 40px;
|
||||||
|
width: 40px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-width: 2px;
|
||||||
|
border-color: #000000;
|
||||||
|
border-style: solid;
|
||||||
|
background-color: #ffffff;
|
||||||
|
font-size: 24px;
|
||||||
}
|
}
|
||||||
|
|
3415
package-lock.json
generated
Normal file
3415
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
7
package.json
Normal file
7
package.json
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"dependencies": {
|
||||||
|
"@turf/turf": "^6.5.0",
|
||||||
|
"@types/d3-ease": "^3.0.0",
|
||||||
|
"d3-ease": "^3.0.1"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Add table
Reference in a new issue