diff --git a/frontend/src/metadata/components/view-toolbar/index.js b/frontend/src/metadata/components/view-toolbar/index.js
index efa6bd8386..0c6fc2b158 100644
--- a/frontend/src/metadata/components/view-toolbar/index.js
+++ b/frontend/src/metadata/components/view-toolbar/index.js
@@ -114,11 +114,9 @@ const ViewToolBar = ({ viewId, isCustomPermission, onToggleDetail, onCloseDetail
{viewType === VIEW_TYPE.MAP && (
)}
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 b961c3a458..5b5e779858 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,20 +1,14 @@
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import React, { useMemo } from 'react';
import PropTypes from 'prop-types';
-import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY, VIEW_TYPE } from '../../../constants';
-import { FilterSetter, GalleryGroupBySetter, MapTypeSetter, SortSetter } from '../../data-process-setter';
-import { gettext } from '../../../../utils/constants';
+import { PRIVATE_COLUMN_KEY, VIEW_TYPE } from '../../../constants';
+import { FilterSetter, MapTypeSetter } from '../../data-process-setter';
const MapViewToolBar = ({
- isCustomPermission,
readOnly,
view: oldView,
collaborators,
modifyFilters,
- onToggleDetail,
}) => {
- const [showGalleryToolbar, setShowGalleryToolbar] = useState(false);
- const [view, setView] = useState(oldView);
-
const viewType = useMemo(() => VIEW_TYPE.MAP, []);
const viewColumns = useMemo(() => {
if (!oldView) return [];
@@ -26,54 +20,6 @@ const MapViewToolBar = ({
return viewColumns && viewColumns.filter(c => c.key !== PRIVATE_COLUMN_KEY.FILE_TYPE);
}, [viewColumns]);
- const onToggle = useCallback((value) => {
- setShowGalleryToolbar(value);
- }, []);
-
- const modifySorts = useCallback((sorts) => {
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, { sorts });
- }, []);
-
- const resetView = useCallback(view => {
- setView(view);
- }, []);
-
- useEffect(() => {
- setShowGalleryToolbar(false);
- const unsubscribeToggle = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, onToggle);
- const unsubscribeView = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.RESET_VIEW, resetView);
- return () => {
- unsubscribeToggle && unsubscribeToggle();
- unsubscribeView && unsubscribeView();
- };
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, [viewID]);
-
- if (showGalleryToolbar) {
- return (
- <>
-
@@ -99,11 +45,10 @@ const MapViewToolBar = ({
};
MapViewToolBar.propTypes = {
- isCustomPermission: PropTypes.bool,
readOnly: PropTypes.bool,
+ view: PropTypes.object,
collaborators: PropTypes.array,
modifyFilters: PropTypes.func,
- onToggleDetail: PropTypes.func,
};
export default MapViewToolBar;
diff --git a/frontend/src/metadata/views/map/cluster-photos/index.css b/frontend/src/metadata/views/map/cluster-photos/index.css
deleted file mode 100644
index e190c9727a..0000000000
--- a/frontend/src/metadata/views/map/cluster-photos/index.css
+++ /dev/null
@@ -1,4 +0,0 @@
-.sf-metadata-map-photos-container {
- padding: 0 !important;
- overflow: hidden !important;
-}
diff --git a/frontend/src/metadata/views/map/cluster-photos/index.js b/frontend/src/metadata/views/map/cluster-photos/index.js
deleted file mode 100644
index 4678e616fc..0000000000
--- a/frontend/src/metadata/views/map/cluster-photos/index.js
+++ /dev/null
@@ -1,159 +0,0 @@
-import React, { useCallback, useEffect, useState } from 'react';
-import PropTypes from 'prop-types';
-import deepCopy from 'deep-copy';
-import dayjs from 'dayjs';
-import utc from 'dayjs/plugin/utc';
-import { CenteredLoading } from '@seafile/sf-metadata-ui-component';
-import Gallery from '../../gallery/main';
-import { EVENT_BUS_TYPE, UTC_FORMAT_DEFAULT } from '../../../constants';
-import metadataAPI from '../../../api';
-import { Utils } from '../../../../utils/utils';
-import toaster from '../../../../components/toast';
-import { useMetadataView } from '../../../hooks/metadata-view';
-import { getRowsByIds } from '../../../utils/table';
-import Metadata from '../../../model/metadata';
-import { sortTableRows } from '../../../utils/sort';
-import { useCollaborators } from '../../../hooks/collaborators';
-import { getRecordIdFromRecord, getParentDirFromRecord, getFileNameFromRecord } from '../../../utils/cell';
-
-import './index.css';
-
-dayjs.extend(utc);
-
-const ClusterPhotos = ({ photoIds, onClose }) => {
- const { repoID, viewID, metadata: allMetadata, store, addFolder, deleteRecords } = useMetadataView();
- const { collaborators } = useCollaborators();
-
- const [isLoading, setLoading] = useState(true);
- const [metadata, setMetadata] = useState({ rows: getRowsByIds(allMetadata, photoIds), columns: allMetadata?.columns || [] });
-
- const loadData = useCallback((view) => {
- setLoading(true);
- const columns = metadata.columns;
- const orderRows = sortTableRows({ columns }, metadata.rows, view?.sorts || [], { collaborators, isReturnID: false });
- let newMetadata = new Metadata({ rows: orderRows, columns, view });
- newMetadata.hasMore = false;
- setMetadata(newMetadata);
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.RESET_VIEW, newMetadata.view);
- setLoading(false);
- }, [metadata, collaborators]);
-
- const deletedByIds = useCallback((ids) => {
- if (!Array.isArray(ids) || ids.length === 0) return;
- const newMetadata = deepCopy(metadata);
- const idNeedDeletedMap = ids.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
- newMetadata.rows = newMetadata.rows.filter((row) => !idNeedDeletedMap[row._id]);
- newMetadata.row_ids = newMetadata.row_ids.filter((id) => !idNeedDeletedMap[id]);
-
- // delete rows in id_row_map
- ids.forEach(rowId => {
- delete newMetadata.id_row_map[rowId];
- });
- newMetadata.recordsCount = newMetadata.row_ids.length;
- setMetadata(newMetadata);
-
- if (newMetadata.rows.length === 0) {
- onClose && onClose();
- }
- }, [metadata, onClose]);
-
- const handelDelete = useCallback((deletedImages, { success_callback } = {}) => {
- if (!deletedImages.length) return;
- let recordIds = [];
- deletedImages.forEach((record) => {
- const { id, parentDir, name } = record || {};
- if (parentDir && name) {
- recordIds.push(id);
- }
- });
- deleteRecords(recordIds, {
- success_callback: () => {
- success_callback();
- deletedByIds(recordIds);
- }
- });
- }, [deleteRecords, deletedByIds]);
-
- const onViewChange = useCallback((update) => {
- metadataAPI.modifyView(repoID, viewID, update).then(res => {
- store.modifyLocalView(update);
- const newView = { ...metadata.view, ...update };
- loadData(newView);
- }).catch(error => {
- const errorMessage = Utils.getErrorMsg(error);
- toaster.danger(errorMessage);
- });
- }, [metadata, repoID, viewID, store, loadData]);
-
- const onRecordChange = useCallback(({ recordId, parentDir, fileName }, update) => {
- const modifyTime = dayjs().utc().format(UTC_FORMAT_DEFAULT);
- const modifier = window.sfMetadataContext.getUsername();
- const { rows, columns, view } = metadata;
- let newRows = [...rows];
- newRows.forEach((row, index) => {
- const _rowId = getRecordIdFromRecord(row);
- const _parentDir = getParentDirFromRecord(row);
- const _fileName = getFileNameFromRecord(row);
- if ((_rowId === recordId || (_parentDir === parentDir && _fileName === fileName)) && update) {
- const updatedRow = Object.assign({}, row, update, {
- '_mtime': modifyTime,
- '_last_modifier': modifier,
- });
- newRows[index] = updatedRow;
- }
- });
- let updatedColumnKeyMap = {
- '_mtime': true,
- '_last_modifier': true
- };
- Object.keys(update).forEach(key => {
- updatedColumnKeyMap[key] = true;
- });
- if (view.sorts.some(sort => updatedColumnKeyMap[sort.column_key])) {
- newRows = sortTableRows({ columns }, newRows, view?.sorts || [], { collaborators, isReturnID: false });
- }
- let newMetadata = new Metadata({ rows: newRows, columns, view });
- newMetadata.hasMore = false;
- setMetadata(newMetadata);
- }, [metadata, collaborators]);
-
- useEffect(() => {
- const eventBus = window?.sfMetadataContext?.eventBus;
- if (!eventBus) return;
- eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, true);
- return () => {
- eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, false);
- };
- }, []);
-
- useEffect(() => {
- const eventBus = window?.sfMetadataContext?.eventBus;
- if (!eventBus) return;
- const unsubscribeViewChange = eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, onViewChange);
- const localRecordChangedSubscribe = eventBus.subscribe(EVENT_BUS_TYPE.LOCAL_RECORD_CHANGED, onRecordChange);
- return () => {
- unsubscribeViewChange && unsubscribeViewChange();
- localRecordChangedSubscribe && localRecordChangedSubscribe();
- };
- }, [onViewChange, onRecordChange]);
-
- useEffect(() => {
- loadData({ sorts: allMetadata.view.sorts });
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- if (isLoading) return (
);
-
- return (
-
-
-
- );
-};
-
-ClusterPhotos.propTypes = {
- photoIds: PropTypes.array,
- onClose: PropTypes.func,
-};
-
-export default ClusterPhotos;
diff --git a/frontend/src/metadata/views/map/map-view/control/geolocation-control/index.css b/frontend/src/metadata/views/map/control/geolocation-control/index.css
similarity index 61%
rename from frontend/src/metadata/views/map/map-view/control/geolocation-control/index.css
rename to frontend/src/metadata/views/map/control/geolocation-control/index.css
index 49359bb5bd..f0e25e0ace 100644
--- a/frontend/src/metadata/views/map/map-view/control/geolocation-control/index.css
+++ b/frontend/src/metadata/views/map/control/geolocation-control/index.css
@@ -1,4 +1,4 @@
.sf-map-control-container.sf-map-geolocation-control {
- width: 40px;
- line-height: 40px;
+ width: 30px;
+ line-height: 30px;
}
diff --git a/frontend/src/metadata/views/map/map-view/control/geolocation-control/index.js b/frontend/src/metadata/views/map/control/geolocation-control/index.js
similarity index 96%
rename from frontend/src/metadata/views/map/map-view/control/geolocation-control/index.js
rename to frontend/src/metadata/views/map/control/geolocation-control/index.js
index 62e05f53c3..575cc15088 100644
--- a/frontend/src/metadata/views/map/map-view/control/geolocation-control/index.js
+++ b/frontend/src/metadata/views/map/control/geolocation-control/index.js
@@ -1,5 +1,5 @@
import classnames from 'classnames';
-import { Utils } from '../../../../../../utils/utils';
+import { Utils } from '../../../../../utils/utils';
import './index.css';
diff --git a/frontend/src/metadata/views/map/map-view/control/index.css b/frontend/src/metadata/views/map/control/index.css
similarity index 87%
rename from frontend/src/metadata/views/map/map-view/control/index.css
rename to frontend/src/metadata/views/map/control/index.css
index c9157de961..97181cb2b6 100644
--- a/frontend/src/metadata/views/map/map-view/control/index.css
+++ b/frontend/src/metadata/views/map/control/index.css
@@ -1,12 +1,12 @@
.sf-map-control-container {
- height: 40px;
+ height: 30px;
width: fit-content;
background-color: rgba(255, 255, 255, .9);
opacity: 1;
overflow: hidden;
border-radius: 6px;
box-shadow: -2px -2px 4px 2px rgba(0, 0, 0, 0.1);
- line-height: 40px;
+ line-height: 30px;
}
.sf-map-control-container.sf-map-control-loading {
@@ -14,8 +14,8 @@
}
.sf-map-control-container.sf-map-control-container-mobile {
- height: 35px;
- line-height: 35px;
+ height: 25px;
+ line-height: 25px;
opacity: .75;
}
@@ -36,21 +36,21 @@
}
.sf-map-control-container .sf-map-control {
- height: 40px;
- width: 40px;
+ height: 30px;
+ width: 30px;
color: #666;
background-color: inherit;
text-align: center;
}
.sf-map-control-container.sf-map-control-container-mobile .sf-map-control {
- height: 35px;
- width: 35px;
+ height: 25px;
+ width: 25px;
opacity: .75;
}
.sf-map-control-container .sf-map-control .sf-map-control-icon {
- font-size: 18px;
+ font-size: 14px;
}
.sf-map-control-container .sf-map-control:not(.disabled):hover {
diff --git a/frontend/src/metadata/views/map/map-view/control/index.js b/frontend/src/metadata/views/map/control/index.js
similarity index 100%
rename from frontend/src/metadata/views/map/map-view/control/index.js
rename to frontend/src/metadata/views/map/control/index.js
diff --git a/frontend/src/metadata/views/map/map-view/control/zoom-control/index.css b/frontend/src/metadata/views/map/control/zoom-control/index.css
similarity index 83%
rename from frontend/src/metadata/views/map/map-view/control/zoom-control/index.css
rename to frontend/src/metadata/views/map/control/zoom-control/index.css
index 98deb1eabd..f2dd9dd107 100644
--- a/frontend/src/metadata/views/map/map-view/control/zoom-control/index.css
+++ b/frontend/src/metadata/views/map/control/zoom-control/index.css
@@ -1,3 +1,3 @@
.sf-map-control-container.sf-map-zoom-control-container .sf-map-control {
- width: 55px;
+ width: 40px;
}
diff --git a/frontend/src/metadata/views/map/map-view/control/zoom-control/index.js b/frontend/src/metadata/views/map/control/zoom-control/index.js
similarity index 94%
rename from frontend/src/metadata/views/map/map-view/control/zoom-control/index.js
rename to frontend/src/metadata/views/map/control/zoom-control/index.js
index 79057f567e..5a82f84ff9 100644
--- a/frontend/src/metadata/views/map/map-view/control/zoom-control/index.js
+++ b/frontend/src/metadata/views/map/control/zoom-control/index.js
@@ -1,12 +1,12 @@
import classnames from 'classnames';
-import { Utils } from '../../../../../../utils/utils';
+import { Utils } from '../../../../../utils/utils';
import './index.css';
export function createBMapZoomControl(BMapGL, { maxZoom, minZoom }, callback) {
function ZoomControl() {
this.defaultAnchor = window.BMAP_ANCHOR_BOTTOM_RIGHT;
- this.defaultOffset = new BMapGL.Size(80, Utils.isDesktop() ? 30 : 90);
+ this.defaultOffset = new BMapGL.Size(66, Utils.isDesktop() ? 30 : 90);
}
ZoomControl.prototype = new BMapGL.Control();
ZoomControl.prototype.initialize = function (map) {
diff --git a/frontend/src/metadata/views/map/index.css b/frontend/src/metadata/views/map/index.css
index 22ff37c943..faf70d2ac4 100644
--- a/frontend/src/metadata/views/map/index.css
+++ b/frontend/src/metadata/views/map/index.css
@@ -4,3 +4,86 @@
display: flex;
flex-direction: column;
}
+
+.sf-metadata-view-map #platform div:has(.custom-avatar-overlay) {
+ display: block !important;
+}
+
+.sf-metadata-view-map #platform div:has(.custom-image-overlay) {
+ display: block !important;
+}
+
+.sf-metadata-view-map .sf-metadata-map-container {
+ width: 100%;
+ height: 100%;
+}
+
+.sf-metadata-view-map .custom-image-container {
+ width: 86px;
+ height: 86px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ background-color: #fff;
+ padding: 3px;
+ border-radius: 6px;
+ position: relative;
+ cursor: default;
+}
+
+.sf-metadata-view-map .custom-image-container img {
+ border-radius: 6px;
+}
+
+.sf-metadata-view-map .custom-image-number {
+ position: absolute;
+ right: -16px;
+ top: -16px;
+ width: 32px;
+ height: 32px;
+ line-height: 32px;
+ background: #007bff;
+ color: #fff;
+ border-radius: 50%;
+ text-align: center;
+ font-size: 16px;
+ font-weight: 400;
+}
+
+.sf-metadata-view-map .custom-image-container:active::before,
+.sf-metadata-view-map .custom-image-container:active .custom-image-number::before,
+.sf-metadata-view-map .custom-image-number:active::before,
+.sf-metadata-view-map .custom-image-number:active .custom-image-container::before {
+ content: '';
+ position: absolute;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ background: rgba(0, 0, 0, 0.4);
+ border-radius: 6px;
+}
+
+.sf-metadata-view-map .custom-image-container:active .custom-image-number::before,
+.sf-metadata-view-map .custom-image-number:active::before {
+ border-radius: 50%;
+}
+
+.sf-metadata-view-map .custom-image-container::after {
+ content: '';
+ position: absolute;
+ bottom: -10px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 0;
+ height: 0;
+ border-left: 10px solid transparent;
+ border-right: 10px solid transparent;
+ border-top: 10px solid #fff;
+ border-radius: 2px;
+}
+
+.sf-metadata-view-map .custom-image-container:active::after,
+.sf-metadata-view-map .custom-image-number:active .custom-image-container::after {
+ border-top: 10px solid rgba(0, 0, 0, 0.4);
+}
diff --git a/frontend/src/metadata/views/map/index.js b/frontend/src/metadata/views/map/index.js
index b6d25eecce..35d90aeab4 100644
--- a/frontend/src/metadata/views/map/index.js
+++ b/frontend/src/metadata/views/map/index.js
@@ -1,17 +1,32 @@
-import React, { useEffect, useMemo } from 'react';
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getFileNameFromRecord, getFileTypeFromRecord, getImageLocationFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell';
-import MapView from './map-view';
-import { PREDEFINED_FILE_TYPE_OPTION_KEY } from '../../constants';
import { useMetadataView } from '../../hooks/metadata-view';
import { Utils } from '../../../utils/utils';
-import { fileServerRoot, siteRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
import { isValidPosition } from '../../utils/validate';
import { gcj02_to_bd09, wgs84_to_gcj02 } from '../../../utils/coord-transform';
-import { PRIVATE_FILE_TYPE } from '../../../constants';
+import loadBMap, { initMapInfo } from '../../../utils/map-utils';
+import { appAvatarURL, baiduMapKey, fileServerRoot, googleMapKey, mediaUrl, siteRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
+import { MAP_TYPE as MAP_PROVIDER, PRIVATE_FILE_TYPE } from '../../../constants';
+import { EVENT_BUS_TYPE, MAP_TYPE, PREDEFINED_FILE_TYPE_OPTION_KEY, STORAGE_MAP_CENTER_KEY, STORAGE_MAP_TYPE_KEY, STORAGE_MAP_ZOOM_KEY } from '../../constants';
+import { createBMapGeolocationControl, createBMapZoomControl } from './control';
+import { customAvatarOverlay, customImageOverlay } from './overlay';
+import ModalPortal from '../../../components/modal-portal';
+import ImageDialog from '../../../components/dialog/image-dialog';
import './index.css';
+const DEFAULT_POSITION = { lng: 104.195, lat: 35.861 };
+const DEFAULT_ZOOM = 4;
+const MAX_ZOOM = 21;
+const MIN_ZOOM = 3;
+
const Map = () => {
+ const [imageIndex, setImageIndex] = useState(0);
+ const [clusterLeaveIds, setClusterLeaveIds] = useState([]);
+
+ const mapRef = useRef(null);
+ const clusterRef = useRef(null);
+ const clickTimeoutRef = useRef(null);
const { metadata, viewID, updateCurrentPath } = useMetadataView();
const repoID = window.sfMetadataContext.getSetting('repoID');
@@ -52,18 +67,219 @@ const Map = () => {
downloadURL: `${fileServerRoot}repos/${repoID}/files${path}?op=download`,
thumbnail,
parentDir,
- location: { lng: bdPosition.lng, lat: bdPosition.lat }
+ location: bdPosition
};
})
.filter(Boolean);
}, [repoID, repoInfo.encrypted, metadata]);
+ const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
+ const clusterLeaves = useMemo(() => images.filter(image => clusterLeaveIds.includes(image.id)), [images, clusterLeaveIds]);
+
+ const getPoints = useCallback(() => {
+ if (!window.Cluster || !images) return [];
+ return window.Cluster.pointTransformer(images, (data) => ({
+ point: [data.location.lng, data.location.lat],
+ properties: {
+ id: data.id,
+ src: data.src,
+ }
+ }));
+ }, [images]);
+
+ const saveMapState = useCallback(() => {
+ if (!mapRef.current) return;
+ const point = mapRef.current.getCenter && mapRef.current.getCenter();
+ const zoom = mapRef.current.getZoom && mapRef.current.getZoom();
+ window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_CENTER_KEY, point);
+ window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_ZOOM_KEY, zoom);
+ }, []);
+
+ const addMapController = useCallback(() => {
+ const ZoomControl = createBMapZoomControl(window.BMapGL, { maxZoom: MAX_ZOOM, minZoom: MIN_ZOOM }, saveMapState);
+ const zoomControl = new ZoomControl();
+ const GeolocationControl = createBMapGeolocationControl(window.BMapGL, (point) => {
+ point && mapRef.current && mapRef.current.setCenter(point);
+ });
+
+ const geolocationControl = new GeolocationControl();
+ mapRef.current.addControl(zoomControl);
+ mapRef.current.addControl(geolocationControl);
+ }, [saveMapState]);
+
+ const initializeUserMarker = useCallback((centerPoint) => {
+ if (!window.BMapGL || !mapRef.current) return;
+ const imageUrl = `${mediaUrl}img/marker.png`;
+ const avatarMarker = customAvatarOverlay(centerPoint, appAvatarURL, imageUrl);
+ mapRef.current.addOverlay(avatarMarker);
+ }, []);
+
+ const getBMapType = useCallback((type) => {
+ switch (type) {
+ case MAP_TYPE.SATELLITE: {
+ return window.BMAP_EARTH_MAP;
+ }
+ default: {
+ return window.BMAP_NORMAL_MAP;
+ }
+ }
+ }, []);
+
+ const loadMapState = useCallback(() => {
+ const savedCenter = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_CENTER_KEY) || DEFAULT_POSITION;
+ const savedZoom = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_ZOOM_KEY) || DEFAULT_ZOOM;
+ return { center: savedCenter, zoom: savedZoom };
+ }, []);
+
+ const initializeCluster = useCallback(() => {
+ clusterRef.current = new window.Cluster.View(mapRef.current, {
+ clusterRadius: 60,
+ updateRealTime: true,
+ fitViewOnClick: false,
+ isAnimation: true,
+ clusterMap: (properties) => ({ src: properties.src, id: properties.id }),
+ clusterReduce: (acc, properties) => {
+ if (!acc.properties) {
+ acc.properties = [];
+ }
+ acc.properties.push(properties);
+ },
+ renderClusterStyle: {
+ type: window.Cluster.ClusterRender.DOM,
+ style: { offsetX: -40, offsetY: -80 },
+ inject: (props) => customImageOverlay(props),
+ },
+ });
+
+ clusterRef.current.setData(getPoints());
+
+ clusterRef.current.on(window.Cluster.ClusterEvent.CLICK, (element) => {
+ if (clickTimeoutRef.current) {
+ clearTimeout(clickTimeoutRef.current);
+ clickTimeoutRef.current = null;
+ return;
+ } else {
+ clickTimeoutRef.current = setTimeout(() => {
+ let imageIds = [];
+ if (element.isCluster) {
+ imageIds = clusterRef.current.getLeaves(element.id).map(item => item.properties.id).filter(Boolean);
+ } else {
+ imageIds = [element.properties.id];
+ }
+ clickTimeoutRef.current = null;
+ setClusterLeaveIds(imageIds);
+ }, 300);
+ }
+ });
+ window.BMapCluster = clusterRef.current;
+ }, [getPoints]);
+
+ const renderBaiduMap = useCallback(() => {
+ if (!window.BMapGL.Map) return;
+ let { center, zoom } = loadMapState();
+ let userPosition = { lng: 116.40396418840683, lat: 39.915106021711345 };
+ // ask for user location
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition((userInfo) => {
+ const gcPosition = wgs84_to_gcj02(userInfo.coords.longitude, userInfo.coords.latitude);
+ const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
+ const { lng, lat } = bdPosition;
+ userPosition = new window.BMapGL.Point(lng, lat);
+ center = userPosition;
+ window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_CENTER_KEY, center);
+ });
+ }
+ const mapTypeValue = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_TYPE_KEY);
+
+ if (!window.BMapInstance) {
+ mapRef.current = new window.BMapGL.Map('sf-metadata-map-container', {
+ enableMapClick: false,
+ minZoom: MIN_ZOOM,
+ maxZoom: MAX_ZOOM,
+ mapType: getBMapType(mapTypeValue),
+ });
+ window.BMapInstance = mapRef.current;
+
+ if (isValidPosition(center?.lng, center?.lat)) {
+ mapRef.current.centerAndZoom(center, zoom);
+ }
+
+ mapRef.current.enableScrollWheelZoom(true);
+ addMapController();
+
+ initializeUserMarker(userPosition);
+ initializeCluster();
+ } else {
+ const viewDom = document.getElementById('sf-metadata-view-map');
+ const container = window.BMapInstance.getContainer();
+ viewDom.replaceChild(container, mapRef.current);
+
+ mapRef.current = window.BMapInstance;
+ clusterRef.current = window.BMapCluster;
+ clusterRef.current.setData(getPoints());
+ }
+ }, [addMapController, initializeCluster, initializeUserMarker, getBMapType, loadMapState, getPoints]);
+
+ const handleClose = useCallback(() => {
+ setImageIndex(0);
+ setClusterLeaveIds([]);
+ }, []);
+
+ const moveToPrevImage = useCallback(() => {
+ setImageIndex((imageIndex + clusterLeaves.length - 1) % clusterLeaves.length);
+ }, [imageIndex, clusterLeaves.length]);
+
+ const moveToNextImage = useCallback(() => {
+ setImageIndex((imageIndex + 1) % clusterLeaves.length);
+ }, [imageIndex, clusterLeaves.length]);
+
useEffect(() => {
updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}`);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- return (
);
+ useEffect(() => {
+ const modifyMapTypeSubscribe = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_MAP_TYPE, (newType) => {
+ window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_TYPE_KEY, newType);
+ const mapType = getBMapType(newType);
+ mapRef.current && mapRef.current.setMapType(mapType);
+ mapRef.current.setCenter(mapRef.current.getCenter());
+ });
+
+ return () => {
+ modifyMapTypeSubscribe();
+ };
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ useEffect(() => {
+ if (mapInfo.type === MAP_PROVIDER.B_MAP) {
+ loadBMap(mapInfo.key).then(() => renderBaiduMap());
+ return () => {
+ window.renderMap = null;
+ };
+ }
+ return;
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+
+ );
};
export default Map;
diff --git a/frontend/src/metadata/views/map/map-view/index.css b/frontend/src/metadata/views/map/map-view/index.css
deleted file mode 100644
index 58f9773770..0000000000
--- a/frontend/src/metadata/views/map/map-view/index.css
+++ /dev/null
@@ -1,82 +0,0 @@
-.sf-metadata-view-map #platform div:has(.custom-avatar-overlay) {
- display: block !important;
-}
-
-.sf-metadata-view-map #platform div:has(.custom-image-overlay) {
- display: block !important;
-}
-
-.sf-metadata-view-map .sf-metadata-map-container {
- width: 100%;
- height: 100%;
-}
-
-.sf-metadata-view-map .custom-image-container {
- width: 86px;
- height: 86px;
- display: flex;
- align-items: center;
- justify-content: center;
- background-color: #fff;
- padding: 3px;
- border-radius: 6px;
- position: relative;
- cursor: default;
-}
-
-.sf-metadata-view-map .custom-image-container img {
- border-radius: 6px;
-}
-
-.sf-metadata-view-map .custom-image-number {
- position: absolute;
- right: -16px;
- top: -16px;
- width: 32px;
- height: 32px;
- line-height: 32px;
- background: #007bff;
- color: #fff;
- border-radius: 50%;
- text-align: center;
- font-size: 16px;
- font-weight: 400;
-}
-
-.sf-metadata-view-map .custom-image-container:active::before,
-.sf-metadata-view-map .custom-image-container:active .custom-image-number::before,
-.sf-metadata-view-map .custom-image-number:active::before,
-.sf-metadata-view-map .custom-image-number:active .custom-image-container::before {
- content: '';
- position: absolute;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- background: rgba(0, 0, 0, 0.4);
- border-radius: 6px;
-}
-
-.sf-metadata-view-map .custom-image-container:active .custom-image-number::before,
-.sf-metadata-view-map .custom-image-number:active::before {
- border-radius: 50%;
-}
-
-.sf-metadata-view-map .custom-image-container::after {
- content: '';
- position: absolute;
- bottom: -10px;
- left: 50%;
- transform: translateX(-50%);
- width: 0;
- height: 0;
- border-left: 10px solid transparent;
- border-right: 10px solid transparent;
- border-top: 10px solid #fff;
- border-radius: 2px;
-}
-
-.sf-metadata-view-map .custom-image-container:active::after,
-.sf-metadata-view-map .custom-image-number:active .custom-image-container::after {
- border-top: 10px solid rgba(0, 0, 0, 0.4);
-}
diff --git a/frontend/src/metadata/views/map/map-view/index.js b/frontend/src/metadata/views/map/map-view/index.js
deleted file mode 100644
index dea03ed4b7..0000000000
--- a/frontend/src/metadata/views/map/map-view/index.js
+++ /dev/null
@@ -1,226 +0,0 @@
-import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import PropTypes from 'prop-types';
-import loadBMap, { initMapInfo } from '../../../../utils/map-utils';
-import { appAvatarURL, baiduMapKey, googleMapKey, mediaUrl } from '../../../../utils/constants';
-import { isValidPosition } from '../../../utils/validate';
-import { wgs84_to_gcj02, gcj02_to_bd09 } from '../../../../utils/coord-transform';
-import { MAP_TYPE as MAP_PROVIDER } from '../../../../constants';
-import { EVENT_BUS_TYPE, MAP_TYPE, STORAGE_MAP_CENTER_KEY, STORAGE_MAP_TYPE_KEY, STORAGE_MAP_ZOOM_KEY } from '../../../constants';
-import { createBMapGeolocationControl, createBMapZoomControl } from './control';
-import { customAvatarOverlay, customImageOverlay } from './overlay';
-import ModalPortal from '../../../../components/modal-portal';
-import ImageDialog from '../../../../components/dialog/image-dialog';
-
-import './index.css';
-
-const DEFAULT_POSITION = { lng: 104.195, lat: 35.861 };
-const DEFAULT_ZOOM = 4;
-const MAX_ZOOM = 21;
-const MIN_ZOOM = 3;
-
-const MapView = ({ images }) => {
- const [imageIndex, setImageIndex] = useState(0);
- const [clusterLeaveIds, setClusterLeaveIds] = useState([]);
-
- const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
- const clusterLeaves = useMemo(() => images.filter(image => clusterLeaveIds.includes(image.id)), [images, clusterLeaveIds]);
-
- const mapRef = useRef(null);
- const clusterRef = useRef(null);
- const batchIndexRef = useRef(0);
- const clickTimeoutRef = useRef(null);
-
- const saveMapState = useCallback(() => {
- if (!mapRef.current) return;
- const point = mapRef.current.getCenter && mapRef.current.getCenter();
- const zoom = mapRef.current.getZoom && mapRef.current.getZoom();
- window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_CENTER_KEY, point);
- window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_ZOOM_KEY, zoom);
- }, []);
-
- const addMapController = useCallback(() => {
- const ZoomControl = createBMapZoomControl(window.BMapGL, { maxZoom: MAX_ZOOM, minZoom: MIN_ZOOM }, saveMapState);
- const zoomControl = new ZoomControl();
- const GeolocationControl = createBMapGeolocationControl(window.BMapGL, (point) => {
- point && mapRef.current && mapRef.current.setCenter(point);
- });
-
- const geolocationControl = new GeolocationControl();
- mapRef.current.addControl(zoomControl);
- mapRef.current.addControl(geolocationControl);
- }, [saveMapState]);
-
- const initializeUserMarker = useCallback((centerPoint) => {
- if (!window.BMapGL || !mapRef.current) return;
- const imageUrl = `${mediaUrl}img/marker.png`;
- const avatarMarker = customAvatarOverlay(centerPoint, appAvatarURL, imageUrl);
- mapRef.current.addOverlay(avatarMarker);
- }, []);
-
- const getBMapType = useCallback((type) => {
- switch (type) {
- case MAP_TYPE.SATELLITE: {
- return window.BMAP_EARTH_MAP;
- }
- default: {
- return window.BMAP_NORMAL_MAP;
- }
- }
- }, []);
-
- const loadMapState = useCallback(() => {
- const savedCenter = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_CENTER_KEY) || DEFAULT_POSITION;
- const savedZoom = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_ZOOM_KEY) || DEFAULT_ZOOM;
- return { center: savedCenter, zoom: savedZoom };
- }, []);
-
- const getPoints = useCallback((images) => {
- if (!window.Cluster || !images) return [];
- return window.Cluster.pointTransformer(images, (data) => ({
- point: [data.location.lng, data.location.lat],
- properties: {
- id: data.id,
- src: data.src,
- }
- }));
- }, []);
-
- const initializeCluster = useCallback(() => {
- clusterRef.current = new window.Cluster.View(mapRef.current, {
- clusterRadius: 80,
- updateRealTime: true,
- fitViewOnClick: false,
- isAnimation: true,
- clusterMap: (properties) => ({ src: properties.src, id: properties.id }),
- clusterReduce: (acc, properties) => {
- if (!acc.properties) {
- acc.properties = [];
- }
- acc.properties.push(properties);
- },
- renderClusterStyle: {
- type: window.Cluster.ClusterRender.DOM,
- inject: (props) => customImageOverlay(props),
- },
- });
-
- clusterRef.current.setData(getPoints(images));
-
- clusterRef.current.on(window.Cluster.ClusterEvent.CLICK, (element) => {
- if (clickTimeoutRef.current) {
- clearTimeout(clickTimeoutRef.current);
- clickTimeoutRef.current = null;
- return;
- } else {
- clickTimeoutRef.current = setTimeout(() => {
- let imageIds = [];
- if (element.isCluster) {
- imageIds = clusterRef.current.getLeaves(element.id).map(item => item.properties.id).filter(Boolean);
- } else {
- imageIds = [element.properties.id];
- }
- clickTimeoutRef.current = null;
- setClusterLeaveIds(imageIds);
- }, 300);
- }
- });
- }, [images, getPoints]);
-
- const renderBaiduMap = useCallback(() => {
- if (!mapRef.current || !window.BMapGL.Map) return;
- let { center, zoom } = loadMapState();
- let userPosition = { lng: 116.40396418840683, lat: 39.915106021711345 };
- // ask for user location
- if (navigator.geolocation) {
- navigator.geolocation.getCurrentPosition((userInfo) => {
- const gcPosition = wgs84_to_gcj02(userInfo.coords.longitude, userInfo.coords.latitude);
- const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
- const { lng, lat } = bdPosition;
- userPosition = new window.BMapGL.Point(lng, lat);
- center = userPosition;
- window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_CENTER_KEY, center);
- });
- }
- const mapTypeValue = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_TYPE_KEY);
- mapRef.current = new window.BMapGL.Map('sf-metadata-map-container', {
- enableMapClick: false,
- minZoom: MIN_ZOOM,
- maxZoom: MAX_ZOOM,
- mapType: getBMapType(mapTypeValue),
- });
-
- if (isValidPosition(center?.lng, center?.lat)) {
- mapRef.current.centerAndZoom(center, zoom);
- }
-
- mapRef.current.enableScrollWheelZoom(true);
- addMapController();
-
- initializeUserMarker(userPosition);
- initializeCluster();
-
- batchIndexRef.current = 0;
- }, [addMapController, initializeCluster, initializeUserMarker, getBMapType, loadMapState]);
-
- const handleClose = useCallback(() => {
- setImageIndex(0);
- setClusterLeaveIds([]);
- }, []);
-
- const moveToPrevImage = useCallback(() => {
- setImageIndex((imageIndex + clusterLeaves.length - 1) % clusterLeaves.length);
- }, [imageIndex, clusterLeaves.length]);
-
- const moveToNextImage = useCallback(() => {
- setImageIndex((imageIndex + 1) % clusterLeaves.length);
- }, [imageIndex, clusterLeaves.length]);
-
- useEffect(() => {
- const modifyMapTypeSubscribe = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_MAP_TYPE, (newType) => {
- window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_TYPE_KEY, newType);
- const mapType = getBMapType(newType);
- mapRef.current && mapRef.current.setMapType(mapType);
- });
-
- return () => {
- modifyMapTypeSubscribe();
- };
-
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- useEffect(() => {
- if (mapInfo.type === MAP_PROVIDER.B_MAP) {
- loadBMap(mapInfo.key).then(() => renderBaiduMap());
- return () => {
- window.renderMap = null;
- };
- }
- return;
- // eslint-disable-next-line react-hooks/exhaustive-deps
- }, []);
-
- return (
-
-
- {clusterLeaveIds.length > 0 && (
-
-
-
- )}
-
- );
-};
-
-MapView.propTypes = {
- images: PropTypes.array,
- onOpenCluster: PropTypes.func,
-};
-
-export default MapView;
diff --git a/frontend/src/metadata/views/map/map-view/overlay/custom-avatar-overlay.js b/frontend/src/metadata/views/map/overlay/custom-avatar-overlay.js
similarity index 100%
rename from frontend/src/metadata/views/map/map-view/overlay/custom-avatar-overlay.js
rename to frontend/src/metadata/views/map/overlay/custom-avatar-overlay.js
diff --git a/frontend/src/metadata/views/map/map-view/overlay/custom-image-overlay.js b/frontend/src/metadata/views/map/overlay/custom-image-overlay.js
similarity index 100%
rename from frontend/src/metadata/views/map/map-view/overlay/custom-image-overlay.js
rename to frontend/src/metadata/views/map/overlay/custom-image-overlay.js
diff --git a/frontend/src/metadata/views/map/map-view/overlay/index.js b/frontend/src/metadata/views/map/overlay/index.js
similarity index 100%
rename from frontend/src/metadata/views/map/map-view/overlay/index.js
rename to frontend/src/metadata/views/map/overlay/index.js