diff --git a/frontend/src/tag/api.js b/frontend/src/tag/api.js index a34428664db..22fd9024d71 100644 --- a/frontend/src/tag/api.js +++ b/frontend/src/tag/api.js @@ -62,8 +62,8 @@ class TagsManagerAPI { return this.req.delete(url); }; - getTags = (repoID) => { - const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/tags/'; + getTags = (repoID, start = 0, limit = 1000) => { + const url = this.server + '/api/v2.1/repos/' + repoID + '/metadata/tags/?start=' + start + '&limit=' + limit; return this.req.get(url); }; diff --git a/frontend/src/tag/context.js b/frontend/src/tag/context.js index b4e5a421e68..5a37dafc90e 100644 --- a/frontend/src/tag/context.js +++ b/frontend/src/tag/context.js @@ -93,8 +93,8 @@ class Context { }; // tags - getTags = () => { - return this.api.getTags(this.repoId); + getTags = ({ start, limit }) => { + return this.api.getTags(this.repoId, start, limit); }; addTags = (tags = []) => { diff --git a/frontend/src/tag/model/tagsData.js b/frontend/src/tag/model/tagsData.js index 76625764317..59102b1c4ee 100644 --- a/frontend/src/tag/model/tagsData.js +++ b/frontend/src/tag/model/tagsData.js @@ -16,6 +16,8 @@ class TagsData { this.row_ids.push(record._id); this.id_row_map[record._id] = record; }); + + this.hasMore = true; } } diff --git a/frontend/src/tag/store/operations/apply.js b/frontend/src/tag/store/operations/apply.js index ac17908f8b2..c092b3e0883 100644 --- a/frontend/src/tag/store/operations/apply.js +++ b/frontend/src/tag/store/operations/apply.js @@ -14,11 +14,17 @@ export default function apply(data, operation) { case OPERATION_TYPE.ADD_RECORDS: { const { tags } = operation; const { rows } = data; - const updatedRows = [...rows, ...tags]; + const updatedRows = [...rows]; tags.forEach(tag => { - const id = tag[PRIVATE_COLUMN_KEY.ID]; - data.id_row_map[id] = tag; - data.row_ids.push(id); + const tagID = tag[PRIVATE_COLUMN_KEY.ID]; + const rowIndex = updatedRows.findIndex(r => r._id === tagID); + data.id_row_map[tagID] = tag; + if (rowIndex === -1) { + data.row_ids.push(tagID); + updatedRows.push(tag); + } else { + updatedRows[rowIndex] = tag; + } }); data.rows = updatedRows; return data; diff --git a/frontend/src/tag/views/all-tags/index.js b/frontend/src/tag/views/all-tags/index.js index ac436652b17..6ed3352aa93 100644 --- a/frontend/src/tag/views/all-tags/index.js +++ b/frontend/src/tag/views/all-tags/index.js @@ -2,15 +2,18 @@ import React, { useEffect, useState, useMemo, useCallback } from 'react'; import { CenteredLoading } from '@seafile/sf-metadata-ui-component'; import { useTags } from '../../hooks'; import Main from './main'; -import { EVENT_BUS_TYPE } from '../../../metadata/constants'; +import { EVENT_BUS_TYPE, PER_LOAD_NUMBER } from '../../../metadata/constants'; import TagFiles from './tag-files'; +import { Utils } from '../../../utils/utils'; +import toaster from '../../../components/toast'; import './index.css'; const AllTags = ({ ...params }) => { const [displayTag, setDisplayTag] = useState(''); + const [isLoadingMore, setLoadingMore] = useState(false); - const { isLoading, isReloading, tagsData, context } = useTags(); + const { isLoading, isReloading, tagsData, store, context } = useTags(); useEffect(() => { const eventBus = context.eventBus; @@ -28,6 +31,23 @@ const AllTags = ({ ...params }) => { setDisplayTag(tagID); }, [displayTag]); + const onLoadMore = useCallback(async () => { + if (isLoadingMore) return; + if (!tagsData.hasMore) return; + setLoadingMore(true); + + try { + await store.loadMore(PER_LOAD_NUMBER); + setLoadingMore(false); + } catch (error) { + const errorMsg = Utils.getErrorMsg(error); + toaster.danger(errorMsg); + setLoadingMore(false); + return; + } + + }, [isLoadingMore, tagsData, store]); + if (isLoading || isReloading) return (); if (displayTag) { @@ -38,7 +58,7 @@ const AllTags = ({ ...params }) => {
-
+
diff --git a/frontend/src/tag/views/all-tags/main/index.js b/frontend/src/tag/views/all-tags/main/index.js index ae0b7444654..d34fb0b5131 100644 --- a/frontend/src/tag/views/all-tags/main/index.js +++ b/frontend/src/tag/views/all-tags/main/index.js @@ -9,7 +9,7 @@ import { isCellValueChanged } from '../../../../metadata/utils/cell'; import './index.css'; -const Main = React.memo(({ context, tags, onChangeDisplayTag }) => { +const Main = React.memo(({ context, tags, onChangeDisplayTag, onLoadMore }) => { const tableRef = useRef(null); useEffect(() => { @@ -22,10 +22,16 @@ const Main = React.memo(({ context, tags, onChangeDisplayTag }) => { // eslint-disable-next-line react-hooks/exhaustive-deps }, []); - const handelScroll = debounce(() => { + const handelScroll = debounce(async () => { + if (!tableRef.current) return; + const { scrollTop, scrollHeight, clientHeight } = tableRef.current; const currentLocalStorage = context.localStorage; - const currentScrollTop = tableRef.current.scrollTop || 0; + const currentScrollTop = scrollTop || 0; currentLocalStorage.setItem('scroll_top', currentScrollTop); + + if (scrollTop + clientHeight >= scrollHeight - 10) { + onLoadMore(); + } }, 200); if (tags.length === 0) { @@ -57,6 +63,7 @@ Main.propTypes = { context: PropTypes.object, tags: PropTypes.array, onChangeDisplayTag: PropTypes.func, + onLoadMore: PropTypes.func, }; export default Main; diff --git a/seahub/repo_metadata/apis.py b/seahub/repo_metadata/apis.py index 7b17e630aee..72ad8deb117 100644 --- a/seahub/repo_metadata/apis.py +++ b/seahub/repo_metadata/apis.py @@ -1660,6 +1660,24 @@ class MetadataTags(APIView): throttle_classes = (UserRateThrottle,) def get(self, request, repo_id): + start = request.GET.get('start', 0) + limit = request.GET.get('limit', 100) + + try: + start = int(start) + limit = int(limit) + except: + start = 0 + limit = 1000 + + if start < 0: + error_msg = 'start invalid' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + + if limit < 0: + error_msg = 'limit invalid' + return api_error(status.HTTP_400_BAD_REQUEST, error_msg) + metadata = RepoMetadata.objects.filter(repo_id=repo_id).first() if not metadata or not metadata.enabled: error_msg = f'The metadata module is disabled for repo {repo_id}.' @@ -1690,7 +1708,7 @@ def get(self, request, repo_id): if not tags_table_id: return api_error(status.HTTP_404_NOT_FOUND, 'tags not be used') - sql = f'SELECT * FROM `{TAGS_TABLE.name}` ORDER BY `_ctime` LIMIT {0}, {1000}' + sql = f'SELECT * FROM `{TAGS_TABLE.name}` ORDER BY `_ctime` LIMIT {start}, {limit}' try: query_result = metadata_server_api.query_rows(sql) @@ -1715,7 +1733,7 @@ def post(self, request, repo_id): repo = seafile_api.get_repo(repo_id) if not repo: - error_msg = 'Library %s not found.' % repo_id + error_msg = f'Library {repo_id} not found.' return api_error(status.HTTP_404_NOT_FOUND, error_msg) if not can_read_metadata(request, repo_id): @@ -1738,29 +1756,55 @@ def post(self, request, repo_id): if not tags_table_id: return api_error(status.HTTP_404_NOT_FOUND, 'tags not be used') + exist_tags = [] + new_tags = [] + tags_names = [tag_data.get(TAGS_TABLE.columns.name.name, '') for tag_data in tags_data] + tags_names_str = ', '.join([f'"{tag_name}"' for tag_name in tags_names]) + sql = f'SELECT * FROM {TAGS_TABLE.name} WHERE `{TAGS_TABLE.columns.name.name}` in ({tags_names_str})' + try: - resp = metadata_server_api.insert_rows(tags_table_id, tags_data) + exist_rows = metadata_server_api.query_rows(sql) + exist_tags = exist_rows.get('results', []) except Exception as e: - logger.error(e) + logger.exception(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - row_ids = resp.get('row_ids', []) + if exist_tags: + for tag_data in tags_data: + tag_name = tag_data.get(TAGS_TABLE.columns.name.name, '') + if tag_name not in tags_names: + new_tags.append(tag_data) + else: + new_tags = tags_data - if not row_ids: + tags = exist_tags + if not new_tags: + return Response({ 'tags': tags }) + + try: + resp = metadata_server_api.insert_rows(tags_table_id, new_tags) + except Exception as e: + logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - sql = 'SELECT * FROM %s WHERE `%s` in (%s)' % (TAGS_TABLE.name, TAGS_TABLE.columns.id.name, ', '.join(["'%s'" % id for id in row_ids])) + row_ids = resp.get('row_ids', []) + if not row_ids: + return Response({ 'tags': tags }) + row_ids_str = ', '.join([f'"{id}"' for id in row_ids]) + sql = f'SELECT * FROM {TAGS_TABLE.name} WHERE `{TAGS_TABLE.columns.id.name}` in ({row_ids_str})' try: query_new_rows = metadata_server_api.query_rows(sql) + new_tags_data = query_new_rows.get('results', []) + tags.extend(new_tags_data) except Exception as e: logger.error(e) error_msg = 'Internal Server Error' return api_error(status.HTTP_500_INTERNAL_SERVER_ERROR, error_msg) - return Response({ 'tags': query_new_rows.get('results', []) }) + return Response({ 'tags': tags }) def put(self, request, repo_id): tags_data = request.data.get('tags_data')