From c54da593bb4cee8797002a423be15d240d478139 Mon Sep 17 00:00:00 2001 From: zhouhanseng Date: Fri, 10 Dec 2021 16:50:34 +0800 Subject: [PATCH 01/24] refactor: lazy query tokenURI --- .../web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts b/packages/web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts index 5139159ada39..5f4dc886b713 100644 --- a/packages/web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts +++ b/packages/web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts @@ -17,6 +17,7 @@ export function useERC721TokenDetailedOwnerList(contractDetailed: ERC721Contract const chainId = useChainId() const erc721TokenContract = useERC721TokenContract(contractDetailed?.address ?? '', true) const allListRef = useRef([]) + const loadMore = useRef(false) const [refreshing, setRefreshing] = useState(false) useEffect(() => { From 2f31786203ae0ef2ca29a0651d1626b3e76edbaf Mon Sep 17 00:00:00 2001 From: zhouhanseng Date: Mon, 13 Dec 2021 13:13:35 +0800 Subject: [PATCH 02/24] chore: rpc proxy --- packages/web3-constants/evm/rpc.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/web3-constants/evm/rpc.json b/packages/web3-constants/evm/rpc.json index b6ddf4eb70ba..742c65c199e9 100644 --- a/packages/web3-constants/evm/rpc.json +++ b/packages/web3-constants/evm/rpc.json @@ -1,13 +1,13 @@ { "RPC": { - "Mainnet": ["https://mainnet.infura.io/v3/d74bd8586b9e44449cef131d39ceeefb"], + "Mainnet": ["https://infura.r2d2.to/mainnet"], "Ropsten": ["https://ropsten.infura.io/v3/d74bd8586b9e44449cef131d39ceeefb"], "Rinkeby": ["https://rinkeby.infura.io/v3/d74bd8586b9e44449cef131d39ceeefb"], "Kovan": ["https://kovan.infura.io/v3/d74bd8586b9e44449cef131d39ceeefb"], "Gorli": ["https://goerli.infura.io/v3/d74bd8586b9e44449cef131d39ceeefb"], "BSC": ["https://bsc-dataseed.binance.org/"], "BSCT": ["https://data-seed-prebsc-1-s1.binance.org:8545/"], - "Matic": ["https://polygon-mainnet.infura.io/v3/d74bd8586b9e44449cef131d39ceeefb"], + "Matic": ["https://infura.r2d2.to/polygon-mainnet"], "Mumbai": ["https://polygon-mumbai.infura.io/v3/d74bd8586b9e44449cef131d39ceeefb"], "Arbitrum": ["https://arb1.arbitrum.io/rpc"], "Arbitrum_Rinkeby": ["https://rinkeby.arbitrum.io/rpc"], From 35321ce879d48819c4a74a7a4e8f8a9529f80b4d Mon Sep 17 00:00:00 2001 From: zhouhanseng Date: Mon, 13 Dec 2021 13:46:52 +0800 Subject: [PATCH 03/24] chore: rm lazy loading nft list --- .../web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts b/packages/web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts index 5f4dc886b713..5139159ada39 100644 --- a/packages/web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts +++ b/packages/web3-shared/evm/hooks/useERC721TokenDetailedOwnerList.ts @@ -17,7 +17,6 @@ export function useERC721TokenDetailedOwnerList(contractDetailed: ERC721Contract const chainId = useChainId() const erc721TokenContract = useERC721TokenContract(contractDetailed?.address ?? '', true) const allListRef = useRef([]) - const loadMore = useRef(false) const [refreshing, setRefreshing] = useState(false) useEffect(() => { From 431510fa73d2de1512fa5e0e3bd6c5c0515e3ef5 Mon Sep 17 00:00:00 2001 From: zhouhanseng Date: Mon, 20 Dec 2021 13:49:08 +0800 Subject: [PATCH 04/24] chore: shift select --- packages/mask/shared-ui/locales/en-US.json | 1 + .../SNSAdaptor/RedPacketERC721Form.tsx | 10 ++- .../SNSAdaptor/SelectNftTokenDialog.tsx | 82 ++++++++++++++++--- 3 files changed, 77 insertions(+), 16 deletions(-) diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 5d0dceda7fd0..2414c52b90c2 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -368,6 +368,7 @@ "plugin_red_packet_nft_account_name": "Wallet account", "plugin_red_packet_nft_attached_message": "Attached Message", "plugin_red_packet_nft_total_amount": "Total Amount", + "plugin_red_packet_nft_select_collection": "Choose your collection", "plugin_red_packet_completed": "Completed", "plugin_red_packet_expired": "Expired", "plugin_red_packet_indivisible": "The minimum amount for each share is {{amount}} {{symbol}}", diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC721Form.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC721Form.tsx index c72a4213107c..b0047d3c2faa 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC721Form.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC721Form.tsx @@ -150,14 +150,18 @@ export function RedPacketERC721Form(props: RedPacketERC721FormProps) { const account = useAccount() const chainId = useChainId() const [contract, setContract] = useState() - const [existTokenDetailedList, setExistTokenDetailedList] = useState([]) + const [existTokenDetailedList, setExistTokenDetailedList] = useState<(ERC721TokenDetailed & { index: number })[]>( + [], + ) const [message, setMessage] = useState('Best Wishes!') const { asyncRetry: { loading: loadingOwnerList }, - tokenDetailedOwnerList = [], + tokenDetailedOwnerList: _tokenDetailedOwnerList = [], clearTokenDetailedOwnerList, } = useERC721TokenDetailedOwnerList(contract, account) - + const tokenDetailedOwnerList = _tokenDetailedOwnerList.map( + (v, index) => ({ ...v, index } as ERC721TokenDetailed & { index: number }), + ) const removeToken = useCallback((token: ERC721TokenDetailed) => { setExistTokenDetailedList((list) => list.filter((t) => t.tokenId !== token.tokenId)) }, []) diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx index 593a1897e9b4..7d5a2c667ee8 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx @@ -9,6 +9,7 @@ import { } from '@masknet/web3-shared-evm' import { useI18N } from '../../../utils' import { DialogContent, Box, InputBase, Paper, Button, Typography, ListItem, CircularProgress } from '@mui/material' +import QuestionMarkIcon from '@mui/icons-material/QuestionMark' import { makeStyles } from '@masknet/theme' import { useCallback, useState, useEffect } from 'react' import { SearchIcon } from '@masknet/icons' @@ -141,6 +142,7 @@ const useStyles = makeStyles()((theme) => ({ flexDirection: 'column', borderRadius: 6, height: 180, + userSelect: 'none', width: 120, }, loadingWrapper: { @@ -199,9 +201,9 @@ export interface SelectNftTokenDialogProps extends withClasses { loadingOwnerList: boolean onClose: () => void contract: ERC721ContractDetailed | undefined - existTokenDetailedList: ERC721TokenDetailed[] - tokenDetailedOwnerList: ERC721TokenDetailed[] - setExistTokenDetailedList: React.Dispatch> + existTokenDetailedList: (ERC721TokenDetailed & { index: number })[] + tokenDetailedOwnerList: (ERC721TokenDetailed & { index: number })[] + setExistTokenDetailedList: React.Dispatch> } export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { @@ -217,10 +219,10 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { } = props const { t } = useI18N() const account = useAccount() - const [tokenDetailed, setTokenDetailed] = useState() + const [tokenDetailed, setTokenDetailed] = useState() const [searched, setSearched] = useState(false) const [tokenDetailedSelectedList, setTokenDetailedSelectedList] = - useState(existTokenDetailedList) + useState<(ERC721TokenDetailed & { index: number })[]>(existTokenDetailedList) const [loadingToken, setLoadingToken] = useState(false) const [tokenId, setTokenId, erc721TokenDetailedCallback] = useERC721TokenDetailedCallback(contract) @@ -237,11 +239,48 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { }, [tokenDetailedOwnerList]) const selectToken = useCallback( - (token: ERC721TokenDetailed, findToken: ERC721TokenDetailed | undefined) => { - if (findToken) { - setTokenDetailedSelectedList(tokenDetailedSelectedList.filter((v) => v.tokenId !== findToken.tokenId)) + ( + token: ERC721TokenDetailed & { index: number }, + findToken: (ERC721TokenDetailed & { index: number }) | undefined, + shiftKey: boolean, + index: number, + ) => { + if (!shiftKey) { + if (findToken) { + setTokenDetailedSelectedList( + tokenDetailedSelectedList.filter((v) => v.tokenId !== findToken.tokenId), + ) + } else { + setTokenDetailedSelectedList(tokenDetailedSelectedList.concat({ ...token, index })) + } } else { - setTokenDetailedSelectedList(tokenDetailedSelectedList.concat(token)) + const tokenDetailedSelectedListSorted = tokenDetailedSelectedList.sort( + (a, b) => (b.index ?? 0) - (a.index ?? 0), + ) + if (findToken) { + const unselectedTokenIdList: string[] = [] + let nextToken: (ERC721TokenDetailed & { index: number }) | undefined = findToken + while (nextToken) { + unselectedTokenIdList.push(nextToken.tokenId) + const nextTokenIndex: number = (nextToken?.index ?? 0) + 1 + nextToken = tokenDetailedSelectedListSorted.find((v) => (v.index ?? 0) === nextTokenIndex) + } + setTokenDetailedSelectedList( + tokenDetailedSelectedList.filter((v) => !unselectedTokenIdList.includes(v.tokenId)), + ) + } else { + const lastSelectedToken = tokenDetailedSelectedListSorted.filter((v) => (v?.index ?? 0) < index)[0] + + const lastSelectedTokenIndex = findLastIndex( + tokenDetailedOwnerList, + (v, i) => v.tokenId === lastSelectedToken?.tokenId && i < index, + ) + setTokenDetailedSelectedList( + tokenDetailedSelectedList.concat( + tokenDetailedOwnerList.slice(lastSelectedTokenIndex + 1, index + 1), + ), + ) + } } }, [tokenDetailedSelectedList, setTokenDetailedSelectedList], @@ -250,7 +289,7 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { const onSearch = useCallback(async () => { setLoadingToken(true) const _tokenDetailed = await erc721TokenDetailedCallback() - setTokenDetailed(_tokenDetailed?.info.owner ? _tokenDetailed : undefined) + setTokenDetailed(_tokenDetailed?.info.owner ? { ..._tokenDetailed, index: 0 } : undefined) setSearched(true) setLoadingToken(false) }, [erc721TokenDetailedCallback]) @@ -260,6 +299,10 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { setSearched(false) }, [tokenId]) + useEffect(() => { + if (tokenDetailedOwnerList.length > 0) setTokenDetailed(undefined) + }, [tokenDetailedOwnerList]) + const isOwner = isSameAddress(account, tokenDetailed?.info.owner) || tokenDetailedSelectedList.length > 0 const isAdded = existTokenDetailedList.map((t) => t.tokenId).includes(tokenDetailed?.tokenId ?? '') //#endregion @@ -272,7 +315,11 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { }, [tokenDetailed, tokenDetailedSelectedList, setExistTokenDetailedList, onClose]) return ( - + {tokenDetailedOwnerList.length === 0 ? ( @@ -335,7 +382,7 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { setTokenId(e.target.value)} /> @@ -391,7 +438,7 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { classes.checkbox, findToken ? classes.checked : '', )} - onClick={() => selectToken(token, findToken)}> + onClick={(event) => selectToken(token, findToken, event.shiftKey, i)}> {findToken ? : null} @@ -405,6 +452,15 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { )} + + + + + {tokenDetailedSelectedList.length + ' '} + + /{tokenDetailedOwnerList.length} + + @@ -416,7 +453,15 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { const findToken = tokenDetailedSelectedList.find((t) => t.tokenId === token.tokenId) return ( - + 0 && + !tokenIdFilterList.includes(token.tokenId) + ? classes.hide + : '', + )} + key={i.toString()}> selectToken(token, findToken, event.shiftKey, i)}> + onClick={(event) => + selectToken(token, findToken, event.shiftKey, token.index) + }> {findToken ? : null} From 21f09a05da0a55cc75202d493f5462ed93357ab1 Mon Sep 17 00:00:00 2001 From: zhouhanseng Date: Fri, 24 Dec 2021 11:12:21 +0800 Subject: [PATCH 06/24] fix: mask 524 --- packages/mask/shared-ui/locales/en-US.json | 1 + .../RedPacket/SNSAdaptor/RedPacketNft.tsx | 5 + .../SNSAdaptor/SelectNftTokenDialog.tsx | 178 +++++++++++++----- .../hooks/useClaimNftRedpacketCallback.ts | 17 +- .../mask/src/plugins/RedPacket/constants.ts | 1 + 5 files changed, 140 insertions(+), 62 deletions(-) diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index 2414c52b90c2..f54f0e28ada7 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -369,6 +369,7 @@ "plugin_red_packet_nft_attached_message": "Attached Message", "plugin_red_packet_nft_total_amount": "Total Amount", "plugin_red_packet_nft_select_collection": "Choose your collection", + "plugin_red_packet_nft_max_shares": "The maximum number of NFTs to be sold in one collectioin lucky drop contract is {{amount}}.", "plugin_red_packet_completed": "Completed", "plugin_red_packet_expired": "Expired", "plugin_red_packet_indivisible": "The minimum amount for each share is {{amount}} {{symbol}}", diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx index 97121ec708ae..ed8a0f6d7182 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx @@ -285,6 +285,11 @@ export function RedPacketNft({ payload }: RedPacketNftProps) { resetCallback() }, [claimState, retryAvailability]) + useEffect(() => { + retryAvailability() + resetCallback() + }, [account]) + const previewNftImg = new URL('./assets/nft-preview.png', import.meta.url).toString() const rpNftImg = new URL('./assets/redpacket.nft.png', import.meta.url).toString() //#region on share diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx index cea5a2681c09..20b35a17509f 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx @@ -9,6 +9,7 @@ import { } from '@masknet/web3-shared-evm' import { useI18N } from '../../../utils' import { DialogContent, Box, InputBase, Paper, Button, Typography, ListItem, CircularProgress } from '@mui/material' +import { ShadowRootTooltip } from '../../../utils' import QuestionMarkIcon from '@mui/icons-material/QuestionMark' import { makeStyles } from '@masknet/theme' import { useCallback, useState, useEffect } from 'react' @@ -17,13 +18,14 @@ import CheckIcon from '@mui/icons-material/Check' import { useUpdate } from 'react-use' import { NftImage } from './NftImage' import { findLastIndex } from 'lodash-unified' +import { NFT_RED_PACKET_MAX_SHARES } from '../constants' const useStyles = makeStyles()((theme) => ({ dialogContent: { minHeight: 380, }, dialogContentFixedHeight: { - height: 550, + height: 600, }, tokenBox: { background: theme.palette.mode === 'light' ? '#F7F9FA' : '#17191D', @@ -184,7 +186,7 @@ const useStyles = makeStyles()((theme) => ({ backgroundColor: 'white', }, checked: { - borderColor: '#1C68F3', + borderColor: '#1C68F3 !important', }, checkIcon: { width: 15, @@ -207,14 +209,52 @@ const useStyles = makeStyles()((theme) => ({ selectAmountBox: { display: 'flex', flexDirection: 'row-reverse', + alignItems: 'center', + margin: '24px 0px', }, questionMarkIcon: { padding: 2, width: 12, - border: '1px solid white', + border: `1px solid ${theme.palette.mode === 'light' ? '#0F1419' : '#D9D9D9'}`, borderRadius: 999, + transform: 'translateY(-1px)', height: 12, marginLeft: 5, + cursor: 'pointer', + }, + selectBar: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + marginTop: 12, + padding: '0 8px', + }, + selectAll: { + display: 'flex', + alignItems: 'center', + fontSize: 14, + }, + selectAllCheckBox: { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + cursor: 'pointer', + width: 17, + height: 17, + borderRadius: 6, + marginRight: 5, + border: '2px solid #6E748E', + backgroundColor: 'white', + }, + arrow: { + color: theme.palette.mode === 'dark' ? '#fff' : '#111418', + transform: 'translateX(260px) !important', + }, + tooltip: { + transform: 'translateX(20px) !important', + padding: '10px 20px', + width: 256, + backgroundColor: theme.palette.mode === 'dark' ? '#fff' : '#111418', }, })) @@ -249,6 +289,14 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { const [tokenId, setTokenId, erc721TokenDetailedCallback] = useERC721TokenDetailedCallback(contract) const [tokenIdListInput, setTokenIdListInput] = useState('') const [tokenIdFilterList, setTokenIdFilterList] = useState([]) + const [selectAll, setSelectAll] = useState(false) + + useEffect(() => { + if (!selectAll) { + setTokenDetailedSelectedList([]) + } else { + } + }, [selectAll]) useEffect(() => { setTokenDetailed(undefined) @@ -333,8 +381,6 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { const isAdded = existTokenDetailedList.map((t) => t.tokenId).includes(tokenDetailed?.tokenId ?? '') //#endregion - const r = /^(\s?(\d+)\s?,?)+$/.test(tokenIdListInput) - const onFilter = useCallback(() => { if (!/^(\s?(\d+)?\s?,?)+$/.test(tokenIdListInput)) return setTokenIdFilterList(tokenIdListInput.split(',').map((v) => Number(v).toString())) @@ -448,59 +494,91 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { ) : ( -
- {tokenDetailedOwnerList.map((token, i) => { - const findToken = tokenDetailedSelectedList.find((t) => t.tokenId === token.tokenId) - - return ( - +
+
+
0 && - !tokenIdFilterList.includes(token.tokenId) - ? classes.hide - : '', + classes.selectAllCheckBox, + selectAll ? classes.checked : '', )} - key={i.toString()}> - -
- - {token?.info.name} - -
-
setSelectAll(!selectAll)}> + {selectAll ? : null} +
+ Select all +
+ + You can also use Shift to select + multiple NFTs. + +
+
+ {tokenDetailedOwnerList.map((token, i) => { + const findToken = tokenDetailedSelectedList.find( + (t) => t.tokenId === token.tokenId, + ) + + return ( + 0 && + !tokenIdFilterList.includes(token.tokenId) + ? classes.hide + : '', )} - onClick={(event) => - selectToken(token, findToken, event.shiftKey, token.index) - }> - {findToken ? : null} -
+ key={i.toString()}> + +
+ + {token?.info.name} + +
+ +
+ selectToken(token, findToken, event.shiftKey, token.index) + }> + {findToken ? : null} +
+ + ) + })} + {loadingOwnerList ? ( + + - ) - })} - {loadingOwnerList ? ( - - - - ) : null} -
+ ) : null} +
+ )} - + + {t('plugin_red_packet_nft_max_shares', { amount: NFT_RED_PACKET_MAX_SHARES })} + + } + placement="top-end" + classes={{ tooltip: classes.tooltip, arrow: classes.arrow }} + arrow> + + {tokenDetailedSelectedList.length + ' '} diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/hooks/useClaimNftRedpacketCallback.ts b/packages/mask/src/plugins/RedPacket/SNSAdaptor/hooks/useClaimNftRedpacketCallback.ts index fa72d3ea55bb..a1a3df88d21c 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/hooks/useClaimNftRedpacketCallback.ts +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/hooks/useClaimNftRedpacketCallback.ts @@ -25,9 +25,6 @@ export function useClaimNftRedpacketCallback(id: string, totalAmount: number | u }) return } - setClaimState({ - type: TransactionStateType.WAIT_FOR_CONFIRMING, - }) type MethodParameters = Parameters @@ -47,6 +44,10 @@ export function useClaimNftRedpacketCallback(id: string, totalAmount: number | u chainId, } + setClaimState({ + type: TransactionStateType.WAIT_FOR_CONFIRMING, + }) + return new Promise(async (resolve, reject) => { nftRedPacketContract.methods .claim(...params) @@ -59,14 +60,6 @@ export function useClaimNftRedpacketCallback(id: string, totalAmount: number | u }) resolve() }) - .on(TransactionEventType.CONFIRMATION, (no: number, receipt: TransactionReceipt) => { - setClaimState({ - type: TransactionStateType.CONFIRMED, - no, - receipt, - }) - resolve() - }) .on(TransactionEventType.ERROR, (error: Error) => { setClaimState({ type: TransactionStateType.FAILED, @@ -81,7 +74,7 @@ export function useClaimNftRedpacketCallback(id: string, totalAmount: number | u setClaimState({ type: TransactionStateType.UNKNOWN, }) - }, []) + }, [account]) return [claimState, claimCallback, resetCallback] as const } diff --git a/packages/mask/src/plugins/RedPacket/constants.ts b/packages/mask/src/plugins/RedPacket/constants.ts index c64105683aff..4560953c2ec8 100644 --- a/packages/mask/src/plugins/RedPacket/constants.ts +++ b/packages/mask/src/plugins/RedPacket/constants.ts @@ -12,3 +12,4 @@ export const RedPacketPluginID = 'com.maskbook.red_packet' export const RED_PACKET_DEFAULT_SHARES = 5 export const RED_PACKET_MIN_SHARES = 1 export const RED_PACKET_MAX_SHARES = 999 +export const NFT_RED_PACKET_MAX_SHARES = 50 From 3de31917cabe17d12b370c17e1402be55f9a010c Mon Sep 17 00:00:00 2001 From: zhouhanseng Date: Mon, 27 Dec 2021 14:38:38 +0800 Subject: [PATCH 07/24] chore: select all --- packages/mask/shared-ui/locales/en-US.json | 3 + .../SNSAdaptor/SelectNftTokenDialog.tsx | 310 ++++++++++++------ 2 files changed, 215 insertions(+), 98 deletions(-) diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index ee62881a9fba..e0aff158eabd 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -370,6 +370,9 @@ "plugin_red_packet_nft_total_amount": "Total Amount", "plugin_red_packet_nft_select_collection": "Choose your collection", "plugin_red_packet_nft_max_shares": "The maximum number of NFTs to be sold in one collectioin lucky drop contract is {{amount}}.", + "plugin_red_packet_nft_max_shares_tip": "The collection lucky drop contract supports up to {{amount}} NFTs for sale.", + "plugin_red_packet_nft_shift_select_tip": "You can also use {{text}} to select multiple NFTs.", + "plugin_red_packet_nft_non_existed_tip": "Token ID does not exist or belong to you.", "plugin_red_packet_completed": "Completed", "plugin_red_packet_expired": "Expired", "plugin_red_packet_indivisible": "The minimum amount for each share is {{amount}} {{symbol}}", diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx index 20b35a17509f..c8a5950529b3 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx @@ -15,12 +15,17 @@ import { makeStyles } from '@masknet/theme' import { useCallback, useState, useEffect } from 'react' import { SearchIcon } from '@masknet/icons' import CheckIcon from '@mui/icons-material/Check' +import { Trans } from 'react-i18next' import { useUpdate } from 'react-use' import { NftImage } from './NftImage' -import { findLastIndex } from 'lodash-unified' +import { findLastIndex, uniqBy } from 'lodash-unified' import { NFT_RED_PACKET_MAX_SHARES } from '../constants' -const useStyles = makeStyles()((theme) => ({ +interface StyleProps { + isSelectSharesExceed: boolean +} + +const useStyles = makeStyles()((theme, props) => ({ dialogContent: { minHeight: 380, }, @@ -38,9 +43,9 @@ const useStyles = makeStyles()((theme) => ({ ownerTokenBox: { background: theme.palette.mode === 'light' ? '#F7F9FA' : '#17191D', width: '96%', - height: 450, + height: 480, borderRadius: 12, - margin: '16px auto', + margin: '14px auto', padding: 10, }, ownerList: { @@ -123,6 +128,8 @@ const useStyles = makeStyles()((theme) => ({ }, confirmButton: { width: '100%', + backgroundColor: theme.palette.mode === 'dark' ? '#fff' : '#000', + color: theme.palette.mode === 'dark' ? '#000' : '#fff', }, tokenSelector: { display: 'grid', @@ -149,7 +156,7 @@ const useStyles = makeStyles()((theme) => ({ width: 120, }, hide: { - display: 'none', + display: 'none !important', }, loadingWrapper: { display: 'flex', @@ -201,7 +208,7 @@ const useStyles = makeStyles()((theme) => ({ height: 64, }, selectedTokenAmount: { - color: '#1C68F3', + color: props.isSelectSharesExceed ? '#FF5F5F' : '#1C68F3', }, totalAmount: { paddingLeft: 1, @@ -210,7 +217,6 @@ const useStyles = makeStyles()((theme) => ({ display: 'flex', flexDirection: 'row-reverse', alignItems: 'center', - margin: '24px 0px', }, questionMarkIcon: { padding: 2, @@ -246,6 +252,18 @@ const useStyles = makeStyles()((theme) => ({ border: '2px solid #6E748E', backgroundColor: 'white', }, + selectAllCheckBoxText: { + pointerEvents: 'none', + }, + selectSharesExceed: { + color: '#FF5F5F', + }, + selectSharesExceedBox: { + display: 'flex', + alignItems: 'center', + justifyContent: 'space-between', + margin: '14px 4px', + }, arrow: { color: theme.palette.mode === 'dark' ? '#fff' : '#111418', transform: 'translateX(260px) !important', @@ -256,6 +274,19 @@ const useStyles = makeStyles()((theme) => ({ width: 256, backgroundColor: theme.palette.mode === 'dark' ? '#fff' : '#111418', }, + tooltipText: { + color: theme.palette.mode === 'dark' ? '#000' : '#fff', + }, + tokenId: { + color: '#1D9BF0', + }, + nonExistedTokenBox: { + height: 388, + width: '100%', + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + }, })) export interface SelectNftTokenDialogProps extends withClasses { @@ -269,7 +300,6 @@ export interface SelectNftTokenDialogProps extends withClasses { } export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { - const { classes } = useStyles() const { open, contract, @@ -289,14 +319,37 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { const [tokenId, setTokenId, erc721TokenDetailedCallback] = useERC721TokenDetailedCallback(contract) const [tokenIdListInput, setTokenIdListInput] = useState('') const [tokenIdFilterList, setTokenIdFilterList] = useState([]) + const [nonExistedTokenIdList, setNonExistedTokenIdList] = useState([]) const [selectAll, setSelectAll] = useState(false) + const maxSelectShares = Math.min(NFT_RED_PACKET_MAX_SHARES, tokenDetailedOwnerList.length) + const isSelectSharesExceed = NFT_RED_PACKET_MAX_SHARES < tokenDetailedSelectedList.length + const { classes } = useStyles({ isSelectSharesExceed }) + const selectAllHandler = useCallback(() => { + setTokenDetailedSelectedList( + tokenIdFilterList.length === 0 + ? [] + : selectAll + ? tokenDetailedSelectedList.filter((t) => !tokenIdFilterList.includes(t.tokenId)) + : tokenDetailedSelectedList, + ) - useEffect(() => { if (!selectAll) { - setTokenDetailedSelectedList([]) - } else { + const newlyAdded = tokenDetailedOwnerList + .filter((t) => tokenIdFilterList.length === 0 || tokenIdFilterList.includes(t.tokenId)) + .slice(0, maxSelectShares) + setTokenDetailedSelectedList(uniqBy(newlyAdded.concat(tokenDetailedSelectedList), 'tokenId')) } - }, [selectAll]) + + setSelectAll(!selectAll) + }, [selectAll, maxSelectShares, tokenIdFilterList, tokenDetailedSelectedList]) + + useEffect(() => { + setSelectAll( + tokenIdFilterList.length === 0 + ? tokenDetailedSelectedList.length >= maxSelectShares + : tokenIdFilterList.every((id) => tokenDetailedSelectedList.map((t) => t.tokenId).includes(id)), + ) + }, [tokenDetailedSelectedList, maxSelectShares, tokenIdListInput, tokenIdFilterList]) useEffect(() => { setTokenDetailed(undefined) @@ -307,6 +360,7 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { useEffect(() => { setTokenIdFilterList([]) + setNonExistedTokenIdList([]) }, [tokenIdListInput]) const update = useUpdate() @@ -319,7 +373,7 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { shiftKey: boolean, index: number, ) => { - if (!shiftKey) { + if (!shiftKey || tokenIdFilterList.length > 0) { if (findToken) { setTokenDetailedSelectedList( tokenDetailedSelectedList.filter((v) => v.tokenId !== findToken.tokenId), @@ -357,7 +411,7 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { } } }, - [tokenDetailedSelectedList, setTokenDetailedSelectedList], + [tokenDetailedSelectedList, setTokenDetailedSelectedList, tokenIdFilterList], ) //#region fetch token detail const onSearch = useCallback(async () => { @@ -383,7 +437,9 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { const onFilter = useCallback(() => { if (!/^(\s?(\d+)?\s?,?)+$/.test(tokenIdListInput)) return - setTokenIdFilterList(tokenIdListInput.split(',').map((v) => Number(v).toString())) + const list = tokenIdListInput.split(',').map((v) => Number(v).toString()) + setTokenIdFilterList(list) + setNonExistedTokenIdList(list.filter((v) => !tokenDetailedOwnerList.map((t) => t.tokenId).includes(v))) }, [tokenIdListInput]) const onSubmit = useCallback(() => { @@ -393,6 +449,24 @@ export function SelectNftTokenDialog(props: SelectNftTokenDialogProps) { onClose() }, [tokenDetailed, tokenDetailedSelectedList, setExistTokenDetailedList, onClose]) + const NonExistedTokenList = () => ( + + {nonExistedTokenIdList.map((id, i) => ( + + {' #' + id} + {i < nonExistedTokenIdList.length - 1 ? ', ' : ' '} + + ))} + + ), + }} + /> + ) + return ( ) : ( <> -
-
-
setSelectAll(!selectAll)}> - {selectAll ? : null} -
- Select all -
- - You can also use Shift to select - multiple NFTs. - -
-
- {tokenDetailedOwnerList.map((token, i) => { - const findToken = tokenDetailedSelectedList.find( - (t) => t.tokenId === token.tokenId, - ) - - return ( - +
+
0 && - !tokenIdFilterList.includes(token.tokenId) - ? classes.hide - : '', + classes.selectAllCheckBox, + selectAll ? classes.checked : '', )} - key={i.toString()}> - -
- - {token?.info.name} - -
+ onClick={selectAllHandler}> + {selectAll ? : null} +
+ + {t('select_all')} + +
+ + , + }} + values={{ + text: 'Shift', + }} + /> + +
+ ) : null} + {nonExistedTokenIdList.length > 0 && + nonExistedTokenIdList.length === tokenIdFilterList.length ? ( +
+ + + +
+ ) : ( +
+ {tokenDetailedOwnerList.map((token, i) => { + const findToken = tokenDetailedSelectedList.find( + (t) => t.tokenId === token.tokenId, + ) -
0 && + !tokenIdFilterList.includes(token.tokenId) + ? classes.hide + : '', )} - onClick={(event) => - selectToken(token, findToken, event.shiftKey, token.index) - }> - {findToken ? : null} -
+ key={i.toString()}> + +
+ + {token?.info.name} + +
+ +
+ selectToken(token, findToken, event.shiftKey, token.index) + }> + {findToken ? : null} +
+ + ) + })} + {loadingOwnerList ? ( + + - ) - })} - {loadingOwnerList ? ( - - - - ) : null} -
+ ) : null} + + )} )}
- - - {t('plugin_red_packet_nft_max_shares', { amount: NFT_RED_PACKET_MAX_SHARES })} - - } - placement="top-end" - classes={{ tooltip: classes.tooltip, arrow: classes.arrow }} - arrow> - - - - - {tokenDetailedSelectedList.length + ' '} - - /{tokenDetailedOwnerList.length} - - +
+
+ + {nonExistedTokenIdList.length > 0 && + nonExistedTokenIdList.length !== tokenIdFilterList.length ? ( + + ) : null} + + + {isSelectSharesExceed + ? t('plugin_red_packet_nft_max_shares_tip', { amount: NFT_RED_PACKET_MAX_SHARES }) + : null} + +
+ + + + {t('plugin_red_packet_nft_max_shares', { + amount: NFT_RED_PACKET_MAX_SHARES, + })} + + } + placement="top-end" + classes={{ tooltip: classes.tooltip, arrow: classes.arrow }} + arrow> + + + + + {tokenDetailedSelectedList.length + ' '} + + /{tokenDetailedOwnerList.length} + + +