mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-06 01:12:03 +00:00
Merge pull request #7275 from haiwen/optimize/map_view
Optimize/map view
This commit is contained in:
@@ -9,7 +9,7 @@ import { siteRoot, gettext } from '../../utils/constants';
|
||||
import { Utils } from '../../utils/utils';
|
||||
import { PRIVATE_FILE_TYPE } from '../../constants';
|
||||
import { debounce } from '../../metadata/utils/common';
|
||||
import { EVENT_BUS_TYPE, FACE_RECOGNITION_VIEW_ID } from '../../metadata/constants';
|
||||
import { EVENT_BUS_TYPE } from '../../metadata/constants';
|
||||
import { ALL_TAGS_ID } from '../../tag/constants';
|
||||
|
||||
const propTypes = {
|
||||
@@ -126,13 +126,12 @@ class DirPath extends React.Component {
|
||||
turnViewPathToLink = (pathList) => {
|
||||
if (!Array.isArray(pathList) || pathList.length === 0) return null;
|
||||
const [, , viewId, children] = pathList;
|
||||
const isViewSupportClick = viewId === FACE_RECOGNITION_VIEW_ID && children;
|
||||
return (
|
||||
<>
|
||||
<span className="path-split">/</span>
|
||||
<span className="path-item">{gettext('Views')}</span>
|
||||
<span className="path-split">/</span>
|
||||
<span className="path-item" role={isViewSupportClick ? 'button' : null} onClick={isViewSupportClick ? this.handleRefresh : () => {}}>
|
||||
<span className="path-item" role={children ? 'button' : null} onClick={children ? this.handleRefresh : () => {}}>
|
||||
<MetadataViewName id={viewId} />
|
||||
</span>
|
||||
{children && (
|
||||
|
@@ -0,0 +1,37 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EVENT_BUS_TYPE, GALLERY_DATE_MODE, STORAGE_GALLERY_DATE_MODE_KEY } from '../../constants';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import RadioGroup from '../radio-group';
|
||||
|
||||
const DATE_MODES = [
|
||||
{ value: GALLERY_DATE_MODE.YEAR, label: gettext('Year') },
|
||||
{ value: GALLERY_DATE_MODE.MONTH, label: gettext('Month') },
|
||||
{ value: GALLERY_DATE_MODE.DAY, label: gettext('Day') },
|
||||
{ value: GALLERY_DATE_MODE.ALL, label: gettext('All') },
|
||||
];
|
||||
|
||||
const GalleryGroupBySetter = ({ view }) => {
|
||||
const [currentMode, setCurrentMode] = useState(GALLERY_DATE_MODE.DAY);
|
||||
|
||||
useEffect(() => {
|
||||
const savedValue = window.sfMetadataContext.localStorage.getItem(STORAGE_GALLERY_DATE_MODE_KEY) || GALLERY_DATE_MODE.DAY;
|
||||
setCurrentMode(savedValue);
|
||||
}, [view?._id]);
|
||||
|
||||
const handleGroupByChange = useCallback((newMode) => {
|
||||
if (currentMode === newMode) return;
|
||||
setCurrentMode(newMode);
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY, newMode);
|
||||
}, [currentMode]);
|
||||
|
||||
return (<RadioGroup value={currentMode} options={DATE_MODES} onChange={handleGroupByChange} />);
|
||||
};
|
||||
|
||||
GalleryGroupBySetter.propTypes = {
|
||||
view: PropTypes.shape({
|
||||
_id: PropTypes.string
|
||||
})
|
||||
};
|
||||
|
||||
export default GalleryGroupBySetter;
|
@@ -1,57 +0,0 @@
|
||||
.metadata-gallery-group-by-setter {
|
||||
width: 272px;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid #e2e2e2;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.metadata-gallery-group-by-setter .metadata-gallery-group-by-button {
|
||||
width: 66px;
|
||||
height: 28px;
|
||||
color: #212529;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.875rem;
|
||||
border: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.metadata-gallery-group-by-setter .metadata-gallery-group-by-button:hover {
|
||||
background-color: #f0f0f0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.metadata-gallery-group-by-setter .metadata-gallery-group-by-button.active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.metadata-gallery-group-by-setter .metadata-gallery-group-by-button span {
|
||||
display: block;
|
||||
text-align: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.metadata-gallery-group-by-button:not(:first-child)::before {
|
||||
content: '';
|
||||
width: 1px;
|
||||
height: 22px;
|
||||
background-color: #e2e2e2;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
transition: opacity 0.3s;
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.metadata-gallery-group-by-button:hover::before,
|
||||
.metadata-gallery-group-by-button.active::before,
|
||||
.metadata-gallery-group-by-button:hover + .metadata-gallery-group-by-button::before,
|
||||
.metadata-gallery-group-by-button.active + .metadata-gallery-group-by-button::before {
|
||||
opacity: 0;
|
||||
}
|
@@ -1,51 +0,0 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { EVENT_BUS_TYPE, GALLERY_DATE_MODE } from '../../../constants';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const DATE_MODE_MAP = {
|
||||
[GALLERY_DATE_MODE.YEAR]: gettext('Year'),
|
||||
[GALLERY_DATE_MODE.MONTH]: gettext('Month'),
|
||||
[GALLERY_DATE_MODE.DAY]: gettext('Day'),
|
||||
[GALLERY_DATE_MODE.ALL]: gettext('All')
|
||||
};
|
||||
|
||||
const GalleryGroupBySetter = ({ view }) => {
|
||||
const [currentMode, setCurrentMode] = useState(GALLERY_DATE_MODE.DAY);
|
||||
|
||||
useEffect(() => {
|
||||
const savedValue = window.sfMetadataContext.localStorage.getItem('gallery-group-by', GALLERY_DATE_MODE.DAY);
|
||||
setCurrentMode(savedValue || GALLERY_DATE_MODE.DAY);
|
||||
}, [view?._id]);
|
||||
|
||||
const handleGroupByChange = useCallback((newMode) => {
|
||||
setCurrentMode(newMode);
|
||||
window.sfMetadataContext.localStorage.setItem('gallery-group-by', newMode);
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY, newMode);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="metadata-gallery-group-by-setter">
|
||||
{Object.entries(DATE_MODE_MAP).map(([dateMode, label]) => (
|
||||
<button
|
||||
key={dateMode}
|
||||
className={classnames('metadata-gallery-group-by-button', { active: currentMode === dateMode })}
|
||||
onClick={() => handleGroupByChange(dateMode)}
|
||||
>
|
||||
<span>{label}</span>
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
GalleryGroupBySetter.propTypes = {
|
||||
view: PropTypes.shape({
|
||||
_id: PropTypes.string
|
||||
})
|
||||
};
|
||||
|
||||
export default GalleryGroupBySetter;
|
@@ -1,10 +1,11 @@
|
||||
import GalleryGroupBySetter from './gallery-group-by-setter/index';
|
||||
import GalleryGroupBySetter from './gallery-group-by-setter';
|
||||
import GallerySliderSetter from './gallery-slider-setter/index';
|
||||
import FilterSetter from './filter-setter';
|
||||
import SortSetter from './sort-setter';
|
||||
import GroupbySetter from './groupby-setter';
|
||||
import PreHideColumnSetter from './pre-hide-column-setter';
|
||||
import HideColumnSetter from './hide-column-setter';
|
||||
import MapTypeSetter from './map-type-setter';
|
||||
|
||||
export {
|
||||
GalleryGroupBySetter,
|
||||
@@ -14,4 +15,5 @@ export {
|
||||
GroupbySetter,
|
||||
PreHideColumnSetter,
|
||||
HideColumnSetter,
|
||||
MapTypeSetter,
|
||||
};
|
||||
|
@@ -0,0 +1,35 @@
|
||||
import React, { useState, useCallback, useEffect } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { EVENT_BUS_TYPE, MAP_TYPE, STORAGE_MAP_TYPE_KEY } from '../../constants';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import RadioGroup from '../radio-group';
|
||||
|
||||
const MAP_TYPES = [
|
||||
{ value: MAP_TYPE.MAP, label: gettext('Map') },
|
||||
{ value: MAP_TYPE.SATELLITE, label: gettext('Satellite') },
|
||||
];
|
||||
|
||||
const MapTypeSetter = ({ view }) => {
|
||||
const [currentType, setCurrentType] = useState(MAP_TYPE.MAP);
|
||||
|
||||
useEffect(() => {
|
||||
const type = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_TYPE_KEY) || MAP_TYPE.MAP;
|
||||
setCurrentType(type);
|
||||
}, [view?._id]);
|
||||
|
||||
const onChange = useCallback((type) => {
|
||||
if (currentType === type) return;
|
||||
setCurrentType(type);
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MODIFY_MAP_TYPE, type);
|
||||
}, [currentType]);
|
||||
|
||||
return (<RadioGroup options={MAP_TYPES} value={currentType} onChange={onChange} />);
|
||||
};
|
||||
|
||||
MapTypeSetter.propTypes = {
|
||||
view: PropTypes.shape({
|
||||
_id: PropTypes.string
|
||||
})
|
||||
};
|
||||
|
||||
export default MapTypeSetter;
|
@@ -47,7 +47,7 @@ const FileOrFolderFilter = ({ readOnly, value = 'all', onChange: onChangeAPI })
|
||||
return (
|
||||
<CustomizeSelect
|
||||
readOnly={readOnly}
|
||||
className="sf-metadata-basic-filters-select"
|
||||
className="sf-metadata-basic-filters-select mr-4"
|
||||
value={displayValue}
|
||||
options={options}
|
||||
onSelectOption={onChange}
|
||||
|
@@ -47,7 +47,7 @@ const GalleryFileTypeFilter = ({ readOnly, value = 'picture', onChange: onChange
|
||||
return (
|
||||
<CustomizeSelect
|
||||
readOnly={readOnly}
|
||||
className="sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select ml-4"
|
||||
className="sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select mr-4"
|
||||
value={displayValue}
|
||||
options={options}
|
||||
onSelectOption={onChange}
|
||||
|
@@ -54,7 +54,7 @@ const TableFileTypeFilter = ({ readOnly, value, onChange: onChangeAPI }) => {
|
||||
return (
|
||||
<CustomizeSelect
|
||||
readOnly={readOnly}
|
||||
className="sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select ml-4"
|
||||
className="sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select mr-4"
|
||||
value={displayValue}
|
||||
options={options}
|
||||
onSelectOption={onChange}
|
||||
|
@@ -84,7 +84,7 @@ const TagsFilter = ({ readOnly, value: oldValue, onChange: onChangeAPI }) => {
|
||||
readOnly={readOnly}
|
||||
searchable={true}
|
||||
supportMultipleSelect={true}
|
||||
className="sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select ml-4"
|
||||
className="sf-metadata-basic-filters-select sf-metadata-table-view-basic-filter-file-type-select mr-4"
|
||||
value={displayValue}
|
||||
options={options}
|
||||
onSelectOption={onChange}
|
||||
|
53
frontend/src/metadata/components/radio-group/index.css
Normal file
53
frontend/src/metadata/components/radio-group/index.css
Normal file
@@ -0,0 +1,53 @@
|
||||
.sf-metadata-radio-group {
|
||||
width: fit-content;
|
||||
height: 36px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
border: 1px solid #e2e2e2;
|
||||
border-radius: 3px;
|
||||
padding: 0 3px;
|
||||
}
|
||||
|
||||
.sf-metadata-radio-group .sf-metadata-radio-group-option {
|
||||
min-width: 66px;
|
||||
width: fit-content;
|
||||
height: 28px;
|
||||
color: #212529;
|
||||
background-color: #fff;
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.875rem;
|
||||
border: 0;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.sf-metadata-radio-group .sf-metadata-radio-group-option:hover {
|
||||
background-color: #f0f0f0;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-metadata-radio-group .sf-metadata-radio-group-option.active {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
||||
.sf-metadata-radio-group .sf-metadata-radio-group-option:not(:first-child)::before {
|
||||
content: '';
|
||||
width: 1px;
|
||||
height: 22px;
|
||||
background-color: #e2e2e2;
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
transition: opacity 0.3s;
|
||||
left: -1px;
|
||||
}
|
||||
|
||||
.sf-metadata-radio-group .sf-metadata-radio-group-option:hover::before,
|
||||
.sf-metadata-radio-group .sf-metadata-radio-group-option.active::before,
|
||||
.sf-metadata-radio-group .sf-metadata-radio-group-option:hover + .sf-metadata-radio-group-option::before,
|
||||
.sf-metadata-radio-group .sf-metadata-radio-group-option.active + .sf-metadata-radio-group-option::before {
|
||||
opacity: 0;
|
||||
}
|
45
frontend/src/metadata/components/radio-group/index.js
Normal file
45
frontend/src/metadata/components/radio-group/index.js
Normal file
@@ -0,0 +1,45 @@
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const RadioGroup = ({ value, options, className, onChange: onChangeAPI }) => {
|
||||
const selected = useMemo(() => {
|
||||
const selectedOption = options.find(o => value === o.value) || options[0];
|
||||
return selectedOption.value;
|
||||
}, [value, options]);
|
||||
|
||||
const onChange = useCallback((event) => {
|
||||
const newValue = event.target.dataset.option;
|
||||
if (selected === newValue) return;
|
||||
onChangeAPI(newValue);
|
||||
}, [selected, onChangeAPI]);
|
||||
|
||||
return (
|
||||
<div className={classnames('sf-metadata-radio-group', className)}>
|
||||
{options.map(option => {
|
||||
const { value, label } = option;
|
||||
return (
|
||||
<div
|
||||
key={value}
|
||||
data-option={value}
|
||||
className={classnames('sf-metadata-radio-group-option', { 'active': value === selected })}
|
||||
onClick={onChange}
|
||||
>
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
RadioGroup.propTypes = {
|
||||
value: PropTypes.string,
|
||||
options: PropTypes.array,
|
||||
className: PropTypes.string,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
export default RadioGroup;
|
@@ -17,17 +17,17 @@ const FaceRecognitionViewToolbar = ({ readOnly, isCustomPermission, onToggleDeta
|
||||
setShow(isShow);
|
||||
}, []);
|
||||
|
||||
const setRecognitionView = useCallback(view => {
|
||||
const resetView = useCallback(view => {
|
||||
setView(view);
|
||||
}, []);
|
||||
|
||||
const modifySorts = useCallback((sorts) => {
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.FACE_RECOGNITION_VIEW_CHANGE, { sorts });
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, { sorts });
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeToggle = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, onToggle);
|
||||
const unsubscribeView = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.FACE_RECOGNITION_VIEW, setRecognitionView);
|
||||
const unsubscribeView = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.RESET_VIEW, resetView);
|
||||
return () => {
|
||||
unsubscribeToggle && unsubscribeToggle();
|
||||
unsubscribeView && unsubscribeView();
|
||||
|
@@ -94,8 +94,8 @@ const ViewToolBar = ({ viewId, isCustomPermission, onToggleDetail, onCloseDetail
|
||||
)}
|
||||
{viewType === VIEW_TYPE.FACE_RECOGNITION && (
|
||||
<FaceRecognitionViewToolbar
|
||||
readOnly={readOnly}
|
||||
isCustomPermission={isCustomPermission}
|
||||
view={view}
|
||||
onToggleDetail={onToggleDetail}
|
||||
/>
|
||||
)}
|
||||
@@ -114,9 +114,11 @@ const ViewToolBar = ({ viewId, isCustomPermission, onToggleDetail, onCloseDetail
|
||||
{viewType === VIEW_TYPE.MAP && (
|
||||
<MapViewToolBar
|
||||
readOnly={readOnly}
|
||||
view={view}
|
||||
isCustomPermission={isCustomPermission}
|
||||
viewID={view._id}
|
||||
collaborators={collaborators}
|
||||
modifyFilters={modifyFilters}
|
||||
onToggleDetail={onToggleDetail}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
@@ -1,27 +1,83 @@
|
||||
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 } 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,
|
||||
viewID,
|
||||
collaborators,
|
||||
modifyFilters,
|
||||
onToggleDetail,
|
||||
}) => {
|
||||
const viewType = useMemo(() => view.type, [view]);
|
||||
const [showGalleryToolbar, setShowGalleryToolbar] = useState(false);
|
||||
const [view, setView] = useState({});
|
||||
|
||||
const viewType = useMemo(() => VIEW_TYPE.MAP, []);
|
||||
const viewColumns = useMemo(() => {
|
||||
if (!view) return [];
|
||||
return view.columns;
|
||||
}, [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.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 view={{ _id: viewID }} />
|
||||
<GallerySliderSetter view={{ _id: 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 (
|
||||
<>
|
||||
<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"
|
||||
@@ -43,10 +99,11 @@ const MapViewToolBar = ({
|
||||
};
|
||||
|
||||
MapViewToolBar.propTypes = {
|
||||
isCustomPermission: PropTypes.bool,
|
||||
readOnly: PropTypes.bool,
|
||||
view: PropTypes.object,
|
||||
collaborators: PropTypes.array,
|
||||
modifyFilters: PropTypes.func,
|
||||
onToggleDetail: PropTypes.func,
|
||||
};
|
||||
|
||||
export default MapViewToolBar;
|
||||
|
@@ -60,17 +60,21 @@ export const EVENT_BUS_TYPE = {
|
||||
SAVED: 'saved',
|
||||
ERROR: 'error',
|
||||
|
||||
// view
|
||||
TOGGLE_VIEW_TOOLBAR: 'toggle_view_toolbar',
|
||||
RESET_VIEW: 'reset_view',
|
||||
UPDATE_SERVER_VIEW: 'update_server_view',
|
||||
|
||||
// gallery
|
||||
MODIFY_GALLERY_ZOOM_GEAR: 'modify_gallery_zoom_gear',
|
||||
SWITCH_GALLERY_GROUP_BY: 'switch_gallery_group_by',
|
||||
|
||||
// face recognition
|
||||
TOGGLE_VIEW_TOOLBAR: 'toggle_view_toolbar',
|
||||
FACE_RECOGNITION_VIEW: 'face_recognition_view',
|
||||
FACE_RECOGNITION_VIEW_CHANGE: 'face_recognition_view_change',
|
||||
|
||||
// kanban
|
||||
TOGGLE_KANBAN_SETTINGS: 'toggle_kanban_settings',
|
||||
OPEN_KANBAN_SETTINGS: 'open_kanban_settings',
|
||||
CLOSE_KANBAN_SETTINGS: 'close_kanban_settings',
|
||||
|
||||
// map
|
||||
MODIFY_MAP_TYPE: 'modify_map_type',
|
||||
MAP_VIEW: 'map_view',
|
||||
};
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import CellType from './column/type';
|
||||
import { EVENT_BUS_TYPE } from './event-bus-type';
|
||||
import TRANSFER_TYPES from './TransferTypes';
|
||||
import * as metadataZIndexes from './z-index';
|
||||
@@ -13,137 +12,8 @@ export * from './sort';
|
||||
export * from './error';
|
||||
export * from './view';
|
||||
|
||||
export const CELL_NAVIGATION_MODE = {
|
||||
NONE: 'none',
|
||||
CHANGE_ROW: 'changeRow',
|
||||
LOOP_OVER_ROW: 'loopOverRow',
|
||||
};
|
||||
|
||||
export const SEQUENCE_COLUMN_WIDTH = 80;
|
||||
|
||||
export const ROW_HEIGHT = 32;
|
||||
|
||||
export const GRID_HEADER_DEFAULT_HEIGHT = 32;
|
||||
|
||||
export const GRID_HEADER_DOUBLE_HEIGHT = 56;
|
||||
|
||||
export const GROUP_VIEW_OFFSET = 16;
|
||||
|
||||
export const GROUP_HEADER_HEIGHT = 48;
|
||||
|
||||
export const TABLE_LEFT_MARGIN = 10;
|
||||
|
||||
export const TABLE_BORDER_WIDTH = 1;
|
||||
|
||||
export const UNABLE_TO_CALCULATE = '--';
|
||||
|
||||
export const FROZEN_COLUMN_SHADOW = '2px 0 5px -2px hsla(0,0%,53.3%,.3)';
|
||||
|
||||
export const TABLE_NOT_SUPPORT_EDIT_TYPE_MAP = {
|
||||
[CellType.CREATOR]: true,
|
||||
[CellType.LAST_MODIFIER]: true,
|
||||
[CellType.CTIME]: true,
|
||||
[CellType.MTIME]: true,
|
||||
[CellType.FILE_NAME]: true,
|
||||
};
|
||||
|
||||
export const TABLE_SUPPORT_EDIT_TYPE_MAP = {
|
||||
[CellType.TEXT]: true,
|
||||
[CellType.DATE]: true,
|
||||
[CellType.NUMBER]: true,
|
||||
[CellType.SINGLE_SELECT]: true,
|
||||
[CellType.MULTIPLE_SELECT]: true,
|
||||
[CellType.COLLABORATOR]: true,
|
||||
[CellType.CHECKBOX]: true,
|
||||
[CellType.LONG_TEXT]: true,
|
||||
[CellType.LINK]: true,
|
||||
[CellType.TAGS]: true,
|
||||
};
|
||||
|
||||
export const TABLE_MOBILE_SUPPORT_EDIT_CELL_TYPE_MAP = {
|
||||
[CellType.TEXT]: true,
|
||||
};
|
||||
|
||||
export const CANVAS_RIGHT_INTERVAL = 44;
|
||||
|
||||
export const LEFT_NAV = 280;
|
||||
export const ROW_DETAIL_PADDING = 40 * 2;
|
||||
export const ROW_DETAIL_MARGIN = 20 * 2;
|
||||
export const EDITOR_PADDING = 1.5 * 16; // 1.5: 0.75 * 2
|
||||
|
||||
export const COLUMN_RATE_MAX_NUMBER = [
|
||||
{ name: 1 },
|
||||
{ name: 2 },
|
||||
{ name: 3 },
|
||||
{ name: 4 },
|
||||
{ name: 5 },
|
||||
{ name: 6 },
|
||||
{ name: 7 },
|
||||
{ name: 8 },
|
||||
{ name: 9 },
|
||||
{ name: 10 },
|
||||
];
|
||||
|
||||
export const GROUP_ROW_TYPE = {
|
||||
GROUP_CONTAINER: 'group_container',
|
||||
ROW: 'row',
|
||||
BTN_INSERT_ROW: 'btn_insert_row',
|
||||
};
|
||||
|
||||
export const INSERT_ROW_HEIGHT = 32;
|
||||
|
||||
export const CHANGE_HEADER_WIDTH = 'CHANGE_HEADER_WIDTH';
|
||||
|
||||
export const NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES = [
|
||||
];
|
||||
|
||||
export const SUPPORT_PREVIEW_COLUMN_TYPES = [];
|
||||
|
||||
export const OVER_SCAN_COLUMNS = 10;
|
||||
|
||||
export const DELETED_OPTION_BACKGROUND_COLOR = '#eaeaea';
|
||||
|
||||
export const DELETED_OPTION_TIPS = 'deleted_option';
|
||||
|
||||
export const SUPPORT_BATCH_DOWNLOAD_TYPES = [];
|
||||
|
||||
export const PER_LOAD_NUMBER = 1000;
|
||||
|
||||
export const DEFAULT_RETRY_TIMES = 4;
|
||||
|
||||
export const DEFAULT_RETRY_INTERVAL = 1000;
|
||||
|
||||
export const MAX_LOAD_NUMBER = 10000;
|
||||
|
||||
export const EDITOR_TYPE = {
|
||||
PREVIEWER: 'previewer',
|
||||
ADDITION: 'addition',
|
||||
};
|
||||
|
||||
export {
|
||||
EVENT_BUS_TYPE,
|
||||
TRANSFER_TYPES,
|
||||
metadataZIndexes,
|
||||
};
|
||||
|
||||
export const DATE_TAG_HEIGHT = 44;
|
||||
|
||||
export const GALLERY_ZOOM_GEAR_MIN = -2;
|
||||
|
||||
export const GALLERY_ZOOM_GEAR_MAX = 2;
|
||||
|
||||
export const GALLERY_IMAGE_GAP = 2;
|
||||
|
||||
export const GALLERY_DATE_MODE = {
|
||||
YEAR: 'year',
|
||||
MONTH: 'month',
|
||||
DAY: 'day',
|
||||
ALL: 'all',
|
||||
};
|
||||
|
||||
export const UNCATEGORIZED = '_uncategorized';
|
||||
|
||||
export const PASTE_SOURCE = {
|
||||
COPY: 'copy',
|
||||
CUT: 'cut',
|
||||
};
|
||||
|
16
frontend/src/metadata/constants/view/gallery.js
Normal file
16
frontend/src/metadata/constants/view/gallery.js
Normal file
@@ -0,0 +1,16 @@
|
||||
export const DATE_TAG_HEIGHT = 44;
|
||||
|
||||
export const GALLERY_ZOOM_GEAR_MIN = -2;
|
||||
|
||||
export const GALLERY_ZOOM_GEAR_MAX = 2;
|
||||
|
||||
export const GALLERY_IMAGE_GAP = 2;
|
||||
|
||||
export const GALLERY_DATE_MODE = {
|
||||
YEAR: 'year',
|
||||
MONTH: 'month',
|
||||
DAY: 'day',
|
||||
ALL: 'all',
|
||||
};
|
||||
|
||||
export const STORAGE_GALLERY_DATE_MODE_KEY = 'gallery_date_mode';
|
@@ -1,8 +1,13 @@
|
||||
import { PRIVATE_COLUMN_KEY } from './column';
|
||||
import { FILTER_PREDICATE_TYPE } from './filter';
|
||||
import { PRIVATE_COLUMN_KEY } from '../column';
|
||||
import { FILTER_PREDICATE_TYPE } from '../filter';
|
||||
import { SORT_COLUMN_OPTIONS, GALLERY_SORT_COLUMN_OPTIONS, GALLERY_FIRST_SORT_COLUMN_OPTIONS, SORT_TYPE,
|
||||
GALLERY_SORT_PRIVATE_COLUMN_KEYS, GALLERY_FIRST_SORT_PRIVATE_COLUMN_KEYS,
|
||||
} from './sort';
|
||||
} from '../sort';
|
||||
|
||||
export * from './gallery';
|
||||
export * from './kanban';
|
||||
export * from './map';
|
||||
export * from './table';
|
||||
|
||||
export const METADATA_VIEWS_KEY = 'sf-metadata-views';
|
||||
|
||||
@@ -98,7 +103,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 +111,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 +119,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 = {
|
1
frontend/src/metadata/constants/view/kanban.js
Normal file
1
frontend/src/metadata/constants/view/kanban.js
Normal file
@@ -0,0 +1 @@
|
||||
export const UNCATEGORIZED = '_uncategorized';
|
15
frontend/src/metadata/constants/view/map.js
Normal file
15
frontend/src/metadata/constants/view/map.js
Normal file
@@ -0,0 +1,15 @@
|
||||
export const MAP_TYPE = {
|
||||
MAP: 'map',
|
||||
SATELLITE: 'satellite',
|
||||
};
|
||||
|
||||
export const STORAGE_MAP_TYPE_KEY = 'map_type';
|
||||
|
||||
export const STORAGE_MAP_CENTER_KEY = 'map_center';
|
||||
|
||||
export const STORAGE_MAP_ZOOM_KEY = 'map_zoom';
|
||||
|
||||
export const MAP_VIEW_TOOLBAR_MODE = {
|
||||
MAP: 'map',
|
||||
GALLERY: 'gallery',
|
||||
};
|
100
frontend/src/metadata/constants/view/table.js
Normal file
100
frontend/src/metadata/constants/view/table.js
Normal file
@@ -0,0 +1,100 @@
|
||||
import { CellType } from '../column';
|
||||
|
||||
export const CELL_NAVIGATION_MODE = {
|
||||
NONE: 'none',
|
||||
CHANGE_ROW: 'changeRow',
|
||||
LOOP_OVER_ROW: 'loopOverRow',
|
||||
};
|
||||
|
||||
export const SEQUENCE_COLUMN_WIDTH = 80;
|
||||
|
||||
export const ROW_HEIGHT = 32;
|
||||
|
||||
export const GRID_HEADER_DEFAULT_HEIGHT = 32;
|
||||
|
||||
export const GRID_HEADER_DOUBLE_HEIGHT = 56;
|
||||
|
||||
export const GROUP_VIEW_OFFSET = 16;
|
||||
|
||||
export const GROUP_HEADER_HEIGHT = 48;
|
||||
|
||||
export const TABLE_LEFT_MARGIN = 10;
|
||||
|
||||
export const TABLE_BORDER_WIDTH = 1;
|
||||
|
||||
export const UNABLE_TO_CALCULATE = '--';
|
||||
|
||||
export const FROZEN_COLUMN_SHADOW = '2px 0 5px -2px hsla(0,0%,53.3%,.3)';
|
||||
|
||||
export const TABLE_NOT_SUPPORT_EDIT_TYPE_MAP = {
|
||||
[CellType.CREATOR]: true,
|
||||
[CellType.LAST_MODIFIER]: true,
|
||||
[CellType.CTIME]: true,
|
||||
[CellType.MTIME]: true,
|
||||
[CellType.FILE_NAME]: true,
|
||||
};
|
||||
|
||||
export const TABLE_SUPPORT_EDIT_TYPE_MAP = {
|
||||
[CellType.TEXT]: true,
|
||||
[CellType.DATE]: true,
|
||||
[CellType.NUMBER]: true,
|
||||
[CellType.SINGLE_SELECT]: true,
|
||||
[CellType.MULTIPLE_SELECT]: true,
|
||||
[CellType.COLLABORATOR]: true,
|
||||
[CellType.CHECKBOX]: true,
|
||||
[CellType.LONG_TEXT]: true,
|
||||
[CellType.LINK]: true,
|
||||
[CellType.TAGS]: true,
|
||||
};
|
||||
|
||||
export const TABLE_MOBILE_SUPPORT_EDIT_CELL_TYPE_MAP = {
|
||||
[CellType.TEXT]: true,
|
||||
};
|
||||
|
||||
export const CANVAS_RIGHT_INTERVAL = 44;
|
||||
|
||||
export const LEFT_NAV = 280;
|
||||
export const ROW_DETAIL_PADDING = 40 * 2;
|
||||
export const ROW_DETAIL_MARGIN = 20 * 2;
|
||||
export const EDITOR_PADDING = 1.5 * 16; // 1.5: 0.75 * 2
|
||||
|
||||
export const GROUP_ROW_TYPE = {
|
||||
GROUP_CONTAINER: 'group_container',
|
||||
ROW: 'row',
|
||||
BTN_INSERT_ROW: 'btn_insert_row',
|
||||
};
|
||||
|
||||
export const INSERT_ROW_HEIGHT = 32;
|
||||
|
||||
export const CHANGE_HEADER_WIDTH = 'CHANGE_HEADER_WIDTH';
|
||||
|
||||
export const NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES = [
|
||||
];
|
||||
|
||||
export const SUPPORT_PREVIEW_COLUMN_TYPES = [];
|
||||
|
||||
export const OVER_SCAN_COLUMNS = 10;
|
||||
|
||||
export const DELETED_OPTION_BACKGROUND_COLOR = '#eaeaea';
|
||||
|
||||
export const DELETED_OPTION_TIPS = 'deleted_option';
|
||||
|
||||
export const SUPPORT_BATCH_DOWNLOAD_TYPES = [];
|
||||
|
||||
export const PER_LOAD_NUMBER = 1000;
|
||||
|
||||
export const DEFAULT_RETRY_TIMES = 4;
|
||||
|
||||
export const DEFAULT_RETRY_INTERVAL = 1000;
|
||||
|
||||
export const MAX_LOAD_NUMBER = 10000;
|
||||
|
||||
export const EDITOR_TYPE = {
|
||||
PREVIEWER: 'previewer',
|
||||
ADDITION: 'addition',
|
||||
};
|
||||
|
||||
export const PASTE_SOURCE = {
|
||||
COPY: 'copy',
|
||||
CUT: 'cut',
|
||||
};
|
@@ -340,6 +340,8 @@ export const MetadataViewProvider = ({
|
||||
<MetadataViewContext.Provider
|
||||
value={{
|
||||
isLoading,
|
||||
repoID,
|
||||
viewID,
|
||||
isBeingBuilt,
|
||||
errorMessage,
|
||||
metadata,
|
||||
|
@@ -59,13 +59,13 @@ const sortRowsWithMultiSorts = (tableRows, sorts, { collaborators }) => {
|
||||
* @param {object} value e.g. { collaborators, ... }
|
||||
* @returns sorted rows ids, array
|
||||
*/
|
||||
const sortTableRows = (table, rows, sorts, { collaborators }) => {
|
||||
const sortTableRows = (table, rows, sorts, { collaborators, isReturnID = true } = {}) => {
|
||||
const { columns } = table;
|
||||
if (!Array.isArray(rows) || rows.length === 0) return [];
|
||||
const sortRows = rows.slice(0);
|
||||
const validSorts = deleteInvalidSort(sorts, columns);
|
||||
sortRowsWithMultiSorts(sortRows, validSorts, { collaborators });
|
||||
return sortRows.map((row) => row._id);
|
||||
return isReturnID ? sortRows.map((row) => row._id) : sortRows;
|
||||
};
|
||||
|
||||
export {
|
||||
|
@@ -1,6 +1,8 @@
|
||||
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 metadataAPI from '../../../api';
|
||||
import Metadata from '../../../model/metadata';
|
||||
@@ -10,11 +12,16 @@ import { Utils } from '../../../../utils/utils';
|
||||
import toaster from '../../../../components/toast';
|
||||
import Gallery from '../../gallery/main';
|
||||
import { useMetadataView } from '../../../hooks/metadata-view';
|
||||
import { PER_LOAD_NUMBER, EVENT_BUS_TYPE, FACE_RECOGNITION_VIEW_ID } from '../../../constants';
|
||||
import { PER_LOAD_NUMBER, EVENT_BUS_TYPE, FACE_RECOGNITION_VIEW_ID, UTC_FORMAT_DEFAULT } from '../../../constants';
|
||||
import { getRecordIdFromRecord, getParentDirFromRecord, getFileNameFromRecord } from '../../../utils/cell';
|
||||
import { sortTableRows } from '../../../utils/sort';
|
||||
import { useCollaborators } from '../../../hooks/collaborators';
|
||||
|
||||
import './index.css';
|
||||
import '../../gallery/index.css';
|
||||
|
||||
dayjs.extend(utc);
|
||||
|
||||
const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onRemovePeoplePhotos }) => {
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [isLoadingMore, setLoadingMore] = useState(false);
|
||||
@@ -22,6 +29,7 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onRemovePeo
|
||||
const repoID = window.sfMetadataContext.getSetting('repoID');
|
||||
|
||||
const { deleteFilesCallback, store } = useMetadataView();
|
||||
const { collaborators } = useCollaborators();
|
||||
|
||||
const onLoadMore = useCallback(async () => {
|
||||
if (isLoadingMore) return;
|
||||
@@ -130,7 +138,7 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onRemovePeo
|
||||
metadata.hasMore = false;
|
||||
}
|
||||
setMetadata(metadata);
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.FACE_RECOGNITION_VIEW, metadata.view);
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.RESET_VIEW, metadata.view);
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
const errorMessage = Utils.getErrorMsg(error);
|
||||
@@ -150,6 +158,38 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onRemovePeo
|
||||
});
|
||||
}, [repoID, metadata, 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(() => {
|
||||
loadData({ sorts: view.sorts });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
@@ -164,11 +204,15 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onRemovePeo
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeViewChange = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.FACE_RECOGNITION_VIEW_CHANGE, onViewChange);
|
||||
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]);
|
||||
}, [onViewChange, onRecordChange]);
|
||||
|
||||
if (isLoading) return (<CenteredLoading />);
|
||||
|
||||
|
@@ -30,17 +30,17 @@ const GalleryContextMenu = ({ metadata, selectedImages, onDelete, onDuplicate, a
|
||||
|
||||
const options = useMemo(() => {
|
||||
let validOptions = [{ value: CONTEXT_MENU_KEY.DOWNLOAD, label: gettext('Download') }];
|
||||
if (checkCanDeleteRow) {
|
||||
if (onDelete && checkCanDeleteRow) {
|
||||
validOptions.push({ value: CONTEXT_MENU_KEY.DELETE, label: selectedImages.length > 1 ? gettext('Delete') : gettext('Delete file') });
|
||||
}
|
||||
if (canDuplicateRow && selectedImages.length === 1) {
|
||||
if (onDuplicate && canDuplicateRow && selectedImages.length === 1) {
|
||||
validOptions.push({ value: CONTEXT_MENU_KEY.DUPLICATE, label: gettext('Duplicate') });
|
||||
}
|
||||
if (canRemovePhotoFromPeople) {
|
||||
if (onRemoveImage && canRemovePhotoFromPeople) {
|
||||
validOptions.push({ value: CONTEXT_MENU_KEY.REMOVE, label: gettext('Remove from this group') });
|
||||
}
|
||||
return validOptions;
|
||||
}, [checkCanDeleteRow, canDuplicateRow, canRemovePhotoFromPeople, selectedImages]);
|
||||
}, [checkCanDeleteRow, canDuplicateRow, canRemovePhotoFromPeople, selectedImages, onDuplicate, onDelete, onRemoveImage]);
|
||||
|
||||
const closeZipDialog = () => {
|
||||
setIsZipDialogOpen(false);
|
||||
|
@@ -8,7 +8,7 @@ import { useMetadataView } from '../../hooks/metadata-view';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { getDateDisplayString, getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell';
|
||||
import { siteRoot, fileServerRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
|
||||
import { EVENT_BUS_TYPE, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP } from '../../constants';
|
||||
import { EVENT_BUS_TYPE, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP, STORAGE_GALLERY_DATE_MODE_KEY } from '../../constants';
|
||||
import { getRowById } from '../../utils/table';
|
||||
import { getEventClassName } from '../../utils/common';
|
||||
import GalleryContextmenu from './context-menu';
|
||||
@@ -129,14 +129,14 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
const gear = window.sfMetadataContext.localStorage.getItem('zoom-gear', 0) || 0;
|
||||
setZoomGear(gear);
|
||||
|
||||
const mode = window.sfMetadataContext.localStorage.getItem('gallery-group-by', GALLERY_DATE_MODE.DAY) || GALLERY_DATE_MODE.DAY;
|
||||
const mode = window.sfMetadataContext.localStorage.getItem(STORAGE_GALLERY_DATE_MODE_KEY, GALLERY_DATE_MODE.DAY) || GALLERY_DATE_MODE.DAY;
|
||||
setMode(mode);
|
||||
|
||||
const switchGalleryModeSubscribe = window.sfMetadataContext.eventBus.subscribe(
|
||||
EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY,
|
||||
(mode) => {
|
||||
setMode(mode);
|
||||
window.sfMetadataContext.localStorage.setItem('gallery-group-by', mode);
|
||||
window.sfMetadataContext.localStorage.setItem(STORAGE_GALLERY_DATE_MODE_KEY, mode);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -193,7 +193,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
|
||||
if (!containerRef.current) return;
|
||||
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
|
||||
if (scrollTop + clientHeight >= scrollHeight - 10) {
|
||||
onLoadMore();
|
||||
onLoadMore && onLoadMore();
|
||||
} else {
|
||||
const { scrollTop, clientHeight } = containerRef.current;
|
||||
const overScanTop = Math.max(0, scrollTop - (imageSize + GALLERY_IMAGE_GAP) * OVER_SCAN_ROWS);
|
||||
|
4
frontend/src/metadata/views/map/cluster-photos/index.css
Normal file
4
frontend/src/metadata/views/map/cluster-photos/index.css
Normal file
@@ -0,0 +1,4 @@
|
||||
.sf-metadata-map-photos-container {
|
||||
padding: 0 !important;
|
||||
overflow: hidden !important;
|
||||
}
|
159
frontend/src/metadata/views/map/cluster-photos/index.js
Normal file
159
frontend/src/metadata/views/map/cluster-photos/index.js
Normal file
@@ -0,0 +1,159 @@
|
||||
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,50 +0,0 @@
|
||||
import { mediaUrl } from '../../../utils/constants';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
|
||||
export function createBMapGeolocationControl(BMap, callback) {
|
||||
function GeolocationControl() {
|
||||
this.defaultAnchor = window.BMAP_ANCHOR_BOTTOM_RIGHT;
|
||||
this.defaultOffset = new BMap.Size(10, Utils.isDesktop() ? 20 : 90);
|
||||
}
|
||||
GeolocationControl.prototype = new window.BMap.Control();
|
||||
GeolocationControl.prototype.initialize = function (map) {
|
||||
const div = document.createElement('div');
|
||||
div.className = 'sf-BMap-geolocation-control';
|
||||
div.style = 'display: flex; justify-content: center; align-items: center;';
|
||||
|
||||
const icon = document.createElement('img');
|
||||
icon.className = 'sf-BMap-icon-current-location';
|
||||
icon.src = `${mediaUrl}/img/current-location.svg`;
|
||||
icon.style = 'width: 16px; height: 16px; display: block;';
|
||||
div.appendChild(icon);
|
||||
if (Utils.isDesktop()) {
|
||||
setNodeStyle(div, 'height: 30px; width: 30px; line-height: 30px');
|
||||
} else {
|
||||
setNodeStyle(div, 'height: 35px; width: 35px; line-height: 35px; opacity: 0.75');
|
||||
}
|
||||
div.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const geolocation = new BMap.Geolocation();
|
||||
div.className = 'sf-BMap-geolocation-control sf-BMap-geolocation-control-loading';
|
||||
geolocation.getCurrentPosition((result) => {
|
||||
div.className = 'sf-BMap-geolocation-control';
|
||||
if (result) {
|
||||
const point = result.point;
|
||||
map.setCenter(point);
|
||||
callback(null, point);
|
||||
} else {
|
||||
// Positioning failed
|
||||
callback(true);
|
||||
}
|
||||
});
|
||||
};
|
||||
map.getContainer().appendChild(div);
|
||||
return div;
|
||||
};
|
||||
|
||||
return GeolocationControl;
|
||||
}
|
||||
|
||||
function setNodeStyle(dom, styleText) {
|
||||
dom.style.cssText += styleText;
|
||||
}
|
@@ -1,85 +1,6 @@
|
||||
.sf-metadata-view-map {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-metadata-map-container {
|
||||
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%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .custom-image-number {
|
||||
position: absolute;
|
||||
right: -15px;
|
||||
top: -8px;
|
||||
padding: 0 12px;
|
||||
background: #20AD7E;
|
||||
color: #fff;
|
||||
border-radius: 10px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .custom-image-container .plugin-label-arrow {
|
||||
position: absolute;
|
||||
bottom: 5px;
|
||||
transform: translate( -50%, 100%);
|
||||
left: 50%;
|
||||
color: #fff;
|
||||
display: inline-block;
|
||||
line-height: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
.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::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;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-geolocation-control {
|
||||
background-color: #ffffff;
|
||||
box-shadow: 0 0 4px rgb(0 0 0 / 12%);
|
||||
border-radius: 4px;
|
||||
text-align: center;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-geolocation-control-loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-geolocation-control:hover {
|
||||
background-color: #f5f5f5;
|
||||
}
|
||||
|
@@ -1,40 +1,26 @@
|
||||
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 { MAP_TYPE } from '../../../constants';
|
||||
import { isValidPosition } from '../../utils/validate';
|
||||
import { appAvatarURL, baiduMapKey, gettext, googleMapKey, mediaUrl, siteRoot, thumbnailSizeForGrid } from '../../../utils/constants';
|
||||
import { useMetadataView } from '../../hooks/metadata-view';
|
||||
import { getFileNameFromRecord, getFileTypeFromRecord, getImageLocationFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell';
|
||||
import ClusterPhotos from './cluster-photos';
|
||||
import MapView from './map-view';
|
||||
import { PREDEFINED_FILE_TYPE_OPTION_KEY } from '../../constants';
|
||||
import { getRecordIdFromRecord, getFileNameFromRecord, getImageLocationFromRecord, getParentDirFromRecord,
|
||||
getFileTypeFromRecord
|
||||
} from '../../utils/cell';
|
||||
import { useMetadataView } from '../../hooks/metadata-view';
|
||||
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 { gettext, siteRoot, thumbnailSizeForGrid } from '../../../utils/constants';
|
||||
import { isValidPosition } from '../../utils/validate';
|
||||
import { gcj02_to_bd09, wgs84_to_gcj02 } from '../../../utils/coord-transform';
|
||||
import { PRIVATE_FILE_TYPE } from '../../../constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
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 [showCluster, setShowCluster] = useState(false);
|
||||
const { metadata, viewID, updateCurrentPath } = useMetadataView();
|
||||
|
||||
const mapRef = useRef(null);
|
||||
const clusterRef = useRef(null);
|
||||
const batchIndexRef = useRef(0);
|
||||
const clusterRef = useRef([]);
|
||||
|
||||
const { metadata } = useMetadataView();
|
||||
|
||||
const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
|
||||
const repoID = window.sfMetadataContext.getSetting('repoID');
|
||||
|
||||
const validImages = useMemo(() => {
|
||||
const images = useMemo(() => {
|
||||
return metadata.rows
|
||||
.map(record => {
|
||||
const recordType = getFileTypeFromRecord(record);
|
||||
@@ -53,124 +39,31 @@ const Map = () => {
|
||||
return { id, src, lng: bdPosition.lng, lat: bdPosition.lat };
|
||||
})
|
||||
.filter(Boolean);
|
||||
}, [repoID, metadata]);
|
||||
}, [repoID, metadata.rows]);
|
||||
|
||||
const addMapController = useCallback(() => {
|
||||
var navigation = new window.BMap.NavigationControl();
|
||||
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(geolocationControl);
|
||||
mapRef.current.addControl(navigation);
|
||||
}, []);
|
||||
const openCluster = useCallback((clusterIds) => {
|
||||
clusterRef.current = clusterIds;
|
||||
updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}/${gettext('Location')}`);
|
||||
setShowCluster(true);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [viewID, updateCurrentPath]);
|
||||
|
||||
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 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);
|
||||
|
||||
addMapController();
|
||||
initializeUserMarker();
|
||||
initializeClusterer();
|
||||
|
||||
batchIndexRef.current = 0; // Reset batch index
|
||||
renderMarkersBatch();
|
||||
}, [addMapController, initializeClusterer, initializeUserMarker, renderMarkersBatch]);
|
||||
const closeCluster = useCallback(() => {
|
||||
clusterRef.current = [];
|
||||
updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}`);
|
||||
setShowCluster(false);
|
||||
}, [viewID, updateCurrentPath]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mapInfo.type === MAP_TYPE.B_MAP) {
|
||||
window.renderMap = renderBaiduMap;
|
||||
loadBMap(mapInfo.key).then(() => renderBaiduMap());
|
||||
return () => {
|
||||
window.renderMap = null;
|
||||
};
|
||||
}
|
||||
return;
|
||||
}, [mapInfo, renderBaiduMap]);
|
||||
updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}`);
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className="w-100 h-100 sf-metadata-view-map">
|
||||
{isLoading ? (
|
||||
<CenteredLoading />
|
||||
) : (
|
||||
<div className="sf-metadata-map-container" ref={mapRef} id="sf-metadata-map-container"></div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
if (showCluster) {
|
||||
return (<ClusterPhotos photoIds={clusterRef.current} onClose={closeCluster} />);
|
||||
}
|
||||
|
||||
return (<MapView images={images} onOpenCluster={openCluster} />);
|
||||
};
|
||||
|
||||
export default Map;
|
||||
|
@@ -0,0 +1,4 @@
|
||||
.sf-map-control-container.sf-map-geolocation-control {
|
||||
width: 40px;
|
||||
line-height: 40px;
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
import classnames from 'classnames';
|
||||
import { Utils } from '../../../../../../utils/utils';
|
||||
|
||||
import './index.css';
|
||||
|
||||
export function createBMapGeolocationControl(BMapGL, callback) {
|
||||
function GeolocationControl() {
|
||||
this.defaultAnchor = window.BMAP_ANCHOR_BOTTOM_RIGHT;
|
||||
this.defaultOffset = new BMapGL.Size(30, Utils.isDesktop() ? 30 : 90);
|
||||
}
|
||||
GeolocationControl.prototype = new BMapGL.Control();
|
||||
GeolocationControl.prototype.initialize = function (map) {
|
||||
const div = document.createElement('div');
|
||||
let className = classnames('sf-map-control-container sf-map-geolocation-control d-flex align-items-center justify-content-center', {
|
||||
'sf-map-geolocation-control-mobile': !Utils.isDesktop()
|
||||
});
|
||||
|
||||
const locationButton = document.createElement('div');
|
||||
locationButton.className = 'sf-map-control d-flex align-items-center justify-content-center';
|
||||
locationButton.innerHTML = '<i class="sf-map-control-icon sf3-font sf3-font-current-location"></i>';
|
||||
div.appendChild(locationButton);
|
||||
|
||||
div.className = className;
|
||||
div.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const geolocation = new BMapGL.Geolocation();
|
||||
div.className = classnames(className, 'sf-map-control-loading');
|
||||
geolocation.getCurrentPosition((result) => {
|
||||
div.className = className;
|
||||
if (result) {
|
||||
const point = result.point;
|
||||
callback(point);
|
||||
} else {
|
||||
// Positioning failed
|
||||
callback();
|
||||
}
|
||||
});
|
||||
};
|
||||
map.getContainer().appendChild(div);
|
||||
return div;
|
||||
};
|
||||
|
||||
return GeolocationControl;
|
||||
}
|
63
frontend/src/metadata/views/map/map-view/control/index.css
Normal file
63
frontend/src/metadata/views/map/map-view/control/index.css
Normal file
@@ -0,0 +1,63 @@
|
||||
.sf-map-control-container {
|
||||
height: 40px;
|
||||
width: fit-content;
|
||||
background-color: rgba(255, 255, 255, .9);
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
box-shadow: -2px -2px 4px 2px rgba(0, 0, 0, 0.1);
|
||||
line-height: 40px;
|
||||
}
|
||||
|
||||
.sf-map-control-container.sf-map-control-loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.sf-map-control-container.sf-map-control-container-mobile {
|
||||
height: 35px;
|
||||
line-height: 35px;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.sf-map-control-container .sf-map-control-divider {
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
position: relative;
|
||||
background-color: inherit;
|
||||
}
|
||||
|
||||
.sf-map-control-container .sf-map-control-divider::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 9px;
|
||||
height: 22px;
|
||||
width: 1px;
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.sf-map-control-container .sf-map-control {
|
||||
height: 40px;
|
||||
width: 40px;
|
||||
color: #666;
|
||||
background-color: inherit;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sf-map-control-container.sf-map-control-container-mobile .sf-map-control {
|
||||
height: 35px;
|
||||
width: 35px;
|
||||
opacity: .75;
|
||||
}
|
||||
|
||||
.sf-map-control-container .sf-map-control .sf-map-control-icon {
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.sf-map-control-container .sf-map-control:not(.disabled):hover {
|
||||
cursor: pointer;
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.sf-map-control-container .sf-map-control.disabled {
|
||||
color: #ccc;
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
import { createBMapGeolocationControl } from './geolocation-control';
|
||||
import { createBMapZoomControl } from './zoom-control';
|
||||
|
||||
import './index.css';
|
||||
|
||||
export {
|
||||
createBMapGeolocationControl,
|
||||
createBMapZoomControl
|
||||
};
|
@@ -0,0 +1,3 @@
|
||||
.sf-map-control-container.sf-map-zoom-control-container .sf-map-control {
|
||||
width: 55px;
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
import classnames from 'classnames';
|
||||
import { Utils } from '../../../../../../utils/utils';
|
||||
|
||||
import './index.css';
|
||||
|
||||
export function createBMapZoomControl(BMapGL, { maxZoom, minZoom }, callback) {
|
||||
function ZoomControl() {
|
||||
this.defaultAnchor = window.BMAP_ANCHOR_BOTTOM_RIGHT;
|
||||
this.defaultOffset = new BMapGL.Size(80, Utils.isDesktop() ? 30 : 90);
|
||||
}
|
||||
ZoomControl.prototype = new BMapGL.Control();
|
||||
ZoomControl.prototype.initialize = function (map) {
|
||||
const zoomLevel = map.getZoom();
|
||||
const div = document.createElement('div');
|
||||
div.className = classnames('sf-map-control-container sf-map-zoom-control-container d-flex align-items-center justify-content-center', {
|
||||
'sf-map-control-container-mobile': !Utils.isDesktop()
|
||||
});
|
||||
|
||||
const buttonClassName = 'sf-map-control d-flex align-items-center justify-content-center';
|
||||
const zoomInButton = document.createElement('div');
|
||||
zoomInButton.className = classnames(buttonClassName, { 'disabled': zoomLevel >= maxZoom });
|
||||
zoomInButton.innerHTML = '<i class="sf-map-control-icon sf3-font sf3-font-zoom-in"></i>';
|
||||
div.appendChild(zoomInButton);
|
||||
|
||||
const divider = document.createElement('div');
|
||||
divider.className = 'sf-map-control-divider';
|
||||
div.appendChild(divider);
|
||||
|
||||
const zoomOutButton = document.createElement('div');
|
||||
zoomOutButton.className = classnames(buttonClassName, { 'disabled': zoomLevel <= minZoom });
|
||||
zoomOutButton.innerHTML = '<i class="sf-map-control-icon sf3-font sf3-font-zoom-out"></i>';
|
||||
div.appendChild(zoomOutButton);
|
||||
|
||||
const updateButtonStates = () => {
|
||||
const zoomLevel = map.getZoom();
|
||||
zoomInButton.className = classnames(buttonClassName, { 'disabled': zoomLevel >= maxZoom });
|
||||
zoomOutButton.className = classnames(buttonClassName, { 'disabled': zoomLevel <= minZoom });
|
||||
callback && callback(zoomLevel);
|
||||
};
|
||||
|
||||
zoomInButton.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const nextZoom = map.getZoom() + 1;
|
||||
map.zoomTo(Math.min(nextZoom, maxZoom));
|
||||
};
|
||||
|
||||
zoomOutButton.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const nextZoom = map.getZoom() - 1;
|
||||
map.zoomTo(Math.max(nextZoom, minZoom));
|
||||
};
|
||||
|
||||
map.addEventListener('zoomend', updateButtonStates);
|
||||
map.getContainer().appendChild(div);
|
||||
return div;
|
||||
};
|
||||
|
||||
return ZoomControl;
|
||||
}
|
82
frontend/src/metadata/views/map/map-view/index.css
Normal file
82
frontend/src/metadata/views/map/map-view/index.css
Normal file
@@ -0,0 +1,82 @@
|
||||
.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);
|
||||
}
|
184
frontend/src/metadata/views/map/map-view/index.js
Normal file
184
frontend/src/metadata/views/map/map-view/index.js
Normal file
@@ -0,0 +1,184 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef } 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 './index.css';
|
||||
|
||||
const DEFAULT_POSITION = { lng: 104.195, lat: 35.861 };
|
||||
const DEFAULT_ZOOM = 4;
|
||||
const BATCH_SIZE = 500;
|
||||
const MAX_ZOOM = 21;
|
||||
const MIN_ZOOM = 3;
|
||||
|
||||
const MapView = ({ images, onOpenCluster }) => {
|
||||
const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
|
||||
|
||||
const mapRef = useRef(null);
|
||||
const clusterRef = useRef(null);
|
||||
const batchIndexRef = useRef(0);
|
||||
|
||||
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 onClickMarker = useCallback((e, markers) => {
|
||||
saveMapState();
|
||||
const imageIds = markers.map(marker => marker._id);
|
||||
onOpenCluster(imageIds);
|
||||
}, [onOpenCluster, saveMapState]);
|
||||
|
||||
const renderMarkersBatch = useCallback(() => {
|
||||
if (!images.length || !clusterRef.current) return;
|
||||
|
||||
const startIndex = batchIndexRef.current * BATCH_SIZE;
|
||||
const endIndex = Math.min(startIndex + BATCH_SIZE, images.length);
|
||||
const batchMarkers = [];
|
||||
|
||||
for (let i = startIndex; i < endIndex; i++) {
|
||||
const image = images[i];
|
||||
const { lng, lat } = image;
|
||||
const point = new window.BMapGL.Point(lng, lat);
|
||||
const marker = customImageOverlay(point, image, {
|
||||
callback: (e, markers) => onClickMarker(e, markers)
|
||||
});
|
||||
batchMarkers.push(marker);
|
||||
}
|
||||
clusterRef.current.addMarkers(batchMarkers);
|
||||
|
||||
if (endIndex < images.length) {
|
||||
batchIndexRef.current += 1;
|
||||
setTimeout(renderMarkersBatch, 20); // Schedule the next batch
|
||||
}
|
||||
}, [images, onClickMarker]);
|
||||
|
||||
const initializeCluster = useCallback(() => {
|
||||
if (mapRef.current && !clusterRef.current) {
|
||||
clusterRef.current = new window.BMapLib.MarkerCluster(mapRef.current, {
|
||||
callback: (e, markers) => onClickMarker(e, markers),
|
||||
maxZoom: 21,
|
||||
});
|
||||
}
|
||||
}, [onClickMarker]);
|
||||
|
||||
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;
|
||||
renderMarkersBatch();
|
||||
}, [addMapController, initializeCluster, initializeUserMarker, renderMarkersBatch, getBMapType, loadMapState]);
|
||||
|
||||
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>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
MapView.propTypes = {
|
||||
images: PropTypes.array,
|
||||
onOpenCluster: PropTypes.func,
|
||||
};
|
||||
|
||||
export default MapView;
|
@@ -1,5 +1,5 @@
|
||||
const customAvatarOverlay = (point, avatarUrl, bgUrl, width = 20, height = 25) => {
|
||||
class AvatarOverlay extends window.BMap.Overlay {
|
||||
class AvatarOverlay extends window.BMapGL.Overlay {
|
||||
constructor(point, avatarUrl, bgUrl, width, height) {
|
||||
super();
|
||||
this._point = point;
|
||||
@@ -13,6 +13,7 @@ const customAvatarOverlay = (point, avatarUrl, bgUrl, width = 20, height = 25) =
|
||||
this._map = map;
|
||||
const divBox = document.createElement('div');
|
||||
const divImg = new Image();
|
||||
divBox.className = 'custom-avatar-overlay';
|
||||
divBox.style.position = 'absolute';
|
||||
divBox.style.width = `${this._width}px`;
|
||||
divBox.style.height = `${this._height}px`;
|
@@ -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._URL = image.src;
|
||||
this._id = 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._URL} />`;
|
||||
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>
|
||||
${this._URL ? imageElement : '<div class="empty-custom-image-wrapper"></div>'}
|
||||
</div>
|
||||
`;
|
||||
const labelDocument = new DOMParser().parseFromString(htmlString, 'text/html');
|
||||
@@ -31,12 +30,30 @@ const customImageOverlay = (center, imageUrl) => {
|
||||
this._div.append(label);
|
||||
|
||||
const eventHandler = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this._callback && this._callback(event, [{ _id: this._id }]);
|
||||
};
|
||||
|
||||
if (Utils.isDesktop()) {
|
||||
this._div.addEventListener('click', eventHandler);
|
||||
let clickTimeout;
|
||||
this._div.addEventListener('click', (event) => {
|
||||
if (clickTimeout) {
|
||||
clearTimeout(clickTimeout);
|
||||
clickTimeout = null;
|
||||
return;
|
||||
}
|
||||
clickTimeout = setTimeout(() => {
|
||||
eventHandler(event);
|
||||
clickTimeout = null;
|
||||
}, 300);
|
||||
});
|
||||
this._div.addEventListener('dblclick', (e) => {
|
||||
e.preventDefault();
|
||||
if (clickTimeout) {
|
||||
clearTimeout(clickTimeout);
|
||||
clickTimeout = null;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
this._div.addEventListener('touchend', eventHandler);
|
||||
}
|
||||
@@ -51,7 +68,7 @@ const customImageOverlay = (center, imageUrl) => {
|
||||
}
|
||||
|
||||
getImageUrl() {
|
||||
return imageUrl || '';
|
||||
return image.src || '';
|
||||
}
|
||||
|
||||
getPosition() {
|
||||
@@ -63,7 +80,7 @@ const customImageOverlay = (center, imageUrl) => {
|
||||
}
|
||||
}
|
||||
|
||||
return new ImageOverlay(center, imageUrl);
|
||||
return new ImageOverlay(center, image, callback);
|
||||
};
|
||||
|
||||
export default customImageOverlay;
|
@@ -0,0 +1,7 @@
|
||||
import customAvatarOverlay from './custom-avatar-overlay';
|
||||
import customImageOverlay from './custom-image-overlay';
|
||||
|
||||
export {
|
||||
customAvatarOverlay,
|
||||
customImageOverlay
|
||||
};
|
@@ -2,8 +2,7 @@ import dayjs from 'dayjs';
|
||||
import { getCellValueByColumn, getFileNameFromRecord, getRecordIdFromRecord, isCellValueChanged } from '../../../utils/cell';
|
||||
import { getColumnByIndex, getColumnOriginName } from '../../../utils/column';
|
||||
import { CellType, NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES, PRIVATE_COLUMN_KEY, TRANSFER_TYPES,
|
||||
REG_NUMBER_DIGIT, REG_STRING_NUMBER_PARTS, COLUMN_RATE_MAX_NUMBER,
|
||||
PASTE_SOURCE,
|
||||
REG_NUMBER_DIGIT, REG_STRING_NUMBER_PARTS, RATE_MAX_NUMBER, PASTE_SOURCE,
|
||||
} from '../../../constants';
|
||||
import { getGroupRecordByIndex } from './group-metrics';
|
||||
import { convertCellValue } from './convert-utils';
|
||||
@@ -594,7 +593,7 @@ class GridUtils {
|
||||
}
|
||||
|
||||
_getRatingLeastSquares(numberList, data) {
|
||||
const { rate_max_number = COLUMN_RATE_MAX_NUMBER[4].name } = data || {};
|
||||
const { rate_max_number = RATE_MAX_NUMBER[4].name } = data || {};
|
||||
let slope;
|
||||
let intercept;
|
||||
let xAverage;
|
||||
|
@@ -2150,6 +2150,7 @@ class LibContentView extends React.Component {
|
||||
};
|
||||
|
||||
updatePath = (path) => {
|
||||
if (this.state.path === path) return;
|
||||
this.setState({ path });
|
||||
};
|
||||
|
||||
|
@@ -1,6 +1,8 @@
|
||||
import { MAP_TYPE } from '../constants';
|
||||
import { mediaUrl } from './constants';
|
||||
|
||||
const STATIC_RESOURCE_VERSION = 0.1;
|
||||
|
||||
export const initMapInfo = ({ baiduMapKey, googleMapKey, mineMapKey }) => {
|
||||
if (baiduMapKey) return { type: MAP_TYPE.B_MAP, key: baiduMapKey };
|
||||
if (googleMapKey) return { type: MAP_TYPE.G_MAP, key: googleMapKey };
|
||||
@@ -30,8 +32,8 @@ export const loadMapSource = (type, key, callback) => {
|
||||
export default function loadBMap(ak) {
|
||||
return new Promise((resolve, reject) => {
|
||||
asyncLoadBaiduJs(ak)
|
||||
.then(() => asyncLoadJs(`${mediaUrl}/js/map/text-icon-overlay.js`))
|
||||
.then(() => asyncLoadJs(`${mediaUrl}/js/map/marker-clusterer.js`))
|
||||
.then(() => asyncLoadJs(`${mediaUrl}/js/map/text-icon-overlay.js?v=${STATIC_RESOURCE_VERSION}`))
|
||||
.then(() => asyncLoadJs(`${mediaUrl}/js/map/marker-cluster.js?v=${STATIC_RESOURCE_VERSION}`))
|
||||
.then(() => resolve(true))
|
||||
.catch((err) => reject(err));
|
||||
});
|
||||
@@ -39,16 +41,16 @@ export default function loadBMap(ak) {
|
||||
|
||||
export function asyncLoadBaiduJs(ak) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof window.BMap !== 'undefined') {
|
||||
resolve(window.BMap);
|
||||
if (typeof window.BMapGL !== 'undefined') {
|
||||
resolve(window.BMapGL);
|
||||
return;
|
||||
}
|
||||
window.renderMap = function () {
|
||||
resolve(window.BMap);
|
||||
resolve(window.BMapGL);
|
||||
};
|
||||
let script = document.createElement('script');
|
||||
script.type = 'text/javascript';
|
||||
script.src = `https://api.map.baidu.com/api?v=3.0&ak=${ak}&callback=renderMap`;
|
||||
script.src = `https://api.map.baidu.com/api?type=webgl&v=1.0&ak=${ak}&callback=renderMap`;
|
||||
script.onerror = reject;
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
|
@@ -1,11 +1,11 @@
|
||||
@font-face {
|
||||
font-family: "sf3-font"; /* Project id 1230969 */
|
||||
src: url('./iconfont.eot?t=1733301127109'); /* IE9 */
|
||||
src: url('./iconfont.eot?t=1733301127109#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('./iconfont.woff2?t=1733301127109') format('woff2'),
|
||||
url('./iconfont.woff?t=1733301127109') format('woff'),
|
||||
url('./iconfont.ttf?t=1733301127109') format('truetype'),
|
||||
url('./iconfont.svg?t=1733301127109#sf3-font') format('svg');
|
||||
src: url('./iconfont.eot?t=1736476800596'); /* IE9 */
|
||||
src: url('./iconfont.eot?t=1736476800596#iefix') format('embedded-opentype'), /* IE6-IE8 */
|
||||
url('./iconfont.woff2?t=1736476800596') format('woff2'),
|
||||
url('./iconfont.woff?t=1736476800596') format('woff'),
|
||||
url('./iconfont.ttf?t=1736476800596') format('truetype'),
|
||||
url('./iconfont.svg?t=1736476800596#sf3-font') format('svg');
|
||||
}
|
||||
|
||||
.sf3-font {
|
||||
@@ -16,6 +16,30 @@
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.sf3-font-zoom-out:before {
|
||||
content: "\e630";
|
||||
}
|
||||
|
||||
.sf3-font-current-location:before {
|
||||
content: "\e62e";
|
||||
}
|
||||
|
||||
.sf3-font-zoom-in:before {
|
||||
content: "\e62f";
|
||||
}
|
||||
|
||||
.sf3-font-ai:before {
|
||||
content: "\e854";
|
||||
}
|
||||
|
||||
.sf3-font-time:before {
|
||||
content: "\e852";
|
||||
}
|
||||
|
||||
.sf3-font-description:before {
|
||||
content: "\e853";
|
||||
}
|
||||
|
||||
.sf3-font-hi:before {
|
||||
content: "\e603";
|
||||
}
|
||||
@@ -24,10 +48,6 @@
|
||||
content: "\e851";
|
||||
}
|
||||
|
||||
.sf3-font-current-location:before {
|
||||
content: "\e850";
|
||||
}
|
||||
|
||||
.sf3-font-ai_generated:before {
|
||||
content: "\e84f";
|
||||
}
|
||||
|
Binary file not shown.
@@ -14,12 +14,22 @@
|
||||
/>
|
||||
<missing-glyph />
|
||||
|
||||
<glyph glyph-name="zoom-out" unicode="" d="M0 440.888889m56.888889 0l910.222222 0q56.888889 0 56.888889-56.888889l0 0q0-56.888889-56.888889-56.888889l-910.222222 0q-56.888889 0-56.888889 56.888889l0 0q0 56.888889 56.888889 56.888889Z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="current-location" unicode="" d="M512 896c282.7776 0 512-229.2224 512-512s-229.2224-512-512-512S0 101.2224 0 384 229.2224 896 512 896z m51.2512-105.5744L563.2 691.2a51.2 51.2 0 0 0-102.4 0V790.4256a409.8048 409.8048 0 0 1-355.2256-355.328L107.52 435.2h102.4a51.2 51.2 0 1 0 0-102.4h-102.4l-1.9456 0.0512A409.8048 409.8048 0 0 1 460.8-22.4768V76.8a51.2 51.2 0 0 0 102.4 0l0.0512-99.2256a409.8048 409.8048 0 0 1 355.1744 355.1744L824.32 332.8a51.2 51.2 0 0 0 0 102.4h94.1056a409.8048 409.8048 0 0 1-355.1744 355.2256zM384 512m128 0l0 0q128 0 128-128l0 0q0-128-128-128l0 0q-128 0-128 128l0 0q0 128 128 128Z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="zoom-in" unicode="" d="M19.132632 448.512m53.894736 0l862.31579 0q53.894737 0 53.894737-53.894737l0 0q0-53.894737-53.894737-53.894737l-862.31579 0q-53.894737 0-53.894736 53.894737l0 0q0 53.894737 53.894736 53.894737ZM450.290526-36.540632V825.775158a53.894737 53.894737 0 0 0 107.789474 0v-862.31579a53.894737 53.894737 0 1 0-107.789474 0z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="ai" unicode="" d="M515.2 896C796.8 896 1024 678.4 1024 416s-227.2-480-508.8-480h-19.2c-12.8 0-118.4-6.4-156.8-19.2-32-6.4-89.6-28.8-124.8-38.4-3.2-3.2-16-6.4-22.4-6.4-16 0-28.8 12.8-32 32-6.4 35.2-6.4 96 12.8 150.4C67.2 144 0 268.8 0 416 3.2 678.4 230.4 896 515.2 896zM512 800C281.6 800 96 620.8 96 403.2c0-112 54.4-211.2 144-291.2l38.4-35.2c-12.8-25.6-22.4-41.6-25.6-54.4-6.4-16-9.6-41.6-6.4-54.4 25.6 12.8 64 32 92.8 38.4 54.4 12.8 134.4 19.2 172.8 19.2 182.4 0 416 160 416 377.6S742.4 800 512 800z m-92.8-124.8C329.6 675.2 256 604.8 256 518.4v-320c0-3.2 3.2-9.6 9.6-9.6h89.6V515.2c0 32 28.8 60.8 60.8 60.8s60.8-28.8 60.8-60.8v-73.6H384c-3.2 0-9.6 0-12.8-3.2-3.2-3.2-3.2-9.6-3.2-12.8v-60.8c0-12.8 9.6-19.2 19.2-19.2H480v-156.8h89.6c3.2 0 9.6 3.2 9.6 9.6v320c0 86.4-70.4 156.8-160 156.8z m297.6-131.2h-57.6c-12.8 0-19.2-6.4-19.2-19.2v-313.6c0-12.8 6.4-19.2 19.2-19.2h57.6c12.8 0 19.2 6.4 19.2 19.2v313.6c0 12.8-6.4 19.2-19.2 19.2z m-28.8 32c25.6 0 48 22.4 48 48S713.6 672 688 672 640 649.6 640 624s22.4-48 48-48z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="time" unicode="" d="M896 384c0 211.2-172.8 384-384 384S128 595.2 128 384s172.8-384 384-384 384 172.8 384 384m96 0c0-262.4-217.6-480-480-480S32 121.6 32 384 249.6 864 512 864s480-217.6 480-480m-192-48c0-25.6-22.4-48-48-48h-192c-54.4 0-96 41.6-96 96V624c0 25.6 22.4 48 48 48s48-22.4 48-48v-192c0-25.6 22.4-48 48-48h144c25.6 0 48-22.4 48-48" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="description" unicode="" d="M800 809.6l134.4-134.4c32-32 32-86.4 0-115.2L332.8-44.8 64-64l16 265.6 604.8 608c32 28.8 86.4 28.8 115.2 0z m-204.8-230.4L179.2 160l-6.4-118.4 118.4 9.6 416 416-112 112z m150.4 150.4l-80-80 112-112 80 80-112 112z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="hi" unicode="" d="M512 864c262.4 0 480-214.4 480-480 0-96-28.8-188.8-76.8-262.4l64-150.4c3.2-9.6 0-19.2-9.6-22.4-3.2 0-6.4-3.2-9.6 0l-172.8 44.8c-80-54.4-172.8-83.2-272-83.2C249.6-96 32 121.6 32 384S249.6 864 512 864zM512 528v-131.2H390.4V528h-64v-326.4h64V336H512v-134.4h60.8V528H512z m147.2-67.2c22.4 0 38.4 16 38.4 38.4 0 19.2-16 38.4-38.4 38.4-19.2 0-38.4-19.2-38.4-38.4 3.2-19.2 22.4-38.4 38.4-38.4z m-25.6-259.2h57.6V435.2h-57.6v-233.6z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="refresh" unicode="" d="M512 864c163.2 0 304-76.8 390.4-201.6V742.4c0 25.6 22.4 44.8 44.8 44.8S992 768 992 742.4v-179.2c0-25.6-22.4-44.8-44.8-44.8h-195.2c-16 0-32 9.6-38.4 22.4-9.6 16-9.6 35.2 0 48 6.4 12.8 22.4 19.2 38.4 19.2h76.8c-70.4 99.2-185.6 166.4-316.8 166.4C294.4 774.4 121.6 601.6 121.6 384S294.4-6.4 512-6.4c182.4 0 332.8 121.6 377.6 291.2 6.4 19.2 19.2 32 41.6 32 12.8 0 25.6-3.2 38.4-16 16-16 12.8-32 9.6-51.2C921.6 48 729.6-96 512-96 249.6-96 32 121.6 32 384S249.6 864 512 864z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="current-location" unicode="" d="M512 864c265.6 0 480-214.4 480-480s-214.4-480-480-480S32 118.4 32 384 246.4 864 512 864z m48-118.4v-96c0-25.6-22.4-48-48-48s-48 22.4-48 48v96c-160-22.4-288-150.4-310.4-310.4h96c25.6 0 48-22.4 48-48s-22.4-48-48-48h-96c22.4-163.2 150.4-291.2 310.4-313.6v99.2c0 25.6 22.4 48 48 48s48-22.4 48-48v-99.2c163.2 22.4 291.2 150.4 313.6 313.6h-99.2c-25.6 0-48 22.4-48 48s22.4 48 48 48h99.2c-22.4 160-153.6 288-313.6 310.4zM512 512c70.4 0 128-57.6 128-128s-57.6-128-128-128-128 57.6-128 128 57.6 128 128 128z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="ai_generated" unicode="" d="M486.4 896C214.4 883.2 0 659.2 0 384c0-115.2 35.2-220.8 105.6-313.6L6.4-105.6c-3.2-3.2-3.2-9.6 0-12.8 3.2-6.4 9.6-9.6 12.8-9.6H512c275.2 0 499.2 214.4 512 486.4 6.4 144-44.8 278.4-140.8 380.8S656 896 512 896h-25.6z m-121.6-288h102.4L640 160h-102.4l-41.6 102.4h-160L294.4 160H192l172.8 448z m336 0H800v-448h-99.2V608zM416 499.2l-60.8-156.8h118.4L416 499.2z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="map" unicode="" d="M976 646.4c9.6-6.4 16-19.2 16-32v-563.2c0-16-9.6-32-25.6-38.4l-281.6-105.6c-9.6-3.2-19.2-3.2-25.6 0L352 9.6l-265.6-102.4c-12.8-3.2-25.6-3.2-38.4 3.2-9.6 6.4-16 19.2-16 32V505.6c0 16 9.6 32 25.6 38.4l128 48c-6.4-35.2-6.4-73.6 0-108.8l-64-28.8v-428.8l179.2 76.8v172.8l89.6-112v-86.4l240-80v166.4l89.6 112v-256l179.2 70.4V531.2l-60.8-25.6c3.2 35.2 3.2 70.4-6.4 105.6l102.4 38.4c16 6.4 28.8 3.2 41.6-3.2zM297.6 777.6C416 892.8 604.8 892.8 723.2 777.6c105.6-99.2 118.4-256 35.2-374.4l-198.4-246.4-9.6-9.6c-28.8-22.4-67.2-19.2-89.6 9.6l-195.2 243.2c-83.2 118.4-67.2 275.2 32 377.6z m355.2-67.2c-76.8 70.4-204.8 70.4-281.6 0-70.4-64-76.8-163.2-22.4-236.8l131.2-176c16-22.4 48-22.4 64 0l131.2 176c54.4 73.6 48 172.8-22.4 236.8zM512 704c70.4 0 128-57.6 128-128 0-44.8-25.6-86.4-64-112s-89.6-22.4-128 0-64 64-64 112c0 70.4 57.6 128 128 128z m0-96c-19.2 0-32-12.8-32-32s12.8-32 32-32 32 12.8 32 32-12.8 32-32 32z" horiz-adv-x="1024" />
|
||||
@@ -188,7 +198,7 @@
|
||||
|
||||
<glyph glyph-name="ai-search" unicode="" d="M448.099844 896c246.015601 0 447.301092-201.285491 447.301092-447.301092 0-95.850234-28.75507-182.115445-79.875195-255.600624l182.115445-182.115445c31.950078-31.950078 31.950078-83.070203 0-115.02028s-83.070203-31.950078-115.020281 0L703.700468 78.078003c-73.485179-51.120125-159.75039-79.875195-255.600624-79.875195-246.015601 0-447.301092 201.285491-447.301092 447.301092C0.798752 694.714509 202.084243 896 448.099844 896z m0-127.800312C272.374415 768.199688 128.599064 624.424337 128.599064 448.698908s143.775351-319.50078 319.50078-319.50078 319.50078 143.775351 319.50078 319.50078S623.825273 768.199688 448.099844 768.199688zM396.979719 640.399376c15.975039 0 28.75507-9.585023 35.145086-25.560062l99.045242-309.915757c6.390016-22.365055-9.585023-47.925117-35.145086-47.925117h-3.195008c-15.975039 0-31.950078 9.585023-35.145086 25.560062l-19.170046 70.290172H317.104524l-19.170047-70.290172c-6.390016-15.975039-19.170047-25.560062-38.340093-25.560062-25.560062 0-41.535101 25.560062-35.145086 47.925117l99.045242 309.915757c6.390016 15.975039 22.365055 25.560062 38.340093 25.560062h35.145086z m-19.170047-57.51014c-12.780031-41.535101-22.365055-86.265211-35.145085-124.605305l-12.780032-41.535101h92.655227l-12.780032 41.535101c-9.585023 38.340094-19.170047 83.070203-31.950078 124.605305zM636.605304 640.399376c19.170047 0 35.145086-15.975039 35.145086-35.145086v-309.915756c0-19.170047-15.975039-35.145086-35.145086-35.145086h-3.195008c-19.170047 0-35.145086 15.975039-35.145085 35.145086V605.25429c0 19.170047 15.975039 35.145086 35.145085 35.145086h3.195008z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="search" unicode="" d="M448.099844 896c246.015601 0 447.301092-201.285491 447.301092-447.301092 0-95.850234-28.75507-182.115445-79.875195-255.600624l182.115445-182.115445c31.950078-31.950078 31.950078-83.070203 0-115.02028s-83.070203-31.950078-115.020281 0L703.700468 78.078003c-73.485179-51.120125-159.75039-79.875195-255.600624-79.875195-246.015601 0-447.301092 201.285491-447.301092 447.301092C0.798752 694.714509 202.084243 896 448.099844 896z m0-127.800312C272.374415 768.199688 128.599064 624.424337 128.599064 448.698908s143.775351-319.50078 319.50078-319.50078 319.50078 143.775351 319.50078 319.50078S623.825273 768.199688 448.099844 768.199688z" horiz-adv-x="1024" />
|
||||
<glyph glyph-name="search" unicode="" d="M448 864c230.4 0 416-185.6 416-416 0-99.2-35.2-188.8-89.6-259.2l204.8-204.8c19.2-19.2 19.2-48 0-67.2-19.2-19.2-48-19.2-67.2 0l-204.8 204.8C636.8 64 547.2 32 448 32 217.6 32 32 217.6 32 448S217.6 864 448 864z m0-96C272 768 128 624 128 448s144-320 320-320 320 144 320 320S624 768 448 768z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="set-up" unicode="" d="M512 576c105.6 0 192-86.4 192-192s-86.4-192-192-192-192 86.4-192 192 86.4 192 192 192z m0-96c-54.4 0-96-41.6-96-96s41.6-96 96-96 96 41.6 96 96-41.6 96-96 96zM432 835.2c-9.6 19.2-32 28.8-54.4 22.4-83.2-19.2-153.6-60.8-214.4-112-12.8-16-16-35.2-9.6-54.4 6.4-12.8 9.6-25.6 9.6-41.6 0-51.2-44.8-92.8-92.8-92.8-19.2 0-38.4-12.8-44.8-32-19.2-41.6-25.6-92.8-25.6-144 0-35.2 3.2-67.2 9.6-99.2 3.2-22.4 25.6-38.4 51.2-35.2h9.6c54.4 0 92.8-41.6 92.8-92.8 0-22.4-9.6-41.6-22.4-57.6-16-16-12.8-41.6 3.2-60.8 60.8-60.8 137.6-105.6 224-131.2 22.4-6.4 51.2 6.4 57.6 28.8 12.8 38.4 48 64 89.6 64s76.8-25.6 89.6-64c6.4-22.4 32-35.2 57.6-28.8 86.4 25.6 163.2 70.4 224 131.2 16 16 16 41.6 3.2 60.8-12.8 16-22.4 35.2-22.4 57.6 0 51.2 44.8 92.8 92.8 92.8h9.6c22.4-3.2 48 12.8 51.2 35.2 6.4 32 9.6 67.2 9.6 99.2 0 51.2-6.4 102.4-22.4 150.4-6.4 19.2-22.4 32-44.8 32-54.4 0-92.8 41.6-92.8 92.8 0 16 3.2 28.8 9.6 41.6 3.2 16-3.2 38.4-16 51.2-60.8 54.4-134.4 92.8-211.2 115.2-19.2 0-44.8-9.6-54.4-28.8-16-28.8-48-51.2-83.2-51.2s-70.4 22.4-83.2 51.2zM262.4 656c0 16-3.2 32-6.4 44.8 35.2 28.8 76.8 48 118.4 67.2 32-41.6 83.2-67.2 140.8-67.2 57.6 0 108.8 25.6 140.8 67.2 44.8-12.8 83.2-38.4 118.4-67.2-3.2-12.8-6.4-32-6.4-44.8 0-86.4 64-166.4 150.4-179.2 6.4-32 9.6-64 9.6-96 0-16 0-32-3.2-48-89.6-9.6-160-86.4-160-179.2 0-28.8 6.4-57.6 19.2-80-35.2-32-76.8-57.6-118.4-73.6-32 51.2-89.6 83.2-153.6 83.2s-121.6-35.2-153.6-83.2c-44.8 16-83.2 41.6-118.4 73.6 6.4 25.6 16 51.2 16 80 0 89.6-67.2 169.6-160 179.2 3.2 12.8 3.2 32 3.2 44.8 0 32 3.2 64 9.6 96 89.6 16 153.6 89.6 153.6 182.4z" horiz-adv-x="1024" />
|
||||
|
||||
@@ -224,7 +234,7 @@
|
||||
|
||||
<glyph glyph-name="helpful" unicode="" d="M988.8 496c-32 44.8-83.2 67.2-137.6 70.4l-150.4 3.2c12.8 51.2 16 99.2 12.8 140.8-6.4 48-19.2 89.6-44.8 121.6-16 22.4-35.2 38.4-60.8 48-16 9.6-35.2 16-57.6 16-83.2 0-147.2-64-147.2-140.8 0-124.8-80-227.2-176-227.2H86.4c-48 0-86.4-38.4-86.4-89.6v-480C0-89.6 38.4-128 86.4-128h678.4c41.6 0 80 12.8 112 38.4 32 25.6 54.4 60.8 64 102.4L1020.8 352c9.6 51.2 0 102.4-32 144zM185.6-44.8h-96-3.2V438.4c0 3.2 3.2 3.2 3.2 3.2h96v-486.4z m755.2 416L860.8 32c-6.4-22.4-16-41.6-35.2-54.4-19.2-12.8-41.6-22.4-64-22.4H265.6V448c22.4 3.2 41.6 12.8 64 22.4 32 16 57.6 38.4 83.2 67.2 48 57.6 73.6 134.4 73.6 217.6 0 32 28.8 54.4 67.2 54.4 3.2 0 32 0 54.4-28.8 32-44.8 35.2-131.2 3.2-243.2-3.2-12.8 0-25.6 6.4-35.2 6.4-9.6 19.2-16 32-16l201.6-3.2c28.8 0 57.6-12.8 76.8-38.4 12.8-19.2 16-48 12.8-73.6z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="x-01" unicode="" d="M851.2 723.2c19.2-19.2 19.2-48 0-67.2L579.2 384l272-268.8c16-16 19.2-44.8 3.2-64l-3.2-3.2c-19.2-19.2-48-19.2-67.2 0L512 316.79999999999995l-272-272c-19.2-19.2-48-19.2-67.2 0-19.2 19.2-19.2 48 0 67.2l272 268.8-272 272c-16 19.2-16 48-3.2 64l3.2 3.2c19.2 19.2 51.2 19.2 70.4 3.2l272-272 268.8 272c16 16 48 16 67.2 0z" horiz-adv-x="1024" />
|
||||
<glyph glyph-name="x-01" unicode="" d="M880 752c19.2-19.2 19.2-54.4 0-73.6L585.6 384l294.4-294.4c19.2-19.2 19.2-48 3.2-70.4l-3.2-3.2c-19.2-19.2-54.4-19.2-73.6 0L512 310.4l-294.4-294.4c-19.2-19.2-54.4-19.2-73.6 0-19.2 19.2-19.2 54.4 0 73.6l294.4 294.4-294.4 294.4c-19.2 19.2-22.4 51.2-3.2 70.4l3.2 3.2c19.2 19.2 54.4 19.2 73.6 0l294.4-294.4 294.4 294.4c22.4 22.4 54.4 22.4 73.6 0z" horiz-adv-x="1024" />
|
||||
|
||||
<glyph glyph-name="desktop" unicode="" d="M928 832c35.2 0 64-28.8 64-64v-608c0-35.2-28.8-64-64-64H604.8c3.2-25.6 9.6-44.8 16-64h121.6c32 0 57.6-25.6 57.6-57.6s-25.6-57.6-57.6-57.6H281.6c-32 0-57.6 25.6-57.6 57.6S249.6 32 281.6 32h121.6c6.4 19.2 12.8 38.4 19.2 64H96c-35.2 0-64 28.8-64 64V768c0 35.2 28.8 64 64 64h832z m-51.2-112H147.2v-486.4h729.6V720z" horiz-adv-x="1024" />
|
||||
|
||||
|
Before Width: | Height: | Size: 77 KiB After Width: | Height: | Size: 79 KiB |
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* @fileoverview MarkerClusterer标记聚合器用来解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能。
|
||||
* 主入口类是<a href="symbols/BMapLib.MarkerClusterer.html">MarkerClusterer</a>,
|
||||
* @fileoverview MarkerCluster标记聚合器用来解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能。
|
||||
* 主入口类是<a href="symbols/BMapLib.MarkerCluster.html">MarkerCluster</a>,
|
||||
* 基于Baidu Map API 1.2。
|
||||
*
|
||||
* @author Baidu Map Api Group
|
||||
@@ -15,11 +15,11 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
/**
|
||||
* 获取一个扩展的视图范围,把上下左右都扩大一样的像素值。
|
||||
* @param {Map} map BMap.Map的实例化对象
|
||||
* @param {BMap.Bounds} bounds BMap.Bounds的实例化对象
|
||||
* @param {Map} map BMapGL.Map的实例化对象
|
||||
* @param {BMapGL.Bounds} bounds BMapGL.Bounds的实例化对象
|
||||
* @param {Number} gridSize 要扩大的像素值
|
||||
*
|
||||
* @return {BMap.Bounds} 返回扩大后的视图范围。
|
||||
* @return {BMapGL.Bounds} 返回扩大后的视图范围。
|
||||
*/
|
||||
var getExtendedBounds = function(map, bounds, gridSize){
|
||||
bounds = cutBoundsInRange(bounds);
|
||||
@@ -31,21 +31,22 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
pixelSW.y += gridSize;
|
||||
var newNE = map.pixelToPoint(pixelNE);
|
||||
var newSW = map.pixelToPoint(pixelSW);
|
||||
return new BMap.Bounds(newSW, newNE);
|
||||
if (!newSW || !newNE) return null;
|
||||
return new BMapGL.Bounds(newSW, newNE);
|
||||
};
|
||||
|
||||
/**
|
||||
* 按照百度地图支持的世界范围对bounds进行边界处理
|
||||
* @param {BMap.Bounds} bounds BMap.Bounds的实例化对象
|
||||
* @param {BMapGL.Bounds} bounds BMapGL.Bounds的实例化对象
|
||||
*
|
||||
* @return {BMap.Bounds} 返回不越界的视图范围
|
||||
* @return {BMapGL.Bounds} 返回不越界的视图范围
|
||||
*/
|
||||
var cutBoundsInRange = function (bounds) {
|
||||
var maxX = getRange(bounds.getNorthEast().lng, -180, 180);
|
||||
var minX = getRange(bounds.getSouthWest().lng, -180, 180);
|
||||
var maxY = getRange(bounds.getNorthEast().lat, -74, 74);
|
||||
var minY = getRange(bounds.getSouthWest().lat, -74, 74);
|
||||
return new BMap.Bounds(new BMap.Point(minX, minY), new BMap.Point(maxX, maxY));
|
||||
var maxY = getRange(bounds.getNorthEast().lat, -90, 90);
|
||||
var minY = getRange(bounds.getSouthWest().lat, -90, 90);
|
||||
return new BMapGL.Bounds(new BMapGL.Point(minX, minY), new BMapGL.Point(maxX, maxY));
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -97,11 +98,11 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
};
|
||||
|
||||
/**
|
||||
*@exports MarkerClusterer as BMapLib.MarkerClusterer
|
||||
*@exports MarkerCluster as BMapLib.MarkerCluster
|
||||
*/
|
||||
var MarkerClusterer =
|
||||
var MarkerCluster =
|
||||
/**
|
||||
* MarkerClusterer
|
||||
* MarkerCluster
|
||||
* @class 用来解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能
|
||||
* @constructor
|
||||
* @param {Map} map 地图的一个实例。
|
||||
@@ -110,10 +111,10 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* girdSize {Number} 聚合计算时网格的像素大小,默认60<br />
|
||||
* maxZoom {Number} 最大的聚合级别,大于该级别就不进行相应的聚合<br />
|
||||
* minClusterSize {Number} 最小的聚合数量,小于该数量的不能成为一个聚合,默认为2<br />
|
||||
* isAverangeCenter {Boolean} 聚合点的落脚位置是否是所有聚合在内点的平均值,默认为否,落脚在聚合内的第一个点<br />
|
||||
* isAvgCenter {Boolean} 聚合点的落脚位置是否是所有聚合在内点的平均值,默认为否,落脚在聚合内的第一个点<br />
|
||||
* styles {Array<IconStyle>} 自定义聚合后的图标风格,请参考TextIconOverlay类<br />
|
||||
*/
|
||||
BMapLib.MarkerClusterer = function(map, options){
|
||||
BMapLib.MarkerCluster = function(map, options){
|
||||
if (!map){
|
||||
return;
|
||||
}
|
||||
@@ -123,13 +124,14 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
var opts = options || {};
|
||||
this._gridSize = opts["gridSize"] || 60;
|
||||
this._maxZoom = opts["maxZoom"] || 18;
|
||||
this._maxZoom = opts["maxZoom"] || 21;
|
||||
this._minClusterSize = opts["minClusterSize"] || 2;
|
||||
this._isAverageCenter = false;
|
||||
if (opts['isAverageCenter'] != undefined) {
|
||||
this._isAverageCenter = opts['isAverageCenter'];
|
||||
}
|
||||
this._styles = opts["styles"] || [];
|
||||
this._callback = opts["callback"] || function(){};
|
||||
|
||||
var that = this;
|
||||
this._map.addEventListener("zoomend",function(){
|
||||
@@ -140,8 +142,8 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
// that._redraw();
|
||||
// });
|
||||
|
||||
var mkrs = opts["markers"];
|
||||
isArray(mkrs) && this.addMarkers(mkrs);
|
||||
var markers = opts["markers"];
|
||||
isArray(markers) && this.addMarkers(markers);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -150,7 +152,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
*
|
||||
* @return 无返回值。
|
||||
*/
|
||||
MarkerClusterer.prototype.addMarkers = function(markers){
|
||||
MarkerCluster.prototype.addMarkers = function(markers){
|
||||
for(var i = 0, len = markers.length; i <len ; i++){
|
||||
this._pushMarkerTo(markers[i]);
|
||||
}
|
||||
@@ -159,11 +161,11 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
/**
|
||||
* 把一个标记添加到要聚合的标记数组中
|
||||
* @param {BMap.Marker} marker 要添加的标记
|
||||
* @param {BMapGL.Marker} marker 要添加的标记
|
||||
*
|
||||
* @return 无返回值。
|
||||
*/
|
||||
MarkerClusterer.prototype._pushMarkerTo = function(marker){
|
||||
MarkerCluster.prototype._pushMarkerTo = function(marker){
|
||||
var index = indexOf(marker, this._markers);
|
||||
if(index === -1){
|
||||
marker.isInCluster = false;
|
||||
@@ -173,10 +175,10 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
/**
|
||||
* 添加一个聚合的标记。
|
||||
* @param {BMap.Marker} marker 要聚合的单个标记。
|
||||
* @param {BMapGL.Marker} marker 要聚合的单个标记。
|
||||
* @return 无返回值。
|
||||
*/
|
||||
MarkerClusterer.prototype.addMarker = function(marker) {
|
||||
MarkerCluster.prototype.addMarker = function(marker) {
|
||||
this._pushMarkerTo(marker);
|
||||
this._createClusters();
|
||||
};
|
||||
@@ -185,11 +187,11 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 根据所给定的标记,创建聚合点,并且遍历所有聚合点
|
||||
* @return 无返回值
|
||||
*/
|
||||
MarkerClusterer.prototype._createClusters = function(){
|
||||
MarkerCluster.prototype._createClusters = function(){
|
||||
var mapBounds = this._map.getBounds();
|
||||
var extendedBounds = getExtendedBounds(this._map, mapBounds, this._gridSize);
|
||||
for(var i = 0, marker; marker = this._markers[i]; i++){
|
||||
if(!marker.isInCluster && extendedBounds.containsPoint(marker.getPosition()) ){
|
||||
if(!marker.isInCluster && extendedBounds && extendedBounds.containsPoint(marker._position) ){
|
||||
this._addToClosestCluster(marker);
|
||||
}
|
||||
}
|
||||
@@ -204,11 +206,11 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
/**
|
||||
* 根据标记的位置,把它添加到最近的聚合中
|
||||
* @param {BMap.Marker} marker 要进行聚合的单个标记
|
||||
* @param {BMapGL.Marker} marker 要进行聚合的单个标记
|
||||
*
|
||||
* @return 无返回值。
|
||||
*/
|
||||
MarkerClusterer.prototype._addToClosestCluster = function (marker){
|
||||
MarkerCluster.prototype._addToClosestCluster = function (marker){
|
||||
var distance = 4000000;
|
||||
var clusterToAddTo = null;
|
||||
var position = marker.getPosition();
|
||||
@@ -236,7 +238,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 清除上一次的聚合的结果
|
||||
* @return 无返回值。
|
||||
*/
|
||||
MarkerClusterer.prototype._clearLastClusters = function(){
|
||||
MarkerCluster.prototype._clearLastClusters = function(){
|
||||
for(var i = 0, cluster; cluster = this._clusters[i]; i++){
|
||||
cluster.remove();
|
||||
}
|
||||
@@ -248,7 +250,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 清除某个聚合中的所有标记
|
||||
* @return 无返回值
|
||||
*/
|
||||
MarkerClusterer.prototype._removeMarkersFromCluster = function(){
|
||||
MarkerCluster.prototype._removeMarkersFromCluster = function(){
|
||||
for(var i = 0, marker; marker = this._markers[i]; i++){
|
||||
marker.isInCluster = false;
|
||||
}
|
||||
@@ -258,7 +260,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 把所有的标记从地图上清除
|
||||
* @return 无返回值
|
||||
*/
|
||||
MarkerClusterer.prototype._removeMarkersFromMap = function(){
|
||||
MarkerCluster.prototype._removeMarkersFromMap = function(){
|
||||
for(var i = 0, marker; marker = this._markers[i]; i++){
|
||||
marker.isInCluster = false;
|
||||
this._map.removeOverlay(marker);
|
||||
@@ -267,11 +269,11 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
/**
|
||||
* 删除单个标记
|
||||
* @param {BMap.Marker} marker 需要被删除的marker
|
||||
* @param {BMapGL.Marker} marker 需要被删除的marker
|
||||
*
|
||||
* @return {Boolean} 删除成功返回true,否则返回false
|
||||
*/
|
||||
MarkerClusterer.prototype._removeMarker = function(marker) {
|
||||
MarkerCluster.prototype._removeMarker = function(marker) {
|
||||
var index = indexOf(marker, this._markers);
|
||||
if (index === -1) {
|
||||
return false;
|
||||
@@ -283,11 +285,11 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
/**
|
||||
* 删除单个标记
|
||||
* @param {BMap.Marker} marker 需要被删除的marker
|
||||
* @param {BMapGL.Marker} marker 需要被删除的marker
|
||||
*
|
||||
* @return {Boolean} 删除成功返回true,否则返回false
|
||||
*/
|
||||
MarkerClusterer.prototype.removeMarker = function(marker) {
|
||||
MarkerCluster.prototype.removeMarker = function(marker) {
|
||||
var success = this._removeMarker(marker);
|
||||
if (success) {
|
||||
this._clearLastClusters();
|
||||
@@ -298,11 +300,11 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
/**
|
||||
* 删除一组标记
|
||||
* @param {Array<BMap.Marker>} markers 需要被删除的marker数组
|
||||
* @param {Array<BMapGL.Marker>} markers 需要被删除的marker数组
|
||||
*
|
||||
* @return {Boolean} 删除成功返回true,否则返回false
|
||||
*/
|
||||
MarkerClusterer.prototype.removeMarkers = function(markers) {
|
||||
MarkerCluster.prototype.removeMarkers = function(markers) {
|
||||
var success = false;
|
||||
for (var i = 0; i < markers.length; i++) {
|
||||
var r = this._removeMarker(markers[i]);
|
||||
@@ -320,7 +322,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 从地图上彻底清除所有的标记
|
||||
* @return 无返回值
|
||||
*/
|
||||
MarkerClusterer.prototype.clearMarkers = function() {
|
||||
MarkerCluster.prototype.clearMarkers = function() {
|
||||
this._clearLastClusters();
|
||||
this._removeMarkersFromMap();
|
||||
this._markers = [];
|
||||
@@ -330,7 +332,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 重新生成,比如改变了属性等
|
||||
* @return 无返回值
|
||||
*/
|
||||
MarkerClusterer.prototype._redraw = function () {
|
||||
MarkerCluster.prototype._redraw = function () {
|
||||
this._clearLastClusters();
|
||||
this._createClusters();
|
||||
};
|
||||
@@ -339,7 +341,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 获取网格大小
|
||||
* @return {Number} 网格大小
|
||||
*/
|
||||
MarkerClusterer.prototype.getGridSize = function() {
|
||||
MarkerCluster.prototype.getGridSize = function() {
|
||||
return this._gridSize;
|
||||
};
|
||||
|
||||
@@ -348,7 +350,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* @param {Number} size 网格大小
|
||||
* @return 无返回值
|
||||
*/
|
||||
MarkerClusterer.prototype.setGridSize = function(size) {
|
||||
MarkerCluster.prototype.setGridSize = function(size) {
|
||||
this._gridSize = size;
|
||||
this._redraw();
|
||||
};
|
||||
@@ -357,7 +359,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 获取聚合的最大缩放级别。
|
||||
* @return {Number} 聚合的最大缩放级别。
|
||||
*/
|
||||
MarkerClusterer.prototype.getMaxZoom = function() {
|
||||
MarkerCluster.prototype.getMaxZoom = function() {
|
||||
return this._maxZoom;
|
||||
};
|
||||
|
||||
@@ -366,7 +368,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* @param {Number} maxZoom 聚合的最大缩放级别
|
||||
* @return 无返回值
|
||||
*/
|
||||
MarkerClusterer.prototype.setMaxZoom = function(maxZoom) {
|
||||
MarkerCluster.prototype.setMaxZoom = function(maxZoom) {
|
||||
this._maxZoom = maxZoom;
|
||||
this._redraw();
|
||||
};
|
||||
@@ -375,7 +377,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 获取聚合的样式风格集合
|
||||
* @return {Array<IconStyle>} 聚合的样式风格集合
|
||||
*/
|
||||
MarkerClusterer.prototype.getStyles = function() {
|
||||
MarkerCluster.prototype.getStyles = function() {
|
||||
return this._styles;
|
||||
};
|
||||
|
||||
@@ -384,7 +386,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* @param {Array<IconStyle>} styles 样式风格数组
|
||||
* @return 无返回值
|
||||
*/
|
||||
MarkerClusterer.prototype.setStyles = function(styles) {
|
||||
MarkerCluster.prototype.setStyles = function(styles) {
|
||||
this._styles = styles;
|
||||
this._redraw();
|
||||
};
|
||||
@@ -393,7 +395,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 获取单个聚合的最小数量。
|
||||
* @return {Number} 单个聚合的最小数量。
|
||||
*/
|
||||
MarkerClusterer.prototype.getMinClusterSize = function() {
|
||||
MarkerCluster.prototype.getMinClusterSize = function() {
|
||||
return this._minClusterSize;
|
||||
};
|
||||
|
||||
@@ -402,7 +404,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* @param {Number} size 单个聚合的最小数量。
|
||||
* @return 无返回值。
|
||||
*/
|
||||
MarkerClusterer.prototype.setMinClusterSize = function(size) {
|
||||
MarkerCluster.prototype.setMinClusterSize = function(size) {
|
||||
this._minClusterSize = size;
|
||||
this._redraw();
|
||||
};
|
||||
@@ -411,7 +413,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 获取单个聚合的落脚点是否是聚合内所有标记的平均中心。
|
||||
* @return {Boolean} true或false。
|
||||
*/
|
||||
MarkerClusterer.prototype.isAverageCenter = function() {
|
||||
MarkerCluster.prototype.isAverageCenter = function() {
|
||||
return this._isAverageCenter;
|
||||
};
|
||||
|
||||
@@ -419,7 +421,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 获取聚合的Map实例。
|
||||
* @return {Map} Map的示例。
|
||||
*/
|
||||
MarkerClusterer.prototype.getMap = function() {
|
||||
MarkerCluster.prototype.getMap = function() {
|
||||
return this._map;
|
||||
};
|
||||
|
||||
@@ -427,7 +429,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 获取所有的标记数组。
|
||||
* @return {Array<Marker>} 标记数组。
|
||||
*/
|
||||
MarkerClusterer.prototype.getMarkers = function() {
|
||||
MarkerCluster.prototype.getMarkers = function() {
|
||||
return this._markers;
|
||||
};
|
||||
|
||||
@@ -435,7 +437,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* 获取聚合的总数量。
|
||||
* @return {Number} 聚合的总数量。
|
||||
*/
|
||||
MarkerClusterer.prototype.getClustersCount = function() {
|
||||
MarkerCluster.prototype.getClustersCount = function() {
|
||||
var count = 0;
|
||||
for(var i = 0, cluster; cluster = this._clusters[i]; i++){
|
||||
cluster.isReal() && count++;
|
||||
@@ -443,24 +445,28 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
return count;
|
||||
};
|
||||
|
||||
MarkerCluster.prototype.getCallback = function() {
|
||||
return this._callback;
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
* Cluster
|
||||
* @class 表示一个聚合对象,该聚合,包含有N个标记,这N个标记组成的范围,并有予以显示在Map上的TextIconOverlay等。
|
||||
* @constructor
|
||||
* @param {MarkerClusterer} markerClusterer 一个标记聚合器示例。
|
||||
* @param {MarkerCluster} markerCluster 一个标记聚合器示例。
|
||||
*/
|
||||
function Cluster(markerClusterer){
|
||||
this._markerClusterer = markerClusterer;
|
||||
this._map = markerClusterer.getMap();
|
||||
this._minClusterSize = markerClusterer.getMinClusterSize();
|
||||
this._isAverageCenter = markerClusterer.isAverageCenter();
|
||||
function Cluster(markerCluster){
|
||||
this._markerCluster = markerCluster;
|
||||
this._map = markerCluster.getMap();
|
||||
this._minClusterSize = markerCluster.getMinClusterSize();
|
||||
this._isAverageCenter = markerCluster.isAverageCenter();
|
||||
this._center = null;//落脚位置
|
||||
this._markers = [];//这个Cluster中所包含的markers
|
||||
this._gridBounds = null;//以中心点为准,向四边扩大gridSize个像素的范围,也即网格范围
|
||||
this._isReal = false; //真的是个聚合
|
||||
|
||||
this._clusterMarker = new BMapLib.TextIconOverlay(this._center, this._markers.length, {"styles":this._markerClusterer.getStyles()});
|
||||
this._clusterMarker = new BMapLib.TextIconOverlay(this._center, this._markers.length, {"styles":this._markerCluster.getStyles()});
|
||||
//this._map.addOverlay(this._clusterMarker);
|
||||
}
|
||||
|
||||
@@ -482,7 +488,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
var l = this._markers.length + 1;
|
||||
var lat = (this._center.lat * (l - 1) + marker.getPosition().lat) / l;
|
||||
var lng = (this._center.lng * (l - 1) + marker.getPosition().lng) / l;
|
||||
this._center = new BMap.Point(lng, lat);
|
||||
this._center = new BMapGL.Point(lng, lat);
|
||||
this.updateGridBounds();
|
||||
}//计算新的Center
|
||||
}
|
||||
@@ -545,8 +551,8 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* @return 无返回值。
|
||||
*/
|
||||
Cluster.prototype.updateGridBounds = function() {
|
||||
var bounds = new BMap.Bounds(this._center, this._center);
|
||||
this._gridBounds = getExtendedBounds(this._map, bounds, this._markerClusterer.getGridSize());
|
||||
var bounds = new BMapGL.Bounds(this._center, this._center);
|
||||
this._gridBounds = getExtendedBounds(this._map, bounds, this._markerCluster.getGridSize());
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -554,7 +560,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* @return 无返回值。
|
||||
*/
|
||||
Cluster.prototype.updateClusterMarker = function () {
|
||||
if (this._map.getZoom() > this._markerClusterer.getMaxZoom()) {
|
||||
if (this._map.getZoom() > this._markerCluster.getMaxZoom()) {
|
||||
this._clusterMarker && this._map.removeOverlay(this._clusterMarker);
|
||||
for (var i = 0, marker; marker = this._markers[i]; i++) {
|
||||
this._map.addOverlay(marker);
|
||||
@@ -575,10 +581,29 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
var thatMap = this._map;
|
||||
var thatBounds = this.getBounds();
|
||||
this._clusterMarker.addEventListener("click", function(event){
|
||||
thatMap.setViewport(thatBounds);
|
||||
let clickTimeout;
|
||||
this._clusterMarker.addEventListener("click", (event) => {
|
||||
if (clickTimeout) {
|
||||
clearTimeout(clickTimeout);
|
||||
clickTimeout = null;
|
||||
return;
|
||||
}
|
||||
clickTimeout = setTimeout(() => {
|
||||
if (this._markerCluster && typeof this._markerCluster.getCallback() === 'function') {
|
||||
const markers = this._markers;
|
||||
this._markerCluster.getCallback()(event, markers);
|
||||
}
|
||||
clickTimeout = null;
|
||||
}, 300); // Delay to differentiate between single and double click
|
||||
});
|
||||
|
||||
this._clusterMarker.addEventListener("dblclick", (event) => {
|
||||
if (clickTimeout) {
|
||||
clearTimeout(clickTimeout);
|
||||
clickTimeout = null;
|
||||
}
|
||||
// Do nothing on double click
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -596,10 +621,10 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
/**
|
||||
* 获取该聚合所包含的所有标记的最小外接矩形的范围。
|
||||
* @return {BMap.Bounds} 计算出的范围。
|
||||
* @return {BMapGL.Bounds} 计算出的范围。
|
||||
*/
|
||||
Cluster.prototype.getBounds = function() {
|
||||
var bounds = new BMap.Bounds(this._center,this._center);
|
||||
var bounds = new BMapGL.Bounds(this._center, this._center);
|
||||
for (var i = 0, marker; marker = this._markers[i]; i++) {
|
||||
bounds.extend(marker.getPosition());
|
||||
}
|
||||
@@ -608,7 +633,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
/**
|
||||
* 获取该聚合的落脚点。
|
||||
* @return {BMap.Point} 该聚合的落脚点。
|
||||
* @return {BMapGL.Point} 该聚合的落脚点。
|
||||
*/
|
||||
Cluster.prototype.getCenter = function() {
|
||||
return this._center;
|
@@ -211,7 +211,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* @returns {string} 驼峰化处理后的字符串
|
||||
*/
|
||||
baidu.string.toCamelCase = function (source) {
|
||||
//提前判断,提高getStyle等的效率 thanks xianwei
|
||||
//提前判断,提高getStyle等的效率
|
||||
if (source.indexOf('-') < 0 && source.indexOf('_') < 0) {
|
||||
return source;
|
||||
}
|
||||
@@ -649,7 +649,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
* @grammar obj.dispatchEvent(event, options)
|
||||
* @param {baidu.lang.Event|String} event Event对象,或事件名称(1.1.1起支持)
|
||||
* @param {Object} options 扩展参数,所含属性键值会扩展到Event对象上(1.2起支持)
|
||||
* @remark 处理会调用通过addEventListenr绑定的自定义事件回调函数之外,还会调用直接绑定到对象上面的自定义事件。例如:<br>
|
||||
* @remark 处理会调用通过addEventListener绑定的自定义事件回调函数之外,还会调用直接绑定到对象上面的自定义事件。例如:<br>
|
||||
myobj.onMyEvent = function(){}<br>
|
||||
myobj.addEventListener("onMyEvent", function(){});
|
||||
*/
|
||||
@@ -754,27 +754,28 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
(!this._styles.length) && this._setupDefaultStyles();
|
||||
};
|
||||
|
||||
T.lang.inherits(TextIconOverlay, BMap.Overlay, "TextIconOverlay");
|
||||
T.lang.inherits(TextIconOverlay, BMapGL.Overlay, "TextIconOverlay");
|
||||
|
||||
TextIconOverlay.prototype._setupDefaultStyles = function(){
|
||||
var sizes = [53, 56, 66, 78, 90];
|
||||
for(var i = 0, size; size = sizes[i]; i++){
|
||||
this._styles.push({
|
||||
url:_IMAGE_PATH + i + '.' + _IMAGE_EXTENSION,
|
||||
size: new BMap.Size(size, size)
|
||||
size: new BMapGL.Size(size, size)
|
||||
});
|
||||
}//for循环的简洁写法
|
||||
};
|
||||
|
||||
/**
|
||||
*继承Overlay的intialize方法,自定义覆盖物时必须。
|
||||
*@param {Map} map BMap.Map的实例化对象。
|
||||
*继承Overlay的initialize方法,自定义覆盖物时必须。
|
||||
*@param {Map} map BMapGL.Map的实例化对象。
|
||||
*@return {HTMLElement} 返回覆盖物对应的HTML元素。
|
||||
*/
|
||||
TextIconOverlay.prototype.initialize = function(map){
|
||||
this._map = map;
|
||||
|
||||
this._domElement = document.createElement('div');
|
||||
this._domElement.className = 'custom-image-overlay';
|
||||
// this._updateCss();
|
||||
// this._updateText();
|
||||
this._updatePosition();
|
||||
@@ -859,21 +860,20 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
var style = this.getStyleByText(this._text, this._styles);
|
||||
var newStyle = {
|
||||
url: imageUrl,
|
||||
size: {width: 72, height: 72}
|
||||
size: { width: 86, height: 86 }
|
||||
}
|
||||
if (imageUrl) {
|
||||
style = Object.assign(style, {url: imageUrl, size: {width: 72, height: 72}})
|
||||
style = Object.assign(style, { url: imageUrl, size: { width: 86, height: 86 } })
|
||||
}
|
||||
|
||||
const customImageNumber = `<span class="custom-image-number">${this._text}</span>`;
|
||||
const customImageNumber = `<span class="custom-image-number">${this._text < 1000 ? this._text : '1k+'}</span>`;
|
||||
this._domElement.style.cssText = this.buildImageCssText(newStyle);
|
||||
const imageElement = `<img src=${imageUrl} width="72" height="72" />`
|
||||
const imageElement = `<img src=${imageUrl} width="80" height="80" />`
|
||||
const htmlString = `
|
||||
<div class="custom-image-container">
|
||||
${this._text > 1 ? customImageNumber : ''}
|
||||
${imageUrl ? imageElement : '<div class="empty-custom-image-wrapper"></div>'}
|
||||
<i class='plugin-label-arrow dtable-font dtable-icon-drop-down'></i>
|
||||
</div>
|
||||
<div class="custom-image-container">
|
||||
${this._text > 1 ? customImageNumber : ''}
|
||||
${imageUrl ? imageElement : '<div class="empty-custom-image-wrapper"></div>'}
|
||||
</div>
|
||||
`
|
||||
const labelDocument = new DOMParser().parseFromString(htmlString, 'text/html');
|
||||
const label = labelDocument.body.firstElementChild;
|
||||
@@ -887,14 +887,14 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
var textColor = style['textColor'] || 'black';
|
||||
var textSize = style['textSize'] || 10;
|
||||
|
||||
var csstext = [];
|
||||
var cssText = [];
|
||||
|
||||
csstext.push('height:' + size.height + 'px; line-height:' + size.height + 'px;');
|
||||
csstext.push('width:' + size.width + 'px; text-align:center;');
|
||||
cssText.push('height:' + size.height + 'px; line-height:' + size.height + 'px;');
|
||||
cssText.push('width:' + size.width + 'px; text-align:center;');
|
||||
|
||||
csstext.push('cursor:pointer; color:' + textColor + '; position:absolute; font-size:' +
|
||||
cssText.push('cursor:pointer; color:' + textColor + '; position:absolute; font-size:' +
|
||||
textSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
|
||||
return csstext.join('');
|
||||
return cssText.join('');
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -936,34 +936,34 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
var textColor = style['textColor'] || 'black';
|
||||
var textSize = style['textSize'] || 10;
|
||||
|
||||
var csstext = [];
|
||||
var cssText = [];
|
||||
if (T.browser["ie"] < 7) {
|
||||
csstext.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
|
||||
cssText.push('filter:progid:DXImageTransform.Microsoft.AlphaImageLoader(' +
|
||||
'sizingMethod=scale,src="' + url + '");');
|
||||
} else {
|
||||
csstext.push('background-image:url(' + url + ');');
|
||||
cssText.push('background-image:url(' + url + ');');
|
||||
var backgroundPosition = '0 0';
|
||||
(offset instanceof BMap.Size) && (backgroundPosition = offset.width + 'px' + ' ' + offset.height + 'px');
|
||||
csstext.push('background-position:' + backgroundPosition + ';');
|
||||
(offset instanceof BMapGL.Size) && (backgroundPosition = offset.width + 'px' + ' ' + offset.height + 'px');
|
||||
cssText.push('background-position:' + backgroundPosition + ';');
|
||||
}
|
||||
|
||||
if (size instanceof BMap.Size){
|
||||
if (anchor instanceof BMap.Size) {
|
||||
if (size instanceof BMapGL.Size){
|
||||
if (anchor instanceof BMapGL.Size) {
|
||||
if (anchor.height > 0 && anchor.height < size.height) {
|
||||
csstext.push('height:' + (size.height - anchor.height) + 'px; padding-top:' + anchor.height + 'px;');
|
||||
cssText.push('height:' + (size.height - anchor.height) + 'px; padding-top:' + anchor.height + 'px;');
|
||||
}
|
||||
if(anchor.width > 0 && anchor.width < size.width){
|
||||
csstext.push('width:' + (size.width - anchor.width) + 'px; padding-left:' + anchor.width + 'px;');
|
||||
cssText.push('width:' + (size.width - anchor.width) + 'px; padding-left:' + anchor.width + 'px;');
|
||||
}
|
||||
} else {
|
||||
csstext.push('height:' + size.height + 'px; line-height:' + size.height + 'px;');
|
||||
csstext.push('width:' + size.width + 'px; text-align:center;');
|
||||
cssText.push('height:' + size.height + 'px; line-height:' + size.height + 'px;');
|
||||
cssText.push('width:' + size.width + 'px; text-align:center;');
|
||||
}
|
||||
}
|
||||
|
||||
csstext.push('cursor:pointer; color:' + textColor + '; position:absolute; font-size:' +
|
||||
cssText.push('cursor:pointer; color:' + textColor + '; position:absolute; font-size:' +
|
||||
textSize + 'px; font-family:Arial,sans-serif; font-weight:bold');
|
||||
return csstext.join('');
|
||||
return cssText.join('');
|
||||
};
|
||||
|
||||
|
||||
@@ -998,9 +998,9 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
* <br />"<b>target</b>:{BMapLib.TextIconOverlay} 事件目标
|
||||
|
||||
* <br />"<b>point</b> : {BMap.Point} 最新添加上的节点BMap.Point对象
|
||||
* <br />"<b>point</b> : {BMapGL.Point} 最新添加上的节点BMap.Point对象
|
||||
|
||||
* <br />"<b>pixel</b>:{BMap.pixel} 最新添加上的节点BMap.Pixel对象
|
||||
* <br />"<b>pixel</b>:{BMapGL.pixel} 最新添加上的节点BMap.Pixel对象
|
||||
|
||||
*
|
||||
|
||||
@@ -1024,9 +1024,9 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
* <br />"<b>target</b>:{BMapLib.TextIconOverlay} 事件目标
|
||||
|
||||
* <br />"<b>point</b> : {BMap.Point} 最新添加上的节点BMap.Point对象
|
||||
* <br />"<b>point</b> : {BMapGL.Point} 最新添加上的节点BMap.Point对象
|
||||
|
||||
* <br />"<b>pixel</b>:{BMap.pixel} 最新添加上的节点BMap.Pixel对象
|
||||
* <br />"<b>pixel</b>:{BMapGL.pixel} 最新添加上的节点BMap.Pixel对象
|
||||
|
||||
*
|
||||
|
||||
@@ -1057,7 +1057,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
var y = e.clientY || e.pageY;
|
||||
if (e && be && x && y && elem){
|
||||
var offset = T.dom.getPosition(map.getContainer());
|
||||
be.pixel = new BMap.Pixel(x - offset.left, y - offset.top);
|
||||
be.pixel = new BMapGL.Pixel(x - offset.left, y - offset.top);
|
||||
be.point = map.pixelToPoint(be.pixel);
|
||||
}
|
||||
return be;
|
||||
|
Reference in New Issue
Block a user