Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
be57b2facf | ||
|
|
0ed1f33808 | ||
|
|
48e67f5681 | ||
|
|
da1ac0d087 | ||
|
|
17fce5417a | ||
|
|
b86a346be3 | ||
|
|
5cecc40a1c | ||
|
|
7134fa541d |
+18
-9
@@ -5,29 +5,35 @@ import styled from 'styled-components';
|
|||||||
|
|
||||||
import { store } from '@/state/_store';
|
import { store } from '@/state/_store';
|
||||||
|
|
||||||
|
import { GlobalStyle } from '@/styles/globalStyle';
|
||||||
|
|
||||||
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
|
import { SelectMenu, SelectItem } from '@/components/SelectMenu';
|
||||||
|
|
||||||
|
import { AppThemeProvider } from '@/hooks/useAppTheme';
|
||||||
|
|
||||||
|
import { AppTheme, setAppTheme } from '@/state/configs';
|
||||||
import { setLocaleLoaded } from '@/state/localization';
|
import { setLocaleLoaded } from '@/state/localization';
|
||||||
|
|
||||||
import '@/index.css';
|
import '@/index.css';
|
||||||
import './ladle.css';
|
import './ladle.css';
|
||||||
|
|
||||||
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => {
|
||||||
const [theme, setTheme] = useState('Default theme');
|
const [theme, setTheme] = useState(AppTheme.Classic);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
store.dispatch(setAppTheme(theme));
|
||||||
switch (theme) {
|
switch (theme) {
|
||||||
case 'Dark theme': {
|
case AppTheme.Dark: {
|
||||||
document?.documentElement?.classList.remove('theme-light');
|
document?.documentElement?.classList.remove('theme-light');
|
||||||
document?.documentElement?.classList.add('theme-dark');
|
document?.documentElement?.classList.add('theme-dark');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case 'Light theme': {
|
case AppTheme.Light: {
|
||||||
document?.documentElement?.classList.remove('theme-dark');
|
document?.documentElement?.classList.remove('theme-dark');
|
||||||
document?.documentElement?.classList.add('theme-light');
|
document?.documentElement?.classList.add('theme-light');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default: {
|
case AppTheme.Classic: {
|
||||||
document?.documentElement?.classList.remove('theme-dark', 'theme-light');
|
document?.documentElement?.classList.remove('theme-dark', 'theme-light');
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -48,15 +54,15 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
>
|
>
|
||||||
{[
|
{[
|
||||||
{
|
{
|
||||||
value: 'Default theme',
|
value: AppTheme.Classic,
|
||||||
label: 'Default theme',
|
label: 'Default theme',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'Dark theme',
|
value: AppTheme.Dark,
|
||||||
label: 'Dark theme',
|
label: 'Dark theme',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
value: 'Light theme',
|
value: AppTheme.Light,
|
||||||
label: 'Light theme',
|
label: 'Light theme',
|
||||||
},
|
},
|
||||||
].map(({ value, label }) => (
|
].map(({ value, label }) => (
|
||||||
@@ -69,8 +75,11 @@ export const StoryWrapper: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
</SelectMenu>
|
</SelectMenu>
|
||||||
</StoryHeader>
|
</StoryHeader>
|
||||||
<hr />
|
<hr />
|
||||||
<StoryContent>{children}</StoryContent>
|
<AppThemeProvider>
|
||||||
</Provider>
|
<GlobalStyle />
|
||||||
|
<StoryContent>{children}</StoryContent>
|
||||||
|
</AppThemeProvider>
|
||||||
|
</Provider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
+7
-7
@@ -33,14 +33,14 @@
|
|||||||
"packageManager": "pnpm@8.6.6",
|
"packageManager": "pnpm@8.6.6",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@0xsquid/sdk": "^1.10.0",
|
"@0xsquid/sdk": "^1.10.0",
|
||||||
"@cosmjs/amino": "^0.31.0",
|
"@cosmjs/amino": "^0.32.1",
|
||||||
"@cosmjs/crypto": "^0.31.0",
|
"@cosmjs/crypto": "^0.32.1",
|
||||||
"@cosmjs/encoding": "^0.31.0",
|
"@cosmjs/encoding": "^0.32.1",
|
||||||
"@cosmjs/proto-signing": "^0.31.0",
|
"@cosmjs/proto-signing": "^0.32.1",
|
||||||
"@cosmjs/stargate": "^0.31.0",
|
"@cosmjs/stargate": "^0.32.1",
|
||||||
"@cosmjs/tendermint-rpc": "^0.31.0",
|
"@cosmjs/tendermint-rpc": "^0.32.1",
|
||||||
"@dydxprotocol/v4-abacus": "^1.2.5",
|
"@dydxprotocol/v4-abacus": "^1.2.5",
|
||||||
"@dydxprotocol/v4-client-js": "^1.0.11",
|
"@dydxprotocol/v4-client-js": "^1.0.14",
|
||||||
"@dydxprotocol/v4-localization": "^1.1.11",
|
"@dydxprotocol/v4-localization": "^1.1.11",
|
||||||
"@ethersproject/providers": "^5.7.2",
|
"@ethersproject/providers": "^5.7.2",
|
||||||
"@js-joda/core": "^5.5.3",
|
"@js-joda/core": "^5.5.3",
|
||||||
|
|||||||
Generated
+160
-53
@@ -1,33 +1,37 @@
|
|||||||
lockfileVersion: '6.0'
|
lockfileVersion: '6.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
'@0xsquid/sdk':
|
'@0xsquid/sdk':
|
||||||
specifier: ^1.10.0
|
specifier: ^1.10.0
|
||||||
version: 1.10.0
|
version: 1.10.0
|
||||||
'@cosmjs/amino':
|
'@cosmjs/amino':
|
||||||
specifier: ^0.31.0
|
specifier: ^0.32.1
|
||||||
version: 0.31.0
|
version: 0.32.2
|
||||||
'@cosmjs/crypto':
|
'@cosmjs/crypto':
|
||||||
specifier: ^0.31.0
|
specifier: ^0.32.1
|
||||||
version: 0.31.0
|
version: 0.32.2
|
||||||
'@cosmjs/encoding':
|
'@cosmjs/encoding':
|
||||||
specifier: ^0.31.0
|
specifier: ^0.32.1
|
||||||
version: 0.31.0
|
version: 0.32.2
|
||||||
'@cosmjs/proto-signing':
|
'@cosmjs/proto-signing':
|
||||||
specifier: ^0.31.0
|
specifier: ^0.32.1
|
||||||
version: 0.31.0
|
version: 0.32.2
|
||||||
'@cosmjs/stargate':
|
'@cosmjs/stargate':
|
||||||
specifier: ^0.31.0
|
specifier: ^0.32.1
|
||||||
version: 0.31.0
|
version: 0.32.2
|
||||||
'@cosmjs/tendermint-rpc':
|
'@cosmjs/tendermint-rpc':
|
||||||
specifier: ^0.31.0
|
specifier: ^0.32.1
|
||||||
version: 0.31.0
|
version: 0.32.2
|
||||||
'@dydxprotocol/v4-abacus':
|
'@dydxprotocol/v4-abacus':
|
||||||
specifier: ^1.2.5
|
specifier: ^1.2.5
|
||||||
version: 1.2.5
|
version: 1.2.5
|
||||||
'@dydxprotocol/v4-client-js':
|
'@dydxprotocol/v4-client-js':
|
||||||
specifier: ^1.0.11
|
specifier: ^1.0.14
|
||||||
version: 1.0.11
|
version: 1.0.14
|
||||||
'@dydxprotocol/v4-localization':
|
'@dydxprotocol/v4-localization':
|
||||||
specifier: ^1.1.11
|
specifier: ^1.1.11
|
||||||
version: 1.1.11
|
version: 1.1.11
|
||||||
@@ -684,6 +688,15 @@ packages:
|
|||||||
'@cosmjs/utils': 0.31.0
|
'@cosmjs/utils': 0.31.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@cosmjs/amino@0.32.2:
|
||||||
|
resolution: {integrity: sha512-lcK5RCVm4OfdAooxKcF2+NwaDVVpghOq6o/A40c2mHXDUzUoRZ33VAHjVJ9Me6vOFxshrw/XEFn1f4KObntjYA==}
|
||||||
|
dependencies:
|
||||||
|
'@cosmjs/crypto': 0.32.2
|
||||||
|
'@cosmjs/encoding': 0.32.2
|
||||||
|
'@cosmjs/math': 0.32.2
|
||||||
|
'@cosmjs/utils': 0.32.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/cosmwasm-stargate@0.30.1:
|
/@cosmjs/cosmwasm-stargate@0.30.1:
|
||||||
resolution: {integrity: sha512-W/6SLUCJAJGBN+sJLXouLZikVgmqDd9LCdlMzQaxczcCHTWeJAmRvOiZGSZaSy3shw/JN1qc6g6PKpvTVgj10A==}
|
resolution: {integrity: sha512-W/6SLUCJAJGBN+sJLXouLZikVgmqDd9LCdlMzQaxczcCHTWeJAmRvOiZGSZaSy3shw/JN1qc6g6PKpvTVgj10A==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -737,7 +750,19 @@ packages:
|
|||||||
'@cosmjs/encoding': 0.31.0
|
'@cosmjs/encoding': 0.31.0
|
||||||
'@cosmjs/math': 0.31.0
|
'@cosmjs/math': 0.31.0
|
||||||
'@cosmjs/utils': 0.31.0
|
'@cosmjs/utils': 0.31.0
|
||||||
'@noble/hashes': 1.3.1
|
'@noble/hashes': 1.3.3
|
||||||
|
bn.js: 5.2.1
|
||||||
|
elliptic: 6.5.4
|
||||||
|
libsodium-wrappers-sumo: 0.7.11
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/@cosmjs/crypto@0.32.2:
|
||||||
|
resolution: {integrity: sha512-RuxrYKzhrPF9g6NmU7VEq++Hn1vZJjqqJpZ9Tmw9lOYOV8BUsv+j/0BE86kmWi7xVJ7EwxiuxYsKuM8IR18CIA==}
|
||||||
|
dependencies:
|
||||||
|
'@cosmjs/encoding': 0.32.2
|
||||||
|
'@cosmjs/math': 0.32.2
|
||||||
|
'@cosmjs/utils': 0.32.2
|
||||||
|
'@noble/hashes': 1.3.3
|
||||||
bn.js: 5.2.1
|
bn.js: 5.2.1
|
||||||
elliptic: 6.5.4
|
elliptic: 6.5.4
|
||||||
libsodium-wrappers-sumo: 0.7.11
|
libsodium-wrappers-sumo: 0.7.11
|
||||||
@@ -767,8 +792,8 @@ packages:
|
|||||||
readonly-date: 1.0.0
|
readonly-date: 1.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/encoding@0.31.1:
|
/@cosmjs/encoding@0.32.2:
|
||||||
resolution: {integrity: sha512-IuxP6ewwX6vg9sUJ8ocJD92pkerI4lyG8J5ynAM3NaX3q+n+uMoPRSQXNeL9bnlrv01FF1kIm8if/f5F7ZPtkA==}
|
resolution: {integrity: sha512-WX7m1wLpA9V/zH0zRcz4EmgZdAv1F44g4dbXOgNj1eXZw1PIGR12p58OEkLN51Ha3S4DKRtCv5CkhK1KHEvQtg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
base64-js: 1.5.1
|
base64-js: 1.5.1
|
||||||
bech32: 1.1.4
|
bech32: 1.1.4
|
||||||
@@ -789,6 +814,13 @@ packages:
|
|||||||
xstream: 11.14.0
|
xstream: 11.14.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@cosmjs/json-rpc@0.32.2:
|
||||||
|
resolution: {integrity: sha512-lan2lOgmz4yVE/HR8eCOSiII/1OudIulk8836koyIDCsPEpt6eKBuctnAD168vABGArKccLAo7Mr2gy9nrKrOQ==}
|
||||||
|
dependencies:
|
||||||
|
'@cosmjs/stream': 0.32.2
|
||||||
|
xstream: 11.14.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/launchpad@0.27.1:
|
/@cosmjs/launchpad@0.27.1:
|
||||||
resolution: {integrity: sha512-DcFwGD/z5PK8CzO2sojDxa+Be9EIEtRZb2YawgVnw2Ht/p5FlNv+OVo8qlishpBdalXEN7FvQ1dVeDFEe9TuJw==}
|
resolution: {integrity: sha512-DcFwGD/z5PK8CzO2sojDxa+Be9EIEtRZb2YawgVnw2Ht/p5FlNv+OVo8qlishpBdalXEN7FvQ1dVeDFEe9TuJw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -821,8 +853,8 @@ packages:
|
|||||||
bn.js: 5.2.1
|
bn.js: 5.2.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/math@0.31.1:
|
/@cosmjs/math@0.32.2:
|
||||||
resolution: {integrity: sha512-kiuHV6m6DSB8/4UV1qpFhlc4ul8SgLXTGRlYkYiIIP4l0YNeJ+OpPYaOlEgx4Unk2mW3/O2FWYj7Jc93+BWXng==}
|
resolution: {integrity: sha512-b8+ruAAY8aKtVKWSft2IvtCVCUH1LigIlf9ALIiY8n9jtM4kMASiaRbQ/27etnSAInV88IaezKK9rQZrtxTjcw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
bn.js: 5.2.1
|
bn.js: 5.2.1
|
||||||
dev: false
|
dev: false
|
||||||
@@ -851,6 +883,17 @@ packages:
|
|||||||
long: 4.0.0
|
long: 4.0.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@cosmjs/proto-signing@0.32.2:
|
||||||
|
resolution: {integrity: sha512-UV4WwkE3W3G3s7wwU9rizNcUEz2g0W8jQZS5J6/3fiN0mRPwtPKQ6EinPN9ASqcAJ7/VQH4/9EPOw7d6XQGnqw==}
|
||||||
|
dependencies:
|
||||||
|
'@cosmjs/amino': 0.32.2
|
||||||
|
'@cosmjs/crypto': 0.32.2
|
||||||
|
'@cosmjs/encoding': 0.32.2
|
||||||
|
'@cosmjs/math': 0.32.2
|
||||||
|
'@cosmjs/utils': 0.32.2
|
||||||
|
cosmjs-types: 0.9.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/socket@0.30.1:
|
/@cosmjs/socket@0.30.1:
|
||||||
resolution: {integrity: sha512-r6MpDL+9N+qOS/D5VaxnPaMJ3flwQ36G+vPvYJsXArj93BjgyFB7BwWwXCQDzZ+23cfChPUfhbINOenr8N2Kow==}
|
resolution: {integrity: sha512-r6MpDL+9N+qOS/D5VaxnPaMJ3flwQ36G+vPvYJsXArj93BjgyFB7BwWwXCQDzZ+23cfChPUfhbINOenr8N2Kow==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -875,6 +918,18 @@ packages:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@cosmjs/socket@0.32.2:
|
||||||
|
resolution: {integrity: sha512-Qc8jaw4uSBJm09UwPgkqe3g9TBFx4ZR9HkXpwT6Z9I+6kbLerXPR0Gy3NSJFSUgxIfTpO8O1yqoWAyf0Ay17Mw==}
|
||||||
|
dependencies:
|
||||||
|
'@cosmjs/stream': 0.32.2
|
||||||
|
isomorphic-ws: 4.0.1(ws@7.5.9)
|
||||||
|
ws: 7.5.9
|
||||||
|
xstream: 11.14.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- utf-8-validate
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/stargate@0.30.1:
|
/@cosmjs/stargate@0.30.1:
|
||||||
resolution: {integrity: sha512-RdbYKZCGOH8gWebO7r6WvNnQMxHrNXInY/gPHPzMjbQF6UatA6fNM2G2tdgS5j5u7FTqlCI10stNXrknaNdzog==}
|
resolution: {integrity: sha512-RdbYKZCGOH8gWebO7r6WvNnQMxHrNXInY/gPHPzMjbQF6UatA6fNM2G2tdgS5j5u7FTqlCI10stNXrknaNdzog==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -917,6 +972,25 @@ packages:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@cosmjs/stargate@0.32.2:
|
||||||
|
resolution: {integrity: sha512-AsJa29fT7Jd4xt9Ai+HMqhyj7UQu7fyYKdXj/8+/9PD74xe6lZSYhQPcitUmMLJ1ckKPgXSk5Dd2LbsQT0IhZg==}
|
||||||
|
dependencies:
|
||||||
|
'@confio/ics23': 0.6.8
|
||||||
|
'@cosmjs/amino': 0.32.2
|
||||||
|
'@cosmjs/encoding': 0.32.2
|
||||||
|
'@cosmjs/math': 0.32.2
|
||||||
|
'@cosmjs/proto-signing': 0.32.2
|
||||||
|
'@cosmjs/stream': 0.32.2
|
||||||
|
'@cosmjs/tendermint-rpc': 0.32.2
|
||||||
|
'@cosmjs/utils': 0.32.2
|
||||||
|
cosmjs-types: 0.9.0
|
||||||
|
xstream: 11.14.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- debug
|
||||||
|
- utf-8-validate
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/stream@0.30.1:
|
/@cosmjs/stream@0.30.1:
|
||||||
resolution: {integrity: sha512-Fg0pWz1zXQdoxQZpdHRMGvUH5RqS6tPv+j9Eh7Q953UjMlrwZVo0YFLC8OTf/HKVf10E4i0u6aM8D69Q6cNkgQ==}
|
resolution: {integrity: sha512-Fg0pWz1zXQdoxQZpdHRMGvUH5RqS6tPv+j9Eh7Q953UjMlrwZVo0YFLC8OTf/HKVf10E4i0u6aM8D69Q6cNkgQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -929,6 +1003,12 @@ packages:
|
|||||||
xstream: 11.14.0
|
xstream: 11.14.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@cosmjs/stream@0.32.2:
|
||||||
|
resolution: {integrity: sha512-gpCufLfHAD8Zp1ZKge7AHbDf4RA0TZp66wZY6JaQR5bSiEF2Drjtp4mwXZPGejtaUMnaAgff3LrUzPJfKYdQwg==}
|
||||||
|
dependencies:
|
||||||
|
xstream: 11.14.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/tendermint-rpc@0.30.1:
|
/@cosmjs/tendermint-rpc@0.30.1:
|
||||||
resolution: {integrity: sha512-Z3nCwhXSbPZJ++v85zHObeUggrEHVfm1u18ZRwXxFE9ZMl5mXTybnwYhczuYOl7KRskgwlB+rID0WYACxj4wdQ==}
|
resolution: {integrity: sha512-Z3nCwhXSbPZJ++v85zHObeUggrEHVfm1u18ZRwXxFE9ZMl5mXTybnwYhczuYOl7KRskgwlB+rID0WYACxj4wdQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -967,6 +1047,25 @@ packages:
|
|||||||
- utf-8-validate
|
- utf-8-validate
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@cosmjs/tendermint-rpc@0.32.2:
|
||||||
|
resolution: {integrity: sha512-DXyJHDmcAfCix4H/7/dKR0UMdshP01KxJOXHdHxBCbLIpck94BsWD3B2ZTXwfA6sv98so9wOzhp7qGQa5malxg==}
|
||||||
|
dependencies:
|
||||||
|
'@cosmjs/crypto': 0.32.2
|
||||||
|
'@cosmjs/encoding': 0.32.2
|
||||||
|
'@cosmjs/json-rpc': 0.32.2
|
||||||
|
'@cosmjs/math': 0.32.2
|
||||||
|
'@cosmjs/socket': 0.32.2
|
||||||
|
'@cosmjs/stream': 0.32.2
|
||||||
|
'@cosmjs/utils': 0.32.2
|
||||||
|
axios: 1.6.5
|
||||||
|
readonly-date: 1.0.0
|
||||||
|
xstream: 11.14.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- bufferutil
|
||||||
|
- debug
|
||||||
|
- utf-8-validate
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/utils@0.27.1:
|
/@cosmjs/utils@0.27.1:
|
||||||
resolution: {integrity: sha512-VG7QPDiMUzVPxRdJahDV8PXxVdnuAHiIuG56hldV4yPnOz/si/DLNd7VAUUA5923b6jS1Hhev0Hr6AhEkcxBMg==}
|
resolution: {integrity: sha512-VG7QPDiMUzVPxRdJahDV8PXxVdnuAHiIuG56hldV4yPnOz/si/DLNd7VAUUA5923b6jS1Hhev0Hr6AhEkcxBMg==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -979,8 +1078,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-nNcycZWUYLNJlrIXgpcgVRqdl6BXjF4YlXdxobQWpW9Tikk61bEGeAFhDYtC0PwHlokCNw0KxWiHGJL4nL7Q5A==}
|
resolution: {integrity: sha512-nNcycZWUYLNJlrIXgpcgVRqdl6BXjF4YlXdxobQWpW9Tikk61bEGeAFhDYtC0PwHlokCNw0KxWiHGJL4nL7Q5A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@cosmjs/utils@0.31.1:
|
/@cosmjs/utils@0.32.2:
|
||||||
resolution: {integrity: sha512-n4Se1wu4GnKwztQHNFfJvUeWcpvx3o8cWhSbNs9JQShEuB3nv3R5lqFBtDCgHZF/emFQAP+ZjF8bTfCs9UBGhA==}
|
resolution: {integrity: sha512-Gg5t+eR7vPJMAmhkFt6CZrzPd0EKpAslWwk5rFVYZpJsM8JG5KT9XQ99hgNM3Ov6ScNoIWbXkpX27F6A9cXR4Q==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@cush/relative@1.0.0:
|
/@cush/relative@1.0.0:
|
||||||
@@ -991,28 +1090,29 @@ packages:
|
|||||||
resolution: {integrity: sha512-pB3Fv1bzJRUDp29kAN61daZ34gTSGrSTNh+/AP3TUB63/Q/gaXQVBf91xVcso4Hw23xM3FwIcrRK0cvOSWgXKg==}
|
resolution: {integrity: sha512-pB3Fv1bzJRUDp29kAN61daZ34gTSGrSTNh+/AP3TUB63/Q/gaXQVBf91xVcso4Hw23xM3FwIcrRK0cvOSWgXKg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@dydxprotocol/v4-client-js@1.0.11:
|
/@dydxprotocol/v4-client-js@1.0.14:
|
||||||
resolution: {integrity: sha512-rJNFGTO+HU1GQppXeRLKXNk4HWx/h+DjU2sUCieEv5mnRIMQpLw8dH/L+Uyhxx2oovccY1MaKB4Fr9IngdrF0A==}
|
resolution: {integrity: sha512-nIjj9OKuXb3n3rrihDPkBE6OIBxXNB7gf0NMsrG1Pc9O7M0yjpitTUgnfIzplf0XMrtmozWRYZhlLgBfJcIbBg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@cosmjs/amino': 0.30.1
|
'@cosmjs/amino': 0.32.2
|
||||||
'@cosmjs/encoding': 0.31.1
|
'@cosmjs/encoding': 0.32.2
|
||||||
'@cosmjs/math': 0.31.1
|
'@cosmjs/math': 0.32.2
|
||||||
'@cosmjs/proto-signing': 0.30.1
|
'@cosmjs/proto-signing': 0.32.2
|
||||||
'@cosmjs/stargate': 0.30.1
|
'@cosmjs/stargate': 0.32.2
|
||||||
'@cosmjs/tendermint-rpc': 0.30.1
|
'@cosmjs/tendermint-rpc': 0.32.2
|
||||||
'@cosmjs/utils': 0.31.1
|
'@cosmjs/utils': 0.32.2
|
||||||
'@dydxprotocol/v4-proto': 0.4.1
|
'@dydxprotocol/v4-proto': 3.0.0-dev.0
|
||||||
'@osmonauts/lcd': 0.6.0
|
'@osmonauts/lcd': 0.6.0
|
||||||
'@scure/bip32': 1.3.2
|
'@scure/bip32': 1.3.2
|
||||||
'@scure/bip39': 1.2.1
|
'@scure/bip39': 1.2.1
|
||||||
axios: 1.1.3
|
axios: 1.1.3
|
||||||
bech32: 1.1.4
|
bech32: 1.1.4
|
||||||
bignumber.js: 9.1.1
|
bignumber.js: 9.1.1
|
||||||
|
cosmjs-types: 0.9.0
|
||||||
ethereum-cryptography: 2.1.2
|
ethereum-cryptography: 2.1.2
|
||||||
ethers: 6.6.1
|
ethers: 6.6.1
|
||||||
long: 4.0.0
|
long: 4.0.0
|
||||||
protobufjs: 6.11.4
|
protobufjs: 6.11.4
|
||||||
ws: 8.15.0
|
ws: 8.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- bufferutil
|
- bufferutil
|
||||||
- debug
|
- debug
|
||||||
@@ -1023,8 +1123,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-ZHnyrWD1bEpvY1tctkcmSV6A5+hQSYBFwyjbiyOqepqDqrv24J5a3hlZU94lDyYf+7Sdk3alOvy9Z7Lpk59nvw==}
|
resolution: {integrity: sha512-ZHnyrWD1bEpvY1tctkcmSV6A5+hQSYBFwyjbiyOqepqDqrv24J5a3hlZU94lDyYf+7Sdk3alOvy9Z7Lpk59nvw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@dydxprotocol/v4-proto@0.4.1:
|
/@dydxprotocol/v4-proto@3.0.0-dev.0:
|
||||||
resolution: {integrity: sha512-2pBkokufWcxfIpiXE6QuQOBXUe154OTp6ojCIdOPW9TFLxzchoygxXnb5dL6rAQVNj9Qbg+hhXBllutt4m99dw==}
|
resolution: {integrity: sha512-hT6F/AgaTqv8Bwo7twpIhmjXvJE/Fx+3mmHHTuIXMeL6OhVtlOpcEQyHvBEAdh1VrNq/S7qWvdQD0fvC/UgKyA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
protobufjs: 6.11.4
|
protobufjs: 6.11.4
|
||||||
dev: false
|
dev: false
|
||||||
@@ -7376,7 +7476,7 @@ packages:
|
|||||||
/axios@0.21.4:
|
/axios@0.21.4:
|
||||||
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.2
|
follow-redirects: 1.15.5
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- debug
|
- debug
|
||||||
dev: false
|
dev: false
|
||||||
@@ -7393,7 +7493,17 @@ packages:
|
|||||||
/axios@1.1.3:
|
/axios@1.1.3:
|
||||||
resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==}
|
resolution: {integrity: sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
follow-redirects: 1.15.2
|
follow-redirects: 1.15.5
|
||||||
|
form-data: 4.0.0
|
||||||
|
proxy-from-env: 1.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/axios@1.6.5:
|
||||||
|
resolution: {integrity: sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==}
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.15.5
|
||||||
form-data: 4.0.0
|
form-data: 4.0.0
|
||||||
proxy-from-env: 1.1.0
|
proxy-from-env: 1.1.0
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
@@ -8057,6 +8167,10 @@ packages:
|
|||||||
protobufjs: 6.11.4
|
protobufjs: 6.11.4
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/cosmjs-types@0.9.0:
|
||||||
|
resolution: {integrity: sha512-MN/yUe6mkJwHnCFfsNPeCfXVhyxHYW6c/xDUzrSbBycYzw++XvWDMJArXp2pLdgD6FQ8DW79vkPjeNKVrXaHeQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/cosmos-directory-client@0.0.6:
|
/cosmos-directory-client@0.0.6:
|
||||||
resolution: {integrity: sha512-WIdaQ8uW1vIbYvNnAVunkC6yxTrneJC7VQ5UUQ0kuw8b0C0A39KTIpoQHCfc8tV7o9vF4niwRhdXEdfAgQEsQQ==}
|
resolution: {integrity: sha512-WIdaQ8uW1vIbYvNnAVunkC6yxTrneJC7VQ5UUQ0kuw8b0C0A39KTIpoQHCfc8tV7o9vF4niwRhdXEdfAgQEsQQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -9422,6 +9536,16 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/follow-redirects@1.15.5:
|
||||||
|
resolution: {integrity: sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
dev: false
|
||||||
|
|
||||||
/for-each@0.3.3:
|
/for-each@0.3.3:
|
||||||
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -14677,19 +14801,6 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/ws@8.15.0:
|
|
||||||
resolution: {integrity: sha512-H/Z3H55mrcrgjFwI+5jKavgXvwQLtfPCUEp6pi35VhoB0pfcHnSoyuTzkBEZpzq49g1193CUEwIvmsjcotenYw==}
|
|
||||||
engines: {node: '>=10.0.0'}
|
|
||||||
peerDependencies:
|
|
||||||
bufferutil: ^4.0.1
|
|
||||||
utf-8-validate: '>=5.0.2'
|
|
||||||
peerDependenciesMeta:
|
|
||||||
bufferutil:
|
|
||||||
optional: true
|
|
||||||
utf-8-validate:
|
|
||||||
optional: true
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/ws@8.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10):
|
/ws@8.15.1(bufferutil@4.0.8)(utf-8-validate@5.0.10):
|
||||||
resolution: {integrity: sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==}
|
resolution: {integrity: sha512-W5OZiCjXEmk0yZ66ZN82beM5Sz7l7coYxpRkzS+p9PP+ToQry8szKh+61eNktr7EA9DOwvFGhfC605jDHbP6QQ==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
@@ -14809,7 +14920,3 @@ packages:
|
|||||||
/zwitch@2.0.4:
|
/zwitch@2.0.4:
|
||||||
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
settings:
|
|
||||||
autoInstallPeers: true
|
|
||||||
excludeLinksFromLockfile: false
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"apps": {
|
"apps": {
|
||||||
"ios": {
|
"ios": {
|
||||||
"scheme": "dydxv4"
|
"scheme": "dydx-t-v4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"deployments": {
|
"deployments": {
|
||||||
@@ -215,10 +215,11 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"validators": [
|
"validators": [
|
||||||
"http://validator.v4dev4.dydx.exchange"
|
"https://validator.v4dev4.dydx.exchange"
|
||||||
],
|
],
|
||||||
"0xsquid": "https://testnet.api.0xsquid.com",
|
"0xsquid": "https://testnet.api.0xsquid.com",
|
||||||
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/"
|
"nobleValidator": "https://noble-testnet-rpc.polkachu.com/",
|
||||||
|
"faucet": "https://faucet.v4dev4.dydx.exchange"
|
||||||
},
|
},
|
||||||
"links": {
|
"links": {
|
||||||
"tos": "https://dydx.exchange/v4-terms",
|
"tos": "https://dydx.exchange/v4-terms",
|
||||||
@@ -473,7 +474,7 @@
|
|||||||
"apps": {
|
"apps": {
|
||||||
"ios": {
|
"ios": {
|
||||||
"minimalVersion": "1.0",
|
"minimalVersion": "1.0",
|
||||||
"build":40000,
|
"build": 40000,
|
||||||
"url": "https://apps.apple.com/app/dydx/id1564787350"
|
"url": "https://apps.apple.com/app/dydx/id1564787350"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-34
@@ -16,6 +16,7 @@ import {
|
|||||||
} from '@/hooks';
|
} from '@/hooks';
|
||||||
import { DydxProvider } from '@/hooks/useDydxClient';
|
import { DydxProvider } from '@/hooks/useDydxClient';
|
||||||
import { AccountsProvider } from '@/hooks/useAccounts';
|
import { AccountsProvider } from '@/hooks/useAccounts';
|
||||||
|
import { AppThemeProvider } from '@/hooks/useAppTheme';
|
||||||
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
|
import { DialogAreaProvider, useDialogArea } from '@/hooks/useDialogArea';
|
||||||
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
|
import { LocaleProvider } from '@/hooks/useLocaleSeparators';
|
||||||
import { NotificationsProvider } from '@/hooks/useNotifications';
|
import { NotificationsProvider } from '@/hooks/useNotifications';
|
||||||
@@ -35,6 +36,7 @@ import { GlobalCommandDialog } from '@/views/dialogs/GlobalCommandDialog';
|
|||||||
import { config } from '@/lib/wagmi';
|
import { config } from '@/lib/wagmi';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
|
import { GlobalStyle } from '@/styles/globalStyle';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
import { LoadingSpace } from './components/Loading/LoadingSpinner';
|
import { LoadingSpace } from './components/Loading/LoadingSpinner';
|
||||||
|
|
||||||
@@ -66,49 +68,52 @@ const Content = () => {
|
|||||||
const { chainTokenLabel } = useTokenConfigs();
|
const { chainTokenLabel } = useTokenConfigs();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
|
<>
|
||||||
{isNotTablet && <HeaderDesktop />}
|
<GlobalStyle />
|
||||||
|
<Styled.Content isShowingHeader={isShowingHeader} isShowingFooter={isShowingFooter}>
|
||||||
|
{isNotTablet && <HeaderDesktop />}
|
||||||
|
|
||||||
<Styled.Main>
|
<Styled.Main>
|
||||||
<Suspense fallback={<LoadingSpace id="main" />}>
|
<Suspense fallback={<LoadingSpace id="main" />}>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={AppRoute.Trade}>
|
<Route path={AppRoute.Trade}>
|
||||||
<Route path=":market" element={<TradePage />} />
|
<Route path=":market" element={<TradePage />} />
|
||||||
<Route path={AppRoute.Trade} element={<TradePage />} />
|
<Route path={AppRoute.Trade} element={<TradePage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={AppRoute.Markets} element={<MarketsPage />} />
|
<Route path={AppRoute.Markets} element={<MarketsPage />} />
|
||||||
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
|
<Route path={`/${chainTokenLabel}`} element={<RewardsPage />} />
|
||||||
{isTablet && (
|
{isTablet && (
|
||||||
<>
|
<>
|
||||||
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
|
<Route path={AppRoute.Alerts} element={<AlertsPage />} />
|
||||||
<Route path={AppRoute.Profile} element={<ProfilePage />} />
|
<Route path={AppRoute.Profile} element={<ProfilePage />} />
|
||||||
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
|
<Route path={`${AppRoute.Settings}/*`} element={<SettingsPage />} />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Route element={<GuardedMobileRoute />}>
|
<Route element={<GuardedMobileRoute />}>
|
||||||
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
|
<Route path={`${AppRoute.Portfolio}/*`} element={<PortfolioPage />} />
|
||||||
</Route>
|
</Route>
|
||||||
|
|
||||||
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
|
<Route path={AppRoute.Terms} element={<TermsOfUsePage />} />
|
||||||
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
|
<Route path={AppRoute.Privacy} element={<PrivacyPolicyPage />} />
|
||||||
|
|
||||||
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
|
<Route path="*" element={<Navigate to={DEFAULT_TRADE_ROUTE} replace />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
</Styled.Main>
|
</Styled.Main>
|
||||||
|
|
||||||
{isTablet ? <FooterMobile /> : <FooterDesktop />}
|
{isTablet ? <FooterMobile /> : <FooterDesktop />}
|
||||||
|
|
||||||
<Styled.NotificationsToastArea />
|
<Styled.NotificationsToastArea />
|
||||||
|
|
||||||
<Styled.DialogArea ref={setDialogArea}>
|
<Styled.DialogArea ref={setDialogArea}>
|
||||||
<DialogManager />
|
<DialogManager />
|
||||||
</Styled.DialogArea>
|
</Styled.DialogArea>
|
||||||
|
|
||||||
<GlobalCommandDialog />
|
<GlobalCommandDialog />
|
||||||
</Styled.Content>
|
</Styled.Content>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,6 +136,7 @@ const providers = [
|
|||||||
wrapProvider(LocalNotificationsProvider),
|
wrapProvider(LocalNotificationsProvider),
|
||||||
wrapProvider(NotificationsProvider),
|
wrapProvider(NotificationsProvider),
|
||||||
wrapProvider(DialogAreaProvider),
|
wrapProvider(DialogAreaProvider),
|
||||||
|
wrapProvider(AppThemeProvider),
|
||||||
];
|
];
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { forwardRef } from 'react';
|
||||||
|
|
||||||
|
type CanvasProps = {
|
||||||
|
className?: string;
|
||||||
|
width: number | string;
|
||||||
|
height: number | string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Canvas = forwardRef<HTMLCanvasElement, CanvasProps>(
|
||||||
|
({ className, width, height }, canvasRef) => (
|
||||||
|
<canvas ref={canvasRef} className={className} width={width} height={height} />
|
||||||
|
)
|
||||||
|
);
|
||||||
@@ -14,7 +14,7 @@ export const DiffArrowStory: Story<DiffArrowProps> = (args) => (
|
|||||||
|
|
||||||
DiffArrowStory.argTypes = {
|
DiffArrowStory.argTypes = {
|
||||||
direction: {
|
direction: {
|
||||||
options: ['left', 'right'],
|
options: ['left', 'right', 'up', 'down'],
|
||||||
control: { type: 'select' },
|
control: { type: 'select' },
|
||||||
defaultValue: 'right',
|
defaultValue: 'right',
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ type ElementProps = {
|
|||||||
|
|
||||||
type StyleProps = {
|
type StyleProps = {
|
||||||
className?: string;
|
className?: string;
|
||||||
direction?: 'right' | 'left';
|
direction?: 'right' | 'left' | 'up' | 'down';
|
||||||
};
|
};
|
||||||
|
|
||||||
export type DiffArrowProps = ElementProps & StyleProps;
|
export type DiffArrowProps = ElementProps & StyleProps;
|
||||||
@@ -57,5 +57,11 @@ Styled.DiffArrowContainer = styled.span<DiffArrowProps>`
|
|||||||
left: css`
|
left: css`
|
||||||
transform: scaleX(-1);
|
transform: scaleX(-1);
|
||||||
`,
|
`,
|
||||||
|
up: css`
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
`,
|
||||||
|
down: css`
|
||||||
|
transform: rotate(90deg);
|
||||||
|
`
|
||||||
}[direction || 'right'])}
|
}[direction || 'right'])}
|
||||||
`;
|
`;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ import {
|
|||||||
MenuIcon,
|
MenuIcon,
|
||||||
MigrateIcon,
|
MigrateIcon,
|
||||||
MintscanIcon,
|
MintscanIcon,
|
||||||
|
MoonIcon,
|
||||||
OrderbookIcon,
|
OrderbookIcon,
|
||||||
OrderCanceledIcon,
|
OrderCanceledIcon,
|
||||||
OrderFilledIcon,
|
OrderFilledIcon,
|
||||||
@@ -66,6 +67,7 @@ import {
|
|||||||
ShowIcon,
|
ShowIcon,
|
||||||
TogglesMenuIcon,
|
TogglesMenuIcon,
|
||||||
StarIcon,
|
StarIcon,
|
||||||
|
SunIcon,
|
||||||
TerminalIcon,
|
TerminalIcon,
|
||||||
TradeIcon,
|
TradeIcon,
|
||||||
TransferIcon,
|
TransferIcon,
|
||||||
@@ -119,6 +121,7 @@ export enum IconName {
|
|||||||
Menu = 'Menu',
|
Menu = 'Menu',
|
||||||
Migrate = 'Migrate',
|
Migrate = 'Migrate',
|
||||||
Mintscan = 'Mintscan',
|
Mintscan = 'Mintscan',
|
||||||
|
Moon = 'Moon',
|
||||||
Onboarding = 'Onboarding',
|
Onboarding = 'Onboarding',
|
||||||
Orderbook = 'OrderbookIcon',
|
Orderbook = 'OrderbookIcon',
|
||||||
OrderCanceled = 'OrderCanceled',
|
OrderCanceled = 'OrderCanceled',
|
||||||
@@ -141,6 +144,7 @@ export enum IconName {
|
|||||||
Share = 'Share',
|
Share = 'Share',
|
||||||
Show = 'Show',
|
Show = 'Show',
|
||||||
Star = 'Star',
|
Star = 'Star',
|
||||||
|
Sun = 'Sun',
|
||||||
Terminal = 'Terminal',
|
Terminal = 'Terminal',
|
||||||
TogglesMenu = 'TogglesMenu',
|
TogglesMenu = 'TogglesMenu',
|
||||||
Trade = 'Trade',
|
Trade = 'Trade',
|
||||||
@@ -195,6 +199,7 @@ const icons = {
|
|||||||
[IconName.Menu]: MenuIcon,
|
[IconName.Menu]: MenuIcon,
|
||||||
[IconName.Migrate]: MigrateIcon,
|
[IconName.Migrate]: MigrateIcon,
|
||||||
[IconName.Mintscan]: MintscanIcon,
|
[IconName.Mintscan]: MintscanIcon,
|
||||||
|
[IconName.Moon]: MoonIcon,
|
||||||
[IconName.Orderbook]: OrderbookIcon,
|
[IconName.Orderbook]: OrderbookIcon,
|
||||||
[IconName.OrderCanceled]: OrderCanceledIcon,
|
[IconName.OrderCanceled]: OrderCanceledIcon,
|
||||||
[IconName.OrderFilled]: OrderFilledIcon,
|
[IconName.OrderFilled]: OrderFilledIcon,
|
||||||
@@ -216,6 +221,7 @@ const icons = {
|
|||||||
[IconName.Share]: ShareIcon,
|
[IconName.Share]: ShareIcon,
|
||||||
[IconName.Show]: ShowIcon,
|
[IconName.Show]: ShowIcon,
|
||||||
[IconName.Star]: StarIcon,
|
[IconName.Star]: StarIcon,
|
||||||
|
[IconName.Sun]: SunIcon,
|
||||||
[IconName.Terminal]: TerminalIcon,
|
[IconName.Terminal]: TerminalIcon,
|
||||||
[IconName.TogglesMenu]: TogglesMenuIcon,
|
[IconName.TogglesMenu]: TogglesMenuIcon,
|
||||||
[IconName.Trade]: TradeIcon,
|
[IconName.Trade]: TradeIcon,
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
import styled, { type AnyStyledComponent } from 'styled-components';
|
import styled, { type AnyStyledComponent } from 'styled-components';
|
||||||
|
|
||||||
import { RiskLevels } from '@/constants/abacus';
|
import { RiskLevels } from '@/constants/abacus';
|
||||||
import { UsageColorFromRiskLevel } from '@/styles/colors';
|
|
||||||
|
|
||||||
import { Ring } from '@/components/Ring';
|
import { Ring } from '@/components/Ring';
|
||||||
|
|
||||||
import { abacusHelper } from '@/lib/abacus';
|
import { abacusHelper } from '@/lib/abacus';
|
||||||
|
import { UsageColorFromRiskLevel } from '@/lib/styles';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
value: number;
|
value: number;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { layoutMixins } from '@/styles/layoutMixins';
|
|||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
|
|
||||||
type PanelProps = {
|
type PanelProps = {
|
||||||
slotHeaderContent?: string;
|
slotHeaderContent?: React.ReactNode;
|
||||||
slotHeader?: React.ReactNode;
|
slotHeader?: React.ReactNode;
|
||||||
slotRight?: React.ReactNode;
|
slotRight?: React.ReactNode;
|
||||||
children?: React.ReactNode;
|
children?: React.ReactNode;
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ Styled.Root = styled(Root)`
|
|||||||
--switch-thumb-backgroundColor: var(--color-layer-6);
|
--switch-thumb-backgroundColor: var(--color-layer-6);
|
||||||
|
|
||||||
--switch-active-backgroundColor: var(--color-accent);
|
--switch-active-backgroundColor: var(--color-accent);
|
||||||
--switch-active-thumb-backgroundColor: var(--color-white);
|
--switch-active-thumb-backgroundColor: ${({ theme }) => theme.switchThumbActiveBackground};
|
||||||
|
|
||||||
position: relative;
|
position: relative;
|
||||||
width: var(--switch-width);
|
width: var(--switch-width);
|
||||||
|
|||||||
@@ -982,4 +982,4 @@ Styled.ViewMoreButton = styled(Button)`
|
|||||||
width: 0.675rem;
|
width: 0.675rem;
|
||||||
margin-left: 0.5ch;
|
margin-left: 0.5ch;
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
@@ -2,9 +2,9 @@ import styled, { type AnyStyledComponent } from 'styled-components';
|
|||||||
|
|
||||||
import { type RiskLevels } from '@/constants/abacus';
|
import { type RiskLevels } from '@/constants/abacus';
|
||||||
|
|
||||||
import { UsageColorFromRiskLevel } from '@/styles/colors';
|
|
||||||
|
|
||||||
import { abacusHelper } from '@/lib/abacus';
|
import { abacusHelper } from '@/lib/abacus';
|
||||||
|
import { UsageColorFromRiskLevel } from '@/lib/styles';
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
value: number;
|
value: number;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -110,12 +110,7 @@ Styled.Abbr = styled.abbr`
|
|||||||
|
|
||||||
Styled.Content = styled(Content)`
|
Styled.Content = styled(Content)`
|
||||||
--tooltip-backgroundColor: var(--color-layer-4);
|
--tooltip-backgroundColor: var(--color-layer-4);
|
||||||
--tooltip-backgroundColor: hsl(
|
--tooltip-backgroundColor: ${({ theme }) => theme.tooltipBackground};
|
||||||
var(--layer-base-hue),
|
|
||||||
var(--layer-base-saturation),
|
|
||||||
calc(var(--layer-base-lightness) + 4%),
|
|
||||||
0.66
|
|
||||||
);
|
|
||||||
|
|
||||||
${popoverMixins.popover}
|
${popoverMixins.popover}
|
||||||
--popover-backgroundColor: var(--tooltip-backgroundColor);
|
--popover-backgroundColor: var(--tooltip-backgroundColor);
|
||||||
|
|||||||
@@ -27,12 +27,7 @@ Styled.TooltipContent = styled.aside<{ accentColor?: string }>`
|
|||||||
|
|
||||||
${popoverMixins.popover}
|
${popoverMixins.popover}
|
||||||
--popover-radius: 0.5rem;
|
--popover-radius: 0.5rem;
|
||||||
--popover-background-color: hsl(
|
--popover-background-color: ${({ theme }) => theme.popoverBackground};
|
||||||
var(--layer-base-hue),
|
|
||||||
var(--layer-base-saturation),
|
|
||||||
calc(var(--layer-base-lightness)),
|
|
||||||
0.9
|
|
||||||
);
|
|
||||||
--popover-backdrop-filter: saturate(120%) blur(12px);
|
--popover-backdrop-filter: saturate(120%) blur(12px);
|
||||||
|
|
||||||
display: grid;
|
display: grid;
|
||||||
|
|||||||
+16
-1
@@ -282,13 +282,22 @@ export const TRADE_TYPES: Record<
|
|||||||
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
|
[AbacusOrderType.takeProfitMarket.rawValue]: TradeTypes.TAKE_PROFIT_MARKET,
|
||||||
|
|
||||||
[AbacusOrderType.liquidated.name]: null,
|
[AbacusOrderType.liquidated.name]: null,
|
||||||
[AbacusOrderType.liquidation.name]: null,
|
[AbacusOrderType.liquidated.rawValue]: null,
|
||||||
|
|
||||||
[AbacusOrderType.liquidation.name]: null,
|
[AbacusOrderType.liquidation.name]: null,
|
||||||
[AbacusOrderType.liquidation.rawValue]: null,
|
[AbacusOrderType.liquidation.rawValue]: null,
|
||||||
|
|
||||||
[AbacusOrderType.trailingStop.name]: null,
|
[AbacusOrderType.trailingStop.name]: null,
|
||||||
[AbacusOrderType.trailingStop.rawValue]: null,
|
[AbacusOrderType.trailingStop.rawValue]: null,
|
||||||
|
|
||||||
|
[AbacusOrderType.offsetting.name]: null,
|
||||||
|
[AbacusOrderType.offsetting.rawValue]: null,
|
||||||
|
|
||||||
|
[AbacusOrderType.deleveraged.name]: null,
|
||||||
|
[AbacusOrderType.deleveraged.rawValue]: null,
|
||||||
|
|
||||||
|
[AbacusOrderType.finalSettlement.name]: null,
|
||||||
|
[AbacusOrderType.finalSettlement.rawValue]: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Custom types involving Abacus
|
// Custom types involving Abacus
|
||||||
@@ -307,3 +316,9 @@ export type NetworkConfig = Partial<{
|
|||||||
}>;
|
}>;
|
||||||
|
|
||||||
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
|
export type ConnectNetworkEvent = CustomEvent<Partial<NetworkConfig>>;
|
||||||
|
|
||||||
|
export type PerpetualMarketOrderbookLevel = OrderbookLine & {
|
||||||
|
side?: 'ask' | 'bid';
|
||||||
|
mine: number;
|
||||||
|
key: string;
|
||||||
|
};
|
||||||
|
|||||||
@@ -0,0 +1,15 @@
|
|||||||
|
/**
|
||||||
|
* @description Orderbook display constants
|
||||||
|
*/
|
||||||
|
export const ORDERBOOK_MAX_ROWS_PER_SIDE = 30;
|
||||||
|
export const ORDERBOOK_ANIMATION_DURATION = 400;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Orderbook pixel constants
|
||||||
|
* @note ORDERBOOK_ROW_HEIGHT should be a divisor of ORDERBOOK_HEIGHT so that we do not have a partial row at the bottom
|
||||||
|
* @note To change the Orderbook width, --orderbook-trades-width and ORDERBOOK_WIDTH must be changed to the same value
|
||||||
|
*/
|
||||||
|
export const ORDERBOOK_HEIGHT = 800;
|
||||||
|
export const ORDERBOOK_WIDTH = 300;
|
||||||
|
export const ORDERBOOK_ROW_HEIGHT = 24;
|
||||||
|
export const ORDERBOOK_ROW_PADDING_RIGHT = 8;
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
export enum ColorToken {
|
||||||
|
Black = '#000000',
|
||||||
|
White = '#FFFFFF',
|
||||||
|
|
||||||
|
LightGray0 = '#FAFAFA',
|
||||||
|
LightGray1 = '#F7F8FA',
|
||||||
|
LightGray2 = '#FAFAFD',
|
||||||
|
LightGray3 = '#F5F5F5',
|
||||||
|
LightGray4 = '#EDF0F2',
|
||||||
|
LightGray5 = '#EDEDED',
|
||||||
|
LightGray6 = '#E9ECEE',
|
||||||
|
LightGray7 = '#E6E6E6',
|
||||||
|
LightGray8 = '#DBDBDB',
|
||||||
|
LightGray9 = '#D5DADF',
|
||||||
|
LightGray10 = '#D1D9E0',
|
||||||
|
|
||||||
|
MediumGray0 = '#CACACE',
|
||||||
|
MediumGray1 = '#C2CBD3',
|
||||||
|
|
||||||
|
DarkGray0 = '#888891',
|
||||||
|
DarkGray1 = '#737373',
|
||||||
|
DarkGray2 = '#56565C',
|
||||||
|
DarkGray3 = '#4B4B4B',
|
||||||
|
DarkGray4 = '#494950',
|
||||||
|
DarkGray5 = '#3B3B3F',
|
||||||
|
DarkGray6 = '#303033',
|
||||||
|
DarkGray7 = '#313135',
|
||||||
|
DarkGray8 = '#292929',
|
||||||
|
DarkGray9 = '#23232F',
|
||||||
|
DarkGray10 = '#212124',
|
||||||
|
DarkGray11 = '#18181B',
|
||||||
|
DarkGray12 = '#181818',
|
||||||
|
DarkGray13 = '#0C0C0D',
|
||||||
|
|
||||||
|
GrayBlue0 = '#464659',
|
||||||
|
GrayBlue1 = '#38384D',
|
||||||
|
GrayBlue2 = '#303045',
|
||||||
|
GrayBlue3 = '#29293D',
|
||||||
|
GrayBlue4 = '#212131',
|
||||||
|
GrayBlue5 = '#181825',
|
||||||
|
GrayBlue6 = '#101018',
|
||||||
|
GrayBlue7 = '#08080C',
|
||||||
|
|
||||||
|
GrayPurple0 = '#DFDADF',
|
||||||
|
GrayPurple1 = '#C8C7D8',
|
||||||
|
GrayPurple2 = '#807E98',
|
||||||
|
|
||||||
|
Purple0 = '#7774FF',
|
||||||
|
Purple1 = '#6966FF',
|
||||||
|
|
||||||
|
Green0 = '#1AFFB9',
|
||||||
|
Green1 = '#3ED9A4',
|
||||||
|
Green2 = '#2CCC98',
|
||||||
|
Green3 = '#3EB68A',
|
||||||
|
|
||||||
|
Yellow0 = '#FFCC48',
|
||||||
|
Yellow1 = '#FFB647',
|
||||||
|
|
||||||
|
Red0 = '#FF5C5C',
|
||||||
|
Red1 = '#E76565',
|
||||||
|
Red2 = '#E45555',
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Maps opacity to corresponding hex value */
|
||||||
|
export enum OpacityToken {
|
||||||
|
Opacity16 = '29',
|
||||||
|
Opacity20 = '33',
|
||||||
|
Opacity66 = 'A8',
|
||||||
|
Opacity90 = 'E6',
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
export type ThemeColors = LayerColors &
|
||||||
|
BorderColors &
|
||||||
|
TextColors &
|
||||||
|
GradientColors &
|
||||||
|
AccentColors &
|
||||||
|
StatusColors &
|
||||||
|
DirectionalColors &
|
||||||
|
RiskColors &
|
||||||
|
IconColors &
|
||||||
|
ComponentColors;
|
||||||
|
|
||||||
|
type LayerColors = {
|
||||||
|
layer0: string;
|
||||||
|
layer1: string;
|
||||||
|
layer2: string;
|
||||||
|
layer3: string;
|
||||||
|
layer4: string;
|
||||||
|
layer5: string;
|
||||||
|
layer6: string;
|
||||||
|
layer7: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type BorderColors = {
|
||||||
|
borderDefault: string;
|
||||||
|
borderDestructive: string;
|
||||||
|
borderButton: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type TextColors = {
|
||||||
|
textPrimary: string;
|
||||||
|
textSecondary: string;
|
||||||
|
textTertiary: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type GradientColors = {
|
||||||
|
gradientBase0: string;
|
||||||
|
gradientBase1: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type AccentColors = {
|
||||||
|
accent: string;
|
||||||
|
accentFaded: string;
|
||||||
|
favorite: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type StatusColors = {
|
||||||
|
success: string;
|
||||||
|
warning: string;
|
||||||
|
error: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type DirectionalColors = {
|
||||||
|
positive: string;
|
||||||
|
negative: string;
|
||||||
|
positiveFaded: string;
|
||||||
|
negativeFaded: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type RiskColors = {
|
||||||
|
riskLow: string;
|
||||||
|
riskMedium: string;
|
||||||
|
riskHigh: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type IconColors = {
|
||||||
|
logoFill: string;
|
||||||
|
profileYellow: string;
|
||||||
|
profileRed: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ComponentColors = {
|
||||||
|
inputBackground: string;
|
||||||
|
popoverBackground: string;
|
||||||
|
switchThumbActiveBackground: string;
|
||||||
|
toggleBackground: string;
|
||||||
|
tooltipBackground: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export * from './useCenterOrderbook';
|
||||||
|
export * from './useSpreadRowScrollListener';
|
||||||
|
export * from './useDrawOrderbook';
|
||||||
|
export * from './useOrderbookValues';
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import { useEffect, type RefObject } from 'react';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
marketId: string;
|
||||||
|
orderbookRef: RefObject<HTMLDivElement>;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description Center Orderbook on load and market change
|
||||||
|
* Assumed that the two sides are the same height
|
||||||
|
*/
|
||||||
|
export const useCenterOrderbook = ({ marketId, orderbookRef }: Props) => {
|
||||||
|
const orderbookEl = orderbookRef.current;
|
||||||
|
const { clientHeight, scrollHeight } = orderbookEl ?? {};
|
||||||
|
const shouldScroll = scrollHeight && clientHeight && scrollHeight > clientHeight;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (orderbookEl && shouldScroll) {
|
||||||
|
orderbookEl.scrollTo({ top: (scrollHeight - clientHeight) / 2 });
|
||||||
|
}
|
||||||
|
}, [shouldScroll, marketId]);
|
||||||
|
};
|
||||||
@@ -0,0 +1,373 @@
|
|||||||
|
import { useEffect, useMemo, useRef } from 'react';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import type { PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
||||||
|
import { SMALL_USD_DECIMALS, TOKEN_DECIMALS } from '@/constants/numbers';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ORDERBOOK_ANIMATION_DURATION,
|
||||||
|
ORDERBOOK_HEIGHT,
|
||||||
|
ORDERBOOK_ROW_HEIGHT,
|
||||||
|
ORDERBOOK_ROW_PADDING_RIGHT,
|
||||||
|
ORDERBOOK_WIDTH,
|
||||||
|
} from '@/constants/orderbook';
|
||||||
|
|
||||||
|
import { getCurrentMarketConfig, getCurrentMarketOrderbookMap } from '@/state/perpetualsSelectors';
|
||||||
|
import { getAppTheme } from '@/state/configsSelectors';
|
||||||
|
|
||||||
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
|
|
||||||
|
import {
|
||||||
|
getHistogramXValues,
|
||||||
|
getRektFromIdx,
|
||||||
|
getXByColumn,
|
||||||
|
getYForElements,
|
||||||
|
} from '@/lib/orderbookHelpers';
|
||||||
|
import { useAppThemeContext } from '../useAppTheme';
|
||||||
|
|
||||||
|
type ElementProps = {
|
||||||
|
data: Array<PerpetualMarketOrderbookLevel | undefined>;
|
||||||
|
histogramRange: number;
|
||||||
|
side: PerpetualMarketOrderbookLevel['side'];
|
||||||
|
};
|
||||||
|
|
||||||
|
type StyleProps = {
|
||||||
|
histogramSide: 'left' | 'right';
|
||||||
|
};
|
||||||
|
|
||||||
|
enum OrderbookRowAnimationType {
|
||||||
|
REMOVE,
|
||||||
|
NEW,
|
||||||
|
NONE,
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useDrawOrderbook = ({
|
||||||
|
data,
|
||||||
|
histogramRange,
|
||||||
|
histogramSide,
|
||||||
|
side,
|
||||||
|
}: ElementProps & StyleProps) => {
|
||||||
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
const canvas = canvasRef.current;
|
||||||
|
const currentOrderbookMap = useSelector(getCurrentMarketOrderbookMap, shallowEqual);
|
||||||
|
const { stepSizeDecimals = TOKEN_DECIMALS, tickSizeDecimals = SMALL_USD_DECIMALS } =
|
||||||
|
useSelector(getCurrentMarketConfig, shallowEqual) || {};
|
||||||
|
const prevData = useRef<typeof data>(data);
|
||||||
|
const theme = useAppThemeContext();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scale canvas using device pixel ratio to unblur drawn text
|
||||||
|
* @url https://stackoverflow.com/questions/15661339/how-do-i-fix-blurry-text-in-my-html5-canvas/65124939#65124939
|
||||||
|
* @returns adjusted canvas width/height/rowHeight used in coordinates for drawing
|
||||||
|
**/
|
||||||
|
const { canvasWidth, canvasHeight, rowHeight } = useMemo(() => {
|
||||||
|
const ratio = window.devicePixelRatio || 1;
|
||||||
|
|
||||||
|
if (!canvas) {
|
||||||
|
return {
|
||||||
|
canvasWidth: ORDERBOOK_WIDTH / devicePixelRatio,
|
||||||
|
canvasHeight: ORDERBOOK_HEIGHT / devicePixelRatio,
|
||||||
|
rowHeight: ORDERBOOK_ROW_HEIGHT / devicePixelRatio,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
canvas.width = canvas.offsetWidth * ratio;
|
||||||
|
canvas.height = canvas.offsetHeight * ratio;
|
||||||
|
|
||||||
|
if (ctx) {
|
||||||
|
ctx.scale(ratio, ratio);
|
||||||
|
ctx.font = '12px Satoshi';
|
||||||
|
ctx.textAlign = 'right';
|
||||||
|
ctx.textBaseline = 'middle';
|
||||||
|
ctx.imageSmoothingQuality = 'high';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
canvasWidth: canvas.width / ratio,
|
||||||
|
canvasHeight: canvas.height / ratio,
|
||||||
|
rowHeight: ORDERBOOK_ROW_HEIGHT,
|
||||||
|
};
|
||||||
|
}, [canvas]);
|
||||||
|
|
||||||
|
const drawBars = ({
|
||||||
|
barType,
|
||||||
|
ctx,
|
||||||
|
depthOrSizeValue,
|
||||||
|
gradientMultiplier,
|
||||||
|
histogramAccentColor,
|
||||||
|
histogramSide,
|
||||||
|
idx,
|
||||||
|
}: {
|
||||||
|
barType: 'depth' | 'size';
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
depthOrSizeValue: number;
|
||||||
|
gradientMultiplier: number;
|
||||||
|
histogramAccentColor: string;
|
||||||
|
histogramSide: 'left' | 'right';
|
||||||
|
idx: number;
|
||||||
|
}) => {
|
||||||
|
const { x1, x2, y1, y2 } = getRektFromIdx({
|
||||||
|
idx,
|
||||||
|
rowHeight,
|
||||||
|
canvasWidth,
|
||||||
|
canvasHeight,
|
||||||
|
side,
|
||||||
|
});
|
||||||
|
|
||||||
|
// X values
|
||||||
|
const maxHistogramBarWidth = x2 - x1 - (barType === 'size' ? 8 : 2);
|
||||||
|
const barWidth = depthOrSizeValue
|
||||||
|
? Math.min((depthOrSizeValue / histogramRange) * maxHistogramBarWidth, maxHistogramBarWidth)
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const { gradient, bar } = getHistogramXValues({
|
||||||
|
barWidth,
|
||||||
|
canvasWidth,
|
||||||
|
gradientMultiplier,
|
||||||
|
histogramSide,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Gradient
|
||||||
|
let linearGradient;
|
||||||
|
|
||||||
|
try {
|
||||||
|
linearGradient = ctx.createLinearGradient(gradient.x1, y1, gradient.x2, y2);
|
||||||
|
linearGradient.addColorStop(0, histogramAccentColor);
|
||||||
|
linearGradient.addColorStop(1, 'rgba(0, 0, 0, 0)');
|
||||||
|
ctx.fillStyle = linearGradient;
|
||||||
|
} catch (err) {
|
||||||
|
ctx.fillStyle = 'rgba(0, 0, 0, 0)';
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.beginPath();
|
||||||
|
|
||||||
|
// Bar
|
||||||
|
const { bar: y } = getYForElements({ y: y1, rowHeight });
|
||||||
|
|
||||||
|
if (ctx.roundRect) {
|
||||||
|
ctx.roundRect(
|
||||||
|
bar.x1,
|
||||||
|
y,
|
||||||
|
bar.x2,
|
||||||
|
rowHeight - 2,
|
||||||
|
histogramSide === 'right' ? [2, 0, 0, 2] : [0, 2, 2, 0]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ctx.rect(bar.x1, y, bar.x2, rowHeight - 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.fill();
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawText = ({
|
||||||
|
animationType = OrderbookRowAnimationType.NONE,
|
||||||
|
ctx,
|
||||||
|
idx,
|
||||||
|
mine,
|
||||||
|
price,
|
||||||
|
size,
|
||||||
|
}: {
|
||||||
|
animationType?: OrderbookRowAnimationType;
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
idx: number;
|
||||||
|
mine?: number;
|
||||||
|
price?: number;
|
||||||
|
size?: number;
|
||||||
|
}) => {
|
||||||
|
const { y1 } = getRektFromIdx({
|
||||||
|
idx,
|
||||||
|
rowHeight,
|
||||||
|
canvasWidth,
|
||||||
|
canvasHeight,
|
||||||
|
side,
|
||||||
|
});
|
||||||
|
|
||||||
|
const { text: y } = getYForElements({ y: y1, rowHeight });
|
||||||
|
|
||||||
|
let textColor: string;
|
||||||
|
let updatedTextColor: string | undefined;
|
||||||
|
|
||||||
|
switch (animationType) {
|
||||||
|
case OrderbookRowAnimationType.REMOVE: {
|
||||||
|
textColor = theme.textSecondary;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case OrderbookRowAnimationType.NEW: {
|
||||||
|
updatedTextColor = side === 'bid' ? theme.positive : theme.negative;
|
||||||
|
textColor = theme.textPrimary;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
default: {
|
||||||
|
textColor = theme.textPrimary;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size text
|
||||||
|
if (size) {
|
||||||
|
ctx.fillStyle = updatedTextColor ?? textColor;
|
||||||
|
ctx.fillText(
|
||||||
|
MustBigNumber(size).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
|
||||||
|
getXByColumn({ canvasWidth, colIdx: 0 }) - ORDERBOOK_ROW_PADDING_RIGHT,
|
||||||
|
y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Price text
|
||||||
|
if (price) {
|
||||||
|
ctx.fillStyle = textColor;
|
||||||
|
ctx.fillText(
|
||||||
|
MustBigNumber(price).toFixed(tickSizeDecimals ?? SMALL_USD_DECIMALS),
|
||||||
|
getXByColumn({ canvasWidth, colIdx: 1 }) - ORDERBOOK_ROW_PADDING_RIGHT,
|
||||||
|
y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mine text
|
||||||
|
if (mine) {
|
||||||
|
ctx.fillStyle = textColor;
|
||||||
|
ctx.fillText(
|
||||||
|
MustBigNumber(mine).toFixed(stepSizeDecimals ?? TOKEN_DECIMALS),
|
||||||
|
getXByColumn({ canvasWidth, colIdx: 2 }) - ORDERBOOK_ROW_PADDING_RIGHT,
|
||||||
|
y
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const drawOrderbookRow = ({
|
||||||
|
ctx,
|
||||||
|
idx,
|
||||||
|
rowToRender,
|
||||||
|
animationType = OrderbookRowAnimationType.NONE,
|
||||||
|
}: {
|
||||||
|
ctx: CanvasRenderingContext2D;
|
||||||
|
idx: number;
|
||||||
|
rowToRender?: PerpetualMarketOrderbookLevel;
|
||||||
|
animationType?: OrderbookRowAnimationType;
|
||||||
|
}) => {
|
||||||
|
if (!rowToRender) return;
|
||||||
|
const { depth, mine, price, size } = rowToRender;
|
||||||
|
const histogramAccentColor = side === 'bid' ? theme.positiveFaded : theme.negativeFaded;
|
||||||
|
|
||||||
|
// Depth Bar
|
||||||
|
if (depth) {
|
||||||
|
drawBars({
|
||||||
|
barType: 'depth',
|
||||||
|
ctx,
|
||||||
|
depthOrSizeValue: depth,
|
||||||
|
gradientMultiplier: 1.3,
|
||||||
|
histogramAccentColor,
|
||||||
|
histogramSide,
|
||||||
|
idx,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size Bar
|
||||||
|
drawBars({
|
||||||
|
barType: 'size',
|
||||||
|
ctx,
|
||||||
|
depthOrSizeValue: size,
|
||||||
|
gradientMultiplier: 5,
|
||||||
|
histogramAccentColor,
|
||||||
|
histogramSide,
|
||||||
|
idx,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Size, Price, Mine
|
||||||
|
drawText({
|
||||||
|
animationType,
|
||||||
|
ctx,
|
||||||
|
idx,
|
||||||
|
mine,
|
||||||
|
price,
|
||||||
|
size,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Update histograms and row contents on data change
|
||||||
|
useEffect(() => {
|
||||||
|
const ctx = canvas?.getContext('2d');
|
||||||
|
|
||||||
|
if (!canvas || !ctx) return;
|
||||||
|
|
||||||
|
// Clear canvas before redraw
|
||||||
|
ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height);
|
||||||
|
|
||||||
|
// Animate row removal and update
|
||||||
|
const mapOfOrderbookPriceLevels =
|
||||||
|
side && currentOrderbookMap?.[side === 'ask' ? 'asks' : 'bids'];
|
||||||
|
const empty: number[] = [];
|
||||||
|
const removed: number[] = [];
|
||||||
|
const updated: number[] = [];
|
||||||
|
|
||||||
|
prevData.current.forEach((row, idx) => {
|
||||||
|
if (!row) {
|
||||||
|
empty.push(idx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mapOfOrderbookPriceLevels?.[row.price] === 0) {
|
||||||
|
removed.push(idx);
|
||||||
|
drawOrderbookRow({
|
||||||
|
ctx,
|
||||||
|
idx,
|
||||||
|
rowToRender: row,
|
||||||
|
animationType: OrderbookRowAnimationType.REMOVE,
|
||||||
|
});
|
||||||
|
} else if (mapOfOrderbookPriceLevels?.[row.price] === row?.size) {
|
||||||
|
drawOrderbookRow({
|
||||||
|
ctx,
|
||||||
|
idx,
|
||||||
|
rowToRender: data[idx],
|
||||||
|
animationType: OrderbookRowAnimationType.NONE,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
updated.push(idx);
|
||||||
|
drawOrderbookRow({
|
||||||
|
ctx,
|
||||||
|
idx,
|
||||||
|
rowToRender: row,
|
||||||
|
animationType: OrderbookRowAnimationType.NEW,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
[...empty, ...removed, ...updated].forEach((idx) => {
|
||||||
|
const { x1, y1, x2, y2 } = getRektFromIdx({
|
||||||
|
idx,
|
||||||
|
rowHeight,
|
||||||
|
canvasWidth,
|
||||||
|
canvasHeight,
|
||||||
|
side,
|
||||||
|
});
|
||||||
|
|
||||||
|
ctx.clearRect(x1, y1, x2 - x1, y2 - y1);
|
||||||
|
drawOrderbookRow({
|
||||||
|
ctx,
|
||||||
|
idx,
|
||||||
|
rowToRender: data[idx],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, ORDERBOOK_ANIMATION_DURATION);
|
||||||
|
|
||||||
|
prevData.current = data;
|
||||||
|
}, [
|
||||||
|
canvasHeight,
|
||||||
|
canvasWidth,
|
||||||
|
rowHeight,
|
||||||
|
data,
|
||||||
|
histogramRange,
|
||||||
|
stepSizeDecimals,
|
||||||
|
tickSizeDecimals,
|
||||||
|
histogramSide,
|
||||||
|
side,
|
||||||
|
theme,
|
||||||
|
currentOrderbookMap,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return { canvasRef };
|
||||||
|
};
|
||||||
@@ -0,0 +1,126 @@
|
|||||||
|
import { useMemo } from 'react';
|
||||||
|
import { shallowEqual, useSelector } from 'react-redux';
|
||||||
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
|
|
||||||
|
import { type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
||||||
|
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
|
||||||
|
|
||||||
|
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
|
||||||
|
import { getSubaccountOpenOrdersBySideAndPrice } from '@/state/accountSelectors';
|
||||||
|
|
||||||
|
import { MustBigNumber } from '@/lib/numbers';
|
||||||
|
|
||||||
|
export const useCalculateOrderbookData = ({ maxRowsPerSide }: { maxRowsPerSide: number }) => {
|
||||||
|
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
||||||
|
|
||||||
|
const openOrdersBySideAndPrice =
|
||||||
|
useSelector(getSubaccountOpenOrdersBySideAndPrice, shallowEqual) || {};
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const asks: Array<PerpetualMarketOrderbookLevel | undefined> = (
|
||||||
|
orderbook?.asks?.toArray() ?? []
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(row, idx: number) =>
|
||||||
|
({
|
||||||
|
key: `ask-${idx}`,
|
||||||
|
side: 'ask',
|
||||||
|
mine: openOrdersBySideAndPrice[OrderSide.SELL]?.[row.price]?.size,
|
||||||
|
...row,
|
||||||
|
} as PerpetualMarketOrderbookLevel)
|
||||||
|
)
|
||||||
|
.slice(0, maxRowsPerSide);
|
||||||
|
|
||||||
|
const bids: Array<PerpetualMarketOrderbookLevel | undefined> = (
|
||||||
|
orderbook?.bids?.toArray() ?? []
|
||||||
|
)
|
||||||
|
.map(
|
||||||
|
(row, idx: number) =>
|
||||||
|
({
|
||||||
|
key: `bid-${idx}`,
|
||||||
|
side: 'bid',
|
||||||
|
mine: openOrdersBySideAndPrice[OrderSide.BUY]?.[row.price]?.size,
|
||||||
|
...row,
|
||||||
|
} as PerpetualMarketOrderbookLevel)
|
||||||
|
)
|
||||||
|
.slice(0, maxRowsPerSide);
|
||||||
|
|
||||||
|
// Prevent the bid/ask sides from crossing by using the offsets.
|
||||||
|
// While the books are crossing...
|
||||||
|
while (asks[0] && bids[0] && bids[0]!.price >= asks[0].price) {
|
||||||
|
// Drop the order on the side with the lower offset.
|
||||||
|
// The offset of the other side is higher and so supercedes.
|
||||||
|
if (bids[0]!.offset === asks[0].offset) {
|
||||||
|
// If offsets are the same, give precedence to the larger size. In this case,
|
||||||
|
// one of the sizes *should* be zero, but we simply check for the larger size.
|
||||||
|
if (bids[0]!.size > asks[0].size) {
|
||||||
|
asks.shift();
|
||||||
|
} else {
|
||||||
|
bids.pop();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Offsets are not equal. Give precedence to the larger offset.
|
||||||
|
if (bids[0]!.offset > asks[0].offset) {
|
||||||
|
asks.shift();
|
||||||
|
} else {
|
||||||
|
bids.pop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const spread =
|
||||||
|
asks[0]?.price && bids[0]?.price ? MustBigNumber(asks[0].price).minus(bids[0].price) : null;
|
||||||
|
|
||||||
|
const spreadPercent = orderbook?.spreadPercent;
|
||||||
|
|
||||||
|
const histogramRange = Math.max(
|
||||||
|
isNaN(Number(bids[bids.length - 1]?.depth)) ? 0 : Number(bids[bids.length - 1]?.depth),
|
||||||
|
isNaN(Number(asks[asks.length - 1]?.depth)) ? 0 : Number(asks[asks.length - 1]?.depth)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
asks,
|
||||||
|
bids,
|
||||||
|
spread,
|
||||||
|
spreadPercent,
|
||||||
|
histogramRange,
|
||||||
|
hasOrderbook: !!orderbook,
|
||||||
|
};
|
||||||
|
}, [orderbook, openOrdersBySideAndPrice]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useOrderbookValuesForDepthChart = () => {
|
||||||
|
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
||||||
|
|
||||||
|
return useMemo(() => {
|
||||||
|
const bids = (orderbook?.bids?.toArray() ?? [])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
|
||||||
|
|
||||||
|
const asks = (orderbook?.asks?.toArray() ?? [])
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
|
||||||
|
|
||||||
|
const lowestBid = bids[bids.length - 1];
|
||||||
|
const highestBid = bids[0];
|
||||||
|
const lowestAsk = asks[0];
|
||||||
|
const highestAsk = asks[asks.length - 1];
|
||||||
|
|
||||||
|
const midMarketPrice = orderbook?.midPrice;
|
||||||
|
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
|
||||||
|
const spreadPercent = orderbook?.spreadPercent;
|
||||||
|
|
||||||
|
return {
|
||||||
|
bids,
|
||||||
|
asks,
|
||||||
|
lowestBid,
|
||||||
|
highestBid,
|
||||||
|
lowestAsk,
|
||||||
|
highestAsk,
|
||||||
|
midMarketPrice,
|
||||||
|
spread,
|
||||||
|
spreadPercent,
|
||||||
|
orderbook,
|
||||||
|
};
|
||||||
|
}, [orderbook]);
|
||||||
|
};
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import { type RefObject, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
export const useSpreadRowScrollListener = ({
|
||||||
|
orderbookRef,
|
||||||
|
spreadRowRef,
|
||||||
|
}: {
|
||||||
|
orderbookRef: RefObject<HTMLDivElement>;
|
||||||
|
spreadRowRef: RefObject<HTMLDivElement>;
|
||||||
|
}) => {
|
||||||
|
const [displaySide, setDisplaySide] = useState<string>();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const onScroll = () => {
|
||||||
|
if (spreadRowRef.current && orderbookRef.current) {
|
||||||
|
const { clientHeight } = orderbookRef.current;
|
||||||
|
const parent = orderbookRef.current.getBoundingClientRect();
|
||||||
|
const spread = spreadRowRef.current.getBoundingClientRect();
|
||||||
|
const spreadTop = spread.top - parent.top;
|
||||||
|
const spreadBottom = spread.bottom - parent.top;
|
||||||
|
|
||||||
|
if (spreadBottom > clientHeight) {
|
||||||
|
setDisplaySide('bottom');
|
||||||
|
} else if (spreadTop < 0) {
|
||||||
|
setDisplaySide('top');
|
||||||
|
} else {
|
||||||
|
setDisplaySide(undefined);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
orderbookRef.current?.addEventListener('scroll', onScroll, false);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
orderbookRef.current?.removeEventListener('scroll', onScroll, false);
|
||||||
|
};
|
||||||
|
}, [orderbookRef.current, spreadRowRef.current]);
|
||||||
|
|
||||||
|
return displaySide;
|
||||||
|
};
|
||||||
@@ -4,7 +4,6 @@ import { useSelector } from 'react-redux';
|
|||||||
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
|
import type { IChartingLibraryWidget, ThemeName } from 'public/tradingview/charting_library';
|
||||||
|
|
||||||
import { AppTheme } from '@/state/configs';
|
import { AppTheme } from '@/state/configs';
|
||||||
|
|
||||||
import { getAppTheme } from '@/state/configsSelectors';
|
import { getAppTheme } from '@/state/configsSelectors';
|
||||||
|
|
||||||
import { getWidgetOverrides } from '@/lib/tradingView/utils';
|
import { getWidgetOverrides } from '@/lib/tradingView/utils';
|
||||||
@@ -29,7 +28,7 @@ export const useTradingViewTheme = ({
|
|||||||
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
|
tvWidget: (IChartingLibraryWidget & { _id?: string; _ready?: boolean }) | null;
|
||||||
isWidgetReady?: boolean;
|
isWidgetReady?: boolean;
|
||||||
}) => {
|
}) => {
|
||||||
const appTheme = useSelector(getAppTheme);
|
const appTheme: AppTheme = useSelector(getAppTheme);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (tvWidget && isWidgetReady) {
|
if (tvWidget && isWidgetReady) {
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { ThemeProvider } from 'styled-components';
|
||||||
|
|
||||||
|
import { AppTheme } from '@/state/configs';
|
||||||
|
import { getAppTheme } from '@/state/configsSelectors';
|
||||||
|
|
||||||
|
import { Themes } from '@/styles/themes';
|
||||||
|
|
||||||
|
export const AppThemeProvider = ({ ...props }) => {
|
||||||
|
return <ThemeProvider theme={useAppThemeContext()} {...props} />
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useAppThemeContext = () => {
|
||||||
|
const theme: AppTheme = useSelector(getAppTheme);
|
||||||
|
return Themes[theme];
|
||||||
|
}
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
import { useMemo } from 'react';
|
|
||||||
import { shallowEqual, useSelector } from 'react-redux';
|
|
||||||
|
|
||||||
import { DepthChartSeries, DepthChartDatum } from '@/constants/charts';
|
|
||||||
|
|
||||||
import { getCurrentMarketOrderbook } from '@/state/perpetualsSelectors';
|
|
||||||
|
|
||||||
import { MustBigNumber } from '@/lib/numbers';
|
|
||||||
|
|
||||||
export const useOrderbookValuesForDepthChart = () => {
|
|
||||||
const orderbook = useSelector(getCurrentMarketOrderbook, shallowEqual);
|
|
||||||
|
|
||||||
return useMemo(() => {
|
|
||||||
const bids = (orderbook?.bids?.toArray() ?? [])
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Bids } as DepthChartDatum));
|
|
||||||
|
|
||||||
const asks = (orderbook?.asks?.toArray() ?? [])
|
|
||||||
.filter(Boolean)
|
|
||||||
.map((datum) => ({ ...datum, seriesKey: DepthChartSeries.Asks } as DepthChartDatum));
|
|
||||||
|
|
||||||
const lowestBid = bids[bids.length - 1];
|
|
||||||
const highestBid = bids[0];
|
|
||||||
const lowestAsk = asks[0];
|
|
||||||
const highestAsk = asks[asks.length - 1];
|
|
||||||
|
|
||||||
const midMarketPrice = orderbook?.midPrice;
|
|
||||||
const spread = MustBigNumber(lowestAsk?.price ?? 0).minus(highestBid?.price ?? 0);
|
|
||||||
const spreadPercent = orderbook?.spreadPercent;
|
|
||||||
|
|
||||||
return {
|
|
||||||
bids,
|
|
||||||
asks,
|
|
||||||
lowestBid,
|
|
||||||
highestBid,
|
|
||||||
lowestAsk,
|
|
||||||
highestAsk,
|
|
||||||
midMarketPrice,
|
|
||||||
spread,
|
|
||||||
spreadPercent,
|
|
||||||
orderbook,
|
|
||||||
};
|
|
||||||
}, [orderbook]);
|
|
||||||
};
|
|
||||||
@@ -198,7 +198,13 @@ export const useSubaccountContext = ({ localDydxWallet }: { localDydxWallet?: Lo
|
|||||||
);
|
);
|
||||||
const ibcMsg: EncodeObject = {
|
const ibcMsg: EncodeObject = {
|
||||||
typeUrl: transaction.msgTypeUrl,
|
typeUrl: transaction.msgTypeUrl,
|
||||||
value: transaction.msg,
|
value: {
|
||||||
|
...transaction.msg,
|
||||||
|
timeoutTimestamp: transaction.msg.timeoutTimestamp
|
||||||
|
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
||||||
|
? BigInt(Long.fromValue(transaction.msg.timeoutTimestamp).toString())
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
return await compositeClient.send(
|
return await compositeClient.send(
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export { default as MarketsIcon } from './markets.svg';
|
|||||||
export { default as MenuIcon } from './menu.svg';
|
export { default as MenuIcon } from './menu.svg';
|
||||||
export { default as MigrateIcon } from './migrate.svg';
|
export { default as MigrateIcon } from './migrate.svg';
|
||||||
export { default as MintscanIcon } from './logos/mintscan.svg';
|
export { default as MintscanIcon } from './logos/mintscan.svg';
|
||||||
|
export { default as MoonIcon } from './moon.svg';
|
||||||
export { default as OrderbookIcon } from './orderbook.svg';
|
export { default as OrderbookIcon } from './orderbook.svg';
|
||||||
export { default as OverviewIcon } from './overview.svg';
|
export { default as OverviewIcon } from './overview.svg';
|
||||||
export { default as PencilIcon } from './pencil.svg';
|
export { default as PencilIcon } from './pencil.svg';
|
||||||
@@ -56,6 +57,7 @@ export { default as SendIcon } from './send.svg';
|
|||||||
export { default as ShareIcon } from './share.svg';
|
export { default as ShareIcon } from './share.svg';
|
||||||
export { default as ShowIcon } from './show.svg';
|
export { default as ShowIcon } from './show.svg';
|
||||||
export { default as StarIcon } from './star.svg';
|
export { default as StarIcon } from './star.svg';
|
||||||
|
export { default as SunIcon } from './sun.svg';
|
||||||
export { default as TerminalIcon } from './terminal.svg';
|
export { default as TerminalIcon } from './terminal.svg';
|
||||||
export { default as TogglesMenuIcon } from './toggles-menu.svg';
|
export { default as TogglesMenuIcon } from './toggles-menu.svg';
|
||||||
export { default as TradeIcon } from './trade.svg';
|
export { default as TradeIcon } from './trade.svg';
|
||||||
|
|||||||
+50
-43
@@ -1,45 +1,52 @@
|
|||||||
const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => (
|
import { useAppThemeContext } from '@/hooks/useAppTheme';
|
||||||
<svg
|
|
||||||
width="135"
|
const LogoShortIcon: React.FC<{ id?: string }> = ({ id }: { id?: string }) => {
|
||||||
height="145"
|
const theme = useAppThemeContext();
|
||||||
viewBox="0 0 135 145"
|
const fill = theme.logoFill;
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
return (
|
||||||
>
|
<svg
|
||||||
<path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill="white" />
|
width="135"
|
||||||
<path
|
height="145"
|
||||||
d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z"
|
viewBox="0 0 135 145"
|
||||||
fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`}
|
fill="none"
|
||||||
/>
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
<path
|
>
|
||||||
d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z"
|
<path d="M100.986 0L0 144.988H31.0048L132.514 0H100.986Z" fill={fill} />
|
||||||
fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`}
|
<path
|
||||||
/>
|
d="M34.2346 0L63.9475 42.7232L48.4451 66.0268L2.58386 0H34.2346Z"
|
||||||
<defs>
|
fill={`url(#${id ? `${id}_logo_gradient_0` : 'paint0_linear'})`}
|
||||||
<linearGradient
|
/>
|
||||||
id={id ? `${id}_logo_gradient_0` : 'paint0_linear'}
|
<path
|
||||||
x1="27.1293"
|
d="M103.995 145L71.0526 97.7455L86.555 75.0893L135 145H103.995Z"
|
||||||
y1="9.0625"
|
fill={`url(#${id ? `${id}_logo_gradient_1` : 'paint1_linear'})`}
|
||||||
x2="69.773"
|
/>
|
||||||
y2="60.4324"
|
<defs>
|
||||||
gradientUnits="userSpaceOnUse"
|
<linearGradient
|
||||||
>
|
id={id ? `${id}_logo_gradient_0` : 'paint0_linear'}
|
||||||
<stop stopColor="white" />
|
x1="27.1293"
|
||||||
<stop offset="1" stopColor="white" stopOpacity="0.55" />
|
y1="9.0625"
|
||||||
</linearGradient>
|
x2="69.773"
|
||||||
<linearGradient
|
y2="60.4324"
|
||||||
id={id ? `${id}_logo_gradient_1` : 'paint1_linear'}
|
gradientUnits="userSpaceOnUse"
|
||||||
x1="111.1"
|
>
|
||||||
y1="133.996"
|
<stop stopColor={fill} />
|
||||||
x2="58.6959"
|
<stop offset="1" stopColor={fill} stopOpacity="0.55" />
|
||||||
y2="63.4999"
|
</linearGradient>
|
||||||
gradientUnits="userSpaceOnUse"
|
<linearGradient
|
||||||
>
|
id={id ? `${id}_logo_gradient_1` : 'paint1_linear'}
|
||||||
<stop stopColor="#6966FF" />
|
x1="111.1"
|
||||||
<stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
|
y1="133.996"
|
||||||
</linearGradient>
|
x2="58.6959"
|
||||||
</defs>
|
y2="63.4999"
|
||||||
</svg>
|
gradientUnits="userSpaceOnUse"
|
||||||
);
|
>
|
||||||
|
<stop stopColor="#6966FF" />
|
||||||
|
<stop offset="1" stopColor="#6966FF" stopOpacity="0.36" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default LogoShortIcon;
|
export default LogoShortIcon;
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M21.752 15.002A9.72 9.72 0 0 1 18 15.75c-5.385 0-9.75-4.365-9.75-9.75 0-1.33.266-2.597.748-3.752A9.753 9.753 0 0 0 3 11.25C3 16.635 7.365 21 12.75 21a9.753 9.753 0 0 0 9.002-5.998Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 380 B |
@@ -0,0 +1,3 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-6 h-6">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M12 3v2.25m6.364.386-1.591 1.591M21 12h-2.25m-.386 6.364-1.591-1.591M12 18.75V21m-4.773-4.227-1.591 1.591M5.25 12H3m4.227-4.773L5.636 5.636M15.75 12a3.75 3.75 0 1 1-7.5 0 3.75 3.75 0 0 1 7.5 0Z" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 393 B |
+2
-2
@@ -1,6 +1,5 @@
|
|||||||
@import 'styles/fonts.css';
|
@import 'styles/fonts.css';
|
||||||
@import 'styles/text.css';
|
@import 'styles/text.css';
|
||||||
@import 'styles/colors.css';
|
|
||||||
@import 'styles/animations.css';
|
@import 'styles/animations.css';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
@@ -9,6 +8,7 @@
|
|||||||
background-color: var(--color-layer-2);
|
background-color: var(--color-layer-2);
|
||||||
user-select: none;
|
user-select: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
--border: var(--default-border-width) solid var(--color-border);
|
||||||
}
|
}
|
||||||
|
|
||||||
body,
|
body,
|
||||||
@@ -54,7 +54,7 @@ button:disabled {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
:focus-visible{
|
:focus-visible {
|
||||||
outline: var(--color-accent) 1px solid;
|
outline: var(--color-accent) 1px solid;
|
||||||
outline-offset: -1px;
|
outline-offset: -1px;
|
||||||
/* outline-offset: -2px; */
|
/* outline-offset: -2px; */
|
||||||
|
|||||||
@@ -173,6 +173,10 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
return parsedObj as T;
|
return parsedObj as T;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (typeof x === 'bigint') {
|
||||||
|
return x.toString() as T;
|
||||||
|
}
|
||||||
|
|
||||||
throw new Error(`Unsupported data type: ${typeof x}`);
|
throw new Error(`Unsupported data type: ${typeof x}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -363,7 +367,13 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
try {
|
try {
|
||||||
const ibcMsg = {
|
const ibcMsg = {
|
||||||
typeUrl: params.msgTypeUrl, // '/ibc.applications.transfer.v1.MsgTransfer',
|
typeUrl: params.msgTypeUrl, // '/ibc.applications.transfer.v1.MsgTransfer',
|
||||||
value: params.msg,
|
value: {
|
||||||
|
...params.msg,
|
||||||
|
timeoutTimestamp: params.msg.timeoutTimestamp
|
||||||
|
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
||||||
|
? BigInt(Long.fromValue(params.msg.timeoutTimestamp).toString())
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
const fee = await this.nobleClient.simulateTransaction([ibcMsg]);
|
const fee = await this.nobleClient.simulateTransaction([ibcMsg]);
|
||||||
|
|
||||||
@@ -413,7 +423,13 @@ class DydxChainTransactions implements AbacusDYDXChainTransactionsProtocol {
|
|||||||
);
|
);
|
||||||
const ibcMsg: EncodeObject = {
|
const ibcMsg: EncodeObject = {
|
||||||
typeUrl: parsedIbcPayload.msgTypeUrl,
|
typeUrl: parsedIbcPayload.msgTypeUrl,
|
||||||
value: parsedIbcPayload.msg,
|
value: {
|
||||||
|
...parsedIbcPayload.msg,
|
||||||
|
timeoutTimestamp: parsedIbcPayload.msg.timeoutTimestamp
|
||||||
|
// Squid returns timeoutTimestamp as Long, but the signer expects BigInt
|
||||||
|
? BigInt(Long.fromValue(parsedIbcPayload.msg.timeoutTimestamp).toString())
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
const tx = await this.compositeClient.send(
|
const tx = await this.compositeClient.send(
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
// ------ Canvas helper methods ------ //
|
||||||
|
|
||||||
|
import type { MarketOrderbook, Nullable, PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns top left x,y and bottom x,y from array idx
|
||||||
|
*/
|
||||||
|
export const getRektFromIdx = ({
|
||||||
|
idx,
|
||||||
|
canvasWidth,
|
||||||
|
canvasHeight,
|
||||||
|
rowHeight,
|
||||||
|
side = 'bid',
|
||||||
|
}: {
|
||||||
|
idx: number;
|
||||||
|
canvasWidth: number;
|
||||||
|
canvasHeight: number;
|
||||||
|
rowHeight: number;
|
||||||
|
side: PerpetualMarketOrderbookLevel['side'];
|
||||||
|
}) => {
|
||||||
|
/**
|
||||||
|
* Does not change
|
||||||
|
*/
|
||||||
|
const x1 = 0;
|
||||||
|
const x2 = Math.floor(canvasWidth);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes based on side
|
||||||
|
* Asks: The drawing should starts from the bottom of the orderbook
|
||||||
|
*/
|
||||||
|
if (side === 'ask') {
|
||||||
|
const y1 = Math.floor(canvasHeight - (idx + 1) * rowHeight);
|
||||||
|
const y2 = Math.floor(y1 + rowHeight);
|
||||||
|
return { x1, x2, y1, y2 };
|
||||||
|
}
|
||||||
|
|
||||||
|
const y1 = Math.floor(idx * rowHeight);
|
||||||
|
const y2 = Math.floor(y1 + rowHeight);
|
||||||
|
return { x1, x2, y1, y2 };
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns y value for text and bar
|
||||||
|
*/
|
||||||
|
export const getYForElements = ({ y, rowHeight }: { y: number; rowHeight: number }) => ({
|
||||||
|
text: Math.floor(y + rowHeight / 2),
|
||||||
|
bar: Math.floor(y + 1),
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description X coordinate for text
|
||||||
|
* @returns Get X value by column, colIdx starts at 0
|
||||||
|
*/
|
||||||
|
export const getXByColumn = ({ canvasWidth, colIdx }: { canvasWidth: number; colIdx: number }) => {
|
||||||
|
return Math.floor(((colIdx + 1) * canvasWidth) / 3);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* to === 'right' to === 'left'
|
||||||
|
* |===================| |===================|
|
||||||
|
* bar: a b c d
|
||||||
|
* a = 0
|
||||||
|
* b = depthBarWidth
|
||||||
|
* c = canvasWidth - depthBarWidth
|
||||||
|
* d = canvasWidth
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export const getHistogramXValues = ({
|
||||||
|
barWidth,
|
||||||
|
canvasWidth,
|
||||||
|
gradientMultiplier,
|
||||||
|
histogramSide,
|
||||||
|
}: {
|
||||||
|
barWidth: number;
|
||||||
|
canvasWidth: number;
|
||||||
|
gradientMultiplier: number;
|
||||||
|
histogramSide: 'left' | 'right';
|
||||||
|
}) => {
|
||||||
|
const gradient = {
|
||||||
|
x1: Math.floor(histogramSide === 'left' ? barWidth : canvasWidth - barWidth),
|
||||||
|
x2: Math.floor(
|
||||||
|
histogramSide === 'left'
|
||||||
|
? 0 - (canvasWidth * gradientMultiplier - canvasWidth)
|
||||||
|
: canvasWidth * gradientMultiplier
|
||||||
|
),
|
||||||
|
};
|
||||||
|
const bar = {
|
||||||
|
x1: Math.floor(histogramSide === 'left' ? 0 : canvasWidth - barWidth),
|
||||||
|
x2: Math.floor(histogramSide === 'left' ? Math.min(barWidth, canvasWidth) : canvasWidth),
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
bar,
|
||||||
|
gradient,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
// ------ Orderbook helper methods ------ //
|
||||||
|
export const processOrderbookToCreateMap = ({
|
||||||
|
orderbookMap,
|
||||||
|
newOrderbook,
|
||||||
|
}: {
|
||||||
|
orderbookMap?: {
|
||||||
|
asks: Record<string, number>;
|
||||||
|
bids: Record<string, number>;
|
||||||
|
};
|
||||||
|
newOrderbook: Nullable<MarketOrderbook>;
|
||||||
|
}) => {
|
||||||
|
// Create Orderbooks Map indexed by Price
|
||||||
|
const asks = newOrderbook?.asks?.toArray() ?? [];
|
||||||
|
const bids = newOrderbook?.bids?.toArray() ?? [];
|
||||||
|
const prevAsks = orderbookMap?.asks ?? {};
|
||||||
|
const prevBids = orderbookMap?.bids ?? {};
|
||||||
|
const newAsks = Object.fromEntries(asks.map((ask) => [ask.price, ask.size]) ?? {});
|
||||||
|
const newBids = Object.fromEntries(bids.map((bid) => [bid.price, bid.size]) ?? {});
|
||||||
|
|
||||||
|
Object.keys(prevAsks).forEach((price) => {
|
||||||
|
if (!newAsks[price] || newAsks[price] === 0) {
|
||||||
|
newAsks[price] = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Object.keys(prevBids).forEach((price) => {
|
||||||
|
if (!newBids[price] || newBids[price] === 0) {
|
||||||
|
newBids[price] = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
newAsks,
|
||||||
|
newBids,
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import { css } from 'styled-components';
|
||||||
|
|
||||||
|
import { type RiskLevels } from '@/constants/abacus';
|
||||||
|
|
||||||
|
export const UsageColorFromRiskLevel = (riskLevel: RiskLevels) =>
|
||||||
|
({
|
||||||
|
low: css`
|
||||||
|
color: var(--color-risk-low);
|
||||||
|
`,
|
||||||
|
medium: css`
|
||||||
|
color: var(--color-risk-medium);
|
||||||
|
`,
|
||||||
|
high: css`
|
||||||
|
color: var(--color-risk-high);
|
||||||
|
`,
|
||||||
|
}[riskLevel.name]);
|
||||||
|
|
||||||
|
export const generateFadedColorVariant = (colorHex: string, opacityHex: string) => {
|
||||||
|
return `${colorHex}${opacityHex}`;
|
||||||
|
};
|
||||||
@@ -1,11 +1,9 @@
|
|||||||
import Color from 'color';
|
|
||||||
import isEmpty from 'lodash/isEmpty';
|
|
||||||
|
|
||||||
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
|
import { Candle, TradingViewBar, TradingViewSymbol } from '@/constants/candles';
|
||||||
import { Colors } from '@/styles/colors';
|
|
||||||
|
|
||||||
import { AppTheme } from '@/state/configs';
|
import { AppTheme } from '@/state/configs';
|
||||||
|
|
||||||
|
import { Themes } from '@/styles/themes';
|
||||||
|
|
||||||
export const mapCandle = ({
|
export const mapCandle = ({
|
||||||
startedAt,
|
startedAt,
|
||||||
open,
|
open,
|
||||||
@@ -49,42 +47,42 @@ export const getHistorySlice = ({
|
|||||||
return bars.filter(({ time }) => time >= fromMs);
|
return bars.filter(({ time }) => time >= fromMs);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getWidgetOverrides = (theme: AppTheme) => {
|
export const getWidgetOverrides = (appTheme: AppTheme) => {
|
||||||
const colors = Colors[theme];
|
const theme = Themes[appTheme];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
overrides: {
|
overrides: {
|
||||||
'paneProperties.background': Color(colors.layer2).hex(),
|
'paneProperties.background': theme.layer2,
|
||||||
'paneProperties.horzGridProperties.color': Color(colors.layer3).hex(),
|
'paneProperties.horzGridProperties.color': theme.layer3,
|
||||||
'paneProperties.vertGridProperties.color': Color(colors.layer3).hex(),
|
'paneProperties.vertGridProperties.color': theme.layer3,
|
||||||
'paneProperties.crossHairProperties.style': 1,
|
'paneProperties.crossHairProperties.style': 1,
|
||||||
'paneProperties.legendProperties.showBarChange': false,
|
'paneProperties.legendProperties.showBarChange': false,
|
||||||
'paneProperties.backgroundType': 'solid',
|
'paneProperties.backgroundType': 'solid',
|
||||||
|
|
||||||
'mainSeriesProperties.style': 1,
|
'mainSeriesProperties.style': 1,
|
||||||
'mainSeriesProperties.candleStyle.upColor': Color(colors.positive).hex(),
|
'mainSeriesProperties.candleStyle.upColor': theme.positive,
|
||||||
'mainSeriesProperties.candleStyle.borderUpColor': Color(colors.positive).hex(),
|
'mainSeriesProperties.candleStyle.borderUpColor': theme.positive,
|
||||||
'mainSeriesProperties.candleStyle.wickUpColor': Color(colors.positive).hex(),
|
'mainSeriesProperties.candleStyle.wickUpColor': theme.positive,
|
||||||
'mainSeriesProperties.candleStyle.downColor': Color(colors.negative).hex(),
|
'mainSeriesProperties.candleStyle.downColor': theme.negative,
|
||||||
'mainSeriesProperties.candleStyle.borderDownColor': Color(colors.negative).hex(),
|
'mainSeriesProperties.candleStyle.borderDownColor': theme.negative,
|
||||||
'mainSeriesProperties.candleStyle.wickDownColor': Color(colors.negative).hex(),
|
'mainSeriesProperties.candleStyle.wickDownColor': theme.negative,
|
||||||
'mainSeriesProperties.statusViewStyle.symbolTextSource': 'ticker',
|
'mainSeriesProperties.statusViewStyle.symbolTextSource': 'ticker',
|
||||||
|
|
||||||
'scalesProperties.textColor': Color(colors.text1).hex(),
|
'scalesProperties.textColor': theme.textPrimary,
|
||||||
'scalesProperties.backgroundColor': Color(colors.layer2).hex(),
|
'scalesProperties.backgroundColor': theme.layer2,
|
||||||
'scalesProperties.lineColor': Color(colors.layer3).hex(),
|
'scalesProperties.lineColor': theme.layer3,
|
||||||
},
|
},
|
||||||
studies_overrides: {
|
studies_overrides: {
|
||||||
'volume.volume.color.0': Color(colors.negative).hex(),
|
'volume.volume.color.0': theme.negative,
|
||||||
'volume.volume.color.1': Color(colors.positive).hex(),
|
'volume.volume.color.1': theme.positive,
|
||||||
'volume.volume ma.visible': false,
|
'volume.volume ma.visible': false,
|
||||||
'relative strength index.plot.color': Color(colors.accent).hex(),
|
'relative strength index.plot.color': theme.accent,
|
||||||
'relative strength index.plot.linewidth': 1.5,
|
'relative strength index.plot.linewidth': 1.5,
|
||||||
'relative strength index.hlines background.color': '#134A9F',
|
'relative strength index.hlines background.color': '#134A9F',
|
||||||
},
|
},
|
||||||
loading_screen: {
|
loading_screen: {
|
||||||
backgroundColor: Color(colors.layer2).hex(),
|
backgroundColor: theme.layer2,
|
||||||
foregroundColor: Color(colors.layer2).hex(),
|
foregroundColor: theme.layer2,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
+11
-6
@@ -5,7 +5,7 @@ import { useEnsName } from 'wagmi';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { ButtonSize } from '@/constants/buttons';
|
import { ButtonSize } from '@/constants/buttons';
|
||||||
import { TransferInputField, TransferType } from '@/constants/abacus';
|
import { TransferType } from '@/constants/abacus';
|
||||||
|
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
@@ -26,12 +26,13 @@ import { useAccounts, useStringGetter, useTokenConfigs } from '@/hooks';
|
|||||||
import { getOnboardingState } from '@/state/accountSelectors';
|
import { getOnboardingState } from '@/state/accountSelectors';
|
||||||
import { openDialog } from '@/state/dialogs';
|
import { openDialog } from '@/state/dialogs';
|
||||||
|
|
||||||
import abacusStateManager from '@/lib/abacus';
|
|
||||||
import { isTruthy } from '@/lib/isTruthy';
|
import { isTruthy } from '@/lib/isTruthy';
|
||||||
import { truncateAddress } from '@/lib/wallet';
|
import { truncateAddress } from '@/lib/wallet';
|
||||||
|
|
||||||
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
|
import { DYDXBalancePanel } from './rewards/DYDXBalancePanel';
|
||||||
import { MigratePanel } from './rewards/MigratePanel';
|
import { MigratePanel } from './rewards/MigratePanel';
|
||||||
|
import { GovernancePanel } from './rewards/GovernancePanel';
|
||||||
|
import { StakingPanel } from './rewards/StakingPanel';
|
||||||
|
|
||||||
const ENS_CHAIN_ID = 1; // Ethereum
|
const ENS_CHAIN_ID = 1; // Ethereum
|
||||||
|
|
||||||
@@ -178,7 +179,7 @@ const Profile = () => {
|
|||||||
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
|
onClick={() => dispatch(openDialog({ type: DialogTypes.Help }))}
|
||||||
/>
|
/>
|
||||||
</Styled.EqualGrid>
|
</Styled.EqualGrid>
|
||||||
|
|
||||||
<MigratePanel />
|
<MigratePanel />
|
||||||
<DYDXBalancePanel />
|
<DYDXBalancePanel />
|
||||||
|
|
||||||
@@ -230,6 +231,9 @@ const Profile = () => {
|
|||||||
withInnerBorders={false}
|
withInnerBorders={false}
|
||||||
/>
|
/>
|
||||||
</Styled.TablePanel>
|
</Styled.TablePanel>
|
||||||
|
|
||||||
|
<GovernancePanel />
|
||||||
|
<StakingPanel />
|
||||||
</Styled.MobileProfileLayout>
|
</Styled.MobileProfileLayout>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -243,6 +247,7 @@ Styled.MobileProfileLayout = styled.div`
|
|||||||
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1.25rem 0.9rem;
|
padding: 1.25rem 0.9rem;
|
||||||
|
max-width: 100vw;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Header = styled.header`
|
Styled.Header = styled.header`
|
||||||
@@ -259,8 +264,8 @@ Styled.ProfileIcon = styled.div`
|
|||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background: linear-gradient(
|
background: linear-gradient(
|
||||||
135deg,
|
135deg,
|
||||||
var(--theme-classic-color-yellow) 0%,
|
${({ theme }) => theme.profileYellow} 0%,
|
||||||
var(--theme-classic-color-red) 100%
|
${({ theme }) => theme.profileRed} 100%
|
||||||
);
|
);
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -337,7 +342,7 @@ Styled.Details = styled(Details)`
|
|||||||
|
|
||||||
Styled.RewardsPanel = styled(Panel)`
|
Styled.RewardsPanel = styled(Panel)`
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
|
|
||||||
&,
|
&,
|
||||||
> * {
|
> * {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
||||||
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
|
|
||||||
|
import { useStringGetter, useURLConfigs } from '@/hooks';
|
||||||
|
|
||||||
|
import { Panel } from '@/components/Panel';
|
||||||
|
import { IconName } from '@/components/Icon';
|
||||||
|
import { IconButton } from '@/components/IconButton';
|
||||||
|
import { Link } from '@/components/Link';
|
||||||
|
|
||||||
|
import { openDialog } from '@/state/dialogs';
|
||||||
|
|
||||||
|
export const GovernancePanel = () => {
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { governanceLearnMore } = useURLConfigs();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel
|
||||||
|
slotHeaderContent={
|
||||||
|
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
|
||||||
|
}
|
||||||
|
slotRight={
|
||||||
|
<Styled.Arrow>
|
||||||
|
<Styled.IconButton
|
||||||
|
action={ButtonAction.Base}
|
||||||
|
iconName={IconName.Arrow}
|
||||||
|
size={ButtonSize.Small}
|
||||||
|
/>
|
||||||
|
</Styled.Arrow>
|
||||||
|
}
|
||||||
|
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
||||||
|
>
|
||||||
|
<Styled.Description>
|
||||||
|
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
|
||||||
|
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
|
||||||
|
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
||||||
|
</Link>
|
||||||
|
</Styled.Description>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.Description = styled.div`
|
||||||
|
color: var(--color-text-0);
|
||||||
|
--link-color: var(--color-text-1);
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline;
|
||||||
|
::before {
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.IconButton = styled(IconButton)`
|
||||||
|
color: var(--color-text-0);
|
||||||
|
--color-border: var(--color-layer-6);
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Arrow = styled.div`
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Title = styled.h3`
|
||||||
|
font: var(--font-medium-book);
|
||||||
|
color: var(--color-text-2);
|
||||||
|
margin-bottom: -1rem;
|
||||||
|
`;
|
||||||
@@ -146,14 +146,15 @@ Styled.Title = styled.h3`
|
|||||||
color: var(--color-text-2);
|
color: var(--color-text-2);
|
||||||
|
|
||||||
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
|
padding: var(--panel-paddingY) var(--panel-paddingX) 0;
|
||||||
|
margin-bottom: -0.5rem;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.MigrateAction = styled.div`
|
Styled.MigrateAction = styled.div`
|
||||||
${layoutMixins.flexEqualColumns}
|
${layoutMixins.flexEqualColumns}
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin: 1rem;
|
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
|
margin: 1rem;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
background-color: var(--color-layer-2);
|
background-color: var(--color-layer-2);
|
||||||
@@ -233,4 +234,4 @@ Styled.InlineRow = styled.div`
|
|||||||
${layoutMixins.inlineRow}
|
${layoutMixins.inlineRow}
|
||||||
color: var(--color-text-0);
|
color: var(--color-text-0);
|
||||||
--link-color: var(--color-text-1);
|
--link-color: var(--color-text-1);
|
||||||
`;
|
`;
|
||||||
@@ -3,45 +3,29 @@ import { useDispatch } from 'react-redux';
|
|||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
|
||||||
import { DialogTypes } from '@/constants/dialogs';
|
|
||||||
import { AppRoute } from '@/constants/routes';
|
import { AppRoute } from '@/constants/routes';
|
||||||
|
|
||||||
import { useBreakpoints, useStringGetter, useURLConfigs } from '@/hooks';
|
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
import { BackButton } from '@/components/BackButton';
|
import { BackButton } from '@/components/BackButton';
|
||||||
import { Panel } from '@/components/Panel';
|
import { Panel } from '@/components/Panel';
|
||||||
import { IconName } from '@/components/Icon';
|
|
||||||
import { IconButton } from '@/components/IconButton';
|
|
||||||
import { Link } from '@/components/Link';
|
|
||||||
|
|
||||||
import { openDialog } from '@/state/dialogs';
|
|
||||||
|
|
||||||
import { DYDXBalancePanel } from './DYDXBalancePanel';
|
import { DYDXBalancePanel } from './DYDXBalancePanel';
|
||||||
import { MigratePanel } from './MigratePanel';
|
import { MigratePanel } from './MigratePanel';
|
||||||
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
|
import { LaunchIncentivesPanel } from './LaunchIncentivesPanel';
|
||||||
import { RewardsHelpPanel } from './RewardsHelpPanel';
|
import { RewardsHelpPanel } from './RewardsHelpPanel';
|
||||||
|
import { GovernancePanel } from './GovernancePanel';
|
||||||
|
import { StakingPanel } from './StakingPanel';
|
||||||
|
|
||||||
const RewardsPage = () => {
|
const RewardsPage = () => {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
const { governanceLearnMore, stakingLearnMore } = useURLConfigs();
|
|
||||||
const { isTablet, isNotTablet } = useBreakpoints();
|
const { isTablet, isNotTablet } = useBreakpoints();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const panelArrow = (
|
|
||||||
<Styled.Arrow>
|
|
||||||
<Styled.IconButton
|
|
||||||
action={ButtonAction.Base}
|
|
||||||
iconName={IconName.Arrow}
|
|
||||||
size={ButtonSize.Small}
|
|
||||||
/>
|
|
||||||
</Styled.Arrow>
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Page>
|
<Styled.Page>
|
||||||
{isTablet && (
|
{isTablet && (
|
||||||
@@ -61,37 +45,12 @@ const RewardsPage = () => {
|
|||||||
</Styled.PanelRowIncentivesAndBalance>
|
</Styled.PanelRowIncentivesAndBalance>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<Styled.PanelRow>
|
{isNotTablet && (
|
||||||
<Styled.Panel
|
<Styled.PanelRow>
|
||||||
slotHeaderContent={
|
<GovernancePanel />
|
||||||
<Styled.Title>{stringGetter({ key: STRING_KEYS.GOVERNANCE })}</Styled.Title>
|
<StakingPanel />
|
||||||
}
|
</Styled.PanelRow>
|
||||||
slotRight={panelArrow}
|
)}
|
||||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
|
||||||
>
|
|
||||||
<Styled.Description>
|
|
||||||
{stringGetter({ key: STRING_KEYS.GOVERNANCE_DESCRIPTION })}
|
|
||||||
<Link href={governanceLearnMore} onClick={(e) => e.stopPropagation()}>
|
|
||||||
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
|
||||||
</Link>
|
|
||||||
</Styled.Description>
|
|
||||||
</Styled.Panel>
|
|
||||||
|
|
||||||
<Styled.Panel
|
|
||||||
slotHeaderContent={
|
|
||||||
<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>
|
|
||||||
}
|
|
||||||
slotRight={panelArrow}
|
|
||||||
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
|
||||||
>
|
|
||||||
<Styled.Description>
|
|
||||||
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
|
|
||||||
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
|
|
||||||
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
|
||||||
</Link>
|
|
||||||
</Styled.Description>
|
|
||||||
</Styled.Panel>
|
|
||||||
</Styled.PanelRow>
|
|
||||||
|
|
||||||
<RewardsHelpPanel />
|
<RewardsHelpPanel />
|
||||||
</Styled.Page>
|
</Styled.Page>
|
||||||
@@ -140,24 +99,6 @@ Styled.Panel = styled(Panel)`
|
|||||||
height: fit-content;
|
height: fit-content;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.Title = styled.h3`
|
|
||||||
font: var(--font-medium-book);
|
|
||||||
color: var(--color-text-2);
|
|
||||||
margin-bottom: -1rem;
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Description = styled.div`
|
|
||||||
color: var(--color-text-0);
|
|
||||||
--link-color: var(--color-text-1);
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: inline;
|
|
||||||
::before {
|
|
||||||
content: ' ';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.PanelRow = styled.div`
|
Styled.PanelRow = styled.div`
|
||||||
${layoutMixins.gridEqualColumns}
|
${layoutMixins.gridEqualColumns}
|
||||||
gap: 1.5rem;
|
gap: 1.5rem;
|
||||||
@@ -171,12 +112,3 @@ Styled.PanelRow = styled.div`
|
|||||||
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
|
Styled.PanelRowIncentivesAndBalance = styled(Styled.PanelRow)`
|
||||||
grid-template-columns: 2fr 1fr;
|
grid-template-columns: 2fr 1fr;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
Styled.IconButton = styled(IconButton)`
|
|
||||||
color: var(--color-text-0);
|
|
||||||
--color-border: var(--color-layer-6);
|
|
||||||
`;
|
|
||||||
|
|
||||||
Styled.Arrow = styled.div`
|
|
||||||
padding: 1rem;
|
|
||||||
`;
|
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import styled, { AnyStyledComponent } from 'styled-components';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
|
import { ButtonAction, ButtonSize } from '@/constants/buttons';
|
||||||
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { DialogTypes } from '@/constants/dialogs';
|
||||||
|
|
||||||
|
import { useStringGetter, useURLConfigs } from '@/hooks';
|
||||||
|
|
||||||
|
import { Panel } from '@/components/Panel';
|
||||||
|
import { IconName } from '@/components/Icon';
|
||||||
|
import { IconButton } from '@/components/IconButton';
|
||||||
|
import { Link } from '@/components/Link';
|
||||||
|
|
||||||
|
import { openDialog } from '@/state/dialogs';
|
||||||
|
|
||||||
|
export const StakingPanel = () => {
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { stakingLearnMore } = useURLConfigs();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Panel
|
||||||
|
slotHeaderContent={<Styled.Title>{stringGetter({ key: STRING_KEYS.STAKING })}</Styled.Title>}
|
||||||
|
slotRight={
|
||||||
|
<Styled.Arrow>
|
||||||
|
<Styled.IconButton
|
||||||
|
action={ButtonAction.Base}
|
||||||
|
iconName={IconName.Arrow}
|
||||||
|
size={ButtonSize.Small}
|
||||||
|
/>
|
||||||
|
</Styled.Arrow>
|
||||||
|
}
|
||||||
|
onClick={() => dispatch(openDialog({ type: DialogTypes.ExternalNavKeplr }))}
|
||||||
|
>
|
||||||
|
<Styled.Description>
|
||||||
|
{stringGetter({ key: STRING_KEYS.STAKING_DESCRIPTION })}
|
||||||
|
<Link href={stakingLearnMore} onClick={(e) => e.stopPropagation()}>
|
||||||
|
{stringGetter({ key: STRING_KEYS.LEARN_MORE })} →
|
||||||
|
</Link>
|
||||||
|
</Styled.Description>
|
||||||
|
</Panel>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.Description = styled.div`
|
||||||
|
color: var(--color-text-0);
|
||||||
|
--link-color: var(--color-text-1);
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: inline;
|
||||||
|
::before {
|
||||||
|
content: ' ';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.IconButton = styled(IconButton)`
|
||||||
|
color: var(--color-text-0);
|
||||||
|
--color-border: var(--color-layer-6);
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Arrow = styled.div`
|
||||||
|
padding-right: 1.5rem;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Title = styled.h3`
|
||||||
|
font: var(--font-medium-book);
|
||||||
|
color: var(--color-text-2);
|
||||||
|
margin-bottom: -1rem;
|
||||||
|
`;
|
||||||
@@ -1,6 +1,4 @@
|
|||||||
import { useEffect, useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
|
||||||
import styled, { AnyStyledComponent } from 'styled-components';
|
|
||||||
|
|
||||||
import { TradeLayouts } from '@/constants/layout';
|
import { TradeLayouts } from '@/constants/layout';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
@@ -9,11 +7,9 @@ import { useStringGetter } from '@/hooks';
|
|||||||
|
|
||||||
import { Tabs } from '@/components/Tabs';
|
import { Tabs } from '@/components/Tabs';
|
||||||
|
|
||||||
import { Orderbook, orderbookMixins, OrderbookScrollBehavior } from '@/views/tables/Orderbook';
|
import { CanvasOrderbook } from '@/views/CanvasOrderbook';
|
||||||
import { LiveTrades } from '@/views/tables/LiveTrades';
|
import { LiveTrades } from '@/views/tables/LiveTrades';
|
||||||
|
|
||||||
import { getCurrentMarketId } from '@/state/perpetualsSelectors';
|
|
||||||
|
|
||||||
enum Tab {
|
enum Tab {
|
||||||
Orderbook = 'Orderbook',
|
Orderbook = 'Orderbook',
|
||||||
Trades = 'Trades',
|
Trades = 'Trades',
|
||||||
@@ -27,29 +23,22 @@ const HISTOGRAM_SIDES_BY_LAYOUT = {
|
|||||||
|
|
||||||
export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) => {
|
export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) => {
|
||||||
const stringGetter = useStringGetter();
|
const stringGetter = useStringGetter();
|
||||||
|
|
||||||
const [value, setValue] = useState(Tab.Orderbook);
|
const [value, setValue] = useState(Tab.Orderbook);
|
||||||
const [scrollBehavior, setScrollBehavior] = useState<OrderbookScrollBehavior>('snapToCenter');
|
|
||||||
|
|
||||||
const marketId = useSelector(getCurrentMarketId);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setScrollBehavior('snapToCenter');
|
|
||||||
}, [marketId]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Styled.Tabs
|
<Tabs
|
||||||
fullWidthTabs
|
fullWidthTabs
|
||||||
value={value}
|
value={value}
|
||||||
onValueChange={(value: Tab) => {
|
onValueChange={(value: Tab) => {
|
||||||
setScrollBehavior('snapToCenter');
|
|
||||||
setValue(value);
|
setValue(value);
|
||||||
}}
|
}}
|
||||||
items={[
|
items={[
|
||||||
{
|
{
|
||||||
content: <Orderbook histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
|
asChild: true,
|
||||||
|
content: <CanvasOrderbook histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
|
||||||
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SHORT }),
|
label: stringGetter({ key: STRING_KEYS.ORDERBOOK_SHORT }),
|
||||||
value: Tab.Orderbook,
|
value: Tab.Orderbook,
|
||||||
|
forceMount: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
content: <LiveTrades histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
|
content: <LiveTrades histogramSide={HISTOGRAM_SIDES_BY_LAYOUT[tradeLayout]} />,
|
||||||
@@ -57,26 +46,7 @@ export const VerticalPanel = ({ tradeLayout }: { tradeLayout: TradeLayouts }) =>
|
|||||||
value: Tab.Trades,
|
value: Tab.Trades,
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
onWheel={() => setScrollBehavior('free')}
|
|
||||||
withTransitions={false}
|
withTransitions={false}
|
||||||
isShowingOrderbook={value === Tab.Orderbook}
|
|
||||||
scrollBehavior={scrollBehavior}
|
|
||||||
// style={{
|
|
||||||
// scrollSnapType: {
|
|
||||||
// 'snapToCenter': 'y mandatory',
|
|
||||||
// 'free': 'none',
|
|
||||||
// 'snapToCenterUnlessHovered': 'none',
|
|
||||||
// }[scrollBehavior],
|
|
||||||
// }}
|
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const Styled: Record<string, AnyStyledComponent> = {};
|
|
||||||
|
|
||||||
Styled.Tabs = styled(Tabs)<{
|
|
||||||
isShowingOrderbook: boolean;
|
|
||||||
scrollBehavior: OrderbookScrollBehavior;
|
|
||||||
}>`
|
|
||||||
${orderbookMixins.scrollArea}
|
|
||||||
`;
|
|
||||||
|
|||||||
+26
-5
@@ -14,6 +14,7 @@ import { LocalStorageKey } from '@/constants/localStorage';
|
|||||||
import { DEFAULT_MARKETID } from '@/constants/markets';
|
import { DEFAULT_MARKETID } from '@/constants/markets';
|
||||||
|
|
||||||
import { getLocalStorage } from '@/lib/localStorage';
|
import { getLocalStorage } from '@/lib/localStorage';
|
||||||
|
import { processOrderbookToCreateMap } from '@/lib/orderbookHelpers';
|
||||||
|
|
||||||
interface CandleDataByMarket {
|
interface CandleDataByMarket {
|
||||||
data: Record<string, Candle[]>;
|
data: Record<string, Candle[]>;
|
||||||
@@ -26,6 +27,13 @@ export interface PerpetualsState {
|
|||||||
liveTrades?: Record<string, MarketTrade[]>;
|
liveTrades?: Record<string, MarketTrade[]>;
|
||||||
markets?: Record<string, PerpetualMarket>;
|
markets?: Record<string, PerpetualMarket>;
|
||||||
orderbooks?: Record<string, MarketOrderbook>;
|
orderbooks?: Record<string, MarketOrderbook>;
|
||||||
|
orderbooksMap?: Record<
|
||||||
|
string,
|
||||||
|
{
|
||||||
|
asks: Record<string, number>;
|
||||||
|
bids: Record<string, number>;
|
||||||
|
}
|
||||||
|
>;
|
||||||
historicalFundings: Record<string, MarketHistoricalFunding[]>;
|
historicalFundings: Record<string, MarketHistoricalFunding[]>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -35,6 +43,7 @@ const initialState: PerpetualsState = {
|
|||||||
liveTrades: {},
|
liveTrades: {},
|
||||||
markets: undefined,
|
markets: undefined,
|
||||||
orderbooks: undefined,
|
orderbooks: undefined,
|
||||||
|
orderbooksMap: undefined,
|
||||||
historicalFundings: {},
|
historicalFundings: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -96,12 +105,24 @@ export const perpetualsSlice = createSlice({
|
|||||||
setOrderbook: (
|
setOrderbook: (
|
||||||
state: PerpetualsState,
|
state: PerpetualsState,
|
||||||
action: PayloadAction<{ orderbook?: Nullable<MarketOrderbook>; marketId: string }>
|
action: PayloadAction<{ orderbook?: Nullable<MarketOrderbook>; marketId: string }>
|
||||||
) => ({
|
) => {
|
||||||
...state,
|
state.orderbooks = merge({}, state.orderbooks, {
|
||||||
orderbooks: merge({}, state.orderbooks, {
|
|
||||||
[action.payload.marketId]: action.payload.orderbook,
|
[action.payload.marketId]: action.payload.orderbook,
|
||||||
}),
|
});
|
||||||
}),
|
|
||||||
|
const { newAsks, newBids } = processOrderbookToCreateMap({
|
||||||
|
orderbookMap: state.orderbooksMap?.[action.payload.marketId],
|
||||||
|
newOrderbook: action.payload.orderbook,
|
||||||
|
});
|
||||||
|
|
||||||
|
state.orderbooksMap = {
|
||||||
|
...(state.orderbooksMap ?? {}),
|
||||||
|
[action.payload.marketId]: {
|
||||||
|
asks: newAsks,
|
||||||
|
bids: newBids,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
setTvChartResolution: (
|
setTvChartResolution: (
|
||||||
state: PerpetualsState,
|
state: PerpetualsState,
|
||||||
action: PayloadAction<{ marketId: string; resolution: string }>
|
action: PayloadAction<{ marketId: string; resolution: string }>
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { mapCandle } from '@/lib/tradingView/utils';
|
|||||||
import type { RootState } from './_store';
|
import type { RootState } from './_store';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns marketId of the market the user is currently viewing
|
* @returns marketId of the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketId = (state: RootState) => state.perpetuals.currentMarketId;
|
export const getCurrentMarketId = (state: RootState) => state.perpetuals.currentMarketId;
|
||||||
@@ -21,7 +20,6 @@ export const getCurrentMarketAssetId = (state: RootState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns Record of PerpetualMarket indexed by MarketId
|
* @returns Record of PerpetualMarket indexed by MarketId
|
||||||
*/
|
*/
|
||||||
export const getPerpetualMarkets = (state: RootState) => state.perpetuals.markets;
|
export const getPerpetualMarkets = (state: RootState) => state.perpetuals.markets;
|
||||||
@@ -34,13 +32,11 @@ export const getMarketData = (marketId: string) => (state: RootState) =>
|
|||||||
getPerpetualMarkets(state)?.[marketId];
|
getPerpetualMarkets(state)?.[marketId];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns marketIds of all markets
|
* @returns marketIds of all markets
|
||||||
*/
|
*/
|
||||||
export const getMarketIds = (state: RootState) => Object.keys(getPerpetualMarkets(state) ?? []);
|
export const getMarketIds = (state: RootState) => Object.keys(getPerpetualMarkets(state) ?? []);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns PerpetualMarket data of the market the user is currently viewing
|
* @returns PerpetualMarket data of the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketData = (state: RootState) => {
|
export const getCurrentMarketData = (state: RootState) => {
|
||||||
@@ -49,7 +45,6 @@ export const getCurrentMarketData = (state: RootState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns MarketConfig data of the market the user is currently viewing
|
* @returns MarketConfig data of the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketConfig = (state: RootState) => getCurrentMarketData(state)?.configs;
|
export const getCurrentMarketConfig = (state: RootState) => getCurrentMarketData(state)?.configs;
|
||||||
@@ -62,13 +57,11 @@ export const getMarketConfig = (marketId: string) => (state: RootState) =>
|
|||||||
getMarketData(marketId)(state)?.configs;
|
getMarketData(marketId)(state)?.configs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns Record of list of MarketTrades indexed by marketId
|
* @returns Record of list of MarketTrades indexed by marketId
|
||||||
*/
|
*/
|
||||||
export const getLiveTrades = (state: RootState) => state.perpetuals.liveTrades;
|
export const getLiveTrades = (state: RootState) => state.perpetuals.liveTrades;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns List of MarketTrades of the market the user is currently viewing
|
* @returns List of MarketTrades of the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketLiveTrades = (state: RootState) => {
|
export const getCurrentMarketLiveTrades = (state: RootState) => {
|
||||||
@@ -78,13 +71,11 @@ export const getCurrentMarketLiveTrades = (state: RootState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns Record of subscribed or previously subscribed Orderbook data, indexed by marketId.
|
* @returns Record of subscribed or previously subscribed Orderbook data, indexed by marketId.
|
||||||
*/
|
*/
|
||||||
export const getOrderbooks = (state: RootState) => state.perpetuals.orderbooks;
|
export const getOrderbooks = (state: RootState) => state.perpetuals.orderbooks;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns Orderbook data for the market the user is currently viewing
|
* @returns Orderbook data for the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketOrderbook = (state: RootState) => {
|
export const getCurrentMarketOrderbook = (state: RootState) => {
|
||||||
@@ -94,13 +85,20 @@ export const getCurrentMarketOrderbook = (state: RootState) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
* @returns Orderbook data as a Map of price and size for the current market
|
||||||
|
*/
|
||||||
|
export const getCurrentMarketOrderbookMap = (state: RootState) => {
|
||||||
|
const orderbookMap = state.perpetuals.orderbooksMap;
|
||||||
|
const currentMarketId = getCurrentMarketId(state);
|
||||||
|
return orderbookMap?.[currentMarketId || ''];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
* @returns Record of historical funding indexed by marketId.
|
* @returns Record of historical funding indexed by marketId.
|
||||||
*/
|
*/
|
||||||
export const getHistoricalFundings = (state: RootState) => state.perpetuals.historicalFundings;
|
export const getHistoricalFundings = (state: RootState) => state.perpetuals.historicalFundings;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns Historical funding data for the market the user is currently viewing
|
* @returns Historical funding data for the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketHistoricalFundings = createSelector(
|
export const getCurrentMarketHistoricalFundings = createSelector(
|
||||||
@@ -110,7 +108,6 @@ export const getCurrentMarketHistoricalFundings = createSelector(
|
|||||||
);
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns Mid market price for the market the user is currently viewing
|
* @returns Mid market price for the market the user is currently viewing
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketMidMarketPrice = (state: RootState) => {
|
export const getCurrentMarketMidMarketPrice = (state: RootState) => {
|
||||||
@@ -150,7 +147,6 @@ export const getSelectedResolutionForMarket = (marketId: string) => (state: Root
|
|||||||
state.perpetuals.candles?.[marketId]?.selectedResolution;
|
state.perpetuals.candles?.[marketId]?.selectedResolution;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param state
|
|
||||||
* @returns Current market's next funding rate
|
* @returns Current market's next funding rate
|
||||||
*/
|
*/
|
||||||
export const getCurrentMarketNextFundingRate = createSelector(
|
export const getCurrentMarketNextFundingRate = createSelector(
|
||||||
|
|||||||
@@ -1,180 +0,0 @@
|
|||||||
:root {
|
|
||||||
/* Parameters */
|
|
||||||
|
|
||||||
/*
|
|
||||||
--layer-base-hue
|
|
||||||
--layer-base-saturation
|
|
||||||
--layer-base-lightness
|
|
||||||
|
|
||||||
--color-text-0
|
|
||||||
--color-text-1
|
|
||||||
--color-text-2
|
|
||||||
|
|
||||||
--color-accent
|
|
||||||
--color-positive
|
|
||||||
--color-negative
|
|
||||||
--color-success
|
|
||||||
--color-warning
|
|
||||||
--color-error
|
|
||||||
--color-favorite
|
|
||||||
*/
|
|
||||||
|
|
||||||
/* Computed: Layers */
|
|
||||||
|
|
||||||
--color-layer-0: hsl(
|
|
||||||
var(--layer-base-hue),
|
|
||||||
var(--layer-base-saturation),
|
|
||||||
calc(var(--layer-base-lightness) - 12%)
|
|
||||||
);
|
|
||||||
|
|
||||||
--color-layer-1: hsl(
|
|
||||||
var(--layer-base-hue),
|
|
||||||
var(--layer-base-saturation),
|
|
||||||
calc(var(--layer-base-lightness) - 8%)
|
|
||||||
);
|
|
||||||
|
|
||||||
--color-layer-2: hsl(
|
|
||||||
var(--layer-base-hue),
|
|
||||||
var(--layer-base-saturation),
|
|
||||||
calc(var(--layer-base-lightness) - 4%)
|
|
||||||
);
|
|
||||||
|
|
||||||
--color-layer-3: hsl(
|
|
||||||
var(--layer-base-hue),
|
|
||||||
var(--layer-base-saturation),
|
|
||||||
var(--layer-base-lightness)
|
|
||||||
);
|
|
||||||
|
|
||||||
--color-layer-4: hsl(
|
|
||||||
var(--layer-base-hue),
|
|
||||||
var(--layer-base-saturation),
|
|
||||||
calc(var(--layer-base-lightness) + 4%)
|
|
||||||
);
|
|
||||||
|
|
||||||
--color-layer-5: hsl(
|
|
||||||
var(--layer-base-hue),
|
|
||||||
calc(var(--layer-base-saturation) - 2%),
|
|
||||||
calc(var(--layer-base-lightness) + 7%)
|
|
||||||
);
|
|
||||||
|
|
||||||
--color-layer-6: hsl(
|
|
||||||
var(--layer-base-hue),
|
|
||||||
calc(var(--layer-base-saturation) - 4%),
|
|
||||||
calc(var(--layer-base-lightness) + 10%)
|
|
||||||
);
|
|
||||||
|
|
||||||
--color-layer-7: hsla(var(--layer-base-hue), 0%, 100%, 0.07);
|
|
||||||
|
|
||||||
--color-gradient-base-0: hsl(
|
|
||||||
var(--layer-base-hue),
|
|
||||||
calc(var(--layer-base-saturation) - 5%),
|
|
||||||
var(--layer-base-lightness)
|
|
||||||
);
|
|
||||||
|
|
||||||
--color-gradient-base-1: hsl(
|
|
||||||
var(--layer-base-hue),
|
|
||||||
calc(var(--layer-base-saturation) - 1%),
|
|
||||||
calc(var(--layer-base-lightness) + 7%)
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Computed: Borders */
|
|
||||||
|
|
||||||
--color-border: var(--color-layer-5);
|
|
||||||
--color-border-white: hsla(0, 0%, 100%, 0.2);
|
|
||||||
--color-border-red: hsla(360, 73%, 61%, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Theme: Classic (default) */
|
|
||||||
:root {
|
|
||||||
/* Constants */
|
|
||||||
|
|
||||||
--theme-classic-hue-purple: 240;
|
|
||||||
--theme-classic-color-green: hsl(159, 67%, 39%);
|
|
||||||
--theme-classic-color-yellow: hsl(36, 100%, 64%);
|
|
||||||
--theme-classic-color-red: hsl(360, 73%, 61%);
|
|
||||||
|
|
||||||
/* Parameters */
|
|
||||||
|
|
||||||
--layer-base-hue: var(--theme-classic-hue-purple);
|
|
||||||
--layer-base-saturation: 20%;
|
|
||||||
--layer-base-lightness: 16%;
|
|
||||||
|
|
||||||
--color-text-0: hsl(245, 11%, 55%);
|
|
||||||
--color-text-1: hsl(244, 18%, 81%);
|
|
||||||
--color-text-2: hsl(240, 43%, 99%);
|
|
||||||
|
|
||||||
--color-accent: hsl(var(--theme-classic-hue-purple), 100%, 70%);
|
|
||||||
--color-positive: var(--theme-classic-color-green);
|
|
||||||
--color-negative: var(--theme-classic-color-red);
|
|
||||||
--color-success: var(--theme-classic-color-green);
|
|
||||||
--color-warning: var(--theme-classic-color-yellow);
|
|
||||||
--color-error: var(--theme-classic-color-red);
|
|
||||||
--color-favorite: var(--theme-classic-color-yellow);
|
|
||||||
|
|
||||||
--color-white: #fff;
|
|
||||||
--color-black: #000;
|
|
||||||
|
|
||||||
--color-gradient-positive: hsla(158, 49%, 48%, 0.16);
|
|
||||||
--color-gradient-negative: hsla(0, 100%, 66%, 0.16);
|
|
||||||
--color-accent-faded: hsla(var(--theme-classic-hue-purple), 100%, 70%, 0.16);
|
|
||||||
|
|
||||||
--color-risk-low: var(--theme-classic-color-green);
|
|
||||||
--color-risk-medium: var(--theme-classic-color-yellow);
|
|
||||||
--color-risk-high: var(--theme-classic-color-red);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Theme: Light */
|
|
||||||
:root.theme-light {
|
|
||||||
/* Constants */
|
|
||||||
|
|
||||||
--theme-light-hue-purple: 234;
|
|
||||||
--theme-light-color-green: hsl(159, 67%, 39%);
|
|
||||||
--theme-light-color-yellow: hsl(36, 100%, 64%);
|
|
||||||
--theme-light-color-red: hsl(360, 73%, 61%);
|
|
||||||
|
|
||||||
/* Parameters */
|
|
||||||
|
|
||||||
--layer-base-hue: var(--theme-light-hue-purple);
|
|
||||||
--layer-base-saturation: 0%;
|
|
||||||
--layer-base-lightness: 86%;
|
|
||||||
|
|
||||||
--color-text-0: hsl(0, 0%, 55%);
|
|
||||||
--color-text-1: hsl(0, 0%, 40%);
|
|
||||||
--color-text-2: hsl(0, 0%, 5%);
|
|
||||||
|
|
||||||
--color-accent: hsl(var(--theme-light-hue-purple), 100%, 70%);
|
|
||||||
--color-positive: var(--theme-light-color-green);
|
|
||||||
--color-negative: var(--theme-light-color-red);
|
|
||||||
--color-success: var(--theme-light-color-green);
|
|
||||||
--color-warning: var(--theme-light-color-yellow);
|
|
||||||
--color-error: var(--theme-light-color-red);
|
|
||||||
--color-favorite: var(--theme-light-color-yellow);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Theme: Dark */
|
|
||||||
:root.theme-dark {
|
|
||||||
/* Constants */
|
|
||||||
|
|
||||||
--theme-dark-hue-purple: 240;
|
|
||||||
--theme-dark-color-green: hsl(159, 67%, 39%);
|
|
||||||
--theme-dark-color-yellow: hsl(36, 100%, 64%);
|
|
||||||
--theme-dark-color-red: hsl(360, 73%, 61%);
|
|
||||||
|
|
||||||
/* Parameters */
|
|
||||||
|
|
||||||
--layer-base-hue: 0;
|
|
||||||
--layer-base-saturation: 0%;
|
|
||||||
--layer-base-lightness: 16%;
|
|
||||||
|
|
||||||
--color-text-0: hsl(0, 0%, 47%);
|
|
||||||
--color-text-1: hsl(0, 0%, 67%);
|
|
||||||
--color-text-2: hsl(0, 0%, 100%);
|
|
||||||
|
|
||||||
--color-accent: hsl(var(--theme-dark-hue-purple), 100%, 70%);
|
|
||||||
--color-positive: var(--theme-dark-color-green);
|
|
||||||
--color-negative: var(--theme-dark-color-red);
|
|
||||||
--color-success: var(--theme-dark-color-green);
|
|
||||||
--color-warning: var(--theme-dark-color-yellow);
|
|
||||||
--color-error: var(--theme-dark-color-red);
|
|
||||||
--color-favorite: var(--theme-dark-color-yellow);
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
import { css } from 'styled-components';
|
|
||||||
|
|
||||||
import { type RiskLevels } from '@/constants/abacus';
|
|
||||||
|
|
||||||
import { AppTheme } from '@/state/configs';
|
|
||||||
|
|
||||||
const BaseElements = {
|
|
||||||
[AppTheme.Classic]: {
|
|
||||||
hue: 240,
|
|
||||||
saturation: 20,
|
|
||||||
lightness: 16,
|
|
||||||
},
|
|
||||||
[AppTheme.Dark]: {
|
|
||||||
hue: 0,
|
|
||||||
saturation: 0,
|
|
||||||
lightness: 16,
|
|
||||||
},
|
|
||||||
[AppTheme.Light]: {
|
|
||||||
hue: 234,
|
|
||||||
saturation: 0,
|
|
||||||
lightness: 86,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const LayerColors = ({ theme }: { theme: AppTheme }) => {
|
|
||||||
const { hue, saturation, lightness } = BaseElements[theme];
|
|
||||||
|
|
||||||
return {
|
|
||||||
layer0: `hsl(${hue}, ${saturation}%, ${lightness - 12}%)`,
|
|
||||||
layer1: `hsl(${hue}, ${saturation}%, ${lightness - 8}%)`,
|
|
||||||
layer2: `hsl(${hue}, ${saturation}%, ${lightness - 4}%)`,
|
|
||||||
layer3: `hsl(${hue}, ${saturation}%, ${lightness}%)`,
|
|
||||||
layer4: `hsl(${hue}, ${saturation}%, ${lightness + 4}%)`,
|
|
||||||
layer5: `hsl(${hue}, ${saturation - 2}%, ${lightness + 7}%)`,
|
|
||||||
layer6: `hsl(${hue}, ${saturation - 4}%, ${lightness + 10}%)`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const AccentColor = ({ theme }: { theme: AppTheme }) => {
|
|
||||||
const purpleHue = {
|
|
||||||
Classic: 240,
|
|
||||||
Dark: 240,
|
|
||||||
Light: 234,
|
|
||||||
}[theme];
|
|
||||||
|
|
||||||
return {
|
|
||||||
accent: `hsl(${purpleHue}, 100%, 70%)`,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const Colors = {
|
|
||||||
[AppTheme.Classic]: {
|
|
||||||
...LayerColors({ theme: AppTheme.Classic }),
|
|
||||||
text0: 'hsl(245, 11%, 55%)',
|
|
||||||
text1: 'hsl(244, 18%, 81%)',
|
|
||||||
text2: 'hsl(240, 43%, 99%)',
|
|
||||||
|
|
||||||
...AccentColor({ theme: AppTheme.Classic }),
|
|
||||||
green: 'hsl(159, 67%, 39%)',
|
|
||||||
yellow: 'hsl(36, 100%, 64%)',
|
|
||||||
red: 'hsl(360, 73%, 61%)',
|
|
||||||
|
|
||||||
positive: 'hsl(159, 67%, 39%)',
|
|
||||||
negative: 'hsl(360, 73%, 61%)',
|
|
||||||
|
|
||||||
success: 'hsl(159, 67%, 39%)',
|
|
||||||
warning: 'hsl(36, 100%, 64%)',
|
|
||||||
error: 'hsl(360, 73%, 61%)',
|
|
||||||
|
|
||||||
favorite: 'hsl(36, 100%, 64%)',
|
|
||||||
},
|
|
||||||
[AppTheme.Dark]: {
|
|
||||||
...LayerColors({ theme: AppTheme.Dark }),
|
|
||||||
text0: 'hsl(0, 0%, 47%)',
|
|
||||||
text1: 'hsl(0, 0%, 67%)',
|
|
||||||
text2: 'hsl(0, 0%, 100%)',
|
|
||||||
|
|
||||||
...AccentColor({ theme: AppTheme.Dark }),
|
|
||||||
green: 'hsl(159, 67%, 39%)',
|
|
||||||
yellow: 'hsl(36, 100%, 64%)',
|
|
||||||
red: 'hsl(360, 73%, 61%)',
|
|
||||||
|
|
||||||
positive: 'hsl(159, 67%, 39%)',
|
|
||||||
negative: 'hsl(360, 73%, 61%)',
|
|
||||||
|
|
||||||
success: 'hsl(159, 67%, 39%)',
|
|
||||||
warning: 'hsl(36, 100%, 64%)',
|
|
||||||
error: 'hsl(360, 73%, 61%)',
|
|
||||||
|
|
||||||
favorite: 'hsl(36, 100%, 64%)',
|
|
||||||
},
|
|
||||||
[AppTheme.Light]: {
|
|
||||||
...LayerColors({ theme: AppTheme.Light }),
|
|
||||||
text0: 'hsl(0, 0%, 55%)',
|
|
||||||
text1: 'hsl(0, 0%, 40%)',
|
|
||||||
text2: 'hsl(0, 0%, 5%)',
|
|
||||||
|
|
||||||
...AccentColor({ theme: AppTheme.Light }),
|
|
||||||
green: 'hsl(159, 67%, 39%)',
|
|
||||||
yellow: 'hsl(36, 100%, 64%)',
|
|
||||||
red: 'hsl(360, 73%, 61%)',
|
|
||||||
|
|
||||||
positive: 'hsl(159, 67%, 39%)',
|
|
||||||
negative: 'hsl(360, 73%, 61%)',
|
|
||||||
|
|
||||||
success: 'hsl(159, 67%, 39%)',
|
|
||||||
warning: 'hsl(36, 100%, 64%)',
|
|
||||||
error: 'hsl(360, 73%, 61%)',
|
|
||||||
|
|
||||||
favorite: 'hsl(36, 100%, 64%)',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
export const UsageColorFromRiskLevel = (riskLevel: RiskLevels) =>
|
|
||||||
({
|
|
||||||
low: css`
|
|
||||||
color: var(--color-risk-low);
|
|
||||||
`,
|
|
||||||
medium: css`
|
|
||||||
color: var(--color-risk-medium);
|
|
||||||
`,
|
|
||||||
high: css`
|
|
||||||
color: var(--color-risk-high);
|
|
||||||
`,
|
|
||||||
}[riskLevel.name]);
|
|
||||||
@@ -21,7 +21,7 @@ export const formMixins: Record<
|
|||||||
--input-radius: 0.5em;
|
--input-radius: 0.5em;
|
||||||
--input-height: var(--form-input-height);
|
--input-height: var(--form-input-height);
|
||||||
--input-width: 100%;
|
--input-width: 100%;
|
||||||
--input-backgroundColor: var(--color-layer-4);
|
--input-backgroundColor: ${({ theme }) => theme.inputBackground};
|
||||||
--input-borderColor: var(--color-layer-6);
|
--input-borderColor: var(--color-layer-6);
|
||||||
|
|
||||||
${layoutMixins.row}
|
${layoutMixins.row}
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { createGlobalStyle } from 'styled-components';
|
||||||
|
|
||||||
|
export const GlobalStyle = createGlobalStyle`
|
||||||
|
:root {
|
||||||
|
--color-layer-0: ${({ theme }) => theme.layer0};
|
||||||
|
--color-layer-1: ${({ theme }) => theme.layer1};
|
||||||
|
--color-layer-2: ${({ theme }) => theme.layer2};
|
||||||
|
--color-layer-3: ${({ theme }) => theme.layer3};
|
||||||
|
--color-layer-4: ${({ theme }) => theme.layer4};
|
||||||
|
--color-layer-5: ${({ theme }) => theme.layer5};
|
||||||
|
--color-layer-6: ${({ theme }) => theme.layer6};
|
||||||
|
--color-layer-7: ${({ theme }) => theme.layer7};
|
||||||
|
|
||||||
|
--color-border: ${({ theme }) => theme.borderDefault};
|
||||||
|
--color-border-white: ${({ theme }) => theme.borderButton};
|
||||||
|
--color-border-red: ${({ theme }) => theme.borderDestructive};
|
||||||
|
|
||||||
|
--color-text-0: ${({ theme }) => theme.textTertiary};
|
||||||
|
--color-text-1: ${({ theme }) => theme.textSecondary};
|
||||||
|
--color-text-2: ${({ theme }) => theme.textPrimary};
|
||||||
|
|
||||||
|
--color-gradient-base-0: ${({ theme }) => theme.gradientBase0};
|
||||||
|
--color-gradient-base-1: ${({ theme }) => theme.gradientBase1};
|
||||||
|
|
||||||
|
--color-accent: ${({ theme }) => theme.accent};
|
||||||
|
--color-accent-faded: ${({ theme }) => theme.accentFaded};
|
||||||
|
--color-favorite: ${({ theme }) => theme.favorite};
|
||||||
|
|
||||||
|
--color-success: ${({ theme }) => theme.success};
|
||||||
|
--color-warning: ${({ theme }) => theme.warning};
|
||||||
|
--color-error: ${({ theme }) => theme.error};
|
||||||
|
|
||||||
|
--color-positive: ${({ theme }) => theme.positive};
|
||||||
|
--color-negative: ${({ theme }) => theme.negative};
|
||||||
|
--color-gradient-positive: ${({ theme }) => theme.positiveFaded};
|
||||||
|
--color-gradient-negative: ${({ theme }) => theme.negativeFaded};
|
||||||
|
|
||||||
|
--color-risk-low: ${({ theme }) => theme.riskLow};
|
||||||
|
--color-risk-medium: ${({ theme }) => theme.riskMedium};
|
||||||
|
--color-risk-high: ${({ theme }) => theme.riskHigh};
|
||||||
|
}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
import { AppTheme } from '@/state/configs';
|
||||||
|
import type { ThemeColors } from '@/constants/styles/colors';
|
||||||
|
import { ColorToken, OpacityToken } from '@/constants/styles/base';
|
||||||
|
import { generateFadedColorVariant } from '@/lib/styles';
|
||||||
|
|
||||||
|
const ClassicTheme: ThemeColors = {
|
||||||
|
layer0: ColorToken.GrayBlue7,
|
||||||
|
layer1: ColorToken.GrayBlue6,
|
||||||
|
layer2: ColorToken.GrayBlue5,
|
||||||
|
layer3: ColorToken.GrayBlue4,
|
||||||
|
layer4: ColorToken.GrayBlue3,
|
||||||
|
layer5: ColorToken.GrayBlue2,
|
||||||
|
layer6: ColorToken.GrayBlue1,
|
||||||
|
layer7: ColorToken.GrayBlue0,
|
||||||
|
|
||||||
|
borderDefault: ColorToken.GrayBlue2,
|
||||||
|
borderDestructive: generateFadedColorVariant(ColorToken.Red2, OpacityToken.Opacity20),
|
||||||
|
borderButton: generateFadedColorVariant(ColorToken.White, OpacityToken.Opacity20),
|
||||||
|
|
||||||
|
textPrimary: ColorToken.LightGray2,
|
||||||
|
textSecondary: ColorToken.GrayPurple1,
|
||||||
|
textTertiary: ColorToken.GrayPurple2,
|
||||||
|
|
||||||
|
gradientBase0: ColorToken.DarkGray9,
|
||||||
|
gradientBase1: ColorToken.GrayBlue2,
|
||||||
|
|
||||||
|
accent: ColorToken.Purple1,
|
||||||
|
accentFaded: generateFadedColorVariant(ColorToken.Purple1, OpacityToken.Opacity16),
|
||||||
|
favorite: ColorToken.Yellow0,
|
||||||
|
|
||||||
|
success: ColorToken.Green1,
|
||||||
|
warning: ColorToken.Yellow0,
|
||||||
|
error: ColorToken.Red2,
|
||||||
|
|
||||||
|
positive: ColorToken.Green1,
|
||||||
|
negative: ColorToken.Red2,
|
||||||
|
positiveFaded: generateFadedColorVariant(ColorToken.Green1, OpacityToken.Opacity16),
|
||||||
|
negativeFaded: generateFadedColorVariant(ColorToken.Red2, OpacityToken.Opacity16),
|
||||||
|
|
||||||
|
riskLow: ColorToken.Green1,
|
||||||
|
riskMedium: ColorToken.Yellow0,
|
||||||
|
riskHigh: ColorToken.Red2,
|
||||||
|
|
||||||
|
logoFill: ColorToken.White,
|
||||||
|
profileYellow: ColorToken.Yellow1,
|
||||||
|
profileRed: ColorToken.Red2,
|
||||||
|
|
||||||
|
inputBackground: ColorToken.GrayBlue3,
|
||||||
|
popoverBackground: generateFadedColorVariant(ColorToken.GrayBlue4, OpacityToken.Opacity90),
|
||||||
|
switchThumbActiveBackground: ColorToken.White,
|
||||||
|
toggleBackground: ColorToken.GrayBlue3,
|
||||||
|
tooltipBackground: generateFadedColorVariant(ColorToken.GrayBlue3, OpacityToken.Opacity66),
|
||||||
|
};
|
||||||
|
|
||||||
|
const DarkTheme: ThemeColors = {
|
||||||
|
layer0: ColorToken.Black,
|
||||||
|
layer1: ColorToken.DarkGray11,
|
||||||
|
layer2: ColorToken.DarkGray13,
|
||||||
|
layer3: ColorToken.DarkGray10,
|
||||||
|
layer4: ColorToken.DarkGray6,
|
||||||
|
layer5: ColorToken.DarkGray5,
|
||||||
|
layer6: ColorToken.DarkGray4,
|
||||||
|
layer7: ColorToken.DarkGray2,
|
||||||
|
|
||||||
|
borderDefault: ColorToken.DarkGray4,
|
||||||
|
borderDestructive: generateFadedColorVariant(ColorToken.Red0, OpacityToken.Opacity20),
|
||||||
|
borderButton: generateFadedColorVariant(ColorToken.White, OpacityToken.Opacity20),
|
||||||
|
|
||||||
|
textPrimary: ColorToken.LightGray0,
|
||||||
|
textSecondary: ColorToken.MediumGray0,
|
||||||
|
textTertiary: ColorToken.DarkGray0,
|
||||||
|
|
||||||
|
gradientBase0: ColorToken.DarkGray8,
|
||||||
|
gradientBase1: ColorToken.DarkGray5,
|
||||||
|
|
||||||
|
accent: ColorToken.Purple0,
|
||||||
|
accentFaded: generateFadedColorVariant(ColorToken.Purple0, OpacityToken.Opacity16),
|
||||||
|
favorite: ColorToken.Yellow0,
|
||||||
|
|
||||||
|
success: ColorToken.Green0,
|
||||||
|
warning: ColorToken.Yellow0,
|
||||||
|
error: ColorToken.Red0,
|
||||||
|
|
||||||
|
positive: ColorToken.Green0,
|
||||||
|
negative: ColorToken.Red0,
|
||||||
|
positiveFaded: generateFadedColorVariant(ColorToken.Green0, OpacityToken.Opacity16),
|
||||||
|
negativeFaded: generateFadedColorVariant(ColorToken.Red0, OpacityToken.Opacity16),
|
||||||
|
|
||||||
|
riskLow: ColorToken.Green0,
|
||||||
|
riskMedium: ColorToken.Yellow0,
|
||||||
|
riskHigh: ColorToken.Red0,
|
||||||
|
|
||||||
|
logoFill: ColorToken.White,
|
||||||
|
profileYellow: ColorToken.Yellow1,
|
||||||
|
profileRed: ColorToken.Red2,
|
||||||
|
|
||||||
|
inputBackground: ColorToken.DarkGray6,
|
||||||
|
popoverBackground: generateFadedColorVariant(ColorToken.DarkGray8, OpacityToken.Opacity90),
|
||||||
|
switchThumbActiveBackground: ColorToken.White,
|
||||||
|
toggleBackground: ColorToken.DarkGray6,
|
||||||
|
tooltipBackground: generateFadedColorVariant(ColorToken.DarkGray6, OpacityToken.Opacity66),
|
||||||
|
};
|
||||||
|
|
||||||
|
const LightTheme: ThemeColors = {
|
||||||
|
layer0: ColorToken.White,
|
||||||
|
layer1: ColorToken.LightGray6,
|
||||||
|
layer2: ColorToken.White,
|
||||||
|
layer3: ColorToken.LightGray1,
|
||||||
|
layer4: ColorToken.White,
|
||||||
|
layer5: ColorToken.LightGray4,
|
||||||
|
layer6: ColorToken.LightGray9,
|
||||||
|
layer7: ColorToken.MediumGray1,
|
||||||
|
|
||||||
|
borderDefault: ColorToken.LightGray10,
|
||||||
|
borderDestructive: generateFadedColorVariant(ColorToken.Red1, OpacityToken.Opacity20),
|
||||||
|
borderButton: generateFadedColorVariant(ColorToken.Black, OpacityToken.Opacity20),
|
||||||
|
|
||||||
|
textPrimary: ColorToken.DarkGray12,
|
||||||
|
textSecondary: ColorToken.DarkGray3,
|
||||||
|
textTertiary: ColorToken.DarkGray1,
|
||||||
|
|
||||||
|
gradientBase0: ColorToken.LightGray8,
|
||||||
|
gradientBase1: ColorToken.LightGray5,
|
||||||
|
|
||||||
|
accent: ColorToken.Purple0,
|
||||||
|
accentFaded: generateFadedColorVariant(ColorToken.Purple0, OpacityToken.Opacity16),
|
||||||
|
favorite: ColorToken.Yellow0,
|
||||||
|
|
||||||
|
success: ColorToken.Green2,
|
||||||
|
warning: ColorToken.Yellow0,
|
||||||
|
error: ColorToken.Red1,
|
||||||
|
|
||||||
|
positive: ColorToken.Green2,
|
||||||
|
negative: ColorToken.Red1,
|
||||||
|
positiveFaded: generateFadedColorVariant(ColorToken.Green2, OpacityToken.Opacity16),
|
||||||
|
negativeFaded: generateFadedColorVariant(ColorToken.Red1, OpacityToken.Opacity16),
|
||||||
|
|
||||||
|
riskLow: ColorToken.Green2,
|
||||||
|
riskMedium: ColorToken.Yellow0,
|
||||||
|
riskHigh: ColorToken.Red1,
|
||||||
|
|
||||||
|
logoFill: ColorToken.Black,
|
||||||
|
profileYellow: ColorToken.Yellow1,
|
||||||
|
profileRed: ColorToken.Red2,
|
||||||
|
|
||||||
|
inputBackground: ColorToken.White,
|
||||||
|
popoverBackground: generateFadedColorVariant(ColorToken.LightGray8, OpacityToken.Opacity90),
|
||||||
|
switchThumbActiveBackground: ColorToken.White,
|
||||||
|
toggleBackground: ColorToken.LightGray4,
|
||||||
|
tooltipBackground: generateFadedColorVariant(ColorToken.LightGray7, OpacityToken.Opacity66),
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Themes = {
|
||||||
|
[AppTheme.Classic]: ClassicTheme,
|
||||||
|
[AppTheme.Dark]: DarkTheme,
|
||||||
|
[AppTheme.Light]: LightTheme,
|
||||||
|
};
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
import { forwardRef } from 'react';
|
||||||
|
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||||
|
|
||||||
|
import type { Nullable } from '@/constants/abacus';
|
||||||
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { TOKEN_DECIMALS } from '@/constants/numbers';
|
||||||
|
import { ORDERBOOK_ROW_HEIGHT } from '@/constants/orderbook';
|
||||||
|
import { useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
|
import { Output, OutputType } from '@/components/Output';
|
||||||
|
import { WithTooltip } from '@/components/WithTooltip';
|
||||||
|
|
||||||
|
type StyleProps = {
|
||||||
|
side?: 'top' | 'bottom';
|
||||||
|
};
|
||||||
|
|
||||||
|
type ElementProps = {
|
||||||
|
spread?: Nullable<number>;
|
||||||
|
spreadPercent?: Nullable<number>;
|
||||||
|
tickSizeDecimals?: Nullable<number>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const OrderbookRow = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-items: center;
|
||||||
|
height: ${ORDERBOOK_ROW_HEIGHT}px;
|
||||||
|
min-height: ${ORDERBOOK_ROW_HEIGHT}px;
|
||||||
|
position: relative;
|
||||||
|
padding-right: 0.5rem;
|
||||||
|
font: var(--font-mini-book);
|
||||||
|
|
||||||
|
> span {
|
||||||
|
flex: 1 1 0%;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
export const SpreadRow = forwardRef<HTMLDivElement, StyleProps & ElementProps>(
|
||||||
|
({ side, spread, spreadPercent, tickSizeDecimals = TOKEN_DECIMALS }, ref) => {
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styled.SpreadRow ref={ref} side={side}>
|
||||||
|
<span>
|
||||||
|
<WithTooltip tooltip="spread">
|
||||||
|
{stringGetter({ key: STRING_KEYS.ORDERBOOK_SPREAD })}
|
||||||
|
</WithTooltip>
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<Output type={OutputType.Number} value={spread} fractionDigits={tickSizeDecimals} />
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
<Output type={OutputType.Percent} value={spreadPercent} />
|
||||||
|
</span>
|
||||||
|
</Styled.SpreadRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.SpreadRow = styled(OrderbookRow)<{ side?: 'top' | 'bottom' }>`
|
||||||
|
height: 2rem;
|
||||||
|
border-top: var(--border);
|
||||||
|
border-bottom: var(--border);
|
||||||
|
span {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
${({ side }) =>
|
||||||
|
side &&
|
||||||
|
{
|
||||||
|
top: css`
|
||||||
|
border-top: none;
|
||||||
|
`,
|
||||||
|
bottom: css`
|
||||||
|
border-bottom: none;
|
||||||
|
`,
|
||||||
|
}[side]}
|
||||||
|
`;
|
||||||
@@ -0,0 +1,284 @@
|
|||||||
|
import { forwardRef, useCallback, useMemo, useRef } from 'react';
|
||||||
|
import styled, { AnyStyledComponent, css } from 'styled-components';
|
||||||
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import { Nullable, type PerpetualMarketOrderbookLevel } from '@/constants/abacus';
|
||||||
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { ORDERBOOK_HEIGHT, ORDERBOOK_MAX_ROWS_PER_SIDE } from '@/constants/orderbook';
|
||||||
|
import { useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
|
import {
|
||||||
|
useCalculateOrderbookData,
|
||||||
|
useCenterOrderbook,
|
||||||
|
useDrawOrderbook,
|
||||||
|
useSpreadRowScrollListener,
|
||||||
|
} from '@/hooks/Orderbook';
|
||||||
|
|
||||||
|
import { Canvas } from '@/components/Canvas';
|
||||||
|
import { LoadingSpace } from '@/components/Loading/LoadingSpinner';
|
||||||
|
import { Tag } from '@/components/Tag';
|
||||||
|
|
||||||
|
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
||||||
|
import { getCurrentMarketConfig, getCurrentMarketId } from '@/state/perpetualsSelectors';
|
||||||
|
import { setTradeFormInputs } from '@/state/inputs';
|
||||||
|
import { getCurrentInput } from '@/state/inputsSelectors';
|
||||||
|
|
||||||
|
import { OrderbookRow, SpreadRow } from './OrderbookRow';
|
||||||
|
|
||||||
|
type ElementProps = {
|
||||||
|
maxRowsPerSide?: number;
|
||||||
|
layout?: 'vertical' | 'horizontal';
|
||||||
|
};
|
||||||
|
|
||||||
|
type StyleProps = {
|
||||||
|
hideHeader?: boolean;
|
||||||
|
histogramSide?: 'left' | 'right';
|
||||||
|
className?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const CanvasOrderbook = forwardRef(
|
||||||
|
(
|
||||||
|
{
|
||||||
|
histogramSide = 'right',
|
||||||
|
maxRowsPerSide = ORDERBOOK_MAX_ROWS_PER_SIDE,
|
||||||
|
}: ElementProps & StyleProps,
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const { asks, bids, hasOrderbook, histogramRange, spread, spreadPercent } =
|
||||||
|
useCalculateOrderbookData({
|
||||||
|
maxRowsPerSide,
|
||||||
|
});
|
||||||
|
|
||||||
|
const stringGetter = useStringGetter();
|
||||||
|
const currentMarket = useSelector(getCurrentMarketId) ?? '';
|
||||||
|
const currentMarketConfig = useSelector(getCurrentMarketConfig, shallowEqual);
|
||||||
|
const { id = '' } = useSelector(getCurrentMarketAssetData, shallowEqual) ?? {};
|
||||||
|
|
||||||
|
const { tickSizeDecimals = 2 } = currentMarketConfig ?? {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Slice asks and bids to maxRowsPerSide using empty rows
|
||||||
|
*/
|
||||||
|
const { asksSlice, bidsSlice } = useMemo(() => {
|
||||||
|
let asksSlice: Array<PerpetualMarketOrderbookLevel | undefined> = [];
|
||||||
|
const emptyAskRows =
|
||||||
|
asks.length < maxRowsPerSide
|
||||||
|
? new Array<undefined>(maxRowsPerSide - asks.length).fill(undefined)
|
||||||
|
: [];
|
||||||
|
asksSlice = [...emptyAskRows, ...asks.reverse()];
|
||||||
|
|
||||||
|
let bidsSlice: Array<PerpetualMarketOrderbookLevel | undefined> = [];
|
||||||
|
const emptyBidRows =
|
||||||
|
bids.length < maxRowsPerSide
|
||||||
|
? new Array<undefined>(maxRowsPerSide - bids.length).fill(undefined)
|
||||||
|
: [];
|
||||||
|
bidsSlice = [...bids, ...emptyBidRows];
|
||||||
|
|
||||||
|
return {
|
||||||
|
asksSlice,
|
||||||
|
bidsSlice,
|
||||||
|
};
|
||||||
|
}, [asks, bids]);
|
||||||
|
|
||||||
|
const orderbookRef = useRef<HTMLDivElement>(null);
|
||||||
|
useCenterOrderbook({ orderbookRef, marketId: currentMarket });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display top or bottom spreadRow when center spreadRow is off screen
|
||||||
|
*/
|
||||||
|
const spreadRowRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
const displaySide = useSpreadRowScrollListener({
|
||||||
|
orderbookRef,
|
||||||
|
spreadRowRef,
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Row action
|
||||||
|
*/
|
||||||
|
const currentInput = useSelector(getCurrentInput);
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const onRowAction = useCallback(
|
||||||
|
(price: Nullable<number>) => {
|
||||||
|
if (currentInput === 'trade' && price) {
|
||||||
|
dispatch(setTradeFormInputs({ limitPriceInput: price?.toString() }));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[currentInput]
|
||||||
|
);
|
||||||
|
|
||||||
|
const { canvasRef: asksCanvasRef } = useDrawOrderbook({
|
||||||
|
data: [...asksSlice].reverse(),
|
||||||
|
histogramRange,
|
||||||
|
histogramSide,
|
||||||
|
side: 'ask',
|
||||||
|
});
|
||||||
|
|
||||||
|
const { canvasRef: bidsCanvasRef } = useDrawOrderbook({
|
||||||
|
data: bidsSlice,
|
||||||
|
histogramRange,
|
||||||
|
histogramSide,
|
||||||
|
side: 'bid',
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Styled.OrderbookContainer ref={ref}>
|
||||||
|
<Styled.OrderbookContent $isLoading={!hasOrderbook}>
|
||||||
|
<Styled.Header>
|
||||||
|
<span>
|
||||||
|
{stringGetter({ key: STRING_KEYS.SIZE })} {id && <Tag>{id}</Tag>}
|
||||||
|
</span>
|
||||||
|
<span>
|
||||||
|
{stringGetter({ key: STRING_KEYS.PRICE })} <Tag>USD</Tag>
|
||||||
|
</span>
|
||||||
|
<span>{stringGetter({ key: STRING_KEYS.MINE })}</span>
|
||||||
|
</Styled.Header>
|
||||||
|
|
||||||
|
{displaySide === 'top' && (
|
||||||
|
<Styled.SpreadRow
|
||||||
|
side="top"
|
||||||
|
spread={spread}
|
||||||
|
spreadPercent={spreadPercent}
|
||||||
|
tickSizeDecimals={tickSizeDecimals}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Styled.OrderbookWrapper ref={orderbookRef}>
|
||||||
|
<Styled.OrderbookSideContainer $side="asks">
|
||||||
|
<Styled.HoverRows $bottom>
|
||||||
|
{asksSlice.map((row: PerpetualMarketOrderbookLevel | undefined, idx) =>
|
||||||
|
row ? (
|
||||||
|
<Styled.Row
|
||||||
|
key={idx}
|
||||||
|
title={`${row.price}`}
|
||||||
|
onClick={() => {
|
||||||
|
onRowAction(row.price);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Styled.Row key={idx} />
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Styled.HoverRows>
|
||||||
|
<Styled.OrderbookCanvas ref={asksCanvasRef} width="100%" height="100%" />
|
||||||
|
</Styled.OrderbookSideContainer>
|
||||||
|
|
||||||
|
<SpreadRow
|
||||||
|
ref={spreadRowRef}
|
||||||
|
spread={spread?.toNumber()}
|
||||||
|
spreadPercent={spreadPercent}
|
||||||
|
tickSizeDecimals={tickSizeDecimals}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Styled.OrderbookSideContainer $side="bids">
|
||||||
|
<Styled.HoverRows>
|
||||||
|
{bidsSlice.map((row: PerpetualMarketOrderbookLevel | undefined, idx) =>
|
||||||
|
row ? (
|
||||||
|
<Styled.Row
|
||||||
|
key={idx}
|
||||||
|
title={`${row.price}`}
|
||||||
|
onClick={
|
||||||
|
row?.price
|
||||||
|
? () => {
|
||||||
|
onRowAction(row.price);
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<Styled.Row key={idx} />
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</Styled.HoverRows>
|
||||||
|
<Styled.OrderbookCanvas ref={bidsCanvasRef} width="100%" height="100%" />
|
||||||
|
</Styled.OrderbookSideContainer>
|
||||||
|
</Styled.OrderbookWrapper>
|
||||||
|
{displaySide === 'bottom' && (
|
||||||
|
<Styled.SpreadRow
|
||||||
|
side="bottom"
|
||||||
|
spread={spread}
|
||||||
|
spreadPercent={spreadPercent}
|
||||||
|
tickSizeDecimals={tickSizeDecimals}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</Styled.OrderbookContent>
|
||||||
|
{!hasOrderbook && <LoadingSpace id="canvas-orderbook" />}
|
||||||
|
</Styled.OrderbookContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const Styled: Record<string, AnyStyledComponent> = {};
|
||||||
|
|
||||||
|
Styled.OrderbookContainer = styled.div`
|
||||||
|
display: flex;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.OrderbookContent = styled.div<{ $isLoading?: boolean }>`
|
||||||
|
max-height: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
position: relative;
|
||||||
|
${({ $isLoading }) => $isLoading && 'flex: 1;'}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Header = styled(OrderbookRow)`
|
||||||
|
height: 2rem;
|
||||||
|
border-bottom: var(--border);
|
||||||
|
color: var(--color-text-0);
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.OrderbookWrapper = styled.div`
|
||||||
|
overflow-y: auto;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1 1 0%;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.OrderbookSideContainer = styled.div<{ $side: 'bids' | 'asks' }>`
|
||||||
|
min-height: ${ORDERBOOK_HEIGHT}px;
|
||||||
|
${({ $side }) => css`
|
||||||
|
--accent-color: ${$side === 'bids' ? 'var(--color-positive)' : 'var(--color-negative)'};
|
||||||
|
`}
|
||||||
|
position: relative;
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.OrderbookCanvas = styled(Canvas)`
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
right: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
font-feature-settings: var(--fontFeature-monoNumbers);
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.HoverRows = styled.div<{ $bottom?: boolean }>`
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
${({ $bottom }) => $bottom && 'bottom: 0;'}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.Row = styled(OrderbookRow)<{ onClick?: () => void }>`
|
||||||
|
${({ onClick }) =>
|
||||||
|
onClick
|
||||||
|
? css`
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--color-layer-4);
|
||||||
|
filter: darkness(0.1);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
: css`
|
||||||
|
cursor: default;
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Styled.SpreadRow = styled(SpreadRow)`
|
||||||
|
position: absolute;
|
||||||
|
`;
|
||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
|
||||||
import { useStringGetter } from '@/hooks';
|
import { useStringGetter } from '@/hooks';
|
||||||
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
|
import { useOrderbookValuesForDepthChart } from '@/hooks/Orderbook/useOrderbookValues';
|
||||||
|
|
||||||
import { TooltipContent } from '@/components/visx/TooltipContent';
|
import { TooltipContent } from '@/components/visx/TooltipContent';
|
||||||
import { Details } from '@/components/Details';
|
import { Details } from '@/components/Details';
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
import { StringGetterFunction } from '@/constants/localization';
|
import { StringGetterFunction } from '@/constants/localization';
|
||||||
|
|
||||||
import { useBreakpoints } from '@/hooks';
|
import { useBreakpoints } from '@/hooks';
|
||||||
import { useOrderbookValuesForDepthChart } from '@/hooks/useOrderbookValues';
|
import { useOrderbookValuesForDepthChart } from '@/hooks/Orderbook/useOrderbookValues';
|
||||||
|
|
||||||
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
|
import { getCurrentMarketConfig } from '@/state/perpetualsSelectors';
|
||||||
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
import { getCurrentMarketAssetData } from '@/state/assetsSelectors';
|
||||||
@@ -83,18 +83,8 @@ export const DepthChart = ({
|
|||||||
const { stepSizeDecimals, tickSizeDecimals } =
|
const { stepSizeDecimals, tickSizeDecimals } =
|
||||||
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
|
useSelector(getCurrentMarketConfig, shallowEqual) ?? {};
|
||||||
|
|
||||||
const {
|
const { bids, asks, lowestBid, highestBid, lowestAsk, highestAsk, midMarketPrice, orderbook } =
|
||||||
bids,
|
useOrderbookValuesForDepthChart();
|
||||||
asks,
|
|
||||||
lowestBid,
|
|
||||||
highestBid,
|
|
||||||
lowestAsk,
|
|
||||||
highestAsk,
|
|
||||||
midMarketPrice,
|
|
||||||
spread,
|
|
||||||
spreadPercent,
|
|
||||||
orderbook,
|
|
||||||
} = useOrderbookValuesForDepthChart();
|
|
||||||
|
|
||||||
// Chart state
|
// Chart state
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ export const TradeSideToggle = memo(() => {
|
|||||||
const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
|
const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
|
||||||
--toggle-radius: 0.5em;
|
--toggle-radius: 0.5em;
|
||||||
--toggle-color: var(--color-negative);
|
--toggle-color: var(--color-negative);
|
||||||
|
--toggle-background: ${({ theme }) => theme.toggleBackground};
|
||||||
|
|
||||||
${({ value }) =>
|
${({ value }) =>
|
||||||
value === OrderSide.BUY &&
|
value === OrderSide.BUY &&
|
||||||
@@ -52,7 +53,7 @@ const ToggleContainer = styled(ToggleGroup)<{ value: OrderSide }>`
|
|||||||
`}
|
`}
|
||||||
|
|
||||||
border-radius: var(--toggle-radius);
|
border-radius: var(--toggle-radius);
|
||||||
background-color: var(--color-layer-4);
|
background-color: var(--toggle-background);
|
||||||
position: relative;
|
position: relative;
|
||||||
|
|
||||||
> button {
|
> button {
|
||||||
|
|||||||
@@ -163,6 +163,7 @@ Styled.SizeOutput = styled(Output)<StyleProps>`
|
|||||||
Styled.LiveTradesTable = styled(OrderbookTradesTable)<StyleProps>`
|
Styled.LiveTradesTable = styled(OrderbookTradesTable)<StyleProps>`
|
||||||
tr {
|
tr {
|
||||||
--histogram-bucket-size: 1;
|
--histogram-bucket-size: 1;
|
||||||
|
background-color: var(--color-layer-2);
|
||||||
|
|
||||||
&[data-side=${OrderSide.BUY}] {
|
&[data-side=${OrderSide.BUY}] {
|
||||||
--accent-color: var(--color-positive);
|
--accent-color: var(--color-positive);
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
import { useCallback, useMemo } from 'react';
|
||||||
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
import { shallowEqual, useDispatch, useSelector } from 'react-redux';
|
||||||
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
|
import styled, { type AnyStyledComponent, css, keyframes } from 'styled-components';
|
||||||
|
|
||||||
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
import { OrderSide } from '@dydxprotocol/v4-client-js';
|
||||||
import { type OrderbookLine, TradeInputField } from '@/constants/abacus';
|
import { type OrderbookLine } from '@/constants/abacus';
|
||||||
import { STRING_KEYS } from '@/constants/localization';
|
import { STRING_KEYS } from '@/constants/localization';
|
||||||
|
import { ORDERBOOK_MAX_ROWS_PER_SIDE } from '@/constants/orderbook';
|
||||||
|
|
||||||
import { useBreakpoints, useStringGetter } from '@/hooks';
|
import { useBreakpoints, useStringGetter } from '@/hooks';
|
||||||
|
|
||||||
@@ -29,8 +30,6 @@ import { OrderbookTradesOutput, OrderbookTradesTable } from './OrderbookTradesTa
|
|||||||
import { breakpoints } from '@/styles';
|
import { breakpoints } from '@/styles';
|
||||||
import { layoutMixins } from '@/styles/layoutMixins';
|
import { layoutMixins } from '@/styles/layoutMixins';
|
||||||
|
|
||||||
const ORDERBOOK_MAX_ROWS_PER_SIDE = 35;
|
|
||||||
|
|
||||||
type ElementProps = {
|
type ElementProps = {
|
||||||
maxRowsPerSide?: number;
|
maxRowsPerSide?: number;
|
||||||
layout?: 'vertical' | 'horizontal';
|
layout?: 'vertical' | 'horizontal';
|
||||||
|
|||||||
Reference in New Issue
Block a user