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:
14
frontend/package-lock.json
generated
14
frontend/package-lock.json
generated
@@ -17,7 +17,7 @@
|
||||
"@seafile/sdoc-editor": "1.0.7",
|
||||
"@seafile/seafile-calendar": "0.0.12",
|
||||
"@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/react-codemirror": "^4.19.4",
|
||||
"chart.js": "2.9.4",
|
||||
@@ -4953,9 +4953,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@seafile/sf-metadata-ui-component": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.8.tgz",
|
||||
"integrity": "sha512-5lHcPLC5HQtjfBXS47KUYfM3lSwW2KSH17S+eF1JBrEbeM7SGUN8p//sTMmUcSHwBn0VPs8BxWIvQWRQgVwz3g==",
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.9.tgz",
|
||||
"integrity": "sha512-J0D3DK1TI16QPlhAeBp64ilcKO7pCX9w03Q94D1Ni7phLquqZwCD3PFFyRgv6oUkWtGojTL++SiLVTTXubI68g==",
|
||||
"dependencies": {
|
||||
"@seafile/seafile-calendar": "0.0.24",
|
||||
"classnames": "2.3.2",
|
||||
@@ -32160,9 +32160,9 @@
|
||||
}
|
||||
},
|
||||
"@seafile/sf-metadata-ui-component": {
|
||||
"version": "0.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.8.tgz",
|
||||
"integrity": "sha512-5lHcPLC5HQtjfBXS47KUYfM3lSwW2KSH17S+eF1JBrEbeM7SGUN8p//sTMmUcSHwBn0VPs8BxWIvQWRQgVwz3g==",
|
||||
"version": "0.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@seafile/sf-metadata-ui-component/-/sf-metadata-ui-component-0.0.9.tgz",
|
||||
"integrity": "sha512-J0D3DK1TI16QPlhAeBp64ilcKO7pCX9w03Q94D1Ni7phLquqZwCD3PFFyRgv6oUkWtGojTL++SiLVTTXubI68g==",
|
||||
"requires": {
|
||||
"@seafile/seafile-calendar": "0.0.24",
|
||||
"classnames": "2.3.2",
|
||||
|
@@ -12,7 +12,7 @@
|
||||
"@seafile/sdoc-editor": "1.0.7",
|
||||
"@seafile/seafile-calendar": "0.0.12",
|
||||
"@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/react-codemirror": "^4.19.4",
|
||||
"chart.js": "2.9.4",
|
||||
|
1
frontend/src/assets/icons/checkbox.svg
Normal file
1
frontend/src/assets/icons/checkbox.svg
Normal 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 |
@@ -6,5 +6,8 @@ export const NOT_DISPLAY_COLUMN_KEYS = [
|
||||
PRIVATE_COLUMN_KEY.MTIME,
|
||||
PRIVATE_COLUMN_KEY.CREATOR,
|
||||
PRIVATE_COLUMN_KEY.LAST_MODIFIER,
|
||||
];
|
||||
|
||||
export const VIEW_NOT_DISPLAY_COLUMN_KEYS = [
|
||||
PRIVATE_COLUMN_KEY.IS_DIR,
|
||||
];
|
||||
|
@@ -1,13 +1,15 @@
|
||||
import CellType from './type';
|
||||
|
||||
const DATE_COLUMN_OPTIONS = [
|
||||
CellType.CTIME, CellType.MTIME,
|
||||
CellType.CTIME,
|
||||
CellType.MTIME,
|
||||
];
|
||||
const NUMERIC_COLUMNS_TYPES = [
|
||||
|
||||
];
|
||||
const COLLABORATOR_COLUMN_TYPES = [
|
||||
CellType.CREATOR, CellType.LAST_MODIFIER,
|
||||
CellType.CREATOR,
|
||||
CellType.LAST_MODIFIER,
|
||||
];
|
||||
|
||||
// date
|
||||
|
@@ -8,6 +8,7 @@ const COLUMNS_ICON_CONFIG = {
|
||||
[CellType.DEFAULT]: 'text',
|
||||
[CellType.TEXT]: 'text',
|
||||
[CellType.FILE_NAME]: 'text',
|
||||
[CellType.CHECKBOX]: 'checkbox',
|
||||
};
|
||||
|
||||
const COLUMNS_ICON_NAME = {
|
||||
@@ -18,6 +19,7 @@ const COLUMNS_ICON_NAME = {
|
||||
[CellType.DEFAULT]: 'Text',
|
||||
[CellType.TEXT]: 'Text',
|
||||
[CellType.FILE_NAME]: 'File name',
|
||||
[CellType.CHECKBOX]: 'Checkbox',
|
||||
};
|
||||
|
||||
export {
|
||||
|
@@ -31,5 +31,6 @@ export {
|
||||
};
|
||||
|
||||
export {
|
||||
NOT_DISPLAY_COLUMN_KEYS
|
||||
NOT_DISPLAY_COLUMN_KEYS,
|
||||
VIEW_NOT_DISPLAY_COLUMN_KEYS,
|
||||
} from './common';
|
||||
|
@@ -6,6 +6,7 @@ const CellType = {
|
||||
LAST_MODIFIER: 'last-modifier',
|
||||
MTIME: 'mtime',
|
||||
FILE_NAME: 'file-name',
|
||||
CHECKBOX: 'checkbox',
|
||||
};
|
||||
|
||||
export default CellType;
|
||||
|
@@ -70,6 +70,11 @@ const FILTER_COLUMN_OPTIONS = {
|
||||
FILTER_PREDICATE_TYPE.IS_NOT,
|
||||
],
|
||||
},
|
||||
[CellType.CHECKBOX]: {
|
||||
filterPredicateList: [
|
||||
FILTER_PREDICATE_TYPE.IS,
|
||||
],
|
||||
},
|
||||
[CellType.URL]: {
|
||||
filterPredicateList: [
|
||||
FILTER_PREDICATE_TYPE.CONTAINS,
|
||||
|
@@ -20,6 +20,7 @@ export {
|
||||
SINGLE_CELL_VALUE_COLUMN_TYPE_MAP,
|
||||
PRIVATE_COLUMN_KEY,
|
||||
NOT_DISPLAY_COLUMN_KEYS,
|
||||
VIEW_NOT_DISPLAY_COLUMN_KEYS,
|
||||
} from './column';
|
||||
export {
|
||||
FILTER_CONJUNCTION_TYPE,
|
||||
|
@@ -47,6 +47,7 @@ export {
|
||||
HEADER_HEIGHT_TYPE,
|
||||
PRIVATE_COLUMN_KEY,
|
||||
NOT_DISPLAY_COLUMN_KEYS,
|
||||
VIEW_NOT_DISPLAY_COLUMN_KEYS,
|
||||
} from './constants';
|
||||
|
||||
export {
|
||||
@@ -106,7 +107,6 @@ export {
|
||||
isMac,
|
||||
base64ToFile,
|
||||
bytesToSize,
|
||||
getErrorMsg,
|
||||
DateUtils,
|
||||
CommonlyUsedHotkey,
|
||||
LocalStorage,
|
||||
|
@@ -36,23 +36,6 @@ export const bytesToSize = (bytes) => {
|
||||
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) => {
|
||||
const getType = {};
|
||||
return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
|
||||
|
@@ -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,
|
||||
};
|
@@ -1,3 +1,4 @@
|
||||
export { creatorFilter } from './creator';
|
||||
export { dateFilter } from './date';
|
||||
export { textFilter } from './text';
|
||||
export { checkboxFilter } from './checkbox';
|
||||
|
@@ -6,6 +6,7 @@ import {
|
||||
creatorFilter,
|
||||
dateFilter,
|
||||
textFilter,
|
||||
checkboxFilter,
|
||||
} from './filter-column';
|
||||
import {
|
||||
FILTER_CONJUNCTION_TYPE,
|
||||
@@ -30,6 +31,9 @@ const getFilterResult = (row, filter, { username, userId }) => {
|
||||
case CellType.CREATOR: {
|
||||
return creatorFilter(cellValue, filter, username);
|
||||
}
|
||||
case CellType.CHECKBOX: {
|
||||
return checkboxFilter(cellValue, filter);
|
||||
}
|
||||
default: {
|
||||
return false;
|
||||
}
|
||||
|
@@ -74,7 +74,6 @@ export {
|
||||
isMac,
|
||||
base64ToFile,
|
||||
bytesToSize,
|
||||
getErrorMsg,
|
||||
isFunction,
|
||||
isEmpty,
|
||||
isEmptyObject,
|
||||
|
@@ -33,7 +33,9 @@ class GroupbySetter extends Component {
|
||||
|
||||
const groupbysLength = groupbys ? groupbys.length : 0;
|
||||
const activated = groupbysLength > 0;
|
||||
let groupbyMessage = gettext('Group');
|
||||
|
||||
// need to translate to Group
|
||||
let groupbyMessage = gettext('Group_by');
|
||||
if (groupbysLength === 1) {
|
||||
groupbyMessage = gettext('Grouped by 1 column');
|
||||
} else if (groupbysLength > 1) {
|
||||
|
@@ -404,6 +404,9 @@ class FilterItem extends React.Component {
|
||||
/>
|
||||
);
|
||||
}
|
||||
case CellType.CHECKBOX: {
|
||||
return this.getInputComponent('checkbox');
|
||||
}
|
||||
default: {
|
||||
return null;
|
||||
}
|
||||
|
@@ -108,10 +108,6 @@
|
||||
color: unset;
|
||||
}
|
||||
|
||||
.filter-term .selector-collaborator .sf-metadata-icon-drop-down {
|
||||
padding-left: 5px;
|
||||
}
|
||||
|
||||
.filters-list .selector-collaborator .selected-option-show {
|
||||
text-overflow: unset;
|
||||
}
|
||||
@@ -297,10 +293,6 @@
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.filters-list .filter-checkbox-predicate .sf-metadata-select .selected-option-show {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dropdown-item .collaborator,
|
||||
.filters-list .option-group .option-group-content .collaborator {
|
||||
background-color: unset;
|
||||
|
@@ -14,7 +14,7 @@ const RecordDetailsDialog = () => {
|
||||
updateCollaboratorsCache,
|
||||
queryUserAPI: window.sfMetadataContext.userService.queryUser,
|
||||
record: recordDetails,
|
||||
fields: metadata.columns,
|
||||
fields: metadata.view.columns,
|
||||
fieldIconConfig: COLUMNS_ICON_CONFIG,
|
||||
onToggle: closeRecordDetails,
|
||||
};
|
||||
|
@@ -1,12 +1,14 @@
|
||||
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 { CommonlyUsedHotkey, getErrorMsg } from '../../_basic';
|
||||
import { CommonlyUsedHotkey } from '../../_basic';
|
||||
import { gettext } from '../../utils';
|
||||
import { useMetadata } from '../../hooks';
|
||||
import TableTool from './table-tool';
|
||||
import TableMain from './table-main';
|
||||
import RecordDetailsDialog from '../record-details-dialog';
|
||||
import { PER_LOAD_NUMBER, MAX_LOAD_NUMBER } from '../../constants';
|
||||
import { Utils } from '../../../../utils/utils';
|
||||
|
||||
import './index.css';
|
||||
|
||||
@@ -38,17 +40,39 @@ const Container = () => {
|
||||
setLoadingMore(true);
|
||||
|
||||
try {
|
||||
await store.loadMore();
|
||||
await store.loadMore(PER_LOAD_NUMBER);
|
||||
setLoadingMore(false);
|
||||
} catch (error) {
|
||||
const errorMsg = getErrorMsg(error);
|
||||
toaster.danger(gettext(errorMsg));
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
setLoadingMore(false);
|
||||
return;
|
||||
}
|
||||
|
||||
}, [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) => {
|
||||
// todo: store op
|
||||
}, []);
|
||||
@@ -144,7 +168,7 @@ const Container = () => {
|
||||
return (
|
||||
<>
|
||||
<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">
|
||||
{errorMsg && (<div className="d-center-middle error">{gettext(errorMsg)}</div>)}
|
||||
{!errorMsg && (
|
||||
@@ -161,6 +185,7 @@ const Container = () => {
|
||||
getTableContentWidth={getTableContentWidth}
|
||||
getTableContentLeft={getTableContentLeft}
|
||||
getAdjacentRowsIds={getAdjacentRowsIds}
|
||||
loadAll={loadAll}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
@@ -32,7 +32,7 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
|
||||
return (
|
||||
<div className={classnames('table-main-container container-fluid p-0', { [`group-level-${groupbysCount + 1}`]: groupbysCount > 0 })}>
|
||||
<Records
|
||||
columns={metadata.columns}
|
||||
columns={metadata.view.columns}
|
||||
recordIds={metadata.view.rows || []}
|
||||
groups={metadata.groups}
|
||||
groupbys={metadata.groupbys}
|
||||
@@ -41,7 +41,7 @@ const TableMain = ({ metadata, modifyRecord, modifyRecords, loadMore, loadAll, s
|
||||
hasMore={metadata.hasMore}
|
||||
gridUtils={gridUtils}
|
||||
scrollToLoadMore={loadMore}
|
||||
clickToLoadMore={loadAll}
|
||||
loadAll={loadAll}
|
||||
groupOffsetLeft={groupOffset}
|
||||
modifyRecord={updateRecord}
|
||||
updateRecords={updateRecords}
|
||||
|
@@ -115,26 +115,17 @@ class Records extends Component {
|
||||
|
||||
resizeColumnWidth = (column, width) => {
|
||||
if (width < 50) return;
|
||||
const { table, columns, } = this.props;
|
||||
const { columns } = this.props;
|
||||
const newColumn = Object.assign({}, column, { width });
|
||||
const index = columns.findIndex(item => item.key === column.key);
|
||||
let updateColumns = columns.slice(0);
|
||||
updateColumns[index] = newColumn;
|
||||
updateColumns = setColumnOffsets(updateColumns);
|
||||
const columnMetrics = recalculate(updateColumns, columns, table._id);
|
||||
const columnMetrics = recalculate(updateColumns, columns);
|
||||
this.setState({ columnMetrics }, () => {
|
||||
const oldValue = localStorage.getItem('pages_columns_width');
|
||||
let pagesColumnsWidth = {};
|
||||
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));
|
||||
const oldValue = window.sfMetadataContext.localStorage.getItem('columns_width') || {};
|
||||
window.sfMetadataContext.localStorage.setItem('columns_width', { ...oldValue, [column.key]: width });
|
||||
|
||||
});
|
||||
};
|
||||
|
||||
@@ -742,7 +733,7 @@ class Records extends Component {
|
||||
recordGetterById={this.props.recordGetterById}
|
||||
recordGetterByIndex={this.props.recordGetterByIndex}
|
||||
getRecordsSummaries={() => {}}
|
||||
clickToLoadMore={this.props.clickToLoadMore}
|
||||
loadAll={this.props.loadAll}
|
||||
/>
|
||||
</Fragment>
|
||||
);
|
||||
@@ -768,7 +759,7 @@ Records.propTypes = {
|
||||
updateRecords: PropTypes.func,
|
||||
recordGetterById: PropTypes.func,
|
||||
recordGetterByIndex: PropTypes.func,
|
||||
clickToLoadMore: PropTypes.func,
|
||||
loadAll: PropTypes.func,
|
||||
};
|
||||
|
||||
export default Records;
|
||||
|
@@ -1,13 +1,13 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { toaster } from '@seafile/sf-metadata-ui-component';
|
||||
import toaster from '../../../../../../components/toast';
|
||||
import { gettext } from '../../../../../../utils/constants';
|
||||
|
||||
class LoadAllTip extends React.Component {
|
||||
|
||||
onClick = () => {
|
||||
toaster.closeAll();
|
||||
this.props.clickToLoadMore(100000);
|
||||
this.props.load(100000);
|
||||
};
|
||||
|
||||
render() {
|
||||
@@ -21,7 +21,7 @@ class LoadAllTip extends React.Component {
|
||||
}
|
||||
|
||||
LoadAllTip.propTypes = {
|
||||
clickToLoadMore: PropTypes.func
|
||||
load: PropTypes.func
|
||||
};
|
||||
|
||||
export default LoadAllTip;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { toaster } from '@seafile/sf-metadata-ui-component';
|
||||
import toaster from '../../../../../../components/toast';
|
||||
import { isFunction } from '../../../../_basic';
|
||||
import { isNameColumn } from '../../../../utils/column-utils';
|
||||
import { TABLE_SUPPORT_EDIT_TYPE_MAP } from '../../../../constants';
|
||||
|
@@ -1,6 +1,7 @@
|
||||
import React from 'react';
|
||||
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 LoadAllTip from '../load-all-tip';
|
||||
import RecordMetrics from '../../../../../utils/record-metrics';
|
||||
@@ -17,9 +18,9 @@ class RecordsFooter extends React.Component {
|
||||
return;
|
||||
}
|
||||
const loadNumber = this.props.recordsCount < 50000 ? 50000 : 100000;
|
||||
this.props.clickToLoadMore(loadNumber, (hasMore) => {
|
||||
this.props.loadAll(loadNumber, (hasMore) => {
|
||||
if (hasMore) {
|
||||
toaster.success(<LoadAllTip clickToLoadMore={this.props.clickToLoadMore} />, { duration: 5 });
|
||||
toaster.success(<LoadAllTip load={this.props.loadAll} />, { duration: 5 });
|
||||
} else {
|
||||
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 }}>
|
||||
<span>{this.getRecord()}</span>
|
||||
{!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 &&
|
||||
<span className="loading-message ml-4">
|
||||
@@ -144,7 +145,7 @@ RecordsFooter.propTypes = {
|
||||
recordGetterById: PropTypes.func,
|
||||
recordGetterByIndex: PropTypes.func,
|
||||
getRecordsSummaries: PropTypes.func,
|
||||
clickToLoadMore: PropTypes.func,
|
||||
loadAll: PropTypes.func,
|
||||
};
|
||||
|
||||
export default RecordsFooter;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import deepCopy from 'deep-copy';
|
||||
import { toaster } from '@seafile/sf-metadata-ui-component';
|
||||
import toaster from '../../../../../../components/toast';
|
||||
import {
|
||||
CellType,
|
||||
NOT_SUPPORT_EDIT_COLUMN_TYPE_MAP,
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import classnames from 'classnames';
|
||||
import { FilterSetter, GroupbySetter, SortSetter, HideColumnSetter } from '../../data-process-setter';
|
||||
@@ -8,7 +8,11 @@ import { useCollaborators } from '../../../hooks';
|
||||
|
||||
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();
|
||||
|
||||
@@ -62,7 +66,6 @@ const TableTool = ({ searcherActive, view, columns, modifyFilters, modifySorts,
|
||||
TableTool.propTypes = {
|
||||
searcherActive: PropTypes.bool,
|
||||
view: PropTypes.object,
|
||||
columns: PropTypes.array,
|
||||
modifyFilters: PropTypes.func,
|
||||
modifySorts: PropTypes.func,
|
||||
modifyGroupbys: PropTypes.func,
|
||||
|
@@ -97,7 +97,10 @@ export const DEFAULT_COLUMNS = [
|
||||
{ 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 {
|
||||
EVENT_BUS_TYPE,
|
||||
|
@@ -1,11 +1,10 @@
|
||||
/* eslint-disable react/prop-types */
|
||||
import React, { useContext, useEffect, useRef, useState, useCallback } from 'react';
|
||||
import { toaster } from '@seafile/sf-metadata-ui-component';
|
||||
import { gettext } from '../../../utils/constants';
|
||||
import { getErrorMsg } from '../_basic';
|
||||
import toaster from '../../../components/toast';
|
||||
import Context from '../context';
|
||||
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);
|
||||
|
||||
@@ -22,8 +21,8 @@ export const MetadataProvider = ({
|
||||
}, []);
|
||||
|
||||
const handleTableError = useCallback((error) => {
|
||||
const errorMsg = getErrorMsg(error);
|
||||
toaster.danger(gettext(errorMsg));
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
}, []);
|
||||
|
||||
const updateMetadata = useCallback((data) => {
|
||||
@@ -39,12 +38,12 @@ export const MetadataProvider = ({
|
||||
const repoId = window.sfMetadataContext.getSetting('repoID');
|
||||
storeRef.current = new Store({ context: window.sfMetadataContext, repoId });
|
||||
storeRef.current.initStartIndex();
|
||||
storeRef.current.loadData().then(() => {
|
||||
storeRef.current.loadData(PER_LOAD_NUMBER).then(() => {
|
||||
setMetadata(storeRef.current.data);
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
const errorMsg = getErrorMsg(error);
|
||||
toaster.danger(gettext(errorMsg));
|
||||
const errorMsg = Utils.getErrorMsg(error);
|
||||
toaster.danger(errorMsg);
|
||||
});
|
||||
|
||||
const unsubscribeServerTableChanged = window.sfMetadataContext.eventBus.subscribe(EVENT_BUS_TYPE.SERVER_TABLE_CHANGED, tableChanged);
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { PER_PAGE_COUNT } from '../../constants';
|
||||
import View from './view';
|
||||
|
||||
class Metadata {
|
||||
@@ -12,10 +11,9 @@ class Metadata {
|
||||
this.id_row_map[record._id] = record;
|
||||
});
|
||||
|
||||
this.hasMore = this.rows.length === PER_PAGE_COUNT;
|
||||
|
||||
this.recordsCount = object.recordsCount || this.row_ids.length;
|
||||
this.view = new View(object.view || {});
|
||||
this.hasMore = true;
|
||||
this.recordsCount = this.row_ids.length;
|
||||
this.view = new View(object.view || {}, this.columns);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -1,12 +1,15 @@
|
||||
import { VIEW_NOT_DISPLAY_COLUMN_KEYS } from '../../_basic';
|
||||
|
||||
class View {
|
||||
constructor(object) {
|
||||
constructor(object, columns) {
|
||||
this.filters = object.filters || [];
|
||||
this.filter_conjunction = object.filter_conjunction || 'Or';
|
||||
this.sorts = object.sorts || [];
|
||||
this.groupbys = object.groupbys || [];
|
||||
this.groups = object.groups;
|
||||
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));
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -9,7 +9,7 @@ import DataProcessor from './data-processor';
|
||||
import ServerOperator from './server-operator';
|
||||
import { getColumns } from '../utils/column-utils';
|
||||
import { Metadata, User } from '../model';
|
||||
import { PER_PAGE_COUNT } from '../constants';
|
||||
import { PER_LOAD_NUMBER } from '../constants';
|
||||
|
||||
class Store {
|
||||
|
||||
@@ -17,7 +17,7 @@ class Store {
|
||||
this.repoId = props.repoId;
|
||||
this.data = null;
|
||||
this.context = props.context;
|
||||
this.startIndex = 1;
|
||||
this.startIndex = 0;
|
||||
this.redos = [];
|
||||
this.undos = [];
|
||||
this.pendingOperations = [];
|
||||
@@ -28,7 +28,7 @@ class Store {
|
||||
}
|
||||
|
||||
initStartIndex = () => {
|
||||
this.startIndex = 1;
|
||||
this.startIndex = 0;
|
||||
};
|
||||
|
||||
saveView = () => {
|
||||
@@ -37,21 +37,25 @@ class Store {
|
||||
this.context.localStorage.setItem('view', view);
|
||||
};
|
||||
|
||||
async loadData() {
|
||||
const res = await this.context.getMetadata({ page: this.startIndex });
|
||||
async loadData(limit = PER_LOAD_NUMBER) {
|
||||
const res = await this.context.getMetadata({ start: this.startIndex, limit });
|
||||
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;
|
||||
const loadedCount = rows.length;
|
||||
data.hasMore = loadedCount === limit;
|
||||
this.data = data;
|
||||
this.startIndex += 1;
|
||||
this.startIndex += loadedCount;
|
||||
const collaboratorsRes = await this.context.getCollaborators();
|
||||
this.collaborators = Array.isArray(collaboratorsRes?.data?.user_list) ? collaboratorsRes.data.user_list.map(user => new User(user)) : [];
|
||||
DataProcessor.run(this.data);
|
||||
}
|
||||
|
||||
async loadMore() {
|
||||
async loadMore(limit) {
|
||||
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 || [];
|
||||
if (!Array.isArray(rows) || rows.length === 0) {
|
||||
this.hasMore = false;
|
||||
@@ -63,8 +67,10 @@ class Store {
|
||||
this.data.row_ids.push(record._id);
|
||||
this.data.id_row_map[record._id] = record;
|
||||
});
|
||||
this.data.hasMore = rows.length === PER_PAGE_COUNT;
|
||||
this.startIndex += 1;
|
||||
const loadedCount = rows.length;
|
||||
this.data.hasMore = loadedCount === limit;
|
||||
this.data.recordsCount = this.data.row_ids.length;
|
||||
this.startIndex = this.startIndex + loadedCount;
|
||||
DataProcessor.run(this.data);
|
||||
this.context.eventBus.dispatch(EVENT_BUS_TYPE.LOCAL_TABLE_CHANGED);
|
||||
}
|
||||
|
@@ -132,31 +132,27 @@ export function isColumnSupportDirectEdit(cell, columns) {
|
||||
}
|
||||
|
||||
const _getCustomColumnsWidth = () => {
|
||||
// todo
|
||||
return {};
|
||||
return window.sfMetadataContext.localStorage.getItem('columns_width') || {};
|
||||
};
|
||||
|
||||
export const recalculate = (columns, allColumns, tableId) => {
|
||||
export const recalculate = (columns, allColumns) => {
|
||||
const displayColumns = columns;
|
||||
const displayAllColumns = allColumns;
|
||||
const pageColumnsWidth = _getCustomColumnsWidth(); // get columns width from local storage
|
||||
const totalWidth = displayColumns.reduce((total, column) => {
|
||||
const key = `${tableId}-${column.key}`;
|
||||
const width = pageColumnsWidth[key] || column.width;
|
||||
const width = pageColumnsWidth[column.key] || column.width;
|
||||
total += width;
|
||||
return total;
|
||||
}, 0);
|
||||
let left = SEQUENCE_COLUMN_WIDTH;
|
||||
const frozenColumns = displayColumns.filter(c => isFrozen(c));
|
||||
const frozenColumnsWidth = frozenColumns.reduce((w, column) => {
|
||||
const key = `${tableId}-${column.key}`;
|
||||
const width = pageColumnsWidth[key] || column.width;
|
||||
const width = pageColumnsWidth[column.key] || column.width;
|
||||
return w + width;
|
||||
}, 0);
|
||||
const lastFrozenColumnKey = frozenColumnsWidth > 0 ? frozenColumns[frozenColumns.length - 1].key : null;
|
||||
const newColumns = displayColumns.map((column, index) => {
|
||||
const key = `${tableId}-${column.key}`;
|
||||
const width = pageColumnsWidth[key] || column.width;
|
||||
const width = pageColumnsWidth[column.key] || column.width;
|
||||
column.idx = index; // set column idx
|
||||
column.left = left; // set column offset
|
||||
column.width = width;
|
||||
@@ -192,9 +188,9 @@ export const getColumnName = (key, name) => {
|
||||
case PRIVATE_COLUMN_KEY.FILE_MTIME:
|
||||
return gettext('File last modified time');
|
||||
case PRIVATE_COLUMN_KEY.IS_DIR:
|
||||
return gettext('Is dir');
|
||||
return gettext('Is folder');
|
||||
case PRIVATE_COLUMN_KEY.PARENT_DIR:
|
||||
return gettext('Parent dir');
|
||||
return gettext('Parent folder');
|
||||
case PRIVATE_COLUMN_KEY.FILE_NAME:
|
||||
return gettext('File name');
|
||||
default:
|
||||
@@ -218,6 +214,8 @@ const getColumnType = (key, type) => {
|
||||
return CellType.LAST_MODIFIER;
|
||||
case PRIVATE_COLUMN_KEY.FILE_NAME:
|
||||
return CellType.FILE_NAME;
|
||||
case PRIVATE_COLUMN_KEY.IS_DIR:
|
||||
return CellType.CHECKBOX;
|
||||
default:
|
||||
return type;
|
||||
}
|
||||
@@ -225,6 +223,7 @@ const getColumnType = (key, type) => {
|
||||
|
||||
export const getColumns = (columns) => {
|
||||
if (!Array.isArray(columns) || columns.length === 0) return [];
|
||||
const columnsWidth = window.sfMetadataContext.localStorage.getItem('columns_width') || {};
|
||||
const validColumns = columns.map((column) => {
|
||||
const { type, key, name, ...params } = column;
|
||||
return {
|
||||
@@ -232,7 +231,7 @@ export const getColumns = (columns) => {
|
||||
type: getColumnType(key, type),
|
||||
name: getColumnName(key, name),
|
||||
...params,
|
||||
width: 200,
|
||||
width: columnsWidth[key] || 200,
|
||||
};
|
||||
}).filter(column => !NOT_DISPLAY_COLUMN_KEYS.includes(column.key));
|
||||
let displayColumns = [];
|
||||
|
@@ -153,24 +153,24 @@ class MetadataRecords(APIView):
|
||||
#args check
|
||||
parent_dir = request.GET.get('parent_dir')
|
||||
name = request.GET.get('name')
|
||||
page = request.GET.get('page', 1)
|
||||
per_page = request.GET.get('per_page', 1000)
|
||||
start = request.GET.get('start', 0)
|
||||
limit = request.GET.get('limit', 100)
|
||||
is_dir = request.GET.get('is_dir')
|
||||
order_by = request.GET.get('order_by')
|
||||
|
||||
try:
|
||||
page = int(page)
|
||||
per_page = int(per_page)
|
||||
start = int(start)
|
||||
limit = int(limit)
|
||||
except:
|
||||
page = 1
|
||||
per_page = 1000
|
||||
start = 0
|
||||
limit = 1000
|
||||
|
||||
if page <= 0:
|
||||
error_msg = 'page invalid'
|
||||
if start < 0:
|
||||
error_msg = 'start invalid'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if per_page <= 0:
|
||||
error_msg = 'per_page invalid'
|
||||
if limit < 0:
|
||||
error_msg = 'limit invalid'
|
||||
return api_error(status.HTTP_400_BAD_REQUEST, error_msg)
|
||||
|
||||
if is_dir:
|
||||
@@ -199,7 +199,7 @@ class MetadataRecords(APIView):
|
||||
return api_error(status.HTTP_403_FORBIDDEN, error_msg)
|
||||
|
||||
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:
|
||||
logger.error(err)
|
||||
status_code, reason = err
|
||||
|
@@ -2,7 +2,7 @@ import requests, jwt, time
|
||||
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
|
||||
|
||||
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.file_name.name}` ASC'
|
||||
|
||||
if page:
|
||||
sql += f' LIMIT {(page - 1) * per_page}, {page * per_page}'
|
||||
|
||||
sql += ';'
|
||||
sql += f' LIMIT {start}, {limit};'
|
||||
|
||||
metadata_server_api = MetadataServerAPI(repo_id, user)
|
||||
response_results = metadata_server_api.query_rows(sql, parameters)
|
||||
|
Reference in New Issue
Block a user