Feature/303 orderbook improvements (#312)
* [#151] market-depth code cleanup
* [#303] Make ask and bid relative volume bars relative to maximum bid or ask volume
* [#151] align cmulative vol bars to left
* [#151] replace orderbook zoom in zoom out buttons with dropdown
* [#151] fill gaps in orderbook data
* Order book mocks added
* [#151] mark mid price in orderbook
* [303] Show number in orderbook cumulative volume column
* [#808] show indicative uncrossing volume instead of volume if market is in auction mode
* Method for asserting order book style
* [#303] Add test id attributes to orderbook cells
* Cleanup steps after merge
* Order book test passing
* Change method name
* Revert "[#151] fill gaps in orderbook data"
This reverts commit 90ea4e4ab3
.
* [#303] Orderbook rows render optimization
* test: update feature with @todo tests
Same tests can be found in Notion
* [#303] Orderbook scroll to mid price
* [#303] orderbook scroll to row pixel perfect alignment
* [#303] Bring back best offer horizontal lines
* [#303] Preserve center price level on row number change, adjust indicativePrice to resoluton
* feat(orderbook): add storybook
Refs: #303
* feat(orderbook): fix no rows handling
Refs: #303
* feat(orderbook): add orderbook stories for auction and continous market
Refs: #303
* feat(orderbook): add stories for empty orderbook
Refs: #303
* feat(orderbook): fix footer position when there is no data
Refs: #303
* feat(orderbook): seperate number of rows for buy and sell in storybook
Refs: #303
* feat(orderbook): keep mid price in middle until user will scroll
Refs: #303
* feat(orderbook): style scrollbar
* feat(orderbook): style scrollbar
* feat(orderbook): adjust gaps
* feat(orderbook): adjust gaps
* test: addition for autofilled order and mid price lines
* fix: lint
* feat(orderbook): make it posiible to write RTL tests
* feat(orderbook): fix price focus, add unit tests
* feat(orderbook): fix price scroll to mid proce, add unit tests
* feat(orderbook): improvements
- fix scrollbar colors in firefox
- bring back resolution dropdown chevron
- hide go to mid button when locked on mid price
- right align ask vol bar
- change grid gap to 5px
- add vertical lines between columns
- display "No data" if theis no orderbook data
- align header labels to right
* feat(orderbook): fix formatting
* feat(orderbook): add 5px gap
* feat(orderbook): improvements after code review
* feat(orderbook): display full height vertical lines
* fix: change in mid position
* feat(orderbook): fix number cannot be converted to BigInt because it is not integer
* feat(orderbook): fix TS2307 in trading-e2e caused by .module.scss import
Co-authored-by: Joe <joe@vega.xyz>
This commit is contained in:
parent
71226e3d75
commit
d0452aeb81
1
apps/trading-e2e/declaration.d.ts
vendored
Normal file
1
apps/trading-e2e/declaration.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
declare module '*.scss';
|
@ -76,15 +76,6 @@ Feature: Trading page
|
|||||||
And place a buy 'FOK' market order with amount of 0
|
And place a buy 'FOK' market order with amount of 0
|
||||||
Then Order rejected by wallet error shown containing text "must be positive"
|
Then Order rejected by wallet error shown containing text "must be positive"
|
||||||
|
|
||||||
@manual
|
|
||||||
Scenario: Deal ticket: GTT order failed because invalid date
|
|
||||||
|
|
||||||
@manual
|
|
||||||
Scenario: Deal ticket: GTT order failed because date in the past
|
|
||||||
|
|
||||||
@manual
|
|
||||||
Scenario: Deal ticket: GTT order failed because date over allowed period
|
|
||||||
|
|
||||||
Scenario: Positions: Displayed when connected to wallet
|
Scenario: Positions: Displayed when connected to wallet
|
||||||
Given I am on the trading page for an active market
|
Given I am on the trading page for an active market
|
||||||
And I connect to Vega Wallet
|
And I connect to Vega Wallet
|
||||||
@ -97,9 +88,40 @@ Feature: Trading page
|
|||||||
When I click on accounts tab
|
When I click on accounts tab
|
||||||
Then accounts are displayed
|
Then accounts are displayed
|
||||||
And I can see account for tEURO
|
And I can see account for tEURO
|
||||||
|
|
||||||
Scenario: Orders: Placed orders displayed
|
Scenario: Orders: Placed orders displayed
|
||||||
Given I am on the trading page for an active market
|
Given I am on the trading page for an active market
|
||||||
And I connect to Vega Wallet
|
And I connect to Vega Wallet
|
||||||
When I click on orders tab
|
When I click on orders tab
|
||||||
Then placed orders are displayed
|
Then placed orders are displayed
|
||||||
|
|
||||||
|
Scenario: Orderbook displayed
|
||||||
|
Given I am on the trading page for an active market
|
||||||
|
When I click on order book tab
|
||||||
|
Then orderbook is displayed with expected orders
|
||||||
|
And orderbook can be reduced and expanded
|
||||||
|
|
||||||
|
@todo
|
||||||
|
Scenario: Orderbook paginated with over 100 orders
|
||||||
|
Given I am on the trading page for an active market
|
||||||
|
When I click on order book tab
|
||||||
|
And a large amount is orders are received
|
||||||
|
Then a certain amount of orders are displayed
|
||||||
|
|
||||||
|
@todo
|
||||||
|
Scenario: Orderbook uses non-static prices for market in auction
|
||||||
|
Given I am on the trading page for a market in auction
|
||||||
|
When I click on order book tab
|
||||||
|
Then order book is rendered using non-static offers
|
||||||
|
|
||||||
|
@todo
|
||||||
|
Scenario: Orderbook updated when large order is made
|
||||||
|
Given I am on the trading page for an active market
|
||||||
|
When I place a large order
|
||||||
|
Then I should see my order have an effect on the order book
|
||||||
|
|
||||||
|
@todo
|
||||||
|
Scenario: Able to place order by clicking on order from orderbook
|
||||||
|
Given I am on the trading page for an active market
|
||||||
|
When I place a large order
|
||||||
|
Then I should see my order have an effect on the order book
|
||||||
|
125
apps/trading-e2e/src/support/mocks/generate-order-book.ts
Normal file
125
apps/trading-e2e/src/support/mocks/generate-order-book.ts
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
import merge from 'lodash/merge';
|
||||||
|
import type { PartialDeep } from 'type-fest';
|
||||||
|
import type {
|
||||||
|
MarketDepth,
|
||||||
|
MarketDepth_market,
|
||||||
|
} from '@vegaprotocol/market-depth';
|
||||||
|
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
|
|
||||||
|
export const generateOrderBook = (
|
||||||
|
override?: PartialDeep<MarketDepth>
|
||||||
|
): MarketDepth => {
|
||||||
|
const marketDepth: MarketDepth_market = {
|
||||||
|
id: 'b2426f67b085ba8fb429f1b529d49372b2d096c6fb6f509f76c5863abb6d969e',
|
||||||
|
decimalPlaces: 5,
|
||||||
|
data: {
|
||||||
|
staticMidPrice: '826337',
|
||||||
|
marketTradingMode: MarketTradingMode.Continuous,
|
||||||
|
indicativeVolume: '0',
|
||||||
|
indicativePrice: '0',
|
||||||
|
bestStaticBidPrice: '826336',
|
||||||
|
bestStaticOfferPrice: '826338',
|
||||||
|
market: {
|
||||||
|
id: 'b2426f67b085ba8fb429f1b529d49372b2d096c6fb6f509f76c5863abb6d969e',
|
||||||
|
__typename: 'Market',
|
||||||
|
},
|
||||||
|
__typename: 'MarketData',
|
||||||
|
},
|
||||||
|
depth: {
|
||||||
|
lastTrade: {
|
||||||
|
price: '826338',
|
||||||
|
__typename: 'Trade',
|
||||||
|
},
|
||||||
|
sell: [
|
||||||
|
{
|
||||||
|
price: '826338',
|
||||||
|
volume: '303',
|
||||||
|
numberOfOrders: '8',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826339',
|
||||||
|
volume: '193',
|
||||||
|
numberOfOrders: '4',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826340',
|
||||||
|
volume: '316',
|
||||||
|
numberOfOrders: '7',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826341',
|
||||||
|
volume: '412',
|
||||||
|
numberOfOrders: '9',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826342',
|
||||||
|
volume: '264',
|
||||||
|
numberOfOrders: '6',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
buy: [
|
||||||
|
{
|
||||||
|
price: '826339',
|
||||||
|
volume: '200',
|
||||||
|
numberOfOrders: '5',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826336',
|
||||||
|
volume: '1475',
|
||||||
|
numberOfOrders: '28',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826335',
|
||||||
|
volume: '193',
|
||||||
|
numberOfOrders: '3',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826334',
|
||||||
|
volume: '425',
|
||||||
|
numberOfOrders: '8',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826333',
|
||||||
|
volume: '845',
|
||||||
|
numberOfOrders: '17',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826332',
|
||||||
|
volume: '248',
|
||||||
|
numberOfOrders: '4',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826331',
|
||||||
|
volume: '162',
|
||||||
|
numberOfOrders: '3',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
price: '826328',
|
||||||
|
volume: '20',
|
||||||
|
numberOfOrders: '2',
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
sequenceNumber: '36109974',
|
||||||
|
__typename: 'MarketDepth',
|
||||||
|
},
|
||||||
|
__typename: 'Market',
|
||||||
|
};
|
||||||
|
const defaultResult = {
|
||||||
|
market: marketDepth,
|
||||||
|
};
|
||||||
|
|
||||||
|
return merge(defaultResult, override);
|
||||||
|
};
|
@ -10,7 +10,6 @@ export default class TradingPage extends BasePage {
|
|||||||
collateralTab = 'Collateral';
|
collateralTab = 'Collateral';
|
||||||
tradesTab = 'Trades';
|
tradesTab = 'Trades';
|
||||||
completedTrades = 'Market-trades';
|
completedTrades = 'Market-trades';
|
||||||
orderBookTab = 'Prderbook';
|
|
||||||
|
|
||||||
clickOnOrdersTab() {
|
clickOnOrdersTab() {
|
||||||
cy.getByTestId(this.ordersTab).click();
|
cy.getByTestId(this.ordersTab).click();
|
||||||
@ -37,6 +36,6 @@ export default class TradingPage extends BasePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
clickOrderBookTab() {
|
clickOrderBookTab() {
|
||||||
cy.getByTestId(this.orderBookTab).click();
|
cy.getByTestId(this.orderbookTab).click();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,20 +8,23 @@ import { generateDealTicketQuery } from '../mocks/generate-deal-ticket-query';
|
|||||||
import { generateMarket } from '../mocks/generate-market';
|
import { generateMarket } from '../mocks/generate-market';
|
||||||
import { generateOrders } from '../mocks/generate-orders';
|
import { generateOrders } from '../mocks/generate-orders';
|
||||||
import { generatePositions } from '../mocks/generate-positions';
|
import { generatePositions } from '../mocks/generate-positions';
|
||||||
|
import { generateOrderBook } from '../mocks/generate-order-book';
|
||||||
import { generateAccounts } from '../mocks/generate-accounts';
|
import { generateAccounts } from '../mocks/generate-accounts';
|
||||||
import PositionsList from '../trading-windows/positions-list';
|
import PositionsList from '../trading-windows/positions-list';
|
||||||
import AccountsList from '../trading-windows/accounts-list';
|
import AccountsList from '../trading-windows/accounts-list';
|
||||||
import TradesList from '../trading-windows/trades-list';
|
import TradesList from '../trading-windows/trades-list';
|
||||||
import TradingPage from '../pages/trading-page';
|
import TradingPage from '../pages/trading-page';
|
||||||
import OrdersList from '../trading-windows/orders-list';
|
import OrdersList from '../trading-windows/orders-list';
|
||||||
|
import OrderBookList from '../trading-windows/orderbook-list';
|
||||||
import MarketPage from '../pages/markets-page';
|
import MarketPage from '../pages/markets-page';
|
||||||
|
|
||||||
const tradesList = new TradesList();
|
const tradesList = new TradesList();
|
||||||
const tradingPage = new TradingPage();
|
const tradingPage = new TradingPage();
|
||||||
const marketPage = new MarketPage();
|
|
||||||
const positionsList = new PositionsList();
|
const positionsList = new PositionsList();
|
||||||
const accountList = new AccountsList();
|
const accountList = new AccountsList();
|
||||||
const ordersList = new OrdersList();
|
const ordersList = new OrdersList();
|
||||||
|
const orderBookList = new OrderBookList();
|
||||||
|
const marketPage = new MarketPage();
|
||||||
|
|
||||||
const mockMarket = (state: MarketState) => {
|
const mockMarket = (state: MarketState) => {
|
||||||
cy.mockGQL('Market', (req) => {
|
cy.mockGQL('Market', (req) => {
|
||||||
@ -75,6 +78,12 @@ const mockMarket = (state: MarketState) => {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (hasOperationName(req, 'MarketDepth')) {
|
||||||
|
req.reply({
|
||||||
|
body: { data: generateOrderBook() },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (hasOperationName(req, 'Candles')) {
|
if (hasOperationName(req, 'Candles')) {
|
||||||
req.reply({
|
req.reply({
|
||||||
body: { data: generateCandles() },
|
body: { data: generateCandles() },
|
||||||
@ -159,3 +168,78 @@ When('I click on positions tab', () => {
|
|||||||
Then('positions are displayed', () => {
|
Then('positions are displayed', () => {
|
||||||
positionsList.verifyPositionsDisplayed();
|
positionsList.verifyPositionsDisplayed();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
When('I click on order book tab', () => {
|
||||||
|
tradingPage.clickOrderBookTab();
|
||||||
|
});
|
||||||
|
|
||||||
|
Then('orderbook is displayed with expected orders', () => {
|
||||||
|
orderBookList.verifyOrderBookRow('826342', '0', '8.26342', '264', '1488');
|
||||||
|
orderBookList.verifyOrderBookRow('826336', '1475', '8.26336', '0', '1675');
|
||||||
|
orderBookList.verifyDisplayedVolume(
|
||||||
|
'826342',
|
||||||
|
false,
|
||||||
|
'18%',
|
||||||
|
orderBookList.testingVolume.AskVolume
|
||||||
|
);
|
||||||
|
orderBookList.verifyDisplayedVolume(
|
||||||
|
'826331',
|
||||||
|
true,
|
||||||
|
'100%',
|
||||||
|
orderBookList.testingVolume.CumulativeVolume
|
||||||
|
);
|
||||||
|
// mid level price
|
||||||
|
orderBookList.verifyOrderBookRow('826337', '0', '8.26337', '0', '200');
|
||||||
|
orderBookList.verifyDisplayedVolume(
|
||||||
|
'826337',
|
||||||
|
true,
|
||||||
|
'6%',
|
||||||
|
orderBookList.testingVolume.CumulativeVolume
|
||||||
|
);
|
||||||
|
orderBookList.verifyTopMidPricePosition('129');
|
||||||
|
orderBookList.verifyBottomMidPricePosition('151');
|
||||||
|
|
||||||
|
// autofilled order
|
||||||
|
orderBookList.verifyOrderBookRow('826330', '0', '8.26330', '0', '3548');
|
||||||
|
orderBookList.verifyDisplayedVolume(
|
||||||
|
'826330',
|
||||||
|
true,
|
||||||
|
'0%',
|
||||||
|
orderBookList.testingVolume.BidVolume
|
||||||
|
);
|
||||||
|
orderBookList.verifyDisplayedVolume(
|
||||||
|
'826330',
|
||||||
|
true,
|
||||||
|
'100%',
|
||||||
|
orderBookList.testingVolume.CumulativeVolume
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
Then('orderbook can be reduced and expanded', () => {
|
||||||
|
orderBookList.changePrecision('10');
|
||||||
|
orderBookList.verifyOrderBookRow(
|
||||||
|
'82634',
|
||||||
|
'1868',
|
||||||
|
'8.2634',
|
||||||
|
'1488',
|
||||||
|
'1488/1868'
|
||||||
|
);
|
||||||
|
orderBookList.verifyCumulativeAskBarPercentage('42%');
|
||||||
|
orderBookList.verifyCumulativeBidBarPercentage('53%');
|
||||||
|
orderBookList.changePrecision('100');
|
||||||
|
orderBookList.verifyOrderBookRow('8263', '3568', '8.263', '1488', '');
|
||||||
|
orderBookList.verifyDisplayedVolume(
|
||||||
|
'8263',
|
||||||
|
true,
|
||||||
|
'100%',
|
||||||
|
orderBookList.testingVolume.BidVolume
|
||||||
|
);
|
||||||
|
orderBookList.verifyDisplayedVolume(
|
||||||
|
'8263',
|
||||||
|
false,
|
||||||
|
'42%',
|
||||||
|
orderBookList.testingVolume.AskVolume
|
||||||
|
);
|
||||||
|
orderBookList.changePrecision('1');
|
||||||
|
orderBookList.verifyOrderBookRow('826342', '0', '8.26342', '264', '1488');
|
||||||
|
});
|
||||||
|
129
apps/trading-e2e/src/support/trading-windows/orderbook-list.ts
Normal file
129
apps/trading-e2e/src/support/trading-windows/orderbook-list.ts
Normal file
@ -0,0 +1,129 @@
|
|||||||
|
export default class OrderBookList {
|
||||||
|
cumulativeVolBidBar = 'bid-bar';
|
||||||
|
cumulativeVolAskBar = 'ask-bar';
|
||||||
|
precisionChange = 'resolution';
|
||||||
|
bidColour = 'darkgreen';
|
||||||
|
askColour = 'maroon';
|
||||||
|
testingVolume = TestingVolumeType;
|
||||||
|
topMidPriceLine = 'best-static-offer-price';
|
||||||
|
bottomMidPriceLine = 'best-static-bid-price';
|
||||||
|
|
||||||
|
bidVolTestId(price: string) {
|
||||||
|
return `bid-vol-${price}`;
|
||||||
|
}
|
||||||
|
priceTestId(price: string) {
|
||||||
|
return `price-${price}`;
|
||||||
|
}
|
||||||
|
askVolTestId(price: string) {
|
||||||
|
return `ask-vol-${price}`;
|
||||||
|
}
|
||||||
|
cumulativeVolTestId(price: string) {
|
||||||
|
return `cumulative-vol-${price}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyOrderBookDisplayed(price: string) {
|
||||||
|
cy.getByTestId(this.bidVolTestId(price)).should('not.be.empty');
|
||||||
|
cy.getByTestId(this.priceTestId(price))
|
||||||
|
.invoke('text')
|
||||||
|
.then(($priceText) => {
|
||||||
|
$priceText = $priceText.replace('.', '');
|
||||||
|
expect($priceText).to.equal(price);
|
||||||
|
});
|
||||||
|
cy.getByTestId(this.askVolTestId(price)).should('not.be.empty');
|
||||||
|
cy.getByTestId(this.cumulativeVolTestId(price)).should('not.be.empty');
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyOrderBookRow(
|
||||||
|
price: string,
|
||||||
|
expectedBidVol: string,
|
||||||
|
expectedPrice: string,
|
||||||
|
expectedAskVol: string,
|
||||||
|
expectedCumulativeVol: string
|
||||||
|
) {
|
||||||
|
cy.getByTestId(this.bidVolTestId(price)).should(
|
||||||
|
'have.text',
|
||||||
|
expectedBidVol
|
||||||
|
);
|
||||||
|
cy.getByTestId(this.priceTestId(price)).should('have.text', expectedPrice);
|
||||||
|
cy.getByTestId(this.askVolTestId(price)).should(
|
||||||
|
'have.text',
|
||||||
|
expectedAskVol
|
||||||
|
);
|
||||||
|
cy.getByTestId(this.cumulativeVolTestId(price)).should(
|
||||||
|
'have.text',
|
||||||
|
expectedCumulativeVol
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Value should be 1, 10 or 100
|
||||||
|
changePrecision(precisionValue: string) {
|
||||||
|
cy.getByTestId(this.precisionChange).select(precisionValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyDisplayedVolume(
|
||||||
|
price: string,
|
||||||
|
isBuy: boolean,
|
||||||
|
expectedPercentage: string,
|
||||||
|
volumeType: TestingVolumeType
|
||||||
|
) {
|
||||||
|
let expectedColour = '';
|
||||||
|
let testId = '';
|
||||||
|
|
||||||
|
if (isBuy == true) {
|
||||||
|
expectedColour = this.bidColour;
|
||||||
|
} else {
|
||||||
|
expectedColour = this.askColour;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (volumeType) {
|
||||||
|
case TestingVolumeType.BidVolume:
|
||||||
|
testId = `[data-testid=${this.bidVolTestId(price)}]`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestingVolumeType.AskVolume:
|
||||||
|
testId = `[data-testid=${this.askVolTestId(price)}]`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TestingVolumeType.CumulativeVolume:
|
||||||
|
testId = `[data-testid=${this.cumulativeVolTestId(price)}]`;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
cy.get(`${testId} > div`)
|
||||||
|
.invoke('attr', 'style')
|
||||||
|
.should('contain', `width: ${expectedPercentage}`)
|
||||||
|
.should('contain', `background-color: ${expectedColour}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyCumulativeAskBarPercentage(expectedPercentage: string) {
|
||||||
|
cy.getByTestId(this.cumulativeVolAskBar)
|
||||||
|
.invoke('attr', 'style')
|
||||||
|
.should('contain', `width: ${expectedPercentage}`)
|
||||||
|
.should('contain', `background-color: ${this.askColour}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyCumulativeBidBarPercentage(expectedPercentage: string) {
|
||||||
|
cy.getByTestId(this.cumulativeVolBidBar)
|
||||||
|
.invoke('attr', 'style')
|
||||||
|
.should('contain', `width: ${expectedPercentage}`)
|
||||||
|
.should('contain', `background-color: ${this.bidColour}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyTopMidPricePosition(expectedPosition: string) {
|
||||||
|
cy.getByTestId(this.topMidPriceLine)
|
||||||
|
.invoke('attr', 'style')
|
||||||
|
.should('contain', `top: ${expectedPosition}px`);
|
||||||
|
}
|
||||||
|
|
||||||
|
verifyBottomMidPricePosition(expectedPosition: string) {
|
||||||
|
cy.getByTestId(this.bottomMidPriceLine)
|
||||||
|
.invoke('attr', 'style')
|
||||||
|
.should('contain', `top: ${expectedPosition}px`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum TestingVolumeType {
|
||||||
|
BidVolume = 'BidVolume',
|
||||||
|
AskVolume = 'AskVolume',
|
||||||
|
CumulativeVolume = 'CumulativeVolume',
|
||||||
|
}
|
@ -15,5 +15,5 @@
|
|||||||
"noImplicitReturns": true,
|
"noImplicitReturns": true,
|
||||||
"noFallthroughCasesInSwitch": true
|
"noFallthroughCasesInSwitch": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.js"]
|
"include": ["src/**/*.ts", "src/**/*.js", "./declaration.d.ts"]
|
||||||
}
|
}
|
||||||
|
28
libs/market-depth/.storybook/main.js
Normal file
28
libs/market-depth/.storybook/main.js
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
const rootMain = require('../../../.storybook/main');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
...rootMain,
|
||||||
|
|
||||||
|
core: { ...rootMain.core, builder: 'webpack5' },
|
||||||
|
|
||||||
|
stories: [
|
||||||
|
...rootMain.stories,
|
||||||
|
'../src/lib/**/*.stories.mdx',
|
||||||
|
'../src/lib/**/*.stories.@(js|jsx|ts|tsx)',
|
||||||
|
],
|
||||||
|
addons: [
|
||||||
|
...rootMain.addons,
|
||||||
|
'@nrwl/react/plugins/storybook',
|
||||||
|
'storybook-addon-themes',
|
||||||
|
],
|
||||||
|
webpackFinal: async (config, { configType }) => {
|
||||||
|
// apply any global webpack configs that might have been specified in .storybook/main.js
|
||||||
|
if (rootMain.webpackFinal) {
|
||||||
|
config = await rootMain.webpackFinal(config, { configType });
|
||||||
|
}
|
||||||
|
|
||||||
|
// add your own webpack tweaks if needed
|
||||||
|
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
};
|
1
libs/market-depth/.storybook/preview-head.html
Normal file
1
libs/market-depth/.storybook/preview-head.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<link rel="stylesheet" href="https://static.vega.xyz/fonts.css" />
|
12
libs/market-depth/.storybook/preview.js
Normal file
12
libs/market-depth/.storybook/preview.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import '../src/styles.scss';
|
||||||
|
export const parameters = {
|
||||||
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||||
|
backgrounds: { disable: true },
|
||||||
|
themes: {
|
||||||
|
default: 'dark',
|
||||||
|
list: [
|
||||||
|
{ name: 'dark', class: ['dark', 'bg-black'], color: '#000' },
|
||||||
|
{ name: 'light', class: '', color: '#FFF' },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
19
libs/market-depth/.storybook/tsconfig.json
Normal file
19
libs/market-depth/.storybook/tsconfig.json
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"outDir": ""
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"../../../node_modules/@nrwl/react/typings/styled-jsx.d.ts",
|
||||||
|
"../../../node_modules/@nrwl/react/typings/cssmodule.d.ts",
|
||||||
|
"../../../node_modules/@nrwl/react/typings/image.d.ts"
|
||||||
|
],
|
||||||
|
"exclude": [
|
||||||
|
"../**/*.spec.ts",
|
||||||
|
"../**/*.spec.js",
|
||||||
|
"../**/*.spec.tsx",
|
||||||
|
"../**/*.spec.jsx"
|
||||||
|
],
|
||||||
|
"include": ["../src/**/*", "*.js"]
|
||||||
|
}
|
@ -4,4 +4,4 @@ This library was generated with [Nx](https://nx.dev).
|
|||||||
|
|
||||||
## Running unit tests
|
## Running unit tests
|
||||||
|
|
||||||
Run `nx test orderbook` to execute the unit tests via [Jest](https://jestjs.io).
|
Run `nx test market-depth` to execute the unit tests via [Jest](https://jestjs.io).
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
displayName: 'orderbook',
|
displayName: 'market-depth',
|
||||||
preset: '../../jest.preset.js',
|
preset: '../../jest.preset.js',
|
||||||
globals: {
|
|
||||||
'ts-jest': {
|
|
||||||
tsconfig: '<rootDir>/tsconfig.spec.json',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
transform: {
|
transform: {
|
||||||
'^.+\\.[tj]sx?$': 'ts-jest',
|
'^.+\\.[tj]sx?$': 'babel-jest',
|
||||||
},
|
},
|
||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
|
||||||
coverageDirectory: '../../coverage/libs/market-depth',
|
coverageDirectory: '../../coverage/libs/market-depth',
|
||||||
|
10
libs/market-depth/postcss.config.js
Normal file
10
libs/market-depth/postcss.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
const { join } = require('path');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plugins: {
|
||||||
|
tailwindcss: {
|
||||||
|
config: join(__dirname, 'tailwind.config.js'),
|
||||||
|
},
|
||||||
|
autoprefixer: {},
|
||||||
|
},
|
||||||
|
};
|
@ -38,6 +38,37 @@
|
|||||||
"jestConfig": "libs/market-depth/jest.config.js",
|
"jestConfig": "libs/market-depth/jest.config.js",
|
||||||
"passWithNoTests": true
|
"passWithNoTests": true
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"storybook": {
|
||||||
|
"executor": "@nrwl/storybook:storybook",
|
||||||
|
"options": {
|
||||||
|
"uiFramework": "@storybook/react",
|
||||||
|
"port": 4400,
|
||||||
|
"config": {
|
||||||
|
"configFolder": "libs/market-depth/.storybook"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"quiet": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"build-storybook": {
|
||||||
|
"executor": "@nrwl/storybook:build",
|
||||||
|
"outputs": ["{options.outputPath}"],
|
||||||
|
"options": {
|
||||||
|
"uiFramework": "@storybook/react",
|
||||||
|
"outputPath": "dist/storybook/market-depth",
|
||||||
|
"config": {
|
||||||
|
"configFolder": "libs/market-depth/.storybook"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"ci": {
|
||||||
|
"quiet": true
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,2 +1,3 @@
|
|||||||
export * from './lib/depth-chart';
|
export * from './lib/depth-chart';
|
||||||
export * from './lib/orderbook-container';
|
export * from './lib/orderbook-container';
|
||||||
|
export * from './lib/__generated__/MarketDepth';
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
// @generated
|
// @generated
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { MarketTradingMode } from "@vegaprotocol/types";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL query operation: MarketDepth
|
// GraphQL query operation: MarketDepth
|
||||||
// ====================================================
|
// ====================================================
|
||||||
@ -18,9 +20,29 @@ export interface MarketDepth_market_data_market {
|
|||||||
export interface MarketDepth_market_data {
|
export interface MarketDepth_market_data {
|
||||||
__typename: "MarketData";
|
__typename: "MarketData";
|
||||||
/**
|
/**
|
||||||
* the arithmetic average of the best bid price and best offer price.
|
* the arithmetic average of the best static bid price and best static offer price
|
||||||
*/
|
*/
|
||||||
midPrice: string;
|
staticMidPrice: string;
|
||||||
|
/**
|
||||||
|
* what state the market is in (auction, continuous etc)
|
||||||
|
*/
|
||||||
|
marketTradingMode: MarketTradingMode;
|
||||||
|
/**
|
||||||
|
* indicative volume if the auction ended now, 0 if not in auction mode
|
||||||
|
*/
|
||||||
|
indicativeVolume: string;
|
||||||
|
/**
|
||||||
|
* indicative price if the auction ended now, 0 if not in auction mode
|
||||||
|
*/
|
||||||
|
indicativePrice: string;
|
||||||
|
/**
|
||||||
|
* the highest price level on an order book for buy orders not including pegged orders.
|
||||||
|
*/
|
||||||
|
bestStaticBidPrice: string;
|
||||||
|
/**
|
||||||
|
* the lowest price level on an order book for offer orders not including pegged orders.
|
||||||
|
*/
|
||||||
|
bestStaticOfferPrice: string;
|
||||||
/**
|
/**
|
||||||
* market id of the associated mark price
|
* market id of the associated mark price
|
||||||
*/
|
*/
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
// @generated
|
// @generated
|
||||||
// This file was automatically generated and should not be edited.
|
// This file was automatically generated and should not be edited.
|
||||||
|
|
||||||
|
import { MarketTradingMode } from "@vegaprotocol/types";
|
||||||
|
|
||||||
// ====================================================
|
// ====================================================
|
||||||
// GraphQL subscription operation: MarketDepthSubscription
|
// GraphQL subscription operation: MarketDepthSubscription
|
||||||
// ====================================================
|
// ====================================================
|
||||||
@ -18,9 +20,29 @@ export interface MarketDepthSubscription_marketDepthUpdate_market_data_market {
|
|||||||
export interface MarketDepthSubscription_marketDepthUpdate_market_data {
|
export interface MarketDepthSubscription_marketDepthUpdate_market_data {
|
||||||
__typename: "MarketData";
|
__typename: "MarketData";
|
||||||
/**
|
/**
|
||||||
* the arithmetic average of the best bid price and best offer price.
|
* the arithmetic average of the best static bid price and best static offer price
|
||||||
*/
|
*/
|
||||||
midPrice: string;
|
staticMidPrice: string;
|
||||||
|
/**
|
||||||
|
* what state the market is in (auction, continuous etc)
|
||||||
|
*/
|
||||||
|
marketTradingMode: MarketTradingMode;
|
||||||
|
/**
|
||||||
|
* indicative volume if the auction ended now, 0 if not in auction mode
|
||||||
|
*/
|
||||||
|
indicativeVolume: string;
|
||||||
|
/**
|
||||||
|
* indicative price if the auction ended now, 0 if not in auction mode
|
||||||
|
*/
|
||||||
|
indicativePrice: string;
|
||||||
|
/**
|
||||||
|
* the highest price level on an order book for buy orders not including pegged orders.
|
||||||
|
*/
|
||||||
|
bestStaticBidPrice: string;
|
||||||
|
/**
|
||||||
|
* the lowest price level on an order book for offer orders not including pegged orders.
|
||||||
|
*/
|
||||||
|
bestStaticOfferPrice: string;
|
||||||
/**
|
/**
|
||||||
* market id of the associated mark price
|
* market id of the associated mark price
|
||||||
*/
|
*/
|
||||||
|
@ -107,9 +107,9 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
|||||||
decimalPlacesRef.current
|
decimalPlacesRef.current
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
draft.midPrice = delta.market.data?.midPrice
|
draft.midPrice = delta.market.data?.staticMidPrice
|
||||||
? formatMidPrice(
|
? formatMidPrice(
|
||||||
delta.market.data?.midPrice,
|
delta.market.data?.staticMidPrice,
|
||||||
decimalPlacesRef.current
|
decimalPlacesRef.current
|
||||||
)
|
)
|
||||||
: undefined;
|
: undefined;
|
||||||
@ -133,8 +133,8 @@ export const DepthChartContainer = ({ marketId }: DepthChartManagerProps) => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dataRef.current = {
|
dataRef.current = {
|
||||||
midPrice: data.data?.midPrice
|
midPrice: data.data?.staticMidPrice
|
||||||
? formatMidPrice(data.data?.midPrice, data.decimalPlaces)
|
? formatMidPrice(data.data?.staticMidPrice, data.decimalPlaces)
|
||||||
: undefined,
|
: undefined,
|
||||||
data: {
|
data: {
|
||||||
buy:
|
buy:
|
||||||
|
@ -17,7 +17,12 @@ const MARKET_DEPTH_QUERY = gql`
|
|||||||
id
|
id
|
||||||
decimalPlaces
|
decimalPlaces
|
||||||
data {
|
data {
|
||||||
midPrice
|
staticMidPrice
|
||||||
|
marketTradingMode
|
||||||
|
indicativeVolume
|
||||||
|
indicativePrice
|
||||||
|
bestStaticBidPrice
|
||||||
|
bestStaticOfferPrice
|
||||||
market {
|
market {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
@ -48,7 +53,12 @@ export const MARKET_DEPTH_SUBSCRIPTION_QUERY = gql`
|
|||||||
market {
|
market {
|
||||||
id
|
id
|
||||||
data {
|
data {
|
||||||
midPrice
|
staticMidPrice
|
||||||
|
marketTradingMode
|
||||||
|
indicativeVolume
|
||||||
|
indicativePrice
|
||||||
|
bestStaticBidPrice
|
||||||
|
bestStaticOfferPrice
|
||||||
market {
|
market {
|
||||||
id
|
id
|
||||||
}
|
}
|
||||||
@ -74,7 +84,7 @@ const sequenceNumbers: Record<string, number> = {};
|
|||||||
const update: Update<
|
const update: Update<
|
||||||
MarketDepth_market,
|
MarketDepth_market,
|
||||||
MarketDepthSubscription_marketDepthUpdate
|
MarketDepthSubscription_marketDepthUpdate
|
||||||
> = (draft, delta, restart) => {
|
> = (draft, delta, reload) => {
|
||||||
if (delta.market.id !== draft.id) {
|
if (delta.market.id !== draft.id) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -84,10 +94,11 @@ const update: Update<
|
|||||||
}
|
}
|
||||||
if (sequenceNumber - 1 !== sequenceNumbers[delta.market.id]) {
|
if (sequenceNumber - 1 !== sequenceNumbers[delta.market.id]) {
|
||||||
sequenceNumbers[delta.market.id] = 0;
|
sequenceNumbers[delta.market.id] = 0;
|
||||||
restart(true);
|
reload();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sequenceNumbers[delta.market.id] = sequenceNumber;
|
sequenceNumbers[delta.market.id] = sequenceNumber;
|
||||||
|
Object.assign(draft.data, delta.market.data);
|
||||||
if (delta.buy) {
|
if (delta.buy) {
|
||||||
draft.depth.buy = updateLevels(draft.depth.buy ?? [], delta.buy);
|
draft.depth.buy = updateLevels(draft.depth.buy ?? [], delta.buy);
|
||||||
}
|
}
|
||||||
|
@ -1,57 +0,0 @@
|
|||||||
import type {
|
|
||||||
MarketDepth_market,
|
|
||||||
MarketDepth_market_depth_sell,
|
|
||||||
MarketDepth_market_depth_buy,
|
|
||||||
} from './__generated__/MarketDepth';
|
|
||||||
|
|
||||||
const depthRow = (
|
|
||||||
price: number
|
|
||||||
): MarketDepth_market_depth_sell | MarketDepth_market_depth_buy => {
|
|
||||||
return {
|
|
||||||
__typename: 'PriceLevel',
|
|
||||||
price: price.toString(),
|
|
||||||
volume: Math.round(Math.random() * 100).toString(),
|
|
||||||
numberOfOrders: Math.round(Math.random() * 20).toString(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const sell = (
|
|
||||||
price: number,
|
|
||||||
numberOfRecords: number
|
|
||||||
): MarketDepth_market_depth_sell[] => {
|
|
||||||
const distance = Math.random() * price * 0.1;
|
|
||||||
return new Array(numberOfRecords)
|
|
||||||
.fill(null)
|
|
||||||
.map(() => depthRow(price + Math.round(Math.random() * distance)));
|
|
||||||
};
|
|
||||||
|
|
||||||
const buy = (
|
|
||||||
price: number,
|
|
||||||
numberOfRecords: number
|
|
||||||
): MarketDepth_market_depth_buy[] => {
|
|
||||||
const distance = Math.random() * price * 0.1;
|
|
||||||
return new Array(numberOfRecords)
|
|
||||||
.fill(null)
|
|
||||||
.map(() => depthRow(price - Math.round(Math.random() * distance)));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getMockedData = (id?: string): MarketDepth_market => ({
|
|
||||||
__typename: 'Market',
|
|
||||||
id: id || '',
|
|
||||||
decimalPlaces: 2,
|
|
||||||
// "positionDecimalPlaces": 0,
|
|
||||||
data: {
|
|
||||||
__typename: 'MarketData',
|
|
||||||
midPrice: '0',
|
|
||||||
},
|
|
||||||
depth: {
|
|
||||||
__typename: 'MarketDepth',
|
|
||||||
lastTrade: {
|
|
||||||
__typename: 'Trade',
|
|
||||||
price: '12350',
|
|
||||||
},
|
|
||||||
sell: sell(12350 * 0.99, 100),
|
|
||||||
buy: buy(12350, 100),
|
|
||||||
sequenceNumber: '118118448',
|
|
||||||
},
|
|
||||||
});
|
|
@ -1,31 +1,5 @@
|
|||||||
import { useState } from 'react';
|
|
||||||
import { OrderbookManager } from './orderbook-manager';
|
import { OrderbookManager } from './orderbook-manager';
|
||||||
import { Button } from '@vegaprotocol/ui-toolkit';
|
|
||||||
|
|
||||||
export const OrderbookContainer = ({ marketId }: { marketId: string }) => {
|
export const OrderbookContainer = ({ marketId }: { marketId: string }) => (
|
||||||
const [resolution, setResolution] = useState(1);
|
<OrderbookManager marketId={marketId} />
|
||||||
|
);
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="flex gap-8">
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => setResolution(resolution * 10)}
|
|
||||||
appendIconName="minus"
|
|
||||||
className="flex-1"
|
|
||||||
>
|
|
||||||
Zoom out
|
|
||||||
</Button>
|
|
||||||
<Button
|
|
||||||
variant="secondary"
|
|
||||||
onClick={() => setResolution(Math.max(resolution / 10, 1))}
|
|
||||||
appendIconName="plus"
|
|
||||||
className="flex-1"
|
|
||||||
>
|
|
||||||
Zoom in
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<OrderbookManager resolution={resolution} marketId={marketId} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import {
|
import {
|
||||||
compactData,
|
compactRows,
|
||||||
updateLevels,
|
updateLevels,
|
||||||
updateCompactedData,
|
updateCompactedRows,
|
||||||
} from './orderbook-data';
|
} from './orderbook-data';
|
||||||
import type { OrderbookData } from './orderbook-data';
|
import type { OrderbookRowData } from './orderbook-data';
|
||||||
import type { MarketDepth_market_depth_sell } from './__generated__/MarketDepth';
|
import type { MarketDepth_market_depth_sell } from './__generated__/MarketDepth';
|
||||||
import type {
|
import type {
|
||||||
MarketDepthSubscription_marketDepthUpdate_sell,
|
MarketDepthSubscription_marketDepthUpdate_sell,
|
||||||
MarketDepthSubscription_marketDepthUpdate_buy,
|
MarketDepthSubscription_marketDepthUpdate_buy,
|
||||||
} from './__generated__/MarketDepthSubscription';
|
} from './__generated__/MarketDepthSubscription';
|
||||||
|
|
||||||
describe('compactData', () => {
|
describe('compactRows', () => {
|
||||||
const numberOfRows = 100;
|
const numberOfRows = 100;
|
||||||
const middle = 1000;
|
const middle = 1000;
|
||||||
const sell: MarketDepth_market_depth_sell[] = new Array(numberOfRows)
|
const sell: MarketDepth_market_depth_sell[] = new Array(numberOfRows)
|
||||||
@ -30,26 +30,26 @@ describe('compactData', () => {
|
|||||||
numberOfOrders: (numberOfRows - i).toString(),
|
numberOfOrders: (numberOfRows - i).toString(),
|
||||||
}));
|
}));
|
||||||
it('groups data by price and resolution', () => {
|
it('groups data by price and resolution', () => {
|
||||||
expect(compactData(sell, buy, 1).length).toEqual(200);
|
expect(compactRows(sell, buy, 1).length).toEqual(200);
|
||||||
expect(compactData(sell, buy, 5).length).toEqual(41);
|
expect(compactRows(sell, buy, 5).length).toEqual(41);
|
||||||
expect(compactData(sell, buy, 10).length).toEqual(21);
|
expect(compactRows(sell, buy, 10).length).toEqual(21);
|
||||||
});
|
});
|
||||||
it('counts cumulative vol', () => {
|
it('counts cumulative vol', () => {
|
||||||
const orderbookData = compactData(sell, buy, 10);
|
const orderbookRows = compactRows(sell, buy, 10);
|
||||||
expect(orderbookData[0].cumulativeVol.ask).toEqual(4950);
|
expect(orderbookRows[0].cumulativeVol.ask).toEqual(4950);
|
||||||
expect(orderbookData[0].cumulativeVol.bid).toEqual(0);
|
expect(orderbookRows[0].cumulativeVol.bid).toEqual(0);
|
||||||
expect(orderbookData[10].cumulativeVol.ask).toEqual(390);
|
expect(orderbookRows[10].cumulativeVol.ask).toEqual(390);
|
||||||
expect(orderbookData[10].cumulativeVol.bid).toEqual(579);
|
expect(orderbookRows[10].cumulativeVol.bid).toEqual(579);
|
||||||
expect(orderbookData[orderbookData.length - 1].cumulativeVol.bid).toEqual(
|
expect(orderbookRows[orderbookRows.length - 1].cumulativeVol.bid).toEqual(
|
||||||
4950
|
4950
|
||||||
);
|
);
|
||||||
expect(orderbookData[orderbookData.length - 1].cumulativeVol.ask).toEqual(
|
expect(orderbookRows[orderbookRows.length - 1].cumulativeVol.ask).toEqual(
|
||||||
0
|
0
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
it('stores volume by level', () => {
|
it('stores volume by level', () => {
|
||||||
const orderbookData = compactData(sell, buy, 10);
|
const orderbookRows = compactRows(sell, buy, 10);
|
||||||
expect(orderbookData[0].askByLevel).toEqual({
|
expect(orderbookRows[0].askByLevel).toEqual({
|
||||||
'1095': 5,
|
'1095': 5,
|
||||||
'1096': 4,
|
'1096': 4,
|
||||||
'1097': 3,
|
'1097': 3,
|
||||||
@ -57,7 +57,7 @@ describe('compactData', () => {
|
|||||||
'1099': 1,
|
'1099': 1,
|
||||||
'1100': 0,
|
'1100': 0,
|
||||||
});
|
});
|
||||||
expect(orderbookData[orderbookData.length - 1].bidByLevel).toEqual({
|
expect(orderbookRows[orderbookRows.length - 1].bidByLevel).toEqual({
|
||||||
'901': 0,
|
'901': 0,
|
||||||
'902': 1,
|
'902': 1,
|
||||||
'903': 2,
|
'903': 2,
|
||||||
@ -66,17 +66,17 @@ describe('compactData', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('updates relative data', () => {
|
it('updates relative data', () => {
|
||||||
const orderbookData = compactData(sell, buy, 10);
|
const orderbookRows = compactRows(sell, buy, 10);
|
||||||
expect(orderbookData[0].cumulativeVol.relativeAsk).toEqual(100);
|
expect(orderbookRows[0].cumulativeVol.relativeAsk).toEqual(100);
|
||||||
expect(orderbookData[0].cumulativeVol.relativeBid).toEqual(0);
|
expect(orderbookRows[0].cumulativeVol.relativeBid).toEqual(0);
|
||||||
expect(orderbookData[0].relativeAskVol).toEqual(2);
|
expect(orderbookRows[0].relativeAsk).toEqual(2);
|
||||||
expect(orderbookData[0].relativeBidVol).toEqual(0);
|
expect(orderbookRows[0].relativeBid).toEqual(0);
|
||||||
expect(orderbookData[10].cumulativeVol.relativeAsk).toEqual(8);
|
expect(orderbookRows[10].cumulativeVol.relativeAsk).toEqual(8);
|
||||||
expect(orderbookData[10].cumulativeVol.relativeBid).toEqual(12);
|
expect(orderbookRows[10].cumulativeVol.relativeBid).toEqual(12);
|
||||||
expect(orderbookData[10].relativeAskVol).toEqual(44);
|
expect(orderbookRows[10].relativeAsk).toEqual(44);
|
||||||
expect(orderbookData[10].relativeBidVol).toEqual(66);
|
expect(orderbookRows[10].relativeBid).toEqual(64);
|
||||||
expect(orderbookData[orderbookData.length - 1].relativeAskVol).toEqual(0);
|
expect(orderbookRows[orderbookRows.length - 1].relativeAsk).toEqual(0);
|
||||||
expect(orderbookData[orderbookData.length - 1].relativeBidVol).toEqual(1);
|
expect(orderbookRows[orderbookRows.length - 1].relativeBid).toEqual(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -137,8 +137,8 @@ describe('updateLevels', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('updateCompactedData', () => {
|
describe('updateCompactedRows', () => {
|
||||||
const orderbookData: OrderbookData[] = [
|
const orderbookRows: OrderbookRowData[] = [
|
||||||
{
|
{
|
||||||
price: '120',
|
price: '120',
|
||||||
cumulativeVol: {
|
cumulativeVol: {
|
||||||
@ -153,8 +153,8 @@ describe('updateCompactedData', () => {
|
|||||||
bidByLevel: {},
|
bidByLevel: {},
|
||||||
ask: 10,
|
ask: 10,
|
||||||
bid: 0,
|
bid: 0,
|
||||||
relativeAskVol: 25,
|
relativeAsk: 25,
|
||||||
relativeBidVol: 0,
|
relativeBid: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
price: '100',
|
price: '100',
|
||||||
@ -174,8 +174,8 @@ describe('updateCompactedData', () => {
|
|||||||
},
|
},
|
||||||
ask: 40,
|
ask: 40,
|
||||||
bid: 40,
|
bid: 40,
|
||||||
relativeAskVol: 100,
|
relativeAsk: 100,
|
||||||
relativeBidVol: 100,
|
relativeBid: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
price: '80',
|
price: '80',
|
||||||
@ -191,8 +191,8 @@ describe('updateCompactedData', () => {
|
|||||||
},
|
},
|
||||||
ask: 0,
|
ask: 0,
|
||||||
bid: 10,
|
bid: 10,
|
||||||
relativeAskVol: 0,
|
relativeAsk: 0,
|
||||||
relativeBidVol: 25,
|
relativeBid: 25,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const resolution = 10;
|
const resolution = 10;
|
||||||
@ -210,18 +210,18 @@ describe('updateCompactedData', () => {
|
|||||||
volume: '10',
|
volume: '10',
|
||||||
numberOfOrders: '10',
|
numberOfOrders: '10',
|
||||||
};
|
};
|
||||||
const updatedData = updateCompactedData(
|
const updatedRows = updateCompactedRows(
|
||||||
orderbookData,
|
orderbookRows,
|
||||||
[sell],
|
[sell],
|
||||||
[buy],
|
[buy],
|
||||||
resolution
|
resolution
|
||||||
);
|
);
|
||||||
expect(updatedData[0].ask).toEqual(20);
|
expect(updatedRows[0].ask).toEqual(20);
|
||||||
expect(updatedData[0].askByLevel?.[120]).toEqual(10);
|
expect(updatedRows[0].askByLevel?.[120]).toEqual(10);
|
||||||
expect(updatedData[0].cumulativeVol.ask).toEqual(60);
|
expect(updatedRows[0].cumulativeVol.ask).toEqual(60);
|
||||||
expect(updatedData[2].bid).toEqual(20);
|
expect(updatedRows[2].bid).toEqual(20);
|
||||||
expect(updatedData[2].bidByLevel?.[80]).toEqual(10);
|
expect(updatedRows[2].bidByLevel?.[80]).toEqual(10);
|
||||||
expect(updatedData[2].cumulativeVol.bid).toEqual(60);
|
expect(updatedRows[2].cumulativeVol.bid).toEqual(60);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('remove row', () => {
|
it('remove row', () => {
|
||||||
@ -237,13 +237,13 @@ describe('updateCompactedData', () => {
|
|||||||
volume: '0',
|
volume: '0',
|
||||||
numberOfOrders: '0',
|
numberOfOrders: '0',
|
||||||
};
|
};
|
||||||
const updatedData = updateCompactedData(
|
const updatedRows = updateCompactedRows(
|
||||||
orderbookData,
|
orderbookRows,
|
||||||
[sell],
|
[sell],
|
||||||
[buy],
|
[buy],
|
||||||
resolution
|
resolution
|
||||||
);
|
);
|
||||||
expect(updatedData.length).toEqual(1);
|
expect(updatedRows.length).toEqual(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add new row at the end', () => {
|
it('add new row at the end', () => {
|
||||||
@ -259,17 +259,17 @@ describe('updateCompactedData', () => {
|
|||||||
volume: '5',
|
volume: '5',
|
||||||
numberOfOrders: '5',
|
numberOfOrders: '5',
|
||||||
};
|
};
|
||||||
const updatedData = updateCompactedData(
|
const updatedRows = updateCompactedRows(
|
||||||
orderbookData,
|
orderbookRows,
|
||||||
[sell],
|
[sell],
|
||||||
[buy],
|
[buy],
|
||||||
resolution
|
resolution
|
||||||
);
|
);
|
||||||
expect(updatedData.length).toEqual(5);
|
expect(updatedRows.length).toEqual(5);
|
||||||
expect(updatedData[0].price).toEqual('130');
|
expect(updatedRows[0].price).toEqual('130');
|
||||||
expect(updatedData[0].cumulativeVol.ask).toEqual(55);
|
expect(updatedRows[0].cumulativeVol.ask).toEqual(55);
|
||||||
expect(updatedData[4].price).toEqual('60');
|
expect(updatedRows[4].price).toEqual('60');
|
||||||
expect(updatedData[4].cumulativeVol.bid).toEqual(55);
|
expect(updatedRows[4].cumulativeVol.bid).toEqual(55);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('add new row in the middle', () => {
|
it('add new row in the middle', () => {
|
||||||
@ -285,18 +285,18 @@ describe('updateCompactedData', () => {
|
|||||||
volume: '5',
|
volume: '5',
|
||||||
numberOfOrders: '5',
|
numberOfOrders: '5',
|
||||||
};
|
};
|
||||||
const updatedData = updateCompactedData(
|
const updatedRows = updateCompactedRows(
|
||||||
orderbookData,
|
orderbookRows,
|
||||||
[sell],
|
[sell],
|
||||||
[buy],
|
[buy],
|
||||||
resolution
|
resolution
|
||||||
);
|
);
|
||||||
expect(updatedData.length).toEqual(5);
|
expect(updatedRows.length).toEqual(5);
|
||||||
expect(updatedData[1].price).toEqual('110');
|
expect(updatedRows[1].price).toEqual('110');
|
||||||
expect(updatedData[1].cumulativeVol.ask).toEqual(45);
|
expect(updatedRows[1].cumulativeVol.ask).toEqual(45);
|
||||||
expect(updatedData[0].cumulativeVol.ask).toEqual(55);
|
expect(updatedRows[0].cumulativeVol.ask).toEqual(55);
|
||||||
expect(updatedData[3].price).toEqual('90');
|
expect(updatedRows[3].price).toEqual('90');
|
||||||
expect(updatedData[3].cumulativeVol.bid).toEqual(45);
|
expect(updatedRows[3].cumulativeVol.bid).toEqual(45);
|
||||||
expect(updatedData[4].cumulativeVol.bid).toEqual(55);
|
expect(updatedRows[4].cumulativeVol.bid).toEqual(55);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import produce from 'immer';
|
import produce from 'immer';
|
||||||
import groupBy from 'lodash/groupBy';
|
import groupBy from 'lodash/groupBy';
|
||||||
|
import { VolumeType } from '@vegaprotocol/react-helpers';
|
||||||
|
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
import type {
|
import type {
|
||||||
MarketDepth_market_depth_sell,
|
MarketDepth_market_depth_sell,
|
||||||
MarketDepth_market_depth_buy,
|
MarketDepth_market_depth_buy,
|
||||||
|
MarketDepth_market_data,
|
||||||
} from './__generated__/MarketDepth';
|
} from './__generated__/MarketDepth';
|
||||||
import type {
|
import type {
|
||||||
MarketDepthSubscription_marketDepthUpdate_sell,
|
MarketDepthSubscription_marketDepthUpdate_sell,
|
||||||
MarketDepthSubscription_marketDepthUpdate_buy,
|
MarketDepthSubscription_marketDepthUpdate_buy,
|
||||||
|
MarketDepthSubscription_marketDepthUpdate_market_data,
|
||||||
} from './__generated__/MarketDepthSubscription';
|
} from './__generated__/MarketDepthSubscription';
|
||||||
|
|
||||||
export interface CumulativeVol {
|
export interface CumulativeVol {
|
||||||
@ -16,28 +20,32 @@ export interface CumulativeVol {
|
|||||||
relativeAsk?: number;
|
relativeAsk?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface OrderbookData {
|
export interface OrderbookRowData {
|
||||||
price: string;
|
price: string;
|
||||||
bid: number;
|
bid: number;
|
||||||
bidByLevel: Record<string, number>;
|
bidByLevel: Record<string, number>;
|
||||||
relativeBidVol?: number;
|
relativeBid?: number;
|
||||||
ask: number;
|
ask: number;
|
||||||
askByLevel: Record<string, number>;
|
askByLevel: Record<string, number>;
|
||||||
relativeAskVol?: number;
|
relativeAsk?: number;
|
||||||
cumulativeVol: CumulativeVol;
|
cumulativeVol: CumulativeVol;
|
||||||
}
|
}
|
||||||
|
|
||||||
const getGroupPrice = (price: string, resolution: number) => {
|
export type OrderbookData = Partial<
|
||||||
|
Omit<MarketDepth_market_data, '__typename' | 'market'>
|
||||||
|
> & { rows: OrderbookRowData[] | null };
|
||||||
|
|
||||||
|
export const getPriceLevel = (price: string | bigint, resolution: number) => {
|
||||||
const p = BigInt(price);
|
const p = BigInt(price);
|
||||||
const r = BigInt(resolution);
|
const r = BigInt(resolution);
|
||||||
let groupPrice = (p / r) * r;
|
let priceLevel = (p / r) * r;
|
||||||
if (p - groupPrice >= resolution / 2) {
|
if (p - priceLevel >= resolution / 2) {
|
||||||
groupPrice += BigInt(resolution);
|
priceLevel += BigInt(resolution);
|
||||||
}
|
}
|
||||||
return groupPrice.toString();
|
return priceLevel.toString();
|
||||||
};
|
};
|
||||||
|
|
||||||
const getMaxVolumes = (orderbookData: OrderbookData[]) => ({
|
const getMaxVolumes = (orderbookData: OrderbookRowData[]) => ({
|
||||||
bid: Math.max(...orderbookData.map((data) => data.bid)),
|
bid: Math.max(...orderbookData.map((data) => data.bid)),
|
||||||
ask: Math.max(...orderbookData.map((data) => data.ask)),
|
ask: Math.max(...orderbookData.map((data) => data.ask)),
|
||||||
cumulativeVol: Math.max(
|
cumulativeVol: Math.max(
|
||||||
@ -50,13 +58,14 @@ const getMaxVolumes = (orderbookData: OrderbookData[]) => ({
|
|||||||
const toPercentValue = (value?: number) => Math.ceil((value ?? 0) * 100);
|
const toPercentValue = (value?: number) => Math.ceil((value ?? 0) * 100);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary Updates relativeAskVol, relativeBidVol, cumulativeVol.relativeAsk, cumulativeVol.relativeBid
|
* @summary Updates relativeAsk, relativeBid, cumulativeVol.relativeAsk, cumulativeVol.relativeBid
|
||||||
*/
|
*/
|
||||||
const updateRelativeData = (data: OrderbookData[]) => {
|
const updateRelativeData = (data: OrderbookRowData[]) => {
|
||||||
const { bid, ask, cumulativeVol } = getMaxVolumes(data);
|
const { bid, ask, cumulativeVol } = getMaxVolumes(data);
|
||||||
|
const maxBidAsk = Math.max(bid, ask);
|
||||||
data.forEach((data) => {
|
data.forEach((data) => {
|
||||||
data.relativeAskVol = toPercentValue(data.ask / ask);
|
data.relativeAsk = toPercentValue(data.ask / maxBidAsk);
|
||||||
data.relativeBidVol = toPercentValue(data.bid / bid);
|
data.relativeBid = toPercentValue(data.bid / maxBidAsk);
|
||||||
data.cumulativeVol.relativeAsk = toPercentValue(
|
data.cumulativeVol.relativeAsk = toPercentValue(
|
||||||
data.cumulativeVol.ask / cumulativeVol
|
data.cumulativeVol.ask / cumulativeVol
|
||||||
);
|
);
|
||||||
@ -66,37 +75,37 @@ const updateRelativeData = (data: OrderbookData[]) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const createData = (
|
export const createRow = (
|
||||||
price: string,
|
price: string,
|
||||||
volume = 0,
|
volume = 0,
|
||||||
dataType?: 'sell' | 'buy'
|
dataType?: VolumeType
|
||||||
): OrderbookData => ({
|
): OrderbookRowData => ({
|
||||||
price,
|
price,
|
||||||
ask: dataType === 'sell' ? volume : 0,
|
ask: dataType === VolumeType.ask ? volume : 0,
|
||||||
bid: dataType === 'buy' ? volume : 0,
|
bid: dataType === VolumeType.bid ? volume : 0,
|
||||||
cumulativeVol: {
|
cumulativeVol: {
|
||||||
ask: dataType === 'sell' ? volume : 0,
|
ask: dataType === VolumeType.ask ? volume : 0,
|
||||||
bid: dataType === 'buy' ? volume : 0,
|
bid: dataType === VolumeType.bid ? volume : 0,
|
||||||
},
|
},
|
||||||
askByLevel: dataType === 'sell' ? { [price]: volume } : {},
|
askByLevel: dataType === VolumeType.ask ? { [price]: volume } : {},
|
||||||
bidByLevel: dataType === 'buy' ? { [price]: volume } : {},
|
bidByLevel: dataType === VolumeType.bid ? { [price]: volume } : {},
|
||||||
});
|
});
|
||||||
|
|
||||||
const mapRawData =
|
const mapRawData =
|
||||||
(dataType: 'sell' | 'buy') =>
|
(dataType: VolumeType.ask | VolumeType.bid) =>
|
||||||
(
|
(
|
||||||
data:
|
data:
|
||||||
| MarketDepth_market_depth_sell
|
| MarketDepth_market_depth_sell
|
||||||
| MarketDepthSubscription_marketDepthUpdate_sell
|
| MarketDepthSubscription_marketDepthUpdate_sell
|
||||||
| MarketDepth_market_depth_buy
|
| MarketDepth_market_depth_buy
|
||||||
| MarketDepthSubscription_marketDepthUpdate_buy
|
| MarketDepthSubscription_marketDepthUpdate_buy
|
||||||
): OrderbookData =>
|
): OrderbookRowData =>
|
||||||
createData(data.price, Number(data.volume), dataType);
|
createRow(data.price, Number(data.volume), dataType);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @summary merges sell amd buy data, orders by price desc, group by price level, counts cumulative and relative values
|
* @summary merges sell amd buy data, orders by price desc, group by price level, counts cumulative and relative values
|
||||||
*/
|
*/
|
||||||
export const compactData = (
|
export const compactRows = (
|
||||||
sell:
|
sell:
|
||||||
| (
|
| (
|
||||||
| MarketDepth_market_depth_sell
|
| MarketDepth_market_depth_sell
|
||||||
@ -112,25 +121,25 @@ export const compactData = (
|
|||||||
resolution: number
|
resolution: number
|
||||||
) => {
|
) => {
|
||||||
// map raw sell data to OrderbookData
|
// map raw sell data to OrderbookData
|
||||||
const askOrderbookData = [...(sell ?? [])].map<OrderbookData>(
|
const askOrderbookData = [...(sell ?? [])].map<OrderbookRowData>(
|
||||||
mapRawData('sell')
|
mapRawData(VolumeType.ask)
|
||||||
);
|
);
|
||||||
// map raw buy data to OrderbookData
|
// map raw buy data to OrderbookData
|
||||||
const bidOrderbookData = [...(buy ?? [])].map<OrderbookData>(
|
const bidOrderbookData = [...(buy ?? [])].map<OrderbookRowData>(
|
||||||
mapRawData('buy')
|
mapRawData(VolumeType.bid)
|
||||||
);
|
);
|
||||||
|
|
||||||
// group by price level
|
// group by price level
|
||||||
const groupedByLevel = groupBy<OrderbookData>(
|
const groupedByLevel = groupBy<OrderbookRowData>(
|
||||||
[...askOrderbookData, ...bidOrderbookData],
|
[...askOrderbookData, ...bidOrderbookData],
|
||||||
(row) => getGroupPrice(row.price, resolution)
|
(row) => getPriceLevel(row.price, resolution)
|
||||||
);
|
);
|
||||||
|
|
||||||
// create single OrderbookData from grouped OrderbookData[], sum volumes and atore volume by level
|
// create single OrderbookData from grouped OrderbookData[], sum volumes and atore volume by level
|
||||||
const orderbookData = Object.keys(groupedByLevel).reduce<OrderbookData[]>(
|
const orderbookData = Object.keys(groupedByLevel).reduce<OrderbookRowData[]>(
|
||||||
(rows, price) =>
|
(rows, price) =>
|
||||||
rows.concat(
|
rows.concat(
|
||||||
groupedByLevel[price].reduce<OrderbookData>(
|
groupedByLevel[price].reduce<OrderbookRowData>(
|
||||||
(a, c) => ({
|
(a, c) => ({
|
||||||
...a,
|
...a,
|
||||||
ask: a.ask + c.ask,
|
ask: a.ask + c.ask,
|
||||||
@ -138,7 +147,7 @@ export const compactData = (
|
|||||||
bid: (a.bid ?? 0) + (c.bid ?? 0),
|
bid: (a.bid ?? 0) + (c.bid ?? 0),
|
||||||
bidByLevel: Object.assign(a.bidByLevel, c.bidByLevel),
|
bidByLevel: Object.assign(a.bidByLevel, c.bidByLevel),
|
||||||
}),
|
}),
|
||||||
createData(price)
|
createRow(price)
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
[]
|
[]
|
||||||
@ -175,9 +184,9 @@ export const compactData = (
|
|||||||
* @param modifiedIndex
|
* @param modifiedIndex
|
||||||
* @returns max (sell) or min (buy) modified index in draft data, mutates draft
|
* @returns max (sell) or min (buy) modified index in draft data, mutates draft
|
||||||
*/
|
*/
|
||||||
const partiallyUpdateCompactedData = (
|
const partiallyUpdateCompactedRows = (
|
||||||
dataType: 'sell' | 'buy',
|
dataType: VolumeType,
|
||||||
draft: OrderbookData[],
|
draft: OrderbookRowData[],
|
||||||
delta:
|
delta:
|
||||||
| MarketDepthSubscription_marketDepthUpdate_sell
|
| MarketDepthSubscription_marketDepthUpdate_sell
|
||||||
| MarketDepthSubscription_marketDepthUpdate_buy,
|
| MarketDepthSubscription_marketDepthUpdate_buy,
|
||||||
@ -186,26 +195,27 @@ const partiallyUpdateCompactedData = (
|
|||||||
) => {
|
) => {
|
||||||
const { price } = delta;
|
const { price } = delta;
|
||||||
const volume = Number(delta.volume);
|
const volume = Number(delta.volume);
|
||||||
const groupPrice = getGroupPrice(price, resolution);
|
const priceLevel = getPriceLevel(price, resolution);
|
||||||
const volKey = dataType === 'sell' ? 'ask' : 'bid';
|
const isAskDataType = dataType === VolumeType.ask;
|
||||||
const oppositeVolKey = dataType === 'sell' ? 'bid' : 'ask';
|
const volKey = isAskDataType ? 'ask' : 'bid';
|
||||||
const volByLevelKey = dataType === 'sell' ? 'askByLevel' : 'bidByLevel';
|
const oppositeVolKey = isAskDataType ? 'bid' : 'ask';
|
||||||
const resolveModifiedIndex = dataType === 'sell' ? Math.max : Math.min;
|
const volByLevelKey = isAskDataType ? 'askByLevel' : 'bidByLevel';
|
||||||
let index = draft.findIndex((data) => data.price === groupPrice);
|
const resolveModifiedIndex = isAskDataType ? Math.max : Math.min;
|
||||||
|
let index = draft.findIndex((data) => data.price === priceLevel);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
|
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
|
||||||
draft[index][volKey] =
|
draft[index][volKey] =
|
||||||
draft[index][volKey] - (draft[index][volByLevelKey][price] || 0) + volume;
|
draft[index][volKey] - (draft[index][volByLevelKey][price] || 0) + volume;
|
||||||
draft[index][volByLevelKey][price] = volume;
|
draft[index][volByLevelKey][price] = volume;
|
||||||
} else {
|
} else {
|
||||||
const newData: OrderbookData = createData(groupPrice, volume, dataType);
|
const newData: OrderbookRowData = createRow(priceLevel, volume, dataType);
|
||||||
index = draft.findIndex((data) => BigInt(data.price) < BigInt(groupPrice));
|
index = draft.findIndex((data) => BigInt(data.price) < BigInt(priceLevel));
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
draft.splice(index, 0, newData);
|
draft.splice(index, 0, newData);
|
||||||
newData.cumulativeVol[oppositeVolKey] =
|
newData.cumulativeVol[oppositeVolKey] =
|
||||||
draft[index + (groupPrice === 'sell' ? -1 : 1)].cumulativeVol[
|
draft[index + (isAskDataType ? -1 : 1)]?.cumulativeVol[
|
||||||
oppositeVolKey
|
oppositeVolKey
|
||||||
];
|
] ?? 0;
|
||||||
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
|
modifiedIndex = resolveModifiedIndex(modifiedIndex, index);
|
||||||
} else {
|
} else {
|
||||||
draft.push(newData);
|
draft.push(newData);
|
||||||
@ -218,35 +228,35 @@ const partiallyUpdateCompactedData = (
|
|||||||
/**
|
/**
|
||||||
* Updates OrderbookData[] with new data received from subscription - mutates input
|
* Updates OrderbookData[] with new data received from subscription - mutates input
|
||||||
*
|
*
|
||||||
* @param orderbookData
|
* @param rows
|
||||||
* @param sell
|
* @param sell
|
||||||
* @param buy
|
* @param buy
|
||||||
* @param resolution
|
* @param resolution
|
||||||
* @returns void
|
* @returns void
|
||||||
*/
|
*/
|
||||||
export const updateCompactedData = (
|
export const updateCompactedRows = (
|
||||||
orderbookData: OrderbookData[],
|
rows: OrderbookRowData[],
|
||||||
sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null,
|
sell: MarketDepthSubscription_marketDepthUpdate_sell[] | null,
|
||||||
buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null,
|
buy: MarketDepthSubscription_marketDepthUpdate_buy[] | null,
|
||||||
resolution: number
|
resolution: number
|
||||||
) =>
|
) =>
|
||||||
produce(orderbookData, (draft) => {
|
produce(rows, (draft) => {
|
||||||
let sellModifiedIndex = -1;
|
let sellModifiedIndex = -1;
|
||||||
sell?.forEach((buy) => {
|
sell?.forEach((delta) => {
|
||||||
sellModifiedIndex = partiallyUpdateCompactedData(
|
sellModifiedIndex = partiallyUpdateCompactedRows(
|
||||||
'sell',
|
VolumeType.ask,
|
||||||
draft,
|
draft,
|
||||||
buy,
|
delta,
|
||||||
resolution,
|
resolution,
|
||||||
sellModifiedIndex
|
sellModifiedIndex
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
let buyModifiedIndex = draft.length;
|
let buyModifiedIndex = draft.length;
|
||||||
buy?.forEach((sell) => {
|
buy?.forEach((delta) => {
|
||||||
buyModifiedIndex = partiallyUpdateCompactedData(
|
buyModifiedIndex = partiallyUpdateCompactedRows(
|
||||||
'buy',
|
VolumeType.bid,
|
||||||
draft,
|
draft,
|
||||||
sell,
|
delta,
|
||||||
resolution,
|
resolution,
|
||||||
buyModifiedIndex
|
buyModifiedIndex
|
||||||
);
|
);
|
||||||
@ -283,6 +293,25 @@ export const updateCompactedData = (
|
|||||||
updateRelativeData(draft);
|
updateRelativeData(draft);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const mapMarketData = (
|
||||||
|
data:
|
||||||
|
| MarketDepth_market_data
|
||||||
|
| MarketDepthSubscription_marketDepthUpdate_market_data
|
||||||
|
| null,
|
||||||
|
resolution: number
|
||||||
|
) => ({
|
||||||
|
staticMidPrice:
|
||||||
|
data?.staticMidPrice && getPriceLevel(data?.staticMidPrice, resolution),
|
||||||
|
bestStaticBidPrice:
|
||||||
|
data?.bestStaticBidPrice &&
|
||||||
|
getPriceLevel(data?.bestStaticBidPrice, resolution),
|
||||||
|
bestStaticOfferPrice:
|
||||||
|
data?.bestStaticOfferPrice &&
|
||||||
|
getPriceLevel(data?.bestStaticOfferPrice, resolution),
|
||||||
|
indicativePrice:
|
||||||
|
data?.indicativePrice && getPriceLevel(data?.indicativePrice, resolution),
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates raw data with new data received from subscription - mutates input
|
* Updates raw data with new data received from subscription - mutates input
|
||||||
* @param levels
|
* @param levels
|
||||||
@ -317,3 +346,69 @@ export const updateLevels = (
|
|||||||
});
|
});
|
||||||
return levels;
|
return levels;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface MockDataGeneratorParams {
|
||||||
|
numberOfSellRows: number;
|
||||||
|
numberOfBuyRows: number;
|
||||||
|
overlap: number;
|
||||||
|
midPrice: number;
|
||||||
|
bestStaticBidPrice: number;
|
||||||
|
bestStaticOfferPrice: number;
|
||||||
|
indicativePrice?: number;
|
||||||
|
indicativeVolume?: number;
|
||||||
|
resolution: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const generateMockData = ({
|
||||||
|
numberOfSellRows,
|
||||||
|
numberOfBuyRows,
|
||||||
|
midPrice,
|
||||||
|
overlap,
|
||||||
|
bestStaticBidPrice,
|
||||||
|
bestStaticOfferPrice,
|
||||||
|
indicativePrice,
|
||||||
|
indicativeVolume,
|
||||||
|
resolution,
|
||||||
|
}: MockDataGeneratorParams) => {
|
||||||
|
let matrix = new Array(numberOfSellRows).fill(undefined);
|
||||||
|
let price = midPrice + (numberOfSellRows - Math.ceil(overlap / 2) + 1);
|
||||||
|
const sell: MarketDepth_market_depth_sell[] = matrix.map((row, i) => ({
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
price: (price -= 1).toString(),
|
||||||
|
volume: (numberOfSellRows - i + 1).toString(),
|
||||||
|
numberOfOrders: '',
|
||||||
|
}));
|
||||||
|
price += overlap;
|
||||||
|
matrix = new Array(numberOfBuyRows).fill(undefined);
|
||||||
|
const buy: MarketDepth_market_depth_buy[] = matrix.map((row, i) => ({
|
||||||
|
__typename: 'PriceLevel',
|
||||||
|
price: (price -= 1).toString(),
|
||||||
|
volume: (i + 2).toString(),
|
||||||
|
numberOfOrders: '',
|
||||||
|
}));
|
||||||
|
const rows = compactRows(sell, buy, resolution);
|
||||||
|
return {
|
||||||
|
rows,
|
||||||
|
resolution,
|
||||||
|
indicativeVolume: indicativeVolume?.toString(),
|
||||||
|
...mapMarketData(
|
||||||
|
{
|
||||||
|
__typename: 'MarketData',
|
||||||
|
staticMidPrice: '',
|
||||||
|
marketTradingMode:
|
||||||
|
overlap > 0
|
||||||
|
? MarketTradingMode.BatchAuction
|
||||||
|
: MarketTradingMode.Continuous,
|
||||||
|
bestStaticBidPrice: bestStaticBidPrice.toString(),
|
||||||
|
bestStaticOfferPrice: bestStaticOfferPrice.toString(),
|
||||||
|
indicativePrice: indicativePrice?.toString() ?? '',
|
||||||
|
indicativeVolume: indicativeVolume?.toString() ?? '',
|
||||||
|
market: {
|
||||||
|
__typename: 'Market',
|
||||||
|
id: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
resolution
|
||||||
|
),
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -1,44 +1,54 @@
|
|||||||
import throttle from 'lodash/throttle';
|
import throttle from 'lodash/throttle';
|
||||||
|
import produce from 'immer';
|
||||||
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
import { AsyncRenderer } from '@vegaprotocol/ui-toolkit';
|
||||||
import { Orderbook } from './orderbook';
|
import { Orderbook } from './orderbook';
|
||||||
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
import { useDataProvider } from '@vegaprotocol/react-helpers';
|
||||||
import { marketDepthDataProvider } from './market-depth-data-provider';
|
import { marketDepthDataProvider } from './market-depth-data-provider';
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
||||||
import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription';
|
import type { MarketDepthSubscription_marketDepthUpdate } from './__generated__/MarketDepthSubscription';
|
||||||
import { compactData, updateCompactedData } from './orderbook-data';
|
import {
|
||||||
|
compactRows,
|
||||||
|
updateCompactedRows,
|
||||||
|
mapMarketData,
|
||||||
|
} from './orderbook-data';
|
||||||
import type { OrderbookData } from './orderbook-data';
|
import type { OrderbookData } from './orderbook-data';
|
||||||
|
|
||||||
interface OrderbookManagerProps {
|
interface OrderbookManagerProps {
|
||||||
marketId: string;
|
marketId: string;
|
||||||
resolution: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OrderbookManager = ({
|
export const OrderbookManager = ({ marketId }: OrderbookManagerProps) => {
|
||||||
marketId,
|
const [resolution, setResolution] = useState(1);
|
||||||
resolution,
|
|
||||||
}: OrderbookManagerProps) => {
|
|
||||||
const variables = useMemo(() => ({ marketId }), [marketId]);
|
const variables = useMemo(() => ({ marketId }), [marketId]);
|
||||||
const resolutionRef = useRef(resolution);
|
const resolutionRef = useRef(resolution);
|
||||||
const [orderbookData, setOrderbookData] = useState<OrderbookData[] | null>(
|
const [orderbookData, setOrderbookData] = useState<OrderbookData>({
|
||||||
null
|
rows: null,
|
||||||
);
|
});
|
||||||
const dataRef = useRef<OrderbookData[] | null>(null);
|
const dataRef = useRef<OrderbookData>({ rows: null });
|
||||||
const setOrderbookDataThrottled = useRef(throttle(setOrderbookData, 1000));
|
const setOrderbookDataThrottled = useRef(throttle(setOrderbookData, 1000));
|
||||||
|
|
||||||
const update = useCallback(
|
const update = useCallback(
|
||||||
(delta: MarketDepthSubscription_marketDepthUpdate) => {
|
(delta: MarketDepthSubscription_marketDepthUpdate) => {
|
||||||
if (!dataRef.current) {
|
if (!dataRef.current.rows) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
dataRef.current = updateCompactedData(
|
dataRef.current = produce(dataRef.current, (draft) => {
|
||||||
dataRef.current,
|
Object.assign(draft, delta.market.data);
|
||||||
delta.sell,
|
draft.rows = updateCompactedRows(
|
||||||
delta.buy,
|
draft.rows ?? [],
|
||||||
resolutionRef.current
|
delta.sell,
|
||||||
);
|
delta.buy,
|
||||||
|
resolutionRef.current
|
||||||
|
);
|
||||||
|
Object.assign(
|
||||||
|
draft,
|
||||||
|
mapMarketData(delta.market.data, resolutionRef.current)
|
||||||
|
);
|
||||||
|
});
|
||||||
setOrderbookDataThrottled.current(dataRef.current);
|
setOrderbookDataThrottled.current(dataRef.current);
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
// using resolutionRef.current to avoid using resolution as a dependency - it will cause data provider restart on resolution change
|
||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -50,11 +60,15 @@ export const OrderbookManager = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!data) {
|
if (!data) {
|
||||||
dataRef.current = null;
|
dataRef.current = { rows: null };
|
||||||
setOrderbookData(dataRef.current);
|
setOrderbookData(dataRef.current);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
dataRef.current = compactData(data.depth.sell, data.depth.buy, resolution);
|
dataRef.current = {
|
||||||
|
...data.data,
|
||||||
|
rows: compactRows(data.depth.sell, data.depth.buy, resolution),
|
||||||
|
...mapMarketData(data.data, resolution),
|
||||||
|
};
|
||||||
setOrderbookData(dataRef.current);
|
setOrderbookData(dataRef.current);
|
||||||
}, [data, resolution]);
|
}, [data, resolution]);
|
||||||
|
|
||||||
@ -66,8 +80,10 @@ export const OrderbookManager = ({
|
|||||||
return (
|
return (
|
||||||
<AsyncRenderer loading={loading} error={error} data={data}>
|
<AsyncRenderer loading={loading} error={error} data={data}>
|
||||||
<Orderbook
|
<Orderbook
|
||||||
data={orderbookData}
|
{...orderbookData}
|
||||||
decimalPlaces={data?.decimalPlaces ?? 0}
|
decimalPlaces={data?.decimalPlaces ?? 0}
|
||||||
|
resolution={resolution}
|
||||||
|
onResolutionChange={(resolution: number) => setResolution(resolution)}
|
||||||
/>
|
/>
|
||||||
</AsyncRenderer>
|
</AsyncRenderer>
|
||||||
);
|
);
|
||||||
|
@ -4,41 +4,64 @@ import {
|
|||||||
Vol,
|
Vol,
|
||||||
CumulativeVol,
|
CumulativeVol,
|
||||||
addDecimalsFormatNumber,
|
addDecimalsFormatNumber,
|
||||||
|
VolumeType,
|
||||||
} from '@vegaprotocol/react-helpers';
|
} from '@vegaprotocol/react-helpers';
|
||||||
|
|
||||||
interface OrderbookRowProps {
|
interface OrderbookRowProps {
|
||||||
bid: number;
|
|
||||||
relativeBidVol?: number;
|
|
||||||
price: string;
|
|
||||||
ask: number;
|
ask: number;
|
||||||
relativeAskVol?: number;
|
bid: number;
|
||||||
|
cumulativeAsk?: number;
|
||||||
|
cumulativeBid?: number;
|
||||||
cumulativeRelativeAsk?: number;
|
cumulativeRelativeAsk?: number;
|
||||||
cumulativeRelativeBid?: number;
|
cumulativeRelativeBid?: number;
|
||||||
decimalPlaces: number;
|
decimalPlaces: number;
|
||||||
|
indicativeVolume?: string;
|
||||||
|
price: string;
|
||||||
|
relativeAsk?: number;
|
||||||
|
relativeBid?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const OrderbookRow = React.memo(
|
export const OrderbookRow = React.memo(
|
||||||
({
|
({
|
||||||
bid,
|
|
||||||
relativeBidVol,
|
|
||||||
price,
|
|
||||||
ask,
|
ask,
|
||||||
relativeAskVol,
|
bid,
|
||||||
decimalPlaces,
|
cumulativeAsk,
|
||||||
|
cumulativeBid,
|
||||||
cumulativeRelativeAsk,
|
cumulativeRelativeAsk,
|
||||||
cumulativeRelativeBid,
|
cumulativeRelativeBid,
|
||||||
|
decimalPlaces,
|
||||||
|
indicativeVolume,
|
||||||
|
price,
|
||||||
|
relativeAsk,
|
||||||
|
relativeBid,
|
||||||
}: OrderbookRowProps) => {
|
}: OrderbookRowProps) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Vol value={bid} relativeValue={relativeBidVol} type="bid" />
|
<Vol
|
||||||
|
testId={`bid-vol-${price}`}
|
||||||
|
value={bid}
|
||||||
|
relativeValue={relativeBid}
|
||||||
|
type={VolumeType.bid}
|
||||||
|
/>
|
||||||
<PriceCell
|
<PriceCell
|
||||||
|
testId={`price-${price}`}
|
||||||
value={BigInt(price)}
|
value={BigInt(price)}
|
||||||
valueFormatted={addDecimalsFormatNumber(price, decimalPlaces)}
|
valueFormatted={addDecimalsFormatNumber(price, decimalPlaces)}
|
||||||
/>
|
/>
|
||||||
<Vol value={ask} relativeValue={relativeAskVol} type="ask" />
|
<Vol
|
||||||
|
testId={`ask-vol-${price}`}
|
||||||
|
value={ask}
|
||||||
|
relativeValue={relativeAsk}
|
||||||
|
type={VolumeType.ask}
|
||||||
|
/>
|
||||||
<CumulativeVol
|
<CumulativeVol
|
||||||
|
testId={`cumulative-vol-${price}`}
|
||||||
|
bid={cumulativeBid}
|
||||||
|
ask={cumulativeAsk}
|
||||||
relativeAsk={cumulativeRelativeAsk}
|
relativeAsk={cumulativeRelativeAsk}
|
||||||
relativeBid={cumulativeRelativeBid}
|
relativeBid={cumulativeRelativeBid}
|
||||||
|
indicativeVolume={indicativeVolume}
|
||||||
|
className="pr-4"
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
22
libs/market-depth/src/lib/orderbook.module.scss
Normal file
22
libs/market-depth/src/lib/orderbook.module.scss
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
$scrollbar-width: 6px;
|
||||||
|
|
||||||
|
/* Works on Firefox */
|
||||||
|
.scroll {
|
||||||
|
scrollbar-width: thin;
|
||||||
|
scrollbar-color: #999 #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Works on Chrome, Edge, and Safari */
|
||||||
|
.scroll::-webkit-scrollbar {
|
||||||
|
width: $scrollbar-width;
|
||||||
|
background-color: #999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll::-webkit-scrollbar-thumb {
|
||||||
|
width: $scrollbar-width;
|
||||||
|
background-color: #333;
|
||||||
|
}
|
||||||
|
.scroll::-webkit-scrollbar-track {
|
||||||
|
box-shadow: inset 0 0 #{$scrollbar-width} rgb(0 0 0 / 30%);
|
||||||
|
background-color: #999;
|
||||||
|
}
|
@ -1,10 +1,165 @@
|
|||||||
import { render } from '@testing-library/react';
|
import { render, fireEvent, waitFor, screen } from '@testing-library/react';
|
||||||
|
import { generateMockData } from './orderbook-data';
|
||||||
import Orderbook from './orderbook';
|
import { Orderbook, rowHeight } from './orderbook';
|
||||||
|
|
||||||
describe('Orderbook', () => {
|
describe('Orderbook', () => {
|
||||||
it('should render successfully', () => {
|
const params = {
|
||||||
const { baseElement } = render(<Orderbook data={null} decimalPlaces={4} />);
|
numberOfSellRows: 100,
|
||||||
expect(baseElement).toBeTruthy();
|
numberOfBuyRows: 100,
|
||||||
|
midPrice: 122900,
|
||||||
|
bestStaticBidPrice: 122905,
|
||||||
|
bestStaticOfferPrice: 122895,
|
||||||
|
decimalPlaces: 3,
|
||||||
|
overlap: 10,
|
||||||
|
indicativePrice: 122900,
|
||||||
|
indicativeVolume: 11,
|
||||||
|
resolution: 1,
|
||||||
|
};
|
||||||
|
const onResolutionChange = jest.fn();
|
||||||
|
const decimalPlaces = 3;
|
||||||
|
it('should scroll to mid price on init', async () => {
|
||||||
|
window.innerHeight = 11 * rowHeight;
|
||||||
|
const result = render(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData(params)}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`));
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should keep mid price row in the middle', async () => {
|
||||||
|
window.innerHeight = 11 * rowHeight;
|
||||||
|
const result = render(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData(params)}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`));
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight);
|
||||||
|
result.rerender(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData({
|
||||||
|
...params,
|
||||||
|
numberOfSellRows: params.numberOfSellRows - 1,
|
||||||
|
})}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`));
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(90 * rowHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should scroll to mid price when it will change', async () => {
|
||||||
|
window.innerHeight = 11 * rowHeight;
|
||||||
|
const result = render(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData(params)}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`));
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight);
|
||||||
|
result.rerender(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData({
|
||||||
|
...params,
|
||||||
|
bestStaticBidPrice: params.bestStaticBidPrice + 1,
|
||||||
|
bestStaticOfferPrice: params.bestStaticOfferPrice + 1,
|
||||||
|
})}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`));
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(90 * rowHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should should keep price it the middle', async () => {
|
||||||
|
window.innerHeight = 11 * rowHeight;
|
||||||
|
const result = render(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData(params)}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`));
|
||||||
|
const scrollElement = result.getByTestId('scroll');
|
||||||
|
expect(scrollElement.scrollTop).toBe(91 * rowHeight);
|
||||||
|
scrollElement.scrollTop = 92 * rowHeight;
|
||||||
|
fireEvent.scroll(scrollElement);
|
||||||
|
result.rerender(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData({
|
||||||
|
...params,
|
||||||
|
numberOfSellRows: params.numberOfSellRows - 1,
|
||||||
|
})}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`));
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should should get back to mid price on click', async () => {
|
||||||
|
window.innerHeight = 11 * rowHeight;
|
||||||
|
const result = render(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData(params)}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`));
|
||||||
|
const scrollElement = result.getByTestId('scroll');
|
||||||
|
expect(scrollElement.scrollTop).toBe(91 * rowHeight);
|
||||||
|
scrollElement.scrollTop = 0;
|
||||||
|
fireEvent.scroll(scrollElement);
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(0);
|
||||||
|
const scrollToMidPriceButton = result.getByTestId('scroll-to-midprice');
|
||||||
|
fireEvent.click(scrollToMidPriceButton);
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(91 * rowHeight);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should should get back to mid price on resolution change', async () => {
|
||||||
|
window.innerHeight = 11 * rowHeight;
|
||||||
|
const result = render(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData(params)}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
await waitFor(() => screen.getByTestId(`bid-vol-${params.midPrice}`));
|
||||||
|
const scrollElement = result.getByTestId('scroll');
|
||||||
|
expect(scrollElement.scrollTop).toBe(91 * rowHeight);
|
||||||
|
scrollElement.scrollTop = 0;
|
||||||
|
fireEvent.scroll(scrollElement);
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(0);
|
||||||
|
const resolutionSelect = result.getByTestId(
|
||||||
|
'resolution'
|
||||||
|
) as HTMLSelectElement;
|
||||||
|
fireEvent.change(resolutionSelect, { target: { value: '10' } });
|
||||||
|
expect(onResolutionChange.mock.calls.length).toBe(1);
|
||||||
|
expect(onResolutionChange.mock.calls[0][0]).toBe(10);
|
||||||
|
result.rerender(
|
||||||
|
<Orderbook
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData({
|
||||||
|
...params,
|
||||||
|
resolution: 10,
|
||||||
|
})}
|
||||||
|
onResolutionChange={onResolutionChange}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
expect(result.getByTestId('scroll').scrollTop).toBe(5 * rowHeight);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
71
libs/market-depth/src/lib/orderbook.stories.tsx
Normal file
71
libs/market-depth/src/lib/orderbook.stories.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import type { Story, Meta } from '@storybook/react';
|
||||||
|
import { generateMockData } from './orderbook-data';
|
||||||
|
import type { MockDataGeneratorParams } from './orderbook-data';
|
||||||
|
import { Orderbook } from './orderbook';
|
||||||
|
import { useState } from 'react';
|
||||||
|
|
||||||
|
type Props = Omit<MockDataGeneratorParams, 'resolution'> & {
|
||||||
|
decimalPlaces: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
const OrderbokMockDataProvider = ({ decimalPlaces, ...props }: Props) => {
|
||||||
|
const [resolution, setResolution] = useState(1);
|
||||||
|
return (
|
||||||
|
<div className="absolute inset-0 dark:bg-black dark:text-white-60 bg-white text-black-60">
|
||||||
|
<div
|
||||||
|
className="absolute left-0 top-0 bottom-0"
|
||||||
|
style={{ width: '400px' }}
|
||||||
|
>
|
||||||
|
<Orderbook
|
||||||
|
onResolutionChange={setResolution}
|
||||||
|
decimalPlaces={decimalPlaces}
|
||||||
|
{...generateMockData({ ...props, resolution })}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
component: OrderbokMockDataProvider,
|
||||||
|
title: 'Orderbook',
|
||||||
|
} as Meta;
|
||||||
|
|
||||||
|
const Template: Story<Props> = (args) => <OrderbokMockDataProvider {...args} />;
|
||||||
|
|
||||||
|
export const Continuous = Template.bind({});
|
||||||
|
Continuous.args = {
|
||||||
|
numberOfSellRows: 100,
|
||||||
|
numberOfBuyRows: 100,
|
||||||
|
midPrice: 1000,
|
||||||
|
bestStaticBidPrice: 1000,
|
||||||
|
bestStaticOfferPrice: 1000,
|
||||||
|
decimalPlaces: 3,
|
||||||
|
overlap: -1,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Auction = Template.bind({});
|
||||||
|
Auction.args = {
|
||||||
|
numberOfSellRows: 100,
|
||||||
|
numberOfBuyRows: 100,
|
||||||
|
midPrice: 122900,
|
||||||
|
bestStaticBidPrice: 122905,
|
||||||
|
bestStaticOfferPrice: 122895,
|
||||||
|
decimalPlaces: 3,
|
||||||
|
overlap: 10,
|
||||||
|
indicativePrice: 122900,
|
||||||
|
indicativeVolume: 11,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Empty = Template.bind({});
|
||||||
|
Empty.args = {
|
||||||
|
numberOfSellRows: 0,
|
||||||
|
numberOfBuyRows: 0,
|
||||||
|
midPrice: 0,
|
||||||
|
bestStaticBidPrice: 0,
|
||||||
|
bestStaticOfferPrice: 0,
|
||||||
|
decimalPlaces: 3,
|
||||||
|
overlap: 0,
|
||||||
|
indicativePrice: 0,
|
||||||
|
indicativeVolume: 0,
|
||||||
|
};
|
@ -1,36 +1,395 @@
|
|||||||
import { t } from '@vegaprotocol/react-helpers';
|
import styles from './orderbook.module.scss';
|
||||||
import { OrderbookRow } from './orderbook-row';
|
|
||||||
import type { OrderbookData } from './orderbook-data';
|
|
||||||
|
|
||||||
interface OrderbookProps {
|
import {
|
||||||
data: OrderbookData[] | null;
|
Fragment,
|
||||||
|
useEffect,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
useMemo,
|
||||||
|
useCallback,
|
||||||
|
} from 'react';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
import { formatNumber, t } from '@vegaprotocol/react-helpers';
|
||||||
|
import { MarketTradingMode } from '@vegaprotocol/types';
|
||||||
|
import { OrderbookRow } from './orderbook-row';
|
||||||
|
import { createRow, getPriceLevel } from './orderbook-data';
|
||||||
|
import { Icon, Splash } from '@vegaprotocol/ui-toolkit';
|
||||||
|
import type { OrderbookData, OrderbookRowData } from './orderbook-data';
|
||||||
|
interface OrderbookProps extends OrderbookData {
|
||||||
decimalPlaces: number;
|
decimalPlaces: number;
|
||||||
|
resolution: number;
|
||||||
|
onResolutionChange: (resolution: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Orderbook = ({ data, decimalPlaces }: OrderbookProps) => (
|
const horizontalLine = (top: string, testId: string) => (
|
||||||
<>
|
<div
|
||||||
<div className="grid grid-cols-4 gap-4 border-b-1 text-ui-small mb-2 pb-2">
|
className="border-b-1 absolute inset-x-0"
|
||||||
<div>{t('Bid Vol')}</div>
|
style={{ top }}
|
||||||
<div>{t('Price')}</div>
|
data-testid={testId}
|
||||||
<div>{t('Ask Vol')}</div>
|
></div>
|
||||||
<div>{t('Cumulative Vol')}</div>
|
|
||||||
</div>
|
|
||||||
<div className="grid grid-cols-4 gap-4 text-right text-ui-small">
|
|
||||||
{data?.map((data) => (
|
|
||||||
<OrderbookRow
|
|
||||||
key={data.price}
|
|
||||||
price={data.price}
|
|
||||||
decimalPlaces={decimalPlaces}
|
|
||||||
bid={data.bid}
|
|
||||||
relativeBidVol={data.relativeBidVol}
|
|
||||||
cumulativeRelativeBid={data.cumulativeVol.relativeBid}
|
|
||||||
ask={data.ask}
|
|
||||||
relativeAskVol={data.relativeAskVol}
|
|
||||||
cumulativeRelativeAsk={data.cumulativeVol.relativeAsk}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const getNumberOfRows = (
|
||||||
|
rows: OrderbookRowData[] | null,
|
||||||
|
resolution: number
|
||||||
|
) => {
|
||||||
|
if (!rows || !rows.length) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (rows.length === 1) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
Number(BigInt(rows[0].price) - BigInt(rows[rows.length - 1].price)) /
|
||||||
|
resolution +
|
||||||
|
1
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getRowsToRender = (
|
||||||
|
rows: OrderbookRowData[] | null,
|
||||||
|
resolution: number,
|
||||||
|
offset: number,
|
||||||
|
limit: number
|
||||||
|
): OrderbookRowData[] | null => {
|
||||||
|
if (!rows || !rows.length) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
if (rows.length === 1) {
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
const selectedRows: OrderbookRowData[] = [];
|
||||||
|
let price = BigInt(rows[0].price) - BigInt(offset * resolution);
|
||||||
|
let index = Math.max(
|
||||||
|
rows.findIndex((row) => BigInt(row.price) <= price) - 1,
|
||||||
|
-1
|
||||||
|
);
|
||||||
|
while (selectedRows.length < limit && index + 1 < rows.length) {
|
||||||
|
if (rows[index + 1].price === price.toString()) {
|
||||||
|
selectedRows.push(rows[index + 1]);
|
||||||
|
index += 1;
|
||||||
|
} else {
|
||||||
|
const row = createRow(price.toString());
|
||||||
|
row.cumulativeVol = {
|
||||||
|
bid: rows[index].cumulativeVol.bid,
|
||||||
|
relativeBid: rows[index].cumulativeVol.relativeBid,
|
||||||
|
ask: rows[index + 1].cumulativeVol.ask,
|
||||||
|
relativeAsk: rows[index + 1].cumulativeVol.relativeAsk,
|
||||||
|
};
|
||||||
|
selectedRows.push(row);
|
||||||
|
}
|
||||||
|
price -= BigInt(resolution);
|
||||||
|
}
|
||||||
|
return selectedRows;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 17px of row height plus 5px gap
|
||||||
|
export const rowHeight = 22;
|
||||||
|
// buffer size in rows
|
||||||
|
const bufferSize = 30;
|
||||||
|
// margin size in px, when reached scrollOffset will be updated
|
||||||
|
const marginSize = bufferSize * 0.9 * rowHeight;
|
||||||
|
|
||||||
|
export const Orderbook = ({
|
||||||
|
rows,
|
||||||
|
bestStaticBidPrice,
|
||||||
|
bestStaticOfferPrice,
|
||||||
|
marketTradingMode,
|
||||||
|
indicativeVolume,
|
||||||
|
indicativePrice,
|
||||||
|
decimalPlaces,
|
||||||
|
resolution,
|
||||||
|
onResolutionChange,
|
||||||
|
}: OrderbookProps) => {
|
||||||
|
const scrollElement = useRef<HTMLDivElement>(null);
|
||||||
|
// scroll offset for which rendered rows are selected, will change after user will scroll to margin of rendered data
|
||||||
|
const [scrollOffset, setScrollOffset] = useState(0);
|
||||||
|
// actual scrollTop of scrollElement current element
|
||||||
|
const scrollTopRef = useRef(0);
|
||||||
|
// price level which is rendered in center of viewport, need to preserve price level when rows will be added or removed
|
||||||
|
// if undefined then we render mid price in center
|
||||||
|
const priceInCenter = useRef<string>();
|
||||||
|
const [lockOnMidPrice, setLockOnMidPrice] = useState(true);
|
||||||
|
const resolutionRef = useRef(resolution);
|
||||||
|
// stores rows[0].price value
|
||||||
|
const [maxPriceLevel, setMaxPriceLevel] = useState('');
|
||||||
|
const [viewportHeight, setViewportHeight] = useState(window.innerHeight);
|
||||||
|
const numberOfRows = useMemo(
|
||||||
|
() => getNumberOfRows(rows, resolution),
|
||||||
|
[rows, resolution]
|
||||||
|
);
|
||||||
|
|
||||||
|
const updateScrollOffset = useCallback(
|
||||||
|
(scrollTop: number) => {
|
||||||
|
if (Math.abs(scrollOffset - scrollTop) > marginSize) {
|
||||||
|
setScrollOffset(scrollTop);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[scrollOffset]
|
||||||
|
);
|
||||||
|
|
||||||
|
const onScroll = useCallback(
|
||||||
|
(event: React.UIEvent<HTMLDivElement>) => {
|
||||||
|
const { scrollTop } = event.currentTarget;
|
||||||
|
updateScrollOffset(scrollTop);
|
||||||
|
if (scrollTop === scrollTopRef.current) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
priceInCenter.current = (
|
||||||
|
BigInt(resolution) + // extra row on very top - sticky header
|
||||||
|
BigInt(maxPriceLevel) -
|
||||||
|
BigInt(
|
||||||
|
Math.floor((scrollTop + Math.floor(viewportHeight / 2)) / rowHeight)
|
||||||
|
) *
|
||||||
|
BigInt(resolution)
|
||||||
|
).toString();
|
||||||
|
if (lockOnMidPrice) {
|
||||||
|
setLockOnMidPrice(false);
|
||||||
|
}
|
||||||
|
scrollTopRef.current = scrollTop;
|
||||||
|
},
|
||||||
|
[
|
||||||
|
resolution,
|
||||||
|
lockOnMidPrice,
|
||||||
|
maxPriceLevel,
|
||||||
|
viewportHeight,
|
||||||
|
updateScrollOffset,
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
const scrollToPrice = useCallback(
|
||||||
|
(price: string) => {
|
||||||
|
if (scrollElement.current && maxPriceLevel) {
|
||||||
|
let scrollTop =
|
||||||
|
// distance in rows between midPrice and price from first row * row Height
|
||||||
|
(Number(
|
||||||
|
(BigInt(maxPriceLevel) - BigInt(price)) / BigInt(resolution)
|
||||||
|
) +
|
||||||
|
1) * // add one row for sticky header
|
||||||
|
rowHeight;
|
||||||
|
// minus half height of viewport plus half of row
|
||||||
|
scrollTop -= Math.ceil((viewportHeight - rowHeight) / 2);
|
||||||
|
// adjust to current rows position
|
||||||
|
scrollTop +=
|
||||||
|
(scrollTopRef.current % rowHeight) - (scrollTop % rowHeight);
|
||||||
|
const priceCenterScrollOffset = Math.max(0, Math.min(scrollTop));
|
||||||
|
if (scrollTopRef.current !== priceCenterScrollOffset) {
|
||||||
|
updateScrollOffset(priceCenterScrollOffset);
|
||||||
|
scrollTopRef.current = priceCenterScrollOffset;
|
||||||
|
scrollElement.current.scrollTop = priceCenterScrollOffset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[maxPriceLevel, resolution, viewportHeight, updateScrollOffset]
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const newMaxPriceLevel = rows?.[0]?.price ?? '';
|
||||||
|
if (newMaxPriceLevel !== maxPriceLevel) {
|
||||||
|
setMaxPriceLevel(newMaxPriceLevel);
|
||||||
|
}
|
||||||
|
}, [rows, maxPriceLevel]);
|
||||||
|
|
||||||
|
const scrollToMidPrice = useCallback(() => {
|
||||||
|
if (!bestStaticOfferPrice || !bestStaticBidPrice) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
priceInCenter.current = undefined;
|
||||||
|
setLockOnMidPrice(true);
|
||||||
|
scrollToPrice(
|
||||||
|
getPriceLevel(
|
||||||
|
BigInt(bestStaticOfferPrice) +
|
||||||
|
(BigInt(bestStaticBidPrice) - BigInt(bestStaticOfferPrice)) /
|
||||||
|
BigInt(2),
|
||||||
|
resolution
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [bestStaticOfferPrice, bestStaticBidPrice, scrollToPrice, resolution]);
|
||||||
|
|
||||||
|
// adjust scroll position to keep selected price in center
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (resolutionRef.current !== resolution) {
|
||||||
|
priceInCenter.current = undefined;
|
||||||
|
resolutionRef.current = resolution;
|
||||||
|
setLockOnMidPrice(true);
|
||||||
|
}
|
||||||
|
if (priceInCenter.current) {
|
||||||
|
scrollToPrice(priceInCenter.current);
|
||||||
|
} else {
|
||||||
|
scrollToMidPrice();
|
||||||
|
}
|
||||||
|
}, [scrollToMidPrice, scrollToPrice, resolution]);
|
||||||
|
|
||||||
|
// handles viewport resize
|
||||||
|
useEffect(() => {
|
||||||
|
function handleResize() {
|
||||||
|
if (scrollElement.current) {
|
||||||
|
setViewportHeight(
|
||||||
|
scrollElement.current.clientHeight || window.innerHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
|
handleResize();
|
||||||
|
return () => window.removeEventListener('resize', handleResize);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const renderedRows = useMemo(() => {
|
||||||
|
let offset = Math.max(0, Math.round(scrollOffset / rowHeight));
|
||||||
|
const prependingBufferSize = Math.min(bufferSize, offset);
|
||||||
|
offset -= prependingBufferSize;
|
||||||
|
const viewportSize = Math.round(viewportHeight / rowHeight);
|
||||||
|
const limit = Math.min(
|
||||||
|
prependingBufferSize + viewportSize + bufferSize,
|
||||||
|
numberOfRows - offset
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
offset,
|
||||||
|
limit,
|
||||||
|
data: getRowsToRender(rows, resolution, offset, limit),
|
||||||
|
};
|
||||||
|
}, [rows, scrollOffset, resolution, viewportHeight, numberOfRows]);
|
||||||
|
|
||||||
|
const paddingTop = renderedRows.offset * rowHeight;
|
||||||
|
const paddingBottom =
|
||||||
|
(numberOfRows - renderedRows.offset - renderedRows.limit) * rowHeight;
|
||||||
|
const minPriceLevel =
|
||||||
|
BigInt(maxPriceLevel) - BigInt(Math.floor(numberOfRows * resolution));
|
||||||
|
const hasData = renderedRows.data && renderedRows.data.length !== 0;
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`h-full overflow-auto relative ${styles['scroll']}`}
|
||||||
|
onScroll={onScroll}
|
||||||
|
ref={scrollElement}
|
||||||
|
data-testid="scroll"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className="sticky top-0 grid grid-cols-4 gap-5 text-right border-b-1 text-ui-small mb-2 pb-2 bg-white dark:bg-black z-10"
|
||||||
|
style={{ gridAutoRows: '17px' }}
|
||||||
|
>
|
||||||
|
<div>{t('Bid Vol')}</div>
|
||||||
|
<div>{t('Price')}</div>
|
||||||
|
<div>{t('Ask Vol')}</div>
|
||||||
|
<div className="pr-4">{t('Cumulative Vol')}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
paddingTop: `${paddingTop}px`,
|
||||||
|
paddingBottom: `${paddingBottom}px`,
|
||||||
|
minHeight: `calc(100% - ${2 * rowHeight}px)`,
|
||||||
|
background: hasData
|
||||||
|
? 'linear-gradient(#999,#999) 24.6% 0/1px 100% no-repeat, linear-gradient(#999,#999) 50% 0/1px 100% no-repeat, linear-gradient(#999,#999) 75.2% 0/1px 100% no-repeat'
|
||||||
|
: 'none',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{hasData ? (
|
||||||
|
<div
|
||||||
|
className="grid grid-cols-4 gap-5 text-right text-ui-small"
|
||||||
|
style={{
|
||||||
|
gridAutoRows: '17px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{renderedRows.data?.map((data) => {
|
||||||
|
return (
|
||||||
|
<Fragment key={data.price}>
|
||||||
|
<OrderbookRow
|
||||||
|
price={(BigInt(data.price) / BigInt(resolution)).toString()}
|
||||||
|
decimalPlaces={decimalPlaces - Math.log10(resolution)}
|
||||||
|
bid={data.bid}
|
||||||
|
relativeBid={data.relativeBid}
|
||||||
|
cumulativeBid={data.cumulativeVol.bid}
|
||||||
|
cumulativeRelativeBid={data.cumulativeVol.relativeBid}
|
||||||
|
ask={data.ask}
|
||||||
|
relativeAsk={data.relativeAsk}
|
||||||
|
cumulativeAsk={data.cumulativeVol.ask}
|
||||||
|
cumulativeRelativeAsk={data.cumulativeVol.relativeAsk}
|
||||||
|
indicativeVolume={
|
||||||
|
marketTradingMode !== MarketTradingMode.Continuous &&
|
||||||
|
indicativePrice === data.price
|
||||||
|
? indicativeVolume
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="inset-0 absolute">
|
||||||
|
<Splash>{t('No data')}</Splash>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className="sticky bottom-0 grid grid-cols-4 gap-5 border-t-1 text-ui-small mt-2 pb-2 bg-white dark:bg-black z-10"
|
||||||
|
style={{ gridAutoRows: '17px' }}
|
||||||
|
>
|
||||||
|
<div className="text-ui-small col-start-2">
|
||||||
|
<select
|
||||||
|
onChange={(e) => onResolutionChange(Number(e.currentTarget.value))}
|
||||||
|
value={resolution}
|
||||||
|
className="block bg-black-25 dark:bg-white-25 text-black dark:text-white focus-visible:shadow-focus dark:focus-visible:shadow-focus-dark focus-visible:outline-0 font-mono w-100 text-right w-full h-full"
|
||||||
|
data-testid="resolution"
|
||||||
|
>
|
||||||
|
{new Array(3)
|
||||||
|
.fill(null)
|
||||||
|
.map((v, i) => Math.pow(10, i))
|
||||||
|
.map((r) => (
|
||||||
|
<option key={r} value={r}>
|
||||||
|
{formatNumber(0, decimalPlaces - Math.log10(r))}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div className="text-ui-small col-start-4">
|
||||||
|
<button
|
||||||
|
onClick={scrollToMidPrice}
|
||||||
|
className={classNames('w-full h-full', {
|
||||||
|
hidden: lockOnMidPrice,
|
||||||
|
block: !lockOnMidPrice,
|
||||||
|
})}
|
||||||
|
data-testid="scroll-to-midprice"
|
||||||
|
>
|
||||||
|
Go to mid
|
||||||
|
<span className="ml-4">
|
||||||
|
<Icon name="th-derived" />
|
||||||
|
</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{maxPriceLevel &&
|
||||||
|
bestStaticBidPrice &&
|
||||||
|
BigInt(bestStaticBidPrice) < BigInt(maxPriceLevel) &&
|
||||||
|
BigInt(bestStaticBidPrice) > minPriceLevel &&
|
||||||
|
horizontalLine(
|
||||||
|
`${(
|
||||||
|
((BigInt(maxPriceLevel) - BigInt(bestStaticBidPrice)) /
|
||||||
|
BigInt(resolution) +
|
||||||
|
BigInt(1)) *
|
||||||
|
BigInt(rowHeight) -
|
||||||
|
BigInt(3)
|
||||||
|
).toString()}px`,
|
||||||
|
'best-static-bid-price'
|
||||||
|
)}
|
||||||
|
{maxPriceLevel &&
|
||||||
|
bestStaticOfferPrice &&
|
||||||
|
BigInt(bestStaticOfferPrice) <= BigInt(maxPriceLevel) &&
|
||||||
|
BigInt(bestStaticOfferPrice) > minPriceLevel &&
|
||||||
|
horizontalLine(
|
||||||
|
`${(
|
||||||
|
((BigInt(maxPriceLevel) - BigInt(bestStaticOfferPrice)) /
|
||||||
|
BigInt(resolution) +
|
||||||
|
BigInt(2)) *
|
||||||
|
BigInt(rowHeight) -
|
||||||
|
BigInt(3)
|
||||||
|
).toString()}px`,
|
||||||
|
'best-static-offer-price'
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
export default Orderbook;
|
export default Orderbook;
|
||||||
|
@ -1 +1,2 @@
|
|||||||
import '@testing-library/jest-dom';
|
import '@testing-library/jest-dom';
|
||||||
|
import 'jest-canvas-mock';
|
||||||
|
3
libs/market-depth/src/styles.scss
Normal file
3
libs/market-depth/src/styles.scss
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
17
libs/market-depth/tailwind.config.js
Normal file
17
libs/market-depth/tailwind.config.js
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
const { join } = require('path');
|
||||||
|
const { createGlobPatternsForDependencies } = require('@nrwl/next/tailwind');
|
||||||
|
const theme = require('../tailwindcss-config/src/theme');
|
||||||
|
const vegaCustomClasses = require('../tailwindcss-config/src/vega-custom-classes');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
join(__dirname, 'src/**/*.{ts,tsx,html,mdx}'),
|
||||||
|
join(__dirname, '.storybook/preview.js'),
|
||||||
|
...createGlobPatternsForDependencies(__dirname),
|
||||||
|
],
|
||||||
|
darkMode: 'class',
|
||||||
|
theme: {
|
||||||
|
extend: theme,
|
||||||
|
},
|
||||||
|
plugins: [vegaCustomClasses],
|
||||||
|
};
|
@ -20,6 +20,9 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"path": "./tsconfig.spec.json"
|
"path": "./tsconfig.spec.json"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"path": "./.storybook/tsconfig.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,11 @@
|
|||||||
"**/*.spec.js",
|
"**/*.spec.js",
|
||||||
"**/*.test.js",
|
"**/*.test.js",
|
||||||
"**/*.spec.jsx",
|
"**/*.spec.jsx",
|
||||||
"**/*.test.jsx"
|
"**/*.test.jsx",
|
||||||
|
"**/*.stories.ts",
|
||||||
|
"**/*.stories.js",
|
||||||
|
"**/*.stories.jsx",
|
||||||
|
"**/*.stories.tsx"
|
||||||
],
|
],
|
||||||
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
"include": ["**/*.js", "**/*.jsx", "**/*.ts", "**/*.tsx"]
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"outDir": "../../dist/out-tsc",
|
"outDir": "../../dist/out-tsc",
|
||||||
"module": "commonjs",
|
"module": "commonjs",
|
||||||
"types": ["jest", "node"]
|
"types": ["jest", "node", "@testing-library/jest-dom"]
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"**/*.test.ts",
|
"**/*.test.ts",
|
||||||
|
@ -20,16 +20,16 @@ export function useDataProvider<Data, Delta>(
|
|||||||
const [loading, setLoading] = useState<boolean>(true);
|
const [loading, setLoading] = useState<boolean>(true);
|
||||||
const [error, setError] = useState<Error | undefined>(undefined);
|
const [error, setError] = useState<Error | undefined>(undefined);
|
||||||
const flushRef = useRef<(() => void) | undefined>(undefined);
|
const flushRef = useRef<(() => void) | undefined>(undefined);
|
||||||
const restartRef = useRef<((force?: boolean) => void) | undefined>(undefined);
|
const reloadRef = useRef<((force?: boolean) => void) | undefined>(undefined);
|
||||||
const initialized = useRef<boolean>(false);
|
const initialized = useRef<boolean>(false);
|
||||||
const flush = useCallback(() => {
|
const flush = useCallback(() => {
|
||||||
if (flushRef.current) {
|
if (flushRef.current) {
|
||||||
flushRef.current();
|
flushRef.current();
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const restart = useCallback((force = false) => {
|
const reload = useCallback((force = false) => {
|
||||||
if (restartRef.current) {
|
if (reloadRef.current) {
|
||||||
restartRef.current(force);
|
reloadRef.current(force);
|
||||||
}
|
}
|
||||||
}, []);
|
}, []);
|
||||||
const callback = useCallback(
|
const callback = useCallback(
|
||||||
@ -48,14 +48,14 @@ export function useDataProvider<Data, Delta>(
|
|||||||
[update]
|
[update]
|
||||||
);
|
);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const { unsubscribe, flush, restart } = dataProvider(
|
const { unsubscribe, flush, reload } = dataProvider(
|
||||||
callback,
|
callback,
|
||||||
client,
|
client,
|
||||||
variables
|
variables
|
||||||
);
|
);
|
||||||
flushRef.current = flush;
|
flushRef.current = flush;
|
||||||
restartRef.current = restart;
|
reloadRef.current = reload;
|
||||||
return unsubscribe;
|
return unsubscribe;
|
||||||
}, [client, initialized, dataProvider, callback, variables]);
|
}, [client, initialized, dataProvider, callback, variables]);
|
||||||
return { data, loading, error, flush, restart };
|
return { data, loading, error, flush, reload };
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ export interface Subscribe<Data, Delta> {
|
|||||||
variables?: OperationVariables
|
variables?: OperationVariables
|
||||||
): {
|
): {
|
||||||
unsubscribe: () => void;
|
unsubscribe: () => void;
|
||||||
restart: (force?: boolean) => void;
|
reload: (forceReset?: boolean) => void;
|
||||||
flush: () => void;
|
flush: () => void;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -34,7 +34,11 @@ export interface Subscribe<Data, Delta> {
|
|||||||
type Query<Result> = DocumentNode | TypedDocumentNode<Result, any>;
|
type Query<Result> = DocumentNode | TypedDocumentNode<Result, any>;
|
||||||
|
|
||||||
export interface Update<Data, Delta> {
|
export interface Update<Data, Delta> {
|
||||||
(draft: Draft<Data>, delta: Delta, restart: (force?: boolean) => void): void;
|
(
|
||||||
|
draft: Draft<Data>,
|
||||||
|
delta: Delta,
|
||||||
|
reload: (forceReset?: boolean) => void
|
||||||
|
): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface GetData<QueryData, Data> {
|
interface GetData<QueryData, Data> {
|
||||||
@ -47,7 +51,7 @@ interface GetDelta<SubscriptionData, Delta> {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param subscriptionQuery query that will be used for subscription
|
* @param subscriptionQuery query that will be used for subscription
|
||||||
* @param update function that will be executed on each onNext, it should update data base on delta, it can restart data provider
|
* @param update function that will be execued on each onNext, it should update data base on delta, it can reload data provider
|
||||||
* @param getData transforms received query data to format that will be stored in data provider
|
* @param getData transforms received query data to format that will be stored in data provider
|
||||||
* @param getDelta transforms delta data to format that will be stored in data provider
|
* @param getDelta transforms delta data to format that will be stored in data provider
|
||||||
* @param fetchPolicy
|
* @param fetchPolicy
|
||||||
@ -105,7 +109,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
|||||||
while (updateQueue.length) {
|
while (updateQueue.length) {
|
||||||
const delta = updateQueue.shift();
|
const delta = updateQueue.shift();
|
||||||
if (delta) {
|
if (delta) {
|
||||||
update(draft, delta, restart);
|
update(draft, delta, reload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -123,13 +127,13 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// restart function is passed to update and as a returned by subscribe function
|
// reload function is passed to update and as a returned by subscribe function
|
||||||
const restart = (hard = false) => {
|
const reload = (forceReset = false) => {
|
||||||
if (loading) {
|
if (loading) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// hard reset on demand or when there is no apollo subscription yet
|
// hard reset on demand or when there is no apollo subscription yet
|
||||||
if (hard || !subscription) {
|
if (forceReset || !subscription) {
|
||||||
reset();
|
reset();
|
||||||
initialize();
|
initialize();
|
||||||
} else {
|
} else {
|
||||||
@ -165,7 +169,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
|||||||
updateQueue.push(delta);
|
updateQueue.push(delta);
|
||||||
} else {
|
} else {
|
||||||
const newData = produce(data, (draft) => {
|
const newData = produce(data, (draft) => {
|
||||||
update(draft, delta, restart);
|
update(draft, delta, reload);
|
||||||
});
|
});
|
||||||
if (newData === data) {
|
if (newData === data) {
|
||||||
return;
|
return;
|
||||||
@ -174,12 +178,12 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
|||||||
notifyAll(delta);
|
notifyAll(delta);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
() => restart()
|
() => reload()
|
||||||
);
|
);
|
||||||
await initialFetch();
|
await initialFetch();
|
||||||
};
|
};
|
||||||
|
|
||||||
const reset = () => {
|
const reset = (clean = true) => {
|
||||||
if (subscription) {
|
if (subscription) {
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
subscription = undefined;
|
subscription = undefined;
|
||||||
@ -198,7 +202,6 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//
|
|
||||||
return (callback, c, v) => {
|
return (callback, c, v) => {
|
||||||
callbacks.push(callback);
|
callbacks.push(callback);
|
||||||
if (callbacks.length === 1) {
|
if (callbacks.length === 1) {
|
||||||
@ -210,7 +213,7 @@ function makeDataProviderInternal<QueryData, Data, SubscriptionData, Delta>(
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
unsubscribe: () => unsubscribe(callback),
|
unsubscribe: () => unsubscribe(callback),
|
||||||
restart,
|
reload,
|
||||||
flush: () => notify(callback),
|
flush: () => notify(callback),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@ -243,7 +246,7 @@ const memoize = <Data, Delta>(
|
|||||||
/**
|
/**
|
||||||
* @param query Query<QueryData>
|
* @param query Query<QueryData>
|
||||||
* @param subscriptionQuery Query<SubscriptionData> query that will be used for subscription
|
* @param subscriptionQuery Query<SubscriptionData> query that will be used for subscription
|
||||||
* @param update Update<Data, Delta> function that will be executed on each onNext, it should update data base on delta, it can restart data provider
|
* @param update Update<Data, Delta> function that will be executed on each onNext, it should update data base on delta, it can reload data provider
|
||||||
* @param getData transforms received query data to format that will be stored in data provider
|
* @param getData transforms received query data to format that will be stored in data provider
|
||||||
* @param getDelta transforms delta data to format that will be stored in data provider
|
* @param getDelta transforms delta data to format that will be stored in data provider
|
||||||
* @param fetchPolicy
|
* @param fetchPolicy
|
||||||
@ -252,12 +255,12 @@ const memoize = <Data, Delta>(
|
|||||||
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
* const marketMidPriceProvider = makeDataProvider<QueryData, Data, SubscriptionData, Delta>(
|
||||||
* gql`query MarketMidPrice($marketId: ID!) { market(id: $marketId) { data { midPrice } } }`,
|
* gql`query MarketMidPrice($marketId: ID!) { market(id: $marketId) { data { midPrice } } }`,
|
||||||
* gql`subscription MarketMidPriceSubscription($marketId: ID!) { marketDepthUpdate(marketId: $marketId) { market { data { midPrice } } } }`,
|
* gql`subscription MarketMidPriceSubscription($marketId: ID!) { marketDepthUpdate(marketId: $marketId) { market { data { midPrice } } } }`,
|
||||||
* (draft: Draft<Data>, delta: Delta, restart: (force?: boolean) => void) => { draft.midPrice = delta.midPrice }
|
* (draft: Draft<Data>, delta: Delta, reload: (forceReset?: boolean) => void) => { draft.midPrice = delta.midPrice }
|
||||||
* (data:QueryData) => data.market.data.midPrice
|
* (data:QueryData) => data.market.data.midPrice
|
||||||
* (delta:SubscriptionData) => delta.marketData.market
|
* (delta:SubscriptionData) => delta.marketData.market
|
||||||
* )
|
* )
|
||||||
*
|
*
|
||||||
* const { unsubscribe, flush, restart } = marketMidPriceProvider(
|
* const { unsubscribe, flush, reload } = marketMidPriceProvider(
|
||||||
* ({ data, error, loading, delta }) => { ... },
|
* ({ data, error, loading, delta }) => { ... },
|
||||||
* apolloClient,
|
* apolloClient,
|
||||||
* { id: '1fd726454fa1220038acbf6ff9ac701d8b8bf3f2d77c93a4998544471dc58747' }
|
* { id: '1fd726454fa1220038acbf6ff9ac701d8b8bf3f2d77c93a4998544471dc58747' }
|
||||||
|
@ -1,13 +1,16 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { ICellRendererParams } from 'ag-grid-community';
|
import type { ICellRendererParams } from 'ag-grid-community';
|
||||||
|
import classNames from 'classnames';
|
||||||
import { BID_COLOR, ASK_COLOR } from './vol-cell';
|
import { BID_COLOR, ASK_COLOR } from './vol-cell';
|
||||||
|
|
||||||
const INTERSECT_COLOR = 'darkgray';
|
|
||||||
|
|
||||||
export interface CumulativeVolProps {
|
export interface CumulativeVolProps {
|
||||||
|
ask?: number;
|
||||||
|
bid?: number;
|
||||||
relativeAsk?: number;
|
relativeAsk?: number;
|
||||||
relativeBid?: number;
|
relativeBid?: number;
|
||||||
|
indicativeVolume?: string;
|
||||||
|
testId?: string;
|
||||||
|
className?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ICumulativeVolCellProps extends ICellRendererParams {
|
export interface ICumulativeVolCellProps extends ICellRendererParams {
|
||||||
@ -15,44 +18,57 @@ export interface ICumulativeVolCellProps extends ICellRendererParams {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const CumulativeVol = React.memo(
|
export const CumulativeVol = React.memo(
|
||||||
({ relativeAsk, relativeBid }: CumulativeVolProps) => {
|
({
|
||||||
const bid = relativeBid ? (
|
relativeAsk,
|
||||||
|
relativeBid,
|
||||||
|
ask,
|
||||||
|
bid,
|
||||||
|
indicativeVolume,
|
||||||
|
testId,
|
||||||
|
className,
|
||||||
|
}: CumulativeVolProps) => {
|
||||||
|
const askBar = relativeAsk ? (
|
||||||
<div
|
<div
|
||||||
className="h-full absolute top-0 right-0"
|
data-testid="ask-bar"
|
||||||
style={{
|
className="absolute left-0 top-0"
|
||||||
width: `${relativeBid}%`,
|
|
||||||
backgroundColor:
|
|
||||||
relativeAsk && relativeAsk > relativeBid
|
|
||||||
? INTERSECT_COLOR
|
|
||||||
: BID_COLOR,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
) : null;
|
|
||||||
const ask = relativeAsk ? (
|
|
||||||
<div
|
|
||||||
className="h-full absolute top-0 left-0"
|
|
||||||
style={{
|
style={{
|
||||||
|
height: relativeBid && relativeAsk ? '50%' : '100%',
|
||||||
width: `${relativeAsk}%`,
|
width: `${relativeAsk}%`,
|
||||||
backgroundColor:
|
backgroundColor: ASK_COLOR,
|
||||||
relativeBid && relativeBid > relativeAsk
|
|
||||||
? INTERSECT_COLOR
|
|
||||||
: ASK_COLOR,
|
|
||||||
}}
|
}}
|
||||||
></div>
|
></div>
|
||||||
) : null;
|
) : null;
|
||||||
|
const bidBar = relativeBid ? (
|
||||||
|
<div
|
||||||
|
data-testid="bid-bar"
|
||||||
|
className="absolute top-0 left-0"
|
||||||
|
style={{
|
||||||
|
height: relativeBid && relativeAsk ? '50%' : '100%',
|
||||||
|
top: relativeBid && relativeAsk ? '50%' : '0',
|
||||||
|
width: `${relativeBid}%`,
|
||||||
|
backgroundColor: BID_COLOR,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
) : null;
|
||||||
|
|
||||||
|
const volume = indicativeVolume ? (
|
||||||
|
<span className="relative">({indicativeVolume})</span>
|
||||||
|
) : (
|
||||||
|
<span className="relative">
|
||||||
|
{ask ? ask : null}
|
||||||
|
{ask && bid ? '/' : null}
|
||||||
|
{bid ? bid : null}
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full relative" data-testid="vol">
|
<div
|
||||||
{relativeBid && relativeAsk && relativeBid > relativeAsk ? (
|
className={classNames('h-full relative', className)}
|
||||||
<>
|
data-testid={testId || 'cummulative-vol'}
|
||||||
{ask}
|
>
|
||||||
{bid}
|
{askBar}
|
||||||
</>
|
{bidBar}
|
||||||
) : (
|
{volume}
|
||||||
<>
|
|
||||||
{bid}
|
|
||||||
{ask}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,11 @@ import React from 'react';
|
|||||||
export interface IPriceCellProps {
|
export interface IPriceCellProps {
|
||||||
value: number | bigint | null | undefined;
|
value: number | bigint | null | undefined;
|
||||||
valueFormatted: string;
|
valueFormatted: string;
|
||||||
|
testId?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const PriceCell = React.memo(
|
export const PriceCell = React.memo(
|
||||||
({ value, valueFormatted }: IPriceCellProps) => {
|
({ value, valueFormatted, testId }: IPriceCellProps) => {
|
||||||
if (
|
if (
|
||||||
(!value && value !== 0) ||
|
(!value && value !== 0) ||
|
||||||
(typeof value === 'number' && isNaN(Number(value)))
|
(typeof value === 'number' && isNaN(Number(value)))
|
||||||
@ -13,7 +14,10 @@ export const PriceCell = React.memo(
|
|||||||
return <span data-testid="price">-</span>;
|
return <span data-testid="price">-</span>;
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<span className="font-mono relative text-ui-small" data-testid="price">
|
<span
|
||||||
|
className="font-mono relative text-ui-small"
|
||||||
|
data-testid={testId || 'price'}
|
||||||
|
>
|
||||||
{valueFormatted}
|
{valueFormatted}
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import { render, screen } from '@testing-library/react';
|
import { render, screen } from '@testing-library/react';
|
||||||
import '@testing-library/jest-dom/extend-expect';
|
import '@testing-library/jest-dom/extend-expect';
|
||||||
import * as React from 'react';
|
|
||||||
|
|
||||||
import { PriceFlashCell } from './price-flash-cell';
|
import { PriceFlashCell } from './price-flash-cell';
|
||||||
|
|
||||||
|
@ -1,11 +1,17 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import type { ICellRendererParams } from 'ag-grid-community';
|
import type { ICellRendererParams } from 'ag-grid-community';
|
||||||
import { PriceCell } from './price-cell';
|
import { PriceCell } from './price-cell';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
export enum VolumeType {
|
||||||
|
bid,
|
||||||
|
ask,
|
||||||
|
}
|
||||||
export interface VolProps {
|
export interface VolProps {
|
||||||
value: number | bigint | null | undefined;
|
value: number | bigint | null | undefined;
|
||||||
relativeValue?: number;
|
relativeValue?: number;
|
||||||
type: 'bid' | 'ask';
|
type: VolumeType;
|
||||||
|
testId?: string;
|
||||||
}
|
}
|
||||||
export interface IVolCellProps extends ICellRendererParams {
|
export interface IVolCellProps extends ICellRendererParams {
|
||||||
value: number | bigint | null | undefined;
|
value: number | bigint | null | undefined;
|
||||||
@ -15,23 +21,28 @@ export interface IVolCellProps extends ICellRendererParams {
|
|||||||
export const BID_COLOR = 'darkgreen';
|
export const BID_COLOR = 'darkgreen';
|
||||||
export const ASK_COLOR = 'maroon';
|
export const ASK_COLOR = 'maroon';
|
||||||
|
|
||||||
export const Vol = React.memo(({ value, relativeValue, type }: VolProps) => {
|
export const Vol = React.memo(
|
||||||
if ((!value && value !== 0) || isNaN(Number(value))) {
|
({ value, relativeValue, type, testId }: VolProps) => {
|
||||||
return <div data-testid="vol">-</div>;
|
if ((!value && value !== 0) || isNaN(Number(value))) {
|
||||||
|
return <div data-testid="vol">-</div>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div className="relative" data-testid={testId || 'vol'}>
|
||||||
|
<div
|
||||||
|
className={classNames('h-full absolute top-0', {
|
||||||
|
'left-0': type === VolumeType.bid,
|
||||||
|
'right-0': type === VolumeType.ask,
|
||||||
|
})}
|
||||||
|
style={{
|
||||||
|
width: relativeValue ? `${relativeValue}%` : '0%',
|
||||||
|
backgroundColor: type === VolumeType.bid ? BID_COLOR : ASK_COLOR,
|
||||||
|
}}
|
||||||
|
></div>
|
||||||
|
<PriceCell value={value} valueFormatted={value.toString()} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return (
|
);
|
||||||
<div className="relative" data-testid="vol">
|
|
||||||
<div
|
|
||||||
className="h-full absolute top-0 left-0"
|
|
||||||
style={{
|
|
||||||
width: relativeValue ? `${relativeValue}%` : '0%',
|
|
||||||
backgroundColor: type === 'bid' ? BID_COLOR : ASK_COLOR,
|
|
||||||
}}
|
|
||||||
></div>
|
|
||||||
<PriceCell value={value} valueFormatted={value.toString()} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
Vol.displayName = 'Vol';
|
Vol.displayName = 'Vol';
|
||||||
|
|
||||||
|
@ -79,6 +79,7 @@ module.exports = {
|
|||||||
0: '0px',
|
0: '0px',
|
||||||
2: '0.125rem',
|
2: '0.125rem',
|
||||||
4: '0.25rem',
|
4: '0.25rem',
|
||||||
|
5: '0.3125rem',
|
||||||
8: '0.5rem',
|
8: '0.5rem',
|
||||||
12: '0.75rem',
|
12: '0.75rem',
|
||||||
16: '1rem',
|
16: '1rem',
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import '../src/styles.scss';
|
import '../src/styles.scss';
|
||||||
export const parameters = {
|
export const parameters = {
|
||||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||||
|
backgrounds: { disable: true },
|
||||||
/*themes: {
|
/*themes: {
|
||||||
default: 'dark',
|
default: 'dark',
|
||||||
list: [
|
list: [
|
||||||
|
@ -97,6 +97,7 @@
|
|||||||
"@storybook/addon-a11y": "^6.4.19",
|
"@storybook/addon-a11y": "^6.4.19",
|
||||||
"@storybook/addon-essentials": "~6.4.12",
|
"@storybook/addon-essentials": "~6.4.12",
|
||||||
"@storybook/builder-webpack5": "~6.4.12",
|
"@storybook/builder-webpack5": "~6.4.12",
|
||||||
|
"@storybook/core-server": "~6.4.12",
|
||||||
"@storybook/manager-webpack5": "~6.4.12",
|
"@storybook/manager-webpack5": "~6.4.12",
|
||||||
"@storybook/react": "~6.4.12",
|
"@storybook/react": "~6.4.12",
|
||||||
"@svgr/webpack": "^5.4.0",
|
"@svgr/webpack": "^5.4.0",
|
||||||
|
536
yarn.lock
536
yarn.lock
@ -4158,6 +4158,23 @@
|
|||||||
global "^4.4.0"
|
global "^4.4.0"
|
||||||
regenerator-runtime "^0.13.7"
|
regenerator-runtime "^0.13.7"
|
||||||
|
|
||||||
|
"@storybook/addons@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/addons/-/addons-6.4.22.tgz#e165407ca132c2182de2d466b7ff7c5644b6ad7b"
|
||||||
|
integrity sha512-P/R+Jsxh7pawKLYo8MtE3QU/ilRFKbtCewV/T1o5U/gm8v7hKQdFz3YdRMAra4QuCY8bQIp7MKd2HrB5aH5a1A==
|
||||||
|
dependencies:
|
||||||
|
"@storybook/api" "6.4.22"
|
||||||
|
"@storybook/channels" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
"@storybook/csf" "0.0.2--canary.87bc651.0"
|
||||||
|
"@storybook/router" "6.4.22"
|
||||||
|
"@storybook/theming" "6.4.22"
|
||||||
|
"@types/webpack-env" "^1.16.0"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
global "^4.4.0"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
|
||||||
"@storybook/api@6.4.21", "@storybook/api@^6.0.0":
|
"@storybook/api@6.4.21", "@storybook/api@^6.0.0":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.4.21.tgz#efee41ae7bde37f6fe43ee960fef1a261b1b1dd6"
|
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.4.21.tgz#efee41ae7bde37f6fe43ee960fef1a261b1b1dd6"
|
||||||
@ -4181,6 +4198,29 @@
|
|||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
|
"@storybook/api@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/api/-/api-6.4.22.tgz#d63f7ad3ffdd74af01ae35099bff4c39702cf793"
|
||||||
|
integrity sha512-lAVI3o2hKupYHXFTt+1nqFct942up5dHH6YD7SZZJGyW21dwKC3HK1IzCsTawq3fZAKkgWFgmOO649hKk60yKg==
|
||||||
|
dependencies:
|
||||||
|
"@storybook/channels" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
"@storybook/csf" "0.0.2--canary.87bc651.0"
|
||||||
|
"@storybook/router" "6.4.22"
|
||||||
|
"@storybook/semver" "^7.3.2"
|
||||||
|
"@storybook/theming" "6.4.22"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
fast-deep-equal "^3.1.3"
|
||||||
|
global "^4.4.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
memoizerific "^1.11.3"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
store2 "^2.12.0"
|
||||||
|
telejson "^5.3.2"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@storybook/builder-webpack4@6.4.21":
|
"@storybook/builder-webpack4@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.4.21.tgz#5355ab1bfe7ee153e907d8e64c6088fdb7a95676"
|
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.4.21.tgz#5355ab1bfe7ee153e907d8e64c6088fdb7a95676"
|
||||||
@ -4256,6 +4296,81 @@
|
|||||||
webpack-hot-middleware "^2.25.1"
|
webpack-hot-middleware "^2.25.1"
|
||||||
webpack-virtual-modules "^0.2.2"
|
webpack-virtual-modules "^0.2.2"
|
||||||
|
|
||||||
|
"@storybook/builder-webpack4@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack4/-/builder-webpack4-6.4.22.tgz#d3384b146e97a2b3a6357c6eb8279ff0f1c7f8f5"
|
||||||
|
integrity sha512-A+GgGtKGnBneRFSFkDarUIgUTI8pYFdLmUVKEAGdh2hL+vLXAz9A46sEY7C8LQ85XWa8TKy3OTDxqR4+4iWj3A==
|
||||||
|
dependencies:
|
||||||
|
"@babel/core" "^7.12.10"
|
||||||
|
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
||||||
|
"@babel/plugin-proposal-decorators" "^7.12.12"
|
||||||
|
"@babel/plugin-proposal-export-default-from" "^7.12.1"
|
||||||
|
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1"
|
||||||
|
"@babel/plugin-proposal-object-rest-spread" "^7.12.1"
|
||||||
|
"@babel/plugin-proposal-optional-chaining" "^7.12.7"
|
||||||
|
"@babel/plugin-proposal-private-methods" "^7.12.1"
|
||||||
|
"@babel/plugin-syntax-dynamic-import" "^7.8.3"
|
||||||
|
"@babel/plugin-transform-arrow-functions" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-block-scoping" "^7.12.12"
|
||||||
|
"@babel/plugin-transform-classes" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-destructuring" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-for-of" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-parameters" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-shorthand-properties" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-spread" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-template-literals" "^7.12.1"
|
||||||
|
"@babel/preset-env" "^7.12.11"
|
||||||
|
"@babel/preset-react" "^7.12.10"
|
||||||
|
"@babel/preset-typescript" "^7.12.7"
|
||||||
|
"@storybook/addons" "6.4.22"
|
||||||
|
"@storybook/api" "6.4.22"
|
||||||
|
"@storybook/channel-postmessage" "6.4.22"
|
||||||
|
"@storybook/channels" "6.4.22"
|
||||||
|
"@storybook/client-api" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/components" "6.4.22"
|
||||||
|
"@storybook/core-common" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
"@storybook/node-logger" "6.4.22"
|
||||||
|
"@storybook/preview-web" "6.4.22"
|
||||||
|
"@storybook/router" "6.4.22"
|
||||||
|
"@storybook/semver" "^7.3.2"
|
||||||
|
"@storybook/store" "6.4.22"
|
||||||
|
"@storybook/theming" "6.4.22"
|
||||||
|
"@storybook/ui" "6.4.22"
|
||||||
|
"@types/node" "^14.0.10"
|
||||||
|
"@types/webpack" "^4.41.26"
|
||||||
|
autoprefixer "^9.8.6"
|
||||||
|
babel-loader "^8.0.0"
|
||||||
|
babel-plugin-macros "^2.8.0"
|
||||||
|
babel-plugin-polyfill-corejs3 "^0.1.0"
|
||||||
|
case-sensitive-paths-webpack-plugin "^2.3.0"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
css-loader "^3.6.0"
|
||||||
|
file-loader "^6.2.0"
|
||||||
|
find-up "^5.0.0"
|
||||||
|
fork-ts-checker-webpack-plugin "^4.1.6"
|
||||||
|
glob "^7.1.6"
|
||||||
|
glob-promise "^3.4.0"
|
||||||
|
global "^4.4.0"
|
||||||
|
html-webpack-plugin "^4.0.0"
|
||||||
|
pnp-webpack-plugin "1.6.4"
|
||||||
|
postcss "^7.0.36"
|
||||||
|
postcss-flexbugs-fixes "^4.2.1"
|
||||||
|
postcss-loader "^4.2.0"
|
||||||
|
raw-loader "^4.0.2"
|
||||||
|
stable "^0.1.8"
|
||||||
|
style-loader "^1.3.0"
|
||||||
|
terser-webpack-plugin "^4.2.3"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
url-loader "^4.1.1"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
webpack "4"
|
||||||
|
webpack-dev-middleware "^3.7.3"
|
||||||
|
webpack-filter-warnings-plugin "^1.2.1"
|
||||||
|
webpack-hot-middleware "^2.25.1"
|
||||||
|
webpack-virtual-modules "^0.2.2"
|
||||||
|
|
||||||
"@storybook/builder-webpack5@~6.4.12":
|
"@storybook/builder-webpack5@~6.4.12":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-6.4.21.tgz#d601676083a263a1f03847b12fe2ad1ecd3865bb"
|
resolved "https://registry.yarnpkg.com/@storybook/builder-webpack5/-/builder-webpack5-6.4.21.tgz#d601676083a263a1f03847b12fe2ad1ecd3865bb"
|
||||||
@ -4332,6 +4447,19 @@
|
|||||||
qs "^6.10.0"
|
qs "^6.10.0"
|
||||||
telejson "^5.3.2"
|
telejson "^5.3.2"
|
||||||
|
|
||||||
|
"@storybook/channel-postmessage@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/channel-postmessage/-/channel-postmessage-6.4.22.tgz#8be0be1ea1e667a49fb0f09cdfdeeb4a45829637"
|
||||||
|
integrity sha512-gt+0VZLszt2XZyQMh8E94TqjHZ8ZFXZ+Lv/Mmzl0Yogsc2H+6VzTTQO4sv0IIx6xLbpgG72g5cr8VHsxW5kuDQ==
|
||||||
|
dependencies:
|
||||||
|
"@storybook/channels" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
global "^4.4.0"
|
||||||
|
qs "^6.10.0"
|
||||||
|
telejson "^5.3.2"
|
||||||
|
|
||||||
"@storybook/channel-websocket@6.4.21":
|
"@storybook/channel-websocket@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.4.21.tgz#46db7dbfb9a37907ab12ba2632c46070557b5a97"
|
resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.4.21.tgz#46db7dbfb9a37907ab12ba2632c46070557b5a97"
|
||||||
@ -4343,6 +4471,17 @@
|
|||||||
global "^4.4.0"
|
global "^4.4.0"
|
||||||
telejson "^5.3.2"
|
telejson "^5.3.2"
|
||||||
|
|
||||||
|
"@storybook/channel-websocket@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/channel-websocket/-/channel-websocket-6.4.22.tgz#d541f69125873123c453757e2b879a75a9266c65"
|
||||||
|
integrity sha512-Bm/FcZ4Su4SAK5DmhyKKfHkr7HiHBui6PNutmFkASJInrL9wBduBfN8YQYaV7ztr8ezoHqnYRx8sj28jpwa6NA==
|
||||||
|
dependencies:
|
||||||
|
"@storybook/channels" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
global "^4.4.0"
|
||||||
|
telejson "^5.3.2"
|
||||||
|
|
||||||
"@storybook/channels@6.4.21":
|
"@storybook/channels@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.4.21.tgz#0f1924963f77ec0c3d82aa643a246824ca9f5fca"
|
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.4.21.tgz#0f1924963f77ec0c3d82aa643a246824ca9f5fca"
|
||||||
@ -4352,6 +4491,15 @@
|
|||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
|
"@storybook/channels@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/channels/-/channels-6.4.22.tgz#710f732763d63f063f615898ab1afbe74e309596"
|
||||||
|
integrity sha512-cfR74tu7MLah1A8Rru5sak71I+kH2e/sY6gkpVmlvBj4hEmdZp4Puj9PTeaKcMXh9DgIDPNA5mb8yvQH6VcyxQ==
|
||||||
|
dependencies:
|
||||||
|
core-js "^3.8.2"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@storybook/client-api@6.4.21":
|
"@storybook/client-api@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.4.21.tgz#6dcf41a9e55b5e38638cd4d032f1ceaec305e0eb"
|
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.4.21.tgz#6dcf41a9e55b5e38638cd4d032f1ceaec305e0eb"
|
||||||
@ -4378,6 +4526,32 @@
|
|||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
|
"@storybook/client-api@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/client-api/-/client-api-6.4.22.tgz#df14f85e7900b94354c26c584bab53a67c47eae9"
|
||||||
|
integrity sha512-sO6HJNtrrdit7dNXQcZMdlmmZG1k6TswH3gAyP/DoYajycrTwSJ6ovkarzkO+0QcJ+etgra4TEdTIXiGHBMe/A==
|
||||||
|
dependencies:
|
||||||
|
"@storybook/addons" "6.4.22"
|
||||||
|
"@storybook/channel-postmessage" "6.4.22"
|
||||||
|
"@storybook/channels" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
"@storybook/csf" "0.0.2--canary.87bc651.0"
|
||||||
|
"@storybook/store" "6.4.22"
|
||||||
|
"@types/qs" "^6.9.5"
|
||||||
|
"@types/webpack-env" "^1.16.0"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
fast-deep-equal "^3.1.3"
|
||||||
|
global "^4.4.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
memoizerific "^1.11.3"
|
||||||
|
qs "^6.10.0"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
store2 "^2.12.0"
|
||||||
|
synchronous-promise "^2.0.15"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@storybook/client-logger@6.4.21":
|
"@storybook/client-logger@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.21.tgz#7df21cec4d5426669e828af59232ec44ea19c81a"
|
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.21.tgz#7df21cec4d5426669e828af59232ec44ea19c81a"
|
||||||
@ -4386,6 +4560,14 @@
|
|||||||
core-js "^3.8.2"
|
core-js "^3.8.2"
|
||||||
global "^4.4.0"
|
global "^4.4.0"
|
||||||
|
|
||||||
|
"@storybook/client-logger@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/client-logger/-/client-logger-6.4.22.tgz#51abedb7d3c9bc21921aeb153ac8a19abc625cd6"
|
||||||
|
integrity sha512-LXhxh/lcDsdGnK8kimqfhu3C0+D2ylCSPPQNbU0IsLRmTfbpQYMdyl0XBjPdHiRVwlL7Gkw5OMjYemQgJ02zlw==
|
||||||
|
dependencies:
|
||||||
|
core-js "^3.8.2"
|
||||||
|
global "^4.4.0"
|
||||||
|
|
||||||
"@storybook/components@6.4.21", "@storybook/components@^6.0.0":
|
"@storybook/components@6.4.21", "@storybook/components@^6.0.0":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.4.21.tgz#77483ef429f96d94cf7d2d8c1af8441ef855a77d"
|
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.4.21.tgz#77483ef429f96d94cf7d2d8c1af8441ef855a77d"
|
||||||
@ -4416,6 +4598,36 @@
|
|||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
|
"@storybook/components@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/components/-/components-6.4.22.tgz#4d425280240702883225b6a1f1abde7dc1a0e945"
|
||||||
|
integrity sha512-dCbXIJF9orMvH72VtAfCQsYbe57OP7fAADtR6YTwfCw9Sm1jFuZr8JbblQ1HcrXEoJG21nOyad3Hm5EYVb/sBw==
|
||||||
|
dependencies:
|
||||||
|
"@popperjs/core" "^2.6.0"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/csf" "0.0.2--canary.87bc651.0"
|
||||||
|
"@storybook/theming" "6.4.22"
|
||||||
|
"@types/color-convert" "^2.0.0"
|
||||||
|
"@types/overlayscrollbars" "^1.12.0"
|
||||||
|
"@types/react-syntax-highlighter" "11.0.5"
|
||||||
|
color-convert "^2.0.1"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
fast-deep-equal "^3.1.3"
|
||||||
|
global "^4.4.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
markdown-to-jsx "^7.1.3"
|
||||||
|
memoizerific "^1.11.3"
|
||||||
|
overlayscrollbars "^1.13.1"
|
||||||
|
polished "^4.0.5"
|
||||||
|
prop-types "^15.7.2"
|
||||||
|
react-colorful "^5.1.2"
|
||||||
|
react-popper-tooltip "^3.1.1"
|
||||||
|
react-syntax-highlighter "^13.5.3"
|
||||||
|
react-textarea-autosize "^8.3.0"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@storybook/core-client@6.4.21":
|
"@storybook/core-client@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.4.21.tgz#4882092315c884dca6118202c83a5e6758b7de57"
|
resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.4.21.tgz#4882092315c884dca6118202c83a5e6758b7de57"
|
||||||
@ -4442,6 +4654,32 @@
|
|||||||
unfetch "^4.2.0"
|
unfetch "^4.2.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
|
"@storybook/core-client@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/core-client/-/core-client-6.4.22.tgz#9079eda8a9c8e6ba24b84962a749b1c99668cb2a"
|
||||||
|
integrity sha512-uHg4yfCBeM6eASSVxStWRVTZrAnb4FT6X6v/xDqr4uXCpCttZLlBzrSDwPBLNNLtCa7ntRicHM8eGKIOD5lMYQ==
|
||||||
|
dependencies:
|
||||||
|
"@storybook/addons" "6.4.22"
|
||||||
|
"@storybook/channel-postmessage" "6.4.22"
|
||||||
|
"@storybook/channel-websocket" "6.4.22"
|
||||||
|
"@storybook/client-api" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
"@storybook/csf" "0.0.2--canary.87bc651.0"
|
||||||
|
"@storybook/preview-web" "6.4.22"
|
||||||
|
"@storybook/store" "6.4.22"
|
||||||
|
"@storybook/ui" "6.4.22"
|
||||||
|
airbnb-js-shims "^2.2.1"
|
||||||
|
ansi-to-html "^0.6.11"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
global "^4.4.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
qs "^6.10.0"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
unfetch "^4.2.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@storybook/core-common@6.4.21":
|
"@storybook/core-common@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.4.21.tgz#7151eeb5f628bec1dc1461df2de4c51fec15ac4c"
|
resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.4.21.tgz#7151eeb5f628bec1dc1461df2de4c51fec15ac4c"
|
||||||
@ -4497,6 +4735,61 @@
|
|||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
webpack "4"
|
webpack "4"
|
||||||
|
|
||||||
|
"@storybook/core-common@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/core-common/-/core-common-6.4.22.tgz#b00fa3c0625e074222a50be3196cb8052dd7f3bf"
|
||||||
|
integrity sha512-PD3N/FJXPNRHeQS2zdgzYFtqPLdi3MLwAicbnw+U3SokcsspfsAuyYHZOYZgwO8IAEKy6iCc7TpBdiSJZ/vAKQ==
|
||||||
|
dependencies:
|
||||||
|
"@babel/core" "^7.12.10"
|
||||||
|
"@babel/plugin-proposal-class-properties" "^7.12.1"
|
||||||
|
"@babel/plugin-proposal-decorators" "^7.12.12"
|
||||||
|
"@babel/plugin-proposal-export-default-from" "^7.12.1"
|
||||||
|
"@babel/plugin-proposal-nullish-coalescing-operator" "^7.12.1"
|
||||||
|
"@babel/plugin-proposal-object-rest-spread" "^7.12.1"
|
||||||
|
"@babel/plugin-proposal-optional-chaining" "^7.12.7"
|
||||||
|
"@babel/plugin-proposal-private-methods" "^7.12.1"
|
||||||
|
"@babel/plugin-syntax-dynamic-import" "^7.8.3"
|
||||||
|
"@babel/plugin-transform-arrow-functions" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-block-scoping" "^7.12.12"
|
||||||
|
"@babel/plugin-transform-classes" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-destructuring" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-for-of" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-parameters" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-shorthand-properties" "^7.12.1"
|
||||||
|
"@babel/plugin-transform-spread" "^7.12.1"
|
||||||
|
"@babel/preset-env" "^7.12.11"
|
||||||
|
"@babel/preset-react" "^7.12.10"
|
||||||
|
"@babel/preset-typescript" "^7.12.7"
|
||||||
|
"@babel/register" "^7.12.1"
|
||||||
|
"@storybook/node-logger" "6.4.22"
|
||||||
|
"@storybook/semver" "^7.3.2"
|
||||||
|
"@types/node" "^14.0.10"
|
||||||
|
"@types/pretty-hrtime" "^1.0.0"
|
||||||
|
babel-loader "^8.0.0"
|
||||||
|
babel-plugin-macros "^3.0.1"
|
||||||
|
babel-plugin-polyfill-corejs3 "^0.1.0"
|
||||||
|
chalk "^4.1.0"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
express "^4.17.1"
|
||||||
|
file-system-cache "^1.0.5"
|
||||||
|
find-up "^5.0.0"
|
||||||
|
fork-ts-checker-webpack-plugin "^6.0.4"
|
||||||
|
fs-extra "^9.0.1"
|
||||||
|
glob "^7.1.6"
|
||||||
|
handlebars "^4.7.7"
|
||||||
|
interpret "^2.2.0"
|
||||||
|
json5 "^2.1.3"
|
||||||
|
lazy-universal-dotenv "^3.0.1"
|
||||||
|
picomatch "^2.3.0"
|
||||||
|
pkg-dir "^5.0.0"
|
||||||
|
pretty-hrtime "^1.0.3"
|
||||||
|
resolve-from "^5.0.0"
|
||||||
|
slash "^3.0.0"
|
||||||
|
telejson "^5.3.2"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
webpack "4"
|
||||||
|
|
||||||
"@storybook/core-events@6.4.21", "@storybook/core-events@^6.0.0":
|
"@storybook/core-events@6.4.21", "@storybook/core-events@^6.0.0":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.21.tgz#28fff8b10c0d564259edf4439ff8677615ce59c0"
|
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.21.tgz#28fff8b10c0d564259edf4439ff8677615ce59c0"
|
||||||
@ -4504,6 +4797,13 @@
|
|||||||
dependencies:
|
dependencies:
|
||||||
core-js "^3.8.2"
|
core-js "^3.8.2"
|
||||||
|
|
||||||
|
"@storybook/core-events@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/core-events/-/core-events-6.4.22.tgz#c09b0571951affd4254028b8958a4d8652700989"
|
||||||
|
integrity sha512-5GYY5+1gd58Gxjqex27RVaX6qbfIQmJxcbzbNpXGNSqwqAuIIepcV1rdCVm6I4C3Yb7/AQ3cN5dVbf33QxRIwA==
|
||||||
|
dependencies:
|
||||||
|
core-js "^3.8.2"
|
||||||
|
|
||||||
"@storybook/core-server@6.4.21":
|
"@storybook/core-server@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.4.21.tgz#3f60c68bb21fd1b07113b2bbaefd6e0498bdbd68"
|
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.4.21.tgz#3f60c68bb21fd1b07113b2bbaefd6e0498bdbd68"
|
||||||
@ -4552,6 +4852,54 @@
|
|||||||
webpack "4"
|
webpack "4"
|
||||||
ws "^8.2.3"
|
ws "^8.2.3"
|
||||||
|
|
||||||
|
"@storybook/core-server@~6.4.12":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/core-server/-/core-server-6.4.22.tgz#254409ec2ba49a78b23f5e4a4c0faea5a570a32b"
|
||||||
|
integrity sha512-wFh3e2fa0un1d4+BJP+nd3FVWUO7uHTqv3OGBfOmzQMKp4NU1zaBNdSQG7Hz6mw0fYPBPZgBjPfsJRwIYLLZyw==
|
||||||
|
dependencies:
|
||||||
|
"@discoveryjs/json-ext" "^0.5.3"
|
||||||
|
"@storybook/builder-webpack4" "6.4.22"
|
||||||
|
"@storybook/core-client" "6.4.22"
|
||||||
|
"@storybook/core-common" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
"@storybook/csf" "0.0.2--canary.87bc651.0"
|
||||||
|
"@storybook/csf-tools" "6.4.22"
|
||||||
|
"@storybook/manager-webpack4" "6.4.22"
|
||||||
|
"@storybook/node-logger" "6.4.22"
|
||||||
|
"@storybook/semver" "^7.3.2"
|
||||||
|
"@storybook/store" "6.4.22"
|
||||||
|
"@types/node" "^14.0.10"
|
||||||
|
"@types/node-fetch" "^2.5.7"
|
||||||
|
"@types/pretty-hrtime" "^1.0.0"
|
||||||
|
"@types/webpack" "^4.41.26"
|
||||||
|
better-opn "^2.1.1"
|
||||||
|
boxen "^5.1.2"
|
||||||
|
chalk "^4.1.0"
|
||||||
|
cli-table3 "^0.6.1"
|
||||||
|
commander "^6.2.1"
|
||||||
|
compression "^1.7.4"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
cpy "^8.1.2"
|
||||||
|
detect-port "^1.3.0"
|
||||||
|
express "^4.17.1"
|
||||||
|
file-system-cache "^1.0.5"
|
||||||
|
fs-extra "^9.0.1"
|
||||||
|
globby "^11.0.2"
|
||||||
|
ip "^1.1.5"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
node-fetch "^2.6.1"
|
||||||
|
pretty-hrtime "^1.0.3"
|
||||||
|
prompts "^2.4.0"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
serve-favicon "^2.5.0"
|
||||||
|
slash "^3.0.0"
|
||||||
|
telejson "^5.3.3"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
watchpack "^2.2.0"
|
||||||
|
webpack "4"
|
||||||
|
ws "^8.2.3"
|
||||||
|
|
||||||
"@storybook/core@6.4.21":
|
"@storybook/core@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.4.21.tgz#d92a60a6014df5f88902edfe4fadf1cbdd9ba238"
|
resolved "https://registry.yarnpkg.com/@storybook/core/-/core-6.4.21.tgz#d92a60a6014df5f88902edfe4fadf1cbdd9ba238"
|
||||||
@ -4583,6 +4931,29 @@
|
|||||||
regenerator-runtime "^0.13.7"
|
regenerator-runtime "^0.13.7"
|
||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
|
|
||||||
|
"@storybook/csf-tools@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/csf-tools/-/csf-tools-6.4.22.tgz#f6d64bcea1b36114555972acae66a1dbe9e34b5c"
|
||||||
|
integrity sha512-LMu8MZAiQspJAtMBLU2zitsIkqQv7jOwX7ih5JrXlyaDticH7l2j6Q+1mCZNWUOiMTizj0ivulmUsSaYbpToSw==
|
||||||
|
dependencies:
|
||||||
|
"@babel/core" "^7.12.10"
|
||||||
|
"@babel/generator" "^7.12.11"
|
||||||
|
"@babel/parser" "^7.12.11"
|
||||||
|
"@babel/plugin-transform-react-jsx" "^7.12.12"
|
||||||
|
"@babel/preset-env" "^7.12.11"
|
||||||
|
"@babel/traverse" "^7.12.11"
|
||||||
|
"@babel/types" "^7.12.11"
|
||||||
|
"@mdx-js/mdx" "^1.6.22"
|
||||||
|
"@storybook/csf" "0.0.2--canary.87bc651.0"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
fs-extra "^9.0.1"
|
||||||
|
global "^4.4.0"
|
||||||
|
js-string-escape "^1.0.1"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
prettier ">=2.2.1 <=2.3.0"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
|
||||||
"@storybook/csf@0.0.2--canary.87bc651.0":
|
"@storybook/csf@0.0.2--canary.87bc651.0":
|
||||||
version "0.0.2--canary.87bc651.0"
|
version "0.0.2--canary.87bc651.0"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.0.2--canary.87bc651.0.tgz#c7b99b3a344117ef67b10137b6477a3d2750cf44"
|
resolved "https://registry.yarnpkg.com/@storybook/csf/-/csf-0.0.2--canary.87bc651.0.tgz#c7b99b3a344117ef67b10137b6477a3d2750cf44"
|
||||||
@ -4632,6 +5003,48 @@
|
|||||||
webpack-dev-middleware "^3.7.3"
|
webpack-dev-middleware "^3.7.3"
|
||||||
webpack-virtual-modules "^0.2.2"
|
webpack-virtual-modules "^0.2.2"
|
||||||
|
|
||||||
|
"@storybook/manager-webpack4@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/manager-webpack4/-/manager-webpack4-6.4.22.tgz#eabd674beee901c7f755d9b679e9f969cbab636d"
|
||||||
|
integrity sha512-nzhDMJYg0vXdcG0ctwE6YFZBX71+5NYaTGkxg3xT7gbgnP1YFXn9gVODvgq3tPb3gcRapjyOIxUa20rV+r8edA==
|
||||||
|
dependencies:
|
||||||
|
"@babel/core" "^7.12.10"
|
||||||
|
"@babel/plugin-transform-template-literals" "^7.12.1"
|
||||||
|
"@babel/preset-react" "^7.12.10"
|
||||||
|
"@storybook/addons" "6.4.22"
|
||||||
|
"@storybook/core-client" "6.4.22"
|
||||||
|
"@storybook/core-common" "6.4.22"
|
||||||
|
"@storybook/node-logger" "6.4.22"
|
||||||
|
"@storybook/theming" "6.4.22"
|
||||||
|
"@storybook/ui" "6.4.22"
|
||||||
|
"@types/node" "^14.0.10"
|
||||||
|
"@types/webpack" "^4.41.26"
|
||||||
|
babel-loader "^8.0.0"
|
||||||
|
case-sensitive-paths-webpack-plugin "^2.3.0"
|
||||||
|
chalk "^4.1.0"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
css-loader "^3.6.0"
|
||||||
|
express "^4.17.1"
|
||||||
|
file-loader "^6.2.0"
|
||||||
|
file-system-cache "^1.0.5"
|
||||||
|
find-up "^5.0.0"
|
||||||
|
fs-extra "^9.0.1"
|
||||||
|
html-webpack-plugin "^4.0.0"
|
||||||
|
node-fetch "^2.6.1"
|
||||||
|
pnp-webpack-plugin "1.6.4"
|
||||||
|
read-pkg-up "^7.0.1"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
resolve-from "^5.0.0"
|
||||||
|
style-loader "^1.3.0"
|
||||||
|
telejson "^5.3.2"
|
||||||
|
terser-webpack-plugin "^4.2.3"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
url-loader "^4.1.1"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
webpack "4"
|
||||||
|
webpack-dev-middleware "^3.7.3"
|
||||||
|
webpack-virtual-modules "^0.2.2"
|
||||||
|
|
||||||
"@storybook/manager-webpack5@~6.4.12":
|
"@storybook/manager-webpack5@~6.4.12":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/manager-webpack5/-/manager-webpack5-6.4.21.tgz#f8f20c03bed8c3911a3678e637feef1d36bb45f5"
|
resolved "https://registry.yarnpkg.com/@storybook/manager-webpack5/-/manager-webpack5-6.4.21.tgz#f8f20c03bed8c3911a3678e637feef1d36bb45f5"
|
||||||
@ -4693,6 +5106,17 @@
|
|||||||
npmlog "^5.0.1"
|
npmlog "^5.0.1"
|
||||||
pretty-hrtime "^1.0.3"
|
pretty-hrtime "^1.0.3"
|
||||||
|
|
||||||
|
"@storybook/node-logger@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/node-logger/-/node-logger-6.4.22.tgz#c4ec00f8714505f44eda7671bc88bb44abf7ae59"
|
||||||
|
integrity sha512-sUXYFqPxiqM7gGH7gBXvO89YEO42nA4gBicJKZjj9e+W4QQLrftjF9l+mAw2K0mVE10Bn7r4pfs5oEZ0aruyyA==
|
||||||
|
dependencies:
|
||||||
|
"@types/npmlog" "^4.1.2"
|
||||||
|
chalk "^4.1.0"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
npmlog "^5.0.1"
|
||||||
|
pretty-hrtime "^1.0.3"
|
||||||
|
|
||||||
"@storybook/postinstall@6.4.21":
|
"@storybook/postinstall@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.4.21.tgz#1a0dc4ae0c8bf73fcda3d2abf6f22477dce0a908"
|
resolved "https://registry.yarnpkg.com/@storybook/postinstall/-/postinstall-6.4.21.tgz#1a0dc4ae0c8bf73fcda3d2abf6f22477dce0a908"
|
||||||
@ -4722,6 +5146,28 @@
|
|||||||
unfetch "^4.2.0"
|
unfetch "^4.2.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
|
"@storybook/preview-web@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/preview-web/-/preview-web-6.4.22.tgz#58bfc6492503ff4265b50f42a27ea8b0bfcf738a"
|
||||||
|
integrity sha512-sWS+sgvwSvcNY83hDtWUUL75O2l2LY/GTAS0Zp2dh3WkObhtuJ/UehftzPZlZmmv7PCwhb4Q3+tZDKzMlFxnKQ==
|
||||||
|
dependencies:
|
||||||
|
"@storybook/addons" "6.4.22"
|
||||||
|
"@storybook/channel-postmessage" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
"@storybook/csf" "0.0.2--canary.87bc651.0"
|
||||||
|
"@storybook/store" "6.4.22"
|
||||||
|
ansi-to-html "^0.6.11"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
global "^4.4.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
qs "^6.10.0"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
synchronous-promise "^2.0.15"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
unfetch "^4.2.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@storybook/react-docgen-typescript-plugin@1.0.2-canary.253f8c1.0":
|
"@storybook/react-docgen-typescript-plugin@1.0.2-canary.253f8c1.0":
|
||||||
version "1.0.2-canary.253f8c1.0"
|
version "1.0.2-canary.253f8c1.0"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.253f8c1.0.tgz#f2da40e6aae4aa586c2fb284a4a1744602c3c7fa"
|
resolved "https://registry.yarnpkg.com/@storybook/react-docgen-typescript-plugin/-/react-docgen-typescript-plugin-1.0.2-canary.253f8c1.0.tgz#f2da40e6aae4aa586c2fb284a4a1744602c3c7fa"
|
||||||
@ -4782,6 +5228,23 @@
|
|||||||
react-router-dom "^6.0.0"
|
react-router-dom "^6.0.0"
|
||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
|
|
||||||
|
"@storybook/router@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/router/-/router-6.4.22.tgz#e3cc5cd8595668a367e971efb9695bbc122ed95e"
|
||||||
|
integrity sha512-zeuE8ZgFhNerQX8sICQYNYL65QEi3okyzw7ynF58Ud6nRw4fMxSOHcj2T+nZCIU5ufozRL4QWD/Rg9P2s/HtLw==
|
||||||
|
dependencies:
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
fast-deep-equal "^3.1.3"
|
||||||
|
global "^4.4.0"
|
||||||
|
history "5.0.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
memoizerific "^1.11.3"
|
||||||
|
qs "^6.10.0"
|
||||||
|
react-router "^6.0.0"
|
||||||
|
react-router-dom "^6.0.0"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
|
||||||
"@storybook/semver@^7.3.2":
|
"@storybook/semver@^7.3.2":
|
||||||
version "7.3.2"
|
version "7.3.2"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0"
|
resolved "https://registry.yarnpkg.com/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0"
|
||||||
@ -4827,6 +5290,27 @@
|
|||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
util-deprecate "^1.0.2"
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
|
"@storybook/store@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/store/-/store-6.4.22.tgz#f291fbe3639f14d25f875cac86abb209a97d4e2a"
|
||||||
|
integrity sha512-lrmcZtYJLc2emO+1l6AG4Txm9445K6Pyv9cGAuhOJ9Kks0aYe0YtvMkZVVry0RNNAIv6Ypz72zyKc/QK+tZLAQ==
|
||||||
|
dependencies:
|
||||||
|
"@storybook/addons" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
"@storybook/csf" "0.0.2--canary.87bc651.0"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
fast-deep-equal "^3.1.3"
|
||||||
|
global "^4.4.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
memoizerific "^1.11.3"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
slash "^3.0.0"
|
||||||
|
stable "^0.1.8"
|
||||||
|
synchronous-promise "^2.0.15"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
util-deprecate "^1.0.2"
|
||||||
|
|
||||||
"@storybook/theming@6.4.21", "@storybook/theming@^6.0.0":
|
"@storybook/theming@6.4.21", "@storybook/theming@^6.0.0":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.21.tgz#ea1a33be70c654cb31e5b38fae93f72171e88ef8"
|
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.21.tgz#ea1a33be70c654cb31e5b38fae93f72171e88ef8"
|
||||||
@ -4845,6 +5329,24 @@
|
|||||||
resolve-from "^5.0.0"
|
resolve-from "^5.0.0"
|
||||||
ts-dedent "^2.0.0"
|
ts-dedent "^2.0.0"
|
||||||
|
|
||||||
|
"@storybook/theming@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/theming/-/theming-6.4.22.tgz#19097eec0366447ddd0d6917b0e0f81d0ec5e51e"
|
||||||
|
integrity sha512-NVMKH/jxSPtnMTO4VCN1k47uztq+u9fWv4GSnzq/eezxdGg9ceGL4/lCrNGoNajht9xbrsZ4QvsJ/V2sVGM8wA==
|
||||||
|
dependencies:
|
||||||
|
"@emotion/core" "^10.1.1"
|
||||||
|
"@emotion/is-prop-valid" "^0.8.6"
|
||||||
|
"@emotion/styled" "^10.0.27"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
deep-object-diff "^1.1.0"
|
||||||
|
emotion-theming "^10.0.27"
|
||||||
|
global "^4.4.0"
|
||||||
|
memoizerific "^1.11.3"
|
||||||
|
polished "^4.0.5"
|
||||||
|
resolve-from "^5.0.0"
|
||||||
|
ts-dedent "^2.0.0"
|
||||||
|
|
||||||
"@storybook/ui@6.4.21":
|
"@storybook/ui@6.4.21":
|
||||||
version "6.4.21"
|
version "6.4.21"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.4.21.tgz#03b0ba66663f70b706ca29481bedf08a468dad3d"
|
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.4.21.tgz#03b0ba66663f70b706ca29481bedf08a468dad3d"
|
||||||
@ -4879,6 +5381,40 @@
|
|||||||
resolve-from "^5.0.0"
|
resolve-from "^5.0.0"
|
||||||
store2 "^2.12.0"
|
store2 "^2.12.0"
|
||||||
|
|
||||||
|
"@storybook/ui@6.4.22":
|
||||||
|
version "6.4.22"
|
||||||
|
resolved "https://registry.yarnpkg.com/@storybook/ui/-/ui-6.4.22.tgz#49badd7994465d78d984ca4c42533c1c22201c46"
|
||||||
|
integrity sha512-UVjMoyVsqPr+mkS1L7m30O/xrdIEgZ5SCWsvqhmyMUok3F3tRB+6M+OA5Yy+cIVfvObpA7MhxirUT1elCGXsWQ==
|
||||||
|
dependencies:
|
||||||
|
"@emotion/core" "^10.1.1"
|
||||||
|
"@storybook/addons" "6.4.22"
|
||||||
|
"@storybook/api" "6.4.22"
|
||||||
|
"@storybook/channels" "6.4.22"
|
||||||
|
"@storybook/client-logger" "6.4.22"
|
||||||
|
"@storybook/components" "6.4.22"
|
||||||
|
"@storybook/core-events" "6.4.22"
|
||||||
|
"@storybook/router" "6.4.22"
|
||||||
|
"@storybook/semver" "^7.3.2"
|
||||||
|
"@storybook/theming" "6.4.22"
|
||||||
|
copy-to-clipboard "^3.3.1"
|
||||||
|
core-js "^3.8.2"
|
||||||
|
core-js-pure "^3.8.2"
|
||||||
|
downshift "^6.0.15"
|
||||||
|
emotion-theming "^10.0.27"
|
||||||
|
fuse.js "^3.6.1"
|
||||||
|
global "^4.4.0"
|
||||||
|
lodash "^4.17.21"
|
||||||
|
markdown-to-jsx "^7.1.3"
|
||||||
|
memoizerific "^1.11.3"
|
||||||
|
polished "^4.0.5"
|
||||||
|
qs "^6.10.0"
|
||||||
|
react-draggable "^4.4.3"
|
||||||
|
react-helmet-async "^1.0.7"
|
||||||
|
react-sizeme "^3.0.1"
|
||||||
|
regenerator-runtime "^0.13.7"
|
||||||
|
resolve-from "^5.0.0"
|
||||||
|
store2 "^2.12.0"
|
||||||
|
|
||||||
"@svgr/babel-plugin-add-jsx-attribute@^5.4.0":
|
"@svgr/babel-plugin-add-jsx-attribute@^5.4.0":
|
||||||
version "5.4.0"
|
version "5.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906"
|
resolved "https://registry.yarnpkg.com/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-5.4.0.tgz#81ef61947bb268eb9d50523446f9c638fb355906"
|
||||||
|
Loading…
Reference in New Issue
Block a user