{}}>
+ {}}>
{children && (
diff --git a/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter.js b/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter.js
new file mode 100644
index 0000000000..2952839c39
--- /dev/null
+++ b/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter.js
@@ -0,0 +1,37 @@
+import React, { useState, useCallback, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { EVENT_BUS_TYPE, GALLERY_DATE_MODE, STORAGE_GALLERY_DATE_MODE_KEY } from '../../constants';
+import { gettext } from '../../../utils/constants';
+import RadioGroup from '../radio-group';
+
+const DATE_MODES = [
+ { value: GALLERY_DATE_MODE.YEAR, label: gettext('Year') },
+ { value: GALLERY_DATE_MODE.MONTH, label: gettext('Month') },
+ { value: GALLERY_DATE_MODE.DAY, label: gettext('Day') },
+ { value: GALLERY_DATE_MODE.ALL, label: gettext('All') },
+];
+
+const GalleryGroupBySetter = ({ view }) => {
+ const [currentMode, setCurrentMode] = useState(GALLERY_DATE_MODE.DAY);
+
+ useEffect(() => {
+ const savedValue = window.sfMetadataContext.localStorage.getItem(STORAGE_GALLERY_DATE_MODE_KEY) || GALLERY_DATE_MODE.DAY;
+ setCurrentMode(savedValue);
+ }, [view?._id]);
+
+ const handleGroupByChange = useCallback((newMode) => {
+ if (currentMode === newMode) return;
+ setCurrentMode(newMode);
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY, newMode);
+ }, [currentMode]);
+
+ return ();
+};
+
+GalleryGroupBySetter.propTypes = {
+ view: PropTypes.shape({
+ _id: PropTypes.string
+ })
+};
+
+export default GalleryGroupBySetter;
diff --git a/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.css b/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.css
deleted file mode 100644
index 69560fc4b1..0000000000
--- a/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.css
+++ /dev/null
@@ -1,57 +0,0 @@
-.metadata-gallery-group-by-setter {
- width: 272px;
- height: 36px;
- display: flex;
- justify-content: center;
- align-items: center;
- border: 1px solid #e2e2e2;
- border-radius: 3px;
-}
-
-.metadata-gallery-group-by-setter .metadata-gallery-group-by-button {
- width: 66px;
- height: 28px;
- color: #212529;
- background-color: #fff;
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 0.875rem;
- border: 0;
- border-radius: 2px;
-}
-
-.metadata-gallery-group-by-setter .metadata-gallery-group-by-button:hover {
- background-color: #f0f0f0;
- cursor: pointer;
-}
-
-.metadata-gallery-group-by-setter .metadata-gallery-group-by-button.active {
- background-color: #f5f5f5;
-}
-
-.metadata-gallery-group-by-setter .metadata-gallery-group-by-button span {
- display: block;
- text-align: center;
- width: 100%;
-}
-
-.metadata-gallery-group-by-button:not(:first-child)::before {
- content: '';
- width: 1px;
- height: 22px;
- background-color: #e2e2e2;
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
- transition: opacity 0.3s;
- left: -1px;
-}
-
-.metadata-gallery-group-by-button:hover::before,
-.metadata-gallery-group-by-button.active::before,
-.metadata-gallery-group-by-button:hover + .metadata-gallery-group-by-button::before,
-.metadata-gallery-group-by-button.active + .metadata-gallery-group-by-button::before {
- opacity: 0;
-}
diff --git a/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.js b/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.js
deleted file mode 100644
index a79364d7f4..0000000000
--- a/frontend/src/metadata/components/data-process-setter/gallery-group-by-setter/index.js
+++ /dev/null
@@ -1,51 +0,0 @@
-import React, { useState, useCallback, useEffect } from 'react';
-import PropTypes from 'prop-types';
-import classnames from 'classnames';
-import { EVENT_BUS_TYPE, GALLERY_DATE_MODE } from '../../../constants';
-import { gettext } from '../../../../utils/constants';
-
-import './index.css';
-
-const DATE_MODE_MAP = {
- [GALLERY_DATE_MODE.YEAR]: gettext('Year'),
- [GALLERY_DATE_MODE.MONTH]: gettext('Month'),
- [GALLERY_DATE_MODE.DAY]: gettext('Day'),
- [GALLERY_DATE_MODE.ALL]: gettext('All')
-};
-
-const GalleryGroupBySetter = ({ view }) => {
- const [currentMode, setCurrentMode] = useState(GALLERY_DATE_MODE.DAY);
-
- useEffect(() => {
- const savedValue = window.sfMetadataContext.localStorage.getItem('gallery-group-by', GALLERY_DATE_MODE.DAY);
- setCurrentMode(savedValue || GALLERY_DATE_MODE.DAY);
- }, [view?._id]);
-
- const handleGroupByChange = useCallback((newMode) => {
- setCurrentMode(newMode);
- window.sfMetadataContext.localStorage.setItem('gallery-group-by', newMode);
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY, newMode);
- }, []);
-
- return (
-
- {Object.entries(DATE_MODE_MAP).map(([dateMode, label]) => (
-
- ))}
-
- );
-};
-
-GalleryGroupBySetter.propTypes = {
- view: PropTypes.shape({
- _id: PropTypes.string
- })
-};
-
-export default GalleryGroupBySetter;
diff --git a/frontend/src/metadata/components/data-process-setter/index.js b/frontend/src/metadata/components/data-process-setter/index.js
index 7d87844e98..fb3425f8ab 100644
--- a/frontend/src/metadata/components/data-process-setter/index.js
+++ b/frontend/src/metadata/components/data-process-setter/index.js
@@ -1,4 +1,4 @@
-import GalleryGroupBySetter from './gallery-group-by-setter/index';
+import GalleryGroupBySetter from './gallery-group-by-setter';
import GallerySliderSetter from './gallery-slider-setter/index';
import FilterSetter from './filter-setter';
import SortSetter from './sort-setter';
diff --git a/frontend/src/metadata/components/data-process-setter/map-type-setter.js b/frontend/src/metadata/components/data-process-setter/map-type-setter.js
new file mode 100644
index 0000000000..643d64c180
--- /dev/null
+++ b/frontend/src/metadata/components/data-process-setter/map-type-setter.js
@@ -0,0 +1,35 @@
+import React, { useState, useCallback, useEffect } from 'react';
+import PropTypes from 'prop-types';
+import { EVENT_BUS_TYPE, MAP_TYPE, STORAGE_MAP_TYPE_KEY } from '../../constants';
+import { gettext } from '../../../utils/constants';
+import RadioGroup from '../radio-group';
+
+const MAP_TYPES = [
+ { value: MAP_TYPE.MAP, label: gettext('Map') },
+ { value: MAP_TYPE.SATELLITE, label: gettext('Satellite') },
+];
+
+const MapTypeSetter = ({ view }) => {
+ const [currentType, setCurrentType] = useState(MAP_TYPE.MAP);
+
+ useEffect(() => {
+ const type = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_TYPE_KEY) || MAP_TYPE.MAP;
+ setCurrentType(type);
+ }, [view?._id]);
+
+ const onChange = useCallback((type) => {
+ if (currentType === type) return;
+ setCurrentType(type);
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MODIFY_MAP_TYPE, type);
+ }, [currentType]);
+
+ return ();
+};
+
+MapTypeSetter.propTypes = {
+ view: PropTypes.shape({
+ _id: PropTypes.string
+ })
+};
+
+export default MapTypeSetter;
diff --git a/frontend/src/metadata/components/data-process-setter/map-type-setter/index.css b/frontend/src/metadata/components/data-process-setter/map-type-setter/index.css
deleted file mode 100644
index cac45ca19e..0000000000
--- a/frontend/src/metadata/components/data-process-setter/map-type-setter/index.css
+++ /dev/null
@@ -1,57 +0,0 @@
-.metadata-map-type-setter {
- width: fit-content;
- height: 36px;
- display: flex;
- justify-content: center;
- align-items: center;
- border: 1px solid #e2e2e2;
- border-radius: 3px;
-}
-
-.metadata-map-type-setter .metadata-map-type-button {
- width: 66px;
- height: 28px;
- color: #212529;
- background-color: #fff;
- position: relative;
- display: flex;
- align-items: center;
- justify-content: center;
- font-size: 0.875rem;
- border: 0;
- border-radius: 2px;
-}
-
-.metadata-map-type-setter .metadata-map-type-button:hover {
- background-color: #f0f0f0;
- cursor: pointer;
-}
-
-.metadata-map-type-setter .metadata-map-type-button.active {
- background-color: #f5f5f5;
-}
-
-.metadata-map-type-setter .metadata-map-type-button span {
- display: block;
- text-align: center;
- width: 100%;
-}
-
-.metadata-map-type-button:not(:first-child)::before {
- content: '';
- width: 1px;
- height: 22px;
- background-color: #e2e2e2;
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
- transition: opacity 0.3s;
- left: -1px;
-}
-
-.metadata-map-type-button:hover::before,
-.metadata-map-type-button.active::before,
-.metadata-map-type-button:hover + .metadata-map-type-button::before,
-.metadata-map-type-button.active + .metadata-map-type-button::before {
- opacity: 0;
-}
\ No newline at end of file
diff --git a/frontend/src/metadata/components/data-process-setter/map-type-setter/index.js b/frontend/src/metadata/components/data-process-setter/map-type-setter/index.js
deleted file mode 100644
index 8fe2cb1ba5..0000000000
--- a/frontend/src/metadata/components/data-process-setter/map-type-setter/index.js
+++ /dev/null
@@ -1,49 +0,0 @@
-import React, { useState, useCallback, useEffect } from 'react';
-import PropTypes from 'prop-types';
-import classnames from 'classnames';
-import { EVENT_BUS_TYPE, MAP_TYPE } from '../../../constants';
-import { gettext } from '../../../../utils/constants';
-
-import './index.css';
-
-const TYPE_MAP = {
- [MAP_TYPE.MAP]: gettext('Map'),
- [MAP_TYPE.SATELLITE]: gettext('Satellite')
-};
-
-const MapTypeSetter = ({ view }) => {
- const [currentType, setCurrentType] = useState(MAP_TYPE.MAP);
-
- useEffect(() => {
- const savedValue = window.sfMetadataContext.localStorage.getItem('map-type', MAP_TYPE.MAP);
- setCurrentType(savedValue || MAP_TYPE.MAP);
- }, [view?._id]);
-
- const handleTypeChange = useCallback((newType) => {
- setCurrentType(newType);
- window.sfMetadataContext.localStorage.setItem('map-type', newType);
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.SWITCH_MAP_TYPE, newType);
- }, []);
-
- return (
-
- {Object.entries(TYPE_MAP).map(([type, label]) => (
-
- ))}
-
- );
-};
-
-MapTypeSetter.propTypes = {
- view: PropTypes.shape({
- _id: PropTypes.string
- })
-};
-
-export default MapTypeSetter;
diff --git a/frontend/src/metadata/components/radio-group/index.css b/frontend/src/metadata/components/radio-group/index.css
new file mode 100644
index 0000000000..5bc9682ddb
--- /dev/null
+++ b/frontend/src/metadata/components/radio-group/index.css
@@ -0,0 +1,54 @@
+.sf-metadata-radio-group {
+ width: fit-content;
+ height: 36px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ border: 1px solid #e2e2e2;
+ border-radius: 3px;
+ padding: 0 3px;
+}
+
+.sf-metadata-radio-group .sf-metadata-radio-group-option {
+ min-width: 66px;
+ width: fit-content;
+ height: 28px;
+ color: #212529;
+ background-color: #fff;
+ position: relative;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 0.875rem;
+ border: 0;
+ border-radius: 2px;
+}
+
+.sf-metadata-radio-group .sf-metadata-radio-group-option:hover {
+ background-color: #f0f0f0;
+ cursor: pointer;
+}
+
+.sf-metadata-radio-group .sf-metadata-radio-group-option.active {
+ background-color: #f5f5f5;
+}
+
+.sf-metadata-radio-group .sf-metadata-radio-group-option:not(:first-child)::before {
+ content: '';
+ width: 1px;
+ height: 22px;
+ background-color: #e2e2e2;
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ transition: opacity 0.3s;
+ left: -1px;
+}
+
+.sf-metadata-radio-group .sf-metadata-radio-group-option:hover::before,
+.sf-metadata-radio-group .sf-metadata-radio-group-option.active::before,
+.sf-metadata-radio-group .sf-metadata-radio-group-option:hover + .sf-metadata-radio-group-option::before,
+.sf-metadata-radio-group .sf-metadata-radio-group-option.active + .sf-metadata-radio-group-option::before {
+ opacity: 0;
+}
+
diff --git a/frontend/src/metadata/components/radio-group/index.js b/frontend/src/metadata/components/radio-group/index.js
new file mode 100644
index 0000000000..4718e3409a
--- /dev/null
+++ b/frontend/src/metadata/components/radio-group/index.js
@@ -0,0 +1,46 @@
+
+import React, { useCallback, useMemo } from 'react';
+import PropTypes from 'prop-types';
+import classnames from 'classnames';
+
+import './index.css';
+
+const RadioGroup = ({ value, options, className, onChange: onChangeAPI }) => {
+ const selected = useMemo(() => {
+ const selectedOption = options.find(o => value === o.value) || options[0];
+ return selectedOption.value;
+ }, [value, options]);
+
+ const onChange = useCallback((event) => {
+ const newValue = event.target.dataset.option;
+ if (selected === newValue) return;
+ onChangeAPI(newValue);
+ }, [selected, onChangeAPI]);
+
+ return (
+
+ {options.map(option => {
+ const { value, label } = option;
+ return (
+
+ {label}
+
+ );
+ })}
+
+ );
+};
+
+RadioGroup.propTypes = {
+ value: PropTypes.string,
+ options: PropTypes.array,
+ className: PropTypes.string,
+ onChange: PropTypes.func,
+};
+
+export default RadioGroup;
diff --git a/frontend/src/metadata/components/view-toolbar/face-recognition/index.js b/frontend/src/metadata/components/view-toolbar/face-recognition/index.js
index 601aab0a59..8023d77f5f 100644
--- a/frontend/src/metadata/components/view-toolbar/face-recognition/index.js
+++ b/frontend/src/metadata/components/view-toolbar/face-recognition/index.js
@@ -17,17 +17,17 @@ const FaceRecognitionViewToolbar = ({ readOnly, isCustomPermission, onToggleDeta
setShow(isShow);
}, []);
- const setRecognitionView = useCallback(view => {
+ const resetView = useCallback(view => {
setView(view);
}, []);
const modifySorts = useCallback((sorts) => {
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.FACE_RECOGNITION_VIEW_CHANGE, { sorts });
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, { sorts });
}, []);
useEffect(() => {
const unsubscribeToggle = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, onToggle);
- const unsubscribeView = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.FACE_RECOGNITION_VIEW, setRecognitionView);
+ const unsubscribeView = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.RESET_VIEW, resetView);
return () => {
unsubscribeToggle && unsubscribeToggle();
unsubscribeView && unsubscribeView();
diff --git a/frontend/src/metadata/components/view-toolbar/index.js b/frontend/src/metadata/components/view-toolbar/index.js
index 7227ecea75..8ced5ef287 100644
--- a/frontend/src/metadata/components/view-toolbar/index.js
+++ b/frontend/src/metadata/components/view-toolbar/index.js
@@ -94,8 +94,8 @@ const ViewToolBar = ({ viewId, isCustomPermission, onToggleDetail, onCloseDetail
)}
{viewType === VIEW_TYPE.FACE_RECOGNITION && (
)}
@@ -113,8 +113,9 @@ const ViewToolBar = ({ viewId, isCustomPermission, onToggleDetail, onCloseDetail
)}
{viewType === VIEW_TYPE.MAP && (
view.type, [view]);
+ const viewType = useMemo(() => VIEW_TYPE.MAP, []);
const viewColumns = useMemo(() => {
if (!view) return [];
return view.columns;
@@ -29,69 +30,73 @@ const MapViewToolBar = ({
}, []);
const modifySorts = useCallback((sorts) => {
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MAP_GALLERY_VIEW_CHANGE, { sorts });
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, { sorts });
}, []);
- const setMapView = useCallback(view => setView(view), []);
+ const resetView = useCallback(view => {
+ setView(view);
+ }, []);
useEffect(() => {
- const unsubscribeToggleViewToolbarMode = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_MAP_VIEW_TOOLBAR, onToggle);
- const unsubscribeView = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MAP_VIEW, setMapView);
+ const unsubscribeToggle = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, onToggle);
+ const unsubscribeView = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.RESET_VIEW, resetView);
return () => {
- unsubscribeToggleViewToolbarMode();
- unsubscribeView();
+ unsubscribeToggle && unsubscribeToggle();
+ unsubscribeView && unsubscribeView();
};
- }, [setMapView, onToggle]);
-
- useEffect(() => {
- setView(window.sfMetadataStore.data.view);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
- return (
- <>
- {showGalleryToolbar ? (
+ useEffect(() => {
+ setShowGalleryToolbar(false);
+ }, [viewID]);
+
+ if (showGalleryToolbar) {
+ return (
+ <>
- <>
-
-
-
- >
+
+
+
{!isCustomPermission && (
)}
- ) :
- <>
-
-
-
-
-
- >}
+
+ >
+ );
+ }
+
+ return (
+ <>
+
+
+
+
+
>
);
};
diff --git a/frontend/src/metadata/constants/event-bus-type.js b/frontend/src/metadata/constants/event-bus-type.js
index 9cc80cf45b..43d479dea4 100644
--- a/frontend/src/metadata/constants/event-bus-type.js
+++ b/frontend/src/metadata/constants/event-bus-type.js
@@ -60,22 +60,21 @@ export const EVENT_BUS_TYPE = {
SAVED: 'saved',
ERROR: 'error',
+ // view
+ TOGGLE_VIEW_TOOLBAR: 'toggle_view_toolbar',
+ RESET_VIEW: 'reset_view',
+ UPDATE_SERVER_VIEW: 'update_server_view',
+
// gallery
MODIFY_GALLERY_ZOOM_GEAR: 'modify_gallery_zoom_gear',
SWITCH_GALLERY_GROUP_BY: 'switch_gallery_group_by',
- // face recognition
- TOGGLE_VIEW_TOOLBAR: 'toggle_view_toolbar',
- FACE_RECOGNITION_VIEW: 'face_recognition_view',
- FACE_RECOGNITION_VIEW_CHANGE: 'face_recognition_view_change',
-
// kanban
TOGGLE_KANBAN_SETTINGS: 'toggle_kanban_settings',
OPEN_KANBAN_SETTINGS: 'open_kanban_settings',
CLOSE_KANBAN_SETTINGS: 'close_kanban_settings',
// map
- SWITCH_MAP_TYPE: 'switch_map_type',
- TOGGLE_MAP_VIEW_TOOLBAR: 'toggle_map_view_toolbar',
+ MODIFY_MAP_TYPE: 'modify_map_type',
MAP_VIEW: 'map_view',
};
diff --git a/frontend/src/metadata/constants/index.js b/frontend/src/metadata/constants/index.js
index 0b83427af8..e69790e7eb 100644
--- a/frontend/src/metadata/constants/index.js
+++ b/frontend/src/metadata/constants/index.js
@@ -1,4 +1,3 @@
-import CellType from './column/type';
import { EVENT_BUS_TYPE } from './event-bus-type';
import TRANSFER_TYPES from './TransferTypes';
import * as metadataZIndexes from './z-index';
@@ -13,147 +12,8 @@ export * from './sort';
export * from './error';
export * from './view';
-export const CELL_NAVIGATION_MODE = {
- NONE: 'none',
- CHANGE_ROW: 'changeRow',
- LOOP_OVER_ROW: 'loopOverRow',
-};
-
-export const SEQUENCE_COLUMN_WIDTH = 80;
-
-export const ROW_HEIGHT = 32;
-
-export const GRID_HEADER_DEFAULT_HEIGHT = 32;
-
-export const GRID_HEADER_DOUBLE_HEIGHT = 56;
-
-export const GROUP_VIEW_OFFSET = 16;
-
-export const GROUP_HEADER_HEIGHT = 48;
-
-export const TABLE_LEFT_MARGIN = 10;
-
-export const TABLE_BORDER_WIDTH = 1;
-
-export const UNABLE_TO_CALCULATE = '--';
-
-export const FROZEN_COLUMN_SHADOW = '2px 0 5px -2px hsla(0,0%,53.3%,.3)';
-
-export const TABLE_NOT_SUPPORT_EDIT_TYPE_MAP = {
- [CellType.CREATOR]: true,
- [CellType.LAST_MODIFIER]: true,
- [CellType.CTIME]: true,
- [CellType.MTIME]: true,
- [CellType.FILE_NAME]: true,
-};
-
-export const TABLE_SUPPORT_EDIT_TYPE_MAP = {
- [CellType.TEXT]: true,
- [CellType.DATE]: true,
- [CellType.NUMBER]: true,
- [CellType.SINGLE_SELECT]: true,
- [CellType.MULTIPLE_SELECT]: true,
- [CellType.COLLABORATOR]: true,
- [CellType.CHECKBOX]: true,
- [CellType.LONG_TEXT]: true,
- [CellType.LINK]: true,
- [CellType.TAGS]: true,
-};
-
-export const TABLE_MOBILE_SUPPORT_EDIT_CELL_TYPE_MAP = {
- [CellType.TEXT]: true,
-};
-
-export const CANVAS_RIGHT_INTERVAL = 44;
-
-export const LEFT_NAV = 280;
-export const ROW_DETAIL_PADDING = 40 * 2;
-export const ROW_DETAIL_MARGIN = 20 * 2;
-export const EDITOR_PADDING = 1.5 * 16; // 1.5: 0.75 * 2
-
-export const COLUMN_RATE_MAX_NUMBER = [
- { name: 1 },
- { name: 2 },
- { name: 3 },
- { name: 4 },
- { name: 5 },
- { name: 6 },
- { name: 7 },
- { name: 8 },
- { name: 9 },
- { name: 10 },
-];
-
-export const GROUP_ROW_TYPE = {
- GROUP_CONTAINER: 'group_container',
- ROW: 'row',
- BTN_INSERT_ROW: 'btn_insert_row',
-};
-
-export const INSERT_ROW_HEIGHT = 32;
-
-export const CHANGE_HEADER_WIDTH = 'CHANGE_HEADER_WIDTH';
-
-export const NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES = [
-];
-
-export const SUPPORT_PREVIEW_COLUMN_TYPES = [];
-
-export const OVER_SCAN_COLUMNS = 10;
-
-export const DELETED_OPTION_BACKGROUND_COLOR = '#eaeaea';
-
-export const DELETED_OPTION_TIPS = 'deleted_option';
-
-export const SUPPORT_BATCH_DOWNLOAD_TYPES = [];
-
-export const PER_LOAD_NUMBER = 1000;
-
-export const DEFAULT_RETRY_TIMES = 4;
-
-export const DEFAULT_RETRY_INTERVAL = 1000;
-
-export const MAX_LOAD_NUMBER = 10000;
-
-export const EDITOR_TYPE = {
- PREVIEWER: 'previewer',
- ADDITION: 'addition',
-};
-
export {
EVENT_BUS_TYPE,
TRANSFER_TYPES,
metadataZIndexes,
};
-
-export const DATE_TAG_HEIGHT = 44;
-
-export const GALLERY_ZOOM_GEAR_MIN = -2;
-
-export const GALLERY_ZOOM_GEAR_MAX = 2;
-
-export const GALLERY_IMAGE_GAP = 2;
-
-export const GALLERY_DATE_MODE = {
- YEAR: 'year',
- MONTH: 'month',
- DAY: 'day',
- ALL: 'all',
-};
-
-export const MAP_TYPE = {
- NORMAL_MAP: 'normal_map',
- SATELLITE: 'satellite',
-};
-
-export const MAP_VIEW_TOOLBAR_MODE = {
- MAP: 'map',
- GALLERY: 'gallery',
-};
-
-export const UNCATEGORIZED = '_uncategorized';
-
-export const PASTE_SOURCE = {
- COPY: 'copy',
- CUT: 'cut',
-};
diff --git a/frontend/src/metadata/constants/view/gallery.js b/frontend/src/metadata/constants/view/gallery.js
new file mode 100644
index 0000000000..61f1b82b35
--- /dev/null
+++ b/frontend/src/metadata/constants/view/gallery.js
@@ -0,0 +1,16 @@
+export const DATE_TAG_HEIGHT = 44;
+
+export const GALLERY_ZOOM_GEAR_MIN = -2;
+
+export const GALLERY_ZOOM_GEAR_MAX = 2;
+
+export const GALLERY_IMAGE_GAP = 2;
+
+export const GALLERY_DATE_MODE = {
+ YEAR: 'year',
+ MONTH: 'month',
+ DAY: 'day',
+ ALL: 'all',
+};
+
+export const STORAGE_GALLERY_DATE_MODE_KEY = 'gallery_date_mode';
diff --git a/frontend/src/metadata/constants/view.js b/frontend/src/metadata/constants/view/index.js
similarity index 95%
rename from frontend/src/metadata/constants/view.js
rename to frontend/src/metadata/constants/view/index.js
index 1cede63b77..264711c992 100644
--- a/frontend/src/metadata/constants/view.js
+++ b/frontend/src/metadata/constants/view/index.js
@@ -1,8 +1,13 @@
-import { PRIVATE_COLUMN_KEY } from './column';
-import { FILTER_PREDICATE_TYPE } from './filter';
+import { PRIVATE_COLUMN_KEY } from '../column';
+import { FILTER_PREDICATE_TYPE } from '../filter';
import { SORT_COLUMN_OPTIONS, GALLERY_SORT_COLUMN_OPTIONS, GALLERY_FIRST_SORT_COLUMN_OPTIONS, SORT_TYPE,
GALLERY_SORT_PRIVATE_COLUMN_KEYS, GALLERY_FIRST_SORT_PRIVATE_COLUMN_KEYS,
-} from './sort';
+} from '../sort';
+
+export * from './gallery';
+export * from './kanban';
+export * from './map';
+export * from './table';
export const METADATA_VIEWS_KEY = 'sf-metadata-views';
diff --git a/frontend/src/metadata/constants/view/kanban.js b/frontend/src/metadata/constants/view/kanban.js
new file mode 100644
index 0000000000..d9d7085b5e
--- /dev/null
+++ b/frontend/src/metadata/constants/view/kanban.js
@@ -0,0 +1 @@
+export const UNCATEGORIZED = '_uncategorized';
diff --git a/frontend/src/metadata/constants/view/map.js b/frontend/src/metadata/constants/view/map.js
new file mode 100644
index 0000000000..d9511628ff
--- /dev/null
+++ b/frontend/src/metadata/constants/view/map.js
@@ -0,0 +1,15 @@
+export const MAP_TYPE = {
+ MAP: 'map',
+ SATELLITE: 'satellite',
+};
+
+export const STORAGE_MAP_TYPE_KEY = 'map_type';
+
+export const STORAGE_MAP_CENTER_KEY = 'map_center';
+
+export const STORAGE_MAP_ZOOM_KEY = 'map_zoom';
+
+export const MAP_VIEW_TOOLBAR_MODE = {
+ MAP: 'map',
+ GALLERY: 'gallery',
+};
diff --git a/frontend/src/metadata/constants/view/table.js b/frontend/src/metadata/constants/view/table.js
new file mode 100644
index 0000000000..aab12d2610
--- /dev/null
+++ b/frontend/src/metadata/constants/view/table.js
@@ -0,0 +1,101 @@
+import { CellType } from '../column';
+
+export const CELL_NAVIGATION_MODE = {
+ NONE: 'none',
+ CHANGE_ROW: 'changeRow',
+ LOOP_OVER_ROW: 'loopOverRow',
+};
+
+export const SEQUENCE_COLUMN_WIDTH = 80;
+
+export const ROW_HEIGHT = 32;
+
+export const GRID_HEADER_DEFAULT_HEIGHT = 32;
+
+export const GRID_HEADER_DOUBLE_HEIGHT = 56;
+
+export const GROUP_VIEW_OFFSET = 16;
+
+export const GROUP_HEADER_HEIGHT = 48;
+
+export const TABLE_LEFT_MARGIN = 10;
+
+export const TABLE_BORDER_WIDTH = 1;
+
+export const UNABLE_TO_CALCULATE = '--';
+
+export const FROZEN_COLUMN_SHADOW = '2px 0 5px -2px hsla(0,0%,53.3%,.3)';
+
+export const TABLE_NOT_SUPPORT_EDIT_TYPE_MAP = {
+ [CellType.CREATOR]: true,
+ [CellType.LAST_MODIFIER]: true,
+ [CellType.CTIME]: true,
+ [CellType.MTIME]: true,
+ [CellType.FILE_NAME]: true,
+};
+
+export const TABLE_SUPPORT_EDIT_TYPE_MAP = {
+ [CellType.TEXT]: true,
+ [CellType.DATE]: true,
+ [CellType.NUMBER]: true,
+ [CellType.SINGLE_SELECT]: true,
+ [CellType.MULTIPLE_SELECT]: true,
+ [CellType.COLLABORATOR]: true,
+ [CellType.CHECKBOX]: true,
+ [CellType.LONG_TEXT]: true,
+ [CellType.LINK]: true,
+ [CellType.TAGS]: true,
+};
+
+export const TABLE_MOBILE_SUPPORT_EDIT_CELL_TYPE_MAP = {
+ [CellType.TEXT]: true,
+};
+
+export const CANVAS_RIGHT_INTERVAL = 44;
+
+export const LEFT_NAV = 280;
+export const ROW_DETAIL_PADDING = 40 * 2;
+export const ROW_DETAIL_MARGIN = 20 * 2;
+export const EDITOR_PADDING = 1.5 * 16; // 1.5: 0.75 * 2
+
+export const GROUP_ROW_TYPE = {
+ GROUP_CONTAINER: 'group_container',
+ ROW: 'row',
+ BTN_INSERT_ROW: 'btn_insert_row',
+};
+
+export const INSERT_ROW_HEIGHT = 32;
+
+export const CHANGE_HEADER_WIDTH = 'CHANGE_HEADER_WIDTH';
+
+export const NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES = [
+];
+
+export const SUPPORT_PREVIEW_COLUMN_TYPES = [];
+
+export const OVER_SCAN_COLUMNS = 10;
+
+export const DELETED_OPTION_BACKGROUND_COLOR = '#eaeaea';
+
+export const DELETED_OPTION_TIPS = 'deleted_option';
+
+export const SUPPORT_BATCH_DOWNLOAD_TYPES = [];
+
+export const PER_LOAD_NUMBER = 1000;
+
+export const DEFAULT_RETRY_TIMES = 4;
+
+export const DEFAULT_RETRY_INTERVAL = 1000;
+
+export const MAX_LOAD_NUMBER = 10000;
+
+export const EDITOR_TYPE = {
+ PREVIEWER: 'previewer',
+ ADDITION: 'addition',
+};
+
+export const PASTE_SOURCE = {
+ COPY: 'copy',
+ CUT: 'cut',
+};
+
diff --git a/frontend/src/metadata/hooks/metadata-view.js b/frontend/src/metadata/hooks/metadata-view.js
index f7397091ba..1b0ee43808 100644
--- a/frontend/src/metadata/hooks/metadata-view.js
+++ b/frontend/src/metadata/hooks/metadata-view.js
@@ -340,6 +340,8 @@ export const MetadataViewProvider = ({
{
const errorMessage = Utils.getErrorMsg(error);
@@ -164,7 +164,7 @@ const PeoplePhotos = ({ view, people, onClose, onDeletePeoplePhotos, onRemovePeo
}, []);
useEffect(() => {
- const unsubscribeViewChange = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.FACE_RECOGNITION_VIEW_CHANGE, onViewChange);
+ const unsubscribeViewChange = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, onViewChange);
return () => {
unsubscribeViewChange && unsubscribeViewChange();
};
diff --git a/frontend/src/metadata/views/gallery/context-menu/index.js b/frontend/src/metadata/views/gallery/context-menu/index.js
index 307d39d8c7..756989506b 100644
--- a/frontend/src/metadata/views/gallery/context-menu/index.js
+++ b/frontend/src/metadata/views/gallery/context-menu/index.js
@@ -30,17 +30,17 @@ const GalleryContextMenu = ({ metadata, selectedImages, onDelete, onDuplicate, a
const options = useMemo(() => {
let validOptions = [{ value: CONTEXT_MENU_KEY.DOWNLOAD, label: gettext('Download') }];
- if (checkCanDeleteRow) {
+ if (onDelete && checkCanDeleteRow) {
validOptions.push({ value: CONTEXT_MENU_KEY.DELETE, label: selectedImages.length > 1 ? gettext('Delete') : gettext('Delete file') });
}
- if (canDuplicateRow && selectedImages.length === 1) {
+ if (onDuplicate && canDuplicateRow && selectedImages.length === 1) {
validOptions.push({ value: CONTEXT_MENU_KEY.DUPLICATE, label: gettext('Duplicate') });
}
- if (canRemovePhotoFromPeople) {
+ if (onRemoveImage && canRemovePhotoFromPeople) {
validOptions.push({ value: CONTEXT_MENU_KEY.REMOVE, label: gettext('Remove from this group') });
}
return validOptions;
- }, [checkCanDeleteRow, canDuplicateRow, canRemovePhotoFromPeople, selectedImages]);
+ }, [checkCanDeleteRow, canDuplicateRow, canRemovePhotoFromPeople, selectedImages, onDuplicate, onDelete, onRemoveImage]);
const closeZipDialog = () => {
setIsZipDialogOpen(false);
diff --git a/frontend/src/metadata/views/gallery/main.js b/frontend/src/metadata/views/gallery/main.js
index 1b82a10dc7..76ba2b1f59 100644
--- a/frontend/src/metadata/views/gallery/main.js
+++ b/frontend/src/metadata/views/gallery/main.js
@@ -8,7 +8,7 @@ import { useMetadataView } from '../../hooks/metadata-view';
import { Utils } from '../../../utils/utils';
import { getDateDisplayString, getFileNameFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell';
import { siteRoot, fileServerRoot, thumbnailSizeForGrid, thumbnailSizeForOriginal } from '../../../utils/constants';
-import { EVENT_BUS_TYPE, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP } from '../../constants';
+import { EVENT_BUS_TYPE, GALLERY_DATE_MODE, DATE_TAG_HEIGHT, GALLERY_IMAGE_GAP, STORAGE_GALLERY_DATE_MODE_KEY } from '../../constants';
import { getRowById } from '../../utils/table';
import { getEventClassName } from '../../utils/common';
import GalleryContextmenu from './context-menu';
@@ -129,14 +129,14 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
const gear = window.sfMetadataContext.localStorage.getItem('zoom-gear', 0) || 0;
setZoomGear(gear);
- const mode = window.sfMetadataContext.localStorage.getItem('gallery-group-by', GALLERY_DATE_MODE.DAY) || GALLERY_DATE_MODE.DAY;
+ const mode = window.sfMetadataContext.localStorage.getItem(STORAGE_GALLERY_DATE_MODE_KEY, GALLERY_DATE_MODE.DAY) || GALLERY_DATE_MODE.DAY;
setMode(mode);
const switchGalleryModeSubscribe = window.sfMetadataContext.eventBus.subscribe(
EVENT_BUS_TYPE.SWITCH_GALLERY_GROUP_BY,
(mode) => {
setMode(mode);
- window.sfMetadataContext.localStorage.setItem('gallery-group-by', mode);
+ window.sfMetadataContext.localStorage.setItem(STORAGE_GALLERY_DATE_MODE_KEY, mode);
}
);
@@ -193,7 +193,7 @@ const Main = ({ isLoadingMore, metadata, onDelete, onLoadMore, duplicateRecord,
if (!containerRef.current) return;
const { scrollTop, scrollHeight, clientHeight } = containerRef.current;
if (scrollTop + clientHeight >= scrollHeight - 10) {
- onLoadMore();
+ onLoadMore && onLoadMore();
} else {
const { scrollTop, clientHeight } = containerRef.current;
const overScanTop = Math.max(0, scrollTop - (imageSize + GALLERY_IMAGE_GAP) * OVER_SCAN_ROWS);
diff --git a/frontend/src/metadata/views/map/cluster-photos/index.css b/frontend/src/metadata/views/map/cluster-photos/index.css
index b2f844b537..e190c9727a 100644
--- a/frontend/src/metadata/views/map/cluster-photos/index.css
+++ b/frontend/src/metadata/views/map/cluster-photos/index.css
@@ -1,54 +1,4 @@
.sf-metadata-map-photos-container {
padding: 0 !important;
- overflow-y: hidden !important;
-}
-
-.sf-metadata-map-photos-container .sf-metadata-map-photos-header {
- height: 48px;
- display: flex;
- align-items: center;
- padding: 0 16px;
-}
-
-.sf-metadata-map-photos-container .sf-metadata-icon-btn {
- margin-left: -4px;
- border-radius: 3px;
-}
-
-.sf-metadata-map-photos-container .sf-metadata-icon-btn:hover {
- background-color: #EFEFEF;
- cursor: pointer;
-}
-
-.sf-metadata-map-photos-container .sf-metadata-map-photos-header .sf-metadata-map-photos-header-back {
- font-size: 14px;
- height: 24px;
- min-width: 24px;
- display: flex;
- align-items: center;
- justify-content: center;
- margin-left: -5px;
- border-radius: 3px;
-}
-
-.sf-metadata-map-photos-container .sf-metadata-map-photos-header .sf-metadata-map-photos-header-back:hover {
- background-color: #EFEFEF;
- cursor: pointer;
-}
-
-.sf-metadata-map-photos-container .sf-metadata-map-photos-header-back .sf3-font-arrow {
- color: #666;
- font-size: 14px !important;
-}
-
-.sf-metadata-map-photos-container .sf-metadata-map-photos-header .sf-metadata-map-location {
- margin-left: 4px;
- flex: 1;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
-}
-
-.sf-metadata-map-photos-container .sf-metadata-gallery-container {
- height: calc(100% - 48px);
+ overflow: hidden !important;
}
diff --git a/frontend/src/metadata/views/map/cluster-photos/index.js b/frontend/src/metadata/views/map/cluster-photos/index.js
index 4fc69e70c2..c69fe7c352 100644
--- a/frontend/src/metadata/views/map/cluster-photos/index.js
+++ b/frontend/src/metadata/views/map/cluster-photos/index.js
@@ -1,67 +1,113 @@
-import React, { useCallback, useEffect, useMemo } from 'react';
+import React, { useCallback, useEffect, useMemo, useState } 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 } from '../../../constants';
import metadataAPI from '../../../api';
import { Utils } from '../../../../utils/utils';
import toaster from '../../../../components/toast';
import { useMetadataView } from '../../../hooks/metadata-view';
+import { getRowsByIds } from '../../../utils/table';
+import Metadata from '../../../model/metadata';
+import { sortTableRows } from '../../../utils/sort';
+import { useCollaborators } from '../../../hooks/collaborators';
import './index.css';
-const ClusterPhotos = ({ metadata, markerIds, onClose }) => {
- const { store, duplicateRecord, addFolder } = useMetadataView();
+const ClusterPhotos = ({ markerIds, onClose }) => {
+ const [isLoading, setLoading] = useState(true);
+ const [metadata, setMetadata] = useState({ rows: [] });
- const repoID = window.sfMetadataContext.getSetting('repoID');
+ const { repoID, viewID, metadata: allMetadata, store, addFolder, deleteRecords } = useMetadataView();
+ const { collaborators } = useCollaborators();
- const clusterMetadata = useMemo(() => {
- const filteredRows = metadata.rows.filter(row => markerIds.includes(row._id));
+ const rows = useMemo(() => getRowsByIds(allMetadata, markerIds), [allMetadata, markerIds]);
+ const columns = useMemo(() => allMetadata?.columns || [], [allMetadata]);
+
+ 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);
+ setLoading(false);
+ }, [rows, columns, collaborators]);
+
+ const deletedByIds = useCallback((ids) => {
+ if (!Array.isArray(ids) || ids.length === 0) return;
const newMetadata = deepCopy(metadata);
- newMetadata.rows = filteredRows;
- return newMetadata;
- }, [metadata, markerIds]);
+ const idNeedDeletedMap = ids.reduce((currIdNeedDeletedMap, rowId) => ({ ...currIdNeedDeletedMap, [rowId]: true }), {});
+ newMetadata.rows = newMetadata.rows.filter((row) => !idNeedDeletedMap[row._id]);
+ newMetadata.row_ids = newMetadata.row_ids.filter((id) => !idNeedDeletedMap[id]);
+
+ // delete rows in id_row_map
+ ids.forEach(rowId => {
+ delete newMetadata.id_row_map[rowId];
+ });
+ newMetadata.recordsCount = newMetadata.row_ids.length;
+ setMetadata(newMetadata);
+
+ if (newMetadata.rows.length === 0) {
+ onClose && onClose();
+ }
+ }, [metadata, onClose]);
const handelDelete = useCallback((deletedImages, { success_callback } = {}) => {
if (!deletedImages.length) return;
let recordIds = [];
- deletedImages.forEach((record) => recordIds.push(record.id));
- store.deleteRecords(recordIds, { success_callback });
- }, [store]);
-
- const handleViewChange = useCallback((update) => {
- metadataAPI.modifyView(repoID, metadata.view._id, update).then(res => {
+ deletedImages.forEach((record) => {
+ const { id, parentDir, name } = record || {};
+ if (parentDir && name) {
+ recordIds.push(id);
+ }
+ });
+ deleteRecords(recordIds, {
+ success_callback: () => {
+ success_callback();
+ deletedByIds(recordIds);
+ }
+ });
+ }, [deleteRecords, deletedByIds]);
+ const onViewChange = useCallback((update) => {
+ metadataAPI.modifyView(repoID, viewID, update).then(res => {
+ store.modifyLocalView(update);
+ const newView = { ...metadata.view, ...update };
+ loadData(newView);
}).catch(error => {
const errorMessage = Utils.getErrorMsg(error);
toaster.danger(errorMessage);
});
- }, [metadata, repoID,]);
+ }, [metadata, repoID, viewID, store, loadData]);
useEffect(() => {
- const unsubscribeViewChange = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MAP_GALLERY_VIEW_CHANGE, handleViewChange);
+ const unsubscribeViewChange = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.UPDATE_SERVER_VIEW, onViewChange);
+ window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, true);
return () => {
unsubscribeViewChange();
+ window?.sfMetadataContext?.eventBus?.dispatch(EVENT_BUS_TYPE.TOGGLE_VIEW_TOOLBAR, false);
};
+ }, [onViewChange]);
+
+ useEffect(() => {
+ loadData({ sorts: allMetadata.view.sorts });
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
+ if (isLoading) return ();
+
return (
-
-
-
-
-
{gettext('Location')}
-
-
+
);
};
ClusterPhotos.propTypes = {
- metadata: PropTypes.object,
markerIds: PropTypes.array,
onClose: PropTypes.func,
};
diff --git a/frontend/src/metadata/views/map/index.js b/frontend/src/metadata/views/map/index.js
index a4d51bf9eb..9e55604ec1 100644
--- a/frontend/src/metadata/views/map/index.js
+++ b/frontend/src/metadata/views/map/index.js
@@ -1,24 +1,26 @@
-import React, { useCallback, useEffect, useMemo, useState } from 'react';
+import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import { getFileNameFromRecord, getFileTypeFromRecord, getImageLocationFromRecord, getParentDirFromRecord, getRecordIdFromRecord } from '../../utils/cell';
import ClusterPhotos from './cluster-photos';
-import Main from './main';
-import { EVENT_BUS_TYPE, PREDEFINED_FILE_TYPE_OPTION_KEY } from '../../constants';
+import MapView from './map';
+import { PREDEFINED_FILE_TYPE_OPTION_KEY } from '../../constants';
import { useMetadataView } from '../../hooks/metadata-view';
import { Utils } from '../../../utils/utils';
-import { siteRoot, thumbnailSizeForGrid } from '../../../utils/constants';
+import { gettext, siteRoot, thumbnailSizeForGrid } from '../../../utils/constants';
import { isValidPosition } from '../../utils/validate';
import { gcj02_to_bd09, wgs84_to_gcj02 } from '../../../utils/coord-transform';
+import { PRIVATE_FILE_TYPE } from '../../../constants';
import './index.css';
const Map = () => {
- const [showGallery, setShowGallery] = useState(false);
- const [markerIds, setMarkerIds] = useState([]);
- const { metadata } = useMetadataView();
+ const [showCluster, setShowCluster] = useState(false);
+ const { metadata, viewID, updateCurrentPath } = useMetadataView();
+
+ const clusterRef = useRef([]);
const repoID = window.sfMetadataContext.getSetting('repoID');
- const validImages = useMemo(() => {
+ const images = useMemo(() => {
return metadata.rows
.map(record => {
const recordType = getFileTypeFromRecord(record);
@@ -39,31 +41,28 @@ const Map = () => {
.filter(Boolean);
}, [repoID, metadata.rows]);
- const openGallery = useCallback((cluster_marker_ids) => {
- setMarkerIds(cluster_marker_ids);
- setShowGallery(true);
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MAP_VIEW_TOOLBAR, true);
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MAP_VIEW, metadata.view);
- }, [metadata.view]);
+ const openCluster = useCallback((clusterIds) => {
+ clusterRef.current = clusterIds;
+ updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}/${gettext('Location')}`);
+ setShowCluster(true);
+ }, [viewID, updateCurrentPath]);
- const closeGallery = useCallback(() => {
- setShowGallery(false);
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.TOGGLE_MAP_VIEW_TOOLBAR, false);
- }, []);
+ const closeCluster = useCallback(() => {
+ clusterRef.current = [];
+ updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}`);
+ setShowCluster(false);
+ }, [viewID, updateCurrentPath]);
useEffect(() => {
- window.sfMetadataContext.eventBus.dispatch(EVENT_BUS_TYPE.MAP_VIEW, metadata.view);
- }, [metadata.view]);
+ updateCurrentPath(`/${PRIVATE_FILE_TYPE.FILE_EXTENDED_PROPERTIES}/${viewID}`);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [viewID]);
- return (
- <>
- {showGallery ? (
-
- ) : (
-
- )}
- >
- );
+ if (showCluster) {
+ return ();
+ }
+
+ return ();
};
export default Map;
diff --git a/frontend/src/metadata/views/map/main.js b/frontend/src/metadata/views/map/map.js
similarity index 75%
rename from frontend/src/metadata/views/map/main.js
rename to frontend/src/metadata/views/map/map.js
index e9770b9fc4..89a956035c 100644
--- a/frontend/src/metadata/views/map/main.js
+++ b/frontend/src/metadata/views/map/map.js
@@ -5,7 +5,7 @@ import { appAvatarURL, baiduMapKey, gettext, googleMapKey, mediaUrl } from '../.
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 } 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 toaster from '../../../components/toast';
@@ -14,7 +14,7 @@ const DEFAULT_POSITION = { lng: 104.195, lat: 35.861 };
const DEFAULT_ZOOM = 4;
const BATCH_SIZE = 500;
-const Main = ({ validImages, onOpen }) => {
+const Main = ({ images, onOpenCluster }) => {
const mapInfo = useMemo(() => initMapInfo({ baiduMapKey, googleMapKey }), []);
const mapRef = useRef(null);
@@ -61,13 +61,14 @@ const Main = ({ validImages, onOpen }) => {
);
}, []);
- const getMapType = useCallback((type) => {
- if (!mapRef.current) return;
+ const getBMapType = useCallback((type) => {
switch (type) {
- case MAP_TYPE.SATELLITE:
+ case MAP_TYPE.SATELLITE: {
return window.BMAP_SATELLITE_MAP;
- default:
+ }
+ default: {
return window.BMAP_NORMAL_MAP;
+ }
}
}, []);
@@ -75,13 +76,13 @@ const Main = ({ validImages, onOpen }) => {
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);
+ window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_CENTER_KEY, point);
+ window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_ZOOM_KEY, zoom);
}, []);
const loadMapState = useCallback(() => {
- const savedCenter = window.sfMetadataContext.localStorage.getItem('map-center') || DEFAULT_POSITION;
- const savedZoom = window.sfMetadataContext.localStorage.getItem('map-zoom') || DEFAULT_ZOOM;
+ const savedCenter = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_CENTER_KEY) || DEFAULT_POSITION;
+ const savedZoom = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_ZOOM_KEY) || DEFAULT_ZOOM;
return { center: savedCenter, zoom: savedZoom };
}, []);
@@ -89,18 +90,18 @@ const Main = ({ validImages, onOpen }) => {
saveMapState();
const imageIds = markers.map(marker => marker._imageId);
- onOpen(imageIds);
- }, [onOpen, saveMapState]);
+ onOpenCluster(imageIds);
+ }, [onOpenCluster, saveMapState]);
const renderMarkersBatch = useCallback(() => {
- if (!validImages.length || !clusterRef.current) return;
+ if (!images.length || !clusterRef.current) return;
const startIndex = batchIndexRef.current * BATCH_SIZE;
- const endIndex = Math.min(startIndex + BATCH_SIZE, validImages.length);
+ const endIndex = Math.min(startIndex + BATCH_SIZE, images.length);
const batchMarkers = [];
for (let i = startIndex; i < endIndex; i++) {
- const image = validImages[i];
+ const image = images[i];
const { lng, lat } = image;
const point = new window.BMap.Point(lng, lat);
const marker = customImageOverlay(point, image, {
@@ -110,15 +111,15 @@ const Main = ({ validImages, onOpen }) => {
}
clusterRef.current.addMarkers(batchMarkers);
- if (endIndex < validImages.length) {
+ if (endIndex < images.length) {
batchIndexRef.current += 1;
setTimeout(renderMarkersBatch, 20); // Schedule the next batch
}
- }, [validImages, onClickMarker]);
+ }, [images, onClickMarker]);
- const initializeClusterer = useCallback(() => {
+ const initializeCluster = useCallback(() => {
if (mapRef.current && !clusterRef.current) {
- clusterRef.current = new window.BMapLib.MarkerClusterer(mapRef.current, {
+ clusterRef.current = new window.BMapLib.MarkerCluster(mapRef.current, {
callback: (e, markers) => onClickMarker(e, markers)
});
}
@@ -131,7 +132,7 @@ const Main = ({ validImages, onOpen }) => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition((userInfo) => {
center = { lng: userInfo.coords.longitude, lat: userInfo.coords.latitude };
- window.sfMetadataContext.localStorage.setItem('map-center', center);
+ window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_CENTER_KEY, center);
});
}
if (!isValidPosition(center?.lng, center?.lat)) return;
@@ -145,28 +146,30 @@ const Main = ({ validImages, onOpen }) => {
mapRef.current.centerAndZoom(point, zoom);
mapRef.current.enableScrollWheelZoom(true);
- const savedValue = window.sfMetadataContext.localStorage.getItem('map-type');
- mapRef.current && mapRef.current.setMapType(getMapType(savedValue));
+
+ const savedValue = window.sfMetadataContext.localStorage.getItem(STORAGE_MAP_TYPE_KEY);
+ mapRef.current && mapRef.current.setMapType(getBMapType(savedValue));
addMapController();
initializeUserMarker();
- initializeClusterer();
+ initializeCluster();
batchIndexRef.current = 0;
renderMarkersBatch();
- }, [addMapController, initializeClusterer, initializeUserMarker, renderMarkersBatch, getMapType, loadMapState]);
+ }, [addMapController, initializeCluster, initializeUserMarker, renderMarkersBatch, getBMapType, loadMapState]);
useEffect(() => {
- const switchMapTypeSubscribe = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SWITCH_MAP_TYPE, (newType) => {
- window.sfMetadataContext.localStorage.setItem('map-type', newType);
- mapRef.current && mapRef.current.setMapType(getMapType(newType));
+ const modifyMapTypeSubscribe = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.MODIFY_MAP_TYPE, (newType) => {
+ window.sfMetadataContext.localStorage.setItem(STORAGE_MAP_TYPE_KEY, newType);
+ mapRef.current && mapRef.current.setMapType(getBMapType(newType));
});
return () => {
- switchMapTypeSubscribe();
+ modifyMapTypeSubscribe();
};
- }, [getMapType, saveMapState]);
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, []);
useEffect(() => {
if (mapInfo.type === MAP_PROVIDER.B_MAP) {
@@ -186,8 +189,8 @@ const Main = ({ validImages, onOpen }) => {
};
Main.propTypes = {
- validImages: PropTypes.array,
- onOpen: PropTypes.func,
+ images: PropTypes.array,
+ onOpenCluster: PropTypes.func,
};
export default Main;
diff --git a/frontend/src/metadata/views/table/utils/grid-utils.js b/frontend/src/metadata/views/table/utils/grid-utils.js
index a835e80223..3e0d4d9857 100644
--- a/frontend/src/metadata/views/table/utils/grid-utils.js
+++ b/frontend/src/metadata/views/table/utils/grid-utils.js
@@ -2,8 +2,7 @@ import dayjs from 'dayjs';
import { getCellValueByColumn, getFileNameFromRecord, getRecordIdFromRecord, isCellValueChanged } from '../../../utils/cell';
import { getColumnByIndex, getColumnOriginName } from '../../../utils/column';
import { CellType, NOT_SUPPORT_DRAG_COPY_COLUMN_TYPES, PRIVATE_COLUMN_KEY, TRANSFER_TYPES,
- REG_NUMBER_DIGIT, REG_STRING_NUMBER_PARTS, COLUMN_RATE_MAX_NUMBER,
- PASTE_SOURCE,
+ REG_NUMBER_DIGIT, REG_STRING_NUMBER_PARTS, RATE_MAX_NUMBER, PASTE_SOURCE,
} from '../../../constants';
import { getGroupRecordByIndex } from './group-metrics';
import { convertCellValue } from './convert-utils';
@@ -594,7 +593,7 @@ class GridUtils {
}
_getRatingLeastSquares(numberList, data) {
- const { rate_max_number = COLUMN_RATE_MAX_NUMBER[4].name } = data || {};
+ const { rate_max_number = RATE_MAX_NUMBER[4].name } = data || {};
let slope;
let intercept;
let xAverage;
diff --git a/media/js/map/marker-clusterer.js b/media/js/map/marker-clusterer.js
index 41f1aca44f..57f58edc6f 100644
--- a/media/js/map/marker-clusterer.js
+++ b/media/js/map/marker-clusterer.js
@@ -1,6 +1,6 @@
/**
* @fileoverview MarkerClusterer标记聚合器用来解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能。
- * 主入口类是MarkerClusterer,
+ * 主入口类是MarkerCluster,
* 基于Baidu Map API 1.2。
*
* @author Baidu Map Api Group
@@ -97,11 +97,11 @@ var BMapLib = window.BMapLib = BMapLib || {};
};
/**
- *@exports MarkerClusterer as BMapLib.MarkerClusterer
+ *@exports MarkerCluster as BMapLib.MarkerCluster
*/
- var MarkerClusterer =
+ var MarkerCluster =
/**
- * MarkerClusterer
+ * MarkerCluster
* @class 用来解决加载大量点要素到地图上产生覆盖现象的问题,并提高性能
* @constructor
* @param {Map} map 地图的一个实例。
@@ -113,7 +113,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
* isAverangeCenter {Boolean} 聚合点的落脚位置是否是所有聚合在内点的平均值,默认为否,落脚在聚合内的第一个点
* styles {Array} 自定义聚合后的图标风格,请参考TextIconOverlay类
*/
- BMapLib.MarkerClusterer = function(map, options){
+ BMapLib.MarkerCluster = function(map, options){
if (!map){
return;
}
@@ -151,7 +151,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
*
* @return 无返回值。
*/
- MarkerClusterer.prototype.addMarkers = function(markers){
+ MarkerCluster.prototype.addMarkers = function(markers){
for(var i = 0, len = markers.length; i } 聚合的样式风格集合
*/
- MarkerClusterer.prototype.getStyles = function() {
+ MarkerCluster.prototype.getStyles = function() {
return this._styles;
};
@@ -385,7 +385,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
* @param {Array} styles 样式风格数组
* @return 无返回值
*/
- MarkerClusterer.prototype.setStyles = function(styles) {
+ MarkerCluster.prototype.setStyles = function(styles) {
this._styles = styles;
this._redraw();
};
@@ -394,7 +394,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
* 获取单个聚合的最小数量。
* @return {Number} 单个聚合的最小数量。
*/
- MarkerClusterer.prototype.getMinClusterSize = function() {
+ MarkerCluster.prototype.getMinClusterSize = function() {
return this._minClusterSize;
};
@@ -403,7 +403,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
* @param {Number} size 单个聚合的最小数量。
* @return 无返回值。
*/
- MarkerClusterer.prototype.setMinClusterSize = function(size) {
+ MarkerCluster.prototype.setMinClusterSize = function(size) {
this._minClusterSize = size;
this._redraw();
};
@@ -412,7 +412,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
* 获取单个聚合的落脚点是否是聚合内所有标记的平均中心。
* @return {Boolean} true或false。
*/
- MarkerClusterer.prototype.isAverageCenter = function() {
+ MarkerCluster.prototype.isAverageCenter = function() {
return this._isAverageCenter;
};
@@ -420,7 +420,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
* 获取聚合的Map实例。
* @return {Map} Map的示例。
*/
- MarkerClusterer.prototype.getMap = function() {
+ MarkerCluster.prototype.getMap = function() {
return this._map;
};
@@ -428,7 +428,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
* 获取所有的标记数组。
* @return {Array} 标记数组。
*/
- MarkerClusterer.prototype.getMarkers = function() {
+ MarkerCluster.prototype.getMarkers = function() {
return this._markers;
};
@@ -436,7 +436,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
* 获取聚合的总数量。
* @return {Number} 聚合的总数量。
*/
- MarkerClusterer.prototype.getClustersCount = function() {
+ MarkerCluster.prototype.getClustersCount = function() {
var count = 0;
for(var i = 0, cluster; cluster = this._clusters[i]; i++){
cluster.isReal() && count++;
@@ -444,7 +444,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
return count;
};
- MarkerClusterer.prototype.getCallback = function() {
+ MarkerCluster.prototype.getCallback = function() {
return this._callback;
}
@@ -453,7 +453,7 @@ var BMapLib = window.BMapLib = BMapLib || {};
* Cluster
* @class 表示一个聚合对象,该聚合,包含有N个标记,这N个标记组成的范围,并有予以显示在Map上的TextIconOverlay等。
* @constructor
- * @param {MarkerClusterer} markerClusterer 一个标记聚合器示例。
+ * @param {MarkerCluster} markerClusterer 一个标记聚合器示例。
*/
function Cluster(markerClusterer){
this._markerClusterer = markerClusterer;