From 6d091976d2e3c91151f560e7c9c6fc6f0960e134 Mon Sep 17 00:00:00 2001 From: RustemYuzlibaev Date: Wed, 5 Jul 2023 11:19:21 +0300 Subject: [PATCH 001/176] prework --- .../pages/OrderBook/SetLimitOrder.vue | 29 +++++++++++++++++++ src/consts/index.ts | 15 ++++++++++ src/lang/en.json | 1 + src/router/index.ts | 5 ++++ src/views/OrderBook.vue | 27 +++++++++++++++++ src/views/OrderBook/LimitOrderBuy.vue | 13 +++++++++ src/views/OrderBook/LimitOrderSell.vue | 0 7 files changed, 90 insertions(+) create mode 100644 src/components/pages/OrderBook/SetLimitOrder.vue create mode 100644 src/views/OrderBook.vue create mode 100644 src/views/OrderBook/LimitOrderBuy.vue create mode 100644 src/views/OrderBook/LimitOrderSell.vue diff --git a/src/components/pages/OrderBook/SetLimitOrder.vue b/src/components/pages/OrderBook/SetLimitOrder.vue new file mode 100644 index 000000000..a93fcc3ab --- /dev/null +++ b/src/components/pages/OrderBook/SetLimitOrder.vue @@ -0,0 +1,29 @@ + + + diff --git a/src/consts/index.ts b/src/consts/index.ts index 79db59c5c..5a71c6506 100644 --- a/src/consts/index.ts +++ b/src/consts/index.ts @@ -138,6 +138,9 @@ export enum PageNames { ExploreFarming = 'Explore/Farming', ExploreStaking = 'Explore/Staking', ExplorePools = 'Explore/Pools', + OrderBook = 'OrderBook', + LimitOrderBuy = 'OrderBook/LimitOrderBuy', + LimitOrderSell = 'OrderBook/LimitOrderSell', SoraCard = 'SoraCard', } @@ -195,6 +198,8 @@ export enum Components { SwapStatusActionBadge = 'pages/Swap/StatusActionBadge', SwapTransactionDetails = 'pages/Swap/TransactionDetails', SwapSettings = 'pages/Swap/Settings/Settings', + // Order Book + SetLimitOrder = 'pages/OrderBook/SetLimitOrder', // Referrals Page ReferralsConfirmBonding = 'pages/Referrals/ConfirmBonding', ReferralsConfirmInviteUser = 'pages/Referrals/ConfirmInviteUser', @@ -237,6 +242,11 @@ export enum Components { ChartSkeleton = 'shared/Chart/ChartSkeleton', } +export enum LimitOrderTabsItems { + Buy = PageNames.LimitOrderBuy, + Sell = PageNames.LimitOrderSell, +} + export enum RewardsTabsItems { Rewards = PageNames.Rewards, ReferralProgram = PageNames.ReferralProgram, @@ -298,6 +308,11 @@ const AccountMenu: Array = [ title: PageNames.Wallet, href: '/#/wallet', }, + { + icon: 'grid-block-distribute-vertically-24', + title: PageNames.OrderBook, + href: '/#/book', + }, { icon: 'basic-circle-star-24', title: PageNames.Rewards, diff --git a/src/lang/en.json b/src/lang/en.json index e1b0b02db..d9f69e1d5 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -353,6 +353,7 @@ "CreatePair": "Create Pair", "StakingContainer": "Staking", "Explore\/Container": "Explore", + "OrderBook": "Order Book", "SoraCard": "{Sora} Card" }, "social": { diff --git a/src/router/index.ts b/src/router/index.ts index b36c0a49d..d8cdcc42b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -230,6 +230,11 @@ const routes: Array = [ name: PageNames.Stats, component: lazyView(PageNames.Stats), }, + { + path: '/book', + name: PageNames.OrderBook, + component: lazyView(PageNames.OrderBook), + }, { path: '*', redirect: '/swap', diff --git a/src/views/OrderBook.vue b/src/views/OrderBook.vue new file mode 100644 index 000000000..596df4c63 --- /dev/null +++ b/src/views/OrderBook.vue @@ -0,0 +1,27 @@ + + + + + diff --git a/src/views/OrderBook/LimitOrderBuy.vue b/src/views/OrderBook/LimitOrderBuy.vue new file mode 100644 index 000000000..e739cddff --- /dev/null +++ b/src/views/OrderBook/LimitOrderBuy.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/views/OrderBook/LimitOrderSell.vue b/src/views/OrderBook/LimitOrderSell.vue new file mode 100644 index 000000000..e69de29bb From 8e44f87d802ae78987a02c868f30480a8619f297 Mon Sep 17 00:00:00 2001 From: RustemYuzlibaev Date: Tue, 11 Jul 2023 06:53:50 +0300 Subject: [PATCH 002/176] prework --- src/components/SoraCard/steps/KycView.vue | 2 +- .../pages/OrderBook/LimitOrderBuy.vue | 62 +++++++++++ .../pages/OrderBook/LimitOrderSell.vue | 13 +++ .../pages/OrderBook/SetLimitOrder.vue | 100 ++++++++++++++++++ .../pages/OrderBook/TransactionDetails.vue | 69 ++++++++++++ src/consts/index.ts | 16 +++ src/lang/en.json | 7 ++ src/router/index.ts | 5 + src/types/tabs.ts | 5 + src/views/OrderBook.vue | 23 ++++ 10 files changed, 301 insertions(+), 1 deletion(-) create mode 100644 src/components/pages/OrderBook/LimitOrderBuy.vue create mode 100644 src/components/pages/OrderBook/LimitOrderSell.vue create mode 100644 src/components/pages/OrderBook/SetLimitOrder.vue create mode 100644 src/components/pages/OrderBook/TransactionDetails.vue create mode 100644 src/views/OrderBook.vue diff --git a/src/components/SoraCard/steps/KycView.vue b/src/components/SoraCard/steps/KycView.vue index dddcb6b24..a3c5e4228 100644 --- a/src/components/SoraCard/steps/KycView.vue +++ b/src/components/SoraCard/steps/KycView.vue @@ -93,7 +93,7 @@ export default class KycView extends Mixins(TranslationMixin, mixins.Notificatio const referenceNumber = await this.getReferenceNumber(soraProxy.referenceNumberEndpoint); - await ScriptLoader.unload(kycService.sdkURL, false); + await ScriptLoader.unload(kycService.sdkURL, false).catch(() => {}); ScriptLoader.load(kycService.sdkURL) .then(() => { diff --git a/src/components/pages/OrderBook/LimitOrderBuy.vue b/src/components/pages/OrderBook/LimitOrderBuy.vue new file mode 100644 index 000000000..5273735f6 --- /dev/null +++ b/src/components/pages/OrderBook/LimitOrderBuy.vue @@ -0,0 +1,62 @@ + + + + + diff --git a/src/components/pages/OrderBook/LimitOrderSell.vue b/src/components/pages/OrderBook/LimitOrderSell.vue new file mode 100644 index 000000000..e57f0e808 --- /dev/null +++ b/src/components/pages/OrderBook/LimitOrderSell.vue @@ -0,0 +1,13 @@ + + + diff --git a/src/components/pages/OrderBook/SetLimitOrder.vue b/src/components/pages/OrderBook/SetLimitOrder.vue new file mode 100644 index 000000000..a981c6740 --- /dev/null +++ b/src/components/pages/OrderBook/SetLimitOrder.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/components/pages/OrderBook/TransactionDetails.vue b/src/components/pages/OrderBook/TransactionDetails.vue new file mode 100644 index 000000000..5c282d45a --- /dev/null +++ b/src/components/pages/OrderBook/TransactionDetails.vue @@ -0,0 +1,69 @@ + + + diff --git a/src/consts/index.ts b/src/consts/index.ts index 79db59c5c..677fd8b31 100644 --- a/src/consts/index.ts +++ b/src/consts/index.ts @@ -138,6 +138,7 @@ export enum PageNames { ExploreFarming = 'Explore/Farming', ExploreStaking = 'Explore/Staking', ExplorePools = 'Explore/Pools', + OrderBook = 'OrderBook', SoraCard = 'SoraCard', } @@ -195,6 +196,11 @@ export enum Components { SwapStatusActionBadge = 'pages/Swap/StatusActionBadge', SwapTransactionDetails = 'pages/Swap/TransactionDetails', SwapSettings = 'pages/Swap/Settings/Settings', + // Order Book + SetLimitOrder = 'pages/OrderBook/SetLimitOrder', + OrderBookBuy = 'pages/OrderBook/LimitOrderBuy', + OrderBookSell = 'pages/OrderBook/LimitOrderSell', + BookTransactionDetails = 'pages/OrderBook/TransactionDetails', // Referrals Page ReferralsConfirmBonding = 'pages/Referrals/ConfirmBonding', ReferralsConfirmInviteUser = 'pages/Referrals/ConfirmInviteUser', @@ -237,6 +243,11 @@ export enum Components { ChartSkeleton = 'shared/Chart/ChartSkeleton', } +export enum LimitOrderTabsItems { + buy = 'buy', + sell = 'sell', +} + export enum RewardsTabsItems { Rewards = PageNames.Rewards, ReferralProgram = PageNames.ReferralProgram, @@ -298,6 +309,11 @@ const AccountMenu: Array = [ title: PageNames.Wallet, href: '/#/wallet', }, + { + icon: 'grid-block-distribute-vertically-24', + title: PageNames.OrderBook, + href: '/#/book', + }, { icon: 'basic-circle-star-24', title: PageNames.Rewards, diff --git a/src/lang/en.json b/src/lang/en.json index e1b0b02db..a75da434e 100644 --- a/src/lang/en.json +++ b/src/lang/en.json @@ -353,6 +353,7 @@ "CreatePair": "Create Pair", "StakingContainer": "Staking", "Explore\/Container": "Explore", + "OrderBook": "Order Book", "SoraCard": "{Sora} Card" }, "social": { @@ -522,6 +523,12 @@ "description": "When you add liquidity, you are given pool tokens representing your position. These tokens automatically earn fees proportional to your share of the pool, and can be redeemed at any time.", "strategicBonusApy": "Strategic Bonus APY" }, + "orderBook": { + "buy": "Buy", + "sell": "Sell", + "limit": "Limit", + "market": "Market" + }, "bridge": { "title": "Bridge", "info": "Convert tokens between the {Sora} and {Ethereum} networks.", diff --git a/src/router/index.ts b/src/router/index.ts index b36c0a49d..d8cdcc42b 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -230,6 +230,11 @@ const routes: Array = [ name: PageNames.Stats, component: lazyView(PageNames.Stats), }, + { + path: '/book', + name: PageNames.OrderBook, + component: lazyView(PageNames.OrderBook), + }, { path: '*', redirect: '/swap', diff --git a/src/types/tabs.ts b/src/types/tabs.ts index 7450ad154..87afa39af 100644 --- a/src/types/tabs.ts +++ b/src/types/tabs.ts @@ -1,3 +1,8 @@ +export enum OrderBookTabs { + Limit = 'limit', + Market = 'market', +} + export enum AlertTypeTabs { Drop = 'drop', Raise = 'raise', diff --git a/src/views/OrderBook.vue b/src/views/OrderBook.vue new file mode 100644 index 000000000..fcbbc22d5 --- /dev/null +++ b/src/views/OrderBook.vue @@ -0,0 +1,23 @@ + + + + + From 569b651dcaf6eb78bf9ed518ee8871056aec0878 Mon Sep 17 00:00:00 2001 From: RustemYuzlibaev Date: Wed, 12 Jul 2023 14:42:31 +0300 Subject: [PATCH 003/176] book list --- .../pages/OrderBook/LimitOrderBuy.vue | 155 +++++++++++++- .../pages/OrderBook/PairListPopover.vue | 199 ++++++++++++++++++ src/consts/index.ts | 1 + 3 files changed, 353 insertions(+), 2 deletions(-) create mode 100644 src/components/pages/OrderBook/PairListPopover.vue diff --git a/src/components/pages/OrderBook/LimitOrderBuy.vue b/src/components/pages/OrderBook/LimitOrderBuy.vue index 5273735f6..c10c2525f 100644 --- a/src/components/pages/OrderBook/LimitOrderBuy.vue +++ b/src/components/pages/OrderBook/LimitOrderBuy.vue @@ -1,8 +1,54 @@ diff --git a/src/components/pages/OrderBook/Tables/OpenOrders.vue b/src/components/pages/OrderBook/Tables/OpenOrders.vue index 8070f209d..3ad66c12c 100644 --- a/src/components/pages/OrderBook/Tables/OpenOrders.vue +++ b/src/components/pages/OrderBook/Tables/OpenOrders.vue @@ -1,339 +1,69 @@ - - diff --git a/src/components/pages/OrderBook/Tables/OrderTable.vue b/src/components/pages/OrderBook/Tables/OrderTable.vue new file mode 100644 index 000000000..205d20c2b --- /dev/null +++ b/src/components/pages/OrderBook/Tables/OrderTable.vue @@ -0,0 +1,308 @@ + + + + + diff --git a/src/components/shared/PriceChange.vue b/src/components/shared/PriceChange.vue index 2ed9b589d..0e54dcc05 100644 --- a/src/components/shared/PriceChange.vue +++ b/src/components/shared/PriceChange.vue @@ -51,7 +51,7 @@ export default class PriceChange extends Vue { display: inline-flex; align-items: baseline; color: var(--s-color-theme-accent); - font-size: var(--s-font-size-medium); + font-size: inherit; font-weight: 600; line-height: var(--s-line-height-medium); diff --git a/src/consts/index.ts b/src/consts/index.ts index 8ec93b836..307f3deac 100644 --- a/src/consts/index.ts +++ b/src/consts/index.ts @@ -267,11 +267,6 @@ export enum Components { ChartSkeleton = 'shared/Chart/ChartSkeleton', } -export enum LimitOrderSide { - Buy = 'Buy', - Sell = 'Sell', -} - export enum LimitOrderType { limit = 'limit', market = 'market', @@ -282,12 +277,6 @@ export enum LimitOrderTabsItems { Sell = PageNames.LimitOrderSell, } -export interface LimitOrderConstraint { - tickSize: Nullable; - maxLotSize: Nullable; - minLotSize: Nullable; -} - export enum RewardsTabsItems { Rewards = PageNames.Rewards, ReferralProgram = PageNames.ReferralProgram, diff --git a/src/indexer/queries/orderBook.ts b/src/indexer/queries/orderBook.ts new file mode 100644 index 000000000..79935438b --- /dev/null +++ b/src/indexer/queries/orderBook.ts @@ -0,0 +1,330 @@ +import { PriceVariant } from '@sora-substrate/liquidity-proxy'; +import { FPNumber } from '@sora-substrate/util'; +import { getCurrentIndexer, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; +import { SubqueryIndexer } from '@soramitsu/soraneo-wallet-web/lib/services/indexer'; +import { gql } from '@urql/core'; + +import { OrderStatus } from '@/types/orderBook'; +import type { OrderBookDealData, OrderBookWithStats, OrderBookUpdateData, OrderData } from '@/types/orderBook'; + +import type { OrderBookId } from '@sora-substrate/liquidity-proxy'; +import type { + SubqueryConnectionQueryResponse, + SubquerySubscriptionPayload, +} from '@soramitsu/soraneo-wallet-web/lib/services/indexer/subquery/types'; +import type { + OrderBookEntity, + OrderBookLimitOrderEntity, + OrderBookMarketOrderEntity, + OrderBookDealEntity, +} from '@soramitsu/soraneo-wallet-web/lib/services/indexer/types'; + +/* eslint-disable camelcase */ +type OrderBookEntityMutation = { + price: string; + price_change_day: number; + volume_day_u_s_d: string; + status: string; + last_deals: string; +}; +/* eslint-enable camelcase */ + +type OrderBookEntityResponse = { + data: OrderBookEntity; +}; + +const { IndexerType } = WALLET_CONSTS; + +const parseSide = (isBuy: boolean): PriceVariant => { + return isBuy ? PriceVariant.Buy : PriceVariant.Sell; +}; +const parseTimestamp = (unixTimestamp: number) => { + return unixTimestamp * 1000; +}; +const parseDeals = (lastDeals?: string): OrderBookDealData[] => { + const deals = (lastDeals ? JSON.parse(lastDeals) : []) as OrderBookDealEntity[]; + + return deals.map((deal) => ({ + price: new FPNumber(deal.price ?? 0), + amount: new FPNumber(deal.amount ?? 0), + side: parseSide(deal.isBuy), + timestamp: parseTimestamp(deal.timestamp), + })); +}; + +const SubqueryOrderBooksQuery = gql>` + query OrderBooksQuery($after: Cursor) { + data: orderBooks(after: $after) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + dexId + baseAssetId + quoteAssetId + price + priceChangeDay + volumeDayUSD + status + } + } + } + } +`; + +const parseOrderBookEntity = (item: OrderBookEntity): OrderBookWithStats => { + const { dexId, baseAssetId, quoteAssetId, price, priceChangeDay, volumeDayUSD, status } = item; + + return { + id: { + dexId, + base: baseAssetId, + quote: quoteAssetId, + }, + stats: { + price: new FPNumber(price ?? 0), + priceChange: new FPNumber(priceChangeDay ?? 0), + volume: new FPNumber(volumeDayUSD ?? 0), + status, + }, + }; +}; + +export async function fetchOrderBooks(): Promise> { + const indexer = getCurrentIndexer(); + + switch (indexer.type) { + case IndexerType.SUBQUERY: { + const subqueryIndexer = indexer as SubqueryIndexer; + const response = await subqueryIndexer.services.explorer.fetchAllEntities( + SubqueryOrderBooksQuery, + {}, + parseOrderBookEntity + ); + return response; + } + } + + return null; +} + +const SubqueryAccountMarketOrdersQuery = gql>` + query AccountMarketOrdersQuery($after: Cursor, $filter: OrderBookMarketOrderFilter) { + data: orderBookMarketOrders(orderBy: TIMESTAMP_DESC, after: $after, filter: $filter) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + orderBookId + accountId + timestamp + isBuy + price + amount + } + } + } + } +`; + +const SubqueryAccountLimitOrdersQuery = gql>` + query AccountLimitOrdersQuery($after: Cursor, $filter: OrderBookLimitOrderFilter) { + data: orderBookLimitOrders(orderBy: TIMESTAMP_DESC, after: $after, filter: $filter) { + pageInfo { + hasNextPage + endCursor + } + totalCount + edges { + node { + orderBookId + accountId + timestamp + isBuy + price + amount + amountFilled + orderId + lifetime + expiresAt + status + } + } + } + } +`; + +const parseOrderEntity = (item: OrderBookMarketOrderEntity | OrderBookLimitOrderEntity): OrderData => { + const [dexId, base, quote] = item.orderBookId.split('-'); + const originalAmount = new FPNumber(item.amount); + const filledAmount = new FPNumber('amountFilled' in item ? item.amountFilled : item.amount); + const amount = originalAmount.sub(filledAmount); + + return { + orderBookId: { + dexId: Number(dexId), + base, + quote, + }, + owner: item.accountId, + time: parseTimestamp(item.timestamp), + side: parseSide(item.isBuy), + price: new FPNumber(item.price), + originalAmount, + amount, + id: 'orderId' in item ? item.orderId : 0, + lifespan: parseTimestamp('lifetime' in item ? item.lifetime : 0), + expiresAt: parseTimestamp('expiresAt' in item ? item.expiresAt : item.timestamp), + status: 'status' in item ? item.status : OrderStatus.Filled, + }; +}; + +export async function fetchOrderBookAccountOrders( + accountAddress: string, + id?: OrderBookId +): Promise> { + const filter: any = { + and: [{ accountId: { equalTo: accountAddress } }], + }; + + if (id) { + const orderBookId = [id.dexId, id.base, id.quote].join('-'); + + filter.and.push({ + orderBookId: { equalTo: orderBookId }, + }); + } + + const indexer = getCurrentIndexer(); + + switch (indexer.type) { + case IndexerType.SUBQUERY: { + const subqueryIndexer = indexer as SubqueryIndexer; + const [limitOrders, marketOrders] = await Promise.all([ + subqueryIndexer.services.explorer.fetchAllEntities( + SubqueryAccountLimitOrdersQuery, + { filter }, + parseOrderEntity + ), + subqueryIndexer.services.explorer.fetchAllEntities( + SubqueryAccountMarketOrdersQuery, + { filter }, + parseOrderEntity + ), + ]); + + return [...(limitOrders || []), ...(marketOrders || [])].sort((a, b) => +b.time - +a.time); + } + } + + return null; +} + +const SubqueryOrderBookDataQuery = gql` + query OrderBookDataQuery($id: String!) { + data: orderBook(id: $id) { + price + priceChangeDay + volumeDayUSD + status + lastDeals + } + } +`; + +const parseOrderBookResponse = + (dexId: number, base: string, quote: string) => + (item: OrderBookEntityResponse): OrderBookUpdateData => { + const { price, priceChangeDay, volumeDayUSD, status, lastDeals } = item.data; + + return { + id: { + dexId, + base, + quote, + }, + stats: { + price: new FPNumber(price ?? 0), + priceChange: new FPNumber(priceChangeDay ?? 0), + volume: new FPNumber(volumeDayUSD ?? 0), + status, + }, + deals: parseDeals(lastDeals), + }; + }; + +const SubqueryOrderBookDataSubscription = gql>` + subscription OrderBookDataSubscription($id: [ID!]) { + payload: orderBooks(id: $id, mutation: [UPDATE]) { + id + mutation_type + _entity + } + } +`; + +/* eslint-disable camelcase */ +const parseOrderBookMutation = + (dexId: number, base: string, quote: string) => + (item: OrderBookEntityMutation): OrderBookUpdateData => { + const { price, price_change_day, volume_day_u_s_d, status, last_deals } = item; + + return { + id: { + dexId, + base, + quote, + }, + stats: { + price: new FPNumber(price ?? 0), + priceChange: new FPNumber(price_change_day ?? 0), + volume: new FPNumber(volume_day_u_s_d ?? 0), + status, + }, + deals: parseDeals(last_deals), + }; + }; +/* eslint-enable camelcase */ + +export async function subscribeOnOrderBookUpdates( + dexId: number, + baseAssetId: string, + quoteAssetId: string, + handler: (entity: OrderBookUpdateData) => void, + errorHandler: () => void +): Promise> { + const orderBookId = [dexId, baseAssetId, quoteAssetId].join('-'); + const variables = { id: orderBookId }; + const indexer = getCurrentIndexer(); + + switch (indexer.type) { + case IndexerType.SUBQUERY: { + const subqueryIndexer = indexer as SubqueryIndexer; + const parseResponse = parseOrderBookResponse(dexId, baseAssetId, quoteAssetId); + const parseMutation = parseOrderBookMutation(dexId, baseAssetId, quoteAssetId); + const response = await subqueryIndexer.services.explorer.request(SubqueryOrderBookDataQuery, variables); + + if (!response) return null; + + handler(parseResponse(response)); + + const subscription = subqueryIndexer.services.explorer.createEntitySubscription( + SubqueryOrderBookDataSubscription, + variables, + parseMutation, + handler, + errorHandler + ); + + return subscription; + } + } + + return null; +} diff --git a/src/plugins/soramitsuUI.ts b/src/plugins/soramitsuUI.ts index cd8413a1e..69b1e3bf4 100644 --- a/src/plugins/soramitsuUI.ts +++ b/src/plugins/soramitsuUI.ts @@ -41,12 +41,14 @@ import { setDesignSystem, setTheme } from '@soramitsu/soramitsu-js-ui/lib/utils' import ElCheckbox from 'element-ui/lib/checkbox'; import ElCheckboxGroup from 'element-ui/lib/checkbox-group'; import ElPopover from 'element-ui/lib/popover'; +// import ElProgress from 'element-ui/lib/progress'; import Vue from 'vue'; import store from '@/store'; Vue.use(ElementUIPlugin); Vue.use(SoramitsuUIStorePlugin, { store: store.original }); +// Vue.use(ElProgress); Vue.use(ElPopover); Vue.use(ElCheckbox); Vue.use(ElCheckboxGroup); diff --git a/src/store/orderBook/actions.ts b/src/store/orderBook/actions.ts index fd094ceeb..76f124f8d 100644 --- a/src/store/orderBook/actions.ts +++ b/src/store/orderBook/actions.ts @@ -2,37 +2,48 @@ import { MAX_TIMESTAMP } from '@sora-substrate/util/build/orderBook/consts'; import { api } from '@soramitsu/soraneo-wallet-web'; import { defineActions } from 'direct-vuex'; +import { subscribeOnOrderBookUpdates, fetchOrderBooks } from '@/indexer/queries/orderBook'; +import { serializeKey } from '@/utils/orderBook'; + import { orderBookActionContext } from '.'; -function deserializeKey(key: string) { - const [base, quote] = key.split(','); - return { base, quote }; -} +import type { OrderBook } from '@sora-substrate/liquidity-proxy'; +import type { Subscription } from 'rxjs'; const actions = defineActions({ async getOrderBooksInfo(context): Promise { const { commit, rootGetters } = orderBookActionContext(context); - + const { whitelist } = rootGetters.wallet.account; const orderBooks = await api.orderBook.getOrderBooks(); - // TODO: move to lib - const whitelistAddresses = Object.keys(rootGetters.wallet.account.whitelist); + // const orderBooksAllowed = Object.entries(orderBooks).reduce>((buffer, [key, book]) => { + // const { base, quote } = book.orderBookId; + // if ([base, quote].every((address) => address in whitelist)) { + // buffer[key] = book; + // } + // return buffer; + // }, {}); - const whitelistOrderBook = Object.keys(orderBooks) - .filter((key) => { - const { base } = deserializeKey(key); - return whitelistAddresses.includes(base); - }) - .reduce((current, key) => { - return Object.assign(current, { [key]: orderBooks[key] }); - }, {}); + // commit.setOrderBooks(orderBooksAllowed); - const [orderBookId] = Object.keys(whitelistOrderBook); + commit.setOrderBooks(orderBooks); + }, - if (!orderBookId) return; + async updateOrderBooksStats(context): Promise { + const { commit } = orderBookActionContext(context); - commit.setOrderBooks(whitelistOrderBook); - commit.setCurrentOrderBook(orderBookId); + const orderBooksWithStats = await fetchOrderBooks(); + const orderBooksStats = (orderBooksWithStats ?? []).reduce((buffer, item) => { + const { + id: { base, quote }, + stats, + } = item; + const key = serializeKey(base, quote); + buffer[key] = stats; + return buffer; + }, {}); + + commit.setStats(orderBooksStats); }, async getPlaceOrderNetworkFee(context, timestamp = MAX_TIMESTAMP): Promise { @@ -42,60 +53,117 @@ const actions = defineActions({ commit.setPlaceOrderNetworkFee(codecFee); }, - async subscribeToOrderBook(context, { base, quote }): Promise { - const { commit, getters, dispatch } = orderBookActionContext(context); - - commit.resetAsks(); - commit.resetBids(); - commit.resetOrderBookUpdates(); + async subscribeToBidsAndAsks(context): Promise { + const { commit, dispatch, getters } = orderBookActionContext(context); + const { baseAsset, quoteAsset } = getters; + + dispatch.unsubscribeFromBidsAndAsks(); + + if (!(baseAsset && quoteAsset)) return; + + let asksSubscription!: Subscription; + let bidsSubscription!: Subscription; + + await Promise.all([ + new Promise((resolve) => { + asksSubscription = api.orderBook + .subscribeOnAggregatedAsks(baseAsset.address, quoteAsset.address) + .subscribe((asks) => { + commit.setAsks(asks.reverse()); + resolve(); + }); + }), + new Promise((resolve) => { + bidsSubscription = api.orderBook + .subscribeOnAggregatedBids(baseAsset.address, quoteAsset.address) + .subscribe((bids) => { + commit.setBids(bids.reverse()); + resolve(); + }); + }), + ]); - if (!quote) quote = getters.quoteAsset?.address; + commit.setOrderBookUpdates([asksSubscription, bidsSubscription]); + }, - const asksSubscription = api.orderBook.subscribeOnAggregatedAsks(base, quote).subscribe((asks) => { - commit.setAsks(asks.reverse()); - }); + unsubscribeFromBidsAndAsks(context): void { + const { commit } = orderBookActionContext(context); - const bidsSubscription = api.orderBook.subscribeOnAggregatedBids(base, quote).subscribe((bids) => { - commit.setBids(bids.reverse()); - }); + commit.setAsks(); + commit.setBids(); + commit.resetOrderBookUpdates(); + }, - commit.setOrderBookUpdates([asksSubscription, bidsSubscription]); + async subscribeToOrderBookStats(context): Promise { + const { commit, dispatch, getters, state } = orderBookActionContext(context); + const { dexId } = state; + const { baseAsset, quoteAsset } = getters; + + dispatch.unsubscribeFromOrderBookStats(); + + if (!(baseAsset && quoteAsset)) return; + + const subscription = await subscribeOnOrderBookUpdates( + dexId, + baseAsset.address, + quoteAsset.address, + (data) => { + const { + id: { base, quote }, + stats, + deals, + } = data; + const key = serializeKey(base, quote); + commit.setDeals(deals); + commit.setStats({ [key]: stats }); + }, + console.error + ); + + if (!subscription) return; + + commit.setOrderBookStatsUpdates(subscription); }, - subscribeToUserLimitOrders(context, { base, quote }): void { - const { commit, getters } = orderBookActionContext(context); + unsubscribeFromOrderBookStats(context): void { + const { commit } = orderBookActionContext(context); - commit.resetUserLimitOrderUpdates(); + commit.setDeals(); + commit.resetOrderBookStatsUpdates(); + }, - const address = getters.accountAddress; + async subscribeToUserLimitOrders(context): Promise { + const { commit, dispatch, getters } = orderBookActionContext(context); + const { baseAsset, quoteAsset, accountAddress } = getters; - if (!address) return; + dispatch.unsubscribeFromUserLimitOrders(); - if (!quote) quote = getters.quoteAsset?.address; + if (!(accountAddress && baseAsset && quoteAsset)) return; - let userLimitOrders: Array = []; + let subscription!: Subscription; - const subscription = api.orderBook.subscribeOnUserLimitOrdersIds(base, quote, address).subscribe((ids) => { - userLimitOrders = []; + await new Promise((resolve) => { + subscription = api.orderBook + .subscribeOnUserLimitOrdersIds(baseAsset.address, quoteAsset.address, accountAddress) + .subscribe(async (ids) => { + const userLimitOrders = await Promise.all( + ids.map((id) => api.orderBook.getLimitOrder(baseAsset.address, quoteAsset.address, id)) + ); - ids.forEach(async (id) => { - const order = await api.orderBook.getLimitOrder(base, quote, id); - userLimitOrders.push(order); - }); + commit.setUserLimitOrders(userLimitOrders); - commit.setUserLimitOrders(userLimitOrders); + resolve(); + }); }); commit.setUserLimitOrderUpdates(subscription); }, - unsubscribeFromOrderBook(context): void { + unsubscribeFromUserLimitOrders(context): void { const { commit } = orderBookActionContext(context); - commit.resetAsks(); - commit.resetBids(); + + commit.setUserLimitOrders(); commit.resetUserLimitOrderUpdates(); - commit.resetUserLimitOrders(); - commit.resetOrderBookUpdates(); }, }); diff --git a/src/store/orderBook/getters.ts b/src/store/orderBook/getters.ts index fc696c851..37f260167 100644 --- a/src/store/orderBook/getters.ts +++ b/src/store/orderBook/getters.ts @@ -1,6 +1,8 @@ +import { OrderBook } from '@sora-substrate/liquidity-proxy'; import { defineGetters } from 'direct-vuex'; -import { assetsGetterContext } from '../assets'; +import type { OrderBookStats, OrderBookDealData } from '@/types/orderBook'; +import { serializeKey, getBookDecimals } from '@/utils/orderBook'; import { OrderBookState } from './types'; @@ -11,23 +13,50 @@ import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/t const getters = defineGetters()({ baseAsset(...args): Nullable { const { state, rootGetters } = orderBookGetterContext(args); - const token = rootGetters.assets.assetDataByAddress(state.baseAssetAddress); - - return token || null; + if (!state.baseAssetAddress) return null; + return rootGetters.assets.assetDataByAddress(state.baseAssetAddress); }, quoteAsset(...args): Nullable { - const { rootGetters } = assetsGetterContext(args); + const { state, rootGetters } = orderBookGetterContext(args); + if (!state.quoteAssetAddress) return null; + return rootGetters.assets.assetDataByAddress(state.quoteAssetAddress); + }, + orderBookId(...args): string { + const { getters } = orderBookGetterContext(args); + const { baseAsset, quoteAsset } = getters; + + if (!(baseAsset && quoteAsset)) return ''; - return rootGetters.assets.xor; + return serializeKey(baseAsset.address, quoteAsset.address); + }, + currentOrderBook(...args): Nullable { + const { getters, state } = orderBookGetterContext(args); + + if (!getters.orderBookId) return null; + + return state.orderBooks[getters.orderBookId]; + }, + orderBookStats(...args): Nullable { + const { getters, state } = orderBookGetterContext(args); + + if (!getters.orderBookId) return null; + + return state.orderBooksStats[getters.orderBookId]; + }, + orderBookDecimals(...args): number { + const { getters } = orderBookGetterContext(args); + + return getBookDecimals(getters.currentOrderBook); + }, + orderBookLastDeal(...args): Nullable { + const { state } = orderBookGetterContext(args); + + return state.deals[0] ?? null; }, accountAddress(...args): string { const { rootState } = orderBookGetterContext(args); return rootState.wallet.account.address; }, - - // currentPrice(...args): string {}, - // currentVolume(...args): string {}, - // currenDailyChange(...args): string {}, }); export default getters; diff --git a/src/store/orderBook/mutations.ts b/src/store/orderBook/mutations.ts index 9a8cd897e..b3cb7599a 100644 --- a/src/store/orderBook/mutations.ts +++ b/src/store/orderBook/mutations.ts @@ -1,25 +1,21 @@ +import { PriceVariant } from '@sora-substrate/liquidity-proxy'; import { defineMutations } from 'direct-vuex'; -import type { LimitOrderSide } from '@/consts'; +import type { OrderBookDealData, OrderBookStats } from '@/types/orderBook'; import type { OrderBookState } from './types'; - -function deserializeKey(key: string) { - const [base, quote] = key.split(','); - return { base, quote }; -} +import type { OrderBookId, OrderBookPriceVolume, OrderBook } from '@sora-substrate/liquidity-proxy'; +import type { LimitOrder } from '@sora-substrate/util/build/orderBook/types'; +import type { Subscription } from 'rxjs'; const mutations = defineMutations()({ - setOrderBooks(state, orderBooks): void { + setOrderBooks(state, orderBooks: Record): void { state.orderBooks = orderBooks; }, - setCurrentOrderBook(state, orderBookId: string): void { - const { base } = deserializeKey(orderBookId); - state.currentOrderBook = { [orderBookId]: state.orderBooks[orderBookId] }; + setCurrentOrderBook(state, { dexId, base, quote }: OrderBookId): void { + state.dexId = dexId; state.baseAssetAddress = base; - }, - setBaseAssetAddress(state, address: string): void { - state.baseAssetAddress = address; + state.quoteAssetAddress = quote; }, setBaseValue(state, value: string): void { state.baseValue = value; @@ -27,43 +23,39 @@ const mutations = defineMutations()({ setQuoteValue(state, value: string): void { state.quoteValue = value; }, - setSide(state, side: LimitOrderSide): void { + setSide(state, side: PriceVariant): void { state.side = side; }, - setAsks(state, asks): void { - state.asks = asks; - }, - resetAsks(state): void { - state.asks = []; - }, - setBids(state, bids): void { - state.bids = bids; + setAsks(state, asks: readonly OrderBookPriceVolume[] = []): void { + state.asks = Object.freeze([...asks]); }, - resetBids(state): void { - state.bids = []; + setBids(state, bids: readonly OrderBookPriceVolume[] = []): void { + state.bids = Object.freeze([...bids]); }, - setVolume(state, volume: string): void { - state.volume = volume; + setDeals(state, deals: readonly OrderBookDealData[] = []): void { + state.deals = Object.freeze([...deals]); }, - setUserLimitOrders(state, limitOrders): void { - state.userLimitOrders = limitOrders; + setStats(state, stats: Record): void { + state.orderBooksStats = Object.freeze({ ...state.orderBooksStats, ...stats }); }, - resetUserLimitOrders(state): void { - state.userLimitOrders = []; + setUserLimitOrders(state, limitOrders: readonly LimitOrder[] = []): void { + state.userLimitOrders = Object.freeze([...limitOrders]); }, - setOrderBookUpdates(state, subscriptions): void { + setOrderBookUpdates(state, subscriptions: Array): void { state.orderBookUpdates = subscriptions; }, resetOrderBookUpdates(state): void { - if (state.orderBookUpdates.length) { - state.orderBookUpdates.forEach((limitOrderUpdate) => { - limitOrderUpdate?.unsubscribe(); - }); - } - + state.orderBookUpdates?.forEach((subscription) => subscription?.unsubscribe()); state.orderBookUpdates = []; }, - setUserLimitOrderUpdates(state, subscription): void { + setOrderBookStatsUpdates(state, subscription: VoidFunction): void { + state.orderBookStatsUpdates = subscription; + }, + resetOrderBookStatsUpdates(state): void { + state.orderBookStatsUpdates?.(); + state.orderBookStatsUpdates = null; + }, + setUserLimitOrderUpdates(state, subscription: Subscription): void { state.userLimitOrderUpdates = subscription; }, resetUserLimitOrderUpdates(state): void { diff --git a/src/store/orderBook/state.ts b/src/store/orderBook/state.ts index 0363422d2..fc475d835 100644 --- a/src/store/orderBook/state.ts +++ b/src/store/orderBook/state.ts @@ -1,29 +1,26 @@ +import { PriceVariant } from '@sora-substrate/liquidity-proxy'; import { FPNumber } from '@sora-substrate/util'; - -import { LimitOrderSide, ZeroStringValue } from '@/consts'; +import { DexId } from '@sora-substrate/util/build/dex/consts'; import type { OrderBookState } from './types'; function initialState(): OrderBookState { return { - orderBooks: null, - currentOrderBook: null, + orderBooks: {}, + dexId: DexId.XOR, baseAssetAddress: null, quoteAssetAddress: null, baseValue: '', quoteValue: '', + orderBooksStats: {}, + deals: [], asks: [], bids: [], - volume: '', userLimitOrders: [], - side: LimitOrderSide.Buy, + side: PriceVariant.Buy, orderBookUpdates: [], + orderBookStatsUpdates: null, userLimitOrderUpdates: null, - limitOrderConstraints: { - tickSize: null, - maxLotSize: null, - minLotSize: null, - }, placeOrderNetworkFee: FPNumber.ZERO, }; } diff --git a/src/store/orderBook/types.ts b/src/store/orderBook/types.ts index cf9ec7a1b..f553379c3 100644 --- a/src/store/orderBook/types.ts +++ b/src/store/orderBook/types.ts @@ -1,22 +1,26 @@ -import type { LimitOrderSide, LimitOrderConstraint } from '@/consts'; +import type { OrderBookStats, OrderBookDealData } from '@/types/orderBook'; +import type { PriceVariant, OrderBookPriceVolume, OrderBook } from '@sora-substrate/liquidity-proxy'; import type { FPNumber } from '@sora-substrate/util'; +import type { DexId } from '@sora-substrate/util/build/dex/consts'; +import type { LimitOrder } from '@sora-substrate/util/build/orderBook/types'; import type { Subscription } from 'rxjs'; export type OrderBookState = { - orderBooks: any; - currentOrderBook: any; + orderBooks: Record; + dexId: DexId; baseAssetAddress: Nullable; quoteAssetAddress: Nullable; - asks: []; - bids: []; - volume: string; - userLimitOrders: []; + orderBooksStats: Record; + deals: readonly OrderBookDealData[]; + asks: readonly OrderBookPriceVolume[]; + bids: readonly OrderBookPriceVolume[]; + userLimitOrders: readonly LimitOrder[]; baseValue: string; quoteValue: string; - side: LimitOrderSide; - orderBookUpdates: Array>; + side: PriceVariant; + orderBookUpdates: Array; + orderBookStatsUpdates: Nullable; userLimitOrderUpdates: Nullable; - limitOrderConstraints: LimitOrderConstraint; placeOrderNetworkFee: FPNumber; }; diff --git a/src/types/orderBook.ts b/src/types/orderBook.ts index 698035ddf..ebee3f4ae 100644 --- a/src/types/orderBook.ts +++ b/src/types/orderBook.ts @@ -1,3 +1,9 @@ +import { INDEXER_TYPES } from '@soramitsu/soraneo-wallet-web'; + +import type { OrderBookId, PriceVariant } from '@sora-substrate/liquidity-proxy'; +import type { FPNumber } from '@sora-substrate/util'; +import type { LimitOrder } from '@sora-substrate/util/build/orderBook/types'; + export enum Filter { open = 'open', all = 'all', @@ -8,3 +14,32 @@ export enum Cancel { multiple = 'multiple', all = 'all', } + +export const OrderStatus = INDEXER_TYPES.OrderStatus; + +export type OrderBookDealData = { + timestamp: number; + side: PriceVariant; + price: FPNumber; + amount: FPNumber; +}; + +export type OrderBookStats = { + price: FPNumber; + priceChange: FPNumber; + volume: FPNumber; + status: string; +}; + +export type OrderBookWithStats = { + id: OrderBookId; + stats: OrderBookStats; +}; + +export type OrderBookUpdateData = OrderBookWithStats & { + deals: OrderBookDealData[]; +}; + +export type OrderData = LimitOrder & { + status: string; +}; diff --git a/src/utils/orderBook.ts b/src/utils/orderBook.ts new file mode 100644 index 000000000..052872738 --- /dev/null +++ b/src/utils/orderBook.ts @@ -0,0 +1,14 @@ +import type { OrderBook } from '@sora-substrate/liquidity-proxy'; + +export function serializeKey(base: string, quote: string): string { + return [base, quote].join(','); +} + +export function deserializeKey(key: string) { + const [base, quote] = key.split(','); + return { base, quote }; +} + +export function getBookDecimals(orderBook: Nullable): number { + return orderBook?.stepLotSize?.toString().split('.')[1]?.length ?? 2; +} diff --git a/src/views/OrderBook.vue b/src/views/OrderBook.vue index 911dc1156..5e432f752 100644 --- a/src/views/OrderBook.vue +++ b/src/views/OrderBook.vue @@ -23,7 +23,7 @@
- +
@@ -31,14 +31,14 @@ diff --git a/yarn.lock b/yarn.lock index 7d4d51cc3..59f368f82 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2346,10 +2346,10 @@ vue-property-decorator "^9.1.2" vuex "^3.6.2" -"@soramitsu/soraneo-wallet-web@1.26.0": - version "1.26.0" - resolved "https://nexus.iroha.tech/repository/npm-group/@soramitsu/soraneo-wallet-web/-/soraneo-wallet-web-1.26.0.tgz#7fbf2ef9688e3334810d3c29bbda3fd1bdd58fb9" - integrity sha512-C1I3DJTcK6E9LbK6NsDksqvZZVBuMdkzzhTuLe+CYfmV8YKdOTBEwicMrsA5SCMru9vz6YychJ9yGN2bJr/0Mw== +"@soramitsu/soraneo-wallet-web@1.26.1": + version "1.26.1" + resolved "https://nexus.iroha.tech/repository/npm-group/@soramitsu/soraneo-wallet-web/-/soraneo-wallet-web-1.26.1.tgz#064d9683cbb2a5e7c1b50580cffeab0034744727" + integrity sha512-I1VdxBMlTPOXCTvA9Id9JkniDiGJClo+OP67MLpRZYdU7LbU6AktZH+6G8xN1qHRLOmp2gwaiWejp2ujElExLA== dependencies: "@polkadot/vue-identicon" "2.12.1" "@sora-substrate/util" "1.25.0" From 1b502d335aa0cfc9513dccea04d41820cf126e07 Mon Sep 17 00:00:00 2001 From: Nikita-Polyakov Date: Fri, 17 Nov 2023 14:35:04 +0300 Subject: [PATCH 059/176] refactoring order table & mixins --- src/components/mixins/ExplorePageMixin.ts | 95 ++----------------- src/components/mixins/ScrollableTableMixin.ts | 95 +++++++++++++++++++ .../pages/OrderBook/Tables/OrderTable.vue | 85 ++++++----------- src/views/Explore/Demeter.vue | 2 +- src/views/Explore/Pools.vue | 2 +- src/views/Explore/Tokens.vue | 2 +- 6 files changed, 135 insertions(+), 146 deletions(-) create mode 100644 src/components/mixins/ScrollableTableMixin.ts diff --git a/src/components/mixins/ExplorePageMixin.ts b/src/components/mixins/ExplorePageMixin.ts index 3ade24223..f0d50bd83 100644 --- a/src/components/mixins/ExplorePageMixin.ts +++ b/src/components/mixins/ExplorePageMixin.ts @@ -1,23 +1,12 @@ -import SScrollbar from '@soramitsu/soramitsu-js-ui/lib/components/Scrollbar'; import { SortDirection } from '@soramitsu/soramitsu-js-ui/lib/components/Table/consts'; -import { mixins, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; -import Vue from 'vue'; -import { Component, Mixins, Prop, Ref, Watch } from 'vue-property-decorator'; +import { Component, Mixins, Prop, Watch } from 'vue-property-decorator'; import { getter } from '@/store/decorators'; -import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; +import ScrollableTableMixin from './ScrollableTableMixin'; @Component -export default class ExplorePageMixin extends Mixins( - mixins.LoadingMixin, - mixins.PaginationSearchMixin, - mixins.FormattedAmountMixin -) { - readonly FontSizeRate = WALLET_CONSTS.FontSizeRate; - readonly FontWeightRate = WALLET_CONSTS.FontWeightRate; - - @Ref('table') readonly tableComponent!: any; +export default class ExplorePageMixin extends Mixins(ScrollableTableMixin) { @Prop({ default: '', type: String }) readonly exploreQuery!: string; @Prop({ default: false, type: Boolean }) readonly isAccountItemsOnly!: boolean; @Watch('exploreQuery') @@ -26,15 +15,10 @@ export default class ExplorePageMixin extends Mixins( } @getter.wallet.account.isLoggedIn isLoggedIn!: boolean; - @getter.assets.assetDataByAddress public getAsset!: (addr?: string) => Nullable; order = ''; property = ''; - get loadingState(): boolean { - return this.parentLoading || this.loading; - } - get pricesAvailable(): boolean { return Object.keys(this.fiatPriceObject).length > 0; } @@ -43,26 +27,23 @@ export default class ExplorePageMixin extends Mixins( return !(this.order && this.property); } - get preparedItems(): any[] { - console.warn('[ExplorePageMixin]: "preparedItems" computed property is not implemented'); + // items -> prefilteredItems -> filteredItems -> preparedItems + get prefilteredItems(): any[] { + console.warn('[ExplorePageMixin]: "prefilteredItems" computed property is not implemented'); return []; } - get total(): number { - return this.filteredItems.length; - } - get filteredItems() { const search = this.exploreQuery.toLowerCase().trim(); - if (!search) return this.preparedItems; + if (!search) return this.prefilteredItems; const filterAsset = (asset): boolean => asset?.name?.toLowerCase?.()?.includes?.(search) || asset?.symbol?.toLowerCase?.()?.includes?.(search) || asset?.address?.toLowerCase?.() === search; - return this.preparedItems.filter( + return this.prefilteredItems.filter( (item: any) => filterAsset(item) || filterAsset(item.poolAsset) || @@ -72,7 +53,7 @@ export default class ExplorePageMixin extends Mixins( ); } - get sortedItems() { + get preparedItems() { if (this.isDefaultSort) return this.filteredItems; const isAscending = this.order === SortDirection.ASC; @@ -87,15 +68,7 @@ export default class ExplorePageMixin extends Mixins( }); } - get tableItems() { - return this.getPageItems(this.sortedItems); - } - async mounted(): Promise { - await this.$nextTick(); - - this.initScrollbar(); - await this.updateExploreData(); } @@ -111,54 +84,4 @@ export default class ExplorePageMixin extends Mixins( async updateExploreData(): Promise { console.warn('[ExplorePageMixin]: "updateExploreData" method is not implemented'); } - - handlePaginationClick(button: WALLET_CONSTS.PaginationButton): void { - let current = 1; - - switch (button) { - case WALLET_CONSTS.PaginationButton.Prev: - current = this.currentPage - 1; - break; - case WALLET_CONSTS.PaginationButton.Next: - current = this.currentPage + 1; - break; - case WALLET_CONSTS.PaginationButton.First: - current = 1; - break; - case WALLET_CONSTS.PaginationButton.Last: - current = this.lastPage; - } - - this.currentPage = current; - } - - private initScrollbar(): void { - if (!this.tableComponent) return; - - const Scrollbar = Vue.extend(SScrollbar); - const scrollbar = new Scrollbar(); - scrollbar.$mount(); - - const elTable = this.tableComponent.$refs.table; - const elTableBodyWrapper = elTable.$refs.bodyWrapper; - const elTableHeaderWrapper = elTable.$refs.headerWrapper; - const elTableNativeTable = elTableBodyWrapper.getElementsByTagName('table')[0]; - const scrollbarWrap = scrollbar.$el.getElementsByClassName('el-scrollbar__wrap')[0]; - const scrollbarView = scrollbar.$el.getElementsByClassName('el-scrollbar__view')[0]; - - elTableBodyWrapper.appendChild(scrollbar.$el); - scrollbarView.appendChild(elTableNativeTable); - - this.$watch( - () => (scrollbar.$children[0] as any).moveX, - () => { - const scrollLeft = scrollbarWrap.scrollLeft; - // to scroll table content - elTableBodyWrapper.scrollLeft = scrollLeft; - elTableHeaderWrapper.scrollLeft = scrollLeft; - // to render box shadow on fixed table - elTable.scrollPosition = scrollLeft === 0 ? 'left' : 'right'; - } - ); - } } diff --git a/src/components/mixins/ScrollableTableMixin.ts b/src/components/mixins/ScrollableTableMixin.ts new file mode 100644 index 000000000..410c7813d --- /dev/null +++ b/src/components/mixins/ScrollableTableMixin.ts @@ -0,0 +1,95 @@ +import SScrollbar from '@soramitsu/soramitsu-js-ui/lib/components/Scrollbar'; +import { mixins, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; +import Vue from 'vue'; +import { Component, Mixins, Ref } from 'vue-property-decorator'; + +import { getter } from '@/store/decorators'; + +import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; + +@Component +export default class ExplorePageMixin extends Mixins( + mixins.LoadingMixin, + mixins.PaginationSearchMixin, + mixins.FormattedAmountMixin +) { + readonly FontSizeRate = WALLET_CONSTS.FontSizeRate; + readonly FontWeightRate = WALLET_CONSTS.FontWeightRate; + + @Ref('table') readonly tableComponent!: any; + + @getter.assets.assetDataByAddress public getAsset!: (addr?: string) => Nullable; + + get loadingState(): boolean { + return this.parentLoading || this.loading; + } + + // should be already filtered & sorted + get preparedItems(): any[] { + console.warn('[ScrollableTableMixin]: "preparedItems" computed property is not implemented'); + return []; + } + + get total(): number { + return this.preparedItems.length; + } + + get tableItems() { + return this.getPageItems(this.preparedItems); + } + + async mounted(): Promise { + await this.$nextTick(); + this.initScrollbar(); + } + + public handlePaginationClick(button: WALLET_CONSTS.PaginationButton): void { + let current = 1; + + switch (button) { + case WALLET_CONSTS.PaginationButton.Prev: + current = this.currentPage - 1; + break; + case WALLET_CONSTS.PaginationButton.Next: + current = this.currentPage + 1; + break; + case WALLET_CONSTS.PaginationButton.First: + current = 1; + break; + case WALLET_CONSTS.PaginationButton.Last: + current = this.lastPage; + } + + this.currentPage = current; + } + + public initScrollbar(): void { + if (!this.tableComponent) return; + + const Scrollbar = Vue.extend(SScrollbar); + const scrollbar = new Scrollbar(); + scrollbar.$mount(); + + const elTable = this.tableComponent.$refs.table; + const elTableBodyWrapper = elTable.$refs.bodyWrapper; + const elTableHeaderWrapper = elTable.$refs.headerWrapper; + const elTableNativeTable = elTableBodyWrapper.getElementsByTagName('table')[0]; + const scrollbarWrap = scrollbar.$el.getElementsByClassName('el-scrollbar__wrap')[0]; + const scrollbarView = scrollbar.$el.getElementsByClassName('el-scrollbar__view')[0]; + + elTableBodyWrapper.appendChild(scrollbar.$el); + scrollbarView.appendChild(elTableNativeTable); + + this.$watch( + () => (scrollbar.$children[0] as any).moveX, + () => { + const scrollLeft = scrollbarWrap.scrollLeft; + // to scroll table content + elTableBodyWrapper.scrollLeft = scrollLeft; + elTableHeaderWrapper.scrollLeft = scrollLeft; + // to render box shadow on fixed table + elTable.scrollPosition = scrollLeft === 0 ? 'left' : 'right'; + } + ); + } +} diff --git a/src/components/pages/OrderBook/Tables/OrderTable.vue b/src/components/pages/OrderBook/Tables/OrderTable.vue index 205d20c2b..9b119432c 100644 --- a/src/components/pages/OrderBook/Tables/OrderTable.vue +++ b/src/components/pages/OrderBook/Tables/OrderTable.vue @@ -1,28 +1,28 @@ - + @@ -38,7 +38,7 @@ {{ row.side }} - + @@ -49,7 +49,7 @@ - + @@ -60,18 +60,21 @@ - + - + @@ -98,7 +101,7 @@ :page-amount="pageAmount" :total="total" :last-page="lastPage" - :loading="parentLoading" + :loading="loadingState" @pagination-click="handlePaginationClick" /> @@ -107,12 +110,12 @@ - - diff --git a/src/components/pages/OrderBook/Tables/OrderTable.vue b/src/components/pages/OrderBook/Tables/OrderTable.vue index ff67859b9..71a5a480b 100644 --- a/src/components/pages/OrderBook/Tables/OrderTable.vue +++ b/src/components/pages/OrderBook/Tables/OrderTable.vue @@ -11,7 +11,7 @@ @selection-change="handleSelectionChange" > - + From e62495f22f0c535698ae5018831b836da7c9806f Mon Sep 17 00:00:00 2001 From: RustemYuzlibaev Date: Tue, 21 Nov 2023 12:29:15 +0500 Subject: [PATCH 068/176] improve adaptive ui --- .../pages/OrderBook/Popovers/PairListPopover.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/components/pages/OrderBook/Popovers/PairListPopover.vue b/src/components/pages/OrderBook/Popovers/PairListPopover.vue index 88f51f528..ef5f79fe2 100644 --- a/src/components/pages/OrderBook/Popovers/PairListPopover.vue +++ b/src/components/pages/OrderBook/Popovers/PairListPopover.vue @@ -232,6 +232,14 @@ export default class PairListPopover extends Mixins( background-color: var(--s-color-utility-body); border-radius: var(--s-border-radius-small); + @include tablet(true) { + width: 624px; + } + + @include mobile(true) { + width: 370px; + } + .cell { display: flex; align-items: center; From 211d77f9619de03dfeb46e11f06fe74175579f5a Mon Sep 17 00:00:00 2001 From: RustemYuzlibaev Date: Tue, 21 Nov 2023 20:04:37 +0500 Subject: [PATCH 069/176] implement steps --- src/components/pages/OrderBook/BookWidget.vue | 42 +++++++++++++++---- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/components/pages/OrderBook/BookWidget.vue b/src/components/pages/OrderBook/BookWidget.vue index 6f962ce84..d4bba9dee 100644 --- a/src/components/pages/OrderBook/BookWidget.vue +++ b/src/components/pages/OrderBook/BookWidget.vue @@ -16,12 +16,7 @@ > {{ '0.001' }} @@ -94,10 +89,14 @@ export default class BookWidget extends Mixins(TranslationMixin, mixins.LoadingM scalerOpen = false; + steps: Array = []; + asksFormatted: Array = []; bidsFormatted: Array = []; // Widget subscription data + @getter.orderBook.currentOrderBook currentOrderBook!: any; + @getter.orderBook.orderBookId private orderBookId!: string; @getter.settings.nodeIsConnected private nodeIsConnected!: boolean; @action.orderBook.subscribeToBidsAndAsks private subscribeToBidsAndAsks!: AsyncFnWithoutArgs; @@ -114,6 +113,33 @@ export default class BookWidget extends Mixins(TranslationMixin, mixins.LoadingM }); } + calculateScalerStep(): void { + const min = this.currentOrderBook?.tickSize; + + if (this.bids.length) { + const averagePrice: FPNumber = this.bids[0][0]; + this.steps = []; + + if (averagePrice.isLessThan(FPNumber.ONE)) { + let max = new FPNumber(0.1); + let result = averagePrice; + + while (result.isLessThan(FPNumber.ONE)) { + result = averagePrice.div(max); + if (result.isGreaterThan(FPNumber.ONE)) break; + max = max.mul(max); + } + + for (let inBetweenStep = max; inBetweenStep.isGreaterThanOrEqualTo(min); ) { + this.steps.push(inBetweenStep.toString()); + inBetweenStep = inBetweenStep.div(FPNumber.TEN); + } + + this.steps.map((value) => console.log(value.toString())); + } + } + } + // Widget subscription close beforeDestroy(): void { this.unsubscribeFromBidsAndAsks(); @@ -227,6 +253,8 @@ export default class BookWidget extends Mixins(TranslationMixin, mixins.LoadingM @Watch('asks') @Watch('bids') async prepareLimitOrders(): Promise { + // this.calculateScalerStep(); + this.asksFormatted = []; this.bidsFormatted = []; this.volumeAsks = FPNumber.ZERO; @@ -318,8 +346,6 @@ export default class BookWidget extends Mixins(TranslationMixin, mixins.LoadingM }); } } - - // this.setVolume(this.volumeAsks.add(this.volumeBids).toFixed(4).toString()); } } From f81f5a4d194aaafabe8dbc11cd9c2e0f4f53770c Mon Sep 17 00:00:00 2001 From: RustemYuzlibaev Date: Tue, 21 Nov 2023 20:07:54 +0500 Subject: [PATCH 070/176] Update OrderTable.vue --- src/components/pages/OrderBook/Tables/OrderTable.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/pages/OrderBook/Tables/OrderTable.vue b/src/components/pages/OrderBook/Tables/OrderTable.vue index 71a5a480b..6f650519e 100644 --- a/src/components/pages/OrderBook/Tables/OrderTable.vue +++ b/src/components/pages/OrderBook/Tables/OrderTable.vue @@ -11,7 +11,7 @@ @selection-change="handleSelectionChange" > - + @@ -76,7 +76,7 @@ - + From 44bb07df58efee0a4657a7f153a59b5ca86b4b7e Mon Sep 17 00:00:00 2001 From: RustemYuzlibaev Date: Tue, 21 Nov 2023 23:22:20 +0500 Subject: [PATCH 071/176] fix wiping out --- src/components/pages/OrderBook/BuySell.vue | 4 ++-- src/components/pages/OrderBook/Tables/OrderTable.vue | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/pages/OrderBook/BuySell.vue b/src/components/pages/OrderBook/BuySell.vue index d9dbbc944..f956e1287 100644 --- a/src/components/pages/OrderBook/BuySell.vue +++ b/src/components/pages/OrderBook/BuySell.vue @@ -152,7 +152,7 @@ export default class BuySellWidget extends Mixins(TranslationMixin, mixins.Forma @state.orderBook.side side!: PriceVariant; @state.orderBook.asks asks!: any; @state.orderBook.bids bids!: any; - @state.wallet.settings.networkFees private networkFees!: NetworkFeesObject; + @state.orderBook.baseAssetAddress baseAssetAddress!: string; @state.orderBook.placeOrderNetworkFee networkFee!: CodecString; @getter.assets.xor private xor!: AccountAsset; @@ -186,7 +186,7 @@ export default class BuySellWidget extends Mixins(TranslationMixin, mixins.Forma marketQuotePrice = ''; @Watch('side') - @Watch('baseAsset') + @Watch('baseAssetAddress') private handleSideChange(): void { this.handleTabClick(this.limitOrderType); } diff --git a/src/components/pages/OrderBook/Tables/OrderTable.vue b/src/components/pages/OrderBook/Tables/OrderTable.vue index 6f650519e..baebe733f 100644 --- a/src/components/pages/OrderBook/Tables/OrderTable.vue +++ b/src/components/pages/OrderBook/Tables/OrderTable.vue @@ -11,7 +11,7 @@ @selection-change="handleSelectionChange" > - + From de52277e3ea9be8a1da67d8123337d9bf122c36d Mon Sep 17 00:00:00 2001 From: RustemYuzlibaev Date: Wed, 22 Nov 2023 12:53:04 +0500 Subject: [PATCH 072/176] fix --- src/components/mixins/ScrollableTableMixin.ts | 6 ------ .../pages/OrderBook/HistoryOrderWidget.vue | 8 ++------ .../pages/OrderBook/Tables/OpenOrders.vue | 11 +---------- .../pages/OrderBook/Tables/OrderTable.vue | 15 ++++++++++----- src/store/orderBook/mutations.ts | 3 +++ src/store/orderBook/state.ts | 1 + src/store/orderBook/types.ts | 1 + src/views/Explore/Pools.vue | 3 ++- src/views/Explore/Tokens.vue | 3 ++- 9 files changed, 22 insertions(+), 29 deletions(-) diff --git a/src/components/mixins/ScrollableTableMixin.ts b/src/components/mixins/ScrollableTableMixin.ts index f760abef7..bfd1e1f47 100644 --- a/src/components/mixins/ScrollableTableMixin.ts +++ b/src/components/mixins/ScrollableTableMixin.ts @@ -3,10 +3,6 @@ import { mixins, WALLET_CONSTS } from '@soramitsu/soraneo-wallet-web'; import Vue from 'vue'; import { Component, Mixins, Ref } from 'vue-property-decorator'; -import { getter } from '@/store/decorators'; - -import type { RegisteredAccountAsset } from '@sora-substrate/util/build/assets/types'; - @Component export default class ExplorePageMixin extends Mixins( mixins.LoadingMixin, @@ -18,8 +14,6 @@ export default class ExplorePageMixin extends Mixins( @Ref('table') readonly tableComponent!: any; - @getter.assets.assetDataByAddress public getAsset!: (addr?: string) => Nullable; - get loadingState(): boolean { return this.parentLoading || this.loading; } diff --git a/src/components/pages/OrderBook/HistoryOrderWidget.vue b/src/components/pages/OrderBook/HistoryOrderWidget.vue index 2a273a67f..eb9ece24f 100644 --- a/src/components/pages/OrderBook/HistoryOrderWidget.vue +++ b/src/components/pages/OrderBook/HistoryOrderWidget.vue @@ -19,7 +19,7 @@
- +