mirror of
https://github.com/haiwen/seahub.git
synced 2025-09-14 14:21:23 +00:00
optimize
This commit is contained in:
@@ -623,22 +623,6 @@ class Store {
|
||||
this.applyOperation(operation);
|
||||
};
|
||||
|
||||
// map
|
||||
deleteLocationPhotos = (rows_ids) => {
|
||||
if (!Array.isArray(rows_ids) || rows_ids.length === 0) return;
|
||||
|
||||
const type = OPERATION_TYPE.DELETE_LOCATION_PHOTOS;
|
||||
const valid_rows_ids = rows_ids.filter((rowId) => {
|
||||
const row = getRowById(this.data, rowId);
|
||||
return row && this.context.canModifyRow(row);
|
||||
});
|
||||
const deleted_rows = valid_rows_ids.map((rowId) => getRowById(this.data, rowId));
|
||||
const operation = this.createOperation({
|
||||
type, repo_id: this.repoId, rows_ids, deleted_rows
|
||||
});
|
||||
this.applyOperation(operation);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
export default Store;
|
||||
|
@@ -335,21 +335,6 @@ export default function apply(data, operation) {
|
||||
return data;
|
||||
}
|
||||
|
||||
// map
|
||||
case OPERATION_TYPE.DELETE_LOCATION_PHOTOS: {
|
||||
const { rows_ids } = operation;
|
||||
const idNeedDeletedMap = rows_ids.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
|
||||
data.rows = data.rows.filter((row) => !idNeedDeletedMap[row._id]);
|
||||
data.recordsCount = data.rows.length;
|
||||
// delete rows in id_row_map
|
||||
rows_ids.forEach(rowId => {
|
||||
delete data.id_row_map[rowId];
|
||||
});
|
||||
|
||||
data.row_ids = data.row_ids.filter(row_id => !idNeedDeletedMap[row_id]);
|
||||
return data;
|
||||
}
|
||||
|
||||
default: {
|
||||
return data;
|
||||
}
|
||||
|
@@ -34,9 +34,6 @@ export const OPERATION_TYPE = {
|
||||
|
||||
// tag
|
||||
UPDATE_FILE_TAGS: 'update_file_tags',
|
||||
|
||||
// map
|
||||
DELETE_LOCATION_PHOTOS: 'delete_location_photos',
|
||||
};
|
||||
|
||||
export const COLUMN_DATA_OPERATION_TYPE = {
|
||||
@@ -77,8 +74,6 @@ export const OPERATION_ATTRIBUTES = {
|
||||
[OPERATION_TYPE.MODIFY_SETTINGS]: ['repo_id', 'view_id', 'settings'],
|
||||
|
||||
[OPERATION_TYPE.UPDATE_FILE_TAGS]: ['repo_id', 'file_tags_data'],
|
||||
|
||||
[OPERATION_TYPE.DELETE_LOCATION_PHOTOS]: ['repo_id', 'rows_ids', 'deleted_rows'],
|
||||
};
|
||||
|
||||
export const UNDO_OPERATION_TYPE = [
|
||||
|
@@ -1,114 +1,43 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import deepCopy from 'deep-copy';
|
||||
import { CenteredLoading } from '@seafile/sf-metadata-ui-component';
|
||||
import Gallery from '../../gallery/main';
|
||||
import { gettext } from '../../../../utils/constants';
|
||||
import { EVENT_BUS_TYPE, PER_LOAD_NUMBER } from '../../../constants';
|
||||
import { EVENT_BUS_TYPE } from '../../../constants';
|
||||
import metadataAPI from '../../../api';
|
||||
import { normalizeColumns } from '../../../utils/column';
|
||||
import Metadata from '../../../model/metadata';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
import toaster from '../../../../components/toast';
|
||||
import { useMetadataView } from '../../../hooks/metadata-view';
|
||||
|
||||
import './index.css';
|
||||
|
||||
const ClusterPhotos = ({ view, markerIds, onClose, onDelete }) => {
|
||||
const [isLoading, setLoading] = useState(true);
|
||||
const [metadata, setMetadata] = useState({ rows: [] });
|
||||
|
||||
const { deleteFilesCallback } = useMetadataView();
|
||||
const ClusterPhotos = ({ metadata, markerIds, onClose }) => {
|
||||
const { store, duplicateRecord, addFolder } = useMetadataView();
|
||||
|
||||
const repoID = window.sfMetadataContext.getSetting('repoID');
|
||||
|
||||
const loadData = useCallback((view) => {
|
||||
setLoading(true);
|
||||
const params = {
|
||||
view_id: view._id,
|
||||
start: 0,
|
||||
limit: PER_LOAD_NUMBER,
|
||||
};
|
||||
metadataAPI.getMetadata(repoID, params).then(res => {
|
||||
const rows = res?.data?.results || [];
|
||||
const filteredRows = rows.filter(row => markerIds.includes(row._id));
|
||||
const columns = normalizeColumns(res?.data?.metadata);
|
||||
const metadata = new Metadata({ rows: filteredRows, columns, view });
|
||||
metadata.hasMore = rows.length >= PER_LOAD_NUMBER;
|
||||
setMetadata(metadata);
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MAP_VIEW, metadata.view);
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
const errorMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMessage);
|
||||
setLoading(false);
|
||||
});
|
||||
}, [repoID, markerIds]);
|
||||
|
||||
const deletedByIds = useCallback((ids) => {
|
||||
if (!Array.isArray(ids) || ids.length === 0) return;
|
||||
const clusterMetadata = useMemo(() => {
|
||||
const filteredRows = metadata.rows.filter(row => markerIds.includes(row._id));
|
||||
const newMetadata = deepCopy(metadata);
|
||||
const idNeedDeletedMap = ids.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
|
||||
newMetadata.rows = newMetadata.rows.filter((row) => !idNeedDeletedMap[row._id]);
|
||||
newMetadata.row_ids = newMetadata.row_ids.filter((id) => !idNeedDeletedMap[id]);
|
||||
|
||||
// delete rows in id_row_map
|
||||
ids.forEach(rowId => {
|
||||
delete newMetadata.id_row_map[rowId];
|
||||
});
|
||||
newMetadata.recordsCount = newMetadata.row_ids.length;
|
||||
setMetadata(newMetadata);
|
||||
|
||||
if (newMetadata.rows.length === 0) {
|
||||
onClose && onClose();
|
||||
}
|
||||
|
||||
onDelete(ids);
|
||||
}, [metadata, onClose, onDelete]);
|
||||
newMetadata.rows = filteredRows;
|
||||
return newMetadata;
|
||||
}, [metadata, markerIds]);
|
||||
|
||||
const handelDelete = useCallback((deletedImages, { success_callback } = {}) => {
|
||||
if (!deletedImages.length) return;
|
||||
let recordIds = [];
|
||||
let paths = [];
|
||||
let fileNames = [];
|
||||
deletedImages.forEach((record) => {
|
||||
const { id, parentDir, name } = record || {};
|
||||
if (parentDir && name) {
|
||||
const path = Utils.joinPath(parentDir, name);
|
||||
paths.push(path);
|
||||
fileNames.push(name);
|
||||
recordIds.push(id);
|
||||
}
|
||||
});
|
||||
window.sfMetadataContext.batchDeleteFiles(repoID, paths).then(res => {
|
||||
deletedByIds(recordIds);
|
||||
deleteFilesCallback(paths, fileNames);
|
||||
let msg = fileNames.length > 1
|
||||
? gettext('Successfully deleted {name} and {n} other items')
|
||||
: gettext('Successfully deleted {name}');
|
||||
msg = msg.replace('{name}', fileNames[0])
|
||||
.replace('{n}', fileNames.length - 1);
|
||||
toaster.success(msg);
|
||||
success_callback && success_callback();
|
||||
}).catch(error => {
|
||||
toaster.danger(gettext('Failed to delete records'));
|
||||
});
|
||||
}, [deleteFilesCallback, repoID, deletedByIds]);
|
||||
deletedImages.forEach((record) => recordIds.push(record.id));
|
||||
store.deleteRecords(recordIds, { success_callback });
|
||||
}, [store]);
|
||||
|
||||
const handleViewChange = useCallback((update) => {
|
||||
metadataAPI.modifyView(repoID, view._id, update).then(res => {
|
||||
const newView = { ...view, ...update };
|
||||
loadData(newView);
|
||||
metadataAPI.modifyView(repoID, metadata.view._id, update).then(res => {
|
||||
|
||||
}).catch(error => {
|
||||
const errorMessage = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMessage);
|
||||
});
|
||||
}, [view, repoID, loadData]);
|
||||
|
||||
useEffect(() => {
|
||||
loadData({ _id: view._id, sorts: view.sorts });
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
}, [metadata, repoID,]);
|
||||
|
||||
useEffect(() => {
|
||||
const unsubscribeViewChange = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MAP_GALLERY_VIEW_CHANGE, handleViewChange);
|
||||
@@ -119,27 +48,22 @@ const ClusterPhotos = ({ view, markerIds, onClose, onDelete }) => {
|
||||
}, []);
|
||||
|
||||
return (
|
||||
isLoading ? (
|
||||
<CenteredLoading />
|
||||
) : (
|
||||
<div className="sf-metadata-view-map sf-metadata-map-photos-container">
|
||||
<div className="sf-metadata-map-photos-header" onClick={onClose}>
|
||||
<div className="sf-metadata-map-photos-header-back">
|
||||
<div className="sf-metadata-map-photos-header">
|
||||
<div className="sf-metadata-map-photos-header-back" onClick={onClose}>
|
||||
<i className="sf3-font sf3-font-arrow rotate-180"></i>
|
||||
</div>
|
||||
<div className="sf-metadata-map-location">{gettext('Location')}</div>
|
||||
</div>
|
||||
<Gallery metadata={metadata} onDelete={handelDelete} />
|
||||
<Gallery metadata={clusterMetadata} onDelete={handelDelete} duplicateRecord={duplicateRecord} onAddFolder={addFolder} />
|
||||
</div>
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
ClusterPhotos.propTypes = {
|
||||
view: PropTypes.object,
|
||||
metadata: PropTypes.object,
|
||||
markerIds: PropTypes.array,
|
||||
onClose: PropTypes.func,
|
||||
onDelete: PropTypes.func,
|
||||
};
|
||||
|
||||
export default ClusterPhotos;
|
||||
|
@@ -14,7 +14,7 @@ import './index.css';
|
||||
const Map = () => {
|
||||
const [showGallery, setShowGallery] = useState(false);
|
||||
const [markerIds, setMarkerIds] = useState([]);
|
||||
const { metadata, store } = useMetadataView();
|
||||
const { metadata } = useMetadataView();
|
||||
|
||||
const repoID = window.sfMetadataContext.getSetting('repoID');
|
||||
|
||||
@@ -51,10 +51,6 @@ const Map = () => {
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MAP_VIEW_TOOLBAR, false);
|
||||
}, []);
|
||||
|
||||
const onDeleteLocationPhotos = useCallback((ids) => {
|
||||
store.deleteLocationPhotos(ids);
|
||||
}, [store]);
|
||||
|
||||
useEffect(() => {
|
||||
window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MAP_VIEW, metadata.view);
|
||||
}, [metadata.view]);
|
||||
@@ -62,7 +58,7 @@ const Map = () => {
|
||||
return (
|
||||
<>
|
||||
{showGallery ? (
|
||||
<ClusterPhotos view={metadata.view} markerIds={markerIds} onClose={closeGallery} onDelete={onDeleteLocationPhotos} />
|
||||
<ClusterPhotos metadata={metadata} markerIds={markerIds} onClose={closeGallery} />
|
||||
) : (
|
||||
<Main validImages={validImages} onOpen={openGallery} />
|
||||
)}
|
||||
|
@@ -71,10 +71,26 @@ const Main = ({ validImages, onOpen }) => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
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('map-center', point);
|
||||
window.sfMetadataContext.localStorage.setItem('map-zoom', zoom);
|
||||
}, []);
|
||||
|
||||
const loadMapState = useCallback(() => {
|
||||
const savedCenter = window.sfMetadataContext.localStorage.getItem('map-center') || DEFAULT_POSITION;
|
||||
const savedZoom = window.sfMetadataContext.localStorage.getItem('map-zoom') || DEFAULT_ZOOM;
|
||||
return { center: savedCenter, zoom: savedZoom };
|
||||
}, []);
|
||||
|
||||
const onClickMarker = useCallback((e, markers) => {
|
||||
saveMapState();
|
||||
|
||||
const imageIds = markers.map(marker => marker._imageId);
|
||||
onOpen(imageIds);
|
||||
}, [onOpen]);
|
||||
}, [onOpen, saveMapState]);
|
||||
|
||||
const renderMarkersBatch = useCallback(() => {
|
||||
if (!validImages.length || !clusterRef.current) return;
|
||||
@@ -110,23 +126,23 @@ const Main = ({ validImages, onOpen }) => {
|
||||
|
||||
const renderBaiduMap = useCallback(() => {
|
||||
if (!mapRef.current || !window.BMap.Map) return;
|
||||
let mapCenter = window.sfMetadataContext.localStorage.getItem('map-center') || DEFAULT_POSITION;
|
||||
let { center, zoom } = loadMapState();
|
||||
// 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);
|
||||
center = { lng: userInfo.coords.longitude, lat: userInfo.coords.latitude };
|
||||
window.sfMetadataContext.localStorage.setItem('map-center', center);
|
||||
});
|
||||
}
|
||||
if (!isValidPosition(mapCenter?.lng, mapCenter?.lat)) return;
|
||||
if (!isValidPosition(center?.lng, center?.lat)) return;
|
||||
|
||||
const gcPosition = wgs84_to_gcj02(mapCenter.lng, mapCenter.lat);
|
||||
const gcPosition = wgs84_to_gcj02(center.lng, center.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.centerAndZoom(point, zoom);
|
||||
mapRef.current.enableScrollWheelZoom(true);
|
||||
|
||||
const savedValue = window.sfMetadataContext.localStorage.getItem('map-type');
|
||||
@@ -138,7 +154,7 @@ const Main = ({ validImages, onOpen }) => {
|
||||
|
||||
batchIndexRef.current = 0;
|
||||
renderMarkersBatch();
|
||||
}, [addMapController, initializeClusterer, initializeUserMarker, renderMarkersBatch, getMapType]);
|
||||
}, [addMapController, initializeClusterer, initializeUserMarker, renderMarkersBatch, getMapType, loadMapState]);
|
||||
|
||||
useEffect(() => {
|
||||
const switchMapTypeSubscribe = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SWITCH_MAP_TYPE, (newType) => {
|
||||
@@ -150,7 +166,7 @@ const Main = ({ validImages, onOpen }) => {
|
||||
switchMapTypeSubscribe();
|
||||
};
|
||||
|
||||
}, [getMapType]);
|
||||
}, [getMapType, saveMapState]);
|
||||
|
||||
useEffect(() => {
|
||||
if (mapInfo.type === MAP_PROVIDER.B_MAP) {
|
||||
|
@@ -30,13 +30,30 @@ const customImageOverlay = (center, image, callback) => {
|
||||
this._div.append(label);
|
||||
|
||||
const eventHandler = (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
this._callback && this._callback(event, [{ _imageId: this._imageId }]);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
@@ -580,14 +580,29 @@ var BMapLib = window.BMapLib = BMapLib || {};
|
||||
|
||||
var thatMap = this._map;
|
||||
var thatBounds = this.getBounds();
|
||||
let clickTimeout;
|
||||
this._clusterMarker.addEventListener("click", (event) => {
|
||||
// thatMap.setViewport(thatBounds);
|
||||
if (clickTimeout) {
|
||||
clearTimeout(clickTimeout);
|
||||
clickTimeout = null;
|
||||
return;
|
||||
}
|
||||
clickTimeout = setTimeout(() => {
|
||||
if (this._markerClusterer && typeof this._markerClusterer.getCallback() === 'function') {
|
||||
const markers = this._markers;
|
||||
this._markerClusterer.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
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
|
Reference in New Issue
Block a user