diff --git a/.github/workflows/deploy-proxy.yml b/.github/workflows/deploy-proxy.yml new file mode 100644 index 000000000000..9a8e374dc628 --- /dev/null +++ b/.github/workflows/deploy-proxy.yml @@ -0,0 +1,42 @@ +name: Publish Proxy Package + +on: + push: + branches: [hyper-proxy-deploy] + +jobs: + build: + runs-on: ubuntu-20.04 + permissions: + packages: write + contents: read + steps: + - name: Get cache date + id: get-date + run: echo "::set-output name=date::$(/bin/date -u "+%Y%m%d")" + shell: bash + - uses: actions/checkout@v2 + with: + ref: ${{ github.event.pull_request.head.sha }} + fetch-depth: 0 + - uses: pnpm/action-setup@v2 + with: + version: latest + - uses: actions/setup-node@v2 + with: + cache: pnpm + - name: Restore cache + uses: actions/cache@v2 + with: + path: packages/mask/node_modules/.cache/ + key: ${{ runner.os }}-extension-${{ hashFiles('pnpm-lock.yaml') }}-${{ steps.get-date.outputs.date }} + restore-keys: ${{ runner.os }}-extension-${{ hashFiles('pnpm-lock.yaml') }} + - uses: DimensionDev/github-token-action@latest + with: + registry: true + - run: pnpm install + - run: pnpm build + - run: pnpm build + working-directory: packages/provider-proxy + - run: npm publish + working-directory: packages/provider-proxy/dist diff --git a/cspell.json b/cspell.json index 559697c3796f..65135eac6d37 100644 --- a/cspell.json +++ b/cspell.json @@ -359,9 +359,12 @@ "koda", "Enjin", "poap", - "irss", "CELO", - "flac" + "flac", + "treeshake", + "xtest", + "xdescribe", + "irss" ], "ignoreRegExpList": ["/@servie/"], "overrides": [ diff --git a/jest.config.ts b/jest.config.ts index 32599357c7f9..d089e951e89a 100644 --- a/jest.config.ts +++ b/jest.config.ts @@ -20,9 +20,13 @@ const config: InitialOptionsTsJest = { clearMocks: true, coverageProvider: 'v8', testMatch: ['**/tests/**/*.[jt]s?(x)'], + modulePathIgnorePatterns: ['dist'], extensionsToTreatAsEsm: ['.ts', '.tsx'], moduleNameMapper: { '@masknet/shared-base': '/packages/shared-base/src/index.ts', + 'jest-websocket-mock': '/packages/web3-shared/base/node_modules/jest-websocket-mock', + 'reconnecting-websocket': '/packages/web3-shared/base/node_modules/reconnecting-websocket', + 'date-fns/(.*)': '/packages/web3-shared/base/node_modules/date-fns/$1', }, snapshotSerializers: ['@masknet/serializer'], } diff --git a/packages/dashboard/src/pages/Wallets/components/CollectibleList/index.tsx b/packages/dashboard/src/pages/Wallets/components/CollectibleList/index.tsx index b232c41dbd56..fd5d87fce9bc 100644 --- a/packages/dashboard/src/pages/Wallets/components/CollectibleList/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/CollectibleList/index.tsx @@ -1,4 +1,4 @@ -import { Dispatch, memo, SetStateAction, useCallback, useEffect, useState } from 'react' +import { Dispatch, memo, SetStateAction, useCallback, useEffect, useRef, useState } from 'react' import { Box, Stack, TablePagination } from '@mui/material' import { makeStyles } from '@masknet/theme' import { LoadingPlaceholder } from '../../../../components/LoadingPlaceholder' @@ -33,24 +33,37 @@ interface CollectibleListProps { selectedNetwork: Web3Plugin.NetworkDescriptor | null } +const ITEM_SIZE = { + width: 150, + height: 250, +} + export const CollectibleList = memo(({ selectedNetwork }) => { const [page, setPage] = useState(0) const navigate = useNavigate() const account = useAccount() const { Asset } = useWeb3PluginState() const network = useNetworkDescriptor() + const [loadingSize, setLoadingSize] = useState() + const [loadingCollectible, setLoadingCollectible] = useState(true) + const [renderData, setRenderData] = useState([]) const { value = { data: [], hasNextPage: false }, - loading: collectiblesLoading, error: collectiblesError, retry, } = useAsyncRetry( async () => Asset?.getNonFungibleAssets?.(account, { page: page, size: 20 }, undefined, selectedNetwork ?? undefined), - [account, Asset, network, page, selectedNetwork], + [account, Asset, network, selectedNetwork], ) + useEffect(() => { + if (!loadingSize) return + const render = value.data.slice(page * loadingSize, (page + 1) * loadingSize) + setRenderData(render) + }, [value, loadingSize, page]) + const onSend = useCallback( (detail: Web3Plugin.NonFungibleToken) => navigate(DashboardRoutes.WalletsTransfer, { @@ -63,24 +76,31 @@ export const CollectibleList = memo(({ selectedNetwork }) ) useEffect(() => { - PluginMessages.Wallet.events.erc721TokensUpdated.on(() => { - retry() + PluginMessages.Wallet.events.erc721TokensUpdated.on(() => retry()) + PluginMessages.Wallet.events.socketMessageUpdated.on((info) => { + if (!info.done) { + retry() + } + setLoadingCollectible(false) }) }, [retry]) - const { data: collectibles = [], hasNextPage } = value + const hasNextPage = (page + 1) * (loadingSize ?? 0) < value.data.length return ( setLoadingSize(size)} /> ) }) @@ -95,15 +115,36 @@ export interface CollectibleListUIProps { chainId: number dataSource: Web3Plugin.NonFungibleToken[] onSend(detail: Web3Plugin.NonFungibleToken): void + setLoadingSize(fn: (pre: number | undefined) => number): void } export const CollectibleListUI = memo( - ({ page, onPageChange, isLoading, isEmpty, hasNextPage, showPagination, chainId, dataSource, onSend }) => { + ({ + page, + onPageChange, + isLoading, + isEmpty, + hasNextPage, + showPagination, + chainId, + dataSource, + onSend, + setLoadingSize, + }) => { const t = useDashboardI18N() const { classes } = useStyles() + const ref = useRef(null) + + useEffect(() => { + if (!ref.current) return + const width = ref.current.offsetWidth + const height = ref.current.offsetHeight - 60 + const baseSize = Math.floor(width / ITEM_SIZE.width) * Math.floor(height / ITEM_SIZE.height) + setLoadingSize((prev) => prev ?? Math.floor(baseSize * 0.8)) + }, [ref.current]) return ( - + <> {isLoading ? ( diff --git a/packages/dashboard/src/pages/Wallets/components/FungibleTokenTable/index.tsx b/packages/dashboard/src/pages/Wallets/components/FungibleTokenTable/index.tsx index c88ff649b6b5..05127d1792b5 100644 --- a/packages/dashboard/src/pages/Wallets/components/FungibleTokenTable/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/FungibleTokenTable/index.tsx @@ -87,7 +87,7 @@ export const FungibleTokenTable = memo(({ selectedChainId }) => useEffect(() => { PluginMessages.Wallet.events.erc20TokensUpdated.on(() => - setTimeout(() => setTokenUpdateCount(tokenUpdateCount + 1), 100), + setTimeout(() => setTokenUpdateCount((prev) => prev + 1), 100), ) }, []) diff --git a/packages/dashboard/src/web3/context.ts b/packages/dashboard/src/web3/context.ts index e1d4d70b5824..32e4f3197efc 100644 --- a/packages/dashboard/src/web3/context.ts +++ b/packages/dashboard/src/web3/context.ts @@ -16,6 +16,7 @@ import { isInjectedProvider, } from '@masknet/web3-shared-evm' import { Services, Messages, PluginServices, PluginMessages } from '../API' +import { getProxyWebsocketInstance } from '@masknet/web3-shared-base' const Web3Provider = createExternalProvider() @@ -113,6 +114,9 @@ export const Web3Context: Web3ProviderType = { getAddressNamesList: PluginServices.Wallet.getAddressNames, getTransactionList: PluginServices.Wallet.getTransactionList, fetchERC20TokensFromTokenLists: Services.Ethereum.fetchERC20TokensFromTokenLists, + providerSocket: getProxyWebsocketInstance((info) => + PluginMessages.Wallet.events.socketMessageUpdated.sendToAll(info), + ), } export function createExternalProvider() { diff --git a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx index 4c7d31f722eb..7de6dc3ff9e9 100644 --- a/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx +++ b/packages/mask/src/extension/options-page/DashboardComponents/CollectibleList/index.tsx @@ -1,11 +1,10 @@ -import { createContext, useState, useEffect } from 'react' -import { useUpdateEffect } from 'react-use' +import { createContext, useEffect, useMemo } from 'react' import { useValueRef } from '@masknet/shared' import { ChainId, - NonFungibleAssetProvider, - ERC721TokenCollectionInfo, ERC721TokenDetailed, + NonFungibleAssetProvider, + SocketState, useCollectibles, useCollections, Wallet, @@ -15,9 +14,8 @@ import { makeStyles, useStylesExtends } from '@masknet/theme' import { currentNonFungibleAssetDataProviderSettings } from '../../../../plugins/Wallet/settings' import { useI18N } from '../../../../utils' import { CollectibleCard } from './CollectibleCard' -import { WalletMessages } from '../../../../plugins/Wallet/messages' import { Image } from '../../../../components/shared/Image' -import { uniqBy } from 'lodash-unified' +import { WalletMessages } from '@masknet/plugin-wallet' export const CollectibleContext = createContext<{ collectiblesRetry: () => void @@ -112,7 +110,7 @@ interface CollectibleListUIProps extends withClasses<'empty' | 'button' | 'text' collectibles: ERC721TokenDetailed[] loading: boolean collectiblesRetry: () => void - error: Error | undefined + error: string | undefined readonly?: boolean hasRetry?: boolean } @@ -123,30 +121,28 @@ function CollectibleListUI(props: CollectibleListUIProps) { useEffect(() => WalletMessages.events.erc721TokensUpdated.on(collectiblesRetry)) - if (loading) - return ( - - {Array.from({ length: 3 }) - .fill(0) - .map((_, i) => ( - - - - - ))} - - ) - return ( - {error || collectibles.length === 0 ? ( + {loading && ( + + {Array.from({ length: 3 }) + .fill(0) + .map((_, i) => ( + + + + + ))} + + )} + {error || (collectibles.length === 0 && !loading) ? ( {t('dashboard_no_collectible_found')} {hasRetry ? ( @@ -176,53 +172,25 @@ function CollectibleListUI(props: CollectibleListUIProps) { export interface CollectibleListProps extends withClasses<'empty' | 'button'> { address: string collection?: string - setCount: (count: number) => void + collectibles: ERC721TokenDetailed[] + error?: string + loading: boolean + retry(): void } export function CollectibleList(props: CollectibleListProps) { - const { address, collection, setCount } = props + const { address, collectibles, error, loading, retry } = props const provider = useValueRef(currentNonFungibleAssetDataProviderSettings) - const chainId = ChainId.Mainnet - const [page, setPage] = useState(0) const classes = props.classes ?? {} - const { - value = { collectibles: [], hasNextPage: false }, - loading: collectiblesLoading, - retry: collectiblesRetry, - error: collectiblesError, - } = useCollectibles(address, chainId, provider, page, 50, collection) - const { collectibles = [], hasNextPage } = value - const [rendCollectibles, setRendCollectibles] = useState([]) - - useUpdateEffect(() => { - setPage(0) - }, [provider, address]) - - useEffect(() => { - if (!collectibles.length) return - setRendCollectibles([...rendCollectibles, ...collectibles]) - if (!hasNextPage) return - const timer = setTimeout(() => { - setPage(page + 1) - }, 1000) - return () => { - clearTimeout(timer) - } - }, [collectibles]) - - useEffect(() => { - setCount(rendCollectibles.length) - }, [rendCollectibles]) - return ( @@ -231,33 +199,49 @@ export function CollectibleList(props: CollectibleListProps) { export function CollectionList({ address }: { address: string }) { const chainId = ChainId.Mainnet - const provider = useValueRef(currentNonFungibleAssetDataProviderSettings) const { t } = useI18N() - const [page, setPage] = useState(0) const { classes } = useStyles() - const [counts, setCounts] = useState([]) - const [rendCollections, setRendCollections] = useState([]) - const { value = { collections: [], hasNextPage: false } } = useCollections(address, chainId, provider, page, 10) - const { collections = [], hasNextPage } = value + const { + data: collections, + retry: retryFetchCollection, + state: loadingCollectionDone, + } = useCollections(address, chainId) + const { + data: collectibles, + state: loadingCollectibleDone, + retry: retryFetchCollectible, + } = useCollectibles(address, chainId, !!collections.length) + + const isLoading = loadingCollectibleDone !== SocketState.done || loadingCollectionDone !== SocketState.done - useUpdateEffect(() => { - setPage(0) - }, [provider, address]) + const renderWithRarible = useMemo(() => { + if (isLoading) return [] + return collectibles.filter((item) => !item.collection) + }, [collections?.length, collectibles?.length]) - useEffect(() => { - if (!collections.length) return - setRendCollections(uniqBy([...rendCollections, ...collections], (x) => x.slug)) - if (!hasNextPage) return - const timer = setTimeout(() => { - setPage(page + 1) - }, 3000) - return () => { - clearTimeout(timer) - } - }, [collections]) + if (loadingCollectionDone !== SocketState.done) { + return ( + + {Array.from({ length: 3 }) + .fill(0) + .map((_, i) => ( + + + + + ))} + + ) + } - if (!rendCollections.length) + if (!isLoading && !collections.length) return ( @@ -268,31 +252,63 @@ export function CollectionList({ address }: { address: string }) { return ( - {rendCollections.map((x, i) => ( - - - - {x.image ? : null} + {(collections ?? []).map((x, i) => { + const renderCollectibles = collectibles.filter((c) => c.collection?.slug === x.slug) + return ( + + + + {x.image ? ( + + ) : null} + + + {x.name} + {loadingCollectibleDone && renderCollectibles.length + ? `(${renderCollectibles.length})` + : null} + + { + retryFetchCollectible() + retryFetchCollection() + }} + collectibles={renderCollectibles} + loading={isLoading} + /> + + ) + })} + {!!renderWithRarible.length && ( + + - {x.name} - {counts[i] ? `(${counts[i]})` : null} + Rarible ({renderWithRarible.length}) { - counts[i] = count - setCounts(counts) + collection="Rarible" + retry={() => { + retryFetchCollectible() + retryFetchCollection() }} + collectibles={renderWithRarible} + loading={false} /> - ))} + )} ) } diff --git a/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTAvatar.tsx b/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTAvatar.tsx index 20aef9b42ba6..81684042dafe 100644 --- a/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTAvatar.tsx +++ b/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTAvatar.tsx @@ -1,9 +1,9 @@ import { useCallback, useState } from 'react' import { uniqBy } from 'lodash-unified' import { WalletMessages } from '@masknet/plugin-wallet' -import { useRemoteControlledDialog, useValueRef } from '@masknet/shared' +import { useRemoteControlledDialog } from '@masknet/shared' import { makeStyles, useStylesExtends } from '@masknet/theme' -import { ChainId } from '@masknet/web3-shared-evm' +import { ChainId, SocketState } from '@masknet/web3-shared-evm' import { ERC721TokenDetailed, formatEthereumAddress, @@ -11,8 +11,7 @@ import { useChainId, useCollectibles, } from '@masknet/web3-shared-evm' -import { Box, Button, Skeleton, TablePagination, Typography } from '@mui/material' -import { currentNonFungibleAssetDataProviderSettings } from '../../../plugins/Wallet/settings' +import { Box, Button, Skeleton, Typography } from '@mui/material' import { useI18N } from '../../../utils' import { EthereumChainBoundary } from '../../../web3/UI/EthereumChainBoundary' import { AddNFT } from './AddNFT' @@ -91,22 +90,11 @@ export function NFTAvatar(props: NFTAvatarProps) { const classes = useStylesExtends(useStyles(), props) const account = useAccount() const chainId = useChainId() - const provider = useValueRef(currentNonFungibleAssetDataProviderSettings) - const [page, setPage] = useState(0) const [selectedToken, setSelectedToken] = useState() const [open_, setOpen_] = useState(false) const [collectibles_, setCollectibles_] = useState([]) const { t } = useI18N() - const { - value = { - collectibles: [], - hasNextPage: false, - }, - loading, - retry, - error, - } = useCollectibles(account, ChainId.Mainnet, provider, page, 50) - const { collectibles, hasNextPage } = value + const { data: collectibles, error, retry, state } = useCollectibles(account, ChainId.Mainnet) const onClick = useCallback(async () => { if (!selectedToken) return @@ -157,7 +145,7 @@ export function NFTAvatar(props: NFTAvatarProps) { - {loading + {state !== SocketState.done && collectibles.length === 0 ? LoadStatus : error || (collectibles.length === 0 && collectibles_.length === 0) ? Retry @@ -179,28 +167,6 @@ export function NFTAvatar(props: NFTAvatarProps) { /> ))} - - {hasNextPage || page > 0 ? ( - {}} - page={page} - rowsPerPage={30} - rowsPerPageOptions={[30]} - labelDisplayedRows={() => null} - backIconButtonProps={{ - onClick: () => setPage(page - 1), - size: 'small', - disabled: page === 0, - }} - nextIconButtonProps={{ - onClick: () => setPage(page + 1), - disabled: !hasNextPage, - size: 'small', - }} - /> - ) : null}