diff --git a/.gitignore b/.gitignore index 53b7711d5f..9cdabea555 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules/ build/ +dist/ npm-debug.log *.log coverage/ diff --git a/config-overrides.js b/config-overrides.js index 27f11c7ada..c3493bddc6 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -1,5 +1,6 @@ /* config-overrides.js */ const SentryWebpackPlugin = require('@sentry/webpack-plugin') +const AntdDayjsWebpackPlugin = require('antd-dayjs-webpack-plugin') module.exports = function override(config) { if (config.ignoreWarnings == null) { @@ -26,6 +27,7 @@ module.exports = function override(config) { project: process.env.SENTRY_PROJECT || (isMainnet ? 'ckb-explorer-frontend-mainnet' : 'ckb-explorer-frontend-testnet'), }), + new AntdDayjsWebpackPlugin(), ) } diff --git a/package.json b/package.json index 9b4ce4697c..ed17d60fd1 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "version": "1.0.0", "private": true, "dependencies": { - "@ant-design/icons": "4.8.0", + "@ant-design/icons": "4.8.1", "@nervosnetwork/ckb-sdk-utils": "0.103.1", "@sentry/react": "7.38.0", "@sentry/tracing": "7.38.0", @@ -11,7 +11,7 @@ "axios": "0.21.4", "bignumber.js": "9.0.1", "camelcase": "7.0.0", - "camelcase-keys": "7.0.0", + "camelcase-keys": "7.0.2", "classnames": "2.3.2", "dayjs": "1.11.5", "default-passive-events": "2.0.0", @@ -22,6 +22,7 @@ "i18next": "20.3.5", "jsbi": "3.2.5", "lint-staged": "^13.2.3", + "moment": "2.29.4", "react": "17.0.2", "react-dom": "17.0.2", "react-i18next": "11.11.4", @@ -36,14 +37,14 @@ "three": "0.149.0" }, "devDependencies": { - "@sentry/webpack-plugin": "1.20.0", + "@sentry/webpack-plugin": "1.20.1", "@testing-library/react": "12.1.5", "@types/echarts": "4.9.9", "@types/eslint": "7.28.0", "@types/jest": "26.0.24", "@types/node": "16.4.10", - "@types/react": "17.0.15", - "@types/react-dom": "17.0.9", + "@types/react": "17.0.64", + "@types/react-dom": "17.0.20", "@types/react-outside-click-handler": "^1.3.0", "@types/react-router-dom": "5.1.8", "@types/react-test-renderer": "^17.0.1", @@ -51,6 +52,7 @@ "@types/three": "0.149.0", "@typescript-eslint/eslint-plugin": "^4.29.0", "@typescript-eslint/parser": "5.48.1", + "antd-dayjs-webpack-plugin": "^1.0.6", "create-react-app": "^4.0.3", "eslint": "7.32.0", "eslint-config-airbnb": "18.2.1", diff --git a/src/assets/block_icon.svg b/src/assets/block_icon.svg new file mode 100644 index 0000000000..205a1c3e61 --- /dev/null +++ b/src/assets/block_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/error_icon.svg b/src/assets/error_icon.svg new file mode 100644 index 0000000000..1b8e559e82 --- /dev/null +++ b/src/assets/error_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/export_icon.svg b/src/assets/export_icon.svg new file mode 100644 index 0000000000..4d1b0b02f3 --- /dev/null +++ b/src/assets/export_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/success_icon.svg b/src/assets/success_icon.svg new file mode 100644 index 0000000000..f584a51ef8 --- /dev/null +++ b/src/assets/success_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/CsvExport/index.tsx b/src/components/CsvExport/index.tsx new file mode 100644 index 0000000000..ba4ebd3410 --- /dev/null +++ b/src/components/CsvExport/index.tsx @@ -0,0 +1,18 @@ +import { Link } from 'react-router-dom' +import { useTranslation } from 'react-i18next' +import styles from './styles.module.scss' +import { ReactComponent as ExportIcon } from '../../assets/export_icon.svg' + +export function CsvExport({ type, id }: { type: State.TransactionCsvExportType; id?: string }) { + const [t] = useTranslation() + return ( + +
{t(`export_transactions.csv_export`)}
+ + + ) +} diff --git a/src/components/CsvExport/styles.module.scss b/src/components/CsvExport/styles.module.scss new file mode 100644 index 0000000000..ee443b123b --- /dev/null +++ b/src/components/CsvExport/styles.module.scss @@ -0,0 +1,12 @@ +.exportLink { + height: 50px; + display: flex; + align-items: center; + color: var(--primary-color); + cursor: pointer; + + > div:first-child { + margin-right: 8px; + white-space: nowrap; + } +} diff --git a/src/components/Pagination/styled.tsx b/src/components/Pagination/styled.tsx index 712ced7a51..0fa701a9d9 100644 --- a/src/components/Pagination/styled.tsx +++ b/src/components/Pagination/styled.tsx @@ -9,7 +9,6 @@ export const PaginationPanel = styled.div` justify-content: center; border-radius: 0 0 6px 6px; box-shadow: 0 2px 6px 0 rgb(0 0 0 / 12%); - background-color: #fff; @media (width <= 750px) { margin-bottom: 30px; @@ -204,8 +203,8 @@ export const PaginationRightItem = styled.div` background: #ddd; } - @media (width <= 750px) { - margin: 0 10px; + @media (max-width <= 750px) { + margin-left: 10px; font-size: 12px; } } diff --git a/src/components/PaginationWithRear/index.tsx b/src/components/PaginationWithRear/index.tsx new file mode 100644 index 0000000000..9fe5238352 --- /dev/null +++ b/src/components/PaginationWithRear/index.tsx @@ -0,0 +1,34 @@ +import { ReactNode } from 'react' +import classNames from 'classnames' +import Pagination from '../Pagination' +import styles from './styles.module.scss' + +const PaginationWithRear = ({ + currentPage, + totalPages, + onChange, + paginationClassName, + rear, +}: { + currentPage: number + totalPages: number + onChange: (page: number) => void + paginationClassName?: string + rear: ReactNode +}) => { + return ( +
1 })}> + {totalPages > 1 && ( + + )} +
{rear}
+
+ ) +} + +export default PaginationWithRear diff --git a/src/components/PaginationWithRear/styles.module.scss b/src/components/PaginationWithRear/styles.module.scss new file mode 100644 index 0000000000..2d88474126 --- /dev/null +++ b/src/components/PaginationWithRear/styles.module.scss @@ -0,0 +1,54 @@ +.paginationWithRear { + display: flex; + flex-direction: row; + flex-wrap: wrap; + justify-content: flex-end; + width: 100%; + margin-top: 4px; + background-color: white; + border-radius: 0 0 6px 6px; + box-shadow: 0 2px 6px 0 rgba(0, 0, 0, 0.12); + padding: 0 40px; + + .pagination { + width: fit-content; + box-shadow: none; + flex: 1 0.8 auto; + min-width: 650px; + @media (max-width: 750px) { + min-width: auto; + border-radius: 0; + } + + :global(.pagination__input__page) { + flex: 1; + width: 60px; + max-width: 120px; + } + } + :global(.pagination__goto__page) { + min-width: 42px; + } + + .rear { + display: flex; + justify-content: flex-end; + + @media (max-width: 750px) { + height: 50px; + border-radius: 0 0 6px 6px; + border-top: 1px solid #f0f0f0; + width: 100%; + } + } + + @media (max-width: 750px) { + justify-content: flex-end; + margin-top: 5px; + padding: 0 20px; + + > .pagination { + margin-bottom: 0; + } + } +} diff --git a/src/index.css b/src/index.css index b973f4134a..bbb6b93c93 100644 --- a/src/index.css +++ b/src/index.css @@ -3,6 +3,7 @@ body { --primary-color: #00cc9b; --primary-hover-bg-color: #e8fff1; + --primary-chiffon-color: #e6fcf7; --navbar-height: 64px; --table-separator-color: #f5f5f5; margin: 0; @@ -16,6 +17,7 @@ body { body[data-chain-type='testnet'] { --primary-color: #9a2cec; --primary-hover-bg-color: #eeeafc; + --primary-chiffon-color: #eeeafc; } * { diff --git a/src/locales/en.json b/src/locales/en.json index 2fccb56b70..c8f3220314 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -528,6 +528,29 @@ "transaction_count": "Transaction Count", "n_days_historical_fee_rate": "Average fee rate (Recent {{n}} days)", "count": "Count" + }, + "export_transactions": { + "csv_export": "CSV Export", + "download_data": "Download Data", + "transactions": "Transactions", + "description_str": "The information you requested can be downloaded from this page. Before continuing please verify that you are not a robot by completing the captcha below.", + "select_download_options": "Select download options", + "date": "Date", + "start_date": "Start Date", + "end_date": "End Date", + "from_block": "From Block", + "to_block": "To Block", + "note_str": "The earliest 5,000 records within the selected range will be exported", + "download": "Download", + "block_number": "Block Number", + "please_pick_date": "please pick start date and end date!", + "error_date_order": "end date should be after than start date!", + "download_processed": "Your download is being processed!", + "please_input_block_number": "please input `from block number` and `to block number`!", + "error_block_number_order": "`to block number` should be higher than `from block number`!", + "block_number_should_be_positive": "block number should be positive", + "too_many_blocks": "The selected range exceeds 5,000 blocks,please re-enter the block numbers", + "fetch_processed_export_link_empty": "fetch processed export link empty" } } } diff --git a/src/locales/zh.json b/src/locales/zh.json index ec40910e55..994cd890fe 100644 --- a/src/locales/zh.json +++ b/src/locales/zh.json @@ -527,6 +527,29 @@ "transaction_count": "交易数量", "n_days_historical_fee_rate": " 平均费率 (最近 {{n}} 天)", "count": "数量" + }, + "export_transactions": { + "csv_export": "CSV导出", + "download_data": "下载数据", + "transactions": "交易", + "description_str": "您请求的信息可以从该页面下载。 在继续之前,请通过填写下面的验证码来验证您不是机器人。", + "select_download_options": "选择下载选项", + "date": "日期", + "start_date": "开始日期", + "end_date": "结束日期", + "from_block": "起始块高", + "to_block": "目标块高", + "note_str": "这将导出所选范围内最早的 5,000 条记录", + "download": "下载", + "block_number": "块高", + "please_pick_date": "请选择开始日期和结束日期!", + "error_date_order": "结束日期应该比开始日期晚!", + "download_processed": "您的下载请求已被处理!", + "please_input_block_number": "请输入起始块高和目标块高!", + "error_block_number_order": "目标块高不应该小于起始块高!", + "block_number_should_be_positive": "块高应该输入正数!", + "too_many_blocks": "选择的范围超过了 5,000 个区块, 请重新输入区块高度!", + "fetch_processed_export_link_empty": "获取已处理的导出链接为空" } } } diff --git a/src/pages/Address/AddressComp.tsx b/src/pages/Address/AddressComp.tsx index b50a290c46..13c8191f05 100644 --- a/src/pages/Address/AddressComp.tsx +++ b/src/pages/Address/AddressComp.tsx @@ -2,7 +2,6 @@ import axios, { AxiosResponse } from 'axios' import { useState, useEffect, FC } from 'react' import { useQuery } from 'react-query' import { Radio } from 'antd' -import Pagination from '../../components/Pagination' import OverviewCard, { OverviewItemData } from '../../components/Card/OverviewCard' import TransactionItem from '../../components/TransactionItem/index' import { v2AxiosIns } from '../../service/http/fetcher' @@ -12,7 +11,6 @@ import { shannonToCkb, deprecatedAddrToNewAddr, handleNftImgError, patchMibaoImg import { AddressLockScriptController, AddressLockScriptPanel, - AddressTransactionsPagination, AddressTransactionsPanel, AddressUDTAssetsPanel, AddressUDTItemPanel, @@ -43,6 +41,8 @@ import ArrowUpBlueIcon from '../../assets/arrow_up_blue.png' import ArrowDownIcon from '../../assets/arrow_down.png' import ArrowDownBlueIcon from '../../assets/arrow_down_blue.png' import { omit } from '../../utils/object' +import { CsvExport } from '../../components/CsvExport' +import PaginationWithRear from '../../components/PaginationWithRear' const addressAssetInfo = (address: State.Address, useMiniStyle: boolean) => { const items = [ @@ -411,11 +411,12 @@ export const AddressTransactions = ({ )) )} - {totalPages > 1 && ( - - - - )} + } + /> ) } diff --git a/src/pages/Address/styled.tsx b/src/pages/Address/styled.tsx index f6789933fc..166d712a4e 100644 --- a/src/pages/Address/styled.tsx +++ b/src/pages/Address/styled.tsx @@ -82,11 +82,6 @@ export const AddressTransactionsPanel = styled.div` } ` -export const AddressTransactionsPagination = styled.div` - margin-top: 4px; - width: 100%; -` - export const AddressUDTAssetsPanel = styled.div` display: flex; flex-direction: column; diff --git a/src/pages/BlockList/index.tsx b/src/pages/BlockList/index.tsx index ef85c9f538..bcc531a3c9 100644 --- a/src/pages/BlockList/index.tsx +++ b/src/pages/BlockList/index.tsx @@ -11,7 +11,6 @@ import { deprecatedAddrToNewAddr, shannonToCkb } from '../../utils/util' import { DELAY_BLOCK_NUMBER } from '../../constants/common' import { localeNumberString } from '../../utils/number' import i18n from '../../utils/i18n' -import Pagination from '../../components/Pagination' import DecimalCapacity from '../../components/DecimalCapacity' import { ItemCardData, ItemCardGroup } from '../../components/Card/ItemCard' import AddressText from '../../components/AddressText' @@ -19,6 +18,8 @@ import { useIsMobile, useMediaQuery, usePaginationParamsInListPage, useSortParam import { fetchBlocks } from '../../service/http/fetcher' import { RouteState } from '../../routes/state' import { ReactComponent as SortIcon } from '../../assets/sort_icon.svg' +import { CsvExport } from '../../components/CsvExport' +import PaginationWithRear from '../../components/PaginationWithRear' import styles from './styles.module.scss' const BlockValueItem = ({ value, to }: { value: string; to: string }) => ( @@ -267,9 +268,12 @@ export default () => { )} )} -
- -
+ } + /> ) diff --git a/src/pages/BlockList/styled.tsx b/src/pages/BlockList/styled.tsx index 1ea3b75193..6c3ac94bb2 100644 --- a/src/pages/BlockList/styled.tsx +++ b/src/pages/BlockList/styled.tsx @@ -20,12 +20,6 @@ export const BlockListPanel = styled.div` z-index: 1; } } - - .block_list__pagination { - @media (width <= 750px) { - margin-top: 5px; - } - } ` export const ContentTitle = styled.div` diff --git a/src/pages/ExportTransactions/index.tsx b/src/pages/ExportTransactions/index.tsx new file mode 100644 index 0000000000..44d2582bfd --- /dev/null +++ b/src/pages/ExportTransactions/index.tsx @@ -0,0 +1,297 @@ +import { InputNumber, Tabs } from 'antd' +import DatePicker from 'antd/lib/date-picker' +import * as cnLocale from 'antd/es/date-picker/locale/zh_CN' +import * as enLocale from 'antd/es/date-picker/locale/en_US' +import dayjs, { Dayjs } from 'dayjs' +import { useState } from 'react' +import classNames from 'classnames' +import { useTranslation } from 'react-i18next' +import { useSearchParams, useUpdateSearchParams } from '../../utils/hook' +import Content from '../../components/Content' +import styles from './styles.module.scss' +import 'dayjs/locale/es-us' +import 'dayjs/locale/zh-cn' +import { ReactComponent as BlockIcon } from '../../assets/block_icon.svg' +import { ReactComponent as ErrorIcon } from '../../assets/error_icon.svg' +import { ReactComponent as SuccessIcon } from '../../assets/success_icon.svg' +import { omit } from '../../utils/object' +import { exportTransactions } from '../../service/http/fetcher' + +const ExportTransactions = () => { + const [t, { language }] = useTranslation() + + const locale = language === 'zh' ? cnLocale.default : enLocale.default + + const { + type: typeStr, + id, + tab = 'date', + 'start-date': startDateStr, + 'end-date': endDateStr, + 'from-height': fromHeightStr, + 'to-height': toHeightStr, + } = useSearchParams('type', 'id', 'tab', 'start-date', 'end-date', 'from-height', 'to-height') + function isTransactionCsvExportType(s?: string): s is State.TransactionCsvExportType { + return !!s && ['address_transactions', 'blocks', 'udts', 'nft'].includes(s) + } + + const type = isTransactionCsvExportType(typeStr) ? typeStr : 'blocks' + + const startDate = tab === 'date' && startDateStr ? dayjs(startDateStr) : undefined + const endDate = tab === 'date' && endDateStr ? dayjs(endDateStr) : undefined + + const fromHeight = tab === 'height' && fromHeightStr !== undefined ? parseInt(fromHeightStr, 10) : undefined + const toHeight = tab === 'height' && toHeightStr !== undefined ? parseInt(toHeightStr, 10) : undefined + + const updateSearchParams = useUpdateSearchParams< + 'type' | 'tab' | 'start-date' | 'end-date' | 'from-height' | 'to-height' + >() + + if (!['date', 'height'].includes(tab)) { + updateSearchParams(params => ({ ...params, tab: 'date' }), true) + } + + if (tab === 'date') { + const now = dayjs().endOf('day') + if ((startDate && startDate > now) || (endDate && endDate > now) || (startDate && endDate && startDate > endDate)) { + updateSearchParams(params => omit(params, ['start-date', 'end-date']), true) + } + } + + const [hint, setHint] = useState<{ type: 'success' | 'error' | undefined; msg?: string; extraMsg?: string }>({ + type: undefined, + msg: undefined, + extraMsg: undefined, + }) + + const handleTabChange = (tab?: string) => { + updateSearchParams(params => omit({ ...params, tab }, ['start-date', 'end-date', 'from-height', 'to-height']), true) + setHint({ type: undefined }) + } + + const disabledStartDate = (current: Dayjs) => { + return ( + (current && current > dayjs().endOf('day')) || (current && endDate && current >= endDate.endOf('day')) || false + ) + } + + const disabledEndDate = (current: Dayjs) => { + return ( + (current && current > dayjs().endOf('day')) || + (current && startDate && current <= startDate.endOf('day')) || + false + ) + } + + const handleDownload = () => { + if (tab === 'date') { + if (!startDate || !endDate) { + setHint({ type: 'error', msg: 'please_pick_date' }) + return + } + if (endDate.isBefore(startDate)) { + setHint({ type: 'error', msg: 'error_date_order' }) + return + } + } + if (tab === 'height') { + if (fromHeight === undefined || toHeight === undefined) { + setHint({ type: 'error', msg: 'please_input_block_number' }) + return + } + if (toHeight < fromHeight) { + setHint({ type: 'error', msg: 'error_block_number_order' }) + return + } + if (fromHeight < 0 || toHeight < 0) { + setHint({ type: 'error', msg: 'block_number_should_be_positive' }) + return + } + if (toHeight >= fromHeight + 5000) { + setHint({ type: 'error', msg: 'too_many_blocks' }) + return + } + } + setHint({ type: 'success', msg: 'download_processed' }) + exportTransactions({ + type, + id, + date: tab === 'date' ? { start: startDate, end: endDate } : undefined, + block: tab === 'height' ? { from: fromHeight!, to: toHeight! } : undefined, + }) + .then((resp: string | null) => { + if (!resp) { + setHint({ + type: 'error', + msg: 'fetch_processed_export_link_empty', + }) + return + } + + const a = document.createElement('a') + a.href = encodeURI(`data:,${resp.replaceAll('#', '')}`) + a.download = `exported-txs-${type}${type === 'blocks' ? '' : `-${id}`}-${ + tab === 'date' && startDate ? startDate.format('YYYY-MM-DD') : '' + }${tab === 'height' ? fromHeight : ''}-${tab === 'date' && endDate ? endDate.format('YYYY-MM-DD') : ''}${ + tab === 'height' ? toHeight : '' + }.csv` + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + }) + .catch(reason => { + setHint({ + type: 'error', + msg: 'fetch_processed_export_link_error', + extraMsg: `: ${reason}`, + }) + }) + } + + const heightParser = (t?: string) => { + const res = t ? Math.abs(parseInt(t, 10)) : 0 + return res < 0 ? 0 : res + } + + const disableDownload = + (tab === 'date' && (!startDate || !endDate)) || + (tab === 'height' && (fromHeight === undefined || toHeight === undefined)) + + return ( + +
+
+ {t('export_transactions.download_data')} + ({t('export_transactions.transactions')}) +
+
+
{t('export_transactions.description_str')}
+
+
+
+
{t('export_transactions.select_download_options')}
+
+ +
+
{t('export_transactions.start_date')}
+ { + updateSearchParams( + params => + d + ? { ...params, 'start-date': d.format('YYYY-MM-DD') } + : omit(params, ['start-date']), + true, + ) + }} + /> +
+
+
{t('export_transactions.end_date')}
+ + updateSearchParams( + params => + d ? { ...params, 'end-date': d.format('YYYY-MM-DD') } : omit(params, ['end-date']), + true, + ) + } + /> +
+
+ ), + }, + { + label: t('export_transactions.block_number'), + key: 'height', + children: ( +
+
+
{t('export_transactions.from_block')}
+ } + value={fromHeight} + min={0} + parser={heightParser} + onChange={h => + updateSearchParams( + params => + h ? { ...params, 'from-height': h.toString() } : omit(params, ['from-height']), + true, + ) + } + /> +
+
+
{t('export_transactions.to_block')}
+ } + min={0} + parser={heightParser} + value={toHeight} + onChange={h => + updateSearchParams( + params => (h ? { ...params, 'to-height': h.toString() } : omit(params, ['to-height'])), + true, + ) + } + /> +
+
+ ), + }, + ]} + /> +
+
+
+
{t('export_transactions.note_str')}
+
+
+
+ {hint.type === 'error' && } + {hint.type === 'success' && } +
+ {t(`export_transactions.${hint.msg}`)} + {hint.extraMsg} +
+
+
+
+ +
+
+ +
+ ) +} + +export default ExportTransactions diff --git a/src/pages/ExportTransactions/styles.module.scss b/src/pages/ExportTransactions/styles.module.scss new file mode 100644 index 0000000000..b30230eb96 --- /dev/null +++ b/src/pages/ExportTransactions/styles.module.scss @@ -0,0 +1,335 @@ +.containerPanel { + display: flex; + flex-direction: column; + align-items: center; + + @media screen and (max-width: 750px) { + margin: 0; + } +} + +.title { + display: flex; + justify-content: center; + margin-top: 40px; + @media screen and (max-width: 750px) { + margin-top: 20px; + width: calc(100% - 32px); + justify-content: start; + } + + > span { + font-weight: 500; + font-size: 24px; + line-height: 28px; + + @media screen and (max-width: 750px) { + font-size: 20px; + line-height: normal; + } + } + > span:first-child { + color: #333333; + } + > span:last-child { + color: #999999; + } +} + +.description { + display: flex; + justify-content: center; + margin-top: 16px; + + > div { + width: 60%; + text-align: center; + font-weight: 400; + font-size: 16px; + line-height: 24px; + color: #333333; + } + + @media (max-width: 750px) { + display: none; + } +} + +.exportPanel { + display: flex; + flex-direction: column; + justify-content: center; + margin-top: 24px; + margin-bottom: 80px; + width: 70%; + min-width: 342px; + border-radius: 4px; + background: white; + @media screen and (max-width: 750px) { + margin-top: 12px; + margin-bottom: 20px; + width: calc(100% - 32px); + } + + > div { + display: flex; + justify-content: center; + width: 100%; + } + .exportHeader { + flex-direction: column; + > div:first-child { + padding-top: 24px; + padding-bottom: 4px; + @media screen and (max-width: 750px) { + padding: 16px 0; + } + text-align: center; + font-weight: 500; + font-size: 16px; + color: #333333; + } + + :global(div.ant-tabs-nav-wrap) { + justify-content: center; + } + + :global(div.ant-tabs-tab) { + width: 120px; + margin-left: 90px; + margin-right: 90px; + padding: 20px 0; + justify-content: center; + align-items: center; + + @media (max-width: 750px) { + width: 80px; + margin-left: 10px; + margin-right: 10px; + padding: 8px 0; + } + } + + :global(div.ant-tabs-ink-bar) { + background: var(--primary-color); + } + + :global(.ant-tabs-tab .ant-tabs-tab-btn) { + font-weight: 400; + font-size: 14px; + line-height: 16px; + color: #333333; + } + + :global(.ant-tabs-tab.ant-tabs-tab-active .ant-tabs-tab-btn) { + font-weight: 500; + font-size: 14px; + line-height: 16px; + color: #333333; + } + + @media (max-width: 750px) { + margin-top: 12px; + } + } + + .dateOrBlockPanel { + display: flex; + justify-content: center; + align-items: center; + margin-top: 16px; + color: #666; + + @media (max-width: 750px) { + margin-top: 8px; + flex-direction: column; + + > div:nth-child(2) { + margin-top: 24px; + } + } + + .datePickerPanel, + .heightInputPanel { + margin-left: 24px; + margin-right: 24px; + @media screen and (max-width: 750px) { + margin-left: 16px; + margin-right: 16px; + } + display: flex; + flex-direction: column; + width: 40%; + max-width: 256px; + :global(.ant-picker:hover) { + border-radius: 4px; + &:hover { + border-color: var(--primary-color) !important; + } + } + :global(.ant-input-number-affix-wrapper) { + &:hover { + border-color: var(--primary-color) !important; + } + border-radius: 4px; + } + + :global(.ant-input-number-handler-wrap) { + display: none !important; + } + + @media (max-width: 750px) { + width: 100%; + max-width: 312px; + } + + > div:first-child { + margin-bottom: 12px; + @media (max-width: 750px) { + margin-bottom: 8px; + } + } + + > div:last-child { + width: 100%; + + input { + text-align: center; + } + } + } + + :global(.ant-picker-input) { + flex-direction: row-reverse; + + > input { + text-align: center; + } + } + } + + .note { + display: flex; + justify-content: center; + color: #999; + line-height: 24px; + + > div { + background: white; + text-align: center; + padding-top: 56px; + padding-bottom: 40px; + + @media (max-width: 750px) { + padding: 24px 8px 16px; + max-width: 312px; + } + } + } + + .hint { + > div { + padding: 12px; + font-weight: 400; + font-size: 16px; + line-height: 16px; + border-radius: 4px; + display: flex; + flex-direction: row; + justify-content: center; + + > svg { + min-width: 14px; + min-height: 14px; + margin-right: 8px; + margin-top: 3px; + } + + &.noHint { + visibility: hidden; + } + + &.successHint, + &.errorHint { + width: 100%; + border-radius: 8px; + max-width: 560px; + font-size: 14px; + + @media screen and (max-width: 750px) { + max-width: 312px; + } + } + &.successHint { + border: 1px solid rgba(0, 204, 155, 1); + background: rgba(230, 252, 247, 1); + color: rgba(0, 204, 155, 1); + path { + fill: rgba(0, 204, 155, 1); + } + } + .hintText { + line-height: 20px; + word-break: break-all; + } + &.errorHint { + border: 1px solid #ffa6a6; + background: #fff2f2; + color: #fa504f; + } + } + } + + .downloadButton { + > button { + text-align: center; + margin-top: 32px; + margin-bottom: 56px; + width: 152px; + height: 48px; + &[disabled] { + opacity: 0.5; + cursor: not-allowed; + } + @media screen and (max-width: 750px) { + margin-top: 20px; + margin-bottom: 24px; + } + background: var(--primary-color); + color: white; + border: none; + border-radius: 4px; + padding: 14px 40px; + cursor: pointer; + font-weight: 400; + font-size: 16px; + line-height: 19px; + } + } +} + +.calendar { + :global(.ant-picker-cell-selected .ant-picker-cell-inner) { + background-color: var(--primary-color) !important; + } + + :global(.ant-picker-cell-today .ant-picker-cell-inner) { + &::before { + border: 1px solid var(--primary-color); + } + } + + :global(.ant-picker-today-btn) { + color: var(--primary-color); + &:hover { + color: var(--primary-color); + } + &:active { + color: var(--primary-color); + } + } +} + +:global(.ant-picker-focused) { + border-color: var(--primary-color) !important; +} diff --git a/src/pages/NftCollectionInfo/index.tsx b/src/pages/NftCollectionInfo/index.tsx index 7fa5e3dbc7..18f62bca37 100644 --- a/src/pages/NftCollectionInfo/index.tsx +++ b/src/pages/NftCollectionInfo/index.tsx @@ -15,6 +15,8 @@ import { v2AxiosIns } from '../../service/http/fetcher' import i18n from '../../utils/i18n' import { useSearchParams, useIsMobile } from '../../utils/hook' import styles from './styles.module.scss' +import { CsvExport } from '../../components/CsvExport' +import PaginationWithRear from '../../components/PaginationWithRear' export interface InventoryRes { data: Array<{ @@ -256,10 +258,11 @@ const NftCollectionInfo = () => { isLoading={isTransferListLoading} collection={id} /> - } /> ) : null} diff --git a/src/pages/SimpleUDT/SimpleUDTComp.tsx b/src/pages/SimpleUDT/SimpleUDTComp.tsx index 95923bf4e4..67bb21bbfe 100644 --- a/src/pages/SimpleUDT/SimpleUDTComp.tsx +++ b/src/pages/SimpleUDT/SimpleUDTComp.tsx @@ -1,7 +1,6 @@ import { ReactNode } from 'react' import { Tooltip } from 'antd' import { Link } from 'react-router-dom' -import Pagination from '../../components/Pagination' import OverviewCard, { OverviewItemData } from '../../components/Card/OverviewCard' import TransactionItem from '../../components/TransactionItem/index' import i18n from '../../utils/i18n' @@ -11,6 +10,8 @@ import { ReactComponent as OpenInNew } from '../../assets/open_in_new.svg' import { deprecatedAddrToNewAddr } from '../../utils/util' import styles from './styles.module.scss' import AddressText from '../../components/AddressText' +import PaginationWithRear from '../../components/PaginationWithRear' +import { CsvExport } from '../../components/CsvExport' const addressContent = (address: string) => { if (!address) { @@ -85,6 +86,7 @@ export const SimpleUDTComp = ({ total, onPageChange, filterNoResult, + id, }: { currentPage: number pageSize: number @@ -92,6 +94,7 @@ export const SimpleUDTComp = ({ total: number onPageChange: (page: number) => void filterNoResult?: boolean + id: string }) => { const totalPages = Math.ceil(total / pageSize) @@ -120,7 +123,12 @@ export const SimpleUDTComp = ({ {totalPages > 1 && ( - + } + /> )} diff --git a/src/pages/SimpleUDT/index.tsx b/src/pages/SimpleUDT/index.tsx index 803edd4b32..ed2c1e0ef4 100644 --- a/src/pages/SimpleUDT/index.tsx +++ b/src/pages/SimpleUDT/index.tsx @@ -186,6 +186,7 @@ export const SimpleUDT = () => { total={data.total} onPageChange={setPage} filterNoResult={filterNoResult} + id={typeHash} /> )} diff --git a/src/pages/Tokens/index.tsx b/src/pages/Tokens/index.tsx index 168329c412..e84c4a8671 100644 --- a/src/pages/Tokens/index.tsx +++ b/src/pages/Tokens/index.tsx @@ -13,7 +13,6 @@ import { TokensLoadingPanel, TokensTitlePanel, TokensItemNamePanel, - TokensPagination, } from './styled' import HelpIcon from '../../assets/qa_help.png' import { parseDateNoTime } from '../../utils/date' @@ -27,6 +26,7 @@ import styles from './styles.module.scss' import { useIsMobile, usePaginationParamsInPage } from '../../utils/hook' import { fetchTokens } from '../../service/http/fetcher' import { QueryResult } from '../../components/QueryResult' +import Pagination from '../../components/Pagination' const TokenItem = ({ token, isLast }: { token: State.UDT; isLast?: boolean }) => { const { displayName, fullName, uan } = token @@ -156,11 +156,7 @@ export default () => { )} - {totalPages > 1 && ( - - - - )} + ) diff --git a/src/pages/Tokens/styled.tsx b/src/pages/Tokens/styled.tsx index 331e118fd6..52c15069b4 100644 --- a/src/pages/Tokens/styled.tsx +++ b/src/pages/Tokens/styled.tsx @@ -281,8 +281,3 @@ export const TokensTitlePanel = styled.div` text-align: left; } ` - -export const TokensPagination = styled.div` - margin-top: 4px; - width: 100%; -` diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 6b6a83a737..e81b25a9be 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -56,6 +56,7 @@ const InflationRateChart = lazy(() => import('../pages/StatisticsChart/monetary/ const LiquidityChart = lazy(() => import('../pages/StatisticsChart/monetary/Liquidity')) const ScriptList = lazy(() => import('../pages/ScriptList')) const FeeRateTracker = lazy(() => import('../pages/FeeRateTracker')) +const ExportTransactions = lazy(() => import('../pages/ExportTransactions')) const Containers: CustomRouter.Route[] = [ { @@ -316,6 +317,12 @@ const Containers: CustomRouter.Route[] = [ exact: true, comp: ErrorPage, }, + { + name: 'ExportTransactions', + path: '/export-transactions', + exact: true, + comp: ExportTransactions, + }, ] const useRouter = (callback: Function) => { diff --git a/src/service/http/fetcher.ts b/src/service/http/fetcher.ts index c5d98409c4..f44d07dd81 100644 --- a/src/service/http/fetcher.ts +++ b/src/service/http/fetcher.ts @@ -1,5 +1,6 @@ import axios, { AxiosResponse } from 'axios' import BigNumber from 'bignumber.js' +import { Dayjs } from 'dayjs' import CONFIG from '../../config' import { pick } from '../../utils/object' import { toCamelcase } from '../../utils/util' @@ -480,3 +481,30 @@ export const fetchMaintenanceInfo = () => axiosIns(`/statistics/maintenance_info`).then((res: AxiosResponse) => toCamelcase>(res.data.data), ) + +export const exportTransactions = ({ + type, + id, + date, + block, +}: { + type: State.TransactionCsvExportType + id?: string + date?: Record<'start' | 'end', Dayjs | undefined> + block?: Record<'from' | 'to', number> +}) => { + const rangeParams = { + start_date: date?.start?.valueOf(), + end_date: date?.end?.valueOf(), + start_number: block?.from, + end_number: block?.to, + } + if (type === 'nft') { + return v2AxiosIns + .get(`/nft/transfers/download_csv`, { params: { ...rangeParams, collection_id: id } }) + .then(res => toCamelcase(res.data)) + } + return axiosIns + .get(`/${type}/download_csv`, { params: { ...rangeParams, id } }) + .then(res => toCamelcase(res.data)) +} diff --git a/src/types/datepicker.d.ts b/src/types/datepicker.d.ts new file mode 100644 index 0000000000..d9dd20192d --- /dev/null +++ b/src/types/datepicker.d.ts @@ -0,0 +1,110 @@ +/** + * We use Ant design's date picker component, but it's use moment.js as default, + * which will has enormous size in bundle. + * So we use dayjs instead of moment.js, and use dayjs plugin to make it work. + * This file aims to override the type definition of Ant design's date picker component. + */ + +declare module 'antd/lib/date-picker' { + import type { Dayjs } from 'dayjs' + import type { + PickerDateProps, + PickerProps, + RangePickerProps as BaseRangePickerProps, + } from 'antd/lib/date-picker/generatePicker' + + export type DatePickerProps = PickerProps + export type MonthPickerProps = Omit, 'picker'> + export type WeekPickerProps = Omit, 'picker'> + export type RangePickerProps = BaseRangePickerProps + declare const DatePicker: import('antd/lib/date-picker/generatePicker/interface').PickerComponentClass< + PickerProps & { + status?: '' | 'warning' | 'error' | undefined + dropdownClassName?: string | undefined + popupClassName?: string | undefined + }, + unknown + > & { + WeekPicker: import('antd/lib/date-picker/generatePicker/interface').PickerComponentClass< + Omit< + PickerProps & { + status?: '' | 'warning' | 'error' | undefined + dropdownClassName?: string | undefined + popupClassName?: string | undefined + }, + 'picker' + >, + unknown + > + MonthPicker: import('antd/lib/date-picker/generatePicker/interface').PickerComponentClass< + Omit< + PickerProps & { + status?: '' | 'warning' | 'error' | undefined + dropdownClassName?: string | undefined + popupClassName?: string | undefined + }, + 'picker' + >, + unknown + > + YearPicker: import('antd/lib/date-picker/generatePicker/interface').PickerComponentClass< + Omit< + PickerProps & { + status?: '' | 'warning' | 'error' | undefined + dropdownClassName?: string | undefined + popupClassName?: string | undefined + }, + 'picker' + >, + unknown + > + RangePicker: import('antd/lib/date-picker/generatePicker/interface').PickerComponentClass< + BaseRangePickerProps & { + dropdownClassName?: string | undefined + popupClassName?: string | undefined + }, + unknown + > + TimePicker: import('antd/lib/date-picker/generatePicker/interface').PickerComponentClass< + Omit< + Omit< + import('rc-picker/lib/Picker').PickerTimeProps, + 'locale' | 'generateConfig' | 'hideHeader' | 'components' + > & { + locale?: import('./generatePicker').PickerLocale | undefined + size?: import('../button').ButtonSize + placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight' | undefined + bordered?: boolean | undefined + status?: '' | 'warning' | 'error' | undefined + } & { + status?: '' | 'warning' | 'error' | undefined + dropdownClassName?: string | undefined + popupClassName?: string | undefined + }, + 'picker' + >, + unknown + > + QuarterPicker: import('antd/lib/date-picker/generatePicker/interface').PickerComponentClass< + Omit< + Omit< + import('rc-picker/lib/Picker').PickerTimeProps, + 'locale' | 'generateConfig' | 'hideHeader' | 'components' + > & { + locale?: import('./generatePicker').PickerLocale | undefined + size?: import('../button').ButtonSize + placement?: 'bottomLeft' | 'bottomRight' | 'topLeft' | 'topRight' | undefined + bordered?: boolean | undefined + status?: '' | 'warning' | 'error' | undefined + } & { + status?: '' | 'warning' | 'error' | undefined + dropdownClassName?: string | undefined + popupClassName?: string | undefined + }, + 'picker' + >, + unknown + > + } + export default DatePicker +} diff --git a/src/types/index.d.ts b/src/types/index.d.ts index 38e70d98f2..e9a8ecc890 100644 --- a/src/types/index.d.ts +++ b/src/types/index.d.ts @@ -572,6 +572,8 @@ declare namespace State { export interface PagePayload extends PageState {} + export type TransactionCsvExportType = 'address_transactions' | 'blocks' | 'udts' | 'nft' + export interface App { toast: ToastMessage | null appErrors: [ diff --git a/src/utils/date.ts b/src/utils/date.ts index 9d7adb71c2..d7804fae6f 100644 --- a/src/utils/date.ts +++ b/src/utils/date.ts @@ -4,11 +4,16 @@ import updateLocale from 'dayjs/plugin/updateLocale' import 'dayjs/locale/zh-cn' import 'dayjs/locale/en' import BigNumber from 'bignumber.js' +import weekday from 'dayjs/plugin/weekday' +import localeData from 'dayjs/plugin/localeData' import i18n from './i18n' dayjs.extend(relativeTime) dayjs.extend(updateLocale) +dayjs.extend(weekday) +dayjs.extend(localeData) + export { dayjs } export const formatData = (data: number) => (data < 10 ? `0${data}` : data) diff --git a/src/utils/object.ts b/src/utils/object.ts index ee8c2ca591..c0a0928afc 100644 --- a/src/utils/object.ts +++ b/src/utils/object.ts @@ -6,11 +6,10 @@ export function pick(obj: T, keys: K[]): Pick< return newObj as Pick } -export function omit, K extends keyof T>(obj: T, keys: K[]) { - const newObj = {} as Omit - const newKeys = Object.keys(obj).filter(k => !keys.includes(k as K)) as Exclude[] - newKeys.forEach(key => { - if (obj[key] !== undefined) newObj[key] = obj[key] +export function omit, U extends keyof T>(obj: T, keys: U[]): Omit { + const newObj = { ...obj } + keys.forEach(key => { + delete newObj[key] }) return newObj } diff --git a/yarn.lock b/yarn.lock index 9eac3ae9e6..5615a0e762 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22,7 +22,24 @@ resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.2.1.tgz#8630da8eb4471a4aabdaed7d1ff6a97dcb2cf05a" integrity sha512-EB0iwlKDGpG93hW8f85CTJTs4SvMX7tt5ceupvhALp1IF44SeUFOMhKUOYqpsoYWQKAOuTRDMqn75rEaKDp0Xw== -"@ant-design/icons@4.8.0", "@ant-design/icons@^4.7.0": +"@ant-design/icons-svg@^4.3.0": + version "4.3.0" + resolved "https://registry.yarnpkg.com/@ant-design/icons-svg/-/icons-svg-4.3.0.tgz#cd8d3624bba50975e848591cea12cb6be132cd82" + integrity sha512-WOgvdH/1Wl8Z7VXigRbCa5djO14zxrNTzvrAQzhWiBQtEKT0uTc8K1ltjKZ8U1gPn/wXhMA8/jE39SJl0WNxSg== + +"@ant-design/icons@4.8.1": + version "4.8.1" + resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.8.1.tgz#44f6c81f609811d68d48a123eb5dcc477f8fbcb7" + integrity sha512-JRAuiqllnMsiZIO8OvBOeFconprC3cnMpJ9MvXrHh+H5co9rlg8/aSHQfLf5jKKe18lUgRaIwC2pz8YxH9VuCA== + dependencies: + "@ant-design/colors" "^6.0.0" + "@ant-design/icons-svg" "^4.3.0" + "@babel/runtime" "^7.11.2" + classnames "^2.2.6" + lodash "^4.17.15" + rc-util "^5.9.4" + +"@ant-design/icons@^4.7.0": version "4.8.0" resolved "https://registry.yarnpkg.com/@ant-design/icons/-/icons-4.8.0.tgz#3084e2bb494cac3dad6c0392f77c1efc90ee1fa4" integrity sha512-T89P2jG2vM7OJ0IfGx2+9FC5sQjtTzRSz+mCHTXkFn/ELZc2YpfStmYHmqzq2Jx55J0F7+O6i5/ZKFSVNWCKNg== @@ -1280,6 +1297,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.21.0": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" + integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== + dependencies: + regenerator-runtime "^0.13.11" + "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6": version "7.10.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.10.5.tgz#303d8bd440ecd5a491eae6117fd3367698674c5c" @@ -2018,10 +2042,10 @@ "@sentry/utils" "7.38.0" tslib "^1.9.3" -"@sentry/cli@^1.74.6": - version "1.75.0" - resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.75.0.tgz#4a5e71b5619cd4e9e6238cc77857c66f6b38d86a" - integrity sha512-vT8NurHy00GcN8dNqur4CMIYvFH3PaKdkX3qllVvi4syybKqjwoz+aWRCvprbYv0knweneFkLt1SmBWqazUMfA== +"@sentry/cli@^1.75.1": + version "1.75.2" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-1.75.2.tgz#2c38647b38300e52c9839612d42b7c23f8d6455b" + integrity sha512-CG0CKH4VCKWzEaegouWfCLQt9SFN+AieFESCatJ7zSuJmzF05ywpMusjxqRul6lMwfUhRKjGKOzcRJ1jLsfTBw== dependencies: https-proxy-agent "^5.0.0" mkdirp "^0.5.5" @@ -2082,12 +2106,12 @@ "@sentry/types" "7.38.0" tslib "^1.9.3" -"@sentry/webpack-plugin@1.20.0": - version "1.20.0" - resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.20.0.tgz#e7add76122708fb6b4ee7951294b521019720e58" - integrity sha512-Ssj1mJVFsfU6vMCOM2d+h+KQR7QHSfeIP16t4l20Uq/neqWXZUQ2yvQfe4S3BjdbJXz/X4Rw8Hfy1Sd0ocunYw== +"@sentry/webpack-plugin@1.20.1": + version "1.20.1" + resolved "https://registry.yarnpkg.com/@sentry/webpack-plugin/-/webpack-plugin-1.20.1.tgz#e70a2fe516f3a39a132acfa841e4f2ea2a1cecd2" + integrity sha512-klOLkfM/oSYzcR2M9oDmJA5/Mdaw0Mtck/h820Z+gqpd6WJepjhqVDel1z2VddaP/XMY0Dj6elCGp2/nDWNr0w== dependencies: - "@sentry/cli" "^1.74.6" + "@sentry/cli" "^1.75.1" webpack-sources "^2.0.0 || ^3.0.0" "@sinclair/typebox@^0.24.1": @@ -2513,9 +2537,9 @@ integrity sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow== "@types/prop-types@*": - version "15.7.3" - resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.3.tgz#2ab0d5da2e5815f94b0b9d4b95d1e5f243ab2ca7" - integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw== + version "15.7.5" + resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.5.tgz#5f19d2b85a98e9558036f6a3cacc8819420f05cf" + integrity sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w== "@types/q@^1.5.1": version "1.5.5" @@ -2532,12 +2556,12 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.4.tgz#cd667bcfdd025213aafb7ca5915a932590acdcdc" integrity sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw== -"@types/react-dom@17.0.9": - version "17.0.9" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.9.tgz#441a981da9d7be117042e1a6fd3dac4b30f55add" - integrity sha512-wIvGxLfgpVDSAMH5utdL9Ngm5Owu0VsGmldro3ORLXV8CShrL8awVj06NuEXFQ5xyaYfdca7Sgbk/50Ri1GdPg== +"@types/react-dom@17.0.20": + version "17.0.20" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.20.tgz#e0c8901469d732b36d8473b40b679ad899da1b53" + integrity sha512-4pzIjSxDueZZ90F52mU3aPoogkHIoSIDG+oQ+wQK7Cy2B9S+MvOqY0uEA/qawKz381qrEDkvpwyt8Bm31I8sbA== dependencies: - "@types/react" "*" + "@types/react" "^17" "@types/react-dom@<18.0.0": version "17.0.19" @@ -2571,11 +2595,11 @@ "@types/react" "*" "@types/react-test-renderer@^17.0.1": - version "17.0.1" - resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.1.tgz#3120f7d1c157fba9df0118dae20cb0297ee0e06b" - integrity sha512-3Fi2O6Zzq/f3QR9dRnlnHso9bMl7weKCviFmfF6B4LS1Uat6Hkm15k0ZAQuDz+UBq6B3+g+NM6IT2nr5QgPzCw== + version "17.0.2" + resolved "https://registry.yarnpkg.com/@types/react-test-renderer/-/react-test-renderer-17.0.2.tgz#5f800a39b12ac8d2a2149e7e1885215bcf4edbbf" + integrity sha512-+F1KONQTBHDBBhbHuT2GNydeMpPuviduXIVJRB7Y4nma4NR5DrTJfMMZ+jbhEHbpwL+Uqhs1WXh4KHiyrtYTPg== dependencies: - "@types/react" "*" + "@types/react" "^17" "@types/react@*": version "16.9.32" @@ -2585,19 +2609,19 @@ "@types/prop-types" "*" csstype "^2.2.0" -"@types/react@17.0.15": - version "17.0.15" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.15.tgz#c7533dc38025677e312606502df7656a6ea626d0" - integrity sha512-uTKHDK9STXFHLaKv6IMnwp52fm0hwU+N89w/p9grdUqcFA6WuqDyPhaWopbNyE1k/VhgzmHl8pu1L4wITtmlLw== +"@types/react@17.0.64": + version "17.0.64" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.64.tgz#468162c66c33ddb4548eb1a0e36682028d9e9a62" + integrity sha512-IlgbX/vglDTwrCRgad6fTCzOT+D/5C0xwuvrzfuqfhg9gJrkFqAGADpUFlEtqbrP1IEo9QLSbo41MaFfoIu9Aw== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" csstype "^3.0.2" "@types/react@^17": - version "17.0.56" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.56.tgz#16f54a0b0a4820065b8296f1dd6da80791fcf964" - integrity sha512-Z13f9Qz7Hg8f2g2NsBjiJSVWmON2b3K8RIqFK8mMKCIgvD0CD0ZChTukz87H3lI28X3ukXoNFGzo3ZW1ICTtPA== + version "17.0.64" + resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.64.tgz#468162c66c33ddb4548eb1a0e36682028d9e9a62" + integrity sha512-IlgbX/vglDTwrCRgad6fTCzOT+D/5C0xwuvrzfuqfhg9gJrkFqAGADpUFlEtqbrP1IEo9QLSbo41MaFfoIu9Aw== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" @@ -2616,9 +2640,9 @@ integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== "@types/scheduler@*": - version "0.16.2" - resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.2.tgz#1a62f89525723dde24ba1b01b092bf5df8ad4d39" - integrity sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew== + version "0.16.3" + resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.3.tgz#cef09e3ec9af1d63d2a6cc5b383a737e24e6dcf5" + integrity sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ== "@types/semver@^7.3.12": version "7.3.13" @@ -3273,6 +3297,11 @@ ansi-styles@^6.0.0: resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== +antd-dayjs-webpack-plugin@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/antd-dayjs-webpack-plugin/-/antd-dayjs-webpack-plugin-1.0.6.tgz#7d98bcb51422248b8cd4a32e352a0425a3bffa3a" + integrity sha512-UlK3BfA0iE2c5+Zz/Bd2iPAkT6cICtrKG4/swSik5MZweBHtgmu1aUQCHvICdiv39EAShdZy/edfP6mlkS/xXg== + antd@4.24.5: version "4.24.5" resolved "https://registry.yarnpkg.com/antd/-/antd-4.24.5.tgz#e2489fe7929b53044f239f0893f22baf52e43c48" @@ -3935,17 +3964,7 @@ camelcase-css@^2.0.1: resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== -camelcase-keys@7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.0.tgz#40fcbe171f7432888369d0c871df7cfa5ce4f788" - integrity sha512-qlQlECgDl5Ev+gkvONaiD4X4TF2gyZKuLBvzx0zLo2UwAxmz3hJP/841aaMHTeH1T7v5HRwoRq91daulXoYWvg== - dependencies: - camelcase "^6.2.0" - map-obj "^4.1.0" - quick-lru "^5.1.1" - type-fest "^1.2.1" - -camelcase-keys@^7.0.0: +camelcase-keys@7.0.2, camelcase-keys@^7.0.0: version "7.0.2" resolved "https://registry.yarnpkg.com/camelcase-keys/-/camelcase-keys-7.0.2.tgz#d048d8c69448745bb0de6fc4c1c52a30dfbe7252" integrity sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg== @@ -4642,9 +4661,9 @@ csstype@^2.2.0: integrity sha512-D34BqZU4cIlMCY93rZHbrq9pjTAQJ3U8S8rfBqjwHxkGPThWFjzZDQpgMJY0QViLxth6ZKYiwFBo14RdN44U/w== csstype@^3.0.2: - version "3.0.8" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.0.8.tgz#d2266a792729fb227cd216fb572f43728e1ad340" - integrity sha512-jXKhWqXPmlUeoQnF/EhTtTl4C9SnrxSH/jZUih3jmO6lBKr99rP3/+FmrMj4EFpOXzMtXHAZkd3x0E6h6Fgflw== + version "3.1.2" + resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" + integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== damerau-levenshtein@^1.0.6: version "1.0.6" @@ -4666,19 +4685,21 @@ data-urls@^2.0.0: whatwg-url "^8.0.0" date-fns@2.x: - version "2.29.3" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.29.3.tgz#27402d2fc67eb442b511b70bbdf98e6411cd68a8" - integrity sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA== + version "2.30.0" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0" + integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw== + dependencies: + "@babel/runtime" "^7.21.0" dayjs@1.11.5: version "1.11.5" - resolved "https://registry.npmjs.org/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.5.tgz#00e8cc627f231f9499c19b38af49f56dc0ac5e93" integrity sha512-CAdX5Q3YW3Gclyo5Vpqkgpj8fSdLQcRuzfX6mC6Phy0nfJ0eGYOeS7m4mt2plDWLAtA4TqTakvbboHvUxfe4iA== dayjs@1.x: - version "1.11.7" - resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.7.tgz#4b296922642f70999544d1144a2c25730fce63e2" - integrity sha512-+Yw9U6YO5TQohxLcIkrXBeY73WP3ejHWVvx8XCk3gxvQDCTEmS48ZrSZCKciI7Bhl/uCMyxYtE9UqRILmFphkQ== + version "1.11.8" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.8.tgz#4282f139c8c19dd6d0c7bd571e30c2d0ba7698ea" + integrity sha512-LcgxzFoWMEPO7ggRv1Y2N31hUf2R0Vj7fuy/m+Bg1K8rr+KAs1AEy4y9jd5DXe8pbHgX+srkHNS7TH6Q6ZhYeQ== debug@2.6.9, debug@^2.2.0, debug@^2.6.0, debug@^2.6.9: version "2.6.9" @@ -8041,7 +8062,7 @@ lodash.uniq@^4.5.0: resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ== -lodash@4.x, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: +lodash@4.x, lodash@^4.17.11, lodash@^4.17.13, lodash@^4.17.15, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== @@ -8330,7 +8351,7 @@ mockdate@^2.0.5: resolved "https://registry.yarnpkg.com/mockdate/-/mockdate-2.0.5.tgz#70c6abf9ed4b2dae65c81dfc170dd1a5cec53620" integrity sha512-ST0PnThzWKcgSLyc+ugLVql45PvESt3Ul/wrdV/OPc/6Pr8dbLAIJsN1cIp41FLzbN+srVTNIRn+5Cju0nyV6A== -moment@^2.24.0, moment@^2.29.2: +moment@2.29.4, moment@^2.24.0, moment@^2.29.2: version "2.29.4" resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.4.tgz#3dbe052889fe7c1b2ed966fcb3a77328964ef108" integrity sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w== @@ -9779,9 +9800,9 @@ rc-align@^4.0.0: resize-observer-polyfill "^1.5.1" rc-cascader@~3.7.0: - version "3.7.0" - resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.7.0.tgz#98134df578ce1cca22be8fb4319b04df4f3dca36" - integrity sha512-SFtGpwmYN7RaWEAGTS4Rkc62ZV/qmQGg/tajr/7mfIkleuu8ro9Hlk6J+aA0x1YS4zlaZBtTcSaXM01QMiEV/A== + version "3.7.2" + resolved "https://registry.yarnpkg.com/rc-cascader/-/rc-cascader-3.7.2.tgz#447f2725add7953dee205d1cf59f58a8317bf5f7" + integrity sha512-5nPEM76eMyikd0NFiy1gjwiB9m+bOzjY6Lnd5bVC6Ar3XLlOpOnlCcV3oBFWLN3f7B18tAGpaAVlT2uyEDCv9w== dependencies: "@babel/runtime" "^7.12.5" array-tree-filter "^2.1.0" @@ -9842,23 +9863,24 @@ rc-dropdown@~4.0.0: rc-util "^5.17.0" rc-field-form@~1.27.0: - version "1.27.3" - resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.27.3.tgz#e5262796b91c80848a42a3e7a669bf459f08d63d" - integrity sha512-HGqxHnmGQgkPApEcikV4qTg3BLPC82uB/cwBDftDt1pYaqitJfSl5TFTTUMKVEJVT5RqJ2Zi68ME1HmIMX2HAw== + version "1.27.4" + resolved "https://registry.yarnpkg.com/rc-field-form/-/rc-field-form-1.27.4.tgz#53600714af5b28c226c70d34867a8c52ccd64d44" + integrity sha512-PQColQnZimGKArnOh8V2907+VzDCXcqtFvHgevDLtqWc/P7YASb/FqntSmdS8q3VND5SHX3Y1vgMIzY22/f/0Q== dependencies: "@babel/runtime" "^7.18.0" async-validator "^4.1.0" rc-util "^5.8.0" rc-image@~5.12.0: - version "5.12.1" - resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-5.12.1.tgz#1560eda00ef9d33ebdb3c8c74ab134eb00f973d4" - integrity sha512-FMldR/ODwQmlFlhjR4c6hsOHmnn4s9CxmW7PR/9XCYE1XHlGJ5OkSWOtJruoaLjVwt2tQYDRnLANf/mKZ9ReUg== + version "5.12.2" + resolved "https://registry.yarnpkg.com/rc-image/-/rc-image-5.12.2.tgz#ccaab23fc0f0eb2351724dc0247503022c1dda90" + integrity sha512-12OCOspbN2AW2L1w+7vnYc+k0RexenqfQZIvq3WyYODp9GnTN4GLV8juekm3Apc/pwdfBSp0The1FZ5KXEozhg== dependencies: "@babel/runtime" "^7.11.2" "@rc-component/portal" "^1.0.2" classnames "^2.2.6" rc-dialog "~9.0.0" + rc-motion "^2.6.2" rc-util "^5.0.6" rc-input-number@~7.3.9: @@ -9904,7 +9926,7 @@ rc-menu@~9.8.0: rc-util "^5.12.0" shallowequal "^1.1.0" -rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.2.0, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4, rc-motion@^2.6.1, rc-motion@^2.6.2: +rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.3.0, rc-motion@^2.3.4, rc-motion@^2.4.3, rc-motion@^2.4.4, rc-motion@^2.6.1, rc-motion@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.6.2.tgz#3d31f97e41fb8e4f91a4a4189b6a98ac63342869" integrity sha512-4w1FaX3dtV749P8GwfS4fYnFG4Rb9pxvCYPc/b2fw1cmlHJWNNgOFIz7ysiD+eOrzJSvnLJWlNQQncpNMXwwpg== @@ -9913,6 +9935,15 @@ rc-motion@^2.0.0, rc-motion@^2.0.1, rc-motion@^2.2.0, rc-motion@^2.3.0, rc-motio classnames "^2.2.1" rc-util "^5.21.0" +rc-motion@^2.2.0: + version "2.7.3" + resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.7.3.tgz#126155bb3e687174fb3b92fddade2835c963b04d" + integrity sha512-2xUvo8yGHdOHeQbdI8BtBsCIrWKchEmFEIskf0nmHtJsou+meLd/JE+vnvSX2JxcBrJtXY2LuBpxAOxrbY/wMQ== + dependencies: + "@babel/runtime" "^7.11.1" + classnames "^2.2.1" + rc-util "^5.21.0" + rc-notification@~4.6.0: version "4.6.1" resolved "https://registry.yarnpkg.com/rc-notification/-/rc-notification-4.6.1.tgz#068e8674f4bd7926a447eca512915d4b41b15c91" @@ -9942,9 +9973,9 @@ rc-pagination@~3.2.0: classnames "^2.2.1" rc-picker@~2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.7.0.tgz#3c19881da27a0c5ee4c7e7504e21b552bd43a94c" - integrity sha512-oZH6FZ3j4iuBxHB4NvQ6ABRsS2If/Kpty1YFFsji7/aej6ruGmfM7WnJWQ88AoPfpJ++ya5z+nVEA8yCRYGKyw== + version "2.7.2" + resolved "https://registry.yarnpkg.com/rc-picker/-/rc-picker-2.7.2.tgz#bf656ca274228c84b955dfaa7705738908cb900f" + integrity sha512-KbUKgbzgWVN5L+V9xhZDKSmseHIyFneBlmuMtMrZ9fU7Oypw6D+owS5kuUicIEV08Y17oXt8dUqauMeC5IFBPg== dependencies: "@babel/runtime" "^7.10.1" classnames "^2.2.1" @@ -9984,9 +10015,9 @@ rc-resize-observer@^1.0.0, rc-resize-observer@^1.1.0, rc-resize-observer@^1.2.0: resize-observer-polyfill "^1.5.1" rc-segmented@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/rc-segmented/-/rc-segmented-2.1.0.tgz#0e0afe646c1a0e44a0e18785f518c42633ec8efc" - integrity sha512-hUlonro+pYoZcwrH6Vm56B2ftLfQh046hrwif/VwLIw1j3zGt52p5mREBwmeVzXnSwgnagpOpfafspzs1asjGw== + version "2.1.2" + resolved "https://registry.yarnpkg.com/rc-segmented/-/rc-segmented-2.1.2.tgz#14c9077a1dae9c2ccb2ef5fbc5662c1c48c7ce8e" + integrity sha512-qGo1bCr83ESXpXVOCXjFe1QJlCAQXyi9KCiy8eX3rIMYlTeJr/ftySIaTnYsitL18SvWf5ZEHsfqIWoX0EMfFQ== dependencies: "@babel/runtime" "^7.11.1" classnames "^2.2.1" @@ -9994,9 +10025,9 @@ rc-segmented@~2.1.0: rc-util "^5.17.0" rc-select@~14.1.0, rc-select@~14.1.13: - version "14.1.16" - resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.1.16.tgz#0cc4b5a1fc551a2db7c96bc1ece0896317ecdd47" - integrity sha512-71XLHleuZmufpdV2vis5oituRkhg2WNvLpVMJBGWRar6WGAVOHXaY9DR5HvwWry3EGTn19BqnL6Xbybje6f8YA== + version "14.1.17" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-14.1.17.tgz#e623eabeaa0dd117d5a63354e6ddaaa118abc5ee" + integrity sha512-6qQhMqtoUkkboRqXKKFRR5Nu1mrnw2mC1uxIBIczg7aiJ94qCZBg4Ww8OLT9f4xdyCgbFSGh6r3yB9EBsjoHGA== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" @@ -10120,7 +10151,7 @@ rc-upload@~4.3.0: classnames "^2.2.5" rc-util "^5.2.0" -rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.12.0, rc-util@^5.15.0, rc-util@^5.16.0, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.19.2, rc-util@^5.2.0, rc-util@^5.2.1, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.21.2, rc-util@^5.22.5, rc-util@^5.23.0, rc-util@^5.24.4, rc-util@^5.26.0, rc-util@^5.4.0, rc-util@^5.6.1, rc-util@^5.8.0, rc-util@^5.9.4: +rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.12.0, rc-util@^5.15.0, rc-util@^5.16.0, rc-util@^5.16.1, rc-util@^5.17.0, rc-util@^5.18.1, rc-util@^5.19.2, rc-util@^5.2.0, rc-util@^5.20.1, rc-util@^5.21.0, rc-util@^5.21.2, rc-util@^5.22.5, rc-util@^5.24.4, rc-util@^5.26.0, rc-util@^5.6.1, rc-util@^5.8.0, rc-util@^5.9.4: version "5.27.0" resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.27.0.tgz#52a0793cb18d54c4318a13f8b2655d2c27105944" integrity sha512-lk9HnxVWdpGzkqs6OpZ+xxAHBefqy6+wT5LjSeBoaxzE0j5Tpq0Mf4TMc29B+Z0QP81yDvfgvp1O8oBxj70kEg== @@ -10128,7 +10159,25 @@ rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.12.0, rc-util@^5.15.0, rc-util@^5.16. "@babel/runtime" "^7.18.3" react-is "^16.12.0" -rc-virtual-list@^3.2.0, rc-virtual-list@^3.4.8: +rc-util@^5.2.1, rc-util@^5.23.0, rc-util@^5.4.0: + version "5.33.1" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.33.1.tgz#96e5814400e04b819bace502b6ca40a67f3be37f" + integrity sha512-oMs2OIV/2lUCF8nllevzLccneyxAzdSOaHSs5y91qOLdqaLbIMsuL49C6/DhF/WKMqiAKEKGdVk2F1sB5HQe9A== + dependencies: + "@babel/runtime" "^7.18.3" + react-is "^16.12.0" + +rc-virtual-list@^3.2.0: + version "3.5.2" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.5.2.tgz#5e1028869bae900eacbae6788d4eca7210736006" + integrity sha512-sE2G9hTPjVmatQni8OP2Kx33+Oth6DMKm67OblBBmgMBJDJQOOFpSGH7KZ6Pm85rrI2IGxDRXZCr0QhYOH2pfQ== + dependencies: + "@babel/runtime" "^7.20.0" + classnames "^2.2.6" + rc-resize-observer "^1.0.0" + rc-util "^5.15.0" + +rc-virtual-list@^3.4.8: version "3.4.13" resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.4.13.tgz#20acc934b263abcf7b7c161f50ef82281b2f7e8d" integrity sha512-cPOVDmcNM7rH6ANotanMDilW/55XnFPw0Jh/GQYtrzZSy3AmWvCnqVNyNC/pgg3lfVmX2994dlzAhuUrd4jG7w==