diff --git a/src/botPage/common/symbolApi/index.js b/src/botPage/common/symbolApi/index.js index ef2f1d1035..6ed3269760 100644 --- a/src/botPage/common/symbolApi/index.js +++ b/src/botPage/common/symbolApi/index.js @@ -1,7 +1,7 @@ import ActiveSymbols from './activeSymbols'; import config from '../../common/const'; import { getObjectValue } from '../../../common/utils/tools'; -import { getTokenList } from '../../../common/utils/storageManager'; +import { getTokenList, removeAllTokens } from '../../../common/utils/storageManager'; const noop = () => {}; @@ -44,7 +44,7 @@ export default class _Symbol { constructor(api) { this.api = api; this.initPromise = new Promise(resolve => { - const getActiveSymbolsPromise = () => { + const getActiveSymbolsLogic = () => { this.api.getActiveSymbolsBrief().then(r => { this.activeSymbols = new ActiveSymbols(r.active_symbols); this.api.getAssetIndex().then(r2 => { @@ -53,17 +53,19 @@ export default class _Symbol { }, noop); }, noop); }; - // Authorize when possible for accurate offered symbols, assetindex - const getAuthorisePromise = () => { - const tokenList = getTokenList(); - if (tokenList.length) { - return this.api.authorize(tokenList[0].token); - } - return new Promise(r => r()); - }; - getAuthorisePromise() - .then(() => getActiveSymbolsPromise(), noop) - .catch(() => getActiveSymbolsPromise(), noop); + // Authorize the WS connection when possible for accurate offered Symbols & AssetIndex + const tokenList = getTokenList(); + if (tokenList.length) { + this.api + .authorize(tokenList[0].token) + .then(getActiveSymbolsLogic()) + .catch(() => { + removeAllTokens(); + getActiveSymbolsLogic(); + }); + } else { + getActiveSymbolsLogic(); + } }); } /* eslint-disable class-methods-use-this */ diff --git a/src/botPage/view/View.js b/src/botPage/view/View.js index 165f5a6645..88cf9542fc 100644 --- a/src/botPage/view/View.js +++ b/src/botPage/view/View.js @@ -56,10 +56,6 @@ api.events.on('website_status', response => { } }); -api.send({ time: '1' }).then(response => { - ReactDOM.render(, $('#server-time')[0]); -}); - api.events.on('balance', response => { const { balance: { balance: b, currency }, @@ -175,11 +171,12 @@ const getActiveToken = (tokenList, activeToken) => { const updateTokenList = () => { const tokenList = getTokenList(); - const loginButton = $('#login'); - const accountList = $('#account-list'); + const loginButton = $('#login, #toolbox-login'); + const accountList = $('#account-list, #toolbox-account-list'); if (tokenList.length === 0) { loginButton.show(); accountList.hide(); + $('.account-id') .removeAttr('value') .text(''); @@ -190,7 +187,6 @@ const updateTokenList = () => { } else { loginButton.hide(); accountList.show(); - const activeToken = getActiveToken(tokenList, getStorage(AppConstants.STORAGE_ACTIVE_TOKEN)); updateLogo(activeToken.token); addBalanceForToken(activeToken.token); @@ -200,7 +196,6 @@ const updateTokenList = () => { } tokenList.forEach(tokenInfo => { const prefix = isVirtual(tokenInfo) ? 'Virtual Account' : `${tokenInfo.loginInfo.currency} Account`; - if (tokenInfo === activeToken) { $('.account-id') .attr('value', `${tokenInfo.token}`) @@ -277,6 +272,7 @@ export default class View { applyToolboxPermissions(); renderReactComponents(); if (!getTokenList().length) updateLogo(); + this.showHeader(getStorage('showHeader') !== 'false'); resolve(); }); }); @@ -468,11 +464,13 @@ export default class View { $('#showSummary').click(showSummary); + $('#toggleHeaderButton').click(() => this.showHeader($('#header').is(':hidden'))); + $('#loadXml').click(() => { $('#files').click(); }); - $('#logout').click(() => { + $('#logout, #toolbox-logout').click(() => { setBeforeUnload(true); logout(); hideRealityCheck(); @@ -573,7 +571,7 @@ export default class View { .catch(() => {}); }); - $('#login') + $('#login, #toolbox-login') .bind('click.login', () => { setBeforeUnload(true); document.location = getOAuthURL(); @@ -636,6 +634,22 @@ export default class View { } }); } + showHeader = show => { + const $header = $('#header'); + const $topbarAccount = $('#toolbox-account'); + const $toggleHeaderButton = $('.icon-hide-header'); + if (show) { + $header.show(0); + $topbarAccount.hide(0); + $toggleHeaderButton.removeClass('enabled'); + } else { + $header.hide(0); + $topbarAccount.show(0); + $toggleHeaderButton.addClass('enabled'); + } + setStorage('showHeader', show); + window.dispatchEvent(new Event('resize')); + }; } function initRealityCheck(stopCallback) { @@ -648,6 +662,7 @@ function initRealityCheck(stopCallback) { ); } function renderReactComponents() { + ReactDOM.render(, $('#server-time')[0]); ReactDOM.render(, $('#tour')[0]); ReactDOM.render( { const contractsForStore = JSON.parse(getStorage('contractsForStore') || '[]'); - const tokenList = getTokenList(); + let tokenList = getTokenList(); const defaultDurations = [ [translate('Ticks'), 't'], [translate('Seconds'), 's'], @@ -144,13 +149,19 @@ export const getAvailableDurations = (symbol, selectedContractType) => { [translate('Hours'), 'h'], [translate('Days'), 'd'], ]; + const getContractsForSymbolFromApi = async underlyingSymbol => { // Refactor this when reducing WS connections - let api = generateLiveApiInstance(); + const api = generateLiveApiInstance(); // Try to authorize for accurate contracts response if (tokenList.length) { - await api.authorize(tokenList[0].token); + try { + await api.authorize(tokenList[0].token); + } catch (e) { + removeAllTokens(); + tokenList = []; + } } const response = await api.getContractsForSymbol(underlyingSymbol); @@ -176,7 +187,6 @@ export const getAvailableDurations = (symbol, selectedContractType) => { setStorage('contractsForStore', JSON.stringify(contractsForStore)); } api.disconnect(); - api = null; return contractsForSymbol; }; const getDurationsForContract = contractsForSymbol => { diff --git a/src/botPage/view/blockly/blocks/trade/components.js b/src/botPage/view/blockly/blocks/trade/components.js index 8d0148fe2e..8252e32ac1 100644 --- a/src/botPage/view/blockly/blocks/trade/components.js +++ b/src/botPage/view/blockly/blocks/trade/components.js @@ -76,6 +76,17 @@ export const barrierOffset = block => { .setCheck('Number') .appendField(`${translate('Barrier Offset')} 1:`) .appendField(new Blockly.FieldDropdown(config.barrierTypes), 'BARRIEROFFSETTYPE_LIST'); + } else { + const barrierOffsetList = block.getField('BARRIEROFFSETTYPE_LIST'); + if (!block.workspace.getBlockById('BARRIERVALUE')) { + const barrierValue = block.workspace.newBlock('math_number', 'BARRIERVALUE'); + barrierOffsetList.setValue('+'); + barrierValue.setFieldValue('0.274', 'NUM'); + barrierValue.setShadow(true); + barrierValue.outputConnection.connect(block.getInput('BARRIEROFFSET').connection); + barrierValue.initSvg(); + barrierValue.render(); + } } }; @@ -86,6 +97,17 @@ export const secondBarrierOffset = block => { .setCheck('Number') .appendField(`${translate('Barrier Offset')} 2:`) .appendField(new Blockly.FieldDropdown(config.barrierTypes), 'SECONDBARRIEROFFSETTYPE_LIST'); + } else { + const barrierOffsetList = block.getField('SECONDBARRIEROFFSETTYPE_LIST'); + if (!block.workspace.getBlockById('SECONDBARRIERVALUE')) { + const secondBarrierValue = block.workspace.newBlock('math_number', 'SECONDBARRIERVALUE'); + barrierOffsetList.setValue('-'); + secondBarrierValue.setFieldValue('0.274', 'NUM'); + secondBarrierValue.setShadow(true); + secondBarrierValue.outputConnection.connect(block.getInput('SECONDBARRIEROFFSET').connection); + secondBarrierValue.initSvg(); + secondBarrierValue.render(); + } } }; diff --git a/src/botPage/view/blockly/blocks/trade/index.js b/src/botPage/view/blockly/blocks/trade/index.js index 0821e12617..9468c61588 100644 --- a/src/botPage/view/blockly/blocks/trade/index.js +++ b/src/botPage/view/blockly/blocks/trade/index.js @@ -30,8 +30,7 @@ const decorateTrade = ev => { } if ([Blockly.Events.CHANGE, Blockly.Events.MOVE, Blockly.Events.CREATE].includes(ev.type)) { const symbol = trade.getFieldValue('SYMBOL_LIST'); - - if (symbol && (ev.group === 'reset' || ev.type !== Blockly.Events.CREATE)) { + if (symbol) { globalObserver.emit('bot.init', symbol); } diff --git a/src/botPage/view/blockly/blocks/trade/tradeOptions.js b/src/botPage/view/blockly/blocks/trade/tradeOptions.js index 40cbadc6e5..965d5d8fa4 100644 --- a/src/botPage/view/blockly/blocks/trade/tradeOptions.js +++ b/src/botPage/view/blockly/blocks/trade/tradeOptions.js @@ -38,9 +38,13 @@ export default () => { const tradeType = getTradeType(this); const prevSelectedDuration = durationTypeList.getValue(); + + Blockly.Events.recordUndo = false; this.setFieldValue(translate('Loading...'), 'DURATIONTYPE_LIST'); + Blockly.Events.recordUndo = true; getAvailableDurations(symbol, tradeType).then(durations => { + Blockly.Events.recordUndo = false; // Prevent UI flickering by only updating field if options have changed // eslint-disable-next-line no-underscore-dangle if (JSON.stringify(durationTypeList.menuGenerator_) !== JSON.stringify(durations)) { @@ -56,6 +60,7 @@ export default () => { } else { this.setFieldValue(translate('Not available'), 'DURATIONTYPE_LIST'); } + Blockly.Events.recordUndo = true; }); } } diff --git a/src/botPage/view/blockly/index.js b/src/botPage/view/blockly/index.js index 1770cd03b2..47ec73cead 100644 --- a/src/botPage/view/blockly/index.js +++ b/src/botPage/view/blockly/index.js @@ -243,11 +243,11 @@ export default class _Blockly { this.blocksXmlStr = Blockly.Xml.domToPrettyText(main); Blockly.Xml.domToWorkspace(main.getElementsByTagName('xml')[0], workspace); this.zoomOnPlusMinus(); - Blockly.mainWorkspace.clearUndo(); disposeBlocksWithLoaders(); setTimeout(() => { setBeforeUnload(true); Blockly.mainWorkspace.cleanUp(); + Blockly.mainWorkspace.clearUndo(); }, 0); resolve(); }); @@ -417,3 +417,43 @@ while(true) { } /* eslint-enable */ } + +// Hooks to override default Blockly behaviour +/* eslint-disable no-unused-expressions */ +const originalContextMenuFn = Blockly.ContextMenu.show; +Blockly.ContextMenu.show = (e, menuOptions, rtl) => { + // Rename 'Clean up blocks' + menuOptions.some(option => { + if (option.text === Blockly.Msg.CLEAN_UP) { + option.text = translate('Rearrange vertically'); // eslint-disable-line no-param-reassign + return true; + } + return false; + }) && + /* Remove delete all blocks, but only when 'Clean up blocks' is available (i.e. workspace) + * This allows users to still delete root blocks containing blocks + */ + menuOptions.some((option, i) => { + if ( + option.text === Blockly.Msg.DELETE_BLOCK || + option.text.replace(/[0-9]+/, '%1') === Blockly.Msg.DELETE_X_BLOCKS + ) { + menuOptions.splice(i, 1); + return true; + } + return false; + }); + // Open the Elev.io widget when clicking 'Help' + // eslint-disable-next-line no-underscore-dangle + if (window._elev) { + menuOptions.some(option => { + if (option.text === Blockly.Msg.HELP) { + option.callback = () => window._elev.openHome(); // eslint-disable-line no-param-reassign, no-underscore-dangle + return true; + } + return false; + }); + } + originalContextMenuFn(e, menuOptions, rtl); +}; +/* eslint-enable */ diff --git a/src/botPage/view/react-components/HeaderWidgets.js b/src/botPage/view/react-components/HeaderWidgets.js index a445297589..82a2623dd8 100644 --- a/src/botPage/view/react-components/HeaderWidgets.js +++ b/src/botPage/view/react-components/HeaderWidgets.js @@ -1,25 +1,35 @@ import React, { Component } from 'react'; export default class ServerTime extends Component { - constructor() { + constructor(props) { super(); this.state = {}; + const getServerTime = () => { + props.api.send({ time: '1' }).then(response => + this.setState( + { + date: new Date(response.time * 1000), + }, + this.updateTime() + ) + ); + }; + getServerTime(); + setInterval(() => this.updateTime(), 1000); + setInterval(() => getServerTime(), 30000); } updateTime() { - this.date.setSeconds(this.date.getSeconds() + 1); - const year = this.date.getUTCFullYear(); - const month = `0${this.date.getMonth() + 1}`.slice(-2); - const date = `0${this.date.getUTCDate()}`.slice(-2); - const hours = `0${this.date.getUTCHours()}`.slice(-2); - const minutes = `0${this.date.getMinutes()}`.slice(-2); - const seconds = `0${this.date.getSeconds()}`.slice(-2); - this.setState({ date: `${year}-${month}-${date} ${hours}:${minutes}:${seconds} GMT` }); - } - componentWillMount() { - this.date = new Date(this.props.startTime * 1000); - setInterval(() => this.updateTime(), 1000); + if (!this.state.date) return; + this.state.date.setSeconds(this.state.date.getSeconds() + 1); + const year = this.state.date.getUTCFullYear(); + const month = `0${this.state.date.getMonth() + 1}`.slice(-2); + const day = `0${this.state.date.getUTCDate()}`.slice(-2); + const hours = `0${this.state.date.getUTCHours()}`.slice(-2); + const minutes = `0${this.state.date.getMinutes()}`.slice(-2); + const seconds = `0${this.state.date.getSeconds()}`.slice(-2); + this.setState({ dateString: `${year}-${month}-${day} ${hours}:${minutes}:${seconds} GMT` }); } render() { - return {this.state.date}; + return {this.state.dateString}; } } diff --git a/src/botPage/view/tour/index.js b/src/botPage/view/tour/index.js index 137bb55a5f..385396bece 100644 --- a/src/botPage/view/tour/index.js +++ b/src/botPage/view/tour/index.js @@ -8,7 +8,9 @@ const setDoneCheck = () => { const doNotAskCheck = document.getElementById('do-not-ask-me-again'); if (doNotAskCheck && doNotAskCheck.checked) { setDone('welcomeFinished'); + return true; } + return false; }; class Tour extends PureComponent { @@ -26,6 +28,9 @@ class Tour extends PureComponent { setDoneCheck(); setStorage('closedTourPopup', Date.now()); this.joyride.stop(); + if ($('#toggleHeaderButton').is(':hidden')) { + $('#toggleHeaderButton').show(); + } }, }; } @@ -45,8 +50,23 @@ class Tour extends PureComponent { element.scrollIntoView(); } } - if (data.index === 0 && data.type === 'step:after') { - setDoneCheck(); + const $toggleHeaderButton = $('#toggleHeaderButton'); + if (data.type === 'step:after') { + // Reveal header if user hid it so tour is working properly + const hasSupressedTour = data.index === 0 && setDoneCheck(); + if (!hasSupressedTour) { + if ($('#header').is(':hidden')) { + $toggleHeaderButton.click(); + } + if (data.index < welcome.length - 1) { + $toggleHeaderButton.hide(); + } else { + $toggleHeaderButton.show(); + } + } + } + if (data.action === 'close' && $toggleHeaderButton.is(':hidden')) { + $toggleHeaderButton.show(); } }; const shouldShowTourPopup = () => { diff --git a/src/botPage/view/tour/welcome.js b/src/botPage/view/tour/welcome.js index f1e801bfaf..6eeb0c3db7 100644 --- a/src/botPage/view/tour/welcome.js +++ b/src/botPage/view/tour/welcome.js @@ -58,7 +58,7 @@ const steps = [ text : `

${translate('Login before starting your bot. Always test your strategies with the virtual account.')}

`, - selector: '.right-header', + selector: '.intro-login-logout', position: 'left', }, { diff --git a/src/common/binary-ui/dropdown.js b/src/common/binary-ui/dropdown.js index ea018c59a1..1f36e880c0 100644 --- a/src/common/binary-ui/dropdown.js +++ b/src/common/binary-ui/dropdown.js @@ -22,7 +22,15 @@ function navMenuListener() { event.stopPropagation(); hide_menu($('.top-nav-menu li ul')); hide_menu($('#language_select, #select_language')); - var $el = $('#all-accounts, #all-accounts-top'); + let $el; + if ($(this).is('#toolbox-main-account')) { + $el = $('#toolbox-all-accounts'); + hide_menu($('#all-accounts')); + } else { + $el = $('#all-accounts'); + hide_menu($('#toolbox-all-accounts')); + } + if ($el.css('opacity') == 1) { hide_menu($el); } else { @@ -34,7 +42,7 @@ function navMenuListener() { function topNavMenuListener() { $('.top-nav-menu > li').on('click', function(event) { event.stopPropagation(); - hide_menu($('#all-accounts, #all-accounts-top')); + hide_menu($('#all-accounts, #toolbox-all-accounts')); hide_menu($('#language_select, #select_language')); var childMenu = $(this).find(' > ul'), $el = $('.top-nav-menu li ul'); @@ -61,7 +69,7 @@ function topNavMenuListener() { function documentListener() { $(document).on('click', function() { - hide_menu($('#all-accounts, #all-accounts-top')); + hide_menu($('#all-accounts, #toolbox-all-accounts')); hide_menu($('.top-nav-menu li ul')); hide_menu($('#language_select, #select_language')); }); @@ -71,7 +79,7 @@ function langListener() { $('.languages').on('click', function(event) { event.stopPropagation(); hide_menu($('.top-nav-menu li ul')); - hide_menu($('#all-accounts, #all-accounts-top')); + hide_menu($('#all-accounts, #toolbox-all-accounts')); var $el = $('#language_select, #select_language'); if ($el.css('opacity') == 1) { hide_menu($el); diff --git a/src/common/elevio.js b/src/common/elevio.js index c1a6ad9f31..726eaeb596 100644 --- a/src/common/elevio.js +++ b/src/common/elevio.js @@ -13,6 +13,10 @@ const Elevio = (() => { // if (availableElevLanguages.indexOf(currentLanguage) !== -1) { // window._elev.setLanguage(currentLanguage); // eslint-disable-line no-underscore-dangle // } + // eslint-disable-next-line no-underscore-dangle + window._elev.setSettings({ + page_url: `${document.location.protocol}//${document.location.hostname}${document.location.pathname}`, + }); setUserInfo(elev); setTranslations(elev); }); diff --git a/static/css/_fontello.scss b/static/css/_fontello.scss index 5e9d708905..d8b3e2eea9 100755 --- a/static/css/_fontello.scss +++ b/static/css/_fontello.scss @@ -70,4 +70,13 @@ .icon-trading-view:before { content: '\e810'; } /* '' */ .icon-info:before { content: '\f129'; } /* '' */ .icon-sort:before { content: '\f160'; } /* '' */ -.icon-chart-line:before { content: '\f201'; } /* '' */ \ No newline at end of file +.icon-chart-line:before { content: '\f201'; } /* '' */ +.icon-hide-header { + &:before { + content: ' '; + } + background: url("../image/expand_less.svg") no-repeat center; + &.enabled { + background: url("../image/expand_more.svg") no-repeat center; + } +} \ No newline at end of file diff --git a/static/css/_toolbox.scss b/static/css/_toolbox.scss index e9b2088f44..ec1dd85849 100644 --- a/static/css/_toolbox.scss +++ b/static/css/_toolbox.scss @@ -25,6 +25,7 @@ border-bottom: 1px solid $brand-dark-gray; padding: 2px 3px; z-index: 0; + overflow: auto; #runButton[disabled], #runButton[disabled]:hover { @include toolbox-runButton-disabled; @@ -55,3 +56,57 @@ width: 2px; } } + +#toolbox-login { + margin-top: 1px; + margin-right: 20px; +} + +#toolbox-account { + display: table; + height: 40px; +} + +#toolbox-account-list { + position: relative; + user-select: none; + display: table-cell; + vertical-align: middle; + padding-right: 20px; + &>a { + color: $black; + &:hover, &:visited { + text-decoration: none; + } + } +} + +#toolbox-main-account { + display: inline; + padding-inline-start: 0; + &>li { + display: inline; + margin: 0 2px; + } + &>li:not(:first-child):not(:last-child):before { + content: '\2022'; + margin-right: 7px; + } + &>.nav-caret { + display: inline-block; + } +} + +#toolbox-all-accounts { + border: 0; + z-index: 100; + margin-top: -1px; + background: none; + right: 20px; + & li { + height: 100%; + & a { + margin-top: 0; + } + } +} diff --git a/static/css/bot.scss b/static/css/bot.scss index 026d8aed99..b50babb388 100644 --- a/static/css/bot.scss +++ b/static/css/bot.scss @@ -110,6 +110,14 @@ body { display: table; } +#all-accounts.main-nav, #toolbox-all-accounts.main-nav { + >li { + >ul { + width: 165px; + } + } +} + .left-header { float: left; } diff --git a/static/image/expand_less.svg b/static/image/expand_less.svg new file mode 100644 index 0000000000..99491c6370 --- /dev/null +++ b/static/image/expand_less.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/static/image/expand_more.svg b/static/image/expand_more.svg new file mode 100644 index 0000000000..690a0a1d57 --- /dev/null +++ b/static/image/expand_more.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/templates/bot.mustache b/templates/bot.mustache index 46e9fb39c2..1e5cf21f9e 100644 --- a/templates/bot.mustache +++ b/templates/bot.mustache @@ -1,15 +1,15 @@ - + {{> ../templates/partials/security }} {{> ../templates/partials/head }} Binary Bot {{> bundle_css }} {{> bot_css }} - + - +
@@ -37,126 +37,159 @@

-
- {{> ../templates/partials/loading }}
-
- {{> ../templates/partials/language }} -
-
- -
+
+ {{> ../templates/partials/language }} +
+
+ +
-