Add and remove common base
This commit is contained in:
parent
9c53747071
commit
4b1de9c7e8
|
@ -1,4 +1,4 @@
|
||||||
import { useState } from "react";
|
import { useState, useMemo } from "react";
|
||||||
import {
|
import {
|
||||||
PublicKey,
|
PublicKey,
|
||||||
Keypair,
|
Keypair,
|
||||||
|
@ -222,6 +222,16 @@ export function SwapTokenForm({
|
||||||
})
|
})
|
||||||
: amount;
|
: amount;
|
||||||
|
|
||||||
|
const tokenDialog = useMemo(() => {
|
||||||
|
return (
|
||||||
|
<TokenDialog
|
||||||
|
setMint={setMint}
|
||||||
|
open={showTokenDialog}
|
||||||
|
onClose={() => setShowTokenDialog(false)}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}, [showTokenDialog]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.swapTokenFormContainer} style={style}>
|
<div className={styles.swapTokenFormContainer} style={style}>
|
||||||
<div className={styles.swapTokenSelectorContainer}>
|
<div className={styles.swapTokenSelectorContainer}>
|
||||||
|
@ -252,11 +262,7 @@ export function SwapTokenForm({
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<TokenDialog
|
{tokenDialog}
|
||||||
setMint={setMint}
|
|
||||||
open={showTokenDialog}
|
|
||||||
onClose={() => setShowTokenDialog(false)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,14 +10,16 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
List,
|
List,
|
||||||
ListItem,
|
ListItem,
|
||||||
|
ListSubheader,
|
||||||
Typography,
|
Typography,
|
||||||
Chip,
|
Chip,
|
||||||
Avatar,
|
Avatar,
|
||||||
Tabs,
|
Tabs,
|
||||||
Tab,
|
Tab,
|
||||||
} from "@material-ui/core";
|
} from "@material-ui/core";
|
||||||
|
import { StarOutline, Star } from "@material-ui/icons";
|
||||||
import { TokenIcon } from "./Swap";
|
import { TokenIcon } from "./Swap";
|
||||||
import { useSwappableTokens } from "../context/TokenList";
|
import { useSwappableTokens, useTokenBase } from "../context/TokenList";
|
||||||
import { useMediaQuery } from "@material-ui/core";
|
import { useMediaQuery } from "@material-ui/core";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
|
@ -54,12 +56,9 @@ export default function TokenDialog({
|
||||||
const [tokenFilter, setTokenFilter] = useState("");
|
const [tokenFilter, setTokenFilter] = useState("");
|
||||||
const filter = tokenFilter.toLowerCase();
|
const filter = tokenFilter.toLowerCase();
|
||||||
const styles = useStyles();
|
const styles = useStyles();
|
||||||
const {
|
const { swappableTokens, swappableTokensSollet, swappableTokensWormhole } =
|
||||||
swappableTokens,
|
useSwappableTokens();
|
||||||
swappableTokensSollet,
|
const { tokenBase, addNewBase, tokenBaseMap, removeBase } = useTokenBase();
|
||||||
swappableTokensWormhole,
|
|
||||||
commonTokenBases,
|
|
||||||
} = useSwappableTokens();
|
|
||||||
const displayTabs = !useMediaQuery("(max-width:450px)");
|
const displayTabs = !useMediaQuery("(max-width:450px)");
|
||||||
const selectedTokens =
|
const selectedTokens =
|
||||||
tabSelection === 0
|
tabSelection === 0
|
||||||
|
@ -103,13 +102,17 @@ export default function TokenDialog({
|
||||||
</DialogTitle>
|
</DialogTitle>
|
||||||
<DialogContent className={styles.dialogContent} dividers={true}>
|
<DialogContent className={styles.dialogContent} dividers={true}>
|
||||||
<List disablePadding>
|
<List disablePadding>
|
||||||
<CommonBases
|
{tokenBase?.length != 0 && (
|
||||||
commonTokenBases={commonTokenBases}
|
<ListSubheader style={{ backgroundColor: "white" }}>
|
||||||
onClick={(mint) => {
|
<CommonBases
|
||||||
setMint(mint);
|
commonTokenBases={tokenBase}
|
||||||
onClose();
|
onClick={(mint) => {
|
||||||
}}
|
setMint(mint);
|
||||||
/>
|
onClose();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</ListSubheader>
|
||||||
|
)}
|
||||||
{tokens.map((tokenInfo: TokenInfo) => (
|
{tokens.map((tokenInfo: TokenInfo) => (
|
||||||
<TokenListItem
|
<TokenListItem
|
||||||
key={tokenInfo.address}
|
key={tokenInfo.address}
|
||||||
|
@ -118,6 +121,15 @@ export default function TokenDialog({
|
||||||
setMint(mint);
|
setMint(mint);
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
|
addNewBase={(token) => {
|
||||||
|
addNewBase(token);
|
||||||
|
}}
|
||||||
|
isCommonBase={
|
||||||
|
tokenBaseMap.get(tokenInfo.address.toString()) ? true : false
|
||||||
|
}
|
||||||
|
removeBase={(token) => {
|
||||||
|
removeBase(token);
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
|
@ -159,19 +171,41 @@ export default function TokenDialog({
|
||||||
function TokenListItem({
|
function TokenListItem({
|
||||||
tokenInfo,
|
tokenInfo,
|
||||||
onClick,
|
onClick,
|
||||||
|
addNewBase,
|
||||||
|
removeBase,
|
||||||
|
isCommonBase,
|
||||||
}: {
|
}: {
|
||||||
tokenInfo: TokenInfo;
|
tokenInfo: TokenInfo;
|
||||||
onClick: (mint: PublicKey) => void;
|
onClick: (mint: PublicKey) => void;
|
||||||
|
addNewBase: (token: TokenInfo) => void;
|
||||||
|
removeBase: (token: TokenInfo) => void;
|
||||||
|
isCommonBase: Boolean;
|
||||||
}) {
|
}) {
|
||||||
const mint = new PublicKey(tokenInfo.address);
|
const mint = new PublicKey(tokenInfo.address);
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem>
|
||||||
button
|
<div
|
||||||
onClick={() => onClick(mint)}
|
onClick={() => onClick(mint)}
|
||||||
style={{ padding: "10px 20px" }}
|
style={{
|
||||||
>
|
padding: "10px 20px",
|
||||||
<TokenIcon mint={mint} style={{ width: "30px", borderRadius: "15px" }} />
|
display: "flex",
|
||||||
<TokenName tokenInfo={tokenInfo} />
|
cursor: "pointer",
|
||||||
|
width: "100%",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TokenIcon
|
||||||
|
mint={mint}
|
||||||
|
style={{ width: "30px", borderRadius: "15px" }}
|
||||||
|
/>
|
||||||
|
<TokenName tokenInfo={tokenInfo} />
|
||||||
|
</div>
|
||||||
|
<Chip
|
||||||
|
variant="outlined"
|
||||||
|
label={isCommonBase ? <Star /> : <StarOutline />}
|
||||||
|
onClick={() =>
|
||||||
|
isCommonBase ? removeBase(tokenInfo) : addNewBase(tokenInfo)
|
||||||
|
}
|
||||||
|
/>
|
||||||
</ListItem>
|
</ListItem>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -193,12 +227,12 @@ function CommonBases({
|
||||||
commonTokenBases,
|
commonTokenBases,
|
||||||
onClick,
|
onClick,
|
||||||
}: {
|
}: {
|
||||||
commonTokenBases: TokenInfo[];
|
commonTokenBases: TokenInfo[] | undefined;
|
||||||
onClick: (mint: PublicKey) => void;
|
onClick: (mint: PublicKey) => void;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: "0 20px 20px 20px" }}>
|
<div style={{ padding: "0 20px 20px 20px", position: "sticky" }}>
|
||||||
<h4>Common bases</h4>
|
<h4 style={{ margin: 0 }}>Common bases</h4>
|
||||||
{commonTokenBases?.map((tokenInfo: TokenInfo) => {
|
{commonTokenBases?.map((tokenInfo: TokenInfo) => {
|
||||||
const mint = new PublicKey(tokenInfo.address);
|
const mint = new PublicKey(tokenInfo.address);
|
||||||
return (
|
return (
|
||||||
|
@ -208,7 +242,7 @@ function CommonBases({
|
||||||
variant="outlined"
|
variant="outlined"
|
||||||
label={tokenInfo?.symbol}
|
label={tokenInfo?.symbol}
|
||||||
onClick={() => onClick(mint)}
|
onClick={() => onClick(mint)}
|
||||||
style={{ margin: "5px" }}
|
style={{ margin: "0 1px" }}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import React, { useContext, useMemo } from "react";
|
import React, { useContext, useMemo, useState, useEffect } from "react";
|
||||||
import { TokenInfo } from "@solana/spl-token-registry";
|
import { TokenInfo } from "@solana/spl-token-registry";
|
||||||
import { SOL_MINT } from "../utils/pubkeys";
|
import { SOL_MINT } from "../utils/pubkeys";
|
||||||
import { PublicKey } from "@solana/web3.js";
|
import { PublicKey } from "@solana/web3.js";
|
||||||
|
import { LocalStorage } from "../utils/localStorage";
|
||||||
|
|
||||||
|
interface TokenCommonBaseInfo extends TokenInfo {
|
||||||
|
isCommonBase: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
type TokenListContext = {
|
type TokenListContext = {
|
||||||
tokenMap: Map<string, TokenInfo>;
|
tokenMap: Map<string, TokenInfo>;
|
||||||
|
@ -10,7 +15,10 @@ type TokenListContext = {
|
||||||
swappableTokens: TokenInfo[];
|
swappableTokens: TokenInfo[];
|
||||||
swappableTokensSollet: TokenInfo[];
|
swappableTokensSollet: TokenInfo[];
|
||||||
swappableTokensWormhole: TokenInfo[];
|
swappableTokensWormhole: TokenInfo[];
|
||||||
commonTokenBases: TokenInfo[];
|
tokenBase: TokenInfo[] | undefined;
|
||||||
|
addNewBase: (token: TokenInfo) => void;
|
||||||
|
removeBase: (token: TokenInfo) => void;
|
||||||
|
tokenBaseMap: Map<string, TokenCommonBaseInfo>;
|
||||||
};
|
};
|
||||||
const _TokenListContext = React.createContext<null | TokenListContext>(null);
|
const _TokenListContext = React.createContext<null | TokenListContext>(null);
|
||||||
|
|
||||||
|
@ -99,13 +107,65 @@ export function TokenListContextProvider(props: any) {
|
||||||
];
|
];
|
||||||
}, [tokenList]);
|
}, [tokenList]);
|
||||||
|
|
||||||
|
let [tokenBase, setTokenBase] = useState<TokenCommonBaseInfo[] | undefined>(
|
||||||
|
undefined
|
||||||
|
);
|
||||||
|
let [addrList, setValues, removeValue] = LocalStorage("swapui-common-bases");
|
||||||
|
|
||||||
// Common token bases
|
// Common token bases
|
||||||
const commonTokenBases = useMemo(() => {
|
useEffect(() => {
|
||||||
const cb = props.commonBases?.map((add: PublicKey) => {
|
if (addrList == null) {
|
||||||
return tokenMap.get(add.toString());
|
addrList = props.commonBases ?? [];
|
||||||
|
}
|
||||||
|
if (props.commonBases) {
|
||||||
|
props.commonBases.forEach((add: any) => setValues(add.toString()));
|
||||||
|
addrList.concat(props.commonBases);
|
||||||
|
}
|
||||||
|
addrList = addrList.map((e: string) => new PublicKey(e.toString()));
|
||||||
|
const cb = addrList?.map((add: PublicKey) => {
|
||||||
|
const token = tokenMap.get(add.toString());
|
||||||
|
token.isCommonBase = true;
|
||||||
|
setValues(token.address);
|
||||||
|
return token;
|
||||||
});
|
});
|
||||||
|
setTokenBase(cb);
|
||||||
return cb;
|
return cb;
|
||||||
}, [tokenList]);
|
}, [props.commonBases]);
|
||||||
|
|
||||||
|
const addNewBase = (token: TokenInfo) => {
|
||||||
|
// Check if token already a common base
|
||||||
|
if (
|
||||||
|
tokenBase?.some((t) => token.address.toString() === t.address.toString())
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const c: TokenCommonBaseInfo = { ...token, isCommonBase: true };
|
||||||
|
setValues(token.address);
|
||||||
|
setTokenBase((prevState) => [...(prevState as TokenCommonBaseInfo[]), c]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const removeBase = (token: TokenInfo) => {
|
||||||
|
const index =
|
||||||
|
tokenBase?.findIndex(
|
||||||
|
(t) => token.address.toString() === t.address.toString()
|
||||||
|
) ?? -1;
|
||||||
|
// return if not found
|
||||||
|
if (index == -1) return;
|
||||||
|
const tempTokenBase = tokenBase?.slice();
|
||||||
|
tempTokenBase?.splice(index, 1);
|
||||||
|
setTokenBase(tempTokenBase);
|
||||||
|
removeValue(index);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Token map for quick lookup.
|
||||||
|
const tokenBaseMap = useMemo(() => {
|
||||||
|
const tokenBaseMap = new Map();
|
||||||
|
tokenBase?.forEach((t: TokenCommonBaseInfo) => {
|
||||||
|
tokenBaseMap.set(t.address, t);
|
||||||
|
});
|
||||||
|
return tokenBaseMap;
|
||||||
|
}, [tokenBase]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<_TokenListContext.Provider
|
<_TokenListContext.Provider
|
||||||
value={{
|
value={{
|
||||||
|
@ -115,7 +175,10 @@ export function TokenListContextProvider(props: any) {
|
||||||
swappableTokens,
|
swappableTokens,
|
||||||
swappableTokensWormhole,
|
swappableTokensWormhole,
|
||||||
swappableTokensSollet,
|
swappableTokensSollet,
|
||||||
commonTokenBases,
|
tokenBase,
|
||||||
|
addNewBase,
|
||||||
|
removeBase,
|
||||||
|
tokenBaseMap,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.children}
|
{props.children}
|
||||||
|
@ -137,16 +200,22 @@ export function useTokenMap(): Map<string, TokenInfo> {
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useSwappableTokens() {
|
export function useSwappableTokens() {
|
||||||
const {
|
const { swappableTokens, swappableTokensWormhole, swappableTokensSollet } =
|
||||||
swappableTokens,
|
useTokenListContext();
|
||||||
swappableTokensWormhole,
|
|
||||||
swappableTokensSollet,
|
|
||||||
commonTokenBases,
|
|
||||||
} = useTokenListContext();
|
|
||||||
return {
|
return {
|
||||||
swappableTokens,
|
swappableTokens,
|
||||||
swappableTokensWormhole,
|
swappableTokensWormhole,
|
||||||
swappableTokensSollet,
|
swappableTokensSollet,
|
||||||
commonTokenBases,
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useTokenBase() {
|
||||||
|
const { tokenBase, addNewBase, tokenBaseMap, removeBase } =
|
||||||
|
useTokenListContext();
|
||||||
|
return {
|
||||||
|
tokenBase,
|
||||||
|
addNewBase,
|
||||||
|
tokenBaseMap,
|
||||||
|
removeBase,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
export function LocalStorage(
|
||||||
|
name: string
|
||||||
|
): [any, (val: any) => void, (val: number) => void] {
|
||||||
|
const currentValue = localStorage.getItem(name);
|
||||||
|
let addrList = JSON.parse(currentValue as string);
|
||||||
|
|
||||||
|
function setValues(val: any): void {
|
||||||
|
addrList = addrList ? addrList : [];
|
||||||
|
if (addrList.indexOf(val) == -1) {
|
||||||
|
addrList.push(val);
|
||||||
|
localStorage.setItem(name, JSON.stringify(addrList));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function removeValue(index: number): void {
|
||||||
|
addrList.splice(index, 1);
|
||||||
|
localStorage.setItem(name, JSON.stringify(addrList));
|
||||||
|
}
|
||||||
|
|
||||||
|
return [addrList, setValues, removeValue];
|
||||||
|
}
|
Loading…
Reference in New Issue