1
0
mirror of https://github.com/haiwen/seahub.git synced 2025-09-21 03:18:23 +00:00

feat: metadata checkbox (#6295)

* feat: metadata checkbox

* feat: optimize checkbox

* feat: update code

* feat: update code

---------

Co-authored-by: 杨国璇 <ygx@Hello-word.local>
This commit is contained in:
杨国璇
2024-07-04 16:30:57 +08:00
committed by GitHub
parent ef96b2bd6b
commit 1eb709023e
36 changed files with 173 additions and 133 deletions

View File

@@ -17,7 +17,7 @@
"@seafile/sdoc-editor": "1.0.7", "@seafile/sdoc-editor": "1.0.7",
"@seafile/seafile-calendar": "0.0.12", "@seafile/seafile-calendar": "0.0.12",
"@seafile/seafile-editor": "1.0.99", "@seafile/seafile-editor": "1.0.99",
"@seafile/sf-metadata-ui-component": "0.0.8", "@seafile/sf-metadata-ui-component": "0.0.9",
"@uiw/codemirror-extensions-langs": "^4.19.4", "@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/react-codemirror": "^4.19.4", "@uiw/react-codemirror": "^4.19.4",
"chart.js": "2.9.4", "chart.js": "2.9.4",
@@ -4953,9 +4953,9 @@
} }
}, },
"node_modules/@seafile/sf-metadata-ui-component": { "node_modules/@seafile/sf-metadata-ui-component": {
"version": "0.0.8", "version": "0.0.9",
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.8.tgz", "resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.9.tgz",
"integrity": "sha512-5lHcPLC5HQtjfBXS47KUYfM3lSwW2KSH17S+eF1JBrEbeM7SGUN8p//sTMmUcSHwBn0VPs8BxWIvQWRQgVwz3g==", "integrity": "sha512-J0D3DK1TI16QPlhAeBp64ilcKO7pCX9w03Q94D1Ni7phLquqZwCD3PFFyRgv6oUkWtGojTL++SiLVTTXubI68g==",
"dependencies": { "dependencies": {
"@seafile/seafile-calendar": "0.0.24", "@seafile/seafile-calendar": "0.0.24",
"classnames": "2.3.2", "classnames": "2.3.2",
@@ -32160,9 +32160,9 @@
} }
}, },
"@seafile/sf-metadata-ui-component": { "@seafile/sf-metadata-ui-component": {
"version": "0.0.8", "version": "0.0.9",
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.8.tgz", "resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.9.tgz",
"integrity": "sha512-5lHcPLC5HQtjfBXS47KUYfM3lSwW2KSH17S+eF1JBrEbeM7SGUN8p//sTMmUcSHwBn0VPs8BxWIvQWRQgVwz3g==", "integrity": "sha512-J0D3DK1TI16QPlhAeBp64ilcKO7pCX9w03Q94D1Ni7phLquqZwCD3PFFyRgv6oUkWtGojTL++SiLVTTXubI68g==",
"requires": { "requires": {
"@seafile/seafile-calendar": "0.0.24", "@seafile/seafile-calendar": "0.0.24",
"classnames": "2.3.2", "classnames": "2.3.2",

View File

@@ -12,7 +12,7 @@
"@seafile/sdoc-editor": "1.0.7", "@seafile/sdoc-editor": "1.0.7",
"@seafile/seafile-calendar": "0.0.12", "@seafile/seafile-calendar": "0.0.12",
"@seafile/seafile-editor": "1.0.99", "@seafile/seafile-editor": "1.0.99",
"@seafile/sf-metadata-ui-component": "0.0.8", "@seafile/sf-metadata-ui-component": "0.0.9",
"@uiw/codemirror-extensions-langs": "^4.19.4", "@uiw/codemirror-extensions-langs": "^4.19.4",
"@uiw/react-codemirror": "^4.19.4", "@uiw/react-codemirror": "^4.19.4",
"chart.js": "2.9.4", "chart.js": "2.9.4",

View File

@@ -0,0 +1 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1720076454490" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="15388" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M128 32h768c54.4 0 96 41.6 96 96v768c0 54.4-41.6 96-96 96H128c-54.4 0-96-41.6-96-96V128c0-54.4 41.6-96 96-96z m275.2 713.6c12.8 12.8 28.8 19.2 41.6 19.2 19.2 0 28.8-6.4 41.6-19.2l323.2-323.2c25.6-25.6 25.6-60.8 0-83.2s-60.8-25.6-83.2 0l-281.6 281.6-124.8-128c-25.6-25.6-60.8-25.6-83.2 0s-25.6 60.8 0 83.2l166.4 169.6z" p-id="15389"></path></svg>

After

Width:  |  Height:  |  Size: 679 B

View File

@@ -6,5 +6,8 @@ export const NOT_DISPLAY_COLUMN_KEYS = [
PRIVATE_COLUMN_KEY.MTIME, PRIVATE_COLUMN_KEY.MTIME,
PRIVATE_COLUMN_KEY.CREATOR, PRIVATE_COLUMN_KEY.CREATOR,
PRIVATE_COLUMN_KEY.LAST_MODIFIER, PRIVATE_COLUMN_KEY.LAST_MODIFIER,
];
export const VIEW_NOT_DISPLAY_COLUMN_KEYS = [
PRIVATE_COLUMN_KEY.IS_DIR, PRIVATE_COLUMN_KEY.IS_DIR,
]; ];

View File

@@ -1,13 +1,15 @@
import CellType from './type'; import CellType from './type';
const DATE_COLUMN_OPTIONS = [ const DATE_COLUMN_OPTIONS = [
CellType.CTIME, CellType.MTIME, CellType.CTIME,
CellType.MTIME,
]; ];
const NUMERIC_COLUMNS_TYPES = [ const NUMERIC_COLUMNS_TYPES = [
]; ];
const COLLABORATOR_COLUMN_TYPES = [ const COLLABORATOR_COLUMN_TYPES = [
CellType.CREATOR, CellType.LAST_MODIFIER, CellType.CREATOR,
CellType.LAST_MODIFIER,
]; ];
// date // date

View File

@@ -8,6 +8,7 @@ const COLUMNS_ICON_CONFIG = {
[CellType.DEFAULT]: 'text', [CellType.DEFAULT]: 'text',
[CellType.TEXT]: 'text', [CellType.TEXT]: 'text',
[CellType.FILE_NAME]: 'text', [CellType.FILE_NAME]: 'text',
[CellType.CHECKBOX]: 'checkbox',
}; };
const COLUMNS_ICON_NAME = { const COLUMNS_ICON_NAME = {
@@ -18,6 +19,7 @@ const COLUMNS_ICON_NAME = {
[CellType.DEFAULT]: 'Text', [CellType.DEFAULT]: 'Text',
[CellType.TEXT]: 'Text', [CellType.TEXT]: 'Text',
[CellType.FILE_NAME]: 'File name', [CellType.FILE_NAME]: 'File name',
[CellType.CHECKBOX]: 'Checkbox',
}; };
export { export {

View File

@@ -31,5 +31,6 @@ export {
}; };
export { export {
NOT_DISPLAY_COLUMN_KEYS NOT_DISPLAY_COLUMN_KEYS,
VIEW_NOT_DISPLAY_COLUMN_KEYS,
} from './common'; } from './common';

View File

@@ -6,6 +6,7 @@ const CellType = {
LAST_MODIFIER: 'last-modifier', LAST_MODIFIER: 'last-modifier',
MTIME: 'mtime', MTIME: 'mtime',
FILE_NAME: 'file-name', FILE_NAME: 'file-name',
CHECKBOX: 'checkbox',
}; };
export default CellType; export default CellType;

View File

@@ -70,6 +70,11 @@ const FILTER_COLUMN_OPTIONS = {
FILTER_PREDICATE_TYPE.IS_NOT, FILTER_PREDICATE_TYPE.IS_NOT,
], ],
}, },
[CellType.CHECKBOX]: {
filterPredicateList: [
FILTER_PREDICATE_TYPE.IS,
],
},
[CellType.URL]: { [CellType.URL]: {
filterPredicateList: [ filterPredicateList: [
FILTER_PREDICATE_TYPE.CONTAINS, FILTER_PREDICATE_TYPE.CONTAINS,

View File

@@ -20,6 +20,7 @@ export {
SINGLE_CELL_VALUE_COLUMN_TYPE_MAP, SINGLE_CELL_VALUE_COLUMN_TYPE_MAP,
PRIVATE_COLUMN_KEY, PRIVATE_COLUMN_KEY,
NOT_DISPLAY_COLUMN_KEYS, NOT_DISPLAY_COLUMN_KEYS,
VIEW_NOT_DISPLAY_COLUMN_KEYS,
} from './column'; } from './column';
export { export {
FILTER_CONJUNCTION_TYPE, FILTER_CONJUNCTION_TYPE,

View File

@@ -47,6 +47,7 @@ export {
HEADER_HEIGHT_TYPE, HEADER_HEIGHT_TYPE,
PRIVATE_COLUMN_KEY, PRIVATE_COLUMN_KEY,
NOT_DISPLAY_COLUMN_KEYS, NOT_DISPLAY_COLUMN_KEYS,
VIEW_NOT_DISPLAY_COLUMN_KEYS,
} from './constants'; } from './constants';
export { export {
@@ -106,7 +107,6 @@ export {
isMac, isMac,
base64ToFile, base64ToFile,
bytesToSize, bytesToSize,
getErrorMsg,
DateUtils, DateUtils,
CommonlyUsedHotkey, CommonlyUsedHotkey,
LocalStorage, LocalStorage,

View File

@@ -36,23 +36,6 @@ export const bytesToSize = (bytes) => {
return (bytes / (1000 ** i)).toFixed(1) + ' ' + sizes[i]; return (bytes / (1000 ** i)).toFixed(1) + ' ' + sizes[i];
}; };
export const getErrorMsg = (error) => {
let errorMsg = '';
if (error.response) {
if (error.response.status === 403) {
errorMsg = 'Permission_denied';
} else if (error.response.data &&
error.response.data['error_msg']) {
errorMsg = error.response.data['error_msg'];
} else {
errorMsg = 'Error';
}
} else {
errorMsg = 'Please_check_the_network';
}
return errorMsg;
};
export const isFunction = (functionToCheck) => { export const isFunction = (functionToCheck) => {
const getType = {}; const getType = {};
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]'; return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';

View File

@@ -0,0 +1,15 @@
/**
* Filter checkbox
* @param {bool} checked
* @param {bool} filter_term
* @returns boolean
*/
const checkboxFilter = (checked, { filter_term }) => {
const filterTerm = filter_term || false;
const normalizedChecked = typeof checked === 'string' ? checked.toLocaleUpperCase() === 'TRUE' : checked || false;
return normalizedChecked === filterTerm;
};
export {
checkboxFilter,
};

View File

@@ -1,3 +1,4 @@
export { creatorFilter } from './creator'; export { creatorFilter } from './creator';
export { dateFilter } from './date'; export { dateFilter } from './date';
export { textFilter } from './text'; export { textFilter } from './text';
export { checkboxFilter } from './checkbox';

View File

@@ -6,6 +6,7 @@ import {
creatorFilter, creatorFilter,
dateFilter, dateFilter,
textFilter, textFilter,
checkboxFilter,
} from './filter-column'; } from './filter-column';
import { import {
FILTER_CONJUNCTION_TYPE, FILTER_CONJUNCTION_TYPE,
@@ -30,6 +31,9 @@ const getFilterResult = (row, filter, { username, userId }) => {
case CellType.CREATOR: { case CellType.CREATOR: {
return creatorFilter(cellValue, filter, username); return creatorFilter(cellValue, filter, username);
} }
case CellType.CHECKBOX: {
return checkboxFilter(cellValue, filter);
}
default: { default: {
return false; return false;
} }

View File

@@ -74,7 +74,6 @@ export {
isMac, isMac,
base64ToFile, base64ToFile,
bytesToSize, bytesToSize,
getErrorMsg,
isFunction, isFunction,
isEmpty, isEmpty,
isEmptyObject, isEmptyObject,

View File

@@ -33,7 +33,9 @@ class GroupbySetter extends Component {
const groupbysLength = groupbys ? groupbys.length : 0; const groupbysLength = groupbys ? groupbys.length : 0;
const activated = groupbysLength > 0; const activated = groupbysLength > 0;
let groupbyMessage = gettext('Group');
// need to translate to Group
let groupbyMessage = gettext('Group_by');
if (groupbysLength === 1) { if (groupbysLength === 1) {
groupbyMessage = gettext('Grouped by 1 column'); groupbyMessage = gettext('Grouped by 1 column');
} else if (groupbysLength > 1) { } else if (groupbysLength > 1) {

View File

@@ -404,6 +404,9 @@ class FilterItem extends React.Component {
/> />
); );
} }
case CellType.CHECKBOX: {
return this.getInputComponent('checkbox');
}
default: { default: {
return null; return null;
} }

View File

@@ -108,10 +108,6 @@
color: unset; color: unset;
} }
.filter-term .selector-collaborator .sf-metadata-icon-drop-down {
padding-left: 5px;
}
.filters-list .selector-collaborator .selected-option-show { .filters-list .selector-collaborator .selected-option-show {
text-overflow: unset; text-overflow: unset;
} }
@@ -297,10 +293,6 @@
width: 100%; width: 100%;
} }
.filters-list .filter-checkbox-predicate .sf-metadata-select .selected-option-show {
width: 100%;
}
.dropdown-item .collaborator, .dropdown-item .collaborator,
.filters-list .option-group .option-group-content .collaborator { .filters-list .option-group .option-group-content .collaborator {
background-color: unset; background-color: unset;

View File

@@ -14,7 +14,7 @@ const RecordDetailsDialog = () => {
updateCollaboratorsCache, updateCollaboratorsCache,
queryUserAPI: window.sfMetadataContext.userService.queryUser, queryUserAPI: window.sfMetadataContext.userService.queryUser,
record: recordDetails, record: recordDetails,
fields: metadata.columns, fields: metadata.view.columns,
fieldIconConfig: COLUMNS_ICON_CONFIG, fieldIconConfig: COLUMNS_ICON_CONFIG,
onToggle: closeRecordDetails, onToggle: closeRecordDetails,
}; };

View File

@@ -1,12 +1,14 @@
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { toaster } from '@seafile/sf-metadata-ui-component'; import toaster from '../../../../components/toast';
import { EVENT_BUS_TYPE } from '../../constants'; import { EVENT_BUS_TYPE } from '../../constants';
import { CommonlyUsedHotkey, getErrorMsg } from '../../_basic'; import { CommonlyUsedHotkey } from '../../_basic';
import { gettext } from '../../utils'; import { gettext } from '../../utils';
import { useMetadata } from '../../hooks'; import { useMetadata } from '../../hooks';
import TableTool from './table-tool'; import TableTool from './table-tool';
import TableMain from './table-main'; import TableMain from './table-main';
import RecordDetailsDialog from '../record-details-dialog'; import RecordDetailsDialog from '../record-details-dialog';
import { PER_LOAD_NUMBER, MAX_LOAD_NUMBER } from '../../constants';
import { Utils } from '../../../../utils/utils';
import './index.css'; import './index.css';
@@ -38,17 +40,39 @@ const Container = () => {
setLoadingMore(true); setLoadingMore(true);
try { try {
await store.loadMore(); await store.loadMore(PER_LOAD_NUMBER);
setLoadingMore(false); setLoadingMore(false);
} catch (error) { } catch (error) {
const errorMsg = getErrorMsg(error); const errorMsg = Utils.getErrorMsg(error);
toaster.danger(gettext(errorMsg)); toaster.danger(errorMsg);
setLoadingMore(false); setLoadingMore(false);
return; return;
} }
}, [metadata, store]); }, [metadata, store]);
const loadAll = useCallback(async (maxLoadNumber, callback) => {
if (!metadata.hasMore) return;
setLoadingMore(true);
const rowsCount = metadata.row_ids.length;
const loadNumber = rowsCount % MAX_LOAD_NUMBER !== 0 ? MAX_LOAD_NUMBER - rowsCount % MAX_LOAD_NUMBER : MAX_LOAD_NUMBER;
try {
await store.loadMore(loadNumber);
setLoadingMore(false);
} catch (error) {
const errorMsg = Utils.getErrorMsg(error);
toaster.danger(errorMsg);
setLoadingMore(false);
return;
}
if (store.data.hasMore && store.data.row_ids.length < maxLoadNumber) {
loadAll(maxLoadNumber, callback);
} else {
typeof callback === 'function' && callback(store.data.hasMore);
setLoadingMore(false);
}
}, [metadata, store]);
const modifyRecords = useCallback((rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste = false) => { const modifyRecords = useCallback((rowIds, idRowUpdates, idOriginalRowUpdates, idOldRowData, idOriginalOldRowData, isCopyPaste = false) => {
// todo: store op // todo: store op
}, []); }, []);
@@ -144,7 +168,7 @@ const Container = () => {
return ( return (
<> <>
<div className="sf-metadata-wrapper"> <div className="sf-metadata-wrapper">
<TableTool view={metadata.view} columns={metadata.columns} modifyFilters={modifyFilters} modifySorts={modifySorts} modifyGroupbys={modifyGroupbys} modifyHiddenColumns={modifyHiddenColumns} /> <TableTool view={metadata.view} modifyFilters={modifyFilters} modifySorts={modifySorts} modifyGroupbys={modifyGroupbys} modifyHiddenColumns={modifyHiddenColumns} />
<div className="sf-metadata-main"> <div className="sf-metadata-main">
{errorMsg && (<div className="d-center-middle error">{gettext(errorMsg)}</div>)} {errorMsg && (<div className="d-center-middle error">{gettext(errorMsg)}</div>)}
{!errorMsg && ( {!errorMsg && (
@@ -161,6 +185,7 @@ const Container = () => {
getTableContentWidth={getTableContentWidth} getTableContentWidth={getTableContentWidth}
getTableContentLeft={getTableContentLeft} getTableContentLeft={getTableContentLeft}
getAdjacentRowsIds={getAdjacentRowsIds} getAdjacentRowsIds={getAdjacentRowsIds}
loadAll={loadAll}
/> />
</div> </div>
)} )}

View File

@@ -32,7 +32,7 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
return ( return (
<div className={classnames('table-main-container container-fluid p-0', { [`group-level-${groupbysCount + 1}`]: groupbysCount > 0 })}> <div className={classnames('table-main-container container-fluid p-0', { [`group-level-${groupbysCount + 1}`]: groupbysCount > 0 })}>
<Records <Records
columns={metadata.columns} columns={metadata.view.columns}
recordIds={metadata.view.rows || []} recordIds={metadata.view.rows || []}
groups={metadata.groups} groups={metadata.groups}
groupbys={metadata.groupbys} groupbys={metadata.groupbys}
@@ -41,7 +41,7 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
hasMore={metadata.hasMore} hasMore={metadata.hasMore}
gridUtils={gridUtils} gridUtils={gridUtils}
scrollToLoadMore={loadMore} scrollToLoadMore={loadMore}
clickToLoadMore={loadAll} loadAll={loadAll}
groupOffsetLeft={groupOffset} groupOffsetLeft={groupOffset}
modifyRecord={updateRecord} modifyRecord={updateRecord}
updateRecords={updateRecords} updateRecords={updateRecords}

View File

@@ -115,26 +115,17 @@ class Records extends Component {
resizeColumnWidth = (column, width) => { resizeColumnWidth = (column, width) => {
if (width < 50) return; if (width < 50) return;
const { table, columns, } = this.props; const { columns } = this.props;
const newColumn = Object.assign({}, column, { width }); const newColumn = Object.assign({}, column, { width });
const index = columns.findIndex(item => item.key === column.key); const index = columns.findIndex(item => item.key === column.key);
let updateColumns = columns.slice(0); let updateColumns = columns.slice(0);
updateColumns[index] = newColumn; updateColumns[index] = newColumn;
updateColumns = setColumnOffsets(updateColumns); updateColumns = setColumnOffsets(updateColumns);
const columnMetrics = recalculate(updateColumns, columns, table._id); const columnMetrics = recalculate(updateColumns, columns);
this.setState({ columnMetrics }, () => { this.setState({ columnMetrics }, () => {
const oldValue = localStorage.getItem('pages_columns_width'); const oldValue = window.sfMetadataContext.localStorage.getItem('columns_width') || {};
let pagesColumnsWidth = {}; window.sfMetadataContext.localStorage.setItem('columns_width', { ...oldValue, [column.key]: width });
if (oldValue) {
pagesColumnsWidth = JSON.parse(oldValue);
}
const page = window.app.getPage();
const { id: pageId } = page;
let pageColumnsWidth = pagesColumnsWidth[pageId] || {};
const key = `${table._id}-${column.key}`;
pageColumnsWidth[key] = width;
const updated = Object.assign({}, pagesColumnsWidth, { [pageId]: pageColumnsWidth });
localStorage.setItem('pages_columns_width', JSON.stringify(updated));
}); });
}; };
@@ -742,7 +733,7 @@ class Records extends Component {
recordGetterById={this.props.recordGetterById} recordGetterById={this.props.recordGetterById}
recordGetterByIndex={this.props.recordGetterByIndex} recordGetterByIndex={this.props.recordGetterByIndex}
getRecordsSummaries={() => {}} getRecordsSummaries={() => {}}
clickToLoadMore={this.props.clickToLoadMore} loadAll={this.props.loadAll}
/> />
</Fragment> </Fragment>
); );
@@ -768,7 +759,7 @@ Records.propTypes = {
updateRecords: PropTypes.func, updateRecords: PropTypes.func,
recordGetterById: PropTypes.func, recordGetterById: PropTypes.func,
recordGetterByIndex: PropTypes.func, recordGetterByIndex: PropTypes.func,
clickToLoadMore: PropTypes.func, loadAll: PropTypes.func,
}; };
export default Records; export default Records;

View File

@@ -1,13 +1,13 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { toaster } from '@seafile/sf-metadata-ui-component'; import toaster from '../../../../../../components/toast';
import { gettext } from '../../../../../../utils/constants'; import { gettext } from '../../../../../../utils/constants';
class LoadAllTip extends React.Component { class LoadAllTip extends React.Component {
onClick = () => { onClick = () => {
toaster.closeAll(); toaster.closeAll();
this.props.clickToLoadMore(100000); this.props.load(100000);
}; };
render() { render() {
@@ -21,7 +21,7 @@ class LoadAllTip extends React.Component {
} }
LoadAllTip.propTypes = { LoadAllTip.propTypes = {
clickToLoadMore: PropTypes.func load: PropTypes.func
}; };
export default LoadAllTip; export default LoadAllTip;

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { toaster } from '@seafile/sf-metadata-ui-component'; import toaster from '../../../../../../components/toast';
import { isFunction } from '../../../../_basic'; import { isFunction } from '../../../../_basic';
import { isNameColumn } from '../../../../utils/column-utils'; import { isNameColumn } from '../../../../utils/column-utils';
import { TABLE_SUPPORT_EDIT_TYPE_MAP } from '../../../../constants'; import { TABLE_SUPPORT_EDIT_TYPE_MAP } from '../../../../constants';

View File

@@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { Loading, toaster } from '@seafile/sf-metadata-ui-component'; import { Loading } from '@seafile/sf-metadata-ui-component';
import toaster from '../../../../../../../components/toast';
import { Z_INDEX } from '../../../../../_basic'; import { Z_INDEX } from '../../../../../_basic';
import LoadAllTip from '../load-all-tip'; import LoadAllTip from '../load-all-tip';
import RecordMetrics from '../../../../../utils/record-metrics'; import RecordMetrics from '../../../../../utils/record-metrics';
@@ -17,9 +18,9 @@ class RecordsFooter extends React.Component {
return; return;
} }
const loadNumber = this.props.recordsCount < 50000 ? 50000 : 100000; const loadNumber = this.props.recordsCount < 50000 ? 50000 : 100000;
this.props.clickToLoadMore(loadNumber, (hasMore) => { this.props.loadAll(loadNumber, (hasMore) => {
if (hasMore) { if (hasMore) {
toaster.success(<LoadAllTip clickToLoadMore={this.props.clickToLoadMore} />, { duration: 5 }); toaster.success(<LoadAllTip load={this.props.loadAll} />, { duration: 5 });
} else { } else {
toaster.success(gettext('All records loaded')); toaster.success(gettext('All records loaded'));
} }
@@ -108,7 +109,7 @@ class RecordsFooter extends React.Component {
<div className="rows-record d-flex text-nowrap" style={{ width: recordWidth }}> <div className="rows-record d-flex text-nowrap" style={{ width: recordWidth }}>
<span>{this.getRecord()}</span> <span>{this.getRecord()}</span>
{!isLoadingMore && hasMore && {!isLoadingMore && hasMore &&
<span className="load-all ml-4" onClick={this.onClick}>{gettext('Load_all')}</span> <span className="load-all ml-4" onClick={this.onClick}>{gettext('Load all')}</span>
} }
{isLoadingMore && {isLoadingMore &&
<span className="loading-message ml-4"> <span className="loading-message ml-4">
@@ -144,7 +145,7 @@ RecordsFooter.propTypes = {
recordGetterById: PropTypes.func, recordGetterById: PropTypes.func,
recordGetterByIndex: PropTypes.func, recordGetterByIndex: PropTypes.func,
getRecordsSummaries: PropTypes.func, getRecordsSummaries: PropTypes.func,
clickToLoadMore: PropTypes.func, loadAll: PropTypes.func,
}; };
export default RecordsFooter; export default RecordsFooter;

View File

@@ -1,7 +1,7 @@
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import deepCopy from 'deep-copy'; import deepCopy from 'deep-copy';
import { toaster } from '@seafile/sf-metadata-ui-component'; import toaster from '../../../../../../components/toast';
import { import {
CellType, CellType,
NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP, NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP,

View File

@@ -1,4 +1,4 @@
import React, { useCallback } from 'react'; import React, { useCallback, useMemo } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import classnames from 'classnames'; import classnames from 'classnames';
import { FilterSetter, GroupbySetter, SortSetter, HideColumnSetter } from '../../data-process-setter'; import { FilterSetter, GroupbySetter, SortSetter, HideColumnSetter } from '../../data-process-setter';
@@ -8,7 +8,11 @@ import { useCollaborators } from '../../../hooks';
import './index.css'; import './index.css';
const TableTool = ({ searcherActive, view, columns, modifyFilters, modifySorts, modifyGroupbys, modifyHiddenColumns }) => { const TableTool = ({ searcherActive, view, modifyFilters, modifySorts, modifyGroupbys, modifyHiddenColumns }) => {
const columns = useMemo(() => {
return view.available_columns;
}, [view]);
const { collaborators } = useCollaborators(); const { collaborators } = useCollaborators();
@@ -62,7 +66,6 @@ const TableTool = ({ searcherActive, view, columns, modifyFilters, modifySorts,
TableTool.propTypes = { TableTool.propTypes = {
searcherActive: PropTypes.bool, searcherActive: PropTypes.bool,
view: PropTypes.object, view: PropTypes.object,
columns: PropTypes.array,
modifyFilters: PropTypes.func, modifyFilters: PropTypes.func,
modifySorts: PropTypes.func, modifySorts: PropTypes.func,
modifyGroupbys: PropTypes.func, modifyGroupbys: PropTypes.func,

View File

@@ -97,7 +97,10 @@ export const DEFAULT_COLUMNS = [
{ name: 'Is_dir', type: CellType.TEXT, width: 200, editable: false, key: 'is_dir' }, { name: 'Is_dir', type: CellType.TEXT, width: 200, editable: false, key: 'is_dir' },
]; ];
export const PER_PAGE_COUNT = 1000; export const PER_LOAD_NUMBER = 1000;
// dtable-db limit loads up to 10,000 rows at a time
export const MAX_LOAD_NUMBER = 10000;
export { export {
EVENT_BUS_TYPE, EVENT_BUS_TYPE,

View File

@@ -1,11 +1,10 @@
/* eslint-disable react/prop-types */ /* eslint-disable react/prop-types */
import React, { useContext, useEffect, useRef, useState, useCallback } from 'react'; import React, { useContext, useEffect, useRef, useState, useCallback } from 'react';
import { toaster } from '@seafile/sf-metadata-ui-component'; import toaster from '../../../components/toast';
import { gettext } from '../../../utils/constants';
import { getErrorMsg } from '../_basic';
import Context from '../context'; import Context from '../context';
import Store from '../store'; import Store from '../store';
import { EVENT_BUS_TYPE } from '../constants'; import { EVENT_BUS_TYPE, PER_LOAD_NUMBER } from '../constants';
import { Utils } from '../../../utils/utils';
const MetadataContext = React.createContext(null); const MetadataContext = React.createContext(null);
@@ -22,8 +21,8 @@ export const MetadataProvider = ({
}, []); }, []);
const handleTableError = useCallback((error) => { const handleTableError = useCallback((error) => {
const errorMsg = getErrorMsg(error); const errorMsg = Utils.getErrorMsg(error);
toaster.danger(gettext(errorMsg)); toaster.danger(errorMsg);
}, []); }, []);
const updateMetadata = useCallback((data) => { const updateMetadata = useCallback((data) => {
@@ -39,12 +38,12 @@ export const MetadataProvider = ({
const repoId = window.sfMetadataContext.getSetting('repoID'); const repoId = window.sfMetadataContext.getSetting('repoID');
storeRef.current = new Store({ context: window.sfMetadataContext, repoId }); storeRef.current = new Store({ context: window.sfMetadataContext, repoId });
storeRef.current.initStartIndex(); storeRef.current.initStartIndex();
storeRef.current.loadData().then(() => { storeRef.current.loadData(PER_LOAD_NUMBER).then(() => {
setMetadata(storeRef.current.data); setMetadata(storeRef.current.data);
setLoading(false); setLoading(false);
}).catch(error => { }).catch(error => {
const errorMsg = getErrorMsg(error); const errorMsg = Utils.getErrorMsg(error);
toaster.danger(gettext(errorMsg)); toaster.danger(errorMsg);
}); });
const unsubscribeServerTableChanged = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SERVER_TABLE_CHANGED, tableChanged); const unsubscribeServerTableChanged = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SERVER_TABLE_CHANGED, tableChanged);

View File

@@ -1,4 +1,3 @@
import { PER_PAGE_COUNT } from '../../constants';
import View from './view'; import View from './view';
class Metadata { class Metadata {
@@ -12,10 +11,9 @@ class Metadata {
this.id_row_map[record._id] = record; this.id_row_map[record._id] = record;
}); });
this.hasMore = this.rows.length === PER_PAGE_COUNT; this.hasMore = true;
this.recordsCount = this.row_ids.length;
this.recordsCount = object.recordsCount || this.row_ids.length; this.view = new View(object.view || {}, this.columns);
this.view = new View(object.view || {});
} }
} }

View File

@@ -1,12 +1,15 @@
import { VIEW_NOT_DISPLAY_COLUMN_KEYS } from '../../_basic';
class View { class View {
constructor(object) { constructor(object, columns) {
this.filters = object.filters || []; this.filters = object.filters || [];
this.filter_conjunction = object.filter_conjunction || 'Or'; this.filter_conjunction = object.filter_conjunction || 'Or';
this.sorts = object.sorts || []; this.sorts = object.sorts || [];
this.groupbys = object.groupbys || []; this.groupbys = object.groupbys || [];
this.groups = object.groups; this.groups = object.groups;
this.rows = object.rows || []; this.rows = object.rows || [];
// this.available_columns = object.available_columns || []; this.available_columns = columns || [];
this.columns = this.available_columns.filter(column => !VIEW_NOT_DISPLAY_COLUMN_KEYS.includes(column.key));
} }
} }

View File

@@ -9,7 +9,7 @@ import DataProcessor from './data-processor';
import ServerOperator from './server-operator'; import ServerOperator from './server-operator';
import { getColumns } from '../utils/column-utils'; import { getColumns } from '../utils/column-utils';
import { Metadata, User } from '../model'; import { Metadata, User } from '../model';
import { PER_PAGE_COUNT } from '../constants'; import { PER_LOAD_NUMBER } from '../constants';
class Store { class Store {
@@ -17,7 +17,7 @@ class Store {
this.repoId = props.repoId; this.repoId = props.repoId;
this.data = null; this.data = null;
this.context = props.context; this.context = props.context;
this.startIndex = 1; this.startIndex = 0;
this.redos = []; this.redos = [];
this.undos = []; this.undos = [];
this.pendingOperations = []; this.pendingOperations = [];
@@ -28,7 +28,7 @@ class Store {
} }
initStartIndex = () => { initStartIndex = () => {
this.startIndex = 1; this.startIndex = 0;
}; };
saveView = () => { saveView = () => {
@@ -37,21 +37,25 @@ class Store {
this.context.localStorage.setItem('view', view); this.context.localStorage.setItem('view', view);
}; };
async loadData() { async loadData(limit = PER_LOAD_NUMBER) {
const res = await this.context.getMetadata({ page: this.startIndex }); const res = await this.context.getMetadata({ start: this.startIndex, limit });
const view = this.context.localStorage.getItem('view'); const view = this.context.localStorage.getItem('view');
let data = new Metadata({ rows: res?.data?.results || [], columns: getColumns(res?.data?.metadata), view }); const rows = res?.data?.results || [];
const columns = getColumns(res?.data?.metadata);
let data = new Metadata({ rows, columns, view });
data.view.rows = data.row_ids; data.view.rows = data.row_ids;
const loadedCount = rows.length;
data.hasMore = loadedCount === limit;
this.data = data; this.data = data;
this.startIndex += 1; this.startIndex += loadedCount;
const collaboratorsRes = await this.context.getCollaborators(); const collaboratorsRes = await this.context.getCollaborators();
this.collaborators = Array.isArray(collaboratorsRes?.data?.user_list) ? collaboratorsRes.data.user_list.map(user => new User(user)) : []; this.collaborators = Array.isArray(collaboratorsRes?.data?.user_list) ? collaboratorsRes.data.user_list.map(user => new User(user)) : [];
DataProcessor.run(this.data); DataProcessor.run(this.data);
} }
async loadMore() { async loadMore(limit) {
if (!this.data) return; if (!this.data) return;
const res = await this.context.getMetadata(this.repoId, { page: this.startIndex }); const res = await this.context.getMetadata({ start: this.startIndex, limit });
const rows = res?.data?.results || []; const rows = res?.data?.results || [];
if (!Array.isArray(rows) || rows.length === 0) { if (!Array.isArray(rows) || rows.length === 0) {
this.hasMore = false; this.hasMore = false;
@@ -63,8 +67,10 @@ class Store {
this.data.row_ids.push(record._id); this.data.row_ids.push(record._id);
this.data.id_row_map[record._id] = record; this.data.id_row_map[record._id] = record;
}); });
this.data.hasMore = rows.length === PER_PAGE_COUNT; const loadedCount = rows.length;
this.startIndex += 1; this.data.hasMore = loadedCount === limit;
this.data.recordsCount = this.data.row_ids.length;
this.startIndex = this.startIndex + loadedCount;
DataProcessor.run(this.data); DataProcessor.run(this.data);
this.context.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_TABLE_CHANGED); this.context.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_TABLE_CHANGED);
} }

View File

@@ -132,31 +132,27 @@ export function isColumnSupportDirectEdit(cell, columns) {
} }
const _getCustomColumnsWidth = () => { const _getCustomColumnsWidth = () => {
// todo return window.sfMetadataContext.localStorage.getItem('columns_width') || {};
return {};
}; };
export const recalculate = (columns, allColumns, tableId) => { export const recalculate = (columns, allColumns) => {
const displayColumns = columns; const displayColumns = columns;
const displayAllColumns = allColumns; const displayAllColumns = allColumns;
const pageColumnsWidth = _getCustomColumnsWidth(); // get columns width from local storage const pageColumnsWidth = _getCustomColumnsWidth(); // get columns width from local storage
const totalWidth = displayColumns.reduce((total, column) => { const totalWidth = displayColumns.reduce((total, column) => {
const key = `${tableId}-${column.key}`; const width = pageColumnsWidth[column.key] || column.width;
const width = pageColumnsWidth[key] || column.width;
total += width; total += width;
return total; return total;
}, 0); }, 0);
let left = SEQUENCE_COLUMN_WIDTH; let left = SEQUENCE_COLUMN_WIDTH;
const frozenColumns = displayColumns.filter(c => isFrozen(c)); const frozenColumns = displayColumns.filter(c => isFrozen(c));
const frozenColumnsWidth = frozenColumns.reduce((w, column) => { const frozenColumnsWidth = frozenColumns.reduce((w, column) => {
const key = `${tableId}-${column.key}`; const width = pageColumnsWidth[column.key] || column.width;
const width = pageColumnsWidth[key] || column.width;
return w + width; return w + width;
}, 0); }, 0);
const lastFrozenColumnKey = frozenColumnsWidth > 0 ? frozenColumns[frozenColumns.length - 1].key : null; const lastFrozenColumnKey = frozenColumnsWidth > 0 ? frozenColumns[frozenColumns.length - 1].key : null;
const newColumns = displayColumns.map((column, index) => { const newColumns = displayColumns.map((column, index) => {
const key = `${tableId}-${column.key}`; const width = pageColumnsWidth[column.key] || column.width;
const width = pageColumnsWidth[key] || column.width;
column.idx = index; // set column idx column.idx = index; // set column idx
column.left = left; // set column offset column.left = left; // set column offset
column.width = width; column.width = width;
@@ -192,9 +188,9 @@ export const getColumnName = (key, name) => {
case PRIVATE_COLUMN_KEY.FILE_MTIME: case PRIVATE_COLUMN_KEY.FILE_MTIME:
return gettext('File last modified time'); return gettext('File last modified time');
case PRIVATE_COLUMN_KEY.IS_DIR: case PRIVATE_COLUMN_KEY.IS_DIR:
return gettext('Is dir'); return gettext('Is folder');
case PRIVATE_COLUMN_KEY.PARENT_DIR: case PRIVATE_COLUMN_KEY.PARENT_DIR:
return gettext('Parent dir'); return gettext('Parent folder');
case PRIVATE_COLUMN_KEY.FILE_NAME: case PRIVATE_COLUMN_KEY.FILE_NAME:
return gettext('File name'); return gettext('File name');
default: default:
@@ -218,6 +214,8 @@ const getColumnType = (key, type) => {
return CellType.LAST_MODIFIER; return CellType.LAST_MODIFIER;
case PRIVATE_COLUMN_KEY.FILE_NAME: case PRIVATE_COLUMN_KEY.FILE_NAME:
return CellType.FILE_NAME; return CellType.FILE_NAME;
case PRIVATE_COLUMN_KEY.IS_DIR:
return CellType.CHECKBOX;
default: default:
return type; return type;
} }
@@ -225,6 +223,7 @@ const getColumnType = (key, type) => {
export const getColumns = (columns) => { export const getColumns = (columns) => {
if (!Array.isArray(columns) || columns.length === 0) return []; if (!Array.isArray(columns) || columns.length === 0) return [];
const columnsWidth = window.sfMetadataContext.localStorage.getItem('columns_width') || {};
const validColumns = columns.map((column) => { const validColumns = columns.map((column) => {
const { type, key, name, ...params } = column; const { type, key, name, ...params } = column;
return { return {
@@ -232,7 +231,7 @@ export const getColumns = (columns) => {
type: getColumnType(key, type), type: getColumnType(key, type),
name: getColumnName(key, name), name: getColumnName(key, name),
...params, ...params,
width: 200, width: columnsWidth[key] || 200,
}; };
}).filter(column => !NOT_DISPLAY_COLUMN_KEYS.includes(column.key)); }).filter(column => !NOT_DISPLAY_COLUMN_KEYS.includes(column.key));
let displayColumns = []; let displayColumns = [];

View File

@@ -153,24 +153,24 @@ class MetadataRecords(APIView):
#args check #args check
parent_dir = request.GET.get('parent_dir') parent_dir = request.GET.get('parent_dir')
name = request.GET.get('name') name = request.GET.get('name')
page = request.GET.get('page', 1) start = request.GET.get('start', 0)
per_page = request.GET.get('per_page', 1000) limit = request.GET.get('limit', 100)
is_dir = request.GET.get('is_dir') is_dir = request.GET.get('is_dir')
order_by = request.GET.get('order_by') order_by = request.GET.get('order_by')
try: try:
page = int(page) start = int(start)
per_page = int(per_page) limit = int(limit)
except: except:
page = 1 start = 0
per_page = 1000 limit = 1000
if page <= 0: if start < 0:
error_msg = 'page invalid' error_msg = 'start invalid'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if per_page <= 0: if limit < 0:
error_msg = 'per_page invalid' error_msg = 'limit invalid'
return api_error(status.HTTP_400_BAD_REQUEST, error_msg) return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
if is_dir: if is_dir:
@@ -199,7 +199,7 @@ class MetadataRecords(APIView):
return api_error(status.HTTP_403_FORBIDDEN, error_msg) return api_error(status.HTTP_403_FORBIDDEN, error_msg)
try: try:
results = list_metadata_records(repo_id, request.user.username, parent_dir, name, is_dir, page, per_page, order_by) results = list_metadata_records(repo_id, request.user.username, parent_dir, name, is_dir, start, limit, order_by)
except ConnectionError as err: except ConnectionError as err:
logger.error(err) logger.error(err)
status_code, reason = err status_code, reason = err

View File

@@ -2,7 +2,7 @@ import requests, jwt, time
from seahub.settings import METADATA_SERVER_URL, METADATA_SERVER_SECRET_KEY from seahub.settings import METADATA_SERVER_URL, METADATA_SERVER_SECRET_KEY
def list_metadata_records(repo_id, user, parent_dir=None, name=None, is_dir=None, page=None, per_page=25, order_by=None): def list_metadata_records(repo_id, user, parent_dir=None, name=None, is_dir=None, start=0, limit=1000, order_by=None):
from seafevents.repo_metadata.metadata_server_api import METADATA_TABLE from seafevents.repo_metadata.metadata_server_api import METADATA_TABLE
sql = f'SELECT \ sql = f'SELECT \
@@ -44,10 +44,7 @@ def list_metadata_records(repo_id, user, parent_dir=None, name=None, is_dir=None
`{METADATA_TABLE.columns.is_dir.name}` DESC, \ `{METADATA_TABLE.columns.is_dir.name}` DESC, \
`{METADATA_TABLE.columns.file_name.name}` ASC' `{METADATA_TABLE.columns.file_name.name}` ASC'
if page: sql += f' LIMIT {start}, {limit};'
sql += f' LIMIT {(page - 1) * per_page}, {page * per_page}'
sql += ';'
metadata_server_api = MetadataServerAPI(repo_id, user) metadata_server_api = MetadataServerAPI(repo_id, user)
response_results = metadata_server_api.query_rows(sql, parameters) response_results = metadata_server_api.query_rows(sql, parameters)