mirror of
https://github.com/haiwen/seahub.git
synced 2025-07-15 16:04:01 +00:00
Optimize/map cluster (#7426)
* optimize cluster position, clean up map view toolbar * fix cluster position, optimize controller ui --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
parent
f8112c0306
commit
94e19c58c0
@ -114,11 +114,9 @@ const ViewToolBar = ({ viewId, isCustomPermission, onToggleDetail, onCloseDetail
|
|||||||
{viewType === VIEW_TYPE.MAP && (
|
{viewType === VIEW_TYPE.MAP && (
|
||||||
<MapViewToolBar
|
<MapViewToolBar
|
||||||
readOnly={readOnly}
|
readOnly={readOnly}
|
||||||
isCustomPermission={isCustomPermission}
|
|
||||||
view={view}
|
view={view}
|
||||||
collaborators={collaborators}
|
collaborators={collaborators}
|
||||||
modifyFilters={modifyFilters}
|
modifyFilters={modifyFilters}
|
||||||
onToggleDetail={onToggleDetail}
|
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,20 +1,14 @@
|
|||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useMemo } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { EVENT_BUS_TYPE, PRIVATE_COLUMN_KEY, VIEW_TYPE } from '../../../constants';
|
import { PRIVATE_COLUMN_KEY, VIEW_TYPE } from '../../../constants';
|
||||||
import { FilterSetter, GalleryGroupBySetter, MapTypeSetter, SortSetter } from '../../data-process-setter';
|
import { FilterSetter, MapTypeSetter } from '../../data-process-setter';
|
||||||
import { gettext } from '../../../../utils/constants';
|
|
||||||
|
|
||||||
const MapViewToolBar = ({
|
const MapViewToolBar = ({
|
||||||
isCustomPermission,
|
|
||||||
readOnly,
|
readOnly,
|
||||||
view: oldView,
|
view: oldView,
|
||||||
collaborators,
|
collaborators,
|
||||||
modifyFilters,
|
modifyFilters,
|
||||||
onToggleDetail,
|
|
||||||
}) => {
|
}) => {
|
||||||
const [showGalleryToolbar, setShowGalleryToolbar] = useState(false);
|
|
||||||
const [view, setView] = useState(oldView);
|
|
||||||
|
|
||||||
const viewType = useMemo(() => VIEW_TYPE.MAP, []);
|
const viewType = useMemo(() => VIEW_TYPE.MAP, []);
|
||||||
const viewColumns = useMemo(() => {
|
const viewColumns = useMemo(() => {
|
||||||
if (!oldView) return [];
|
if (!oldView) return [];
|
||||||
@ -26,54 +20,6 @@ const MapViewToolBar = ({
|
|||||||
return viewColumns && viewColumns.filter(c => c.key !== PRIVATE_COLUMN_KEY.FILE_TYPE);
|
return viewColumns && viewColumns.filter(c => c.key !== PRIVATE_COLUMN_KEY.FILE_TYPE);
|
||||||
}, [viewColumns]);
|
}, [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 (
|
|
||||||
<>
|
|
||||||
<div className="sf-metadata-tool-left-operations">
|
|
||||||
<GalleryGroupBySetter viewID={viewID} />
|
|
||||||
<SortSetter
|
|
||||||
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-right-operations"></div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="sf-metadata-tool-left-operations">
|
<div className="sf-metadata-tool-left-operations">
|
||||||
@ -99,11 +45,10 @@ const MapViewToolBar = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
MapViewToolBar.propTypes = {
|
MapViewToolBar.propTypes = {
|
||||||
isCustomPermission: PropTypes.bool,
|
|
||||||
readOnly: PropTypes.bool,
|
readOnly: PropTypes.bool,
|
||||||
|
view: PropTypes.object,
|
||||||
collaborators: PropTypes.array,
|
collaborators: PropTypes.array,
|
||||||
modifyFilters: PropTypes.func,
|
modifyFilters: PropTypes.func,
|
||||||
onToggleDetail: PropTypes.func,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default MapViewToolBar;
|
export default MapViewToolBar;
|
||||||
|
@ -1,4 +0,0 @@
|
|||||||
.sf-metadata-map-photos-container {
|
|
||||||
padding: 0 !important;
|
|
||||||
overflow: hidden !important;
|
|
||||||
}
|
|
@ -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 (<CenteredLoading />);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="sf-metadata-view-map sf-metadata-map-photos-container">
|
|
||||||
<Gallery metadata={metadata} onDelete={handelDelete} onAddFolder={addFolder} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
ClusterPhotos.propTypes = {
|
|
||||||
photoIds: PropTypes.array,
|
|
||||||
onClose: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default ClusterPhotos;
|
|
@ -1,4 +1,4 @@
|
|||||||
.sf-map-control-container.sf-map-geolocation-control {
|
.sf-map-control-container.sf-map-geolocation-control {
|
||||||
width: 40px;
|
width: 30px;
|
||||||
line-height: 40px;
|
line-height: 30px;
|
||||||
}
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Utils } from '../../../../../../utils/utils';
|
import { Utils } from '../../../../../utils/utils';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
@ -1,12 +1,12 @@
|
|||||||
.sf-map-control-container {
|
.sf-map-control-container {
|
||||||
height: 40px;
|
height: 30px;
|
||||||
width: fit-content;
|
width: fit-content;
|
||||||
background-color: rgba(255, 255, 255, .9);
|
background-color: rgba(255, 255, 255, .9);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
box-shadow: -2px -2px 4px 2px rgba(0, 0, 0, 0.1);
|
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 {
|
.sf-map-control-container.sf-map-control-loading {
|
||||||
@ -14,8 +14,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sf-map-control-container.sf-map-control-container-mobile {
|
.sf-map-control-container.sf-map-control-container-mobile {
|
||||||
height: 35px;
|
height: 25px;
|
||||||
line-height: 35px;
|
line-height: 25px;
|
||||||
opacity: .75;
|
opacity: .75;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,21 +36,21 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.sf-map-control-container .sf-map-control {
|
.sf-map-control-container .sf-map-control {
|
||||||
height: 40px;
|
height: 30px;
|
||||||
width: 40px;
|
width: 30px;
|
||||||
color: #666;
|
color: #666;
|
||||||
background-color: inherit;
|
background-color: inherit;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sf-map-control-container.sf-map-control-container-mobile .sf-map-control {
|
.sf-map-control-container.sf-map-control-container-mobile .sf-map-control {
|
||||||
height: 35px;
|
height: 25px;
|
||||||
width: 35px;
|
width: 25px;
|
||||||
opacity: .75;
|
opacity: .75;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sf-map-control-container .sf-map-control .sf-map-control-icon {
|
.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 {
|
.sf-map-control-container .sf-map-control:not(.disabled):hover {
|
@ -1,3 +1,3 @@
|
|||||||
.sf-map-control-container.sf-map-zoom-control-container .sf-map-control {
|
.sf-map-control-container.sf-map-zoom-control-container .sf-map-control {
|
||||||
width: 55px;
|
width: 40px;
|
||||||
}
|
}
|
@ -1,12 +1,12 @@
|
|||||||
import classnames from 'classnames';
|
import classnames from 'classnames';
|
||||||
import { Utils } from '../../../../../../utils/utils';
|
import { Utils } from '../../../../../utils/utils';
|
||||||
|
|
||||||
import './index.css';
|
import './index.css';
|
||||||
|
|
||||||
export function createBMapZoomControl(BMapGL, { maxZoom, minZoom }, callback) {
|
export function createBMapZoomControl(BMapGL, { maxZoom, minZoom }, callback) {
|
||||||
function ZoomControl() {
|
function ZoomControl() {
|
||||||
this.defaultAnchor = window.BMAP_ANCHOR_BOTTOM_RIGHT;
|
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 = new BMapGL.Control();
|
||||||
ZoomControl.prototype.initialize = function (map) {
|
ZoomControl.prototype.initialize = function (map) {
|
@ -4,3 +4,86 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
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);
|
||||||
|
}
|
||||||
|
@ -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 { 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 { useMetadataView } from '../../hooks/metadata-view';
|
||||||
import { Utils } from '../../../utils/utils';
|
import { Utils } from '../../../utils/utils';
|
||||||
import { fileServerRoot, siteRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
|
|
||||||
import { isValidPosition } from '../../utils/validate';
|
import { isValidPosition } from '../../utils/validate';
|
||||||
import { gcj02_to_bd09, wgs84_to_gcj02 } from '../../../utils/coord-transform';
|
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';
|
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 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 { metadata, viewID, updateCurrentPath } = useMetadataView();
|
||||||
|
|
||||||
const repoID = window.sfMetadataContext.getSetting('repoID');
|
const repoID = window.sfMetadataContext.getSetting('repoID');
|
||||||
@ -52,18 +67,219 @@ const Map = () => {
|
|||||||
downloadURL: `${fileServerRoot}repos/${repoID}/files${path}?op=download`,
|
downloadURL: `${fileServerRoot}repos/${repoID}/files${path}?op=download`,
|
||||||
thumbnail,
|
thumbnail,
|
||||||
parentDir,
|
parentDir,
|
||||||
location: { lng: bdPosition.lng, lat: bdPosition.lat }
|
location: bdPosition
|
||||||
};
|
};
|
||||||
})
|
})
|
||||||
.filter(Boolean);
|
.filter(Boolean);
|
||||||
}, [repoID, repoInfo.encrypted, metadata]);
|
}, [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(() => {
|
useEffect(() => {
|
||||||
updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}`);
|
updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}`);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (<MapView images={images} />);
|
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 (
|
||||||
|
<div className="sf-metadata-view-map" id="sf-metadata-view-map">
|
||||||
|
<div ref={mapRef} className="sf-metadata-map-container" id="sf-metadata-map-container"></div>
|
||||||
|
{clusterLeaveIds.length > 0 && (
|
||||||
|
<ModalPortal>
|
||||||
|
<ImageDialog
|
||||||
|
imageItems={clusterLeaves}
|
||||||
|
imageIndex={imageIndex}
|
||||||
|
closeImagePopup={handleClose}
|
||||||
|
moveToPrevImage={moveToPrevImage}
|
||||||
|
moveToNextImage={moveToNextImage}
|
||||||
|
/>
|
||||||
|
</ModalPortal>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Map;
|
export default Map;
|
||||||
|
@ -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);
|
|
||||||
}
|
|
@ -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 (
|
|
||||||
<div className="sf-metadata-view-map">
|
|
||||||
<div className="sf-metadata-map-container" ref={mapRef} id="sf-metadata-map-container"></div>
|
|
||||||
{clusterLeaveIds.length > 0 && (
|
|
||||||
<ModalPortal>
|
|
||||||
<ImageDialog
|
|
||||||
imageItems={clusterLeaves}
|
|
||||||
imageIndex={imageIndex}
|
|
||||||
closeImagePopup={handleClose}
|
|
||||||
moveToPrevImage={moveToPrevImage}
|
|
||||||
moveToNextImage={moveToNextImage}
|
|
||||||
/>
|
|
||||||
</ModalPortal>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
MapView.propTypes = {
|
|
||||||
images: PropTypes.array,
|
|
||||||
onOpenCluster: PropTypes.func,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default MapView;
|
|
Loading…
Reference in New Issue
Block a user