Skip to content

Commit e01148c

Browse files
committed
fix(web): save basemap to project
1 parent a5e86b8 commit e01148c

File tree

8 files changed

+119
-55
lines changed

8 files changed

+119
-55
lines changed

apps/web/app/[lng]/map/[projectId]/page.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,26 +18,24 @@ import {
1818
} from "@/lib/api/projects";
1919
import { PATTERN_IMAGES } from "@/lib/constants/pattern-images";
2020
import { DrawProvider } from "@/lib/providers/DrawProvider";
21-
import { selectActiveBasemap } from "@/lib/store/map/selectors";
2221
import { addOrUpdateMarkerImages, addPatternImages } from "@/lib/transformers/map-image";
2322
import type { FeatureLayerPointProperties } from "@/lib/validations/layer";
2423

2524
import { useAuthZ } from "@/hooks/auth/AuthZ";
2625
import { useJobStatus } from "@/hooks/jobs/JobStatus";
2726
import { useFilteredProjectLayers } from "@/hooks/map/LayerPanelHooks";
28-
import { useAppSelector } from "@/hooks/store/ContextHooks";
27+
import { useBasemap } from "@/hooks/map/MapHooks";
2928

3029
import { LoadingPage } from "@/components/common/LoadingPage";
3130
import Header from "@/components/header/Header";
3231
import MapViewer from "@/components/map/MapViewer";
3332
import ProjectNavigation from "@/components/map/panels/ProjectNavigation";
3433

35-
const UPDATE_VIEW_STATE_DEBOUNCE_TIME = 3000;
34+
const UPDATE_VIEW_STATE_DEBOUNCE_TIME = 200;
3635

3736
export default function MapPage({ params: { projectId } }) {
3837
const theme = useTheme();
3938
const { t } = useTranslation("common");
40-
const activeBasemap = useAppSelector(selectActiveBasemap);
4139
const mapRef = useRef<MapRef | null>(null);
4240
const {
4341
project,
@@ -59,6 +57,8 @@ export default function MapPage({ params: { projectId } }) {
5957
mutate: mutateProjectLayers,
6058
} = useFilteredProjectLayers(projectId, ["table"], []);
6159

60+
const { activeBasemap } = useBasemap(project);
61+
6262
const { isProjectEditor, isLoading: isAuthZLoading } = useAuthZ();
6363

6464
const { scenarioFeatures } = useProjectScenarioFeatures(projectId, project?.active_scenario_id);
@@ -129,6 +129,8 @@ export default function MapPage({ params: { projectId } }) {
129129
}
130130
};
131131

132+
console.log(initialView);
133+
132134
return (
133135
<>
134136
{isLoading && <LoadingPage />}
@@ -152,7 +154,7 @@ export default function MapPage({ params: { projectId } }) {
152154
},
153155
}}>
154156
<Box>
155-
<ProjectNavigation projectId={projectId} />
157+
<ProjectNavigation project={project} onProjectUpdate={handleProjectUpdate} />
156158
</Box>
157159
<MapViewer
158160
layers={projectLayers}

apps/web/app/[lng]/map/public/[projectId]/page.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ import { usePublicProject } from "@/lib/api/projects";
1111
import type { RootState } from "@/lib/store";
1212
import { selectFilteredProjectLayers } from "@/lib/store/layer/selectors";
1313
import { setProjectLayers } from "@/lib/store/layer/slice";
14-
import { selectActiveBasemap } from "@/lib/store/map/selectors";
1514
import { setProject } from "@/lib/store/map/slice";
1615
import type { Project, ProjectLayer } from "@/lib/validations/project";
1716

18-
import { useAppDispatch, useAppSelector } from "@/hooks/store/ContextHooks";
17+
import { useBasemap } from "@/hooks/map/MapHooks";
18+
import { useAppDispatch } from "@/hooks/store/ContextHooks";
1919

2020
import { LoadingPage } from "@/components/common/LoadingPage";
2121
import Header from "@/components/header/Header";
@@ -26,13 +26,13 @@ export default function MapPage({ params: { projectId } }) {
2626
const { sharedProject, isLoading, isError: projectError } = usePublicProject(projectId);
2727
const theme = useTheme();
2828
const dispatch = useAppDispatch();
29-
const activeBasemap = useAppSelector(selectActiveBasemap);
29+
const { activeBasemap } = useBasemap(sharedProject?.config?.["project"] as Project);
3030
const projectLayers = useMemo(() => {
3131
return sharedProject?.config?.["layers"] ?? ([] as ProjectLayer[]);
3232
}, [sharedProject]);
3333
const project = sharedProject?.config?.["project"] as Project;
3434
const mapRef = useRef<MapRef | null>(null);
35-
const initialView = sharedProject?.config?.["initial_view_state"] ?? {};
35+
const initialView = sharedProject?.config?.["project"]?.["initial_view_state"] ?? {};
3636

3737
const _projectLayers = useSelector(
3838
(state: RootState) => selectFilteredProjectLayers(state, ["table"]),
@@ -63,7 +63,7 @@ export default function MapPage({ params: { projectId } }) {
6363
},
6464
}}>
6565
<Box>
66-
<PublicProjectNavigation projectLayers={_projectLayers} />
66+
<PublicProjectNavigation projectLayers={_projectLayers} project={project} />
6767
</Box>
6868
<MapViewer
6969
layers={_projectLayers}

apps/web/components/map/panels/ProjectNavigation.tsx

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@ import { ICON_NAME } from "@p4b/ui/components/Icon";
66
import { useTranslation } from "@/i18n/client";
77

88
import { MAPBOX_TOKEN } from "@/lib/constants";
9-
import { setActiveBasemap, setActiveLeftPanel, setActiveRightPanel } from "@/lib/store/map/slice";
9+
import { setActiveLeftPanel, setActiveRightPanel } from "@/lib/store/map/slice";
1010
import { layerType } from "@/lib/validations/common";
1111
import { FeatureName } from "@/lib/validations/organization";
12+
import type { Project } from "@/lib/validations/project";
1213

1314
import { MapSidebarItemID } from "@/types/map/common";
1415

1516
import { useAuthZ } from "@/hooks/auth/AuthZ";
1617
import { useActiveLayer, useFilteredProjectLayers, useLayerActions } from "@/hooks/map/LayerPanelHooks";
18+
import { useBasemap } from "@/hooks/map/MapHooks";
1719
import { useAppDispatch, useAppSelector } from "@/hooks/store/ContextHooks";
1820

1921
import MapSidebar from "@/components/map/Sidebar";
@@ -35,20 +37,21 @@ const sidebarWidth = 52;
3537
const toolbarHeight = 52;
3638

3739
interface ProjectNavigationProps {
38-
projectId: string;
40+
project: Project;
3941
isPublic?: boolean;
42+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
43+
onProjectUpdate?: (key: string, value: any, refresh?: boolean) => void;
4044
}
4145

42-
const ProjectNavigation = ({ projectId }: ProjectNavigationProps) => {
46+
const ProjectNavigation = ({ project, onProjectUpdate }: ProjectNavigationProps) => {
4347
const theme = useTheme();
44-
const { t, i18n } = useTranslation("common");
48+
const { t } = useTranslation("common");
4549
const dispatch = useAppDispatch();
46-
const basemaps = useAppSelector((state) => state.map.basemaps);
47-
const activeBasemap = useAppSelector((state) => state.map.activeBasemap);
50+
const projectId = project.id;
51+
const { translatedBaseMaps, activeBasemap } = useBasemap(project);
4852
const activeLeft = useAppSelector((state) => state.map.activeLeftPanel);
4953
const activeRight = useAppSelector((state) => state.map.activeRightPanel);
5054
const { activeLayer } = useActiveLayer(projectId);
51-
5255
const prevActiveLeftRef = useRef<MapSidebarItemID | undefined>(undefined);
5356
const prevActiveRightRef = useRef<MapSidebarItemID | undefined>(undefined);
5457
const { isProjectEditor, isAppFeatureEnabled } = useAuthZ();
@@ -75,17 +78,6 @@ const ProjectNavigation = ({ projectId }: ProjectNavigationProps) => {
7578
position: "left",
7679
};
7780

78-
const translatedBaseMaps = useMemo(() => {
79-
return basemaps.map((basemap) => ({
80-
...basemap,
81-
title: i18n.exists(`common:basemap_types.${basemap.value}.title`)
82-
? t(`basemap_types.${basemap.value}.title`)
83-
: t(basemap.title),
84-
subtitle: i18n.exists(`common:basemap_types.${basemap.value}.subtitle`)
85-
? t(`basemap_types.${basemap.value}.subtitle`)
86-
: t(basemap.subtitle),
87-
}));
88-
}, [basemaps, i18n, t]);
8981
const rightSidebar: MapSidebarProps = {
9082
topItems: [
9183
{
@@ -266,9 +258,9 @@ const ProjectNavigation = ({ projectId }: ProjectNavigationProps) => {
266258
<Stack direction="column" sx={{ pointerEvents: "all" }}>
267259
<BasemapSelector
268260
styles={translatedBaseMaps}
269-
active={activeBasemap}
270-
basemapChange={(basemap) => {
271-
dispatch(setActiveBasemap(basemap));
261+
active={activeBasemap.value}
262+
basemapChange={async (basemap) => {
263+
await onProjectUpdate?.("basemap", basemap);
272264
}}
273265
/>
274266
</Stack>

apps/web/components/map/panels/PublicProjectNavigation.tsx

Lines changed: 12 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
import { Stack, useTheme } from "@mui/material";
2-
import React, { useMemo } from "react";
2+
import React from "react";
33

44
import { useTranslation } from "@/i18n/client";
55

66
import { MAPBOX_TOKEN } from "@/lib/constants";
77
import { updateProjectLayer } from "@/lib/store/layer/slice";
8-
import { setActiveBasemap } from "@/lib/store/map/slice";
9-
import type { ProjectLayer } from "@/lib/validations/project";
8+
import type { Project, ProjectLayer } from "@/lib/validations/project";
109

11-
import { useAppDispatch, useAppSelector } from "@/hooks/store/ContextHooks";
10+
import { useBasemap } from "@/hooks/map/MapHooks";
11+
import { useAppDispatch } from "@/hooks/store/ContextHooks";
1212

1313
import { BasemapSelector } from "@/components/map/controls/BasemapSelector";
1414
import { Fullscren } from "@/components/map/controls/Fullscreen";
@@ -19,27 +19,18 @@ import Legend from "@/components/map/panels/Legend";
1919
const toolbarHeight = 52;
2020

2121
interface ProjectNavigationProps {
22+
project: Project;
2223
projectLayers?: ProjectLayer[];
24+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
25+
onProjectUpdate?: (key: string, value: any, refresh?: boolean) => void;
2326
}
2427

25-
const PublicProjectNavigation = ({ projectLayers = [] }: ProjectNavigationProps) => {
28+
const PublicProjectNavigation = ({ projectLayers = [], project }: ProjectNavigationProps) => {
2629
const theme = useTheme();
27-
const { t, i18n } = useTranslation("common");
30+
const { t } = useTranslation("common");
2831
const dispatch = useAppDispatch();
29-
const basemaps = useAppSelector((state) => state.map.basemaps);
30-
const activeBasemap = useAppSelector((state) => state.map.activeBasemap);
3132

32-
const translatedBaseMaps = useMemo(() => {
33-
return basemaps.map((basemap) => ({
34-
...basemap,
35-
title: i18n.exists(`common:basemap_types.${basemap.value}.title`)
36-
? t(`basemap_types.${basemap.value}.title`)
37-
: t(basemap.title),
38-
subtitle: i18n.exists(`common:basemap_types.${basemap.value}.subtitle`)
39-
? t(`basemap_types.${basemap.value}.subtitle`)
40-
: t(basemap.subtitle),
41-
}));
42-
}, [basemaps, i18n, t]);
33+
const { translatedBaseMaps, activeBasemap, setActiveBasemap } = useBasemap(project);
4334

4435
return (
4536
<>
@@ -117,9 +108,9 @@ const PublicProjectNavigation = ({ projectLayers = [] }: ProjectNavigationProps)
117108
<Stack direction="column" sx={{ pointerEvents: "all" }}>
118109
<BasemapSelector
119110
styles={translatedBaseMaps}
120-
active={activeBasemap}
111+
active={activeBasemap?.value}
121112
basemapChange={(basemap) => {
122-
dispatch(setActiveBasemap(basemap));
113+
setActiveBasemap(basemap);
123114
}}
124115
/>
125116
</Stack>

apps/web/hooks/map/CommonHooks.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,4 +37,5 @@ const useLayerFields = (
3737
};
3838
};
3939

40+
4041
export default useLayerFields;

apps/web/hooks/map/MapHooks.ts

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { useCallback, useMemo } from "react";
2+
3+
import type { Basemap } from "@/types/map/common";
4+
5+
import { useAppDispatch, useAppSelector } from "@/hooks/store/ContextHooks";
6+
import { useTranslation } from "@/i18n/client";
7+
import { setActiveBasemap as setActiveBasemapAction } from "@/lib/store/map/slice";
8+
9+
export const useBasemap = (project) => {
10+
const { t, i18n } = useTranslation("common");
11+
12+
const basemaps = useAppSelector((state) => state.map.basemaps);
13+
const localBasemap = useAppSelector((state) => state.map.activeBasemap);
14+
const dispatch = useAppDispatch();
15+
16+
const activeBasemap: Basemap = useMemo(() => {
17+
if (!project) {
18+
return basemaps[0];
19+
}
20+
if (localBasemap) {
21+
return localBasemap;
22+
}
23+
if (project.basemap?.startsWith("http")) {
24+
const obj = {
25+
url: project.basemap,
26+
value: "custom",
27+
title: "Custom",
28+
subtitle: "User defined basemap",
29+
thumbnail: basemaps[0].thumbnail,
30+
};
31+
32+
return obj;
33+
} else {
34+
const found = basemaps.find((b) => b.value === project.basemap);
35+
if (found) {
36+
return found;
37+
}
38+
}
39+
return basemaps[0];
40+
}, [basemaps, localBasemap, project]);
41+
42+
43+
const translatedBaseMaps = useMemo(() => {
44+
return basemaps.map((basemap) => ({
45+
...basemap,
46+
title: i18n.exists(`common:basemap_types.${basemap.value}.title`)
47+
? t(`basemap_types.${basemap.value}.title`)
48+
: t(basemap.title),
49+
subtitle: i18n.exists(`common:basemap_types.${basemap.value}.subtitle`)
50+
? t(`basemap_types.${basemap.value}.subtitle`)
51+
: t(basemap.subtitle),
52+
}));
53+
}, [basemaps, i18n, t]);
54+
55+
const setActiveBasemap = useCallback((value: string) => {
56+
const found = basemaps.find((b) => b.value === value);
57+
if (found) {
58+
dispatch(setActiveBasemapAction(found));
59+
} else if (value.startsWith("http")) {
60+
const customBasemap = {
61+
url: value,
62+
value: "custom",
63+
title: "Custom",
64+
subtitle: "User defined basemap",
65+
thumbnail: basemaps[0].thumbnail,
66+
}
67+
dispatch(setActiveBasemapAction(customBasemap));
68+
}
69+
}, [basemaps, dispatch]);
70+
71+
return {
72+
basemaps,
73+
translatedBaseMaps,
74+
activeBasemap,
75+
setActiveBasemap,
76+
};
77+
};

apps/web/lib/store/map/slice.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import type { Project } from "@/lib/validations/project";
1212
export interface MapState {
1313
project: Project | undefined;
1414
basemaps: Basemap[];
15-
activeBasemap: string;
15+
activeBasemap: Basemap | undefined;
1616
maskLayer: string | undefined; // Toolbox mask layer
1717
toolboxStartingPoints: [number, number][] | undefined;
1818
activeLeftPanel: MapSidebarItemID | undefined;
@@ -80,7 +80,7 @@ const initialState = {
8080
}
8181
],
8282
maskLayer: undefined,
83-
activeBasemap: "streets",
83+
activeBasemap: undefined,
8484
activeLeftPanel: MapSidebarItemID.LAYERS,
8585
toolboxStartingPoints: undefined,
8686
activeRightPanel: undefined,
@@ -105,7 +105,7 @@ const mapSlice = createSlice({
105105
state.project = { ...state.project, ...action.payload };
106106
}
107107
},
108-
setActiveBasemap: (state, action: PayloadAction<string>) => {
108+
setActiveBasemap: (state, action: PayloadAction<Basemap>) => {
109109
state.activeBasemap = action.payload;
110110
},
111111
setActiveLeftPanel: (state, action: PayloadAction<MapSidebarItemID | undefined>) => {

apps/web/lib/validations/project.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export const projectSchema = contentMetadataSchema.extend({
3434
layer_order: z.array(z.number()),
3535
max_extent: z.tuple([z.number(), z.number(), z.number(), z.number()]).optional(),
3636
active_scenario_id: z.string().nullable(),
37+
basemap: z.string().optional(),
3738
updated_at: z.string(),
3839
created_at: z.string(),
3940
shared_with: shareProjectSchema.optional(),

0 commit comments

Comments
 (0)