Skip to content

Commit

Permalink
feat: support standalone add token entry. (#2227)
Browse files Browse the repository at this point in the history
* feat: support standalone add token entry.

* build: shellscript executable.

* feat: adjust operation.

* chore: i18n.

* fix: style & behaviors.

* style: tuning.

* feat: robust change.

* feat: filter out core token, style tuning.

* style: tuning.
  • Loading branch information
richardo2016x committed Apr 29, 2024
1 parent 2231da3 commit 29b519c
Show file tree
Hide file tree
Showing 29 changed files with 1,004 additions and 193 deletions.
14 changes: 12 additions & 2 deletions _raw/locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1104,22 +1104,32 @@
"blockDescription": "Token blocked by you will be shown here",
"unfoldChain": "Unfold 1 chain",
"unfoldChainPlural": "Unfold {{moreLen}} chains",
"customLinkText": "Search address to add custom token",
"customButtonText": "Add custom token",
"customDescription": "Custom token added by you will be shown here",
"comingSoon": "Coming Soon...",
"searchPlaceholder": "Tokens",
"AddMainnetToken": {
"title": "Add Custom Token",
"selectChain": "Select chain",
"searching": "Searching Token",
"tokenAddress": "Token Address",
"tokenAddressPlaceholder": "Token Address",
"notFound": "Token not found"
},
"AddTestnetToken": {
"title": "Add Custom Network Token",
"selectChain": "Select chain",
"searching": "Searching Token",
"tokenAddress": "Token Address",
"tokenAddressPlaceholder": "Token Address",
"notFound": "Token not found"
},
"TestnetAssetListContainer": {
"add": "Token",
"addTestnet": "Network"
},
"noTestnetAssets": "No Custom Network Assets"
"noTestnetAssets": "No Custom Network Assets",
"addTokenEntryText": "Token"
},
"hd": {
"howToConnectLedger": "How to Connect Ledger",
Expand Down
Empty file modified scripts/pack-debug.sh
100644 → 100755
Empty file.
21 changes: 10 additions & 11 deletions src/background/service/preference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -605,19 +605,18 @@ class PreferenceService {
getCustomizedToken = () => {
return this.store.customizedToken || [];
};
hasCustomizedToken = (token: Token) => {
return !!this.store.customizedToken?.find(
(item) =>
isSameAddress(item.address, token.address) && item.chain === token.chain
);
};
addCustomizedToken = (token: Token) => {
if (
!this.store.customizedToken?.find(
(item) =>
isSameAddress(item.address, token.address) &&
item.chain === token.chain
)
) {
this.store.customizedToken = [
...(this.store.customizedToken || []),
token,
];
if (this.hasCustomizedToken(token)) {
throw new Error('Token already added');
}

this.store.customizedToken = [...(this.store.customizedToken || []), token];
};
removeCustomizedToken = (token: Token) => {
this.store.customizedToken = this.store.customizedToken?.filter(
Expand Down
7 changes: 0 additions & 7 deletions src/ui/assets/dashboard/tab-history.svg

This file was deleted.

8 changes: 0 additions & 8 deletions src/ui/assets/dashboard/tab-list.svg

This file was deleted.

5 changes: 0 additions & 5 deletions src/ui/assets/dashboard/tab-summary.svg

This file was deleted.

8 changes: 5 additions & 3 deletions src/ui/assets/open-external-cc.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
19 changes: 19 additions & 0 deletions src/ui/hooks/useRefState.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React, { useCallback, useRef, useState } from 'react';

export function useRefState<T>(initValue: T | null) {
const stateRef = useRef<T>(initValue) as React.MutableRefObject<T>;
const [, setSpinner] = useState(false);

const setRefState = useCallback((newState: T, triggerRerender = true) => {
stateRef.current = newState;
if (triggerRerender) {
setSpinner((prev) => !prev);
}
}, []);

return {
state: stateRef.current,
stateRef,
setRefState,
} as const;
}
268 changes: 265 additions & 3 deletions src/ui/hooks/useSearchToken.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,274 @@
import { useEffect, useRef, useState, useCallback } from 'react';
import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
import { useWallet } from '../utils/WalletContext';
import { TokenItem } from '@rabby-wallet/rabby-api/dist/types';
import { DisplayedToken } from '../utils/portfolio/project';
import {
DisplayedToken,
encodeProjectTokenId,
} from '../utils/portfolio/project';
import { AbstractPortfolioToken } from '../utils/portfolio/types';
import { useRabbySelector } from 'ui/store';
import { useRabbyDispatch, useRabbySelector } from 'ui/store';
import { isSameAddress } from '../utils';
import { requestOpenApiWithChainId } from '../utils/openapi';
import { findChainByServerID } from '@/utils/chain';
import { Chain } from '@debank/common';
import useDebounceValue from './useDebounceValue';
import { useRefState } from './useRefState';

function isSearchInputWeb3Address(q: string) {
return q.length === 42 && q.toLowerCase().startsWith('0x');
}

export function useIsTokenAddedLocally(token?: TokenItem | null) {
const { customize, blocked } = useRabbySelector(
(state) => state.account.tokens
);

const addedInfo = useMemo(() => {
if (!token) return { onCustomize: false, onBlocked: false, isLocal: false };

const onCustomize = !!customize.find(
(t) => t.id === encodeProjectTokenId(token)
);
const onBlocked =
!onCustomize &&
!!blocked.find(
(t) => t.chain && token.chain && isSameAddress(t.id, token.id)
);

return {
onCustomize,
onBlocked,
isLocal: onCustomize || onBlocked,
};
}, [customize, token?.id]);

return addedInfo;
}

export function varyTokensByLocal<
T extends TokenItem[] | AbstractPortfolioToken[]
>(
tokenList: T,
input: {
customize?: AbstractPortfolioToken[];
blocked?: AbstractPortfolioToken[];
}
) {
const { customize = [], blocked = [] } = input;

const varied = {
remote: [] as TokenItem[],
local: [] as TokenItem[],
};
const localMap = {} as Record<TokenItem['id'], TokenItem>;
const wholeList = customize.concat(blocked);
for (let i = 0; i < wholeList.length; i++) {
const item = wholeList[i];
localMap[`${item.chain}-${item.id}`] = item;
}

tokenList.forEach((token) => {
const matched = localMap[`${token.chain}-${token.id}`];
if (matched) {
varied.local.push(matched);
} else {
varied.remote.push(token);
}
});

return varied;
}

export function useVaryTokensByLocal<
T extends TokenItem[] | AbstractPortfolioToken[]
>(tokenList: T) {
const { customize, blocked } = useRabbySelector(
(state) => state.account.tokens
);

const varied = useMemo(() => {
return varyTokensByLocal(tokenList, { customize, blocked });
}, [customize, blocked]);

return varied;
}

export function useOperateCustomToken() {
const dispatch = useRabbyDispatch();

const addToken = useCallback(async (tokenWithAmount: TokenItem) => {
if (!tokenWithAmount) return;

if (tokenWithAmount.is_core) {
return dispatch.account.addBlockedToken(
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
);
} else {
return dispatch.account.addCustomizeToken(
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
);
}
}, []);

const removeToken = useCallback(async (tokenWithAmount: TokenItem) => {
if (!tokenWithAmount) return;

if (tokenWithAmount?.is_core) {
return dispatch.account.removeBlockedToken(
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
);
} else {
return dispatch.account.removeCustomizeToken(
new DisplayedToken(tokenWithAmount) as AbstractPortfolioToken
);
}
}, []);

return {
addToken,
removeToken,
};
}

/** eslint-enable react-hooks/exhaustive-deps */
export function useFindCustomToken(input?: {
// address: string,
chainServerId?: Chain['serverId'];
isTestnet?: boolean;
autoSearch?: boolean;
}) {
const {
// address,
// chainServerId: _propchainServerId,
isTestnet = false,
autoSearch,
} = input || {};

const wallet = useWallet();
const [{ tokenList, portfolioTokenList }, setLists] = useState<{
tokenList: TokenItem[];
portfolioTokenList: AbstractPortfolioToken[];
}>({
tokenList: [],
portfolioTokenList: [],
});
const [isLoading, setIsLoading] = useState(false);
const {
state: searchKeyword,
setRefState: setSearchKeyword,
stateRef: skRef,
} = useRefState('');
const debouncedSearchKeyword = useDebounceValue(searchKeyword, 150);
const { customize, blocked } = useRabbySelector(
(state) => state.account.tokens
);

const searchCustomToken = useCallback(
async (
opt: {
address?: string;
q?: string;
chainServerId?: Chain['serverId'];
} = {}
) => {
const { address, q = debouncedSearchKeyword, chainServerId } = opt || {};

const lists: {
tokenList: TokenItem[];
portfolioTokenList: AbstractPortfolioToken[];
} = {
tokenList: [],
portfolioTokenList: [],
};
if (!address) return lists;

const chainItem = !chainServerId
? null
: findChainByServerID(chainServerId);
if (isTestnet || chainItem?.isTestnet) {
return;
}

setIsLoading(true);

try {
if (isSearchInputWeb3Address(q)) {
lists.tokenList = await requestOpenApiWithChainId(
(ctx) => ctx.openapi.searchToken(address, q, chainServerId, true),
{
isTestnet: !!isTestnet || !!chainItem?.isTestnet,
wallet,
}
// filter out core tokens
).then((res) => res.filter((item) => !item.is_core));
} else {
// lists.tokenList = await requestOpenApiWithChainId(
// (ctx) => ctx.openapi.searchToken(address, q, chainServerId),
// {
// isTestnet: !!isTestnet || !!chainItem?.isTestnet,
// wallet,
// }
// );
}

if (q === skRef.current) {
// const reg = new RegExp(debouncedSearchKeyword, 'i');
// const matchCustomTokens = customize.filter((token) => {
// return (
// reg.test(token.name) ||
// reg.test(token.symbol) ||
// reg.test(token.display_symbol || '')
// );
// });

lists.portfolioTokenList = [
...(lists.tokenList.map(
(item) => new DisplayedToken(item)
) as AbstractPortfolioToken[]),
// ...matchCustomTokens,
].filter((item) => {
const isBlocked = !!blocked.find((b) =>
isSameAddress(b.id, item.id)
);
return !isBlocked;
});
}
setLists(lists);
} catch (error) {
console.error(error);
} finally {
setIsLoading(false);
}

return lists;
},
[debouncedSearchKeyword, blocked, isTestnet]
);

useEffect(() => {
if (autoSearch) {
searchCustomToken();
}
}, [autoSearch, searchCustomToken]);

const resetSearchResult = useCallback(() => {
setLists({
tokenList: [],
portfolioTokenList: [],
});
}, []);

return {
searchCustomToken,
debouncedSearchKeyword,
setSearchKeyword,
resetSearchResult,
tokenList,
foundTokenList: portfolioTokenList,
isLoading,
};
}
/** eslint-disable react-hooks/exhaustive-deps */

const useSearchToken = (
address: string | undefined,
Expand Down
Loading

0 comments on commit 29b519c

Please sign in to comment.