diff --git a/app/components/Network/CanvasNetworkGraph.css b/app/components/Network/CanvasNetworkGraph.css deleted file mode 100644 index fe0f334b..00000000 --- a/app/components/Network/CanvasNetworkGraph.css +++ /dev/null @@ -1,22 +0,0 @@ -.network { - width: 100%; - height: 100vh; - animation: fadein 0.5s; - animation-timing-function:linear; - animation-fill-mode: forwards; - animation-iteration-count: 1; -} - -.links line { - stroke: #999; - stroke-opacity: 0.6; -} - -.nodes circle { - stroke: black ; - stroke-width: 0px; -} - -.active-peer { - fill: pink; -} \ No newline at end of file diff --git a/app/components/Network/CanvasNetworkGraph.js b/app/components/Network/CanvasNetworkGraph.js index d37dd249..10372c44 100644 --- a/app/components/Network/CanvasNetworkGraph.js +++ b/app/components/Network/CanvasNetworkGraph.js @@ -2,7 +2,7 @@ import { findDOMNode } from 'react-dom' import React, { Component } from 'react' import PropTypes from 'prop-types' import * as d3 from 'd3' -// import './CanvasNetworkGraph.css' +import styles from './CanvasNetworkGraph.scss' function generateSimulationData(nodes, edges) { const resNodes = nodes.map(node => Object.assign(node, { id: node.pub_key })) @@ -26,7 +26,9 @@ class CanvasNetworkGraph extends Component { simulationData: { nodes: [], links: [] - } + }, + + svgLoaded: false } this._startSimulation = this._startSimulation.bind(this) @@ -35,10 +37,31 @@ class CanvasNetworkGraph extends Component { this._restart = this._restart.bind(this) } + componentWillReceiveProps(nextProps) { + const { network } = nextProps + const { simulationData: { nodes, links } } = this.state + + const simulationDataEmpty = !nodes.length && !links.length + const networkDataLoaded = network.nodes.length || network.edges.length + + // if the simulationData is empty and we have data + if (simulationDataEmpty && networkDataLoaded) { + this.setState({ + simulationData: generateSimulationData(network.nodes, network.edges) + }) + } + } + componentDidMount() { // wait for the svg to me in the DOM before we start the simulation const svgInterval = setInterval(() => { - if (document.getElementById('map')) { + if (document.getElementById('mapContainer')) { + d3.select('#mapContainer') + .append('svg') + .attr('id', 'map') + .attr('width', 800) + .attr('height', 800) + this._startSimulation() clearInterval(svgInterval) @@ -79,7 +102,6 @@ class CanvasNetworkGraph extends Component { componentWillUnmount() { d3.select('#map') - .selectAll('*') .remove() } @@ -177,6 +199,10 @@ class CanvasNetworkGraph extends Component { .force('link', d3.forceLink(links).id(d => d.pub_key).distance(500)) .force('collide', d3.forceCollide(300)) .on('tick', this._ticked) + .on('end', () => { + this.setState({ svgLoaded: true }) + }) + // zoom const zoom_handler = d3.zoom().on('zoom', this._zoomActions) @@ -235,18 +261,22 @@ class CanvasNetworkGraph extends Component { } render() { - const { simulationData } = this.state - const { - network: { nodes, edges, selectedChannel, networkLoading }, - selectedPeerPubkeys, - selectedChannelIds, - currentRouteChanIds, - identity_pubkey - } = this.props + const { svgLoaded } = this.state return ( -
- +
+ { + !svgLoaded && +
+
+
+
+
+
+
loading
+
+
+ }
) } diff --git a/app/components/Network/CanvasNetworkGraph.scss b/app/components/Network/CanvasNetworkGraph.scss new file mode 100644 index 00000000..7a9d1da9 --- /dev/null +++ b/app/components/Network/CanvasNetworkGraph.scss @@ -0,0 +1,156 @@ +@import '../../variables.scss'; + +@keyframes fadein { + 0% { background: $white; } + 50% { background: lighten($secondary, 50%); } + 100% { background: $secondary; animation-fill-mode:forwards; } +} + +.mapContainer { + position: relative; + display: inline-block; + width: 70%; + height: 100%; +} + +.loadingContainer { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: $secondary; +} + +.loadingWrap { + position: relative; + top: calc(50% - 150px); + width: 150px; + margin: 0 auto; +} + +.loader { + position: absolute; + top: 0; + z-index: 10; + width: 50px; + height: 50px; + border: 15px solid; + border-radius: 50%; + border-top-color: rgba(44,44,44,0); + border-right-color: rgba(55,55,55,0); + border-bottom-color: rgba(66,66,66,0); + border-left-color: rgba(33,33,33,0); + animation: loadEr 3s infinite; +} + +@keyframes loadEr { + 0% { + border-top-color: rgba(44,44,44,0); + border-right-color: rgba(55,55,55,0); + border-bottom-color: rgba(66,66,66,0); + border-left-color: rgba(33,33,33,0); + + } + 10.4% { + border-top-color: rgba(44,44,44,0.5); + border-right-color: rgba(55,55,55,0); + border-bottom-color: rgba(66,66,66,0); + border-left-color: rgba(33,33,33,0); + } + 20.8% { + border-top-color: rgba(44,44,44,0); + border-right-color: rgba(55,55,55,0); + border-bottom-color: rgba(66,66,66,0); + border-left-color: rgba(33,33,33,0); + } +31.2% { + border-top-color: rgba(44,44,44,0); + border-right-color: rgba(55,55,55,0.5); + border-bottom-color: rgba(66,66,66,0); + border-left-color: rgba(33,33,33,0); +} +41.6% { + border-top-color: rgba(44,44,44,0); + border-right-color: rgba(55,55,55,0); + border-bottom-color: rgba(66,66,66,0); + border-left-color: rgba(33,33,33,0); + transform: rotate(40deg); +} +52% { + border-top-color: rgba(44,44,44,0); + border-right-color: rgba(55,55,55,0); + border-bottom-color: rgba(66,66,66,0.5); + border-left-color: rgba(33,33,33,0); +} +62.4% { + border-top-color: rgba(44,44,44,0); + border-right-color: rgba(55,55,55,0); + border-bottom-color: rgba(66,66,66,0); + border-left-color: rgba(33,33,33,0); +} +72.8% { + border-top-color: rgba(44,44,44,0); + border-right-color: rgba(55,55,55,0); + border-bottom-color: rgba(66,66,66,0); + border-left-color: rgba(33,33,33,0.5); +} +} + +.loaderbefore { + width: 50px; + height:50px; + border: 15px solid #ddd; + border-radius: 50%; + position: absolute; + top: 0; + z-index: 9; +} + +.circular { + position: absolute; + top: -15px; + left: -15px; + width: 70px; + height: 70px; + border: 20px solid; + border-radius: 50%; + border-top-color: #333; + border-left-color: #fff; + border-bottom-color: #333; + border-right-color: #fff; + opacity: 0.2; + animation: poof 5s infinite; +} +@keyframes poof { + 0% {transform: scale(1,1) rotate(0deg); opacity: 0.2;} + 50% {transform: scale(4,4) rotate(360deg); opacity: 0;} +} +.another { + opacity: 0.1; + transform: rotate(90deg); + animation: poofity 5s infinite; + animation-delay: 1s; +} +@keyframes poofity { + 0% {transform: scale(1,1) rotate(90deg); opacity: 0.1;} + 50% {transform: scale(4,4) rotate(-360deg); opacity: 0;} +} + +.text { + position: absolute; + top: 95px; + left: 8px; + font-family: Arial; + text-transform: uppercase; + color: #888; + animation: opaa 10s infinite; +} +@keyframes opaa { + 0% {opacity: 1;} +10% {opacity: 0.5} +15% {opacity: 1;} +30% {opacity: 1;} +65% {opacity: 0.3;} +90% {opacity: 0.8;} +} \ No newline at end of file diff --git a/app/reducers/network.js b/app/reducers/network.js index 04f5118e..07680b57 100644 --- a/app/reducers/network.js +++ b/app/reducers/network.js @@ -23,8 +23,10 @@ export const UPDATE_PAY_REQ = 'UPDATE_PAY_REQ' export const RESET_PAY_REQ = 'RESET_PAY_REQ' export const UPDATE_SELECTED_PEERS = 'UPDATE_SELECTED_PEERS' +export const CLEAR_SELECTED_PEERS = 'CLEAR_SELECTED_PEERS' export const UPDATE_SELECTED_CHANNELS = 'UPDATE_SELECTED_CHANNELS' +export const CLEAR_SELECTED_CHANNELS = 'CLEAR_SELECTED_CHANNELS' export const GET_INFO_AND_QUERY_ROUTES = 'GET_INFO_AND_QUERY_ROUTES' export const RECEIVE_INFO_AND_QUERY_ROUTES = 'RECEIVE_INFO_AND_QUERY_ROUTES' @@ -113,6 +115,18 @@ export function clearQueryRoutes() { } } +export function clearSelectedPeers() { + return { + type: CLEAR_SELECTED_PEERS + } +} + +export function clearSelectedChannels() { + return { + type: CLEAR_SELECTED_CHANNELS + } +} + // Send IPC event for describeNetwork export const fetchDescribeNetwork = () => (dispatch) => { dispatch(getDescribeNetwork()) @@ -185,6 +199,7 @@ const ACTION_HANDLERS = { ...state, selectedPeers } }, + [CLEAR_SELECTED_PEERS]: state => ({ ...state, selectedPeers: [] }), [UPDATE_SELECTED_CHANNELS]: (state, { channel }) => { let selectedChannels @@ -200,7 +215,8 @@ const ACTION_HANDLERS = { return { ...state, selectedChannels } - } + }, + [CLEAR_SELECTED_CHANNELS]: state => ({ ...state, selectedChannels: [] }), } // ------------------------------------ diff --git a/app/routes/network/components/Network.js b/app/routes/network/components/Network.js index 60efa0c4..921315cf 100644 --- a/app/routes/network/components/Network.js +++ b/app/routes/network/components/Network.js @@ -34,10 +34,12 @@ class Network extends Component { } componentWillUnmount() { - const { clearQueryRoutes, resetPayReq } = this.props + const { clearQueryRoutes, resetPayReq, clearSelectedChannels, clearSelectedPeers } = this.props clearQueryRoutes() - resetPayReq() + resetPayReq() + clearSelectedChannels() + clearSelectedPeers() } render() { diff --git a/app/routes/network/containers/NetworkContainer.js b/app/routes/network/containers/NetworkContainer.js index 85428b70..f78b4592 100644 --- a/app/routes/network/containers/NetworkContainer.js +++ b/app/routes/network/containers/NetworkContainer.js @@ -5,8 +5,13 @@ import { fetchDescribeNetwork, setCurrentTab, + updateSelectedPeers, + clearSelectedPeers, + updateSelectedChannels, + clearSelectedChannels, + setCurrentRoute, updatePayReq, @@ -25,7 +30,10 @@ import Network from '../components/Network' const mapDispatchToProps = { fetchDescribeNetwork, setCurrentTab, + updateSelectedPeers, + clearSelectedPeers, + updatePayReq, fetchInvoiceAndQueryRoutes, setCurrentRoute, @@ -35,7 +43,8 @@ const mapDispatchToProps = { fetchPeers, fetchChannels, - updateSelectedChannels + updateSelectedChannels, + clearSelectedChannels } const mapStateToProps = state => ({