diff --git a/frontend/src/components/cur-dir-path/dir-path.js b/frontend/src/components/cur-dir-path/dir-path.js index c2cee204af..4f2c89f1ea 100644 --- a/frontend/src/components/cur-dir-path/dir-path.js +++ b/frontend/src/components/cur-dir-path/dir-path.js @@ -9,7 +9,7 @@ import { siteRoot, gettext } from '../../utils/constants'; import { Utils } from '../../utils/utils'; import { PRIVATE_FILE_TYPE } from '../../constants'; import { debounce } from '../../metadata/utils/common'; -import { EVENT_BUS_TYPE, FACE_RECOGNITION_VIEW_ID } from '../../metadata/constants'; +import { EVENT_BUS_TYPE } from '../../metadata/constants'; import { ALL_TAGS_ID } from '../../tag/constants'; const propTypes = { @@ -126,13 +126,12 @@ class DirPath extends React.Component { turnViewPathToLink = (pathList) => { if (!Array.isArray(pathList) || pathList.length === 0) return null; const [, , viewId, children] = pathList; - const isViewSupportClick = viewId === FACE_RECOGNITION_VIEW_ID && children; return ( <> / {gettext('Views')} / - {}}> + {}}> {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;