mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-19 10:26:17 +00:00
fix: ui
This commit is contained in:
@@ -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;
|
||||
@@ -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.UPDATE_SERVER_VIEW, 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 />);
|
||||
|
||||
|
@@ -1,9 +1,11 @@
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
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 } from '../../../constants';
|
||||
import { EVENT_BUS_TYPE, UTC_FORMAT_DEFAULT } from '../../../constants';
|
||||
import metadataAPI from '../../../api';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import toaster from '../../../../components/toast';
|
||||
@@ -12,30 +14,29 @@ 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';
|
||||
|
||||
const ClusterPhotos = ({ markerIds, onClose }) => {
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [metadata, setMetadata] = useState({ rows: [] });
|
||||
dayjs.extend(utc);
|
||||
|
||||
const ClusterPhotos = ({ photoIds, onClose }) => {
|
||||
const { repoID, viewID, metadata: allMetadata, store, addFolder, deleteRecords } = useMetadataView();
|
||||
const { collaborators } = useCollaborators();
|
||||
|
||||
const rows = useMemo(() => getRowsByIds(allMetadata, markerIds), [allMetadata, markerIds]);
|
||||
const columns = useMemo(() => allMetadata?.columns || [], [allMetadata]);
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [metadata, setMetadata] = useState({ rows: getRowsByIds(allMetadata, photoIds), columns: allMetadata?.columns || [] });
|
||||
|
||||
const loadData = useCallback((view) => {
|
||||
setLoading(true);
|
||||
const orderRows = sortTableRows({ columns }, rows, view?.sorts || [], { collaborators });
|
||||
let metadata = new Metadata({ rows, columns, view });
|
||||
metadata.hasMore = false;
|
||||
metadata.row_ids = orderRows;
|
||||
metadata.view.rows = orderRows;
|
||||
setMetadata(metadata);
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.RESET_VIEW, metadata.view);
|
||||
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);
|
||||
}, [rows, columns, collaborators]);
|
||||
}, [metadata, collaborators]);
|
||||
|
||||
const deletedByIds = useCallback((ids) => {
|
||||
if (!Array.isArray(ids) || ids.length === 0) return;
|
||||
@@ -84,23 +85,61 @@ const ClusterPhotos = ({ markerIds, onClose }) => {
|
||||
});
|
||||
}, [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(() => {
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, true);
|
||||
const eventBus = window?.sfMetadataContext?.eventBus;
|
||||
if (!eventBus) return;
|
||||
eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, true);
|
||||
return () => {
|
||||
window?.sfMetadataContext?.eventBus?.dispatch(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, false);
|
||||
eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, false);
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeViewChange = window?.sfMetadataContext?.eventBus?.subscribe(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, 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]);
|
||||
|
||||
useEffect(() => {
|
||||
loadData({ sorts: allMetadata.view.sorts });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
if (isLoading) return (<CenteredLoading />);
|
||||
@@ -113,7 +152,7 @@ const ClusterPhotos = ({ markerIds, onClose }) => {
|
||||
};
|
||||
|
||||
ClusterPhotos.propTypes = {
|
||||
markerIds: PropTypes.array,
|
||||
photoIds: PropTypes.array,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
|
@@ -1,50 +0,0 @@
|
||||
import { mediaUrl } from '../../../../utils/constants';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
|
||||
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');
|
||||
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: 18px; height: 18px; display: block;';
|
||||
div.appendChild(icon);
|
||||
if (Utils.isDesktop()) {
|
||||
setNodeStyle(div, 'height: 40px; width: 40px; line-height: 40px');
|
||||
} else {
|
||||
setNodeStyle(div, 'height: 35px; width: 35px; line-height: 35px; opacity: 0.75');
|
||||
}
|
||||
div.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const geolocation = new BMapGL.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,68 +0,0 @@
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
|
||||
const maxZoom = 18;
|
||||
const minZoom = 3;
|
||||
|
||||
export function createBMapZoomControl(BMapGL, 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 div = document.createElement('div');
|
||||
div.className = 'sf-BMap-zoom-control';
|
||||
div.style = 'display: flex; justify-content: center; align-items: center;';
|
||||
|
||||
const zoomInButton = document.createElement('button');
|
||||
zoomInButton.className = 'sf-BMap-zoom-button btn btn-secondary';
|
||||
zoomInButton.style = 'display: flex; justify-content: center; align-items: center;';
|
||||
zoomInButton.innerHTML = '<svg class="zoom-in-icon"><use xlink:href="#plus_sign" /></svg>';
|
||||
div.appendChild(zoomInButton);
|
||||
|
||||
const divider = document.createElement('div');
|
||||
divider.style = 'height: 22px; width: 1px; background-color: #ccc;';
|
||||
div.appendChild(divider);
|
||||
|
||||
const zoomOutButton = document.createElement('button');
|
||||
zoomOutButton.className = 'sf-BMap-zoom-button btn btn-secondary';
|
||||
zoomOutButton.style = 'display: flex; justify-content: center; align-items: center;';
|
||||
zoomOutButton.innerHTML = '<svg class="zoom-out-icon"><use xlink:href="#minus_sign" /></svg>';
|
||||
div.appendChild(zoomOutButton);
|
||||
|
||||
if (Utils.isDesktop()) {
|
||||
setNodeStyle(div, 'height: 40px; width: 111px; line-height: 40px');
|
||||
} else {
|
||||
setNodeStyle(div, 'height: 35px; width: 80px; line-height: 35px; opacity: 0.75');
|
||||
}
|
||||
|
||||
const updateButtonStates = () => {
|
||||
const zoomLevel = map.getZoom();
|
||||
zoomInButton.disabled = zoomLevel >= maxZoom;
|
||||
zoomOutButton.disabled = zoomLevel <= minZoom;
|
||||
callback && callback(zoomLevel);
|
||||
};
|
||||
|
||||
zoomInButton.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const nextZoom = map.getZoom() + 2;
|
||||
map.zoomTo(Math.min(nextZoom, maxZoom));
|
||||
};
|
||||
|
||||
zoomOutButton.onclick = (e) => {
|
||||
e.preventDefault();
|
||||
const nextZoom = map.getZoom() - 2;
|
||||
map.zoomTo(Math.max(nextZoom, minZoom));
|
||||
};
|
||||
|
||||
map.addEventListener('zoomend', updateButtonStates);
|
||||
map.getContainer().appendChild(div);
|
||||
return div;
|
||||
};
|
||||
|
||||
return ZoomControl;
|
||||
}
|
||||
|
||||
function setNodeStyle(dom, styleText) {
|
||||
dom.style.cssText += styleText;
|
||||
}
|
@@ -4,136 +4,3 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map #platform div:has(.custom-avatar-overlay) {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map #platform div:has(.custom-image-overlay) {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-metadata-map-container {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .custom-image-container {
|
||||
width: 86px;
|
||||
height: 86px;
|
||||
background-color: #fff;
|
||||
padding: 3px;
|
||||
border-radius: 6px;
|
||||
position: relative;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .custom-image-container img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .custom-image-number {
|
||||
position: absolute;
|
||||
right: -16px;
|
||||
top: -16px;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
padding: 6px;
|
||||
background: #007bff;
|
||||
color: #fff;
|
||||
border-radius: 50%;
|
||||
text-align: center;
|
||||
font-size: 16px;
|
||||
line-height: 20px;
|
||||
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);
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-geolocation-control,
|
||||
.sf-metadata-view-map .sf-BMap-zoom-control {
|
||||
background-color: #fff;
|
||||
opacity: 1;
|
||||
overflow: hidden;
|
||||
border-radius: 6px;
|
||||
box-shadow: -2px -2px 4px 2px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-geolocation-control-loading {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-geolocation-control:hover,
|
||||
.sf-metadata-view-map .sf-BMap-zoom-button:not(.disabled):hover {
|
||||
background-color: #f5f5f5;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-zoom-button {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
color: #666;
|
||||
background-color: #fff;
|
||||
border: none;
|
||||
overflow: hidden;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-zoom-button .zoom-in-icon,
|
||||
.sf-metadata-view-map .sf-BMap-zoom-button .zoom-out-icon {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-zoom-button:not(:disabled):active:focus {
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-zoom-button:hover {
|
||||
color: #212529;
|
||||
}
|
||||
|
||||
.sf-metadata-view-map .sf-BMap-zoom-button:disabled {
|
||||
color: #ccc !important;
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { getFileNameFromRecord, getFileTypeFromRecord, getImageLocationFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell';
|
||||
import ClusterPhotos from './cluster-photos';
|
||||
import MapView from './map';
|
||||
import MapView from './map-view';
|
||||
import { PREDEFINED_FILE_TYPE_OPTION_KEY } from '../../constants';
|
||||
import { useMetadataView } from '../../hooks/metadata-view';
|
||||
import { Utils } from '../../../utils/utils';
|
||||
@@ -60,7 +60,7 @@ const Map = () => {
|
||||
}, []);
|
||||
|
||||
if (showCluster) {
|
||||
return (<ClusterPhotos markerIds={clusterRef.current} onClose={closeCluster} />);
|
||||
return (<ClusterPhotos photoIds={clusterRef.current} onClose={closeCluster} />);
|
||||
}
|
||||
|
||||
return (<MapView images={images} onOpenCluster={openCluster} />);
|
||||
|
@@ -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;
|
||||
}
|
@@ -1,6 +1,8 @@
|
||||
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);
|
||||
}
|
@@ -1,19 +1,23 @@
|
||||
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 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 Map = ({ images, onOpenCluster }) => {
|
||||
const MapView = ({ images, onOpenCluster }) => {
|
||||
const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
|
||||
|
||||
const mapRef = useRef(null);
|
||||
@@ -29,16 +33,13 @@ const Map = ({ images, onOpenCluster }) => {
|
||||
}, []);
|
||||
|
||||
const addMapController = useCallback(() => {
|
||||
const ZoomControl = createBMapZoomControl(window.BMapGL, saveMapState);
|
||||
const ZoomControl = createBMapZoomControl(window.BMapGL, { maxZoom: MAX_ZOOM, minZoom: MIN_ZOOM }, saveMapState);
|
||||
const zoomControl = new ZoomControl();
|
||||
const GeolocationControl = createBMapGeolocationControl(window.BMapGL, (err, point) => {
|
||||
if (!err && point) {
|
||||
mapRef.current.setCenter(point);
|
||||
}
|
||||
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]);
|
||||
@@ -124,8 +125,8 @@ const Map = ({ images, onOpenCluster }) => {
|
||||
const mapTypeValue = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_TYPE_KEY);
|
||||
mapRef.current = new window.BMapGL.Map('sf-metadata-map-container', {
|
||||
enableMapClick: false,
|
||||
minZoom: 3,
|
||||
maxZoom: 21,
|
||||
minZoom: MIN_ZOOM,
|
||||
maxZoom: MAX_ZOOM,
|
||||
mapType: getBMapType(mapTypeValue),
|
||||
});
|
||||
|
||||
@@ -175,9 +176,9 @@ const Map = ({ images, onOpenCluster }) => {
|
||||
);
|
||||
};
|
||||
|
||||
Map.propTypes = {
|
||||
MapView.propTypes = {
|
||||
images: PropTypes.array,
|
||||
onOpenCluster: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Map;
|
||||
export default MapView;
|
@@ -1,4 +1,4 @@
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import { Utils } from '../../../../../utils/utils';
|
||||
|
||||
const customImageOverlay = (center, image, callback) => {
|
||||
class ImageOverlay extends window.BMapLib.TextIconOverlay {
|
@@ -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));
|
||||
});
|
||||
|
Reference in New Issue
Block a user