Skip to content

Commit b781f9d

Browse files
committed
chore: draft market dropdown container
1 parent 86363ae commit b781f9d

File tree

12 files changed

+13781
-7991
lines changed

12 files changed

+13781
-7991
lines changed

package-lock.json

+13,433-7,970
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@
106106
"@binary-com/binary-document-uploader": "^2.4.4",
107107
"@binary-com/binary-style": "^0.2.26",
108108
"@binary-com/webtrader-charts": "^0.6.1",
109-
"@deriv-com/quill-ui": "^1.10.27",
109+
"@deriv-com/quill-ui": "^1.11.7",
110110
"@livechat/customer-sdk": "4.0.2",
111111
"canvas-toBlob": "1.0.0",
112112
"classnames": "2.2.5",

src/javascript/app/common/active_symbols.js

+38
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,17 @@ const submarket_order = {
2424
random_nightly : 20,
2525
};
2626

27+
const marketOrder = [
28+
'forex',
29+
'indices',
30+
'cryptocurrency',
31+
'commodities',
32+
'baskets',
33+
'synthetics',
34+
];
35+
36+
const derived = ['baskets','synthetics'];
37+
2738
const ActiveSymbols = (() => {
2839
const groupBy = (xs, key) => (
2940
xs.reduce((rv, x) => {
@@ -218,6 +229,30 @@ const ActiveSymbols = (() => {
218229
return all_symbols;
219230
};
220231

232+
const sortObjectByKeys = (obj, order) => {
233+
const orderedObj = {};
234+
const remainingObj = {};
235+
236+
// Add keys in the specified order
237+
order.forEach(key => {
238+
// eslint-disable-next-line no-prototype-builtins
239+
if (obj.hasOwnProperty(key)) {
240+
orderedObj[key] = obj[key];
241+
}
242+
});
243+
244+
// Add any remaining keys that were not specified in the order array
245+
Object.keys(obj).forEach(key => {
246+
// eslint-disable-next-line no-prototype-builtins
247+
if (!orderedObj.hasOwnProperty(key)) {
248+
remainingObj[key] = obj[key];
249+
}
250+
});
251+
252+
// Combine ordered keys and remaining keys
253+
return { ...orderedObj, ...remainingObj };
254+
};
255+
221256
return {
222257
getMarkets,
223258
getSubmarkets,
@@ -229,6 +264,9 @@ const ActiveSymbols = (() => {
229264
getSymbolsForMarket,
230265
sortSubmarket,
231266
getAvailableUnderlyings,
267+
marketOrder,
268+
derived,
269+
sortObjectByKeys,
232270
};
233271
})();
234272

src/javascript/app/pages/trade/common.js

+15-13
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
const Defaults = require('./defaults');
2-
const Symbols = require('./symbols');
3-
const Tick = require('./tick');
4-
const contractsElement = require('./contracts.jsx');
5-
const marketsElement = require('./markets.jsx');
6-
const TabsElement = require('../bottom/tabs.jsx');
7-
const formatMoney = require('../../common/currency').formatMoney;
8-
const ActiveSymbols = require('../../common/active_symbols');
9-
const elementInnerHtml = require('../../../_common/common_functions').elementInnerHtml;
10-
const getElementById = require('../../../_common/common_functions').getElementById;
11-
const localize = require('../../../_common/localize').localize;
12-
const urlFor = require('../../../_common/url').urlFor;
13-
const cloneObject = require('../../../_common/utility').cloneObject;
1+
const Defaults = require('./defaults');
2+
const Symbols = require('./symbols');
3+
const Tick = require('./tick');
4+
const contractsElement = require('./contracts.jsx');
5+
const marketsElement = require('./markets.jsx');
6+
const MarketDropdownElement = require('./markets/markets-dropdown.jsx');
7+
const TabsElement = require('../bottom/tabs.jsx');
8+
const formatMoney = require('../../common/currency').formatMoney;
9+
const ActiveSymbols = require('../../common/active_symbols');
10+
const elementInnerHtml = require('../../../_common/common_functions').elementInnerHtml;
11+
const getElementById = require('../../../_common/common_functions').getElementById;
12+
const localize = require('../../../_common/localize').localize;
13+
const urlFor = require('../../../_common/url').urlFor;
14+
const cloneObject = require('../../../_common/utility').cloneObject;
1415

1516
/*
1617
* This contains common functions we need for processing the response
@@ -62,6 +63,7 @@ const commonTrading = (() => {
6263

6364
// All other Quill refactored components
6465
TabsElement.init();
66+
MarketDropdownElement.init();
6567
};
6668

6769
/*

src/javascript/app/pages/trade/defaults.js

+2
Original file line numberDiff line numberDiff line change
@@ -44,12 +44,14 @@ const Defaults = (() => {
4444
const getDefault = (key) => {
4545
const p_value = params[key] || Url.param(key);
4646
const s_value = sessionStorage.getItem(key);
47+
4748
if (p_value && (!s_value || p_value !== s_value)) {
4849
sessionStorage.setItem(key, p_value);
4950
}
5051
if (!p_value && s_value) {
5152
setDefault(key, s_value);
5253
}
54+
5355
return p_value || s_value;
5456
};
5557

src/javascript/app/pages/trade/markets.jsx

+1
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,7 @@ class Markets extends React.Component {
131131

132132
this.underlyings = Symbols.getAllSymbols() || {};
133133
let underlying_symbol = Defaults.get(UNDERLYING);
134+
134135
if (!underlying_symbol || !this.underlyings[underlying_symbol]) {
135136
const submarket = Object.keys(this.markets[market_symbol].submarkets).sort(sortSubmarket)[0];
136137
underlying_symbol = Object.keys(this.markets[market_symbol].submarkets[submarket].symbols).sort()[0];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
import React, { useEffect, useRef, useState } from 'react';
2+
import ReactDOM from 'react-dom';
3+
import { DropdownItem, SearchField, Tab, Text } from '@deriv-com/quill-ui';
4+
import { getElementById } from '../../../../_common/common_functions';
5+
import Symbols from '../symbols';
6+
import {
7+
marketOrder,
8+
sortObjectByKeys,
9+
derived,
10+
} from '../../../common/active_symbols';
11+
import Defaults, { PARAM_NAMES } from '../defaults';
12+
import { triggerMarketChange } from '../../../hooks/events';
13+
14+
const MarketsDropdown = () => {
15+
const { UNDERLYING } = PARAM_NAMES;
16+
const [defaultMarkets, setDefaultMarkets] = useState({});
17+
const [markets, setMarkets] = useState({});
18+
const [isMounted, setIsMounted] = useState(false);
19+
const [activeMarket, setActiveMarket] = useState('forex');
20+
const [selectedMarket, setSelectedMarket] = useState(
21+
Defaults.get(UNDERLYING)
22+
);
23+
const [searchKey, setSearchKey] = useState('');
24+
const itemsContainer = useRef(null);
25+
const isScrolling = useRef(false);
26+
27+
const filterMarkets = () => {
28+
const data = JSON.parse(JSON.stringify(defaultMarkets));
29+
const searchStr = searchKey?.toLowerCase();
30+
let foundMatchingSymbol = false;
31+
32+
Object.keys(data).forEach(marketKey => {
33+
const market = data[marketKey];
34+
35+
Object.keys(market.submarkets).forEach(submarketKey => {
36+
const submarket = market.submarkets[submarketKey];
37+
const subMarketName = submarket.name.toLowerCase();
38+
39+
// If submarket name matches to search key then don't filter children
40+
if (!subMarketName.includes(searchStr)){
41+
Object.keys(submarket.symbols).forEach(symbolKey => {
42+
const symbol = submarket.symbols[symbolKey];
43+
const displayName = symbol.display.toLowerCase();
44+
45+
if (!displayName.includes(searchStr)) {
46+
delete submarket.symbols[symbolKey];
47+
} else {
48+
foundMatchingSymbol = true;
49+
}
50+
});
51+
} else {
52+
foundMatchingSymbol = true;
53+
}
54+
55+
if (Object.keys(submarket.symbols).length === 0) {
56+
delete market.submarkets[submarketKey];
57+
}
58+
});
59+
60+
if (Object.keys(market.submarkets).length === 0) {
61+
delete data[marketKey];
62+
}
63+
});
64+
65+
// If no matching symbols were found, return the original markets object
66+
if (!foundMatchingSymbol) {
67+
return defaultMarkets;
68+
}
69+
70+
return data;
71+
};
72+
73+
useEffect(() => {
74+
setMarkets(filterMarkets());
75+
},[searchKey]);
76+
77+
useEffect(() => {
78+
const marketList = Symbols.markets();
79+
const originalMarkets = sortObjectByKeys(marketList, marketOrder);
80+
setDefaultMarkets(originalMarkets);
81+
setMarkets(originalMarkets);
82+
setIsMounted(true);
83+
}, []);
84+
85+
// Handle selecting of tabs on scroll
86+
useEffect(() => {
87+
const container = itemsContainer.current;
88+
89+
const checkActiveMarket = () => {
90+
const marketDivs = container.querySelectorAll('div');
91+
let closestMarket = '';
92+
let closestOffset = Infinity;
93+
94+
marketDivs.forEach((div) => {
95+
const paddingOffset = 110;
96+
const offsetTop = div.offsetTop - container.scrollTop - paddingOffset;
97+
98+
if (offsetTop <= 0 && Math.abs(offsetTop) < Math.abs(closestOffset)) {
99+
closestOffset = offsetTop;
100+
closestMarket = div.getAttribute('data-id');
101+
}
102+
});
103+
104+
if (closestMarket) {
105+
setActiveMarket(closestMarket);
106+
}
107+
};
108+
109+
const handleScroll = () => {
110+
if (!isScrolling.current) {
111+
checkActiveMarket();
112+
}
113+
};
114+
115+
container.addEventListener('scroll', handleScroll);
116+
117+
return () => {
118+
container.removeEventListener('scroll', handleScroll);
119+
};
120+
}, [isMounted]);
121+
122+
const getactiveMarketIndex = () => Object.keys(markets).indexOf(activeMarket);
123+
124+
const scrollToMarketByIndex = (index) => {
125+
isScrolling.current = true;
126+
const container = itemsContainer.current;
127+
128+
if (container) {
129+
const marketDivs = container ? container.children : [];
130+
if (marketDivs[index]) {
131+
const offsetTop = marketDivs[index].offsetTop - container.offsetTop;
132+
container.scrollTo({
133+
top : offsetTop,
134+
behavior: 'smooth',
135+
});
136+
}
137+
setTimeout(() => {
138+
isScrolling.current = false;
139+
}, 1000);
140+
}
141+
};
142+
143+
const handleUnderlyingClick = (underlying) => {
144+
Defaults.set(UNDERLYING, underlying);
145+
setSelectedMarket(underlying);
146+
triggerMarketChange();
147+
};
148+
149+
return (
150+
<div className='quill-market-dropdown-container'>
151+
<div className='quill-market-dropdown-search-container'>
152+
<SearchField
153+
inputSize='sm'
154+
onChange={(e) => setSearchKey(e.target.value)}
155+
placeholder='Search...'
156+
value=''
157+
variant='fill'
158+
/>
159+
</div>
160+
<div className='quill-market-dropdown-tab-container'>
161+
<Tab.Container
162+
size='md'
163+
contentStyle='hug'
164+
selectedTabIndex={getactiveMarketIndex()}
165+
onTabClick={(e) => scrollToMarketByIndex(e)}
166+
>
167+
<Tab.List>
168+
{Object.keys(markets).map((ik) => {
169+
const market = markets[ik];
170+
const marketName = derived.includes(ik)
171+
? `Derived (${market.name})`
172+
: market.name;
173+
return <Tab.Trigger key={ik}>{marketName}</Tab.Trigger>;
174+
})}
175+
</Tab.List>
176+
</Tab.Container>
177+
<div
178+
className='quill-market-dropdown-item-container'
179+
id='quill-market-dropdown-list'
180+
ref={itemsContainer}
181+
>
182+
{Object.keys(markets).map((ik) => {
183+
const market = markets[ik];
184+
const { submarkets } = market;
185+
186+
return (
187+
<div id={`${ik}-dropdown-list`} key={ik} data-id={ik}>
188+
{Object.keys(submarkets).map((sk) => {
189+
const submarket = submarkets[sk];
190+
const { symbols, name } = submarket;
191+
192+
return (
193+
<React.Fragment key={sk}>
194+
{
195+
<Text size='md' bold className='market-item-heading'>
196+
{name}
197+
</Text>
198+
}
199+
{Object.keys(symbols).map((yk) => {
200+
const symbol = symbols[yk];
201+
const { display } = symbol;
202+
203+
return (
204+
<DropdownItem
205+
key={yk}
206+
className=''
207+
onClick={() => handleUnderlyingClick(yk)}
208+
label={display}
209+
selected={yk === selectedMarket}
210+
size='sm'
211+
/>
212+
);
213+
})}
214+
<hr />
215+
</React.Fragment>
216+
);
217+
})}
218+
</div>
219+
);
220+
})}
221+
</div>
222+
</div>
223+
</div>
224+
);
225+
};
226+
227+
export const init = () => {
228+
ReactDOM.render(
229+
<MarketsDropdown />,
230+
getElementById('markets-dropdown-container')
231+
);
232+
};
233+
234+
export default init;

src/javascript/app/pages/trade/process.js

+1-3
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,7 @@ const Process = (() => {
9696
const processMarketUnderlying = () => {
9797
const underlying_element = document.getElementById('underlying');
9898
const underlying = underlying_element.value;
99-
100-
Defaults.set(UNDERLYING, underlying);
101-
99+
102100
commonTrading.showFormOverlay();
103101

104102
// get ticks for current underlying

0 commit comments

Comments
 (0)