diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml index 01ee5807c6be..fff8d66a95f7 100644 --- a/.github/workflows/linters.yml +++ b/.github/workflows/linters.yml @@ -21,7 +21,7 @@ jobs: with: ref: ${{ github.event.pull_request.head.sha }} - uses: actions/setup-node@v2 - - run: npx prettier@2.5.0 --check . + - run: npx prettier@2.6.0 --check . markdownlint: runs-on: ubuntu-20.04 steps: diff --git a/.i18n-codegen.json b/.i18n-codegen.json index 0e88dbae02d2..ed36febbdbaf 100644 --- a/.i18n-codegen.json +++ b/.i18n-codegen.json @@ -181,6 +181,30 @@ "trans": "Translate", "sourceMap": "inline" } + }, + { + "input": "./packages/plugins/GoPlusSecurity/src/locales/en-US.json", + "output": "./packages/plugins/GoPlusSecurity/src/locales/i18n_generated", + "parser": "i18next", + "generator": { + "type": "i18next/react-hooks", + "hooks": "useI18N", + "namespace": "io.gopluslabs.security", + "trans": "Translate", + "sourceMap": "inline" + } + }, + { + "input": "./packages/plugins/CrossChainBridge/src/locales/en-US.json", + "output": "./packages/plugins/CrossChainBridge/src/locales/i18n_generated", + "parser": "i18next", + "generator": { + "type": "i18next/react-hooks", + "hooks": "useI18N", + "namespace": "io.mask.cross-chain-bridge", + "trans": "Translate", + "sourceMap": "inline" + } } ] } diff --git a/cspell.json b/cspell.json index f6e8e7121732..47f3a23cd22f 100644 --- a/cspell.json +++ b/cspell.json @@ -46,6 +46,8 @@ "caip", "canonify", "cashtag", + "cbridge", + "celer", "celo", "checksummed", "choudhary", @@ -308,6 +310,7 @@ "gamification", "getdodoroute", "getx", + "gopluslabs", "gorli", "gwapj", "hookform", @@ -350,6 +353,7 @@ "nyfi", "onflow", "openocean", + "pausable", "pkts", "pnpm", "poap", diff --git a/docs/FAQ.md b/docs/FAQ.md new file mode 100644 index 000000000000..543bef8d0847 --- /dev/null +++ b/docs/FAQ.md @@ -0,0 +1,103 @@ +# FAQ + +## How to resolve merge conflicts in `pnpm-lock.yaml`? + +Merge the target branch into yours and never mind those conflicts in `pnpm-lock.yaml`. And checkout the file to be the one on the target branch to revert changes your branch took in. Then run `pnpm install` to up the lockfile to date. + +E.g., your `feat/fantasy` branch conflicts with `develop` branch. + +```bash +> git branch --show-current +feat/fantasy + +# merge the develop branch and never manually handle the conflicts in lock file +> git merge develop + +# check out the lock file from the base branch +> git checkout develop -- pnpm-lock.yaml + +# up the lockfile to date +> pnpm install +``` + +## Why my Git hooks don't work? + +```bash +npx husky install # on project root directory +``` + +## How to fix cspell errors in CI? + +This project uses [cspell](https://github.com/streetsidesoftware/cspell) for checking typos. You can add unlisted words into `cspell.json` to bypass cspell checking. After you update the configuration file, you could run checking locally before pushing it to make sure your patch is working. + +```bash +npx cspell lint pattern_that_match_your_files + +# e.g. check spell of the RSS3 plugin +npx cspell lint ./packages/plugins/RSS3/**/* +``` + +Learn more: [`cspell.json`](https://cspell.org/configuration/#cspelljson) + +## Why were my components rendered many times? + +All components should working in [Strict Mode](https://reactjs.org/docs/strict-mode.html) and React 18 new [Strict Effects](https://github.com/reactwg/react-18/discussions/19). + +If you found your code not working correctly, please read the documentation above. In addition, you can comment out `` temporarily to verify if it is a problem with your component not supporting Strict Mode. + +DO NOT remove ``. + +## How to download CI builds? + +| Name | Description | +| ----------------------------- | ----------------------------------------------------------------------- | +| MaskNetwork.base.zip | The default build, currently is the same as the Chromium build. | +| MaskNetwork.chromium-beta.zip | Build for Chromium based browsers with some insider features turned on. | +| MaskNetwork.chromium.zip | Build for Chromium based browsers | +| MaskNetwork.firefox.zip | Build for Firefox | +| MaskNetwork.gecko.zip | Build for Android native Mask app | +| MaskNetwork.iOS.zip | Build for iOS native Mask app | + +You can download these builds in two places. + +- Github: Open the pull request page, and click the **Actions** tab. Then on the opened page, click the **build** sub-item on the **Compile** item. On the action detailed page, click the **Summary** tab. Now you can download builds on the **Artifacts** section. + + E.g., + +- CircleCI: Open the pull request page, and scroll down to the review status card. Click **Show all checks** to find the ** + ci/circleci: build** item, and click the **details** link. On the opened CircleCI page, click the **ARTIFACTS** tab. + + E.g., + +## How to resolve "No CORS Headers" problem + +Please contact the service maintainer to add CORS headers, the extension will send requests in following origins: + +| Browser | Origin | +| -------- | --------------------------------------------------- | +| Chromium | chrome-extension://jkoeaghipilijlahjplgbfiocjhldnap | +| Firefox | moz-extension://id | + +The Chromium extension has a fixed id, but only on production mode. And the Firefox browser will set a new id each time it boots an extension. So, in summary, it's better to allow all origins which match the regexp below. + +```txt +/.*-extension:\/\/[^\S]+/ +``` + +If you cannot reach the service maintainer another workaround is to use a proxy server to add CORS headers. To enable it try to change the request URL into `https://cors.r2d2.to/?=[url]`. + +```ts +// before +fetch('https://api.com') + +// after +fetch('https://cors.r2d2.to/?=https://api.com') +``` + +## How to clear the local settings? + +Open the background.html of the extension and execute the following scripts in the console. + +```js +browser.storage.local.clear() +``` diff --git a/docs/evm-integration.md b/docs/evm-integration.md index 7cfddfc9ecf8..489dc889bd55 100644 --- a/docs/evm-integration.md +++ b/docs/evm-integration.md @@ -30,6 +30,10 @@ Mask Network fetches on-chain data from various data sources. Therefore, you can - +### Web3 Constants Compile Config + +Add the chain name to `compileConstants()` in `packages/web3-constants/compile-constants.ts`. + ### Token List The team maintains a token list . So free feel to create one for the chain. And add the token list link to `packages/web3-constants/evm/token-list.json`. diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 000000000000..e85f6b6652de --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,104 @@ +# Setup + +Hi, Welcome to the Mask Network. This guide will quickly take you through setting up the extension development environment. + +## Requirements + +Here is a snippet of engines requirements in the [`package.json`](../package.json) of Mask Network. As you can see, `NodeJS` and `pnpm` are required at least a specific version. + +We suggest you to use the latest Node.js version, and enable [corepack](https://nodejs.org/api/corepack.html). + +## Install + +### pnpm + +The [pnpm](https://pnpm.io/) is a disk space-efficient package manager. After NodeJS is preinstalled. + +If you have [corepack](https://nodejs.org/api/corepack.html) enabled, you can skip the `pnpm` section, `pnpm` is already available! + +If you want to setup pnpm manually, here is the [installation guide from pnpm](https://pnpm.io/installation). + +Now, you will need to have tools installed to start development. + +```bash +pnpm install +``` + +> If you encounter with error `EACCES: permission denied, open...'`, please run `chown -R $USER /pathToYourProject/Mask` to solve. + +### Start the local development server + +For Chromium-based browsers (Chrome, Opera, Edge, etc.), please run `pnpm start`. It's preset of many development commands. + +If you need to develop in other environments (for example, Firefox), please run `npx dev --help` to see the documentation. + +### Load the extension into your browser + +Mask Network has a huge codebase, and it might take time to let the webpack fully startup. It outcomes the `disk/` folder, which contains the unpacked source files of a development version of the Mask Network extension. + +For Chrome, + +- Open `chrome://extensions` or `Settings -> Extensions`. +- Turn on the `Developer mode` on the top right corner. +- It will present a top toolbar with an action button `Load unpacked` on it. Click it and choose the `dist/` folder to load the unpacked version of the Mask Network extension. You can drag and drop the `dist` folder into this page. +- If everything goes fine. Then, the Mask Network will guide you to the setup process. + +For Firefox, it's quite the same process. + +- Open `about:debugging#/runtime/this-firefox` +- Click the `Load Temporary Add-on…` and select the `dist/` folder to load the unpacked extension. +- If everything goes fine. The Mask Network will start to guide you to the setup process. + +## Debugging + +There is no difference between extension development and normal web development. In normal web development, you only have a single web page (SPA), but an extension could have more than one page. + +There is an invisible "background page" running all the time and an "options page" like a normal web page. Moreover, an extension could inject "content script" into the currently visiting web page. + +### Debug the background page + +The background page of the Mask Network extension maintains a bunch of fundamental services for front-end functions. Like Crypto Algorithm, Web3 SDKs, APIs to many third-party data providers, etc. They are stand-by all the time, once to be called for a specific task. + +To debug _background service_, click links right after **Inspect views**. + +![An image displaying Chrome extension manage page](https://user-images.githubusercontent.com/5390719/103509131-5ce0cb00-4e9d-11eb-9aec-b24b9888b863.png) + +### Debug the content script + +Mask Network only injects content script with permission from the user. + +For every new website that Mask Network is going to support, it will show a prompt dialog to ask permission dynamically, rather than asking for all permission when the plugin is installed. + +![An image displaying the Mask Network is asking the permission from the user](https://user-images.githubusercontent.com/52657989/158566232-30c52a17-0168-488c-a292-4fc4059ecb9c.png) + +To debug _content script_, open the dev tools in the web page, then you can select context as the following picture describes. + +![An image displaying how to select Mask Network as the debug context](https://user-images.githubusercontent.com/5390719/103509436-1a6bbe00-4e9e-11eb-9b18-bde021337944.png) + +It's important to select the correct context when you're debugging, +otherwise, you cannot access all the global variables, +_save as temp variables_ also fails. + +### Use React Devtools + +Run the following command to start the React Devtools. It doesn't work if you install it as a browser extension. + +> pnpx react-devtools + +And start the development by the following command: + +> pnpx dev --profile + +## Contribute your working + +### Git conversions + +The `develop` branch is our developing branch, and the `released` branch points to the latest released version. + +Your commit message should follow [Conventional Commits](https://www.conventionalcommits.org). + +### Using Git + +- [Using git rebase on the command line](https://docs.github.com/en/github/getting-started-with-github/using-git-rebase-on-the-command-line) +- [Configuring a remote for a fork](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests/configuring-a-remote-for-a-fork) +- [Syncing a fork](https://docs.github.com/en/github/collaborating-with-issues-and-pull-requests) diff --git a/package.json b/package.json index 45489dd935e6..f2702bbc5981 100644 --- a/package.json +++ b/package.json @@ -2,13 +2,13 @@ "name": "mask-network", "packageManager": "pnpm@6.32.2", "engines": { - "node": ">=16.0.0", + "node": ">=17.0.0", "pnpm": ">=6.32.1", "we-only-allow-pnpm-in-our-project": ">=999.0.0", "yarn": ">=999.0.0", "npm": ">=999.0.0" }, - "version": "2.6.0", + "version": "2.6.1", "private": true, "license": "AGPL-3.0-or-later", "scripts": { @@ -27,7 +27,7 @@ "spellcheck": "cspell lint --no-must-find-files" }, "dependencies": { - "@dimensiondev/kit": "0.0.0-20220223101101-4e6f3b9", + "@dimensiondev/kit": "0.0.0-20220228054820-f2378be", "@emotion/cache": "^11.7.1", "@emotion/react": "^11.8.2", "@emotion/serialize": "^1.0.2", diff --git a/packages/dashboard/package.json b/packages/dashboard/package.json index ce2763edbced..c7f036f22780 100644 --- a/packages/dashboard/package.json +++ b/packages/dashboard/package.json @@ -15,6 +15,7 @@ "@masknet/icons": "workspace:*", "@masknet/plugin-example": "workspace:*", "@masknet/plugin-flow": "workspace:*", + "@masknet/plugin-solana": "workspace:*", "@masknet/plugin-infra": "workspace:*", "@masknet/plugin-wallet": "workspace:*", "@masknet/public-api": "workspace:*", diff --git a/packages/dashboard/src/components/DashboardFrame/Navigation.tsx b/packages/dashboard/src/components/DashboardFrame/Navigation.tsx index 196763c73993..469b906b6ac1 100644 --- a/packages/dashboard/src/components/DashboardFrame/Navigation.tsx +++ b/packages/dashboard/src/components/DashboardFrame/Navigation.tsx @@ -32,7 +32,7 @@ import { import { useDashboardI18N } from '../../locales' import { MaskColorVar } from '@masknet/theme' import { DashboardRoutes } from '@masknet/shared-base' -import { NetworkPluginID, usePluginIDContext } from '@masknet/plugin-infra' +import { NetworkPluginID, useCurrentWeb3NetworkPluginID } from '@masknet/plugin-infra' const ListItemLinkUnStyled = ({ to, ...props }: ListItemProps & { to: string }) => { const navigate = useNavigate() @@ -123,7 +123,7 @@ export function Navigation({ onClose }: NavigationProps) { const isLargeScreen = useMediaQuery((theme) => theme.breakpoints.up('lg')) const t = useDashboardI18N() const mode = useTheme().palette.mode - const currentPluginId = usePluginIDContext() + const currentPluginId = useCurrentWeb3NetworkPluginID() const onExpand = (e: React.MouseEvent) => { e.stopPropagation() diff --git a/packages/dashboard/src/components/FooterLine/index.tsx b/packages/dashboard/src/components/FooterLine/index.tsx index b893f706881d..2dcf9bde0f83 100644 --- a/packages/dashboard/src/components/FooterLine/index.tsx +++ b/packages/dashboard/src/components/FooterLine/index.tsx @@ -7,6 +7,7 @@ import { About } from './About' import { Close } from '@mui/icons-material' import { Version } from './Version' import links from './links.json' +import { openWindow } from '@masknet/shared-base-ui' const useStyles = makeStyles()((theme) => ({ navRoot: { @@ -105,9 +106,9 @@ export const FooterLine = memo(() => { const openVersionLink = (event: React.MouseEvent) => { // `MouseEvent.prototype.metaKey` on macOS (`Command` key), Windows (`Windows` key), Linux (`Super` key) if (process.env.channel === 'stable' && !event.metaKey) { - open(`${links.DOWNLOAD_LINK_STABLE_PREFIX}/v${version}`) + openWindow(`${links.DOWNLOAD_LINK_STABLE_PREFIX}/v${version}`) } else { - open(`${links.DOWNLOAD_LINK_UNSTABLE_PREFIX}/${process.env.COMMIT_HASH}`) + openWindow(`${links.DOWNLOAD_LINK_UNSTABLE_PREFIX}/${process.env.COMMIT_HASH}`) } } return ( diff --git a/packages/dashboard/src/components/PageFrame/FeaturePromotions/index.tsx b/packages/dashboard/src/components/PageFrame/FeaturePromotions/index.tsx index a8b8a77321f6..1dd049428263 100644 --- a/packages/dashboard/src/components/PageFrame/FeaturePromotions/index.tsx +++ b/packages/dashboard/src/components/PageFrame/FeaturePromotions/index.tsx @@ -1,7 +1,7 @@ import { memo, useCallback, useMemo } from 'react' import { useNavigate } from 'react-router-dom' import { makeStyles } from '@masknet/theme' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { openWindow, useRemoteControlledDialog } from '@masknet/shared-base-ui' import { useAccount } from '@masknet/web3-shared-evm' import { PluginMessages, Services } from '../../../API' import { PersonaContext } from '../../../pages/Personas/hooks/usePersonaContext' @@ -64,7 +64,7 @@ export const FeaturePromotions = memo(() => { connectPersona(currentPersona.identifier, EnhanceableSite.Twitter) } - const openMaskNetwork = () => window.open('https://twitter.com/realMaskNetwork') + const openMaskNetwork = () => openWindow('https://twitter.com/realMaskNetwork') return (
diff --git a/packages/dashboard/src/initialization/Dashboard.tsx b/packages/dashboard/src/initialization/Dashboard.tsx index b8d4d7932ac1..a1f3583fc014 100644 --- a/packages/dashboard/src/initialization/Dashboard.tsx +++ b/packages/dashboard/src/initialization/Dashboard.tsx @@ -7,7 +7,7 @@ import { MaskDarkTheme, useSystemPreferencePalette, } from '@masknet/theme' -import { I18NextProviderHMR } from '@masknet/shared' +import { I18NextProviderHMR, SharedContextProvider } from '@masknet/shared' import { ErrorBoundary } from '@masknet/shared-base-ui' import { createInjectHooksRenderer, @@ -62,10 +62,12 @@ export default function DashboardRoot() { - - - - + + + + + + diff --git a/packages/dashboard/src/pages/CreateMaskWallet/components/Welcome/index.tsx b/packages/dashboard/src/pages/CreateMaskWallet/components/Welcome/index.tsx index f5616814e83b..bb1065d382b4 100644 --- a/packages/dashboard/src/pages/CreateMaskWallet/components/Welcome/index.tsx +++ b/packages/dashboard/src/pages/CreateMaskWallet/components/Welcome/index.tsx @@ -6,6 +6,7 @@ import { useDashboardI18N } from '../../../../locales' import { Button } from '@mui/material' import { MaskNotSquareIcon } from '@masknet/icons' import urlcat from 'urlcat' +import { openWindow } from '@masknet/shared-base-ui' const Content = styled('div')` width: 100%; @@ -98,9 +99,7 @@ const Welcome = memo(() => { link?.addEventListener('click', handleLinkClick) } - const handleLinkClick = () => { - window.open(MASK_PRIVACY_POLICY) - } + const handleLinkClick = () => openWindow(MASK_PRIVACY_POLICY) useEffect( () => () => { diff --git a/packages/dashboard/src/pages/Labs/index.tsx b/packages/dashboard/src/pages/Labs/index.tsx index 082e0ce68b52..4375ecc1cd72 100644 --- a/packages/dashboard/src/pages/Labs/index.tsx +++ b/packages/dashboard/src/pages/Labs/index.tsx @@ -23,7 +23,7 @@ import { useDashboardI18N } from '../../locales' import MarketTrendSettingDialog from './components/MarketTrendSettingDialog' import { useAccount } from '@masknet/web3-shared-evm' import { Messages, Services, PluginMessages } from '../../API' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { openWindow, useRemoteControlledDialog } from '@masknet/shared-base-ui' import { TUTORIAL_URLS_EN } from './constants' import { ContentContainer } from '../../components/ContentContainer' import { WalletStateBar } from '../Wallets/components/WalletStateBar' @@ -230,10 +230,7 @@ export default function Plugins() { } function onTutorial(id: string) { - const url = TUTORIAL_URLS_EN[id] - if (url) { - window.open(url, '_blank', 'noopener noreferrer') - } + openWindow(TUTORIAL_URLS_EN[id]) } function onTutorialDialogClose(checked: boolean) { diff --git a/packages/dashboard/src/pages/Personas/components/PersonaCard/index.tsx b/packages/dashboard/src/pages/Personas/components/PersonaCard/index.tsx index ad1badae8cb2..e3faaa4fd2a2 100644 --- a/packages/dashboard/src/pages/Personas/components/PersonaCard/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/PersonaCard/index.tsx @@ -2,13 +2,13 @@ import { memo } from 'react' import { makeStyles, MaskColorVar } from '@masknet/theme' import { Typography } from '@mui/material' import { ConnectedPersonaLine, UnconnectedPersonaLine } from '../PersonaLine' -import type { - NextIDPersonaBindings, - PersonaIdentifier, - ProfileIdentifier, - ProfileInformation, +import { + type NextIDPersonaBindings, + type PersonaIdentifier, + type ProfileIdentifier, + type ProfileInformation, + formatPersonaFingerprint, } from '@masknet/shared-base' -import { formatFingerprint } from '@masknet/shared' import { PersonaContext } from '../../hooks/usePersonaContext' import type { SocialNetwork } from '../../api' import classNames from 'classnames' @@ -103,7 +103,7 @@ export const PersonaCardUI = memo((props) => { {nickname} - {formatFingerprint(identifier.compressedPoint, 4)} + {formatPersonaFingerprint(identifier.compressedPoint, 4)}
diff --git a/packages/dashboard/src/pages/Personas/components/PersonaDrawer/index.tsx b/packages/dashboard/src/pages/Personas/components/PersonaDrawer/index.tsx index a28f8b73a0ea..18e605f82a87 100644 --- a/packages/dashboard/src/pages/Personas/components/PersonaDrawer/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/PersonaDrawer/index.tsx @@ -4,9 +4,8 @@ import { makeStyles, MaskColorVar } from '@masknet/theme' import { PersonaContext } from '../../hooks/usePersonaContext' import { PersonaCard } from '../PersonaCard' import { useDashboardI18N } from '../../../../locales' -import { PersonaIdentifier, PersonaInformation, DashboardRoutes } from '@masknet/shared-base' +import { PersonaIdentifier, PersonaInformation, DashboardRoutes, MAX_PERSONA_LIMIT } from '@masknet/shared-base' import { useNavigate } from 'react-router-dom' -import { MAX_PERSONA_LIMIT } from '@masknet/shared' const useStyles = makeStyles()((theme) => ({ paper: { diff --git a/packages/dashboard/src/pages/Personas/components/PersonaStateBar/index.tsx b/packages/dashboard/src/pages/Personas/components/PersonaStateBar/index.tsx index 8f288d399dfc..ac078f2f144d 100644 --- a/packages/dashboard/src/pages/Personas/components/PersonaStateBar/index.tsx +++ b/packages/dashboard/src/pages/Personas/components/PersonaStateBar/index.tsx @@ -3,7 +3,7 @@ import { Box, IconButton, Stack, Typography } from '@mui/material' import { MaskAvatar } from '../../../../components/MaskAvatar' import { ArrowDownRound, ArrowUpRound } from '@masknet/icons' import { makeStyles, MaskColorVar } from '@masknet/theme' -import { formatFingerprint } from '@masknet/shared' +import { formatPersonaFingerprint } from '@masknet/shared-base' const useStyles = makeStyles()((theme) => ({ iconButton: { @@ -43,7 +43,7 @@ export const PersonaStateBar = memo(({ nickname, toggleDra {nickname} - {formatFingerprint(fingerprint ?? '', 6)} + {formatPersonaFingerprint(fingerprint ?? '', 6)} diff --git a/packages/dashboard/src/pages/Personas/components/PostHistory/Placeholder.tsx b/packages/dashboard/src/pages/Personas/components/PostHistory/Placeholder.tsx index 7b8cb9115e1c..c532fe947d6d 100644 --- a/packages/dashboard/src/pages/Personas/components/PostHistory/Placeholder.tsx +++ b/packages/dashboard/src/pages/Personas/components/PostHistory/Placeholder.tsx @@ -4,6 +4,7 @@ import { EmptyIcon } from '@masknet/icons' import { useDashboardI18N } from '../../../../locales' import urlcat from 'urlcat' import { MaskColorVar } from '@masknet/theme' +import { openWindow } from '@masknet/shared-base-ui' interface PlaceholderProps { network: string @@ -13,7 +14,7 @@ export const Placeholder = memo(({ network }) => { const t = useDashboardI18N() const url = urlcat('https://www.:network', { network: network }) - const handleClick = () => window.open(url) + const handleClick = () => openWindow(url) return ( diff --git a/packages/dashboard/src/pages/Personas/hooks/useOperateBindingProof.ts b/packages/dashboard/src/pages/Personas/hooks/useOperateBindingProof.ts index becfc6857449..7815f6f1ee1e 100644 --- a/packages/dashboard/src/pages/Personas/hooks/useOperateBindingProof.ts +++ b/packages/dashboard/src/pages/Personas/hooks/useOperateBindingProof.ts @@ -1,5 +1,5 @@ import { ECKeyIdentifier, NextIDPlatform } from '@masknet/shared-base' -import { bindProof, createPersonaPayload } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' import { useAsyncFn } from 'react-use' import { Services, Messages } from '../../../API' @@ -9,7 +9,7 @@ export function useDeleteBound() { const username = profile.userId.toLowerCase() const platform = network.split('.')[0] || NextIDPlatform.Twitter if (!persona_ || !persona_.publicHexKey) throw new Error('Failed to get person') - const payload = await createPersonaPayload(persona_.publicHexKey, action, username, platform) + const payload = await NextIDProof.createPersonaPayload(persona_.publicHexKey, action, username, platform) if (!payload) throw new Error('Failed to create persona payload.') const signResult = await Services.Identity.signWithPersona({ method: 'eth', @@ -18,9 +18,17 @@ export function useDeleteBound() { }) if (!signResult) throw new Error('Failed to sign by persona.') const signature = signResult.signature.signature - await bindProof(payload.uuid, persona_.publicHexKey, action, platform, username, payload.createdAt, { - signature: signature, - }) + await NextIDProof.bindProof( + payload.uuid, + persona_.publicHexKey, + action, + platform, + username, + payload.createdAt, + { + signature: signature, + }, + ) Services.Identity.detachProfile(profile) Messages.events.ownProofChanged.sendToAll(undefined) }) diff --git a/packages/dashboard/src/pages/Personas/hooks/usePersonaProof.ts b/packages/dashboard/src/pages/Personas/hooks/usePersonaProof.ts index 845204bdcdb1..d87ba3d025fd 100644 --- a/packages/dashboard/src/pages/Personas/hooks/usePersonaProof.ts +++ b/packages/dashboard/src/pages/Personas/hooks/usePersonaProof.ts @@ -1,11 +1,11 @@ -import { queryExistedBindingByPersona } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' import { useEffect } from 'react' import { useAsyncRetry } from 'react-use' import { Messages } from '../../../API' export function usePersonaProof(publicHexKey: string) { const res = useAsyncRetry(async () => { - return queryExistedBindingByPersona(publicHexKey) + return NextIDProof.queryExistedBindingByPersona(publicHexKey) }, [publicHexKey]) useEffect(() => Messages.events.ownProofChanged.on(res.retry), [res.retry]) return res diff --git a/packages/dashboard/src/pages/SignUp/steps/PreviewDialog.tsx b/packages/dashboard/src/pages/SignUp/steps/PreviewDialog.tsx index 01770a118261..86828c94d9e3 100644 --- a/packages/dashboard/src/pages/SignUp/steps/PreviewDialog.tsx +++ b/packages/dashboard/src/pages/SignUp/steps/PreviewDialog.tsx @@ -3,7 +3,7 @@ import { makeStyles, MaskDialog, MaskColorVar, MaskLightTheme, useCustomSnackbar import { Box, Button, DialogContent, ThemeProvider, Typography } from '@mui/material' import { MnemonicReveal } from '../../../components/Mnemonic' import { MiniMaskIcon, InfoIcon, CopyIcon } from '@masknet/icons' -import { ForwardedRef, forwardRef, useEffect, useRef } from 'react' +import { ForwardedRef, forwardRef, useEffect, useMemo, useRef } from 'react' import { useReactToPrint } from 'react-to-print' import { toJpeg } from 'html-to-image' import { WatermarkURL } from '../../../assets' @@ -107,6 +107,11 @@ const ComponentToPrint = forwardRef((props: PreviewDialogProps, ref: ForwardedRe const [state, copyToClipboard] = useCopyToClipboard() const { showSnackbar } = useCustomSnackbar() + const qrValue = useMemo(() => { + const main = words?.length ? `mnemonic/${btoa(words.join(' '))}` : `privatekey/${privateKey}` + return `mask://persona/${main}?nickname=${personaName}` + }, [words?.join(), privateKey, personaName]) + useEffect(() => { if (state.value) { showSnackbar(t.personas_export_persona_copy_success(), { variant: 'success' }) @@ -157,13 +162,7 @@ const ComponentToPrint = forwardRef((props: PreviewDialogProps, ref: ForwardedRe - + {words?.length ? ( <> diff --git a/packages/dashboard/src/pages/Wallets/components/Assets/index.tsx b/packages/dashboard/src/pages/Wallets/components/Assets/index.tsx index 6775727c3f3f..6a1dcba73df4 100644 --- a/packages/dashboard/src/pages/Wallets/components/Assets/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/Assets/index.tsx @@ -1,17 +1,15 @@ -import { memo, useEffect, useState } from 'react' -import { ContentContainer } from '../../../../components/ContentContainer' +import type { Web3Plugin } from '@masknet/plugin-infra' +import { NetworkPluginID, useCurrentWeb3NetworkPluginID } from '@masknet/plugin-infra' +import { makeStyles, useTabs } from '@masknet/theme' import { TabContext, TabList, TabPanel } from '@mui/lab' import { Box, Button, Tab } from '@mui/material' -import { makeStyles, useTabs } from '@masknet/theme' -import { FungibleTokenTable } from '../FungibleTokenTable' +import { memo, useEffect, useState } from 'react' +import { usePickToken } from '@masknet/shared' +import { ContentContainer } from '../../../../components/ContentContainer' import { useDashboardI18N } from '../../../../locales' -import { CollectibleList } from '../CollectibleList' import { AddCollectibleDialog } from '../AddCollectibleDialog' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { PluginMessages } from '../../../../API' -import type { Web3Plugin } from '@masknet/plugin-infra' -import { NetworkPluginID, usePluginIDContext } from '@masknet/plugin-infra' -import { v4 as uuid } from 'uuid' +import { CollectibleList } from '../CollectibleList' +import { FungibleTokenTable } from '../FungibleTokenTable' const useStyles = makeStyles()((theme) => ({ caption: { @@ -46,9 +44,8 @@ interface TokenAssetsProps { export const Assets = memo(({ network }) => { const t = useDashboardI18N() - const pluginId = usePluginIDContext() + const pluginId = useCurrentWeb3NetworkPluginID() const { classes } = useStyles() - const [id] = useState(uuid()) const assetTabsLabel: Record = { [AssetTab.Token]: t.wallets_assets_token(), [AssetTab.Investment]: t.wallets_assets_investment(), @@ -58,15 +55,13 @@ export const Assets = memo(({ network }) => { const [currentTab, onChange, , setTab] = useTabs(AssetTab.Token, AssetTab.Collectibles) const [addCollectibleOpen, setAddCollectibleOpen] = useState(false) - const { setDialog: setSelectToken } = useRemoteControlledDialog( - PluginMessages.Wallet.events.selectTokenDialogUpdated, - ) useEffect(() => { setTab(AssetTab.Token) }, [pluginId]) const showCollectibles = [NetworkPluginID.PLUGIN_EVM, NetworkPluginID.PLUGIN_SOLANA].includes(pluginId) + const pickToken = usePickToken() return ( <> @@ -85,16 +80,17 @@ export const Assets = memo(({ network }) => { size="small" color="secondary" className={classes.addCustomTokenButton} - onClick={() => - currentTab === AssetTab.Token - ? setSelectToken({ - open: true, - uuid: id, - title: t.wallets_add_token(), - FungibleTokenListProps: { whitelist: [] }, - }) - : setAddCollectibleOpen(true) - }> + onClick={async () => { + if (currentTab === AssetTab.Token) { + // TODO handle result + await pickToken({ + whitelist: [], + title: t.wallets_add_token(), + }) + } else { + setAddCollectibleOpen(true) + } + }}> +{' '} {currentTab === AssetTab.Token ? t.wallets_add_token() @@ -113,9 +109,7 @@ export const Assets = memo(({ network }) => { - {addCollectibleOpen && ( - setAddCollectibleOpen(false)} /> - )} + {addCollectibleOpen && setAddCollectibleOpen(false)} />} ) }) diff --git a/packages/dashboard/src/pages/Wallets/components/CollectibleCard/index.tsx b/packages/dashboard/src/pages/Wallets/components/CollectibleCard/index.tsx index 4aabccd9f475..f6efbf2c828a 100644 --- a/packages/dashboard/src/pages/Wallets/components/CollectibleCard/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/CollectibleCard/index.tsx @@ -9,7 +9,7 @@ import { ChangeNetworkTip } from '../FungibleTokenTableRow/ChangeNetworkTip' import { NetworkPluginID, useNetworkDescriptor, - usePluginIDContext, + useCurrentWeb3NetworkPluginID, useWeb3State, Web3Plugin, } from '@masknet/plugin-infra' @@ -109,7 +109,7 @@ export const CollectibleCard = memo(({ chainId, token, onS const isHovering = useHoverDirty(ref) const networkDescriptor = useNetworkDescriptor(token.contract?.chainId) const isOnCurrentChain = useMemo(() => chainId === token.contract?.chainId, [chainId, token]) - const currentPluginId = usePluginIDContext() + const currentPluginId = useCurrentWeb3NetworkPluginID() useEffect(() => { setHoveringTooltip(false) diff --git a/packages/dashboard/src/pages/Wallets/components/CollectibleList/index.tsx b/packages/dashboard/src/pages/Wallets/components/CollectibleList/index.tsx index 934f6189d4c7..695e940d787f 100644 --- a/packages/dashboard/src/pages/Wallets/components/CollectibleList/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/CollectibleList/index.tsx @@ -1,21 +1,20 @@ import { Dispatch, memo, SetStateAction, useCallback, useEffect, useRef, useState } from 'react' +import { useNavigate } from 'react-router-dom' import { Box, Stack, TablePagination } from '@mui/material' import { makeStyles } from '@masknet/theme' -import { EMPTY_LIST } from '@masknet/web3-shared-evm' import { LoadingPlaceholder } from '../../../../components/LoadingPlaceholder' +import { DashboardRoutes, EMPTY_LIST } from '@masknet/shared-base' import { EmptyPlaceholder } from '../EmptyPlaceholder' import { CollectibleCard } from '../CollectibleCard' import { useDashboardI18N } from '../../../../locales' import { PluginMessages } from '../../../../API' -import { useNavigate } from 'react-router-dom' -import { DashboardRoutes } from '@masknet/shared-base' import { TransferTab } from '../Transfer' import { useNetworkDescriptor, useWeb3State as useWeb3PluginState, Web3Plugin, useAccount, - usePluginIDContext, + useCurrentWeb3NetworkPluginID, NetworkPluginID, } from '@masknet/plugin-infra' import { useAsyncRetry } from 'react-use' @@ -65,6 +64,18 @@ export const CollectibleList = memo(({ selectedNetwork }) Asset?.getNonFungibleAssets?.(account, { page: page, size: 20 }, undefined, selectedNetwork || undefined), [account, Asset?.getNonFungibleAssets, network, selectedNetwork], ) + useEffect(() => { + const unsubscribeTokens = PluginMessages.Wallet.events.erc721TokensUpdated.on(() => retry()) + const unsubscribeSocket = PluginMessages.Wallet.events.socketMessageUpdated.on((info) => { + if (!info.done) { + retry() + } + }) + return () => { + unsubscribeTokens() + unsubscribeSocket() + } + }, [retry]) useEffect(() => { if (!loadingSize) return @@ -72,7 +83,7 @@ export const CollectibleList = memo(({ selectedNetwork }) setRenderData(render) }, [value.data, loadingSize, page]) - const currentPluginId = usePluginIDContext() + const currentPluginId = useCurrentWeb3NetworkPluginID() const onSend = useCallback( (detail: Web3Plugin.NonFungibleToken) => { // Sending NFT is only available on EVM currently. @@ -87,15 +98,6 @@ export const CollectibleList = memo(({ selectedNetwork }) [currentPluginId], ) - useEffect(() => { - PluginMessages.Wallet.events.erc721TokensUpdated.on(() => retry()) - PluginMessages.Wallet.events.socketMessageUpdated.on((info) => { - if (!info.done) { - retry() - } - }) - }, [retry]) - const hasNextPage = (page + 1) * loadingSize < value.data.length const isLoading = renderData.length === 0 && isQuerying diff --git a/packages/dashboard/src/pages/Wallets/components/FungibleTokenTable/index.tsx b/packages/dashboard/src/pages/Wallets/components/FungibleTokenTable/index.tsx index eab1e788ea4b..5ae01fcc022a 100644 --- a/packages/dashboard/src/pages/Wallets/components/FungibleTokenTable/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/FungibleTokenTable/index.tsx @@ -18,7 +18,7 @@ import { Web3Plugin, useAccount, NetworkPluginID, - usePluginIDContext, + useCurrentWeb3NetworkPluginID, } from '@masknet/plugin-infra' const useStyles = makeStyles()((theme) => ({ @@ -133,7 +133,7 @@ export interface TokenTableUIProps { export const TokenTableUI = memo(({ onSwap, onSend, isLoading, isEmpty, dataSource }) => { const t = useDashboardI18N() const { classes } = useStyles() - const currentPluginId = usePluginIDContext() + const currentPluginId = useCurrentWeb3NetworkPluginID() const { Utils } = useWeb3PluginState() return ( diff --git a/packages/dashboard/src/pages/Wallets/components/FungibleTokenTableRow/index.tsx b/packages/dashboard/src/pages/Wallets/components/FungibleTokenTableRow/index.tsx index e241e78fb8e2..9f0346e4d371 100644 --- a/packages/dashboard/src/pages/Wallets/components/FungibleTokenTableRow/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/FungibleTokenTableRow/index.tsx @@ -8,7 +8,7 @@ import { NetworkPluginID, useChainId, useNetworkDescriptors, - usePluginIDContext, + useCurrentWeb3NetworkPluginID, useWeb3State, Web3Plugin, } from '@masknet/plugin-infra' @@ -77,7 +77,7 @@ export const FungibleTokenTableRow = memo(({ asset, onSend, const currentChainId = useChainId() const { Utils } = useWeb3State() const networkDescriptors = useNetworkDescriptors() - const currentPluginId = usePluginIDContext() + const currentPluginId = useCurrentWeb3NetworkPluginID() const isOnCurrentChain = useMemo(() => currentChainId === asset.token.chainId, [asset, currentChainId]) return ( diff --git a/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC20.tsx b/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC20.tsx index ce5f9e651744..201755ea9479 100644 --- a/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC20.tsx +++ b/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC20.tsx @@ -1,6 +1,16 @@ +import { RightIcon } from '@masknet/icons' +import { + NetworkPluginID, + TokenType, + useLookupAddress, + useNetworkDescriptor, + useWeb3State, + Web3Plugin, +} from '@masknet/plugin-infra' +import { NetworkType } from '@masknet/public-api' +import { FormattedAddress, TokenAmountPanel, usePickToken } from '@masknet/shared' import { MaskColorVar, MaskTextField } from '@masknet/theme' -import { Box, Button, IconButton, Link, Popover, Stack, Typography } from '@mui/material' -import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { isGreaterThan, isZero, multipliedBy, rightShift } from '@masknet/web3-shared-base' import { addGasMargin, EthereumTokenType, @@ -17,29 +27,15 @@ import { useTokenConstants, useTokenTransferCallback, } from '@masknet/web3-shared-evm' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { isGreaterThan, isZero, multipliedBy, rightShift } from '@masknet/web3-shared-base' -import BigNumber from 'bignumber.js' -import { - NetworkPluginID, - TokenType, - useLookupAddress, - useNetworkDescriptor, - useWeb3State, - Web3Plugin, -} from '@masknet/plugin-infra' -import { FormattedAddress, TokenAmountPanel } from '@masknet/shared' import TuneIcon from '@mui/icons-material/Tune' +import { Box, Button, IconButton, Link, Popover, Stack, Typography } from '@mui/material' +import BigNumber from 'bignumber.js' +import { memo, useCallback, useEffect, useMemo, useRef, useState } from 'react' +import { useUpdateEffect } from 'react-use' import { EthereumAddress } from 'wallet.ts' import { useDashboardI18N } from '../../../../locales' -import { useNativeTokenPrice } from './useNativeTokenPrice' import { useGasConfig } from '../../hooks/useGasConfig' -import { NetworkType } from '@masknet/public-api' -import { useUpdateEffect } from 'react-use' -import { v4 as uuid } from 'uuid' -import { PluginMessages } from '../../../../API' -import type { SelectTokenDialogEvent } from '@masknet/plugin-wallet' -import { RightIcon } from '@masknet/icons' +import { useNativeTokenPrice } from './useNativeTokenPrice' interface TransferERC20Props { token: FungibleTokenDetailed | Web3Plugin.FungibleToken @@ -50,7 +46,6 @@ export const TransferERC20 = memo(({ token }) => { const t = useDashboardI18N() const { NATIVE_TOKEN_ADDRESS } = useTokenConstants() const anchorEl = useRef(null) - const [id] = useState(uuid()) const [amount, setAmount] = useState('') const [address, setAddress] = useState('') const [memo, setMemo] = useState('') @@ -59,20 +54,10 @@ export const TransferERC20 = memo(({ token }) => { const network = useNetworkDescriptor() const [gasLimit_, setGasLimit_] = useState(0) - const { setDialog: setSelectToken } = useRemoteControlledDialog( - PluginMessages.Wallet.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== id) return - setSelectedToken(ev.token) - }, - [id], - ), - ) - const { value: defaultGasPrice = '0' } = useGasPrice() const [selectedToken, setSelectedToken] = useState(token) + const pickToken = usePickToken() const chainId = useChainId() const is1559Supported = useMemo(() => isEIP1559Supported(chainId), [chainId]) @@ -297,12 +282,12 @@ export const TransferERC20 = memo(({ token }) => { SelectTokenChip={{ loading: false, ChipProps: { - onClick: () => - setSelectToken({ - open: true, - uuid: id, + onClick: async () => { + const pickedToken = await pickToken({ disableNativeToken: false, - }), + }) + if (pickedToken) setSelectedToken(pickedToken) + }, }, }} /> diff --git a/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC721.tsx b/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC721.tsx index ce5c581dcfa3..7e72ca73c272 100644 --- a/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC721.tsx +++ b/packages/dashboard/src/pages/Wallets/components/Transfer/TransferERC721.tsx @@ -75,7 +75,7 @@ export const TransferERC721 = memo(() => { } | null>(null) const [minPopoverWidth, setMinPopoverWidth] = useState(0) const [contract, setContract] = useState() - const [id] = useState(uuid()) + const [id] = useState(uuid) const [gasLimit_, setGasLimit_] = useState(0) const network = useNetworkDescriptor() const { Utils } = useWeb3State() diff --git a/packages/dashboard/src/pages/Wallets/components/WalletStateBar/index.tsx b/packages/dashboard/src/pages/Wallets/components/WalletStateBar/index.tsx index 6b93fbc3013a..574dd8689708 100644 --- a/packages/dashboard/src/pages/Wallets/components/WalletStateBar/index.tsx +++ b/packages/dashboard/src/pages/Wallets/components/WalletStateBar/index.tsx @@ -1,6 +1,6 @@ import { FC, memo } from 'react' import { Box, Button, Stack, Typography } from '@mui/material' -import { EMPTY_LIST, ProviderType, TransactionStatusType } from '@masknet/web3-shared-evm' +import { ProviderType, TransactionStatusType } from '@masknet/web3-shared-evm' import { makeStyles, MaskColorVar } from '@masknet/theme' import { FormattedAddress, LoadingAnimation, WalletIcon } from '@masknet/shared' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' @@ -12,6 +12,7 @@ import { Web3Plugin, useReverseAddress, } from '@masknet/plugin-infra' +import { EMPTY_LIST } from '@masknet/shared-base' import { PluginMessages } from '../../../../API' import { useRecentTransactions } from '../../hooks/useRecentTransactions' import { useDashboardI18N } from '../../../../locales' diff --git a/packages/dashboard/src/pages/Wallets/index.tsx b/packages/dashboard/src/pages/Wallets/index.tsx index 51f53c52697a..676c9be0e2e4 100644 --- a/packages/dashboard/src/pages/Wallets/index.tsx +++ b/packages/dashboard/src/pages/Wallets/index.tsx @@ -4,7 +4,7 @@ import { useAccount, useChainId, useNetworkDescriptor, - usePluginIDContext, + useCurrentWeb3NetworkPluginID, useWallet, useWallets, useWeb3State as useWeb3PluginState, @@ -51,7 +51,7 @@ function Wallets() { const networks = getRegisteredWeb3Networks() const networkDescriptor = useNetworkDescriptor() - const pluginId = usePluginIDContext() + const pluginId = useCurrentWeb3NetworkPluginID() const [selectedNetwork, setSelectedNetwork] = useState( networkDescriptor ?? null, ) diff --git a/packages/dashboard/src/pages/Welcome/index.tsx b/packages/dashboard/src/pages/Welcome/index.tsx index bd4987db60bc..9e4893222ee7 100644 --- a/packages/dashboard/src/pages/Welcome/index.tsx +++ b/packages/dashboard/src/pages/Welcome/index.tsx @@ -6,6 +6,7 @@ import { styled } from '@mui/material/styles' import { memo, MutableRefObject, useEffect, useMemo, useRef } from 'react' import { useDashboardI18N } from '../../locales' import links from '../../components/FooterLine/links.json' +import { openWindow } from '@masknet/shared-base-ui' const Content = styled('div')(({ theme }) => ({ padding: `${theme.spacing(1)} ${theme.spacing(4)}`, @@ -71,9 +72,7 @@ export default function Welcome() { link?.addEventListener('click', handleLinkClick) } - const handleLinkClick = () => { - window.open(links.MASK_PRIVACY_POLICY) - } + const handleLinkClick = () => openWindow(links.MASK_PRIVACY_POLICY) return ( + + , + '0 0 20 24', +) diff --git a/packages/icons/general/Edit2.tsx b/packages/icons/general/Edit2.tsx new file mode 100644 index 000000000000..0e05c13325dd --- /dev/null +++ b/packages/icons/general/Edit2.tsx @@ -0,0 +1,13 @@ +import { createIcon } from '../utils' +import type { SvgIcon } from '@mui/material' + +export const Edit2Icon: typeof SvgIcon = createIcon( + 'Edit2Icon', + + + , + '0 0 24 24', +) diff --git a/packages/icons/general/Risk.tsx b/packages/icons/general/Risk.tsx index ec4879d374e8..954ea2825e52 100644 --- a/packages/icons/general/Risk.tsx +++ b/packages/icons/general/Risk.tsx @@ -5,11 +5,11 @@ export const RiskIcon = createIcon( , '0 0 24 24', diff --git a/packages/icons/general/index.ts b/packages/icons/general/index.ts index a3d4ce6901cf..f9e5c503e831 100644 --- a/packages/icons/general/index.ts +++ b/packages/icons/general/index.ts @@ -12,6 +12,7 @@ export * from './Link' export * from './Author' export * from './Settings' export * from './Edit' +export * from './Edit2' export * from './ArrowDownRound' export * from './ArrowUpRound' export * from './Empty' @@ -79,6 +80,7 @@ export * from './ChevronUp' export * from './BestTrade' export * from './Retweet' export * from './Drop' +export * from './Drop2' export * from './Right' export * from './Tutorial' export * from './AssetLoading' diff --git a/packages/injected-script/main/locationChange.ts b/packages/injected-script/main/locationChange.ts index 4f1e05d0b8bd..26ed22e27521 100644 --- a/packages/injected-script/main/locationChange.ts +++ b/packages/injected-script/main/locationChange.ts @@ -1,12 +1,16 @@ import { apply, dispatchEvent, no_xray_Event, no_xray_Proxy } from './intrinsic' function setupChromium() { + let currentLocationHref = window.location.href // Learn more about this hack from https://stackoverflow.com/a/52809105/1986338 window.history.pushState = new no_xray_Proxy(history.pushState, { apply(target, thisArg, params: any) { const val = apply(target, thisArg, params) apply(dispatchEvent, window, [new no_xray_Event('pushstate')]) - apply(dispatchEvent, window, [new no_xray_Event('locationchange')]) + if (currentLocationHref !== window.location.href) { + currentLocationHref = window.location.href + apply(dispatchEvent, window, [new no_xray_Event('locationchange')]) + } return val }, }) @@ -14,12 +18,17 @@ function setupChromium() { apply(target, thisArg, params: any) { const val = apply(target, thisArg, params) apply(dispatchEvent, window, [new no_xray_Event('replacestate')]) - apply(dispatchEvent, window, [new no_xray_Event('locationchange')]) + if (currentLocationHref !== window.location.href) { + currentLocationHref = window.location.href + apply(dispatchEvent, window, [new no_xray_Event('locationchange')]) + } return val }, }) window.addEventListener('popstate', () => { + if (currentLocationHref === window.location.href) return + currentLocationHref = window.location.href apply(dispatchEvent, window, [new no_xray_Event('locationchange')]) }) } diff --git a/packages/mask/.webpack/config.ts b/packages/mask/.webpack/config.ts index e5a143627592..00855f7fc0bc 100644 --- a/packages/mask/.webpack/config.ts +++ b/packages/mask/.webpack/config.ts @@ -88,6 +88,7 @@ export function createConfiguration(rawFlags: BuildFlags): Configuration { '@masknet/plugin-wallet': join(__dirname, '../../plugins/Wallet/src/'), '@masknet/plugin-file-service': join(__dirname, '../../plugins/FileService/src/'), '@masknet/plugin-cyberconnect': join(__dirname, '../../plugins/CyberConnect/src/'), + '@masknet/plugin-go-plus-security': join(__dirname, '../../plugins/GoPlusSecurity/src/'), '@masknet/external-plugin-previewer': join(__dirname, '../../external-plugin-previewer/src/'), '@masknet/public-api': join(__dirname, '../../public-api/src/'), '@masknet/sdk': join(__dirname, '../../mask-sdk/server/'), @@ -95,6 +96,7 @@ export function createConfiguration(rawFlags: BuildFlags): Configuration { '@masknet/encryption': join(__dirname, '../../encryption/src'), '@masknet/typed-message/dom$': require.resolve('../../typed-message/dom/index.ts'), '@masknet/typed-message$': require.resolve('../../typed-message/base/index.ts'), + '@masknet/plugin-cross-chain-bridge': join(__dirname, '../../plugins/CrossChainBridge/src'), // @masknet/scripts: insert-here '@uniswap/v3-sdk': require.resolve('@uniswap/v3-sdk/dist/index.js'), } diff --git a/packages/mask/package.json b/packages/mask/package.json index 2e6c8b94087c..c7a6c5bd2fc8 100644 --- a/packages/mask/package.json +++ b/packages/mask/package.json @@ -19,12 +19,14 @@ "@masknet/gun-utils": "workspace:*", "@masknet/icons": "workspace:*", "@masknet/injected-script": "workspace:*", + "@masknet/plugin-cross-chain-bridge": "workspace:*", "@masknet/plugin-cyberconnect": "workspace:*", "@masknet/plugin-dao": "workspace:*", "@masknet/plugin-debugger": "workspace:*", "@masknet/plugin-example": "workspace:*", "@masknet/plugin-file-service": "workspace:*", "@masknet/plugin-flow": "workspace:*", + "@masknet/plugin-go-plus-security": "workspace:*", "@masknet/plugin-infra": "workspace:*", "@masknet/plugin-rss3": "workspace:*", "@masknet/plugin-solana": "workspace:*", diff --git a/packages/mask/shared-ui/locales/en-US.json b/packages/mask/shared-ui/locales/en-US.json index b8342b4b448a..5520be92abc7 100644 --- a/packages/mask/shared-ui/locales/en-US.json +++ b/packages/mask/shared-ui/locales/en-US.json @@ -124,7 +124,6 @@ "beta_sup": "(beta)", "post_dialog_plugins_experimental": "Plugins (Experimental)", "post_dialog__button": "Encrypt", - "post_dialog__dismiss_aria": "Dismiss the compose dialog", "post_dialog__image_payload": "Image Payload", "post_dialog__more_options_title": "More Options", "post_dialog__placeholder": "Text goes here…", diff --git a/packages/mask/shared-ui/locales/ja-JP.json b/packages/mask/shared-ui/locales/ja-JP.json index ddbe55582ad1..a178ed0c287c 100644 --- a/packages/mask/shared-ui/locales/ja-JP.json +++ b/packages/mask/shared-ui/locales/ja-JP.json @@ -47,7 +47,6 @@ "personas": "ペルソナ", "browser_action_enter_dashboard": "ダッシュボードに入る", "post_dialog__button": "完了", - "post_dialog__dismiss_aria": "作成ダイアログを閉じる", "post_dialog__image_payload": "画像で暗号化", "post_dialog__more_options_title": "他のオプション", "post_dialog__placeholder": "ここにテキストを持ってきます", @@ -239,7 +238,7 @@ "plugin_ito_qualification_start_time": "参加資格開始時間:", "plugin_ito_error_qualification_start_time": "無効:資格開始時間は ITO の終了時間よりも前でなくてはいけません", "plugin_ito_error_invalid_qualification": "無効な資格アドレス", - "plugin_ito_unlock_time": "ロック解除時間 {{zone}}", + "plugin_ito_unlock_time": "ロック解除時間", "plugin_ito_qualification_label": "プラグインのコントラクト", "plugin_ito_message_label": "タイトル", "plugin_ito_region_label": "IP リージョンの選択", diff --git a/packages/mask/shared-ui/locales/ko-KR.json b/packages/mask/shared-ui/locales/ko-KR.json index 7f6d0b03a5c8..2e3c96d775ab 100644 --- a/packages/mask/shared-ui/locales/ko-KR.json +++ b/packages/mask/shared-ui/locales/ko-KR.json @@ -99,7 +99,6 @@ "beta_sup": "(beta)", "post_dialog_plugins_experimental": "Plugins (Experimental)", "post_dialog__button": "끝내기", - "post_dialog__dismiss_aria": "편집창을 닫기", "post_dialog__image_payload": "이미지 페이로드", "post_dialog__more_options_title": "추가 옵션", "post_dialog__placeholder": "여기서 텍스트 치기...", @@ -432,7 +431,7 @@ "plugin_ito_error_qualification_start_time": "주의: 자격 인정 시작 시간이 ITO 종료 시간보다 빨아야 합니다.", "plugin_ito_error_invalid_qualification": "무효한 인증 주소입니다.", "plugin_ito_unlock_time_cert": "ITO Mask 언락 시간은 {{date}}.", - "plugin_ito_unlock_time": "언락 시간 {{zone}}", + "plugin_ito_unlock_time": "언락 시간", "plugin_ito_launch_campaign": "SocialFi Launch Campaign", "plugin_ito_total_claimable_count": "전체: ", "plugin_ito_qualification_label": "플러그인 컨트랙트", diff --git a/packages/mask/shared-ui/locales/qya-AA.json b/packages/mask/shared-ui/locales/qya-AA.json index cf87cc4ab822..ebad6607635f 100644 --- a/packages/mask/shared-ui/locales/qya-AA.json +++ b/packages/mask/shared-ui/locales/qya-AA.json @@ -123,7 +123,6 @@ "beta_sup": "crwdns10125:0crwdne10125:0", "post_dialog_plugins_experimental": "crwdns10127:0crwdne10127:0", "post_dialog__button": "crwdns4257:0crwdne4257:0", - "post_dialog__dismiss_aria": "crwdns4259:0crwdne4259:0", "post_dialog__image_payload": "crwdns4261:0crwdne4261:0", "post_dialog__more_options_title": "crwdns4263:0crwdne4263:0", "post_dialog__placeholder": "crwdns4265:0crwdne4265:0", diff --git a/packages/mask/shared-ui/locales/zh-CN.json b/packages/mask/shared-ui/locales/zh-CN.json index 632e58ff618c..7b1026789475 100644 --- a/packages/mask/shared-ui/locales/zh-CN.json +++ b/packages/mask/shared-ui/locales/zh-CN.json @@ -115,7 +115,6 @@ "beta_sup": "(测试版)", "post_dialog_plugins_experimental": "插件 (体验版)", "post_dialog__button": "加密", - "post_dialog__dismiss_aria": "关闭贴文发送对话框", "post_dialog__image_payload": "图片Payload", "post_dialog__more_options_title": "更多选项", "post_dialog__placeholder": "文本在此处…", @@ -532,7 +531,7 @@ "plugin_ito_error_qualification_start_time": "无效:资格认证开始时间应该早于ITO结束时间", "plugin_ito_error_invalid_qualification": "无效的资格认证地址", "plugin_ito_unlock_time_cert": "ITO Mask 解锁时间为 {{date}}。", - "plugin_ito_unlock_time": "解锁时间 {{zone}}", + "plugin_ito_unlock_time": "解锁时间", "plugin_ito_total_claimable_count": "总计: ", "plugin_ito_qualification_label": "插件合约", "plugin_ito_message_label": "标题", diff --git a/packages/mask/shared-ui/locales/zh-TW.json b/packages/mask/shared-ui/locales/zh-TW.json index 0d750aed3bd7..a5d1157e8c64 100644 --- a/packages/mask/shared-ui/locales/zh-TW.json +++ b/packages/mask/shared-ui/locales/zh-TW.json @@ -76,7 +76,6 @@ "personas": "角色", "browser_action_enter_dashboard": "進入儀錶板", "post_dialog__button": "完成", - "post_dialog__dismiss_aria": "關閉發表貼文視窗", "post_dialog__image_payload": "圖片", "post_dialog__more_options_title": "更多選項", "post_dialog__placeholder": "在這裡輸入文字…", @@ -376,7 +375,7 @@ "plugin_ito_qualification_start_time": "資格開始時間:", "plugin_ito_error_qualification_start_time": "錯誤:資格開始時間應早於 ITO 結束時間", "plugin_ito_error_invalid_qualification": "錯誤的資格地址", - "plugin_ito_unlock_time": "解鎖時間 {{zone}}", + "plugin_ito_unlock_time": "解鎖時間", "plugin_ito_total_claimable_count": "總計: ", "plugin_ito_qualification_label": "插件合約", "plugin_ito_message_label": "標題", diff --git a/packages/mask/shared/helpers/index.ts b/packages/mask/shared/helpers/index.ts new file mode 100644 index 000000000000..99d94fcfb3c4 --- /dev/null +++ b/packages/mask/shared/helpers/index.ts @@ -0,0 +1 @@ +export * from './download' diff --git a/packages/mask/shared/index.ts b/packages/mask/shared/index.ts index b02aa56ca45f..1a05d3e0ac64 100644 --- a/packages/mask/shared/index.ts +++ b/packages/mask/shared/index.ts @@ -1,4 +1,4 @@ export * from './messages' export * from './flags' export { InMemoryStorages, PersistentStorages } from './kv-storage' -export * from './helpers/download' +export * from './helpers' diff --git a/packages/mask/src/UIRoot.tsx b/packages/mask/src/UIRoot.tsx index 31d738d9740c..169c99453bfe 100644 --- a/packages/mask/src/UIRoot.tsx +++ b/packages/mask/src/UIRoot.tsx @@ -3,7 +3,7 @@ import { CustomSnackbarProvider } from '@masknet/theme' import { Web3Provider } from '@masknet/web3-shared-evm' import { CssBaseline, StyledEngineProvider, Theme, ThemeProvider } from '@mui/material' import { NetworkPluginID, PluginsWeb3ContextProvider, useAllPluginsWeb3State } from '@masknet/plugin-infra' -import { I18NextProviderHMR } from '@masknet/shared' +import { I18NextProviderHMR, SharedContextProvider } from '@masknet/shared' import { ErrorBoundary, ErrorBoundaryBuildInfoContext, useValueRef } from '@masknet/shared-base-ui' import i18nNextInstance from '../shared-ui/locales_legacy' import { Web3Context } from './web3/context' @@ -89,5 +89,6 @@ export function MaskUIRoot({ children, kind, useTheme }: MaskUIRootProps) { {jsx} ), + (jsx) => {jsx}, ) } diff --git a/packages/mask/src/components/CompositionDialog/Composition.tsx b/packages/mask/src/components/CompositionDialog/Composition.tsx index cd5a11b5b22c..59889fd34719 100644 --- a/packages/mask/src/components/CompositionDialog/Composition.tsx +++ b/packages/mask/src/components/CompositionDialog/Composition.tsx @@ -3,8 +3,9 @@ import { DialogContent } from '@mui/material' import { DialogStackingProvider } from '@masknet/theme' import { activatedSocialNetworkUI, globalUIState } from '../../social-network' import { MaskMessages, useI18N } from '../../utils' +import { CrossIsolationMessages } from '@masknet/shared-base' import { useFriendsList as useRecipientsList } from '../DataSource/useActivatedUI' -import { InjectedDialog } from '../shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { CompositionDialogUI, CompositionRef } from './CompositionUI' import { useCompositionClipboardRequest } from './useCompositionClipboardRequest' import Services from '../../extension/service' @@ -45,7 +46,7 @@ export function Composition({ type = 'timeline', requireClipboardPermission }: P }, [onQueryClipboardPermission]) useEffect(() => { - return MaskMessages.events.requestComposition.on(({ reason, open, content, options }) => { + return CrossIsolationMessages.events.requestComposition.on(({ reason, open, content, options }) => { if ( (reason !== 'reply' && reason !== type) || (reason === 'reply' && type === 'popup') || diff --git a/packages/mask/src/components/CompositionDialog/PluginEntryRender.tsx b/packages/mask/src/components/CompositionDialog/PluginEntryRender.tsx index 5db488b0ef98..0f24a4afb9ed 100644 --- a/packages/mask/src/components/CompositionDialog/PluginEntryRender.tsx +++ b/packages/mask/src/components/CompositionDialog/PluginEntryRender.tsx @@ -1,5 +1,5 @@ import { - usePluginIDContext, + useCurrentWeb3NetworkPluginID, useActivatedPluginSNSAdaptor_Web3Supported, useActivatedPluginsSNSAdaptor, Plugin, @@ -28,7 +28,7 @@ export const PluginEntryRender = memo( const [trackPluginRef] = useSetPluginEntryRenderRef(ref) const pluginField = usePluginI18NField() const chainId = useChainId() - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() const operatingSupportedChainMapping = useActivatedPluginSNSAdaptor_Web3Supported(chainId, pluginID) const result = [...useActivatedPluginsSNSAdaptor('any')] .sort((plugin) => { diff --git a/packages/mask/src/components/DataSource/useActivatedUI.ts b/packages/mask/src/components/DataSource/useActivatedUI.ts index a829094b54ff..c5cef6e522c4 100644 --- a/packages/mask/src/components/DataSource/useActivatedUI.ts +++ b/packages/mask/src/components/DataSource/useActivatedUI.ts @@ -1,7 +1,7 @@ import { useMemo } from 'react' import { ValueRef } from '@dimensiondev/holoflows-kit' import { useValueRef } from '@masknet/shared-base-ui' -import { ProfileIdentifier } from '@masknet/shared-base' +import { EMPTY_LIST, ProfileIdentifier } from '@masknet/shared-base' import type { Profile } from '../../database' import type { SocialNetworkUI } from '../../social-network' import { activatedSocialNetworkUI, globalUIState } from '../../social-network' @@ -9,7 +9,7 @@ import { Subscription, useSubscription } from 'use-subscription' export function useFriendsList() { const ref = useValueRef(globalUIState.friends) - return useMemo(() => [...ref.values()], [ref]) + return useMemo(() => (ref.values.length ? [...ref.values()] : EMPTY_LIST), [ref]) } const default_ = new ValueRef({ identifier: ProfileIdentifier.unknown }) diff --git a/packages/mask/src/components/DataSource/useNextID.ts b/packages/mask/src/components/DataSource/useNextID.ts index 9537838e36b9..ac19ace0ae2e 100644 --- a/packages/mask/src/components/DataSource/useNextID.ts +++ b/packages/mask/src/components/DataSource/useNextID.ts @@ -9,13 +9,13 @@ import { SetupGuideStep } from '../InjectedComponents/SetupGuide/types' import type { SetupGuideCrossContextStatus } from '../../settings/types' import { useLastRecognizedIdentity } from './useActivatedUI' import { useValueRef } from '@masknet/shared-base-ui' -import { queryExistedBindingByPersona, queryExistedBindingByPlatform, queryIsBound } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' import Services from '../../extension/service' import { MaskMessages } from '../../utils' export const usePersonaBoundPlatform = (personaPublicKey: string) => { return useAsyncRetry(() => { - return queryExistedBindingByPersona(personaPublicKey) + return NextIDProof.queryExistedBindingByPersona(personaPublicKey) }, [personaPublicKey]) } @@ -33,7 +33,7 @@ const verifyPersona = (personaIdentifier?: PersonaIdentifier, username?: string) export const useNextIDBoundByPlatform = (platform: NextIDPlatform, identity: string) => { const res = useAsyncRetry(() => { - return queryExistedBindingByPlatform(platform, identity) + return NextIDProof.queryExistedBindingByPlatform(platform, identity) }, [platform, identity]) useEffect(() => MaskMessages.events.ownProofChanged.on(res.retry), [res.retry]) return res @@ -99,7 +99,7 @@ export function useNextIDConnectStatus() { const platform = ui.configuration.nextIDConfig?.platform as NextIDPlatform | undefined if (!platform) return NextIDVerificationStatus.Other - const isBound = await queryIsBound(currentConnectedPersona.publicHexKey, platform, username) + const isBound = await NextIDProof.queryIsBound(currentConnectedPersona.publicHexKey, platform, username) if (isBound) return NextIDVerificationStatus.Verified if (isOpenedFromButton) { diff --git a/packages/mask/src/components/InjectedComponents/CommentBox.tsx b/packages/mask/src/components/InjectedComponents/CommentBox.tsx index 9cd9d1e170b6..9bb17fb6473b 100644 --- a/packages/mask/src/components/InjectedComponents/CommentBox.tsx +++ b/packages/mask/src/components/InjectedComponents/CommentBox.tsx @@ -1,8 +1,8 @@ +import { MINDS_ID } from '@masknet/shared' import { makeStyles } from '@masknet/theme' -import { InputBase, Box } from '@mui/material' -import { useI18N } from '../../utils' +import { Box, InputBase } from '@mui/material' import { activatedSocialNetworkUI } from '../../social-network' -import { MINDS_ID } from '../../social-network-adaptor/minds.com/base' +import { useI18N } from '../../utils' interface StyleProps { snsId: string diff --git a/packages/mask/src/components/InjectedComponents/ProfileTab.tsx b/packages/mask/src/components/InjectedComponents/ProfileTab.tsx index 5fd1d7fe5acb..5308f094be12 100644 --- a/packages/mask/src/components/InjectedComponents/ProfileTab.tsx +++ b/packages/mask/src/components/InjectedComponents/ProfileTab.tsx @@ -1,8 +1,9 @@ import { ReactElement, useCallback, useState } from 'react' import classnames from 'classnames' import { Typography } from '@mui/material' -import { MaskMessages, useMatchXS } from '../../utils' -import { useLocationChange } from '../../utils/hooks/useLocationChange' +import { MaskMessages, useMatchXS, useLocationChange } from '../../utils' +import { isTwitter } from '../../social-network-adaptor/twitter.com/base' +import { activatedSocialNetworkUI } from '../../social-network' export interface ProfileTabProps extends withClasses<'tab' | 'button' | 'selected'> { clear(): void @@ -20,6 +21,8 @@ export function ProfileTab(props: ProfileTabProps) { const isMobile = useMatchXS() const onClick = useCallback(() => { + // Change the url hashtag to trigger `locationchange` event from e.g. 'hostname/medias#web3 => hostname/medias' + isTwitter(activatedSocialNetworkUI) && location.assign('#web3') MaskMessages.events.profileTabUpdated.sendToLocal({ show: true }) setActive(true) clear() diff --git a/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx b/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx index ed54e332a37f..feeaeba8c65f 100644 --- a/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx +++ b/packages/mask/src/components/InjectedComponents/ProfileTabContent.tsx @@ -1,19 +1,25 @@ -import { useEffect, useMemo, useState } from 'react' -import { useUpdateEffect } from 'react-use' -import { first } from 'lodash-unified' -import type { NextIDPlatform } from '@masknet/shared-base' -import { Box, CircularProgress } from '@mui/material' +import { + createInjectHooksRenderer, + Plugin, + PluginId, + useActivatedPluginsSNSAdaptor, + useAvailablePlugins, +} from '@masknet/plugin-infra' +import { EMPTY_LIST, NextIDPlatform } from '@masknet/shared-base' import { makeStyles, useStylesExtends } from '@masknet/theme' import { useAddressNames } from '@masknet/web3-shared-evm' -import { createInjectHooksRenderer, useActivatedPluginsSNSAdaptor, Plugin, PluginId } from '@masknet/plugin-infra' -import { PageTab } from '../InjectedComponents/PageTab' +import { Box, CircularProgress } from '@mui/material' +import { first } from 'lodash-unified' +import { useEffect, useMemo, useState } from 'react' +import { useUpdateEffect } from 'react-use' +import { activatedSocialNetworkUI } from '../../social-network' +import { isTwitter } from '../../social-network-adaptor/twitter.com/base' +import { MaskMessages } from '../../utils' import { useLocationChange } from '../../utils/hooks/useLocationChange' -import { MaskMessages, useI18N } from '../../utils' import { useCurrentVisitingIdentity, useLastRecognizedIdentity } from '../DataSource/useActivatedUI' import { useNextIDBoundByPlatform } from '../DataSource/useNextID' import { usePersonaConnectStatus } from '../DataSource/usePersonaConnectStatus' -import { activatedSocialNetworkUI } from '../../social-network' -import { EMPTY_LIST } from '../../../utils-pure' +import { PageTab } from '../InjectedComponents/PageTab' function getTabContent(tabId: string) { return createInjectHooksRenderer(useActivatedPluginsSNSAdaptor.visibility.useAnyMode, (x) => { @@ -38,7 +44,6 @@ const useStyles = makeStyles()((theme) => ({ export interface ProfileTabContentProps extends withClasses<'text' | 'button' | 'root'> {} export function ProfileTabContent(props: ProfileTabContentProps) { - const { t } = useI18N() const classes = useStylesExtends(useStyles(), props) const [hidden, setHidden] = useState(true) @@ -53,11 +58,13 @@ export function ProfileTabContent(props: ProfileTabContentProps) { platform as NextIDPlatform, identity.identifier.userId, ) + const currentAccountNotConnectPersona = currentIdentity.identifier.userId === identity.identifier.userId && personaList.findIndex((persona) => persona?.persona === currentConnectedPersona?.publicHexKey) === -1 - const tabs = useActivatedPluginsSNSAdaptor('any') + const activatedPlugins = useActivatedPluginsSNSAdaptor('any') + const tabs = useAvailablePlugins(activatedPlugins) .flatMap((x) => x.ProfileTabs?.map((y) => ({ ...y, pluginID: x.ID })) ?? []) .filter((z) => z.Utils?.shouldDisplay?.(identity, addressNames) ?? true) .sort((a, z) => { @@ -79,6 +86,7 @@ export function ProfileTabContent(props: ProfileTabContentProps) { return a.priority - z.priority }) + const selectedTabComputed = selectedTab ?? first(tabs) useLocationChange(() => { @@ -102,11 +110,12 @@ export function ProfileTabContent(props: ProfileTabContentProps) { }, [identity]) const ContentComponent = useMemo(() => { - const tab = currentAccountNotConnectPersona - ? tabs?.find((tab) => tab?.pluginID === PluginId.NextID)?.ID - : selectedTabComputed?.ID + const tab = + isTwitter(activatedSocialNetworkUI) && currentAccountNotConnectPersona + ? tabs?.find((tab) => tab?.pluginID === PluginId.NextID)?.ID + : selectedTabComputed?.ID return getTabContent(tab ?? '') - }, [selectedTabComputed, identity.identifier]) + }, [selectedTabComputed?.ID, identity.identifier.userId]) if (hidden) return null diff --git a/packages/mask/src/components/InjectedComponents/SelectPeopleDialog.tsx b/packages/mask/src/components/InjectedComponents/SelectPeopleDialog.tsx index b959a08b554d..613e77cfcf42 100644 --- a/packages/mask/src/components/InjectedComponents/SelectPeopleDialog.tsx +++ b/packages/mask/src/components/InjectedComponents/SelectPeopleDialog.tsx @@ -1,10 +1,10 @@ import { useCallback, useState } from 'react' import { makeStyles, useStylesExtends } from '@masknet/theme' import { Button, CircularProgress, DialogActions, DialogContent } from '@mui/material' +import { InjectedDialog } from '@masknet/shared' import { useI18N } from '../../utils' import { SelectProfileUI } from '../shared/SelectProfileUI' import type { Profile } from '../../database' -import { InjectedDialog } from '../shared/InjectedDialog' export interface SelectProfileDialogProps extends withClasses { open: boolean diff --git a/packages/mask/src/components/InjectedComponents/SetupGuide.tsx b/packages/mask/src/components/InjectedComponents/SetupGuide.tsx index 4122decacfec..d05d309b1703 100644 --- a/packages/mask/src/components/InjectedComponents/SetupGuide.tsx +++ b/packages/mask/src/components/InjectedComponents/SetupGuide.tsx @@ -26,7 +26,7 @@ import { SetupGuideStep } from './SetupGuide/types' import { FindUsername } from './SetupGuide/FindUsername' import { VerifyNextID } from './SetupGuide/VerifyNextID' import { PinExtension } from './SetupGuide/PinExtension' -import { bindProof, createPersonaPayload, queryIsBound } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' // #region setup guide ui interface SetupGuideUIProps { @@ -126,9 +126,9 @@ function SetupGuideUI(props: SetupGuideUIProps) { const platform = ui.configuration.nextIDConfig?.platform as NextIDPlatform | undefined if (!platform) return - const isBound = await queryIsBound(persona_.publicHexKey, platform, username) + const isBound = await NextIDProof.queryIsBound(persona_.publicHexKey, platform, username) if (!isBound) { - const payload = await createPersonaPayload( + const payload = await NextIDProof.createPersonaPayload( persona_.publicHexKey, NextIDAction.Create, username, @@ -151,7 +151,7 @@ function SetupGuideUI(props: SetupGuideUIProps) { const post = collectVerificationPost?.(postContent) if (post && persona_.publicHexKey) { clearInterval(verifyPostCollectTimer.current!) - await bindProof( + await NextIDProof.bindProof( payload.uuid, persona_.publicHexKey, NextIDAction.Create, @@ -174,7 +174,7 @@ function SetupGuideUI(props: SetupGuideUIProps) { }) await waitingPost - const isBound = await queryIsBound(persona_.publicHexKey, platform, username) + const isBound = await NextIDProof.queryIsBound(persona_.publicHexKey, platform, username) if (!isBound) throw new Error('Failed to verify.') MaskMessages.events.ownProofChanged.sendToAll(undefined) } @@ -187,7 +187,7 @@ function SetupGuideUI(props: SetupGuideUIProps) { const onConnected = async () => { if (enableNextID && persona_?.publicHexKey && platform && username) { - const isBound = await queryIsBound(persona_.publicHexKey, platform, username) + const isBound = await NextIDProof.queryIsBound(persona_.publicHexKey, platform, username) if (!isBound) { currentSetupGuideStatus[ui.networkIdentifier].value = stringify({ status: SetupGuideStep.VerifyOnNextID, diff --git a/packages/mask/src/components/shared/ApplicationBoard.tsx b/packages/mask/src/components/shared/ApplicationBoard.tsx index 6df629f5fb51..3762504887d9 100644 --- a/packages/mask/src/components/shared/ApplicationBoard.tsx +++ b/packages/mask/src/components/shared/ApplicationBoard.tsx @@ -1,76 +1,13 @@ -import { useCallback, useState } from 'react' -import classNames from 'classnames' -import { Typography } from '@mui/material' +import { Fragment } from 'react' import { makeStyles } from '@masknet/theme' -import { ChainId, useChainId, useAccount, useWallet } from '@masknet/web3-shared-evm' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { MaskMessages } from '../../utils/messages' -import { useControlledDialog } from '../../utils/hooks/useControlledDialog' -import { RedPacketPluginID } from '../../plugins/RedPacket/constants' -import { ITO_PluginID } from '../../plugins/ITO/constants' -import { base as ITO_Definition } from '../../plugins/ITO/base' -import { PluginTransakMessages } from '../../plugins/Transak/messages' -import { PluginPetMessages } from '../../plugins/Pets/messages' -import { ClaimAllDialog } from '../../plugins/ITO/SNSAdaptor/ClaimAllDialog' -import { EntrySecondLevelDialog } from './EntrySecondLevelDialog' -import { NetworkTab } from './NetworkTab' -import { SavingsDialog } from '../../plugins/Savings/SNSAdaptor/SavingsDialog' -import { TraderDialog } from '../../plugins/Trader/SNSAdaptor/trader/TraderDialog' -import { NetworkPluginID, PluginId, usePluginIDContext } from '@masknet/plugin-infra' -import { FindTrumanDialog } from '../../plugins/FindTruman/SNSAdaptor/FindTrumanDialog' -import { isTwitter } from '../../social-network-adaptor/twitter.com/base' +import { useChainId } from '@masknet/web3-shared-evm' +import { useActivatedPluginsSNSAdaptor, useCurrentWeb3NetworkPluginID, Plugin, useAccount } from '@masknet/plugin-infra' +import { getCurrentSNSNetwork } from '../../social-network-adaptor/utils' import { activatedSocialNetworkUI } from '../../social-network' const useStyles = makeStyles()((theme) => { const smallQuery = `@media (max-width: ${theme.breakpoints.values.sm}px)` return { - abstractTabWrapper: { - position: 'sticky', - top: 0, - width: '100%', - zIndex: 2, - paddingTop: theme.spacing(1), - paddingBottom: theme.spacing(2), - backgroundColor: theme.palette.background.paper, - }, - tab: { - height: 36, - minHeight: 36, - fontWeight: 300, - }, - tabs: { - width: 552, - height: 36, - minHeight: 36, - margin: '0 auto', - borderRadius: 4, - '& .Mui-selected': { - color: theme.palette.primary.contrastText, - backgroundColor: theme.palette.primary.main, - }, - }, - tabPanel: { - marginTop: theme.spacing(3), - }, - indicator: { - display: 'none', - }, - applicationBox: { - display: 'flex', - flexDirection: 'column', - justifyContent: 'center', - alignItems: 'center', - backgroundColor: theme.palette.background.default, - borderRadius: '8px', - cursor: 'pointer', - height: 100, - '@media (hover: hover)': { - '&:hover': { - transform: 'scale(1.05) translateY(-4px)', - boxShadow: theme.palette.mode === 'light' ? '0px 10px 16px rgba(0, 0, 0, 0.1)' : 'none', - }, - }, - }, applicationWrapper: { marginTop: theme.spacing(0.5), display: 'grid', @@ -86,350 +23,56 @@ const useStyles = makeStyles()((theme) => { gridGap: theme.spacing(1), }, }, - applicationImg: { - width: 36, - height: 36, - marginBottom: theme.spacing(1), - }, - disabled: { - pointerEvents: 'none', - opacity: 0.5, - }, - title: { - fontSize: 15, - [smallQuery]: { - fontSize: 13, - }, - }, } }) -const SUPPORTED_CHAIN_ID_LIST = [ - ChainId.Mainnet, - ChainId.BSC, - ChainId.Matic, - ChainId.Arbitrum, - ChainId.xDai, - ChainId.Celo, - ChainId.Fantom, - ChainId.Aurora, - ChainId.Avalanche, -] - -export interface MaskAppEntry { - title: string - img: string - onClick: any - supportedChains?: ChainId[] - hidden: boolean - walletRequired: boolean -} - -interface MaskApplicationBoxProps { - secondEntries?: MaskAppEntry[] - secondEntryChainTabs?: ChainId[] -} - -export function ApplicationBoard({ secondEntries, secondEntryChainTabs }: MaskApplicationBoxProps) { +export function ApplicationBoard() { const { classes } = useStyles() - const currentChainId = useChainId() + const snsAdaptorPlugins = useActivatedPluginsSNSAdaptor('any') + const currentWeb3Network = useCurrentWeb3NetworkPluginID() + const chainId = useChainId() const account = useAccount() - const selectedWallet = useWallet() - const currentPluginId = usePluginIDContext() - const isNotEvm = currentPluginId !== NetworkPluginID.PLUGIN_EVM - - // #region Encrypted message - const openEncryptedMessage = useCallback( - (id?: string) => - MaskMessages.events.requestComposition.sendToLocal({ - reason: 'timeline', - open: true, - options: { - startupPlugin: id, - }, - }), - [], - ) - // #endregion - - // #region Claim All ITO - const { - open: isClaimAllDialogOpen, - onOpen: onClaimAllDialogOpen, - onClose: onClaimAllDialogClose, - } = useControlledDialog() - // #endregion - - // #region Savings - const { - open: isSavingsDialogOpen, - onOpen: onSavingsDialogOpen, - onClose: onSavingsDialogClose, - } = useControlledDialog() - // #endregion - - // #region Swap - const { open: isSwapDialogOpen, onOpen: onSwapDialogOpen, onClose: onSwapDialogClose } = useControlledDialog() - // #endregion - - // #region Fiat on/off ramp - const { setDialog: setBuyDialog } = useRemoteControlledDialog(PluginTransakMessages.buyTokenDialogUpdated) - // #endregion - - // #region pet friends - const { setDialog: setPetDialog } = useRemoteControlledDialog(PluginPetMessages.events.essayDialogUpdated) - // #endregion - - // #region second level entry dialog - const { - open: isSecondLevelEntryDialogOpen, - onOpen: onSecondLevelEntryDialogOpen, - onClose: onSecondLevelEntryDialogClose, - } = useControlledDialog() - - const [secondLevelEntryDialogTitle, setSecondLevelEntryDialogTitle] = useState('') - const [secondLevelEntryChains, setSecondLevelEntryChains] = useState([]) - const [secondLevelEntries, setSecondLevelEntries] = useState([]) - - const [chainId, setChainId] = useState( - secondEntryChainTabs?.includes(currentChainId) ? currentChainId : ChainId.Mainnet, - ) - - const openSecondEntryDir = useCallback( - (title: string, maskAppEntries: MaskAppEntry[], chains: ChainId[] | undefined) => { - setSecondLevelEntryDialogTitle(title) - setSecondLevelEntries(maskAppEntries) - setSecondLevelEntryChains(chains) - onSecondLevelEntryDialogOpen() - }, - [], - ) - // #endregion - - // #region FindTruman - const { - open: isFindTrumanDialogOpen, - onOpen: onFindTrumanDialogOpen, - onClose: onFindTrumanDialogClose, - } = useControlledDialog() - // #endregion - - function createEntry( - title: string, - img: string, - onClick: any, - supportedChains?: ChainId[], - hidden = false, - walletRequired = true, - ) { - return { - title, - img, - onClick, - supportedChains, - hidden, - walletRequired, - } - } - - // Todo: remove this after refactor applicationBoard - const isITOSupportedChain = - ITO_Definition.enableRequirement.web3![currentPluginId]?.supportedChainIds?.includes(currentChainId) - - const firstLevelEntries: MaskAppEntry[] = [ - createEntry( - 'Lucky Drop', - new URL('./assets/lucky_drop.png', import.meta.url).toString(), - () => openEncryptedMessage(RedPacketPluginID), - undefined, - isNotEvm, - ), - createEntry( - 'File Service', - new URL('./assets/files.png', import.meta.url).toString(), - () => openEncryptedMessage(PluginId.FileService), - undefined, - false, - false, - ), - createEntry( - 'ITO', - new URL('./assets/token.png', import.meta.url).toString(), - () => openEncryptedMessage(ITO_PluginID), - undefined, - !isITOSupportedChain, - ), - createEntry( - 'Claim', - new URL('./assets/gift.png', import.meta.url).toString(), - onClaimAllDialogOpen, - undefined, - !isITOSupportedChain, - ), - createEntry( - 'Mask Bridge', - new URL('./assets/bridge.png', import.meta.url).toString(), - () => window.open('https://bridge.mask.io/#/', '_blank', 'noopener noreferrer'), - undefined, - isNotEvm, - false, - ), - createEntry( - 'MaskBox', - new URL('./assets/mask_box.png', import.meta.url).toString(), - () => window.open('https://box.mask.io/#/', '_blank', 'noopener noreferrer'), - undefined, - isNotEvm, - false, - ), - createEntry( - 'Savings', - new URL('./assets/savings.png', import.meta.url).toString(), - onSavingsDialogOpen, - undefined, - isNotEvm, - ), - createEntry( - 'Swap', - new URL('./assets/swap.png', import.meta.url).toString(), - onSwapDialogOpen, - undefined, - isNotEvm || currentChainId === ChainId.Conflux, - ), - createEntry( - 'Fiat On-Ramp', - new URL('./assets/fiat_ramp.png', import.meta.url).toString(), - () => setBuyDialog({ open: true, address: account }), - undefined, - false, - false, - ), - createEntry( - 'NFTs', - new URL('./assets/nft.png', import.meta.url).toString(), - () => - openSecondEntryDir( - 'NFTs', - [ - createEntry( - 'MaskBox', - new URL('./assets/mask_box.png', import.meta.url).toString(), - () => window.open('https://box.mask.io/#/', '_blank', 'noopener noreferrer'), - undefined, - false, - false, - ), - createEntry( - 'Valuables', - new URL('./assets/valuables.png', import.meta.url).toString(), - () => {}, - undefined, - true, - ), - createEntry( - 'Non-F Friends', - new URL('./assets/mintTeam.png', import.meta.url).toString(), - () => setPetDialog({ open: true }), - [ChainId.Mainnet], - currentChainId !== ChainId.Mainnet || !isTwitter(activatedSocialNetworkUI), - true, - ), - ], - undefined, - ), - undefined, - isNotEvm, - ), - createEntry( - 'Investment', - new URL('./assets/investment.png', import.meta.url).toString(), - () => - openSecondEntryDir( - 'Investment', - [ - createEntry('Zerion', new URL('./assets/zerion.png', import.meta.url).toString(), () => {}, [ - ChainId.Mainnet, - ]), - createEntry('dHEDGE', new URL('./assets/dHEDGE.png', import.meta.url).toString(), () => {}), - ], - SUPPORTED_CHAIN_ID_LIST, - ), - undefined, - true, - ), - createEntry( - 'Alternative', - new URL('./assets/more.png', import.meta.url).toString(), - () => - openSecondEntryDir( - 'Alternative', - [ - createEntry( - 'PoolTogether', - new URL('./assets/pool_together.png', import.meta.url).toString(), - () => {}, - ), - ], - SUPPORTED_CHAIN_ID_LIST, - ), - undefined, - true, - ), - createEntry( - 'FindTruman', - new URL('./assets/findtruman.png', import.meta.url).toString(), - onFindTrumanDialogOpen, - [ChainId.Mainnet], - isNotEvm, - true, - ), - ] + const currentSNSNetwork = getCurrentSNSNetwork(activatedSocialNetworkUI.networkIdentifier) return ( <> - {secondEntryChainTabs?.length ? ( -
- -
- ) : null}
- {(secondEntries ?? firstLevelEntries).map( - ({ title, img, onClick, supportedChains, hidden, walletRequired }, i) => - (!supportedChains || supportedChains?.includes(chainId)) && !hidden ? ( -
- - - {title} - -
- ) : null, - )} + {snsAdaptorPlugins + .reduce<{ entry: Plugin.SNSAdaptor.ApplicationEntry; enabled: boolean; pluginId: string }[]>( + (acc, cur) => { + if (!cur.ApplicationEntries) return acc + const currentWeb3NetworkSupportedChainIds = cur.enableRequirement.web3?.[currentWeb3Network] + const isWeb3Enabled = Boolean( + currentWeb3NetworkSupportedChainIds === undefined || + currentWeb3NetworkSupportedChainIds.supportedChainIds?.includes(chainId), + ) + const isWalletConnectedRequired = currentWeb3NetworkSupportedChainIds !== undefined + const currentSNSIsSupportedNetwork = + cur.enableRequirement.networks.networks[currentSNSNetwork] + const isSNSEnabled = + currentSNSIsSupportedNetwork === undefined || currentSNSIsSupportedNetwork + + return acc.concat( + cur.ApplicationEntries.map((x) => { + return { + entry: x, + enabled: isSNSEnabled && (account ? isWeb3Enabled : !isWalletConnectedRequired), + pluginId: cur.ID, + } + }) ?? [], + ) + }, + [], + ) + .sort((a, b) => a.entry.defaultSortingPriority - b.entry.defaultSortingPriority) + .map((X, i) => { + return ( + + + + ) + })}
- {isClaimAllDialogOpen ? : null} - {isSecondLevelEntryDialogOpen ? ( - - ) : null} - {isFindTrumanDialogOpen ? : null} - {isSwapDialogOpen ? : null} - - {isSavingsDialogOpen ? : null} ) } diff --git a/packages/mask/src/components/shared/DebugMetadataInspector.tsx b/packages/mask/src/components/shared/DebugMetadataInspector.tsx index c9fe24d2e9f1..ece4d17ed9b0 100644 --- a/packages/mask/src/components/shared/DebugMetadataInspector.tsx +++ b/packages/mask/src/components/shared/DebugMetadataInspector.tsx @@ -13,7 +13,7 @@ import { Autocomplete, } from '@mui/material' import ExpandMoreIcon from '@mui/icons-material/ExpandMore' -import { InjectedDialog } from './InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { isDataMatchJSONSchema, getKnownMetadataKeys, getMetadataSchema } from '@masknet/typed-message' import { ShadowRootPopper } from '@masknet/theme' import { useState } from 'react' diff --git a/packages/mask/src/components/shared/EntrySecondLevelDialog.tsx b/packages/mask/src/components/shared/EntrySecondLevelDialog.tsx deleted file mode 100644 index 3eaa28361d04..000000000000 --- a/packages/mask/src/components/shared/EntrySecondLevelDialog.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { DialogContent } from '@mui/material' -import { makeStyles } from '@masknet/theme' -import { InjectedDialog } from './InjectedDialog' -import { WalletStatusBox } from './WalletStatusBox' -import { ApplicationBoard, MaskAppEntry } from './ApplicationBoard' -import type { ChainId } from '@masknet/web3-shared-evm' - -const useStyles = makeStyles()((theme) => ({ - content: { - padding: theme.spacing(2, 3, 3), - minHeight: 491, - }, - footer: { - fontSize: 12, - textAlign: 'left', - padding: theme.spacing(2), - borderTop: `1px solid ${theme.palette.divider}`, - justifyContent: 'flex-start', - }, - address: { - fontSize: 16, - marginRight: theme.spacing(1), - display: 'inline-block', - }, - subTitle: { - fontSize: 18, - lineHeight: '24px', - fontWeight: 600, - marginBottom: 11.5, - color: theme.palette.text.primary, - }, -})) - -interface EntrySecondLevelDialogProps { - title: string - entries?: MaskAppEntry[] - chains?: ChainId[] - open: boolean - closeDialog: () => void -} - -export function EntrySecondLevelDialog(props: EntrySecondLevelDialogProps) { - const { classes } = useStyles() - - return ( - - - - - - - ) -} diff --git a/packages/mask/src/components/shared/SelectRecipients/SelectRecipientsDialog.tsx b/packages/mask/src/components/shared/SelectRecipients/SelectRecipientsDialog.tsx index ed1824d6e973..57d6b484b1b6 100644 --- a/packages/mask/src/components/shared/SelectRecipients/SelectRecipientsDialog.tsx +++ b/packages/mask/src/components/shared/SelectRecipients/SelectRecipientsDialog.tsx @@ -1,11 +1,11 @@ -import { useState, useMemo } from 'react' -import Fuse from 'fuse.js' -import { List, ListItem, ListItemText, Button, InputBase, DialogContent, DialogActions } from '@mui/material' import { makeStyles, useStylesExtends } from '@masknet/theme' +import { InjectedDialog } from '@masknet/shared' +import { Button, DialogActions, DialogContent, InputBase, List, ListItem, ListItemText } from '@mui/material' +import Fuse from 'fuse.js' +import { useMemo, useState } from 'react' +import type { Profile } from '../../../database' import { useI18N } from '../../../utils' import { ProfileInList } from './ProfileInList' -import type { Profile } from '../../../database' -import { InjectedDialog } from '../InjectedDialog' const useStyles = makeStyles()((theme) => ({ content: { diff --git a/packages/mask/src/components/shared/assets/bridge.png b/packages/mask/src/components/shared/assets/bridge.png deleted file mode 100644 index e2a12cd3cc72..000000000000 Binary files a/packages/mask/src/components/shared/assets/bridge.png and /dev/null differ diff --git a/packages/mask/src/extension/background-script/IdentityService.ts b/packages/mask/src/extension/background-script/IdentityService.ts index 9668da327efb..ba1095534d49 100644 --- a/packages/mask/src/extension/background-script/IdentityService.ts +++ b/packages/mask/src/extension/background-script/IdentityService.ts @@ -58,7 +58,7 @@ import { getCurrentPersonaIdentifier } from './SettingsService' import { MaskMessages } from '../../utils' import { first, orderBy } from 'lodash-unified' import { recover_ECDH_256k1_KeyPair_ByMnemonicWord } from '../../utils/mnemonic-code' -import { bindProof } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' assertEnvironment(Environment.ManifestBackground) @@ -406,7 +406,7 @@ export async function detachProfileWithNextID( proofLocation?: string }, ) { - await bindProof(uuid, personaPublicKey, NextIDAction.Delete, platform, identity, createdAt, { + await NextIDProof.bindProof(uuid, personaPublicKey, NextIDAction.Delete, platform, identity, createdAt, { signature: options?.signature, }) MaskMessages.events.ownProofChanged.sendToAll(undefined) diff --git a/packages/mask/src/extension/debug-page/DebugInfo.tsx b/packages/mask/src/extension/debug-page/DebugInfo.tsx index abdc3a92b1e6..181889ba9ee1 100644 --- a/packages/mask/src/extension/debug-page/DebugInfo.tsx +++ b/packages/mask/src/extension/debug-page/DebugInfo.tsx @@ -1,6 +1,7 @@ import { map } from 'lodash-unified' import { makeNewBugIssueURL } from './issue' import { useI18N } from '../../utils' +import { openWindow } from '@masknet/shared-base-ui' export const DEBUG_INFO = { 'User Agent': navigator.userAgent, 'Mask Version': process.env.VERSION, @@ -16,9 +17,7 @@ export const DEBUG_INFO = { export const DebugInfo = () => { const { t } = useI18N() - const onNewBugIssue = () => { - open(makeNewBugIssueURL()) - } + const onNewBugIssue = () => openWindow(makeNewBugIssueURL()) return ( <> diff --git a/packages/mask/src/extension/popups/pages/Personas/Home/index.tsx b/packages/mask/src/extension/popups/pages/Personas/Home/index.tsx index 2783893dbbcd..f9e0ab588bca 100644 --- a/packages/mask/src/extension/popups/pages/Personas/Home/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/Home/index.tsx @@ -4,8 +4,7 @@ import { PersonaContext } from '../hooks/usePersonaContext' import { useNavigate } from 'react-router-dom' import { DeleteIcon, MasksIcon, EditIcon } from '@masknet/icons' import { Button, Typography } from '@mui/material' -import { formatFingerprint, MAX_PERSONA_LIMIT } from '@masknet/shared' -import { PopupRoutes } from '@masknet/shared-base' +import { PopupRoutes, formatPersonaFingerprint, MAX_PERSONA_LIMIT } from '@masknet/shared-base' import { ChevronDown, ChevronUp } from 'react-feather' import { ProfileList } from '../components/ProfileList' import { EnterDashboard } from '../../../components/EnterDashboard' @@ -114,7 +113,7 @@ const PersonaHome = memo(() => { /> - {formatFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)} + {formatPersonaFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)} { diff --git a/packages/mask/src/extension/popups/pages/Personas/Logout/index.tsx b/packages/mask/src/extension/popups/pages/Personas/Logout/index.tsx index a36d617fe1bb..8b32d2fd38fa 100644 --- a/packages/mask/src/extension/popups/pages/Personas/Logout/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/Logout/index.tsx @@ -8,9 +8,7 @@ import { PersonaContext } from '../hooks/usePersonaContext' import Services from '../../../../service' import { LoadingButton } from '@mui/lab' import { useNavigate } from 'react-router-dom' -import { PopupRoutes } from '@masknet/shared-base' -import type { PersonaInformation } from '@masknet/shared-base' -import { formatFingerprint } from '@masknet/shared' +import { PopupRoutes, formatPersonaFingerprint, type PersonaInformation } from '@masknet/shared-base' import { PasswordField } from '../../../components/PasswordField' const useStyles = makeStyles()((theme) => ({ @@ -147,7 +145,7 @@ export const LogoutUI = memo(({ backupPassword, loading, onLogout
{deletingPersona?.nickname} - {formatFingerprint(deletingPersona?.identifier.compressedPoint ?? '', 10)} + {formatPersonaFingerprint(deletingPersona?.identifier.compressedPoint ?? '', 10)}
diff --git a/packages/mask/src/extension/popups/pages/Personas/components/DisconnectDialog/index.tsx b/packages/mask/src/extension/popups/pages/Personas/components/DisconnectDialog/index.tsx index 7401e743c323..381b349871f2 100644 --- a/packages/mask/src/extension/popups/pages/Personas/components/DisconnectDialog/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/components/DisconnectDialog/index.tsx @@ -2,8 +2,7 @@ import { memo } from 'react' import { Button, Dialog, DialogActions, DialogContent, Typography, DialogProps } from '@mui/material' import { makeStyles } from '@masknet/theme' import classNames from 'classnames' -import type { ProfileIdentifier } from '@masknet/shared-base' -import { formatFingerprint } from '@masknet/shared' +import { formatPersonaFingerprint, type ProfileIdentifier } from '@masknet/shared-base' import { PersonaContext } from '../../hooks/usePersonaContext' import { LoadingButton } from '@mui/lab' import { useI18N } from '../../../../../../utils' @@ -71,7 +70,8 @@ export const DisconnectDialog = memo( {t('popups_persona_disconnect_confirmation_description')} - {t('popups_persona')}: {formatFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)} + {t('popups_persona')}:{' '} + {formatPersonaFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)}
{t('popups_twitter_id')}: @{unbundledIdentity.userId}
diff --git a/packages/mask/src/extension/popups/pages/Personas/components/PersonaList/index.tsx b/packages/mask/src/extension/popups/pages/Personas/components/PersonaList/index.tsx index 3d46091d664d..332f162fee70 100644 --- a/packages/mask/src/extension/popups/pages/Personas/components/PersonaList/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/components/PersonaList/index.tsx @@ -1,11 +1,14 @@ import { memo, useCallback } from 'react' import { makeStyles } from '@masknet/theme' import { PersonaContext } from '../../hooks/usePersonaContext' -import type { PersonaInformation, ECKeyIdentifier } from '@masknet/shared-base' +import { + formatPersonaFingerprint, + PopupRoutes, + type PersonaInformation, + type ECKeyIdentifier, +} from '@masknet/shared-base' import { ListItemButton, List, Typography } from '@mui/material' import { DeleteIcon, MasksIcon } from '@masknet/icons' -import { formatFingerprint } from '@masknet/shared' -import { PopupRoutes } from '@masknet/shared-base' import { useNavigate } from 'react-router-dom' import Services from '../../../../../service' @@ -95,7 +98,7 @@ export const PersonaListUI = memo(({ personas, onLogout, onC
{nickname} - {formatFingerprint(identifier.compressedPoint ?? '', 10)} + {formatPersonaFingerprint(identifier.compressedPoint ?? '', 10)} { diff --git a/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx b/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx index e06e71a181e2..e946b430fdfe 100644 --- a/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx +++ b/packages/mask/src/extension/popups/pages/Personas/components/ProfileList/index.tsx @@ -18,7 +18,7 @@ import { useAsyncFn, useAsyncRetry } from 'react-use' import Services from '../../../../../service' import { GrayMasks } from '@masknet/icons' import { DisconnectDialog } from '../DisconnectDialog' -import { createPersonaPayload, queryExistedBindingByPersona } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' import classNames from 'classnames' import { useNavigate } from 'react-router-dom' import urlcat from 'urlcat' @@ -151,7 +151,7 @@ export const ProfileList = memo(() => { const { value: mergedProfiles, retry: refreshProfileList } = useAsyncRetry(async () => { if (!currentPersona) return [] if (!currentPersona.publicHexKey) return currentPersona.linkedProfiles - const response = await queryExistedBindingByPersona(currentPersona.publicHexKey) + const response = await NextIDProof.queryExistedBindingByPersona(currentPersona.publicHexKey) if (!response) return currentPersona.linkedProfiles return currentPersona.linkedProfiles.map((profile) => { @@ -177,7 +177,7 @@ export const ProfileList = memo(() => { const publicHexKey = currentPersona.publicHexKey if (!publicHexKey || !unbind || !unbind.identity || !unbind.platform) return - const result = await createPersonaPayload( + const result = await NextIDProof.createPersonaPayload( publicHexKey, NextIDAction.Delete, unbind.identity, diff --git a/packages/mask/src/extension/popups/pages/Swap/index.tsx b/packages/mask/src/extension/popups/pages/Swap/index.tsx index 34727e3f5296..4adb25540b66 100644 --- a/packages/mask/src/extension/popups/pages/Swap/index.tsx +++ b/packages/mask/src/extension/popups/pages/Swap/index.tsx @@ -1,6 +1,7 @@ import { Appearance, applyMaskColorVars, makeStyles } from '@masknet/theme' import { TransactionStatusType, useChainId, useWallet, Web3Provider } from '@masknet/web3-shared-evm' import { ThemeProvider, Typography } from '@mui/material' +import { SharedContextProvider } from '@masknet/shared' import { useCallback } from 'react' import { useRecentTransactions } from '../../../../plugins/Wallet/hooks/useRecentTransactions' import Services from '../../../service' @@ -86,30 +87,32 @@ export default function SwapPage() { return ( -
-
-
- 0} - openConnectWalletDialog={openPopupsWindow} - walletName={wallet?.name} - domain={domain} - walletAddress={wallet?.address} - /> -
-
- - {t('plugin_trader_swap')} - - - - - - -
+ +
+
+
+ 0} + openConnectWalletDialog={openPopupsWindow} + walletName={wallet?.name} + domain={domain} + walletAddress={wallet?.address} + /> +
+
+ + {t('plugin_trader_swap')} + + + + + + +
+
-
+ ) diff --git a/packages/mask/src/extension/popups/pages/Wallet/TokenDetail/index.tsx b/packages/mask/src/extension/popups/pages/Wallet/TokenDetail/index.tsx index 42840cfa30f6..ef3bd91b5fa8 100644 --- a/packages/mask/src/extension/popups/pages/Wallet/TokenDetail/index.tsx +++ b/packages/mask/src/extension/popups/pages/Wallet/TokenDetail/index.tsx @@ -23,6 +23,7 @@ import Services from '../../../../service' import { compact, intersectionWith } from 'lodash-unified' import urlcat from 'urlcat' import { ActivityList } from '../components/ActivityList' +import { openWindow } from '@masknet/shared-base-ui' const useStyles = makeStyles()({ content: { @@ -93,30 +94,25 @@ const TokenDetail = memo(() => { open: 'Transak', code: currentToken?.token.symbol ?? currentToken?.token.name, }) - window.open(browser.runtime.getURL(url), 'BUY_DIALOG', 'noopener noreferrer') + openWindow(browser.runtime.getURL(url), 'BUY_DIALOG') } }, [wallet?.address, isActiveSocialNetwork, currentToken]) const openSwapDialog = useCallback(async () => { - window.open( - browser.runtime.getURL( - urlcat( - 'popups.html#/', - PopupRoutes.Swap, - !isSameAddress(nativeToken?.address, currentToken?.token.address) - ? { - id: currentToken?.token.address, - name: currentToken?.token.name, - symbol: currentToken?.token.symbol, - contract_address: currentToken?.token.address, - decimals: currentToken?.token.decimals, - } - : {}, - ), - ), - 'SWAP_DIALOG', - 'noopener noreferrer', + const url = urlcat( + 'popups.html#/', + PopupRoutes.Swap, + !isSameAddress(nativeToken?.address, currentToken?.token.address) + ? { + id: currentToken?.token.address, + name: currentToken?.token.name, + symbol: currentToken?.token.symbol, + contract_address: currentToken?.token.address, + decimals: currentToken?.token.decimals, + } + : {}, ) + openWindow(browser.runtime.getURL(url), 'SWAP_DIALOG') }, [currentToken, nativeToken]) if (!currentToken) return null diff --git a/packages/mask/src/manifest.json b/packages/mask/src/manifest.json index f436056eeecf..e1ca0e475f5f 100644 --- a/packages/mask/src/manifest.json +++ b/packages/mask/src/manifest.json @@ -1,6 +1,6 @@ { "name": "Mask Network", - "version": "2.6.0", + "version": "2.6.1", "manifest_version": 2, "permissions": ["storage", "downloads", "webNavigation", "activeTab"], "optional_permissions": ["", "notifications", "clipboardRead"], diff --git a/packages/mask/src/plugin-infra/register.js b/packages/mask/src/plugin-infra/register.js index 46f4973e183a..38c8b49ce83e 100644 --- a/packages/mask/src/plugin-infra/register.js +++ b/packages/mask/src/plugin-infra/register.js @@ -10,6 +10,8 @@ import '@masknet/plugin-rss3' import '@masknet/plugin-dao' import '@masknet/plugin-solana' import '@masknet/plugin-cyberconnect' +import '@masknet/plugin-go-plus-security' +import '@masknet/plugin-cross-chain-bridge' import '../plugins/Wallet' import '../plugins/EVM' import '../plugins/RedPacket' diff --git a/packages/mask/src/plugins/ArtBlocks/SNSAdaptor/PurchaseDialog.tsx b/packages/mask/src/plugins/ArtBlocks/SNSAdaptor/PurchaseDialog.tsx index d53bbbbd27ac..b01ad22f3610 100644 --- a/packages/mask/src/plugins/ArtBlocks/SNSAdaptor/PurchaseDialog.tsx +++ b/packages/mask/src/plugins/ArtBlocks/SNSAdaptor/PurchaseDialog.tsx @@ -12,7 +12,6 @@ import { Typography, } from '@mui/material' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' import { ERC20TokenDetailed, @@ -23,11 +22,11 @@ import { TransactionStateType, } from '@masknet/web3-shared-evm' import { Trans } from 'react-i18next' -import { usePurchaseCallback } from '../hooks/usePurchaseCallback' -import { TokenAmountPanel } from '@masknet/shared' -import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary' -import { leftShift } from '@masknet/web3-shared-base' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog, TokenAmountPanel } from '@masknet/shared' +import { leftShift } from '@masknet/web3-shared-base' +import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary' +import { usePurchaseCallback } from '../hooks/usePurchaseCallback' import { WalletMessages } from '../../Wallet/messages' import type { Project } from '../types' diff --git a/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx b/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx index a01ed117f905..97c7c707fe60 100644 --- a/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx +++ b/packages/mask/src/plugins/Avatar/SNSAdaptor/AddNFT.tsx @@ -2,7 +2,7 @@ import { makeStyles } from '@masknet/theme' import { ERC721TokenDetailed, isSameAddress, useAccount } from '@masknet/web3-shared-evm' import { Button, DialogContent, Typography } from '@mui/material' import { useCallback, useState } from 'react' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { InputBox } from '../../../extension/options-page/DashboardComponents/InputBox' import { useI18N } from '../../../utils' import { createNFT } from '../utils' diff --git a/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTBadge.tsx b/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTBadge.tsx index be85dd024b92..42a0d94f1b07 100644 --- a/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTBadge.tsx +++ b/packages/mask/src/plugins/Avatar/SNSAdaptor/NFTBadge.tsx @@ -1,3 +1,4 @@ +import { openWindow } from '@masknet/shared-base-ui' import { makeStyles, useStylesExtends } from '@masknet/theme' import { resolveOpenSeaLink } from '@masknet/web3-shared-evm' import Link from '@mui/material/Link' @@ -42,7 +43,7 @@ export function NFTBadge(props: NFTBadgeProps) { className={classes.root} onClick={(e) => { e.preventDefault() - window.open(resolveOpenSeaLink(avatar.address, avatar.tokenId), '_blank') + openWindow(resolveOpenSeaLink(avatar.address, avatar.tokenId)) }}> { padding: theme.spacing(8, 0), }, markdown: { - 'text-overflow': 'ellipsis', + textOverflow: 'ellipsis', display: '-webkit-box', - '-webkit-box-orient': 'vertical', - '-webkit-line-clamp': '3', + webkitBoxOrient: 'vertical', + webkitLineClamp: '3', }, } }) diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx index fd1fef98b836..cd7c207be1eb 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/MakeOfferDialog.tsx @@ -16,8 +16,8 @@ import BigNumber from 'bignumber.js' import { FungibleTokenDetailed, EthereumTokenType, useAccount, useFungibleTokenWatched } from '@masknet/web3-shared-evm' import formatDateTime from 'date-fns/format' import { useI18N } from '../../../utils' +import { InjectedDialog } from '@masknet/shared' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import { UnreviewedWarning } from './UnreviewedWarning' import ActionButton, { ActionButtonPromise } from '../../../extension/options-page/DashboardComponents/ActionButton' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' diff --git a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx index 29ba310a63ed..524573ebd2ad 100644 --- a/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx +++ b/packages/mask/src/plugins/Collectible/SNSAdaptor/PostListingDialog.tsx @@ -2,7 +2,7 @@ import { useState } from 'react' import { DialogContent, Tab, Tabs } from '@mui/material' import { makeStyles } from '@masknet/theme' import { useI18N } from '../../../utils' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { ListingByPriceCard } from './ListingByPriceCard' import { ListingByHighestBidCard } from './ListingByHighestBidCard' import { useFungibleTokenWatched } from '@masknet/web3-shared-evm' diff --git a/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts b/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts index ae211e1ea6cd..068223e11a47 100644 --- a/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts +++ b/packages/mask/src/plugins/Collectible/hooks/useAssetOrder.ts @@ -10,7 +10,7 @@ import type { AssetOrder, CollectibleToken } from '../types' export function useAssetOrder(provider: NonFungibleAssetProvider, token?: CollectibleToken) { return useAsyncRetry(async () => { - if (!token) return + if (!token?.contractAddress || !token?.tokenId) return switch (provider) { case NonFungibleAssetProvider.OPENSEA: const openSeaResponse = await PluginCollectibleRPC.getAssetFromSDK(token.contractAddress, token.tokenId) @@ -37,5 +37,5 @@ export function useAssetOrder(provider: NonFungibleAssetProvider, token?: Collec default: unreachable(provider) } - }, [provider, token]) + }, [provider, token?.contractAddress, token?.tokenId]) } diff --git a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/CheckoutDialog.tsx b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/CheckoutDialog.tsx index 33cebe8fafbf..bd4e1683115d 100644 --- a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/CheckoutDialog.tsx +++ b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/CheckoutDialog.tsx @@ -4,11 +4,11 @@ import { makeStyles } from '@masknet/theme' import { first } from 'lodash-unified' import BigNumber from 'bignumber.js' import { useChainId, useFungibleTokenWatched, TransactionStateType, formatBalance } from '@masknet/web3-shared-evm' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { useI18N } from '../../../utils' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import { WalletMessages } from '../../Wallet/messages' import type { useAsset } from '../hooks/useAsset' import { resolvePaymentTokensOnCryptoartAI, resolveAssetLinkOnCryptoartAI } from '../pipes' diff --git a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/MakeOfferDialog.tsx b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/MakeOfferDialog.tsx index ebe26afb17a2..eb1a07ac67be 100644 --- a/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/MakeOfferDialog.tsx +++ b/packages/mask/src/plugins/CryptoartAI/SNSAdaptor/MakeOfferDialog.tsx @@ -11,12 +11,12 @@ import { formatBalance, TransactionStateType, } from '@masknet/web3-shared-evm' +import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { useI18N } from '../../../utils' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { SelectTokenAmountPanel } from '../../ITO/SNSAdaptor/SelectTokenAmountPanel' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import { WalletMessages } from '../../Wallet/messages' import type { useAsset } from '../hooks/useAsset' import { resolvePaymentTokensOnCryptoartAI, resolveAssetLinkOnCryptoartAI } from '../pipes' diff --git a/packages/mask/src/plugins/EVM/UI/Web3State/createNonFungibleToken.ts b/packages/mask/src/plugins/EVM/UI/Web3State/createNonFungibleToken.ts new file mode 100644 index 000000000000..218ba80a7734 --- /dev/null +++ b/packages/mask/src/plugins/EVM/UI/Web3State/createNonFungibleToken.ts @@ -0,0 +1,27 @@ +import { TokenType, Web3Plugin } from '@masknet/plugin-infra' +import type { ERC721TokenDetailed } from '@masknet/web3-shared-evm' + +export function createNonFungibleToken(token: ERC721TokenDetailed) { + return { + ...token, + id: `${token.contractDetailed.address}_${token.tokenId}`, + tokenId: token.tokenId, + chainId: token.contractDetailed.chainId, + type: TokenType.NonFungible, + name: token.info.name ?? `${token.contractDetailed.name} ${token.tokenId}`, + description: token.info.description ?? '', + owner: token.info.owner, + contract: { + ...token.contractDetailed, + type: TokenType.NonFungible, + id: token.contractDetailed.address, + }, + metadata: { + name: token.info.name ?? `${token.contractDetailed.name} ${token.tokenId}`, + description: token.info.description ?? '', + mediaType: 'Unknown', + iconURL: token.contractDetailed.iconURL, + assetURL: token.info.mediaUrl, + }, + } as Web3Plugin.NonFungibleToken +} diff --git a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts index 69f40729cae1..d52b42264d73 100644 --- a/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts +++ b/packages/mask/src/plugins/EVM/UI/Web3State/getAssetsFn.ts @@ -1,33 +1,40 @@ -import Web3 from 'web3' import { + getRegisteredWeb3Networks, + NetworkPluginID, + Pageable, + Pagination, + TokenType, + Web3Plugin, +} from '@masknet/plugin-infra' +import BalanceCheckerABI from '@masknet/web3-contracts/abis/BalanceChecker.json' +import ERC721ABI from '@masknet/web3-contracts/abis/ERC721.json' +import type { ERC721 } from '@masknet/web3-contracts/types/ERC721' +import { TokenPrice } from '@masknet/web3-providers' +import { pow10 } from '@masknet/web3-shared-base' +import { + ChainId, createContract, + createExternalProvider, createNativeToken, + CurrencyType, ERC721TokenDetailed, formatEthereumAddress, - getERC721TokenDetailedFromChain, + FungibleAssetProvider, + getCoinGeckoCoinId, getERC721TokenAssetFromChain, + getERC721TokenDetailedFromChain, getEthereumConstants, isSameAddress, - Web3ProviderType, - FungibleAssetProvider, - createExternalProvider, - getCoinGeckoCoinId, - CurrencyType, PriceRecord, - ChainId, + Web3ProviderType, } from '@masknet/web3-shared-evm' import BigNumber from 'bignumber.js' -import { Pageable, Pagination, TokenType, Web3Plugin } from '@masknet/plugin-infra' -import BalanceCheckerABI from '@masknet/web3-contracts/abis/BalanceChecker.json' -import ERC721ABI from '@masknet/web3-contracts/abis/ERC721.json' -import type { AbiItem } from 'web3-utils' import { uniqBy } from 'lodash-unified' -import { PLUGIN_NETWORKS } from '../../constants' +import Web3 from 'web3' +import type { AbiItem } from 'web3-utils' import { makeSortAssertWithoutChainFn } from '../../utils/token' import { createGetLatestBalance } from './createGetLatestBalance' -import type { ERC721 } from '@masknet/web3-contracts/types/ERC721' -import { pow10 } from '@masknet/web3-shared-base' -import { TokenPrice } from '@masknet/web3-providers' +import { createNonFungibleToken } from './createNonFungibleToken' // tokens unavailable neither from api or balance checker. // https://forum.conflux.fun/t/how-to-upvote-debank-proposal-for-conflux-espace-integration/13935 @@ -38,7 +45,10 @@ export const getFungibleAssetsFn = async (address: string, providerType: string, network: Web3Plugin.NetworkDescriptor, pagination?: Pagination) => { const chainId = context.chainId.getCurrentValue() const wallet = context.wallets.getCurrentValue().find((x) => isSameAddress(x.address, address)) - const networks = PLUGIN_NETWORKS + const networks = getRegisteredWeb3Networks().filter( + (x) => NetworkPluginID.PLUGIN_EVM === x.networkSupporterPluginID && x.isMainnet, + ) + const supportedNetworkIds = networks.map((x) => x.chainId) const trustedTokens = uniqBy( context.erc20Tokens .getCurrentValue() @@ -51,22 +61,24 @@ export const getFungibleAssetsFn = ) const { BALANCE_CHECKER_ADDRESS } = getEthereumConstants(chainId) const dataFromProvider = await context.getAssetsList(address, FungibleAssetProvider.DEBANK) - const assetsFromProvider: Web3Plugin.Asset[] = dataFromProvider.map((x) => ({ - id: x.token.address, - chainId: x.token.chainId, - balance: x.balance, - price: x.price, - value: x.value, - logoURI: x.logoURI, - token: { - ...x.token, - type: TokenType.Fungible, - name: x.token.name ?? 'Unknown Token', - symbol: x.token.symbol ?? 'Unknown', + const assetsFromProvider = dataFromProvider + .map>((x) => ({ id: x.token.address, chainId: x.token.chainId, - }, - })) + balance: x.balance, + price: x.price, + value: x.value, + logoURI: x.logoURI, + token: { + ...x.token, + name: x.token.name ?? 'Unknown Token', + symbol: x.token.symbol ?? 'Unknown', + id: x.token.address, + chainId: x.token.chainId, + type: TokenType.Fungible, + }, + })) + .filter((x) => supportedNetworkIds.includes(x.chainId)) const balanceCheckerContract = createContract( web3, @@ -215,31 +227,7 @@ export const getNonFungibleTokenFn = const tokenFromProvider = socket.getResult(socketId) const allData: Web3Plugin.NonFungibleToken[] = [...tokenInDb, ...tokenFromProvider] - .map( - (x) => - ({ - ...x, - id: `${x.contractDetailed.address}_${x.tokenId}`, - tokenId: x.tokenId, - chainId: x.contractDetailed.chainId, - type: TokenType.NonFungible, - name: x.info.name ?? `${x.contractDetailed.name} ${x.tokenId}`, - description: x.info.description ?? '', - owner: x.info.owner, - contract: { - ...x.contractDetailed, - type: TokenType.NonFungible, - id: x.contractDetailed.address, - }, - metadata: { - name: x.info.name ?? `${x.contractDetailed.name} ${x.tokenId}`, - description: x.info.description ?? '', - mediaType: 'Unknown', - iconURL: x.contractDetailed.iconURL, - assetURL: x.info.mediaUrl, - }, - } as Web3Plugin.NonFungibleToken), - ) + .map(createNonFungibleToken) .filter((x) => isSameAddress(x.owner, address)) .filter((x) => !network || x.chainId === network.chainId) diff --git a/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx b/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx index 6c9618e7c638..f0564cb61c90 100644 --- a/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx +++ b/packages/mask/src/plugins/EVM/UI/components/ProviderIconClickBait.tsx @@ -1,7 +1,7 @@ import { useCallback, cloneElement, isValidElement } from 'react' import { unreachable } from '@dimensiondev/kit' import type { Web3Plugin } from '@masknet/plugin-infra' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { openWindow, useRemoteControlledDialog } from '@masknet/shared-base-ui' import { isDashboardPage } from '@masknet/shared-base' import { getChainIdFromNetworkType, @@ -47,7 +47,7 @@ export function ProviderIconClickBait({ if (!isProviderAvailable) { const downloadLink = resolveProviderDownloadLink(providerType) - if (downloadLink) window.open(downloadLink, '_blank', 'noopener noreferrer') + openWindow(downloadLink) return } } diff --git a/packages/mask/src/plugins/EVM/constants.ts b/packages/mask/src/plugins/EVM/constants.ts index 2c17f3d96cbf..2a508a0600db 100644 --- a/packages/mask/src/plugins/EVM/constants.ts +++ b/packages/mask/src/plugins/EVM/constants.ts @@ -1,8 +1,8 @@ -import { PluginId, Web3Plugin } from '@masknet/plugin-infra' +import { NetworkPluginID, Web3Plugin } from '@masknet/plugin-infra' import { ChainId, NetworkType, ProviderType } from '@masknet/web3-shared-evm' -export const PLUGIN_ID = PluginId.EVM -export const PLUGIN_META_KEY = `${PluginId.EVM}:1` +export const PLUGIN_ID = NetworkPluginID.PLUGIN_EVM +export const PLUGIN_META_KEY = `${PLUGIN_ID}:1` export const PLUGIN_NAME = 'EVM' export const PLUGIN_ICON = '\u039E' export const PLUGIN_DESCRIPTION = '' diff --git a/packages/mask/src/plugins/EVM/messages.ts b/packages/mask/src/plugins/EVM/messages.ts index 8a8ea304aae9..28bf194d5b6d 100644 --- a/packages/mask/src/plugins/EVM/messages.ts +++ b/packages/mask/src/plugins/EVM/messages.ts @@ -25,8 +25,10 @@ export interface EVM_Messages { rpc: unknown } +const evmEventEmitter: PluginMessageEmitter = createPluginMessage(PLUGIN_ID, serializer) + export const EVM_Messages: { events: PluginMessageEmitter } = { - events: createPluginMessage(PLUGIN_ID, serializer), + events: evmEventEmitter, } export const EVM_RPC = createPluginRPC(PLUGIN_ID, () => import('./services'), EVM_Messages.events.rpc) diff --git a/packages/mask/src/plugins/External/components/CompositionEntry.tsx b/packages/mask/src/plugins/External/components/CompositionEntry.tsx index f27b2b708379..d24e9f18910e 100644 --- a/packages/mask/src/plugins/External/components/CompositionEntry.tsx +++ b/packages/mask/src/plugins/External/components/CompositionEntry.tsx @@ -4,7 +4,7 @@ import { DialogContent } from '@mui/material' import { useEffect } from 'react' import { MaskMessages, useI18N } from '../../../utils' import { PluginLoader } from './PluginLoader' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' export function ThirdPartyPluginCompositionEntry(props: Plugin.SNSAdaptor.CompositionDialogEntry_DialogProps) { const { t } = useI18N() diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/FindTrumanDialog.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/FindTrumanDialog.tsx index 990b9695b833..6450ab921425 100644 --- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/FindTrumanDialog.tsx +++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/FindTrumanDialog.tsx @@ -1,6 +1,6 @@ import { Box, DialogContent } from '@mui/material' import { TabContext, TabPanel } from '@mui/lab' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { makeStyles, useStylesExtends, useTabs } from '@masknet/theme' import { WalletStatusBox } from '../../../components/shared/WalletStatusBox' import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab' diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/MainDialog.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/MainDialog.tsx index d9af958dbc43..b2278b82343a 100644 --- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/MainDialog.tsx +++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/MainDialog.tsx @@ -2,7 +2,7 @@ import { DialogContent, Card, Grid, Alert, Box, Typography, Button } from '@mui/ import { makeStyles } from '@masknet/theme' import { useContext, useEffect, useState } from 'react' import { useI18N } from '../../../utils' -import { InjectedDialog, InjectedDialogProps } from '../../../components/shared/InjectedDialog' +import { InjectedDialog, InjectedDialogProps } from '@masknet/shared' import { useAccount } from '@masknet/web3-shared-evm' import { fetchConst, fetchUserParticipatedStoryStatus } from '../Worker/apis' import type { UserStoryStatus, FindTrumanConst } from '../types' diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/ParticipatePanel.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/ParticipatePanel.tsx index b1453bdedc6b..2cabace3e88c 100644 --- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/ParticipatePanel.tsx +++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/ParticipatePanel.tsx @@ -6,7 +6,7 @@ import { Box, Button, Card, DialogActions, DialogContent, Typography } from '@mu import { TabContext, TabPanel } from '@mui/lab' import StageCard from './StageCard' import { useControlledDialog } from '../../../utils' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { useCallback, useContext, useEffect, useMemo, useState } from 'react' import OptionsCard from './OptionsCard' import ResultCard from './ResultCard' diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/PartsPanel.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/PartsPanel.tsx index a6bcee3cb432..00ca5c8ba097 100644 --- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/PartsPanel.tsx +++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/PartsPanel.tsx @@ -20,7 +20,7 @@ import { Typography, } from '@mui/material' import formatDateTime from 'date-fns/format' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { useContext, useMemo, useState } from 'react' import { LoadingButton } from '@mui/lab' import getUnixTime from 'date-fns/getUnixTime' diff --git a/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx b/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx index 93b229a4f97e..5c9c787ce528 100644 --- a/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/FindTruman/SNSAdaptor/index.tsx @@ -1,11 +1,13 @@ import { base } from '../base' -import { useMemo, Suspense } from 'react' +import { useMemo, Suspense, useState } from 'react' import { Skeleton } from '@mui/material' import { makeStyles } from '@masknet/theme' import { type Plugin, usePostInfoDetails, usePluginWrapper } from '@masknet/plugin-infra' import { extractTextFromTypedMessage } from '@masknet/typed-message' import { parseURL } from '@masknet/shared-base' import { PostInspector } from './PostInspector' +import { ApplicationEntry } from '@masknet/shared' +import { FindTrumanDialog } from './FindTrumanDialog' const useStyles = makeStyles()((theme) => { return { @@ -84,6 +86,25 @@ const sns: Plugin.SNSAdaptor.Definition = { if (!link) return null return }, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + const [open, setOpen] = useState(false) + return ( + <> + setOpen(true)} + /> + setOpen(false)} /> + + ) + }, + defaultSortingPriority: 11, + }, + ], } export default sns diff --git a/packages/mask/src/components/shared/assets/findtruman.png b/packages/mask/src/plugins/FindTruman/assets/findtruman.png similarity index 100% rename from packages/mask/src/components/shared/assets/findtruman.png rename to packages/mask/src/plugins/FindTruman/assets/findtruman.png diff --git a/packages/mask/src/plugins/FindTruman/base.ts b/packages/mask/src/plugins/FindTruman/base.ts index 8b9bb909e652..8ded5d9bb91f 100644 --- a/packages/mask/src/plugins/FindTruman/base.ts +++ b/packages/mask/src/plugins/FindTruman/base.ts @@ -1,4 +1,5 @@ -import type { Plugin } from '@masknet/plugin-infra' +import { Plugin, NetworkPluginID } from '@masknet/plugin-infra' +import { ChainId } from '@masknet/web3-shared-evm' import { FIND_TRUMAN_PLUGIN_ID, FIND_TRUMAN_PLUGIN_NAME } from './constants' export const base: Plugin.Shared.Definition = { @@ -13,6 +14,23 @@ export const base: Plugin.Shared.Definition = { architecture: { app: true, web: true }, networks: { type: 'opt-out', networks: {} }, target: 'stable', + web3: { + [NetworkPluginID.PLUGIN_EVM]: { + supportedChainIds: [ + ChainId.Mainnet, + ChainId.BSC, + ChainId.Matic, + ChainId.Arbitrum, + ChainId.xDai, + ChainId.Fantom, + ChainId.Avalanche, + ChainId.Aurora, + ChainId.Conflux, + ], + }, + [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] }, + [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] }, + }, }, contribution: { postContent: new Set([ diff --git a/packages/mask/src/plugins/Gitcoin/SNSAdaptor/DonateDialog.tsx b/packages/mask/src/plugins/Gitcoin/SNSAdaptor/DonateDialog.tsx index edf2dcfd6c97..fa58d7bee67e 100644 --- a/packages/mask/src/plugins/Gitcoin/SNSAdaptor/DonateDialog.tsx +++ b/packages/mask/src/plugins/Gitcoin/SNSAdaptor/DonateDialog.tsx @@ -1,3 +1,7 @@ +import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { usePickToken, InjectedDialog } from '@masknet/shared' +import { makeStyles, useStylesExtends } from '@masknet/theme' +import { rightShift } from '@masknet/web3-shared-base' import { EthereumTokenType, formatBalance, @@ -5,29 +9,24 @@ import { TransactionStateType, useAccount, useChainId, + useFungibleTokenBalance, useGitcoinConstants, useNativeTokenDetailed, - useFungibleTokenBalance, } from '@masknet/web3-shared-evm' import { DialogContent, Link, Typography } from '@mui/material' -import { makeStyles, useStylesExtends } from '@masknet/theme' import { useCallback, useEffect, useMemo, useState } from 'react' import { Trans } from 'react-i18next' -import { v4 as uuid } from 'uuid' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { activatedSocialNetworkUI } from '../../../social-network' -import { isTwitter } from '../../../social-network-adaptor/twitter.com/base' import { isFacebook } from '../../../social-network-adaptor/facebook.com/base' +import { isTwitter } from '../../../social-network-adaptor/twitter.com/base' import { useI18N } from '../../../utils' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel' -import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages' +import { WalletMessages } from '../../Wallet/messages' import { useDonateCallback } from '../hooks/useDonateCallback' import { PluginGitcoinMessages } from '../messages' -import { rightShift } from '@masknet/web3-shared-base' const useStyles = makeStyles()((theme) => ({ paper: { @@ -87,27 +86,14 @@ export function DonateDialog(props: DonateDialogProps) { // #endregion // #region select token dialog - const [id] = useState(uuid()) - const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog( - WalletMessages.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== id) return - setToken(ev.token) - }, - [id], - ), - ) - const onSelectTokenChipClick = useCallback(() => { - setSelectTokenDialog({ - open: true, - uuid: id, + const pickToken = usePickToken() + const onSelectTokenChipClick = useCallback(async () => { + const pickedToken = await pickToken({ disableNativeToken: false, - FungibleTokenListProps: { - selectedTokens: token ? [token.address] : [], - }, + selectedTokens: token?.address ? [token.address] : [], }) - }, [id, token?.address]) + if (pickedToken) setToken(pickedToken) + }, [pickToken, token?.address]) // #endregion // #region amount diff --git a/packages/mask/src/plugins/Gitcoin/base.ts b/packages/mask/src/plugins/Gitcoin/base.ts index 351f388d12bd..e3387df5a855 100644 --- a/packages/mask/src/plugins/Gitcoin/base.ts +++ b/packages/mask/src/plugins/Gitcoin/base.ts @@ -16,6 +16,8 @@ export const base: Plugin.Shared.Definition = { [NetworkPluginID.PLUGIN_EVM]: { supportedChainIds: [ChainId.Mainnet, ChainId.Rinkeby, ChainId.Matic, ChainId.Mumbai], }, + [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] }, + [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] }, }, }, contribution: { postContent: new Set([/https:\/\/gitcoin.co\/grants\/\d+/]) }, diff --git a/packages/mask/src/plugins/GoodGhosting/UI/GameActionDialog.tsx b/packages/mask/src/plugins/GoodGhosting/UI/GameActionDialog.tsx index 51ef10098ffb..5e02a0039cb7 100644 --- a/packages/mask/src/plugins/GoodGhosting/UI/GameActionDialog.tsx +++ b/packages/mask/src/plugins/GoodGhosting/UI/GameActionDialog.tsx @@ -1,6 +1,6 @@ import { Box, Button, DialogContent, DialogActions, Typography } from '@mui/material' import { makeStyles } from '@masknet/theme' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' import { ERC20TokenDetailed, formatBalance, useERC20TokenBalance } from '@masknet/web3-shared-evm' import type { GoodGhostingInfo } from '../types' diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ClaimAllDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ClaimAllDialog.tsx index 1b786419ad6c..d58a66ae9710 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/ClaimAllDialog.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ClaimAllDialog.tsx @@ -1,10 +1,10 @@ -import { usePluginIDContext, PluginId, useActivatedPlugin } from '@masknet/plugin-infra' +import { useCurrentWeb3NetworkPluginID, PluginId, useActivatedPlugin } from '@masknet/plugin-infra' import { useCallback, useEffect, useState, useLayoutEffect, useRef } from 'react' import { flatten, uniq } from 'lodash-unified' import formatDateTime from 'date-fns/format' import { SnackbarProvider, makeStyles } from '@masknet/theme' -import { FormattedBalance } from '@masknet/shared' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { openWindow, useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog, FormattedBalance } from '@masknet/shared' import { DialogContent, CircularProgress, Typography, List, ListItem, useTheme } from '@mui/material' import { formatBalance, @@ -26,7 +26,6 @@ import { useI18N } from '../../../utils' import { Flags } from '../../../../shared' import { useSpaceStationCampaignInfo } from './hooks/useSpaceStationCampaignInfo' import { NftAirdropCard } from './NftAirdropCard' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import { useClaimAll } from './hooks/useClaimAll' import { WalletMessages } from '../../Wallet/messages' import { useClaimCallback } from './hooks/useClaimCallback' @@ -230,7 +229,7 @@ export function ClaimAllDialog(props: ClaimAllDialogProps) { const { t } = useI18N() const { open, onClose } = props const ITO_Definition = useActivatedPlugin(PluginId.ITO, 'any') - const pluginId = usePluginIDContext() + const pluginId = useCurrentWeb3NetworkPluginID() const chainIdList = ITO_Definition?.enableRequirement.web3?.[pluginId]?.supportedChainIds ?? [] const DialogRef = useRef(null) const account = useAccount() @@ -301,7 +300,7 @@ export function ClaimAllDialog(props: ClaimAllDialogProps) { if (claimState.type === TransactionStateType.HASH) { const { hash } = claimState setTimeout(() => { - window.open(resolveTransactionLinkOnExplorer(chainId, hash), '_blank', 'noopener noreferrer') + openWindow(resolveTransactionLinkOnExplorer(chainId, hash)) }, 2000) return } diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/CompositionDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/CompositionDialog.tsx index 0057a4bb343a..2a09f23d9c3a 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/CompositionDialog.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/CompositionDialog.tsx @@ -5,7 +5,7 @@ import { makeStyles } from '@masknet/theme' import { useI18N } from '../../../utils' import { useCurrentIdentity } from '../../../components/DataSource/useActivatedUI' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { InjectedDialog, InjectedDialogProps } from '../../../components/shared/InjectedDialog' +import { InjectedDialog, InjectedDialogProps, MINDS_ID } from '@masknet/shared' import { ITO_MetaKey_2, MSG_DELIMITER } from '../constants' import { DialogTabs, JSON_PayloadInMask } from '../types' import { CreateForm } from './CreateForm' @@ -20,7 +20,6 @@ import { ConfirmDialog } from './ConfirmDialog' import { WalletMessages } from '../../Wallet/messages' import { omit, set } from 'lodash-unified' import { useCompositionContext } from '@masknet/plugin-infra' -import { MINDS_ID } from '../../../social-network-adaptor/minds.com/base' import { activatedSocialNetworkUI } from '../../../social-network' interface StyleProps { diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ExchangeTokenPanel.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ExchangeTokenPanel.tsx index 096a903f9ff5..976730baf092 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/ExchangeTokenPanel.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ExchangeTokenPanel.tsx @@ -1,15 +1,14 @@ -import { EthereumTokenType, FungibleTokenDetailed, useFungibleTokenBalance } from '@masknet/web3-shared-evm' -import { IconButton, Paper } from '@mui/material' import { makeStyles } from '@masknet/theme' +import { EthereumTokenType, FungibleTokenDetailed, useFungibleTokenBalance } from '@masknet/web3-shared-evm' import AddIcon from '@mui/icons-material/AddOutlined' import RemoveIcon from '@mui/icons-material/RemoveOutlined' +import { IconButton, Paper } from '@mui/material' import { useCallback, useEffect, useState } from 'react' -import { v4 as uuid } from 'uuid' +import { usePickToken } from '@masknet/shared' import { useI18N } from '../../../utils' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import type { TokenAmountPanelProps } from '../../../web3/UI/TokenAmountPanel' import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel' -import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages' + const useStyles = makeStyles()((theme) => ({ root: { width: '100%', @@ -80,28 +79,21 @@ export function ExchangeTokenPanel(props: ExchangeTokenPanelProps) { const { t } = useI18N() const { classes } = useStyles() // #region select token dialog - const [id] = useState(uuid()) - const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog( - WalletMessages.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== id) return - onExchangeTokenChange(ev.token, dataIndex) - }, - [id, dataIndex], - ), - ) - const onSelectTokenChipClick = useCallback(() => { - setSelectTokenDialog({ - open: true, - uuid: id, + const pickToken = usePickToken() + const onSelectTokenChipClick = useCallback(async () => { + const picked = await pickToken({ disableNativeToken: isSell, - FungibleTokenListProps: { - blacklist: excludeTokensAddress, - selectedTokens: [exchangeToken?.address ?? '', ...selectedTokensAddress], - }, + blacklist: excludeTokensAddress, + selectedTokens: [exchangeToken?.address || '', ...selectedTokensAddress], }) - }, [id, isSell, exchangeToken, excludeTokensAddress.sort().join(), selectedTokensAddress.sort().join()]) + if (picked) onExchangeTokenChange(picked, dataIndex) + }, [ + isSell, + dataIndex, + exchangeToken?.address, + excludeTokensAddress.sort().join(), + selectedTokensAddress.sort().join(), + ]) // #endregion // #region balance diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx index d35d84c6d41a..7c5dd5875a20 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ITO.tsx @@ -29,7 +29,7 @@ import { usePostLink } from '../../../components/DataSource/usePostInfo' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { TokenIcon } from '@masknet/shared' import { activatedSocialNetworkUI } from '../../../social-network' -import { getAssetAsBlobURL, getTextUILength, useI18N } from '../../../utils' +import { getTextUILength, useI18N } from '../../../utils' import { WalletMessages } from '../../Wallet/messages' import { ITO_EXCHANGE_RATION_MAX, MSG_DELIMITER, TIME_WAIT_BLOCKCHAIN } from '../constants' import { sortTokens } from './helpers' @@ -39,6 +39,7 @@ import { useDestructCallback } from './hooks/useDestructCallback' import { useIfQualified } from './hooks/useIfQualified' import { usePoolTradeInfo } from './hooks/usePoolTradeInfo' import { checkRegionRestrict, decodeRegionCode, useIPRegion } from './hooks/useRegion' +import { usePoolBackground } from './hooks/usePoolBackground' import { ITO_Status, JSON_PayloadInMask } from '../types' import { StyledLinearProgress } from './StyledLinearProgress' import { SwapGuide, SwapStatus } from './SwapGuide' @@ -233,7 +234,7 @@ export function ITO(props: ITO_Props) { const [claimDialogStatus, setClaimDialogStatus] = useState(SwapStatus.Remind) // assets - const PoolBackground = getAssetAsBlobURL(new URL('../assets/pool-background.jpg', import.meta.url)) + const { value: PoolBackground } = usePoolBackground() const { pid, payload } = props const { regions: defaultRegions = '-' } = props.payload @@ -493,7 +494,7 @@ export function ITO(props: ITO_Props) { tradeInfo?.buyInfo?.token.symbol, ]) - const footerStartTime = useMemo(() => { + const FooterStartTime = useMemo(() => { return ( {t('plugin_ito_list_start_date', { date: formatDateTime(startTime, 'yyyy-MM-dd HH:mm') })} @@ -501,7 +502,7 @@ export function ITO(props: ITO_Props) { ) }, [startTime]) - const footerEndTime = useMemo( + const FooterEndTime = useMemo( () => ( {t('plugin_ito_swap_end_date', { date: formatDateTime(endTime, 'yyyy-MM-dd HH:mm') })} @@ -510,13 +511,13 @@ export function ITO(props: ITO_Props) { [endTime, t], ) - const footerSwapInfo = useMemo( + const FooterSwapInfo = useMemo( () => ( <> {swapResultText} - {footerEndTime} + {FooterEndTime} {hasLockTime && !isUnlocked && unlockTime > Date.now() && @@ -529,10 +530,10 @@ export function ITO(props: ITO_Props) { ) : null} ), - [footerEndTime, swapResultText], + [FooterEndTime, swapResultText], ) - const footerNormal = useMemo( + const FooterNormal = useMemo( () => ( <> @@ -543,13 +544,87 @@ export function ITO(props: ITO_Props) { {listOfStatus.includes(ITO_Status.waited) - ? footerStartTime + ? FooterStartTime : listOfStatus.includes(ITO_Status.started) - ? footerEndTime + ? FooterEndTime : null} ), - [footerEndTime, footerStartTime, limit, listOfStatus, token.decimals, token.symbol], + [FooterEndTime, FooterStartTime, limit, listOfStatus, token.decimals, token.symbol], + ) + + const FooterBuyerLockedButton = useMemo(() => { + if (!availability?.claimed) { + return ( + + {claimState.type === TransactionStateType.HASH ? t('plugin_ito_claiming') : t('plugin_ito_claim')} + + ) + } + + if (canWithdraw) { + return ( + + {t('plugin_ito_withdraw')} + + ) + } + return null + }, [availability?.claimed, canWithdraw, claimState]) + + const FooterBuyerWithLockTimeButton = useMemo( + () => ( + + {(() => { + if (isUnlocked) return FooterBuyerLockedButton + + return ( + undefined} + variant="contained" + disabled + size="large" + className={classNames(classes.actionButton, classes.textInOneLine)}> + {t('plugin_ito_claim')} + + ) + })()} + + ), + [noRemain, listOfStatus, isUnlocked], + ) + + const FooterBuyerButton = useMemo( + () => ( +
+ {(() => { + if (hasLockTime) + return ( + + {FooterBuyerWithLockTimeButton} + + ) + if (canWithdraw) { + return ( + + {t('plugin_ito_withdraw')} + + ) + } + return null + })()} +
+ ), + [hasLockTime, canWithdraw], ) return ( @@ -611,10 +686,10 @@ export function ITO(props: ITO_Props) {
{isBuyer - ? footerSwapInfo + ? FooterSwapInfo : listOfStatus.includes(ITO_Status.expired) - ? footerEndTime - : footerNormal} + ? FooterEndTime + : FooterNormal}
From: @{sellerName} @@ -623,163 +698,147 @@ export function ITO(props: ITO_Props) { - {loadingRegion && isRegionRestrict ? null : !isRegionAllow ? ( - undefined} - variant="contained" - size="large" - className={classes.actionButton}> - {t('plugin_ito_region_ban')} - - ) : (noRemain || listOfStatus.includes(ITO_Status.expired)) && - !canWithdraw && - ((availability?.claimed && hasLockTime) || !hasLockTime) ? null : loadingTradeInfo || - loadingAvailability ? ( - undefined} - variant="contained" - size="large" - className={classes.actionButton}> - {t('plugin_ito_loading')} - - ) : !account || !chainIdValid ? ( - - {t('plugin_wallet_connect_a_wallet')} - - ) : isBuyer ? ( - - {hasLockTime ? ( - - {isUnlocked ? ( - !availability?.claimed ? ( - - {claimState.type === TransactionStateType.HASH - ? t('plugin_ito_claiming') - : t('plugin_ito_claim')} - - ) : canWithdraw ? ( - - {t('plugin_ito_withdraw')} - - ) : null - ) : ( - undefined} - variant="contained" - disabled - size="large" - className={classNames(classes.actionButton, classes.textInOneLine)}> - {t('plugin_ito_claim')} - - )} - - ) : canWithdraw ? ( - - - {t('plugin_ito_withdraw')} - - - ) : null} - {noRemain || listOfStatus.includes(ITO_Status.expired) ? null : ( - - - {t('plugin_ito_share')} - - - )} - - ) : canWithdraw ? ( - - {t('plugin_ito_withdraw')} - - ) : (!ifQualified || !(ifQualified as Qual_V2).qualified) && - !isNativeTokenAddress(qualificationAddress) ? ( - - {loadingIfQualified - ? t('plugin_ito_qualification_loading') - : !ifQualified - ? t('plugin_ito_qualification_failed') - : !(ifQualified as Qual_V2).qualified - ? startCase((ifQualified as Qual_V2).errorMsg) - : null} - - ) : listOfStatus.includes(ITO_Status.expired) ? null : listOfStatus.includes(ITO_Status.waited) ? ( - - + {(() => { + if (loadingRegion && isRegionRestrict) return null + + if (!isRegionAllow) { + return ( undefined} variant="contained" size="large" className={classes.actionButton}> - {t('plugin_ito_unlock_in_advance')} + {t('plugin_ito_region_ban')} - - {shareText ? ( - - - {t('plugin_ito_share')} - - - ) : undefined} - - ) : listOfStatus.includes(ITO_Status.started) ? ( - - + ) + } + + if ( + (noRemain || listOfStatus.includes(ITO_Status.expired)) && + !canWithdraw && + ((availability?.claimed && hasLockTime) || !hasLockTime) + ) { + return null + } + + if (loadingTradeInfo || loadingAvailability) { + return ( undefined} variant="contained" size="large" className={classes.actionButton}> - {t('plugin_ito_enter')} + {t('plugin_ito_loading')} - - + ) + } + + if (!account || !chainIdValid) { + return ( - {t('plugin_ito_share')} + {t('plugin_wallet_connect_a_wallet')} - - - ) : null} + ) + } + + if (isBuyer) return FooterBuyerButton + + if (canWithdraw) { + return ( + + {t('plugin_ito_withdraw')} + + ) + } + + if ( + (!ifQualified || !(ifQualified as Qual_V2).qualified) && + !isNativeTokenAddress(qualificationAddress) + ) { + return ( + + {loadingIfQualified + ? t('plugin_ito_qualification_loading') + : !ifQualified + ? t('plugin_ito_qualification_failed') + : !(ifQualified as Qual_V2).qualified + ? startCase((ifQualified as Qual_V2).errorMsg) + : null} + + ) + } + + if (listOfStatus.includes(ITO_Status.expired)) return null + + if (listOfStatus.includes(ITO_Status.waited)) { + return ( + + + + {t('plugin_ito_unlock_in_advance')} + + + {shareText ? ( + + + {t('plugin_ito_share')} + + + ) : undefined} + + ) + } + + if (listOfStatus.includes(ITO_Status.started)) { + return ( + + + + {t('plugin_ito_enter')} + + + + + {t('plugin_ito_share')} + + + + ) + } + + return null + })()} @@ -819,7 +878,7 @@ export function ITO_Loading() { export function ITO_Error({ retryPoolPayload }: { retryPoolPayload: () => void }) { const { t } = useI18N() const { classes } = useStyles({}) - const PoolBackground = getAssetAsBlobURL(new URL('../assets/pool-loading-background.jpg', import.meta.url)) + const { value: PoolBackground } = usePoolBackground() return ( { - if (ev.open || !ev.token || ev.uuid !== id) return - onTokenChange(ev.token) - }, - [id, onTokenChange], - ), - ) - const onSelectTokenChipClick = useCallback(() => { - setSelectTokenDialog({ - open: true, - uuid: id, + const pickToken = usePickToken() + const onSelectTokenChipClick = useCallback(async () => { + const picked = await pickToken({ disableNativeToken, disableSearchBar, - FungibleTokenListProps, + ...FungibleTokenListProps, }) - }, [id, disableNativeToken, disableSearchBar, FungibleTokenListProps]) + if (picked) onTokenChange(picked) + }, [disableNativeToken, disableSearchBar, FungibleTokenListProps]) // #endregion return ( diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx index 432658ea1c99..854ceddae0ef 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/ShareDialog.tsx @@ -6,7 +6,8 @@ import type { BigNumber } from 'bignumber.js' import { useCallback } from 'react' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { activatedSocialNetworkUI } from '../../../social-network' -import { getAssetAsBlobURL, useI18N } from '../../../utils' +import { useI18N } from '../../../utils' +import { usePoolBackground } from './hooks/usePoolBackground' const useStyles = makeStyles()((theme) => ({ shareWrapper: { @@ -62,7 +63,7 @@ export interface ShareDialogProps extends withClasses<'root'> { } export function ShareDialog(props: ShareDialogProps) { - const ShareBackground = getAssetAsBlobURL(new URL('../assets/share-background.jpg', import.meta.url)) + const { value: ShareBackground } = usePoolBackground() const { t } = useI18N() const classes = useStylesExtends(useStyles(), {}) const { token, actualSwapAmount, shareSuccessText, onClose } = props diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx index 0863d75fc0b1..de1be2f70f98 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/SwapDialog.tsx @@ -1,35 +1,35 @@ -import { useCallback, useEffect, useMemo, useState } from 'react' -import BigNumber from 'bignumber.js' -import { v4 as uuid } from 'uuid' -import { CircularProgress, Slider, Typography } from '@mui/material' +import { openWindow, useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { usePickToken } from '@masknet/shared' import { makeStyles, useStylesExtends } from '@masknet/theme' -import { useI18N } from '../../../utils' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' +import { leftShift, rightShift, ZERO } from '@masknet/web3-shared-base' import { ChainId, currySameAddress, EthereumTokenType, formatBalance, FungibleTokenDetailed, + isNativeTokenAddress, + isSameAddress, resolveTransactionLinkOnExplorer, TransactionStateType, useChainId, useFungibleTokenBalance, useFungibleTokenDetailed, - isSameAddress, useTokenConstants, - isNativeTokenAddress, } from '@masknet/web3-shared-evm' -import { leftShift, rightShift, ZERO } from '@masknet/web3-shared-base' -import { SelectTokenDialogEvent, WalletMessages, WalletRPC } from '../../Wallet/messages' -import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel' -import { useSwapCallback } from './hooks/useSwapCallback' -import type { JSON_PayloadInMask } from '../types' -import { SwapStatus } from './SwapGuide' +import { CircularProgress, Slider, Typography } from '@mui/material' +import BigNumber from 'bignumber.js' +import { useCallback, useEffect, useMemo, useState } from 'react' +import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' +import { useI18N } from '../../../utils' import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' +import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel' +import { WalletMessages, WalletRPC } from '../../Wallet/messages' +import type { JSON_PayloadInMask } from '../types' import { useQualificationVerify } from './hooks/useQualificationVerify' +import { useSwapCallback } from './hooks/useSwapCallback' +import { SwapStatus } from './SwapGuide' const useStyles = makeStyles()((theme) => ({ button: { @@ -138,46 +138,25 @@ export function SwapDialog(props: SwapDialogProps) { swapAmount.isZero() ? '' : formatBalance(swapAmount, swapToken?.decimals), ) // #region select token - const [id] = useState(uuid()) - const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog( - WalletMessages.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== id) return - const at = exchangeTokens.findIndex(currySameAddress(ev.token!.address)) - const ratio = new BigNumber(payload.exchange_amounts[at * 2]).dividedBy( - payload.exchange_amounts[at * 2 + 1], - ) - setRatio(ratio) - setSwapToken(ev.token) - setTokenAmount(initAmount) - setSwapAmount(initAmount.multipliedBy(ratio)) - setInputAmountForUI( - initAmount.isZero() ? '' : formatBalance(initAmount.multipliedBy(ratio), ev.token.decimals), - ) - }, - [ - id, - payload, - initAmount, - exchangeTokens - .map((x) => x.address) - .sort() - .join(), - ], - ), - ) - const onSelectTokenChipClick = useCallback(() => { - setSelectTokenDialog({ - open: true, - uuid: id, + const pickToken = usePickToken() + const onSelectTokenChipClick = useCallback(async () => { + const picked = await pickToken({ disableNativeToken: !exchangeTokens.some(isNativeTokenAddress), disableSearchBar: true, - FungibleTokenListProps: { - whitelist: exchangeTokens.map((x) => x.address), - }, + whitelist: exchangeTokens.map((x) => x.address), }) + if (!picked) return + const at = exchangeTokens.findIndex(currySameAddress(picked.address)) + const ratio = new BigNumber(payload.exchange_amounts[at * 2]).dividedBy(payload.exchange_amounts[at * 2 + 1]) + setRatio(ratio) + setSwapToken(picked) + setTokenAmount(initAmount) + setSwapAmount(initAmount.multipliedBy(ratio)) + setInputAmountForUI(initAmount.isZero() ? '' : formatBalance(initAmount.multipliedBy(ratio), picked.decimals)) }, [ + initAmount, + payload, + pickToken, exchangeTokens .map((x) => x.address) .sort() @@ -239,7 +218,7 @@ export function SwapDialog(props: SwapDialogProps) { if (swapState.type === TransactionStateType.HASH) { const { hash } = swapState setTimeout(() => { - window.open(resolveTransactionLinkOnExplorer(chainId, hash), '_blank', 'noopener noreferrer') + openWindow(resolveTransactionLinkOnExplorer(chainId, hash)) }, 2000) return } diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/SwapGuide.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/SwapGuide.tsx index 962a662858a1..429c546fcb71 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/SwapGuide.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/SwapGuide.tsx @@ -4,7 +4,7 @@ import { DialogContent } from '@mui/material' import { makeStyles } from '@masknet/theme' import BigNumber from 'bignumber.js' import { useCallback, useEffect, useMemo, useState, useTransition } from 'react' -import { InjectedDialog, InjectedDialogProps } from '../../../components/shared/InjectedDialog' +import { InjectedDialog, InjectedDialogProps } from '@masknet/shared' import { useI18N } from '../../../utils' import { RemindDialog } from './RemindDialog' import { ShareDialog } from './ShareDialog' diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/UnlockDialog.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/UnlockDialog.tsx index c2ba6a47acf5..39afec2f26fe 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/UnlockDialog.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/UnlockDialog.tsx @@ -1,12 +1,7 @@ -import { Link, Typography } from '@mui/material' import { makeStyles } from '@masknet/theme' +import { isGreaterThan, rightShift } from '@masknet/web3-shared-base' import { useCallback, useState } from 'react' -import { v4 as uuid } from 'uuid' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { useI18N } from '../../../utils' -import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { - useITOConstants, ERC20TokenDetailed, EthereumTokenType, formatBalance, @@ -14,13 +9,16 @@ import { resolveAddressLinkOnExplorer, useChainId, useFungibleTokenBalance, + useITOConstants, } from '@masknet/web3-shared-evm' -import { isGreaterThan, rightShift } from '@masknet/web3-shared-base' +import { Link, Typography } from '@mui/material' +import { Trans } from 'react-i18next' +import { usePickToken } from '@masknet/shared' +import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' +import { useI18N } from '../../../utils' import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel' -import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages' -import { Trans } from 'react-i18next' function isMoreThanMillion(allowance: string, decimals: number) { return isGreaterThan(allowance, `100000000000e${decimals}`) // 100 billion @@ -51,30 +49,16 @@ export function UnlockDialog(props: UnlockDialogProps) { // #region select token const [token, setToken] = useState(tokens[0]) - const [id] = useState(uuid()) - const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog( - WalletMessages.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== id) return - if (ev.token.type !== EthereumTokenType.ERC20) return - setToken(ev.token) - }, - [id], - ), - ) - const onSelectTokenChipClick = useCallback(() => { - setSelectTokenDialog({ - open: true, - uuid: id, + const pickToken = usePickToken() + const onSelectTokenChipClick = useCallback(async () => { + const picked = await pickToken({ disableNativeToken: true, disableSearchBar: true, - FungibleTokenListProps: { - selectedTokens: token ? [token.address] : [], - whitelist: tokens.map((x) => x.address), - }, + selectedTokens: token?.address ? [token.address] : [], + whitelist: tokens.map((x) => x.address), }) - }, [id, token?.address]) + if (picked) setToken(picked as ERC20TokenDetailed) + }, [tokens, token?.address]) // #endregion // #region amount const [rawAmount, setRawAmount] = useState('') diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/hooks/usePoolBackground.ts b/packages/mask/src/plugins/ITO/SNSAdaptor/hooks/usePoolBackground.ts new file mode 100644 index 000000000000..8b40e0b945aa --- /dev/null +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/hooks/usePoolBackground.ts @@ -0,0 +1,6 @@ +import { useAsync } from 'react-use' +import { getAssetAsBlobURL } from '../../../../utils' + +export function usePoolBackground() { + return useAsync(async () => getAssetAsBlobURL(new URL('../../assets/pool-background.jpg', import.meta.url)), []) +} diff --git a/packages/mask/src/plugins/ITO/SNSAdaptor/index.tsx b/packages/mask/src/plugins/ITO/SNSAdaptor/index.tsx index a1e82a69ff96..d88bf44cac20 100644 --- a/packages/mask/src/plugins/ITO/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/ITO/SNSAdaptor/index.tsx @@ -1,4 +1,5 @@ import { Plugin, usePluginWrapper } from '@masknet/plugin-infra' +import { useState } from 'react' import { ItoLabelIcon } from '../assets/ItoLabelIcon' import { makeStyles } from '@masknet/theme' import { @@ -16,6 +17,9 @@ import { CompositionDialog } from './CompositionDialog' import { set } from 'lodash-unified' import { EthereumChainBoundary } from '../../../web3/UI/EthereumChainBoundary' import { MarketsIcon } from '@masknet/icons' +import { ApplicationEntry } from '@masknet/shared' +import { CrossIsolationMessages } from '@masknet/shared-base' +import { ClaimAllDialog } from './ClaimAllDialog' const useStyles = makeStyles()((theme) => ({ root: { @@ -57,6 +61,46 @@ const sns: Plugin.SNSAdaptor.Definition = { ), }, }, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + return ( + + CrossIsolationMessages.events.requestComposition.sendToLocal({ + reason: 'timeline', + open: true, + options: { + startupPlugin: base.ID, + }, + }) + } + /> + ) + }, + defaultSortingPriority: 3, + }, + { + RenderEntryComponent({ disabled }) { + const [open, setOpen] = useState(false) + return ( + <> + setOpen(true)} + /> + setOpen(false)} /> + + ) + }, + defaultSortingPriority: 4, + }, + ], } function onAttached_ITO(payload: JSON_PayloadComposeMask) { diff --git a/packages/mask/src/components/shared/assets/gift.png b/packages/mask/src/plugins/ITO/assets/gift.png similarity index 100% rename from packages/mask/src/components/shared/assets/gift.png rename to packages/mask/src/plugins/ITO/assets/gift.png diff --git a/packages/mask/src/components/shared/assets/token.png b/packages/mask/src/plugins/ITO/assets/token.png similarity index 100% rename from packages/mask/src/components/shared/assets/token.png rename to packages/mask/src/plugins/ITO/assets/token.png diff --git a/packages/mask/src/plugins/ITO/base.ts b/packages/mask/src/plugins/ITO/base.ts index eceee4b13b46..272f230febbc 100644 --- a/packages/mask/src/plugins/ITO/base.ts +++ b/packages/mask/src/plugins/ITO/base.ts @@ -12,7 +12,10 @@ export const base: Plugin.Shared.Definition = { publisher: { name: { fallback: 'Mask Network' }, link: 'https://mask.io/' }, enableRequirement: { architecture: { app: true, web: true }, - networks: { type: 'opt-out', networks: {} }, + networks: { + type: 'opt-out', + networks: {}, + }, target: 'stable', web3: { [NetworkPluginID.PLUGIN_EVM]: { @@ -27,6 +30,8 @@ export const base: Plugin.Shared.Definition = { ChainId.Fantom, ], }, + [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] }, + [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] }, }, }, contribution: { metadataKeys: new Set([ITO_MetaKey_1, ITO_MetaKey_2]) }, diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx index 3175cd46946c..3b54781299a1 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawDialog.tsx @@ -3,7 +3,7 @@ import { useContainer } from 'unstated-next' import { makeStyles } from '@masknet/theme' import { Add, Remove } from '@mui/icons-material' import { useProviderDescriptor } from '@masknet/plugin-infra' -import { FormattedAddress, FormattedBalance, ImageIcon } from '@masknet/shared' +import { FormattedAddress, FormattedBalance, ImageIcon, InjectedDialog } from '@masknet/shared' import { Box, Button, DialogContent, TextField, Typography } from '@mui/material' import { formatBalance, @@ -13,7 +13,6 @@ import { useMaskBoxConstants, EthereumTokenType, } from '@masknet/web3-shared-evm' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import ActionButton from '../../../../extension/options-page/DashboardComponents/ActionButton' import { EthereumERC20TokenApprovedBoundary } from '../../../../web3/UI/EthereumERC20TokenApprovedBoundary' import { EthereumWalletConnectedBoundary } from '../../../../web3/UI/EthereumWalletConnectedBoundary' diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawResultDialog.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawResultDialog.tsx index efc34c74250e..cbcd770a0b99 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawResultDialog.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/components/DrawResultDialog.tsx @@ -5,7 +5,7 @@ import { Box, DialogContent } from '@mui/material' import type { ERC721ContractDetailed } from '@masknet/web3-shared-evm' import type { BoxInfo } from '../../type' import { TokenCard } from './TokenCard' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import ActionButton from '../../../../extension/options-page/DashboardComponents/ActionButton' import { activatedSocialNetworkUI } from '../../../../social-network' import { usePostLink } from '../../../../components/DataSource/usePostInfo' diff --git a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx index 7fa3108da7bb..a4ef2807ea8a 100644 --- a/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/MaskBox/SNSAdaptor/index.tsx @@ -6,6 +6,8 @@ import { parseURL } from '@masknet/shared-base' import { EthereumChainBoundary } from '../../../web3/UI/EthereumChainBoundary' import { PreviewCard } from './components/PreviewCard' import { Context } from '../hooks/useContext' +import { ApplicationEntry } from '@masknet/shared' +import { openWindow } from '@masknet/shared-base-ui' const isMaskBox = (x: string) => x.startsWith('https://box-beta.mask.io') || x.startsWith('https://box.mask.io') @@ -26,6 +28,21 @@ const sns: Plugin.SNSAdaptor.Definition = { if (!link) return null return }, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + return ( + openWindow('https://box.mask.io/#/')} + /> + ) + }, + defaultSortingPriority: 6, + }, + ], } export default sns diff --git a/packages/mask/src/components/shared/assets/mask_box.png b/packages/mask/src/plugins/MaskBox/assets/mask_box.png similarity index 100% rename from packages/mask/src/components/shared/assets/mask_box.png rename to packages/mask/src/plugins/MaskBox/assets/mask_box.png diff --git a/packages/mask/src/plugins/NextID/SNSAdaptor/index.tsx b/packages/mask/src/plugins/NextID/SNSAdaptor/index.tsx index 0df313132cef..f95c8b0dc1fb 100644 --- a/packages/mask/src/plugins/NextID/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/NextID/SNSAdaptor/index.tsx @@ -1,15 +1,18 @@ import type { Plugin } from '@masknet/plugin-infra' +import { EMPTY_LIST } from '@masknet/shared-base' +import { Flags } from '../../../../shared' import { base } from '../base' -import { PLUGIN_ID } from '../constants' import { NextIdPage } from '../components/NextIdPage' -import { RootContext } from '../contexts' import { PostTipButton, TipTaskManager } from '../components/Tip' -import { Flags } from '../../../../shared' -import { EMPTY_LIST } from '@masknet/web3-shared-evm' +import { PLUGIN_ID } from '../constants' +import { RootContext } from '../contexts' +import { setupStorage, storageDefaultValue } from '../storage' const sns: Plugin.SNSAdaptor.Definition = { ...base, - init() {}, + init(signal, context) { + setupStorage(context.createKVStorage('memory', storageDefaultValue)) + }, ProfileTabs: [ { ID: `${PLUGIN_ID}_tabContent`, diff --git a/packages/mask/src/plugins/NextID/base.ts b/packages/mask/src/plugins/NextID/base.ts index 14d2791bcfce..832aeb38641c 100644 --- a/packages/mask/src/plugins/NextID/base.ts +++ b/packages/mask/src/plugins/NextID/base.ts @@ -31,6 +31,8 @@ export const base: Plugin.Shared.Definition = { ChainId.Conflux, ], }, + [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] }, + [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] }, }, }, experimentalMark: true, diff --git a/packages/mask/src/plugins/NextID/components/BindDialog.tsx b/packages/mask/src/plugins/NextID/components/BindDialog.tsx index 525e608bc341..4879f7f3e952 100644 --- a/packages/mask/src/plugins/NextID/components/BindDialog.tsx +++ b/packages/mask/src/plugins/NextID/components/BindDialog.tsx @@ -12,7 +12,8 @@ import { delay } from '@dimensiondev/kit' import { useBindPayload } from '../hooks/useBindPayload' import { usePersonaSign } from '../hooks/usePersonaSign' import { useWalletSign } from '../hooks/useWalletSign' -import { bindProof } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' +import { MaskMessages } from '../../../../shared' interface BindDialogProps { open: boolean @@ -36,7 +37,7 @@ export const BindDialog = memo(({ open, onClose, persona, onBou useAsyncRetry(async () => { if (!personaSignState.value || !walletSignState.value || isBound || !message || !persona.publicHexKey) return try { - await bindProof( + await NextIDProof.bindProof( message.uuid, persona.publicHexKey, NextIDAction.Create, @@ -52,6 +53,9 @@ export const BindDialog = memo(({ open, onClose, persona, onBou variant: 'success', message: t.notify_wallet_sign_request_success(), }) + + MaskMessages.events.ownProofChanged.sendToAll() + await delay(2000) onBound() onClose() diff --git a/packages/mask/src/plugins/NextID/components/BindPanelUI.tsx b/packages/mask/src/plugins/NextID/components/BindPanelUI.tsx index 56e9cbe617c2..886164d5a15e 100644 --- a/packages/mask/src/plugins/NextID/components/BindPanelUI.tsx +++ b/packages/mask/src/plugins/NextID/components/BindPanelUI.tsx @@ -7,9 +7,9 @@ import DoneIcon from '@mui/icons-material/Done' import { useI18N } from '../locales' import { getMaskColor, makeStyles, MaskColorVar } from '@masknet/theme' import type { Persona } from '../../../database' -import { formatFingerprint, LoadingAnimation } from '@masknet/shared' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' -import { NetworkPluginID, usePluginIDContext } from '@masknet/plugin-infra' +import { InjectedDialog, LoadingAnimation } from '@masknet/shared' +import { NetworkPluginID, useCurrentWeb3NetworkPluginID } from '@masknet/plugin-infra' +import { formatPersonaFingerprint } from '@masknet/shared-base' const useStyles = makeStyles()((theme) => ({ persona: { @@ -93,7 +93,7 @@ export const BindPanelUI = memo( ({ onPersonaSign, onWalletSign, currentPersona, signature, isBound, title, onClose, open, isCurrentAccount }) => { const t = useI18N() const { classes } = useStyles() - const pluginId = usePluginIDContext() + const pluginId = useCurrentWeb3NetworkPluginID() const isSupported = SUPPORTED_PLUGINS.includes(pluginId) const isWalletSigned = !!signature.wallet.value @@ -162,7 +162,7 @@ export const BindPanelUI = memo(
{currentPersona?.nickname} - {formatFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)} + {formatPersonaFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)}
diff --git a/packages/mask/src/plugins/NextID/components/ConfirmModal.tsx b/packages/mask/src/plugins/NextID/components/ConfirmModal.tsx index 2e619b520f55..bae9341cd67d 100644 --- a/packages/mask/src/plugins/NextID/components/ConfirmModal.tsx +++ b/packages/mask/src/plugins/NextID/components/ConfirmModal.tsx @@ -1,7 +1,7 @@ import { makeStyles } from '@masknet/theme' import { Button, DialogActions, DialogContent, Typography } from '@mui/material' import type { FC, ReactNode } from 'react' -import { InjectedDialog, InjectedDialogProps } from '../../../components/shared/InjectedDialog' +import { InjectedDialog, InjectedDialogProps } from '@masknet/shared' const useStyles = makeStyles()((theme) => ({ confirmDialog: { diff --git a/packages/mask/src/plugins/NextID/components/NextIdPage.tsx b/packages/mask/src/plugins/NextID/components/NextIdPage.tsx index 12a63395ffb9..24232623a75b 100644 --- a/packages/mask/src/plugins/NextID/components/NextIdPage.tsx +++ b/packages/mask/src/plugins/NextID/components/NextIdPage.tsx @@ -1,6 +1,6 @@ import { NextIDPlatform } from '@masknet/shared-base' import { makeStyles } from '@masknet/theme' -import { queryExistedBindingByPersona, queryIsBound } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' import { Box, Button, Skeleton, Stack, Typography } from '@mui/material' import { useMemo, useState } from 'react' import { useAsync, useAsyncRetry } from 'react-use' @@ -58,12 +58,13 @@ export function NextIdPage({ personaList }: NextIDPageProps) { const currentProfileIdentifier = useLastRecognizedIdentity() const visitingPersonaIdentifier = useCurrentVisitingIdentity() const personaConnectStatus = usePersonaConnectStatus() - const { reset, isVerified, action } = useNextIDConnectStatus() + const { reset, isVerified } = useNextIDConnectStatus() const [openBindDialog, toggleBindDialog] = useState(false) const [unbindAddress, setUnBindAddress] = useState() const platform = activatedSocialNetworkUI.configuration.nextIDConfig?.platform as NextIDPlatform const isOwn = currentProfileIdentifier.identifier.toText() === visitingPersonaIdentifier.identifier.toText() + const tipable = !isOwn const personaActionButton = useMemo(() => { if (!personaConnectStatus.action) return null @@ -83,7 +84,11 @@ export function NextIdPage({ personaList }: NextIDPageProps) { const { value: isAccountVerified, loading: loadingVerifyInfo } = useAsync(async () => { if (!currentPersona?.publicHexKey) return - return queryIsBound(currentPersona.publicHexKey, platform, visitingPersonaIdentifier.identifier.userId) + return NextIDProof.queryIsBound( + currentPersona.publicHexKey, + platform, + visitingPersonaIdentifier.identifier.userId, + ) }, [isOwn, currentPersona, visitingPersonaIdentifier, isVerified]) const { @@ -92,7 +97,7 @@ export function NextIdPage({ personaList }: NextIDPageProps) { retry: retryQueryBinding, } = useAsyncRetry(async () => { if (!currentPersona) return - return queryExistedBindingByPersona(currentPersona.publicHexKey!) + return NextIDProof.queryExistedBindingByPersona(currentPersona.publicHexKey!) }, [currentPersona, isOwn]) const onVerify = async () => { @@ -149,7 +154,7 @@ export function NextIdPage({ personaList }: NextIDPageProps) { {bindings.proofs.map((x) => ( ({ + addButton: { + marginLeft: 'auto', + }, + chain: { + display: 'flex', + }, + row: { + marginTop: theme.spacing(1.5), + }, + chainName: { + marginLeft: theme.spacing(1), + }, + error: { + display: 'flex', + height: 16, + alignItems: 'center', + color: theme.palette.error.main, + '&::before': { + backgroundColor: theme.palette.error.main, + content: '""', + width: 2, + height: 16, + marginRight: theme.spacing(0.5), + }, + }, +})) + +interface Props extends InjectedDialogProps { + onAdd?(token: ERC721TokenDetailed): void +} + +export const AddDialog: FC = ({ onAdd, onClose, ...rest }) => { + const { classes } = useStyles() + const chainId = useChainId() + const account = useAccount() + const [contractAddress, setContractAddress] = useState('') + const [tokenId, setTokenId] = useState('') + const t = useI18N() + const allNetworks = useNetworkDescriptors() + const network = useMemo(() => allNetworks.find((n) => n.chainId === chainId), [allNetworks, chainId]) + const erc721TokenContract = useERC721TokenContract(contractAddress) + + const [, checkOwner] = useAsyncFn(async () => { + if (!erc721TokenContract || !account) return false + const ownerAddress = await erc721TokenContract.methods.ownerOf(tokenId).call() + return isSameAddress(ownerAddress, account) + }, [erc721TokenContract, tokenId, account]) + + const [message, setMessage] = useState('') + const reset = useCallback(() => { + setMessage('') + setContractAddress('') + setTokenId('') + }, []) + + useEffect(() => { + setMessage('') + }, [tokenId, contractAddress]) + + const [state, handleAdd] = useAsyncFn(async () => { + if (!erc721TokenContract || !EthereumAddress.isValid(contractAddress)) { + setMessage(t.tip_add_collectibles_error()) + return + } + + const hasOwnership = await checkOwner() + if (!hasOwnership) { + setMessage(t.tip_add_collectibles_error()) + return + } + const erc721ContractDetailed = await getERC721ContractDetailed(erc721TokenContract, contractAddress, chainId) + const erc721TokenDetailed = await getERC721TokenDetailed( + erc721ContractDetailed, + erc721TokenContract, + tokenId, + chainId, + ) + + if (!erc721TokenDetailed) { + setMessage(t.tip_add_collectibles_error()) + return + } + await WalletRPC.addToken(erc721TokenDetailed) + onAdd?.(erc721TokenDetailed) + reset() + }, [onAdd, t, contractAddress, tokenId]) + + const handleClose = useCallback(() => { + onClose?.() + reset() + }, [onClose]) + + const addButton = useMemo(() => { + return ( + + ) + }, [t, handleAdd, state.loading]) + + if (!network) return null + return ( + + +
+ + {network.name} +
+ + setContractAddress(e.currentTarget.value)} + placeholder={t.tip_add_collectibles_contract_address()} + /> + + + setTokenId(e.currentTarget.value)} + placeholder={t.tip_add_collectibles_token_id()} + /> + + {message ? ( + + {message} + + ) : null} +
+
+ ) +} diff --git a/packages/mask/src/plugins/NextID/components/Tip/NFTSection/NFTList.tsx b/packages/mask/src/plugins/NextID/components/Tip/NFTSection/NFTList.tsx index f19c33b3bcbf..bafa0ba86ed1 100644 --- a/packages/mask/src/plugins/NextID/components/Tip/NFTSection/NFTList.tsx +++ b/packages/mask/src/plugins/NextID/components/Tip/NFTSection/NFTList.tsx @@ -1,16 +1,17 @@ +import { useWeb3State, Web3Plugin } from '@masknet/plugin-infra' import { NFTCardStyledAssetPlayer } from '@masknet/shared' -import { makeStyles } from '@masknet/theme' -import { ERC721TokenDetailed, formatNFT_TokenId, useChainId } from '@masknet/web3-shared-evm' -import { Checkbox, List, ListItem, Radio, Typography } from '@mui/material' +import { makeStyles, ShadowRootTooltip } from '@masknet/theme' +import { formatNFT_TokenId, isSameAddress, useChainId } from '@masknet/web3-shared-evm' +import { Checkbox, Link, List, ListItem, Radio } from '@mui/material' import classnames from 'classnames' import { noop } from 'lodash-unified' import { FC, useCallback } from 'react' +import type { TipNFTKeyPair } from '../../../types' interface Props { - selectedIds: string[] - tokens: ERC721TokenDetailed[] - enableTokenIds?: string[] - onChange?: (ids: string[]) => void + selectedPairs: TipNFTKeyPair[] + tokens: Web3Plugin.NonFungibleToken[] + onChange?: (id: string | null, contractAddress: string) => void limit?: number className: string } @@ -21,111 +22,157 @@ const useStyles = makeStyles()((theme) => ({ right: 0, top: 0, }, + list: { + gridGap: 13, + display: 'grid', + gridTemplateColumns: 'repeat(5, 1fr)', + }, nftItem: { position: 'relative', cursor: 'pointer', - background: theme.palette.mode === 'light' ? '#fff' : '#2F3336', + background: theme.palette.mode === 'light' ? '#EDEFEF' : '#2F3336', display: 'flex', overflow: 'hidden', padding: 0, flexDirection: 'column', - borderRadius: 8, - height: 180, + borderRadius: 12, + height: 100, userSelect: 'none', - width: 120, + width: 100, justifyContent: 'center', }, disabled: { opacity: 0.5, cursor: 'not-allowed', }, + selected: { + position: 'relative', + '&::after': { + position: 'absolute', + border: `2px solid ${theme.palette.primary.main}`, + content: '""', + left: 0, + top: 0, + pointerEvents: 'none', + boxSizing: 'border-box', + width: '100%', + height: '100%', + borderRadius: 12, + }, + }, + unselected: { + opacity: 0.5, + }, loadingFailImage: { width: 64, height: 64, }, + assetPlayerIframe: { + height: 100, + width: 100, + }, + imgWrapper: { + height: '100px !important', + width: '100px !important', + img: { + height: '100%', + width: '100%', + }, + }, + tooltip: { + marginBottom: `${theme.spacing(0.5)} !important`, + }, })) -function arrayRemove(arr: T[], item: T): T[] { - const idx = arr.indexOf(item) - return arr.splice(idx, 1) -} - interface NFTItemProps { - token: ERC721TokenDetailed + token: Web3Plugin.NonFungibleToken } export const NFTItem: FC = ({ token }) => { const { classes } = useStyles() const chainId = useChainId() return ( - <> - - - {formatNFT_TokenId(token.tokenId, 2)} - - + ) } -export const NFTList: FC = ({ selectedIds, tokens, enableTokenIds = [], onChange, limit = 1, className }) => { +const includes = (pairs: TipNFTKeyPair[], pair: TipNFTKeyPair): boolean => { + return !!pairs.find(([address, tokenId]) => isSameAddress(address, pair[0]) && tokenId === pair[1]) +} + +export const NFTList: FC = ({ selectedPairs, tokens, onChange, limit = 1, className }) => { const { classes } = useStyles() const isRadio = limit === 1 - const reachedLimit = selectedIds.length >= limit + const reachedLimit = selectedPairs.length >= limit const toggleItem = useCallback( - (currentId: string) => { - if (!onChange) return - if (isRadio) return onChange([currentId]) - let newIds = [...selectedIds] - if (selectedIds.includes(currentId)) { - arrayRemove(newIds, currentId) - } else if (!reachedLimit) { - newIds = [...selectedIds, currentId] - } - onChange(newIds) + (currentId: string | null, contractAddress: string) => { + onChange?.(currentId, contractAddress) }, - [selectedIds, onChange, isRadio, reachedLimit], + [onChange, isRadio, reachedLimit], ) const SelectComponent = isRadio ? Radio : Checkbox + const { Utils } = useWeb3State() return ( - + {tokens.map((token) => { - const isNotOwned = !enableTokenIds.includes(token.tokenId) - const disabled = (!isRadio && reachedLimit && !selectedIds.includes(token.tokenId)) || isNotOwned + const selected = includes(selectedPairs, [token.contract?.address!, token.tokenId]) + const disabled = !isRadio && reachedLimit && !selected + const link = + Utils?.resolveNonFungibleTokenLink && token.contract + ? Utils.resolveNonFungibleTokenLink( + token.contract?.chainId, + token.contract?.address, + token.tokenId, + ) + : undefined return ( - { - if (disabled) return - toggleItem(token.tokenId) - }}> - - { - if (disabled) return - toggleItem(token.tokenId) - }} - className={classes.checkbox} - checked={selectedIds.includes(token.tokenId)} - /> - + title={`${token.contract?.name} ${formatNFT_TokenId(token.tokenId, 2)}`} + placement="top" + arrow> + 0 && !selected, + })}> + + + + { + if (disabled || !token.contract?.address) return + if (selected) { + toggleItem(null, '') + } else { + toggleItem(token.tokenId, token.contract.address) + } + }} + className={classes.checkbox} + checked={selected} + /> + + ) })} diff --git a/packages/mask/src/plugins/NextID/components/Tip/NFTSection/index.tsx b/packages/mask/src/plugins/NextID/components/Tip/NFTSection/index.tsx index 8232f6a1b325..890b18d2701f 100644 --- a/packages/mask/src/plugins/NextID/components/Tip/NFTSection/index.tsx +++ b/packages/mask/src/plugins/NextID/components/Tip/NFTSection/index.tsx @@ -1,14 +1,17 @@ +import { useNetworkDescriptor, useWeb3State as useWeb3PluginState } from '@masknet/plugin-infra' +import { EMPTY_LIST } from '@masknet/shared-base' import { makeStyles } from '@masknet/theme' -import { ERC721TokenDetailed, useAccount, useERC721TokenDetailedCallback } from '@masknet/web3-shared-evm' -import { useERC721TokenDetailedOwnerList } from '@masknet/web3-providers' -import { Button, FormControl, Typography } from '@mui/material' +import { isSameAddress, useAccount } from '@masknet/web3-shared-evm' +import { Button, CircularProgress, Typography } from '@mui/material' import classnames from 'classnames' -import { FC, HTMLProps, useCallback, useMemo, useState } from 'react' -import { SearchInput } from '../../../../../extension/options-page/DashboardComponents/SearchInput' -import { ERC721ContractSelectPanel } from '../../../../../web3/UI/ERC721ContractSelectPanel' -import { TargetChainIdContext, useTip } from '../../../contexts' -import { NFTList } from './NFTList' +import { uniqWith } from 'lodash-unified' +import { FC, HTMLProps, useEffect, useMemo, useState } from 'react' +import { useAsyncFn, useTimeoutFn } from 'react-use' +import { WalletMessages } from '../../../../Wallet/messages' +import { useTip } from '../../../contexts' import { useI18N } from '../../../locales' +import type { TipNFTKeyPair } from '../../../types' +import { NFTList } from './NFTList' export * from './NFTList' @@ -17,36 +20,28 @@ const useStyles = makeStyles()((theme) => ({ display: 'flex', flexDirection: 'column', overflow: 'auto', + height: 282, }, selectSection: { - marginTop: theme.spacing(1.5), display: 'flex', flexDirection: 'column', overflow: 'auto', }, + statusBox: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + justifyContent: 'center', + height: 282, + }, + loadingText: { + marginTop: theme.spacing(1), + }, list: { flexGrow: 1, - marginTop: theme.spacing(2), - display: 'grid', - gridTemplateColumns: 'repeat(4, 1fr)', maxHeight: 400, overflow: 'auto', - gridGap: 18, - backgroundColor: theme.palette.background.default, borderRadius: 4, - padding: theme.spacing(1), - }, - keyword: { - borderRadius: 8, - marginRight: theme.spacing(1.5), - }, - searchButton: { - borderRadius: 8, - width: 100, - }, - row: { - display: 'flex', - flexDirection: 'row', }, errorMessage: { marginTop: theme.spacing(3), @@ -56,74 +51,103 @@ const useStyles = makeStyles()((theme) => ({ }, })) -interface Props extends HTMLProps {} +interface Props extends HTMLProps { + onAddToken?(): void + onEmpty?(empty: boolean): void +} -export const NFTSection: FC = ({ className, ...rest }) => { - const t = useI18N() - const { targetChainId: chainId } = TargetChainIdContext.useContainer() - const { erc721Contract, setErc721Contract, erc721TokenId, setErc721TokenId, isSending } = useTip() - const [tokenId, setTokenId, erc721TokenDetailedCallback] = useERC721TokenDetailedCallback(erc721Contract) +export const NFTSection: FC = ({ className, onAddToken, onEmpty, ...rest }) => { + const { erc721Address, erc721TokenId, setErc721TokenId, setErc721Address } = useTip() const { classes } = useStyles() + const t = useI18N() const account = useAccount() - const { tokenDetailedOwnerList: myTokens = [] } = useERC721TokenDetailedOwnerList(erc721Contract, account) + const selectedPairs: TipNFTKeyPair[] = useMemo( + () => (erc721Address && erc721TokenId ? [[erc721Address, erc721TokenId]] : []), + [erc721TokenId, erc721TokenId], + ) + const { Asset } = useWeb3PluginState() + + const networkDescriptor = useNetworkDescriptor() + + // Cannot get the loading status of fetching via websocket + // loading status of `useAsyncRetry` is not the real status + const [guessLoading, setGuessLoading] = useState(true) + useTimeoutFn(() => { + setGuessLoading(false) + }, 10000) + + const [{ value = { data: EMPTY_LIST }, loading }, fetchTokens] = useAsyncFn(async () => { + const result = await Asset?.getNonFungibleAssets?.(account, { page: 0 }, undefined, networkDescriptor) + return result + }, [account, Asset?.getNonFungibleAssets, networkDescriptor]) + + useEffect(() => { + fetchTokens() + }, [fetchTokens]) + + useEffect(() => { + const unsubscribeTokens = WalletMessages.events.erc721TokensUpdated.on(fetchTokens) + const unsubscribeSocket = WalletMessages.events.socketMessageUpdated.on((info) => { + setGuessLoading(info.done) + if (!info.done) { + fetchTokens() + } + }) + return () => { + unsubscribeTokens() + unsubscribeSocket() + } + }, [fetchTokens]) + + const fetchedTokens = value?.data ?? EMPTY_LIST - const selectedIds = useMemo(() => (erc721TokenId ? [erc721TokenId] : []), [erc721TokenId]) + const tokens = useMemo(() => { + return uniqWith(fetchedTokens, (v1, v2) => { + return isSameAddress(v1.contract?.address, v2.contract?.address) && v1.tokenId === v2.tokenId + }) + }, [fetchedTokens]) - const [searchedToken, setSearchedToken] = useState(null) - const onSearch = useCallback(async () => { - const token = await erc721TokenDetailedCallback() - setSearchedToken(token?.info.owner ? token : null) - }, [erc721TokenDetailedCallback]) + const showLoadingIndicator = tokens.length === 0 && !loading && !guessLoading - const tokens = useMemo(() => (searchedToken ? [searchedToken] : myTokens), [searchedToken, myTokens]) - const enableTokenIds = useMemo(() => myTokens.map((t) => t.tokenId), [myTokens]) + useEffect(() => { + onEmpty?.(showLoadingIndicator) + }, [onEmpty, showLoadingIndicator]) return (
- - - - {erc721Contract ? ( -
- - setTokenId(id)} - inputBaseProps={{ - disabled: isSending, - }} - label="" - /> - - - { - setErc721TokenId(ids.length ? ids[0] : null) - }} - /> -
- ) : null} - {tokens.length === 1 && !enableTokenIds.includes(tokens[0].tokenId) ? ( - - {t.nft_not_belong_to_you()} - - ) : null} +
+ {(() => { + if (tokens.length) { + return ( + { + setErc721TokenId(id) + setErc721Address(address) + }} + /> + ) + } + if (loading || guessLoading) { + return ( +
+ + {t.tip_loading()} +
+ ) + } + return ( +
+ {t.tip_empty_nft()} + +
+ ) + })()} +
) } diff --git a/packages/mask/src/plugins/NextID/components/Tip/TipButton.tsx b/packages/mask/src/plugins/NextID/components/Tip/TipButton.tsx index aaaad373542e..fa107279382e 100644 --- a/packages/mask/src/plugins/NextID/components/Tip/TipButton.tsx +++ b/packages/mask/src/plugins/NextID/components/Tip/TipButton.tsx @@ -1,13 +1,14 @@ import { TipCoin } from '@masknet/icons' import { usePostInfoDetails } from '@masknet/plugin-infra' -import { NextIDPlatform, ProfileIdentifier } from '@masknet/shared-base' +import { EMPTY_LIST, NextIDPlatform, ProfileIdentifier } from '@masknet/shared-base' import { makeStyles, ShadowRootTooltip } from '@masknet/theme' -import { queryExistedBindingByPersona, queryIsBound } from '@masknet/web3-providers' -import { EMPTY_LIST } from '@masknet/web3-shared-evm' +import { NextIDProof } from '@masknet/web3-providers' +import type { TooltipProps } from '@mui/material' import classnames from 'classnames' import { uniq } from 'lodash-unified' -import { FC, HTMLProps, MouseEventHandler, useCallback, useMemo } from 'react' +import { FC, HTMLProps, MouseEventHandler, useCallback, useEffect, useMemo } from 'react' import { useAsync, useAsyncFn, useAsyncRetry } from 'react-use' +import { MaskMessages } from '../../../../../shared' import Services from '../../../../extension/service' import { activatedSocialNetworkUI } from '../../../../social-network' import { useI18N } from '../../locales' @@ -16,6 +17,7 @@ import { PluginNextIdMessages } from '../../messages' interface Props extends HTMLProps { addresses?: string[] receiver?: ProfileIdentifier + tooltipProps?: Partial } const useStyles = makeStyles()({ @@ -26,20 +28,41 @@ const useStyles = makeStyles()({ alignItems: 'center', fontFamily: '-apple-system, system-ui, sans-serif', }, - postTipButton: { - display: 'flex', + buttonWrapper: { // temporarily hard code height: 46, + display: 'flex', alignItems: 'center', color: '#8899a6', }, + postTipButton: { + cursor: 'pointer', + width: 34, + height: 34, + borderRadius: '100%', + '&:hover': { + backgroundColor: 'rgba(20,155,240,0.1)', + }, + }, + tooltip: { + backgroundColor: 'rgb(102,102,102)', + color: 'white', + marginTop: '0 !important', + }, disabled: { opacity: 0.4, cursor: 'default', }, }) -export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LIST, children, ...rest }) => { +export const TipButton: FC = ({ + className, + receiver, + addresses = EMPTY_LIST, + children, + tooltipProps, + ...rest +}) => { const { classes } = useStyles() const t = useI18N() @@ -49,9 +72,13 @@ export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LI return Services.Identity.queryPersonaByProfile(receiver) }, [receiver]) - const { value: isAccountVerified, loading: loadingVerifyInfo } = useAsync(() => { + const { + value: isAccountVerified, + loading: loadingVerifyInfo, + retry: retryLoadVerifyInfo, + } = useAsyncRetry(() => { if (!receiverPersona?.publicHexKey || !receiver?.userId) return Promise.resolve(false) - return queryIsBound(receiverPersona.publicHexKey, platform, receiver.userId, true) + return NextIDProof.queryIsBound(receiverPersona.publicHexKey, platform, receiver.userId, true) }, [receiverPersona?.publicHexKey, platform, receiver?.userId]) const [walletsState, queryBindings] = useAsyncFn(async () => { @@ -60,7 +87,7 @@ export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LI const persona = await Services.Identity.queryPersonaByProfile(receiver) if (!persona?.publicHexKey) return EMPTY_LIST - const bindings = await queryExistedBindingByPersona(persona.publicHexKey, true) + const bindings = await NextIDProof.queryExistedBindingByPersona(persona.publicHexKey, true) if (!bindings) return EMPTY_LIST const wallets = bindings.proofs.filter((p) => p.platform === NextIDPlatform.Ethereum).map((p) => p.identity) @@ -69,6 +96,13 @@ export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LI useAsync(queryBindings, [queryBindings]) + useEffect(() => { + return MaskMessages.events.ownProofChanged.on(() => { + retryLoadVerifyInfo() + queryBindings() + }) + }, []) + const allAddresses = useMemo(() => { return uniq([...(walletsState.value || []), ...addresses]) }, [walletsState.value, addresses]) @@ -104,15 +138,24 @@ export const TipButton: FC = ({ className, receiver, addresses = EMPTY_LI if (disabled) return ( - + {dom} ) return dom } -export const PostTipButton: FC = (props) => { +export const PostTipButton: FC = ({ className, ...rest }) => { const identifier = usePostInfoDetails.author() const { classes } = useStyles() - return + return ( +
+ +
+ ) } diff --git a/packages/mask/src/plugins/NextID/components/Tip/TipDialog.tsx b/packages/mask/src/plugins/NextID/components/Tip/TipDialog.tsx index 0f0a364ce07b..e0e4c8c442e0 100644 --- a/packages/mask/src/plugins/NextID/components/Tip/TipDialog.tsx +++ b/packages/mask/src/plugins/NextID/components/Tip/TipDialog.tsx @@ -1,21 +1,46 @@ -import { SuccessIcon } from '@masknet/icons' -import { PluginId, useActivatedPlugin, usePluginIDContext } from '@masknet/plugin-infra' -import { NFTCardStyledAssetPlayer } from '@masknet/shared' +import { Drop2Icon, LinkOutIcon, SuccessIcon } from '@masknet/icons' +import { + PluginId, + useActivatedPlugin, + useCurrentWeb3NetworkPluginID, + useNetworkDescriptor, + useProviderDescriptor, + useReverseAddress, + useWeb3State, +} from '@masknet/plugin-infra' +import { InjectedDialog, NFTCardStyledAssetPlayer, WalletIcon } from '@masknet/shared' +import { EMPTY_LIST } from '@masknet/shared-base' +import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import { makeStyles } from '@masknet/theme' -import { EMPTY_LIST, TransactionStateType, useChainId, useERC721TokenDetailed } from '@masknet/web3-shared-evm' -import { DialogContent, Typography } from '@mui/material' +import { + ERC721TokenDetailed, + TransactionStateType, + useAccount, + useChainId, + useERC721TokenDetailed, +} from '@masknet/web3-shared-evm' +import { DialogContent, Link, Typography } from '@mui/material' import { useCallback, useEffect, useMemo } from 'react' import { useBoolean } from 'react-use' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' +import { hasNativeAPI, nativeAPI } from '../../../../../shared/native-rpc' import { NetworkTab } from '../../../../components/shared/NetworkTab' import { activatedSocialNetworkUI } from '../../../../social-network' +import { WalletMessages } from '../../../Wallet/messages' import { TargetChainIdContext, useTip } from '../../contexts' import { useI18N } from '../../locales' import { TipType } from '../../types' import { ConfirmModal } from '../ConfirmModal' +import { AddDialog } from './AddDialog' import { TipForm } from './TipForm' const useStyles = makeStyles()((theme) => ({ + dialog: { + width: 600, + backgroundImage: 'none', + }, + dialogTitle: { + height: 60, + }, content: { display: 'flex', flexDirection: 'column', @@ -68,6 +93,53 @@ const useStyles = makeStyles()((theme) => ({ width: 64, height: 64, }, + walletChip: { + marginLeft: 'auto', + height: 40, + boxSizing: 'border-box', + display: 'flex', + alignItems: 'center', + backgroundColor: theme.palette.background.default, + padding: theme.spacing(0.5, 1), + borderRadius: 99, + }, + wallet: { + marginLeft: theme.spacing(1), + }, + walletTitle: { + marginLeft: theme.spacing(1), + lineHeight: '18px', + height: 18, + fontSize: 14, + fontWeight: 'bold', + }, + walletAddress: { + height: 12, + display: 'flex', + alignItems: 'center', + fontSize: 10, + color: theme.palette.text.secondary, + }, + changeWalletButton: { + marginLeft: theme.spacing(0.5), + display: 'flex', + alignItems: 'center', + cursor: 'pointer', + }, + link: { + cursor: 'pointer', + lineHeight: '10px', + marginTop: 2, + '&:hover': { + textDecoration: 'none', + }, + }, + linkIcon: { + fill: 'none', + width: 12, + height: 12, + marginLeft: theme.spacing(0.5), + }, })) interface TipDialogProps { @@ -76,31 +148,47 @@ interface TipDialogProps { } export function TipDialog({ open = false, onClose }: TipDialogProps) { - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() const tipDefinition = useActivatedPlugin(PluginId.NextID, 'any') const chainIdList = tipDefinition?.enableRequirement.web3?.[pluginID]?.supportedChainIds ?? EMPTY_LIST const t = useI18N() const { classes } = useStyles() + const [addTokenDialogIsOpen, openAddTokenDialog] = useBoolean(false) const [confirmModalIsOpen, openConfirmModal] = useBoolean(false) const { targetChainId, setTargetChainId } = TargetChainIdContext.useContainer() - const { tipType, amount, token, recipientSnsId, recipient, sendState, erc721Contract, erc721TokenId } = useTip() + const { + tipType, + amount, + token, + recipientSnsId, + recipient, + sendState, + erc721Contract, + erc721TokenId, + setErc721Address, + setErc721TokenId, + reset, + } = useTip() const isTokenTip = tipType === TipType.Token - const shareLink = useMemo(() => { + const shareText = useMemo(() => { + const promote = t.tip_mask_promote() const message = isTokenTip ? t.tip_token_share_post({ amount, symbol: token?.symbol || 'token', recipientSnsId, recipient, + promote, }) : t.tip_nft_share_post({ - name: erc721Contract?.name || '', + name: erc721Contract?.name || 'NFT', recipientSnsId, recipient, + promote, }) - return activatedSocialNetworkUI.utils.getShareLinkURL?.(message) + return message }, [amount, isTokenTip, erc721Contract?.name, token, recipient, recipientSnsId, t]) const { tokenDetailed: erc721Token } = useERC721TokenDetailed(erc721Contract, erc721TokenId) @@ -139,14 +227,73 @@ export function TipDialog({ open = false, onClose }: TipDialogProps) { }, [sendState.type]) const handleConfirm = useCallback(() => { - window.open(shareLink) + activatedSocialNetworkUI.utils.share?.(shareText) openConfirmModal(false) onClose?.() - }, [shareLink, onClose]) + }, [shareText, onClose]) + const networkDescriptor = useNetworkDescriptor() + const providerDescriptor = useProviderDescriptor() + + const { Utils } = useWeb3State() + const account = useAccount() + const { value: domain } = useReverseAddress(account) + const walletTitle = + Utils?.formatDomainName?.(domain) || Utils?.formatAddress?.(account, 4) || providerDescriptor?.name + + // #region change provider + const { openDialog: openSelectProviderDialog } = useRemoteControlledDialog( + WalletMessages.events.selectProviderDialogUpdated, + ) + // #endregion + const openWallet = useCallback(() => { + if (hasNativeAPI) return nativeAPI?.api.misc_openCreateWalletView() + return openSelectProviderDialog() + }, [openSelectProviderDialog, hasNativeAPI]) + + const handleAddToken = useCallback((token: ERC721TokenDetailed) => { + setErc721Address(token.contractDetailed.address ?? '') + setErc721TokenId(token.tokenId) + openAddTokenDialog(false) + }, []) + + const walletChip = account ? ( +
+ +
+ + {walletTitle} + + + + {Utils?.formatAddress?.(account, 4)} + + + + +
+
+ +
+
+ ) : null return ( <> - +
- + openAddTokenDialog(true)} />
openConfirmModal(false)} + onClose={() => { + openConfirmModal(false) + reset() + onClose?.() + }} icon={isTokenTip ? : null} message={successMessage} confirmText={t.tip_share()} onConfirm={handleConfirm} /> + openAddTokenDialog(false)} onAdd={handleAddToken} /> ) } diff --git a/packages/mask/src/plugins/NextID/components/Tip/TipForm.tsx b/packages/mask/src/plugins/NextID/components/Tip/TipForm.tsx index 208680a59c5a..d0f947d54542 100644 --- a/packages/mask/src/plugins/NextID/components/Tip/TipForm.tsx +++ b/packages/mask/src/plugins/NextID/components/Tip/TipForm.tsx @@ -1,17 +1,29 @@ import { useWeb3State } from '@masknet/plugin-infra' -import { SelectTokenDialogEvent, WalletMessages } from '@masknet/plugin-wallet' +import { WalletMessages } from '@masknet/plugin-wallet' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import { makeStyles } from '@masknet/theme' -import { EthereumTokenType, useAccount, useFungibleTokenBalance } from '@masknet/web3-shared-evm' -import { Box, BoxProps, FormControl, MenuItem, Select, Typography } from '@mui/material' +import { ChainId, useAccount, useChainId } from '@masknet/web3-shared-evm' +import { + Box, + BoxProps, + Button, + FormControl, + FormControlLabel, + MenuItem, + Radio, + RadioGroup, + Select, + Typography, +} from '@mui/material' import classnames from 'classnames' -import { FC, memo, useCallback, useRef, useState } from 'react' -import { v4 as uuid } from 'uuid' +import { FC, memo, useRef, useState } from 'react' import ActionButton from '../../../../extension/options-page/DashboardComponents/ActionButton' import { EthereumChainBoundary } from '../../../../web3/UI/EthereumChainBoundary' -import { TokenAmountPanel } from '../../../../web3/UI/TokenAmountPanel' import { TargetChainIdContext, useTip, useTipValidate } from '../../contexts' import { useI18N } from '../../locales' +import { TipType } from '../../types' +import { NFTSection } from './NFTSection' +import { TokenSection } from './TokenSection' const useStyles = makeStyles()((theme) => { return { @@ -25,6 +37,20 @@ const useStyles = makeStyles()((theme) => { flexGrow: 1, overflow: 'auto', }, + receiverRow: { + display: 'flex', + flexDirection: 'row', + alignItems: 'center', + }, + to: { + fontSize: 19, + fontWeight: 500, + }, + address: { + height: 48, + flexGrow: 1, + marginLeft: theme.spacing(1), + }, actionButton: { marginTop: theme.spacing(1.5), fontSize: 16, @@ -47,76 +73,60 @@ const useStyles = makeStyles()((theme) => { borderRadius: 24, height: 'auto', }, + controls: { + marginTop: theme.spacing(1), + display: 'flex', + flexDirection: 'row', + }, + addButton: { + marginLeft: 'auto', + }, tokenField: { marginTop: theme.spacing(2), }, } }) -interface Props extends BoxProps {} +interface Props extends BoxProps { + onAddToken?(): void +} -export const TipForm: FC = memo(({ className, ...rest }) => { +export const TipForm: FC = memo(({ className, onAddToken, ...rest }) => { const t = useI18N() + const currentChainId = useChainId() const { targetChainId: chainId } = TargetChainIdContext.useContainer() const { classes } = useStyles() const { recipient, recipients: recipientAddresses, setRecipient, - token, - setToken, - amount, - setAmount, isSending, sendTip, + tipType, + setTipType, } = useTip() const [isValid, validateMessage] = useTipValidate() const { Utils } = useWeb3State() const selectRef = useRef(null) - const [id] = useState(uuid) const account = useAccount() const { openDialog: openSelectProviderDialog } = useRemoteControlledDialog( WalletMessages.events.selectProviderDialogUpdated, ) - const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog( - WalletMessages.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== id) return - setToken(ev.token) - }, - [id], - ), - ) - const onSelectTokenChipClick = useCallback(() => { - setSelectTokenDialog({ - chainId, - open: true, - uuid: id, - disableNativeToken: false, - FungibleTokenListProps: { - selectedTokens: token ? [token.address] : [], - }, - }) - }, [id, token?.address, chainId]) - - // balance - const { value: tokenBalance = '0', loading: loadingTokenBalance } = useFungibleTokenBalance( - token?.type || EthereumTokenType.Native, - token?.address || '', - chainId, - ) - // #endregion + const [empty, setEmpty] = useState(false) const buttonLabel = isSending ? t.sending_tip() : isValid || !validateMessage ? t.send_tip() : validateMessage + const enabledNft = + !isSending && + chainId === currentChainId && + [ChainId.Mainnet, ChainId.BSC, ChainId.Matic].includes(currentChainId) return (
- {t.tip_to()} - - + + {t.tip_to()} - - + + setTipType(e.target.value as TipType)}> + } + label={t.tip_type_token()} + /> + } + label={t.tip_type_nft()} + /> + + {tipType === TipType.NFT && !empty ? ( + + ) : null} + {tipType === TipType.Token ? ( + + + + ) : ( + + )}
{account ? ( { + const { token, setToken, amount, setAmount, isSending } = useTip() + const { targetChainId: chainId } = TargetChainIdContext.useContainer() + // balance + const { value: tokenBalance = '0', loading: loadingTokenBalance } = useFungibleTokenBalance( + token?.type || EthereumTokenType.Native, + token?.address || '', + chainId, + ) + const gasConfig = useGasConfig(chainId) + const maxAmount = useMemo(() => { + if (!isNativeTokenAddress(token?.address)) return tokenBalance + const gasPrice = gasConfig.gasPrice ?? '1' + const gasFee = new BigNumber(gasPrice).times(GAS_LIMIT) + return new BigNumber(tokenBalance).minus(gasFee).toFixed() + }, [token?.address, tokenBalance, gasConfig.gasPrice]) + const pickToken = usePickToken() + const onSelectTokenChipClick = useCallback(async () => { + const picked = await pickToken({ + chainId, + disableNativeToken: false, + selectedTokens: token ? [token.address] : [], + }) + if (picked) { + setToken(picked) + } + }, [pickToken, token?.address, chainId]) + // #endregion + return ( + + ) +} diff --git a/packages/mask/src/plugins/NextID/components/UnbindDialog.tsx b/packages/mask/src/plugins/NextID/components/UnbindDialog.tsx index 16f8b9a498d9..cc0b95ba29b0 100644 --- a/packages/mask/src/plugins/NextID/components/UnbindDialog.tsx +++ b/packages/mask/src/plugins/NextID/components/UnbindDialog.tsx @@ -12,7 +12,8 @@ import { useBindPayload } from '../hooks/useBindPayload' import { delay } from '@dimensiondev/kit' import { UnbindPanelUI } from './UnbindPanelUI' import { UnbindConfirm } from './UnbindConfirm' -import { bindProof } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' +import { MaskMessages } from '../../../../shared' interface VerifyWalletDialogProps { unbindAddress: string @@ -40,7 +41,7 @@ export const UnbindDialog = memo(({ unbindAddress, onCl if (!personaSignState.value && !walletSignState.value) return if (!message || !persona.publicHexKey) return try { - await bindProof( + await NextIDProof.bindProof( message.uuid, persona.publicHexKey, NextIDAction.Delete, @@ -56,6 +57,9 @@ export const UnbindDialog = memo(({ unbindAddress, onCl variant: 'success', message: t.notify_wallet_sign_request_success(), }) + + MaskMessages.events.ownProofChanged.sendToAll() + await delay(2000) onUnBound() onClose() diff --git a/packages/mask/src/plugins/NextID/components/UnbindPanelUI.tsx b/packages/mask/src/plugins/NextID/components/UnbindPanelUI.tsx index 7743ada26936..ab18c807d452 100644 --- a/packages/mask/src/plugins/NextID/components/UnbindPanelUI.tsx +++ b/packages/mask/src/plugins/NextID/components/UnbindPanelUI.tsx @@ -7,10 +7,10 @@ import DoneIcon from '@mui/icons-material/Done' import { useI18N } from '../locales' import { getMaskColor, makeStyles, MaskColorVar } from '@masknet/theme' import type { Persona } from '../../../database' -import { formatFingerprint, LoadingAnimation } from '@masknet/shared' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' -import { NetworkPluginID, usePluginIDContext } from '@masknet/plugin-infra' +import { InjectedDialog, LoadingAnimation } from '@masknet/shared' +import { NetworkPluginID, useCurrentWeb3NetworkPluginID } from '@masknet/plugin-infra' import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab' +import { formatPersonaFingerprint } from '@masknet/shared-base' const useStyles = makeStyles()((theme) => ({ persona: { @@ -95,7 +95,7 @@ export const UnbindPanelUI = memo( ({ onPersonaSign, onWalletSign, currentPersona, signature, isBound, title, onClose, open, isCurrentAccount }) => { const t = useI18N() const { classes } = useStyles() - const pluginId = usePluginIDContext() + const pluginId = useCurrentWeb3NetworkPluginID() const isSupported = SUPPORTED_PLUGINS.includes(pluginId) const isWalletSigned = !!signature.wallet.value @@ -116,7 +116,7 @@ export const UnbindPanelUI = memo(
{currentPersona?.nickname} - {formatFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)} + {formatPersonaFingerprint(currentPersona?.identifier.compressedPoint ?? '', 10)}
diff --git a/packages/mask/src/plugins/NextID/contexts/TargetChainIdContext.ts b/packages/mask/src/plugins/NextID/contexts/TargetChainIdContext.ts index 17df3f66514f..ad2d904a03a7 100644 --- a/packages/mask/src/plugins/NextID/contexts/TargetChainIdContext.ts +++ b/packages/mask/src/plugins/NextID/contexts/TargetChainIdContext.ts @@ -1,11 +1,15 @@ import { ChainId, useChainId } from '@masknet/web3-shared-evm' -import { useState } from 'react' +import { useEffect, useState } from 'react' import { createContainer } from 'unstated-next' function useTargetChainId() { const chainId = useChainId() const [targetChainId, setTargetChainId] = useState(chainId) + useEffect(() => { + setTargetChainId(chainId) + }, [chainId]) + return { targetChainId, setTargetChainId, diff --git a/packages/mask/src/plugins/NextID/contexts/Tip/TipContext.ts b/packages/mask/src/plugins/NextID/contexts/Tip/TipContext.ts index 2082cac59765..8fa4be5af260 100644 --- a/packages/mask/src/plugins/NextID/contexts/Tip/TipContext.ts +++ b/packages/mask/src/plugins/NextID/contexts/Tip/TipContext.ts @@ -1,3 +1,4 @@ +import type { Web3Plugin } from '@masknet/plugin-infra' import { ERC721ContractDetailed, FungibleTokenDetailed, @@ -22,10 +23,13 @@ export interface ContextOptions { erc721TokenId: string | null setErc721TokenId: Dispatch> erc721Contract: ERC721ContractDetailed | null - setErc721Contract: Dispatch> + erc721Address: string + setErc721Address: Dispatch> sendTip: () => Promise isSending: boolean sendState: TransactionState + storedTokens: Web3Plugin.NonFungibleToken[] + reset: () => void } export const TipContext = createContext({ @@ -42,8 +46,11 @@ export const TipContext = createContext({ erc721TokenId: null, setErc721TokenId: noop, erc721Contract: null, - setErc721Contract: noop, + erc721Address: '', + setErc721Address: noop, sendTip: noop as () => Promise, isSending: false, sendState: { type: TransactionStateType.UNKNOWN }, + storedTokens: [], + reset: noop, }) diff --git a/packages/mask/src/plugins/NextID/contexts/Tip/TipTaskProvider.tsx b/packages/mask/src/plugins/NextID/contexts/Tip/TipTaskProvider.tsx index de39fd31f565..7b8722fa717e 100644 --- a/packages/mask/src/plugins/NextID/contexts/Tip/TipTaskProvider.tsx +++ b/packages/mask/src/plugins/NextID/contexts/Tip/TipTaskProvider.tsx @@ -1,5 +1,12 @@ -import { TransactionStateType, useNativeTokenDetailed } from '@masknet/web3-shared-evm' +import { + TransactionStateType, + useChainId, + useERC721ContractDetailed, + useNativeTokenDetailed, +} from '@masknet/web3-shared-evm' import { FC, useContext, useEffect, useMemo, useState } from 'react' +import { useSubscription } from 'use-subscription' +import { getStorage } from '../../storage' import { TipTask, TipType } from '../../types' import { TargetChainIdContext } from '../TargetChainIdContext' import { ContextOptions, TipContext } from './TipContext' @@ -15,10 +22,14 @@ export const TipTaskProvider: FC = ({ children, task }) => { const [tipType, setTipType] = useState(TipType.Token) const [amount, setAmount] = useState('') const { targetChainId } = TargetChainIdContext.useContainer() - const [erc721Contract, setErc721Contract] = useState(null) + const chainId = useChainId() + const [erc721Address, setErc721Address] = useState('') const { value: nativeTokenDetailed = null } = useNativeTokenDetailed(targetChainId) const [token, setToken] = useState(nativeTokenDetailed) const [erc721TokenId, setErc721TokenId] = useState(null) + const storedTokens = useSubscription(getStorage().addedTokens.subscription) + + const { value: erc721Contract } = useERC721ContractDetailed(erc721Address) useEffect(() => { setTipType(TipType.Token) @@ -38,7 +49,7 @@ export const TipTaskProvider: FC = ({ children, task }) => { setToken(nativeTokenDetailed) }, [nativeTokenDetailed]) const tokenTipTuple = useTokenTip(recipient, token, amount) - const nftTipTuple = useNftTip(recipient, erc721TokenId, erc721Contract) + const nftTipTuple = useNftTip(recipient, erc721TokenId, erc721Address) const sendTipTuple = tipType === TipType.Token ? tokenTipTuple : nftTipTuple const sendState = sendTipTuple[0] @@ -51,6 +62,12 @@ export const TipTaskProvider: FC = ({ children, task }) => { TransactionStateType.RECEIPT, ].includes(sendState.type) + const reset = () => { + setAmount('') + setErc721TokenId(null) + setErc721Address('') + } + return { recipient, recipientSnsId: task.recipientSnsId || '', @@ -64,13 +81,17 @@ export const TipTaskProvider: FC = ({ children, task }) => { setAmount, erc721TokenId, setErc721TokenId, - erc721Contract, - setErc721Contract, + erc721Contract: erc721Contract || null, + erc721Address, + setErc721Address, sendTip, isSending, sendState, + storedTokens: storedTokens.filter((t) => t.contract?.chainId === chainId), + reset, } }, [ + chainId, recipient, task.recipientSnsId, task.addresses, @@ -78,9 +99,11 @@ export const TipTaskProvider: FC = ({ children, task }) => { amount, erc721TokenId, erc721Contract, + erc721Address, token, sendTip, sendState, + storedTokens, ]) return {children} } diff --git a/packages/mask/src/plugins/NextID/contexts/Tip/useNftTip.ts b/packages/mask/src/plugins/NextID/contexts/Tip/useNftTip.ts index 398d2cfbe769..da0a1a620572 100644 --- a/packages/mask/src/plugins/NextID/contexts/Tip/useNftTip.ts +++ b/packages/mask/src/plugins/NextID/contexts/Tip/useNftTip.ts @@ -1,21 +1,32 @@ -import { ERC721ContractDetailed, EthereumTokenType, useTokenTransferCallback } from '@masknet/web3-shared-evm' -import { useCallback } from 'react' +import { + EthereumTokenType, + useERC721ContractDetailed, + useERC721TokenDetailedCallback, + useTokenTransferCallback, +} from '@masknet/web3-shared-evm' +import { useCallback, useEffect } from 'react' +import { WalletRPC } from '../../../Wallet/messages' import type { TipTuple } from './type' -export function useNftTip( - recipient: string, - tokenId: string | null, - contract: ERC721ContractDetailed | null, -): TipTuple { - const [transferState, transferCallback] = useTokenTransferCallback( - EthereumTokenType.ERC721, - contract?.address || '', - ) +export function useNftTip(recipient: string, tokenId: string | null, contractAddress?: string): TipTuple { + const [transferState, transferCallback] = useTokenTransferCallback(EthereumTokenType.ERC721, contractAddress || '') + const { value: contractDetailed } = useERC721ContractDetailed(contractAddress) + const [, setTokenId, erc721TokenDetailedCallback] = useERC721TokenDetailedCallback(contractDetailed) + + useEffect(() => { + if (tokenId) { + setTokenId(tokenId) + } + }, [tokenId]) const sendTip = useCallback(async () => { - if (!tokenId) return + if (!tokenId || !contractAddress) return await transferCallback(tokenId, recipient) - }, [tokenId, recipient, transferCallback]) + const tokenDetailed = await erc721TokenDetailedCallback() + if (tokenDetailed) { + await WalletRPC.removeToken(tokenDetailed) + } + }, [tokenId, contractAddress, erc721TokenDetailedCallback, recipient, transferCallback]) return [transferState, sendTip] } diff --git a/packages/mask/src/plugins/NextID/contexts/Tip/useTipValidate.ts b/packages/mask/src/plugins/NextID/contexts/Tip/useTipValidate.ts index 741010377f9c..2eca80b4dc8d 100644 --- a/packages/mask/src/plugins/NextID/contexts/Tip/useTipValidate.ts +++ b/packages/mask/src/plugins/NextID/contexts/Tip/useTipValidate.ts @@ -8,7 +8,7 @@ import { TipContext } from './TipContext' type ValidationTuple = [isValid: boolean, message?: string] export function useTipValidate(): ValidationTuple { - const { tipType, amount, token, erc721TokenId, erc721Contract } = useContext(TipContext) + const { tipType, amount, token, erc721TokenId, erc721Address } = useContext(TipContext) const { value: balance = '0' } = useFungibleTokenBalance(token?.type || EthereumTokenType.Native, token?.address) const t = useI18N() @@ -18,10 +18,10 @@ export function useTipValidate(): ValidationTuple { if (isGreaterThan(rightShift(amount, token?.decimals), balance)) return [false, t.token_insufficient_balance()] } else { - if (!erc721TokenId || !erc721Contract) return [false] + if (!erc721TokenId || !erc721Address) return [false] } return [true] - }, [tipType, amount, token?.decimals, balance, erc721Contract, erc721TokenId, t]) + }, [tipType, amount, token?.decimals, balance, erc721TokenId, erc721Address, t]) return result } diff --git a/packages/mask/src/plugins/NextID/hooks/useBindPayload.ts b/packages/mask/src/plugins/NextID/hooks/useBindPayload.ts index b995a4577f79..8ae719362503 100644 --- a/packages/mask/src/plugins/NextID/hooks/useBindPayload.ts +++ b/packages/mask/src/plugins/NextID/hooks/useBindPayload.ts @@ -1,11 +1,11 @@ import { useAsyncRetry } from 'react-use' -import { createPersonaPayload } from '@masknet/web3-providers' +import { NextIDProof } from '@masknet/web3-providers' import { NextIDAction, NextIDPlatform } from '@masknet/shared-base' export const useBindPayload = (action: NextIDAction, address?: string, currentIdentifier?: string) => { return useAsyncRetry(() => { if (!address) return Promise.resolve(undefined) if (!currentIdentifier || !address) return Promise.resolve(undefined) - return createPersonaPayload(currentIdentifier, action, address, NextIDPlatform.Ethereum) + return NextIDProof.createPersonaPayload(currentIdentifier, action, address, NextIDPlatform.Ethereum) }, [currentIdentifier, address]) } diff --git a/packages/mask/src/plugins/NextID/locales/en-US.json b/packages/mask/src/plugins/NextID/locales/en-US.json index a271b612e0ed..fda218ebf57a 100644 --- a/packages/mask/src/plugins/NextID/locales/en-US.json +++ b/packages/mask/src/plugins/NextID/locales/en-US.json @@ -53,13 +53,22 @@ "send_tip": "Send", "sending_tip": "Sending...", "nft_not_belong_to_you": "The collectible doesn't exist or belong to you.", - "send_tip_successfully": "Sent tip successfully", - "send_specific_tip_successfully": "Sent {{amount}} {{name}} tip successfully", + "send_tip_successfully": "Sent tip successfully.", + "send_specific_tip_successfully": "Sent {{amount}} {{name}} tip successfully.", "tip_share": "Share", "tip_connect_wallet_message": "Please connect to a wallet.", "tip_connect_wallet": "Connect Wallet", "search": "Search", "tip_contracts": "Contracts", - "tip_token_share_post": "I just tipped {{amount}} {{symbol}} to @{{recipientSnsId}}'s wallet address {{recipient}}\n\nInstall https://mask.io/download-links to send my first tip.", - "tip_nft_share_post": "I just tipped a {{name}} to @{{recipientSnsId}}'s wallet address {{recipient}}\n\nInstall https://mask.io/download-links to send my first tip." + "tip_mask_promote": "Install https://mask.io/download-links to send your first tip.", + "tip_token_share_post": "I just tipped {{amount}} {{symbol}} to @{{recipientSnsId}}'s wallet address {{recipient}}\n\n{{promote}}", + "tip_nft_share_post": "I just tipped a {{name}} to @{{recipientSnsId}}'s wallet address {{recipient}}\n\n{{promote}}", + "tip_add": "Add", + "tip_adding": "Adding", + "tip_add_collectibles": "Add Collectibles", + "tip_add_collectibles_contract_address": "Input contract address", + "tip_add_collectibles_token_id": "Token ID", + "tip_add_collectibles_error": "The contract address is incorrect or the collectible does not belong to you.", + "tip_loading": "Loading", + "tip_empty_nft": "No any collectible is available to preview. Please add your collectible here." } diff --git a/packages/mask/src/plugins/NextID/storage/index.ts b/packages/mask/src/plugins/NextID/storage/index.ts new file mode 100644 index 000000000000..6c9312b13e59 --- /dev/null +++ b/packages/mask/src/plugins/NextID/storage/index.ts @@ -0,0 +1,38 @@ +import type { Web3Plugin } from '@masknet/plugin-infra' +import type { ScopedStorage } from '@masknet/shared-base' +import { isSameAddress } from '@masknet/web3-shared-evm' +import { remove } from 'lodash-unified' + +interface StorageValue { + addedTokens: Web3Plugin.NonFungibleToken[] +} + +export const storageDefaultValue = { + publicKey: null as null | string, + addedTokens: [], +} + +let storage: ScopedStorage = null! + +export function setupStorage(_: typeof storage) { + storage = _ +} + +export function getStorage() { + return storage.storage +} + +export function getTokens() { + return storage.storage.addedTokens.value +} + +export function storeToken(token: Web3Plugin.NonFungibleToken) { + const tokens = [token, ...getTokens()] + storage.storage.addedTokens.setValue(tokens) +} + +export function deleteToken(address: string, tokenId: string) { + const tokens = getTokens() + remove(tokens, (t) => t.tokenId === tokenId && isSameAddress(t.contract?.address, address)) + storage.storage.addedTokens.setValue(tokens) +} diff --git a/packages/mask/src/plugins/NextID/types/tip.ts b/packages/mask/src/plugins/NextID/types/tip.ts index 2e6cbde0e5cd..8fefaf25e33a 100644 --- a/packages/mask/src/plugins/NextID/types/tip.ts +++ b/packages/mask/src/plugins/NextID/types/tip.ts @@ -8,3 +8,5 @@ export interface TipTask { recipientSnsId?: string addresses: string[] } + +export type TipNFTKeyPair = [address: string, tokenId: string] diff --git a/packages/mask/src/plugins/Pets/SNSAdaptor/PetDialog.tsx b/packages/mask/src/plugins/Pets/SNSAdaptor/PetDialog.tsx index b9efb0a31221..43b6a5c96385 100644 --- a/packages/mask/src/plugins/Pets/SNSAdaptor/PetDialog.tsx +++ b/packages/mask/src/plugins/Pets/SNSAdaptor/PetDialog.tsx @@ -2,9 +2,9 @@ import { useState } from 'react' import { useAsync, useTimeout } from 'react-use' import type { Constant } from '@masknet/web3-shared-evm/constants/utils' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { DialogContent } from '@mui/material' import { PluginPetMessages, PluginPetRPC } from '../messages' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import { useI18N } from '../../../utils' import { PetShareDialog } from './PetShareDialog' import { PetSetDialog } from './PetSetDialog' diff --git a/packages/mask/src/plugins/Pets/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Pets/SNSAdaptor/index.tsx index 9c3e1ca4d06e..b8e87d5b6bf3 100644 --- a/packages/mask/src/plugins/Pets/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Pets/SNSAdaptor/index.tsx @@ -2,6 +2,9 @@ import type { Plugin } from '@masknet/plugin-infra' import { base } from '../base' import AnimatePic from './Animate' import { PetDialog } from './PetDialog' +import { PluginPetMessages } from '../messages' +import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { ApplicationEntry } from '@masknet/shared' const sns: Plugin.SNSAdaptor.Definition = { ...base, @@ -14,6 +17,23 @@ const sns: Plugin.SNSAdaptor.Definition = { ) }, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + const { openDialog } = useRemoteControlledDialog(PluginPetMessages.events.essayDialogUpdated) + + return ( + + ) + }, + defaultSortingPriority: 10, + }, + ], } export default sns diff --git a/packages/mask/src/components/shared/assets/mintTeam.png b/packages/mask/src/plugins/Pets/assets/mintTeam.png similarity index 100% rename from packages/mask/src/components/shared/assets/mintTeam.png rename to packages/mask/src/plugins/Pets/assets/mintTeam.png diff --git a/packages/mask/src/plugins/Pets/base.tsx b/packages/mask/src/plugins/Pets/base.tsx index 119d8bf64690..903598b14901 100644 --- a/packages/mask/src/plugins/Pets/base.tsx +++ b/packages/mask/src/plugins/Pets/base.tsx @@ -1,4 +1,5 @@ -import { CurrentSNSNetwork, Plugin } from '@masknet/plugin-infra' +import { CurrentSNSNetwork, Plugin, NetworkPluginID } from '@masknet/plugin-infra' +import { ChainId } from '@masknet/web3-shared-evm' import { PetsPluginID } from './constants' import { PETSIcon } from '../../resources/PETSIcon' @@ -13,6 +14,13 @@ export const base: Plugin.Shared.Definition = { enableRequirement: { architecture: { app: false, web: true }, networks: { type: 'opt-in', networks: { [CurrentSNSNetwork.Twitter]: true } }, + web3: { + [NetworkPluginID.PLUGIN_EVM]: { + supportedChainIds: [ChainId.Mainnet], + }, + [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] }, + [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] }, + }, target: 'stable', }, } diff --git a/packages/mask/src/plugins/Polls/SNSAdaptor/PollsDialog.tsx b/packages/mask/src/plugins/Polls/SNSAdaptor/PollsDialog.tsx index 66df37c2f593..ce0ff662c48f 100644 --- a/packages/mask/src/plugins/Polls/SNSAdaptor/PollsDialog.tsx +++ b/packages/mask/src/plugins/Polls/SNSAdaptor/PollsDialog.tsx @@ -15,6 +15,7 @@ import { import { makeStyles, useStylesExtends, usePortalShadowRoot } from '@masknet/theme' import AddIcon from '@mui/icons-material/Add' import addDate from 'date-fns/add' +import { InjectedDialog } from '@masknet/shared' import { useI18N } from '../../../utils' import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab' import { useCurrentIdentity } from '../../../components/DataSource/useActivatedUI' @@ -22,7 +23,6 @@ import type { PollGunDB } from '../Services' import { PollCardUI } from './Polls' import type { PollMetaData } from '../types' import { PLUGIN_META_KEY } from '../constants' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import { PluginPollRPC } from '../messages' import { useCompositionContext } from '@masknet/plugin-infra' diff --git a/packages/mask/src/plugins/PoolTogether/UI/DepositDialog.tsx b/packages/mask/src/plugins/PoolTogether/UI/DepositDialog.tsx index bce5d39ccd42..552221d0c1e5 100644 --- a/packages/mask/src/plugins/PoolTogether/UI/DepositDialog.tsx +++ b/packages/mask/src/plugins/PoolTogether/UI/DepositDialog.tsx @@ -1,21 +1,21 @@ import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { usePickToken, InjectedDialog } from '@masknet/shared' +import { keyframes, makeStyles } from '@masknet/theme' +import { isZero, rightShift } from '@masknet/web3-shared-base' import { EthereumTokenType, formatBalance, FungibleTokenDetailed, TransactionStateType, useAccount, - ZERO_ADDRESS, useFungibleTokenBalance, + ZERO_ADDRESS, } from '@masknet/web3-shared-evm' -import { isZero, rightShift } from '@masknet/web3-shared-base' import { DialogContent, Grid, Typography } from '@mui/material' -import { keyframes, makeStyles } from '@masknet/theme' import { useCallback, useEffect, useMemo, useState } from 'react' -import { v4 as uuid } from 'uuid' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { activatedSocialNetworkUI } from '../../../social-network' +import { isFacebook } from '../../../social-network-adaptor/facebook.com/base' import { isTwitter } from '../../../social-network-adaptor/twitter.com/base' import { useI18N } from '../../../utils/i18n-next-ui' import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary' @@ -23,12 +23,11 @@ import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWallet import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel' import { PluginTraderMessages } from '../../Trader/messages' import type { Coin } from '../../Trader/types' -import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages' +import { WalletMessages } from '../../Wallet/messages' import { useDepositCallback } from '../hooks/useDepositCallback' import { PluginPoolTogetherMessages } from '../messages' import type { Pool } from '../types' import { calculateOdds, getPrizePeriod } from '../utils' -import { isFacebook } from '../../../social-network-adaptor/facebook.com/base' const rainbow_animation = keyframes` 0% { @@ -82,7 +81,6 @@ const useStyles = makeStyles()((theme) => ({ export function DepositDialog() { const { t } = useI18N() const { classes } = useStyles() - const [id] = useState(uuid()) const [pool, setPool] = useState() const [token, setToken] = useState() const [odds, setOdds] = useState() @@ -102,28 +100,16 @@ export function DepositDialog() { // #endregion // #region select token - const { setDialog: setSelectTokenDialogOpen } = useRemoteControlledDialog( - WalletMessages.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== id) return - setToken(ev.token) - }, - [id], - ), - ) - const onSelectTokenChipClick = useCallback(() => { + const pickToken = usePickToken() + const onSelectTokenChipClick = useCallback(async () => { if (!token) return - setSelectTokenDialogOpen({ - open: true, - uuid: id, + const picked = await pickToken({ disableNativeToken: true, - FungibleTokenListProps: { - selectedTokens: [token.address], - whitelist: [token.address], - }, + selectedTokens: [token.address], + whitelist: [token.address], }) - }, [id, token?.address]) + if (picked) setToken(picked) + }, [token, pickToken]) // #endregion // #region amount @@ -216,7 +202,7 @@ export function DepositDialog() { if (depositState.type === TransactionStateType.HASH) setRawAmount('') resetDepositCallback() }, - [id, depositState, retryLoadTokenBalance, retryLoadTokenBalance, onClose], + [depositState, retryLoadTokenBalance, retryLoadTokenBalance, onClose], ), ) diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketCreateNew.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketCreateNew.tsx index 2c1107f5b84f..22a399ef1503 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketCreateNew.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketCreateNew.tsx @@ -1,10 +1,10 @@ import { makeStyles } from '@masknet/theme' +import { MINDS_ID } from '@masknet/shared' import { useChainId, ChainId } from '@masknet/web3-shared-evm' import { RedPacketFormProps, RedPacketERC20Form } from './RedPacketERC20Form' import { RedPacketERC721Form } from './RedPacketERC721Form' import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab' import { useI18N } from '../../../utils' -import { MINDS_ID } from '../../../social-network-adaptor/minds.com/base' import { activatedSocialNetworkUI } from '../../../social-network' import { IconURLs } from './IconURL' diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx index 919c9432e430..953069f0ff3c 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketDialog.tsx @@ -2,6 +2,7 @@ import { useState, useCallback, useRef, useMemo, useEffect } from 'react' import { DialogContent } from '@mui/material' import { usePortalShadowRoot, makeStyles } from '@masknet/theme' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { useI18N } from '../../../utils' import AbstractTab, { AbstractTabProps } from '../../../components/shared/AbstractTab' import { RedPacketJSONPayload, DialogTabs, RedPacketRecord, RpTypeTabs } from '../types' @@ -9,7 +10,6 @@ import { RedPacketRPC } from '../messages' import { RedPacketMetaKey } from '../constants' import { RedPacketCreateNew } from './RedPacketCreateNew' import { RedPacketPast } from './RedPacketPast' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import Services from '../../../extension/service' import Web3Utils from 'web3-utils' import { diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC20Form.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC20Form.tsx index 2bf6f54f6503..6e0673921c71 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC20Form.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketERC20Form.tsx @@ -1,28 +1,26 @@ -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { makeStyles, useStylesExtends } from '@masknet/theme' +import { isGreaterThan, isZero, multipliedBy, rightShift } from '@masknet/web3-shared-base' import { EthereumTokenType, formatBalance, FungibleTokenDetailed, useAccount, + useChainId, + useFungibleTokenBalance, useNativeTokenDetailed, useRedPacketConstants, - useFungibleTokenBalance, - useChainId, } from '@masknet/web3-shared-evm' -import { isGreaterThan, isZero, multipliedBy, rightShift } from '@masknet/web3-shared-base' -import { omit } from 'lodash-unified' import { FormControl, InputLabel, MenuItem, MenuProps, Select, TextField } from '@mui/material' -import { makeStyles, useStylesExtends } from '@masknet/theme' import BigNumber from 'bignumber.js' -import { ChangeEvent, useCallback, useMemo, useState, useEffect } from 'react' -import { v4 as uuid } from 'uuid' +import { omit } from 'lodash-unified' +import { ChangeEvent, useCallback, useEffect, useMemo, useState } from 'react' +import { usePickToken } from '@masknet/shared' import { useCurrentIdentity } from '../../../components/DataSource/useActivatedUI' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { useI18N } from '../../../utils' import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel' -import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages' import { RED_PACKET_DEFAULT_SHARES, RED_PACKET_MAX_SHARES, RED_PACKET_MIN_SHARES } from '../constants' import type { RedPacketSettings } from './hooks/useCreateCallback' @@ -96,27 +94,14 @@ export function RedPacketERC20Form(props: RedPacketFormProps) { // #region select token const { value: nativeTokenDetailed } = useNativeTokenDetailed() const [token = nativeTokenDetailed, setToken] = useState(origin?.token) - const [id] = useState(uuid) - const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog( - WalletMessages.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== id) return - setToken(ev.token) - }, - [id], - ), - ) - const onSelectTokenChipClick = useCallback(() => { - setSelectTokenDialog({ - open: true, - uuid: id, + const pickToken = usePickToken() + const onSelectTokenChipClick = useCallback(async () => { + const picked = await pickToken({ disableNativeToken: false, - FungibleTokenListProps: { - selectedTokens: token ? [token.address] : [], - }, + selectedTokens: token ? [token.address] : [], }) - }, [id, token?.address]) + if (picked) setToken(picked) + }, [pickToken, token?.address]) // #endregion // #region packet settings diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketInHistoryList.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketInHistoryList.tsx index 73fe3c6ce61e..3a5495fe28ca 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketInHistoryList.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketInHistoryList.tsx @@ -4,6 +4,7 @@ import classNames from 'classnames' import { Box, ListItem, Typography, Popper, useMediaQuery, Theme } from '@mui/material' import { makeStyles } from '@masknet/theme' import { Trans } from 'react-i18next' +import { omit } from 'lodash-unified' import { RedPacketJSONPayload, RedPacketStatus, RedPacketJSONPayloadFromChain } from '../types' import { TokenIcon } from '@masknet/shared' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' @@ -14,6 +15,7 @@ import { useAccount, isSameAddress, EthereumTokenType, + FungibleTokenDetailed, useFungibleTokenDetailed, useTokenConstants, } from '@masknet/web3-shared-evm' @@ -236,7 +238,7 @@ export function RedPacketInHistoryList(props: RedPacketInHistoryListProps) { const onSendOrRefund = useCallback(async () => { if (canRefund) await refundCallback() - if (canSend) onSelect({ ...history, token: historyToken }) + if (canSend) onSelect(removeUselessSendParams({ ...history, token: historyToken as FungibleTokenDetailed })) }, [onSelect, refundCallback, canRefund, canSend, history]) // #region password lost tips @@ -379,3 +381,10 @@ export function RedPacketInHistoryList(props: RedPacketInHistoryListProps) { ) } + +function removeUselessSendParams(payload: RedPacketJSONPayload): RedPacketJSONPayload { + return { + ...omit(payload, ['block_number', 'claimers']), + token: omit(payload.token, ['logoURI']) as FungibleTokenDetailed, + } +} diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx index 81fbde4d68bc..996f2814db13 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNft.tsx @@ -22,6 +22,7 @@ import { activatedSocialNetworkUI } from '../../../social-network' import { isTwitter } from '../../../social-network-adaptor/twitter.com/base' import { isFacebook } from '../../../social-network-adaptor/facebook.com/base' import { NFTCardStyledAssetPlayer } from '@masknet/shared' +import { openWindow } from '@masknet/shared-base-ui' const useStyles = makeStyles()((theme) => ({ root: { @@ -261,11 +262,7 @@ export function RedPacketNft({ payload }: RedPacketNftProps) { const isClaiming = claimState.type === TransactionStateType.WAIT_FOR_CONFIRMING const openAddressLinkOnExplorer = useCallback(() => { - window.open( - resolveAddressLinkOnExplorer(payload.chainId, payload.contractAddress), - '_blank', - 'noopener noreferrer', - ) + openWindow(resolveAddressLinkOnExplorer(payload.chainId, payload.contractAddress)) }, [payload]) const [sourceType, setSourceType] = useState('') diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNftShare.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNftShare.tsx index 58cec1c5cf91..4afea0220ecd 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNftShare.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedPacketNftShare.tsx @@ -1,6 +1,6 @@ import { makeStyles } from '@masknet/theme' import { Button, DialogActions, DialogContent } from '@mui/material' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { useI18N } from '../../../utils' const useStyles = makeStyles()((theme) => ({ diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedpacketNftConfirmDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedpacketNftConfirmDialog.tsx index 0c7b53582212..cd7890b3b85f 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedpacketNftConfirmDialog.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/RedpacketNftConfirmDialog.tsx @@ -13,10 +13,9 @@ import { isNativeTokenAddress, formatNFT_TokenId, } from '@masknet/web3-shared-evm' -import { NFTCardStyledAssetPlayer } from '@masknet/shared' +import { InjectedDialog, NFTCardStyledAssetPlayer } from '@masknet/shared' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import classNames from 'classnames' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import { Button, Grid, Link, Typography, DialogContent, List, ListItem } from '@mui/material' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx index 21a3ac584938..5a7c8435cfa0 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/SelectNftTokenDialog.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog, NFTCardStyledAssetPlayer } from '@masknet/shared' import { ERC721TokenDetailed, ERC721ContractDetailed, @@ -17,7 +17,6 @@ import { SearchIcon } from '@masknet/icons' import CheckIcon from '@mui/icons-material/Check' import { Trans } from 'react-i18next' import { useUpdate } from 'react-use' -import { NFTCardStyledAssetPlayer } from '@masknet/shared' import { findLastIndex } from 'lodash-unified' import { NFT_RED_PACKET_MAX_SHARES } from '../constants' diff --git a/packages/mask/src/components/shared/assets/lucky_drop.png b/packages/mask/src/plugins/RedPacket/SNSAdaptor/assets/lucky_drop.png similarity index 100% rename from packages/mask/src/components/shared/assets/lucky_drop.png rename to packages/mask/src/plugins/RedPacket/SNSAdaptor/assets/lucky_drop.png diff --git a/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx b/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx index 3b60fb8794f0..e8e633e2390e 100644 --- a/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/RedPacket/SNSAdaptor/index.tsx @@ -20,6 +20,8 @@ import RedPacketDialog from './RedPacketDialog' import { RedPacketInPost } from './RedPacketInPost' import { RedPacketNftInPost } from './RedPacketNftInPost' import { RedPacketIcon, NFTRedPacketIcon } from '@masknet/icons' +import { CrossIsolationMessages } from '@masknet/shared-base' +import { ApplicationEntry } from '@masknet/shared' function Render(props: React.PropsWithChildren<{ name: string }>) { usePluginWrapper(true, { name: props.name }) @@ -90,6 +92,29 @@ const sns: Plugin.SNSAdaptor.Definition = { ), }, }, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + return ( + + CrossIsolationMessages.events.requestComposition.sendToLocal({ + reason: 'timeline', + open: true, + options: { + startupPlugin: base.ID, + }, + }) + } + /> + ) + }, + defaultSortingPriority: 1, + }, + ], } interface ERC20RedpacketBadgeProps { payload: RedPacketJSONPayload diff --git a/packages/mask/src/plugins/RedPacket/base.ts b/packages/mask/src/plugins/RedPacket/base.ts index 25fc85ada510..c7d4bfa4c6c5 100644 --- a/packages/mask/src/plugins/RedPacket/base.ts +++ b/packages/mask/src/plugins/RedPacket/base.ts @@ -13,7 +13,10 @@ export const base: Plugin.Shared.Definition = { publisher: { name: { fallback: 'Mask Network' }, link: 'https://mask.io/' }, enableRequirement: { architecture: { app: true, web: true }, - networks: { type: 'opt-out', networks: {} }, + networks: { + type: 'opt-out', + networks: {}, + }, target: 'stable', web3: { [NetworkPluginID.PLUGIN_EVM]: { @@ -29,6 +32,8 @@ export const base: Plugin.Shared.Definition = { ChainId.Conflux, ], }, + [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] }, + [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] }, }, }, contribution: { diff --git a/packages/mask/src/plugins/Savings/SNSAdaptor/SavingsDialog.tsx b/packages/mask/src/plugins/Savings/SNSAdaptor/SavingsDialog.tsx index dfa2dd5b16be..81c9d8a89829 100644 --- a/packages/mask/src/plugins/Savings/SNSAdaptor/SavingsDialog.tsx +++ b/packages/mask/src/plugins/Savings/SNSAdaptor/SavingsDialog.tsx @@ -2,10 +2,9 @@ import { useState, useMemo } from 'react' import { useAsync } from 'react-use' import { Typography, DialogContent } from '@mui/material' import { ChainId, getChainIdFromNetworkType, useChainId } from '@masknet/web3-shared-evm' -import { isDashboardPage } from '@masknet/shared-base' +import { isDashboardPage, EMPTY_LIST } from '@masknet/shared-base' import { useI18N } from '../../../utils' -import { EMPTY_LIST } from '../../../../utils-pure' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { WalletStatusBox } from '../../../components/shared/WalletStatusBox' import { AllProviderTradeContext } from '../../Trader/trader/useAllProviderTradeContext' import { TargetChainIdContext } from '../../Trader/trader/useTargetChainIdContext' @@ -58,7 +57,7 @@ export function SavingsDialog({ open, onClose }: SavingsDialogProps) { { if (selectedProtocol === null) { onClose?.() diff --git a/packages/mask/src/components/shared/assets/savings.png b/packages/mask/src/plugins/Savings/SNSAdaptor/assets/savings.png similarity index 100% rename from packages/mask/src/components/shared/assets/savings.png rename to packages/mask/src/plugins/Savings/SNSAdaptor/assets/savings.png diff --git a/packages/mask/src/plugins/Savings/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Savings/SNSAdaptor/index.tsx index cbabb7a159eb..500c25fb60b7 100644 --- a/packages/mask/src/plugins/Savings/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Savings/SNSAdaptor/index.tsx @@ -1,9 +1,31 @@ import type { Plugin } from '@masknet/plugin-infra' +import { ApplicationEntry } from '@masknet/shared' +import { useState } from 'react' import { base } from '../base' +import { SavingsDialog } from './SavingsDialog' const sns: Plugin.SNSAdaptor.Definition = { ...base, init(signal) {}, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + const [open, setOpen] = useState(false) + return ( + <> + setOpen(true)} + /> + setOpen(false)} /> + + ) + }, + defaultSortingPriority: 7, + }, + ], } export default sns diff --git a/packages/mask/src/plugins/Savings/base.ts b/packages/mask/src/plugins/Savings/base.ts index 8dc78be6713d..9133a1f2b61e 100644 --- a/packages/mask/src/plugins/Savings/base.ts +++ b/packages/mask/src/plugins/Savings/base.ts @@ -1,4 +1,5 @@ -import type { Plugin } from '@masknet/plugin-infra' +import { Plugin, NetworkPluginID } from '@masknet/plugin-infra' +import { ChainId } from '@masknet/web3-shared-evm' import { SAVINGS_PLUGIN_ID } from './constants' export const base: Plugin.Shared.Definition = { @@ -11,7 +12,26 @@ export const base: Plugin.Shared.Definition = { publisher: { name: { fallback: 'Mask Network' }, link: 'https://mask.io/' }, enableRequirement: { architecture: { app: true, web: true }, - networks: { type: 'opt-out', networks: {} }, + networks: { + type: 'opt-out', + networks: {}, + }, target: 'stable', + web3: { + [NetworkPluginID.PLUGIN_EVM]: { + supportedChainIds: [ + ChainId.Mainnet, + ChainId.BSC, + ChainId.Matic, + ChainId.Arbitrum, + ChainId.xDai, + ChainId.Aurora, + ChainId.Avalanche, + ChainId.Fantom, + ], + }, + [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] }, + [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] }, + }, }, } diff --git a/packages/mask/src/plugins/Snapshot/SNSAdaptor/VoteConfirmDialog.tsx b/packages/mask/src/plugins/Snapshot/SNSAdaptor/VoteConfirmDialog.tsx index fd09775e3b8f..54ab254ce7a0 100644 --- a/packages/mask/src/plugins/Snapshot/SNSAdaptor/VoteConfirmDialog.tsx +++ b/packages/mask/src/plugins/Snapshot/SNSAdaptor/VoteConfirmDialog.tsx @@ -14,7 +14,7 @@ import millify from 'millify' import OpenInNew from '@mui/icons-material/OpenInNew' import { resolveBlockLinkOnExplorer, ChainId } from '@masknet/web3-shared-evm' import { useI18N } from '../../../utils' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { InfoField } from './InformationCard' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/index.tsx index 29c369c34569..170cab6a531c 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/index.tsx @@ -2,8 +2,11 @@ import type { Plugin } from '@masknet/plugin-infra' import { base } from '../base' import { TraderDialog } from './trader/TraderDialog' import { SearchResultInspector } from './trending/SearchResultInspector' +import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import { TagInspector } from './trending/TagInspector' import { enhanceTag } from './cashTag' +import { ApplicationEntry } from '@masknet/shared' +import { PluginTraderMessages } from '../messages' const sns: Plugin.SNSAdaptor.Definition = { ...base, @@ -18,6 +21,23 @@ const sns: Plugin.SNSAdaptor.Definition = { ) }, enhanceTag, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + const { openDialog } = useRemoteControlledDialog(PluginTraderMessages.swapDialogUpdated) + + return ( + + ) + }, + defaultSortingPriority: 8, + }, + ], } export default sns diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/ConfirmDialog.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/ConfirmDialog.tsx index d96df5edf97c..b66e38e1452c 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/ConfirmDialog.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/ConfirmDialog.tsx @@ -3,9 +3,9 @@ import { ExternalLink } from 'react-feather' import BigNumber from 'bignumber.js' import { Alert, Box, Button, DialogActions, DialogContent, Link, Typography } from '@mui/material' import { makeStyles, MaskColorVar, useStylesExtends } from '@masknet/theme' -import { FormattedAddress, FormattedBalance, TokenIcon } from '@masknet/shared' +import { useValueRef } from '@masknet/shared-base-ui' +import { InjectedDialog, FormattedAddress, FormattedBalance, TokenIcon } from '@masknet/shared' import type { TradeComputed } from '../../types' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import type { FungibleTokenDetailed, Wallet } from '@masknet/web3-shared-evm' import { createNativeToken, @@ -19,7 +19,6 @@ import { useI18N } from '../../../../utils' import { InfoIcon, RetweetIcon, CramIcon } from '@masknet/icons' import { isZero, multipliedBy } from '@masknet/web3-shared-base' import { isDashboardPage } from '@masknet/shared-base' -import { useValueRef } from '@masknet/shared-base-ui' import { TargetChainIdContext } from '../../trader/useTargetChainIdContext' import { currentSlippageSettings } from '../../settings' import { useNativeTokenPrice } from '../../../Wallet/hooks/useTokenPrice' diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/SettingsDialog.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/SettingsDialog.tsx index 919cd78c7776..95d5176f9000 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/SettingsDialog.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/SettingsDialog.tsx @@ -1,11 +1,11 @@ import { useState, useCallback, useEffect } from 'react' import { Accordion, AccordionDetails, AccordionSummary, Alert, DialogContent, Typography } from '@mui/material' import { makeStyles, MaskColorVar, useStylesExtends } from '@masknet/theme' +import { InjectedDialog } from '@masknet/shared' import { useValueRef, useRemoteControlledDialog } from '@masknet/shared-base-ui' import { useI18N } from '../../../../utils' import { SlippageSlider } from './SlippageSlider' import { currentSlippageSettings } from '../../settings' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { PluginTraderMessages } from '../../messages' import { ExpandMore } from '@mui/icons-material' import { Gas1559Settings } from './Gas1559Settings' diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx index c3accaad1d32..0e19cbd6c91e 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/Trader.tsx @@ -17,6 +17,7 @@ import { UST, } from '@masknet/web3-shared-evm' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { usePickToken } from '@masknet/shared' import { delay } from '@dimensiondev/kit' import { useGasConfig } from './hooks/useGasConfig' import type { Coin } from '../../types' @@ -24,7 +25,7 @@ import { TokenPanelType, TradeInfo } from '../../types' import { useI18N } from '../../../../utils' import { TradeForm } from './TradeForm' import { AllProviderTradeActionType, AllProviderTradeContext } from '../../trader/useAllProviderTradeContext' -import { SelectTokenDialogEvent, WalletMessages } from '@masknet/plugin-wallet' +import { WalletMessages } from '@masknet/plugin-wallet' import { useUnmount, useUpdateEffect } from 'react-use' import { isTwitter } from '../../../../social-network-adaptor/twitter.com/base' import { activatedSocialNetworkUI } from '../../../../social-network' @@ -204,43 +205,24 @@ export function Trader(props: TraderProps) { // #region select token const excludeTokens = [inputToken, outputToken].filter(Boolean).map((x) => x?.address) as string[] - const [focusedTokenPanelType, setFocusedTokenPanelType] = useState(TokenPanelType.Input) - const { setDialog: setSelectTokenDialog } = useRemoteControlledDialog( - WalletMessages.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== String(focusedTokenPanelType)) return + const pickToken = usePickToken() + const onTokenChipClick = useCallback( + async (panelType: TokenPanelType) => { + const picked = await pickToken({ + chainId, + disableNativeToken: false, + selectedTokens: excludeTokens, + }) + if (picked) { dispatchTradeStore({ type: - focusedTokenPanelType === TokenPanelType.Input + panelType === TokenPanelType.Input ? AllProviderTradeActionType.UPDATE_INPUT_TOKEN : AllProviderTradeActionType.UPDATE_OUTPUT_TOKEN, - token: ev.token, + token: picked, }) - if (focusedTokenPanelType === TokenPanelType.Input) { - dispatchTradeStore({ - type: AllProviderTradeActionType.UPDATE_INPUT_AMOUNT, - amount: '', - }) - } - }, - [dispatchTradeStore, focusedTokenPanelType], - ), - ) - - const onTokenChipClick = useCallback( - (type: TokenPanelType) => { - setFocusedTokenPanelType(type) - setSelectTokenDialog({ - chainId, - open: true, - uuid: String(type), - disableNativeToken: false, - FungibleTokenListProps: { - selectedTokens: excludeTokens, - }, - }) + } }, [excludeTokens.join(), chainId], ) diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/TraderDialog.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/TraderDialog.tsx index 9abcfc2213ae..e3ee9f2853f4 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trader/TraderDialog.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trader/TraderDialog.tsx @@ -1,9 +1,9 @@ import { useEffect, useState } from 'react' -import { usePluginIDContext, useActivatedPlugin, PluginId } from '@masknet/plugin-infra' +import { useCurrentWeb3NetworkPluginID, useActivatedPlugin, PluginId } from '@masknet/plugin-infra' import { ChainId, useChainId, useChainIdValid } from '@masknet/web3-shared-evm' import { DialogContent } from '@mui/material' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { AllProviderTradeContext } from '../../trader/useAllProviderTradeContext' import { TargetChainIdContext } from '../../trader/useTargetChainIdContext' import { PluginTraderMessages } from '../../messages' @@ -64,7 +64,7 @@ interface TraderDialogProps { export function TraderDialog({ open, onClose }: TraderDialogProps) { const isDashboard = isDashboardPage() - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() const traderDefinition = useActivatedPlugin(PluginId.Trader, 'any') const chainIdList = traderDefinition?.enableRequirement.web3?.[pluginID]?.supportedChainIds ?? [] const { t } = useI18N() diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/CoinMetadataTags.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/CoinMetadataTags.tsx index 15addd3a130a..11577896ffa5 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/CoinMetadataTags.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/CoinMetadataTags.tsx @@ -1,7 +1,7 @@ import { Chip, DialogContent } from '@mui/material' import { makeStyles } from '@masknet/theme' import { useCallback, useState } from 'react' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { Linking } from './Linking' const useStyles = makeStyles()((theme) => ({ diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/PriceChart.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/PriceChart.tsx index 3c15c3c6464b..bd58072d5ddc 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/PriceChart.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/PriceChart.tsx @@ -7,6 +7,7 @@ import { useI18N } from '../../../../utils' import type { Coin, Currency, Stat } from '../../types' import { useDimension, Dimension } from '../../../hooks/useDimension' import { usePriceLineChart } from '../../../hooks/usePriceLineChart' +import { openWindow } from '@masknet/shared-base-ui' const DEFAULT_DIMENSION: Dimension = { top: 32, @@ -110,9 +111,7 @@ export function PriceChart(props: PriceChartProps) { viewBox={`0 0 ${dimension.width} ${dimension.height}`} preserveAspectRatio="xMidYMid meet" onClick={() => { - props.stats.length && - props.coin?.platform_url && - window.open(props.coin.platform_url, '_blank', 'noopener noreferrer') + props.stats.length && openWindow(props.coin?.platform_url) }} /> diff --git a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/TrendingPopper.tsx b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/TrendingPopper.tsx index 3e525596bedc..27642f95a731 100644 --- a/packages/mask/src/plugins/Trader/SNSAdaptor/trending/TrendingPopper.tsx +++ b/packages/mask/src/plugins/Trader/SNSAdaptor/trending/TrendingPopper.tsx @@ -13,6 +13,8 @@ export interface TrendingPopperProps { PopperProps?: Partial } +const TIMEOUT = 1500 + export function TrendingPopper(props: TrendingPopperProps) { const popperRef = useRef<{ update(): void } | null>(null) const [freezed, setFreezed] = useState(false) // disable any click @@ -21,13 +23,14 @@ export function TrendingPopper(props: TrendingPopperProps) { const [type, setType] = useState() const [anchorEl, setAnchorEl] = useState(null) const [availableDataProviders, setAvailableDataProviders] = useState([]) + const popper = useRef(null) + const [mouseIn, setMouseIn] = useState(false) // #region select token and provider dialog could be open by trending view const onFreezed = useCallback((ev) => setFreezed(ev.open), []) useRemoteControlledDialog(WalletMessages.events.transactionDialogUpdated, onFreezed) useRemoteControlledDialog(WalletMessages.events.walletStatusDialogUpdated, onFreezed) useRemoteControlledDialog(WalletMessages.events.selectProviderDialogUpdated, onFreezed) - useRemoteControlledDialog(WalletMessages.events.selectTokenDialogUpdated, onFreezed) useRemoteControlledDialog(WalletMessages.events.selectWalletDialogUpdated, onFreezed) useRemoteControlledDialog(WalletMessages.events.walletConnectQRCodeDialogUpdated, onFreezed) useRemoteControlledDialog(PluginTransakMessages.buyTokenDialogUpdated, onFreezed) @@ -67,14 +70,12 @@ export function TrendingPopper(props: TrendingPopperProps) { // close popper if scroll out of visual screen const position = useWindowScroll() useEffect(() => { - if (!anchorEl) return - const { top } = anchorEl.getBoundingClientRect() - if ( - top < 0 || // out off top bound - top > document.documentElement.clientHeight // out off bottom bound - ) + if (!popper.current) return + const { top, height } = popper.current?.getBoundingClientRect() + if ((top < 0 && -1 * top > height) || top > document.documentElement.clientHeight) + // out off bottom bound setAnchorEl(null) - }, [anchorEl, Math.floor(position.y / 50)]) + }, [popper, Math.floor(position.y / 50)]) // #endregion if (locked) return null @@ -85,6 +86,7 @@ export function TrendingPopper(props: TrendingPopperProps) { if (!freezed) setAnchorEl(null) }}> }, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + const { openDialog } = useRemoteControlledDialog(PluginTransakMessages.buyTokenDialogUpdated) + + return ( + + ) + }, + defaultSortingPriority: 9, + }, + ], } export default sns diff --git a/packages/mask/src/components/shared/assets/fiat_ramp.png b/packages/mask/src/plugins/Transak/assets/fiat_ramp.png similarity index 100% rename from packages/mask/src/components/shared/assets/fiat_ramp.png rename to packages/mask/src/plugins/Transak/assets/fiat_ramp.png diff --git a/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/SelectRecipientsUnlockDialog.tsx b/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/SelectRecipientsUnlockDialog.tsx index 2c204ced01da..ce26d0c2da63 100644 --- a/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/SelectRecipientsUnlockDialog.tsx +++ b/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/SelectRecipientsUnlockDialog.tsx @@ -1,4 +1,4 @@ -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import Fuse from 'fuse.js' import { InputBase, diff --git a/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/UnlockProtocolDialog.tsx b/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/UnlockProtocolDialog.tsx index 6f33270650f5..d395b446a59a 100644 --- a/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/UnlockProtocolDialog.tsx +++ b/packages/mask/src/plugins/UnlockProtocol/SNSAdaptor/UnlockProtocolDialog.tsx @@ -1,7 +1,7 @@ import { useAccount, useChainId } from '@masknet/web3-shared-evm' import { DialogActions, DialogContent, DialogProps, Chip, Button, InputBase } from '@mui/material' import { useEffect, useState } from 'react' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { useI18N } from '../../../utils' import { pluginMetaKey } from '../constants' import type { UnlockLocks } from '../types' diff --git a/packages/mask/src/plugins/UnlockProtocol/base.ts b/packages/mask/src/plugins/UnlockProtocol/base.ts index b6be16f2bda6..7b29c145ed32 100644 --- a/packages/mask/src/plugins/UnlockProtocol/base.ts +++ b/packages/mask/src/plugins/UnlockProtocol/base.ts @@ -16,6 +16,8 @@ export const base: Plugin.Shared.Definition = { [NetworkPluginID.PLUGIN_EVM]: { supportedChainIds: [ChainId.Mainnet, ChainId.xDai, ChainId.Matic, ChainId.Rinkeby], }, + [NetworkPluginID.PLUGIN_FLOW]: { supportedChainIds: [] }, + [NetworkPluginID.PLUGIN_SOLANA]: { supportedChainIds: [] }, }, }, contribution: { diff --git a/packages/mask/src/plugins/Wallet/Dashboard/index.tsx b/packages/mask/src/plugins/Wallet/Dashboard/index.tsx index 9a70a55c4cc2..780608eec12f 100644 --- a/packages/mask/src/plugins/Wallet/Dashboard/index.tsx +++ b/packages/mask/src/plugins/Wallet/Dashboard/index.tsx @@ -1,6 +1,5 @@ import type { Plugin } from '@masknet/plugin-infra' import { base } from '@masknet/plugin-wallet' -import { SelectTokenDialog } from '../SNSAdaptor/SelectTokenDialog' import { SelectNftContractDialog } from '../SNSAdaptor/SelectNftContractDialog' import { SelectProviderDialog } from '../SNSAdaptor/SelectProviderDialog' import { SelectWalletDialog } from '../SNSAdaptor/SelectWalletDialog' @@ -23,7 +22,6 @@ const dashboard: Plugin.Dashboard.Definition = { - @@ -31,7 +29,6 @@ const dashboard: Plugin.Dashboard.Definition = { - diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx index becd7b16a80d..866f5eae1503 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/ConnectWalletDialog/index.tsx @@ -13,7 +13,7 @@ import { resolveProviderName, } from '@masknet/web3-shared-evm' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { WalletMessages, WalletRPC } from '../../messages' import { ConnectionProgress } from './ConnectionProgress' import Services from '../../../../extension/service' diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/GasSettingDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/GasSettingDialog/index.tsx index 783e51eba5c8..62f0ba6d1d5c 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/GasSettingDialog/index.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/GasSettingDialog/index.tsx @@ -3,8 +3,8 @@ import { DialogContent } from '@mui/material' import { WalletMessages } from '@masknet/plugin-wallet' import { makeStyles } from '@masknet/theme' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { GasOption } from '@masknet/web3-shared-evm' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { useI18N } from '../../../../utils' import { GasSetting } from './GasSetting' diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/RenameWalletDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/RenameWalletDialog.tsx index 05e5c2826060..804abf70cac7 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/RenameWalletDialog.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/RenameWalletDialog.tsx @@ -3,10 +3,9 @@ import { Button, DialogContent, DialogActions, TextField } from '@mui/material' import { makeStyles } from '@masknet/theme' import type { Wallet } from '@masknet/web3-shared-evm' import { WalletMessages, WalletRPC } from '../messages' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' -import { useI18N } from '../../../utils/i18n-next-ui' -import { useSnackbarCallback } from '@masknet/shared' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog, useSnackbarCallback } from '@masknet/shared' +import { useI18N } from '../../../utils/i18n-next-ui' const useStyles = makeStyles()((theme) => ({ content: { diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/RestoreLegacyWalletDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/RestoreLegacyWalletDialog/index.tsx index 5c3ef92a33fc..25d21fd38af3 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/RestoreLegacyWalletDialog/index.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/RestoreLegacyWalletDialog/index.tsx @@ -1,12 +1,12 @@ import { useCallback, useEffect } from 'react' import { useAsyncRetry } from 'react-use' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { Box } from '@mui/system' import { makeStyles } from '@masknet/theme' import { ProviderType } from '@masknet/web3-shared-evm' import { Button, DialogContent, Typography } from '@mui/material' import { PopupRoutes } from '@masknet/shared-base' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { WalletMessages, WalletRPC } from '../../messages' import { useI18N } from '../../../../utils' diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/RiskWarningDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/RiskWarningDialog/index.tsx index 28ff99b7d217..0c01a034f884 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/RiskWarningDialog/index.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/RiskWarningDialog/index.tsx @@ -2,11 +2,11 @@ import { useCallback } from 'react' import classnames from 'classnames' import { Trans } from 'react-i18next' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { formatEthereumAddress, useAccount } from '@masknet/web3-shared-evm' import PriorityHighIcon from '@mui/icons-material/PriorityHigh' import { Avatar, Button, DialogActions, DialogContent, Paper, Typography } from '@mui/material' import { getMaskColor, makeStyles, useCustomSnackbar } from '@masknet/theme' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { useI18N, useMatchXS } from '../../../../utils' import { WalletMessages, WalletRPC } from '../../messages' import { ActionButtonPromise } from '../../../../extension/options-page/DashboardComponents/ActionButton' diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx index 2572cabda523..a0fb39bda34e 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectNftContractDialog.tsx @@ -13,10 +13,10 @@ import { useERC721ContractDetailed, useERC721Tokens, } from '@masknet/web3-shared-evm' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { WalletMessages } from '../messages' import { useI18N } from '../../../utils' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' import { EthereumAddress } from 'wallet.ts' import { SearchInput } from '../../../extension/options-page/DashboardComponents/SearchInput' import OpenInNewIcon from '@mui/icons-material/OpenInNew' diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx index a76885ba440c..f3dadd8190a5 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectProviderDialog/index.tsx @@ -1,6 +1,8 @@ import { useCallback, useEffect, useState } from 'react' import { makeStyles } from '@masknet/theme' import { DialogContent } from '@mui/material' +import { useRemoteControlledDialog, useValueRef } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { getRegisteredWeb3Networks, getRegisteredWeb3Providers, @@ -10,10 +12,8 @@ import { useWeb3UI, } from '@masknet/plugin-infra' import { isDashboardPage } from '@masknet/shared-base' -import { useRemoteControlledDialog, useValueRef } from '@masknet/shared-base-ui' import { useI18N } from '../../../../utils/i18n-next-ui' import { WalletMessages } from '../../messages' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { hasNativeAPI, nativeAPI } from '../../../../../shared/native-rpc' import { PluginProviderRender } from './PluginProviderRender' import { pluginIDSettings } from '../../../../settings/settings' @@ -51,7 +51,7 @@ export function SelectProviderDialog(props: SelectProviderDialogProps) { const pluginID = useValueRef(pluginIDSettings) as NetworkPluginID const network = useNetworkDescriptor() const [undeterminedPluginID, setUndeterminedPluginID] = useState(pluginID) - const [undeterminedNetworkID, setUndeterminedNetworkID] = useState(network?.ID) + const [undeterminedNetworkID, setUndeterminedNetworkID] = useState(network?.ID ?? NetworkPluginID.PLUGIN_EVM) const undeterminedNetwork = useNetworkDescriptor(undeterminedNetworkID, undeterminedPluginID) const networkType = useNetworkType(undeterminedPluginID) diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectTokenDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectTokenDialog.tsx deleted file mode 100644 index 73d036761b4e..000000000000 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectTokenDialog.tsx +++ /dev/null @@ -1,126 +0,0 @@ -import { useCallback, useEffect, useState } from 'react' -import { DialogContent, Theme, useMediaQuery } from '@mui/material' -import { makeStyles, MaskColorVar, useStylesExtends } from '@masknet/theme' -import { FungibleTokenDetailed, useChainId, ChainId, useTokenConstants } from '@masknet/web3-shared-evm' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' -import { WalletMessages } from '../../Wallet/messages' -import { useI18N } from '../../../utils' -import { ERC20TokenList, ERC20TokenListProps } from '@masknet/shared' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { delay } from '@dimensiondev/kit' -import { MINDS_ID } from '../../../social-network-adaptor/minds.com/base' -import { activatedSocialNetworkUI } from '../../../social-network' - -interface StyleProps { - snsId: string - isDashboard: boolean -} - -const useStyles = makeStyles()((theme, { snsId, isDashboard }) => ({ - content: { - ...(snsId === MINDS_ID ? { minWidth: 552 } : {}), - padding: theme.spacing(3), - paddingTop: isDashboard ? 0 : theme.spacing(2.8), - }, - list: { - scrollbarWidth: 'none', - '&::-webkit-scrollbar': { - display: 'none', - }, - }, - placeholder: { - textAlign: 'center', - height: 288, - paddingTop: theme.spacing(14), - boxSizing: 'border-box', - }, - search: { - backgroundColor: 'transparent !important', - border: `solid 1px ${MaskColorVar.twitterBorderLine}`, - }, -})) - -export interface SelectTokenDialogProps extends withClasses {} - -export function SelectTokenDialog(props: SelectTokenDialogProps) { - const { t } = useI18N() - const isDashboard = location.href.includes('dashboard.html') - const classes = useStylesExtends( - useStyles({ snsId: activatedSocialNetworkUI.networkIdentifier, isDashboard }), - props, - ) - const chainId = useChainId() - const { NATIVE_TOKEN_ADDRESS } = useTokenConstants(chainId) - const isMdScreen = useMediaQuery((theme) => theme.breakpoints.down('md')) - - const [id, setId] = useState('') - const [targetChainId, setChainId] = useState(chainId) - const [rowSize, setRowSize] = useState(54) - - const [title, setTitle] = useState(t('plugin_wallet_select_a_token')) - const [disableNativeToken, setDisableNativeToken] = useState(true) - const [disableSearchBar, setDisableSearchBar] = useState(false) - const [FungibleTokenListProps, setFungibleTokenListProps] = useState(null) - - useEffect(() => { - try { - const fontSize = Number.parseFloat(getComputedStyle(document.documentElement).fontSize) - setRowSize(fontSize * 4) - } catch { - setRowSize(60) - } - }, []) - - const { open, setDialog } = useRemoteControlledDialog(WalletMessages.events.selectTokenDialogUpdated, (ev) => { - if (!ev.open) return - setTitle(ev.title ?? t('plugin_wallet_select_a_token')) - setId(ev.uuid) - setDisableNativeToken(ev.disableNativeToken ?? true) - setDisableSearchBar(ev.disableSearchBar ?? false) - setFungibleTokenListProps(ev.FungibleTokenListProps ?? null) - setChainId(ev.chainId ?? undefined) - }) - const onSubmit = useCallback( - async (token: FungibleTokenDetailed) => { - setDialog({ - open: false, - uuid: id, - token, - }) - await delay(300) - }, - [id, setDialog], - ) - const onClose = useCallback(async () => { - setDialog({ - open: false, - uuid: id, - }) - await delay(300) - }, [id, setDialog]) - - return ( - - - - - - ) -} diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx index c661e2bc02d9..c02c08b27c72 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/SelectWalletDialog.tsx @@ -3,12 +3,12 @@ import { Button, DialogActions, DialogContent } from '@mui/material' import { makeStyles, useStylesExtends } from '@masknet/theme' import { ProviderType, useWallets, useWallet, NetworkType } from '@masknet/web3-shared-evm' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' import { DashboardRoutes } from '@masknet/shared-base' import { useI18N } from '../../../utils' import { WalletMessages, WalletRPC } from '../messages' import { WalletInList } from '../../../components/shared/SelectWallet/WalletInList' import Services from '../../../extension/service' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import { delay } from '@dimensiondev/kit' const useStyles = makeStyles()({ diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/TransactionDialog.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/TransactionDialog.tsx index 3d0d3a010904..4e819e6cdac7 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/TransactionDialog.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/TransactionDialog.tsx @@ -9,9 +9,9 @@ import { TransactionStateType, resolveTransactionLinkOnExplorer, } from '@masknet/web3-shared-evm' -import { useI18N } from '../../../utils' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' +import { useI18N } from '../../../utils' import { WalletMessages } from '../messages' import { activatedSocialNetworkUI } from '../../../social-network' diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/SafariPlatform.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/SafariPlatform.tsx index f560d8a699b3..7076453818c2 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/SafariPlatform.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/SafariPlatform.tsx @@ -5,6 +5,8 @@ import { createElement } from 'react' import { useI18N } from '../../../../utils' import { Provider } from '../Provider' import { IMTokenIcon, MetaMaskIcon, RainbowIcon, TrustIcon } from './Icons' +import urlcat from 'urlcat' +import { openWindow } from '@masknet/shared-base-ui' const useStyles = makeStyles()({ container: { display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center' }, @@ -27,11 +29,7 @@ const providers: WalletProvider[] = [ export const SafariPlatform: React.FC<{ uri: string }> = ({ uri }) => { const { t } = useI18N() const { classes } = useStyles() - const makeConnect = (link: string) => () => { - const url = new URL(link) - url.searchParams.set('uri', uri) - open(url.toString()) - } + const makeConnect = (link: string) => () => openWindow(urlcat(link, { uri })) const descriptionMapping: Record = { MetaMask: t('plugin_wallet_connect_safari_metamask'), Rainbow: t('plugin_wallet_connect_safari_rainbow'), diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/index.tsx index f84c6c711972..7de9b3a09cc9 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/index.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletConnectQRCodeDialog/index.tsx @@ -1,11 +1,11 @@ import { useState } from 'react' import { Button, DialogActions, DialogContent } from '@mui/material' import { makeStyles } from '@masknet/theme' -import { useI18N } from '../../../../utils' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' +import { useI18N } from '../../../../utils' import { WalletMessages } from '../../messages' import Services from '../../../../extension/service' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { SafariPlatform } from './SafariPlatform' import { FirefoxPlatform } from './FirefoxPlatform' import { QRCodeModel } from './QRCodeModel' diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletStatusDialog/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletStatusDialog/index.tsx index 90227a1f8b07..2e2400361dbb 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletStatusDialog/index.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/WalletStatusDialog/index.tsx @@ -2,13 +2,13 @@ import { useCallback } from 'react' import { DialogActions, DialogContent, Typography } from '@mui/material' import ErrorIcon from '@mui/icons-material/Error' import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { InjectedDialog } from '@masknet/shared' +import { CrossIsolationMessages } from '@masknet/shared-base' import { useChainIdValid } from '@masknet/web3-shared-evm' import { makeStyles } from '@masknet/theme' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' import { WalletStatusBox } from '../../../../components/shared/WalletStatusBox' import { useI18N } from '../../../../utils' import { WalletMessages } from '../../messages' -import { MaskMessages } from '../../../../utils/messages' import { ApplicationBoard } from '../../../../components/shared/ApplicationBoard' const useStyles = makeStyles()((theme) => ({ @@ -51,7 +51,7 @@ export function WalletStatusDialog(props: WalletStatusDialogProps) { const closeDialog = useCallback(() => { _closeDialog() - MaskMessages.events.requestComposition.sendToLocal({ + CrossIsolationMessages.events.requestComposition.sendToLocal({ reason: 'timeline', open: false, }) diff --git a/packages/mask/src/plugins/Wallet/SNSAdaptor/index.tsx b/packages/mask/src/plugins/Wallet/SNSAdaptor/index.tsx index 954cdf2aa90f..25751a58a88b 100644 --- a/packages/mask/src/plugins/Wallet/SNSAdaptor/index.tsx +++ b/packages/mask/src/plugins/Wallet/SNSAdaptor/index.tsx @@ -1,6 +1,5 @@ import type { Plugin } from '@masknet/plugin-infra' import { base } from '@masknet/plugin-wallet' -import { SelectTokenDialog } from './SelectTokenDialog' import { SelectNftContractDialog } from './SelectNftContractDialog' import { SelectProviderDialog } from './SelectProviderDialog' import { SelectWalletDialog } from './SelectWalletDialog' @@ -23,7 +22,6 @@ const sns: Plugin.SNSAdaptor.Definition = { - diff --git a/packages/mask/src/plugins/Wallet/messages.ts b/packages/mask/src/plugins/Wallet/messages.ts index ff23a6a24d43..3d03a916363a 100644 --- a/packages/mask/src/plugins/Wallet/messages.ts +++ b/packages/mask/src/plugins/Wallet/messages.ts @@ -3,7 +3,7 @@ import { PLUGIN_ID, WalletMessages } from '@masknet/plugin-wallet' import type { _AsyncVersionOf } from 'async-call-rpc' export { WalletMessages } from '@masknet/plugin-wallet' -export type { SelectTokenDialogEvent, SelectNftContractDialogEvent } from '@masknet/plugin-wallet' +export type { SelectNftContractDialogEvent } from '@masknet/plugin-wallet' export const WalletRPC: _AsyncVersionOf = createPluginRPC( PLUGIN_ID, () => import('./services') as any, diff --git a/packages/mask/src/plugins/Wallet/services/assets.ts b/packages/mask/src/plugins/Wallet/services/assets.ts index 8cb0df11296f..1d1c8c2c3915 100644 --- a/packages/mask/src/plugins/Wallet/services/assets.ts +++ b/packages/mask/src/plugins/Wallet/services/assets.ts @@ -143,6 +143,7 @@ export async function getAssetsList( function formatAssetsFromDebank(data: WalletTokenRecord[], network?: NetworkType) { return data + .filter((x) => getChainIdFromName(x.chain)) .filter((x) => !network || getChainIdFromName(x.chain) === getChainIdFromNetworkType(network)) .filter((x) => x.is_verified) .map((y): Asset => { diff --git a/packages/mask/src/plugins/dHEDGE/UI/InvestDialog.tsx b/packages/mask/src/plugins/dHEDGE/UI/InvestDialog.tsx index 96e30a9465bd..88cf755db47f 100644 --- a/packages/mask/src/plugins/dHEDGE/UI/InvestDialog.tsx +++ b/packages/mask/src/plugins/dHEDGE/UI/InvestDialog.tsx @@ -1,3 +1,7 @@ +import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { usePickToken, InjectedDialog } from '@masknet/shared' +import { makeStyles } from '@masknet/theme' +import { isZero, rightShift } from '@masknet/web3-shared-base' import { EthereumTokenType, formatBalance, @@ -6,24 +10,20 @@ import { useAccount, useFungibleTokenBalance, } from '@masknet/web3-shared-evm' -import { isZero, rightShift } from '@masknet/web3-shared-base' import { DialogContent } from '@mui/material' -import { makeStyles } from '@masknet/theme' import { useCallback, useEffect, useMemo, useState } from 'react' import { v4 as uuid } from 'uuid' -import { InjectedDialog } from '../../../components/shared/InjectedDialog' import ActionButton from '../../../extension/options-page/DashboardComponents/ActionButton' import { activatedSocialNetworkUI } from '../../../social-network' -import { isTwitter } from '../../../social-network-adaptor/twitter.com/base' import { isFacebook } from '../../../social-network-adaptor/facebook.com/base' -import { useRemoteControlledDialog } from '@masknet/shared-base-ui' +import { isTwitter } from '../../../social-network-adaptor/twitter.com/base' import { useI18N } from '../../../utils/i18n-next-ui' import { EthereumERC20TokenApprovedBoundary } from '../../../web3/UI/EthereumERC20TokenApprovedBoundary' import { EthereumWalletConnectedBoundary } from '../../../web3/UI/EthereumWalletConnectedBoundary' import { TokenAmountPanel } from '../../../web3/UI/TokenAmountPanel' import { PluginTraderMessages } from '../../Trader/messages' import type { Coin } from '../../Trader/types' -import { SelectTokenDialogEvent, WalletMessages } from '../../Wallet/messages' +import { WalletMessages } from '../../Wallet/messages' import { useInvestCallback } from '../hooks/useInvestCallback' import { PluginDHedgeMessages } from '../messages' import type { Pool } from '../types' @@ -54,7 +54,7 @@ const useStyles = makeStyles()((theme) => ({ export function InvestDialog() { const { t } = useI18N() const { classes } = useStyles() - const [id] = useState(uuid()) + const [id] = useState(uuid) const [pool, setPool] = useState() const [token, setToken] = useState() const [allowedTokens, setAllowedTokens] = useState() @@ -77,26 +77,14 @@ export function InvestDialog() { // #endregion // #region select token - const { setDialog: setSelectTokenDialogOpen } = useRemoteControlledDialog( - WalletMessages.events.selectTokenDialogUpdated, - useCallback( - (ev: SelectTokenDialogEvent) => { - if (ev.open || !ev.token || ev.uuid !== id) return - setToken(ev.token) - }, - [id], - ), - ) - const onSelectTokenChipClick = useCallback(() => { - setSelectTokenDialogOpen({ - open: true, - uuid: id, + const pickToken = usePickToken() + const onSelectTokenChipClick = useCallback(async () => { + const picked = await pickToken({ disableNativeToken: true, - FungibleTokenListProps: { - whitelist: allowedTokens, - }, + whitelist: allowedTokens, }) - }, [id, token?.address, allowedTokens]) + if (picked) setToken(picked) + }, [pickToken, token?.address, allowedTokens]) // #endregion // #region amount diff --git a/packages/mask/src/social-network-adaptor/facebook.com/automation/openComposeBox.ts b/packages/mask/src/social-network-adaptor/facebook.com/automation/openComposeBox.ts index 2bd58acc75d6..1e789aee829b 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/automation/openComposeBox.ts +++ b/packages/mask/src/social-network-adaptor/facebook.com/automation/openComposeBox.ts @@ -1,5 +1,5 @@ import { LiveSelector } from '@dimensiondev/holoflows-kit' -import { MaskMessages, CompositionRequest } from '../../../utils/messages' +import { CrossIsolationMessages, CompositionRequest } from '@masknet/shared-base' import { i18n } from '../../../../shared-ui/locales_legacy' import { makeTypedMessageText, SerializableTypedMessages } from '@masknet/typed-message' import { delay, waitDocumentReadyState } from '@dimensiondev/kit' @@ -56,7 +56,7 @@ export async function taskOpenComposeBoxFacebook( } await delay(2000) - MaskMessages.events.requestComposition.sendToLocal({ + CrossIsolationMessages.events.requestComposition.sendToLocal({ reason: 'popup', open: true, content: typeof content === 'string' ? makeTypedMessageText(content) : content, diff --git a/packages/mask/src/social-network-adaptor/facebook.com/injection/Composition.tsx b/packages/mask/src/social-network-adaptor/facebook.com/injection/Composition.tsx index 7ebdf51ce00b..1ee7a87a0858 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/injection/Composition.tsx +++ b/packages/mask/src/social-network-adaptor/facebook.com/injection/Composition.tsx @@ -1,10 +1,10 @@ import { useCallback } from 'react' import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' +import { CrossIsolationMessages } from '@masknet/shared-base' import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot' import { Composition } from '../../../components/CompositionDialog/Composition' import { isMobileFacebook } from '../utils/isMobile' import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint' -import { MaskMessages } from '../../../utils/messages' import { startWatch } from '../../../utils/watcher' import { taskOpenComposeBoxFacebook } from '../automation/openComposeBox' import { makeStyles } from '@masknet/theme' @@ -53,7 +53,7 @@ export function injectCompositionFacebook(signal: AbortSignal) { signal.addEventListener( 'abort', - MaskMessages.events.requestComposition.on((data) => { + CrossIsolationMessages.events.requestComposition.on((data) => { if (data.reason === 'popup') return if (data.open === false) return taskOpenComposeBoxFacebook(data.content || '', data.options) @@ -63,7 +63,7 @@ export function injectCompositionFacebook(signal: AbortSignal) { function UI() { const { classes } = useStyles() const onHintButtonClicked = useCallback( - () => MaskMessages.events.requestComposition.sendToLocal({ reason: 'popup', open: true }), + () => CrossIsolationMessages.events.requestComposition.sendToLocal({ reason: 'popup', open: true }), [], ) return ( diff --git a/packages/mask/src/social-network-adaptor/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx b/packages/mask/src/social-network-adaptor/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx index 1280efe19771..fc0caf2bf5c7 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx +++ b/packages/mask/src/social-network-adaptor/facebook.com/injection/NFT/NFTAvatarEditProfile.tsx @@ -1,8 +1,7 @@ import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' import { searchFacebookEditProfileSelector, searchFacebookProfileSettingButtonSelector } from '../../utils/selector' -import { createReactRootShadowed, startWatch } from '../../../../utils' +import { createReactRootShadowed, startWatch, useLocationChange } from '../../../../utils' import { useLayoutEffect, useState } from 'react' -import { useLocationChange } from '../../../../utils/hooks/useLocationChange' import { NFTAvatarButton } from '../../../../plugins/Avatar/SNSAdaptor/NFTAvatarButton' import { makeStyles } from '@masknet/theme' diff --git a/packages/mask/src/social-network-adaptor/facebook.com/shared.ts b/packages/mask/src/social-network-adaptor/facebook.com/shared.ts index 18a3e5fa00c0..305dcbc30890 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/facebook.com/shared.ts @@ -4,6 +4,8 @@ import { getPostUrlAtFacebook, isValidFacebookUsername } from './utils/parse-use import { PostIdentifier, ProfileIdentifier } from '@masknet/shared-base' import { deconstructPayload } from '../../utils' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' +import { openWindow } from '@masknet/shared-base-ui' +import urlcat from 'urlcat' const getPostURL = (post: PostIdentifier): URL | null => { if (post.identifier instanceof ProfileIdentifier) @@ -19,14 +21,14 @@ export const facebookShared: SocialNetwork.Shared & SocialNetwork.Base = { textPayloadPostProcessor: undefined, getPostURL, share(message) { - const url = this.getShareLinkURL!(message) - window.open(url, '_blank', 'noopener noreferrer') + openWindow(this.getShareLinkURL?.(message)) }, getShareLinkURL(message) { - const url = new URL('https://www.facebook.com/sharer/sharer.php') - url.searchParams.set('quote', message) - url.searchParams.set('u', 'mask.io') - return url + const url = urlcat('https://www.facebook.com/sharer/sharer.php', { + quote: message, + u: 'mask.io', + }) + return new URL(url) }, createPostContext: createSNSAdaptorSpecializedPostContext({ payloadParser: deconstructPayload, diff --git a/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts b/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts index f10fb500ca71..842709e8305c 100644 --- a/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts +++ b/packages/mask/src/social-network-adaptor/facebook.com/ui-provider.ts @@ -146,10 +146,12 @@ const facebookUI: SocialNetworkUI.Definition = { }, customization: { paletteMode: PaletteModeProviderFacebook, - componentOverwrite: { + sharedComponentOverwrite: { InjectedDialog: { classes: useInjectedDialogClassesOverwriteFacebook, }, + }, + componentOverwrite: { RenderFragments: FacebookRenderFragments, }, useTheme: useThemeFacebookVariant, diff --git a/packages/mask/src/social-network-adaptor/instagram.com/base.ts b/packages/mask/src/social-network-adaptor/instagram.com/base.ts index 54bbad8276e2..3135f0cde0c2 100644 --- a/packages/mask/src/social-network-adaptor/instagram.com/base.ts +++ b/packages/mask/src/social-network-adaptor/instagram.com/base.ts @@ -1,17 +1,17 @@ +import { INSTAGRAM_ID } from '@masknet/shared' import { SocialNetworkEnum } from '@masknet/encryption' import type { SocialNetwork, SocialNetworkWorker } from '../../social-network/types' -const id = 'instagram.com' const origins = ['https://www.instagram.com/*', 'https://m.instagram.com/*', 'https://instagram.com/*'] export const instagramBase: SocialNetwork.Base = { - networkIdentifier: id, + networkIdentifier: INSTAGRAM_ID, encryptionNetwork: SocialNetworkEnum.Instagram, declarativePermissions: { origins }, shouldActivate(location) { - return location.host.endsWith(id) + return location.host.endsWith(INSTAGRAM_ID) }, } export const instagramWorkerBase: SocialNetworkWorker.WorkerBase & SocialNetwork.Base = { ...instagramBase, - gunNetworkHint: id, + gunNetworkHint: INSTAGRAM_ID, } diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/Entry.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/Entry.tsx index eef3f66a41ab..f817252195cc 100644 --- a/packages/mask/src/social-network-adaptor/instagram.com/injection/Entry.tsx +++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/Entry.tsx @@ -1,9 +1,9 @@ import { Fab, styled } from '@mui/material' import { Create } from '@mui/icons-material' +import { CrossIsolationMessages } from '@masknet/shared-base' import { Composition } from '../../../components/CompositionDialog/Composition' import { useState, useEffect } from 'react' import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' -import { MaskMessages } from '../../../utils' const Container = styled('div')` position: fixed; @@ -30,7 +30,7 @@ export function Entry() { { - MaskMessages.events.requestComposition.sendToLocal({ open: true, reason: 'timeline' }) + CrossIsolationMessages.events.requestComposition.sendToLocal({ open: true, reason: 'timeline' }) }}> Create with Mask diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx index 3442a7af4232..3f350a513860 100644 --- a/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx +++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/NFTAvatarSettingDialog.tsx @@ -4,7 +4,7 @@ import { useCurrentVisitingIdentity } from '../../../../components/DataSource/us import { toPNG } from '../../../../plugins/Avatar/utils' import { useMount } from 'react-use' import { getAvatarId } from '../../utils/user' -import { InjectedDialog } from '../../../../components/shared/InjectedDialog' +import { InjectedDialog } from '@masknet/shared' import { DialogContent } from '@mui/material' import { NFTAvatar } from '../../../../plugins/Avatar/SNSAdaptor/NFTAvatar' import { DialogStackingProvider, makeStyles } from '@masknet/theme' diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/ProfileNFTAvatar.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/ProfileNFTAvatar.tsx index 5956adb2f5c8..4b3fd6fc0e95 100644 --- a/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/ProfileNFTAvatar.tsx +++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/NFT/ProfileNFTAvatar.tsx @@ -1,9 +1,8 @@ import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' import { searchInstagramAvatarListSelector } from '../../utils/selector' -import { createReactRootShadowed, MaskMessages, startWatch, useI18N } from '../../../../utils' +import { createReactRootShadowed, MaskMessages, startWatch, useI18N, useLocationChange } from '../../../../utils' import { makeStyles } from '@masknet/theme' import { useCallback, useLayoutEffect, useState } from 'react' -import { useLocationChange } from '../../../../utils/hooks/useLocationChange' import { useLocation } from 'react-use' export async function injectProfileNFTAvatarInInstagram(signal: AbortSignal) { diff --git a/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx b/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx index 4bca7610b3c3..f52a94f53e5c 100644 --- a/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx +++ b/packages/mask/src/social-network-adaptor/instagram.com/injection/ProfileTab.tsx @@ -151,7 +151,7 @@ export function ProfileTabAtInstagram() { ele.style.display = '' } } - const clear = async () => { + const clear = () => { const style = getStyleProps({ activeColor, color }) const activeTab = searchProfileActiveTabSelector().evaluate() if (activeTab?.style) { diff --git a/packages/mask/src/social-network-adaptor/minds.com/automation/openComposeBox.ts b/packages/mask/src/social-network-adaptor/minds.com/automation/openComposeBox.ts index 660925f17e3b..8194b804fa08 100644 --- a/packages/mask/src/social-network-adaptor/minds.com/automation/openComposeBox.ts +++ b/packages/mask/src/social-network-adaptor/minds.com/automation/openComposeBox.ts @@ -1,7 +1,7 @@ import { makeTypedMessageText, SerializableTypedMessages } from '@masknet/typed-message' +import { CrossIsolationMessages, CompositionRequest } from '@masknet/shared-base' import { delay, waitDocumentReadyState } from '@dimensiondev/kit' import { i18n } from '../../../../shared-ui/locales_legacy' -import { MaskMessages, CompositionRequest } from '../../../utils/messages' import { composeButtonSelector, composeDialogIndicatorSelector, composeTextareaSelector } from '../utils/selector' export async function openComposeBoxMinds( @@ -26,7 +26,7 @@ export async function openComposeBoxMinds( } await delay(800) - MaskMessages.events.requestComposition.sendToLocal({ + CrossIsolationMessages.events.requestComposition.sendToLocal({ reason: 'popup', open: true, content: typeof content === 'string' ? makeTypedMessageText(content) : content, diff --git a/packages/mask/src/social-network-adaptor/minds.com/base.ts b/packages/mask/src/social-network-adaptor/minds.com/base.ts index e827f3b6372b..33027b5eb400 100644 --- a/packages/mask/src/social-network-adaptor/minds.com/base.ts +++ b/packages/mask/src/social-network-adaptor/minds.com/base.ts @@ -1,7 +1,7 @@ +import { MINDS_ID } from '@masknet/shared' import { SocialNetworkEnum } from '@masknet/encryption' import type { SocialNetwork, SocialNetworkWorker } from '../../social-network/types' -export const MINDS_ID = 'minds.com' const origins = ['https://www.minds.com/*', 'https://minds.com/*', 'https://cdn.minds.com/*'] export const mindsBase: SocialNetwork.Base = { networkIdentifier: MINDS_ID, diff --git a/packages/mask/src/social-network-adaptor/minds.com/injection/PostDialogHint.tsx b/packages/mask/src/social-network-adaptor/minds.com/injection/PostDialogHint.tsx index 78bc4a0e4338..88c409cabefd 100644 --- a/packages/mask/src/social-network-adaptor/minds.com/injection/PostDialogHint.tsx +++ b/packages/mask/src/social-network-adaptor/minds.com/injection/PostDialogHint.tsx @@ -1,8 +1,8 @@ import { LiveSelector, MutationObserverWatcher } from '@dimensiondev/holoflows-kit' import { makeStyles } from '@masknet/theme' +import { CrossIsolationMessages } from '@masknet/shared-base' import { useCallback } from 'react' import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint' -import { MaskMessages } from '../../../utils/messages' import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot' import { startWatch } from '../../../utils/watcher' import { postEditorInPopupSelector, postEditorInTimelineSelector } from '../utils/selector' @@ -45,7 +45,7 @@ function PostDialogHintAtMinds({ reason }: { reason: 'timeline' | 'popup' }) { const { classes } = useStyles({ reason }) const onHintButtonClicked = useCallback( - () => MaskMessages.events.requestComposition.sendToLocal({ reason, open: true }), + () => CrossIsolationMessages.events.requestComposition.sendToLocal({ reason, open: true }), [reason], ) return ( diff --git a/packages/mask/src/social-network-adaptor/minds.com/shared.ts b/packages/mask/src/social-network-adaptor/minds.com/shared.ts index bbc72b7a3382..ab4e841712e5 100644 --- a/packages/mask/src/social-network-adaptor/minds.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/minds.com/shared.ts @@ -1,4 +1,6 @@ import type { PostIdentifier } from '@masknet/shared-base' +import { openWindow } from '@masknet/shared-base-ui' +import urlcat from 'urlcat' import type { SocialNetwork } from '../../social-network/types' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' import { deconstructPayload } from '../../utils' @@ -17,11 +19,13 @@ export const mindsShared: SocialNetwork.Shared & SocialNetwork.Base = { textPayloadPostProcessor: undefined, getPostURL, share(message) { - const url = this.getShareLinkURL!(message) - window.open(url, '_blank', 'noopener noreferrer') + openWindow(this.getShareLinkURL?.(message)) }, getShareLinkURL(message) { - return new URL(`https://www.minds.com/newsfeed/subscriptions?intentUrl=${encodeURIComponent(message)}`) + const url = urlcat('https://www.minds.com/newsfeed/subscriptions', { + intentUrl: message, + }) + return new URL(url) }, createPostContext: createSNSAdaptorSpecializedPostContext({ payloadParser: deconstructPayload, diff --git a/packages/mask/src/social-network-adaptor/minds.com/ui-provider.ts b/packages/mask/src/social-network-adaptor/minds.com/ui-provider.ts index db2f22d589ae..1c0bbfb1941d 100644 --- a/packages/mask/src/social-network-adaptor/minds.com/ui-provider.ts +++ b/packages/mask/src/social-network-adaptor/minds.com/ui-provider.ts @@ -125,10 +125,12 @@ const mindsUI: SocialNetworkUI.Definition = { }, customization: { paletteMode: PaletteModeProviderMinds, - componentOverwrite: { + sharedComponentOverwrite: { InjectedDialog: { classes: useInjectedDialogClassesOverwriteMinds, }, + }, + componentOverwrite: { RenderFragments: MindsRenderFragments, }, useTheme: useThemeMindsVariant, diff --git a/packages/mask/src/social-network-adaptor/opensea.io/base.ts b/packages/mask/src/social-network-adaptor/opensea.io/base.ts index 6757cb5464da..4e32a6a4a87b 100644 --- a/packages/mask/src/social-network-adaptor/opensea.io/base.ts +++ b/packages/mask/src/social-network-adaptor/opensea.io/base.ts @@ -1,7 +1,7 @@ +import { OPENSEA_ID } from '@masknet/shared' import { SocialNetworkEnum } from '@masknet/encryption' import type { SocialNetwork, SocialNetworkWorker } from '../../social-network/types' -const OPENSEA_ID = 'opensea.io' const origins = ['https://opensea.io/*'] export const openseaBase: SocialNetwork.Base = { networkIdentifier: OPENSEA_ID, diff --git a/packages/mask/src/social-network-adaptor/twitter.com/automation/openComposeBox.ts b/packages/mask/src/social-network-adaptor/twitter.com/automation/openComposeBox.ts index a40eda021270..8312260fc5d5 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/automation/openComposeBox.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/automation/openComposeBox.ts @@ -1,11 +1,11 @@ -import { MaskMessages, CompositionRequest } from '../../../utils/messages' +import { CrossIsolationMessages, CompositionRequest } from '@masknet/shared-base' import { makeTypedMessageText, SerializableTypedMessages } from '@masknet/typed-message' export function openComposeBoxTwitter( content: string | SerializableTypedMessages, options?: CompositionRequest['options'], ) { - MaskMessages.events.requestComposition.sendToLocal({ + CrossIsolationMessages.events.requestComposition.sendToLocal({ reason: 'timeline', open: true, content: typeof content === 'string' ? makeTypedMessageText(content) : content, diff --git a/packages/mask/src/social-network-adaptor/twitter.com/base.ts b/packages/mask/src/social-network-adaptor/twitter.com/base.ts index 6f6a912ac250..a90412694e6f 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/base.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/base.ts @@ -1,10 +1,10 @@ +import { TWITTER_ID } from '@masknet/shared' import { SocialNetworkEnum } from '@masknet/encryption' import type { SocialNetwork, SocialNetworkWorker } from '../../social-network/types' -const id = 'twitter.com' const origins = ['https://mobile.twitter.com/*', 'https://twitter.com/*'] export const twitterBase: SocialNetwork.Base = { - networkIdentifier: id, + networkIdentifier: TWITTER_ID, encryptionNetwork: SocialNetworkEnum.Twitter, declarativePermissions: { origins }, shouldActivate(location) { @@ -13,7 +13,7 @@ export const twitterBase: SocialNetwork.Base = { } export function isTwitter(ui: SocialNetwork.Base) { - return ui.networkIdentifier === id + return ui.networkIdentifier === TWITTER_ID } export const twitterWorkerBase: SocialNetworkWorker.WorkerBase & SocialNetwork.Base = { diff --git a/packages/mask/src/social-network-adaptor/twitter.com/collecting/identity.ts b/packages/mask/src/social-network-adaptor/twitter.com/collecting/identity.ts index 77cab412d085..906d2ce076c3 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/collecting/identity.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/collecting/identity.ts @@ -5,7 +5,7 @@ import { ProfileIdentifier } from '@masknet/shared-base' import { creator, SocialNetworkUI as Next } from '../../../social-network' import Services from '../../../extension/service' import { twitterBase } from '../base' -import { getAvatar, getBioDescription, getNickname, getTwitterId, getPersonalHomepage } from '../utils/user' +import { getAvatar, getBio, getNickname, getTwitterId, getPersonalHomepage } from '../utils/user' import { delay } from '@dimensiondev/kit' function resolveLastRecognizedIdentityInner( @@ -43,7 +43,7 @@ function resolveCurrentVisitingIdentityInner( const avatarMetaSelector = searchAvatarMetaSelector() const assign = async () => { await delay(500) - const bio = getBioDescription() + const bio = getBio() const homepage = getPersonalHomepage() const nickname = getNickname() const handle = getTwitterId() diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx index 086e8df6b830..6f701edbf04b 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarEditProfile.tsx @@ -2,8 +2,7 @@ import { MutationObserverWatcher } from '@dimensiondev/holoflows-kit' import { makeStyles } from '@masknet/theme' import { useState, useEffect } from 'react' import { NFTAvatarButton } from '../../../../plugins/Avatar/SNSAdaptor/NFTAvatarButton' -import { startWatch, createReactRootShadowed } from '../../../../utils' -import { useLocationChange } from '../../../../utils/hooks/useLocationChange' +import { startWatch, createReactRootShadowed, useLocationChange } from '../../../../utils' import { searchEditProfileSelector } from '../../utils/selector' export function injectOpenNFTAvatarEditProfileButton(signal: AbortSignal) { diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx index debe79e92c43..530a742310ca 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/NFT/NFTAvatarInTwitter.tsx @@ -15,6 +15,7 @@ import { useAsync, useLocation, useUpdateEffect, useWindowSize } from 'react-use import { rainbowBorderKeyFrames } from '../../../../plugins/Avatar/SNSAdaptor/RainbowBox' import { trim } from 'lodash-unified' import { RSS3_KEY_SNS } from '../../../../plugins/Avatar/constants' +import { openWindow } from '@masknet/shared-base-ui' export function injectNFTAvatarInTwitter(signal: AbortSignal) { const watcher = new MutationObserverWatcher(searchTwitterAvatarSelector()) @@ -194,7 +195,7 @@ function NFTAvatarInTwitter() { if (!avatar || !linkParentDom || !showAvatar) return const handler = () => { - window.open(resolveOpenSeaLink(avatar.address, avatar.tokenId), '_blank') + openWindow(resolveOpenSeaLink(avatar.address, avatar.tokenId)) } linkParentDom.addEventListener('click', handler) diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostActions/index.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostActions/index.tsx index b9d9eb617f35..637903840779 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostActions/index.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostActions/index.tsx @@ -2,8 +2,8 @@ import type { PostInfo } from '@masknet/plugin-infra' import { Flags } from '../../../../../shared' import { createPostActionsInjector } from '../../../../social-network/defaults/inject/PostActions' -export function injectPostActionsAtTwitter(signal: AbortSignal, current: PostInfo) { +export function injectPostActionsAtTwitter(signal: AbortSignal, postInfo: PostInfo) { if (!Flags.post_actions_enabled) return const injector = createPostActionsInjector() - return injector(current, signal) + return injector(postInfo, signal) } diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx index 2eb6d896327d..5fc8d4baed8d 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/PostDialogHint.tsx @@ -1,9 +1,9 @@ import { useCallback, useEffect, useState } from 'react' import { MutationObserverWatcher, LiveSelector } from '@dimensiondev/holoflows-kit' +import { CrossIsolationMessages } from '@masknet/shared-base' import { isReplyPageSelector, postEditorInPopupSelector, searchReplyToolbarSelector } from '../utils/selector' import { createReactRootShadowed } from '../../../utils/shadow-root/renderInShadowRoot' import { PostDialogHint } from '../../../components/InjectedComponents/PostDialogHint' -import { MaskMessages } from '../../../utils/messages' import { startWatch } from '../../../utils/watcher' import { makeStyles, MaskColorVar } from '@masknet/theme' import { alpha } from '@mui/material' @@ -61,7 +61,7 @@ function PostDialogHintAtTwitter({ reason }: { reason: 'timeline' | 'popup' }) { t('setup_guide_say_hello_follow', { account: '@realMaskNetwork' }), ) - MaskMessages.events.requestComposition.sendToLocal({ + CrossIsolationMessages.events.requestComposition.sendToLocal({ reason: isReplyPageSelector() ? 'reply' : reason, open: true, content, diff --git a/packages/mask/src/social-network-adaptor/twitter.com/injection/Tip/ProfileTipButton.tsx b/packages/mask/src/social-network-adaptor/twitter.com/injection/Tip/ProfileTipButton.tsx index 2329b7d9ec1d..b23b444d28a9 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/injection/Tip/ProfileTipButton.tsx +++ b/packages/mask/src/social-network-adaptor/twitter.com/injection/Tip/ProfileTipButton.tsx @@ -3,8 +3,7 @@ import { makeStyles } from '@masknet/theme' import { useEffect, useState } from 'react' import { useCurrentVisitingIdentity } from '../../../../components/DataSource/useActivatedUI' import { TipButton } from '../../../../plugins/NextID/components/Tip/TipButton' -import { createReactRootShadowed, startWatch } from '../../../../utils' -import { useLocationChange } from '../../../../utils/hooks/useLocationChange' +import { createReactRootShadowed, startWatch, useLocationChange } from '../../../../utils' import { profileFollowButtonSelector as selector, profileMenuButtonSelector as menuButtonSelector, @@ -17,14 +16,14 @@ export function injectOpenTipButtonOnProfile(signal: AbortSignal) { } interface StyleProps { - minHeight: number + size: number fontSize: number marginBottom: number } const useStyles = makeStyles()((theme, props) => ({ button: { - height: 34, - width: 34, + height: props.size, + width: props.size, display: 'flex', justifyContent: 'center', alignItems: 'center', @@ -47,7 +46,7 @@ const useStyles = makeStyles()((theme, props) => ({ })) function OpenTipDialog() { - const [style, setStyle] = useState({ minHeight: 32, fontSize: 14, marginBottom: 11 }) + const [style, setStyle] = useState({ size: 34, fontSize: 14, marginBottom: 11 }) const visitingPersona = useCurrentVisitingIdentity() const setStyleFromEditProfileSelector = () => { @@ -55,7 +54,7 @@ function OpenTipDialog() { if (!menuButton) return const css = window.getComputedStyle(menuButton) setStyle({ - minHeight: Number.parseFloat(css.minHeight.replace('px', '')), + size: Number.parseFloat(css.height.replace('px', '')), fontSize: Number.parseFloat(css.fontSize.replace('px', '')), marginBottom: Number.parseFloat(css.marginBottom.replace('px', '')), }) diff --git a/packages/mask/src/social-network-adaptor/twitter.com/shared.ts b/packages/mask/src/social-network-adaptor/twitter.com/shared.ts index de652947450b..f971f7549c1b 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/shared.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/shared.ts @@ -1,8 +1,9 @@ import { PostIdentifier, ProfileIdentifier } from '@masknet/shared-base' +import { openWindow } from '@masknet/shared-base-ui' +import urlcat from 'urlcat' import type { SocialNetwork } from '../../social-network/types' import { createSNSAdaptorSpecializedPostContext } from '../../social-network/utils/create-post-context' import { deconstructPayload } from '../../utils' -import { createCenterWindowConfig } from '../utils' import { twitterBase } from './base' import { twitterEncoding } from './encoding' import { usernameValidator } from './utils/user' @@ -27,12 +28,30 @@ export const twitterShared: SocialNetwork.Shared & SocialNetwork.Base = { }, getPostURL, share(text) { - const config = createCenterWindowConfig(700, 520) const url = this.getShareLinkURL!(text) - window.open(url, 'share', config) || window.location.assign(url) + const width = 700 + const height = 520 + const openedWindow = openWindow(url, 'share', { + width, + height, + screenX: window.screenX + (window.innerWidth - width) / 2, + screenY: window.screenY + (window.innerHeight - height) / 2, + opener: true, + referrer: true, + behaviors: { + toolbar: true, + status: true, + resizable: true, + scrollbars: true, + }, + }) + if (openedWindow === null) { + location.assign(url) + } }, getShareLinkURL(message) { - return new URL(`https://twitter.com/intent/tweet?text=${encodeURIComponent(message)}`) + const url = urlcat('https://twitter.com/intent/tweet', { text: message }) + return new URL(url) }, createPostContext: createSNSAdaptorSpecializedPostContext({ payloadParser: deconstructPayload, diff --git a/packages/mask/src/social-network-adaptor/twitter.com/ui-provider.ts b/packages/mask/src/social-network-adaptor/twitter.com/ui-provider.ts index ad5d4536c559..872aed50431c 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/ui-provider.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/ui-provider.ts @@ -134,10 +134,12 @@ const twitterUI: SocialNetworkUI.Definition = { }, customization: { paletteMode: PaletteModeProviderTwitter, - componentOverwrite: { + sharedComponentOverwrite: { InjectedDialog: { classes: useInjectedDialogClassesOverwriteTwitter, }, + }, + componentOverwrite: { RenderFragments: TwitterRenderFragments, }, useTheme: useThemeTwitterVariant, diff --git a/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts b/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts index 398d94890015..fb567de08e29 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/utils/selector.ts @@ -57,7 +57,7 @@ export const searchNewTweetButtonSelector: () => LiveSelector = () => { } export const searchNickNameSelector: () => LiveSelector = () => - querySelector('[data-testid="tweet"] a:not([target]) > div > div[dir="auto"] > span > span') + querySelector('[data-testid="primaryColumn"] [data-testid="UserName"] div[dir="auto"] > span > span') export const searchAvatarSelector = () => querySelector('[data-testid="primaryColumn"] a[href$="/photo"] img[src*="profile_images"]') export const searchNFTAvatarSelector = () => @@ -146,7 +146,7 @@ export const twitterMainAvatarSelector: () => LiveSelector = () => export const newPostButtonSelector = () => querySelector('[data-testid="SideNav_NewTweet_Button"]') -export const bioDescriptionSelector = () => querySelector('[data-testid="UserDescription"]') +export const profileBioSelector = () => querySelector('[data-testid="UserDescription"]') export const personalHomepageSelector = () => querySelector('[data-testid="UserUrl"]') diff --git a/packages/mask/src/social-network-adaptor/twitter.com/utils/user.ts b/packages/mask/src/social-network-adaptor/twitter.com/utils/user.ts index 1fab6ced45c1..d0f6a1f62a6e 100644 --- a/packages/mask/src/social-network-adaptor/twitter.com/utils/user.ts +++ b/packages/mask/src/social-network-adaptor/twitter.com/utils/user.ts @@ -1,7 +1,7 @@ import { isNull } from 'lodash-unified' import type { SocialNetwork } from '../../../social-network' import { - bioDescriptionSelector, + profileBioSelector, searchAvatarSelector, searchNickNameSelector, personalHomepageSelector, @@ -23,7 +23,7 @@ export const usernameValidator: NonNullable { - const node = searchNickNameSelector().evaluate()?.querySelector('span span') + const node = searchNickNameSelector().evaluate() if (!node) return '' return collectNodeText(node) @@ -42,8 +42,8 @@ export const getTwitterId = () => { return '' } -export const getBioDescription = () => { - const node = bioDescriptionSelector().evaluate() +export const getBio = () => { + const node = profileBioSelector().evaluate() return node ? collectNodeText(node) : '' } diff --git a/packages/mask/src/social-network-adaptor/utils.ts b/packages/mask/src/social-network-adaptor/utils.ts index a0e6273f3514..1349c4980437 100644 --- a/packages/mask/src/social-network-adaptor/utils.ts +++ b/packages/mask/src/social-network-adaptor/utils.ts @@ -23,18 +23,3 @@ export const getCurrentIdentifier = () => { globalUIState.profiles.value[0] ) } - -export const createCenterWindowConfig = (width: number, height: number) => { - const x = window.screenX + (window.innerWidth - width) / 2 - const y = window.screenY + (window.innerHeight - height) / 2 - return [ - `screenX=${x}`, - `screenY=${y}`, - 'toolbar=1', - 'status=1', - 'resizable=1', - 'scrollbars=1', - `height=${height}`, - `width=${width}`, - ].join(',') -} diff --git a/packages/mask/src/social-network/types.ts b/packages/mask/src/social-network/types.ts index 09ea52e4fb6b..5601bec36c8b 100644 --- a/packages/mask/src/social-network/types.ts +++ b/packages/mask/src/social-network/types.ts @@ -11,13 +11,12 @@ import type { } from '@masknet/shared-base' import type { SerializableTypedMessages } from '@masknet/typed-message' import type { RenderFragmentsContextType } from '@masknet/typed-message/dom' +import type { SharedComponentOverwrite } from '@masknet/shared' import type { PaletteMode, Theme } from '@mui/material' import type { Subscription } from 'use-subscription' -import type { InjectedDialogClassKey, InjectedDialogProps } from '../components/shared/InjectedDialog' import type { Profile } from '../database' import type { createSNSAdaptorSpecializedPostContext } from './utils/create-post-context' -type ClassNameMap = { [P in ClassKey]: string } // Don't define values in namespaces export namespace SocialNetwork { export interface PayloadEncoding { @@ -270,6 +269,7 @@ export namespace SocialNetworkUI { /** Provide the ability to detect the current color scheme (light or dark) in the current SNS */ paletteMode?: PaletteModeProvider i18nOverwrite?: I18NOverwrite + sharedComponentOverwrite?: SharedComponentOverwrite componentOverwrite?: ComponentOverwrite } export interface PaletteModeProvider { @@ -277,13 +277,8 @@ export namespace SocialNetworkUI { start(signal: AbortSignal): void } export interface ComponentOverwrite { - InjectedDialog?: ComponentOverwriteConfig RenderFragments?: RenderFragmentsContextType } - export interface ComponentOverwriteConfig { - classes?: () => { classes: Partial> } - props?: (props: Props) => Props - } export interface I18NOverwrite { [namespace: string]: I18NOverwriteNamespace } diff --git a/packages/mask/src/social-network/ui.ts b/packages/mask/src/social-network/ui.ts index a83e52c5f38d..4c29d701ee16 100644 --- a/packages/mask/src/social-network/ui.ts +++ b/packages/mask/src/social-network/ui.ts @@ -21,6 +21,7 @@ import { createPluginHost } from '../plugin-infra/host' import { definedSocialNetworkUIs } from './define' import { setupShadowRootPortal, MaskMessages } from '../utils' import { delay, waitDocumentReadyState } from '@dimensiondev/kit' +import { sharedUINetworkIdentifier, sharedUIComponentOverwrite } from '@masknet/shared' import { SocialNetworkEnum } from '@masknet/encryption' const definedSocialNetworkUIsResolved = new Map() @@ -51,6 +52,12 @@ export async function activateSocialNetworkUIInner(ui_deferred: SocialNetworkUI. console.log('Activating provider', ui_deferred.networkIdentifier) const ui = (activatedSocialNetworkUI = await loadSocialNetworkUI(ui_deferred.networkIdentifier)) + + sharedUINetworkIdentifier.value = ui_deferred.networkIdentifier + if (ui.customization.sharedComponentOverwrite) { + sharedUIComponentOverwrite.value = ui.customization.sharedComponentOverwrite + } + console.log('Provider activated. You can access it by globalThis.ui', ui) Object.assign(globalThis, { ui }) diff --git a/packages/mask/src/social-network/utils/create-post-context.ts b/packages/mask/src/social-network/utils/create-post-context.ts index 8e6561790e5f..bc1c175702ff 100644 --- a/packages/mask/src/social-network/utils/create-post-context.ts +++ b/packages/mask/src/social-network/utils/create-post-context.ts @@ -18,10 +18,11 @@ import { SubscriptionFromValueRef, SubscriptionDebug as debug, mapSubscription, + EMPTY_LIST, } from '@masknet/shared-base' import { Err, Result } from 'ts-results' import type { Subscription } from 'use-subscription' -import { activatedSocialNetworkUI } from '../' +import { activatedSocialNetworkUI } from '../ui' import { resolveFacebookLink } from '../../social-network-adaptor/facebook.com/utils/resolveFacebookLink' import type { SupportedPayloadVersions } from '@masknet/encryption' @@ -60,7 +61,7 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct }), ) const linksSubscribe: Subscription = debug({ - getCurrentValue: () => [...links], + getCurrentValue: () => (links.size ? [...links] : EMPTY_LIST), subscribe: (sub) => links.event.on(ALL_EVENTS, sub), }) // #endregion @@ -138,7 +139,7 @@ export function createSNSAdaptorSpecializedPostContext(create: PostContextSNSAct postMetadataImages: opt.postImagesProvider || debug({ - getCurrentValue: () => [], + getCurrentValue: () => EMPTY_LIST, subscribe: () => () => {}, }), @@ -181,11 +182,12 @@ export function createRefsForCreatePostContext() { snsID: SubscriptionFromValueRef(postID), rawMessage: SubscriptionFromValueRef(postMessage), postImagesProvider: debug({ - getCurrentValue: () => [...postMetadataImages], + getCurrentValue: () => (postMetadataImages.size ? [...postMetadataImages] : EMPTY_LIST), subscribe: (sub) => postMetadataImages.event.on(ALL_EVENTS, sub), }), postMentionedLinksProvider: debug({ - getCurrentValue: () => [...postMetadataMentionedLinks.values()], + getCurrentValue: () => + postMetadataMentionedLinks.size ? [...postMetadataMentionedLinks.values()] : EMPTY_LIST, subscribe: (sub) => postMetadataMentionedLinks.event.on(ALL_EVENTS, sub), }), } diff --git a/packages/mask/src/tsconfig.json b/packages/mask/src/tsconfig.json index 40b24ac85751..eb5b6ba34de4 100644 --- a/packages/mask/src/tsconfig.json +++ b/packages/mask/src/tsconfig.json @@ -23,6 +23,7 @@ { "path": "../../web3-shared/base" }, { "path": "../../web3-shared/evm" }, { "path": "../../web3-shared/flow" }, + { "path": "../../web3-shared/solana" }, { "path": "../../web3-providers" }, { "path": "../../theme" }, { "path": "../../icons" }, diff --git a/packages/mask/src/utils/hooks/index.ts b/packages/mask/src/utils/hooks/index.ts index 5d302e13ece0..924c4887e5d5 100644 --- a/packages/mask/src/utils/hooks/index.ts +++ b/packages/mask/src/utils/hooks/index.ts @@ -4,3 +4,4 @@ export * from './useMenu' export * from './useQueryNavigatorPermission' export * from './useSettingSwitcher' export * from './useSuspense' +export * from './useLocationChange' diff --git a/packages/mask/src/utils/shadow-root/index.ts b/packages/mask/src/utils/shadow-root/index.ts index b7f722bb05a6..046fdafecb69 100644 --- a/packages/mask/src/utils/shadow-root/index.ts +++ b/packages/mask/src/utils/shadow-root/index.ts @@ -1,6 +1 @@ export * from './renderInShadowRoot' -export { - usePortalShadowRoot, - createShadowRootForwardedComponent, - createShadowRootForwardedPopperComponent, -} from '@masknet/theme' diff --git a/packages/mask/src/web3/UI/EthereumChainBoundary.tsx b/packages/mask/src/web3/UI/EthereumChainBoundary.tsx index 91ed0bb4a016..ae7288a76a70 100644 --- a/packages/mask/src/web3/UI/EthereumChainBoundary.tsx +++ b/packages/mask/src/web3/UI/EthereumChainBoundary.tsx @@ -2,7 +2,7 @@ import React, { useCallback } from 'react' import { Box, Typography, Theme } from '@mui/material' import { makeStyles, useStylesExtends } from '@masknet/theme' import type { SxProps } from '@mui/system' -import { NetworkPluginID, useActivatedPlugin, usePluginIDContext, useAccount } from '@masknet/plugin-infra' +import { NetworkPluginID, useActivatedPlugin, useCurrentWeb3NetworkPluginID, useAccount } from '@masknet/plugin-infra' import { ChainId, getChainDetailedCAIP, @@ -45,7 +45,7 @@ export interface EthereumChainBoundaryProps extends withClasses<'switchButton'> export function EthereumChainBoundary(props: EthereumChainBoundaryProps) { const { t } = useI18N() - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() const plugin = useActivatedPlugin(pluginID, 'any') const account = useAccount() diff --git a/packages/mask/src/web3/UI/EthereumERC721TokenApprovedBoundary.tsx b/packages/mask/src/web3/UI/EthereumERC721TokenApprovedBoundary.tsx index f30417c61a95..e10bd3c2760b 100644 --- a/packages/mask/src/web3/UI/EthereumERC721TokenApprovedBoundary.tsx +++ b/packages/mask/src/web3/UI/EthereumERC721TokenApprovedBoundary.tsx @@ -107,7 +107,13 @@ export function EthereumERC721TokenApprovedBoundary(props: EthereumERC712TokenAp loading disabled {...props.ActionButtonProps}> - {t('plugin_wallet_nft_approving_all', { symbol: contractDetailed?.symbol })} + {t('plugin_wallet_nft_approving_all', { + symbol: contractDetailed?.symbol + ? contractDetailed.symbol.toLowerCase() === 'unknown' + ? 'All' + : contractDetailed.symbol + : 'All', + })} ) } else if (validationMessage) { @@ -143,7 +149,13 @@ export function EthereumERC721TokenApprovedBoundary(props: EthereumERC712TokenAp fullWidth onClick={approveCallback} {...props.ActionButtonProps}> - {t('plugin_wallet_approve_all_nft', { symbol: contractDetailed?.symbol })} + {t('plugin_wallet_approve_all_nft', { + symbol: contractDetailed?.symbol + ? contractDetailed.symbol.toLowerCase() === 'unknown' + ? 'All' + : contractDetailed.symbol + : 'All', + })} ) } else if (value === undefined) { diff --git a/packages/mask/utils-pure/index.ts b/packages/mask/utils-pure/index.ts index 6ba5881a1d04..3a1a43716896 100644 --- a/packages/mask/utils-pure/index.ts +++ b/packages/mask/utils-pure/index.ts @@ -1,5 +1,4 @@ export * from './type' -export * from './misc' export * from './hmr' export * from './crypto' export * from './OnDemandWorker' diff --git a/packages/plugin-infra/src/hooks/index.ts b/packages/plugin-infra/src/hooks/index.ts index e9272b78222b..9ebf21266102 100644 --- a/packages/plugin-infra/src/hooks/index.ts +++ b/packages/plugin-infra/src/hooks/index.ts @@ -2,6 +2,7 @@ export * from './useActivatedPlugin' export * from './useActivatedPluginWeb3UI' export * from './useActivatedPluginWeb3State' export * from './useAllPluginsWeb3State' +export * from './useAvailablePlugins' export * from './useLookupDomain' export * from './useReverseAddress' export * from './useI18N' diff --git a/packages/plugin-infra/src/hooks/useAvailablePlugins.ts b/packages/plugin-infra/src/hooks/useAvailablePlugins.ts new file mode 100644 index 000000000000..46f3776e6c81 --- /dev/null +++ b/packages/plugin-infra/src/hooks/useAvailablePlugins.ts @@ -0,0 +1,21 @@ +import { useMemo } from 'react' +import type { Plugin } from '../types' +import { useChainId, useCurrentWeb3NetworkPluginID } from '../web3' +import type { NetworkPluginID } from '../web3-types' + +type HasRequirement = { enableRequirement: Plugin.Shared.Definition['enableRequirement'] } + +function checkPluginAvailable(plugin: HasRequirement, pluginId: NetworkPluginID, chainId: number) { + const supportedChainIds = plugin.enableRequirement.web3?.[pluginId]?.supportedChainIds + if (!supportedChainIds) return true + return supportedChainIds.includes(chainId) +} + +export function useAvailablePlugins(plugins: T[]) { + const networkPluginId = useCurrentWeb3NetworkPluginID() + const chainId = useChainId(networkPluginId) + return useMemo( + () => plugins.filter((plugin) => checkPluginAvailable(plugin, networkPluginId, chainId)), + [plugins, networkPluginId, chainId], + ) +} diff --git a/packages/plugin-infra/src/types.ts b/packages/plugin-infra/src/types.ts index 309b9b3c5af1..94a2be28412d 100644 --- a/packages/plugin-infra/src/types.ts +++ b/packages/plugin-infra/src/types.ts @@ -242,10 +242,8 @@ export namespace Plugin.SNSAdaptor { CompositionDialogEntry?: CompositionDialogEntry /** This UI will be use when there is known badges. */ CompositionDialogMetadataBadgeRender?: CompositionMetadataBadgeRender - /** This UI will be rendered as an entry in the toolbar (if the SNS has a Toolbar support) */ - ToolbarEntry?: ToolbarEntry /** This UI will be rendered as an entry in the wallet status dialog */ - ApplicationEntry?: ApplicationEntry + ApplicationEntries?: ApplicationEntry[] /** This UI will be rendered as sliders on the profile page */ ProfileSliders?: ProfileSlider[] /** This UI will be rendered as tabs on the profile page */ @@ -310,51 +308,15 @@ export namespace Plugin.SNSAdaptor { } // #endregion - // #region Toolbar entry - export interface ToolbarEntry { - image: string - // TODO: remove string - label: I18NStringField | string - /** - * Used to order the toolbars - * - * TODO: can we make them unordered? - */ - priority: number - /** - * This is a React hook. If it returns false, this entry will not be displayed. - */ - useShouldDisplay?(): boolean - /** - * What to do if the entry is clicked. - */ - // TODO: add support for DialogEntry. - // TODO: add support for onClick event. - onClick: 'openCompositionEntry' - } - // #endregion - export interface ApplicationEntry { /** - * The icon image URL - */ - icon: URL - /** - * The name of the application - */ - label: I18NStringField | string - /** - * Also an entrance in a sub-folder + * Render entry component */ - categoryID?: string + RenderEntryComponent: (props: { disabled: boolean }) => JSX.Element /** * Used to order the applications on the board */ - priority: number - /** - * What to do if the application icon is clicked. - */ - onClick(): void + defaultSortingPriority: number } export interface ProfileIdentity { @@ -726,6 +688,8 @@ export enum PluginId { UnlockProtocol = 'com.maskbook.unlockprotocol', FileService = 'com.maskbook.fileservice', CyberConnect = 'me.cyberconnect.app', + GoPlusSecurity = 'io.gopluslabs.security', + CrossChainBridge = 'io.mask.cross-chain-bridge', // @masknet/scripts: insert-here } diff --git a/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx b/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx index 2da5547361b3..1e68250fbe85 100644 --- a/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx +++ b/packages/plugin-infra/src/utils/createInjectHooksRenderer.tsx @@ -1,9 +1,9 @@ -import { useEffect, useState, useRef, memo, createContext, useContext } from 'react' import { ErrorBoundary } from '@masknet/shared-base-ui' import { ShadowRootIsolation } from '@masknet/theme' -import type { Plugin } from '../types' -import { usePluginI18NField, PluginWrapperComponent, PluginWrapperMethods } from '../hooks' +import { createContext, memo, useContext, useEffect, useRef, useState } from 'react' +import { PluginWrapperComponent, PluginWrapperMethods, useAvailablePlugins, usePluginI18NField } from '../hooks' import { PluginWrapperMethodsContext } from '../hooks/usePluginWrapper' +import type { Plugin } from '../types' type Inject = Plugin.InjectUI type Raw = Plugin.InjectUIRaw @@ -43,15 +43,15 @@ export function createInjectHooksRenderer ( - - - - - - )) + const allPlugins = usePlugins() + const availablePlugins = useAvailablePlugins(allPlugins) as PluginDefinition[] + const all = availablePlugins.filter(pickInjectorHook).map((plugin) => ( + + + + + + )) return <>{all} } return memo(function PluginsInjectionHookRenderErrorBoundary(props: PropsType) { diff --git a/packages/plugin-infra/src/web3-types.ts b/packages/plugin-infra/src/web3-types.ts index d851716caf51..5c1442031d6b 100644 --- a/packages/plugin-infra/src/web3-types.ts +++ b/packages/plugin-infra/src/web3-types.ts @@ -29,6 +29,18 @@ export type Color = | `#${string}${string}${string}` | `hsl(${number}, ${number}%, ${number}%)` +// Borrow from @masknet/web3-shared-evm +interface ERC721TokenInfo { + name?: string + description?: string + tokenURI?: string + mediaUrl?: string + imageURL?: string + owner?: string + // loading tokenURI + hasTokenDetailed?: boolean +} + export declare namespace Web3Plugin { /** * Plugin can declare what chain it supports to trigger side effects (e.g. create a new transaction). @@ -197,6 +209,7 @@ export declare namespace Web3Plugin { description?: string owner?: string metadata?: NonFungibleTokenMetadata + info?: ERC721TokenInfo contract?: NonFungibleContract } diff --git a/packages/plugin-infra/src/web3/Context.tsx b/packages/plugin-infra/src/web3/Context.tsx index 0e2fb578cee0..be8fd9e12190 100644 --- a/packages/plugin-infra/src/web3/Context.tsx +++ b/packages/plugin-infra/src/web3/Context.tsx @@ -78,7 +78,7 @@ function usePluginsWeb3State() { ) } -export function usePluginIDContext() { +export function useCurrentWeb3NetworkPluginID() { return useContext(PluginIDContext) } @@ -87,7 +87,7 @@ const PluginsWeb3StateContext = createContainer(usePluginsWeb3State) export const usePluginsWeb3StateContext = PluginsWeb3StateContext.useContainer export function usePluginWeb3StateContext(expectedPluginID?: NetworkPluginID) { - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() const pluginsWeb3State = usePluginsWeb3StateContext() return pluginsWeb3State[expectedPluginID ?? pluginID] ?? {} } diff --git a/packages/plugin-infra/src/web3/useNetworkDescriptor.ts b/packages/plugin-infra/src/web3/useNetworkDescriptor.ts index 5f906dc28ba2..f7bb10353f38 100644 --- a/packages/plugin-infra/src/web3/useNetworkDescriptor.ts +++ b/packages/plugin-infra/src/web3/useNetworkDescriptor.ts @@ -1,13 +1,13 @@ import type { NetworkPluginID } from '..' import { useNetworkType } from './useNetworkType' -import { usePluginIDContext } from './Context' +import { useCurrentWeb3NetworkPluginID } from './Context' import { getPluginDefine } from '../manager/store' export function useNetworkDescriptor( expectedChainIdOrNetworkTypeOrID?: number | string, expectedPluginID?: NetworkPluginID, ) { - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() const pid = expectedPluginID ?? pluginID const networkType = useNetworkType(pid) diff --git a/packages/plugin-infra/src/web3/useNetworkDescriptors.ts b/packages/plugin-infra/src/web3/useNetworkDescriptors.ts index bc344101ae5c..162d5fe303e7 100644 --- a/packages/plugin-infra/src/web3/useNetworkDescriptors.ts +++ b/packages/plugin-infra/src/web3/useNetworkDescriptors.ts @@ -1,7 +1,7 @@ -import { usePluginIDContext } from '.' +import { useCurrentWeb3NetworkPluginID } from '.' import { getPluginDefine } from '..' export function useNetworkDescriptors(expectedPluginID?: string) { - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() return getPluginDefine(expectedPluginID ?? pluginID)?.declareWeb3Networks ?? [] } diff --git a/packages/plugin-infra/src/web3/useProviderDescriptor.ts b/packages/plugin-infra/src/web3/useProviderDescriptor.ts index 76f40c997d06..3b48bd3b49d7 100644 --- a/packages/plugin-infra/src/web3/useProviderDescriptor.ts +++ b/packages/plugin-infra/src/web3/useProviderDescriptor.ts @@ -1,10 +1,10 @@ import type { NetworkPluginID } from '..' import { useProviderType } from './useProviderType' -import { usePluginIDContext } from './Context' +import { useCurrentWeb3NetworkPluginID } from './Context' import { getPluginDefine } from '../manager/store' export function useProviderDescriptor(expectedProviderTypeOrID?: string, expectedPluginID?: NetworkPluginID) { - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() const providerType = useProviderType(expectedPluginID ?? pluginID) return getPluginDefine(expectedPluginID ?? pluginID)?.declareWeb3Providers?.find((x) => diff --git a/packages/plugin-infra/src/web3/useProviderDescriptors.ts b/packages/plugin-infra/src/web3/useProviderDescriptors.ts index 352b3c3116a0..0db2b0167c14 100644 --- a/packages/plugin-infra/src/web3/useProviderDescriptors.ts +++ b/packages/plugin-infra/src/web3/useProviderDescriptors.ts @@ -1,7 +1,7 @@ -import { usePluginIDContext } from '.' +import { useCurrentWeb3NetworkPluginID } from '.' import { getPluginDefine } from '..' export function useProviderDescriptors(expectedPluginID?: string) { - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() return getPluginDefine(expectedPluginID ?? pluginID)?.declareWeb3Providers ?? [] } diff --git a/packages/plugin-infra/src/web3/useWeb3State.ts b/packages/plugin-infra/src/web3/useWeb3State.ts index 07689afb6548..381cb694a293 100644 --- a/packages/plugin-infra/src/web3/useWeb3State.ts +++ b/packages/plugin-infra/src/web3/useWeb3State.ts @@ -1,8 +1,8 @@ -import { usePluginIDContext } from './Context' +import { useCurrentWeb3NetworkPluginID } from './Context' import { useActivatedPluginWeb3State } from '../hooks/useActivatedPluginWeb3State' import type { NetworkPluginID } from '..' export function useWeb3State(expectedPluginID?: NetworkPluginID) { - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() return useActivatedPluginWeb3State(expectedPluginID ?? pluginID) ?? {} } diff --git a/packages/plugin-infra/src/web3/useWeb3UI.ts b/packages/plugin-infra/src/web3/useWeb3UI.ts index c30e3eb98baa..4a23eb31c66d 100644 --- a/packages/plugin-infra/src/web3/useWeb3UI.ts +++ b/packages/plugin-infra/src/web3/useWeb3UI.ts @@ -1,8 +1,8 @@ -import { usePluginIDContext } from './Context' +import { useCurrentWeb3NetworkPluginID } from './Context' import { useActivatedPluginWeb3UI } from '../hooks/useActivatedPluginWeb3UI' import type { NetworkPluginID } from '..' export function useWeb3UI(expectedPluginID?: NetworkPluginID) { - const pluginID = usePluginIDContext() + const pluginID = useCurrentWeb3NetworkPluginID() return useActivatedPluginWeb3UI(expectedPluginID ?? pluginID) ?? {} } diff --git a/packages/plugins/CrossChainBridge/README.md b/packages/plugins/CrossChainBridge/README.md new file mode 100644 index 000000000000..369c4cfa282c --- /dev/null +++ b/packages/plugins/CrossChainBridge/README.md @@ -0,0 +1,5 @@ +# Cross-chain bridge plugin + +## TODOs + +add more cross-chain bridge website diff --git a/packages/plugins/CrossChainBridge/package.json b/packages/plugins/CrossChainBridge/package.json new file mode 100644 index 000000000000..d4499c87bdbd --- /dev/null +++ b/packages/plugins/CrossChainBridge/package.json @@ -0,0 +1,12 @@ +{ + "name": "@masknet/plugin-cross-chain-bridge", + "private": true, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "@masknet/plugin-infra": "workspace:*", + "@masknet/shared": "workspace:*", + "@masknet/shared-base": "workspace:*", + "react-use": "^17.3.2" + } +} diff --git a/packages/plugins/CrossChainBridge/src/Dashboard/index.tsx b/packages/plugins/CrossChainBridge/src/Dashboard/index.tsx new file mode 100644 index 000000000000..3052bb2f8ad3 --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/Dashboard/index.tsx @@ -0,0 +1,9 @@ +import type { Plugin } from '@masknet/plugin-infra' +import { base } from '../base' + +const dashboard: Plugin.Dashboard.Definition = { + ...base, + init(signal, context) {}, +} + +export default dashboard diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/CrossChainBridgeDialog.tsx b/packages/plugins/CrossChainBridge/src/SNSAdaptor/CrossChainBridgeDialog.tsx new file mode 100644 index 000000000000..c7f75df14c97 --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/SNSAdaptor/CrossChainBridgeDialog.tsx @@ -0,0 +1,99 @@ +import { DialogContent, Stack, Typography } from '@mui/material' +import { makeStyles, MaskDialog, useStylesExtends } from '@masknet/theme' +import { useI18N } from '../locales' +import { getCrossChainBridge } from '../constants' +import { openWindow } from '@masknet/shared-base-ui' + +const useStyles = makeStyles()((theme) => ({ + root: { + width: 600, + }, + paperRoot: { + backgroundImage: 'none', + '&>h2': { + height: 30, + border: `1px solid ${theme.palette.divider}`, + padding: theme.spacing(1.875, 2.5, 1.875, 2.5), + marginBottom: 24, + }, + }, + content: { + width: 552, + maxHeight: 510, + paddingBottom: theme.spacing(3), + }, + bridgeItem: { + display: 'flex', + padding: '12px', + ':hover': { + backgroundColor: theme.palette.background.default, + cursor: 'pointer', + }, + }, + bridgeInfo: { + marginLeft: '8px', + }, + bridgeName: { + fontSize: '24px', + fontWeight: 600, + display: 'flex', + }, + bridgeIntro: { + fontSize: '12px', + fontWeight: 400, + color: theme.palette.grey[700], + }, + officialTag: { + width: '39px', + height: '15px', + fontSize: '12px', + fontWeight: 700, + alignSelf: 'center', + borderRadius: '8px', + backgroundColor: 'rgba(28, 104, 243, 0.1)', + color: '#1c68f3', + padding: '4px 12px 6px 8px', + marginLeft: '4px', + }, +})) + +export interface CrossChainBridgeDialogProps extends withClasses { + open: boolean + onClose(): void +} + +export function CrossChainBridgeDialog(props: CrossChainBridgeDialogProps) { + const t = useI18N() + const classes = useStylesExtends(useStyles(), props) + const { open, onClose } = props + const bridges = getCrossChainBridge(t) + + return ( + + + + {bridges?.map((bridge) => ( +
openWindow(bridge?.link)}> + {bridge?.icon} +
+
+ {bridge?.name} + {bridge?.isOfficial && ( + {t.official()} + )} +
+ {bridge?.intro && ( + {bridge.intro} + )} +
+
+ ))} +
+
+
+ ) +} diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/MaskIcon.tsx b/packages/plugins/CrossChainBridge/src/SNSAdaptor/MaskIcon.tsx new file mode 100644 index 000000000000..5bf482a61c53 --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/SNSAdaptor/MaskIcon.tsx @@ -0,0 +1,54 @@ +export function ArbitrumOneBridgeIcon({ size = 36 }) { + return ( + + ) +} + +export function BobaBridgeIcon({ size = 36 }) { + return ( + + ) +} + +export function CBridgeIcon({ size = 36 }) { + return ( + + ) +} + +export function PolygonBridgeIcon({ size = 36 }) { + return ( + + ) +} + +export function RainbowBridgeIcon({ size = 36 }) { + return ( + + ) +} diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/arbitrum-one-bridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/arbitrum-one-bridge.png new file mode 100644 index 000000000000..634ec832596d Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/arbitrum-one-bridge.png differ diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/boba-bridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/boba-bridge.png new file mode 100644 index 000000000000..b41c5c9b9b2c Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/boba-bridge.png differ diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cbridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cbridge.png new file mode 100644 index 000000000000..e9ced6d2c547 Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cbridge.png differ diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cross-chain.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cross-chain.png new file mode 100644 index 000000000000..892dd5876ecb Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/cross-chain.png differ diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/polygon-bridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/polygon-bridge.png new file mode 100644 index 000000000000..a8c63f65c0c4 Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/polygon-bridge.png differ diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/rainbow-bridge.png b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/rainbow-bridge.png new file mode 100644 index 000000000000..712f8e2b9a34 Binary files /dev/null and b/packages/plugins/CrossChainBridge/src/SNSAdaptor/assets/rainbow-bridge.png differ diff --git a/packages/plugins/CrossChainBridge/src/SNSAdaptor/index.tsx b/packages/plugins/CrossChainBridge/src/SNSAdaptor/index.tsx new file mode 100644 index 000000000000..21a663849bfd --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/SNSAdaptor/index.tsx @@ -0,0 +1,31 @@ +import type { Plugin } from '@masknet/plugin-infra' +import { ApplicationEntry } from '@masknet/shared' +import { base } from '../base' +import { useState } from 'react' +import { CrossChainBridgeDialog } from './CrossChainBridgeDialog' + +const sns: Plugin.SNSAdaptor.Definition = { + ...base, + init(signal, context) {}, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + const [open, setOpen] = useState(false) + return ( + <> + setOpen(true)} + /> + setOpen(false)} /> + + ) + }, + defaultSortingPriority: 4, + }, + ], +} + +export default sns diff --git a/packages/plugins/CrossChainBridge/src/Worker/index.ts b/packages/plugins/CrossChainBridge/src/Worker/index.ts new file mode 100644 index 000000000000..6c436f1cb4fb --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/Worker/index.ts @@ -0,0 +1,9 @@ +import type { Plugin } from '@masknet/plugin-infra' +import { base } from '../base' + +const worker: Plugin.Worker.Definition = { + ...base, + init(signal, context) {}, +} + +export default worker diff --git a/packages/plugins/CrossChainBridge/src/base.ts b/packages/plugins/CrossChainBridge/src/base.ts new file mode 100644 index 000000000000..a593b93d23aa --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/base.ts @@ -0,0 +1,17 @@ +import type { Plugin } from '@masknet/plugin-infra' +import { PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from './constants' +import { languages } from './locales/languages' + +export const base: Plugin.Shared.Definition = { + ID: PLUGIN_ID, + name: { fallback: PLUGIN_NAME }, + description: { fallback: PLUGIN_DESCRIPTION }, + publisher: { name: { fallback: '' }, link: '' }, + enableRequirement: { + architecture: { app: false, web: true }, + networks: { type: 'opt-out', networks: {} }, + target: 'stable', + }, + experimentalMark: false, + i18n: languages, +} diff --git a/packages/plugins/CrossChainBridge/src/constants.tsx b/packages/plugins/CrossChainBridge/src/constants.tsx new file mode 100644 index 000000000000..03d5249b97f2 --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/constants.tsx @@ -0,0 +1,53 @@ +import { PluginId } from '@masknet/plugin-infra' +import { + CBridgeIcon, + ArbitrumOneBridgeIcon, + BobaBridgeIcon, + PolygonBridgeIcon, + RainbowBridgeIcon, +} from './SNSAdaptor/MaskIcon' + +export const PLUGIN_ID = PluginId.CrossChainBridge +export const PLUGIN_DESCRIPTION = 'A cross-chain-bridge plugin' +export const PLUGIN_NAME = 'CrossChainBridge' +export function getCrossChainBridge(t: Record string>) { + return [ + { + name: 'CBridge', + ID: `${PLUGIN_ID}_cBridge`, + intro: t.cbridge_intro(), + icon: , + isOfficial: false, + link: 'https://cbridge.celer.network/#/transfer', + }, + { + name: 'Arbitrum One Bridge', + ID: `${PLUGIN_ID}_arbitrum_one_bridge`, + isOfficial: true, + icon: , + link: 'https://bridge.arbitrum.io/', + }, + { + name: 'BOBA Bridge', + ID: `${PLUGIN_ID}_boba_bridge`, + isOfficial: true, + icon: , + link: 'https://gateway.boba.network/', + }, + { + name: 'Polygon Bridge', + ID: `${PLUGIN_ID}_polygon_bridge`, + isOfficial: true, + icon: , + link: 'https://wallet.polygon.technology/bridge/', + }, + { + name: 'Rainbow Bridge', + ID: `${PLUGIN_ID}_rainbow_bridge`, + isOfficial: true, + intro: t.rainbow_bridge_intro(), + icon: , + link: 'https://rainbowbridge.app/transfer', + }, + ] +} diff --git a/packages/plugins/CrossChainBridge/src/env.d.ts b/packages/plugins/CrossChainBridge/src/env.d.ts new file mode 100644 index 000000000000..868322d5ff30 --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/plugins/CrossChainBridge/src/index.ts b/packages/plugins/CrossChainBridge/src/index.ts new file mode 100644 index 000000000000..ec9b1a34e7af --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/index.ts @@ -0,0 +1,23 @@ +import { registerPlugin } from '@masknet/plugin-infra' +import { base } from './base' + +export * from './constants' + +registerPlugin({ + ...base, + SNSAdaptor: { + load: () => import('./SNSAdaptor'), + hotModuleReload: (hot) => + import.meta.webpackHot && import.meta.webpackHot.accept('./SNSAdaptor', () => hot(import('./SNSAdaptor'))), + }, + Dashboard: { + load: () => import('./Dashboard'), + hotModuleReload: (hot) => + import.meta.webpackHot && import.meta.webpackHot.accept('./Dashboard', () => hot(import('./Dashboard'))), + }, + Worker: { + load: () => import('./Worker'), + hotModuleReload: (hot) => + import.meta.webpackHot && import.meta.webpackHot.accept('./Worker', () => hot(import('./Worker'))), + }, +}) diff --git a/packages/plugins/CrossChainBridge/src/locales/en-US.json b/packages/plugins/CrossChainBridge/src/locales/en-US.json new file mode 100644 index 000000000000..37075139d2a9 --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/locales/en-US.json @@ -0,0 +1,6 @@ +{ + "dialog_title": "Cross-chain", + "official": "Official", + "cbridge_intro": "Powered by Celer Network. Support $Mask!", + "rainbow_bridge_intro": "Bridge between or send within Ethereum, NEAR and Aurora! " +} diff --git a/packages/plugins/CrossChainBridge/src/locales/index.ts b/packages/plugins/CrossChainBridge/src/locales/index.ts new file mode 100644 index 000000000000..d6ead60252e4 --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/locales/index.ts @@ -0,0 +1,6 @@ +// This file is auto generated. DO NOT EDIT +// Run `npx gulp sync-languages` to regenerate. +// Default fallback language in a family of languages are chosen by the alphabet order +// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts + +export * from './i18n_generated' diff --git a/packages/plugins/CrossChainBridge/src/locales/ja-JP.json b/packages/plugins/CrossChainBridge/src/locales/ja-JP.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/locales/ja-JP.json @@ -0,0 +1 @@ +{} diff --git a/packages/plugins/CrossChainBridge/src/locales/ko-KR.json b/packages/plugins/CrossChainBridge/src/locales/ko-KR.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/locales/ko-KR.json @@ -0,0 +1 @@ +{} diff --git a/packages/plugins/CrossChainBridge/src/locales/languages.ts b/packages/plugins/CrossChainBridge/src/locales/languages.ts new file mode 100644 index 000000000000..cff1155ab4cb --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/locales/languages.ts @@ -0,0 +1,36 @@ +// This file is auto generated. DO NOT EDIT +// Run `npx gulp sync-languages` to regenerate. +// Default fallback language in a family of languages are chosen by the alphabet order +// To overwrite this, please overwrite packages/scripts/src/locale-kit-next/index.ts +import en_US from './en-US.json' +import ja_JP from './ja-JP.json' +import ko_KR from './ko-KR.json' +import qya_AA from './qya-AA.json' +import zh_CN from './zh-CN.json' +import zh_TW from './zh-TW.json' +export const languages = { + en: en_US, + ja: ja_JP, + ko: ko_KR, + qy: qya_AA, + 'zh-CN': zh_CN, + zh: zh_TW, +} +import { createI18NBundle } from '@masknet/shared-base' +export const add__template__I18N = createI18NBundle('__template__', languages) +// @ts-ignore +if (import.meta.webpackHot) { + // @ts-ignore + import.meta.webpackHot.accept( + ['./en-US.json', './ja-JP.json', './ko-KR.json', './qya-AA.json', './zh-CN.json', './zh-TW.json'], + () => + globalThis.dispatchEvent?.( + new CustomEvent('MASK_I18N_HMR', { + detail: [ + '__template__', + { en: en_US, ja: ja_JP, ko: ko_KR, qy: qya_AA, 'zh-CN': zh_CN, zh: zh_TW }, + ], + }), + ), + ) +} diff --git a/packages/plugins/CrossChainBridge/src/locales/qya-AA.json b/packages/plugins/CrossChainBridge/src/locales/qya-AA.json new file mode 100644 index 000000000000..439535f8dd96 --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/locales/qya-AA.json @@ -0,0 +1,4 @@ +{ + "name": "crwdns13280:0crwdne13280:0", + "__entry__": "crwdns13282:0crwdne13282:0" +} diff --git a/packages/plugins/CrossChainBridge/src/locales/zh-CN.json b/packages/plugins/CrossChainBridge/src/locales/zh-CN.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/locales/zh-CN.json @@ -0,0 +1 @@ +{} diff --git a/packages/plugins/CrossChainBridge/src/locales/zh-TW.json b/packages/plugins/CrossChainBridge/src/locales/zh-TW.json new file mode 100644 index 000000000000..0967ef424bce --- /dev/null +++ b/packages/plugins/CrossChainBridge/src/locales/zh-TW.json @@ -0,0 +1 @@ +{} diff --git a/packages/plugins/CrossChainBridge/tsconfig.json b/packages/plugins/CrossChainBridge/tsconfig.json new file mode 100644 index 000000000000..c8c34c61f89b --- /dev/null +++ b/packages/plugins/CrossChainBridge/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist", + "tsBuildInfoFile": "dist/.tsbuildinfo" + }, + "include": ["src", "src/**/*.json"], + "references": [{ "path": "../../plugin-infra" }, { "path": "../../shared" }] +} diff --git a/packages/plugins/CyberConnect/src/SNSAdaptor/ConnectButton.tsx b/packages/plugins/CyberConnect/src/SNSAdaptor/ConnectButton.tsx index da4bdf16d6d2..531f6b007dad 100644 --- a/packages/plugins/CyberConnect/src/SNSAdaptor/ConnectButton.tsx +++ b/packages/plugins/CyberConnect/src/SNSAdaptor/ConnectButton.tsx @@ -1,7 +1,7 @@ import { useEffect, useState, useCallback } from 'react' import { makeStyles, MaskColorVar } from '@masknet/theme' import { useWeb3, isSameAddress } from '@masknet/web3-shared-evm' -import { useAccount, usePluginIDContext, NetworkPluginID } from '@masknet/plugin-infra' +import { useAccount, useCurrentWeb3NetworkPluginID, NetworkPluginID } from '@masknet/plugin-infra' import CyberConnect, { Env } from '@cyberlab/cyberconnect' import { PluginCyberConnectRPC } from '../messages' import { CircularProgress, useTheme, Typography } from '@mui/material' @@ -96,7 +96,7 @@ export default function ConnectButton({ const [cc, setCc] = useState(null) const [isFollowing, setIsFollowing] = useState(false) const [isLoading, setIsLoading] = useState(false) - const blockChainNetwork = usePluginIDContext() + const blockChainNetwork = useCurrentWeb3NetworkPluginID() useAsync(async () => { if (isSameAddress(myAddress, address)) return const res = await PluginCyberConnectRPC.fetchFollowStatus(myAddress, address) diff --git a/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx b/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx index 808bb952cbb7..f10a66f1ed4a 100644 --- a/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx +++ b/packages/plugins/FileService/src/SNSAdaptor/Preview.tsx @@ -7,6 +7,7 @@ import { CopyableCode } from './components/Copyable' import type { FileInfo } from '../types' import { resolveGatewayAPI } from '../helpers' import urlcat from 'urlcat' +import { openWindow } from '@masknet/shared-base-ui' const useStyles = makeStyles()((theme) => ({ root: { @@ -64,7 +65,7 @@ export function Preview({ info }: { info: FileInfo }) { const onClick = (event: React.MouseEvent) => { event.preventDefault() event.stopPropagation() - open(info.key ? `${link}#${info.key}` : link) + openWindow(info.key ? `${link}#${info.key}` : link) } return ( { const linkPrefix = resolveGatewayAPI(state.provider) const link = urlcat(linkPrefix, '/:txId', { txId: state.landingTxID }) - open(state.key ? `${link}#${state.key}` : link) + openWindow(state.key ? `${link}#${state.key}` : link) } return ( diff --git a/packages/plugins/FileService/src/SNSAdaptor/index.tsx b/packages/plugins/FileService/src/SNSAdaptor/index.tsx index 279620daec0a..b38c6466452a 100644 --- a/packages/plugins/FileService/src/SNSAdaptor/index.tsx +++ b/packages/plugins/FileService/src/SNSAdaptor/index.tsx @@ -8,6 +8,8 @@ import type { FileInfo } from '../types' import FileServiceDialog from './MainDialog' import { Preview } from './Preview' import { FileServiceIcon } from '@masknet/icons' +import { CrossIsolationMessages } from '@masknet/shared-base' +import { ApplicationEntry } from '@masknet/shared' const definition: Plugin.SNSAdaptor.Definition = { ...base, @@ -32,6 +34,29 @@ const definition: Plugin.SNSAdaptor.Definition = { }, dialog: FileServiceDialog, }, + ApplicationEntries: [ + { + RenderEntryComponent({ disabled }) { + return ( + + CrossIsolationMessages.events.requestComposition.sendToLocal({ + reason: 'timeline', + open: true, + options: { + startupPlugin: base.ID, + }, + }) + } + /> + ) + }, + defaultSortingPriority: 2, + }, + ], } export default definition diff --git a/packages/plugins/GoPlusSecurity/package.json b/packages/plugins/GoPlusSecurity/package.json new file mode 100644 index 000000000000..602edf644031 --- /dev/null +++ b/packages/plugins/GoPlusSecurity/package.json @@ -0,0 +1,19 @@ +{ + "name": "@masknet/plugin-go-plus-security", + "private": true, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "dependencies": { + "react-feather": "^2.0.9", + "@masknet/icons": "workspace:*", + "@masknet/plugin-infra": "workspace:*", + "@masknet/shared": "workspace:*", + "@masknet/shared-base-ui": "workspace:*", + "@masknet/shared-base": "workspace:*", + "@masknet/theme": "workspace:*", + "@masknet/web3-providers": "workspace:*", + "@masknet/web3-shared-evm": "workspace:*", + "react-use": "^17.3.2", + "urlcat": "^2.0.4" + } +} diff --git a/packages/plugins/GoPlusSecurity/src/Dashboard/index.tsx b/packages/plugins/GoPlusSecurity/src/Dashboard/index.tsx new file mode 100644 index 000000000000..c11379ee47db --- /dev/null +++ b/packages/plugins/GoPlusSecurity/src/Dashboard/index.tsx @@ -0,0 +1,9 @@ +import type { Plugin } from '@masknet/plugin-infra' +import { base } from '../base' + +const dashboard: Plugin.Dashboard.Definition = { + ...base, + init(signal) {}, +} + +export default dashboard diff --git a/packages/plugins/GoPlusSecurity/src/SNSAdaptor/CheckSecurityDialog.tsx b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/CheckSecurityDialog.tsx new file mode 100644 index 000000000000..f679c307e226 --- /dev/null +++ b/packages/plugins/GoPlusSecurity/src/SNSAdaptor/CheckSecurityDialog.tsx @@ -0,0 +1,86 @@ +import { Box, DialogContent, Stack } from '@mui/material' +import { makeStyles, MaskDialog, useStylesExtends } from '@masknet/theme' +import { useI18N } from '../locales' +import { SearchBox } from './components/SearchBox' +import { useAsyncFn } from 'react-use' +import { GoPlusLabs } from '@masknet/web3-providers' +import { Searching } from './components/Searching' +import { SecurityPanel } from './components/SecurityPanel' +import { Footer } from './components/Footer' +import { Center, TokenSecurity } from './components/Common' +import { DefaultPlaceholder } from './components/DefaultPlaceholder' +import { NotFound } from './components/NotFound' +import type { ChainId } from '@masknet/web3-shared-evm' + +const useStyles = makeStyles()((theme) => ({ + root: { + width: 600, + }, + paperRoot: { + backgroundImage: 'none', + '&>h2': { + height: 30, + border: `1px solid ${theme.palette.divider}`, + padding: theme.spacing(1.875, 2.5, 1.875, 2.5), + marginBottom: 24, + }, + }, + content: { + width: 552, + height: 510, + maxHeight: 510, + paddingBottom: theme.spacing(3), + }, +})) + +export interface BuyTokenDialogProps extends withClasses { + open: boolean + onClose(): void +} + +export function CheckSecurityDialog(props: BuyTokenDialogProps) { + const t = useI18N() + const classes = useStylesExtends(useStyles(), props) + const { open, onClose } = props + + const [{ value, loading: searching, error }, onSearch] = useAsyncFn(async (chainId: ChainId, content: string) => { + const values = await GoPlusLabs.getTokenSecurity(chainId, [content.trim()]) + if (!Object.keys(values ?? {}).length) throw new Error('Contract Not Found') + return Object.entries(values ?? {}).map((x) => ({ ...x[1], contract: x[0], chainId }))[0] as + | TokenSecurity + | undefined + }, []) + + return ( + + + + + + + + {searching && ( +
+ +
+ )} + {error && !searching && } + {!error && !searching && value && } + {!error && !searching && !value && ( +
+ +
+ )} +
+ +