diff --git a/frontend/src/metadata/components/view-toolbar/index.js b/frontend/src/metadata/components/view-toolbar/index.js
index 7bfb186b5c..7227ecea75 100644
--- a/frontend/src/metadata/components/view-toolbar/index.js
+++ b/frontend/src/metadata/components/view-toolbar/index.js
@@ -113,10 +113,11 @@ 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 8d2e0721f4..39fc30be0a 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,14 +1,19 @@
-import React, { useMemo } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types';
-import { PRIVATE_COLUMN_KEY } from '../../../constants';
-import { FilterSetter, MapTypeSetter } from '../../data-process-setter';
+import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY, VIEW_TYPE } from '../../../constants';
+import { FilterSetter, GalleryGroupBySetter, GallerySliderSetter, MapTypeSetter, SortSetter } from '../../data-process-setter';
+import { gettext } from '../../../../utils/constants';
const MapViewToolBar = ({
+ isCustomPermission,
readOnly,
- view,
collaborators,
modifyFilters,
+ onToggleDetail,
}) => {
+ const [showGalleryToolbar, setShowGalleryToolbar] = useState(false);
+ const [view, setView] = useState({});
+
const viewType = useMemo(() => view.type, [view]);
const viewColumns = useMemo(() => {
if (!view) return [];
@@ -16,38 +21,87 @@ const MapViewToolBar = ({
}, [view]);
const filterColumns = useMemo(() => {
- return viewColumns.filter(c => c.key !== PRIVATE_COLUMN_KEY.FILE_TYPE);
+ 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.MAP_GALLERY_VIEW_CHANGE, { sorts });
+ }, []);
+
+ const setMapView = useCallback(view => setView(view), []);
+
+ useEffect(() => {
+ const unsubscribeToggleViewToolbarMode = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_MAP_VIEW_TOOLBAR, onToggle);
+ const unsubscribeView = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MAP_VIEW, setMapView);
+ return () => {
+ unsubscribeToggleViewToolbarMode();
+ unsubscribeView();
+ };
+ }, [setMapView, onToggle]);
+
+ useEffect(() => {
+ setView(window.sfMetadataStore.data.view);
+ }, []);
+
return (
<>
-
-
-
-
-
+ {showGalleryToolbar ? (
+
+ <>
+
+
+
+ >
+ {!isCustomPermission && (
+
+
+
+ )}
+
+ ) :
+ <>
+
+
+
+
+
+ >}
>
);
};
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/constants/event-bus-type.js b/frontend/src/metadata/constants/event-bus-type.js
index c05d276939..9cc80cf45b 100644
--- a/frontend/src/metadata/constants/event-bus-type.js
+++ b/frontend/src/metadata/constants/event-bus-type.js
@@ -76,4 +76,6 @@ export const EVENT_BUS_TYPE = {
// map
SWITCH_MAP_TYPE: 'switch_map_type',
+ TOGGLE_MAP_VIEW_TOOLBAR: 'toggle_map_view_toolbar',
+ MAP_VIEW: 'map_view',
};
diff --git a/frontend/src/metadata/constants/index.js b/frontend/src/metadata/constants/index.js
index 01604d5dde..0b83427af8 100644
--- a/frontend/src/metadata/constants/index.js
+++ b/frontend/src/metadata/constants/index.js
@@ -142,10 +142,15 @@ export const GALLERY_DATE_MODE = {
};
export const MAP_TYPE = {
- MAP: 'map',
+ NORMAL_MAP: 'normal_map',
SATELLITE: 'satellite',
};
+export const MAP_VIEW_TOOLBAR_MODE = {
+ MAP: 'map',
+ GALLERY: 'gallery',
+};
+
export const UNCATEGORIZED = '_uncategorized';
export const PASTE_SOURCE = {
diff --git a/frontend/src/metadata/constants/view.js b/frontend/src/metadata/constants/view.js
index fd7beb7aa0..1cede63b77 100644
--- a/frontend/src/metadata/constants/view.js
+++ b/frontend/src/metadata/constants/view.js
@@ -98,7 +98,7 @@ export const VIEW_TYPE_DEFAULT_SORTS = {
[VIEW_TYPE.GALLERY]: [{ column_key: PRIVATE_COLUMN_KEY.FILE_CTIME, sort_type: SORT_TYPE.DOWN }],
[VIEW_TYPE.FACE_RECOGNITION]: [{ column_key: PRIVATE_COLUMN_KEY.FILE_CTIME, sort_type: SORT_TYPE.DOWN }],
[VIEW_TYPE.KANBAN]: [],
- [VIEW_TYPE.MAP]: [],
+ [VIEW_TYPE.MAP]: [{ column_key: PRIVATE_COLUMN_KEY.FILE_CTIME, sort_type: SORT_TYPE.DOWN }],
};
export const VIEW_SORT_COLUMN_RULES = {
@@ -106,7 +106,7 @@ export const VIEW_SORT_COLUMN_RULES = {
[VIEW_TYPE.GALLERY]: (column) => GALLERY_SORT_COLUMN_OPTIONS.includes(column.type) || GALLERY_SORT_PRIVATE_COLUMN_KEYS.includes(column.key),
[VIEW_TYPE.FACE_RECOGNITION]: (column) => GALLERY_SORT_COLUMN_OPTIONS.includes(column.type) || GALLERY_SORT_PRIVATE_COLUMN_KEYS.includes(column.key),
[VIEW_TYPE.KANBAN]: (column) => SORT_COLUMN_OPTIONS.includes(column.type),
- [VIEW_TYPE.MAP]: () => {},
+ [VIEW_TYPE.MAP]: (column) => GALLERY_SORT_COLUMN_OPTIONS.includes(column.type) || GALLERY_SORT_PRIVATE_COLUMN_KEYS.includes(column.key),
};
export const VIEW_FIRST_SORT_COLUMN_RULES = {
@@ -114,7 +114,7 @@ export const VIEW_FIRST_SORT_COLUMN_RULES = {
[VIEW_TYPE.GALLERY]: (column) => GALLERY_FIRST_SORT_COLUMN_OPTIONS.includes(column.type) || GALLERY_FIRST_SORT_PRIVATE_COLUMN_KEYS.includes(column.key),
[VIEW_TYPE.FACE_RECOGNITION]: (column) => GALLERY_FIRST_SORT_COLUMN_OPTIONS.includes(column.type) || GALLERY_FIRST_SORT_PRIVATE_COLUMN_KEYS.includes(column.key),
[VIEW_TYPE.KANBAN]: (column) => SORT_COLUMN_OPTIONS.includes(column.type),
- [VIEW_TYPE.MAP]: () => {},
+ [VIEW_TYPE.MAP]: (column) => GALLERY_FIRST_SORT_COLUMN_OPTIONS.includes(column.type) || GALLERY_FIRST_SORT_PRIVATE_COLUMN_KEYS.includes(column.key),
};
export const KANBAN_SETTINGS_KEYS = {
diff --git a/frontend/src/metadata/store/index.js b/frontend/src/metadata/store/index.js
index 3bf4422302..a07a40a2f5 100644
--- a/frontend/src/metadata/store/index.js
+++ b/frontend/src/metadata/store/index.js
@@ -623,6 +623,22 @@ class Store {
this.applyOperation(operation);
};
+ // map
+ deleteLocationPhotos = (rows_ids) => {
+ if (!Array.isArray(rows_ids) || rows_ids.length === 0) return;
+
+ const type = OPERATION_TYPE.DELETE_LOCATION_PHOTOS;
+ const valid_rows_ids = rows_ids.filter((rowId) => {
+ const row = getRowById(this.data, rowId);
+ return row && this.context.canModifyRow(row);
+ });
+ const deleted_rows = valid_rows_ids.map((rowId) => getRowById(this.data, rowId));
+ const operation = this.createOperation({
+ type, repo_id: this.repoId, rows_ids, deleted_rows
+ });
+ this.applyOperation(operation);
+ };
+
}
export default Store;
diff --git a/frontend/src/metadata/store/operations/apply.js b/frontend/src/metadata/store/operations/apply.js
index 7e809ea710..64df8f7eca 100644
--- a/frontend/src/metadata/store/operations/apply.js
+++ b/frontend/src/metadata/store/operations/apply.js
@@ -335,6 +335,21 @@ export default function apply(data, operation) {
return data;
}
+ // map
+ case OPERATION_TYPE.DELETE_LOCATION_PHOTOS: {
+ const { rows_ids } = operation;
+ const idNeedDeletedMap = rows_ids.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
+ data.rows = data.rows.filter((row) => !idNeedDeletedMap[row._id]);
+ data.recordsCount = data.rows.length;
+ // delete rows in id_row_map
+ rows_ids.forEach(rowId => {
+ delete data.id_row_map[rowId];
+ });
+
+ data.row_ids = data.row_ids.filter(row_id => !idNeedDeletedMap[row_id]);
+ return data;
+ }
+
default: {
return data;
}
diff --git a/frontend/src/metadata/store/operations/constants.js b/frontend/src/metadata/store/operations/constants.js
index 2872778e2a..ee4ce65503 100644
--- a/frontend/src/metadata/store/operations/constants.js
+++ b/frontend/src/metadata/store/operations/constants.js
@@ -34,6 +34,9 @@ export const OPERATION_TYPE = {
// tag
UPDATE_FILE_TAGS: 'update_file_tags',
+
+ // map
+ DELETE_LOCATION_PHOTOS: 'delete_location_photos',
};
export const COLUMN_DATA_OPERATION_TYPE = {
@@ -74,6 +77,8 @@ export const OPERATION_ATTRIBUTES = {
[OPERATION_TYPE.MODIFY_SETTINGS]: ['repo_id', 'view_id', 'settings'],
[OPERATION_TYPE.UPDATE_FILE_TAGS]: ['repo_id', 'file_tags_data'],
+
+ [OPERATION_TYPE.DELETE_LOCATION_PHOTOS]: ['repo_id', 'rows_ids', 'deleted_rows'],
};
export const UNDO_OPERATION_TYPE = [
diff --git a/frontend/src/metadata/views/map/cluster-photos/index.css b/frontend/src/metadata/views/map/cluster-photos/index.css
new file mode 100644
index 0000000000..b2f844b537
--- /dev/null
+++ b/frontend/src/metadata/views/map/cluster-photos/index.css
@@ -0,0 +1,54 @@
+.sf-metadata-map-photos-container {
+ padding: 0 !important;
+ overflow-y: hidden !important;
+}
+
+.sf-metadata-map-photos-container .sf-metadata-map-photos-header {
+ height: 48px;
+ display: flex;
+ align-items: center;
+ padding: 0 16px;
+}
+
+.sf-metadata-map-photos-container .sf-metadata-icon-btn {
+ margin-left: -4px;
+ border-radius: 3px;
+}
+
+.sf-metadata-map-photos-container .sf-metadata-icon-btn:hover {
+ background-color: #EFEFEF;
+ cursor: pointer;
+}
+
+.sf-metadata-map-photos-container .sf-metadata-map-photos-header .sf-metadata-map-photos-header-back {
+ font-size: 14px;
+ height: 24px;
+ min-width: 24px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ margin-left: -5px;
+ border-radius: 3px;
+}
+
+.sf-metadata-map-photos-container .sf-metadata-map-photos-header .sf-metadata-map-photos-header-back:hover {
+ background-color: #EFEFEF;
+ cursor: pointer;
+}
+
+.sf-metadata-map-photos-container .sf-metadata-map-photos-header-back .sf3-font-arrow {
+ color: #666;
+ font-size: 14px !important;
+}
+
+.sf-metadata-map-photos-container .sf-metadata-map-photos-header .sf-metadata-map-location {
+ margin-left: 4px;
+ flex: 1;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.sf-metadata-map-photos-container .sf-metadata-gallery-container {
+ height: calc(100% - 48px);
+}
diff --git a/frontend/src/metadata/views/map/cluster-photos/index.js b/frontend/src/metadata/views/map/cluster-photos/index.js
new file mode 100644
index 0000000000..921b1645e7
--- /dev/null
+++ b/frontend/src/metadata/views/map/cluster-photos/index.js
@@ -0,0 +1,145 @@
+import React, { useCallback, useEffect, useState } from 'react';
+import PropTypes from 'prop-types';
+import deepCopy from 'deep-copy';
+import { CenteredLoading } from '@seafile/sf-metadata-ui-component';
+import Gallery from '../../gallery/main';
+import { gettext } from '../../../../utils/constants';
+import { EVENT_BUS_TYPE, PER_LOAD_NUMBER } from '../../../constants';
+import metadataAPI from '../../../api';
+import { normalizeColumns } from '../../../utils/column';
+import Metadata from '../../../model/metadata';
+import { Utils } from '../../../../utils/utils';
+import toaster from '../../../../components/toast';
+import { useMetadataView } from '../../../hooks/metadata-view';
+
+import './index.css';
+
+const ClusterPhotos = ({ view, markerIds, onClose, onDelete }) => {
+ const [isLoading, setLoading] = useState(true);
+ const [metadata, setMetadata] = useState({ rows: [] });
+
+ const { deleteFilesCallback } = useMetadataView();
+
+ const repoID = window.sfMetadataContext.getSetting('repoID');
+
+ const loadData = useCallback((view) => {
+ setLoading(true);
+ const params = {
+ view_id: view._id,
+ start: 0,
+ limit: PER_LOAD_NUMBER,
+ };
+ metadataAPI.getMetadata(repoID, params).then(res => {
+ const rows = res?.data?.results || [];
+ const filteredRows = rows.filter(row => markerIds.includes(row._id));
+ const columns = normalizeColumns(res?.data?.metadata);
+ const metadata = new Metadata({ rows: filteredRows, columns, view });
+ metadata.hasMore = rows.length >= PER_LOAD_NUMBER;
+ setMetadata(metadata);
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MAP_VIEW, metadata.view);
+ setLoading(false);
+ }).catch(error => {
+ const errorMessage = Utils.getErrorMsg(error);
+ toaster.danger(errorMessage);
+ setLoading(false);
+ });
+ }, [repoID, markerIds]);
+
+ 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();
+ }
+
+ onDelete(ids);
+ }, [metadata, onClose, onDelete]);
+
+ const handelDelete = useCallback((deletedImages, { success_callback } = {}) => {
+ if (!deletedImages.length) return;
+ let recordIds = [];
+ let paths = [];
+ let fileNames = [];
+ deletedImages.forEach((record) => {
+ const { id, parentDir, name } = record || {};
+ if (parentDir && name) {
+ const path = Utils.joinPath(parentDir, name);
+ paths.push(path);
+ fileNames.push(name);
+ recordIds.push(id);
+ }
+ });
+ window.sfMetadataContext.batchDeleteFiles(repoID, paths).then(res => {
+ deletedByIds(recordIds);
+ deleteFilesCallback(paths, fileNames);
+ let msg = fileNames.length > 1
+ ? gettext('Successfully deleted {name} and {n} other items')
+ : gettext('Successfully deleted {name}');
+ msg = msg.replace('{name}', fileNames[0])
+ .replace('{n}', fileNames.length - 1);
+ toaster.success(msg);
+ success_callback && success_callback();
+ }).catch(error => {
+ toaster.danger(gettext('Failed to delete records'));
+ });
+ }, [deleteFilesCallback, repoID, deletedByIds]);
+
+ const handleViewChange = useCallback((update) => {
+ metadataAPI.modifyView(repoID, view._id, update).then(res => {
+ const newView = { ...view, ...update };
+ loadData(newView);
+ }).catch(error => {
+ const errorMessage = Utils.getErrorMsg(error);
+ toaster.danger(errorMessage);
+ });
+ }, [view, repoID, loadData]);
+
+ useEffect(() => {
+ loadData({ _id: view._id, sorts: view.sorts });
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ useEffect(() => {
+ const unsubscribeViewChange = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MAP_GALLERY_VIEW_CHANGE, handleViewChange);
+ return () => {
+ unsubscribeViewChange();
+ };
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
+
+ return (
+ isLoading ? (
+
+ ) : (
+
+
+
+
+
+
{gettext('Location')}
+
+
+
+ )
+ );
+};
+
+ClusterPhotos.propTypes = {
+ view: PropTypes.object,
+ markerIds: PropTypes.array,
+ onClose: PropTypes.func,
+ onDelete: PropTypes.func,
+};
+
+export default ClusterPhotos;
diff --git a/frontend/src/metadata/views/map/geolocation-control.js b/frontend/src/metadata/views/map/control/geolocation-control.js
similarity index 94%
rename from frontend/src/metadata/views/map/geolocation-control.js
rename to frontend/src/metadata/views/map/control/geolocation-control.js
index 6a2b227daa..8ce5e38053 100644
--- a/frontend/src/metadata/views/map/geolocation-control.js
+++ b/frontend/src/metadata/views/map/control/geolocation-control.js
@@ -1,5 +1,5 @@
-import { mediaUrl } from '../../../utils/constants';
-import { Utils } from '../../../utils/utils';
+import { mediaUrl } from '../../../../utils/constants';
+import { Utils } from '../../../../utils/utils';
export function createBMapGeolocationControl(BMap, callback) {
function GeolocationControl() {
diff --git a/frontend/src/metadata/views/map/control/index.js b/frontend/src/metadata/views/map/control/index.js
new file mode 100644
index 0000000000..46577bee52
--- /dev/null
+++ b/frontend/src/metadata/views/map/control/index.js
@@ -0,0 +1,7 @@
+import { createBMapGeolocationControl } from './geolocation-control';
+import { createBMapZoomControl } from './zoom-control';
+
+export {
+ createBMapGeolocationControl,
+ createBMapZoomControl
+};
diff --git a/frontend/src/metadata/views/map/zoom-control.js b/frontend/src/metadata/views/map/control/zoom-control.js
similarity index 87%
rename from frontend/src/metadata/views/map/zoom-control.js
rename to frontend/src/metadata/views/map/control/zoom-control.js
index 3600ba85ff..995590dc31 100644
--- a/frontend/src/metadata/views/map/zoom-control.js
+++ b/frontend/src/metadata/views/map/control/zoom-control.js
@@ -1,4 +1,4 @@
-import { Utils } from '../../../utils/utils';
+import { Utils } from '../../../../utils/utils';
export function createBMapZoomControl(BMap, callback) {
function ZoomControl() {
@@ -12,7 +12,7 @@ export function createBMapZoomControl(BMap, callback) {
div.style = 'display: flex; justify-content: center; align-items: center;';
const zoomInButton = document.createElement('button');
- zoomInButton.className = 'sf-BMap-zoom-button';
+ zoomInButton.className = 'sf-BMap-zoom-button btn btn-secondary';
zoomInButton.style = 'display: flex; justify-content: center; align-items: center;';
zoomInButton.innerHTML = '';
div.appendChild(zoomInButton);
@@ -22,7 +22,7 @@ export function createBMapZoomControl(BMap, callback) {
div.appendChild(divider);
const zoomOutButton = document.createElement('button');
- zoomOutButton.className = 'sf-BMap-zoom-button';
+ zoomOutButton.className = 'sf-BMap-zoom-button btn btn-secondary';
zoomOutButton.style = 'display: flex; justify-content: center; align-items: center;';
zoomOutButton.innerHTML = '';
div.appendChild(zoomOutButton);
@@ -35,8 +35,8 @@ export function createBMapZoomControl(BMap, callback) {
const updateButtonStates = () => {
const zoomLevel = map.getZoom();
- const maxZoom = map.getMaxZoom();
- const minZoom = map.getMinZoom();
+ const maxZoom = map.getMapType().getMaxZoom();
+ const minZoom = map.getMapType().getMinZoom();
zoomInButton.disabled = zoomLevel >= maxZoom;
zoomOutButton.disabled = zoomLevel <= minZoom;
diff --git a/frontend/src/metadata/views/map/index.css b/frontend/src/metadata/views/map/index.css
index 3a8524d8f2..98cbfca6d2 100644
--- a/frontend/src/metadata/views/map/index.css
+++ b/frontend/src/metadata/views/map/index.css
@@ -1,30 +1,38 @@
.sf-metadata-view-map {
+ width: 100%;
+ height: 100%;
display: flex;
flex-direction: column;
}
.sf-metadata-view-map .sf-metadata-map-container {
- width: 100%;;
+ width: 100%;
height: 100%;
- min-height: 0;
}
.sf-metadata-view-map .custom-image-container {
- padding: 4px;
- background: #fff;
- width: 80px;
- height: 80px;
- cursor: default;
- border-radius: 4px;
- box-shadow: 0 1px 3px 0 rgb(0 0 0 / 10%);
+ width: 86px;
+ height: 86px;
+ background-color: #fff;
+ padding: 3px;
+ border-radius: 6px;
position: relative;
+ cursor: default;
+}
+
+.sf-metadata-view-map .custom-image-container img {
+ width: 100%;
+ height: 100%;
+ border-radius: 6px;
}
.sf-metadata-view-map .custom-image-number {
position: absolute;
right: -15px;
- top: -8px;
- padding: 0 12px;
+ top: -16px;
+ width: 32px;
+ height: 32px;
+ padding: 6px;
background: #007bff;
color: #fff;
border-radius: 50%;
@@ -33,26 +41,23 @@
line-height: 20px;
}
-.sf-metadata-view-map .custom-image-container .plugin-label-arrow {
+.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;
- bottom: 5px;
- transform: translate( -50%, 100%);
- left: 50%;
- color: #fff;
- display: inline-block;
- line-height: 16px;
- height: 16px;
+ 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 .image-overlay-arrow {
- bottom: 5px;
- color: #fff;
- display: inline-block;
- height: 16px;
- left: 50%;
- line-height: 16px;
- position: absolute;
- transform: translate(-50%, 100%);
+.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 {
@@ -66,14 +71,21 @@
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 10px solid #fff;
+ border-radius: 2px;
}
-.sf-metadata-view-map .sf-BMap-geolocation-control {
- background-color: #ffffff;
- box-shadow: 0 0 4px rgb(0 0 0 / 12%);
+.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);
+}
+
+.sf-metadata-view-map .sf-BMap-geolocation-control,
+.sf-metadata-view-map .sf-BMap-zoom-control {
+ background-color: #fff;
+ opacity: 1;
+ overflow: hidden;
border-radius: 6px;
- text-align: center;
- color: #212529;
+ box-shadow: -2px -2px 4px 2px rgba(0, 0, 0, 0.1);
}
.sf-metadata-view-map .sf-BMap-geolocation-control-loading {
@@ -81,24 +93,17 @@
}
.sf-metadata-view-map .sf-BMap-geolocation-control:hover,
-.sf-metadata-view-map .sf-BMap-zoom-button:hover {
+.sf-metadata-view-map .sf-BMap-zoom-button:not(.disabled):hover {
background-color: #f5f5f5;
cursor: pointer;
}
-.sf-metadata-view-map .sf-BMap-zoom-control {
- background-color: #fff;
- opacity: 1;
- overflow: hidden;
- border-radius: 6px;
- box-shadow: 2px 2px 4px 2px rgba(0, 0, 0, 0.1);
-}
-
.sf-metadata-view-map .sf-BMap-zoom-button {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
+ color: #666;
background-color: #fff;
border: none;
overflow: hidden;
@@ -109,15 +114,17 @@
.sf-metadata-view-map .sf-BMap-zoom-button .zoom-out-icon {
width: 18px;
height: 18px;
- fill: #666;
+ fill: currentColor;
}
-.sf-metadata-view-map .sf-BMap-zoom-button:hover .zoom-in-icon,
-.sf-metadata-view-map .sf-BMap-zoom-button:hover .zoom-out-icon {
- fill: #212529;
+.sf-metadata-view-map .sf-BMap-zoom-button:not(:disabled):active:focus {
+ box-shadow: none;
}
-.sf-metadata-view-map .sf-BMap-zoom-button:disabled .zoom-in-icon,
-.sf-metadata-view-map .sf-BMap-zoom-button:disabled .zoom-out-icon {
- fill: #ccc;
+.sf-metadata-view-map .sf-BMap-zoom-button:hover {
+ color: #212529;
+}
+
+.sf-metadata-view-map .sf-BMap-zoom-button:disabled {
+ color: #ccc !important;
}
diff --git a/frontend/src/metadata/views/map/index.js b/frontend/src/metadata/views/map/index.js
index 1f735b16e4..cf57e2bda6 100644
--- a/frontend/src/metadata/views/map/index.js
+++ b/frontend/src/metadata/views/map/index.js
@@ -1,38 +1,21 @@
-import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
-import { CenteredLoading } from '@seafile/sf-metadata-ui-component';
-import loadBMap, { initMapInfo } from '../../../utils/map-utils';
-import { wgs84_to_gcj02, gcj02_to_bd09 } from '../../../utils/coord-transform';
-import { isValidPosition } from '../../utils/validate';
-import { MAP_TYPE as MAP_PROVIDER } from '../../../constants';
-import { appAvatarURL, baiduMapKey, gettext, googleMapKey, mediaUrl, siteRoot, thumbnailSizeForGrid } from '../../../utils/constants';
+import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import { getFileNameFromRecord, getFileTypeFromRecord, getImageLocationFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell';
+import ClusterPhotos from './cluster-photos';
+import Main from './main';
+import { EVENT_BUS_TYPE, PREDEFINED_FILE_TYPE_OPTION_KEY } from '../../constants';
import { useMetadataView } from '../../hooks/metadata-view';
-import { EVENT_BUS_TYPE, PREDEFINED_FILE_TYPE_OPTION_KEY, MAP_TYPE } from '../../constants';
-import { getRecordIdFromRecord, getFileNameFromRecord, getImageLocationFromRecord, getParentDirFromRecord,
- getFileTypeFromRecord
-} from '../../utils/cell';
import { Utils } from '../../../utils/utils';
-import customImageOverlay from './custom-image-overlay';
-import customAvatarOverlay from './custom-avatar-overlay';
-import { createBMapGeolocationControl } from './geolocation-control';
-import toaster from '../../../components/toast';
+import { siteRoot, thumbnailSizeForGrid } from '../../../utils/constants';
+import { isValidPosition } from '../../utils/validate';
+import { gcj02_to_bd09, wgs84_to_gcj02 } from '../../../utils/coord-transform';
import './index.css';
-import { createBMapZoomControl } from './zoom-control';
-
-const DEFAULT_POSITION = { lng: 104.195, lat: 35.861 };
-const DEFAULT_ZOOM = 4;
-const BATCH_SIZE = 500;
const Map = () => {
- const [isLoading, setIsLoading] = useState(true);
+ const [showGallery, setShowGallery] = useState(false);
+ const [markerIds, setMarkerIds] = useState([]);
+ const { metadata, store } = useMetadataView();
- const mapRef = useRef(null);
- const clusterRef = useRef(null);
- const batchIndexRef = useRef(0);
-
- const { metadata } = useMetadataView();
-
- const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
const repoID = window.sfMetadataContext.getSetting('repoID');
const validImages = useMemo(() => {
@@ -54,150 +37,36 @@ const Map = () => {
return { id, src, lng: bdPosition.lng, lat: bdPosition.lat };
})
.filter(Boolean);
- }, [repoID, metadata]);
+ }, [repoID, metadata.rows]);
- const addMapController = useCallback(() => {
- 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 openGallery = useCallback((cluster_marker_ids) => {
+ setMarkerIds(cluster_marker_ids);
+ setShowGallery(true);
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MAP_VIEW_TOOLBAR, true);
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MAP_VIEW, metadata.view);
+ }, [metadata.view]);
- const geolocationControl = new GeolocationControl();
-
- mapRef.current.addControl(zoomControl);
- mapRef.current.addControl(geolocationControl);
+ const closeGallery = useCallback(() => {
+ setShowGallery(false);
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MAP_VIEW_TOOLBAR, false);
}, []);
- const renderMarkersBatch = useCallback(() => {
- if (!validImages.length || !clusterRef.current) return;
-
- const startIndex = batchIndexRef.current * BATCH_SIZE;
- const endIndex = Math.min(startIndex + BATCH_SIZE, validImages.length);
- const batchMarkers = [];
-
- for (let i = startIndex; i < endIndex; i++) {
- const image = validImages[i];
- const { lng, lat } = image;
- const point = new window.BMap.Point(lng, lat);
- const marker = customImageOverlay(point, image.src);
- batchMarkers.push(marker);
- }
-
- clusterRef.current.addMarkers(batchMarkers);
-
- if (endIndex < validImages.length) {
- batchIndexRef.current += 1;
- setTimeout(renderMarkersBatch, 20); // Schedule the next batch
- }
- }, [validImages]);
-
- const initializeClusterer = useCallback(() => {
- if (mapRef.current && !clusterRef.current) {
- clusterRef.current = new window.BMapLib.MarkerClusterer(mapRef.current);
- }
- }, []);
-
- const initializeUserMarker = useCallback(() => {
- if (!window.BMap) return;
-
- const imageUrl = `${mediaUrl}/img/marker.png`;
- const addMarker = (lng, lat) => {
- const gcPosition = wgs84_to_gcj02(lng, lat);
- const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
- const point = new window.BMap.Point(bdPosition.lng, bdPosition.lat);
- const avatarMarker = customAvatarOverlay(point, appAvatarURL, imageUrl);
- mapRef.current.addOverlay(avatarMarker);
- };
-
- if (!navigator.geolocation) {
- addMarker(DEFAULT_POSITION.lng, DEFAULT_POSITION.lat);
- return;
- }
- navigator.geolocation.getCurrentPosition(
- position => addMarker(position.coords.longitude, position.coords.latitude),
- () => {
- addMarker(DEFAULT_POSITION.lng, DEFAULT_POSITION.lat);
- toaster.danger(gettext('Failed to get user location'));
- }
- );
- }, []);
-
- 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;
- let mapCenter = window.sfMetadataContext.localStorage.getItem('map-center') || DEFAULT_POSITION;
- // ask for user location
- if (navigator.geolocation) {
- navigator.geolocation.getCurrentPosition((userInfo) => {
- mapCenter = { lng: userInfo.coords.longitude, lat: userInfo.coords.latitude };
- window.sfMetadataContext.localStorage.setItem('map-center', mapCenter);
- });
- }
- if (!isValidPosition(mapCenter?.lng, mapCenter?.lat)) return;
-
- const gcPosition = wgs84_to_gcj02(mapCenter.lng, mapCenter.lat);
- const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
- const { lng, lat } = bdPosition;
-
- mapRef.current = new window.BMap.Map('sf-metadata-map-container', { enableMapClick: false });
- 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();
- initializeClusterer();
-
- batchIndexRef.current = 0; // Reset batch index
- renderMarkersBatch();
- }, [addMapController, initializeClusterer, initializeUserMarker, renderMarkersBatch]);
+ const onDeleteLocationPhotos = useCallback((ids) => {
+ store.deleteLocationPhotos(ids);
+ }, [store]);
useEffect(() => {
- 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 () => {
- window.renderMap = null;
- };
- }
- return;
- }, [mapInfo, renderBaiduMap]);
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MAP_VIEW, metadata.view);
+ }, [metadata.view]);
return (
-
- {isLoading ? (
-
+ <>
+ {showGallery ? (
+
) : (
-
+
)}
-
+ >
);
};
diff --git a/frontend/src/metadata/views/map/main.js b/frontend/src/metadata/views/map/main.js
new file mode 100644
index 0000000000..d1f04d94c0
--- /dev/null
+++ b/frontend/src/metadata/views/map/main.js
@@ -0,0 +1,177 @@
+import React, { useCallback, useEffect, useMemo, useRef } from 'react';
+import PropTypes from 'prop-types';
+import loadBMap, { initMapInfo } from '../../../utils/map-utils';
+import { appAvatarURL, baiduMapKey, gettext, 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 } from '../../constants';
+import { createBMapGeolocationControl, createBMapZoomControl } from './control';
+import { customAvatarOverlay, customImageOverlay } from './overlay';
+import toaster from '../../../components/toast';
+
+const DEFAULT_POSITION = { lng: 104.195, lat: 35.861 };
+const DEFAULT_ZOOM = 4;
+const BATCH_SIZE = 500;
+
+const Main = ({ validImages, onOpen }) => {
+ const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
+
+ const mapRef = useRef(null);
+ const clusterRef = useRef(null);
+ const batchIndexRef = useRef(0);
+
+ const addMapController = useCallback(() => {
+ 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);
+ }, []);
+
+ const initializeUserMarker = useCallback(() => {
+ if (!window.BMap) return;
+
+ const imageUrl = `${mediaUrl}/img/marker.png`;
+ const addMarker = (lng, lat) => {
+ const gcPosition = wgs84_to_gcj02(lng, lat);
+ const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
+ const point = new window.BMap.Point(bdPosition.lng, bdPosition.lat);
+ const avatarMarker = customAvatarOverlay(point, appAvatarURL, imageUrl);
+ mapRef.current && mapRef.current.addOverlay(avatarMarker);
+ };
+
+ if (!navigator.geolocation) {
+ addMarker(DEFAULT_POSITION.lng, DEFAULT_POSITION.lat);
+ return;
+ }
+ navigator.geolocation.getCurrentPosition(
+ position => addMarker(position.coords.longitude, position.coords.latitude),
+ () => {
+ addMarker(DEFAULT_POSITION.lng, DEFAULT_POSITION.lat);
+ toaster.danger(gettext('Failed to get user location'));
+ }
+ );
+ }, []);
+
+ 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 onClickMarker = useCallback((e, markers) => {
+ const imageIds = markers.map(marker => marker._imageId);
+ onOpen(imageIds);
+ }, [onOpen]);
+
+ const renderMarkersBatch = useCallback(() => {
+ if (!validImages.length || !clusterRef.current) return;
+
+ const startIndex = batchIndexRef.current * BATCH_SIZE;
+ const endIndex = Math.min(startIndex + BATCH_SIZE, validImages.length);
+ const batchMarkers = [];
+
+ for (let i = startIndex; i < endIndex; i++) {
+ const image = validImages[i];
+ const { lng, lat } = image;
+ const point = new window.BMap.Point(lng, lat);
+ const marker = customImageOverlay(point, image, {
+ callback: (e, markers) => onClickMarker(e, markers)
+ });
+ batchMarkers.push(marker);
+ }
+ clusterRef.current.addMarkers(batchMarkers);
+
+ if (endIndex < validImages.length) {
+ batchIndexRef.current += 1;
+ setTimeout(renderMarkersBatch, 20); // Schedule the next batch
+ }
+ }, [validImages, onClickMarker]);
+
+ const initializeClusterer = useCallback(() => {
+ if (mapRef.current && !clusterRef.current) {
+ clusterRef.current = new window.BMapLib.MarkerClusterer(mapRef.current, {
+ callback: (e, markers) => onClickMarker(e, markers)
+ });
+ }
+ }, [onClickMarker]);
+
+ const renderBaiduMap = useCallback(() => {
+ if (!mapRef.current || !window.BMap.Map) return;
+ let mapCenter = window.sfMetadataContext.localStorage.getItem('map-center') || DEFAULT_POSITION;
+ // ask for user location
+ if (navigator.geolocation) {
+ navigator.geolocation.getCurrentPosition((userInfo) => {
+ mapCenter = { lng: userInfo.coords.longitude, lat: userInfo.coords.latitude };
+ window.sfMetadataContext.localStorage.setItem('map-center', mapCenter);
+ });
+ }
+ if (!isValidPosition(mapCenter?.lng, mapCenter?.lat)) return;
+
+ const gcPosition = wgs84_to_gcj02(mapCenter.lng, mapCenter.lat);
+ const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
+ const { lng, lat } = bdPosition;
+
+ mapRef.current = new window.BMap.Map('sf-metadata-map-container', { enableMapClick: false });
+ const point = new window.BMap.Point(lng, lat);
+ mapRef.current.centerAndZoom(point, DEFAULT_ZOOM);
+ mapRef.current.enableScrollWheelZoom(true);
+
+ const savedValue = window.sfMetadataContext.localStorage.getItem('map-type');
+ mapRef.current && mapRef.current.setMapType(getMapType(savedValue));
+
+ addMapController();
+ initializeUserMarker();
+ initializeClusterer();
+
+ batchIndexRef.current = 0;
+ renderMarkersBatch();
+ }, [addMapController, initializeClusterer, initializeUserMarker, renderMarkersBatch, getMapType]);
+
+ useEffect(() => {
+ 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) {
+ loadBMap(mapInfo.key).then(() => renderBaiduMap());
+ return () => {
+ window.renderMap = null;
+ };
+ }
+ return;
+ }, [mapInfo, renderBaiduMap]);
+
+ return (
+
+ );
+};
+
+Main.propTypes = {
+ validImages: PropTypes.array,
+ onOpen: PropTypes.func,
+};
+
+export default Main;
diff --git a/frontend/src/metadata/views/map/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/custom-avatar-overlay.js
rename to frontend/src/metadata/views/map/overlay/custom-avatar-overlay.js
diff --git a/frontend/src/metadata/views/map/custom-image-overlay.js b/frontend/src/metadata/views/map/overlay/custom-image-overlay.js
similarity index 70%
rename from frontend/src/metadata/views/map/custom-image-overlay.js
rename to frontend/src/metadata/views/map/overlay/custom-image-overlay.js
index ddb61059b9..b58ecd096e 100644
--- a/frontend/src/metadata/views/map/custom-image-overlay.js
+++ b/frontend/src/metadata/views/map/overlay/custom-image-overlay.js
@@ -1,29 +1,28 @@
-import { Utils } from '../../../utils/utils';
+import { Utils } from '../../../../utils/utils';
-const customImageOverlay = (center, imageUrl) => {
- class ImageOverlay extends window.BMap.Overlay {
- constructor(center, imageUrl) {
- super();
+const customImageOverlay = (center, image, callback) => {
+ class ImageOverlay extends window.BMapLib.TextIconOverlay {
+ constructor(center, image, { callback } = {}) {
+ super(center, '', { styles: [] });
this._center = center;
- this._imageUrl = imageUrl;
+ this._imageUrl = image.src;
+ this._imageId = image.id;
+ this._callback = callback;
}
initialize(map) {
this._map = map;
const div = document.createElement('div');
div.style.position = 'absolute';
- div.style.width = '80px';
- div.style.height = '80px';
div.style.zIndex = 2000;
map.getPanes().markerPane.appendChild(div);
this._div = div;
- const imageElement = `
`;
+ const imageElement = `
`;
const htmlString =
`
${this._imageUrl ? imageElement : '
'}
-
`;
const labelDocument = new DOMParser().parseFromString(htmlString, 'text/html');
@@ -33,6 +32,7 @@ const customImageOverlay = (center, imageUrl) => {
const eventHandler = (event) => {
event.stopPropagation();
event.preventDefault();
+ this._callback && this._callback(event, [{ _imageId: this._imageId }]);
};
if (Utils.isDesktop()) {
@@ -51,7 +51,7 @@ const customImageOverlay = (center, imageUrl) => {
}
getImageUrl() {
- return imageUrl || '';
+ return image.src || '';
}
getPosition() {
@@ -63,7 +63,7 @@ const customImageOverlay = (center, imageUrl) => {
}
}
- return new ImageOverlay(center, imageUrl);
+ return new ImageOverlay(center, image, callback);
};
export default customImageOverlay;
diff --git a/frontend/src/metadata/views/map/overlay/index.js b/frontend/src/metadata/views/map/overlay/index.js
new file mode 100644
index 0000000000..21f2d71fa9
--- /dev/null
+++ b/frontend/src/metadata/views/map/overlay/index.js
@@ -0,0 +1,7 @@
+import customAvatarOverlay from './custom-avatar-overlay';
+import customImageOverlay from './custom-image-overlay';
+
+export {
+ customAvatarOverlay,
+ customImageOverlay
+};
diff --git a/media/js/map/marker-clusterer.js b/media/js/map/marker-clusterer.js
index 66cc930def..5f7e3100f0 100644
--- a/media/js/map/marker-clusterer.js
+++ b/media/js/map/marker-clusterer.js
@@ -130,6 +130,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
this._isAverageCenter = opts['isAverageCenter'];
}
this._styles = opts["styles"] || [];
+ this._callback = opts["callback"] || function(){};
var that = this;
this._map.addEventListener("zoomend",function(){
@@ -443,6 +444,10 @@ var BMapLib = window.BMapLib = BMapLib || {};
return count;
};
+ MarkerClusterer.prototype.getCallback = function() {
+ return this._callback;
+ }
+
/**
* @ignore
* Cluster
@@ -575,8 +580,12 @@ var BMapLib = window.BMapLib = BMapLib || {};
var thatMap = this._map;
var thatBounds = this.getBounds();
- this._clusterMarker.addEventListener("click", function(event){
- thatMap.setViewport(thatBounds);
+ this._clusterMarker.addEventListener("click",(event) => {
+ // thatMap.setViewport(thatBounds);
+ if (this._markerClusterer && typeof this._markerClusterer.getCallback() === 'function') {
+ const markers = this._markers;
+ this._markerClusterer.getCallback()(event, markers);
+ }
});
};
diff --git a/media/js/map/text-icon-overlay.js b/media/js/map/text-icon-overlay.js
index 725e04efe2..9c4ead85a0 100644
--- a/media/js/map/text-icon-overlay.js
+++ b/media/js/map/text-icon-overlay.js
@@ -774,7 +774,8 @@ var BMapLib = window.BMapLib = BMapLib || {};
TextIconOverlay.prototype.initialize = function(map){
this._map = map;
- this._domElement = document.createElement('div');
+ this._domElement = document.createElement('div');
+ this._domElement.className = 'custom-image-overlay';
// this._updateCss();
// this._updateText();
this._updatePosition();