mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-24 12:58:34 +00:00
Feature/location editor (#8084)
* add geolocation editor * update location with selected position * optimize * google map * optimize * optimize ui & clean up code * fix position validation * update geolocation modal zIndex --------- Co-authored-by: zhouwenxuan <aries@Mac.local>
This commit is contained in:
16
frontend/src/assets/icons/full-screen.svg
Normal file
16
frontend/src/assets/icons/full-screen.svg
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 21.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 32 32" style="enable-background:new 0 0 32 32;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#999999;}
|
||||
</style>
|
||||
<path class="st0" d="M4.6,12.7c0.9,0,1.7-0.7,1.7-1.6V7.8c0-0.4,0.2-0.8,0.5-1.1s0.7-0.5,1.1-0.5h3.3c0.9,0,1.6-0.7,1.6-1.6
|
||||
C12.7,3.7,12,3,11.2,3H4.6C3.7,3,3,3.7,3,4.6v6.6C3,12,3.7,12.7,4.6,12.7z M7.9,25.8c-0.4,0-0.8-0.2-1.1-0.5s-0.5-0.7-0.5-1.1v-3.3
|
||||
c0-0.4-0.2-0.8-0.5-1.1s-0.7-0.5-1.1-0.5c-1,0-1.7,0.7-1.7,1.6v6.5C3,28.3,3.7,29,4.6,29h6.5c0.9,0,1.6-0.7,1.6-1.6
|
||||
s-0.7-1.6-1.6-1.6C11.1,25.8,7.9,25.8,7.9,25.8z M27.3,19.2c-0.9,0-1.6,0.7-1.6,1.6v3.3c0,0.4-0.2,0.8-0.5,1.1
|
||||
c-0.3,0.3-0.7,0.5-1.1,0.5h-3.3c-0.9,0-1.6,0.7-1.6,1.6v0.1c0,0.4,0.2,0.8,0.5,1.1c0.3,0.3,0.7,0.5,1.1,0.5h6.6
|
||||
c0.4,0,0.8-0.2,1.1-0.5c0.3-0.3,0.5-0.7,0.5-1.1v-6.6C29,20,28.3,19.2,27.3,19.2z M20.8,3c-0.9,0-1.6,0.7-1.6,1.6v0.1
|
||||
c0,0.9,0.7,1.6,1.6,1.6h3.3c0.4,0,0.8,0.2,1.1,0.5c0.3,0.3,0.5,0.7,0.5,1.1v3.3c0,0.9,0.7,1.6,1.6,1.6h0.1c0.9-0.1,1.6-0.8,1.6-1.6
|
||||
V4.6C29,3.7,28.3,3,27.4,3H20.8z M21,13v6c0,1.1-0.9,2-2,2h-6c-1.1,0-2-0.9-2-2v-6c0-1.1,0.9-2,2-2h6C20.1,11,21,11.9,21,13z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 1.3 KiB |
@@ -6,7 +6,7 @@ import { CellType, COLUMNS_ICON_CONFIG } from '../../../metadata/constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const DetailItem = ({ readonly = true, field, className, children }) => {
|
||||
const DetailItem = ({ id, readonly = true, field, className, children }) => {
|
||||
const icon = useMemo(() => {
|
||||
if (field.type === 'size') {
|
||||
return COLUMNS_ICON_CONFIG[CellType.NUMBER];
|
||||
@@ -15,7 +15,7 @@ const DetailItem = ({ readonly = true, field, className, children }) => {
|
||||
}, [field]);
|
||||
|
||||
return (
|
||||
<div className={classnames('dirent-detail-item', className)}>
|
||||
<div id={id} className={classnames('dirent-detail-item', className)}>
|
||||
<div className="dirent-detail-item-name d-flex">
|
||||
<div><Icon className="sf-metadata-icon" symbol={icon} /></div>
|
||||
<span className="dirent-detail-item-name-value">{field.name}</span>
|
||||
|
@@ -0,0 +1,162 @@
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
|
||||
const generateLabelContent = (info, isBMap = false) => {
|
||||
const { location_translated, title, tag } = info;
|
||||
const { address } = location_translated;
|
||||
const tagContent = Array.isArray(tag) && tag.length > 0 ? tag[0] : '';
|
||||
|
||||
if (isBMap) {
|
||||
return `
|
||||
<div
|
||||
class='${title ? 'selection-label-content' : 'selection-label-content simple'}'
|
||||
id='selection-label-content'
|
||||
>
|
||||
${`
|
||||
<div class="w-100 d-flex align-items-center">
|
||||
${title ? `
|
||||
<span class="label-title text-truncate" title="${title}">${title}</span>
|
||||
<span class="close-btn" id="selection-label-close">
|
||||
<i class="sf3-font sf3-font-x-01"></i>
|
||||
</span>
|
||||
` : `
|
||||
<span class="label-address text-truncate simple" title="${address}">${address}</span>
|
||||
<span class="close-btn" id="selection-label-close">
|
||||
<i class="sf3-font sf3-font-x-01"></i>
|
||||
</span>
|
||||
`}
|
||||
</div>
|
||||
${title ? `
|
||||
${tagContent ? `<span class="label-tag">${tagContent}</span>` : ''}
|
||||
<span class="label-address-tip">${gettext('Address')}</span>
|
||||
<span class="label-address text-truncate" title="${address}">${address}</span>
|
||||
` : ''}
|
||||
<div class="label-submit btn btn-primary" id="selection-label-submit">${gettext('Fill in')}</div>
|
||||
`}
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
const container = document.createElement('div');
|
||||
container.className = title ? 'selection-label-content' : 'selection-label-content simple';
|
||||
container.id = 'selection-label-content';
|
||||
|
||||
const content = `
|
||||
<div class="w-100 d-flex align-items-center">
|
||||
${title ? `
|
||||
<span class="label-title text-truncate" title="${title}">${title}</span>
|
||||
<span class="close-btn" id="selection-label-close">
|
||||
<i class="sf3-font sf3-font-x-01"></i>
|
||||
</span>
|
||||
` : `
|
||||
<span class="label-address text-truncate simple" title="${address}">${address}</span>
|
||||
<span class="close-btn" id="selection-label-close">
|
||||
<i class="sf3-font sf3-font-x-01"></i>
|
||||
</span>
|
||||
`}
|
||||
</div>
|
||||
${title ? `
|
||||
${tagContent ? `<span class="label-tag">${tagContent}</span>` : ''}
|
||||
<span class="label-address-tip">${gettext('Address')}</span>
|
||||
<span class="label-address text-truncate" title="${address}">${address}</span>
|
||||
` : ''}
|
||||
<div class="label-submit btn btn-primary" id="selection-label-submit">${gettext('Fill in')}</div>
|
||||
`;
|
||||
|
||||
container.innerHTML = content;
|
||||
return container;
|
||||
}
|
||||
};
|
||||
|
||||
export const customBMapLabel = (info) => {
|
||||
const content = generateLabelContent(info, true);
|
||||
const label = new window.BMapGL.Label(content, { offset: new window.BMapGL.Size(9, -5) });
|
||||
const style = info.title
|
||||
? { transform: 'translateY(-50%, 10%)' }
|
||||
: { transform: 'translateY(-50%, 15%)' };
|
||||
label.setStyle(style);
|
||||
|
||||
return label;
|
||||
};
|
||||
|
||||
export const customGMapLabel = (info, submit) => {
|
||||
class Popup extends window.google.maps.OverlayView {
|
||||
constructor(position, content) {
|
||||
super();
|
||||
this.position = position;
|
||||
this.info = info;
|
||||
this.containerDiv = document.createElement('div');
|
||||
this.containerDiv.classList.add('popup-label-container');
|
||||
this.containerDiv.appendChild(content);
|
||||
|
||||
const closeBtn = this.containerDiv.querySelector('#selection-label-close');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.onRemove();
|
||||
});
|
||||
}
|
||||
|
||||
const submitBtn = this.containerDiv.querySelector('#selection-label-submit');
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
submit(info);
|
||||
});
|
||||
}
|
||||
|
||||
Popup.preventMapHitsAndGesturesFrom(this.containerDiv);
|
||||
}
|
||||
onAdd() {
|
||||
this.getPanes().floatPane.appendChild(this.containerDiv);
|
||||
}
|
||||
onRemove() {
|
||||
if (this.containerDiv.parentElement) {
|
||||
this.containerDiv.parentElement.removeChild(this.containerDiv);
|
||||
}
|
||||
}
|
||||
setPosition(position) {
|
||||
this.position = position;
|
||||
this.draw();
|
||||
}
|
||||
setInfo(info) {
|
||||
this.info = info;
|
||||
this.containerDiv.innerHTML = generateLabelContent(info, true);
|
||||
const closeBtn = this.containerDiv.querySelector('#selection-label-close');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
this.onRemove();
|
||||
});
|
||||
}
|
||||
const submitBtn = this.containerDiv.querySelector('#selection-label-submit');
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
submit(info);
|
||||
});
|
||||
}
|
||||
this.draw();
|
||||
}
|
||||
draw() {
|
||||
const divPosition = this.getProjection().fromLatLngToDivPixel(
|
||||
this.position,
|
||||
);
|
||||
// Hide the popup when it is far out of view.
|
||||
const display =
|
||||
Math.abs(divPosition.x) < 4000 && Math.abs(divPosition.y) < 4000
|
||||
? 'block'
|
||||
: 'none';
|
||||
|
||||
if (display === 'block') {
|
||||
this.containerDiv.style.left = divPosition.x + 'px';
|
||||
this.containerDiv.style.top = divPosition.y + 'px';
|
||||
}
|
||||
|
||||
if (this.containerDiv.style.display !== display) {
|
||||
this.containerDiv.style.display = display;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const content = generateLabelContent(info);
|
||||
return new Popup(info.position, content);
|
||||
};
|
@@ -0,0 +1,284 @@
|
||||
.sf-geolocation-editor-container {
|
||||
width: 100%;
|
||||
height: 434px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 4px;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container.full-screen {
|
||||
width: 100%;
|
||||
height: 600px;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 0 10px #0000004d;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .editor-header {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 0 12px;
|
||||
background-color: #fff;
|
||||
color: var(--bs-body-color);
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .editor-header .title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .editor-header .title .location-icon {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .editor-header .full-screen {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .editor-header .full-screen:hover {
|
||||
background-color: var(--bs-hover-bg);
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-container {
|
||||
width: 90%;
|
||||
max-width: 450px;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
left: 24px;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-container .search-input {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
padding-right: 38px;
|
||||
border-radius: 3px 0 0 3px;
|
||||
border-right: none;
|
||||
box-shadow: 0 0 2px #0000004d;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-container .clean-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
color: #666;
|
||||
top: 7px;
|
||||
right: 8px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-container .clean-btn:hover {
|
||||
background-color: var(--bs-hover-bg);
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-container .search-btn {
|
||||
width: 12%;
|
||||
height: 38px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background-color: var(--bs-body-tertiary-bg);
|
||||
color: #666;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 0 3px 3px 0;
|
||||
box-shadow: 0 0 2px #0000004d;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content {
|
||||
width: 225px;
|
||||
height: 130px;
|
||||
position: absolute;
|
||||
background-color: #fff;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
box-shadow: 1px 2px 1px rgba(0, 0, 0, .15);
|
||||
padding: 10px;
|
||||
font-weight: 500;
|
||||
cursor: default;
|
||||
left: -115px;
|
||||
top: 16px;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content .close-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
right: 4px;
|
||||
top: 4px;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content .close-btn:hover {
|
||||
background-color: var(--bs-hover-bg);
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content .label-title {
|
||||
width: 90%;
|
||||
color: #212529;
|
||||
font-size: 18px;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content .label-tag {
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content .label-address-tip {
|
||||
position: absolute;
|
||||
top: 58px;
|
||||
left: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content .label-address {
|
||||
width: 90%;
|
||||
position: absolute;
|
||||
top: 74px;
|
||||
left: 10px;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content .label-submit {
|
||||
width: fit-content;
|
||||
min-width: 50px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: absolute;
|
||||
bottom: 4px;
|
||||
right: 4px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content seafile-multicolor-icon-drop-down {
|
||||
position: absolute;
|
||||
transform: rotate(180deg);
|
||||
top: -16px;
|
||||
left: 47%;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content.simple {
|
||||
height: 90px;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .selection-label-content.simple .label-address {
|
||||
width: 205px;
|
||||
position: absolute;
|
||||
top: 32px;
|
||||
font-size: 14px;
|
||||
cursor: text;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .sf-map-control-container {
|
||||
width: 30px;
|
||||
height: fit-content;
|
||||
flex-direction: column;
|
||||
right: 30px;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .sf-map-control-container.sf-map-geolocation-control-container {
|
||||
right: 16px !important;
|
||||
bottom: 96px !important;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .sf-map-control-container.sf-map-zoom-control-container {
|
||||
right: 16px !important;
|
||||
bottom: 30px !important;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .sf-map-control-container .sf-map-control-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .sf-map-control-container .sf-map-control-divider::before {
|
||||
width: 22px;
|
||||
height: 1px;
|
||||
left: 4px;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-results-container {
|
||||
width: 404px;
|
||||
height: fit-content;
|
||||
max-height: 300px;
|
||||
overflow-y: scroll;
|
||||
position: absolute;
|
||||
top: 62px;
|
||||
left: 20px;
|
||||
background-color: #fff;
|
||||
border: 1px solid var(--bs-border-color);
|
||||
border-radius: 3px;
|
||||
padding: 0 10px;
|
||||
box-shadow: 0 -0 3px rgb(0 0 0 / 30%);
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-result-item {
|
||||
height: 56px;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-around;
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid var(--bs-border-color);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-result-item:hover {
|
||||
background-color: var(--bs-th-bg);
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-result-item .search-result-item-title {
|
||||
position: relative;
|
||||
height: 16px;
|
||||
color: #212529;
|
||||
font-size: 14px;
|
||||
line-height: 14px;
|
||||
}
|
||||
|
||||
.sf-geolocation-editor-container .search-result-item .search-result-item-address {
|
||||
position: relative;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
line-height: 12px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.popup-label-container {
|
||||
cursor: auto;
|
||||
height: 0;
|
||||
position: absolute;
|
||||
width: 200px;
|
||||
}
|
@@ -0,0 +1,501 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { createBMapGeolocationControl, createBMapZoomControl } from '../../map-controller';
|
||||
import { initMapInfo, loadMapSource } from '../../../../utils/map-utils';
|
||||
import { baiduMapKey, gettext, googleMapId, googleMapKey, lang } from '../../../../utils/constants';
|
||||
import { KeyCodes, MAP_TYPE } from '../../../../constants';
|
||||
import Icon from '../../../../components/icon';
|
||||
import IconBtn from '../../../../components/icon-btn';
|
||||
import toaster from '../../../../components/toast';
|
||||
import { createZoomControl } from '../../map-controller/zoom';
|
||||
import { createGeolocationControl } from '../../map-controller/geolocation';
|
||||
import { customBMapLabel, customGMapLabel } from './custom-label';
|
||||
import { isValidPosition } from '../../../utils/validate';
|
||||
import { DEFAULT_POSITION } from '../../../constants';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const GeolocationEditor = ({ position, isFullScreen, onSubmit, onFullScreen, onReadyToEraseLocation }) => {
|
||||
const [inputValue, setInputValue] = useState('');
|
||||
const [searchResults, setSearchResults] = useState([]);
|
||||
|
||||
const type = useMemo(() => {
|
||||
const { type } = initMapInfo({ baiduMapKey, googleMapKey });
|
||||
return type;
|
||||
}, []);
|
||||
|
||||
const ref = useRef(null);
|
||||
const mapRef = useRef(null);
|
||||
const geocRef = useRef(null);
|
||||
const markerRef = useRef(null);
|
||||
const labelRef = useRef(null);
|
||||
const googlePlacesRef = useRef(null);
|
||||
|
||||
const onChange = useCallback((e) => {
|
||||
setInputValue(e.target.value);
|
||||
}, []);
|
||||
|
||||
const search = useCallback(() => {
|
||||
if (type === MAP_TYPE.B_MAP) {
|
||||
const options = {
|
||||
onSearchComplete: (results) => {
|
||||
const status = local.getStatus();
|
||||
if (status !== window.BMAP_STATUS_SUCCESS) {
|
||||
toaster.danger(gettext('Search failed, please enter detailed address.'));
|
||||
return;
|
||||
}
|
||||
let searchResults = [];
|
||||
for (let i = 0; i < results.getCurrentNumPois(); i++) {
|
||||
const value = results.getPoi(i);
|
||||
const position = {
|
||||
address: value.address || '',
|
||||
title: value.title || '',
|
||||
tag: value.tags || [],
|
||||
lngLat: {
|
||||
lng: value.point.lng,
|
||||
lat: value.point.lat,
|
||||
}
|
||||
};
|
||||
searchResults.push(position);
|
||||
}
|
||||
setSearchResults(searchResults);
|
||||
}
|
||||
};
|
||||
|
||||
const local = new window.BMapGL.LocalSearch(mapRef.current, options);
|
||||
local.search(inputValue);
|
||||
} else if (type === MAP_TYPE.G_MAP) {
|
||||
const request = {
|
||||
query: inputValue,
|
||||
language: lang,
|
||||
};
|
||||
googlePlacesRef.current.textSearch(request, (results, status) => {
|
||||
if (status === 'OK' && results[0]) {
|
||||
let searchResults = [];
|
||||
for (let i = 0; i < results.length; i++) {
|
||||
const value = {
|
||||
address: results[i].formatted_address || '',
|
||||
title: results[i].name || '',
|
||||
tag: results[i].types || [],
|
||||
lngLat: {
|
||||
lng: results[i].geometry.location.lng(),
|
||||
lat: results[i].geometry.location.lat(),
|
||||
}
|
||||
};
|
||||
searchResults.push(value);
|
||||
}
|
||||
setSearchResults(searchResults);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [type, inputValue]);
|
||||
|
||||
const onKeyDown = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
if (e.keyCode === KeyCodes.Enter) {
|
||||
search();
|
||||
} else if (e.keyCode === KeyCodes.Backspace) {
|
||||
setSearchResults([]);
|
||||
}
|
||||
}, [search]);
|
||||
|
||||
const clear = useCallback(() => {
|
||||
setInputValue('');
|
||||
setSearchResults([]);
|
||||
if (type === MAP_TYPE.B_MAP) {
|
||||
mapRef.current.clearOverlays();
|
||||
} else {
|
||||
labelRef.current.setMap(null);
|
||||
labelRef.current = null;
|
||||
}
|
||||
onReadyToEraseLocation();
|
||||
}, [type, onReadyToEraseLocation]);
|
||||
|
||||
const close = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
if (type === MAP_TYPE.B_MAP) {
|
||||
markerRef.current.getLabel()?.remove();
|
||||
} else {
|
||||
labelRef.current.setMap(null);
|
||||
labelRef.current = null;
|
||||
}
|
||||
}, [type]);
|
||||
|
||||
const submit = useCallback((value) => {
|
||||
const { position, location_translated } = value;
|
||||
const location = {
|
||||
position,
|
||||
location_translated,
|
||||
};
|
||||
onSubmit(location);
|
||||
}, [onSubmit]);
|
||||
|
||||
const parseBMapAddress = useCallback((result) => {
|
||||
let value = {};
|
||||
const { surroundingPois, address, addressComponents, point } = result;
|
||||
if (surroundingPois.length === 0) {
|
||||
const { province, city, district, street } = addressComponents;
|
||||
value.position = { lng: point.lng, lat: point.lat };
|
||||
value.location_translated = {
|
||||
address,
|
||||
country: '',
|
||||
province,
|
||||
city,
|
||||
district,
|
||||
street,
|
||||
};
|
||||
value.title = '';
|
||||
value.tags = [];
|
||||
} else {
|
||||
const position = surroundingPois[0];
|
||||
const { address, title, tags, point, city, province } = position;
|
||||
value.position = { lng: point.lng, lat: point.lat };
|
||||
value.location_translated = {
|
||||
address,
|
||||
country: '',
|
||||
province,
|
||||
city,
|
||||
district: '',
|
||||
street: '',
|
||||
};
|
||||
value.title = title || '';
|
||||
value.tags = tags || [];
|
||||
}
|
||||
return value;
|
||||
}, []);
|
||||
|
||||
const parseGMapAddress = useCallback((result) => {
|
||||
const location_translated = {
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
street: ''
|
||||
};
|
||||
|
||||
result.address_components.forEach(component => {
|
||||
if (component.types.includes('country')) {
|
||||
location_translated.country = component.long_name;
|
||||
} else if (component.types.includes('administrative_area_level_1')) {
|
||||
location_translated.province = component.long_name;
|
||||
} else if (component.types.includes('locality')) {
|
||||
location_translated.city = component.long_name;
|
||||
} else if (component.types.includes('sublocality')) {
|
||||
location_translated.district = component.long_name;
|
||||
} else if (component.types.includes('route')) {
|
||||
location_translated.street = component.long_name;
|
||||
}
|
||||
});
|
||||
location_translated.address = result.formatted_address;
|
||||
|
||||
const position = {
|
||||
lng: result.geometry.location.lng(),
|
||||
lat: result.geometry.location.lat()
|
||||
};
|
||||
|
||||
return { position, location_translated };
|
||||
}, []);
|
||||
|
||||
const addLabel = useCallback((point) => {
|
||||
if (type === MAP_TYPE.B_MAP) {
|
||||
markerRef.current.getLabel()?.remove();
|
||||
geocRef.current.getLocation(point, (result) => {
|
||||
const info = parseBMapAddress(result);
|
||||
const { title, location_translated } = info;
|
||||
setInputValue(title || location_translated.address);
|
||||
const label = customBMapLabel(info);
|
||||
markerRef.current.setLabel(label);
|
||||
|
||||
setTimeout(() => {
|
||||
const label = document.getElementById('selection-label-content');
|
||||
if (label) {
|
||||
label.addEventListener('click', (e) => e.stopPropagation());
|
||||
}
|
||||
|
||||
const closeBtn = document.getElementById('selection-label-close');
|
||||
if (closeBtn) {
|
||||
closeBtn.addEventListener('click', close);
|
||||
}
|
||||
|
||||
const submitBtn = document.getElementById('selection-label-submit');
|
||||
if (submitBtn) {
|
||||
submitBtn.addEventListener('click', () => submit(info));
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
} else {
|
||||
geocRef.current.geocode({ location: point, language: lang }, (results, status) => {
|
||||
if (status === 'OK' && results[0]) {
|
||||
const info = parseGMapAddress(results[0]);
|
||||
labelRef.current = customGMapLabel(info, submit);
|
||||
labelRef.current.setMap(mapRef.current);
|
||||
setInputValue(info.location_translated.address);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [type, close, submit, parseBMapAddress, parseGMapAddress]);
|
||||
|
||||
const renderBaiduMap = useCallback(() => {
|
||||
if (!window.BMapGL.Map) return;
|
||||
|
||||
mapRef.current = new window.BMapGL.Map(ref.current);
|
||||
const initPos = isValidPosition(position?.lng, position?.lat) ? position : DEFAULT_POSITION;
|
||||
const point = new window.BMapGL.Point(initPos.lng, initPos.lat);
|
||||
mapRef.current.centerAndZoom(point, 16);
|
||||
mapRef.current.enableScrollWheelZoom();
|
||||
mapRef.current.clearOverlays();
|
||||
|
||||
const ZoomControl = createBMapZoomControl({
|
||||
anchor: window.BMAP_ANCHOR_BOTTOM_RIGHT,
|
||||
offset: { x: 16, y: 30 }
|
||||
});
|
||||
const zoomControl = new ZoomControl();
|
||||
mapRef.current.addControl(zoomControl);
|
||||
|
||||
const GeolocationControl = createBMapGeolocationControl({
|
||||
anchor: window.BMAP_ANCHOR_BOTTOM_RIGHT,
|
||||
offset: { x: 16, y: 96 },
|
||||
callback: (point) => {
|
||||
if (mapRef.current.getOverlays().length === 0) {
|
||||
mapRef.current.addOverlay(markerRef.current);
|
||||
}
|
||||
mapRef.current.centerAndZoom(point, 16);
|
||||
markerRef.current.setPosition(point);
|
||||
addLabel(point);
|
||||
}
|
||||
});
|
||||
const geolocationControl = new GeolocationControl();
|
||||
mapRef.current.addControl(geolocationControl);
|
||||
|
||||
markerRef.current = new window.BMapGL.Marker(point, { offset: new window.BMapGL.Size(-2, -5) });
|
||||
geocRef.current = new window.BMapGL.Geocoder();
|
||||
if (isValidPosition(position?.lng, position?.lat)) {
|
||||
mapRef.current.addOverlay(markerRef.current);
|
||||
addLabel(point);
|
||||
}
|
||||
|
||||
mapRef.current.addEventListener('click', (e) => {
|
||||
if (searchResults.length > 0) {
|
||||
setSearchResults([]);
|
||||
return;
|
||||
}
|
||||
const { lng, lat } = e.latlng;
|
||||
const point = new window.BMapGL.Point(lng, lat);
|
||||
if (mapRef.current.getOverlays().length === 0) {
|
||||
mapRef.current.addOverlay(markerRef.current);
|
||||
}
|
||||
markerRef.current.setPosition(point);
|
||||
mapRef.current.setCenter(point);
|
||||
addLabel(point);
|
||||
});
|
||||
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [position?.lng, position?.lat]);
|
||||
|
||||
const renderGoogleMap = useCallback(() => {
|
||||
const isValid = isValidPosition(position?.lng, position?.lat);
|
||||
const initPos = isValid ? position : DEFAULT_POSITION;
|
||||
mapRef.current = new window.google.maps.Map(ref.current, {
|
||||
center: initPos,
|
||||
zoom: 16,
|
||||
mapId: googleMapId,
|
||||
zoomControl: false,
|
||||
mapTypeControl: false,
|
||||
scaleControl: false,
|
||||
streetViewControl: false,
|
||||
rotateControl: false,
|
||||
fullscreenControl: false,
|
||||
disableDefaultUI: true,
|
||||
gestrueHandling: 'cooperative',
|
||||
clickableIcons: false,
|
||||
});
|
||||
|
||||
// control
|
||||
const zoomControl = createZoomControl({ map: mapRef.current });
|
||||
const geolocationControl = createGeolocationControl({
|
||||
map: mapRef.current,
|
||||
callback: (lngLat) => {
|
||||
geocRef.current.geocode({ location: lngLat, language: lang }, (results, status) => {
|
||||
if (status === 'OK' && results[0]) {
|
||||
const info = parseGMapAddress(results[0]);
|
||||
setInputValue(info.location_translated.address);
|
||||
if (!markerRef.current) {
|
||||
markerRef.current = new window.google.maps.marker.AdvancedMarkerElement({
|
||||
position: lngLat,
|
||||
map: mapRef.current,
|
||||
});
|
||||
} else {
|
||||
markerRef.current.position = lngLat;
|
||||
}
|
||||
if (!labelRef.current) {
|
||||
addLabel(lngLat);
|
||||
} else {
|
||||
labelRef.current.setPosition(lngLat);
|
||||
labelRef.current.setInfo(info);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
mapRef.current.controls[window.google.maps.ControlPosition.RIGHT_BOTTOM].push(zoomControl);
|
||||
mapRef.current.controls[window.google.maps.ControlPosition.RIGHT_BOTTOM].push(geolocationControl);
|
||||
|
||||
// marker
|
||||
if (isValid) {
|
||||
markerRef.current = new window.google.maps.marker.AdvancedMarkerElement({
|
||||
position,
|
||||
map: mapRef.current,
|
||||
});
|
||||
}
|
||||
|
||||
// geocoder
|
||||
geocRef.current = new window.google.maps.Geocoder();
|
||||
isValid && addLabel(position);
|
||||
|
||||
googlePlacesRef.current = new window.google.maps.places.PlacesService(mapRef.current);
|
||||
|
||||
// map click event
|
||||
window.google.maps.event.addListener(mapRef.current, 'click', (e) => {
|
||||
if (searchResults.length > 0) {
|
||||
setSearchResults([]);
|
||||
return;
|
||||
}
|
||||
const latLng = e.latLng;
|
||||
const point = { lat: latLng.lat(), lng: latLng.lng() };
|
||||
|
||||
if (!markerRef.current) {
|
||||
markerRef.current = new window.google.maps.marker.AdvancedMarkerElement({
|
||||
position: point,
|
||||
map: mapRef.current,
|
||||
});
|
||||
} else {
|
||||
markerRef.current.position = latLng;
|
||||
}
|
||||
mapRef.current.panTo(latLng);
|
||||
|
||||
geocRef.current.geocode({ location: point, language: lang }, (results, status) => {
|
||||
if (status === 'OK' && results[0]) {
|
||||
const info = parseGMapAddress(results[0]);
|
||||
if (!labelRef.current) {
|
||||
addLabel(point);
|
||||
} else {
|
||||
labelRef.current.setPosition(latLng);
|
||||
labelRef.current.setInfo(info);
|
||||
}
|
||||
setInputValue(info.location_translated.address);
|
||||
}
|
||||
});
|
||||
});
|
||||
}, [searchResults, position, addLabel, parseGMapAddress]);
|
||||
|
||||
const toggleFullScreen = useCallback((e) => {
|
||||
e.stopPropagation();
|
||||
e.nativeEvent.stopImmediatePropagation();
|
||||
onFullScreen();
|
||||
}, [onFullScreen]);
|
||||
|
||||
const onSelect = useCallback((result) => {
|
||||
const { lngLat, title, address } = result;
|
||||
let point = lngLat;
|
||||
if (type === MAP_TYPE.B_MAP) {
|
||||
const { lng, lat } = lngLat;
|
||||
point = new window.BMapGL.Point(lng, lat);
|
||||
if (mapRef.current.getOverlays().length === 0) {
|
||||
mapRef.current.addOverlay(markerRef.current);
|
||||
}
|
||||
markerRef.current.setPosition(point);
|
||||
mapRef.current.setCenter(point);
|
||||
addLabel(point);
|
||||
} else {
|
||||
const point = { lat: lngLat.lat, lng: lngLat.lng };
|
||||
markerRef.current.position = point;
|
||||
if (!labelRef.current) {
|
||||
addLabel(point);
|
||||
} else {
|
||||
labelRef.current.setPosition(point);
|
||||
labelRef.current.setInfo({
|
||||
title,
|
||||
tag: [],
|
||||
position: point,
|
||||
location_translated: {
|
||||
address,
|
||||
country: '',
|
||||
province: '',
|
||||
city: '',
|
||||
district: '',
|
||||
street: '',
|
||||
}
|
||||
});
|
||||
}
|
||||
mapRef.current.panTo(point);
|
||||
}
|
||||
setSearchResults([]);
|
||||
setInputValue(title || address);
|
||||
}, [type, addLabel]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mapRef.current) return;
|
||||
const { type, key } = initMapInfo({ baiduMapKey, googleMapKey });
|
||||
if (type === MAP_TYPE.B_MAP) {
|
||||
if (!window.BMapGL) {
|
||||
window.renderBaiduMap = () => renderBaiduMap();
|
||||
loadMapSource(type, key);
|
||||
} else {
|
||||
renderBaiduMap();
|
||||
}
|
||||
} else if (type === MAP_TYPE.G_MAP) {
|
||||
if (!window.google?.maps.Map) {
|
||||
window.renderGoogleMap = () => renderGoogleMap();
|
||||
loadMapSource(type, key);
|
||||
} else {
|
||||
renderGoogleMap();
|
||||
}
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div className={classNames('sf-geolocation-editor-container', { 'full-screen': isFullScreen })}>
|
||||
<div className="editor-header">
|
||||
<div className="title">
|
||||
<Icon symbol="location" size={24} className="location-icon" />
|
||||
<span className="ml-2">{gettext('Address')}</span>
|
||||
</div>
|
||||
<IconBtn className="full-screen" symbol="full-screen" size={24} onClick={toggleFullScreen} />
|
||||
</div>
|
||||
<div className="w-100 h-100 position-relative">
|
||||
<div className="search-container">
|
||||
<div className="flex-1 d-flex position-relative">
|
||||
<input
|
||||
type="text"
|
||||
value={inputValue}
|
||||
className="form-control search-input"
|
||||
placeholder={gettext('Please enter the address')}
|
||||
onChange={onChange}
|
||||
onKeyDown={onKeyDown}
|
||||
autoFocus
|
||||
/>
|
||||
{inputValue && <IconBtn symbol="close" className="clean-btn" size={24} onClick={clear} />}
|
||||
</div>
|
||||
<span className="search-btn" onClick={search}>
|
||||
<i className="sf3-font sf3-font-search"></i>
|
||||
</span>
|
||||
</div>
|
||||
<div ref={ref} className="w-100 h-100 sf-metadata-geolocation-editor-container"></div>
|
||||
{searchResults.length > 0 && (
|
||||
<div className="search-results-container">
|
||||
{searchResults.map((result, index) => (
|
||||
<div key={index} className="search-result-item" onClick={() => onSelect(result)}>
|
||||
<span className="search-result-item-title">{result.title || ''}</span>
|
||||
<span className="search-result-item-address">{result.address || ''}</span>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default GeolocationEditor;
|
@@ -2,7 +2,7 @@ import classnames from 'classnames';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
import { wgs84_to_gcj02 } from '../../../utils/coord-transform';
|
||||
|
||||
export const createGeolocationControl = (map) => {
|
||||
export const createGeolocationControl = ({ map, callback }) => {
|
||||
const container = document.createElement('div');
|
||||
container.className = classnames(
|
||||
'sf-map-control-container sf-map-geolocation-control-container d-flex align-items-center justify-content-center',
|
||||
@@ -23,6 +23,7 @@ export const createGeolocationControl = (map) => {
|
||||
navigator.geolocation.getCurrentPosition((userInfo) => {
|
||||
const gcPosition = wgs84_to_gcj02(userInfo.coords.longitude, userInfo.coords.latitude);
|
||||
map.setCenter(gcPosition);
|
||||
callback(gcPosition);
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -39,7 +40,7 @@ export function createBMapGeolocationControl({ anchor, offset, callback }) {
|
||||
GeolocationControl.prototype.initialize = function (map) {
|
||||
const div = document.createElement('div');
|
||||
let className = classnames('sf-map-control-container sf-map-geolocation-control-container d-flex align-items-center justify-content-center', {
|
||||
'sf-map-geolocation-control-mobile': !Utils.isDesktop()
|
||||
'sf-map-control-container-mobile': !Utils.isDesktop()
|
||||
});
|
||||
|
||||
const locationButton = document.createElement('div');
|
||||
|
@@ -37,21 +37,11 @@
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.sf-map-control-container.sf-map-geolocation-control-container {
|
||||
right: 30px !important;
|
||||
bottom: 30px !important;
|
||||
}
|
||||
|
||||
.sf-map-control-container .sf-map-geolocation-control {
|
||||
width: 30px;
|
||||
line-height: 30px;
|
||||
}
|
||||
|
||||
.sf-map-control-container.sf-map-zoom-control-container {
|
||||
right: 66px !important;
|
||||
bottom: 30px !important;
|
||||
}
|
||||
|
||||
.sf-map-control-container .sf-map-zoom-control {
|
||||
width: 40px;
|
||||
}
|
||||
|
@@ -32,7 +32,7 @@ const updateButtonStates = (map, zoomIn, zoomOut) => {
|
||||
zoomOut.className = classnames(buttonClassName, { 'disabled': zoomLevel <= MIN_ZOOM });
|
||||
};
|
||||
|
||||
export const createZoomControl = (map) => {
|
||||
export const createZoomControl = ({ map }) => {
|
||||
const container = createZoomContainer();
|
||||
|
||||
const zoomInButton = createButton('<i class="sf-map-control-icon sf3-font sf3-font-zoom-in"></i>');
|
||||
@@ -60,7 +60,7 @@ export const createZoomControl = (map) => {
|
||||
return container;
|
||||
};
|
||||
|
||||
export function createBMapZoomControl(anchor, offset) {
|
||||
export function createBMapZoomControl({ anchor, offset }) {
|
||||
function ZoomControl() {
|
||||
this.defaultAnchor = anchor || window.BMAP_ANCHOR_BOTTOM_RIGHT;
|
||||
this.defaultOffset = new window.BMapGL.Size(offset?.x || 66, offset?.y || 30);
|
||||
|
@@ -33,8 +33,8 @@ const MetadataDetails = ({ readOnly, tagsData }) => {
|
||||
if (isDir && FOLDER_NOT_DISPLAY_COLUMN_KEYS.includes(field.key)) return null;
|
||||
const value = getCellValueByColumn(record, field);
|
||||
|
||||
if (field.key === PRIVATE_COLUMN_KEY.LOCATION && Utils.imageCheck(fileName) && value) {
|
||||
return <Location key={field.key} position={value} record={record} />;
|
||||
if (field.key === PRIVATE_COLUMN_KEY.LOCATION && Utils.imageCheck(fileName)) {
|
||||
return <Location key={field.key} position={value} record={record} onChange={onChange} />;
|
||||
}
|
||||
|
||||
let canEdit = canModifyRecord && field.editable && !readOnly;
|
||||
|
@@ -32,3 +32,27 @@
|
||||
right: 10px !important;
|
||||
bottom: 16px !important;
|
||||
}
|
||||
|
||||
.dirent-detail-item-value-map .sf-metadata-ui.sf-metadata-geolocation-formatter {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-metadata-record-cell-empty {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 7px 6px 0 6px;
|
||||
}
|
||||
|
||||
.sf-metadata-record-cell-empty:empty::before {
|
||||
content: attr(placeholder);
|
||||
color: rgba(255, 255, 255, .7);
|
||||
}
|
||||
|
||||
.sf-metadata-geolocation-property-detail-editor-popover .popover.show {
|
||||
width: 500px;
|
||||
max-width: 500px;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
transform: translateX(-140px);
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { Modal, Popover } from 'reactstrap';
|
||||
import { initMapInfo, loadMapSource } from '../../../../utils/map-utils';
|
||||
import { wgs84_to_gcj02, gcj02_to_bd09 } from '../../../../utils/coord-transform';
|
||||
import { MAP_TYPE } from '../../../../constants';
|
||||
import Loading from '../../../../components/loading';
|
||||
import { gettext, baiduMapKey, googleMapKey, googleMapId } from '../../../../utils/constants';
|
||||
@@ -15,6 +15,8 @@ import { createBMapZoomControl } from '../../map-controller';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { eventBus } from '../../../../components/common/event-bus';
|
||||
import { createZoomControl } from '../../map-controller/zoom';
|
||||
import ClickOutside from '../../../../components/click-outside';
|
||||
import GeolocationEditor from '../../cell-editors/geolocation-editor';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@@ -23,6 +25,7 @@ class Location extends React.Component {
|
||||
static propTypes = {
|
||||
position: PropTypes.object,
|
||||
record: PropTypes.object,
|
||||
onChange: PropTypes.func,
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -31,9 +34,14 @@ class Location extends React.Component {
|
||||
this.mapType = type;
|
||||
this.mapKey = key;
|
||||
this.map = null;
|
||||
this.marker = null;
|
||||
this.state = {
|
||||
address: '',
|
||||
latLng: this.props.position,
|
||||
address: this.props.record?._location_translated?.address || '',
|
||||
isLoading: false,
|
||||
isEditorShown: false,
|
||||
isFullScreen: false,
|
||||
isReadyToEraseLocation: false,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,109 +56,96 @@ class Location extends React.Component {
|
||||
});
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps) {
|
||||
const { position, record } = this.props;
|
||||
if (!isValidPosition(position?.lng, position?.lat) || typeof record !== 'object') return;
|
||||
if (prevProps.position?.lng === position?.lng && prevProps.position?.lat === position?.lat) return;
|
||||
let transformedPos = wgs84_to_gcj02(position.lng, position.lat);
|
||||
if (this.mapType === MAP_TYPE.B_MAP) {
|
||||
transformedPos = gcj02_to_bd09(transformedPos.lng, transformedPos.lat);
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
const { latLng } = this.state;
|
||||
if (prevProps.record._id !== this.props.record._id) {
|
||||
this.setState({
|
||||
latLng: this.props.position,
|
||||
address: this.props.record?._location_translated?.address || '',
|
||||
isReadyToEraseLocation: false,
|
||||
});
|
||||
}
|
||||
this.addMarkerByPosition(transformedPos.lng, transformedPos.lat);
|
||||
if (!this.map) return;
|
||||
if (!isValidPosition(latLng?.lng, latLng?.lat)) return;
|
||||
|
||||
this.setState({ address: record._location_translated?.address });
|
||||
if (prevState.latLng?.lat !== latLng.lat || prevState.latLng?.lng !== latLng.lng) {
|
||||
if (this.mapType === MAP_TYPE.B_MAP) {
|
||||
this.marker.setPosition(latLng);
|
||||
this.map.panTo(latLng);
|
||||
} else if (this.mapType === MAP_TYPE.G_MAP) {
|
||||
this.marker.position = latLng;
|
||||
this.map.panTo(latLng);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.unsubscribeClearMapInstance();
|
||||
this.map = null;
|
||||
}
|
||||
|
||||
initMap = () => {
|
||||
if (this.map) return;
|
||||
const { record } = this.props;
|
||||
const { latLng } = this.state;
|
||||
if (!isValidPosition(latLng?.lng, latLng?.lat) || typeof record !== 'object') return;
|
||||
|
||||
const { position, record } = this.props;
|
||||
if (!isValidPosition(position?.lng, position?.lat) || typeof record !== 'object') return;
|
||||
|
||||
this.setState({ isLoading: true, address: record._location_translated?.address });
|
||||
this.setState({ isLoading: true }, () => {
|
||||
if (this.mapType === MAP_TYPE.B_MAP) {
|
||||
if (!window.BMapGL) {
|
||||
window.renderBaiduMap = () => this.renderBaiduMap(position);
|
||||
window.renderBaiduMap = () => this.renderBaiduMap();
|
||||
loadMapSource(this.mapType, this.mapKey);
|
||||
} else {
|
||||
this.renderBaiduMap(position);
|
||||
this.renderBaiduMap();
|
||||
}
|
||||
} else if (this.mapType === MAP_TYPE.G_MAP) {
|
||||
if (!window.google?.maps.Map) {
|
||||
window.renderGoogleMap = () => this.renderGoogleMap(position);
|
||||
window.renderGoogleMap = () => this.renderGoogleMap();
|
||||
loadMapSource(this.mapType, this.mapKey);
|
||||
} else {
|
||||
this.renderGoogleMap(position);
|
||||
this.renderGoogleMap();
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
addMarkerByPosition = (lng, lat) => {
|
||||
if (!this.map) {
|
||||
this.initMap(this.props.position);
|
||||
return;
|
||||
}
|
||||
addMarker = () => {
|
||||
const { latLng } = this.state;
|
||||
if (this.mapType === MAP_TYPE.B_MAP) {
|
||||
if (this.lastLng === lng && this.lastLat === lat) return;
|
||||
this.lastLng = lng;
|
||||
this.lastLat = lat;
|
||||
|
||||
const point = new window.BMapGL.Point(lng, lat);
|
||||
const marker = new window.BMapGL.Marker(point, { offset: new window.BMapGL.Size(-2, -5) });
|
||||
this.map.clearOverlays();
|
||||
this.map.addOverlay(marker);
|
||||
this.map.setCenter(point);
|
||||
}
|
||||
if (this.mapType === MAP_TYPE.G_MAP) {
|
||||
if (!this.googleMarker) {
|
||||
this.googleMarker = new window.google.maps.marker.AdvancedMarkerElement({
|
||||
position: { lat, lng },
|
||||
this.marker = new window.BMapGL.Marker(latLng);
|
||||
this.map.addOverlay(this.marker);
|
||||
} else if (this.mapType === MAP_TYPE.G_MAP) {
|
||||
this.marker = new window.google.maps.marker.AdvancedMarkerElement({
|
||||
position: latLng,
|
||||
map: this.map,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
this.googleMarker.position = { lat, lng };
|
||||
this.map.setCenter({ lat, lng });
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
renderBaiduMap = (position = {}) => {
|
||||
renderBaiduMap = () => {
|
||||
this.setState({ isLoading: false }, () => {
|
||||
if (!window.BMapGL.Map) return;
|
||||
|
||||
window.mapInstance = new window.BMapGL.Map('sf-geolocation-map-container', { enableMapClick: false });
|
||||
this.map = window.mapInstance;
|
||||
|
||||
const gcPosition = wgs84_to_gcj02(position.lng, position.lat);
|
||||
const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
|
||||
const { lng, lat } = bdPosition;
|
||||
const point = new window.BMapGL.Point(lng, lat);
|
||||
this.map.centerAndZoom(point, 16);
|
||||
const { latLng } = this.state;
|
||||
this.map = new window.BMapGL.Map('sf-geolocation-map-container');
|
||||
this.map.disableScrollWheelZoom(true);
|
||||
this.map.centerAndZoom(latLng, 16);
|
||||
|
||||
const offset = { x: 10, y: Utils.isDesktop() ? 16 : 40 };
|
||||
const ZoomControl = createBMapZoomControl(window.BMapGL, { maxZoom: 21, minZoom: 3, offset });
|
||||
const zoomControl = new ZoomControl();
|
||||
this.map.addControl(zoomControl);
|
||||
this.addMarkerByPosition(lng, lat);
|
||||
this.addMarker();
|
||||
});
|
||||
};
|
||||
|
||||
renderGoogleMap = (position) => {
|
||||
renderGoogleMap = () => {
|
||||
const { latLng } = this.state;
|
||||
this.setState({ isLoading: false }, () => {
|
||||
if (!window.google.maps.Map) return;
|
||||
|
||||
const gcPosition = wgs84_to_gcj02(position.lng, position.lat);
|
||||
const { lng, lat } = gcPosition || {};
|
||||
window.mapInstance = new window.google.maps.Map(this.ref, {
|
||||
this.map = new window.google.maps.Map(this.ref, {
|
||||
zoom: 16,
|
||||
center: gcPosition,
|
||||
center: latLng,
|
||||
mapId: googleMapId,
|
||||
zoomControl: false,
|
||||
mapTypeControl: false,
|
||||
@@ -161,39 +156,77 @@ class Location extends React.Component {
|
||||
disableDefaultUI: true,
|
||||
gestureHandling: 'cooperative',
|
||||
});
|
||||
this.map = window.mapInstance;
|
||||
|
||||
this.addMarkerByPosition(lng, lat);
|
||||
const zoomControl = createZoomControl(this.map);
|
||||
this.addMarker();
|
||||
const zoomControl = createZoomControl({ map: this.map });
|
||||
this.map.controls[window.google.maps.ControlPosition.RIGHT_BOTTOM].push(zoomControl);
|
||||
this.map.setCenter(gcPosition);
|
||||
this.map.panTo(latLng);
|
||||
});
|
||||
};
|
||||
|
||||
openEditor = () => {
|
||||
this.setState({ isEditorShown: true });
|
||||
};
|
||||
|
||||
onFullScreen = () => {
|
||||
this.setState({ isFullScreen: !this.state.isFullScreen });
|
||||
};
|
||||
|
||||
closeEditor = () => {
|
||||
this.setState({ isEditorShown: false });
|
||||
if (this.state.isReadyToEraseLocation) {
|
||||
this.props.onChange(PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED, null);
|
||||
this.props.onChange(PRIVATE_COLUMN_KEY.LOCATION, null);
|
||||
this.setState({ latLng: null, address: '', isReadyToEraseLocation: false });
|
||||
this.mapType === MAP_TYPE.B_MAP && this.map.destroy();
|
||||
this.map = null;
|
||||
}
|
||||
};
|
||||
|
||||
onSubmit = (value) => {
|
||||
const { position, location_translated } = value;
|
||||
this.props.onChange(PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED, location_translated);
|
||||
this.props.onChange(PRIVATE_COLUMN_KEY.LOCATION, position);
|
||||
this.setState({
|
||||
latLng: position,
|
||||
address: location_translated?.address,
|
||||
isEditorShown: false,
|
||||
isFullScreen: false,
|
||||
}, () => {
|
||||
if (!this.map) {
|
||||
this.initMap();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
onReadyToEraseLocation = () => {
|
||||
this.setState({ isReadyToEraseLocation: true });
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isLoading, address } = this.state;
|
||||
const { position } = this.props;
|
||||
const isValid = isValidPosition(position?.lng, position?.lat);
|
||||
const { isLoading, latLng, address, isEditorShown, isFullScreen } = this.state;
|
||||
const isValid = isValidPosition(latLng?.lng, latLng?.lat);
|
||||
return (
|
||||
<>
|
||||
<DetailItem
|
||||
id="location-item"
|
||||
field={{
|
||||
key: PRIVATE_COLUMN_KEY.LOCATION,
|
||||
type: CellType.GEOLOCATION,
|
||||
name: getColumnDisplayName(PRIVATE_COLUMN_KEY.LOCATION)
|
||||
}}
|
||||
readonly={true}
|
||||
readonly={false}
|
||||
>
|
||||
{isValid ? (
|
||||
<div className="sf-metadata-ui cell-formatter-container geolocation-formatter sf-metadata-geolocation-formatter">
|
||||
<div ref={ref => this.editorRef = ref} className="sf-metadata-ui cell-formatter-container geolocation-formatter sf-metadata-geolocation-formatter w-100 cursor-pointer" onClick={this.openEditor}>
|
||||
{!isLoading && this.mapType && address ? (
|
||||
<span>{address}</span>
|
||||
) : (
|
||||
<span>{getGeolocationDisplayString(position, { geo_format: GEOLOCATION_FORMAT.LNG_LAT })}</span>
|
||||
<span>{getGeolocationDisplayString(latLng, { geo_format: GEOLOCATION_FORMAT.LNG_LAT })}</span>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
<div className="sf-metadata-record-cell-empty" placeholder={gettext('Empty')}></div>
|
||||
<div ref={ref => this.editorRef = ref} className="sf-metadata-property-detail-editor sf-metadata-record-cell-empty cursor-pointer" placeholder={gettext('Empty')} onClick={this.openEditor}></div>
|
||||
)}
|
||||
</DetailItem>
|
||||
{isLoading ? (<Loading />) : this.mapType && (
|
||||
@@ -201,6 +234,37 @@ class Location extends React.Component {
|
||||
<div className="w-100 h-100" ref={ref => this.ref = ref} id="sf-geolocation-map-container"></div>
|
||||
</div>
|
||||
)}
|
||||
{isEditorShown && (
|
||||
!isFullScreen ? (
|
||||
<ClickOutside onClickOutside={this.closeEditor}>
|
||||
<Popover
|
||||
target="location-item"
|
||||
isOpen={true}
|
||||
hideArrow={true}
|
||||
fade={false}
|
||||
placement="left"
|
||||
className="sf-metadata-property-detail-editor-popover sf-metadata-geolocation-property-detail-editor-popover"
|
||||
boundariesElement="viewport"
|
||||
style={{
|
||||
width: '100%',
|
||||
border: 'none',
|
||||
background: 'transparent',
|
||||
}}
|
||||
>
|
||||
<GeolocationEditor position={latLng} onSubmit={this.onSubmit} onFullScreen={this.onFullScreen} onReadyToEraseLocation={this.onReadyToEraseLocation} />
|
||||
</Popover>
|
||||
</ClickOutside>
|
||||
) : (
|
||||
<Modal
|
||||
size='lg'
|
||||
isOpen={true}
|
||||
toggle={this.onFullScreen}
|
||||
zIndex={1052}
|
||||
>
|
||||
<GeolocationEditor position={latLng} isFullScreen={isFullScreen} onSubmit={this.onSubmit} onFullScreen={this.onFullScreen} onReadyToEraseLocation={this.onReadyToEraseLocation} />
|
||||
</Modal>
|
||||
)
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@@ -93,6 +93,8 @@ export const EDITABLE_PRIVATE_COLUMN_KEYS = [
|
||||
PRIVATE_COLUMN_KEY.OWNER,
|
||||
PRIVATE_COLUMN_KEY.FILE_RATE,
|
||||
PRIVATE_COLUMN_KEY.TAGS,
|
||||
PRIVATE_COLUMN_KEY.LOCATION,
|
||||
PRIVATE_COLUMN_KEY.LOCATION_TRANSLATED,
|
||||
];
|
||||
|
||||
export const EDITABLE_DATA_PRIVATE_COLUMN_KEYS = [
|
||||
|
@@ -76,13 +76,13 @@ export const createBaiduMap = ({ type, center, zoom, onMapState }) => {
|
||||
// add controls
|
||||
const ZoomControl = createBMapZoomControl({
|
||||
anchor: window.BMAP_ANCHOR_BOTTOM_RIGHT,
|
||||
offset: new window.BMapGL.Size(66, Utils.isDesktop() ? 30 : 90),
|
||||
offset: { x: 66, y: Utils.isDesktop() ? 30 : 90 },
|
||||
});
|
||||
const zoomControl = new ZoomControl();
|
||||
|
||||
const GeolocationControl = createBMapGeolocationControl({
|
||||
anchor: window.BMAP_ANCHOR_BOTTOM_RIGHT,
|
||||
offset: new window.BMapGL.Size(30, Utils.isDesktop() ? 30 : 90),
|
||||
offset: { x: 30, y: Utils.isDesktop() ? 30 : 90 },
|
||||
callback: (point) => {
|
||||
const gcPosition = wgs84_to_gcj02(point.lng, point.lat);
|
||||
const bdPosition = gcj02_to_bd09(gcPosition.lng, gcPosition.lat);
|
||||
|
@@ -82,8 +82,8 @@ export const createGoogleMap = ({ center, zoom, onMapState }) => {
|
||||
maxZoom: MAX_ZOOM,
|
||||
});
|
||||
|
||||
const zoomControl = createZoomControl(map);
|
||||
const geolocationControl = createGeolocationControl(map);
|
||||
const zoomControl = createZoomControl({ map });
|
||||
const geolocationControl = createGeolocationControl({ map });
|
||||
|
||||
map.controls[window.google.maps.ControlPosition.RIGHT_BOTTOM].push(zoomControl);
|
||||
map.controls[window.google.maps.ControlPosition.RIGHT_BOTTOM].push(geolocationControl);
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { MAP_TYPE } from '../constants';
|
||||
import { mediaUrl } from './constants';
|
||||
import { lang, mediaUrl } from './constants';
|
||||
|
||||
export const initMapInfo = ({ baiduMapKey, googleMapKey, mineMapKey }) => {
|
||||
if (baiduMapKey) return { type: MAP_TYPE.B_MAP, key: baiduMapKey };
|
||||
@@ -16,7 +16,7 @@ export const loadMapSource = (type, key, callback) => {
|
||||
if (type === MAP_TYPE.B_MAP) {
|
||||
scriptUrl = `https://api.map.baidu.com/api?type=webgl&v=3.0&ak=${key}&callback=renderBaiduMap`;
|
||||
} else if (type === MAP_TYPE.G_MAP) {
|
||||
scriptUrl = `https://maps.googleapis.com/maps/api/js?key=${key}&libraries=marker,geometry&v=weekly&callback=renderGoogleMap`;
|
||||
scriptUrl = `https://maps.googleapis.com/maps/api/js?key=${key}&language=${lang}&libraries=marker,geometry,core,places&v=weekly&callback=renderGoogleMap`;
|
||||
}
|
||||
|
||||
if (scriptUrl) {
|
||||
|
@@ -101,7 +101,6 @@ def get_unmodifiable_columns():
|
||||
METADATA_TABLE.columns.file_name.to_dict(),
|
||||
METADATA_TABLE.columns.is_dir.to_dict(),
|
||||
METADATA_TABLE.columns.file_type.to_dict(),
|
||||
METADATA_TABLE.columns.location.to_dict(),
|
||||
METADATA_TABLE.columns.obj_id.to_dict(),
|
||||
METADATA_TABLE.columns.size.to_dict(),
|
||||
METADATA_TABLE.columns.suffix.to_dict(),
|
||||
|
Reference in New Issue
Block a user