diff --git a/frontend/src/components/button.js b/frontend/src/components/button.js
index 50b3f7fd02..4deed2140f 100644
--- a/frontend/src/components/button.js
+++ b/frontend/src/components/button.js
@@ -3,7 +3,7 @@ import { Link } from '@reach/router';
import { LoadingIcon } from './svgIcons';
const IconSpace = ({ children }) => {children};
-const AnimatedLoadingIcon = () => (
+export const AnimatedLoadingIcon = () => (
diff --git a/frontend/src/components/projectCreate/messages.js b/frontend/src/components/projectCreate/messages.js
index 6da745b853..2b57df0e1d 100644
--- a/frontend/src/components/projectCreate/messages.js
+++ b/frontend/src/components/projectCreate/messages.js
@@ -147,7 +147,7 @@ export default defineMessages({
},
showProjectsAOILayer: {
id: 'management.projects.create.show_aois',
- defaultMessage: 'Show existing projects',
+ defaultMessage: 'Show existing projects AoIs',
},
disabledAOILayer: {
id: 'management.projects.create.show_aois.disabled',
diff --git a/frontend/src/components/projectCreate/projectCreationMap.js b/frontend/src/components/projectCreate/projectCreationMap.js
index e26851638f..faf67951db 100644
--- a/frontend/src/components/projectCreate/projectCreationMap.js
+++ b/frontend/src/components/projectCreate/projectCreationMap.js
@@ -7,6 +7,7 @@ import MapboxLanguage from '@mapbox/mapbox-gl-language';
import MapboxGeocoder from '@mapbox/mapbox-gl-geocoder';
import '@mapbox/mapbox-gl-geocoder/dist/mapbox-gl-geocoder.css';
import { useDropzone } from 'react-dropzone';
+import { mapboxLayerDefn } from '../projects/projectsMap';
import {
MAPBOX_TOKEN,
@@ -32,23 +33,31 @@ const ProjectCreationMap = ({ mapObj, setMapObj, metadata, updateMetadata, step,
const mapRef = React.createRef();
const locale = useSelector((state) => state.preferences['locale']);
const token = useSelector((state) => state.auth.get('token'));
- const [showProjectsAOILayer, setShowProjectsAOILayer] = useState(false);
+ const [showProjectsAOILayer, setShowProjectsAOILayer] = useState(true);
const [aoiCanBeActivated, setAOICanBeActivated] = useState(false);
+ const [existingProjectsList, setExistingProjectsList] = useState([]);
+ const [isAoiLoading, setIsAoiLoading] = useState(false);
const [debouncedGetProjectsAOI] = useDebouncedCallback(() => getProjectsAOI(), 1500);
const { getRootProps, getInputProps } = useDropzone({
onDrop: step === 1 ? uploadFile : () => {}, // drag&drop is activated only on the first step
noClick: true,
noKeyboard: true,
});
- const minZoomLevelToAOIVisualization = 11;
+ const minZoomLevelToAOIVisualization = 9;
+
+ useEffect(() => {
+ fetchLocalJSONAPI('projects/').then((res) => setExistingProjectsList(res.mapResults));
+ }, []);
const getProjectsAOI = () => {
if (aoiCanBeActivated && showProjectsAOILayer && step === 1) {
+ setIsAoiLoading(true);
let bounds = mapObj.map.getBounds();
let bbox = `${bounds._sw.lng},${bounds._sw.lat},${bounds._ne.lng},${bounds._ne.lat}`;
- fetchLocalJSONAPI(`projects/queries/bbox/?bbox=${bbox}&srid=4326`, token).then((res) =>
- mapObj.map.getSource('otherProjects').setData(res),
- );
+ fetchLocalJSONAPI(`projects/queries/bbox/?bbox=${bbox}&srid=4326`, token).then((res) => {
+ mapObj.map.getSource('otherProjects').setData(res);
+ setIsAoiLoading(false);
+ });
}
};
@@ -187,6 +196,41 @@ const ProjectCreationMap = ({ mapObj, setMapObj, metadata, updateMetadata, step,
}
};
+ const noop = () => {};
+
+ useLayoutEffect(() => {
+ /* docs: https://docs.mapbox.com/mapbox-gl-js/example/cluster/ */
+ const { map } = mapObj;
+
+ const someResultsReady =
+ existingProjectsList &&
+ existingProjectsList.features &&
+ existingProjectsList.features.length > 0;
+
+ const mapReadyProjectsReady =
+ map !== null &&
+ map.isStyleLoaded() &&
+ map.getSource('projects') === undefined &&
+ someResultsReady;
+ const projectsReadyMapLoading =
+ map !== null &&
+ !map.isStyleLoaded() &&
+ map.getSource('projects') === undefined &&
+ someResultsReady;
+
+ /* set up style/sources for the map, either immediately or on base load */
+ if (mapReadyProjectsReady) {
+ mapboxLayerDefn(map, existingProjectsList, noop, true);
+ } else if (projectsReadyMapLoading) {
+ map.on('load', () => mapboxLayerDefn(map, existingProjectsList, noop, true));
+ }
+
+ /* refill the source on existingProjectsList changes */
+ if (map !== null && map.getSource('projects') !== undefined && someResultsReady) {
+ map.getSource('projects').setData(existingProjectsList);
+ }
+ }, [mapObj, existingProjectsList]);
+
useLayoutEffect(() => {
if (mapObj.map !== null && mapboxgl.supported()) {
mapObj.map.on('moveend', (event) => {
@@ -247,6 +291,7 @@ const ProjectCreationMap = ({ mapObj, setMapObj, metadata, updateMetadata, step,
isActive={showProjectsAOILayer}
setActive={setShowProjectsAOILayer}
disabled={!aoiCanBeActivated}
+ isAoiLoading={isAoiLoading}
/>
)}
diff --git a/frontend/src/components/projectCreate/projectsAOILayerCheckBox.js b/frontend/src/components/projectCreate/projectsAOILayerCheckBox.js
index 36de58f1ff..14d784a9e2 100644
--- a/frontend/src/components/projectCreate/projectsAOILayerCheckBox.js
+++ b/frontend/src/components/projectCreate/projectsAOILayerCheckBox.js
@@ -5,8 +5,9 @@ import ReactTooltip from 'react-tooltip';
import messages from './messages';
import statusMessages from '../projectDetail/messages';
import { TASK_COLOURS } from '../../config';
+import { AnimatedLoadingIcon } from '../button';
-export const ProjectsAOILayerCheckBox = ({ isActive, setActive, disabled }) => {
+export const ProjectsAOILayerCheckBox = ({ isActive, setActive, disabled, isAoiLoading }) => {
return (
<>
@@ -19,6 +20,7 @@ export const ProjectsAOILayerCheckBox = ({ isActive, setActive, disabled }) => {
+
{isAoiLoading && }
{disabled ? (
diff --git a/frontend/src/components/projectCreate/tests/projectsAOILayerCheckBox.test.js b/frontend/src/components/projectCreate/tests/projectsAOILayerCheckBox.test.js
index fafc5d11f4..c63aa0334f 100644
--- a/frontend/src/components/projectCreate/tests/projectsAOILayerCheckBox.test.js
+++ b/frontend/src/components/projectCreate/tests/projectsAOILayerCheckBox.test.js
@@ -13,10 +13,10 @@ describe('ProjectsAOILayerCheckBox', () => {
,
);
- expect(screen.getByText('Show existing projects')).toBeInTheDocument();
+ expect(screen.getByText('Show existing projects AoIs')).toBeInTheDocument();
expect(screen.getByRole('checkbox').className).toContain('b--grey-light');
expect(screen.getByRole('checkbox').className).not.toContain('b--red');
- userEvent.hover(screen.getByText('Show existing projects'));
+ userEvent.hover(screen.getByText('Show existing projects AoIs'));
expect(
screen.getByText(
"Zoom in to be able to activate the visualization of other projects' areas of interest.",
@@ -31,10 +31,10 @@ describe('ProjectsAOILayerCheckBox', () => {
,
);
- expect(screen.getByText('Show existing projects')).toBeInTheDocument();
+ expect(screen.getByText('Show existing projects AoIs')).toBeInTheDocument();
expect(screen.getByRole('checkbox').className).not.toContain('b--grey-light');
expect(screen.getByRole('checkbox').className).toContain('b--red');
- userEvent.hover(screen.getByText('Show existing projects'));
+ userEvent.hover(screen.getByText('Show existing projects AoIs'));
expect(
screen.getByText("Enable the visualization of the existing projects' areas of interest."),
).toBeInTheDocument();
diff --git a/frontend/src/components/projects/projectsMap.js b/frontend/src/components/projects/projectsMap.js
index 1f2f5576eb..b0d0f1f660 100644
--- a/frontend/src/components/projects/projectsMap.js
+++ b/frontend/src/components/projects/projectsMap.js
@@ -22,7 +22,7 @@ const licensedFonts = MAPBOX_TOKEN
? ['DIN Offc Pro Medium', 'Arial Unicode MS Bold']
: ['Open Sans Semibold'];
-export const mapboxLayerDefn = (map, mapResults, clickOnProjectID) => {
+export const mapboxLayerDefn = (map, mapResults, clickOnProjectID, disablePoiClick = false) => {
map.addImage('mapMarker', markerIcon, { width: 15, height: 15, data: markerIcon });
map.addSource('projects', {
type: 'geojson',
@@ -80,7 +80,9 @@ export const mapboxLayerDefn = (map, mapResults, clickOnProjectID) => {
});
map.on('mouseenter', 'projects-unclustered-points', function (e) {
// Change the cursor style as a UI indicator.
- map.getCanvas().style.cursor = 'pointer';
+ if (!disablePoiClick) {
+ map.getCanvas().style.cursor = 'pointer';
+ }
});
map.on('mouseleave', 'projects-unclustered-points', function (e) {
// Change the cursor style as a UI indicator.
diff --git a/frontend/src/locales/en.json b/frontend/src/locales/en.json
index 41add66813..bc206b65e0 100644
--- a/frontend/src/locales/en.json
+++ b/frontend/src/locales/en.json
@@ -254,7 +254,7 @@
"management.projects.create.errors.fileSize": "We only accept files up to {fileSize} MB. Please reduce the size of your file and try again.",
"management.projects.create.split_task.description": "Make tasks smaller by clicking on specific tasks or drawing an area on the map.",
"management.projects.create.reset.button": "Reset",
- "management.projects.create.show_aois": "Show existing projects",
+ "management.projects.create.show_aois": "Show existing projects AoIs",
"management.projects.create.show_aois.disabled": "Zoom in to be able to activate the visualization of other projects' areas of interest.",
"management.projects.create.show_aois.enable": "Enable the visualization of the existing projects' areas of interest.",
"management.projects.create.show_aois.legend": "Color legend:",
@@ -359,6 +359,7 @@
"projects.formInputs.campaign.title": "Campaign",
"projects.formInputs.categories.title": "Categories",
"projects.formInputs.organisation.description": "Organization that is coordinating the project, if there is any. The managers of that organization will have administration rights over the project.",
+ "projects.formInputs.admins.title": "TM Admins",
"projects.formInputs.imagery.select": "Select imagery",
"projects.formInputs.license.select": "Select license",
"projects.formInputs.organisation.select": "Select organization",
@@ -855,6 +856,7 @@
"management.teams.visibility.public": "Public",
"management.teams.visibility.private": "Private",
"management.teams.invite_only.description": "Managers need to approve a member's request to join.",
+ "management.teams.newJoinRequestNotification": "Enable for team managers to receive (email) notifications each time a new join request is made",
"teamsAndOrgs.management.teams.messages.waiting_approval": "Your request to join this team is waiting for approval.",
"management.projects.no_found": "This {entity} doesn't have projects yet.",
"management.organisation.teams.no_found": "No teams found.",