diff --git a/docusaurus.config.js b/docusaurus.config.js index 62578b81..4c65c77e 100644 --- a/docusaurus.config.js +++ b/docusaurus.config.js @@ -45,7 +45,24 @@ const config = { logo: { alt: 'Darwinia', src: 'img/favicon.svg', - } + }, + items: [ + /*{to: 'blog', label: 'Blog', position: 'left'}, + { + href: 'https://github.com/darwinia-network/darwinia/tree/master/runtime/crab', + label: 'GitHub', + position: 'right', + }, + { + href: 'https://darwinia.network/Darwinia_Genepaper_EN.pdf', + label: 'WhitePaper', + position: 'right', + }, + { + type: 'localeDropdown', + position: 'right', + },*/ + ], }, footer: { style: 'dark', diff --git a/package.json b/package.json index b8336a10..72d8cbd9 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "clsx": "^1.1.1", "prism-react-renderer": "^1.3.1", "react": "^17.0.2", - "react-dom": "^17.0.2" + "react-dom": "^17.0.2", + "react-transition-group": "^4.4.5" }, "browserslist": { "production": [ diff --git a/src/components/ConnectWalletButton/index.js b/src/components/ConnectWalletButton/index.js new file mode 100644 index 00000000..6ab3cda2 --- /dev/null +++ b/src/components/ConnectWalletButton/index.js @@ -0,0 +1,170 @@ +import React, {useRef, useState} from 'react'; +import styles from './styles.module.css'; +import Notification from "../Notification"; +import Dialog from "../Dialog"; + + + +const networksList = [ + { + chainId: "0x2e", + chainName: "Darwinia Smart Chain", + nativeCurrency: { + name: "RING", + symbol: "RING", + decimals: 18, + }, + rpcUrls: ["https://rpc.darwinia.network"], + blockExplorerUrls: ["https://darwinia.subscan.io/"], + }, + { + chainId: "0x2d", + chainName: "Pangoro Smart Chain", + nativeCurrency: { + name: "ORING", + symbol: "ORING", + decimals: 18, + }, + rpcUrls: ["https://pangoro-rpc.darwinia.network"], + blockExplorerUrls: ["https://pangoro.subscan.io/"], + }, +]; + +const ellipsisAddress = (address) => { + return `${address.substring(0, 6)}...${address.substring(address.length - 4)}`; +}; + +const ConnectWalletButton = () => { + const [connected, setConnected] = useState(null); + const provider = useRef(); + const notificationRef = useRef(null); + const dialogRef = useRef(null); + const handleConnectToWallet = () => { + if(!dialogRef.current) { + return; + } + dialogRef.current.show(); + } + + const getMetamaskAccounts = async (networkIndex, chainName) => { + const accounts = await provider.current.request({ + method: "eth_requestAccounts", + }); + setConnected({ + index: networkIndex, + account: accounts[0], + chainName, + }); + return true; + } + + const onSelectNetwork = async (index) => { + if (!provider.current) { + provider.current = window.ethereum; + } + + if (typeof provider.current !== 'undefined') { + // Metamask is installed + const networkParams = networksList[index]; + try { + const response = await provider.current.request({ + method: "wallet_switchEthereumChain", + params: [{chainId: networkParams.chainId}], + }); + // chainId already exists in Metamask + if (!response) { + await getMetamaskAccounts(index, networkParams.chainName); + } + } catch (switchNetworkError) { + if (switchNetworkError.code === 4902) { + // chainId doesn't exist in Metamask, add it with wallet_addEthereumChain + try { + const response = await provider.current.request({ + method: "wallet_addEthereumChain", + params: [ + { + chainId: networkParams.chainId, + chainName: networkParams.chainName, + nativeCurrency: networkParams.nativeCurrency, + rpcUrls: [...networkParams.rpcUrls], + blockExplorerUrls: [...networkParams.blockExplorerUrls], + }, + ], + }); + if (!response) { + await getMetamaskAccounts(index, networkParams.chainName); + } + } catch (addNetworkError) { + notificationRef.current.show({ + title: "Oops, something wrong", + message: (addNetworkError).message, + type: 'danger' + }); + } + } else { + notificationRef.current.show({ + title: "Oops, something wrong", + message: (switchNetworkError).message, + type: 'danger' + }); + } + } + } else { + // Metamask is not installed + notificationRef.current.show({ + title: "Oops, something is not quite right.", + message: ( +

+ It looks like MetaMask hasn't been installed. Please{" "} + + install MetaMask + {" "} + and try again. +

+ ), + }); + } + } + + + const dialogBody = () => { + return ( + + ) + } + + return ( +
+ + Please select a network to connect
} + body={dialogBody()}/> + + + ) +} + +export default ConnectWalletButton; diff --git a/src/components/ConnectWalletButton/styles.module.css b/src/components/ConnectWalletButton/styles.module.css new file mode 100644 index 00000000..b1cafaca --- /dev/null +++ b/src/components/ConnectWalletButton/styles.module.css @@ -0,0 +1,45 @@ +.connectWalletBtn { + outline: none; + border: 1px solid rgb(216,0,115); + border-radius: 0.25rem; + padding: 6px 15px; + background: transparent; + color: rgb(216,0,115); + margin-right: 10px; + font-size: medium; +} +.connectWalletBtn:hover { + opacity: 0.7; + cursor: pointer; +} +.chainSelectModalTitle { + font-weight: 400; + font-size: 19px; +} +.networkItem { + margin: 10px 0; +} +.networkOption { + padding: 8px 20px; + display: inline-block; + border: 1px solid rgb(216,0,115); + border-radius: 0.25rem; + min-width: 110px; + text-align: center; + background: transparent; + font-size: 16px; +} +.networkOption:hover { + opacity: 0.7; + cursor: pointer; +} +.connectedTo { + padding: 4.55px 0; + display: inline-block; +} + +@media (max-width: 996px) { + .connectWalletBtn { + margin-right: 0; + } +} diff --git a/src/components/Dialog/index.js b/src/components/Dialog/index.js new file mode 100644 index 00000000..21658d6c --- /dev/null +++ b/src/components/Dialog/index.js @@ -0,0 +1,46 @@ +import React, {forwardRef, useImperativeHandle, useState} from "react"; +import styles from './styles.module.css'; +import {CSSTransition} from "react-transition-group"; + +const Dialog = forwardRef(({title, body}, ref) => { + const [isVisible, setVisible] = useState(false); + + const show = () => { + setVisible(true); + } + + const hide = () => { + setVisible(false); + } + + useImperativeHandle(ref, ()=> { + return { + show + } + }) + + return ( + +
{hide()}} className={styles.wrapper}> +
{e.stopPropagation()}} className={`${styles.enterDone} ${styles.content}`}> +
+ {title} +
{hide()}} className={styles.closeBtn}> + +
+
+
{body}
+
+
+
+ ); +}) + +export default Dialog diff --git a/src/components/Dialog/styles.module.css b/src/components/Dialog/styles.module.css new file mode 100644 index 00000000..cd906a98 --- /dev/null +++ b/src/components/Dialog/styles.module.css @@ -0,0 +1,93 @@ +.wrapper { + position: fixed; + top: 0; + right: 0; + bottom: 0; + left: 0; + overflow: auto; + outline: 0; + background: rgba(0, 0, 0, 0.45); + display: flex; + justify-content: center; + align-items: center; + z-index: 200; +} +html[data-theme='dark'] .content { + background: #444444; + color: white; +} +.content { + position: absolute; + width: 85%; + max-width: 520px; + background: white; + border-radius: 8px; +} +.title { + text-transform: capitalize; + font-weight: bold; + text-align: center; + border-bottom: 1px solid #f0f0f0; + padding: 15px 45px; + position: relative; +} +html[data-theme='dark'] .title { + border-bottom-color: #606770; +} +.closeBtn { + position: absolute; + right: 15px; + top: 50%; + transform: translateY(-50%); +} +.closeBtn:hover { + opacity: 0.7; + cursor: pointer; +} +.body { + padding: 10px; +} + +.enter { + opacity: 0; +} +.enterActive { + transition: opacity 200ms ease-in-out; + opacity: 1; +} +.enterDone { + opacity: 1; +} +.exit { + opacity: 1; +} +.exitActive { + transition: opacity 200ms ease-in-out; + opacity: 0; +} +.exitDone { + opacity: 0; +} +.enter .content { + transform: scale(0.5); +} +.enterActive .content { + transform: scale(1.15); + transition: transform 150ms ease-in-out; + transition-delay: 50ms; +} +.enterDone .content { + transform: scale(1); + transition: transform 150ms ease-in-out; +} +.exit .content { + transform: scale(1); +} +.exitActive .content { + transform: scale(0.5); + transition: transform 150ms ease-in-out; +} +.exitDone .content { + transform: scale(0.5); + transition: transform 150ms ease-in-out; +} diff --git a/src/components/Notification/index.js b/src/components/Notification/index.js new file mode 100644 index 00000000..71bfaf31 --- /dev/null +++ b/src/components/Notification/index.js @@ -0,0 +1,52 @@ +import React, {useState, forwardRef, useImperativeHandle, useRef} from "react"; +import styles from './styles.module.css'; +import {CSSTransition} from 'react-transition-group'; + +const Notification = forwardRef(({}, ref) => { + const [isVisible, setVisible] = useState(false); + const [notification, setNotification] = useState({title:'title', message: 'message', type: 'success'}); + const timerRef = useRef(null); + const show = (notification) => { + setNotification(notification); + setVisible(true); + timerRef.current = setTimeout(()=> { + setVisible(false); + },4000); + } + const hide = () => { + try { + if(timerRef.current) { + clearTimeout(timerRef.current); + } + setVisible(false); + }catch (e) { + //ignore + } + } + useImperativeHandle(ref, ()=> { + return { + show + } + }); + const titleClasses = `${notification.type === 'danger' ? styles.danger : ''}`; + return ( + +
+
{notification.title}
+
{notification.message}
+
{hide()}} className={styles.closeBtn}> + +
+
+
+ ) +}); + +export default Notification diff --git a/src/components/Notification/styles.module.css b/src/components/Notification/styles.module.css new file mode 100644 index 00000000..5c1073d9 --- /dev/null +++ b/src/components/Notification/styles.module.css @@ -0,0 +1,55 @@ +.wrapper { + box-shadow: 0 3px 6px -4px #0000001f, 0 6px 16px #00000014, 0 9px 28px 8px #0000000d; + position: fixed; + top: 40px; + right: 20px; + width: 80%; + max-width: 320px; + z-index: 9999; + background: white; + border-radius: 8px; + padding: 10px; + min-height: 90px; +} +html[data-theme='dark'] .wrapper { + background: #444444; + color: white; +} +.title { + text-transform: capitalize; + font-weight: bold; + margin-bottom: 5px; + padding-right: 20px; +} +.danger { + color: red; +} +.closeBtn { + position: absolute; + right: 10px; + top: 14px; +} +.closeBtn:hover { + opacity: 0.7; + cursor: pointer; +} +.enter { + transform: translate3d(110%, 0, 0); +} +.enterActive { + transition: transform 200ms ease-in-out; + transform: translate3d(1px, 0, 0); +} +.enterDone { + transform: translate3d(0px, 0, 0); +} +.exit { + transform: translate3d(1px, 0, 0); +} +.exitActive { + transition: transform 200ms ease-in-out; + transform: translate3d(109%, 0, 0); +} +.exitDone { + transform: translate3d(110%, 0, 0); +} diff --git a/src/theme/Navbar/Content/index.js b/src/theme/Navbar/Content/index.js new file mode 100644 index 00000000..f571eb0d --- /dev/null +++ b/src/theme/Navbar/Content/index.js @@ -0,0 +1,68 @@ +import React from 'react'; +import NavbarItem from '@theme/NavbarItem'; +import NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle'; +import SearchBar from '@theme/SearchBar'; +import { + splitNavbarItems, + useNavbarMobileSidebar, + useThemeConfig, +} from '@docusaurus/theme-common'; +import NavbarMobileSidebarToggle from '@theme/Navbar/MobileSidebar/Toggle'; +import NavbarLogo from '@theme/Navbar/Logo'; +import styles from './styles.module.css'; +import ConnectWalletButton from "../../../components/ConnectWalletButton"; + +function useNavbarItems() { + return useThemeConfig().navbar.items || []; +} + +function NavbarItems({items}) { + return ( + <> + {items.map((item, i) => ( + + ))} + + ); +} + +function NavbarContentLayout({ + left, + right, +}) { + return ( +
+
{left}
+
{right}
+
+ ); +} + +export default function NavbarContent() { + const mobileSidebar = useNavbarMobileSidebar(); + + const items = useNavbarItems(); + const [leftItems, rightItems] = splitNavbarItems(items); + + const autoAddSearchBar = !items.some((item) => item.type === 'search'); + + return ( + + {!mobileSidebar.disabled && } + + + + } + right={ + <> + + + + {autoAddSearchBar && } + + } + /> + ); +} diff --git a/src/theme/Navbar/Content/styles.module.css b/src/theme/Navbar/Content/styles.module.css new file mode 100644 index 00000000..3cbe701f --- /dev/null +++ b/src/theme/Navbar/Content/styles.module.css @@ -0,0 +1,15 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +/* +Hide color mode toggle in small viewports + */ +@media (max-width: 996px) { + .colorModeToggle { + display: none; + } +} diff --git a/src/theme/Navbar/index.js b/src/theme/Navbar/index.js new file mode 100644 index 00000000..2d7687b9 --- /dev/null +++ b/src/theme/Navbar/index.js @@ -0,0 +1,14 @@ +import React from 'react'; +import NavbarLayout from '@theme/Navbar/Layout'; +import NavbarContent from './Content'; + +const Navbar = () => { + return ( + + + + ); +} + +export default Navbar; + diff --git a/yarn.lock b/yarn.lock index a3e9aa77..fd8f94d3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1131,6 +1131,13 @@ dependencies: regenerator-runtime "^0.13.4" +"@babel/runtime@^7.5.5", "@babel/runtime@^7.8.7": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== + dependencies: + regenerator-runtime "^0.13.4" + "@babel/template@^7.12.7", "@babel/template@^7.16.7": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -3678,6 +3685,14 @@ dom-converter@^0.2.0: dependencies: utila "~0.4" +dom-helpers@^5.0.1: + version "5.2.1" + resolved "https://registry.yarnpkg.com/dom-helpers/-/dom-helpers-5.2.1.tgz#d9400536b2bf8225ad98fe052e029451ac40e902" + integrity sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA== + dependencies: + "@babel/runtime" "^7.8.7" + csstype "^3.0.2" + dom-iterator@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/dom-iterator/-/dom-iterator-1.0.0.tgz#9c09899846ec41c2d257adc4d6015e4759ef05ad" @@ -6435,6 +6450,16 @@ react-textarea-autosize@^8.3.2: use-composed-ref "^1.3.0" use-latest "^1.2.1" +react-transition-group@^4.4.5: + version "4.4.5" + resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" + integrity sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g== + dependencies: + "@babel/runtime" "^7.5.5" + dom-helpers "^5.0.1" + loose-envify "^1.4.0" + prop-types "^15.6.2" + react@^17.0.2: version "17.0.2" resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"