diff --git a/frontend/src/metadata/components/data-process-setter/index.js b/frontend/src/metadata/components/data-process-setter/index.js index 92add309eb..7d87844e98 100644 --- a/frontend/src/metadata/components/data-process-setter/index.js +++ b/frontend/src/metadata/components/data-process-setter/index.js @@ -5,6 +5,7 @@ import SortSetter from './sort-setter'; import GroupbySetter from './groupby-setter'; import PreHideColumnSetter from './pre-hide-column-setter'; import HideColumnSetter from './hide-column-setter'; +import MapTypeSetter from './map-type-setter'; export { GalleryGroupBySetter, @@ -14,4 +15,5 @@ export { GroupbySetter, PreHideColumnSetter, HideColumnSetter, + MapTypeSetter, }; diff --git a/frontend/src/metadata/components/data-process-setter/map-type-setter/index.css b/frontend/src/metadata/components/data-process-setter/map-type-setter/index.css new file mode 100644 index 0000000000..cac45ca19e --- /dev/null +++ b/frontend/src/metadata/components/data-process-setter/map-type-setter/index.css @@ -0,0 +1,57 @@ +.metadata-map-type-setter { + width: fit-content; + height: 36px; + display: flex; + justify-content: center; + align-items: center; + border: 1px solid #e2e2e2; + border-radius: 3px; +} + +.metadata-map-type-setter .metadata-map-type-button { + width: 66px; + height: 28px; + color: #212529; + background-color: #fff; + position: relative; + display: flex; + align-items: center; + justify-content: center; + font-size: 0.875rem; + border: 0; + border-radius: 2px; +} + +.metadata-map-type-setter .metadata-map-type-button:hover { + background-color: #f0f0f0; + cursor: pointer; +} + +.metadata-map-type-setter .metadata-map-type-button.active { + background-color: #f5f5f5; +} + +.metadata-map-type-setter .metadata-map-type-button span { + display: block; + text-align: center; + width: 100%; +} + +.metadata-map-type-button:not(:first-child)::before { + content: ''; + width: 1px; + height: 22px; + background-color: #e2e2e2; + position: absolute; + top: 50%; + transform: translateY(-50%); + transition: opacity 0.3s; + left: -1px; +} + +.metadata-map-type-button:hover::before, +.metadata-map-type-button.active::before, +.metadata-map-type-button:hover + .metadata-map-type-button::before, +.metadata-map-type-button.active + .metadata-map-type-button::before { + opacity: 0; +} \ No newline at end of file diff --git a/frontend/src/metadata/components/data-process-setter/map-type-setter/index.js b/frontend/src/metadata/components/data-process-setter/map-type-setter/index.js new file mode 100644 index 0000000000..8fe2cb1ba5 --- /dev/null +++ b/frontend/src/metadata/components/data-process-setter/map-type-setter/index.js @@ -0,0 +1,49 @@ +import React, { useState, useCallback, useEffect } from 'react'; +import PropTypes from 'prop-types'; +import classnames from 'classnames'; +import { EVENT_BUS_TYPE, MAP_TYPE } from '../../../constants'; +import { gettext } from '../../../../utils/constants'; + +import './index.css'; + +const TYPE_MAP = { + [MAP_TYPE.MAP]: gettext('Map'), + [MAP_TYPE.SATELLITE]: gettext('Satellite') +}; + +const MapTypeSetter = ({ view }) => { + const [currentType, setCurrentType] = useState(MAP_TYPE.MAP); + + useEffect(() => { + const savedValue = window.sfMetadataContext.localStorage.getItem('map-type', MAP_TYPE.MAP); + setCurrentType(savedValue || MAP_TYPE.MAP); + }, [view?._id]); + + const handleTypeChange = useCallback((newType) => { + setCurrentType(newType); + window.sfMetadataContext.localStorage.setItem('map-type', newType); + window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SWITCH_MAP_TYPE, newType); + }, []); + + return ( +
+ {Object.entries(TYPE_MAP).map(([type, label]) => ( + + ))} +
+ ); +}; + +MapTypeSetter.propTypes = { + view: PropTypes.shape({ + _id: PropTypes.string + }) +}; + +export default MapTypeSetter; diff --git a/frontend/src/metadata/components/view-toolbar/map-view-toolbar/index.js b/frontend/src/metadata/components/view-toolbar/map-view-toolbar/index.js index 4be657a74f..8d2e0721f4 100644 --- a/frontend/src/metadata/components/view-toolbar/map-view-toolbar/index.js +++ b/frontend/src/metadata/components/view-toolbar/map-view-toolbar/index.js @@ -1,7 +1,7 @@ import React, { useMemo } from 'react'; import PropTypes from 'prop-types'; import { PRIVATE_COLUMN_KEY } from '../../../constants'; -import { FilterSetter } from '../../data-process-setter'; +import { FilterSetter, MapTypeSetter } from '../../data-process-setter'; const MapViewToolBar = ({ readOnly, @@ -22,6 +22,7 @@ const MapViewToolBar = ({ return ( <>
+ { }, [repoID, metadata]); const addMapController = useCallback(() => { - var navigation = new window.BMap.NavigationControl(); + const ZoomControl = createBMapZoomControl(window.BMap); + const zoomControl = new ZoomControl(); const GeolocationControl = createBMapGeolocationControl(window.BMap, (err, point) => { if (!err && point) { mapRef.current.setCenter({ lng: point.lng, lat: point.lat }); } }); + const geolocationControl = new GeolocationControl(); + + mapRef.current.addControl(zoomControl); mapRef.current.addControl(geolocationControl); - mapRef.current.addControl(navigation); }, []); const renderMarkersBatch = useCallback(() => { @@ -121,6 +125,16 @@ const Map = () => { ); }, []); + const getMapType = useCallback((type) => { + if (!mapRef.current) return; + switch (type) { + case MAP_TYPE.SATELLITE: + return window.BMAP_SATELLITE_MAP; + default: + return window.BMAP_NORMAL_MAP; + } + }, []); + const renderBaiduMap = useCallback(() => { setIsLoading(false); if (!window.BMap.Map) return; @@ -142,6 +156,8 @@ const Map = () => { const point = new window.BMap.Point(lng, lat); mapRef.current.centerAndZoom(point, DEFAULT_ZOOM); mapRef.current.enableScrollWheelZoom(true); + // const type = window.sfMetadataContext.localStorage.getItem('map-type'); + // mapRef.current.setMapType(getMapType(type)); addMapController(); initializeUserMarker(); @@ -152,7 +168,19 @@ const Map = () => { }, [addMapController, initializeClusterer, initializeUserMarker, renderMarkersBatch]); useEffect(() => { - if (mapInfo.type === MAP_TYPE.B_MAP) { + const switchMapTypeSubscribe = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SWITCH_MAP_TYPE, (newType) => { + window.sfMetadataContext.localStorage.setItem('map-type', newType); + mapRef.current && mapRef.current.setMapType(getMapType(newType)); + }); + + return () => { + switchMapTypeSubscribe(); + }; + + }, [getMapType]); + + useEffect(() => { + if (mapInfo.type === MAP_PROVIDER.B_MAP) { window.renderMap = renderBaiduMap; loadBMap(mapInfo.key).then(() => renderBaiduMap()); return () => { diff --git a/frontend/src/metadata/views/map/zoom-control.js b/frontend/src/metadata/views/map/zoom-control.js new file mode 100644 index 0000000000..3600ba85ff --- /dev/null +++ b/frontend/src/metadata/views/map/zoom-control.js @@ -0,0 +1,65 @@ +import { Utils } from '../../../utils/utils'; + +export function createBMapZoomControl(BMap, callback) { + function ZoomControl() { + this.defaultAnchor = window.BMAP_ANCHOR_BOTTOM_RIGHT; + this.defaultOffset = new BMap.Size(80, Utils.isDesktop() ? 30 : 90); + } + ZoomControl.prototype = new window.BMap.Control(); + ZoomControl.prototype.initialize = function (map) { + const div = document.createElement('div'); + div.className = 'sf-BMap-zoom-control'; + div.style = 'display: flex; justify-content: center; align-items: center;'; + + const zoomInButton = document.createElement('button'); + zoomInButton.className = 'sf-BMap-zoom-button'; + zoomInButton.style = 'display: flex; justify-content: center; align-items: center;'; + zoomInButton.innerHTML = ''; + div.appendChild(zoomInButton); + + const divider = document.createElement('div'); + divider.style = 'height: 22px; width: 1px; background-color: #ccc;'; + div.appendChild(divider); + + const zoomOutButton = document.createElement('button'); + zoomOutButton.className = 'sf-BMap-zoom-button'; + zoomOutButton.style = 'display: flex; justify-content: center; align-items: center;'; + zoomOutButton.innerHTML = ''; + div.appendChild(zoomOutButton); + + if (Utils.isDesktop()) { + setNodeStyle(div, 'height: 40px; width: 111px; line-height: 40px'); + } else { + setNodeStyle(div, 'height: 35px; width: 80px; line-height: 35px; opacity: 0.75'); + } + + const updateButtonStates = () => { + const zoomLevel = map.getZoom(); + const maxZoom = map.getMaxZoom(); + const minZoom = map.getMinZoom(); + + zoomInButton.disabled = zoomLevel >= maxZoom; + zoomOutButton.disabled = zoomLevel <= minZoom; + }; + + zoomInButton.onclick = (e) => { + e.preventDefault(); + map.zoomTo(map.getZoom() + 2); + }; + + zoomOutButton.onclick = (e) => { + e.preventDefault(); + map.zoomTo(map.getZoom() - 2); + }; + + map.addEventListener('zoomend', updateButtonStates); + map.getContainer().appendChild(div); + return div; + }; + + return ZoomControl; +} + +function setNodeStyle(dom, styleText) { + dom.style.cssText += styleText; +}