diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 4d65f2ce..eb7274c1 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -31,3 +31,7 @@ jobs: - name: Build run: | make build site=testnet + + - name: Format + run: | + npm run format:check diff --git a/src/App.js b/src/App.js index 56a47738..53d31e1b 100644 --- a/src/App.js +++ b/src/App.js @@ -29,8 +29,13 @@ import VersionError from './screens/VersionError'; import WebSocketHandler from './WebSocketHandler'; import NanoContractDetail from './screens/nano/NanoContractDetail'; import BlueprintDetail from './screens/nano/BlueprintDetail'; -import { apiLoadErrorUpdate, dashboardUpdate, isVersionAllowedUpdate, updateServerInfo } from "./actions/index"; -import { connect } from "react-redux"; +import { + apiLoadErrorUpdate, + dashboardUpdate, + isVersionAllowedUpdate, + updateServerInfo, +} from './actions/index'; +import { connect } from 'react-redux'; import versionApi from './api/version'; import helpers from './utils/helpers'; import { axios as hathorLibAxios, config as hathorLibConfig } from '@hathor/wallet-lib'; @@ -48,47 +53,48 @@ const mapDispatchToProps = dispatch => { }; }; - -const mapStateToProps = (state) => { +const mapStateToProps = state => { return { isVersionAllowed: state.isVersionAllowed, apiLoadError: state.apiLoadError }; }; - class Root extends React.Component { componentDidMount() { WebSocketHandler.on('dashboard', this.handleWebsocket); hathorLibAxios.registerNewCreateRequestInstance(createRequestInstance); - this.props.apiLoadErrorUpdate({apiLoadError: false}); - - versionApi.getVersion().then((data) => { - let network = data.network; - if (data.network.includes('testnet')) { - network = 'testnet'; + this.props.apiLoadErrorUpdate({ apiLoadError: false }); + + versionApi.getVersion().then( + data => { + let network = data.network; + if (data.network.includes('testnet')) { + network = 'testnet'; + } + hathorLibConfig.setNetwork(network); + this.props.updateServerInfo(data); + this.props.isVersionAllowedUpdate({ allowed: helpers.isVersionAllowed(data.version) }); + }, + e => { + // Error in request + console.log(e); + this.props.apiLoadErrorUpdate({ apiLoadError: true }); } - hathorLibConfig.setNetwork(network); - this.props.updateServerInfo(data); - this.props.isVersionAllowedUpdate({allowed: helpers.isVersionAllowed(data.version)}); - }, (e) => { - // Error in request - console.log(e); - this.props.apiLoadErrorUpdate({apiLoadError: true}); - }); + ); } componentWillUnmount() { WebSocketHandler.removeListener('dashboard', this.handleWebsocket); } - handleWebsocket = (wsData) => { + handleWebsocket = wsData => { if (wsData.type === 'dashboard:metrics') { this.updateWithWs(wsData); } - } + }; - updateWithWs = (data) => { + updateWithWs = data => { this.props.dashboardUpdate({ ...data }); - } + }; render() { if (this.props.isVersionAllowed === undefined) { @@ -97,14 +103,18 @@ class Root extends React.Component { <> - { this.props.apiLoadError ? + {this.props.apiLoadError ? (
-

Error loading the explorer. Please reload the page to try again.

+

+ Error loading the explorer. Please reload the page to try again. +

- : } + ) : ( + + )} -
); - + + ); } else if (!this.props.isVersionAllowed) { return ; } else { @@ -125,22 +135,36 @@ class Root extends React.Component { - - + + - ) + ); } } } const NavigationRoute = ({ component: Component, ...rest }) => ( - ( -
- )} /> -) + ( +
+ + +
+ )} + /> +); export default connect(mapStateToProps, mapDispatchToProps)(Root); diff --git a/src/WebSocketHandler.js b/src/WebSocketHandler.js index eff392a9..2ae93162 100644 --- a/src/WebSocketHandler.js +++ b/src/WebSocketHandler.js @@ -8,11 +8,10 @@ import EventEmitter from 'events'; import { WS_URL } from './constants'; -const HEARTBEAT_TMO = 30000; // 30s - +const HEARTBEAT_TMO = 30000; // 30s class WS extends EventEmitter { - constructor(){ + constructor() { if (!WS.instance) { super(); this.connected = false; @@ -33,45 +32,44 @@ class WS extends EventEmitter { this.ws.onmessage = this.onMessage; this.ws.onerror = this.onError; this.ws.onclose = this.onClose; - } + }; onMessage = evt => { - const message = JSON.parse(evt.data) - const _type = message.type.split(':')[0] - this.emit(_type, message) - } + const message = JSON.parse(evt.data); + const _type = message.type.split(':')[0]; + this.emit(_type, message); + }; onOpen = () => { this.connected = true; console.log('ws connection established'); this.heartbeat = setInterval(this.sendPing, HEARTBEAT_TMO); - } + }; onClose = () => { this.connected = false; setTimeout(this.setup, 500); clearInterval(this.heartbeat); console.log('ws connection closed'); - } + }; onError = evt => { console.log('ws error', evt); - } + }; - sendMessage = (msg) => { + sendMessage = msg => { if (!this.connected) { console.log('ws not connected, cannot send message'); return; } - + this.ws.send(msg); - } + }; sendPing = () => { - const msg = JSON.stringify({'type': 'ping'}) - this.sendMessage(msg) - } - + const msg = JSON.stringify({ type: 'ping' }); + this.sendMessage(msg); + }; } const instance = new WS(); diff --git a/src/actions/index.js b/src/actions/index.js index e2c3021b..8cf6abb8 100644 --- a/src/actions/index.js +++ b/src/actions/index.js @@ -5,9 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -export const dashboardUpdate = data => ({ type: "dashboard_update", payload: data }); +export const dashboardUpdate = data => ({ type: 'dashboard_update', payload: data }); -export const isVersionAllowedUpdate = data => ({ type: "is_version_allowed_update", payload: data }); +export const isVersionAllowedUpdate = data => ({ + type: 'is_version_allowed_update', + payload: data, +}); export const apiLoadErrorUpdate = data => ({ type: 'api_load_error_update', payload: data }); diff --git a/src/api/addressApi.js b/src/api/addressApi.js index 30896260..6c7c075d 100644 --- a/src/api/addressApi.js +++ b/src/api/addressApi.js @@ -15,27 +15,29 @@ const addressApi = { offset (optional): int -> offset this many transactions before fetching */ - const data = {address}; + const data = { address }; if (limit) { - data['limit'] = limit; + data.limit = limit; } if (offset) { - data['offset'] = offset; + data.offset = offset; } - return requestExplorerServiceV1.get(`address/tokens`, {params: data}).then((res) => { + return requestExplorerServiceV1.get(`address/tokens`, { params: data }).then(res => { if (res && res.data) { - return res.data + return res.data; } }); }, getBalance(address, token) { - return requestExplorerServiceV1.get(`address/balance`, {params: {address, token}}).then((res) => { - if (res && res.data) { - return res.data - } - }); + return requestExplorerServiceV1 + .get(`address/balance`, { params: { address, token } }) + .then(res => { + if (res && res.data) { + return res.data; + } + }); }, getHistory(address, token, limit, lastTx, lastTs) { @@ -47,22 +49,22 @@ const addressApi = { lastTs (optional): int -> last timestamp of the page, so we can retrieve the next page */ - const data = {address, token}; + const data = { address, token }; if (limit) { - data['limit'] = limit; + data.limit = limit; } if (lastTx) { - data['last_tx'] = lastTx; + data.last_tx = lastTx; } if (lastTs) { - data['last_ts'] = lastTs; + data.last_ts = lastTs; } - return requestExplorerServiceV1.get(`address/history`, {params: data}).then((res) => { + return requestExplorerServiceV1.get(`address/history`, { params: data }).then(res => { if (res && res.data) { - return res.data + return res.data; } }); }, diff --git a/src/api/addressApiLegacy.js b/src/api/addressApiLegacy.js index 12688881..92d0b926 100644 --- a/src/api/addressApiLegacy.js +++ b/src/api/addressApiLegacy.js @@ -9,13 +9,16 @@ import requestExplorerServiceV1 from './axiosInstance'; const addressApi = { getBalance(address) { - return requestExplorerServiceV1.get(`node_api/address_balance`, {params: {address}}).then((res) => { - if (res && res.data) { - return res.data - } - }).catch((error) => { - // something wrong with request - }); + return requestExplorerServiceV1 + .get(`node_api/address_balance`, { params: { address } }) + .then(res => { + if (res && res.data) { + return res.data; + } + }) + .catch(error => { + // something wrong with request + }); }, search(address, count, hash, page, token) { @@ -27,23 +30,26 @@ const addressApi = { if 'next', we get the objects after the hash reference token (optional): str -> only fetch txs related to this token uid */ - - const data = {address, count}; + + const data = { address, count }; if (hash) { - data['hash'] = hash; - data['page'] = page; + data.hash = hash; + data.page = page; } if (token) { - data['token'] = token; + data.token = token; } - return requestExplorerServiceV1.get(`node_api/address_search`, {params: data}).then((res) => { - if (res && res.data) { - return res.data - } - }).catch((error) => { - // something wrong with request - }); + return requestExplorerServiceV1 + .get(`node_api/address_search`, { params: data }) + .then(res => { + if (res && res.data) { + return res.data; + } + }) + .catch(error => { + // something wrong with request + }); }, }; diff --git a/src/api/axiosInstance.js b/src/api/axiosInstance.js index be836100..d380f5f7 100644 --- a/src/api/axiosInstance.js +++ b/src/api/axiosInstance.js @@ -5,12 +5,12 @@ * LICENSE file in the root directory of this source tree. */ -import { EXPLORER_SERVICE_BASE_URL } from '../constants.js'; import axios from 'axios'; +import { EXPLORER_SERVICE_BASE_URL } from '../constants.js'; -const errorHandler = (error) => { - console.log("ERROR RESPONSE", error); -} +const errorHandler = error => { + console.log('ERROR RESPONSE', error); +}; const requestExplorerServiceV1 = () => { const defaultOptions = { @@ -18,13 +18,13 @@ const requestExplorerServiceV1 = () => { headers: { 'Content-Type': 'application/json', }, - } + }; - let instance = axios.create(defaultOptions); - instance.interceptors.response.use((response) => { + const instance = axios.create(defaultOptions); + instance.interceptors.response.use(response => { return response; }, errorHandler); return instance; -} +}; export default requestExplorerServiceV1(); diff --git a/src/api/blockApi.js b/src/api/blockApi.js index 9e43e97a..a6808438 100644 --- a/src/api/blockApi.js +++ b/src/api/blockApi.js @@ -9,20 +9,20 @@ import helpers from '../utils/helpers'; import requestExplorerServiceV1 from './axiosInstance'; - const BLOCK_API_DEFAULT_TIMEOUT = 35000; // ms const blockApi = { + /** + * Get the best chain height available on ElasticSearch + * + * @returns {Promise} + */ + async getBestChainHeight() { + const response = await requestExplorerServiceV1.get('blocks/best_chain_height', { + timeout: BLOCK_API_DEFAULT_TIMEOUT, + }); + return helpers.handleExplorerServiceResponse(response); + }, +}; - /** - * Get the best chain height available on ElasticSearch - * - * @returns {Promise} - */ - async getBestChainHeight() { - const response = await requestExplorerServiceV1.get('blocks/best_chain_height', { timeout: BLOCK_API_DEFAULT_TIMEOUT }); - return helpers.handleExplorerServiceResponse(response); - } -} - -export default blockApi; \ No newline at end of file +export default blockApi; diff --git a/src/api/customAxiosInstance.js b/src/api/customAxiosInstance.js index bad77fb8..0c7d4751 100644 --- a/src/api/customAxiosInstance.js +++ b/src/api/customAxiosInstance.js @@ -23,6 +23,6 @@ const createRequestInstance = (resolve, timeout) => { // Will override lib axios instance increasing the default request timeout const instance = hathorLib.axios.defaultCreateRequestInstance(resolve, 30000); return instance; -} +}; export default createRequestInstance; diff --git a/src/api/featureApi.js b/src/api/featureApi.js index 6eb94b82..fd72e093 100644 --- a/src/api/featureApi.js +++ b/src/api/featureApi.js @@ -8,20 +8,17 @@ import requestExplorerServiceV1 from './axiosInstance'; const featureApi = { - async getFeatures(block = null) { return requestExplorerServiceV1.get(`node_api/feature`, { params: { block } }).then(res => { - return res.data + return res.data; }); }, async getSignalBits(block) { return this.getFeatures(block).then(data => { - return data.signal_bits + return data.signal_bits; }); - } - - + }, }; export default featureApi; diff --git a/src/api/graphvizApi.js b/src/api/graphvizApi.js index 20dc52af..611723d0 100644 --- a/src/api/graphvizApi.js +++ b/src/api/graphvizApi.js @@ -10,12 +10,15 @@ import { MAX_GRAPH_LEVEL } from '../constants'; const graphvizApi = { dotNeighbors(tx, graphType) { - const data = {tx, "graph_type": graphType, "max_level": MAX_GRAPH_LEVEL} - return requestExplorerServiceV1.get(`node_api/graphviz/neighbours.dot`, {params: data}).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + const data = { tx, graph_type: graphType, max_level: MAX_GRAPH_LEVEL }; + return requestExplorerServiceV1.get(`node_api/graphviz/neighbours.dot`, { params: data }).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, }; diff --git a/src/api/metadataApi.js b/src/api/metadataApi.js index d4a43497..07513262 100644 --- a/src/api/metadataApi.js +++ b/src/api/metadataApi.js @@ -9,14 +9,17 @@ import requestExplorerServiceV1 from './axiosInstance'; const metadataApi = { getDagMetadata(id) { - return requestExplorerServiceV1.get(`metadata/dag`, {params: {id}}).then((res) => { - if (res && id in res.data) { - return res.data[id] - } - }).catch((error) => { - // something wrong with request - }); - } + return requestExplorerServiceV1 + .get(`metadata/dag`, { params: { id } }) + .then(res => { + if (res && id in res.data) { + return res.data[id]; + } + }) + .catch(error => { + // something wrong with request + }); + }, }; export default metadataApi; diff --git a/src/api/nanoApi.js b/src/api/nanoApi.js index ac47b34a..d0d2d151 100644 --- a/src/api/nanoApi.js +++ b/src/api/nanoApi.js @@ -20,11 +20,14 @@ const nanoApi = { */ getState(id, fields, balances, calls) { const data = { id, fields, balances, calls }; - return requestExplorerServiceV1.get(`node_api/nc_state`, {params: data}).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + return requestExplorerServiceV1.get(`node_api/nc_state`, { params: data }).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, /** @@ -36,11 +39,14 @@ const nanoApi = { */ getHistory(id) { const data = { id }; - return requestExplorerServiceV1.get(`node_api/nc_history`, {params: data}).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + return requestExplorerServiceV1.get(`node_api/nc_history`, { params: data }).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, /** @@ -52,12 +58,15 @@ const nanoApi = { */ getBlueprintInformation(blueprintId) { const data = { blueprint_id: blueprintId }; - return requestExplorerServiceV1.get(`node_api/nc_blueprint_information`, {params: data}).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + return requestExplorerServiceV1.get(`node_api/nc_blueprint_information`, { params: data }).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, }; -export default nanoApi; \ No newline at end of file +export default nanoApi; diff --git a/src/api/networkApi.js b/src/api/networkApi.js index c7336829..fb05fbe5 100644 --- a/src/api/networkApi.js +++ b/src/api/networkApi.js @@ -9,25 +9,31 @@ import requestExplorerServiceV1 from './axiosInstance'; const networkApi = { getPeerList() { - return requestExplorerServiceV1.get(`node`).then((res) => { - if (!res.data) { - throw new Error("unknown_error"); - } - return res.data - }).catch((res) => { - throw new Error(res.data.message); - }); + return requestExplorerServiceV1 + .get(`node`) + .then(res => { + if (!res.data) { + throw new Error('unknown_error'); + } + return res.data; + }) + .catch(res => { + throw new Error(res.data.message); + }); }, getPeer(hash) { - return requestExplorerServiceV1.get(`node/${hash}`).then((res) => { - if (!res.data) { - throw new Error("unknown_error"); - } - return res.data - }).catch((res) => { - throw new Error(res.data.message); - }); - } + return requestExplorerServiceV1 + .get(`node/${hash}`) + .then(res => { + if (!res.data) { + throw new Error('unknown_error'); + } + return res.data; + }) + .catch(res => { + throw new Error(res.data.message); + }); + }, }; export default networkApi; diff --git a/src/api/tokenApi.js b/src/api/tokenApi.js index c63f7106..efd2e93d 100644 --- a/src/api/tokenApi.js +++ b/src/api/tokenApi.js @@ -10,29 +10,38 @@ import { TX_COUNT } from '../constants'; const tokenApi = { getList() { - return requestExplorerServiceV1.get(`node_api/tokens`).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + return requestExplorerServiceV1.get(`node_api/tokens`).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, get(id) { - const data = {id}; - return requestExplorerServiceV1.get(`node_api/token`, {params: data}).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + const data = { id }; + return requestExplorerServiceV1.get(`node_api/token`, { params: data }).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, getHistory(id, timestamp, hash, page) { - const data = {id, timestamp, hash, page, count: TX_COUNT}; - return requestExplorerServiceV1.get(`node_api/token_history`, {params: data}).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + const data = { id, timestamp, hash, page, count: TX_COUNT }; + return requestExplorerServiceV1.get(`node_api/token_history`, { params: data }).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, }; diff --git a/src/api/tokensApi.js b/src/api/tokensApi.js index 8bea9c11..ce7d61e4 100644 --- a/src/api/tokensApi.js +++ b/src/api/tokensApi.js @@ -24,13 +24,16 @@ const tokensApi = { */ async getList(searchText, sortBy, order, searchAfter) { const data = { - 'search_text': searchText, - 'sort_by': sortBy, - 'order': order, - 'search_after': searchAfter.join(','), + search_text: searchText, + sort_by: sortBy, + order, + search_after: searchAfter.join(','), }; - const response = await requestExplorerServiceV1.get('tokens', { params: data, timeout: TOKENS_API_DEFAULT_TIMEOUT }); + const response = await requestExplorerServiceV1.get('tokens', { + params: data, + timeout: TOKENS_API_DEFAULT_TIMEOUT, + }); return helpers.handleExplorerServiceResponse(response); }, @@ -43,7 +46,9 @@ const tokensApi = { * @return {Promise} */ async getToken(tokenId) { - const response = await requestExplorerServiceV1.get(`tokens/${tokenId}`, { timeout: TOKENS_API_DEFAULT_TIMEOUT }); + const response = await requestExplorerServiceV1.get(`tokens/${tokenId}`, { + timeout: TOKENS_API_DEFAULT_TIMEOUT, + }); return helpers.handleExplorerServiceResponse(response); }, @@ -61,13 +66,16 @@ const tokensApi = { */ async getBalances(tokenId, sortBy, order, searchAfter) { const data = { - 'token_id': tokenId, - 'sort_by': sortBy, - 'order': order, - 'search_after': searchAfter.join(',') + token_id: tokenId, + sort_by: sortBy, + order, + search_after: searchAfter.join(','), }; - const response = await requestExplorerServiceV1.get('token_balances', { params: data, timeout: TOKENS_API_DEFAULT_TIMEOUT }); + const response = await requestExplorerServiceV1.get('token_balances', { + params: data, + timeout: TOKENS_API_DEFAULT_TIMEOUT, + }); return helpers.handleExplorerServiceResponse(response); }, @@ -81,10 +89,13 @@ const tokensApi = { */ async getBalanceInformation(tokenId) { const data = { - 'token_id': tokenId, + token_id: tokenId, }; - const response = await requestExplorerServiceV1.get('token_balances/information', { params: data, timeout: TOKENS_API_DEFAULT_TIMEOUT}); + const response = await requestExplorerServiceV1.get('token_balances/information', { + params: data, + timeout: TOKENS_API_DEFAULT_TIMEOUT, + }); return helpers.handleExplorerServiceResponse(response); }, diff --git a/src/api/txApi.js b/src/api/txApi.js index 9d817487..523145a8 100644 --- a/src/api/txApi.js +++ b/src/api/txApi.js @@ -17,52 +17,67 @@ const txApi = { page (optional): 'previous' or 'next' -> if 'previous', we get the objects before the hash reference if 'next', we get the objects after the hash reference */ - const data = {type, count}; + const data = { type, count }; if (hash) { - data['hash'] = hash; - data['timestamp'] = timestamp; - data['page'] = page; + data.hash = hash; + data.timestamp = timestamp; + data.page = page; } - return requestExplorerServiceV1.get(`node_api/transactions`, {params: data}).then(res => { - return res.data - }).catch(e => { - throw new Error(e); - }); + return requestExplorerServiceV1 + .get(`node_api/transactions`, { params: data }) + .then(res => { + return res.data; + }) + .catch(e => { + throw new Error(e); + }); }, getTransaction(id) { - return requestExplorerServiceV1.get(`node_api/transaction`, {params: {id}}).then(res => { - return res.data - }).catch(e => { - throw new Error(e); - }); + return requestExplorerServiceV1 + .get(`node_api/transaction`, { params: { id } }) + .then(res => { + return res.data; + }) + .catch(e => { + throw new Error(e); + }); }, decodeTx(hex_tx) { - const data = {hex_tx} - return requestExplorerServiceV1.get(`node_api/decode_tx`, {params: data}).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + const data = { hex_tx }; + return requestExplorerServiceV1.get(`node_api/decode_tx`, { params: data }).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, pushTx(hex_tx, force) { - const data = {hex_tx, force} - return requestExplorerServiceV1.post(`node_api/push_tx`, data).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + const data = { hex_tx, force }; + return requestExplorerServiceV1.post(`node_api/push_tx`, data).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, getDashboardTx(block, tx) { - const data = {block, tx} - return requestExplorerServiceV1.get(`node_api/dashboard_tx`, {params: data}).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); + const data = { block, tx }; + return requestExplorerServiceV1.get(`node_api/dashboard_tx`, { params: data }).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); }, /* @@ -75,12 +90,15 @@ const txApi = { * @inner */ getConfirmationData(id) { - const data = {id}; - return requestExplorerServiceV1.get(`node_api/transaction_acc_weight`, {params: data}).then((res) => { - return res.data; - }, (res) => { - return Promise.reject(res); - }); + const data = { id }; + return requestExplorerServiceV1.get(`node_api/transaction_acc_weight`, { params: data }).then( + res => { + return res.data; + }, + res => { + return Promise.reject(res); + } + ); }, }; diff --git a/src/api/version.js b/src/api/version.js index 51572a83..58c75b50 100644 --- a/src/api/version.js +++ b/src/api/version.js @@ -9,12 +9,15 @@ import requestExplorerServiceV1 from './axiosInstance'; const versionApi = { getVersion() { - return requestExplorerServiceV1.get(`node_api/version`).then((res) => { - return res.data - }, (res) => { - throw new Error(res.data.message); - }); - } + return requestExplorerServiceV1.get(`node_api/version`).then( + res => { + return res.data; + }, + res => { + throw new Error(res.data.message); + } + ); + }, }; export default versionApi; diff --git a/src/components/AddressDetailExplorer.js b/src/components/AddressDetailExplorer.js index 7d622d2c..d1e861f0 100644 --- a/src/components/AddressDetailExplorer.js +++ b/src/components/AddressDetailExplorer.js @@ -18,12 +18,11 @@ import { isEqual, find } from 'lodash'; import metadataApi from '../api/metadataApi'; import addressApi from '../api/addressApi'; import txApi from '../api/txApi'; -import ErrorMessageWithIcon from '../components/error/ErrorMessageWithIcon' - +import ErrorMessageWithIcon from '../components/error/ErrorMessageWithIcon'; class AddressDetailExplorer extends React.Component { pagination = new PaginationURL({ - 'token': {required: true}, + token: { required: true }, }); addressSummaryRef = React.createRef(); @@ -69,14 +68,16 @@ class AddressDetailExplorer extends React.Component { txCache: {}, showReloadDataButton: false, showReloadTokenButton: false, - pageSearchAfter: [{ - page: 0, - searchAfter: { - lastTx: null, - lastTs: null, + pageSearchAfter: [ + { + page: 0, + searchAfter: { + lastTx: null, + lastTs: null, + }, }, - }] - } + ], + }; componentDidMount() { // Expects address on URL @@ -126,14 +127,14 @@ class AddressDetailExplorer extends React.Component { * * @param {Object} wsData Data from websocket */ - handleWebsocket = (wsData) => { + handleWebsocket = wsData => { if (wsData.type === 'network:new_tx_accepted') { if (this.shouldUpdate(wsData, false) && !this.state.warningRefreshPage) { // If the search address is in one of the inputs or outputs this.setState({ warningRefreshPage: true }); } } - } + }; /** * Check if address is valid and then update the state and fetch data @@ -141,36 +142,39 @@ class AddressDetailExplorer extends React.Component { * * @param {Object} address New searched address to update state */ - updateAddress = (address) => { + updateAddress = address => { this.setState({ queryParams: this.pagination.obtainQueryParams() }, () => { const network = hathorLib.config.getNetwork(); const addressObj = new hathorLib.Address(address, { network }); if (addressObj.isValid()) { - this.setState({ - address, - loadingTokens: true, - loadingSummary: true, - loadingHistory: true, - addressTokens: {}, - transactions: [], - balance: {}, - errorMessage: '', - }, () => { - const queryParams = this.pagination.obtainQueryParams(); - if (queryParams.token !== null) { - // User already have a token selected on the URL - this.setState({ selectedToken: queryParams.token }, () => { + this.setState( + { + address, + loadingTokens: true, + loadingSummary: true, + loadingHistory: true, + addressTokens: {}, + transactions: [], + balance: {}, + errorMessage: '', + }, + () => { + const queryParams = this.pagination.obtainQueryParams(); + if (queryParams.token !== null) { + // User already have a token selected on the URL + this.setState({ selectedToken: queryParams.token }, () => { + this.reloadData(); + }); + } else { this.reloadData(); - }); - } else { - this.reloadData(); + } } - }); + ); } else { this.setState({ errorMessage: 'Invalid address.' }); } }); - } + }; /** * Update transactions data state after requesting data from the server @@ -178,188 +182,214 @@ class AddressDetailExplorer extends React.Component { * @param {Object} queryParams URL parameters */ getHistoryData = (lastTx, lastTs) => { - return addressApi.getHistory(this.state.address, this.state.selectedToken, TX_COUNT, lastTx, lastTs).then((response) => { - if (!response) { - // An error happened with the API call - this.setState({ - showReloadTokenButton: true, - }); - return; - } - - const { has_next, history } = response; - this.setState({ - transactions: history, - hasAfter: has_next, - }, () => { - const promises = []; - for (const tx of history) { - if (!this.state.txCache[tx.tx_id]) { - /** - * The explorer-service address api does not retrieve all metadata of the transactions - * So there are some information that are not retrieved, e.g. whether the transaction only has authorities - * We fetch the transaction with all it's metadata to make this assertions. - */ - promises.push(txApi.getTransaction(tx.tx_id)); - } - } - - Promise.all(promises).then(results => { - const cache = {...this.state.txCache}; - for (const result of results) { - const tx = {...result.tx, meta: result.meta}; - cache[tx.hash] = tx; - } - this.setState({txCache: cache}); - }); - }); - return history; - }).finally(() => { - this.setState({ loadingHistory: false }); - }); - } - - reloadData = () => { - this.setState({ - loadingTokens: true, - }, () => { - addressApi.getTokens(this.state.address, TOKEN_COUNT).then(response => { + return addressApi + .getHistory(this.state.address, this.state.selectedToken, TX_COUNT, lastTx, lastTs) + .then(response => { if (!response) { // An error happened with the API call - this.setState({ showReloadDataButton: true }); + this.setState({ + showReloadTokenButton: true, + }); return; } - let selectedToken = ''; - - const tokens = response.tokens || {}; - const total = response.total || 0; + const { has_next, history } = response; + this.setState( + { + transactions: history, + hasAfter: has_next, + }, + () => { + const promises = []; + for (const tx of history) { + if (!this.state.txCache[tx.tx_id]) { + /** + * The explorer-service address api does not retrieve all metadata of the transactions + * So there are some information that are not retrieved, e.g. whether the transaction only has authorities + * We fetch the transaction with all it's metadata to make this assertions. + */ + promises.push(txApi.getTransaction(tx.tx_id)); + } + } - if (total > Object.keys(tokens).length) { - // There were unfetched tokens - this.setState({ warnMissingTokens: total }); - } else { - // This will turn off the missing tokens alert - this.setState({ warnMissingTokens: 0 }); - } + Promise.all(promises).then(results => { + const cache = { ...this.state.txCache }; + for (const result of results) { + const tx = { ...result.tx, meta: result.meta }; + cache[tx.hash] = tx; + } + this.setState({ txCache: cache }); + }); + } + ); + return history; + }) + .finally(() => { + this.setState({ loadingHistory: false }); + }); + }; - if (this.state.selectedToken && tokens[this.state.selectedToken]) { - // use has a selected token, we will keep the selected token - selectedToken = this.state.selectedToken; - } else { - const hathorUID = hathorLib.constants.NATIVE_TOKEN_UID - if (tokens[hathorUID]) { - // If HTR is in the token list of this address, it's the default selection - selectedToken = hathorUID; - } else { - // Otherwise we get the first element, if there is one - const keys = Object.keys(tokens); - if (keys.length === 0) { - // In case the length is 0, we have no transactions for this address - this.setState({ - loadingTokens: false, - loadingSummary: false, - loadingHistory: false, - }); + reloadData = () => { + this.setState( + { + loadingTokens: true, + }, + () => { + addressApi + .getTokens(this.state.address, TOKEN_COUNT) + .then(response => { + if (!response) { + // An error happened with the API call + this.setState({ showReloadDataButton: true }); return; } - selectedToken = keys[0]; - } - } - const tokenDidChange = selectedToken !== this.state.selectedToken || this.state.selectedToken === ''; + let selectedToken = ''; - this.setState({ - addressTokens: tokens, - loadingTokens: false, - selectedToken, - }, () => { - if (tokenDidChange || !this.state.metadataLoaded) { - this.getSelectedTokenMetadata(selectedToken); - } + const tokens = response.tokens || {}; + const total = response.total || 0; - // Update token in the URL - this.updateTokenURL(selectedToken); - this.reloadTokenData(selectedToken); - }); + if (total > Object.keys(tokens).length) { + // There were unfetched tokens + this.setState({ warnMissingTokens: total }); + } else { + // This will turn off the missing tokens alert + this.setState({ warnMissingTokens: 0 }); + } - }).catch(error => { - this.setState({ - loadingTokens: false, - errorMessage: error.toString(), - }); - }); - }); - } + if (this.state.selectedToken && tokens[this.state.selectedToken]) { + // use has a selected token, we will keep the selected token + selectedToken = this.state.selectedToken; + } else { + const hathorUID = hathorLib.constants.NATIVE_TOKEN_UID; + if (tokens[hathorUID]) { + // If HTR is in the token list of this address, it's the default selection + selectedToken = hathorUID; + } else { + // Otherwise we get the first element, if there is one + const keys = Object.keys(tokens); + if (keys.length === 0) { + // In case the length is 0, we have no transactions for this address + this.setState({ + loadingTokens: false, + loadingSummary: false, + loadingHistory: false, + }); + return; + } + selectedToken = keys[0]; + } + } - reloadTokenData = (token) => { - this.setState({ - loadingSummary: true, - loadingHistory: true, - }, () => { - addressApi.getBalance(this.state.address, token).then(response => { - if (!response) { - // An error happened with the API call - this.setState({ showReloadTokenButton: true }); - return; - } - const balance = response; - this.setState({ balance }); - return balance; - }).then(balance => { - return this.getHistoryData().then(txhistory => { - if (!this.state.metadataLoaded) { - this.getSelectedTokenMetadata(token); - } - }); - }).catch(error => { - this.setState({ errorMessage: error }); - }).finally(() => { - this.setState({ loadingSummary: false }); - }); - }); - } + const tokenDidChange = + selectedToken !== this.state.selectedToken || this.state.selectedToken === ''; - getSelectedTokenMetadata = (selectedToken) => { + this.setState( + { + addressTokens: tokens, + loadingTokens: false, + selectedToken, + }, + () => { + if (tokenDidChange || !this.state.metadataLoaded) { + this.getSelectedTokenMetadata(selectedToken); + } + + // Update token in the URL + this.updateTokenURL(selectedToken); + this.reloadTokenData(selectedToken); + } + ); + }) + .catch(error => { + this.setState({ + loadingTokens: false, + errorMessage: error.toString(), + }); + }); + } + ); + }; + + reloadTokenData = token => { + this.setState( + { + loadingSummary: true, + loadingHistory: true, + }, + () => { + addressApi + .getBalance(this.state.address, token) + .then(response => { + if (!response) { + // An error happened with the API call + this.setState({ showReloadTokenButton: true }); + return; + } + const balance = response; + this.setState({ balance }); + return balance; + }) + .then(balance => { + return this.getHistoryData().then(txhistory => { + if (!this.state.metadataLoaded) { + this.getSelectedTokenMetadata(token); + } + }); + }) + .catch(error => { + this.setState({ errorMessage: error }); + }) + .finally(() => { + this.setState({ loadingSummary: false }); + }); + } + ); + }; + + getSelectedTokenMetadata = selectedToken => { if (selectedToken === hathorLib.constants.NATIVE_TOKEN_UID) { this.setState({ metadataLoaded: true }); return; } - metadataApi.getDagMetadata(selectedToken).then((data) => { + metadataApi.getDagMetadata(selectedToken).then(data => { if (data) { this.setState({ selectedTokenMetadata: data }); } this.setState({ metadataLoaded: true }); }); - } + }; /** * Callback to be executed when user changes token on select input * * @param {String} Value of the selected item */ - onTokenSelectChanged = (value) => { - this.setState({ - selectedToken: value, - metadataLoaded: false, - selectedTokenMetadata: null, - balance: {}, - transactions: [], - }, () => { - this.updateTokenURL(value); - this.reloadTokenData(value); - }); - } + onTokenSelectChanged = value => { + this.setState( + { + selectedToken: value, + metadataLoaded: false, + selectedTokenMetadata: null, + balance: {}, + transactions: [], + }, + () => { + this.updateTokenURL(value); + this.reloadTokenData(value); + } + ); + }; /** * Update URL with new selected token and trigger didUpdate * * @param {String} New token selected */ - updateTokenURL = (token) => { + updateTokenURL = token => { const newURL = this.pagination.setURLParameters({ token }); this.props.history.push(newURL); - } + }; /** * Check if the searched address is on the inputs or outputs of the new tx @@ -384,57 +414,60 @@ class AddressDetailExplorer extends React.Component { } return false; - } + }; /** * Redirects to transaction detail screen after clicking on a table row * * @param {String} hash Hash of tx clicked */ - onRowClicked = (hash) => { + onRowClicked = hash => { this.props.history.push(`/transaction/${hash}`); - } + }; /** * Refresh all data for the selected token * * @param {Event} e Click event */ - refreshTokenData = (e) => { + refreshTokenData = e => { e.preventDefault(); this.setState({ showReloadTokenButton: false }, () => { - this.reloadTokenData(); - }) - } + this.reloadTokenData(); + }); + }; /** * Refresh all data. * * @param {Event} e Click event */ - refreshPageData = (e) => { + refreshPageData = e => { e.preventDefault(); - this.setState({ - showReloadDataButton: false, - warningRefreshPage: false - }, () => { - this.reloadData(); - }) - } + this.setState( + { + showReloadDataButton: false, + warningRefreshPage: false, + }, + () => { + this.reloadData(); + } + ); + }; /** * Reset query params then refresh data. * * @param {Event} e Click event */ - reloadPage = (e) => { + reloadPage = e => { this.pagination.clearOptionalQueryParams(); this.refreshPageData(e); - } + }; lastPage = () => { return Math.ceil(this.state.balance.transactions / TX_COUNT); - } + }; onNextPageClicked = async () => { this.setState({ loadingPagination: true }); @@ -453,11 +486,12 @@ class AddressDetailExplorer extends React.Component { lastTs, }; - const newPageSearchAfter = [ - ...this.state.pageSearchAfter, { + const newPageSearchAfter = [ + ...this.state.pageSearchAfter, + { page: nextPage, searchAfter, - } + }, ]; await this.getHistoryData(searchAfter.lastTx, searchAfter.lastTs); @@ -468,7 +502,7 @@ class AddressDetailExplorer extends React.Component { loadingPagination: false, page: nextPage, }); - } + }; onPreviousPageClicked = async () => { this.setState({ loadingPagination: true }); @@ -484,78 +518,89 @@ class AddressDetailExplorer extends React.Component { }); this.setState({ loadingPagination: false }); - } + }; render() { const renderWarningAlert = () => { if (this.state.warningRefreshPage) { return (
- There is a new transaction for this address. Please refresh the page to see the newest data. + There is a new transaction for this address. Please{' '} + + refresh + {' '} + the page to see the newest data.
- ) + ); } return null; - } + }; const renderReloadTokenButton = () => { if (this.state.showReloadTokenButton) { return ( - - ) + + ); } return null; - } + }; const renderReloadDataButton = () => { if (this.state.showReloadDataButton) { return ( - - ) + + ); } return null; - } + }; const renderMissingTokensAlert = () => { if (this.state.warnMissingTokens) { return (
- This address has {this.state.warnMissingTokens} tokens but we are showing only the {TOKEN_COUNT} with the most recent activity. + This address has {this.state.warnMissingTokens} tokens but we are showing only the{' '} + {TOKEN_COUNT} with the most recent activity.
- ) + ); } return null; - } + }; const isNFT = () => { return this.state.selectedTokenMetadata && this.state.selectedTokenMetadata.nft; - } + }; const renderData = () => { if (this.state.errorMessage) { return (

{this.state.errorMessage}

- +
); } else if (this.state.address === null) { return null; } else if (this.state.showReloadDataButton || this.state.showReloadTokenButton) { - return ( -
- - {renderReloadDataButton()} - {renderReloadTokenButton()} -
- ); + return ( +
+ + {renderReloadDataButton()} + {renderReloadTokenButton()} +
+ ); } else { if (this.state.loadingSummary || this.state.loadingHistory || this.state.loadingTokens) { - return + return ; } else { return (
@@ -590,13 +635,9 @@ class AddressDetailExplorer extends React.Component { ); } } - } + }; - return ( -
- {renderData()} -
- ); + return
{renderData()}
; } } diff --git a/src/components/AddressDetailLegacy.js b/src/components/AddressDetailLegacy.js index 9c73f676..4fdc5900 100644 --- a/src/components/AddressDetailLegacy.js +++ b/src/components/AddressDetailLegacy.js @@ -19,12 +19,11 @@ import helpers from '../utils/helpers'; import metadataApi from '../api/metadataApi'; import addressApiLegacy from '../api/addressApiLegacy'; - class AddressDetailLegacy extends React.Component { pagination = new PaginationURL({ - 'hash': {required: false}, - 'page': {required: false}, - 'token': {required: true} + hash: { required: false }, + page: { required: false }, + token: { required: true }, }); addressSummaryRef = React.createRef(); @@ -60,7 +59,7 @@ class AddressDetailLegacy extends React.Component { warningRefreshPage: false, selectedTokenMetadata: null, metadataLoaded: false, - } + }; componentDidMount() { // Expects address on URL @@ -111,14 +110,14 @@ class AddressDetailLegacy extends React.Component { * * @param {Object} wsData Data from websocket */ - handleWebsocket = (wsData) => { + handleWebsocket = wsData => { if (wsData.type === 'network:new_tx_accepted') { if (this.shouldUpdate(wsData, false) && !this.state.warningRefreshPage) { // If the search address is in one of the inputs or outputs this.setState({ warningRefreshPage: true }); } } - } + }; /** * Update the list with a new tx arrived in a ws message @@ -127,7 +126,7 @@ class AddressDetailLegacy extends React.Component { * * @param {Object} tx Data of a newly arrived tx */ - updateListWs = (tx) => { + updateListWs = tx => { // We only add new tx/blocks if it's the first page if (!this.state.hasBefore) { if (this.shouldUpdate(tx, true)) { @@ -141,7 +140,7 @@ class AddressDetailLegacy extends React.Component { this.setState({ transactions, hasAfter, numberOfTransactions: newNumberOfTransactions }); } } - } + }; /** * Check if address is valid and then update the state and get data from full node @@ -149,7 +148,7 @@ class AddressDetailLegacy extends React.Component { * * @param {Object} address New searched address to update state */ - updateAddress = (address) => { + updateAddress = address => { this.setState({ queryParams: this.pagination.obtainQueryParams() }, () => { const network = hathorLib.config.getNetwork(); const addressObj = new hathorLib.Address(address, { network }); @@ -173,23 +172,24 @@ class AddressDetailLegacy extends React.Component { this.setState({ errorMessage: 'Invalid address.' }); } }); - } + }; /** * Update transactions data state after requesting data from the server * * @param {Object} queryParams URL parameters */ - getHistoryData = (queryParams) => { - addressApiLegacy.search(this.state.address, TX_COUNT, queryParams.hash, queryParams.page, queryParams.token).then((response) => { - if (response.success) { - this.handleFetchedData(response, queryParams); - } - // fetch metadata for selected token - this.getSelectedTokenMetadata(queryParams.token); - }); - } - + getHistoryData = queryParams => { + addressApiLegacy + .search(this.state.address, TX_COUNT, queryParams.hash, queryParams.page, queryParams.token) + .then(response => { + if (response.success) { + this.handleFetchedData(response, queryParams); + } + // fetch metadata for selected token + this.getSelectedTokenMetadata(queryParams.token); + }); + }; /** * Update component state when new list data arrives @@ -227,20 +227,20 @@ class AddressDetailLegacy extends React.Component { queryParams, numberOfTransactions: data.total, }); - } + }; /** * Request data from server and update state balance */ getSummaryData = () => { - addressApiLegacy.getBalance(this.state.address).then((response) => { + addressApiLegacy.getBalance(this.state.address).then(response => { if (response.success) { let selectedToken = ''; if (this.state.selectedToken && this.state.selectedToken in response.tokens_data) { // If user had selected a token already, should continue the same selectedToken = this.state.selectedToken; } else { - const hathorUID = hathorLib.constants.NATIVE_TOKEN_UID + const hathorUID = hathorLib.constants.NATIVE_TOKEN_UID; if (hathorUID in response.tokens_data) { // If HTR is in the token list of this address, it's the default selection selectedToken = hathorUID; @@ -273,38 +273,38 @@ class AddressDetailLegacy extends React.Component { }); } }); - } + }; - getSelectedTokenMetadata = (selectedToken) => { - metadataApi.getDagMetadata(selectedToken).then((data) => { + getSelectedTokenMetadata = selectedToken => { + metadataApi.getDagMetadata(selectedToken).then(data => { if (data) { this.setState({ selectedTokenMetadata: data }); } this.setState({ metadataLoaded: true }); }); - } + }; /** * Callback to be executed when user changes token on select input * * @param {String} Value of the selected item */ - onTokenSelectChanged = (value) => { + onTokenSelectChanged = value => { this.setState({ selectedToken: value, metadataLoaded: false, selectedTokenMetadata: null }); this.updateTokenURL(value); - } + }; /** * Update URL with new selected token and trigger didUpdate * * @param {String} New token selected */ - updateTokenURL = (token) => { + updateTokenURL = token => { const queryParams = this.pagination.obtainQueryParams(); queryParams.token = token; const newURL = this.pagination.setURLParameters(queryParams); this.props.history.push(newURL); - } + }; /** * Check if the searched address is on the inputs or outputs of the new tx @@ -329,54 +329,56 @@ class AddressDetailLegacy extends React.Component { } return false; - } + }; /** * Redirects to transaction detail screen after clicking on a table row * * @param {String} hash Hash of tx clicked */ - onRowClicked = (hash) => { + onRowClicked = hash => { this.props.history.push(`/transaction/${hash}`); - } + }; /** * Refresh web page * * @param {Event} e Click event */ - refreshPage = (e) => { + refreshPage = e => { e.preventDefault(); window.location.reload(); - } + }; render() { const renderWarningAlert = () => { if (this.state.warningRefreshPage) { return (
- There is a new transaction for this address. Please refresh the page to see the newest data. + There is a new transaction for this address. Please{' '} + + refresh + {' '} + the page to see the newest data.
); } return null; - } + }; const isNFT = () => { return this.state.selectedTokenMetadata && this.state.selectedTokenMetadata.nft; - } + }; const renderData = () => { if (this.state.errorMessage) { - return ( -

{this.state.errorMessage}

- ); + return

{this.state.errorMessage}

; } else if (this.state.address === null) { return null; } else { if (this.state.loadingSummary || this.state.loadingHistory) { - return + return ; } else { return (
@@ -405,13 +407,9 @@ class AddressDetailLegacy extends React.Component { ); } } - } + }; - return ( -
- {renderData()} -
- ); + return
{renderData()}
; } } diff --git a/src/components/AddressHistory.js b/src/components/AddressHistory.js index c061f2fa..99faa7d4 100644 --- a/src/components/AddressHistory.js +++ b/src/components/AddressHistory.js @@ -11,16 +11,13 @@ import hathorLib from '@hathor/wallet-lib'; import PropTypes from 'prop-types'; import PaginationURL from '../utils/pagination'; import SortableTable from './SortableTable'; -import { connect } from "react-redux"; +import { connect } from 'react-redux'; - -const mapStateToProps = (state) => ({ +const mapStateToProps = state => ({ decimalPlaces: state.serverInfo.decimal_places, }); - class AddressHistory extends SortableTable { - /** * Check if the tx has only inputs and outputs that are authorities in the search address * @@ -28,28 +25,34 @@ class AddressHistory extends SortableTable { * * @return {boolean} If the tx has only authority in the search address */ - isAllAuthority = (tx) => { + isAllAuthority = tx => { for (let txin of tx.inputs) { - if (!hathorLib.transactionUtils.isAuthorityOutput(txin) && txin.decoded.address === this.props.address) { + if ( + !hathorLib.transactionUtils.isAuthorityOutput(txin) && + txin.decoded.address === this.props.address + ) { return false; } } for (let txout of tx.outputs) { - if (!hathorLib.transactionUtils.isAuthorityOutput(txout) && txout.decoded.address === this.props.address) { + if ( + !hathorLib.transactionUtils.isAuthorityOutput(txout) && + txout.decoded.address === this.props.address + ) { return false; } } return true; - } + }; renderTable(content) { return ( - { content } + {content}
- ) + ); } renderTableHead() { @@ -60,7 +63,13 @@ class AddressHistory extends SortableTable { Timestamp Value - Type
Hash
Timestamp + + Type +
+ Hash +
+ Timestamp + ); } @@ -70,27 +79,46 @@ class AddressHistory extends SortableTable { return 'Loading...'; } - return hathorLib.numberUtils.prettyValue(value, this.props.isNFT ? 0 : this.props.decimalPlaces); + return hathorLib.numberUtils.prettyValue( + value, + this.props.isNFT ? 0 : this.props.decimalPlaces + ); } renderTableBody() { - return this.props.data.map((tx) => { + return this.props.data.map(tx => { let statusElement = ''; let trClass = ''; let prettyValue = this.renderValue(tx.balance); if (tx.balance > 0) { if (tx.version === hathorLib.constants.CREATE_TOKEN_TX_VERSION) { - statusElement = Token creation ; + statusElement = ( + + Token creation + + ); } else { - statusElement = Received ; + statusElement = ( + + Received + + ); } trClass = 'output-tr'; } else if (tx.balance < 0) { if (tx.version === hathorLib.constants.CREATE_TOKEN_TX_VERSION) { - statusElement = Token deposit + statusElement = ( + + Token deposit + + ); } else { - statusElement = Sent + statusElement = ( + + Sent + + ); } trClass = 'input-tr'; } else { @@ -107,15 +135,29 @@ class AddressHistory extends SortableTable { trClass = ''; } return ( - this.props.onRowClicked(tx.tx_id)}> - {hathorLib.transactionUtils.getTxType(tx)} - {hathorLib.helpersUtils.getShortHash(tx.tx_id)} - {dateFormatter.parseTimestamp(tx.timestamp)} + this.props.onRowClicked(tx.tx_id)}> + + {hathorLib.transactionUtils.getTxType(tx)} + + + {hathorLib.helpersUtils.getShortHash(tx.tx_id)} + + + {dateFormatter.parseTimestamp(tx.timestamp)} + {statusElement} - {prettyValue} - {hathorLib.transactionUtils.getTxType(tx)}
{hathorLib.helpersUtils.getShortHash(tx.tx_id)}
{dateFormatter.parseTimestamp(tx.timestamp)} + + {prettyValue} + + + {hathorLib.transactionUtils.getTxType(tx)} +
+ {hathorLib.helpersUtils.getShortHash(tx.tx_id)} +
+ {dateFormatter.parseTimestamp(tx.timestamp)} + - ) + ); }); } } @@ -139,5 +181,4 @@ AddressHistory.propTypes = { txCache: PropTypes.object.isRequired, }; - export default connect(mapStateToProps)(AddressHistory); diff --git a/src/components/AddressHistoryLegacy.js b/src/components/AddressHistoryLegacy.js index a12d5a3f..867dd328 100644 --- a/src/components/AddressHistoryLegacy.js +++ b/src/components/AddressHistoryLegacy.js @@ -13,11 +13,10 @@ import PropTypes from 'prop-types'; import PaginationURL from '../utils/pagination'; import { connect } from 'react-redux'; -const mapStateToProps = (state) => ({ +const mapStateToProps = state => ({ decimalPlaces: state.serverInfo.decimal_places, }); - class AddressHistory extends React.Component { /** * We get the final balance of one tx of the list @@ -26,7 +25,7 @@ class AddressHistory extends React.Component { * * @return {Number} Final tx balance value (can be negative value, in case we spent more than received for the search address) */ - calculateAddressBalance = (tx) => { + calculateAddressBalance = tx => { const token = this.props.selectedToken; let value = 0; @@ -47,7 +46,7 @@ class AddressHistory extends React.Component { } return value; - } + }; /** * Check if the tx has only inputs and outputs that are authorities in the search address @@ -56,34 +55,40 @@ class AddressHistory extends React.Component { * * @return {boolean} If the tx has only authority in the search address */ - isAllAuthority = (tx) => { + isAllAuthority = tx => { for (let txin of tx.inputs) { - if (!hathorLib.transactionUtils.isAuthorityOutput(txin) && txin.decoded.address === this.props.address) { + if ( + !hathorLib.transactionUtils.isAuthorityOutput(txin) && + txin.decoded.address === this.props.address + ) { return false; } } for (let txout of tx.outputs) { - if (!hathorLib.transactionUtils.isAuthorityOutput(txout) && txout.decoded.address === this.props.address) { + if ( + !hathorLib.transactionUtils.isAuthorityOutput(txout) && + txout.decoded.address === this.props.address + ) { return false; } } return true; - } + }; render() { if (this.props.transactions.length === 0) { - return

This address does not have any transactions yet.

+ return

This address does not have any transactions yet.

; } const getFirstHash = () => { return this.props.transactions[0].tx_id; - } + }; const getLastHash = () => { - return this.props.transactions[this.props.transactions.length-1].tx_id; - } + return this.props.transactions[this.props.transactions.length - 1].tx_id; + }; const loadPagination = () => { if (this.props.transactions.length === 0) { @@ -92,17 +97,36 @@ class AddressHistory extends React.Component { return ( ); } - } + }; const loadTable = () => { return ( @@ -116,24 +140,28 @@ class AddressHistory extends React.Component { Value - Type
Hash
Timestamp + + Type +
+ Hash +
+ Timestamp + - - {loadTableBody()} - + {loadTableBody()}
); - } + }; - const renderValue = (value) => { + const renderValue = value => { if (!this.props.metadataLoaded) { return 'Loading...'; } return numberUtils.prettyValue(value, this.props.isNFT ? 0 : this.props.decimalPlaces); - } + }; const loadTableBody = () => { return this.props.transactions.map((tx, idx) => { @@ -143,16 +171,32 @@ class AddressHistory extends React.Component { let prettyValue = renderValue(value); if (value > 0) { if (tx.version === hathorLib.constants.CREATE_TOKEN_TX_VERSION) { - statusElement = Token creation ; + statusElement = ( + + Token creation + + ); } else { - statusElement = Received ; + statusElement = ( + + Received + + ); } trClass = 'output-tr'; } else if (value < 0) { if (tx.version === hathorLib.constants.CREATE_TOKEN_TX_VERSION) { - statusElement = Token deposit + statusElement = ( + + Token deposit + + ); } else { - statusElement = Sent + statusElement = ( + + Sent + + ); } trClass = 'input-tr'; } else { @@ -168,18 +212,32 @@ class AddressHistory extends React.Component { } return ( - this.props.onRowClicked(tx.tx_id)}> - {hathorLib.transactionUtils.getTxType(tx)} - {hathorLib.helpersUtils.getShortHash(tx.tx_id)} - {dateFormatter.parseTimestamp(tx.timestamp)} + this.props.onRowClicked(tx.tx_id)}> + + {hathorLib.transactionUtils.getTxType(tx)} + + + {hathorLib.helpersUtils.getShortHash(tx.tx_id)} + + + {dateFormatter.parseTimestamp(tx.timestamp)} + {statusElement} {tx.is_voided && Voided} - {prettyValue} - {hathorLib.transactionUtils.getTxType(tx)}
{hathorLib.helpersUtils.getShortHash(tx.tx_id)}
{dateFormatter.parseTimestamp(tx.timestamp)} + + {prettyValue} + + + {hathorLib.transactionUtils.getTxType(tx)} +
+ {hathorLib.helpersUtils.getShortHash(tx.tx_id)} +
+ {dateFormatter.parseTimestamp(tx.timestamp)} + ); }); - } + }; return (
@@ -209,5 +267,4 @@ AddressHistory.propTypes = { hasBefore: PropTypes.bool.isRequired, }; - export default connect(mapStateToProps)(AddressHistory); diff --git a/src/components/AddressSummary.js b/src/components/AddressSummary.js index 2ac09ecc..102c873d 100644 --- a/src/components/AddressSummary.js +++ b/src/components/AddressSummary.js @@ -10,8 +10,7 @@ import PropTypes from 'prop-types'; import { numberUtils } from '@hathor/wallet-lib'; import { connect } from 'react-redux'; - -const mapStateToProps = (state) => ({ +const mapStateToProps = state => ({ decimalPlaces: state.serverInfo.decimal_places, }); @@ -21,9 +20,9 @@ class AddressSummary extends React.Component { * * @param {Object} e Event emitted when select is changed */ - selectChanged = (e) => { + selectChanged = e => { this.props.tokenSelectChanged(e.target.value); - } + }; render() { if (Object.keys(this.props.tokens).length === 0) { @@ -34,12 +33,13 @@ class AddressSummary extends React.Component { return (
- Address: {this.props.address}
+ Address: {this.props.address} +
Number of tokens: {Object.keys(this.props.tokens).length}
); - } + }; // We show 'Loading' until all metadatas are loaded // to prevent switching from decimal to integer if one of the tokens is an NFT @@ -53,36 +53,53 @@ class AddressSummary extends React.Component { } else { return 'Custom token'; } - } + }; - const renderValue = (value) => { + const renderValue = value => { if (!this.props.metadataLoaded) { return 'Loading...'; } return numberUtils.prettyValue(value, this.props.isNFT ? 0 : this.props.decimalPlaces); - } + }; const loadBalanceInfo = () => { return (
- Token: {renderTokenData()}
- Type: {renderType()}
- Number of transactions: {this.props.balance.transactions}
- Total received: {renderValue(this.props.balance.total_received)}
- Total spent: {renderValue(this.props.balance.total_received - this.props.balance.unlocked_balance - this.props.balance.locked_balance)}
- Unlocked balance: {renderValue(this.props.balance.unlocked_balance)}
- Locked balance: {renderValue(this.props.balance.locked_balance)} + Token: {renderTokenData()} +
+ Type: {renderType()} +
+ Number of transactions: {this.props.balance.transactions} +
+ Total received: {renderValue(this.props.balance.total_received)} +
+ Total spent:{' '} + {renderValue( + this.props.balance.total_received - + this.props.balance.unlocked_balance - + this.props.balance.locked_balance + )} +
+ Unlocked balance: + {renderValue(this.props.balance.unlocked_balance)} +
+ Locked balance: + {renderValue(this.props.balance.locked_balance)}
); - } + }; const renderTokenData = () => { if (Object.keys(this.props.tokens).length === 1) { const token = this.props.tokens[this.props.selectedToken]; - return {token.name} ({token.symbol}) + return ( + + {token.name} ({token.symbol}) + + ); } else { return ( ); } - } + }; const renderTokenOptions = () => { - return Object.keys(this.props.tokens).map((uid) => { + return Object.keys(this.props.tokens).map(uid => { const token = this.props.tokens[uid]; - return ; + return ( + + ); }); - } + }; const loadSummary = () => { return ( @@ -106,13 +127,9 @@ class AddressSummary extends React.Component { {loadBalanceInfo()}
); - } + }; - return ( -
- {loadSummary()} -
- ); + return
{loadSummary()}
; } } diff --git a/src/components/AddressSummaryLegacy.js b/src/components/AddressSummaryLegacy.js index 34da906e..069c369f 100644 --- a/src/components/AddressSummaryLegacy.js +++ b/src/components/AddressSummaryLegacy.js @@ -10,8 +10,7 @@ import PropTypes from 'prop-types'; import { connect } from 'react-redux'; import { numberUtils } from '@hathor/wallet-lib'; - -const mapStateToProps = (state) => ({ +const mapStateToProps = state => ({ decimalPlaces: state.serverInfo.decimal_places, }); @@ -21,9 +20,9 @@ class AddressSummary extends React.Component { * * @param {Object} e Event emitted when select is changed */ - selectChanged = (e) => { + selectChanged = e => { this.props.tokenSelectChanged(e.target.value); - } + }; render() { if (Object.keys(this.props.balance).length === 0) { @@ -34,12 +33,13 @@ class AddressSummary extends React.Component { return (
- Address: {this.props.address}
+ Address: {this.props.address} +
Number of tokens: {Object.keys(this.props.balance).length}
); - } + }; // We show 'Loading' until all metadatas are loaded // to prevent switching from decimal to integer if one of the tokens is an NFT @@ -53,36 +53,46 @@ class AddressSummary extends React.Component { } else { return 'Custom token'; } - } + }; - const renderValue = (value) => { + const renderValue = value => { if (!this.props.metadataLoaded) { return 'Loading...'; } return numberUtils.prettyValue(value, this.props.isNFT ? 0 : this.props.decimalPlaces); - } + }; const loadBalanceInfo = () => { const balance = this.props.balance[this.props.selectedToken]; return (
- Token: {renderTokenData()}
- Type: {renderType()}
- Number of transactions: {this.props.numberOfTransactions}
- Total received: {renderValue(balance.received)}
- Total spent: {renderValue(balance.spent)}
- Final balance: {renderValue(balance.received - balance.spent)} + Token: {renderTokenData()} +
+ Type: {renderType()} +
+ Number of transactions: {this.props.numberOfTransactions} +
+ Total received: {renderValue(balance.received)} +
+ Total spent: {renderValue(balance.spent)} +
+ Final balance: + {renderValue(balance.received - balance.spent)}
); - } + }; const renderTokenData = () => { if (Object.keys(this.props.balance).length === 1) { const balance = this.props.balance[this.props.selectedToken]; - return {balance.name} ({balance.symbol}) + return ( + + {balance.name} ({balance.symbol}) + + ); } else { return ( ); } - } + }; const renderTokenOptions = () => { - return Object.keys(this.props.balance).map((uid) => { + return Object.keys(this.props.balance).map(uid => { const tokenData = this.props.balance[uid]; - return ; + return ( + + ); }); - } + }; const loadSummary = () => { return ( @@ -106,13 +120,9 @@ class AddressSummary extends React.Component { {loadBalanceInfo()}
); - } + }; - return ( -
- {loadSummary()} -
- ); + return
{loadSummary()}
; } } diff --git a/src/components/ConditionalNavigation.js b/src/components/ConditionalNavigation.js index 3c735650..030ef24c 100644 --- a/src/components/ConditionalNavigation.js +++ b/src/components/ConditionalNavigation.js @@ -1,16 +1,23 @@ -import React from "react"; +import React from 'react'; import PropTypes from 'prop-types'; import { NavLink } from 'react-router-dom'; import { useFlag } from '@unleash/proxy-client-react'; const ConditionalNavigation = ({ featureToggle, to, label }) => { - return ( - useFlag(featureToggle) ? -
  • - {label} -
  • : null - ) -} + return useFlag(featureToggle) ? ( +
  • + + {label} + +
  • + ) : null; +}; /** * featureToggle: The feature flag that will be evaluated to check if this component must be rendered @@ -18,9 +25,9 @@ const ConditionalNavigation = ({ featureToggle, to, label }) => { * to: Where this navigation link will point to */ ConditionalNavigation.propTypes = { - featureToggle: PropTypes.string.isRequired, - label: PropTypes.string.isRequired, - to: PropTypes.string.isRequired -} + featureToggle: PropTypes.string.isRequired, + label: PropTypes.string.isRequired, + to: PropTypes.string.isRequired, +}; export default ConditionalNavigation; diff --git a/src/components/DagComponent.js b/src/components/DagComponent.js index ab85a35f..f79d725e 100644 --- a/src/components/DagComponent.js +++ b/src/components/DagComponent.js @@ -17,19 +17,18 @@ import { zoom, zoomIdentity } from 'd3-zoom'; // When we have a better control of timestamp in the graph we should change // the throttle background to cover a timestamp region and not a tx/block - class DagComponent extends React.Component { constructor(props) { super(props); // Tx circle radius and color this.txRadius = 8; - this.txColor = "steelblue"; + this.txColor = 'steelblue'; // Block rectangle width, height and color this.blockWidth = 20; this.blockHeight = 10; - this.blockColor = "darkgoldenrod"; + this.blockColor = 'darkgoldenrod'; // X distance between txs and blocks this.txMargin = 40; @@ -62,7 +61,7 @@ class DagComponent extends React.Component { this.lastZoomScale = 1; // Throttle background - this.throttleBackground = {'size': 2*this.txRadius + this.txMargin, 'color': '#eee'}; + this.throttleBackground = { size: 2 * this.txRadius + this.txMargin, color: '#eee' }; this.newTxs = this.newTxs.bind(this); this.newBlocks = this.newBlocks.bind(this); @@ -78,16 +77,15 @@ class DagComponent extends React.Component { } componentDidMount() { - this.tooltip = select(".tooltip") - .style("opacity", 0); + this.tooltip = select('.tooltip').style('opacity', 0); this.drawGraph(); } getTxY() { // We show same timestamp tx in a vertical axis alternating top and bottom from the block line - let signal = this.currentTimestampIndex % 2 === 0 ? -1 : 1; - let multiplier = Math.floor(this.currentTimestampIndex / 2) + 1; + const signal = this.currentTimestampIndex % 2 === 0 ? -1 : 1; + const multiplier = Math.floor(this.currentTimestampIndex / 2) + 1; return this.height / 2 + signal * multiplier * this.txMargin; } @@ -103,36 +101,36 @@ class DagComponent extends React.Component { if (isBlock) { // Calculate new x value to add this block x = this.startBlockX + this.indexBlock * this.txMargin; - let graphData = { - "id": data.tx_id, - "isBlock": true, - "x": x, - "y": this.blockY, - "links": [], - "timestamp": data.timestamp + const graphData = { + id: data.tx_id, + isBlock: true, + x, + y: this.blockY, + links: [], + timestamp: data.timestamp, }; this.graph[data.tx_id] = graphData; - let newLinks = []; - for (let parent of parents) { + const newLinks = []; + for (const parent of parents) { // Validate if parent is in the data, otherwise no need to add a link if (this.graph.hasOwnProperty(parent)) { // Creating link for each parent - let linkData = { - "source": { - "id": data.tx_id, - "x": x + this.blockWidth / 2, - "y": this.blockY + this.blockHeight / 2 + const linkData = { + source: { + id: data.tx_id, + x: x + this.blockWidth / 2, + y: this.blockY + this.blockHeight / 2, + }, + target: { + id: parent, + x: this.graph[parent].x + (this.graph[parent].isBlock ? this.blockWidth / 2 : 0), + y: this.graph[parent].y + (this.graph[parent].isBlock ? this.blockHeight / 2 : 0), }, - "target": { - "id": parent, - "x": this.graph[parent].x + (this.graph[parent].isBlock ? this.blockWidth / 2 : 0), - "y": this.graph[parent].y + (this.graph[parent].isBlock ? this.blockHeight / 2 : 0) - } }; this.links.push(linkData); newLinks.push(linkData); - this.graph[parent]["links"].push(data.tx_id); - this.graph[data.tx_id]["links"].push(parent); + this.graph[parent].links.push(data.tx_id); + this.graph[data.tx_id].links.push(parent); } } // Add new links to graph @@ -151,35 +149,35 @@ class DagComponent extends React.Component { } // Calculate new x value to add this tx x = this.startTxX + this.indexTx * this.txMargin; - let graphData = { - "id": data.tx_id, - "isBlock": false, - "x": x, - "y": this.getTxY(), - "links": [], - "timestamp": data.timestamp + const graphData = { + id: data.tx_id, + isBlock: false, + x, + y: this.getTxY(), + links: [], + timestamp: data.timestamp, }; this.graph[data.tx_id] = graphData; - let newLinks = []; - for (let parent of parents) { + const newLinks = []; + for (const parent of parents) { // Validate if parent is in the data, otherwise no need to add a link if (this.graph.hasOwnProperty(parent)) { // Creating link for each parent - let linkData = { - "source": { - "id": data.tx_id, - "x": x, - "y": this.getTxY() + const linkData = { + source: { + id: data.tx_id, + x, + y: this.getTxY(), + }, + target: { + id: parent, + x: this.graph[parent].x, + y: this.graph[parent].y, }, - "target": { - "id": parent, - "x": this.graph[parent].x, - "y": this.graph[parent].y - } }; this.links.push(linkData); - this.graph[parent]["links"].push(data.tx_id); - this.graph[data.tx_id]["links"].push(parent); + this.graph[parent].links.push(data.tx_id); + this.graph[data.tx_id].links.push(parent); newLinks.push(linkData); } } @@ -204,13 +202,18 @@ class DagComponent extends React.Component { translateGraph(x) { // Translate the graph to show the last added element // Get diff from last x to the one that is being added - let diff = x - ((this.width - this.txMargin) / this.lastZoomScale - this.lastZoomX); + const diff = x - ((this.width - this.txMargin) / this.lastZoomScale - this.lastZoomX); if (diff > 0) { // If diff > 0, means that it's not appearing, so we translate the graph this.gDraw - .transition() - .duration(300) - .call( this.zoomCall.transform, zoomIdentity.scale(this.lastZoomScale).translate(this.lastZoomX - diff, this.lastZoomY / this.lastZoomScale) ); + .transition() + .duration(300) + .call( + this.zoomCall.transform, + zoomIdentity + .scale(this.lastZoomScale) + .translate(this.lastZoomX - diff, this.lastZoomY / this.lastZoomScale) + ); // Save new X zoom this.lastZoomX -= diff; } @@ -221,13 +224,22 @@ class DagComponent extends React.Component { this.gLinks .selectAll() .data(links) - .enter().append("g") - .attr("class", "link") - .append("line") - .attr("x1", function(d) { return d.source.x; }) - .attr("y1", function(d) { return d.source.y; }) - .attr("x2", function(d) { return d.target.x; }) - .attr("y2", function(d) { return d.target.y; }); + .enter() + .append('g') + .attr('class', 'link') + .append('line') + .attr('x1', function(d) { + return d.source.x; + }) + .attr('y1', function(d) { + return d.source.y; + }) + .attr('x2', function(d) { + return d.target.x; + }) + .attr('y2', function(d) { + return d.target.y; + }); this.link = this.gLinks.selectAll('line'); } @@ -236,44 +248,55 @@ class DagComponent extends React.Component { // Add new txs to the svg // Add g auxiliar element - var tx = this.gTxs + const tx = this.gTxs .selectAll() .data(txs) .enter() - .filter(function(d){ return !d.isBlock; }) - .append("g") - .attr("class", "tx") + .filter(function(d) { + return !d.isBlock; + }) + .append('g') + .attr('class', 'tx'); // Add circle with tx data - tx.append("circle") - .attr("r", this.txRadius) - .attr("fill", this.txColor) - .attr("cx", (d) => { return d.x}) - .attr("cy", (d) => { return d.y}) + tx.append('circle') + .attr('r', this.txRadius) + .attr('fill', this.txColor) + .attr('cx', d => { + return d.x; + }) + .attr('cy', d => { + return d.y; + }); // Mouseover event to show/move/remove tooltip - tx.on('mouseover.tooltip', (d) => { + tx.on('mouseover.tooltip', d => { this.createTooltip(d); }) - .on('mouseover.fade', this.fade(0.1)) - .on("mouseout.tooltip", (e) => { - this.removeTooltip(e); - }) - .on('mouseout.fade', this.fade(1)) - .on("mousemove", (e) => { - this.moveTooltip(e); - }) + .on('mouseover.fade', this.fade(0.1)) + .on('mouseout.tooltip', e => { + this.removeTooltip(e); + }) + .on('mouseout.fade', this.fade(1)) + .on('mousemove', e => { + this.moveTooltip(e); + }); // Add text to show tx info - tx - .append("text") - .append("tspan") - .attr("class", "tx-text") - .attr("text-anchor", "middle") - .attr("alignment-baseline", "central") - .attr("x", (d) => { return d.x}) - .attr("y", (d) => { return d.y}) - .text(function(d) { return d.id.substring(0,4); }); + tx.append('text') + .append('tspan') + .attr('class', 'tx-text') + .attr('text-anchor', 'middle') + .attr('alignment-baseline', 'central') + .attr('x', d => { + return d.x; + }) + .attr('y', d => { + return d.y; + }) + .text(function(d) { + return d.id.substring(0, 4); + }); this.tx = this.gTxs.selectAll('circle'); } @@ -282,45 +305,62 @@ class DagComponent extends React.Component { // Add new blocks to the svg // Create g auxiliar element - var block = this.gBlocks + const block = this.gBlocks .selectAll() .data(blocks) .enter() - .filter(function(d){ return d.isBlock; }) - .append("g") - .attr("class", "block") + .filter(function(d) { + return d.isBlock; + }) + .append('g') + .attr('class', 'block'); // Add tooltip events - block.on('mouseover.tooltip', (d) => { - this.createTooltip(d); - }) - .on('mouseover.fade', this.fade(0.1)) - .on("mouseout.tooltip", (e) => { - this.removeTooltip(e); - }) - .on('mouseout.fade', this.fade(1)) - .on("mousemove", (e) => { - this.moveTooltip(e); - }) + block + .on('mouseover.tooltip', d => { + this.createTooltip(d); + }) + .on('mouseover.fade', this.fade(0.1)) + .on('mouseout.tooltip', e => { + this.removeTooltip(e); + }) + .on('mouseout.fade', this.fade(1)) + .on('mousemove', e => { + this.moveTooltip(e); + }); // Add rectangle with block data - block.append("rect") - .attr("fill", this.blockColor) - .attr("width", this.blockWidth) - .attr("height", this.blockHeight) - .attr("x", (d) => {return d.x}) - .attr("y", (d) => {return d.y}) + block + .append('rect') + .attr('fill', this.blockColor) + .attr('width', this.blockWidth) + .attr('height', this.blockHeight) + .attr('x', d => { + return d.x; + }) + .attr('y', d => { + return d.y; + }); // Add text to show block info - block.filter(function(d){return d.isBlock; }) - .append("text") - .append("tspan") - .attr("class", "block-text") - .attr("text-anchor", "middle") - .attr("alignment-baseline", "central") - .attr("x", (d) => { return d.x + this.blockWidth / 2}) - .attr("y", (d) => { return d.y + this.blockHeight / 2}) - .text(function(d) { return d.id.substring(0,4); }); + block + .filter(function(d) { + return d.isBlock; + }) + .append('text') + .append('tspan') + .attr('class', 'block-text') + .attr('text-anchor', 'middle') + .attr('alignment-baseline', 'central') + .attr('x', d => { + return d.x + this.blockWidth / 2; + }) + .attr('y', d => { + return d.y + this.blockHeight / 2; + }) + .text(function(d) { + return d.id.substring(0, 4); + }); this.block = this.gBlocks.selectAll('rect'); } @@ -329,14 +369,19 @@ class DagComponent extends React.Component { this.gThrottleBg .selectAll() .data(data) - .enter().append("g") - .attr("class", "throttle-bg") - .append("rect") - .attr("fill", this.throttleBackground.color) - .attr("width", this.throttleBackground.size) - .attr("height", this.throttleBackground.size) - .attr("x", (d) => {return d.x - this.throttleBackground.size / 2}) - .attr("y", (d) => {return d.y - this.throttleBackground.size / 2}) + .enter() + .append('g') + .attr('class', 'throttle-bg') + .append('rect') + .attr('fill', this.throttleBackground.color) + .attr('width', this.throttleBackground.size) + .attr('height', this.throttleBackground.size) + .attr('x', d => { + return d.x - this.throttleBackground.size / 2; + }) + .attr('y', d => { + return d.y - this.throttleBackground.size / 2; + }); } drawGraph() { @@ -348,10 +393,10 @@ class DagComponent extends React.Component { .attr('width', this.width) .attr('height', this.height); - this.gMain = this.svg.append('g') - .classed('g-main', true); + this.gMain = this.svg.append('g').classed('g-main', true); - this.gMain.append('rect') + this.gMain + .append('rect') .attr('width', this.width) .attr('height', this.height) .style('fill', 'white'); @@ -364,21 +409,20 @@ class DagComponent extends React.Component { this.gBlocks = this.gDraw.append('g'); // Adding zoom handler - this.zoomCall = zoom() - .on('zoom', this.zoomed) + this.zoomCall = zoom().on('zoom', this.zoomed); this.gMain.call(this.zoomCall); // Handle initial data translating in the end to last X let maxX = 0; - for(let tx of this.props.txs) { - let newX = this.newData(tx, false, true); + for (const tx of this.props.txs) { + const newX = this.newData(tx, false, true); maxX = Math.max(maxX, newX); } - for(let block of this.props.blocks) { - let newX = this.newData(block, true, true); + for (const block of this.props.blocks) { + const newX = this.newData(block, true, true); maxX = Math.max(maxX, newX); } @@ -391,24 +435,26 @@ class DagComponent extends React.Component { /** Data from the tx being hovered */ const d = mouseEvent.currentTarget.__data__; if (this.tx) { - this.tx.style('stroke-opacity', function (o) { - const thisOpacity = (d.links.indexOf(o.id) > -1 || d.id === o.id) ? 1 : opacity; + this.tx.style('stroke-opacity', function(o) { + const thisOpacity = d.links.indexOf(o.id) > -1 || d.id === o.id ? 1 : opacity; this.setAttribute('fill-opacity', thisOpacity); return thisOpacity; }); } if (this.block) { - this.block.style('stroke-opacity', function (o) { - const thisOpacity = (d.links.indexOf(o.id) > -1 || d.id === o.id) ? 1 : opacity; + this.block.style('stroke-opacity', function(o) { + const thisOpacity = d.links.indexOf(o.id) > -1 || d.id === o.id ? 1 : opacity; this.setAttribute('fill-opacity', thisOpacity); return thisOpacity; }); } if (this.link) { - let linkOpacity = opacity === 1 ? 1 : 0; - this.link.style('stroke-opacity', o => {return ((o.source.id === d.id) || (o.target.id === d.id) ? 1 : linkOpacity)}); + const linkOpacity = opacity === 1 ? 1 : 0; + this.link.style('stroke-opacity', o => { + return o.source.id === d.id || o.target.id === d.id ? 1 : linkOpacity; + }); } }; } @@ -436,25 +482,27 @@ class DagComponent extends React.Component { const data = mouseEvent.currentTarget.__data__; // Create tooltip on mouse over in a block or tx to show their info - this.tooltip.transition() + this.tooltip + .transition() .duration(300) - .style("opacity", 1); - this.tooltip.html(`Hash:${ data.id }
    Timestamp: ${ data.timestamp }`) - .style("left", (mouseEvent.pageX) + "px") - .style("top", (mouseEvent.pageY + 10) + "px"); + .style('opacity', 1); + this.tooltip + .html(`Hash:${data.id}
    Timestamp: ${data.timestamp}`) + .style('left', `${mouseEvent.pageX}px`) + .style('top', `${mouseEvent.pageY + 10}px`); } removeTooltip() { // Remove tooltip when mouse out in a block or tx - this.tooltip.transition() + this.tooltip + .transition() .duration(100) - .style("opacity", 0); + .style('opacity', 0); } moveTooltip(event) { // Move tooltip when mouse move in a block or tx - this.tooltip.style("left", (event.pageX) + "px") - .style("top", (event.pageY + 10) + "px"); + this.tooltip.style('left', `${event.pageX}px`).style('top', `${event.pageY + 10}px`); } render() { diff --git a/src/components/GDPRConsent.js b/src/components/GDPRConsent.js index dbac9617..6f949f47 100644 --- a/src/components/GDPRConsent.js +++ b/src/components/GDPRConsent.js @@ -7,13 +7,11 @@ import React, { useEffect } from 'react'; -import { GTM_ID } from '../constants'; -import CookieConsent from "react-cookie-consent"; +import CookieConsent from 'react-cookie-consent'; import TagManager from 'react-gtm-module'; - +import { GTM_ID } from '../constants'; const GDPRConsent = () => { - useEffect(() => { // Just to ensure that when we initialize GTM, the message on this component will be shown TagManager.initialize({ gtmId: GTM_ID }); @@ -28,9 +26,17 @@ const GDPRConsent = () => { buttonClasses="btn btn-hathor m-3" > This website uses cookies to ensure you get the best experience on our website. - Learn more. + + {' '} + Learn more + + . - ) -} + ); +}; export default GDPRConsent; diff --git a/src/components/HathorAlert.js b/src/components/HathorAlert.js index d69ca07e..6d55ca25 100644 --- a/src/components/HathorAlert.js +++ b/src/components/HathorAlert.js @@ -7,7 +7,6 @@ import React from 'react'; - class HathorAlert extends React.Component { show(duration) { const el = this.refs.alertDiv; @@ -19,7 +18,11 @@ class HathorAlert extends React.Component { render() { return ( -
    +
    {this.props.text}