Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 15 additions & 9 deletions app/src/components/domain/Admin2Input/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,10 @@ import {
MAX_PAGE_LIMIT,
} from '#utils/constants';
import { getGeoJsonBounds } from '#utils/geo';
import {
getAdmin2CentroidTileset,
getAdmin2Tileset,
} from '#utils/map';
import {
useLazyRequest,
useRequest,
Expand Down Expand Up @@ -190,7 +194,7 @@ function Admin2Input<const NAME>(props: Props<NAME>) {

return {
type: 'line',
'source-layer': `go-admin2-${iso3}-staging`,
'source-layer': getAdmin2Tileset(iso3).sourceLayer,
paint: {
'line-color': COLOR_BLACK,
'line-opacity': 1,
Expand All @@ -213,7 +217,7 @@ function Admin2Input<const NAME>(props: Props<NAME>) {
];
const options: Omit<FillLayer, 'id'> = {
type: 'fill',
'source-layer': `go-admin2-${iso3}-staging`,
'source-layer': getAdmin2Tileset(iso3).sourceLayer,
paint: {
'fill-color': (!value || value.length <= 0)
? defaultColor
Expand All @@ -237,6 +241,10 @@ function Admin2Input<const NAME>(props: Props<NAME>) {
}, [iso3, value, admin2CodeMap]);

const adminTwoLabelLayerOptions = useMemo((): Omit<SymbolLayer, 'id'> | undefined => {
if (!iso3) {
return undefined;
}

const textColor: NonNullable<SymbolLayer['paint']>['text-color'] = (
value && value.length > 0
? [
Expand All @@ -253,7 +261,7 @@ function Admin2Input<const NAME>(props: Props<NAME>) {

const options: Omit<SymbolLayer, 'id'> = {
type: 'symbol',
'source-layer': `go-admin2-${iso3}-centroids`,
'source-layer': getAdmin2CentroidTileset(iso3).sourceLayer,
paint: {
'text-color': textColor,
'text-opacity': 1,
Expand Down Expand Up @@ -314,11 +322,10 @@ function Admin2Input<const NAME>(props: Props<NAME>) {
<Container
heading={strings.heading}
headingLevel={6}
footer={(
footer={hasAdmin2 && !readOnly && (
<Button
name={undefined}
onClick={setShowModalTrue}
disabled={readOnly || !hasAdmin2}
>
{strings.buttonLabel}
</Button>
Expand Down Expand Up @@ -398,14 +405,13 @@ function Admin2Input<const NAME>(props: Props<NAME>) {
/>
)}
{/* eslint-disable-next-line max-len */}
{adminTwoFillLayerOptions && adminTwoLineLayerOptions && adminTwoLabelLayerOptions && (
{iso3 && adminTwoFillLayerOptions && adminTwoLineLayerOptions && adminTwoLabelLayerOptions && (
<>
<MapSource
sourceKey="country-admin-2"
sourceOptions={{
type: 'vector',
// FIXME: this should be defined as a constant
url: `mapbox://go-ifrc.go-admin2-${iso3}-staging`,
url: getAdmin2Tileset(iso3).url,
}}
>
<MapLayer
Expand All @@ -423,7 +429,7 @@ function Admin2Input<const NAME>(props: Props<NAME>) {
sourceKey="country-admin-2-labels"
sourceOptions={{
type: 'vector',
url: `mapbox://go-ifrc.go-admin2-${iso3}-centroids`,
url: getAdmin2CentroidTileset(iso3).url,
}}
>
<MapLayer
Expand Down
298 changes: 298 additions & 0 deletions app/src/components/domain/Admin2Map/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
import {
useContext,
useEffect,
useMemo,
} from 'react';
import {
_cs,
isDefined,
isNotDefined,
} from '@togglecorp/fujs';
import {
MapBounds,
MapChildContext,
MapContainer,
MapLayer,
MapSource,
} from '@togglecorp/re-map';
import {
type Expression,
type FillLayer,
type LineLayer,
type SymbolLayer,
} from 'mapbox-gl';

import useCountry from '#hooks/domain/useCountry';
import {
COLOR_DARK_GREY,
COLOR_LIGHT_GREY,
COLOR_PRIMARY_RED,
COLOR_WHITE,
} from '#utils/constants';
import { getGeoJsonBounds } from '#utils/geo';
import {
getAdmin2CentroidTileset,
getAdmin2Tileset,
getBboxListBoundingBox,
} from '#utils/map';

import BaseMap from '../BaseMap';

import styles from './styles.module.css';

const MAP_LOAD_TIMEOUT = 15_000;
const ADMIN_2_MIN_ZOOM = 5;

const MAP_PADDING = 8;

interface Admin2 {
code: string;
bbox?: Record<string, unknown> | null;
}

interface MapControllerProps {
bounds: [number, number, number, number] | undefined;
onLoad?: () => void;
}

// NOTE: This lets the consumer (eg. pdf export) know that the map is
// completely rendered
function MapController(props: MapControllerProps) {
const {
bounds,
onLoad,
} = props;

const { map } = useContext(MapChildContext);

useEffect(() => {
if (isNotDefined(map) || isNotDefined(onLoad) || isNotDefined(bounds)) {
return undefined;
}
const handleIdle = () => {
if (!map.areTilesLoaded()) {
return;
}
map.off('idle', handleIdle);
onLoad();
};

map.on('idle', handleIdle);

return () => {
map.off('idle', handleIdle);
};
}, [map, onLoad, bounds]);

if (isNotDefined(bounds)) {
return null;
}
return (
<MapBounds
bounds={bounds}
padding={MAP_PADDING}
duration={0}
/>
);
}

interface Props {
className?: string;
countryId: number;
admin2Details: Admin2[] | undefined;
onLoad?: () => void;
}

function Admin2Map(props: Props) {
const {
className,
countryId,
admin2Details,
onLoad,
} = props;

const countryDetails = useCountry({ id: countryId });
const iso3 = countryDetails?.iso3;

useEffect(() => {
if (isNotDefined(onLoad)) {
return undefined;
}

const timeout = setTimeout(onLoad, MAP_LOAD_TIMEOUT);

return () => {
clearTimeout(timeout);
};
}, [onLoad]);

const isSelectedExpression = useMemo<Expression>(
() => [
'in',
['get', 'code'],
['literal', admin2Details?.map(({ code }) => code) ?? []],
],
[admin2Details],
);

const bounds = useMemo(() => {
const selectedBounds = getBboxListBoundingBox(
admin2Details?.map(({ bbox }) => bbox),
);

if (isDefined(selectedBounds)) {
return selectedBounds;
}

if (isNotDefined(countryDetails?.bbox)) {
return undefined;
}

return getGeoJsonBounds(countryDetails.bbox);
}, [admin2Details, countryDetails]);

const adminOneLabelLayerOptions: Omit<SymbolLayer, 'id'> = useMemo(() => ({
type: 'symbol',
paint: {
'text-opacity': [
'match',
['get', 'country_id'],
countryId,
1,
0,
],
},
layout: {
'text-offset': [
0,
1,
],
visibility: 'visible',
},
}), [countryId]);

const adminTwoLayerOptions = useMemo(() => {
if (isNotDefined(iso3)) {
return undefined;
}

const { sourceLayer } = getAdmin2Tileset(iso3);
const { sourceLayer: centroidSourceLayer } = getAdmin2CentroidTileset(iso3);

const fill: Omit<FillLayer, 'id'> = {
type: 'fill',
'source-layer': sourceLayer,
paint: {
'fill-color': [
'case',
isSelectedExpression,
COLOR_PRIMARY_RED,
COLOR_LIGHT_GREY,
],
'fill-opacity': [
'case',
isSelectedExpression,
1,
0.5,
],
},
layout: {
visibility: 'visible',
},
};

const line: Omit<LineLayer, 'id'> = {
type: 'line',
'source-layer': sourceLayer,
paint: {
'line-color': [
'case',
isSelectedExpression,
COLOR_WHITE,
COLOR_DARK_GREY,
],
'line-width': 0.5,
'line-opacity': 1,
},
layout: {
visibility: 'visible',
},
};

const label: Omit<SymbolLayer, 'id'> = {
type: 'symbol',
'source-layer': centroidSourceLayer,
filter: isSelectedExpression,
layout: {
'text-field': ['get', 'name'],
'text-anchor': 'center',
'text-size': 10,
'text-padding': 4,
},
};

return {
fill,
line,
label,
};
}, [iso3, isSelectedExpression]);

return (
<BaseMap
mapOptions={{
interactive: false,
minZoom: ADMIN_2_MIN_ZOOM,
fadeDuration: 0,
}}
navControlShown={false}
scaleControlShown={false}
baseLayers={(
<MapLayer
layerKey="admin-1-label"
layerOptions={adminOneLabelLayerOptions}
/>
)}
>
<MapContainer className={_cs(styles.admin2Map, className)} />
<MapController
bounds={bounds}
onLoad={onLoad}
/>
{isNotDefined(iso3) || isNotDefined(adminTwoLayerOptions) ? null : (
<>
<MapSource
sourceKey="country-admin-2"
sourceOptions={{
type: 'vector',
url: getAdmin2Tileset(iso3).url,
}}
>
<MapLayer
layerKey="admin-2-fill"
layerOptions={adminTwoLayerOptions.fill}
/>
<MapLayer
layerKey="admin-2-line"
layerOptions={adminTwoLayerOptions.line}
/>
</MapSource>
<MapSource
sourceKey="country-admin-2-labels"
sourceOptions={{
type: 'vector',
url: getAdmin2CentroidTileset(iso3).url,
}}
>
<MapLayer
layerKey="admin-2-label"
layerOptions={adminTwoLayerOptions.label}
/>
</MapSource>
</>
)}
</BaseMap>
);
}

export default Admin2Map;
3 changes: 3 additions & 0 deletions app/src/components/domain/Admin2Map/styles.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.admin2-map {
min-height: 12rem;
}
8 changes: 8 additions & 0 deletions app/src/components/domain/ConfirmationModal/i18n.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"namespace": "confirmationModal",
"strings": {
"confirmationHeading": "Are you sure?",
"confirmationCancelButton": "Cancel",
"confirmationConfirmButton": "Continue"
}
}
Loading
Loading