Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
25 changes: 23 additions & 2 deletions src/main/MainMap.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useCallback } from 'react';
import React, { useCallback, memo } from 'react';
import { useTheme } from '@mui/material/styles';
import useMediaQuery from '@mui/material/useMediaQuery';
import { useDispatch, useSelector } from 'react-redux';
Expand All @@ -18,6 +18,13 @@ import MapGeocoder from '../map/geocoder/MapGeocoder';
import MapScale from '../map/MapScale';
import MapNotification from '../map/notification/MapNotification';
import useFeatures from '../common/util/useFeatures';
import MapOverlayButton from '../map/overlay/MapOverlayButton';
import usePersistedState from '../common/util/usePersistedState';
import useMapOverlays from '../map/overlay/useMapOverlays';
import { useAttributePreference } from '../common/util/preferences';

// Memoize MapOverlay to avoid unnecessary re-renders
const MemoizedMapOverlay = memo(MapOverlay);

const MainMap = ({ filteredPositions, selectedPosition, onEventsClick }) => {
const theme = useTheme();
Expand All @@ -27,16 +34,29 @@ const MainMap = ({ filteredPositions, selectedPosition, onEventsClick }) => {

const eventsAvailable = useSelector((state) => !!state.events.items.length);

const [overlayEnabled, setOverlayEnabled] = usePersistedState('mapOverlayEnabled', true);

const features = useFeatures();
const mapOverlays = useMapOverlays();

const selectedMapOverlay = useAttributePreference('selectedMapOverlay');

const activeOverlay = mapOverlays
.filter((overlay) => overlay.available)
.find((overlay) => overlay.id === selectedMapOverlay);

const onMarkerClick = useCallback((_, deviceId) => {
dispatch(devicesActions.selectId(deviceId));
}, [dispatch]);

const onOverlayButtonClick = useCallback(() => {
setOverlayEnabled((enabled) => !enabled);
}, [setOverlayEnabled]);

return (
<>
<MapView>
<MapOverlay />
{overlayEnabled && <MemoizedMapOverlay activeOverlay={activeOverlay} />}
<MapGeofence />
<MapAccuracy positions={filteredPositions} />
<MapLiveRoutes />
Expand All @@ -51,6 +71,7 @@ const MainMap = ({ filteredPositions, selectedPosition, onEventsClick }) => {
<PoiMap />
</MapView>
<MapScale />
{activeOverlay && <MapOverlayButton enabled={overlayEnabled} onClick={onOverlayButtonClick} />}
<MapCurrentLocation />
<MapGeocoder />
{!features.disableEvents && (
Expand Down
9 changes: 1 addition & 8 deletions src/map/overlay/MapOverlay.js
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
import { useId, useEffect } from 'react';
import { useAttributePreference } from '../../common/util/preferences';
import { map } from '../core/MapView';
import useMapOverlays from './useMapOverlays';

const MapOverlay = () => {
const MapOverlay = ({ activeOverlay }) => {
const id = useId();

const mapOverlays = useMapOverlays();
const selectedMapOverlay = useAttributePreference('selectedMapOverlay');

const activeOverlay = mapOverlays.filter((overlay) => overlay.available).find((overlay) => overlay.id === selectedMapOverlay);

useEffect(() => {
if (activeOverlay) {
map.addSource(id, activeOverlay.source);
Expand Down
53 changes: 53 additions & 0 deletions src/map/overlay/MapOverlayButton.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useEffect, useMemo } from 'react';
import { map } from '../core/MapView';
import './overlay.css';

const statusClass = (status) => `maplibregl-ctrl-icon maplibre-ctrl-overlay maplibre-ctrl-overlay-${status}`;

class OverlayControl {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this basically a copy of the alarm button? I think we need to figure out how to reuse a button instead of copying.

Copy link
Author

@TheYakuzo TheYakuzo Apr 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's true, I'll see tomorrow to redo these two buttons.
Do you usually put them in src/common/components?

Or maybe src/map and I create a new components folder in it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map components should be under map folder.

constructor(eventHandler) {
this.eventHandler = eventHandler;
}

static getDefaultPosition() {
return 'top-right';
}

onAdd() {
this.button = document.createElement('button');
this.button.className = statusClass('off');
this.button.type = 'button';
this.button.onclick = () => this.eventHandler(this);

this.container = document.createElement('div');
this.container.className = 'maplibregl-ctrl-group maplibregl-ctrl';
this.container.appendChild(this.button);

return this.container;
}

onRemove() {
this.container.parentNode.removeChild(this.container);
}

setEnabled(enabled) {
this.button.className = statusClass(enabled ? 'on' : 'off');
}
}

const MapOverlayButton = ({ enabled, onClick }) => {
const control = useMemo(() => new OverlayControl(onClick), [onClick]);

useEffect(() => {
map.addControl(control);
return () => map.removeControl(control);
}, [onClick]);

useEffect(() => {
control.setEnabled(enabled);
}, [enabled]);

return null;
};

export default MapOverlayButton;
28 changes: 28 additions & 0 deletions src/map/overlay/overlay.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
.maplibre-ctrl-overlay {
background-repeat: no-repeat;
background-position: center;
pointer-events: auto;
width: 20px;
height: 20px;
display: block;
padding: 5px;
border: none;
cursor: pointer;
}

.maplibregl-ctrl-group button {
width: 30px;
height: 30px;
margin: 0;
padding: 5px;
border: none;
cursor: pointer;
}

.maplibre-ctrl-overlay-on {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 32 32'%3E%3Cpath fill='%2333cc33' d='M28 8h-4V4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h4v4a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2M4 22V4h18v4H10a2 2 0 0 0-2 2v12Zm18 0h-2.586L10 12.586V10h2.586L22 19.416Zm-12-6.586L16.586 22H10Zm12.001 1.173L15.414 10H22ZM10 28v-4h12a2 2 0 0 0 2-2V10h4v18Z'/%3E%3C/svg%3E");
}

.maplibre-ctrl-overlay-off {
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='20' viewBox='0 0 32 32'%3E%3Cpath fill='%23cc3333' d='M28 8h-4V4a2 2 0 0 0-2-2H4a2 2 0 0 0-2 2v18a2 2 0 0 0 2 2h4v4a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2V10a2 2 0 0 0-2-2M4 22V4h18v4H10a2 2 0 0 0-2 2v12Zm18 0h-2.586L10 12.586V10h2.586L22 19.416Zm-12-6.586L16.586 22H10Zm12.001 1.173L15.414 10H22ZM10 28v-4h12a2 2 0 0 0 2-2V10h4v18Z'/%3E%3C/svg%3E");
}