mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-15 06:44:16 +00:00
show cluster photos
This commit is contained in:
@@ -113,10 +113,11 @@ const ViewToolBar = ({ viewId, isCustomPermission, onToggleDetail, onCloseDetail
|
||||
)}
|
||||
{viewType === VIEW_TYPE.MAP && (
|
||||
<MapViewToolBar
|
||||
isCustomPermission={isCustomPermission}
|
||||
readOnly={readOnly}
|
||||
view={view}
|
||||
collaborators={collaborators}
|
||||
modifyFilters={modifyFilters}
|
||||
onToggleDetail={onToggleDetail}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -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 (
|
||||
<>
|
||||
<div className="sf-metadata-tool-left-operations">
|
||||
<MapTypeSetter view={view} />
|
||||
<FilterSetter
|
||||
isNeedSubmit={true}
|
||||
wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-filter"
|
||||
filtersClassName="sf-metadata-filters"
|
||||
target="sf-metadata-filter-popover"
|
||||
readOnly={readOnly}
|
||||
filterConjunction={view.filter_conjunction}
|
||||
basicFilters={view.basic_filters}
|
||||
filters={view.filters}
|
||||
columns={filterColumns}
|
||||
modifyFilters={modifyFilters}
|
||||
collaborators={collaborators}
|
||||
viewType={viewType}
|
||||
/>
|
||||
</div>
|
||||
<div className="sf-metadata-tool-right-operations"></div>
|
||||
{showGalleryToolbar ? (
|
||||
<div className="sf-metadata-tool-left-operations">
|
||||
<>
|
||||
<GalleryGroupBySetter view={{ _id: view._id }} />
|
||||
<GallerySliderSetter view={{ _id: view._id }} />
|
||||
<SortSetter
|
||||
isNeedSubmit={true}
|
||||
wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-sort"
|
||||
target="sf-metadata-sort-popover"
|
||||
readOnly={readOnly}
|
||||
sorts={view.sorts}
|
||||
type={VIEW_TYPE.MAP}
|
||||
columns={viewColumns}
|
||||
modifySorts={modifySorts}
|
||||
/>
|
||||
</>
|
||||
{!isCustomPermission && (
|
||||
<div className="cur-view-path-btn ml-2" onClick={onToggleDetail}>
|
||||
<span className="sf3-font sf3-font-info" aria-label={gettext('Properties')} title={gettext('Properties')}></span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
) :
|
||||
<>
|
||||
<div className="sf-metadata-tool-left-operations">
|
||||
<MapTypeSetter view={view} />
|
||||
<FilterSetter
|
||||
isNeedSubmit={true}
|
||||
wrapperClass="sf-metadata-view-tool-operation-btn sf-metadata-view-tool-filter"
|
||||
filtersClassName="sf-metadata-filters"
|
||||
target="sf-metadata-filter-popover"
|
||||
readOnly={readOnly}
|
||||
filterConjunction={view.filter_conjunction}
|
||||
basicFilters={view.basic_filters}
|
||||
filters={view.filters}
|
||||
columns={filterColumns}
|
||||
modifyFilters={modifyFilters}
|
||||
collaborators={collaborators}
|
||||
viewType={viewType}
|
||||
/>
|
||||
</div>
|
||||
<div className="sf-metadata-tool-right-operations"></div>
|
||||
</>}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
MapViewToolBar.propTypes = {
|
||||
isCustomPermission: PropTypes.bool,
|
||||
readOnly: PropTypes.bool,
|
||||
view: PropTypes.object,
|
||||
collaborators: PropTypes.array,
|
||||
modifyFilters: PropTypes.func,
|
||||
onToggleDetail: PropTypes.func,
|
||||
};
|
||||
|
||||
export default MapViewToolBar;
|
||||
|
@@ -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',
|
||||
};
|
||||
|
@@ -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 = {
|
||||
|
@@ -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 = {
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 = [
|
||||
|
54
frontend/src/metadata/views/map/cluster-photos/index.css
Normal file
54
frontend/src/metadata/views/map/cluster-photos/index.css
Normal file
@@ -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);
|
||||
}
|
145
frontend/src/metadata/views/map/cluster-photos/index.js
Normal file
145
frontend/src/metadata/views/map/cluster-photos/index.js
Normal file
@@ -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 ? (
|
||||
<CenteredLoading />
|
||||
) : (
|
||||
<div className="sf-metadata-view-map sf-metadata-map-photos-container">
|
||||
<div className="sf-metadata-map-photos-header" onClick={onClose}>
|
||||
<div className="sf-metadata-map-photos-header-back">
|
||||
<i className="sf3-font sf3-font-arrow rotate-180"></i>
|
||||
</div>
|
||||
<div className="sf-metadata-map-location">{gettext('Location')}</div>
|
||||
</div>
|
||||
<Gallery metadata={metadata} onDelete={handelDelete} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
ClusterPhotos.propTypes = {
|
||||
view: PropTypes.object,
|
||||
markerIds: PropTypes.array,
|
||||
onClose: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ClusterPhotos;
|
@@ -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() {
|
7
frontend/src/metadata/views/map/control/index.js
Normal file
7
frontend/src/metadata/views/map/control/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createBMapGeolocationControl } from './geolocation-control';
|
||||
import { createBMapZoomControl } from './zoom-control';
|
||||
|
||||
export {
|
||||
createBMapGeolocationControl,
|
||||
createBMapZoomControl
|
||||
};
|
@@ -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 = '<svg class="zoom-in-icon"><use xlink:href="#plus_sign" /></svg>';
|
||||
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 = '<svg class="zoom-out-icon"><use xlink:href="#minus_sign" /></svg>';
|
||||
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;
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 (
|
||||
<div className="w-100 h-100 sf-metadata-view-map">
|
||||
{isLoading ? (
|
||||
<CenteredLoading />
|
||||
<>
|
||||
{showGallery ? (
|
||||
<ClusterPhotos view={metadata.view} markerIds={markerIds} onClose={closeGallery} onDelete={onDeleteLocationPhotos} />
|
||||
) : (
|
||||
<div className="sf-metadata-map-container" ref={mapRef} id="sf-metadata-map-container"></div>
|
||||
<Main validImages={validImages} onOpen={openGallery} />
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
177
frontend/src/metadata/views/map/main.js
Normal file
177
frontend/src/metadata/views/map/main.js
Normal file
@@ -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 (
|
||||
<div className="sf-metadata-view-map">
|
||||
<div className="sf-metadata-map-container" ref={mapRef} id="sf-metadata-map-container"></div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
Main.propTypes = {
|
||||
validImages: PropTypes.array,
|
||||
onOpen: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Main;
|
@@ -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 = `<img src=${this._imageUrl} width="72" height="72" />`;
|
||||
const imageElement = `<img src=${this._imageUrl} />`;
|
||||
const htmlString =
|
||||
`
|
||||
<div class="custom-image-container">
|
||||
${this._imageUrl ? imageElement : '<div class="empty-custom-image-wrapper"></div>'}
|
||||
<i class='sf3-font image-overlay-arrow'></i>
|
||||
</div>
|
||||
`;
|
||||
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;
|
7
frontend/src/metadata/views/map/overlay/index.js
Normal file
7
frontend/src/metadata/views/map/overlay/index.js
Normal file
@@ -0,0 +1,7 @@
|
||||
import customAvatarOverlay from './custom-avatar-overlay';
|
||||
import customImageOverlay from './custom-image-overlay';
|
||||
|
||||
export {
|
||||
customAvatarOverlay,
|
||||
customImageOverlay
|
||||
};
|
@@ -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);
|
||||
}
|
||||
});
|
||||
|
||||
};
|
||||
|
@@ -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();
|
||||
|
Reference in New Issue
Block a user