// @ts-nocheck import React, { Children, cloneElement, Component, ReactElement, ReactNode, ReactText } from 'react'; import classNames from 'classnames'; import { action, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import omit from 'lodash/omit'; import noop from 'lodash/noop'; import DataSet from 'choerodon-ui/pro/lib/data-set/DataSet'; import DsRecord from 'choerodon-ui/pro/lib/data-set/Record'; import Pagination from 'choerodon-ui/pro/lib/pagination'; import { TablePaginationConfig } from 'choerodon-ui/pro/lib/table/interface'; import ObserverCheckBox from 'choerodon-ui/pro/lib/check-box'; import { PaginationProps } from 'choerodon-ui/pro/lib/pagination/interface'; import autobind from 'choerodon-ui/pro/lib/_util/autobind'; import { BooleanValue, DataSetEvents } from 'choerodon-ui/pro/lib/data-set/enum'; import { getKey } from 'choerodon-ui/pro/lib/tree/util'; import Spin, { SpinProps } from '../spin'; import LocaleReceiver from '../locale-provider/LocaleReceiver'; import defaultLocale from '../locale-provider/default'; import { Size } from '../_util/enum'; import { Row } from '../grid'; import Item from './Item'; import { ListContextProvider } from './ListContext'; import ConfigContext, { ConfigContextValue } from '../config-provider/ConfigContext'; export { ListItemProps, ListItemMetaProps } from './Item'; export type ColumnCount = 1 | 2 | 3 | 4 | 6 | 8 | 12 | 24; export type ColumnType = 'gutter' | 'column' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl'; export interface ListGridType { gutter?: number; column?: ColumnCount; xs?: ColumnCount; sm?: ColumnCount; md?: ColumnCount; lg?: ColumnCount; xl?: ColumnCount; xxl?: ColumnCount; } export interface RowSelection { selectedRowKeys?: ReactText[]; defaultSelectedRowKeys?: ReactText[]; onChange?: (keys: ReactText[]) => void } export interface ListProps { bordered?: boolean; className?: string; children?: ReactNode; dataSource?: any; extra?: ReactNode; grid?: ListGridType; id?: string; itemLayout?: string; loading?: boolean | SpinProps; loadMore?: ReactNode; paginationProps?: PaginationProps & { current?: number, defaultCurrent?: number, defaultPageSize?: number, tiny?: boolean, size?: string, onShowSizeChange?: Function; }; pagination?: TablePaginationConfig | boolean; prefixCls?: string; rowPrefixCls?: string; spinPrefixCls?: string; rowKey?: any; renderItem: any; size?: Size; split?: boolean; header?: ReactNode; footer?: ReactNode; empty?: ReactNode; locale?: Record<string, any>; dataSet?: DataSet; rowSelection?: RowSelection | boolean; } export interface ListLocale { emptyText: string; } @observer export default class List extends Component<ListProps> { static displayName = 'List'; static Item: typeof Item = Item; static get contextType(): typeof ConfigContext { return ConfigContext; } static defaultProps = { dataSource: [], bordered: false, split: true, loading: false, pagination: false, }; context: ConfigContextValue; @observable stateCheckedKeys: ReactText[]; @observable paginationCurrent: number; defaultPaginationProps = { page: 1, pageSize: 10, onChange: (page: number, pageSize: number) => { const { pagination } = this.props; this.paginationCurrent = page; if (pagination && pagination.onChange) { pagination.onChange(page, pageSize); } }, total: 0, }; private keys: { [key: string]: string } = {}; get rowSelectionKeys(): ReactText[] | undefined { const { rowSelection } = this.props; if (rowSelection && typeof rowSelection === 'object') { const { selectedRowKeys, defaultSelectedRowKeys} = rowSelection; return selectedRowKeys || defaultSelectedRowKeys; } } get checkedKeys(): ReactText[] { const { dataSet } = this.props; if (dataSet) { const { checkField, primaryKey } = dataSet.props; if (checkField) { const keys: string[] = []; const field = dataSet.getField(checkField); dataSet.forEach(record => { const key = getKey(record, primaryKey); if (record.get(checkField) === (field ? field.get(BooleanValue.trueValue, record) : true)) { keys.push(key); } }); return keys; } return dataSet.selected.map(selected => String(primaryKey ? selected.get(primaryKey) : selected.id)); } return this.rowSelectionKeys || this.stateCheckedKeys || []; } get paginationProps() { const { dataSet, dataSource, pagination } = this.props; const { pageSize, ...otherProps } = this.defaultPaginationProps; const paginationProps = { ...otherProps, ...pagination || {}, total: dataSet ? dataSet.totalCount : dataSource.length, page: (dataSet && dataSet.paging) ? dataSet.currentPage : (this.paginationCurrent || 1), pageSize: (dataSet && dataSet.paging) ? dataSet.pageSize : (pagination && pagination.pageSize || pageSize), }; return paginationProps; } componentWillMount() { this.handleDataSetLoad(); this.processDataSetListener(true); runInAction(() => { this.stateCheckedKeys = []; }) } componentWillUnmount() { this.processDataSetListener(false); } @autobind handleDataSetLoad() { this.initDefaultCheckRows(); } processDataSetListener(flag: boolean) { const { dataSet } = this.props; if (dataSet) { const handler = flag ? dataSet.addEventListener : dataSet.removeEventListener; handler.call(dataSet, DataSetEvents.load, this.handleDataSetLoad); } } @action initDefaultCheckRows() { const { props: { dataSet, }, } = this; if (dataSet && dataSet.selection) { const { checkField } = dataSet.props; if (checkField) { const field = dataSet.getField(checkField); dataSet.forEach(record => { if (record.get(checkField) === (field ? field.get(BooleanValue.trueValue, record) : true)) { record.isSelected = true; } }); } } } getContextValue() { const { grid } = this.props; const { getPrefixCls } = this.context; return { grid, getPrefixCls, }; } renderItem = (item: ReactElement<any> | DsRecord, index: number) => { const { renderItem, rowKey, dataSet } = this.props; let key; if (typeof rowKey === 'function') { key = rowKey(item); } else if (typeof rowKey === 'string') { key = dataSet ? (item as DsRecord).get(rowKey) : item[rowKey]; } else { key = item.key; } if (!key) { key = `list-item-${index}`; } this.keys[index] = key; if (dataSet) { return renderItem({ dataSet, record: item, index: dataSet.indexOf(item as DsRecord) }); } return renderItem(item, index); }; isSomethingAfterLastItem() { const { loadMore, pagination, footer } = this.props; return !!(loadMore || pagination || footer); } renderEmpty = (contextLocale: ListLocale) => { const { props } = this; const locale = { ...contextLocale, ...props.locale }; return <div className={`${this.getPrefixCls()}-empty-text`}>{locale.emptyText}</div>; }; getPrefixCls() { const { prefixCls } = this.props; const { getPrefixCls } = this.context; return getPrefixCls('list', prefixCls); } @action updateStateKey(value, key) { if (value && !this.stateCheckedKeys.includes(key!)) { this.stateCheckedKeys = [...this.stateCheckedKeys, key] } else if (!value) { this.stateCheckedKeys = this.stateCheckedKeys.filter(x => x !== key) } return this.stateCheckedKeys; } handleChange(value, key) { const { rowSelection, dataSet } = this.props; if (dataSet && dataSet.selection) { const { primaryKey } = dataSet.props; dataSet.forEach(record => { if (getKey(record, primaryKey) === key) { if (!record.isSelected) { dataSet.select(record); } else { dataSet.unSelect(record); } } }); return; } if (rowSelection) { if (typeof rowSelection === 'object') { const { selectedRowKeys, onChange = noop } = rowSelection; let resultKeys: ReactText[] = [] if (selectedRowKeys) { if (value && !selectedRowKeys.includes(key!)) { resultKeys = [...selectedRowKeys, key]; } else if (!value) { resultKeys = selectedRowKeys.filter(x => x !== key); } onChange(resultKeys); return; } const newKeys = this.updateStateKey(value, key); onChange(newKeys); } else if (typeof rowSelection === 'boolean') { this.updateStateKey(value, key); } } } renderCheckBox(key) { const { rowSelection, dataSet } = this.props; const checkboxWrapper = () => { const isChecked = this.checkedKeys.includes(key); return { element: () => ( <div className={`${this.getPrefixCls()}-selection-checkbox`}> <ObserverCheckBox name="ckBox" value={key} onChange={(val) => this.handleChange(val, key)} checked={isChecked} /> </div> ), isChecked, } } if (dataSet) { if (dataSet.selection) { return checkboxWrapper(); } return undefined; } if (rowSelection) { return checkboxWrapper(); } return undefined; } render() { const { paginationProps: { pageSize, page, total, position, onChange = noop }, props: { bordered, split, className, children, itemLayout, loadMore, pagination, grid, dataSource, size, header, footer, empty, loading, rowPrefixCls, spinPrefixCls, dataSet, ...rest }, } = this; const prefixCls = this.getPrefixCls(); let loadingProp = loading; if (typeof loadingProp === 'boolean') { loadingProp = { spinning: loadingProp, }; } const isLoading = loadingProp && loadingProp.spinning; // large => lg // small => sm let sizeCls = ''; switch (size) { case Size.large: sizeCls = 'lg'; break; case Size.small: sizeCls = 'sm'; break; default: } const classString = classNames(prefixCls, className, { [`${prefixCls}-vertical`]: itemLayout === 'vertical', [`${prefixCls}-${sizeCls}`]: sizeCls, [`${prefixCls}-split`]: split, [`${prefixCls}-bordered`]: bordered, [`${prefixCls}-loading`]: isLoading, [`${prefixCls}-grid`]: grid, [`${prefixCls}-something-after-last-item`]: this.isSomethingAfterLastItem(), }); const largestPage = Math.ceil( total / pageSize, ); if (page > largestPage) { this.paginationProps.page = largestPage; } const isDsPagination = (dataSet && dataSet.paging); const paginationContent = pagination ? ( <div className={`${prefixCls}-pagination`}> <Pagination {...this.paginationProps} onChange={onChange} dataSet={isDsPagination ? dataSet : undefined} /> </div> ) : null; let splitDataSource = dataSet ? dataSet.records : [...dataSource]; if (pagination && pageSize) { if ( !dataSet && dataSource.length > (page - 1) * pageSize ) { splitDataSource = [...dataSource].splice( (page - 1) * pageSize, pageSize, ); } else if (dataSet && !dataSet.paging) { splitDataSource = dataSet.slice( (page - 1) * pageSize, pageSize * page, ) } } let childrenContent; childrenContent = isLoading && <div style={{ minHeight: 53 }} />; if (splitDataSource.length > 0) { const items = splitDataSource.map((item: any, index: number) => this.renderItem(item, index)); const childrenList = Children.map(items, (child: any, index) => { const recordKey = this.keys[index]; return cloneElement(child, { key: recordKey, renderCheckBox: this.renderCheckBox(recordKey), }); }); childrenContent = grid ? <Row prefixCls={rowPrefixCls} gutter={grid.gutter}>{childrenList}</Row> : childrenList; } else if (!children && !isLoading && !empty) { childrenContent = ( <LocaleReceiver componentName="Table" defaultLocale={defaultLocale.Table}> {this.renderEmpty} </LocaleReceiver> ); } else { childrenContent = empty; } const paginationPosition = position || 'bottom'; const content = ( <Spin prefixCls={spinPrefixCls} {...loadingProp}> {childrenContent} {children} </Spin> ); return ( <ListContextProvider {...this.getContextValue()}> <div className={classString} {...omit(rest, ['prefixCls', 'rowKey', 'renderItem', 'selectable', 'renderItem', 'locale', 'rowSelection'])}> {(paginationPosition === 'top' || paginationPosition === 'both') && paginationContent} {header && <div className={`${prefixCls}-header`}>{header}</div>} {content} {footer && <div className={`${prefixCls}-footer`}>{footer}</div>} {loadMore || (paginationPosition === 'bottom' || paginationPosition === 'both') && paginationContent} </div> </ListContextProvider> ); } }